EpiSODIC 0.24.0
New
- Each detection run nowcasts every stream carrying an open cluster, from the stream’s own reporting delays (
surveillance::nowcast(),bayes.trunc), configured underforecast.nowcast - Each run’s nowcast is stored in
episodic_forecastandepisodic_forecast_value(schema version 11), with the reason when a stream could not be nowcast - The outbreak and Epidemics dossier curves draw the nowcast’s median and 90% interval on the days and weeks still being reported
- The Performance screen scores stored nowcasts once reporting is complete: interval coverage, weighted interval score, and the error of taking the reported count as final
- Each detection run gives every open outbreak of a transmissible pathogen the probability that it is over, from the serial interval, the pathogen’s offspring distribution and the stream’s reporting completeness, configured under
forecast.outbreak_end - The pathogen CSV takes
end_randend_k, the offspring mean and dispersion, shipped empty for every pathogen - The outbreak dossier shows the probability beside the case-free days, with the date it reaches
forecast.outbreak_end.probability_threshold, or why there is none - Each run’s outbreak-end probability is stored in
episodic_cluster_forecastandepisodic_cluster_forecast_value(schema version 12) - The Performance screen scores stored outbreak-end probabilities once the case-free window after the last case is reported: mean probability, share over, and Brier score
- The Pathogen screen’s parameter panel shows a transmissible pathogen’s offspring distribution
- Each detection run estimates every open epidemic’s weekly growth rate, the chance it is growing and the chance it is past its peak so far, on the nowcast-corrected weekly counts, configured under
forecast.epidemic_direction - The Epidemics dossier shows the weekly growth with its doubling or halving time and the weeks it covers, and the chance the epidemic is past its peak, or why there is none
- Each run’s epidemic direction is stored in
episodic_cluster_forecastandepisodic_cluster_forecast_estimate(schema version 13) - The Performance screen scores stored epidemic directions once their weeks are fully reported: mean probability, share true, and Brier score
- A seasonal epidemic stores the typical past season from the MEM fit that set its thresholds: its curve, start week and length, in
episodic_epidemic_seasonandepisodic_epidemic_typical_week(schema version 13) - The Epidemics dossier draws the typical past season on the weekly curve, aligned to the epidemic’s onset, and shows the typical length and how much earlier or later this epidemic started
- The typical past season’s curve is the median of the past seasons by default, set by
mem.typical_curve(median,geometric_meanorarithmetic_mean) - The tuning vignette’s reference covers the
notificationskeys - Each detection run forecasts every open epidemic’s weekly counts over the next weeks, with the chance of reaching each MEM band, as the quantile average of a negative binomial
surveillance::hhh4()model of the stream’s history and the extended trend of its last weeks, configured underforecast.epidemic_outlook - Each run’s epidemic outlook is stored in
episodic_cluster_forecast_week(schema version 14) - The Epidemics dossier shows a pathogen’s outlook, as a table and on the weekly curve, only once its outlooks have been scored on
forecast.epidemic_outlook.min_scoredweeks and beat expecting no change, and states that record - The Performance screen scores stored outlooks per horizon: interval coverage, weighted interval score, and the score relative to expecting no change
Changed
episodic_demo()replays the lastreplay_daysdays of its synthetic data as daily runs with a one-week reporting horizon, so the demo has measured reporting delays and a nowcast, and gives its transmissible pathogens an illustrative offspring distribution, so an outbreak shows its end-of-outbreak probabilityThe outbreak dossier shows the end-of-outbreak tile only with a probability, or when the forecast failed
The Epidemics dossier’s intensity tile names the peak so far only when its band differs from the current week’s
The Performance screen’s outbreaks-only note is limited to its detection and assessment metrics, since its nowcast section covers epidemics too
A stat tile can carry a line between its caption and its value saying what the value measures
The Epidemics dossier’s “Outbreaks during” tile is “Linked outbreaks”, with a line saying what links them
EpiSODIC 0.23.0
New
- Five
severity_*palette roles colour both verdicts and MEM intensity bands, overridable throughEPISODIC_STYLE - The Epidemics dossier header shows the current MEM band as an “Intensity” chip
- Each seasonal epidemic in the Epidemics list shows its current MEM band as an “Intensity” chip
- Text on a filled chip or selected picker button takes ink, white or black, whichever reads on the fill
- The Pathogen and Epidemics weekly curves shade the MEM intensity bands behind the bars
- Each detection run stores every open seasonal epidemic’s weekly counts and intensity bands in
episodic_epidemic_week(schema version 10) - An assessed cluster’s assessment pane says which cluster it would otherwise be filed under, with a link to it
- The weekly curve’s note explains why there is no Low band when MEM’s medium threshold is at or below the start threshold
Changed
- The weekly curve on the Pathogen screen and the Epidemics dossier is titled “Confirmed cases per week” instead of “Weekly incidence”, with no y-axis title
- Verdicts, intensity chips, intensity stats and the Pathogen chart’s intensity lines take their colours from the severity scale
- Synthetic Influenza A and RSV come in winter waves of varying size and timing, so the demo always has a MEM season with intensity bands
- MEM threshold lines are thin and solid, and the weekly curve’s legend, titled “Epidemic Severity” in every language, names the shaded intensity bands on the right, with the start and end thresholds, the only lines drawn, below them at one width
- The Epidemics dossier and list read a seasonal epidemic’s thresholds, latest week and bands from what the detection runs stored instead of refitting MEM
-
suppression.parent_min_flagged_childrenships as 1, so a parent absorbs a single flagged child holding under half its cases
Fixed
- MEM intensity bands are read from
mem::memmodel()’sintensity.thresholds, so they are no longer always missing - A MEM fit without intensity bands says why in the run log
- A seasonal epidemic closes on the trough backstop only once its count is back at or below the pre-epidemic threshold, so an out-of-season epidemic is no longer closed while still rising
EpiSODIC 0.22.2
New
- A generated outbreak report version in the dossier links to its report in a new tab for signed-in users, while its file still exists
Changed
-
episodic_check_cases()advises onpcvalues missing from the configuredEPISODIC_GEO_DATAinstead of onpcvalues that are not four digits - Country-specific terms (BSN, GGD, Gesundheitsamt) replaced by generic ones in messages, translations and documentation
- The unused
wpg_notifiable,ggd_informedandggd_notecolumns are dropped fromepisodic_assessment_eventwhere they hold no data (schema version 9) - A schema migration drops a column only when nothing uses it and it holds no data
EpiSODIC 0.22.1
New
-
EPISODIC_MAIL_TEMPLATE_NEW_CLUSTERSandEPISODIC_MAIL_TEMPLATE_REPORTset a Quarto template for the body of the new-clusters and scheduled-report emails -
episodic_mail_template_preview()renders an email template against example data
EpiSODIC 0.22.0
New
- A detection run logs the time its stream loop spent per stage
- MEM fits are cached per stream in
episodic_detector_cacheand reused only when their exact input is unchanged (schema version 8) -
episodic_run_cron()migrates a database behind the installed package before running, unlessdatabase.auto_migrateisfalse -
episodic_db_migrate()copies a SQLite database beside itself before the first step (backup = TRUE) - A run that refuses a database records the refusal as a failed run
- A run that stops before it begins (database refused, case data failing its checks) sends the
run_failurenotification - The run log names the database server and version it connected to (SQLite, MariaDB or MySQL)
Changed
- The
same_placescan is linear in a place’s case count instead of quadratic - Each stream takes its cases from an index built once per run instead of filtering the whole case table
- Farrington’s weekly bins, the MEM anchor, seasonality statistic and season matrix are counted per distinct date instead of per case
- Baseline exclusions, patient-days and trend row counts are read once per run instead of once per stream
- The Farrington detector and the trend cache share one fit per stream instead of fitting the same week twice
- Reconciliation reads assessment events once per run and ages a stream’s undetected clusters in one statement
Fixed
- A case import naming more distinct patients than one SQL statement allows no longer fails the run
- Farrington with institution patient-days no longer fails the run under
surveillance1.26 - Lattice suppression no longer suppresses a child cluster behind a parent it shares no case with, nor re-suppresses one already suppressed
EpiSODIC 0.21.0
New
- The Epidemics dossier shows the latest complete week against the week before, the peak so far, the current MEM intensity and the latest Rt
- The Epidemics dossier has a choropleth of the epidemic’s cases, beside the postcode breakdown
- The Epidemics dossier compares the epidemic’s season with earlier seasons on one axis
- The Epidemics dossier shows Rt, age and sex against the area’s own baseline, and the care lines cases were found in
- Outbreaks during an epidemic are listed in the cluster table, with their state, row hover and click-through
- Lattice suppression writes progress lines during a detection run
- MEM log lines name a stream’s region code as well as its pathogen and level
- The Archive search combines terms with
ANDandOR - The Epidemics dossier shows its detection settings
- The Epidemics dossier warns when the area’s case history is missing cases linked to the epidemic, naming both catchment codes where they differ
Changed
- Outbreak and epidemic identifiers take their prefix from the reader’s language (e.g.
U-/E-in Dutch,A-/E-in German) - Lattice suppression reads case sets and assessments in two batched queries instead of per cluster
- The Outbreaks and Epidemics rails leave closed clusters out in the database query
- The epidemic dossier reads the stream’s case history once and its lead-in, thresholds, Rt and baseline come from the epidemic’s own area
- Contributing institutions on the Epidemics dossier are counted from the epidemic’s own cases
- The season overlay assigns season weeks per distinct day instead of per case
- Positivity is drawn on an axis scaled to the series instead of a fixed 0-100%
- Every chart’s y axis leaves 25% headroom above the highest value and none below the lowest
- Axes of case and test counts are labelled in whole numbers only
- Rt is drawn on a log2 axis everywhere
- The Pathogens screen’s selector lists pathogens alphabetically, case-insensitively
- Screen-level loading spinners are three to four times larger and are not dimmed with the content
-
ggplot2(>= 3.5.0) is required - The phenotypic resistance profile placeholder panel is removed from the Outbreaks dossier
- The Epidemics dossier shows one map instead of two, labelled with postcode and count, and Rt and positivity at full width
- A closed epidemic’s curve ends four weeks after its last case, and its dossier reads its intensity at the peak
- Every screen shares one neutral background and one accent colour (primary)
- Charts draw their main series in primary, earlier seasons in fading greys, and test volume in a light primary
- The Archive search box is up to 44rem wide and full width on narrow screens
- The rails show a care-line chip for ‘other’
- Care-line chips differ in form as well as colour: first line filled, second tinted, third outlined, other dashed
- The detection-settings card is titled for its outbreak or epidemic and reports the run that last detected it
- Translation files list their keys in sorted order
Fixed
- The positivity feed is summed per ISO week, so period starts in the same week no longer produce duplicate week labels
- A positivity above 100% is left out of the chart instead of drawn at 100%
- Outbreaks are linked to the epidemics they ran during whatever either cluster’s state, so historical epidemics get their outbreaks
- A cluster whose first or last day cannot be read as a date is left out of the during links and named in the run log, instead of failing the run
- An epidemic’s length is counted from its first to its last case instead of to today
- The line list and outbreak report show sex as a translated word instead of its stored code
- The Pathogens screen’s detection-parameters panel italicises the pathogen name in its title and text
- Map areas with the fewest cases are tinted instead of drawn in the case-free colour
- A map’s frame no longer extends past the reference geometry by more than its minimum margin, so a widespread cluster’s map fills its box
-
devtools::load_all()no longer replaces the session’sEPISODIC_CONFIGwith the test suite’s configuration
EpiSODIC 0.20.6
Changed
- The Archive reads the database once per data change; searching and the level chips filter in memory
- The Archive shows closed clusters 100 per page
- The dashboard sends its shell and rail before the first dossier, which follows on the next flush
- Cluster tables resolve the configuration once per table instead of twice per row
- A configuration file is re-read and re-hashed only when its size or modification time changes
- Cluster states for the rail and the Archive are derived in one pass instead of one scan per cluster
- The Archive builds place labels once per stream and closure authors once per user
- Batched reads over many clusters or streams run in chunks
EpiSODIC 0.20.5
Fixed
- Opening an epidemic dossier on a MariaDB/MySQL instance no longer aborts the R session; the epidemic object is built before the dossier’s first query rather than inside it
- Every query now evaluates its parameters before the statement is prepared, so no query parameter that itself queries the database can abort the R session on MariaDB/MySQL
EpiSODIC 0.20.4
Fixed
-
episodic_add_manual_cluster()’slevelargument pointed atvignette("architecture"), a vignette that no longer exists; it now links to the pkgdown article that documents the five lattice levels - The pkgdown deploy step purges the published site before every rebuild (
clean: true, pluspkgdown::clean_site()), so a page whose source vignette is removed no longer lingers on the live site
EpiSODIC 0.20.1
EpiSODIC 0.20.0
New
- An Epidemics screen with a rail of open epidemics, a dossier with seasonal evidence, and the assessment pane a season is declared in
- The navigation bar carries four links: Outbreaks, Epidemics, Pathogen, Instance
- An outbreak’s dossier header carries a “During E-123” chip for each epidemic it ran during, which opens that epidemic
- The Epidemics screen carries a notes panel and the full assessment form, so an epidemic is assessed and annotated like any other signal
- The epidemic dossier shows weekly case curve with MEM threshold overlay, tests and positivity, contributing institutions with concentration, and linked outbreaks
- The Clusters rail and Performance screen filter to outbreaks only (L1-L3)
- Epidemiologists can record seasonal declarations (
season_started,season_not_yet,season_ended) on seasonal epidemics - Cluster references render as
O-{id}for outbreaks andE-{id}for epidemics, uniform across all eight languages -
episodic_object_ref()helper renders the scale-aware reference from either a scale string or a lattice level - User-facing “cluster” wording replaced throughout: outbreak-only contexts say “outbreak”, mixed contexts say “outbreaks and epidemics”, detection contexts say “signal”
- Full i18n vocabulary sweep across all eight language files for the outbreak/epidemic terminology
- Report filenames changed from
cluster-{id}tooutbreak-{id}
Changed
- The Archive is reached from a card on the Instance screen instead of its own link, so the navigation bar fits on a phone width
-
access.require_loginnow ships asfalse: a freshly installed instance reads open, and an operator closes it deliberately - The Clusters screen is called Outbreaks
- The Epidemics screen holds its three panes in the app shell, like the Outbreaks screen, so its rail keeps its scroll position and gains the phone-tier pane switcher
- The Epidemics rail is the Outbreaks rail’s markup, with more room per row
- The seasonal declaration is offered inside the epidemic’s assessment form rather than as a form of its own, alongside the ordinary classification verdicts
- The classification form and the notes panel namespace their element ids, so both surveillance screens can carry one
- An outbreak’s “Linked to” chips cover the outbreak scale only; the epidemic it ran during is a “During” chip instead
- The confirmed-outbreak classification’s explanation no longer names one scale while its label names the other
- The mobile pane-switcher’s active tab, the recalculating spinner, the Outbreaks screen’s recommendation box, the epi curve/trend/Rt/map/weekly-cases charts, and every cluster table’s row-link colour now read the current nav section’s own colour instead of always primary; the age/sex pyramid keeps its own colour-coding unchanged, and buttons and chips are untouched
Fixed
- The shared cluster table (Pathogen screen, Archive, and the dossier’s related and similar-clusters panels) always prefixed ids with
O-; each row now reads its own scale viaepisodic_object_ref(), so an epidemic-scale row readsE-{id} - Screen titles (Pathogen activity, Archive, Activity, Performance, Info, Streams) were sized in a fixed pixel value and no longer scaled with the mobile font-size bump; a shared rem-based
.episodic-screen-titleclass replaces the inline style -
episodic_geography_nests()no longer fails for region-level epidemics after the generic refactoring removed the L5 fast path - The Pathogens screen’s weekly-cases chart now uses the section accent colour, consistent with the Rt and geo charts on the same screen
EpiSODIC 0.19.0
New
- An Instance screen, holding the Streams, Activity, Performance, Info and Settings screens the navigation bar no longer carries
- Per-output loading spinners in the primary colour, so each panel, chart and table says it is the one being waited for
- A “Last 3 months” period on the Pathogen screen, left of “Last 12 months”
- An open-by-number box in the rail header, which opens any cluster the instance will show, closed and archived ones included
- A row about a cluster on the Activity screen opens that cluster
- Schema version 5: two composite indexes on
episodic_case, for the dashboard’s date-bounded and institution-bounded pathogen reads - MEM derives its season anchor and seasonality eligibility from the data, so it fires in any ISO week for any pathogen whose cases show a season, in either hemisphere
- MEM runs at province (L4) and region (L5) level, configurable via
mem.levels -
mem_modereplacesmem_applicable: a three-state override (auto, yes, no) per pathogen - Schema version 6:
mem_modecolumn onepisodic_pathogen_config - Full-year MEM seasons with no NA off-season period
- Schema version 7:
scalediscriminator onepisodic_cluster,episodic_epidemic_seasonsatellite table,episodic_cluster_linkrelation table, and declaration verdicts onepisodic_assessment_event - Configurable scale boundary (
scale.epidemic_levels) determines which lattice levels produce epidemics rather than outbreaks - Verdict labels derive the outbreak/epidemic boundary from configuration rather than a hardcoded constant
- Clusters opened at epidemic-level streams receive
scale = 'epidemic'; all others receivescale = 'outbreak' - A seasonal satellite row (
episodic_epidemic_season) is written when a new epidemic cluster opens from a MEM detection - Seasonal epidemics close automatically when the weekly case count drops below the post-epidemic threshold or the evaluation week enters the trough
- Epidemic identity is preserved across the season anchor rollover
- Outbreaks are linked to epidemics they occur during, based on pathogen, time overlap and geographic nesting (
episodic_cluster_link) - Lattice suppression works across the outbreak/epidemic scale boundary
Changed
- The navigation bar is four links and is visible at every width, with no collapsed menu anywhere in the app
- Every screen is rendered once and shown or hidden, instead of being torn out and rebuilt from the database on every navigation
- Navigation, pane and cluster state live in three data attributes on the app shell, and every highlight is derived from them by the stylesheet
- All navigation behaviour moved into one cached script, replacing the inline event handlers on nav links, rail rows, cluster table rows and chips
- The clusters screen’s three panes carry their own geometry, so each scrolls inside itself instead of scrolling the document
- The rail row is a button beside its bulk-select checkbox rather than a div wrapped around it
- The phone-tier pane switcher names the open cluster from the server’s selection instead of copying it out of the rail’s markup
- Every navigation control is at least 44px on a touch viewport and answers a press before the screen changes
-
shinyis required at version 1.14.0 or newer, for observer-based output visibility tracking - The header navigation renders once per access state instead of on every navigation, so it no longer arrives with the screen it exists to navigate away from
- The resolved YAML configuration is cached per file content, instead of being re-read, re-validated and re-merged at every
episodic_geography_config()call - The denominator panel reads only the weeks it plots, rather than a pathogen’s whole recorded history
- Weekly case counts are computed by binary search rather than a pass over every date per week
- The demography baseline query is a correlated
NOT EXISTSrather than a nestedNOT IN - The Activity screen resolves a row’s cluster by lookup rather than by scanning every cluster per row
- The open-cluster list is no longer invalidated by leaving the Clusters screen and coming back
- Opening a cluster builds its cluster object once instead of three times, and its completion curve once instead of twice
- A ward stream’s cases and an area stream’s postcodes are narrowed by the database rather than after every case of the pathogen has reached R
- A run log line saying a component produced nothing it could have produced is marked as a warning, and one saying something failed outright as an error
- The Activity screen’s run modal marks a failed or partial run, rows the run skipped, and a run that had no eligible stream to detect on
Fixed
- Saving a cluster note that repeats the note already on file no longer records a version in which nothing changed, and an empty note on a cluster that never had one is not recorded at all
- A cluster id that names nothing viewable no longer selects an absent cluster in the dossier
- The epidemic dossier’s weekly curve errored instead of drawing, its weekly counts carrying no completeness flag
- The epidemic dossier’s statistics stacked vertically instead of forming a row, from a class the stylesheet does not draw
- The screen no longer stays behind when a cluster is opened from the Epidemics screen after the reader has returned to a screen the server already believed they were on
- An outbreak’s “during” links no longer name epidemics that lattice suppression has folded into another
EpiSODIC 0.18.0
New
- The first run against a database reports the whole case history it is given, so importing years of history fills the Archive instead of leaving an empty dashboard
- Clusters a backfill opens whose last case is past
reconciliation.stale_open_daysclose in that same run, and go straight to the Archive -
episodic_run_cron(backfill =)overrides that decision;episodic_validate_detection()passesFALSE, so a replay still measures detection delay rather than an import - Schema version 4:
episodic_cluster.opened_in_backfillandepisodic_detection_run.is_backfill
Changed
- The Performance screen’s time-to-detection excludes clusters a backfill opened, since their
opened_atis the day the archive was imported - Farrington tests every week its streams can carry on a backfill run, rather than the
max_weeks_testedcatch-up cap - A run states the span of the case history it holds, and says outright when the newest case is older than every rule-based detector’s lookback window
-
same_placeandrare_triggerreport how many hits their lookback window kept out, so a run that reports nothing says which kind of nothing it found - The Farrington history-shortfall line says how many of the eligible streams it fitted on, not only how many it skipped
EpiSODIC 0.17.2
Changed
-
episodic_check_cases(stop_on_problem = TRUE)runs its checks once and throws on that report, instead of running the whole check suite twice
Fixed
-
episodic_run_cron()failed on any case data at all, reading the case data itself where the check report was expected
EpiSODIC 0.17.1
New
-
episodic_report_version_claimregisters a report’s version number before the render rather than after it, so two concurrent renders of one cluster never collide - Schema version 3:
episodic_report_version_claim, and a unique index onepisodic_report_render(cluster_id, version_no) -
episodic_format_number()writes every number a reader sees in the session language’s own marks, from fourmisc.decimal.mark/misc.thousands.*keys per language - Hindi numbers group the Indian way (12,34,567) and Spanish starts grouping at five digits (2000, but 12.345), both from those keys
-
misc.language.<code>names all eight languages in every language file, so a message can say “Dutch” where it used to saynl -
EPISODIC_LANGUAGEtakes regional variants:en-USwrites January 7, 2025 andes-419writes 1,234.5 -
en-GBandes-ESare accepted as names forenandes, and a region that is not shipped (nl-BE) now falls back to its language rather than to English - Four
date.format.*keys per language, so German puts a point after the day, Spanish two “de”s in, and Chinese writes 2025年1月7日
Changed
- A reporting delay that could not be measured is now
NArather than 0, and the epi curve, the pathogen curve, Rt and the doubling time each say so instead of assuming complete reporting - Timestamps on the dashboard and in reports are written in the session language rather than as
%d-%m-%Y - The report’s similar-clusters table applies the same small-count suppression as the concentration table
- The synthetic generators put the caller’s random stream back when they are done, instead of leaving their own behind
- The Info screen names the language it renders in, with the code beside it, rather than printing the code alone
- The unsupported-language warning lists each code with the language it stands for
Fixed
- The dossier failed to render at all on a database whose last run recorded no package versions, taking eleven tests down with it
- The dossier crashed on a cluster with no case linked to it, where the case-free tile compared against a day count that does not exist
- Detector names were joined with the Dutch word “en” in all eight languages
- The Streams screen’s Farrington tooltip was written in English on every language
- An
NAin an epi curve’s incomplete flag silently dropped that day’s bar from the chart - The Pathogen screen counted a comparison period that predates the database as zero cases rather than as no data
- Chart axes wrote 1.234,5 in Arabic, Hindi and Chinese, which all write 1,234.5 with the digits this package renders
- Dates were written day-month-year in every language, including the three that do not write them that way
- The English translation wrote “License” where British English writes “licence”
- Numbers on every screen ignored the session language: an R-formatted 1,234.5 reached a Dutch, German, French or Spanish reader unchanged
-
episodic_report_diff()and the scheduled-report mail picked their previous version by row order when two shared a version number - A failed
file.copy()of a rendered report was recorded as a successful render - A synthetic window too short to produce a baseline case returned a list where a data frame was expected
EpiSODIC 0.17.0
New
-
episodic_validate_detection()replays generated history week by week and measures the detectors against the outbreaks that were injected into it -
episodic_validate_comparator()runs a naive same-place rule or a 2-SD Shewhart limit over the same data, through the same metrics -
episodic_validate_rethreshold()re-matches a validation result at different thresholds without replaying anything -
episodic_synthetic_ground_truth()returns whatepisodic_synthetic_cases()injected, per outbreak and per case -
episodic_synthetic_cases()gainsoutbreaks, for a history with none injected, andoutbreak_offsets, to disperse them across the window -
config$<detector>$enabledswitches any of the four detectors off, and is part ofconfig_hash -
data-raw/validation/runs the full multi-seed study and writes the numbers a paper would quote - CI runs the suite against a real MariaDB server, so DDL a server rejects fails the build
- CI also runs the suite against a real MySQL server, which is a different server: the two disagree about column-level foreign keys
Fixed
- The MariaDB schema was never accepted by a real server: the tables are declared in reading order, not foreign-key order, so creation failed on the first statement
- On MySQL the schema created no foreign keys at all: inline column-level
REFERENCESare parsed and discarded there, so every constraint was silently absent -
episodic_db_exists()treated a schema shared with another application as an existing EpiSODIC database, so a first run refused and sent the operator toepisodic_db_migrate(), which stamped it current with two of its twenty-four tables -
episodic_db_runs()bound a double toLIMIT, which MySQL refuses, so the Activity screen failed on that dialect - Half the lattice resolved its geography from the run’s own configuration and half from
EPISODIC_CONFIG, so a run given a config path silently matched no case to any geographic stream -
episodic_db_create()leaked the connection it opened when refusing a database that already had tables - With
EPISODIC_PC_PROVINCE_MAPunset, a run said the shipped Dutch province ranges were in use, which they no longer are - Scheduled reports, on-demand renders and config exports derived their output directory from
dirname()of the database path, which for a MariaDB DSN turned the database password into a directory name on disk; a newreport.output_dirconfig key is now required for a DSN and refuses outright when unset
EpiSODIC 0.16.0
New
-
episodic_db_migrate()brings an existing database up to the current schema; the schema now carries a recorded version andepisodic_db_connect()refuses a database it does not recognise -
config$geographynames the whole-catchment code and the area-code rule, so the two coarsest lattice levels are no longer hardcoded to one country -
config$same_place$lookback_daysandconfig$rare_trigger$lookback_daysbound how far back the rule-based detectors report hits -
config$report$small_count_thresholdis documented in the shipped defaults instead of only existing in code - An instance configuration is validated against the shipped defaults: an unknown key, a wrong type, or a null where a value is needed stops the run and names the key
-
episodic_add_user()gainsmust_change, for an account that does not need a forced password change - Refused sign-ins are recorded in
episodic_app_login_failurewith the username tried and which of the three reasons it was - The Activity screen gains category filter chips: assessments, closures, mutes, sign-ins, detection runs
Changed
- BREAKING:
access.require_loginnow defaults totrue, so a freshly installed instance is closed to anonymous visitors - BREAKING:
EPISODIC_CONFIG,EPISODIC_STYLEandEPISODIC_QUARTO_REPORTset to a path that does not exist are now errors rather than silent fallbacks - BREAKING:
EPISODIC_GEO_DATAno longer falls back to the bundled Netherlands geometry; without it the dashboard shows its bar-chart fallback - BREAKING:
EPISODIC_PC_PROVINCE_MAPno longer falls back to Dutch province ranges; without it the province level of the lattice stays empty - BREAKING: the whole-catchment region code and the
GEBIED-area prefix are nowconfig$geography, which changes every geographic stream key - BREAKING:
episodic_demo()refuses adb_paththat already exists and refuses a MariaDB DSN;overwrite = TRUErebuilds a demo in place - An unsupported
EPISODIC_LANGUAGEwarns once and falls back to English instead of erroring on every render - The dashboard sets
langanddiron the document, and the stylesheet uses logical properties, so Arabic renders right to left - Mail headers are RFC 2047 encoded and message bodies base64 encoded, so non-ASCII subjects and reports survive every relay
-
episodic_demo()configures its geography through the documented environment variables rather than relying on built-in defaults -
episodic_demo()dates its run from the last day a suppliedcasesextract covers, and says so;episodic_run_cron()still dates every run from the system date - An explicit YAML
nullnow keeps its key during the configuration merge instead of removing it - Sign-in rows on the Activity screen are withheld from a reader who has not signed in
-
episodic_run_cron(debug = TRUE)no longer prints per-query SQL and encoding dumps; it reports phase and per-stream progress
Fixed
-
same_placeandrare_triggerre-emitted every hit in the entire case history on every run, which resetruns_since_detectedsoclose_after_runscould never fire and grewepisodic_detectionwithout bound - Farrington aggregated to the week containing
run_date, testing a partial week against full-week baselines, so it was near-blind on every day except Sunday -
episodic_run_cron(run_date = ...)was ignored by reconciliation, which judged staleness against the wall clock - An already-closed cluster was closed again on every subsequent run, filling
episodic_cluster_statewith duplicate closures - A closed cluster that was re-detected went on absorbing cases silently instead of returning to the board for reassessment
- A merge into an already-assessed cluster did not flag it as changed since assessment
- The forced password change was skipped for any account named
demowhose password wasdemo - Lattice suppression could hide every detected cluster behind a manually added one, which holds no
episodic_caserows so scored zero overlap with all of them - Lattice suppression could chain, putting a cluster behind one that was itself suppressed and showing the far end of the chain on no dossier at all
- The reporting-completion curve skipped every lag at which nothing had arrived yet, so short lags read as far more complete than they were and the incompleteness zone sized from it was too narrow
- Deduplication grouped on
patient_keyandpathogenglued together with no separator, so two patients whose keys and pathogens concatenate alike were treated as one and a positive was silently dropped - A suppression parent’s case list was re-read once per overlapping child
- The institution activity feed compared the operator’s own
institution_keyagainst the stored hash of it, so every row was skipped and patient-day normalisation never engaged on any real deployment - A manual cluster’s detector agreement was scored out of one detector instead of four, so every manual cluster outranked a comparable detected one
- The dossier narrative could state that positivity had risen, or stayed flat, for a cluster whose positivity was never measured at all
- A first positive test was reported as positivity rising
-
episodic_html_escape()left single quotes unescaped, and every attribute in the notification HTML is single-quoted -
same_placemerged its hit windows on gaps in index rather than gaps in time, so two outbreaks years apart at one place became a single window spanning both -
same_placeandrare_triggerreported cases sampled afterrun_date, so a replayed run reported outbreaks from its own future -
episodic_app_data_asof()took its date from a UTC timestamp and compared it against a local one, putting the incompleteness window a day out for the hours after local midnight, and read the wrong date entirely for a backfilled run
EpiSODIC 0.15.0
New
- The assessment form gets a “close this cluster” checkbox, submitted alongside the classification, so closure is always a deliberate, individual decision rather than implied by a verdict
- The checkbox is pre-ticked (with a highlighted outline) when artefact/expected variation is chosen, but can always be unticked to leave the cluster open for a second opinion
- Submitting an assessment now asks for confirmation, naming the verdict and stating plainly whether it will close the cluster
- A cluster explicitly closed and then reassessed shows a banner naming who closed it, when, and that new cases have come in since
-
reconciliation.autoclose_unassessed(defaulttrue) controls whether the cron auto-closes a cluster nobody ever assessed once it goes stale - The Archive now names who closed each cluster (a person’s full name, or “System” for an automatic closure)
- A closed cluster’s assessment form is hidden behind a “Re-open” button instead of staying open for a new assessment, reachable the same way from the rail, the Archive, or any related/similar-clusters panel
Changed
- No verdict, including artefact/expected variation, closes a cluster by itself any more - closure is always a separate, deliberate act, whether taken by a person or by the cron’s unassessed-and-stale rule
- A cluster explicitly closed reopens into “Reassessment needed” the moment new cases arrive on its stream, rather than staying closed regardless
- The
"closable"cluster state is removed; a classified, unclosed cluster now reads as “Monitoring” regardless of verdict - The dossier’s large case-count stat now reads “Cases” instead of “Confirmed”
- Cluster notes and the config-change audit log now show the acting user’s full name, never their login name
Fixed
- The cluster notes history modal showed the login name instead of the full name
- The scheduled report panel’s “set by” line showed the login name instead of the full name
- The assessment form’s rationale field, close checkbox and submit button are now disabled until a verdict is picked, so recording an assessment without one is no longer possible
EpiSODIC 0.14.1
Changed
- Chart panels now name the specific R package(s) at fault instead of showing the raw “Graphics API version mismatch” error when the graphics-rendering stack (e.g. ragg, systemfonts, textshaping) is installed but incompatible with the running R version, in all shipped languages
- A distinct message covers the rarer case where no PNG graphics device works at all (a build-time R configuration issue), rather than wrongly telling the operator to reinstall a package
EpiSODIC 0.14.0
New
- The dashboard now adapts to phone and tablet screens: the clusters screen’s rail goes off-canvas below 1200px, and below 768px shows one of rail, dossier or assessment at a time through a sticky segmented control that also names the open cluster
- The header’s navigation collapses behind a menu button below 1200px
- Tables, charts and controls throughout the dashboard get a horizontal scroll, larger tap targets and iOS-safe input sizing below 1200px
Changed
-
.episodic-body/.episodic-streams-screenno longer size themselves with a hardcodedcalc(100vh - 44px); the app shell is now a column flex so the header can grow past its usual height without breaking either screen’s layout - The dossier’s interpretation/notes pair and the Pathogen screen’s care-line/institution pair now stack below 768px instead of staying side by side
EpiSODIC 0.13.2
Changed
-
episodic_demo()now takes any additional argument thatepisodic_run_app()provides
EpiSODIC 0.13.0
New
- Clusters can now carry a scheduled-report subscription: an epidemiologist sets an every-N-days cadence and a list of recipient email addresses from the Reports panel, for colleagues without an EpiSODIC account (or only a viewer one) who need updates without opening the dashboard
-
episodic_run_cron()sends every due scheduled report after each run, as a self-contained HTML attachment through an already-configured, email-capable notification channel (SMTP, sendmail or Microsoft 365); every attempt, sent or failed, is logged - A schedule sends its first report on the very next cron run, then every N days after the last successful send; it stops automatically, after one final report, once its cluster closes, merges, or is suppressed
- Every outbreak report now shows what changed since the previous version - new cases, and the priority score, ratio and case period deltas - in Quarto callout boxes
- New
vignette("scheduled-reports")documenting the cadence, closure and delivery rules
EpiSODIC 0.12.0
New
- Every cluster now has a notes panel next to the interpretation panel: a free-text, markdown-formatted scratchpad open to any signed-in role, stored as plain text and rendered on display
- New
episodic_add_manual_cluster()adds clusters detected by another algorithm or system, vectorised over any number of clusters in one call, never connected to this instance’s own case data - for output from another programming language or system, fed in as already-parsed R values
Changed
-
bslib,cli,commonmark,htmltoolsandjsonlitemoved from Imports to Suggests:shiny(a hard Import) already Imports every one of them, so they were never an independent dependency in the first place -
vignette("data-format")documentsepisodic_add_manual_cluster()in a new “Clusters from another system” section
EpiSODIC 0.11.0
New
- New
access.require_loginconfiguration key (defaultfalse, keeping the current behaviour): whentrue, an anonymous visitor gets a sign-in prompt and nothing else, because the server renders no navigation, no status strip and no screen for them rather than hiding it client-side - The Settings screen has a read-only “Dashboard access” panel reporting which way
access.require_loginis set; it is deliberately not editable in-app, so a login wall cannot be switched off from inside the app by a single account - A cluster’s dossier can now be opened by URL:
?cluster=<id>on the dashboard address selects that cluster on the Clusters screen - New-cluster notifications now link each cluster id straight to its dossier when
dashboard_urlis configured - The geography panel and the outbreak report now name the province each postcode falls in, alongside the postcode itself
- The Info screen has a “Reference data” panel reporting, per
EPISODIC_*variable, whether the instance’s own file was read, was rejected, or was read and matched nothing, and what it delivered - The reference-data panel reports how many of the case data’s own postcodes resolve to a province, which is what distinguishes a
EPISODIC_PC_PROVINCE_MAPthat was never read from one whose postcode format does not match the case data -
episodic_default_style.yaml(andEPISODIC_STYLE) now also carries typography:font(a CSS font-family stack) andfont_size_base, which scales every other font size in the dashboard, since those are now expressed inremrelative to it - Every cluster table now has a “Case days” column: the number of distinct dates with at least one case, as its own figure next to the case count and the calendar duration - a cluster can run 90 days with cases on only 10 of them (a sharp peak) or on 39 of them (a flat, sustained rise), the same duration and case count, two different epidemic shapes
-
episodic_add_user()is now exported, so accounts can be provisioned from a script without reaching into the package’s internals
Changed
- Trimmed the exported API surface to what an operator actually needs:
episodic_care_lines(),episodic_case_columns(),episodic_config_hash(),episodic_config_resolve(),episodic_institution_types(),episodic_interpretation_slots(),episodic_provision_user(),episodic_resolve_data(),episodic_sex_codes(),episodic_tr()andepisodic_validate_cases()are no longer exported (still used internally) - The shipped default config, style, pathogen and report files are renamed to self-explanatory names:
default.yaml->episodic_default_config.yaml,palette.yaml->episodic_default_style.yaml,pathogen_config.csv->episodic_default_pathogen_config.csv,cluster_report.qmd->episodic_default_report.qmd - Every table of clusters (Archive, Pathogen screen, the dossier’s related and similar-clusters panels, the outbreak report and the new-cluster notification) now shares one implementation with the same columns in the same order: cluster id, then context, then the case period (first case - last case, as one range), cases, case days, then what was decided (classification/state/closure date/shared cases), then duration and priority
- Every table of clusters is now sorted on the last case day, most recent first, with priority and cluster id breaking ties
- A cluster row that does not open a dossier, because lattice suppression folded it into another, now shows its id in the warning colour with a tooltip explaining why, instead of a link that does nothing
- The per-screen cluster table column translation keys are replaced by one
column.*family shared by every cluster table -
episodic_config_hash()now also excludes theaccesssection, for the same reason it excludesnotifications: it governs how the instance is operated, never what a run computes -
EPISODIC_PC_PROVINCE_MAPpointing at a missing, unreadable, empty, wrongly-columned or duplicate-postcode CSV is now a run-stopping error naming the file and the problem, instead of a silent fall back to the shipped Dutch demo ranges - A detection run in which no postcode resolves to a province now says so in its trace, instead of leaving province-level detection silently empty
- The dashboard’s area/province/region place label now shows
region_codeverbatim instead of cosmetically reformatting it, so an operator can see at a glance, character for character, whetherEPISODIC_PC_PROVINCE_MAPresolved the code they expected, without risking a mangled real place name (e.g. the hyphen in “Noord-Holland”)
Fixed
- Opening a cluster from a table row or a “linked to #N” chip now moves the rail’s own highlight to match; it used to leave the rail showing whichever cluster was selected before, because the rail deliberately does not re-render on every selection
- Opening the dashboard via a
?cluster=<id>link no longer crashes with “Can’t access reactive value ‘url_search’ outside of reactive consumer” -
episodic_demo()no longer leavesEPISODIC_CONFIG/EPISODIC_DB/EPISODIC_GEO_DATApermanently overwritten for the rest of the R session once it returns
EpiSODIC 0.10.3
Fixed
- Email/notification messages for new clusters and run failures are now rendered in
EPISODIC_LANGUAGE, in all 8 shipped languages, instead of always in English - The new-clusters email’s numeric columns are now horizontally centred, and its dates are formatted the same way the dashboard formats them
-
microsoft365notifications under the cached-login flow (Option C) now honourfrom: when set, mail is sent from that mailbox viaMicrosoft365R::get_business_outlook(shared_mbox_email = from)instead of always sending as the signed-in account - Fixed a
grepl(..., ignore.case = TRUE, fixed = TRUE)warning when matching a Microsoft 365 tenant against cached Azure AD logins; the match is now done case-insensitively without triggering the warning
EpiSODIC 0.10.2
New
-
microsoft365notifications can now reuse an already-cached Azure AD login (e.g. one obtained viaMicrosoft365R::get_business_outlook()orepisodic_setup_microsoft365()) by tenant, with noclient_id,client_secret, orfromrequired
EpiSODIC 0.10.1
Fixed
-
episodic_demo()’s startup message once again mentions “demo account”, after an unrelated styling commit reworded it away from that phrase
Changed
- Redesigned the Settings screen: notification channels now sit in a compact wrapping card grid with a toggle switch per channel instead of full-width stacked blocks, and the read-only detection configuration is grouped by section with readable field labels instead of one dense grid of dotted, all-caps keys
- Documentation updates
EpiSODIC 0.10.0
New
- Implemented various ways to send notifications of new clusters: ntfy, SMTP, sendmail, Microsoft 365, Teams (Power Automate Workflow), Slack
- Added
vignette("notifications")with setup steps per channel - Added an
is_adminaccount property and an in-app Settings screen for managing notification channels, dashboard accounts, and viewing (read-only) detection configuration, without needing server access - Added
episodic_config_export()to export the resolved configuration as a zip file, with notification secrets masked by default
Changed
- Documented every
episodic_default_config.yamlconfig key with a short inline explanation - Every write action now re-resolves the signed-in account against the database immediately before writing (
episodic_auth_refresh_user()), so deactivating or demoting an account from the Settings screen takes effect on its already-open sessions too, not only on its next sign-in - An
is_adminaccount can no longer revoke its own admin access or deactivate its own account from the Settings screen (self-lockout guard)
EpiSODIC 0.8.16
EpiSODIC 0.8.15
EpiSODIC 0.8.14
Fixed
- Fixed
n_casescounting wrong cases for ward/area-level clusters; now uses one shared function,episodic_db_cases_for_stream_id() - Fixed stream muting doing nothing; a run now actually suppresses new detections on muted streams and reports how many were suppressed
- Reconciliation functions no longer swallow database errors via
tryCatch(); only a genuinely missing stream still falls back safely
EpiSODIC 0.8.13
Fixed
- Fixed Farrington detector being silently inactive on <4 years of history; now reports shortfall and
farrington.bdefault lowered to 2
Changed
- Removed unbounded
episodic_reporting_triangletable; delay curve now derived on the fly from existing case/run data -
episodic_detection_rungains arun_datecolumn representing the run’s business date (distinct fromstarted_at) - Batched remaining per-row database writes in a run; round trips dropped from 1,868 to 203 on a synthetic run
- Streams screen baseline-window queries reduced from 52 per page to 3 via
episodic_baseline_excluded_windows_many()
EpiSODIC 0.8.12
Changed
-
episodic_run_cron()now batches writes instead of row-by-row, cutting MariaDB round trips from 12,549 to 1,868 on a synthetic run - Note: chunked
dbBind()is not a true batch; single multi-row statements via newepisodic_db_write_many()are what collapse round trips
EpiSODIC 0.8.10
EpiSODIC 0.8.7
Changed
- Author list trimmed to its actual contributors and copyright holders: Matthijs Berends, Certe Medical Diagnostics & Advice Foundation, and University Medical Center Groningen (ROR-identified, same format as Certe)
- Every language listing (README, vignettes, roxygen docs) now orders English first, then the rest alphabetically, instead of leading with Dutch
- Geography is documented as operator-supplied throughout, not Netherlands-specific: the “Geographic reference data” section in
vignette("data-format")is substantially expanded, now also coveringEPISODIC_PC_PROVINCE_MAP(L4 stream detection) alongside the map data, and the FAQ’s geography entry does the same -
vignette("overview")’s screen-by-screen section no longer stops at three screens (“a third screen closes the loop”) - it now covers all seven (Clusters, Pathogen, Performance, Archive, Streams, Activity, Info) -
episodic_check_denominators()andepisodic_check_institution_activity()are now mentioned alongsideepisodic_check_cases()everywhere the docs discuss checking input data before a run (vignette("data-format"),vignette("deployment"),vignette("faq"), README)
EpiSODIC 0.8.6
Changed
- Reverted 0.8.5’s
gc(full = TRUE)forcing inepisodic_run_cron(debug = TRUE): a second lab reproduction, with that forcing in place, showed agc()completing cleanly immediately afterepisodic_growth_slope()and then the crash landing on the very next call,episodic_spatial_concentration()- ruling out delayed-onset corruption from an earlier call and pointing at that one call on that specific data instead, which the extragc()calls were never able to help with and were only slowing every run down (~600-1000ms added per stream, a ~5 minute run turning into “10-20 seconds on SQLite” by the lab’s own comparison) -
episodic_spatial_concentration()(the isolated crash site) no longer usestable()/factor(), which sort their levels and so invoke locale-aware string collation - not guaranteed safe against a string whose declared encoding does not match its actual bytes, which is exactly what a client/server character-set mismatch can hand back from a MariaDB connection. Counts viatabulate(match(pc, unique(pc)))instead, which only ever hashes and byte-compares the strings themselves and sorts nothing but the resulting integer codes -
episodic_run_cron(debug = TRUE)now dumps each reconciliation candidate’spcvalues (encoding, validity, raw bytes) immediately beforeepisodic_spatial_concentration()runs, in place of the removedgc()forcing - cheap (a handful of values at most) and aimed squarely at confirming or ruling out the character-encoding hypothesis above
EpiSODIC 0.8.5
Changed
-
episodic_run_cron(debug = TRUE)now forces a full garbage collection (gc(full = TRUE)) immediately after every database round trip inside the per-stream reconciliation loop. A fatal crash from corrupted memory typically surfaces later than its actual cause - whenever R’s own, otherwise lazily scheduled, garbage collector happens to stumble on the damage - so forcing one right after each call is what lets the trace log land on the actual culprit instead of a downstream symptom several calls away. Confirmed necessary: a lab reproduction of the ongoing MariaDB crash showed the trace stopping at a different line on each run (once afterepisodic_growth_slope(), once mid-episodic_app_density()) despite hitting the exact same stream and data, the signature of exactly this kind of delayed-onset corruption. Noisy and slow;debug = TRUEwas already meant only for chasing a failure like this, not for routine runs
EpiSODIC 0.8.4
Fixed
-
episodic_db_truncate()against MariaDB/MySQL crashed withError: bad_weak_ptrright after successfully truncating every table.on.exit(..., add = TRUE)appends to the end of the exit-handler list, so the handler that restoresFOREIGN_KEY_CHECKS(registered after the connection’s ownon.exit(dbDisconnect(con))) ran after the connection had already been closed - RMariaDB’s response to a query against a freed connection is a C++ abort, not a catchable R error. Now restores foreign-key checks explicitly right after truncating, with theon.exithandler only as a safety net for the error path, registered withafter = FALSEso it runs before the disconnect even then
EpiSODIC 0.8.3
New
-
episodic_db_truncate(): empties every EpiSODIC table (including dashboard accounts) while keeping the schema itself, for a hard reset back to “freshly created, no data” without a schema migration. Refuses to run outside an interactive session, and requires typing the database’s own name back at a prompt before it deletes anything
EpiSODIC 0.8.2
Fixed
- Every cron-side database write function’s optional parameters defaulted to a bare, untyped
NA(R’s logicalNA) rather than the column’s own type (NA_integer_,NA_real_,NA_character_). RSQLite coerces this silently; RMariaDB does not treat a logicalNAbound to a non-logical column the same way as a properly typed one, which is a documented source of crashes rather than a catchable error. Most notably,episodic_db_detection_insert()’scluster_id- untyped, and hit on every single detection insert in every run, since a detection is always written before a cluster is attached to it - now defaults toNA_integer_
EpiSODIC 0.8.1
Changed
-
episodic_run_cron(debug = TRUE)traces every sub-step inside a stream’s own reconciliation (triangle update, MEM/Farrington detection, baseline exclusion, population vector, trend caching, detection insert, and each reconciliation candidate’s priority-score components) instead of only the stream as a whole - narrows a crash or a stall down to the exact call inside a single stream’s processing, not just which stream
EpiSODIC 0.8.0
New
-
episodic_check_denominators()andepisodic_check_institution_activity(): the same non-throwing pre-flight reportepisodic_check_cases()gives the case feed, now available for the optional positivity and hospital-activity feeds too -
episodic_run_cron()now refuses on a malformedinstitution_activityfeed before writing anything (when handed as a plain data frame), instead of only mid-transaction -
episodic_run_cron()writes a timestampedmessage()at every phase of a run (config resolution, DB connect, each feed load, lattice enumeration, each detector, and each stream’s reconciliation), always on - useful in any interactive load-through and in a real cron job’s own log. A newdebug = TRUEargument addssessionInfo(), package/driver versions, memory snapshots, and per-stream detail on top, for chasing a failure (including a crashed session) that leaves no R-level error behind
Changed
- L4 (province) stream derivation supports an operator-supplied PC-to-province lookup via
EPISODIC_PC_PROVINCE_MAP, falling back to the bundled Northern Netherlands demo ranges as before - a deployment outside that demo region previously got no L4 streams at all, silently - Archive screen sorts by Period (descending) instead of by closing date
- Dossier top card reorders to confirmed, unique patients, duration (new), doubling time, case-free days, priority, with ratio/incidence density (where shown) moved after priority
- Pathogen screen’s detection parameter table shows a long
source_refreference in the “What it does” column instead of “Value” - Pathogen screen’s positivity chart no longer renders a percentage axis past 100%
- Pathogen screen’s weekly incidence and season/year comparison charts share the same x-axis span for any period that falls within a single season/year
- Geography map charts drop their fill legend (redundant with the on-map count labels) so the render height computed for the map’s own aspect ratio is not shortened by legend space, removing the whitespace above/below the map
-
.shiny-plot-outputcentres its contents vertically and horizontally; the geo-panel map group centres horizontally as well
EpiSODIC 0.7.0
This release includes a database schema migration (episodic_case gains a required lab_number column) - existing databases need to be recreated or migrated before running against this version.
New
- Case data requirements gain a required
lab_numbercolumn: your laboratory’s own specimen/culture number, distinct fromsource_keyand, unlike it, not required to be unique - two rows may share one when a single culture yields more than one reported result. Shown on the line list alongsidepatient_key, replacing the previously-shown (and not useful)source_key - Similar-clusters panel on the dossier gains a cluster ID column and opens the same way every other cluster table does
EpiSODIC 0.6.*
New
- Unassessed clusters auto-close once their last case day is older than
config$reconciliation$stale_open_days(default 60 days) - Run-detail modal shows how many clusters a run auto-closed
- Info screen shows the app’s version, description, license and website, read live from
DESCRIPTION - Line list gains
patient_keyas its first column
EpiSODIC 0.5.*
New
-
episodic_check_cases()reports all case data problems at once, with row-level detail and fixes - Lattice suppression collapses one outbreak seen at multiple levels into a single dossier
- Linked clusters show e.g. “Linked to #123” and a Related clusters panel
-
episodic_case_datadocuments every column’s type, nullability and accepted values - Deduplication now checks against the most recently stored episode, not just the current batch
- “Isolate” renamed to “positive” throughout the app, reports and validation advice
- New “Frequently asked questions” vignette
- Rail cards show the cluster’s care line as a colour-coded chip (1st/2nd/3rd line) beside the pathogen name, and the cluster id in small type next to it
- The geography panel shows a second, uncropped choropleth alongside the existing cropped one, for region-wide context
-
episodic_care_linesgains"third"(tertiary care)
Changed
- Accounts now have exactly two roles:
"epidemiologist"(read/write) and"viewer"(read-only) -
episodic_validate_cases()reports all problems in one error, not just the first -
episodic_run_cron()checks feeds before writing and stops cleanly on failure - Status strip and Activity screen show why a run failed
- Farrington now retests every week since the last completed run, capped at 8 weeks
-
config$effect_size_floornow gates new-cluster candidates against borderline signals -
episodic_synthetic_cases()demo data rebalanced to avoid spurious chance clusters -
langnow defaults correctly toEPISODIC_LANGUAGEeverywhere; unset means English - Empty strings in required columns are now treated as invalid, like
NA - Cluster status chips render filled (colour background, white text) throughout, not just outlined
- Rail card dates spell the month out when the range falls within a single month (
"2-12 January 2025"), abbreviated otherwise ("12 Jan. - 4 Feb. 2025") - Rail card’s last line shows case count and priority score; ratio dropped
- Bar chart labels widen from ~30 to ~50 characters before truncating, with a mouse-over tooltip for the full value
Fixed
- Region-level streams no longer report the whole catchment’s counts under one region’s name
- Cluster line lists no longer include unrelated cases from the same ward or catchment
- Reconciliation no longer opens duplicate dossiers for an ageing cluster
- Translation placeholders no longer break on backslashes
- Epidemic curve thousands separator now follows the language by default
-
episodic_run_cron()no longer corruptssample_date/receipt_date/period_start/period_endwhen the operator supplies them asDatecolumns (asepisodic_case_dataexplicitly allows) - RSQLite was binding them by their raw numeric epoch value instead ofYYYY-MM-DDtext, so a later read failed with “character string is not in a standard unambiguous format” -
episodic_check_cases()’s allowed-value messages (care_line,institution_type,sex) now quote each value, and its?episodic_case_datareferences are clickable in terminals/RStudio that support it -
episodic_db_create()against MySQL no longer fails with “BLOB/TEXT column can’t have a default value” [1101] -episodic_stream.denominatorandepisodic_case.care_lineare now widened toVARCHARunder themariadbdialect, since MySQL (unlike MariaDB and SQLite) rejects aDEFAULTonTEXTcolumns outright -
episodic_db_create()against MySQL no longer fails with “BLOB/TEXT column … used in key specification without a key length” [1170] - every remainingTEXTcolumn used in aPRIMARY KEY, compositePRIMARY KEY, orCREATE INDEX(pathogen,patient_key,sample_date,run_date,week_start,period_start,care_line,area_code) is now widened to a boundedVARCHARunder themariadbdialect, since MySQL requires an explicit key length onTEXT/BLOBcolumns used as a key -
episodic_denominators_load()against MySQL no longer fails to write a denominator row with no area stratum (area_code = NA, whichepisodic_validate_denominators()has always allowed) -episodic_denominator’s compositePRIMARY KEYincluded the nullablearea_code, and MySQL implicitly forces everyPRIMARY KEYcolumnNOT NULL; it now has a surrogatedenominator_idprimary key with the natural key as aUNIQUEconstraint instead, which permits multipleNULLs in both MySQL and SQLite -
episodic_db_create()against MySQL no longer fails with “you have an error in your SQL syntax … near ‘trigger’” [1064] -triggeris a reserved word in MySQL;episodic_cluster_state.triggerand the raw SQL that reads/writes it are now backtick-quoted -
episodic_detect_same_place()no longer hardcodescare_line = NAon every stream it creates or upserts - it is the detector responsible for the institution-level streams (LTC, out-of-hours, general practice) that lattice enumeration never creates on its own, and for hospital ward-level streams, so this had silently hidden the care line on almost every stream capable of showing one - Logo no longer shows an opaque white background outside the hexagon
EpiSODIC 0.4.0
Changed
- Renamed data interface from “ingestion” to case-based naming (
R/cases*.R,episodic_cases_load(), etc.), no deprecation shims - Documented data frames/tibbles as normal input; zero-argument functions still accepted
-
care_linenow acceptsNA, normalised to"unknown" - Corrected
care_line’s documented values tofirst,second,other,unknown - A run that skips rows now finishes
partial, notsuccess - Skipped institution activity rows now warn with counts and unmatched keys
- Denominators and institution activity validated like cases, with clear errors
New
- Every case column documents type, nullability and permitted values
- Exported
episodic_care_lines,episodic_institution_types,episodic_sex_codes -
episodic_validate_cases()enforces the full data requirements -
episodic_detection_runrecords per-feed supply/dedup/insert/skip counts - Activity screen shows per-run arrival and skip counts
EpiSODIC 0.3.1
New
- New Pathogen screen: weekly incidence, seasonal comparison, Rt, positivity, age/sex, geography, signals
- MEM thresholds drawn against the season curve with intensity bands
- Rt now estimated per pathogen, not only per cluster
-
memdetector documented on the Info screen
Fixed
-
expected,excessandrationow correctly persisted and carried through reconciliation - Priority score now computes all seven components instead of defaulting most to zero
- Missing components no longer drag the score down
- Doubling time now uses a Poisson fit to daily counts instead of cumulative log-regression
- Positivity now compares region-wide cases and tests over the same week
- Reporting lag now measured from cluster’s last case day, not run date
- Rt no longer overestimated for clusters younger than one serial interval
-
episodic_mem_status()no longer returns ambiguousNULLoff-season - MEM now evaluates the last complete week and fits only on full seasons
- Concentration now a share of cases with known postcode, not all cases
- Demographic baseline now excludes the cluster being compared
- Top navigation now correctly highlights the active screen
Changed
- Choropleth cropped to relevant areas instead of the full reference dataset
- Geography panel now spans the full dossier width
- Standardised terminology to “pathogen” across all languages
- Axis labels and legend text standardised to 11pt secondary grey
- Season overlay stops at the current week instead of running flat to zero
- Dutch terminology standardised to “casus”
-
case_free_daysnow translated instead of shown as a raw column name - Weekly charts now show ISO week number with month/year, thinning past ~18 months
- Cluster ids now shown beside pathogen name in dossier and Pathogen screen
- Pathogen screen’s cluster table renamed “Clusters in this period” and links through properly
- Archive now links through and includes a cluster id column
- Top navigation now follows links out of the Pathogen screen
- Pathogen picker’s count now labelled as an all-time case count
EpiSODIC 0.2.0
EpiSODIC 0.1.0
First development version.
- Detection: lattice enumeration, five detectors, cluster reconciliation, cross-lattice suppression, baseline feedback
- Interface: bilingual (NL/EN) Shiny app, dossier view, audit-trailed classification, Performance screen
- Reporting: versioned Quarto outbreak reports with small-count suppression
- SQLite backend, with optional
EpiEstim/mem/quarto/sf -
episodic_demo()runs the whole system against bundled synthetic data