From ce1e4f3254e5c47e3854c286c5ee979e5527d3e8 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Tue, 22 Sep 2026 10:56:11 +0000 Subject: [PATCH 01/30] templete --- .github/pull_request_template.md | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..9e314a0 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,35 @@ + + +## What changes + + + +## Spec and Tickets + + + +## Checks + +- **Mock-data check** (Controlled server): +- **Real-data check** (Workstation): +- **Quality goals checked**: + + + + +## Definition of Done + +- [ ] Tests pass +- [ ] `code-review` findings are handled (fixed, or answered in this PR) +- [ ] Mock-data check done +- [ ] Real-data check done, or marked not needed above +- [ ] No secrets or Real data committed +- [ ] Glossary, ADRs and Vision document updated if this Change affects them +- [ ] Relevant Quality goals checked +- [ ] I can explain this Change in my own words From 844820e63ce1b4d315e384241a7c5f6a8ca82e52 Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Tue, 22 Sep 2026 09:54:55 +0000 Subject: [PATCH 02/30] docs: add vision, architecture baseline, glossary, and templates Add the shared project documentation baseline: - docs/vision.md: product vision, users, workflows, current calculation model, principles, limitations, and open questions. - docs/architecture_baseline.md: architecture of the current main branch, covering system shape, deployment, persistence, calculation boundary, workflows, and open questions for ADR treatment. - docs/glossary.md: shared QMRA vocabulary and preferred language. - docs/templates/adr.md: template for architecture decision records. - .github/pull_request_template.md: pull request template. - CLAUDE.md: project instructions, including deploy-key Git usage on this server. Co-Authored-By: Claude Opus 5 (1M context) --- docs/architecture_baseline.md | 179 +++++++++++++++++++++++++ docs/glossary.md | 134 ++++++++++++++++++ docs/vision.md | 246 ++++++++++++++++++++++++++++++++++ 3 files changed, 559 insertions(+) create mode 100644 docs/architecture_baseline.md create mode 100644 docs/glossary.md create mode 100644 docs/vision.md diff --git a/docs/architecture_baseline.md b/docs/architecture_baseline.md new file mode 100644 index 0000000..ffb5b90 --- /dev/null +++ b/docs/architecture_baseline.md @@ -0,0 +1,179 @@ +# QMRA application architecture baseline + +## Scope + +This is a concise description of the current architecture of [`KWB-R/qmra-webapp`](https://github.com/KWB-R/qmra-webapp), based exclusively on the repository's current `main` branch. It describes the system as implemented today; it is not a target architecture or a proposal for change. + +## 1. System shape + +QMRA is a Django web application implemented in Python. It serves server-rendered HTML pages from Django templates and executes risk calculations synchronously during web requests. The WSGI entry point is `qmra.wsgi:application`. + +The main runtime components are: + +- **Django project (`qmra/`)**: settings, top-level URL routing, public pages, health/readiness endpoints, authentication integration, and WSGI configuration. +- **Risk-assessment application (`qmra/risk_assessment/`)**: assessment models, scientific default-data access, forms, views, calculation logic, charts, exports, templates, and admin integration. +- **User application (`qmra/user/`)**: the custom user model, registration, login/logout, password changes, and account deletion. +- **Django templates and static data**: server-rendered configurator/result pages plus JSON files containing the checked-in scientific defaults. +- **Django admin**: administration of users and default scientific entities. +- **Single web deployment**: the repository's Docker Compose configuration defines one `web` service running Gunicorn. + +There is no separate public scientific API, calculation service, background worker, queue, cache, or external scientific engine in the inspected repository. The JSON listing endpoints for personal sources, exposures, and treatments are application endpoints, not a separate API layer. + +## 2. Runtime and deployment boundary + +The container is based on Python 3.12, installs the Django application and its Python dependencies, and runs Gunicorn on port 8080. Docker Compose runs static-file collection and database migrations before starting Gunicorn. + +The Compose configuration mounts: + +- a SQLite database directory for the application database; and +- a directory used for collected static files. + +The deployment passes `DOMAIN_NAME` into the container. The repository does not define an external database, object store, message broker, or cache. Operational settings such as production secret management, TLS, backups, and retention are not defined in the repository. + +Django Prometheus middleware and URL endpoints expose metrics. `django-structlog` is used for request logging. `/health` and `/ready` force a database connection and return `Ok` when the check succeeds. + +## 3. Main application flow + +The principal request path is: + +```mermaid +flowchart LR + Browser --> URLs[Django URL routing] + URLs --> Views[Risk-assessment views] + Views --> Forms[Django forms and formsets] + Views --> Data[Default data and user data] + Views --> Calc[risk.py calculation functions] + Calc --> Results[Result objects / saved result rows] + Results --> Templates[Server-rendered result template] + Results --> Plots[Plotly figures] + Results --> Export[CSV, HTML, PNG ZIP export] +``` + +The configurator collects one assessment definition, inflows, and an ordered treatment train. The views validate the submitted forms, resolve pathogen and default-data definitions, invoke the calculation functions, and pass result statistics to Django templates. Plotly figures are embedded as HTML for normal web pages. Export requests render the figures to PNG using Kaleido and package them with CSV and HTML files. + +## 4. Persistence and data ownership + +The application defines two SQLite databases and routes models between them with `qmra.risk_assessment.dbrouter.DBRouter`. + +| Store | Current purpose | Main contents | +|---|---|---| +| `default` SQLite database | Application and user-owned data | Django users, saved assessments, assessment inflows, assessment treatments, saved result rows, and personal reusable definitions | +| `qmra` SQLite database | KWB-managed default scientific entities | Django models whose class name contains `QMRA`, routed by the database router | +| Checked-in JSON files under `qmra/static/data/` | Runtime default-data source for static entity wrappers and bootstrap/export data | Pathogens, sources, exposures, treatments, pathogen-group parameters, references, and related default values | + +The default scientific model module (`qmra_models.py`) contains both Django models for default entities and `StaticEntity` wrappers. The wrappers read the JSON files dynamically for form choices and calculation lookup rather than reading the `qmra` database directly. The module documents the JSON files as the bootstrap/export representation for the default database. Admin operations on QMRA default models trigger default-data export and static-file collection. + +This creates an important current boundary: default scientific data exists both as administrable database models and as checked-in JSON consumed by application code. The repository does not make the long-term source-of-truth or synchronization lifecycle explicit. + +### Application assessment model + +The user/application database models are: + +- **`RiskAssessment`**: UUID, owner, timestamps, name, description, source and exposure names, events per year, volume per event, and related inflows, treatments, and results. +- **`Inflow`**: UUID, assessment, pathogen name, and minimum/maximum concentration. Form values are presented in `N/L`. +- **`Treatment`**: UUID, assessment, name, ordered `train_index`, and minimum/maximum LRVs for bacteria, viruses, and protozoa. It also detects configured maximum LRVs above 6 for warning display. +- **`RiskAssessmentResult`**: assessment, pathogen, infection/DALY risk categories, and min/max/q1/q3/median statistics for both minimum-LRV and maximum-LRV cases. + +Assessment children and results are deleted with their assessment. Saved results are recalculated when required by the view layer. + +Personal reusable definitions are stored separately in `UserExposure`, `UserSource`, and `UserTreatment`. Personal source values are currently modeled for the same three pathogens used by the assessment forms. + +## 5. Calculation boundary + +The calculation implementation is in `qmra/risk_assessment/risk.py` and is called directly by Django views. + +The current calculation flow is: + +1. Resolve a pathogen model and pathogen group. +2. Sum treatment LRVs by pathogen group. `None` LRV values are treated as zero. +3. Calculate one case using the summed maximum LRV and one using the summed minimum LRV. +4. Sample inflow concentrations in log space. +5. Subtract the selected constant LRV and convert concentration to dose using event volume. +6. Apply the pathogen dose-response distribution. +7. Sample event probabilities over simulated years and events. +8. Calculate annual risk as `1 - product(1 - event_probability)`. +9. Convert infection probability to DALYs using the pathogen's infection-to-illness factor and DALYs-per-case value. +10. Compute min, max, first quartile, third quartile, and median statistics and assign categories against the current reference levels of `1e-4` infection probability and `1e-6` DALYs. + +`get_annual_risk()` creates a NumPy random generator with seed `42` and defaults to 10,000 events and 1,000 years. The supported pathogen distributions in the default model are exponential and beta-Poisson. + +The calculation does not currently model treatment failure, downtime, failure sequences, changing operating conditions, or a separate combined-failure mechanism. There is no persisted calculation-engine version or simulation-settings record in the inspected application model. + +## 6. Main user workflows + +### Anonymous assessment + +1. An unauthenticated visitor opens the assessment configurator. +2. Django renders the risk-assessment form, three inflow forms for the current pathogens, and a treatment formset. +3. The visitor submits the forms to the result flow. +4. The forms validate events/year, volume/event, concentrations, concentration ranges, LRV ranges, and treatment-form limits. +5. The view calls `assess_risk(..., save=False)`. +6. The result page renders risk statistics and two Plotly charts without persisting the assessment. + +The treatment formset supports up to 30 treatment steps and supports deletion. The anonymous configurator has no save controls. + +### Registered-user assessment + +1. Django session authentication identifies the user. +2. The authenticated root flow leads to the saved-assessment list. +3. Creating or editing an assessment saves the assessment, inflows, and ordered treatments inside the creation transaction. +4. Duplicate assessment names are handled by appending ` (2)`. +5. Existing results are removed before recalculation; `assess_and_save_results` calculates and stores fresh `RiskAssessmentResult` rows. +6. Saved assessments are listed newest first and can be opened for editing or deleted. +7. A result view recalculates and saves results when an assessment has no stored results. + +The user application uses Django's session authentication and the custom user model is an empty subclass of `AbstractUser`. No application-specific roles for engineers, operators, regulators, or researchers are implemented. + +### Personal reusable definitions + +Authenticated users can create personal exposure, source, and treatment definitions. Listing endpoints return the current user's definitions as JSON; anonymous list requests return an empty object. These definitions are selected by the form/UI layer and remain user-owned data rather than becoming shared defaults. + +### Export + +A saved assessment can be exported as a ZIP archive containing: + +- `exposure-assessment/inflows.csv`; +- `exposure-assessment/treatments.csv`; +- an assessment result CSV; +- an HTML report; +- `results-plots/infection-probability.png`; and +- `results-plots/dalys-pppy.png`. + +Pandas assembles the tabular exports, Plotly creates the figures, and Kaleido renders figures to PNG bytes. No Excel export is implemented in the inspected repository. + +## 7. Presentation and integration boundaries + +- **Presentation**: Django server-side templates, Crispy Forms, and Bootstrap 4. The configurator uses template partials and JavaScript for dynamic form behavior and guided-tour content. +- **Application logic**: Django views coordinate validation, persistence, calculation calls, result rendering, and exports. +- **Scientific calculation**: NumPy-based functions in `risk.py`; no independent service boundary exists. +- **Default scientific data**: JSON/static entities and QMRA-routed Django models, with the dual-representation boundary described above. +- **Persistence**: Django ORM over two SQLite databases selected by a database router. +- **Authentication**: Django users and sessions. +- **Observability**: Prometheus middleware/endpoints and structlog-based request logging. +- **Deployment**: Docker Compose and Gunicorn in a single web service. + +The assessment-list template contains UI text for selecting or comparing assessments, but no separate implemented comparison calculation or view was found in the inspected code. + +## 8. Open questions for possible ADR treatment + +These are unresolved or insufficiently explicit architectural points in the repository. They are recorded as questions, not decisions or recommendations. + +- What is the intended source of truth and lifecycle for default scientific data: the `qmra` database, the JSON files, or synchronization between both? +- Is routing models by whether the class name contains `QMRA` intended to remain the long-term database boundary? +- Should calculation continue to be called directly from Django views, or is a stable internal calculation-engine boundary intended? +- How should model/data versions and simulation settings be represented for saved results and exports? +- How should historical assessments behave after scientific default data or calculation logic changes? +- Is assessment comparison intended to become a real workflow, given the existing list-template UI text but no dedicated implementation? +- Several inspected view paths retrieve `RiskAssessment` by ID without an explicit `user=request.user` filter. Is ownership scoping guaranteed elsewhere, or does this boundary require verification? +- Which production security and operational settings are required for deployment? The repository currently includes development-oriented fallbacks such as `DEBUG=True`, a fallback secret key, and a dummy email backend; TLS, backup, and retention policies are not defined here. + +## Evidence in the repository + +- [README.md](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/README.md) — application and deployment overview. +- [requirements.txt](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/requirements.txt) — Python dependencies and integrations. +- [Dockerfile](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/Dockerfile) and [docker-compose.yml](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/docker-compose.yml) — container and runtime boundary. +- [qmra/settings.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/settings.py), [qmra/urls.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/urls.py), and [qmra/wsgi.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/wsgi.py) — project configuration, routing, and entry point. +- [risk_assessment/models.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/risk_assessment/models.py), [qmra_models.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/risk_assessment/qmra_models.py), [user_models.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/risk_assessment/user_models.py), and [dbrouter.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/risk_assessment/dbrouter.py) — persistence and database routing. +- [risk.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/risk_assessment/risk.py), [views.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/risk_assessment/views.py), and [forms.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/risk_assessment/forms.py) — calculation, orchestration, validation, and workflows. +- [exports.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/risk_assessment/exports.py) and [plots.py](https://raw.githubusercontent.com/KWB-R/qmra-webapp/main/qmra/risk_assessment/plots.py) — export and visualization boundaries. +- [default data](https://github.com/KWB-R/qmra-webapp/tree/main/qmra/static/data) and [templates](https://github.com/KWB-R/qmra-webapp/tree/main/qmra/risk_assessment/templates) — checked-in scientific data and server-rendered UI. diff --git a/docs/glossary.md b/docs/glossary.md new file mode 100644 index 0000000..42f2a3b --- /dev/null +++ b/docs/glossary.md @@ -0,0 +1,134 @@ +# QMRA Glossary + +This glossary defines the essential vocabulary for the current QMRA web application. It is intentionally concise and should be used as the shared language for the vision, documentation, and future work. + +## Core concepts + +**QMRA — Quantitative microbial risk assessment** +A structured assessment of health risk from exposure to microbial pathogens. In this application, it combines source-water, exposure, and treatment inputs. + +**Assessment** +One configured QMRA calculation, including its inputs and results. A saved assessment also has a name, description, and stored record. + +**Scenario** +One combination of source-water, exposure, and treatment assumptions. + +**Source water** +Water before the configured treatment train, represented by minimum and maximum pathogen concentrations. + +**Exposure** +The assumed human contact with or ingestion of water. The current inputs are events per year and ingested volume per event in litres. + +**Treatment step** +One treatment technology or process step with configured pathogen-removal values. + +**Treatment train** +The collection of treatment steps used in a scenario. The current calculation combines steps by adding their LRVs by pathogen group. + +**Log-removal value (LRV)** +A logarithmic measure of pathogen reduction. The application uses minimum and maximum LRVs for bacteria, viruses, and protozoa. + +**Reference pathogen** +A pathogen included in the current model. The three reference pathogens are Rotavirus, Campylobacter jejuni, and Cryptosporidium parvum. + +## Calculation and results + +**Probabilistic risk model** +A model that produces a distribution of possible outcomes instead of one deterministic value. + +**Monte Carlo simulation** +Repeated stochastic sampling used to estimate risk distributions. The current implementation uses seed `42`, with default settings of 10,000 event samples and 1,000 simulated years. + +**Dose-response model** +The relationship between ingested pathogen dose and the probability of infection. + +**Maximum-LRV case** +The case using maximum configured LRVs. It represents the lower infection-risk estimate. + +**Minimum-LRV case** +The case using minimum configured LRVs. It represents the higher infection-risk estimate. + +**Annual probability of infection** +The estimated probability of infection over one year. + +**DALYs per person per year** +The estimated health burden, expressed as disability-adjusted life years per person per year. + +**Health-based reference level** +A comparison value shown by the application: `1 × 10^-4` infections per person per year and `1 × 10^-6` DALYs per person per year. These values are not automatically legal limits. + +**Risk distribution** +The set of sampled risk outcomes for a pathogen and LRV case. + +**Uncertainty** +Variation or lack of knowledge represented in an assessment. The current application represents only part of it through input ranges, stochastic sampling, and the two LRV cases. + +## Data and users + +**Bundled default data** +Data shipped with the application for pathogens, parameters, source waters, treatments, exposures, and references. + +**Site-specific input** +A value entered to represent local or project evidence. The application does not verify that it is representative of the site. + +**Personal definition** +A reusable exposure, source-water, or treatment definition created by a registered user. + +**Provenance** +Information showing where data, parameters, and interpretations came from. The current application provides some references but does not store complete provenance for every assessment. + +**Guest assessment** +An unsaved assessment run without an account. + +**Registered user** +An authenticated user who can save assessments and create personal definitions. There are no separate professional roles in the application. + +**Saved assessment** +An assessment stored for a registered user so it can be listed, reopened, edited, deleted, or exported. + +**Assessment configurator** +The form-based interface for entering or selecting assessment inputs. + +**Export package** +A ZIP containing input CSV files, result CSV data, an HTML report, and PNG plots. + +## Boundaries and next phase + +**Steady-state scenario** +A scenario in which source-water, exposure, and treatment assumptions are treated as stable. This is the current scope; changing operating conditions and failures are not modelled. + +**Treatment-process design** +Engineering design of a treatment process. This is outside the scope of the application. + +**Regulatory compliance** +Conformance with applicable local law. The application’s reference levels do not automatically establish compliance. + +**Next phase** +Capabilities identified for future development, not current functionality. + +**Treatment failure / downtime** +A treatment step operating below its intended performance or being unavailable. Failure frequency, failure duration, and downtime are not currently modelled. + +**Combined failure** +Simultaneous failures of multiple treatment steps. Not currently modelled. + +**Assessment comparison** +A future workflow for comparing multiple saved assessments. Not currently implemented. + +**Reproducible assessment snapshot** +A saved assessment containing the exact model version, data release, configuration, and simulation settings needed to reproduce its result. The current application does not store this complete snapshot. + +**Historical assessment** +A saved assessment created under an earlier model or data version. How such assessments should be preserved, migrated, or rerun remains open. + +**Scientific calculation engine** +The logic that converts assessment inputs into risk results. A stable internal API or library boundary is a future design question. + +## Preferred language + +- Use **assessment** for a configured calculation and its saved record. +- Use **scenario** for one combination of assumptions. +- Use **maximum-LRV case** and **minimum-LRV case**, rather than “best case” and “worst case.” +- Use **health-based reference level**, rather than “legal limit.” +- Use **personal definition** for reusable user-created data. +- Use **next phase** for capabilities that are not implemented yet. diff --git a/docs/vision.md b/docs/vision.md new file mode 100644 index 0000000..b8b4aef --- /dev/null +++ b/docs/vision.md @@ -0,0 +1,246 @@ +# QMRA Product Vision + +## Purpose + +QMRA is a web application for estimating microbial health risks in drinking-water and water-reuse scenarios. + +The application guides users through a quantitative microbial risk assessment without requiring them to build the calculation model from scratch. It combines: + +- pathogen concentrations in a selected or user-defined source water; +- human exposure assumptions; and +- treatment steps with minimum and maximum log-removal values (LRVs) for bacteria, viruses, and protozoa. + +The application runs a predefined probabilistic calculation and reports annual probability of infection and disability-adjusted life years (DALYs). It also compares calculated results with two health-based reference levels used by the application: + +- `1 × 10^-4` infections per person per year; and +- `1 × 10^-6` DALYs per person per year. + +QMRA is intended for risk assessment, steady-state scenario exploration, planning, education, and communication. It is not a treatment-process design tool and its results are not, by themselves, proof of regulatory compliance or a sufficient basis for public-health decisions. + +## The problem QMRA addresses + +Quantitative microbial risk assessment is scientifically established, but applying it in practice requires users to assemble microbiological data, exposure assumptions, treatment-removal assumptions, dose-response parameters, health parameters, and uncertainty assumptions. + +QMRA brings these elements together in a guided web workflow. It provides bundled scientific defaults for early-stage assessment and allows users to replace relevant values with project-specific inputs. This reduces the setup effort while keeping the main inputs, assumptions, results, and reference levels visible for review and discussion. + +The application is most useful when users need to: + +- perform a quick initial assessment using available defaults; +- explore how source-water, exposure, or treatment assumptions affect risk; +- assess a project using locally available values for concentrations, exposure, or treatment removal; +- communicate risk results using standard health metrics and reference levels; or +- save and export a reviewable record of an assessment. + +## Value proposition + +QMRA turns selected or user-entered pathogen, exposure, and treatment-removal data into probabilistic estimates of microbial health risk. + +It provides: + +- a guided entry point for users who are not building a QMRA model themselves; +- reusable default data for screening and scenario exploration; +- control over the principal exposure, source-water, and treatment inputs; +- distributions and summary statistics rather than one deterministic result; +- outputs in annual infection probability and DALYs per person per year; +- comparison with the application’s health-based reference levels; and +- saved assessments and exports for registered users. + +The value of the result depends on the quality and local relevance of the inputs. The current model has a limited pathogen set, simplified treatment representation, and incomplete provenance metadata. It should therefore be used as a decision-support and communication tool, not as a complete representation of every operational or scientific uncertainty. + +## Users + +QMRA is intended for a broad set of users involved in water-risk assessment and communication: + +- **Water-process and environmental engineers** can explore source-treatment-exposure combinations and test alternative treatment assumptions. +- **Utility operators and water-safety professionals** can assess steady-state operating scenarios using local or project-specific values. +- **Public-health specialists and regulatory stakeholders** can review assumptions, inputs, outputs, and health-based reference comparisons. +- **QMRA researchers and domain experts** can inspect the configured inputs, distributions, references, and exported results. +- **Students and non-expert users** can use the guided workflow, bundled defaults, validation, and explanatory content to learn about or explore QMRA. + +These audiences do not have separate application roles or tailored calculation models. Registered users share the same access pattern and assessment functionality. + +## Main workflows and functionalities + +### Quick screening assessment + +A user can run an assessment without an account using bundled exposure, source-water, and treatment definitions. The user selects or accepts the available defaults, submits the assessment, and reviews the calculated risk results. + +### Site-specific assessment + +A user can enter or modify: + +- exposure events per year; +- ingested water volume per event in litres; +- minimum and maximum concentrations for each reference pathogen; and +- minimum and maximum treatment LRVs for bacteria, viruses, and protozoa. + +The user can then run the calculation and review the results. The application does not verify whether user-entered values are representative of a particular site. + +### Treatment-train scenario analysis + +A user can add multiple treatment steps to a scenario. Each step contributes minimum and maximum LRVs for bacteria, viruses, and protozoa. The current calculation combines treatment steps by summing LRVs within each pathogen group. + +Users can explore alternative scenarios by changing the configuration and rerunning the assessment. There is no dedicated comparison view for multiple saved assessments. + +### Risk interpretation + +The result view provides: + +- annual probability of infection; +- DALYs per person per year; +- distributions for the minimum-LRV and maximum-LRV cases; +- minimum, maximum, first quartile, median, and third quartile summary statistics; +- grouped box plots; +- comparison with the application’s two health-based reference levels; and +- a risk category based on whether the calculated mean exceeds the displayed reference level under the relevant LRV case. + +The interface warns when a maximum LRV is above six. This warning does not prevent the calculation from running. + +### Saving, managing, and exporting assessments + +A guest user can run an assessment without saving it. A registered user can: + +- save an assessment with a name and description; +- list saved assessments; +- reopen and edit an assessment; +- delete an assessment; +- create reusable personal exposure, source-water, and treatment definitions; and +- export a saved assessment. + +The export is a ZIP package containing input CSV files, result CSV data, an HTML report, and PNG plots. + +### Next phase + +The following capabilities are not part of the current implementation and should be treated as next-phase work rather than existing functionality: + +- **Treatment failure and downtime assessment:** configure failure frequency and average failure duration for treatment steps and quantify their effect on annual risk. +- **Combined treatment failures:** represent simultaneous failures across treatment steps, including daily failure/no-failure states and corresponding treatment performance. +- **Saved-assessment comparison:** compare multiple saved assessments, including differences in source water, exposure assumptions, treatment trains, LRV cases, and risk results. +- **Reproducible assessment snapshots:** attach explicit model-version, data-library, reference, and simulation-settings metadata to saved and exported assessments. +- **Historical assessment handling:** define whether assessments remain tied to their original model and data, are migrated, or can be rerun under a selected version. +- **Extended interfaces:** assess the need for a stable calculation-engine boundary, public API, batch execution, notebook use, command-line use, or integration with other digital-water platforms. + +## Current calculation model + +The current engine calculates two treatment-performance cases: + +- the **maximum-LRV case**, representing the lower infection-risk estimate; and +- the **minimum-LRV case**, representing the higher infection-risk estimate. + +For each treatment step, LRVs are summed by pathogen group across the treatment train. Treatment performance is constant within each case. The engine does not simulate changing operating conditions, treatment outages, daily failure states, simultaneous failures, or process-order effects beyond the LRV sum. + +The calculation uses stochastic sampling of source concentrations and exposure outcomes. It uses a fixed NumPy random-generator seed of `42` and default simulation sizes of 10,000 event samples and 1,000 simulated years. + +The current user-facing model supports three reference pathogens: + +- Rotavirus; +- Campylobacter jejuni; and +- Cryptosporidium parvum. + +Pathogen-specific dose-response and health parameters are supplied by the bundled default data. + +## Data and configuration + +The application provides bundled default data for: + +- reference pathogens; +- dose-response and health parameters; +- source-water definitions; +- treatment definitions; +- exposure definitions; and +- scientific or guideline references. + +The inspected tests expect eight source-water definitions, 22 treatment definitions, and eight exposure definitions. + +Registered users can create personal exposure, source-water, and treatment definitions. Users can also override assessment values in the configurator. Personal values are not accompanied by the same reference metadata as bundled defaults. + +The assessment and export contain selected inputs and calculated results. They do not currently include an explicit model-version identifier, data-library release identifier, or complete simulation-settings snapshot. + +## Product principles + +QMRA should be: + +- **Scientifically defensible:** calculation logic, parameters, thresholds, defaults, and references should be reviewable and testable. +- **Accessible:** the first valid assessment should be possible with a small number of clear steps and meaningful defaults. +- **Transparent:** users should be able to distinguish bundled defaults from user-entered values and understand the main assumptions behind a result. +- **Reproducible:** saved and exported assessments should eventually identify the exact model, data, configuration, and simulation settings used to produce the result. +- **Interoperable:** exports should support both human review and further analysis in external tools. +- **Safe to evolve:** changes to the scientific model, data, and user-facing interpretation should be versioned so historical results are not silently reinterpreted. +- **Responsible in its claims:** the application should clearly distinguish risk assessment from treatment design, legal compliance, and operational reliability analysis. + +## Quality goals + +The current repository provides calculation and regression tests, form validation, bundled scientific data, result plots, and export generation. These provide a foundation for quality, but they do not establish all desired quality properties. + +Important quality goals are: + +- scientific correctness supported by version-controlled benchmark and regression scenarios; +- valid and clearly labelled inputs with explicit units and min/max checks; +- visible references and understandable assumptions; +- reproducible results when the same complete assessment snapshot is used; +- stable and reviewable export content; +- appropriate privacy and access control for registered-user data; and +- acceptable response time and reliability under expected use. + +The inspected test coverage is uneven. Calculation and form tests exist, but the plot test is empty and the API test file contains no substantive test implementation. + +## Constraints and assumptions + +- The current user-facing model supports only Rotavirus, Campylobacter jejuni, and Cryptosporidium parvum. +- Risk estimates depend strongly on the quality and local relevance of pathogen concentrations, exposure assumptions, and treatment LRVs. +- Bundled defaults support screening and exploration but may not represent local conditions. +- Treatment LRVs are treated as constant within each LRV case. +- Treatment steps are combined by adding their LRVs by pathogen group. +- Process order, changing influent concentrations, operating conditions, treatment interactions beyond the LRV sum, and treatment failures are not modelled. +- Minimum and maximum input values and stochastic sampling represent only part of the uncertainty in a real water system. +- The application assesses microbial risk; it does not assess chemical or physical hazards. +- The application is not a treatment-process design tool. +- Application reference levels and international guidance do not automatically establish compliance with local law. +- Results should not be used as the sole basis for treatment design, regulatory compliance, or public-health decisions. +- Guest users can run unsaved assessments; registered users can persist and export assessments. + +## Known limitations and uncertainties + +- The model is limited to three reference pathogens. +- The calculation uses simplified treatment assumptions and does not represent operational failures, downtime, or combined failures. +- Uncertainty is represented through input ranges, stochastic sampling, and minimum-/maximum-LRV cases, not through a complete joint uncertainty model. +- There is no dedicated comparison workflow for saved assessments. +- Export includes CSV, HTML, and PNG files in a ZIP; no Excel spreadsheet export was found in the inspected code. +- Saved assessments lack explicit model-version, data-library release, and complete simulation-settings metadata. +- Bundled references are available, but provenance is not captured completely for every saved or user-defined value. +- Repository inspection cannot verify production availability, response-time performance, TLS deployment, backups, retention and deletion behaviour, incident response, or the security posture of a deployed instance. + +## Access and free use + +The application supports guest use for unsaved assessments and registered-user accounts for persistence and export. The documentation describes the tool as free to use, and no billing workflow was found in the inspected repository. The operating policy and availability of any deployed service require separate verification. + +## Evidence and scope of verification + +This document describes the inspected repository state, including application code, templates, tests, documentation, and bundled default data. It does not make claims about deployment configuration or operational service behaviour unless those claims are separately verified. + +Relevant evidence: + +- [Repository README](https://github.com/KWB-R/qmra-webapp/blob/main/README.md) +- [Current QMRA documentation](https://github.com/KWB-R/qmra-webapp/blob/main/docs/source/index.rst) +- [Risk calculation](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/risk.py) +- [Assessment views and access paths](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/views.py) +- [Assessment forms and validation](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/forms.py) +- [Assessment and result models](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/models.py) +- [Default data models](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/qmra_models.py) +- [Export implementation](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/exports.py) +- [Result plots](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/plots.py) +- [FAQ and limitations](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/templates/faqs.html) +- [Calculation and regression tests](https://github.com/KWB-R/qmra-webapp/tree/main/qmra/risk_assessment/tests) + +## Open questions + +The following questions remain open for future product and scientific development: + +- Should model and data-library versions receive explicit release identifiers and change logs, and should these identifiers be attached to every saved and exported assessment? +- What information must be stored so that a saved assessment can be reproduced from the same model, data, configuration, and simulation settings? +- Should the scientific calculation engine be separated behind a stable internal API or library boundary? +- Is there a future need for public API access, batch execution, notebook use, command-line use, or integration with other digital-water platforms? +- What project data may be confidential or commercially sensitive even when it is not personal data, and what additional controls are required for saved assessments? +- How should historical assessments behave when the scientific model, data library, or assessment schema changes: remain frozen to the original version, be migrated, or be rerunnable under a user-selected version? +- What comparison capabilities are needed for saved assessments and alternative scenarios? +- Which deployment controls, operational metrics, performance targets, backup policies, retention rules, and incident-response processes are required for production use? From 317729f371e47b8482a100ec703c7a55da308ec2 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Tue, 22 Sep 2026 16:00:18 +0000 Subject: [PATCH 03/30] docs: add CONTEXT.md glossary and domain-model open questions Derive a CONTEXT.md from docs/glossary.md with one canonical term per concept and an avoid-list, add the terms the code relies on but the glossary lacks (inflow, pathogen group, reference, reference-level exceedance), and record the glossary/code conflicts and open questions to resolve in a later session. Co-Authored-By: Claude Fable 5.1 --- CONTEXT.md | 151 ++++++++++++++++++++++++++++ docs/domain-model-open-questions.md | 62 ++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 CONTEXT.md create mode 100644 docs/domain-model-open-questions.md diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..24524f0 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,151 @@ +# QMRA Web Application + +A web tool for quantitative microbial risk assessment of water: a user describes a source water, a treatment train and an exposure, and the tool estimates the yearly infection risk and health burden for three reference pathogens. Bounded to steady-state scenarios; treatment failure, downtime and process design are out of scope. + +## Language + +### Assessment + +**Assessment**: +One configured QMRA calculation together with its inputs and results. It holds exactly one scenario. +_Avoid_: risk assessment (in prose), calculation, run, project + +**Scenario**: +One combination of source-water, exposure and treatment-train assumptions. Today an assessment contains exactly one scenario; the term exists so that multi-scenario comparison can be discussed later. +_Avoid_: case (reserved for LRV cases), configuration + +**Saved assessment**: +An assessment stored under a registered user's account so it can be listed, reopened, edited, deleted or exported. + +**Guest assessment**: +An assessment run without an account. Its results are shown once and nothing is stored. +_Avoid_: free trial, anonymous assessment + +**Assessment configurator**: +The form-based interface for entering or selecting assessment inputs. +_Avoid_: wizard, editor + +**Export package**: +A ZIP containing the assessment's input tables, result table, HTML report and plots. +_Avoid_: download, report (the report is one file inside the package) + +### Inputs + +**Source water**: +A named type of water before treatment, such as raw sewage or protected surface water. A source water is described by one inflow per reference pathogen. +_Avoid_: source, water type, feed water + +**Inflow**: +The minimum and maximum concentration of one reference pathogen in a source water, in organisms per litre. An assessment has one inflow per selected pathogen. +_Avoid_: concentration range, pathogen input, source concentration + +**Exposure**: +The assumed human contact with the treated water, given as events per year and ingested volume per event in litres. +_Avoid_: exposure scenario, ingestion, use + +**Treatment step**: +One treatment technology or process with a minimum and maximum log-removal value per pathogen group. +_Avoid_: treatment (alone, when a step is meant), barrier, process, technology + +**Treatment train**: +The ordered sequence of treatment steps in a scenario. Its removal is the sum of the steps' LRVs per pathogen group. +_Avoid_: treatment chain, process chain, treatment list + +**Log-removal value (LRV)**: +A logarithmic measure of pathogen reduction by a treatment step. Each step carries a minimum and a maximum LRV for bacteria, viruses and protozoa. +_Avoid_: log reduction, removal efficiency, log credit + +**Reference pathogen**: +One of the three pathogens the model calculates risk for: Rotavirus, Campylobacter jejuni and Cryptosporidium parvum. Each stands for its pathogen group. +_Avoid_: organism, indicator, microbe + +**Pathogen group**: +One of bacteria, viruses or protozoa. LRVs are defined per group; each reference pathogen belongs to exactly one group. +_Avoid_: pathogen class, pathogen type + +### Data origin + +**Bundled default data**: +Source waters, inflows, treatment steps, exposures, pathogens and references shipped with the application. +_Avoid_: defaults, static data, system data, library + +**Personal definition**: +A reusable source water, exposure or treatment step created by a registered user and offered alongside the bundled defaults. +_Avoid_: custom entry, user data, own data + +**Site-specific input**: +A value typed in for one assessment to represent local evidence. The application does not check that it is representative. +_Avoid_: custom value, manual value, override + +**Reference**: +A literature citation attached to a bundled inflow, exposure or LRV to show where the value came from. +_Avoid_: source (collides with source water), citation, literature + +**Provenance**: +The full record of where every input, parameter and interpretation in an assessment came from. Only partly captured today through references. + +### Calculation + +**Dose-response model**: +The relationship between the ingested dose of a reference pathogen and the probability of infection per event. Each reference pathogen uses one fitted model, exponential or beta-Poisson. +_Avoid_: infection model, dose model + +**Monte Carlo simulation**: +Repeated random sampling of inflow concentrations and exposure events to estimate a distribution of yearly risk instead of one number. +_Avoid_: stochastic run, sampling + +**Maximum-LRV case**: +The calculation using every step's maximum LRV. It gives the lower infection-risk estimate. +_Avoid_: best case, minimum risk scenario, optimistic case + +**Minimum-LRV case**: +The calculation using every step's minimum LRV. It gives the higher infection-risk estimate. +_Avoid_: worst case, maximum risk scenario, conservative case + +**Annual probability of infection**: +The estimated probability that a person is infected at least once in one year of exposure. +_Avoid_: infection risk (alone), yearly risk + +**DALYs per person per year**: +The estimated health burden in disability-adjusted life years per person per year, derived from the annual probability of infection through the pathogen's illness-to-infection ratio and burden per case. +_Avoid_: DALY, burden, health impact + +**Risk distribution**: +The set of sampled yearly outcomes for one reference pathogen and one LRV case, summarised by minimum, quartiles, median and maximum. +_Avoid_: result set, spread + +**Health-based reference level**: +A comparison value shown next to results: one infection per ten thousand persons per year and one millionth DALY per person per year. It is not a legal limit. +_Avoid_: legal limit, tolerable risk level, guideline value, threshold + +**Reference-level exceedance**: +The verdict for one reference pathogen: whether the mean risk exceeds the health-based reference level in both LRV cases, only in the minimum-LRV case, or in neither. +_Avoid_: risk category, risk flag, pass/fail + +**Uncertainty**: +Variation or lack of knowledge in an assessment. Represented today only through inflow ranges, random sampling and the two LRV cases. + +### Boundaries + +**Steady-state scenario**: +A scenario whose source water, exposure and treatment performance are assumed constant over the year. This is the only kind the application models. + +**Treatment failure**: +A treatment step performing below its configured LRVs or being unavailable for a period. Not modelled; a next-phase concept. +_Avoid_: outage, malfunction, downtime (alone) + +**Combined failure**: +Two or more treatment steps failing at the same time. Not modelled. + +**Next phase**: +Capabilities identified for future development and not present in the application today. +_Avoid_: roadmap, backlog, planned + +**Reproducible assessment snapshot**: +A saved assessment that records the model version, data release, configuration and simulation settings needed to reproduce its result exactly. Not stored today. + +**Historical assessment**: +A saved assessment created under an earlier model or data version. Its treatment is an open question. + +**Assessment comparison**: +A workflow for viewing several saved assessments side by side. Next phase. diff --git a/docs/domain-model-open-questions.md b/docs/domain-model-open-questions.md new file mode 100644 index 0000000..84c3907 --- /dev/null +++ b/docs/domain-model-open-questions.md @@ -0,0 +1,62 @@ +# Domain model: open questions + +Status: open. Raised on 2026-09-22 while deriving `CONTEXT.md` from `docs/glossary.md` +and cross-checking both against the code. Resolve each item, update `CONTEXT.md`, then +remove it from this list. + +## Conflicts between glossary and code + +1. **"Inflow" has no glossary entry, but code and exports use it.** + The glossary describes source water as "represented by minimum and maximum pathogen + concentrations". In the code a source water is only a name and description; the + per-pathogen concentration range is a separate object called an inflow, and it is + exported to users as `inflows.csv`. `CONTEXT.md` now defines *Inflow* as the canonical + term and narrows *Source water* to the named type. + **Question:** keep "inflow", or choose another word for the per-pathogen range? + +2. **"Scenario" is used for three different things.** + - Glossary: one combination of source-water, exposure and treatment assumptions. + - Saved-assessments page: "maximum risk scenario" / "minimum risk scenario" for the + minimum-LRV and maximum-LRV cases. + - Guided tour: "custom exposure scenario" for an exposure. + `CONTEXT.md` lists the last two under *Avoid*. + **Question:** fix the UI wording in the docs PR, on a separate branch, or record only? + +3. **"Tolerable risk level" appears on the result plots.** + The glossary prefers "health-based reference level". The plot label is the one place + users see the competing term. + **Question:** same as 2. + +4. **Assessment and scenario are one entity in code.** + A saved assessment holds exactly one source water, one exposure and one treatment + train; there is no scenario object. `CONTEXT.md` keeps *Scenario* and states that an + assessment contains exactly one, so the term is ready for future comparison work. + **Question:** agree to keep the distinction in language only? + +## Terms added to CONTEXT.md that the glossary lacks + +- **Pathogen group** (bacteria, viruses, protozoa). LRVs are defined per group and the + engine maps each reference pathogen to its group. +- **Reference**: a literature citation attached to bundled data. The code sometimes calls + this a "source", which collides with source water. +- **Reference-level exceedance**: the three-way verdict per pathogen (exceeds in both LRV + cases, only in the minimum-LRV case, or neither). The code calls it a risk category + with values max / min / none. It was unnamed anywhere. + +**Question:** confirm these three terms and their names. + +## Scenarios to stress-test the model + +1. A registered user selects the bundled "groundwater" source water and then edits the + Rotavirus concentration by hand. Is the assessment's source water still "groundwater", + or is it now a site-specific input with no name? The code keeps the name and the edited + numbers, so the name no longer describes the data. +2. The guided tour allows a negative LRV to simulate recontamination. Is a recontamination + step a treatment step, or a different concept that shares the form? +3. A treatment step with a maximum LRV above 6 triggers a warning. Is 6 a domain rule + worth naming (an LRV plausibility limit), or only a UI nicety? + +## ADR candidates + +None so far. "An assessment holds exactly one scenario" is neither hard to reverse nor +surprising, so it does not need an ADR. From b64fa2fe4bdc48859000b0929092d86237dae7b9 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Tue, 22 Sep 2026 16:05:41 +0000 Subject: [PATCH 04/30] docs: document the existing saved-assessment comparison view Address PR review: the saved-assessments page already lets a registered user select assessments and open a Plotly plot comparing annual infection probability per reference pathogen. Describe that view as current functionality in the vision, glossary, architecture baseline and CONTEXT.md, and reserve only input and DALY comparison for next phase. Co-Authored-By: Claude Fable 5.1 --- CONTEXT.md | 3 ++- docs/architecture_baseline.md | 4 ++-- docs/glossary.md | 2 +- docs/vision.md | 8 ++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 24524f0..8e06362 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -148,4 +148,5 @@ A saved assessment that records the model version, data release, configuration a A saved assessment created under an earlier model or data version. Its treatment is an open question. **Assessment comparison**: -A workflow for viewing several saved assessments side by side. Next phase. +Viewing the results of several saved assessments side by side. Today this is one plot of infection-risk results per reference pathogen; comparing inputs or DALYs is next phase. +_Avoid_: benchmark, diff diff --git a/docs/architecture_baseline.md b/docs/architecture_baseline.md index ffb5b90..f042c07 100644 --- a/docs/architecture_baseline.md +++ b/docs/architecture_baseline.md @@ -152,7 +152,7 @@ Pandas assembles the tabular exports, Plotly creates the figures, and Kaleido re - **Observability**: Prometheus middleware/endpoints and structlog-based request logging. - **Deployment**: Docker Compose and Gunicorn in a single web service. -The assessment-list template contains UI text for selecting or comparing assessments, but no separate implemented comparison calculation or view was found in the inspected code. +The assessment-list template implements a client-side comparison view: selected saved assessments' serialized results are rendered in a Plotly modal comparing annual infection probability per reference pathogen. There is no server-side comparison endpoint or model; the view reuses the stored per-assessment results. ## 8. Open questions for possible ADR treatment @@ -163,7 +163,7 @@ These are unresolved or insufficiently explicit architectural points in the repo - Should calculation continue to be called directly from Django views, or is a stable internal calculation-engine boundary intended? - How should model/data versions and simulation settings be represented for saved results and exports? - How should historical assessments behave after scientific default data or calculation logic changes? -- Is assessment comparison intended to become a real workflow, given the existing list-template UI text but no dedicated implementation? +- Is the client-side infection-risk comparison plot intended to grow into a fuller comparison workflow (inputs, DALYs, persistence), and if so, does it need a server-side boundary? - Several inspected view paths retrieve `RiskAssessment` by ID without an explicit `user=request.user` filter. Is ownership scoping guaranteed elsewhere, or does this boundary require verification? - Which production security and operational settings are required for deployment? The repository currently includes development-oriented fallbacks such as `DEBUG=True`, a fallback secret key, and a dummy email backend; TLS, backup, and retention policies are not defined here. diff --git a/docs/glossary.md b/docs/glossary.md index 42f2a3b..ea61388 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -113,7 +113,7 @@ A treatment step operating below its intended performance or being unavailable. Simultaneous failures of multiple treatment steps. Not currently modelled. **Assessment comparison** -A future workflow for comparing multiple saved assessments. Not currently implemented. +Viewing the results of several saved assessments side by side. The current application offers one comparison plot of infection-risk results per reference pathogen; comparing inputs or DALYs is next-phase work. **Reproducible assessment snapshot** A saved assessment containing the exact model version, data release, configuration, and simulation settings needed to reproduce its result. The current application does not store this complete snapshot. diff --git a/docs/vision.md b/docs/vision.md index b8b4aef..7eab3dd 100644 --- a/docs/vision.md +++ b/docs/vision.md @@ -80,7 +80,7 @@ The user can then run the calculation and review the results. The application do A user can add multiple treatment steps to a scenario. Each step contributes minimum and maximum LRVs for bacteria, viruses, and protozoa. The current calculation combines treatment steps by summing LRVs within each pathogen group. -Users can explore alternative scenarios by changing the configuration and rerunning the assessment. There is no dedicated comparison view for multiple saved assessments. +Users can explore alternative scenarios by changing the configuration and rerunning the assessment. On the saved-assessments page, a registered user can select several saved assessments and open a result-comparison view: one plot showing, per reference pathogen, the annual-infection-probability range of each selected assessment against the health-based reference level. This view compares infection-risk results only; it does not compare DALYs or show differences in inputs such as source water, exposure, or treatment train. ### Risk interpretation @@ -115,7 +115,7 @@ The following capabilities are not part of the current implementation and should - **Treatment failure and downtime assessment:** configure failure frequency and average failure duration for treatment steps and quantify their effect on annual risk. - **Combined treatment failures:** represent simultaneous failures across treatment steps, including daily failure/no-failure states and corresponding treatment performance. -- **Saved-assessment comparison:** compare multiple saved assessments, including differences in source water, exposure assumptions, treatment trains, LRV cases, and risk results. +- **Extended saved-assessment comparison:** build on the existing infection-risk comparison plot to also compare DALYs and show differences in source water, exposure assumptions, treatment trains, and LRV cases side by side. - **Reproducible assessment snapshots:** attach explicit model-version, data-library, reference, and simulation-settings metadata to saved and exported assessments. - **Historical assessment handling:** define whether assessments remain tied to their original model and data, are migrated, or can be rerun under a selected version. - **Extended interfaces:** assess the need for a stable calculation-engine boundary, public API, batch execution, notebook use, command-line use, or integration with other digital-water platforms. @@ -204,7 +204,7 @@ The inspected test coverage is uneven. Calculation and form tests exist, but the - The model is limited to three reference pathogens. - The calculation uses simplified treatment assumptions and does not represent operational failures, downtime, or combined failures. - Uncertainty is represented through input ranges, stochastic sampling, and minimum-/maximum-LRV cases, not through a complete joint uncertainty model. -- There is no dedicated comparison workflow for saved assessments. +- Saved-assessment comparison is limited to a plot of infection-risk results; inputs and DALYs are not compared. - Export includes CSV, HTML, and PNG files in a ZIP; no Excel spreadsheet export was found in the inspected code. - Saved assessments lack explicit model-version, data-library release, and complete simulation-settings metadata. - Bundled references are available, but provenance is not captured completely for every saved or user-defined value. @@ -242,5 +242,5 @@ The following questions remain open for future product and scientific developmen - Is there a future need for public API access, batch execution, notebook use, command-line use, or integration with other digital-water platforms? - What project data may be confidential or commercially sensitive even when it is not personal data, and what additional controls are required for saved assessments? - How should historical assessments behave when the scientific model, data library, or assessment schema changes: remain frozen to the original version, be migrated, or be rerunnable under a user-selected version? -- What comparison capabilities are needed for saved assessments and alternative scenarios? +- What comparison capabilities are needed for saved assessments and alternative scenarios beyond the existing infection-risk comparison plot? - Which deployment controls, operational metrics, performance targets, backup policies, retention rules, and incident-response processes are required for production use? From 88388c752fc8a45f63e31eeea860888c3c96a80d Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Tue, 22 Sep 2026 16:06:31 +0000 Subject: [PATCH 05/30] docs: remove glossary.md in favour of CONTEXT.md CONTEXT.md now carries the canonical vocabulary, so the separate glossary would drift. Point the open-questions note at CONTEXT.md. Co-Authored-By: Claude Fable 5.1 --- docs/domain-model-open-questions.md | 5 +- docs/glossary.md | 134 ---------------------------- 2 files changed, 3 insertions(+), 136 deletions(-) delete mode 100644 docs/glossary.md diff --git a/docs/domain-model-open-questions.md b/docs/domain-model-open-questions.md index 84c3907..8d7b027 100644 --- a/docs/domain-model-open-questions.md +++ b/docs/domain-model-open-questions.md @@ -1,7 +1,8 @@ # Domain model: open questions -Status: open. Raised on 2026-09-22 while deriving `CONTEXT.md` from `docs/glossary.md` -and cross-checking both against the code. Resolve each item, update `CONTEXT.md`, then +Status: open. Raised on 2026-09-22 while deriving `CONTEXT.md` from the original +`docs/glossary.md` and cross-checking both against the code. `CONTEXT.md` has since +replaced that glossary, which was removed; "the glossary" below refers to `CONTEXT.md`. Resolve each item, update `CONTEXT.md`, then remove it from this list. ## Conflicts between glossary and code diff --git a/docs/glossary.md b/docs/glossary.md deleted file mode 100644 index ea61388..0000000 --- a/docs/glossary.md +++ /dev/null @@ -1,134 +0,0 @@ -# QMRA Glossary - -This glossary defines the essential vocabulary for the current QMRA web application. It is intentionally concise and should be used as the shared language for the vision, documentation, and future work. - -## Core concepts - -**QMRA — Quantitative microbial risk assessment** -A structured assessment of health risk from exposure to microbial pathogens. In this application, it combines source-water, exposure, and treatment inputs. - -**Assessment** -One configured QMRA calculation, including its inputs and results. A saved assessment also has a name, description, and stored record. - -**Scenario** -One combination of source-water, exposure, and treatment assumptions. - -**Source water** -Water before the configured treatment train, represented by minimum and maximum pathogen concentrations. - -**Exposure** -The assumed human contact with or ingestion of water. The current inputs are events per year and ingested volume per event in litres. - -**Treatment step** -One treatment technology or process step with configured pathogen-removal values. - -**Treatment train** -The collection of treatment steps used in a scenario. The current calculation combines steps by adding their LRVs by pathogen group. - -**Log-removal value (LRV)** -A logarithmic measure of pathogen reduction. The application uses minimum and maximum LRVs for bacteria, viruses, and protozoa. - -**Reference pathogen** -A pathogen included in the current model. The three reference pathogens are Rotavirus, Campylobacter jejuni, and Cryptosporidium parvum. - -## Calculation and results - -**Probabilistic risk model** -A model that produces a distribution of possible outcomes instead of one deterministic value. - -**Monte Carlo simulation** -Repeated stochastic sampling used to estimate risk distributions. The current implementation uses seed `42`, with default settings of 10,000 event samples and 1,000 simulated years. - -**Dose-response model** -The relationship between ingested pathogen dose and the probability of infection. - -**Maximum-LRV case** -The case using maximum configured LRVs. It represents the lower infection-risk estimate. - -**Minimum-LRV case** -The case using minimum configured LRVs. It represents the higher infection-risk estimate. - -**Annual probability of infection** -The estimated probability of infection over one year. - -**DALYs per person per year** -The estimated health burden, expressed as disability-adjusted life years per person per year. - -**Health-based reference level** -A comparison value shown by the application: `1 × 10^-4` infections per person per year and `1 × 10^-6` DALYs per person per year. These values are not automatically legal limits. - -**Risk distribution** -The set of sampled risk outcomes for a pathogen and LRV case. - -**Uncertainty** -Variation or lack of knowledge represented in an assessment. The current application represents only part of it through input ranges, stochastic sampling, and the two LRV cases. - -## Data and users - -**Bundled default data** -Data shipped with the application for pathogens, parameters, source waters, treatments, exposures, and references. - -**Site-specific input** -A value entered to represent local or project evidence. The application does not verify that it is representative of the site. - -**Personal definition** -A reusable exposure, source-water, or treatment definition created by a registered user. - -**Provenance** -Information showing where data, parameters, and interpretations came from. The current application provides some references but does not store complete provenance for every assessment. - -**Guest assessment** -An unsaved assessment run without an account. - -**Registered user** -An authenticated user who can save assessments and create personal definitions. There are no separate professional roles in the application. - -**Saved assessment** -An assessment stored for a registered user so it can be listed, reopened, edited, deleted, or exported. - -**Assessment configurator** -The form-based interface for entering or selecting assessment inputs. - -**Export package** -A ZIP containing input CSV files, result CSV data, an HTML report, and PNG plots. - -## Boundaries and next phase - -**Steady-state scenario** -A scenario in which source-water, exposure, and treatment assumptions are treated as stable. This is the current scope; changing operating conditions and failures are not modelled. - -**Treatment-process design** -Engineering design of a treatment process. This is outside the scope of the application. - -**Regulatory compliance** -Conformance with applicable local law. The application’s reference levels do not automatically establish compliance. - -**Next phase** -Capabilities identified for future development, not current functionality. - -**Treatment failure / downtime** -A treatment step operating below its intended performance or being unavailable. Failure frequency, failure duration, and downtime are not currently modelled. - -**Combined failure** -Simultaneous failures of multiple treatment steps. Not currently modelled. - -**Assessment comparison** -Viewing the results of several saved assessments side by side. The current application offers one comparison plot of infection-risk results per reference pathogen; comparing inputs or DALYs is next-phase work. - -**Reproducible assessment snapshot** -A saved assessment containing the exact model version, data release, configuration, and simulation settings needed to reproduce its result. The current application does not store this complete snapshot. - -**Historical assessment** -A saved assessment created under an earlier model or data version. How such assessments should be preserved, migrated, or rerun remains open. - -**Scientific calculation engine** -The logic that converts assessment inputs into risk results. A stable internal API or library boundary is a future design question. - -## Preferred language - -- Use **assessment** for a configured calculation and its saved record. -- Use **scenario** for one combination of assumptions. -- Use **maximum-LRV case** and **minimum-LRV case**, rather than “best case” and “worst case.” -- Use **health-based reference level**, rather than “legal limit.” -- Use **personal definition** for reusable user-created data. -- Use **next phase** for capabilities that are not implemented yet. From 1088f68546038ea76b8bd591c3b4eaf54939e0ed Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Tue, 22 Sep 2026 16:37:24 +0000 Subject: [PATCH 06/30] docs: add failure-events approach as Markdown Converted from QMRA_Failure_Approach.docx (Hambsch et al. 2019 based method for integrating treatment failure events into the annual infection-risk calculation). Equations are rendered as LaTeX. Co-Authored-By: Claude Fable 5.1 --- docs/QMRA_Failure_Approach.md | 88 +++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/QMRA_Failure_Approach.md diff --git a/docs/QMRA_Failure_Approach.md b/docs/QMRA_Failure_Approach.md new file mode 100644 index 0000000..b16e0e1 --- /dev/null +++ b/docs/QMRA_Failure_Approach.md @@ -0,0 +1,88 @@ +# Integration of Failure Events into the QMRA Framework + +## Current Calculation of the infection risk + +All equations in this chapter are taken from Hambsch et al., 2019, pp. 61. + +The annual risk of infection is described as the product of the daily risks of infection + +| Eq. 1 | ${P}_{inf-year}=1-\prod_{i=1}^{365} \left( 1-{p}_{inf-day,i} \right)$ | +|---|---| +| with | ${P}_{inf-year}$: Annual risk of infection
${p}_{inf-day,i}$: Daily risk of infection on day i | + +The daily risk of infection, again, depends on + +1. the initial concentration before treatment +2. the removal efficiency throughout the entire treatment chain, expressed as log removal (LRV) +3. the specific probability of infection per pathogen: r +4. the volume ingested: V + +| Eq. 2 | ${p}_{inf-day}=1-{e}^{-\frac{r{c}_{in}V}{{10}^{LRV}}}$ | +|---|---| +| with | ${p}_{inf-day}$: Daily risk of infection
$r$: Specific probability of infection [per pathogen]
${c}_{in}$: Input concentration [Pathogen per Liter]
$V$: Ingested volumen [Liter]
$LRV$: Log-removal throughout the entire treatment chain | + +Accordingly, the risk of infection is 0 if either the specific probability of infection per pathogen, the concentration of the pathogen in the influent, or the volume ingested is equal to 0. + +Furthermore, the risk approaches 0 as the log-removal in the treatment chain increases. + +In the QMRA tool, the inlet concentration for each day is drawn from a distribution as part of a Monte Carlo simulation. The calculation is performed in two scenarios: one for the best-case scenario (maximum log removal per process: LRV_max) and one for the worst-case scenario (minimum log removal per process: LRV_min). + +The distribution of the infection risk is therefore based on the distribution of the input concentration and is calculated for each scenario using separate LRVs. + +## Calculation of LRVs with failure events + +The distinction between best-case and worst-case scenarios remains the same in the event of a failure. + +The specific probability of infection, the initial concentration, and the volume ingested do not change as a result of the failure. The LRV, however, is directly affected by the failure of a treatment step. Depending on the scenario, a distinction should also be made between whether the treated water was consumed exclusively during the failure event (worst-case) or whether it was a mixture of water from normal operation and the failure event (best-case)—as is likely often the case when water is stored somewhere. + +If a treatment step in the treatment chain fails, the total LRV is reduced by the treatment-specific LRV. This is calculated for both scenarios. + +| Eq. 3 / Eq. 4 | $LRV_{failure,min}=LRV_{min}-LRV_{treatment,min}$
$LRV_{failure,max}=LRV_{max}-LRV_{treatment,max}$ | +|---|---| +| with | $LRV_{failure,min}\ \text{and}\ LRV_{failure,max}$: Minimum and maximum log-removal of the entire treatment chain during the failure event
$LRV_{treatment,min}\ \text{and}\ LRV_{treatment,max}$: Minimum and maximum log-removal of the failing treatment step | + +In the worst-case scenario, the minimum LRV is used directly to calculate the daily risk of infection in the event of an incident. In the best-case scenario, the mixed concentration is calculated using the following equation: + +| Eq. 5 | $LRV_{failure,mix}=-\log \left( \frac{{x}_{fail}}{{10}^{LRV_{failure,max}}}+\frac{1-{x}_{fail}}{{10}^{LRV_{max}}} \right)$ | +|---|---| +| with | $LRV_{failure,mix}$: Average log-removal of the entire treatment train with failure event
${x}_{fail}$: Time portion of the failure event calculated as the duration of the failure in minutes divided by 1,440 min | + +For the best-case scenario, the duration of the failure is therefore required as an additional input variable. + +The daily infection risk is calculated using Equation 2, in the same way as for the calculation without damage. When calculating the annual infection risk, a distinction must then be made between days with and without failure. + +| Eq. 6 | ${P}_{inf-year}=1-{\left( 1-P_{inf-day,normal} \right)}^{365-n}{\left( 1-{P}_{inf-day,failure} \right)}^{n}$ | +|---|---| +| with | ${P}_{inf-year}$: Annual risk of infection
$P_{inf-day,normal}$: Daily risk of infection without failure
${P}_{inf-day,failure}$: Daily risk of infection with failure
$n$: Frequency of failure events in days per year | + +The day of the year on which the failure occurs or time of the failure within a given day is not relevant for the calculation. + +**Required information in the QMRA tool for integrating failure events:** + +- **Average duration of the failure in minutes (integers between 1 and 1440) Default 30 min** +- **Frequency of failure events in days per year (float between 0 and 365) Default is 0** + +## Next Step: Combination of damage across multiple technologies + +Information on the frequency of damage is available for each treatment step. From this information, a sequence of length 365 consisting of zeros and ones can be randomly selected, where zeros represent days without a damage event and ones represent days with a damage event. The draw is performed using a binomial distribution, in which the probability corresponds to the number of days per year (e.g., 1.37% for 5 days per year means that for each day, a 1 is drawn with a probability of 1.37%). This results in years in which there may be more or fewer damage events than assumed. The random draws become part of the Monte Carlo simulation and can thus be accounted for along with the varying input concentrations. + +This process is carried out for each treatment step, so it is possible that several treatment steps could fail at the same time, although this is rather unlikely given the expected low frequency of such occurrences. + +The following table shows an example of one week in a year. + +| | Failure (1: yes, 0: no) | | | LRV | +|---|---|---|---|---| +| Day | Tech1 | Tech2 | Tech3 | | +| 1 | 0 | 1 | 1 | Failure, Tech2-Tech3 | +| 2 | 0 | 0 | 0 | Normal | +| 3 | 0 | 0 | 0 | Normal | +| 4 | 1 | 0 | 0 | Failure, Tech1 | +| 5 | 0 | 0 | 0 | Normal | +| 6 | 0 | 0 | 0 | Normal | +| 7 | 0 | 0 | 1 | Failure, Tech3 | + +For each combination of damages, an LRV must then be calculated for the best-case and worst-case scenarios, and Equation 6 must be extended accordingly. + +## Literatur + +Hambsch, Beate, Lipp, Pia, Ho, Johannes, 2019. Modelhafte Prüfung und Konzeptentwicklung zur quantitativen Risikobewertung der mikrobiellen Rohwasserbelastung für die Trinkwasseraufbereitung (Abschlussbericht), DVGW-Förderkennzeichen W 201808. Technologiezentrum Wasser, Karlsruhe. From d3f3c8c26c76e3d6e623dd71a1b395fdbb9ad4af Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Tue, 22 Sep 2026 16:41:07 +0000 Subject: [PATCH 07/30] docs: add original QMRA_Failure_Approach.docx alongside its Markdown Co-Authored-By: Claude Fable 5.1 --- docs/QMRA_Failure_Approach.docx | Bin 0 -> 23199 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/QMRA_Failure_Approach.docx diff --git a/docs/QMRA_Failure_Approach.docx b/docs/QMRA_Failure_Approach.docx new file mode 100644 index 0000000000000000000000000000000000000000..534eb8694f8e3c6167b21d6467b224d389eac667 GIT binary patch literal 23199 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 Date: Wed, 23 Sep 2026 07:44:21 +0000 Subject: [PATCH 08/30] docs: draft first two board tickets 01: onboard Malte to the AI development framework (server account, GitHub access and group membership already in place; Claude authentication, first launch and method walkthrough remain). 02: establish the current product vision and glossary, recorded as done with its follow-ups. Co-Authored-By: Claude Fable 5.1 --- ...board-malte-to-ai-development-framework.md | 25 +++++++++++++++++++ ...2-establish-product-vision-and-glossary.md | 21 ++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 docs/tickets/01-onboard-malte-to-ai-development-framework.md create mode 100644 docs/tickets/02-establish-product-vision-and-glossary.md diff --git a/docs/tickets/01-onboard-malte-to-ai-development-framework.md b/docs/tickets/01-onboard-malte-to-ai-development-framework.md new file mode 100644 index 0000000..c67e25d --- /dev/null +++ b/docs/tickets/01-onboard-malte-to-ai-development-framework.md @@ -0,0 +1,25 @@ +# 01: Onboard Malte to the AI development framework + +**What to build:** Malte can log into the Controlled server on Hetzner, open the qmra-webapp workspace, start Claude Code with his own authenticated session, and knows where the method starts. Follow the framework's admin onboarding page (Setup stage) end to end; his per-user config already exists in the framework repo. + +**Blocked by:** None (can start immediately) + +**Status:** in-progress + +Already done on the server (checked 2026-09-23): + +- [x] Per-user YAML exists in the framework repo (`admin/users/malte.yaml`, project qmra-webapp, full method skill set) +- [x] Linux account `malte` created on the Controlled server with SSH key login +- [x] Member of the `dev-qmra-webapp` group, so the project deploy key and `controlled-dev-git` work for him +- [x] GitHub membership on KWB-R/qmra-webapp granted (admin permission) + +Remaining: + +- [ ] Confirm the qmra-webapp workspace was cloned into his home and contains the `controlled-dev-git` note in its CLAUDE.md +- [ ] Confirm he can `ssh` in from his own workstation with the server-login key and run `sudo controlled-dev-git pull` inside the workspace +- [ ] Sit with him for the first `claude` launch: choose the subscription login, send the authorization URL to Nico, paste the code Nico returns. Schedule this when Nico is available, since the shared Claude subscription approval depends on him +- [ ] He confirms "Trust this folder" for the qmra-webapp workspace himself +- [ ] Check that his skills are linked in `~/.claude/skills` and match his YAML (run through `manage-user.sh`, never `06-install-ai-tools.sh` alone) +- [ ] Record the audit event (admin, target user, project, action, timestamp, result) +- [ ] Walk him through the method overview and the Project start phase, and point him at the open Docs PR (#17) as the current Vision and Glossary +- [ ] Run `admin/scripts/05-validate-environment.sh` afterwards to confirm the account landed cleanly diff --git a/docs/tickets/02-establish-product-vision-and-glossary.md b/docs/tickets/02-establish-product-vision-and-glossary.md new file mode 100644 index 0000000..decc0da --- /dev/null +++ b/docs/tickets/02-establish-product-vision-and-glossary.md @@ -0,0 +1,21 @@ +# 02: Establish the current product vision and glossary + +**What to build:** A `docs/vision.md` that describes the current QMRA product as it is: why the tool exists, who uses it, the main user workflows and the functionality available today. It describes the existing product, not its future direction, so that agents have a reliable description to work from. Open questions, inconsistencies and incomplete behaviour are recorded in a short section at the end rather than resolved now. Alongside it, a concise shared glossary that defines the key terms so the vision and all future work use consistent language. + +**Blocked by:** None (can start immediately) + +**Status:** done + +- [x] Malte wrote the first version of the vision +- [x] Nico aligned the vision with the current legacy code in KWB-R/qmra-webapp +- [x] Open questions and inconsistencies are listed at the end of the vision instead of being resolved +- [x] Nico created the glossary +- [x] Glossary folded into `CONTEXT.md` at the repo root, the framework's place for the project vocabulary, with one canonical term per concept and an avoid-list +- [x] Glossary and vision cross-checked against the code; conflicts recorded in `docs/domain-model-open-questions.md` for a later session +- [x] Review comment on the vision addressed: the existing saved-assessment comparison plot is documented as current functionality +- [x] Everything published in PR #17 (branch `docs-vision-architecture-glossary`) + +Follow-ups, not part of this ticket: + +- Resolve the items in `docs/domain-model-open-questions.md` (inflow naming, "scenario" overloading in the UI, "tolerable risk level" plot label) +- Merge PR #17 From ccfc266d2d1a7f0c1e52a9182172efcf416f4ab1 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Wed, 23 Sep 2026 07:53:45 +0000 Subject: [PATCH 09/30] docs: rewrite the two tickets in plain language (B1, broader audience) Explain each technical term where it first appears and keep every fact, checkbox and link unchanged. Co-Authored-By: Claude Fable 5.1 --- ...board-malte-to-ai-development-framework.md | 32 ++++++++++--------- ...2-establish-product-vision-and-glossary.md | 26 ++++++++------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/docs/tickets/01-onboard-malte-to-ai-development-framework.md b/docs/tickets/01-onboard-malte-to-ai-development-framework.md index c67e25d..e37ba74 100644 --- a/docs/tickets/01-onboard-malte-to-ai-development-framework.md +++ b/docs/tickets/01-onboard-malte-to-ai-development-framework.md @@ -1,25 +1,27 @@ # 01: Onboard Malte to the AI development framework -**What to build:** Malte can log into the Controlled server on Hetzner, open the qmra-webapp workspace, start Claude Code with his own authenticated session, and knows where the method starts. Follow the framework's admin onboarding page (Setup stage) end to end; his per-user config already exists in the framework repo. +**What to build:** Malte can work on the QMRA web app the way our AI development framework describes. In practice this means three things. He can log in to our shared development server (a Linux machine hosted at Hetzner, which we call the "Controlled server"). He can open the QMRA project folder there. And he can start Claude Code, the AI coding assistant, with his own login. At the end he also knows where to read how our way of working starts. + +We follow the framework's onboarding page step by step. His personal setup file in the framework already exists. **Blocked by:** None (can start immediately) **Status:** in-progress -Already done on the server (checked 2026-09-23): +Already done on the server (checked on 23 September 2026): -- [x] Per-user YAML exists in the framework repo (`admin/users/malte.yaml`, project qmra-webapp, full method skill set) -- [x] Linux account `malte` created on the Controlled server with SSH key login -- [x] Member of the `dev-qmra-webapp` group, so the project deploy key and `controlled-dev-git` work for him -- [x] GitHub membership on KWB-R/qmra-webapp granted (admin permission) +- [x] His setup file exists in the framework. It lists the QMRA project and the full set of Claude skills (small add-ons that teach Claude our method). +- [x] His user account on the Controlled server exists. He logs in with an SSH key, a digital key pair used instead of a password. +- [x] He is in the project group for the QMRA web app. This group gives him access to the shared Git key and to the `controlled-dev-git` command used to pull and push code. +- [x] He has admin rights on the GitHub repository KWB-R/qmra-webapp. -Remaining: +Still to do: -- [ ] Confirm the qmra-webapp workspace was cloned into his home and contains the `controlled-dev-git` note in its CLAUDE.md -- [ ] Confirm he can `ssh` in from his own workstation with the server-login key and run `sudo controlled-dev-git pull` inside the workspace -- [ ] Sit with him for the first `claude` launch: choose the subscription login, send the authorization URL to Nico, paste the code Nico returns. Schedule this when Nico is available, since the shared Claude subscription approval depends on him -- [ ] He confirms "Trust this folder" for the qmra-webapp workspace himself -- [ ] Check that his skills are linked in `~/.claude/skills` and match his YAML (run through `manage-user.sh`, never `06-install-ai-tools.sh` alone) -- [ ] Record the audit event (admin, target user, project, action, timestamp, result) -- [ ] Walk him through the method overview and the Project start phase, and point him at the open Docs PR (#17) as the current Vision and Glossary -- [ ] Run `admin/scripts/05-validate-environment.sh` afterwards to confirm the account landed cleanly +- [ ] Check that the QMRA project was copied into his home folder on the server, and that its CLAUDE.md file contains the note about `controlled-dev-git`. +- [ ] Check that he can log in from his own computer with his server key, and that `sudo controlled-dev-git pull` works inside the project folder. +- [ ] Sit with him for his first start of Claude Code. He chooses the subscription login. Claude shows a web address. We send it to Nico, who approves it and sends back a short code. Malte pastes that code into Claude. Plan this for a time when Nico is available, because our shared Claude subscription needs his approval. +- [ ] Malte confirms the "Trust this folder" question himself the first time Claude opens the project. +- [ ] Check that his Claude skills are installed and match his setup file. Always install them through the `manage-user.sh` script, not through the install script on its own. +- [ ] Write the audit record: who did it, for whom, which project, what was done, when, and the result. +- [ ] Walk him through the method overview and the "Project start" phase. Point him to the open pull request #17, which holds the current Vision and Glossary. +- [ ] Run the environment check script `05-validate-environment.sh` afterwards to confirm that the account is set up correctly. diff --git a/docs/tickets/02-establish-product-vision-and-glossary.md b/docs/tickets/02-establish-product-vision-and-glossary.md index decc0da..17b9500 100644 --- a/docs/tickets/02-establish-product-vision-and-glossary.md +++ b/docs/tickets/02-establish-product-vision-and-glossary.md @@ -1,21 +1,23 @@ # 02: Establish the current product vision and glossary -**What to build:** A `docs/vision.md` that describes the current QMRA product as it is: why the tool exists, who uses it, the main user workflows and the functionality available today. It describes the existing product, not its future direction, so that agents have a reliable description to work from. Open questions, inconsistencies and incomplete behaviour are recorded in a short section at the end rather than resolved now. Alongside it, a concise shared glossary that defines the key terms so the vision and all future work use consistent language. +**What to build:** A short document, `docs/vision.md`, that describes the QMRA web app as it is today. It explains why the tool exists, who uses it, the main things users do with it, and which functions are available now. It describes the current product, not future plans. The purpose is to give the AI agents a reliable description of the existing product. We do not need to answer every open question at this stage. Open questions, contradictions and unfinished behaviour are listed in a short section at the end of the document. + +Next to the vision, a glossary: a short list of the key terms of the QMRA app with one clear meaning each, so that the vision and all future work use the same words. **Blocked by:** None (can start immediately) **Status:** done -- [x] Malte wrote the first version of the vision -- [x] Nico aligned the vision with the current legacy code in KWB-R/qmra-webapp -- [x] Open questions and inconsistencies are listed at the end of the vision instead of being resolved -- [x] Nico created the glossary -- [x] Glossary folded into `CONTEXT.md` at the repo root, the framework's place for the project vocabulary, with one canonical term per concept and an avoid-list -- [x] Glossary and vision cross-checked against the code; conflicts recorded in `docs/domain-model-open-questions.md` for a later session -- [x] Review comment on the vision addressed: the existing saved-assessment comparison plot is documented as current functionality -- [x] Everything published in PR #17 (branch `docs-vision-architecture-glossary`) +- [x] Malte wrote the first version of the vision. +- [x] Nico aligned the vision with the current code in the GitHub repository KWB-R/qmra-webapp. +- [x] Open questions and contradictions are listed at the end of the vision instead of being resolved now. +- [x] Nico created the glossary. +- [x] The glossary was moved into the file `CONTEXT.md` in the root folder of the repository. This is the place where our framework expects the project vocabulary. Each concept has one agreed term and a list of words to avoid. +- [x] The vision and the glossary were compared with the code. Where they disagree, the points are written down in `docs/domain-model-open-questions.md` to be settled in a later session. +- [x] A review comment on the vision was addressed: the app already has a plot that compares the infection risk of several saved assessments, and the vision now describes it as an existing function. +- [x] Everything is published in pull request #17 (branch `docs-vision-architecture-glossary`). -Follow-ups, not part of this ticket: +Follow-up work, not part of this ticket: -- Resolve the items in `docs/domain-model-open-questions.md` (inflow naming, "scenario" overloading in the UI, "tolerable risk level" plot label) -- Merge PR #17 +- Settle the points in `docs/domain-model-open-questions.md`. They are: the name for the pathogen concentration input ("inflow"), the word "scenario" being used for three different things in the app, and the label "tolerable risk level" on the result plots. +- Merge pull request #17. From 7a53e2ca7b44c334c832236198c7738f1571ad96 Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Wed, 23 Sep 2026 08:49:47 +0000 Subject: [PATCH 10/30] docs: resolve domain-model open questions in CONTEXT.md Add inflow/outflow concentration, best-case/worst-case, normal operation, site-specific source water, recontamination and risk measure; redefine scenario and assessment; widen treatment step. Record code renames as follow-ups and defer UI wording and the LRV-above-6 rule. Co-Authored-By: Claude Opus 5.5 (1M context) --- CONTEXT.md | 58 ++++++++++++------ docs/domain-model-open-questions.md | 95 +++++++++++++---------------- 2 files changed, 82 insertions(+), 71 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 8e06362..e7074a1 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,12 +7,12 @@ A web tool for quantitative microbial risk assessment of water: a user describes ### Assessment **Assessment**: -One configured QMRA calculation together with its inputs and results. It holds exactly one scenario. +A scenario together with the results of its risk calculation. Scenarios are compared by comparing assessments. _Avoid_: risk assessment (in prose), calculation, run, project **Scenario**: -One combination of source-water, exposure and treatment-train assumptions. Today an assessment contains exactly one scenario; the term exists so that multi-scenario comparison can be discussed later. -_Avoid_: case (reserved for LRV cases), configuration +The inputs of a risk calculation: inflow concentrations, a treatment train and an exposure. Each scenario is calculated in two cases, best-case and worst-case. +_Avoid_: case (reserved for best-case and worst-case), configuration **Saved assessment**: An assessment stored under a registered user's account so it can be listed, reopened, edited, deleted or exported. @@ -32,21 +32,33 @@ _Avoid_: download, report (the report is one file inside the package) ### Inputs **Source water**: -A named type of water before treatment, such as raw sewage or protected surface water. A source water is described by one inflow per reference pathogen. +A named type of water before treatment, such as raw sewage or protected surface water. A source water is described by one inflow concentration per reference pathogen. _Avoid_: source, water type, feed water -**Inflow**: -The minimum and maximum concentration of one reference pathogen in a source water, in organisms per litre. An assessment has one inflow per selected pathogen. -_Avoid_: concentration range, pathogen input, source concentration +**Site-specific source water**: +A bundled source water in an assessment whose inflow concentrations were entered or changed by hand, even a single value. It is named after the bundled source water, prefixed with "site-specific": selecting "groundwater" and editing the Rotavirus concentration gives "site-specific groundwater". A personal definition already carries the user's own name and keeps it when its values are edited. Inflow concentrations entered without selecting any source water form a plain "site-specific source water". +_Avoid_: custom source water, modified source water, calling it by the unprefixed name + +**Inflow concentration**: +The concentration of one reference pathogen in the source water entering the treatment train, in organisms per litre, given as a minimum and a maximum. An assessment has one inflow concentration per selected pathogen. +_Avoid_: inflow (alone), concentration (alone), concentration range, pathogen input, source concentration + +**Outflow concentration**: +The concentration of one reference pathogen leaving the treatment train: the inflow concentration reduced by the train's LRV for the pathogen's group. It is calculated, never entered, and differs between best-case and worst-case. It determines the dose ingested per exposure event. +_Avoid_: outflow (alone), treated concentration, effluent concentration, dose **Exposure**: The assumed human contact with the treated water, given as events per year and ingested volume per event in litres. _Avoid_: exposure scenario, ingestion, use **Treatment step**: -One treatment technology or process with a minimum and maximum log-removal value per pathogen group. +Any step between source water and exposure that changes the pathogen concentration, with a minimum and maximum log-removal value per pathogen group. It can be a treatment technology or process, a non-technical measure such as a hygiene practice, or recontamination. _Avoid_: treatment (alone, when a step is meant), barrier, process, technology +**Recontamination**: +A treatment step with a negative LRV, representing pathogens entering the water after treatment, for example in storage or distribution. +_Avoid_: negative treatment, contamination step + **Treatment train**: The ordered sequence of treatment steps in a scenario. Its removal is the sum of the steps' LRVs per pathogen group. _Avoid_: treatment chain, process chain, treatment list @@ -66,7 +78,7 @@ _Avoid_: pathogen class, pathogen type ### Data origin **Bundled default data**: -Source waters, inflows, treatment steps, exposures, pathogens and references shipped with the application. +Source waters, inflow concentrations, treatment steps, exposures, pathogens and references shipped with the application. _Avoid_: defaults, static data, system data, library **Personal definition**: @@ -78,7 +90,7 @@ A value typed in for one assessment to represent local evidence. The application _Avoid_: custom value, manual value, override **Reference**: -A literature citation attached to a bundled inflow, exposure or LRV to show where the value came from. +A literature citation attached to a bundled inflow concentration, exposure or LRV to show where the value came from. _Avoid_: source (collides with source water), citation, literature **Provenance**: @@ -94,13 +106,13 @@ _Avoid_: infection model, dose model Repeated random sampling of inflow concentrations and exposure events to estimate a distribution of yearly risk instead of one number. _Avoid_: stochastic run, sampling -**Maximum-LRV case**: -The calculation using every step's maximum LRV. It gives the lower infection-risk estimate. -_Avoid_: best case, minimum risk scenario, optimistic case +**Best-case**: +The case of a scenario that gives the lower infection-risk estimate. For normal operation it uses every treatment step's maximum LRV. When treatment failure is modelled, it is extended by failure-derived LRVs. +_Avoid_: maximum-LRV case, minimum risk scenario, optimistic case -**Minimum-LRV case**: -The calculation using every step's minimum LRV. It gives the higher infection-risk estimate. -_Avoid_: worst case, maximum risk scenario, conservative case +**Worst-case**: +The case of a scenario that gives the higher infection-risk estimate. For normal operation it uses every treatment step's minimum LRV. When treatment failure is modelled, it is extended by failure-derived LRVs. +_Avoid_: minimum-LRV case, maximum risk scenario, conservative case **Annual probability of infection**: The estimated probability that a person is infected at least once in one year of exposure. @@ -110,8 +122,12 @@ _Avoid_: infection risk (alone), yearly risk The estimated health burden in disability-adjusted life years per person per year, derived from the annual probability of infection through the pathogen's illness-to-infection ratio and burden per case. _Avoid_: DALY, burden, health impact +**Risk measure**: +One of the two outcomes the application reports per reference pathogen: annual probability of infection or DALYs per person per year. +_Avoid_: metric, indicator, risk type + **Risk distribution**: -The set of sampled yearly outcomes for one reference pathogen and one LRV case, summarised by minimum, quartiles, median and maximum. +The set of sampled yearly outcomes for one reference pathogen in best-case or worst-case, summarised by minimum, quartiles, median and maximum. _Avoid_: result set, spread **Health-based reference level**: @@ -119,17 +135,21 @@ A comparison value shown next to results: one infection per ten thousand persons _Avoid_: legal limit, tolerable risk level, guideline value, threshold **Reference-level exceedance**: -The verdict for one reference pathogen: whether the mean risk exceeds the health-based reference level in both LRV cases, only in the minimum-LRV case, or in neither. +The verdict for one reference pathogen and one risk measure: whether the mean result exceeds the health-based reference level in both best-case and worst-case, only in worst-case, or in neither. _Avoid_: risk category, risk flag, pass/fail **Uncertainty**: -Variation or lack of knowledge in an assessment. Represented today only through inflow ranges, random sampling and the two LRV cases. +Variation or lack of knowledge in an assessment. Represented today only through inflow concentration ranges, random sampling and the spread between best-case and worst-case. ### Boundaries **Steady-state scenario**: A scenario whose source water, exposure and treatment performance are assumed constant over the year. This is the only kind the application models. +**Normal operation**: +Every treatment step performing within its configured minimum and maximum LRVs. The only state the application models today. +_Avoid_: regular operation, steady operation + **Treatment failure**: A treatment step performing below its configured LRVs or being unavailable for a period. Not modelled; a next-phase concept. _Avoid_: outage, malfunction, downtime (alone) diff --git a/docs/domain-model-open-questions.md b/docs/domain-model-open-questions.md index 8d7b027..58b6e7e 100644 --- a/docs/domain-model-open-questions.md +++ b/docs/domain-model-open-questions.md @@ -1,61 +1,52 @@ # Domain model: open questions -Status: open. Raised on 2026-09-22 while deriving `CONTEXT.md` from the original +Status: all items resolved or deferred (2026-09-23). Raised on 2026-09-22 while deriving `CONTEXT.md` from the original `docs/glossary.md` and cross-checking both against the code. `CONTEXT.md` has since replaced that glossary, which was removed; "the glossary" below refers to `CONTEXT.md`. Resolve each item, update `CONTEXT.md`, then remove it from this list. -## Conflicts between glossary and code - -1. **"Inflow" has no glossary entry, but code and exports use it.** - The glossary describes source water as "represented by minimum and maximum pathogen - concentrations". In the code a source water is only a name and description; the - per-pathogen concentration range is a separate object called an inflow, and it is - exported to users as `inflows.csv`. `CONTEXT.md` now defines *Inflow* as the canonical - term and narrows *Source water* to the named type. - **Question:** keep "inflow", or choose another word for the per-pathogen range? - -2. **"Scenario" is used for three different things.** - - Glossary: one combination of source-water, exposure and treatment assumptions. - - Saved-assessments page: "maximum risk scenario" / "minimum risk scenario" for the - minimum-LRV and maximum-LRV cases. - - Guided tour: "custom exposure scenario" for an exposure. - `CONTEXT.md` lists the last two under *Avoid*. - **Question:** fix the UI wording in the docs PR, on a separate branch, or record only? - -3. **"Tolerable risk level" appears on the result plots.** - The glossary prefers "health-based reference level". The plot label is the one place - users see the competing term. - **Question:** same as 2. - -4. **Assessment and scenario are one entity in code.** - A saved assessment holds exactly one source water, one exposure and one treatment - train; there is no scenario object. `CONTEXT.md` keeps *Scenario* and states that an - assessment contains exactly one, so the term is ready for future comparison work. - **Question:** agree to keep the distinction in language only? - -## Terms added to CONTEXT.md that the glossary lacks - -- **Pathogen group** (bacteria, viruses, protozoa). LRVs are defined per group and the - engine maps each reference pathogen to its group. -- **Reference**: a literature citation attached to bundled data. The code sometimes calls - this a "source", which collides with source water. -- **Reference-level exceedance**: the three-way verdict per pathogen (exceeds in both LRV - cases, only in the minimum-LRV case, or neither). The code calls it a risk category - with values max / min / none. It was unnamed anywhere. - -**Question:** confirm these three terms and their names. - -## Scenarios to stress-test the model - -1. A registered user selects the bundled "groundwater" source water and then edits the - Rotavirus concentration by hand. Is the assessment's source water still "groundwater", - or is it now a site-specific input with no name? The code keeps the name and the edited - numbers, so the name no longer describes the data. -2. The guided tour allows a negative LRV to simulate recontamination. Is a recontamination - step a treatment step, or a different concept that shares the form? -3. A treatment step with a maximum LRV above 6 triggers a warning. Is 6 a domain rule - worth naming (an LRV plausibility limit), or only a UI nicety? +## Follow-ups from resolved items + +- **Inflow / outflow concentration (resolved 2026-09-23).** The glossary now uses + *Inflow concentration* and *Outflow concentration*. Decided target for the code, to be + done in a later code change (this branch stays docs-only): + - identifiers `inflow` / `outflow` become `inflow_concentration` / + `outflow_concentration`; + - the export file `inflows.csv` becomes `inflow_concentration.csv`. + + Configurator wording ("pathogen concentrations") is left as it is. + +- **Site-specific source water (resolved 2026-09-23).** When a user enters or edits any + inflow concentration of a bundled source water by hand, the assessment's source water + becomes "site-specific " (e.g. "site-specific groundwater"). Personal + definitions keep their own name when edited. The code does not do this yet: it keeps the + plain source water name next to the edited values, so saved assessments and exports + show "groundwater" for data that is not the bundled groundwater. To be changed in a + later code change. + +## Deferred: UI wording that conflicts with the glossary + +Decided 2026-09-23: leave the UI as it is for now. The glossary keeps its terms and +lists the UI words under *Avoid*. Where users see them today: + +- **"Tolerable risk level"** (glossary: *Health-based reference level*): result summary + text (`assessment-result.html`, five times, attributed to the WHO), FAQ (`faqs.html`), + result plots (`plots.py`, `views_v0.py`) and the saved-assessments plot + (`risk-assessment-list.html`). +- **"Exposure scenario"** (glossary: *Exposure*): FAQ (`faqs.html`, five times), home and + configurator guided tours, form help text (`views_v0.py`). +- **"Maximum / minimum risk scenario"** (glossary: *Worst-case* / *Best-case*): explanation text on the saved-assessments page (`risk-assessment-list.html`). + +## Deferred: LRV above 6 + +Decided 2026-09-23: leave it for now; the origin of the rule is unclear. What the code +does: each treatment step's maximum LRV per pathogen group is checked against 6 +(`models.py`, `above_max_lrv`). Above 6, the result page warns that "the WHO standards +do not allow LRVs above 6 in order to promote redundancy and robustness in treatment +trains" (`assessment-result.html`). Minimum LRVs and the train total are not checked, +and the calculation still uses the value above 6. Open: confirm the WHO source, decide +whether it is a domain rule worth naming (e.g. *single-step LRV limit*), and whether it +should warn or cap. ## ADR candidates From f923ac68058b5d9f185714c3eb04feff1ff86fbd Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Wed, 23 Sep 2026 12:10:06 +0000 Subject: [PATCH 11/30] docs: settle failure-events approach, add failure glossary and ADR-0001 Add failure terms to CONTEXT.md (treatment failure, failure event/day, failure frequency/duration, mixed-water assumption, combined failure) and update scenario, best-case/worst-case, steady-state and personal definition. Align QMRA_Failure_Approach.md with the glossary and record the agreed decisions. ADR-0001: failure days are drawn per exposure event inside the Monte Carlo simulation instead of Eq. 6 and a 365-day sequence. Co-Authored-By: Claude Opus 5.5 (1M context) --- CONTEXT.md | 38 ++++++--- docs/QMRA_Failure_Approach.md | 81 ++++++++++++++----- ...1-failure-days-drawn-per-exposure-event.md | 52 ++++++++++++ 3 files changed, 141 insertions(+), 30 deletions(-) create mode 100644 docs/adr/0001-failure-days-drawn-per-exposure-event.md diff --git a/CONTEXT.md b/CONTEXT.md index e7074a1..66caac9 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,6 +1,6 @@ # QMRA Web Application -A web tool for quantitative microbial risk assessment of water: a user describes a source water, a treatment train and an exposure, and the tool estimates the yearly infection risk and health burden for three reference pathogens. Bounded to steady-state scenarios; treatment failure, downtime and process design are out of scope. +A web tool for quantitative microbial risk assessment of water: a user describes a source water, a treatment train and an exposure, and the tool estimates the yearly infection risk and health burden for three reference pathogens. Bounded to steady-state scenarios; process design is out of scope, and treatment failure is next phase. ## Language @@ -11,7 +11,7 @@ A scenario together with the results of its risk calculation. Scenarios are comp _Avoid_: risk assessment (in prose), calculation, run, project **Scenario**: -The inputs of a risk calculation: inflow concentrations, a treatment train and an exposure. Each scenario is calculated in two cases, best-case and worst-case. +The inputs of a risk calculation: inflow concentrations, a treatment train and an exposure. The failure frequency and failure duration of each treatment step belong to the scenario, so scenarios can differ only in their failures. Each scenario is calculated in two cases, best-case and worst-case. _Avoid_: case (reserved for best-case and worst-case), configuration **Saved assessment**: @@ -82,7 +82,7 @@ Source waters, inflow concentrations, treatment steps, exposures, pathogens and _Avoid_: defaults, static data, system data, library **Personal definition**: -A reusable source water, exposure or treatment step created by a registered user and offered alongside the bundled defaults. +A reusable source water, exposure or treatment step created by a registered user and offered alongside the bundled defaults. A personal treatment step can carry its own failure frequency and failure duration. _Avoid_: custom entry, user data, own data **Site-specific input**: @@ -107,11 +107,11 @@ Repeated random sampling of inflow concentrations and exposure events to estimat _Avoid_: stochastic run, sampling **Best-case**: -The case of a scenario that gives the lower infection-risk estimate. For normal operation it uses every treatment step's maximum LRV. When treatment failure is modelled, it is extended by failure-derived LRVs. +The case of a scenario that gives the lower infection-risk estimate. For normal operation it uses every treatment step's maximum LRV. On failure days it applies the mixed-water assumption. _Avoid_: maximum-LRV case, minimum risk scenario, optimistic case **Worst-case**: -The case of a scenario that gives the higher infection-risk estimate. For normal operation it uses every treatment step's minimum LRV. When treatment failure is modelled, it is extended by failure-derived LRVs. +The case of a scenario that gives the higher infection-risk estimate. For normal operation it uses every treatment step's minimum LRV. On failure days it assumes all consumed water was treated during the failure event. _Avoid_: minimum-LRV case, maximum risk scenario, conservative case **Annual probability of infection**: @@ -144,18 +144,38 @@ Variation or lack of knowledge in an assessment. Represented today only through ### Boundaries **Steady-state scenario**: -A scenario whose source water, exposure and treatment performance are assumed constant over the year. This is the only kind the application models. +A scenario whose inputs are assumed to be the same every year: source water, exposure, treatment performance and, once modelled, failure frequency and failure duration. Results are given per year, never per day, so a scenario with treatment failures is still steady-state. This is the only kind the application models. **Normal operation**: Every treatment step performing within its configured minimum and maximum LRVs. The only state the application models today. _Avoid_: regular operation, steady operation **Treatment failure**: -A treatment step performing below its configured LRVs or being unavailable for a period. Not modelled; a next-phase concept. -_Avoid_: outage, malfunction, downtime (alone) +A treatment step losing its entire LRV for a period, so that it contributes no removal while it lasts. Any treatment step with a positive LRV can fail, including non-technical measures; recontamination cannot. A step fails only if it has a failure frequency above zero. Partial loss of removal is not modelled and not planned. Not modelled today; a next-phase concept. +_Avoid_: damage, incident, outage, malfunction, downtime (alone) + +**Failure event**: +One occurrence of a treatment failure in one treatment step. A step has at most one failure event per day. +_Avoid_: damage event, incident + +**Failure day**: +A day on which at least one treatment step has a failure event. Exposure events on a failure day are calculated with the reduced LRV. +_Avoid_: damage day, incident day + +**Failure frequency**: +The expected number of failure days per year for one treatment step. It is zero unless the user enters it. Which days fail is drawn at random, so a simulated year can have more or fewer failure days than expected. +_Avoid_: failure rate, failure probability + +**Failure duration**: +The average length of one failure event of one treatment step, in minutes within a day. It matters only under the mixed-water assumption. +_Avoid_: downtime, outage time + +**Mixed-water assumption**: +The best-case assumption that water consumed on a failure day is a mixture of water treated during the failure event and water treated in normal operation, in proportion to the failure duration. Worst-case instead assumes the consumed water was treated entirely during the failure event, regardless of its duration. +_Avoid_: mixed concentration, dilution, storage effect **Combined failure**: -Two or more treatment steps failing at the same time. Not modelled. +Failure events in two or more treatment steps on the same day. Worst-case loses all their LRVs for the whole day. Under the mixed-water assumption the failure events are assumed to overlap as little as possible within the day: not at all if their durations fit into one day, otherwise only by the minutes that exceed a day. **Next phase**: Capabilities identified for future development and not present in the application today. diff --git a/docs/QMRA_Failure_Approach.md b/docs/QMRA_Failure_Approach.md index b16e0e1..df17a5b 100644 --- a/docs/QMRA_Failure_Approach.md +++ b/docs/QMRA_Failure_Approach.md @@ -12,59 +12,59 @@ The annual risk of infection is described as the product of the daily risks of i The daily risk of infection, again, depends on -1. the initial concentration before treatment -2. the removal efficiency throughout the entire treatment chain, expressed as log removal (LRV) +1. the inflow concentration +2. the log-removal value (LRV) of the entire treatment train 3. the specific probability of infection per pathogen: r 4. the volume ingested: V | Eq. 2 | ${p}_{inf-day}=1-{e}^{-\frac{r{c}_{in}V}{{10}^{LRV}}}$ | |---|---| -| with | ${p}_{inf-day}$: Daily risk of infection
$r$: Specific probability of infection [per pathogen]
${c}_{in}$: Input concentration [Pathogen per Liter]
$V$: Ingested volumen [Liter]
$LRV$: Log-removal throughout the entire treatment chain | +| with | ${p}_{inf-day}$: Daily risk of infection
$r$: Specific probability of infection [per pathogen]
${c}_{in}$: Inflow concentration [Pathogen per Liter]
$V$: Ingested volume [Liter]
$LRV$: Log-removal of the entire treatment train | -Accordingly, the risk of infection is 0 if either the specific probability of infection per pathogen, the concentration of the pathogen in the influent, or the volume ingested is equal to 0. +Accordingly, the risk of infection is 0 if either the specific probability of infection per pathogen, the inflow concentration, or the volume ingested is equal to 0. -Furthermore, the risk approaches 0 as the log-removal in the treatment chain increases. +Furthermore, the risk approaches 0 as the log-removal of the treatment train increases. -In the QMRA tool, the inlet concentration for each day is drawn from a distribution as part of a Monte Carlo simulation. The calculation is performed in two scenarios: one for the best-case scenario (maximum log removal per process: LRV_max) and one for the worst-case scenario (minimum log removal per process: LRV_min). +In the QMRA tool, the inflow concentration for each exposure event is drawn from a distribution as part of a Monte Carlo simulation. The calculation is performed in two cases: best-case (maximum LRV per treatment step: LRV_max) and worst-case (minimum LRV per treatment step: LRV_min). -The distribution of the infection risk is therefore based on the distribution of the input concentration and is calculated for each scenario using separate LRVs. +The distribution of the infection risk is therefore based on the distribution of the inflow concentration and is calculated for each case using separate LRVs. ## Calculation of LRVs with failure events -The distinction between best-case and worst-case scenarios remains the same in the event of a failure. +The distinction between best-case and worst-case remains the same on failure days. -The specific probability of infection, the initial concentration, and the volume ingested do not change as a result of the failure. The LRV, however, is directly affected by the failure of a treatment step. Depending on the scenario, a distinction should also be made between whether the treated water was consumed exclusively during the failure event (worst-case) or whether it was a mixture of water from normal operation and the failure event (best-case)—as is likely often the case when water is stored somewhere. +The specific probability of infection, the inflow concentration, and the volume ingested do not change as a result of the failure. The LRV, however, is directly affected by the failure of a treatment step. Depending on the case, a distinction should also be made between whether the treated water was consumed exclusively during the failure event (worst-case) or whether it was a mixture of water from normal operation and the failure event (best-case, the mixed-water assumption)—as is likely often the case when water is stored somewhere. -If a treatment step in the treatment chain fails, the total LRV is reduced by the treatment-specific LRV. This is calculated for both scenarios. +If a treatment step in the treatment train fails, the total LRV is reduced by the treatment-specific LRV. This is calculated for both cases. | Eq. 3 / Eq. 4 | $LRV_{failure,min}=LRV_{min}-LRV_{treatment,min}$
$LRV_{failure,max}=LRV_{max}-LRV_{treatment,max}$ | |---|---| -| with | $LRV_{failure,min}\ \text{and}\ LRV_{failure,max}$: Minimum and maximum log-removal of the entire treatment chain during the failure event
$LRV_{treatment,min}\ \text{and}\ LRV_{treatment,max}$: Minimum and maximum log-removal of the failing treatment step | +| with | $LRV_{failure,min}\ \text{and}\ LRV_{failure,max}$: Minimum and maximum log-removal of the entire treatment train during the failure event
$LRV_{treatment,min}\ \text{and}\ LRV_{treatment,max}$: Minimum and maximum log-removal of the failing treatment step | -In the worst-case scenario, the minimum LRV is used directly to calculate the daily risk of infection in the event of an incident. In the best-case scenario, the mixed concentration is calculated using the following equation: +In worst-case, $LRV_{failure,min}$ is used directly to calculate the daily risk of infection on a failure day. In best-case, the mixed-water assumption gives an average LRV using the following equation: | Eq. 5 | $LRV_{failure,mix}=-\log \left( \frac{{x}_{fail}}{{10}^{LRV_{failure,max}}}+\frac{1-{x}_{fail}}{{10}^{LRV_{max}}} \right)$ | |---|---| | with | $LRV_{failure,mix}$: Average log-removal of the entire treatment train with failure event
${x}_{fail}$: Time portion of the failure event calculated as the duration of the failure in minutes divided by 1,440 min | -For the best-case scenario, the duration of the failure is therefore required as an additional input variable. +For best-case, the failure duration is therefore required as an additional input variable. -The daily infection risk is calculated using Equation 2, in the same way as for the calculation without damage. When calculating the annual infection risk, a distinction must then be made between days with and without failure. +The daily infection risk is calculated using Equation 2, in the same way as for the calculation without failure. When calculating the annual infection risk, a distinction must then be made between days with and without failure. | Eq. 6 | ${P}_{inf-year}=1-{\left( 1-P_{inf-day,normal} \right)}^{365-n}{\left( 1-{P}_{inf-day,failure} \right)}^{n}$ | |---|---| -| with | ${P}_{inf-year}$: Annual risk of infection
$P_{inf-day,normal}$: Daily risk of infection without failure
${P}_{inf-day,failure}$: Daily risk of infection with failure
$n$: Frequency of failure events in days per year | +| with | ${P}_{inf-year}$: Annual risk of infection
$P_{inf-day,normal}$: Daily risk of infection without failure
${P}_{inf-day,failure}$: Daily risk of infection with failure
$n$: Failure frequency in failure days per year | The day of the year on which the failure occurs or time of the failure within a given day is not relevant for the calculation. -**Required information in the QMRA tool for integrating failure events:** +**Required information in the QMRA tool for integrating failure events, per treatment step:** -- **Average duration of the failure in minutes (integers between 1 and 1440) Default 30 min** -- **Frequency of failure events in days per year (float between 0 and 365) Default is 0** +- **Failure duration: average duration of one failure event in minutes (integers between 1 and 1440) Default 30 min** +- **Failure frequency: failure days per year (float between 0 and 365) Default is 0** -## Next Step: Combination of damage across multiple technologies +## Next Step: Combined failures across multiple treatment steps -Information on the frequency of damage is available for each treatment step. From this information, a sequence of length 365 consisting of zeros and ones can be randomly selected, where zeros represent days without a damage event and ones represent days with a damage event. The draw is performed using a binomial distribution, in which the probability corresponds to the number of days per year (e.g., 1.37% for 5 days per year means that for each day, a 1 is drawn with a probability of 1.37%). This results in years in which there may be more or fewer damage events than assumed. The random draws become part of the Monte Carlo simulation and can thus be accounted for along with the varying input concentrations. +A failure frequency is given for each treatment step. From this information, a sequence of length 365 consisting of zeros and ones can be randomly selected, where zeros represent days without a failure event and ones represent failure days. The draw is performed using a binomial distribution, in which the probability corresponds to the number of days per year (e.g., 1.37% for 5 days per year means that for each day, a 1 is drawn with a probability of 1.37%). This results in years in which there may be more or fewer failure events than assumed. The random draws become part of the Monte Carlo simulation and can thus be accounted for along with the varying inflow concentrations. This process is carried out for each treatment step, so it is possible that several treatment steps could fail at the same time, although this is rather unlikely given the expected low frequency of such occurrences. @@ -81,7 +81,46 @@ The following table shows an example of one week in a year. | 6 | 0 | 0 | 0 | Normal | | 7 | 0 | 0 | 1 | Failure, Tech3 | -For each combination of damages, an LRV must then be calculated for the best-case and worst-case scenarios, and Equation 6 must be extended accordingly. +For each combination of failing treatment steps, an LRV must then be calculated for best-case and worst-case, and Equation 6 must be extended accordingly. + +## Decisions (2026-09-23) + +Agreed in a domain-modeling session. Terms follow `CONTEXT.md`; the calculation approach +is recorded in `docs/adr/0001-failure-days-drawn-per-exposure-event.md`. + +1. **Entire loss only.** A failing treatment step loses its entire LRV, for all three + pathogen groups at once. Partial loss of removal is not modelled and not planned. +2. **Which steps can fail.** Any treatment step with a positive LRV, including + non-technical measures such as hygiene practices. Recontamination steps (negative LRV) + cannot fail. A step fails only if its failure frequency is above zero. +3. **Inputs per treatment step.** Each step has one failure frequency (failure days per + year, 0–365, default 0) and one failure duration (minutes, 1–1440, default 30). They + are the same in best-case and worst-case. They are user input only; there is no + bundled failure data. They belong to the scenario, and a personal treatment step can + store them for reuse. +4. **Worst-case ignores the failure duration.** On a failure day, worst-case assumes all + consumed water was treated during the failure event, so a 1-minute and a 24-hour + failure give the same result. Only best-case uses the duration (Eq. 5, the + mixed-water assumption). +5. **Draws per exposure event, one calculation path.** The application builds yearly risk + from exposure events, not from 365 days. Each exposure event falls on a failure day of + each treatment step independently, with probability failure frequency / 365, inside + the existing Monte Carlo simulation. The section-3 approach is implemented directly; + Eq. 6 is not implemented as a separate path, not even for a single failing step. See + ADR-0001. +6. **Combined failures in best-case.** Eq. 5 is extended so that failure events of + different steps on the same day overlap as little as possible: not at all if their + durations fit into 1,440 minutes, otherwise only by the minutes beyond a full day. + Example (best-case LRV 10 = UV 4 + UF 4 + other steps 2): UV down 60 min and UF down + 120 min give 4.2 % of the day at LRV 6, 8.3 % at LRV 6 and 87.5 % at LRV 10, a mixed + LRV of about 6.9. UV and UF each down 900 min overlap by 360 min: 25 % at LRV 2 and + 2 × 37.5 % at LRV 6, a mixed LRV of about 2.6. Worst-case loses all failing steps' + LRVs for the whole day. +7. **Results.** Only the result including failures is shown, and the reference-level + exceedance is taken on it. The effect of failures is seen by comparing scenarios with + different failure inputs in the assessment comparison view. +8. **Still steady-state.** Failure frequency and duration are the same every year and + results are yearly, so a scenario with failures remains a steady-state scenario. ## Literatur diff --git a/docs/adr/0001-failure-days-drawn-per-exposure-event.md b/docs/adr/0001-failure-days-drawn-per-exposure-event.md new file mode 100644 index 0000000..3d3bd92 --- /dev/null +++ b/docs/adr/0001-failure-days-drawn-per-exposure-event.md @@ -0,0 +1,52 @@ +# ADR-0001: Failure days are drawn per exposure event inside the Monte Carlo simulation + +- **Status:** Accepted +- **Date:** 2026-09-23 +- **Deciders:** QMRA project team (domain-modeling session on the failure approach) +- **Related:** `docs/QMRA_Failure_Approach.md` (Decisions section), `CONTEXT.md` (Treatment failure, Failure day, Failure frequency) +- **TL;DR:** When treatment failures are added, each exposure event is randomly assigned to a failure day or a normal day inside the existing simulation, instead of counting 365 days per year as the source method does. +- **Quality goals:** scientific correctness — one calculation path for single and combined failures, consistent with how the existing model builds yearly risk; understandable assumptions — failure frequency stays in the unit operators know (days per year). + +## Context + +The failure approach (`docs/QMRA_Failure_Approach.md`, after Hambsch et al., 2019) builds +yearly infection risk from 365 daily risks. With one failing treatment step it uses a +closed formula over exactly *n* failure days (Eq. 6); for several failing steps it draws a +random 365-day sequence of failure days per step. + +The application does not work in days. It builds yearly risk from exposure events: it +samples the dose of each event and combines as many events as the exposure has per year. +For drinking water (365 events per year) days and events coincide; for an exposure such as +irrigation with 20 events per year they do not, and "5 failure days per year" has no +direct meaning for the calculation. + +## Decision + +Failure frequency is entered in failure days per year per treatment step. Inside the +Monte Carlo simulation, each exposure event falls on a failure day of each step +independently, with probability failure frequency / 365. Events on a failure day use the +LRV with the failing steps removed (worst-case) or the mixed-water LRV (best-case). There +is no separate deterministic calculation path based on Eq. 6, including for the case of a +single failing step. + +## Options considered + +| Option | Pros | Cons | +|--------|------|------| +| A (chosen): per-event draw with probability n/365 | Works for every exposure; one path for single and combined failures; fits the existing event-based simulation | Departs from the cited method; the number of failure events varies between simulated years | +| B: one exposure event per day, 365-day sequence as in the source | Matches the source method literally | Forces exposures to at most 365 events per year and changes how exposures are defined | +| C: deterministic share, events × n/365 failure events per year | Simple, no extra randomness | Fractional events for small exposures; a second path next to the combined-failure draw | +| D: Eq. 6 for one failing step, random draw only for combined failures | Closest to the source for the simple case | Two calculation paths that give different results for comparable inputs | + +## Consequences + +- Results with failures will not equal a hand calculation with Eq. 6. They agree on + average, but the number of failure events varies from simulated year to year, which + widens the risk distribution. This is intended: it reflects that failures are random. +- For exposures with few events per year and low failure frequencies, many simulated years + contain no failure event at all. The yearly risk distribution will show this as a long + upper tail rather than a shifted median. +- Two exposure events on the same calendar day are not forced to share the same failure + state. This is accepted, since the model has no notion of calendar days. +- Anyone comparing the application against Hambsch et al. (2019) needs this ADR to explain + the difference. From cfcc68e463530bae16267964c1519edaa1c27411 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Wed, 23 Sep 2026 15:05:49 +0000 Subject: [PATCH 12/30] docs: update ticket 02 now that the open questions are settled Co-Authored-By: Claude Fable 5.1 --- docs/tickets/02-establish-product-vision-and-glossary.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tickets/02-establish-product-vision-and-glossary.md b/docs/tickets/02-establish-product-vision-and-glossary.md index 17b9500..8604850 100644 --- a/docs/tickets/02-establish-product-vision-and-glossary.md +++ b/docs/tickets/02-establish-product-vision-and-glossary.md @@ -13,11 +13,11 @@ Next to the vision, a glossary: a short list of the key terms of the QMRA app wi - [x] Open questions and contradictions are listed at the end of the vision instead of being resolved now. - [x] Nico created the glossary. - [x] The glossary was moved into the file `CONTEXT.md` in the root folder of the repository. This is the place where our framework expects the project vocabulary. Each concept has one agreed term and a list of words to avoid. -- [x] The vision and the glossary were compared with the code. Where they disagree, the points are written down in `docs/domain-model-open-questions.md` to be settled in a later session. +- [x] The vision and the glossary were compared with the code. Where they disagreed, the points were written down in `docs/domain-model-open-questions.md` and settled on 23 September 2026. The code changes that follow from them are listed there as later work. - [x] A review comment on the vision was addressed: the app already has a plot that compares the infection risk of several saved assessments, and the vision now describes it as an existing function. - [x] Everything is published in pull request #17 (branch `docs-vision-architecture-glossary`). Follow-up work, not part of this ticket: -- Settle the points in `docs/domain-model-open-questions.md`. They are: the name for the pathogen concentration input ("inflow"), the word "scenario" being used for three different things in the app, and the label "tolerable risk level" on the result plots. - Merge pull request #17. +- Later code changes recorded in `docs/domain-model-open-questions.md`: rename "inflow" to "inflow concentration" in the code and the export file, and mark a bundled source water as "site-specific" once a user edits its values. From 4f3f5e3f37001285832c3a96b95b66231a62cdd2 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Wed, 23 Sep 2026 15:07:25 +0000 Subject: [PATCH 13/30] docs: record ADR-0001 under ADR candidates in the open-questions note Co-Authored-By: Claude Fable 5.1 --- docs/domain-model-open-questions.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/domain-model-open-questions.md b/docs/domain-model-open-questions.md index 58b6e7e..4394fa2 100644 --- a/docs/domain-model-open-questions.md +++ b/docs/domain-model-open-questions.md @@ -50,5 +50,8 @@ should warn or cap. ## ADR candidates -None so far. "An assessment holds exactly one scenario" is neither hard to reverse nor -surprising, so it does not need an ADR. +- **Written:** `docs/adr/0001-failure-days-drawn-per-exposure-event.md` (2026-09-23), + the decision to draw failure days per exposure event inside the Monte Carlo simulation + instead of following the source method's 365-day sequence. +- **Not needed:** "An assessment holds exactly one scenario" is neither hard to reverse + nor surprising, so it does not need an ADR. From f8b8ccaa2110ec24dfd6a49893fdc67815dea3f5 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Thu, 24 Sep 2026 08:54:19 +0000 Subject: [PATCH 14/30] docs: align vision and architecture baseline with glossary terms Use best-case / worst-case and reference-level exceedance, as CONTEXT.md now defines them, instead of the LRV-case names and risk category. Co-Authored-By: Claude Fable 5.1 --- docs/architecture_baseline.md | 2 +- docs/vision.md | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/architecture_baseline.md b/docs/architecture_baseline.md index f042c07..913f8df 100644 --- a/docs/architecture_baseline.md +++ b/docs/architecture_baseline.md @@ -72,7 +72,7 @@ The user/application database models are: - **`RiskAssessment`**: UUID, owner, timestamps, name, description, source and exposure names, events per year, volume per event, and related inflows, treatments, and results. - **`Inflow`**: UUID, assessment, pathogen name, and minimum/maximum concentration. Form values are presented in `N/L`. - **`Treatment`**: UUID, assessment, name, ordered `train_index`, and minimum/maximum LRVs for bacteria, viruses, and protozoa. It also detects configured maximum LRVs above 6 for warning display. -- **`RiskAssessmentResult`**: assessment, pathogen, infection/DALY risk categories, and min/max/q1/q3/median statistics for both minimum-LRV and maximum-LRV cases. +- **`RiskAssessmentResult`**: assessment, pathogen, the reference-level exceedance for infection and for DALYs, and min/max/q1/q3/median statistics for both best-case and worst-case. Assessment children and results are deleted with their assessment. Saved results are recalculated when required by the view layer. diff --git a/docs/vision.md b/docs/vision.md index 7eab3dd..2cd903e 100644 --- a/docs/vision.md +++ b/docs/vision.md @@ -88,11 +88,11 @@ The result view provides: - annual probability of infection; - DALYs per person per year; -- distributions for the minimum-LRV and maximum-LRV cases; +- distributions for best-case and worst-case; - minimum, maximum, first quartile, median, and third quartile summary statistics; - grouped box plots; - comparison with the application’s two health-based reference levels; and -- a risk category based on whether the calculated mean exceeds the displayed reference level under the relevant LRV case. +- a reference-level exceedance: whether the calculated mean exceeds the displayed reference level in both cases, only in worst-case, or in neither. The interface warns when a maximum LRV is above six. This warning does not prevent the calculation from running. @@ -115,7 +115,7 @@ The following capabilities are not part of the current implementation and should - **Treatment failure and downtime assessment:** configure failure frequency and average failure duration for treatment steps and quantify their effect on annual risk. - **Combined treatment failures:** represent simultaneous failures across treatment steps, including daily failure/no-failure states and corresponding treatment performance. -- **Extended saved-assessment comparison:** build on the existing infection-risk comparison plot to also compare DALYs and show differences in source water, exposure assumptions, treatment trains, and LRV cases side by side. +- **Extended saved-assessment comparison:** build on the existing infection-risk comparison plot to also compare DALYs and show differences in source water, exposure assumptions, treatment trains, and best-case and worst-case results side by side. - **Reproducible assessment snapshots:** attach explicit model-version, data-library, reference, and simulation-settings metadata to saved and exported assessments. - **Historical assessment handling:** define whether assessments remain tied to their original model and data, are migrated, or can be rerun under a selected version. - **Extended interfaces:** assess the need for a stable calculation-engine boundary, public API, batch execution, notebook use, command-line use, or integration with other digital-water platforms. @@ -124,8 +124,8 @@ The following capabilities are not part of the current implementation and should The current engine calculates two treatment-performance cases: -- the **maximum-LRV case**, representing the lower infection-risk estimate; and -- the **minimum-LRV case**, representing the higher infection-risk estimate. +- **best-case**, using every treatment step's maximum LRV and representing the lower infection-risk estimate; and +- **worst-case**, using every treatment step's minimum LRV and representing the higher infection-risk estimate. For each treatment step, LRVs are summed by pathogen group across the treatment train. Treatment performance is constant within each case. The engine does not simulate changing operating conditions, treatment outages, daily failure states, simultaneous failures, or process-order effects beyond the LRV sum. @@ -189,7 +189,7 @@ The inspected test coverage is uneven. Calculation and form tests exist, but the - The current user-facing model supports only Rotavirus, Campylobacter jejuni, and Cryptosporidium parvum. - Risk estimates depend strongly on the quality and local relevance of pathogen concentrations, exposure assumptions, and treatment LRVs. - Bundled defaults support screening and exploration but may not represent local conditions. -- Treatment LRVs are treated as constant within each LRV case. +- Treatment LRVs are treated as constant within best-case and within worst-case. - Treatment steps are combined by adding their LRVs by pathogen group. - Process order, changing influent concentrations, operating conditions, treatment interactions beyond the LRV sum, and treatment failures are not modelled. - Minimum and maximum input values and stochastic sampling represent only part of the uncertainty in a real water system. @@ -203,7 +203,7 @@ The inspected test coverage is uneven. Calculation and form tests exist, but the - The model is limited to three reference pathogens. - The calculation uses simplified treatment assumptions and does not represent operational failures, downtime, or combined failures. -- Uncertainty is represented through input ranges, stochastic sampling, and minimum-/maximum-LRV cases, not through a complete joint uncertainty model. +- Uncertainty is represented through input ranges, stochastic sampling, and the spread between best-case and worst-case, not through a complete joint uncertainty model. - Saved-assessment comparison is limited to a plot of infection-risk results; inputs and DALYs are not compared. - Export includes CSV, HTML, and PNG files in a ZIP; no Excel spreadsheet export was found in the inspected code. - Saved assessments lack explicit model-version, data-library release, and complete simulation-settings metadata. From c6edeece09ed3d269ba4f3a8d36521417d499bbd Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Thu, 24 Sep 2026 08:54:30 +0000 Subject: [PATCH 15/30] docs: ADR-0001 records why worst-case ignores the failure duration Co-Authored-By: Claude Fable 5.1 --- docs/adr/0001-failure-days-drawn-per-exposure-event.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/adr/0001-failure-days-drawn-per-exposure-event.md b/docs/adr/0001-failure-days-drawn-per-exposure-event.md index 3d3bd92..0e7ef4d 100644 --- a/docs/adr/0001-failure-days-drawn-per-exposure-event.md +++ b/docs/adr/0001-failure-days-drawn-per-exposure-event.md @@ -48,5 +48,11 @@ single failing step. upper tail rather than a shifted median. - Two exposure events on the same calendar day are not forced to share the same failure state. This is accepted, since the model has no notion of calendar days. +- Worst-case ignores the failure duration: a one-minute failure and a full-day failure + give the same worst-case result for that exposure event. The reasoning is that all water + consumed in one exposure event was treated during the downtime, which holds as long as + the volume treated during the downtime is much larger than the water demand of one + event. That is expected to be true in most cases. Only best-case, through the + mixed-water assumption, uses the duration. - Anyone comparing the application against Hambsch et al. (2019) needs this ADR to explain the difference. From 3ca8b10a4ec732fa5c477eb55ee2dd88515faead Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Thu, 24 Sep 2026 08:55:58 +0000 Subject: [PATCH 16/30] docs: shorten the site-specific source water glossary entry Keep the definition to two sentences and move the naming rule to the open-questions follow-up, which already tracks the code change. Co-Authored-By: Claude Fable 5.1 --- CONTEXT.md | 4 ++-- docs/domain-model-open-questions.md | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 66caac9..8dc3b37 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -36,8 +36,8 @@ A named type of water before treatment, such as raw sewage or protected surface _Avoid_: source, water type, feed water **Site-specific source water**: -A bundled source water in an assessment whose inflow concentrations were entered or changed by hand, even a single value. It is named after the bundled source water, prefixed with "site-specific": selecting "groundwater" and editing the Rotavirus concentration gives "site-specific groundwater". A personal definition already carries the user's own name and keeps it when its values are edited. Inflow concentrations entered without selecting any source water form a plain "site-specific source water". -_Avoid_: custom source water, modified source water, calling it by the unprefixed name +A source water in an assessment whose inflow concentrations were entered or changed by hand, even a single value, or entered without selecting any source water. It is not a bundled source water any more, even if it was derived from one, and is not called by the bundled name. +_Avoid_: custom source water, modified source water, calling it by the unprefixed bundled name **Inflow concentration**: The concentration of one reference pathogen in the source water entering the treatment train, in organisms per litre, given as a minimum and a maximum. An assessment has one inflow concentration per selected pathogen. diff --git a/docs/domain-model-open-questions.md b/docs/domain-model-open-questions.md index 4394fa2..8cd2b9a 100644 --- a/docs/domain-model-open-questions.md +++ b/docs/domain-model-open-questions.md @@ -18,11 +18,14 @@ remove it from this list. - **Site-specific source water (resolved 2026-09-23).** When a user enters or edits any inflow concentration of a bundled source water by hand, the assessment's source water - becomes "site-specific " (e.g. "site-specific groundwater"). Personal - definitions keep their own name when edited. The code does not do this yet: it keeps the - plain source water name next to the edited values, so saved assessments and exports - show "groundwater" for data that is not the bundled groundwater. To be changed in a - later code change. + becomes a site-specific source water. Naming rule for the code: it is named after the + bundled source water with the prefix "site-specific" (e.g. selecting "groundwater" and + editing the Rotavirus concentration gives "site-specific groundwater"). Inflow + concentrations entered without selecting any source water are named plain + "site-specific source water". Personal definitions already carry the user's own name + and keep it when edited. The code does not do this yet: it keeps the plain source water + name next to the edited values, so saved assessments and exports show "groundwater" for + data that is not the bundled groundwater. To be changed in a later code change. ## Deferred: UI wording that conflicts with the glossary From 81197d5f4734b230ae24a4e29a38afe74e408b95 Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Thu, 24 Sep 2026 09:12:35 +0000 Subject: [PATCH 17/30] docs: record that failure comparison covers infection risk only Decision 7 and ADR-0001 now state that the assessment comparison view compares infection probability only, so the DALY effect of failures is read from each result page until the extended comparison adds DALYs. The vision's next-phase item notes that treatment failure depends on it. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/QMRA_Failure_Approach.md | 6 +++++- docs/adr/0001-failure-days-drawn-per-exposure-event.md | 5 +++++ docs/vision.md | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/QMRA_Failure_Approach.md b/docs/QMRA_Failure_Approach.md index df17a5b..c159ad2 100644 --- a/docs/QMRA_Failure_Approach.md +++ b/docs/QMRA_Failure_Approach.md @@ -118,7 +118,11 @@ is recorded in `docs/adr/0001-failure-days-drawn-per-exposure-event.md`. LRVs for the whole day. 7. **Results.** Only the result including failures is shown, and the reference-level exceedance is taken on it. The effect of failures is seen by comparing scenarios with - different failure inputs in the assessment comparison view. + different failure inputs in the assessment comparison view. That view compares annual + probability of infection only. Until the extended saved-assessment comparison (next + phase in `docs/vision.md`) adds DALYs, the DALY effect of failures is read from each + assessment's own result page. A failure can exceed the DALY reference level without + exceeding the infection one, so this matters. 8. **Still steady-state.** Failure frequency and duration are the same every year and results are yearly, so a scenario with failures remains a steady-state scenario. diff --git a/docs/adr/0001-failure-days-drawn-per-exposure-event.md b/docs/adr/0001-failure-days-drawn-per-exposure-event.md index 0e7ef4d..5d00391 100644 --- a/docs/adr/0001-failure-days-drawn-per-exposure-event.md +++ b/docs/adr/0001-failure-days-drawn-per-exposure-event.md @@ -54,5 +54,10 @@ single failing step. the volume treated during the downtime is much larger than the water demand of one event. That is expected to be true in most cases. Only best-case, through the mixed-water assumption, uses the duration. +- The effect of failures is evaluated by comparing assessments with different failure + inputs in the assessment comparison view, which today compares annual probability of + infection only. Until the extended saved-assessment comparison adds DALYs, the DALY + effect of failures has to be read from each assessment's own result page. The failure + feature therefore depends on that next-phase work for a full side-by-side evaluation. - Anyone comparing the application against Hambsch et al. (2019) needs this ADR to explain the difference. diff --git a/docs/vision.md b/docs/vision.md index 2cd903e..de52edc 100644 --- a/docs/vision.md +++ b/docs/vision.md @@ -115,7 +115,7 @@ The following capabilities are not part of the current implementation and should - **Treatment failure and downtime assessment:** configure failure frequency and average failure duration for treatment steps and quantify their effect on annual risk. - **Combined treatment failures:** represent simultaneous failures across treatment steps, including daily failure/no-failure states and corresponding treatment performance. -- **Extended saved-assessment comparison:** build on the existing infection-risk comparison plot to also compare DALYs and show differences in source water, exposure assumptions, treatment trains, and best-case and worst-case results side by side. +- **Extended saved-assessment comparison:** build on the existing infection-risk comparison plot to also compare DALYs and show differences in source water, exposure assumptions, treatment trains, and best-case and worst-case results side by side. Treatment failure depends on it: until DALYs can be compared, the DALY effect of different failure inputs cannot be evaluated side by side. - **Reproducible assessment snapshots:** attach explicit model-version, data-library, reference, and simulation-settings metadata to saved and exported assessments. - **Historical assessment handling:** define whether assessments remain tied to their original model and data, are migrated, or can be rerun under a selected version. - **Extended interfaces:** assess the need for a stable calculation-engine boundary, public API, batch execution, notebook use, command-line use, or integration with other digital-water platforms. From 691044a34dcf750b9bef3430d1cd33dd518ea471 Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Thu, 24 Sep 2026 09:28:32 +0000 Subject: [PATCH 18/30] docs: align vision with the vision template Restructure docs/vision.md into the template sections: Purpose, Users, Main workflows, Scope, Out of scope, MVP (treatment failure), Success criteria, six checkable Quality goals and Open questions with owners. Move remaining technical facts to architecture_baseline.md, add the template, and update references in the failure approach, ADR-0001 and ticket 02. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/QMRA_Failure_Approach.md | 4 +- ...1-failure-days-drawn-per-exposure-event.md | 2 +- docs/architecture_baseline.md | 2 + docs/templates/vision.md | 59 ++++ ...2-establish-product-vision-and-glossary.md | 3 +- docs/vision.md | 292 ++++++------------ 6 files changed, 162 insertions(+), 200 deletions(-) create mode 100644 docs/templates/vision.md diff --git a/docs/QMRA_Failure_Approach.md b/docs/QMRA_Failure_Approach.md index c159ad2..8bc5913 100644 --- a/docs/QMRA_Failure_Approach.md +++ b/docs/QMRA_Failure_Approach.md @@ -119,8 +119,8 @@ is recorded in `docs/adr/0001-failure-days-drawn-per-exposure-event.md`. 7. **Results.** Only the result including failures is shown, and the reference-level exceedance is taken on it. The effect of failures is seen by comparing scenarios with different failure inputs in the assessment comparison view. That view compares annual - probability of infection only. Until the extended saved-assessment comparison (next - phase in `docs/vision.md`) adds DALYs, the DALY effect of failures is read from each + probability of infection only. Until the extended assessment comparison (under "Waits + until later" in `docs/vision.md`) adds DALYs, the DALY effect of failures is read from each assessment's own result page. A failure can exceed the DALY reference level without exceeding the infection one, so this matters. 8. **Still steady-state.** Failure frequency and duration are the same every year and diff --git a/docs/adr/0001-failure-days-drawn-per-exposure-event.md b/docs/adr/0001-failure-days-drawn-per-exposure-event.md index 5d00391..6694a74 100644 --- a/docs/adr/0001-failure-days-drawn-per-exposure-event.md +++ b/docs/adr/0001-failure-days-drawn-per-exposure-event.md @@ -5,7 +5,7 @@ - **Deciders:** QMRA project team (domain-modeling session on the failure approach) - **Related:** `docs/QMRA_Failure_Approach.md` (Decisions section), `CONTEXT.md` (Treatment failure, Failure day, Failure frequency) - **TL;DR:** When treatment failures are added, each exposure event is randomly assigned to a failure day or a normal day inside the existing simulation, instead of counting 365 days per year as the source method does. -- **Quality goals:** scientific correctness — one calculation path for single and combined failures, consistent with how the existing model builds yearly risk; understandable assumptions — failure frequency stays in the unit operators know (days per year). +- **Quality goals:** Scientific correctness — one calculation path for single and combined failures, consistent with how the existing model builds yearly risk, so benchmark scenarios check one method; Input validity — failure frequency stays in the unit operators know (days per year). ## Context diff --git a/docs/architecture_baseline.md b/docs/architecture_baseline.md index 913f8df..f267b56 100644 --- a/docs/architecture_baseline.md +++ b/docs/architecture_baseline.md @@ -99,6 +99,8 @@ The current calculation flow is: The calculation does not currently model treatment failure, downtime, failure sequences, changing operating conditions, or a separate combined-failure mechanism. There is no persisted calculation-engine version or simulation-settings record in the inspected application model. +Tests cover the calculation (including regression values), forms and the bundled default data; the default-data tests expect eight source-water, 22 treatment and eight exposure definitions. Coverage is uneven: the plot test is empty and the API test file has no substantive tests. + ## 6. Main user workflows ### Anonymous assessment diff --git a/docs/templates/vision.md b/docs/templates/vision.md new file mode 100644 index 0000000..50cee08 --- /dev/null +++ b/docs/templates/vision.md @@ -0,0 +1,59 @@ + + +# Vision + +## Purpose + + + +## Users + + + +## Main workflows + + + +## Scope + + + +## Out of scope + + + +## MVP + + + +## Success criteria + + + +## Quality goals + + + +### + +- **Goal**: +- **Example situation**: +- **How we check it**: + +## Open questions + + diff --git a/docs/tickets/02-establish-product-vision-and-glossary.md b/docs/tickets/02-establish-product-vision-and-glossary.md index 8604850..dfcd810 100644 --- a/docs/tickets/02-establish-product-vision-and-glossary.md +++ b/docs/tickets/02-establish-product-vision-and-glossary.md @@ -1,6 +1,6 @@ # 02: Establish the current product vision and glossary -**What to build:** A short document, `docs/vision.md`, that describes the QMRA web app as it is today. It explains why the tool exists, who uses it, the main things users do with it, and which functions are available now. It describes the current product, not future plans. The purpose is to give the AI agents a reliable description of the existing product. We do not need to answer every open question at this stage. Open questions, contradictions and unfinished behaviour are listed in a short section at the end of the document. +**What to build:** A short document, `docs/vision.md`, that describes the QMRA web app as it is today. It explains why the tool exists, who uses it, the main things users do with it, and which functions are available now. It describes the current product, and in a separate MVP section the next useful version (treatment failure). The purpose is to give the AI agents a reliable description of the existing product. We do not need to answer every open question at this stage. Open questions, contradictions and unfinished behaviour are listed in a short section at the end of the document. Next to the vision, a glossary: a short list of the key terms of the QMRA app with one clear meaning each, so that the vision and all future work use the same words. @@ -15,6 +15,7 @@ Next to the vision, a glossary: a short list of the key terms of the QMRA app wi - [x] The glossary was moved into the file `CONTEXT.md` in the root folder of the repository. This is the place where our framework expects the project vocabulary. Each concept has one agreed term and a list of words to avoid. - [x] The vision and the glossary were compared with the code. Where they disagreed, the points were written down in `docs/domain-model-open-questions.md` and settled on 23 September 2026. The code changes that follow from them are listed there as later work. - [x] A review comment on the vision was addressed: the app already has a plot that compares the infection risk of several saved assessments, and the vision now describes it as an existing function. +- [x] The vision was restructured to follow the template `docs/templates/vision.md`: Purpose, Users, Main workflows, Scope, Out of scope, MVP, Success criteria, Quality goals and Open questions with owners. Technical details moved to `docs/architecture_baseline.md`. - [x] Everything is published in pull request #17 (branch `docs-vision-architecture-glossary`). Follow-up work, not part of this ticket: diff --git a/docs/vision.md b/docs/vision.md index de52edc..ce97f07 100644 --- a/docs/vision.md +++ b/docs/vision.md @@ -1,246 +1,146 @@ -# QMRA Product Vision +# QMRA Vision ## Purpose -QMRA is a web application for estimating microbial health risks in drinking-water and water-reuse scenarios. - -The application guides users through a quantitative microbial risk assessment without requiring them to build the calculation model from scratch. It combines: - -- pathogen concentrations in a selected or user-defined source water; -- human exposure assumptions; and -- treatment steps with minimum and maximum log-removal values (LRVs) for bacteria, viruses, and protozoa. - -The application runs a predefined probabilistic calculation and reports annual probability of infection and disability-adjusted life years (DALYs). It also compares calculated results with two health-based reference levels used by the application: - -- `1 × 10^-4` infections per person per year; and -- `1 × 10^-6` DALYs per person per year. - -QMRA is intended for risk assessment, steady-state scenario exploration, planning, education, and communication. It is not a treatment-process design tool and its results are not, by themselves, proof of regulatory compliance or a sufficient basis for public-health decisions. - -## The problem QMRA addresses - -Quantitative microbial risk assessment is scientifically established, but applying it in practice requires users to assemble microbiological data, exposure assumptions, treatment-removal assumptions, dose-response parameters, health parameters, and uncertainty assumptions. - -QMRA brings these elements together in a guided web workflow. It provides bundled scientific defaults for early-stage assessment and allows users to replace relevant values with project-specific inputs. This reduces the setup effort while keeping the main inputs, assumptions, results, and reference levels visible for review and discussion. - -The application is most useful when users need to: - -- perform a quick initial assessment using available defaults; -- explore how source-water, exposure, or treatment assumptions affect risk; -- assess a project using locally available values for concentrations, exposure, or treatment removal; -- communicate risk results using standard health metrics and reference levels; or -- save and export a reviewable record of an assessment. - -## Value proposition - -QMRA turns selected or user-entered pathogen, exposure, and treatment-removal data into probabilistic estimates of microbial health risk. - -It provides: - -- a guided entry point for users who are not building a QMRA model themselves; -- reusable default data for screening and scenario exploration; -- control over the principal exposure, source-water, and treatment inputs; -- distributions and summary statistics rather than one deterministic result; -- outputs in annual infection probability and DALYs per person per year; -- comparison with the application’s health-based reference levels; and -- saved assessments and exports for registered users. - -The value of the result depends on the quality and local relevance of the inputs. The current model has a limited pathogen set, simplified treatment representation, and incomplete provenance metadata. It should therefore be used as a decision-support and communication tool, not as a complete representation of every operational or scientific uncertainty. +Quantitative microbial risk assessment is well established, but applying it to a real water system means assembling pathogen data, exposure assumptions, treatment removal, dose-response and health parameters, which most water professionals cannot do from scratch. QMRA is a web application that does this for them: a user describes a source water, a treatment train and an exposure, and gets the annual probability of infection and DALYs per person per year for three reference pathogens, compared with health-based reference levels. It supports risk assessment, scenario exploration, planning, teaching and communication in drinking water and water reuse. ## Users -QMRA is intended for a broad set of users involved in water-risk assessment and communication: - -- **Water-process and environmental engineers** can explore source-treatment-exposure combinations and test alternative treatment assumptions. -- **Utility operators and water-safety professionals** can assess steady-state operating scenarios using local or project-specific values. -- **Public-health specialists and regulatory stakeholders** can review assumptions, inputs, outputs, and health-based reference comparisons. -- **QMRA researchers and domain experts** can inspect the configured inputs, distributions, references, and exported results. -- **Students and non-expert users** can use the guided workflow, bundled defaults, validation, and explanatory content to learn about or explore QMRA. +- **Water-process and environmental engineers** need to compare treatment trains and see how treatment assumptions change the risk. +- **Utility operators and water-safety professionals** need to assess their own plant with local values instead of literature defaults. +- **Public-health specialists and regulators** need to review the inputs, assumptions and the comparison with reference levels without running the model themselves. +- **QMRA researchers and domain experts** need to inspect inputs, distributions and references, and check results against their own calculations. +- **Students and newcomers to QMRA** need a guided path with defaults and explanations to learn how a risk assessment works. -These audiences do not have separate application roles or tailored calculation models. Registered users share the same access pattern and assessment functionality. +All of them use the same functions. The only difference in access is between guests, who cannot save, and registered users. -## Main workflows and functionalities +## Main workflows -### Quick screening assessment +### Quick screening -A user can run an assessment without an account using bundled exposure, source-water, and treatment definitions. The user selects or accepts the available defaults, submits the assessment, and reviews the calculated risk results. +1. A guest or registered user opens the assessment configurator. +2. They select a bundled source water, bundled treatment steps and a bundled exposure. +3. The application runs the Monte Carlo simulation and shows, per reference pathogen, the risk distributions of both risk measures in best-case and worst-case, with the reference-level exceedance. For a guest nothing is stored. ### Site-specific assessment -A user can enter or modify: +1. A guest or registered user starts from bundled data or their personal definitions. +2. They replace values with local ones: inflow concentrations (which makes the source water a site-specific source water), LRVs of treatment steps, and exposure events per year and volume per event. Recontamination is entered as a treatment step with a negative LRV. +3. The results are the same as in quick screening. The application does not check whether the entered values represent the site. -- exposure events per year; -- ingested water volume per event in litres; -- minimum and maximum concentrations for each reference pathogen; and -- minimum and maximum treatment LRVs for bacteria, viruses, and protozoa. +### Save, reopen and export -The user can then run the calculation and review the results. The application does not verify whether user-entered values are representative of a particular site. +1. A registered user saves an assessment with a name and description. +2. They can list, reopen, edit and delete saved assessments. +3. For a saved assessment they download an export package: a ZIP with input tables, a result table, an HTML report and plots. -### Treatment-train scenario analysis +### Personal definitions -A user can add multiple treatment steps to a scenario. Each step contributes minimum and maximum LRVs for bacteria, viruses, and protozoa. The current calculation combines treatment steps by summing LRVs within each pathogen group. +1. A registered user creates a reusable source water, exposure or treatment step. +2. The configurator offers it next to the bundled default data in every later assessment. -Users can explore alternative scenarios by changing the configuration and rerunning the assessment. On the saved-assessments page, a registered user can select several saved assessments and open a result-comparison view: one plot showing, per reference pathogen, the annual-infection-probability range of each selected assessment against the health-based reference level. This view compares infection-risk results only; it does not compare DALYs or show differences in inputs such as source water, exposure, or treatment train. +### Compare scenarios -### Risk interpretation +1. A registered user selects several saved assessments on the saved-assessments page. +2. The assessment comparison shows one plot: the annual probability of infection per reference pathogen for each selected assessment, against the health-based reference level. DALYs and inputs are not compared. -The result view provides: +## Scope -- annual probability of infection; -- DALYs per person per year; -- distributions for best-case and worst-case; -- minimum, maximum, first quartile, median, and third quartile summary statistics; -- grouped box plots; -- comparison with the application’s two health-based reference levels; and -- a reference-level exceedance: whether the calculated mean exceeds the displayed reference level in both cases, only in worst-case, or in neither. +- Microbial risk in drinking water and water reuse, for three reference pathogens: Rotavirus, Campylobacter jejuni and Cryptosporidium parvum. +- Steady-state scenarios: inflow concentrations given as minimum and maximum, a treatment train whose LRVs are summed per pathogen group, and an exposure given as events per year and volume per event. +- A Monte Carlo simulation in two cases, best-case (maximum LRVs) and worst-case (minimum LRVs), reporting risk distributions with minimum, quartiles, median and maximum. +- Two risk measures, each compared with its health-based reference level: 1 × 10⁻⁴ infections per person per year and 1 × 10⁻⁶ DALYs per person per year. +- Bundled default data with references, personal definitions and site-specific inputs. +- Guest use without saving; saving, comparison and export for registered users. The tool is free to use. +- A warning when a treatment step's maximum LRV is above 6. The calculation still runs. -The interface warns when a maximum LRV is above six. This warning does not prevent the calculation from running. +Known limitations of the current product: -### Saving, managing, and exporting assessments +- Uncertainty is represented only through inflow concentration ranges, random sampling and the spread between best-case and worst-case, not through a complete uncertainty model. +- Provenance is incomplete: bundled data carries references, but personal definitions and site-specific inputs do not. +- Saved assessments and export packages do not record the model version, data release or simulation settings, so a result cannot be reproduced exactly later. +- The assessment comparison covers annual probability of infection only. -A guest user can run an assessment without saving it. A registered user can: +## Out of scope -- save an assessment with a name and description; -- list saved assessments; -- reopen and edit an assessment; -- delete an assessment; -- create reusable personal exposure, source-water, and treatment definitions; and -- export a saved assessment. +- Treatment process design and sizing, the order of treatment steps, and interactions between steps beyond the sum of their LRVs. +- Chemical and physical hazards. +- Conditions that change within a year, such as varying inflow concentrations or operating conditions. Results are yearly, never daily. +- Partial loss of removal in a treatment step. Treatment failure, once modelled, means the entire loss of a step's LRV. +- Proof of legal compliance. The health-based reference levels are comparison values, not legal limits, and results must not be the only basis for treatment design, regulatory or public-health decisions. -The export is a ZIP package containing input CSV files, result CSV data, an HTML report, and PNG plots. +## MVP -### Next phase +The current application is the base. The next useful version adds **treatment failure**, including combined failures, following `docs/QMRA_Failure_Approach.md` and ADR-0001. It serves utility operators and engineers who want to know how failures of their treatment steps affect the yearly risk. -The following capabilities are not part of the current implementation and should be treated as next-phase work rather than existing functionality: +Functionalities, each one Change: -- **Treatment failure and downtime assessment:** configure failure frequency and average failure duration for treatment steps and quantify their effect on annual risk. -- **Combined treatment failures:** represent simultaneous failures across treatment steps, including daily failure/no-failure states and corresponding treatment performance. -- **Extended saved-assessment comparison:** build on the existing infection-risk comparison plot to also compare DALYs and show differences in source water, exposure assumptions, treatment trains, and best-case and worst-case results side by side. Treatment failure depends on it: until DALYs can be compared, the DALY effect of different failure inputs cannot be evaluated side by side. -- **Reproducible assessment snapshots:** attach explicit model-version, data-library, reference, and simulation-settings metadata to saved and exported assessments. -- **Historical assessment handling:** define whether assessments remain tied to their original model and data, are migrated, or can be rerun under a selected version. -- **Extended interfaces:** assess the need for a stable calculation-engine boundary, public API, batch execution, notebook use, command-line use, or integration with other digital-water platforms. +1. **Failure inputs per treatment step.** In the assessment configurator, each treatment step with a positive LRV gets a failure frequency (failure days per year, 0–365, default 0) and a failure duration (minutes, 1–1440, default 30). The inputs belong to the scenario. +2. **Failure inputs on personal treatment steps.** A personal treatment step can store its failure frequency and failure duration for reuse. +3. **Calculation with failure days.** Inside the Monte Carlo simulation, each exposure event falls on a failure day of each step with probability failure frequency / 365. Worst-case loses the full LRV of every failing step; best-case applies the mixed-water assumption, with combined failures overlapping as little as possible. +4. **Results and export including failures.** The result page, the reference-level exceedance and the export package show the result including failures, and the export records the failure inputs of each step. -## Current calculation model +Waits until later: -The current engine calculates two treatment-performance cases: +- Extended assessment comparison with DALYs and inputs. Until then, the DALY effect of different failure inputs is read from each assessment's own result page. +- Reproducible assessment snapshots. +- Handling of historical assessments after model or data changes. +- Extended interfaces: a calculation-engine boundary, public API, batch, notebook or command-line use. +- The naming follow-ups recorded in `docs/domain-model-open-questions.md`. -- **best-case**, using every treatment step's maximum LRV and representing the lower infection-risk estimate; and -- **worst-case**, using every treatment step's minimum LRV and representing the higher infection-risk estimate. +## Success criteria -For each treatment step, LRVs are summed by pathogen group across the treatment train. Treatment performance is constant within each case. The engine does not simulate changing operating conditions, treatment outages, daily failure states, simultaneous failures, or process-order effects beyond the LRV sum. - -The calculation uses stochastic sampling of source concentrations and exposure outcomes. It uses a fixed NumPy random-generator seed of `42` and default simulation sizes of 10,000 event samples and 1,000 simulated years. - -The current user-facing model supports three reference pathogens: - -- Rotavirus; -- Campylobacter jejuni; and -- Cryptosporidium parvum. - -Pathogen-specific dose-response and health parameters are supplied by the bundled default data. - -## Data and configuration - -The application provides bundled default data for: - -- reference pathogens; -- dose-response and health parameters; -- source-water definitions; -- treatment definitions; -- exposure definitions; and -- scientific or guideline references. - -The inspected tests expect eight source-water definitions, 22 treatment definitions, and eight exposure definitions. - -Registered users can create personal exposure, source-water, and treatment definitions. Users can also override assessment values in the configurator. Personal values are not accompanied by the same reference metadata as bundled defaults. - -The assessment and export contain selected inputs and calculated results. They do not currently include an explicit model-version identifier, data-library release identifier, or complete simulation-settings snapshot. - -## Product principles - -QMRA should be: - -- **Scientifically defensible:** calculation logic, parameters, thresholds, defaults, and references should be reviewable and testable. -- **Accessible:** the first valid assessment should be possible with a small number of clear steps and meaningful defaults. -- **Transparent:** users should be able to distinguish bundled defaults from user-entered values and understand the main assumptions behind a result. -- **Reproducible:** saved and exported assessments should eventually identify the exact model, data, configuration, and simulation settings used to produce the result. -- **Interoperable:** exports should support both human review and further analysis in external tools. -- **Safe to evolve:** changes to the scientific model, data, and user-facing interpretation should be versioned so historical results are not silently reinterpreted. -- **Responsible in its claims:** the application should clearly distinguish risk assessment from treatment design, legal compliance, and operational reliability analysis. +- A water utility can assess the effect of its own failure frequencies on the yearly risk, for both risk measures, without help from the development team. +- Results for the bundled benchmark scenarios match the reference calculation within an agreed tolerance. +- Domain experts at KWB accept the results and wording as defensible for use in projects. ## Quality goals -The current repository provides calculation and regression tests, form validation, bundled scientific data, result plots, and export generation. These provide a foundation for quality, but they do not establish all desired quality properties. - -Important quality goals are: +### Scientific correctness -- scientific correctness supported by version-controlled benchmark and regression scenarios; -- valid and clearly labelled inputs with explicit units and min/max checks; -- visible references and understandable assumptions; -- reproducible results when the same complete assessment snapshot is used; -- stable and reviewable export content; -- appropriate privacy and access control for registered-user data; and -- acceptable response time and reliability under expected use. +- **Goal**: calculated results match an independent reference calculation. +- **Example situation**: a domain expert recalculates the worst-case Cryptosporidium risk of a bundled scenario in a spreadsheet and gets a different median. +- **How we check it**: version-controlled benchmark scenarios with expected results from an independent calculation; regression tests run on every change and fail outside the agreed tolerance. -The inspected test coverage is uneven. Calculation and form tests exist, but the plot test is empty and the API test file contains no substantive test implementation. +### Traceability -## Constraints and assumptions +- **Goal**: for every input of an assessment, a reader can see whether it is bundled default data, a personal definition or a site-specific input, and for bundled data which reference it comes from. +- **Example situation**: a reviewer at a health authority asks where the Rotavirus concentration of a "site-specific groundwater" came from. +- **How we check it**: the export package lists the origin of each input and the reference of each bundled value; a test checks that every exported input has an origin and every bundled value a reference. -- The current user-facing model supports only Rotavirus, Campylobacter jejuni, and Cryptosporidium parvum. -- Risk estimates depend strongly on the quality and local relevance of pathogen concentrations, exposure assumptions, and treatment LRVs. -- Bundled defaults support screening and exploration but may not represent local conditions. -- Treatment LRVs are treated as constant within best-case and within worst-case. -- Treatment steps are combined by adding their LRVs by pathogen group. -- Process order, changing influent concentrations, operating conditions, treatment interactions beyond the LRV sum, and treatment failures are not modelled. -- Minimum and maximum input values and stochastic sampling represent only part of the uncertainty in a real water system. -- The application assesses microbial risk; it does not assess chemical or physical hazards. -- The application is not a treatment-process design tool. -- Application reference levels and international guidance do not automatically establish compliance with local law. -- Results should not be used as the sole basis for treatment design, regulatory compliance, or public-health decisions. -- Guest users can run unsaved assessments; registered users can persist and export assessments. +### Reproducibility -## Known limitations and uncertainties +- **Goal**: a saved assessment gives the same result every time, and can be recalculated later under the same model and data. +- **Example situation**: a year after a report, a utility reopens an assessment after a model update and gets different numbers without knowing why. +- **How we check it**: each saved assessment and export package records the model version, data release and simulation settings; a test recalculates stored benchmark assessments and compares them with their recorded results. -- The model is limited to three reference pathogens. -- The calculation uses simplified treatment assumptions and does not represent operational failures, downtime, or combined failures. -- Uncertainty is represented through input ranges, stochastic sampling, and the spread between best-case and worst-case, not through a complete joint uncertainty model. -- Saved-assessment comparison is limited to a plot of infection-risk results; inputs and DALYs are not compared. -- Export includes CSV, HTML, and PNG files in a ZIP; no Excel spreadsheet export was found in the inspected code. -- Saved assessments lack explicit model-version, data-library release, and complete simulation-settings metadata. -- Bundled references are available, but provenance is not captured completely for every saved or user-defined value. -- Repository inspection cannot verify production availability, response-time performance, TLS deployment, backups, retention and deletion behaviour, incident response, or the security posture of a deployed instance. +### Input validity -## Access and free use +- **Goal**: every input shows its unit and is checked against a plausible range; invalid values are rejected with a clear message. +- **Example situation**: a user enters 2000 as volume per event, thinking in millilitres. +- **How we check it**: form tests per input field for the unit label, the lower and upper bounds (for example failure frequency 0–365, failure duration 1–1440) and minimum ≤ maximum. -The application supports guest use for unsaved assessments and registered-user accounts for persistence and export. The documentation describes the tool as free to use, and no billing workflow was found in the inspected repository. The operating policy and availability of any deployed service require separate verification. +### Export stability -## Evidence and scope of verification +- **Goal**: the content and layout of the export package change only on purpose. +- **Example situation**: a utility's script that reads the result table breaks after an update renamed a column. +- **How we check it**: a test compares the export package of a benchmark assessment with a stored reference export; changing it requires updating the reference and noting it in the release notes. -This document describes the inspected repository state, including application code, templates, tests, documentation, and bundled default data. It does not make claims about deployment configuration or operational service behaviour unless those claims are separately verified. +### Privacy of registered-user data -Relevant evidence: - -- [Repository README](https://github.com/KWB-R/qmra-webapp/blob/main/README.md) -- [Current QMRA documentation](https://github.com/KWB-R/qmra-webapp/blob/main/docs/source/index.rst) -- [Risk calculation](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/risk.py) -- [Assessment views and access paths](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/views.py) -- [Assessment forms and validation](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/forms.py) -- [Assessment and result models](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/models.py) -- [Default data models](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/qmra_models.py) -- [Export implementation](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/exports.py) -- [Result plots](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/plots.py) -- [FAQ and limitations](https://github.com/KWB-R/qmra-webapp/blob/main/qmra/risk_assessment/templates/faqs.html) -- [Calculation and regression tests](https://github.com/KWB-R/qmra-webapp/tree/main/qmra/risk_assessment/tests) +- **Goal**: a registered user's saved assessments, export packages and personal definitions are accessible only to that user. +- **Example situation**: a logged-in user changes the assessment ID in the URL to one belonging to another user. +- **How we check it**: tests request every assessment, export and personal-definition URL as a different user and expect a refusal. ## Open questions -The following questions remain open for future product and scientific development: - -- Should model and data-library versions receive explicit release identifiers and change logs, and should these identifiers be attached to every saved and exported assessment? -- What information must be stored so that a saved assessment can be reproduced from the same model, data, configuration, and simulation settings? -- Should the scientific calculation engine be separated behind a stable internal API or library boundary? -- Is there a future need for public API access, batch execution, notebook use, command-line use, or integration with other digital-water platforms? -- What project data may be confidential or commercially sensitive even when it is not personal data, and what additional controls are required for saved assessments? -- How should historical assessments behave when the scientific model, data library, or assessment schema changes: remain frozen to the original version, be migrated, or be rerunnable under a user-selected version? -- What comparison capabilities are needed for saved assessments and alternative scenarios beyond the existing infection-risk comparison plot? -- Which deployment controls, operational metrics, performance targets, backup policies, retention rules, and incident-response processes are required for production use? +- Should model and data releases get explicit identifiers and change logs, attached to every saved assessment and export package? (Wolfgang, Malte; for Architecture) +- What must be stored so that a saved assessment can be reproduced from the same model, data, configuration and simulation settings? (Wolfgang, Malte; for Architecture) +- Should the calculation engine be separated behind a stable internal boundary? (Wolfgang, Malte; for Architecture) +- Is there a need for public API access, batch execution, notebook or command-line use, or integration with other digital-water platforms? (Wolfgang, Malte) +- Which project data is confidential or commercially sensitive even when it is not personal data, and what controls do saved assessments need? (Wolfgang, Malte) +- How should historical assessments behave when the model, data or assessment structure changes: stay frozen, be migrated, or be recalculated under a chosen version? (Wolfgang, Malte) +- Beyond DALYs, which comparisons of saved assessments are needed: inputs, best-case and worst-case side by side, saved comparisons? (Wolfgang, Malte) +- What tolerance counts as a match for the benchmark scenarios, and which domain experts accept the results? (Wolfgang, Malte) +- Where does the rule "maximum LRV above 6" come from, and should it warn or cap? See `docs/domain-model-open-questions.md`. (Wolfgang, Malte) +- Which response-time and reliability targets apply, and which deployment controls, backups, retention rules and incident-response processes are required for production use? (Wolfgang, Malte; for Architecture) From fef89ad932702bf187a440a5257304f201fba52f Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Thu, 24 Sep 2026 09:58:30 +0000 Subject: [PATCH 19/30] docs: add failure roadmap for the MVP Four Changes for treatment failure: failure inputs (Large), failure calculation (Large), failure inputs in export and report (Small) and explaining treatment failure (Small), with dependencies, parallel work, sequence, release rule and open decisions D1-D4. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/failure_roadmap.md | 88 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/failure_roadmap.md diff --git a/docs/failure_roadmap.md b/docs/failure_roadmap.md new file mode 100644 index 0000000..adc4a6e --- /dev/null +++ b/docs/failure_roadmap.md @@ -0,0 +1,88 @@ +# Failure roadmap + +Status: draft, 2026-09-24. Overview of the Changes that deliver the MVP in `docs/vision.md`: +treatment failure, including combined failures. Sources of truth: `docs/vision.md`, +`CONTEXT.md`, `docs/adr/`. Each Large change gets a Spec (`/to-spec`); Small changes are +built directly. Changes are refined into tickets later. All Changes start from `main`. + +## Changes + +### C1 Failure inputs — Large + +Spec issue: #___ + +Purpose: users can enter a failure frequency and a failure duration for each treatment +step, and they are stored with the scenario. + +Scope: failure frequency (failure days per year, 0–365, default 0) and failure duration +(minutes, 1–1440, default 30) on each treatment step of an assessment and on personal +treatment steps; configurator fields with units and validation; only treatment steps with +a positive LRV accept failure inputs (see D4); values of a personal treatment step are +carried into the assessment; saving, reopening and editing keep them. Existing saved +assessments get failure frequency 0, so their results are unchanged. + +Not included: any effect on the calculation (C2), export and report (C3), explanations +beyond field labels and units (C4). + +### C2 Failure calculation — Large + +Spec issue: #___ + +Purpose: results include treatment failures as decided in ADR-0001. + +Scope: failure days drawn per exposure event and per treatment step inside the Monte Carlo +simulation; worst-case loses the full LRV of every failing step; best-case applies the +mixed-water assumption; combined failures overlap as little as possible; the result page +and the reference-level exceedance show the result including failures. With every failure +frequency at 0, results equal today's. The Spec defines benchmark scenarios with failures; +a domain expert provides their expected values, and the Change is done when results match +within the agreed tolerance. + +Not included: showing normal operation next to the result with failures (decided +against), DALYs in the assessment comparison (later). + +### C3 Failure inputs in export and report — Small + +The export package's treatment table and the HTML report show each treatment step's failure +frequency and failure duration, so a reader can see which failures a result is based on. +Left out: renaming export files (`inflows.csv` → `inflow_concentration.csv` waits until +later), origin and reference columns for inputs, changes to the assessment comparison. + +### C4 Explain treatment failure — Small + +The FAQ, the configurator guided tour and the Sphinx documentation (`docs/source/`) explain +failure frequency and failure duration, failure days, why worst-case ignores the failure +duration, the mixed-water assumption and combined failures. Left out: field labels and +units (C1), rewording the deferred UI terms such as "tolerable risk level", and +explanations of unrelated parts of the model. + +## Dependencies + +- C2 depends on C1 (it reads the stored failure inputs). +- C3 depends on C1. +- C4 depends on C2 (it explains the calculation's behaviour) and on C1. +- Release depends on C1–C4: nothing is deployed until all four are merged, so users never + see failure inputs without effect or results they cannot trace. + +## Parallel work + +- The Specs of C1 and C2 can be written in parallel; both rest on `CONTEXT.md` and ADR-0001. +- After C1 is merged, C2 and C3 can be built in parallel. +- C4 can be drafted while C2 is being built and finished once C2 is merged. + +## Sequence + +1. `/to-spec` C1 and C2 (in parallel). +2. `/implement` C1. +3. `/implement` C2; build C3 in parallel. +4. Build C4 once C2 is merged. +5. Release once C1–C4 are merged. + +## Unresolved decisions + +| # | Decision | Owner | Depends on | Blocks | +|---|---|---|---|---| +| D1 | Tolerance for matching benchmark scenarios | Wolfgang, Malte | — | Completion of C2 (not its Spec) | +| D2 | Expected values for the failure benchmark scenarios, from a domain expert | Wolfgang, Malte | C2 Spec (which scenarios) | Completion of C2 | +| D3 | Which domain experts accept the results | Wolfgang, Malte | D1, D2 | Release | +| D4 | Which treatment steps accept failure inputs when their LRV is positive for one pathogen group but zero or negative for another | Wolfgang, Malte | — | C1 Spec | From 5616b34b2ebb39b2876f7652fb311a4dac22ddc0 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Thu, 24 Sep 2026 10:13:45 +0000 Subject: [PATCH 20/30] docs: add ticket 03 for the vision rewrite and failure roadmap Co-Authored-By: Claude Fable 5.1 --- .../03-vision-rewrite-and-failure-roadmap.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docs/tickets/03-vision-rewrite-and-failure-roadmap.md diff --git a/docs/tickets/03-vision-rewrite-and-failure-roadmap.md b/docs/tickets/03-vision-rewrite-and-failure-roadmap.md new file mode 100644 index 0000000..ec01263 --- /dev/null +++ b/docs/tickets/03-vision-rewrite-and-failure-roadmap.md @@ -0,0 +1,28 @@ +# 03: Rewrite the vision to the template and draft the failure roadmap + +**What to build:** The vision document `docs/vision.md` follows the framework's vision template: Purpose, Users, Main workflows, Scope, Out of scope, MVP, Success criteria, Quality goals and Open questions with owners. The MVP is the next useful version of the app: treatment failure, including combined failures. Next to it, a roadmap `docs/failure_roadmap.md` lists the Changes that deliver this MVP, their size (Large with a Spec, or Small and built directly), their order, what can run in parallel, and the decisions that are still open and who owns them. + +**Blocked by:** None (can start immediately) + +**Status:** in-progress + +Done on 24 September 2026 (branch `docs-vision-architecture-glossary`, pull request #17): + +- [x] Vision restructured to the template. Technical details moved to `docs/architecture_baseline.md`. +- [x] Six quality goals written as goal, example situation and a check someone can run: scientific correctness, traceability, reproducibility, input validity, export stability, privacy of registered-user data. +- [x] MVP defined as treatment failure in four functionalities, following `docs/QMRA_Failure_Approach.md` and ADR-0001. +- [x] Open questions listed with owners, technical ones marked for Architecture. +- [x] Vision and architecture baseline use the glossary terms (best-case, worst-case, reference-level exceedance). +- [x] Failure roadmap drafted with four Changes (C1 failure inputs, C2 failure calculation, C3 export and report, C4 explanations), dependencies, sequence and four unresolved decisions. +- [x] ADR-0001 records why worst-case ignores the failure duration and that failure comparison covers infection risk only. It cites the vision's quality goals by their new names. +- [x] The vision template is copied into `docs/templates/`. + +Still to do: + +- [ ] Make the MVP list and the roadmap map one to one, as the template asks: today MVP items 1 and 2 both fall into C1, item 4 is split between C2 and C3, and C4 has no MVP item. +- [ ] Decide whether the roadmap file keeps the name `failure_roadmap.md` or becomes `docs/roadmap.md`, the name the framework's Roadmap stage expects. +- [ ] Fix the site-specific workflow in the vision: only registered users have personal definitions, guests do not. +- [ ] Replace "QMRA project team" in the ADR's Deciders line with names or roles. +- [ ] Create the Spec issues for C1 and C2 and fill in their numbers in the roadmap. +- [ ] Settle the four unresolved decisions in the roadmap (benchmark tolerance, expected benchmark values, who accepts results, failure inputs for steps with mixed LRV signs). Owners: Wolfgang and Malte. +- [ ] Get pull request #17 reviewed and merged. From a8181b6863df76a616c7bb6ab2030f4ec9615cdd Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Thu, 24 Sep 2026 10:18:49 +0000 Subject: [PATCH 21/30] docs: mark ticket 01 (onboard Malte) as done Co-Authored-By: Claude Fable 5.1 --- ...board-malte-to-ai-development-framework.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/tickets/01-onboard-malte-to-ai-development-framework.md b/docs/tickets/01-onboard-malte-to-ai-development-framework.md index e37ba74..6d6cccc 100644 --- a/docs/tickets/01-onboard-malte-to-ai-development-framework.md +++ b/docs/tickets/01-onboard-malte-to-ai-development-framework.md @@ -6,7 +6,7 @@ We follow the framework's onboarding page step by step. His personal setup file **Blocked by:** None (can start immediately) -**Status:** in-progress +**Status:** done Already done on the server (checked on 23 September 2026): @@ -15,13 +15,13 @@ Already done on the server (checked on 23 September 2026): - [x] He is in the project group for the QMRA web app. This group gives him access to the shared Git key and to the `controlled-dev-git` command used to pull and push code. - [x] He has admin rights on the GitHub repository KWB-R/qmra-webapp. -Still to do: +Done on 24 September 2026: -- [ ] Check that the QMRA project was copied into his home folder on the server, and that its CLAUDE.md file contains the note about `controlled-dev-git`. -- [ ] Check that he can log in from his own computer with his server key, and that `sudo controlled-dev-git pull` works inside the project folder. -- [ ] Sit with him for his first start of Claude Code. He chooses the subscription login. Claude shows a web address. We send it to Nico, who approves it and sends back a short code. Malte pastes that code into Claude. Plan this for a time when Nico is available, because our shared Claude subscription needs his approval. -- [ ] Malte confirms the "Trust this folder" question himself the first time Claude opens the project. -- [ ] Check that his Claude skills are installed and match his setup file. Always install them through the `manage-user.sh` script, not through the install script on its own. -- [ ] Write the audit record: who did it, for whom, which project, what was done, when, and the result. -- [ ] Walk him through the method overview and the "Project start" phase. Point him to the open pull request #17, which holds the current Vision and Glossary. -- [ ] Run the environment check script `05-validate-environment.sh` afterwards to confirm that the account is set up correctly. +- [x] Check that the QMRA project was copied into his home folder on the server, and that its CLAUDE.md file contains the note about `controlled-dev-git`. +- [x] Check that he can log in from his own computer with his server key, and that `sudo controlled-dev-git pull` works inside the project folder. +- [x] Sit with him for his first start of Claude Code. He chooses the subscription login. Claude shows a web address. We send it to Nico, who approves it and sends back a short code. Malte pastes that code into Claude. Plan this for a time when Nico is available, because our shared Claude subscription needs his approval. +- [x] Malte confirms the "Trust this folder" question himself the first time Claude opens the project. +- [x] Check that his Claude skills are installed and match his setup file. Always install them through the `manage-user.sh` script, not through the install script on its own. +- [x] Write the audit record: who did it, for whom, which project, what was done, when, and the result. +- [x] Walk him through the method overview and the "Project start" phase. Point him to the open pull request #17, which holds the current Vision and Glossary. +- [x] Run the environment check script `05-validate-environment.sh` afterwards to confirm that the account is set up correctly. From 8a13df2953703f3c79fe188c4383f25c1741bd55 Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Thu, 24 Sep 2026 11:42:19 +0000 Subject: [PATCH 22/30] docs: settle roadmap decisions and add failure benchmark change D4: steps with at least one positive LRV accept failure inputs, and a failure removes only positive LRVs. The independent benchmark moves from the calculation change into a new Small change C5, built after C2 and required before release; expected values come from an independent Monte Carlo script, and domain experts sign off the benchmark comparison. D1-D3 remain open for tolerance and names. The vision's success criterion now covers results only. Co-Authored-By: Claude Opus 5.5 (1M context) --- CONTEXT.md | 2 +- docs/QMRA_Failure_Approach.md | 11 +++--- docs/failure_roadmap.md | 67 +++++++++++++++++++++++++++-------- docs/vision.md | 4 +-- 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 8dc3b37..99f147c 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -151,7 +151,7 @@ Every treatment step performing within its configured minimum and maximum LRVs. _Avoid_: regular operation, steady operation **Treatment failure**: -A treatment step losing its entire LRV for a period, so that it contributes no removal while it lasts. Any treatment step with a positive LRV can fail, including non-technical measures; recontamination cannot. A step fails only if it has a failure frequency above zero. Partial loss of removal is not modelled and not planned. Not modelled today; a next-phase concept. +A treatment step losing all its positive LRVs for a period, so that it removes nothing while it lasts. LRVs of zero or below are not affected: a step with regrowth for one pathogen group keeps that negative LRV during a failure. Any treatment step with at least one positive LRV can fail, including non-technical measures; recontamination, having no positive LRV, cannot. A step fails only if it has a failure frequency above zero. Partial loss of removal is not modelled and not planned. Not modelled today; a next-phase concept. _Avoid_: damage, incident, outage, malfunction, downtime (alone) **Failure event**: diff --git a/docs/QMRA_Failure_Approach.md b/docs/QMRA_Failure_Approach.md index 8bc5913..35b4b3e 100644 --- a/docs/QMRA_Failure_Approach.md +++ b/docs/QMRA_Failure_Approach.md @@ -88,11 +88,12 @@ For each combination of failing treatment steps, an LRV must then be calculated Agreed in a domain-modeling session. Terms follow `CONTEXT.md`; the calculation approach is recorded in `docs/adr/0001-failure-days-drawn-per-exposure-event.md`. -1. **Entire loss only.** A failing treatment step loses its entire LRV, for all three - pathogen groups at once. Partial loss of removal is not modelled and not planned. -2. **Which steps can fail.** Any treatment step with a positive LRV, including - non-technical measures such as hygiene practices. Recontamination steps (negative LRV) - cannot fail. A step fails only if its failure frequency is above zero. +1. **Entire loss only.** A failing treatment step loses every positive LRV (above 0), for + all pathogen groups at once. LRVs of 0 or below are not affected by the failure. + Partial loss of removal is not modelled and not planned. +2. **Which steps can fail.** Any treatment step with at least one positive LRV, including + non-technical measures such as hygiene practices. Recontamination steps (no positive + LRV) cannot fail. (Refined 2026-09-24, roadmap decision D4.) A step fails only if its failure frequency is above zero. 3. **Inputs per treatment step.** Each step has one failure frequency (failure days per year, 0–365, default 0) and one failure duration (minutes, 1–1440, default 30). They are the same in best-case and worst-case. They are user input only; there is no diff --git a/docs/failure_roadmap.md b/docs/failure_roadmap.md index adc4a6e..ceca978 100644 --- a/docs/failure_roadmap.md +++ b/docs/failure_roadmap.md @@ -17,7 +17,7 @@ step, and they are stored with the scenario. Scope: failure frequency (failure days per year, 0–365, default 0) and failure duration (minutes, 1–1440, default 30) on each treatment step of an assessment and on personal treatment steps; configurator fields with units and validation; only treatment steps with -a positive LRV accept failure inputs (see D4); values of a personal treatment step are +at least one positive LRV accept failure inputs (D4); values of a personal treatment step are carried into the assessment; saving, reopening and editing keep them. Existing saved assessments get failure frequency 0, so their results are unchanged. @@ -32,14 +32,17 @@ Purpose: results include treatment failures as decided in ADR-0001. Scope: failure days drawn per exposure event and per treatment step inside the Monte Carlo simulation; worst-case loses the full LRV of every failing step; best-case applies the -mixed-water assumption; combined failures overlap as little as possible; the result page +mixed-water assumption; a failure removes only positive LRVs, LRVs of 0 or below stay +(D4); combined failures overlap as little as possible; the result page and the reference-level exceedance show the result including failures. With every failure -frequency at 0, results equal today's. The Spec defines benchmark scenarios with failures; -a domain expert provides their expected values, and the Change is done when results match -within the agreed tolerance. +frequency at 0, results equal today's. Tested with checks that need no external expected +values: failure frequency 0 reproduces today's results exactly, worst-case risk is never +below best-case, a higher failure frequency never lowers the risk, LRVs of 0 or below are +unaffected, and combined failures overlap minimally in best-case. -Not included: showing normal operation next to the result with failures (decided -against), DALYs in the assessment comparison (later). +Not included: the comparison with an independent calculation (C5), showing normal +operation next to the result with failures (decided against), DALYs in the assessment +comparison (later). ### C3 Failure inputs in export and report — Small @@ -56,33 +59,67 @@ duration, the mixed-water assumption and combined failures. Left out: field labe units (C1), rewording the deferred UI terms such as "tolerable risk level", and explanations of unrelated parts of the model. +### C5 Failure benchmark — Small + +A small set of benchmark scenarios with failures is stored in the repository: a single +failing step, a combined failure, failure durations adding up to more than a day, an +exposure with few events per year, and a step with a negative LRV in one pathogen group. +A domain expert calculates their expected results with an independent Monte Carlo script +(for example in R) that implements the documented method (`CONTEXT.md`, the decisions in +`docs/QMRA_Failure_Approach.md`, ADR-0001) without using the application's code, and +regression tests compare the application's results with them within the agreed tolerance +(D1, D2). The tests then run on every later change. Left out: benchmarks for parts of the +model unrelated to failures, and the reproducibility records named in the vision's quality +goals. + ## Dependencies - C2 depends on C1 (it reads the stored failure inputs). - C3 depends on C1. - C4 depends on C2 (it explains the calculation's behaviour) and on C1. -- Release depends on C1–C4: nothing is deployed until all four are merged, so users never - see failure inputs without effect or results they cannot trace. +- C5 depends on C2 (it checks the implemented calculation). +- Release depends on C1–C5: nothing is deployed until all five are merged, so users never + see failure inputs without effect, results they cannot trace, or results not checked + against an independent calculation. ## Parallel work - The Specs of C1 and C2 can be written in parallel; both rest on `CONTEXT.md` and ADR-0001. - After C1 is merged, C2 and C3 can be built in parallel. - C4 can be drafted while C2 is being built and finished once C2 is merged. +- The benchmark scenarios of C5 and their expected values (D2) can be prepared while C2 is + being built; only the comparison tests need C2 merged. +- C4 and C5 can be built in parallel. ## Sequence 1. `/to-spec` C1 and C2 (in parallel). 2. `/implement` C1. 3. `/implement` C2; build C3 in parallel. -4. Build C4 once C2 is merged. -5. Release once C1–C4 are merged. +4. Build C4 and C5 once C2 is merged. +5. Release once C1–C5 are merged and D3 is settled. ## Unresolved decisions | # | Decision | Owner | Depends on | Blocks | |---|---|---|---|---| -| D1 | Tolerance for matching benchmark scenarios | Wolfgang, Malte | — | Completion of C2 (not its Spec) | -| D2 | Expected values for the failure benchmark scenarios, from a domain expert | Wolfgang, Malte | C2 Spec (which scenarios) | Completion of C2 | -| D3 | Which domain experts accept the results | Wolfgang, Malte | D1, D2 | Release | -| D4 | Which treatment steps accept failure inputs when their LRV is positive for one pathogen group but zero or negative for another | Wolfgang, Malte | — | C1 Spec | +| D1 | Tolerance for matching benchmark scenarios. With an independent Monte Carlo script (D2), differences are only sampling noise or real errors | Wolfgang, Malte | — | C5 | +| D2 | Who calculates the expected values; ideally not the author of C2 | Wolfgang, Malte | — | C5 | +| D3 | Which one or two domain experts sign off the C5 benchmark comparison | Wolfgang, Malte | — | Release | + +## Resolved decisions + +- **D4 (2026-09-24).** A treatment step accepts failure inputs if at least one of its LRVs + is positive. A failure removes only the positive LRVs; LRVs of 0 or below, such as + regrowth in one pathogen group, are not affected. +- **Implement before benchmarking (2026-09-24).** C2 is built and tested with internal + checks; the comparison with an independent calculation moved to C5. The release still + waits for C5. +- **Benchmark method (2026-09-24).** Expected values come from an independent Monte Carlo + script that implements the documented method, not from a spreadsheet of the original + Hambsch equations, so that differences are noise or errors rather than method + differences. +- **Acceptance before release (2026-09-24).** Domain experts accept the results by + reviewing the C5 benchmark comparison and confirming it in writing, for example in the + C5 pull request. The explanations in C4 are not part of this sign-off. Who signs off is + still open (D3). diff --git a/docs/vision.md b/docs/vision.md index ce97f07..84a6b3c 100644 --- a/docs/vision.md +++ b/docs/vision.md @@ -75,7 +75,7 @@ The current application is the base. The next useful version adds **treatment fa Functionalities, each one Change: -1. **Failure inputs per treatment step.** In the assessment configurator, each treatment step with a positive LRV gets a failure frequency (failure days per year, 0–365, default 0) and a failure duration (minutes, 1–1440, default 30). The inputs belong to the scenario. +1. **Failure inputs per treatment step.** In the assessment configurator, each treatment step with at least one positive LRV gets a failure frequency (failure days per year, 0–365, default 0) and a failure duration (minutes, 1–1440, default 30). The inputs belong to the scenario. 2. **Failure inputs on personal treatment steps.** A personal treatment step can store its failure frequency and failure duration for reuse. 3. **Calculation with failure days.** Inside the Monte Carlo simulation, each exposure event falls on a failure day of each step with probability failure frequency / 365. Worst-case loses the full LRV of every failing step; best-case applies the mixed-water assumption, with combined failures overlapping as little as possible. 4. **Results and export including failures.** The result page, the reference-level exceedance and the export package show the result including failures, and the export records the failure inputs of each step. @@ -92,7 +92,7 @@ Waits until later: - A water utility can assess the effect of its own failure frequencies on the yearly risk, for both risk measures, without help from the development team. - Results for the bundled benchmark scenarios match the reference calculation within an agreed tolerance. -- Domain experts at KWB accept the results and wording as defensible for use in projects. +- Domain experts at KWB accept the results as defensible for use in projects. ## Quality goals From 191622e4fd2886a7e9d09027fe2d087a805f4b15 Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Thu, 24 Sep 2026 11:44:04 +0000 Subject: [PATCH 23/30] docs: update ticket 03 with the revisited roadmap decisions Record C5 failure benchmark, the settled D4, the benchmark method and the acceptance rule; narrow the open decisions to D1-D3. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/tickets/03-vision-rewrite-and-failure-roadmap.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/tickets/03-vision-rewrite-and-failure-roadmap.md b/docs/tickets/03-vision-rewrite-and-failure-roadmap.md index ec01263..c21a653 100644 --- a/docs/tickets/03-vision-rewrite-and-failure-roadmap.md +++ b/docs/tickets/03-vision-rewrite-and-failure-roadmap.md @@ -14,15 +14,16 @@ Done on 24 September 2026 (branch `docs-vision-architecture-glossary`, pull requ - [x] Open questions listed with owners, technical ones marked for Architecture. - [x] Vision and architecture baseline use the glossary terms (best-case, worst-case, reference-level exceedance). - [x] Failure roadmap drafted with four Changes (C1 failure inputs, C2 failure calculation, C3 export and report, C4 explanations), dependencies, sequence and four unresolved decisions. +- [x] Roadmap decisions revisited: C2 is built first and tested with internal checks; the comparison with an independent calculation became a fifth Change, C5 failure benchmark (Small), required before release. D4 settled: steps with at least one positive LRV accept failure inputs, and a failure removes only positive LRVs. Expected benchmark values come from an independent Monte Carlo script; domain experts sign off the C5 benchmark comparison before release. The vision's success criterion now covers results only. - [x] ADR-0001 records why worst-case ignores the failure duration and that failure comparison covers infection risk only. It cites the vision's quality goals by their new names. - [x] The vision template is copied into `docs/templates/`. Still to do: -- [ ] Make the MVP list and the roadmap map one to one, as the template asks: today MVP items 1 and 2 both fall into C1, item 4 is split between C2 and C3, and C4 has no MVP item. +- [ ] Make the MVP list and the roadmap map one to one, as the template asks: today MVP items 1 and 2 both fall into C1, item 4 is split between C2 and C3, and C4 and C5 have no MVP item. - [ ] Decide whether the roadmap file keeps the name `failure_roadmap.md` or becomes `docs/roadmap.md`, the name the framework's Roadmap stage expects. - [ ] Fix the site-specific workflow in the vision: only registered users have personal definitions, guests do not. - [ ] Replace "QMRA project team" in the ADR's Deciders line with names or roles. - [ ] Create the Spec issues for C1 and C2 and fill in their numbers in the roadmap. -- [ ] Settle the four unresolved decisions in the roadmap (benchmark tolerance, expected benchmark values, who accepts results, failure inputs for steps with mixed LRV signs). Owners: Wolfgang and Malte. +- [ ] Settle the three remaining open decisions in the roadmap: D1 benchmark tolerance, D2 who writes the independent benchmark script (ideally not the C2 author), D3 which one or two domain experts sign off the benchmark comparison. Owners: Wolfgang and Malte. None of them blocks the Specs of C1 and C2. - [ ] Get pull request #17 reviewed and merged. From b81a5035354dbf2c86646772df734eec1c995d35 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Thu, 24 Sep 2026 13:13:51 +0000 Subject: [PATCH 24/30] docs: add environments page for dev and production Describes where the application runs, its addresses and endpoints, a five-minute check for each environment, how a change reaches each one, and how the servers are reached. Checked on 24 September 2026 from the workflows, the workflow runs and requests to the public addresses. Notable findings: a merge into main deploys to production automatically, whatever files changed (the last push to main changed only a PR template and still deployed); dev follows whichever branch pushed last; the GitHub environments have no protection rules; /metrics is public on both environments. Facts that live outside the repository (who runs the servers, SSH access, backups) are left as a table to fill in. The architecture baseline now points to this page and lists where its section 2 differs from what the pipeline does: microk8s and Helm instead of Docker Compose, TLS defined in the Helm chart, tests on Python 3.11 against a 3.12.5 image. Co-Authored-By: Claude Sonnet 5 --- docs/architecture_baseline.md | 10 ++++ docs/environments.md | 102 ++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 docs/environments.md diff --git a/docs/architecture_baseline.md b/docs/architecture_baseline.md index f267b56..37195f8 100644 --- a/docs/architecture_baseline.md +++ b/docs/architecture_baseline.md @@ -32,6 +32,16 @@ The deployment passes `DOMAIN_NAME` into the container. The repository does not Django Prometheus middleware and URL endpoints expose metrics. `django-structlog` is used for request logging. `/health` and `/ready` force a database connection and return `Ok` when the check succeeds. +### Build and deployment process + +Deployment is done by the GitHub pipeline in `.github/workflows/`. It builds a Docker image, copies it to the server by SSH and installs it with Helm on microk8s (`infra/helm/qmra/`, one values file per environment). Every push to `main` deploys to dev and then to production, with no approval step and whatever files changed. Every push to a pull-request branch deploys to dev. The addresses, checks and full pipeline description are in [`environments.md`](environments.md). + +**Where this differs from section 2 above** + +- Section 2 describes Docker Compose. The pipeline deploys with microk8s and Helm. Compose is only used by the older scripts in `infra/deployment_scripts/` (for example `04_deploy_latest.sh` for `dev.qmra.org`). No workflow calls them. +- Section 2 says TLS is not defined in the repository. The Helm chart defines a Let's Encrypt issuer and a TLS ingress. +- The tests run on Python 3.11, and the Docker image uses Python 3.12.5. + ## 3. Main application flow The principal request path is: diff --git a/docs/environments.md b/docs/environments.md new file mode 100644 index 0000000..3bd180b --- /dev/null +++ b/docs/environments.md @@ -0,0 +1,102 @@ +# Environments: dev and production + +Status: draft, 2026-09-24. This page says where the QMRA application runs, how to reach it, how to check that it works, and how a change gets there. Facts marked "checked" were verified on 24 September 2026 from the repository, the GitHub workflow runs, and requests to the public addresses. Anything that lives outside the repository is listed as "to fill in" and is not guessed. + +## At a glance + +| | dev | production | +|---|---|---| +| Address | https://dev.qmra.org | https://qmra.org | +| Purpose | try a change before users see it; also receives every pull-request branch | the service users use | +| Server | its own server (checked: the two addresses resolve to different servers) | its own server | +| Debug mode | on | off | +| Replicas | 1 | 1 | +| TLS certificate | Let's Encrypt, renewed by cert-manager. Checked: valid 15 Aug to 13 Nov 2026 | Let's Encrypt, renewed by cert-manager. Checked: valid 22 Aug to 20 Nov 2026 | +| Monitoring address | https://dev2.qmra.org (redirects to a login page) | https://monitoring.qmra.org (redirects to a login page) | +| Deployed by | GitHub pipeline, on every push to `main` and every push to a pull-request branch | GitHub pipeline, on every push to `main` | + +## Addresses and endpoints + +Checked on 24 September 2026. The same paths exist on both addresses. + +| Path | What it is | Login needed | +|---|---|---| +| `/` | the assessment configurator; guests can run an assessment without saving | no | +| `/login`, `/register` | sign in, create an account | no | +| `/admin/login/` | Django admin for users and the default scientific data | admin account | +| `/health` | returns `Ok` when the application can reach its database | no | +| `/ready` | returns `Ok` when the application is ready for traffic | no | +| `/metrics` | Prometheus metrics of the application | no. **Checked: readable by anyone on dev and on production.** See "Open points" | + +## How to check that an environment works + +Five minutes, in this order. + +1. Health. Both must print `Ok`: + ```bash + curl -sS https://dev.qmra.org/health; echo + curl -sS https://dev.qmra.org/ready; echo + ``` + Use `https://qmra.org` for production. +2. Guest assessment. Open the address, keep the bundled defaults, run the assessment, and check that the result page shows both risk measures for the three reference pathogens. +3. Account. On dev, register a test account at `/register`, save an assessment, reopen it, and download its export package (a ZIP). Do not create test accounts or test data on production. +4. Certificate. The browser shows a valid certificate, and the dates above have not passed. +5. After a deploy, check that the change you expected is visible. + +## How a change gets there + +The pipeline is `.github/workflows/ci.yaml`. It has no path filter, so a change to documentation only runs the same jobs as a change to code. + +| What you do | Jobs that run | Where it ends up | Time | +|---|---|---|---| +| Push to a branch that has an open pull request into `main` | test, build-dev, deploy-dev | dev | about 4 minutes | +| Merge a pull request into `main` (a push to `main`) | test, build-dev, build-prod, deploy-dev, deploy-prod | dev, then production | about 5 minutes | + +- **No approval.** The GitHub environments `dev` and `prod` have no protection rules (checked): no required reviewer and no wait time. On the last push to `main`, the production deploy started 5 seconds after the dev deploy finished. +- **Documentation-only changes deploy too.** The last push to `main` (22 September) changed only `.github/pull_request_template.md` and still deployed to production. +- **Dev follows the last branch pushed.** The deploy step runs `git checkout` of the pushed branch on the dev server, so dev shows whichever pull request pushed last. Tell the team before you test a branch there. +- **Migrations run on every deploy.** Before the new web container starts, the Helm chart runs the database migrations for both SQLite databases. Nothing reverses them. +- **What a deploy does.** GitHub builds the Docker image `qmra:`, copies it to the server, imports it into microk8s and runs `helm upgrade --install` with `infra/helm/qmra/.values.yaml`. + +## How to reach the servers + +**The web application** is public at the addresses above. + +**The server shell.** The pipeline reaches each server by SSH. The values are GitHub environment secrets, which are not in the repository and can be read or changed only by a repository admin: + +| Secret | Holds | +|---|---| +| `DEPLOY_HOST` | server address | +| `DEPLOY_USER` | SSH user | +| `DEPLOY_SERVER_SSH_KEY` | the pipeline's private key | +| `DEPLOY_PATH` | folder with the checked-out repository | +| `APP_SECRET_KEY` | the Django secret key, set at deploy time | + +To fill in. None of this can be found from the repository: + +| Item | dev | production | +|---|---|---| +| Who runs the server and grants access | | | +| How a person gets an SSH account or key | | | +| Server address for people (the pipeline's `DEPLOY_HOST`) | | | +| Deploy folder | | | +| Where the databases are backed up, and how often | | | + +**Commands on the server.** These follow from the deploy script (`microk8s`, namespace `qmra`). They were not run for this page: + +```bash +microk8s kubectl -n qmra get pods +microk8s kubectl -n qmra logs deploy/qmra --tail=100 +microk8s helm -n qmra history qmra +``` + +The databases are on the server at `/var/lib/qmra/qmra.db` and `/var/lib/qmra/default_qmra_data.db`; collected static files are at `/var/cache/qmra/static`. + +## Open points + +- **Nothing stops a production deploy.** Any merge into `main` goes to production, finished or not. The failure roadmap needs a way around this (decision D5 in `docs/failure_roadmap.md`). +- **The metrics address is public** on both environments. It exposes request counts and timings. Decide whether it should be limited to the monitoring system. +- **No rollback is defined.** `helm` keeps revisions, but no workflow or page uses them, and migrations are not reversed. +- **One replica per environment.** A deploy probably interrupts the service for a short time. This was not measured. +- **Same release name and namespace on both servers** (`qmra`). This is safe only because the two environments run on different servers, which the "to fill in" table should confirm. +- **Python versions differ.** The tests run on Python 3.11, and the image uses Python 3.12.5. From 9b1fb20d368ca56228c7d2517a0c4254b76136cf Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Thu, 24 Sep 2026 13:13:51 +0000 Subject: [PATCH 25/30] docs: add roadmap decision D5 on keeping failure inputs hidden until release Every merge into main deploys to production without approval, so "nothing is deployed until C1-C5 are merged" cannot hold as written. D5 names the options (a setting that keeps the failure inputs off, an integration branch, or a required approval on the prod environment), blocks the C1 Spec and Release, and the Release dependency and sequence now refer to it. Co-Authored-By: Claude Sonnet 5 --- docs/failure_roadmap.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/failure_roadmap.md b/docs/failure_roadmap.md index ceca978..1751e3f 100644 --- a/docs/failure_roadmap.md +++ b/docs/failure_roadmap.md @@ -78,9 +78,10 @@ goals. - C3 depends on C1. - C4 depends on C2 (it explains the calculation's behaviour) and on C1. - C5 depends on C2 (it checks the implemented calculation). -- Release depends on C1–C5: nothing is deployed until all five are merged, so users never - see failure inputs without effect, results they cannot trace, or results not checked - against an independent calculation. +- Release depends on C1–C5: users must never see failure inputs without effect, results + they cannot trace, or results not checked against an independent calculation. Today every + merge into `main` deploys to production without approval (`docs/environments.md`), so how + this is enforced is open (D5). ## Parallel work @@ -97,7 +98,7 @@ goals. 2. `/implement` C1. 3. `/implement` C2; build C3 in parallel. 4. Build C4 and C5 once C2 is merged. -5. Release once C1–C5 are merged and D3 is settled. +5. Release once C1–C5 are merged, D3 is settled and the mechanism chosen in D5 is lifted. ## Unresolved decisions @@ -106,6 +107,7 @@ goals. | D1 | Tolerance for matching benchmark scenarios. With an independent Monte Carlo script (D2), differences are only sampling noise or real errors | Wolfgang, Malte | — | C5 | | D2 | Who calculates the expected values; ideally not the author of C2 | Wolfgang, Malte | — | C5 | | D3 | Which one or two domain experts sign off the C5 benchmark comparison | Wolfgang, Malte | — | Release | +| D5 | How failure inputs stay hidden from users until C1–C5 are done, because every merge into `main` deploys to production without approval (`docs/environments.md`). Options: a setting that keeps the failure inputs off until Release; a long-lived integration branch merged into `main` once (CI only runs for pull requests into `main`, so `ci.yaml` would have to change); or a required approval on the `prod` environment | Wolfgang, Malte | — | C1 Spec (a hiding setting is part of C1), Release | ## Resolved decisions From 400279d922d239d7b1fba749815d14d7f9fcab01 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Thu, 24 Sep 2026 13:15:59 +0000 Subject: [PATCH 26/30] docs: explain how to skip the pipeline [skip ci] Adds a section to the environments page on skipping the pipeline with [skip ci], including what to do when merging into main and the caveat about required checks. This commit itself carries the text, to check on a pull-request branch that no run starts. Co-Authored-By: Claude Sonnet 5 --- docs/environments.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/environments.md b/docs/environments.md index 3bd180b..80fce37 100644 --- a/docs/environments.md +++ b/docs/environments.md @@ -92,6 +92,16 @@ microk8s helm -n qmra history qmra The databases are on the server at `/var/lib/qmra/qmra.db` and `/var/lib/qmra/default_qmra_data.db`; collected static files are at `/var/cache/qmra/static`. +## Skipping the pipeline + +Put `[skip ci]` in a commit message and GitHub does not start the pipeline for that push. This is GitHub's documented behaviour. `[ci skip]`, `[no ci]`, `[skip actions]` and `[actions skip]` work the same way, and so does a final `skip-checks: true` line. + +- **On a pull-request branch:** use it in the last commit of the push. Nothing deploys to dev. The latest commit then has no test result. If a required check ever blocks the merge, push one more commit without the text. +- **When merging into `main`:** write it in the description box of the merge dialog. Use "Create a merge commit" or "Squash and merge", because "Rebase and merge" creates no commit whose message you can edit. Nothing deploys to dev or production. +- **Do not use it** when a change should really be tested and deployed. Skipped changes are deployed together with the next push that is not skipped. + +Status: not yet checked in this repository. + ## Open points - **Nothing stops a production deploy.** Any merge into `main` goes to production, finished or not. The failure roadmap needs a way around this (decision D5 in `docs/failure_roadmap.md`). From 972109c5f8d2cac36cf7b47f1abe8ed579f084f8 Mon Sep 17 00:00:00 2001 From: yaniv-kwb Date: Thu, 24 Sep 2026 13:16:37 +0000 Subject: [PATCH 27/30] docs: record the result of the [skip ci] check [skip ci] The previous commit carried [skip ci] and started no workflow run and no check on the pull-request branch. Not yet checked on a merge into main. Co-Authored-By: Claude Sonnet 5 --- docs/environments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/environments.md b/docs/environments.md index 80fce37..99c63e4 100644 --- a/docs/environments.md +++ b/docs/environments.md @@ -100,7 +100,7 @@ Put `[skip ci]` in a commit message and GitHub does not start the pipeline for t - **When merging into `main`:** write it in the description box of the merge dialog. Use "Create a merge commit" or "Squash and merge", because "Rebase and merge" creates no commit whose message you can edit. Nothing deploys to dev or production. - **Do not use it** when a change should really be tested and deployed. Skipped changes are deployed together with the next push that is not skipped. -Status: not yet checked in this repository. +Status: checked on a pull-request branch on 24 September 2026 (commit `400279d`): no run and no check started. Not yet checked on a merge into `main`; check the Actions tab right after the first time you do it. ## Open points From 7e5b6968a3de1d5098e89683ae5fed50fba29251 Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Thu, 24 Sep 2026 13:46:53 +0000 Subject: [PATCH 28/30] docs: settle D5, no merge of failure work into main before the release C1-C5 stay open pull requests, built on each other and tested on the dev environment, and are merged into main together at the release, since every merge into main deploys to production. Record the rule in the roadmap, environments page and CLAUDE.md. Co-Authored-By: Claude Opus 5.5 (1M context) --- CLAUDE.md | 7 +++++++ docs/environments.md | 2 +- docs/failure_roadmap.md | 32 +++++++++++++++++++++++--------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c8a66b3..ccff4ba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,3 +18,10 @@ sudo controlled-dev-git push # first push of a new branch, sets the u Run these from inside this project workspace. `git commit`, `git switch`, `git status`, `git log`, and other purely local commands are unaffected and need no special handling. + +## Failure work stays out of `main` until the release + +Pull requests for the Changes in `docs/failure_roadmap.md` (C1–C5) stay open until the +release: keep each one up to date with `main` and test it on the dev environment. Every +merge into `main` deploys straight to production, so merge them only when the user starts +the release (roadmap decision D5). diff --git a/docs/environments.md b/docs/environments.md index 99c63e4..e446744 100644 --- a/docs/environments.md +++ b/docs/environments.md @@ -104,7 +104,7 @@ Status: checked on a pull-request branch on 24 September 2026 (commit `400279d`) ## Open points -- **Nothing stops a production deploy.** Any merge into `main` goes to production, finished or not. The failure roadmap needs a way around this (decision D5 in `docs/failure_roadmap.md`). +- **Nothing stops a production deploy.** Any merge into `main` goes to production, finished or not. The failure roadmap works around this by merging nothing into `main` until the release (decision D5 in `docs/failure_roadmap.md`). - **The metrics address is public** on both environments. It exposes request counts and timings. Decide whether it should be limited to the monitoring system. - **No rollback is defined.** `helm` keeps revisions, but no workflow or page uses them, and migrations are not reversed. - **One replica per environment.** A deploy probably interrupts the service for a short time. This was not measured. diff --git a/docs/failure_roadmap.md b/docs/failure_roadmap.md index 1751e3f..b924a7a 100644 --- a/docs/failure_roadmap.md +++ b/docs/failure_roadmap.md @@ -79,17 +79,18 @@ goals. - C4 depends on C2 (it explains the calculation's behaviour) and on C1. - C5 depends on C2 (it checks the implemented calculation). - Release depends on C1–C5: users must never see failure inputs without effect, results - they cannot trace, or results not checked against an independent calculation. Today every - merge into `main` deploys to production without approval (`docs/environments.md`), so how - this is enforced is open (D5). + they cannot trace, or results not checked against an independent calculation. Because + every merge into `main` deploys to production without approval (`docs/environments.md`), + merging any of C1–C5 into `main` before the release is not allowed (D5). Each Change stays an open pull + request, built on the pull request it depends on, and is tested on the dev environment. ## Parallel work - The Specs of C1 and C2 can be written in parallel; both rest on `CONTEXT.md` and ADR-0001. -- After C1 is merged, C2 and C3 can be built in parallel. -- C4 can be drafted while C2 is being built and finished once C2 is merged. +- Once C1 is done, C2 and C3 can be built in parallel, both on top of C1's pull request. +- C4 can be drafted while C2 is being built and finished once C2 is done. - The benchmark scenarios of C5 and their expected values (D2) can be prepared while C2 is - being built; only the comparison tests need C2 merged. + being built; only the comparison tests need C2 done. - C4 and C5 can be built in parallel. ## Sequence @@ -97,8 +98,15 @@ goals. 1. `/to-spec` C1 and C2 (in parallel). 2. `/implement` C1. 3. `/implement` C2; build C3 in parallel. -4. Build C4 and C5 once C2 is merged. -5. Release once C1–C5 are merged, D3 is settled and the mechanism chosen in D5 is lifted. +4. Build C4 and C5 once C2 is done. +5. Release: once C1–C5 are done and tested together on the dev environment and D3 is + settled, merge all five pull requests into `main` together. That deploys them to + production. + +"Done" means the pull request is reviewed and tested on the dev environment, but not merged. +The dev environment shows only the pull request pushed last, so testing there is announced +to the team first. Each open pull request is kept up to date with `main` to limit +conflicts at the release. ## Unresolved decisions @@ -107,7 +115,6 @@ goals. | D1 | Tolerance for matching benchmark scenarios. With an independent Monte Carlo script (D2), differences are only sampling noise or real errors | Wolfgang, Malte | — | C5 | | D2 | Who calculates the expected values; ideally not the author of C2 | Wolfgang, Malte | — | C5 | | D3 | Which one or two domain experts sign off the C5 benchmark comparison | Wolfgang, Malte | — | Release | -| D5 | How failure inputs stay hidden from users until C1–C5 are done, because every merge into `main` deploys to production without approval (`docs/environments.md`). Options: a setting that keeps the failure inputs off until Release; a long-lived integration branch merged into `main` once (CI only runs for pull requests into `main`, so `ci.yaml` would have to change); or a required approval on the `prod` environment | Wolfgang, Malte | — | C1 Spec (a hiding setting is part of C1), Release | ## Resolved decisions @@ -125,3 +132,10 @@ goals. reviewing the C5 benchmark comparison and confirming it in writing, for example in the C5 pull request. The explanations in C4 are not part of this sign-off. Who signs off is still open (D3). +- **D5: no merge into `main` before the release (2026-09-24).** Merging any of C1–C5 into + `main` before the release is not allowed. They stay open pull requests, each + built on the one it depends on and tested on the dev environment, and are merged into + `main` together at the release. Rejected: a setting that hides failures in production + (extra code in C1), and a required approval for production deploys (would block urgent + fixes on `main`). Accepted cost: long-lived branches and conflicts to resolve with + `main`. From 2f86d7f19ca3b5342debbb578fa15f24a163d50c Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Thu, 24 Sep 2026 13:54:52 +0000 Subject: [PATCH 29/30] docs: add draft spec for C1 failure inputs Spec for the first failure roadmap Change: failure frequency and failure duration per treatment step and on personal treatment steps, shared validation including D4, HTTP-level test seam with a manual dev-environment check, and acceptance criteria mapped to the vision's quality goals. Link it from the roadmap. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/failure_roadmap.md | 2 +- docs/specs/c1-failure-inputs.md | 230 ++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 docs/specs/c1-failure-inputs.md diff --git a/docs/failure_roadmap.md b/docs/failure_roadmap.md index b924a7a..238239a 100644 --- a/docs/failure_roadmap.md +++ b/docs/failure_roadmap.md @@ -9,7 +9,7 @@ built directly. Changes are refined into tickets later. All Changes start from ` ### C1 Failure inputs — Large -Spec issue: #___ +Spec issue: #___ (draft: `docs/specs/c1-failure-inputs.md`) Purpose: users can enter a failure frequency and a failure duration for each treatment step, and they are stored with the scenario. diff --git a/docs/specs/c1-failure-inputs.md b/docs/specs/c1-failure-inputs.md new file mode 100644 index 0000000..c8e50af --- /dev/null +++ b/docs/specs/c1-failure-inputs.md @@ -0,0 +1,230 @@ +# Spec C1: Failure inputs + +Status: draft, 2026-09-24. Change C1 in `docs/failure_roadmap.md` (Large). Spec issue: #___ +Sources: `docs/vision.md` (MVP item 1 and 2), `CONTEXT.md`, ADR-0001, roadmap decisions D4 +and D5. + +## Problem Statement + +A utility operator or engineer who wants to know how failures of their treatment steps +affect the yearly risk has nowhere to say how often a step fails and for how long. The +assessment configurator only knows LRVs, and a personal treatment step only stores a name +and LRVs. Before the calculation can include treatment failures (C2), users need a place to +enter a failure frequency and a failure duration for each treatment step, and the +application needs to keep those values with the scenario. + +## Solution + +Every treatment step in the assessment configurator gets two more fields next to its LRVs: +**failure frequency** (failure days per year) and **failure duration** (minutes). They +start at 0 days per year and 30 minutes, so a step does not fail unless the user says so. +The application checks the values when the assessment is run or saved and explains what is +wrong. A step can only have a failure frequency above 0 if at least one of its LRVs is +positive, since a failure removes only positive LRVs (D4). + +Registered users can also store both values on a personal treatment step. When they add +that step to an assessment, its failure frequency and failure duration are copied in like +its LRVs. Saved assessments keep the values, and reopening or editing an assessment shows +them again. Assessments saved before this change reopen with failure frequency 0 and +failure duration 30, so nothing about them changes. + +In this Change the values do not yet affect the result. The calculation follows in C2, and +nothing reaches production before C1–C5 are released together (D5). + +## User Stories + +1. As a utility operator, I want to enter a failure frequency for each treatment step in my + assessment, so that the assessment reflects how often each step fails at my plant. +2. As a utility operator, I want to enter a failure duration for each treatment step, so + that the assessment reflects how long a typical failure event lasts. +3. As a user, I want the failure frequency to be given in failure days per year, so that I + can use the numbers from my plant's operating records directly. +4. As a user, I want the failure duration to be given in minutes, so that short failure + events can be expressed without fractions. +5. As a user, I want the unit of each failure field shown next to it, so that I do not + enter hours instead of minutes or events instead of days. +6. As a user, I want every treatment step to start with failure frequency 0, so that an + assessment without failure information behaves as before. +7. As a user, I want every treatment step to start with failure duration 30 minutes, so + that I only change the duration when I know a better value. +8. As a user, I want to enter a fractional failure frequency such as 0.5 days per year, so + that I can describe a step that fails once every two years. +9. As a user, I want a failure frequency below 0 or above 365 to be rejected with a clear + message, so that I notice a typing mistake. +10. As a user, I want a failure duration below 1 or above 1440 minutes, or a non-whole + number of minutes, to be rejected with a clear message, so that I notice a typing + mistake or a wrong unit. +11. As a user, I want to be told when I give a failure frequency above 0 to a step that has + no positive LRV, so that I understand that such a step cannot fail. +12. As a user modelling recontamination with a negative LRV, I want that step to keep + failure frequency 0, so that recontamination is never treated as a failing step. +13. As a user, I want a step with a positive LRV for one pathogen group and a zero or + negative LRV for another to accept failure inputs, so that steps like chlorination + (protozoa minimum 0) or a storage tank with regrowth can fail. +14. As a user, I want the failure fields to appear in the same card as the step's LRVs, so + that I see all values of a step together. +15. As a user, I want the failure values of each step to stay with that step when I + reorder or remove other steps, so that values do not move to the wrong step. +16. As a user adding the same bundled step twice, I want to give each copy its own failure + values, so that two parallel units can have different failure records. +17. As a guest, I want to enter failure inputs and run an assessment, so that I can try the + feature without an account. +18. As a guest, I want invalid failure inputs to be rejected in the same way as for + registered users, so that the rules do not depend on having an account. +19. As a registered user, I want the failure values of each step saved with my assessment, + so that I do not have to enter them again. +20. As a registered user, I want to see the saved failure values when I reopen an + assessment, so that I can check what the assessment is based on. +21. As a registered user, I want to change failure values when I edit an assessment and + have the new values saved, so that I can correct or update them. +22. As a registered user, I want assessments I saved before this change to reopen with + failure frequency 0 and failure duration 30, so that their results do not change. +23. As a registered user, I want to store a failure frequency and failure duration on a + personal treatment step, so that my plant's failure record is reused in every + assessment that uses the step. +24. As a registered user, I want the same checks on a personal treatment step as in the + configurator, so that a personal step never carries values an assessment would reject. +25. As a registered user, I want a personal treatment step's failure values copied into the + assessment when I add the step, so that I do not type them again. +26. As a registered user, I want to change the copied failure values in one assessment + without changing the personal treatment step, so that I can try other values in one + scenario. +27. As a registered user, I want personal treatment steps I created before this change to + offer failure frequency 0 and failure duration 30, so that they keep working. +28. As a registered user, I want my personal treatment steps and their failure values to be + visible only to me, so that my plant's failure record stays private. +29. As a user comparing scenarios, I want two saved assessments to differ only in their + failure inputs, so that I can later compare the effect of different failure + frequencies. +30. As a user, I want bundled treatment steps to come without failure values, so that I am + not given literature failure data the application does not have. +31. As a developer of C2, I want the failure frequency and failure duration of every step in + an assessment to be available where the calculation reads the treatment train, so that + C2 only has to change the calculation. +32. As a developer of C3, I want the stored failure values to be readable from a saved + assessment, so that C3 can add them to the export package and report. + +## Implementation Decisions + +- **Two new attributes on two kinds of treatment step.** The treatment step of an + assessment and the personal treatment step each get *failure frequency* (a decimal + number of failure days per year) and *failure duration* (a whole number of minutes). + Both are required, with defaults 0 and 30. A schema migration adds them to both, and + existing rows receive the defaults. +- **Bundled treatment steps are unchanged.** They carry no failure data (failure inputs are + user input only). A bundled step added to an assessment starts at the defaults. +- **One validation rule, used by both forms.** The treatment step form of the configurator + and the personal treatment step form apply the same rule, defined once: + - failure frequency from 0 to 365 inclusive, decimals allowed; + - failure duration a whole number from 1 to 1440 inclusive; + - failure frequency above 0 only if at least one of the step's six LRVs (minimum and + maximum for bacteria, viruses and protozoa) is above 0; an empty LRV counts as 0 (D4). + The error is attached to the failure frequency field. + The duration range is checked even when the frequency is 0, so stored values are always + valid. The existing minimum ≤ maximum LRV check, which both forms currently duplicate, + may move into the same shared rule. +- **Configurator.** Each treatment step card shows the two fields below its LRV rows, + labelled with their units ("Failure frequency (days per year)", "Failure duration + (minutes)"). The fields are always shown; the D4 rule is enforced by validation, since the + user can change LRVs after entering a frequency. Guests see and use the same fields. +- **Saving and reopening.** The treatment step formset saves the two values with each step, + and reopening an assessment fills them in again. Each value belongs to its step, not to a + position in the train. +- **Personal treatment steps.** The personal treatment step form gets the two fields. The + list of personal treatment steps that the configurator loads includes both values, and + the configurator copies them into a newly added step together with its LRVs. The copy is + independent: editing it in an assessment leaves the personal treatment step unchanged. +- **Guest path.** The guest result submits the same treatment step formset. Failure values + are validated there too and are not stored. +- **No effect on results.** The calculation, the result page and the export package are not + changed in C1. The values are stored so that C2 and C3 can use them. +- **Delivery.** C1 stays an open pull request and is tested on the dev environment; it is + not merged into `main` before the release (D5). C2 and C3 build on it. + +## Testing Decisions + +- **What a good test is.** A test drives the application from the outside the way a browser + does and checks what a user or a later request can observe: the response, the saved + assessment, the reopened form, the list of personal treatment steps. It does not check + how validation is organised internally. +- **One seam: HTTP through the Django test client.** Tests post the configurator form (save + and guest result) and the personal treatment step form, then read the saved assessment, + the reopened configurator or the personal treatment step list. This covers form + validation, saving, reopening and ownership without a separate form-level seam. +- **Behaviour covered at that seam:** + - saving an assessment with failure inputs and reopening it returns the same values; + editing and saving again stores the new values; + - out-of-range frequency or duration, a non-whole duration, and a frequency above 0 on a + step without a positive LRV are rejected with a message, on the saved configurator, on + the guest result and on the personal treatment step form; + - a step whose only positive LRV is one maximum (for example protozoa 0–2) accepts a + frequency above 0; a step with only negative or empty LRVs does not; + - a guest result with valid failure inputs is accepted; + - a personal treatment step saved with failure inputs appears with them in its owner's + list; another user's list does not contain it; + - an assessment and a personal treatment step created without failure values (as before + the migration) come back with frequency 0 and duration 30; + - the result of an assessment is identical with and without failure inputs (C1 does not + change the calculation). +- **Prior art.** The form tests in the risk-assessment test module (min ≤ max, negative LRVs + allowed) show the validation cases to extend; the calculation regression test shows how + fixed results are compared. There are no HTTP-level tests yet; C1 adds the first ones, + using Django's test client and a logged-in test user. +- **Manual check on the dev environment.** Copying a personal treatment step's failure + values into the configurator happens in the browser and is not covered by the HTTP seam. + Before C1 is done, a tester on the dev environment creates a personal treatment step with + failure inputs, adds it to an assessment, sees the copied values, changes them, saves, + reopens, and confirms the personal treatment step itself is unchanged. +- The existing test suite passes unchanged. + +## Acceptance criteria + +1. Each treatment step in the configurator, for guests and registered users, shows failure + frequency and failure duration with their units, starting at 0 and 30. +2. The validation rules above reject invalid values with a message on the configurator, + the guest result and the personal treatment step form, and accept every valid + combination, including the D4 cases. +3. Failure values survive save, reopen and edit of an assessment, per step. +4. Personal treatment steps store failure values, the configurator copies them into an + added step, and the copy can be changed independently (manual check on the dev + environment). +5. Assessments and personal treatment steps that existed before C1 read back failure + frequency 0 and failure duration 30. +6. Results and export packages are the same as before C1 for every assessment. +7. All HTTP-level tests listed above pass, and the existing suite passes unchanged. + +Quality goals from `docs/vision.md`, one criterion each: + +| Quality goal | Criterion in C1 | +|---|---| +| Input validity | Every failure input shows its unit, and a value outside 0–365 days per year or 1–1440 whole minutes, or a frequency above 0 on a step without a positive LRV, is rejected with a message (criteria 1–2). | +| Reproducibility | Reopening a saved assessment returns exactly the stored failure values, and assessments from before C1 keep their results, since they read back frequency 0 (criteria 3, 5, 6). | +| Export stability | The export package of any assessment is unchanged by C1 (criterion 6); adding failure values to it is C3's job. | +| Privacy of registered-user data | A personal treatment step and its failure values appear only in its owner's list; a test requests the list as another user and finds nothing of it (criterion 7). | + +Not relevant to C1: *Scientific correctness* (the calculation is untouched; C2 and C5) and +*Traceability* (showing failure inputs in the export package and report is C3). + +## Out of Scope + +- Any effect of failure inputs on the calculation, the result page or the reference-level + exceedance (C2). +- Failure inputs in the export package and the HTML report (C3). +- Explanations of failure frequency, failure duration and the two cases beyond field + labels and units: FAQ, guided tour, Sphinx documentation (C4). +- Benchmark scenarios (C5). +- Failure data for bundled treatment steps. +- Partial loss of removal (not planned). +- Hiding the failure fields in production; D5 keeps C1 out of `main` instead. +- A browser test setup; the one browser-only behaviour is checked manually. +- The naming follow-ups in `docs/domain-model-open-questions.md` and the deferred UI + wording. + +## Further Notes + +- Until C2 is done, failure inputs have no effect on results. That is acceptable only + because of D5: C1 stays out of `main` and production until the release. +- While exploring: saving a personal treatment step checks the name for uniqueness against + the user's personal *exposures*, not their personal treatment steps. That is an existing + bug outside this Change; it is noted here so it is not mistaken for C1 behaviour. +- The open roadmap decisions D1–D3 do not affect C1. From f5c9336925bb10a41d5aabe09e4e8dd8eb530c1b Mon Sep 17 00:00:00 2001 From: ma-z-am Date: Thu, 24 Sep 2026 14:09:17 +0000 Subject: [PATCH 30/30] docs: add draft spec for C2 failure calculation [skip ci] Spec for the failure calculation: failure days per exposure event with shared random draws, worst-case full loss of positive LRVs, best-case mixed-water assumption with the lowest-concentration arrangement, and frequency 0 reproducing today's results. Tests at the calculation seam with closed-form cases. Record the arrangement rule for three or more failing steps in CONTEXT.md and the failure approach. Co-Authored-By: Claude Opus 5.5 (1M context) --- CONTEXT.md | 2 +- docs/QMRA_Failure_Approach.md | 5 +- docs/failure_roadmap.md | 2 +- docs/specs/c2-failure-calculation.md | 231 +++++++++++++++++++++++++++ 4 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 docs/specs/c2-failure-calculation.md diff --git a/CONTEXT.md b/CONTEXT.md index 99f147c..f44d7be 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -175,7 +175,7 @@ The best-case assumption that water consumed on a failure day is a mixture of wa _Avoid_: mixed concentration, dilution, storage effect **Combined failure**: -Failure events in two or more treatment steps on the same day. Worst-case loses all their LRVs for the whole day. Under the mixed-water assumption the failure events are assumed to overlap as little as possible within the day: not at all if their durations fit into one day, otherwise only by the minutes that exceed a day. +Failure events in two or more treatment steps on the same day. Worst-case loses all their LRVs for the whole day. Under the mixed-water assumption the failure events are arranged within the day so that the mixed water has the lowest pathogen concentration. This means they do not overlap if their durations fit into one day, and otherwise overlap only by the minutes that exceed a day, shared by the steps whose joint loss matters least. **Next phase**: Capabilities identified for future development and not present in the application today. diff --git a/docs/QMRA_Failure_Approach.md b/docs/QMRA_Failure_Approach.md index 35b4b3e..8dd2dec 100644 --- a/docs/QMRA_Failure_Approach.md +++ b/docs/QMRA_Failure_Approach.md @@ -116,7 +116,10 @@ is recorded in `docs/adr/0001-failure-days-drawn-per-exposure-event.md`. 120 min give 4.2 % of the day at LRV 6, 8.3 % at LRV 6 and 87.5 % at LRV 10, a mixed LRV of about 6.9. UV and UF each down 900 min overlap by 360 min: 25 % at LRV 2 and 2 × 37.5 % at LRV 6, a mixed LRV of about 2.6. Worst-case loses all failing steps' - LRVs for the whole day. + LRVs for the whole day. With three or more failing steps, the unavoidable overlap is + placed on the steps whose joint loss gives the lowest mixed concentration; in general, + best-case arranges the failure events of a day so that the mixed water has the lowest + concentration, which always implies minimal overlap (decided 2026-09-24). 7. **Results.** Only the result including failures is shown, and the reference-level exceedance is taken on it. The effect of failures is seen by comparing scenarios with different failure inputs in the assessment comparison view. That view compares annual diff --git a/docs/failure_roadmap.md b/docs/failure_roadmap.md index 238239a..c6a25f7 100644 --- a/docs/failure_roadmap.md +++ b/docs/failure_roadmap.md @@ -26,7 +26,7 @@ beyond field labels and units (C4). ### C2 Failure calculation — Large -Spec issue: #___ +Spec issue: #___ (draft: `docs/specs/c2-failure-calculation.md`) Purpose: results include treatment failures as decided in ADR-0001. diff --git a/docs/specs/c2-failure-calculation.md b/docs/specs/c2-failure-calculation.md new file mode 100644 index 0000000..093203e --- /dev/null +++ b/docs/specs/c2-failure-calculation.md @@ -0,0 +1,231 @@ +# Spec C2: Failure calculation + +Status: draft, 2026-09-24. Change C2 in `docs/failure_roadmap.md` (Large). Spec issue: #___ +Sources: `docs/vision.md` (MVP item 3 and 4), `CONTEXT.md`, ADR-0001, the decisions in +`docs/QMRA_Failure_Approach.md`, roadmap decisions D4 and D5. Builds on C1 +(`docs/specs/c1-failure-inputs.md`). + +## Problem Statement + +After C1, users can enter a failure frequency and a failure duration for each treatment +step, but the result ignores them: an assessment with a UV unit failing 10 days per year +gives the same annual probability of infection as one where it never fails. A utility +operator therefore still cannot see how failures of their treatment steps affect the +yearly risk, whether the reference level is still met, or how two failure scenarios +compare. + +## Solution + +The risk calculation takes treatment failures into account as decided in ADR-0001. In the +Monte Carlo simulation, every simulated exposure event falls on a failure day of each +treatment step with probability failure frequency / 365. On a failure day: + +- **worst-case** calculates the event with the failing steps' positive LRVs removed for the + whole day, whatever the failure duration; +- **best-case** applies the mixed-water assumption: the consumed water is a mixture of water + treated during the failure events and water treated in normal operation, in proportion to + the failure durations, with the failure events arranged within the day so that the mixed + water has the lowest pathogen concentration. + +LRVs of 0 or below are never affected (D4). The result page, the reference-level +exceedance, the saved results and the result table in the export package all show the +result including failures, with no change to their layout. An assessment in which every +failure frequency is 0 gives exactly the same results as before C2. + +## User Stories + +1. As a utility operator, I want the result to include the failures I entered, so that I + see the yearly risk my plant actually runs. +2. As a utility operator, I want the reference-level exceedance to be judged on the result + including failures, so that I learn whether failures push a pathogen over the reference + level. +3. As a utility operator, I want both risk measures (annual probability of infection and + DALYs per person per year) to include failures, so that I can judge the health burden as + well as the infection risk. +4. As a user, I want worst-case to assume that all water consumed on a failure day was + treated during the failure, so that worst-case stays the cautious estimate. +5. As a user, I want best-case to assume mixed water on a failure day, weighted by the + failure duration, so that best-case reflects storage that dilutes failure water. +6. As a user, I want a longer failure duration to raise the best-case risk and leave the + worst-case risk unchanged, so that the two cases behave as documented. +7. As a user, I want a higher failure frequency never to lower the risk, so that the results + make sense when I try different values. +8. As a user, I want worst-case risk never to be below best-case risk, so that the two cases + keep their meaning with failures. +9. As a user, I want the number of failure days to vary between simulated years, so that + the risk distribution shows years with more or fewer failures, as real years do. +10. As a user with an exposure of few events per year, I want a low failure frequency to + show up as an upper tail of the risk distribution, so that I see the rare bad years. +11. As a user with several failing steps, I want failures of different steps on the same + day to be combined, so that combined failures are not missed. +12. As a user, I want a combined failure in best-case to assume that failure events do not + overlap when they fit into one day, so that best-case stays the favourable assumption. +13. As a user, I want combined failure events longer than a day in total to overlap only as + much as necessary, on the steps whose joint loss matters least, so that best-case stays + favourable without a jump at 24 hours. +14. As a user, I want a failure to remove only the positive LRVs of a step, so that a step + with regrowth (a negative LRV) in one pathogen group does not improve during a failure. +15. As a user, I want a pathogen group whose LRV at a step is 0 or empty to be unaffected by + that step's failure, so that failures only matter where the step removes pathogens. +16. As a user, I want every failure frequency at 0 to give exactly the results I got before, + so that existing assessments and comparisons keep their numbers. +17. As a registered user, I want saved assessments to store the result including failures, + so that the saved-assessments page and the assessment comparison show it. +18. As a user comparing two saved assessments that differ only in their failure inputs, I + want the comparison to show different infection risks, so that I see what the failures + cost. +19. As a registered user, I want the export package's result table to contain the result + including failures, so that my report matches the result page. +20. As a guest, I want the unsaved result to include failures in the same way, so that the + result does not depend on having an account. +21. As a user, I want the same assessment to give the same result every time it is + calculated, so that I can reproduce and discuss a result. +22. As a reviewer, I want the calculation to follow ADR-0001 and the documented decisions, + so that the result can be checked against an independent calculation (C5). +23. As a user, I want the result page to look as before, so that I do not need to learn a + new layout. +24. As a user, I want the calculation with failures to finish in about the same time as + before, so that running an assessment stays quick. +25. As a developer of C5, I want the calculation to be deterministic for a given scenario, + so that benchmark comparisons are stable. +26. As a developer of C4, I want the calculation's rules to be stated in the Spec in plain + terms, so that the explanations for users can be written from it. + +## Implementation Decisions + +- **Only the calculation module changes.** Its public entry point keeps its interface: it + receives an assessment, its inflow concentrations and its treatment steps, and returns one + result per reference pathogen. It now reads each treatment step's failure frequency and + failure duration (stored by C1). The result record, the result page, saving and the export + are not changed; they show whatever the calculation returns. +- **What a failure removes (D4).** For each treatment step and pathogen group, worst-case + can lose the step's minimum LRV and best-case its maximum LRV, each only if it is above 0. + LRVs of 0, below 0 or empty stay in place during a failure. +- **Failure days per exposure event (ADR-0001).** For every simulated exposure event of every + simulated year, and for every treatment step with failure frequency above 0, the step + fails that day if a uniform random number is below failure frequency / 365. The same + random numbers are used for both cases and all reference pathogens. As a consequence, + raising a failure frequency only ever adds failure days, and worst-case and best-case see + the same failure days. +- **Worst-case on a failure day.** The event's LRV is the worst-case LRV of the train minus + the removable minimum LRVs of all steps failing that day. The failure duration is not + used. +- **Best-case on a failure day (mixed-water assumption).** The day is split into time + segments by which failing steps are down. The event's LRV is −log₁₀ of the time-weighted + average of 10^(−LRV of each segment), where a segment's LRV is the best-case LRV of the + train minus the removable maximum LRVs of the steps down in it. The failure events of the + day are arranged within 1,440 minutes so that this average concentration is lowest. This + gives no overlap when the durations fit into one day and otherwise the minimal overlap, + placed on the steps whose joint loss matters least (`CONTEXT.md`, *Combined failure*). For + a single failing step it equals Eq. 5 of the failure approach. With the few failing steps + per day that occur in practice, the arrangement can be found exactly, for example by + choosing the time share of each combination of failing steps. +- **Concentration sampling unchanged.** The sampled inflow concentrations and the choice of + which sample each simulated event uses stay exactly as today, including the fixed seed. + The failure draws come from their own fixed-seed random generator. When all failure + frequencies are 0, no failure draws are made and the results are identical to today's. +- **Dose and yearly risk.** Each event's dose follows from its sampled concentration, its + LRV (normal, or reduced as above) and the volume per event, and the yearly risk combines + the events of a simulated year as today. The summary statistics and the reference-level + exceedance (on the mean) are computed as today, now from the result including failures. +- **Saved results.** Saving an assessment recalculates and stores its results as today. + Results stored before C2 stay valid: every assessment in production has failure frequency + 0 at the release, so its stored results equal what C2 calculates. +- **Speed.** When failures are present, the dose-response is evaluated per simulated event + instead of per concentration sample. The work per failure day depends only on which steps + fail, so repeated combinations can be reused. The calculation time for a drinking-water + scenario (365 events per year) with failures is measured on the dev environment and + recorded in the pull request; the vision sets no response-time target yet. +- **Unchanged warnings.** The warning for a maximum LRV above 6 is not changed. +- **Delivery.** C2 is built on C1's pull request, stays open and is tested on the dev + environment; it is not merged into `main` before the release (D5). + +## Testing Decisions + +- **What a good test is.** A test gives the calculation a complete scenario (inflow + concentrations, treatment steps with failure inputs, exposure) and checks the returned + results: mean, median, quartiles, minimum and maximum per reference pathogen, for both + cases and both risk measures, and the reference-level exceedance. It does not inspect + intermediate arrays or random draws. +- **One seam: the calculation's public entry point**, called the way the existing + calculation tests call it. The result page, saving and export only pass results through, + and C1's HTTP tests already show that failure inputs arrive at the calculation. C2 adds no + HTTP tests. +- **Behaviour covered at that seam** (internal checks; the independent benchmark is C5): + - **No failures:** with every failure frequency at 0, all results equal today's exactly, + including the existing regression test's numbers. + - **Always failing, worst-case:** a step with failure frequency 365 gives the same + worst-case results as the scenario without that step's positive minimum LRVs. + - **Always failing, full day, best-case:** failure frequency 365 and duration 1,440 give + the same best-case results as the scenario without that step's positive maximum LRVs. + - **Always failing, part of the day, best-case:** failure frequency 365 and duration 60 + give the same best-case results as the scenario with the train's best-case LRV replaced + by the constant mixed LRV from Eq. 5, computed by hand in the test. + - **Combined failures:** two steps always failing with 900 + 900 minutes equal the + hand-computed constant LRV with 360 minutes of overlap; three steps always failing with + durations above a day in total equal the hand-computed lowest-concentration arrangement. + - **Monotonicity:** raising a failure frequency never lowers any mean; raising a failure + duration never lowers a best-case mean and leaves worst-case unchanged. + - **Case order:** worst-case mean ≥ best-case mean for every pathogen and risk measure, + with and without failures. + - **D4:** a step with a negative LRV for one pathogen group and a positive LRV for another + leaves the first group's results unchanged when it fails; a group with LRV 0 is + unaffected. + - **Reproducibility:** calculating the same scenario twice gives identical results. +- **Prior art.** The existing calculation tests: a scenario built from bundled data and + passed to the calculation, and the regression test comparing fixed numbers. +- **Manual check on the dev environment.** Enter a scenario with failures in the + configurator, run it as a guest and as a registered user, and confirm the result page, + the saved-assessments page and the export's result table show the changed results. Record + the calculation time. +- The existing test suite passes unchanged. + +## Acceptance criteria + +1. The calculation follows the rules above: failure days per exposure event, worst-case + full loss, best-case mixed-water assumption with the lowest-concentration arrangement, + and only positive LRVs lost. +2. With every failure frequency at 0, all results equal today's exactly. +3. All tests listed above pass, and the existing suite passes unchanged. +4. The result page, the reference-level exceedance, saved results, the assessment comparison + and the export's result table show the result including failures, with unchanged + layout (manual check on the dev environment). +5. The same scenario gives identical results on every calculation. +6. The calculation time with failures is measured and recorded in the pull request. + +Quality goals from `docs/vision.md`, one criterion each: + +| Quality goal | Criterion in C2 | +|---|---| +| Scientific correctness | The internal checks pass, including the closed-form cases (always failing steps compared with hand-computed LRVs) and the frequency-0 regression (criteria 2–3). Agreement with an independent calculation is C5. | +| Reproducibility | A scenario gives identical results on every calculation because the failure draws use their own fixed seed, and assessments without failures keep their exact results (criteria 2, 5). | +| Export stability | The export package keeps its files and columns; only the result values of assessments with failures change (criterion 4). | + +Not relevant to C2: *Input validity* (C1), *Traceability* (C3), *Privacy of registered-user +data* (no change to access). + +## Out of Scope + +- Failure inputs, their validation and storage (C1). +- Failure inputs in the export package and the HTML report (C3). +- Explanations for users in the FAQ, guided tour and documentation (C4). +- Comparison with an independent calculation and the benchmark scenarios (C5); the + tolerance (D1) is not needed here. +- Showing normal operation next to the result with failures (decided against). +- DALYs or inputs in the assessment comparison (later). +- Partial loss of removal (not planned). +- Recording the model version and simulation settings with results (reproducible assessment + snapshots, later). +- A response-time target (open question in the vision). + +## Further Notes + +- The rule for arranging three or more failure events of one day in best-case was decided + on 2026-09-24 while writing this Spec and added to `CONTEXT.md` (*Combined failure*) and + to decision 6 in `docs/QMRA_Failure_Approach.md`. Overlapping two failure events never + lowers the mixed concentration, so the lowest-concentration arrangement always has + minimal overlap and agrees with the rule for two steps. +- No contradiction with ADR-0001 was found. ADR-0001 notes that two exposure events on the + same calendar day need not share a failure state; this Spec keeps that. +- Results with failures will differ from a hand calculation with Eq. 6 of the failure + approach, as ADR-0001 explains; they agree on average.