COD Bucket Reporting
COD Bucket Reporting
DigitVA now supports versioned ICD-to-bucket reporting schemes for aggregating coded submissions without mutating the submission's final ICD record.
Routes:
- page:
/data-management/cod-buckets - API:
/api/v1/cod-buckets/schemes/api/v1/cod-buckets/aggregates
- admin editor:
/admin/panels/cod-buckets
ICD-10 and ICD-11 deaths
Every death is bucketed through the rows of its own classification, decided by the final COD's code shape (the ICD-10 and ICD-11 shapes do not overlap). There is no crosswalk.
- ICD-10 deaths use the scheme's
icd10rows, with the legacy reporting aliases, as before. - ICD-11 deaths use the scheme's
icd11rows: the exact code, else its nearest ancestor on themas_icd11_mmsparent chain that has a row. A post-coordinated value (1G40&XN...,1G40/...) is bucketed by its first stem. The ancestor is chosen ignoring age scope. va_submission_cod_detail_mvcarriesfinal_icd(ICD-10 code) and, since migratione7b2c9d4a1f3,final_icd11(ICD-11 stem). At most one is set. The admin unmapped-ICD grid still readsfinal_icdonly, so ICD-11 codes never appear there as unmapped ICD-10.- An ICD-11 death with no bucket is shown in the "View dropped CoDs" modal
under "ICD-11 codes not included in
", never among the ICD-10 sections. In a scheme with no ICD-11 rows ( WHO_2022_VA,SRS_INDIA,CMEA10) every ICD-11 death is unmapped, under "ICD-11 codes:has no ICD-11 mappings, so every ICD-11 death is unmapped". - The report CSV's "Final authoritative COD" is the ICD-10 code or the ICD-11 stem.
Since migration d1a6e3b7c2f4, va_submission_cod_snapshot_mv (and the coded
COD snapshot export it backs) does the same in WHO_2022_VA_2026, and records
each bucket's provenance (icd10, icd11_native, unmapped). See
Submission Analytics.
On the dev dataset (7,879 final CODs, all ICD-10), moving the snapshot from
WHO_2022_VA to WHO_2022_VA_2026 changed 15 authoritative buckets:
R10 Other GI to Acute abdomen (4), R95 Cause of death unknown to Other
perinatal (6), and R19/R51/R55 from no bucket to Cause of death unknown
(5).
Reporting UI
The data-management report page at /data-management/cod-buckets now:
- defaults to the
WHO_2022_VA_2026scheme when available (owner, 2026-09-24); the scheme filter still offersWHO_2022_VA. The/aggregatesand/export.csvAPI routes resolve an omittedscheme_codethrough the same one resolver (default_reporting_scheme_code():WHO_2022_VA_2026when active, else the first active scheme, else a 400 explaining that no scheme is configured) — they no longer fall back silently to the ICD-10-onlyWHO_2022_VA, which left every ICD-11 death unmatched - exposes filters for scheme, project, site, form, submission date, and gender
- scopes all page bootstrap data and aggregate API results to the caller's data-manager project/site grants
- shows key indicators first for
Total coded,Scheme used, and the current filter scope - shows a top-10 causes section with a ranked table and horizontal bar chart
- shows a first-level cause-group section with counts and percentages
- shows demographic summaries as:
- an age-sex table
- an age pyramid
- a sex pie chart
- uses SRS scheme age bands for SRS demographic summaries
- uses these custom age bands for WHO 2022 VA and CMEA10 demographic summaries:
0-<28 days28 days-<365 days365 days-<12 years12 years-<50 years>=50 years
- keeps
All Agesfirst in the bottom detailed COD hierarchy section and, for WHO 2022 VA and CMEA10, adds additional detailed tables for each reporting age band after the all-ages table - shows a top-10 cause heatmap that can pivot across country, year, sex, and age
- shows an all-causes treemap for the same filtered matched population
- shows the bottom COD hierarchy tables with
Male n (%),Female n (%),Unknown n (%), andTotal n (%)columns for each scheme row - supports CSV export of the current scoped page data, including SID, age, age group, sex, final authoritative COD, scheme hierarchy levels, project, site, form code, and year
- keeps the bottom COD hierarchy tables ordered by the scheme-defined sort order fields rather than applying separate label-based sorting
- keeps the detailed age-scope tables and dropped-COD drilldown modal below the chart
- is styled for printing without internal table/card scroll regions
Public bucket scheme page
/help/cod-bucket-schemes (GET, 60 per minute, no login) lists the active
schemes and shows one at a time: age band, bucket tree, mapped ICD-10 and
ICD-11 codes, client-side search. /help/cod-bucket-schemes/<code>.json and
.csv are the downloads. Data: export_public_cod_bucket_scheme
(app/services/cod_bucket_mapping_service.py), a public-fields-only
projection of export_cod_bucket_scheme_json (a fixed number of queries per
scheme, no cache). Policy: docs/policy/icd10-to-icd11-transition.md section 8.
The cod-buckets help page stays restricted to data managers and admins.
Public mapping list
/help/va-code-mappings (and /help/va-code-mappings.csv) publishes, without
login, every active ICD-10 and ICD-11 row of WHO_2022_VA_2026: code, code
title, VA cause, the row's note, and an origin (who, who_resolved,
digitva) derived at read time against WHO's 2026 annex. Policy:
docs/policy/icd10-to-icd11-transition.md section 7.
- Service:
app/services/va_code_mapping_public_service.py; routes inapp/routes/help.py(GET only, 60 per minute, 100 rows per page, search and filters validated against fixed sets). - The annex and footnotes are read from
resource/copies, becausedocs/is not in the image; a test keeps them equal to thedocs/copies. - The derived table is cached per process and rebuilt when the scheme's
version, row count, latest row or node edit, or an annex file changes. ICD-11
ranges are expanded with the native generator's functions against the
2026-01catalogue. - Footnote f: land transport codes it lists (V01-V89) and
Y85.0are road traffic (VAs-12.01); its tailV90-V99; Y85.9and every unlisted V/Y85 code are other transport (VAs-12.02). - Sub-pages (
digitva-xud):/help/va-code-mappings/compare?va_code=shows the ICD-10 and ICD-11 codes of one VA cause side by side, as chapter/block trees./help/va-code-mappings/unmapped(and.csv) lists every in-scope ICD-11 category with its VA cause or "Unmapped", selectability and policy review status. It is filtered by selectable, origin, policy review and search, and paged block-aligned (icd11_block_pages): a page fills to 100 codes with whole catalogue blocks, so a block is never split across two pages. Expanding a block or chapter node expands its whole subtree in one click (tree_table's opt-inexpand_subtree).- The ICD-11 hierarchy and policy come from
get_icd11_catalogue(). It makes one pass overmas_icd11_mmsfor the release and is cached per process. The cache key is the release's row count and latestupdated_at, so an admin policy edit shows on the next load. ICD-10 chapter and block come frommas_icd10_2019_2, joined in the same query that builds the mapping rows. - The
/unmapped.csvdownload without a search, origin or policy-review filter is served from a file cache underAPP_DATA/public_csv/, one file per selectable variant (all / yes / no). The file name carries a digest of the mapping and ICD-11 cache keys, so an edit writes a new file and removes the old one. Files are written to a temp file and moved into place withos.replace, so every gunicorn worker sees a whole file. Only public data is written. Any other filter combination always streams live and never creates a file. If the directory cannot be written, the download streams live. - The mapping cache key also covers the ICD-10 catalogue (row count and
latest
updated_at), so chapter and block title edits reach the compare view, and the ICD-11 catalogue of the release (row count and latestupdated_at), so an ICD-11 title edit alone refreshes the mapping rows too. - Both pages render with the shared Wunderbaum tree-table:
app/templates/components/tree_table.html,app/static/js/tree_table.jsandapp/static/css/tree_table.css. It takes a flat node list and sets every value as text.
Storage model
The reporting layer uses four tables:
mas_cod_bucket_schemes- one row per reporting taxonomy, for example
SRS India,CMEA10, orWHO 2022 VA
- one row per reporting taxonomy, for example
mas_cod_bucket_scheme_age_bands- one row per scheme age band
- stores label, display order, explicit lower and upper bounds, and level count
mas_cod_bucket_nodes- hierarchy nodes within a scheme
- supports variable depth and optional age scope
map_icd_cod_buckets- maps an ICD code and optional age scope to a leaf node
Scheme shape
SRS India
Imported from:
Characteristics:
- age-scoped mapping
- currently uses:
adult_over5ychild_1_59mneonate
- hierarchy depth:
- category
- optional subcategory
- field
CMEA10
Imported from:
docs/icd-causegrp-mappings/migration-artifacts/cmea10-cod-2026-04-27/icd-10-CODES_CMEA10_mapped.xlsx
Characteristics:
- flat mapping
- no age scope
- hierarchy depth:
- field only
WHO 2022 VA
Imported from:
Characteristics:
- generated from the WHO 2022 VA crosswalk and ICD-10 2019 hierarchy
- splits
VAs-12.01 Road traffic accidentandVAs-12.02 Other transport accidentfrom the reviewed RTA/non-RTA transport workbook - flat all-ages age scope
- hierarchy depth:
- WHO VA section
- WHO VA cause bucket
- includes WHO-valid three-character ICD rows and dotted detailed ICD rows
- remains document-derived and is not narrowed by the later WHO assignability disable review; disabled codes simply stop being assignable during coding
- ambiguous ICD matches are resolved in the workbook before import; the import
uses the primary bucket in
ICD_Mapped
Migration Artifacts
The source workbooks under docs/icd-causegrp-mappings/ICD-to-VA-Buckets/ are
working inputs. Alembic migrations use fixed copies under
docs/icd-causegrp-mappings/migration-artifacts/ so master-data rebuilds are
repeatable.
For the built-in schemes (SRS India, CMEA10, and WHO 2022 VA), those
same migration-artifact workbook copies are also the source of truth for
Reset Default in the admin COD bucket editor.
The 2026-04-27 rebuild migration imports:
- ICD-10 2019 base hierarchy from
migration-artifacts/icd10-2019-base-2026-04-27/icd10_2019_hierarchy.csv - reviewed WHO 2022 assignability policy from
migration-artifacts/who-2022-va-icd-cod-2026-04-27/who_2022_icd10_2019_2_policy_reviewed.json - SRS India COD buckets from
migration-artifacts/srs-india-cod-2026-04-27/icd-10-CODES_SRS_India.xlsx - CMEA10 COD buckets from
migration-artifacts/cmea10-cod-2026-04-27/icd-10-CODES_CMEA10_mapped.xlsx - WHO 2022 VA COD buckets from
migration-artifacts/who-2022-va-icd-cod-2026-04-27/WHO_2022_VA_Bucket_Mapping_document_derived.xlsx - reviewed VAs-12.01/VAs-12.02 transport decisions from the archived support
workbook
archive/migration-artifacts/who-2022-va-icd-cod-2026-04-27/WHO_2022_VA_RTA_NonRTA_Review.xlsx
Import commands
Run inside Docker:
docker compose exec minerva_app_service uv run flask cod-buckets import-srs-india
docker compose exec minerva_app_service uv run flask cod-buckets import-cmea10
docker compose exec minerva_app_service uv run flask cod-buckets import-who-2022-va
docker compose exec minerva_app_service uv run flask cod-buckets list
Each import replaces the scheme's nodes and ICD mappings and increments that
scheme's mapping_version.
Mapping integrity rules:
map_icd_cod_bucketsis logically one row per(scheme, age_scope, icd_code)- all-ages mappings are stored with
age_scope = NULL, but they are still treated as unique per ICD inside a scheme - JSON import now coalesces exact duplicate mapping rows and rejects conflicting duplicate ICD targets within the same age scope
- a null-safe unique index backs that rule at the database level so duplicate all-ages mappings cannot be reinserted
Imported schemes can then be maintained in the admin COD Buckets panel. The current editor supports:
- selecting a scheme and age scope from top-level scheme cards
- exporting any scheme as JSON from its scheme card
- importing an exported JSON snapshot back into a selected scheme from its
scheme card; the import fully replaces that scheme's age bands, nodes, and
ICD mappings while keeping the selected scheme code stable
- this importer accepts only COD bucket scheme export JSON
- it does not accept ICD policy review artifacts such as
who_2022_icd10_2019_2_policy_reviewed.json
- exporting a scheme-level bucket XLSX workbook from its scheme card, including bucket hierarchy rows and ICD mapping rows with manual override status
- creating a new scheme with age bands, min/max age bounds, units, and level count
- editing an existing scheme's name and age-band metadata from the scheme card
- resetting a built-in source-backed age band from the editor heading bar with confirmation
- resetting an entire built-in source-backed scheme from the
Edit COD Schememodal with confirmation- built-in reset-default always reloads from the frozen migration-artifact
workbook copy, not from mutable working workbooks under
docs/icd-causegrp-mappings/ICD-to-VA-Buckets/
- built-in reset-default always reloads from the frozen migration-artifact
workbook copy, not from mutable working workbooks under
- editing category/subcategory/field labels
- editing display
sort_order - loading ICD mappings on demand only for the selected last-level disease leaf
- searching within the selected leaf's mapped ICD list in the right-side card
- adding ICD codes through a modal ICD search that shows the current mapped
path for each result, can be filtered to unmapped codes only, and keeps
non-assignable master ICD rows available with a
Currently not assignable in codingmarker - showing a scheme-wide grid of active ICD master rows not yet mapped anywhere
in the selected scheme, across all age groups, as a single ICD code list,
including non-assignable rows flagged as
Currently not assignable in coding - showing finalized COD and SmartVA usage counts in that unmapped ICD grid so operators can see whether an unmapped ICD is already present in active outcome data
- including the SmartVA pseudo-codes
UU1andUU2in that reporting-side unmapped/searchable ICD catalog so schemes can map them explicitly even though they do not exist in the ICD-10 master - filtering that unmapped ICD grid to codes already used in finalized COD outcomes, so operationally observed non-default ICDs can be reviewed and manually allocated
- filtering that unmapped ICD grid independently by finalized COD usage and by SmartVA usage
- bulk-allocating selected rows from that unmapped ICD list to a chosen age band and disease leaf in the current scheme
- filtering the selected disease leaf's mapped ICD list to manual overrides or source-derived mappings
- unmapping an ICD code directly from the selected disease leaf
- deleting a bucket level from the edit modal with a choice to either:
- unmap affected ICD codes
- move affected ICD codes to an
Unmappedreplacement branch
- remapping an ICD code to exactly one disease leaf within the selected scheme + age scope
For the built-in WHO 2022 VA scheme, the migration-artifact source XLSX is the default mapping baseline. Admin edits are marked as manual overrides only when the saved ICD-to-bucket target differs from that XLSX-derived default, or when the ICD code is absent from the source workbook. If an admin moves a code back to its XLSX default bucket, the editor restores the source metadata instead of leaving a manual override marker.
The admin ICD picker used in the COD bucket editor now searches the ICD-10
2019 master table (mas_icd10_2019_2) and returns active 3-character or
detailed ICD rows. It does not apply age or sex policy filtering inside the COD
bucket editor, and rows that are not currently assignable in coding remain
available for bucket mapping with an explicit warning marker.
Age band bound semantics are:
- lower bound: inclusive (
>=) - upper bound: exclusive (
<) - all scheme age bands persist explicit min/max bounds; there are no open-ended
NULLranges - built-in open-ended scopes use
120 yearsas the explicit upper cap
Age-band normalization in COD reporting uses:
days->1months->365 / 12years->365
The admin creator shows gap and overlap feedback using that same conversion logic. Those warnings are surfaced on scheme cards and age-band buttons as hover help, but they do not block scheme creation or update.
Aggregation input contract
Bucketed aggregation is driven from:
va_submission_analytics_core_mvva_submission_analytics_demographics_mvva_submission_cod_detail_mv
The aggregate query uses:
- authoritative
final_icd - demographics-derived age band
- the active scheme mapping
- optional reporting-only legacy ICD alias normalization before scheme lookup
- the caller's allowed
(project_id, site_id)scope pairs fromdm_scoped_forms(...) - a small reporting-only legacy ICD normalization layer before bucket lookup for selected historical codes that no longer exist in the ICD-10 2019 master
Current normalization baseline:
A90->A97A91->A97I84->K64
Current command-line aggregation:
docker compose exec minerva_app_service uv run flask cod-buckets aggregate --scheme-code SRS_INDIA
docker compose exec minerva_app_service uv run flask cod-buckets aggregate --scheme-code CMEA10
The /data-management/cod-buckets page now renders separate tables per age
scope. Each table shows:
Main GroupSubgroupDiseaseCountTotal (%)
If coded submissions in an age group have a final ICD that does not map to any active node in the selected scheme, those submissions are not rendered as hierarchy rows. Instead, the page shows a note beneath that age-group table stating how many submitted CODs were dropped because they did not match any configured category. That note now includes a modal link showing two tabulated sections for the same age group:
- ICD codes not included in the selected scheme's CoD categories for that age group
- ICD codes not eligible for coding
Rows are sorted by the active scheme hierarchy display order, using the stored
sort_order values on category, subcategory, and field nodes rather than label
sorting or count sorting.
Important behavior
- submission COD rows remain unchanged when reporting mappings change
- selected historical ICD codes may normalize to current ICD-10 2019 three-character equivalents for reporting only; this does not rewrite stored final COD values
- reporting schemes are versioned separately from coding data
- SRS chooses a mapping branch by age scope
- CMEA10 does not branch by age scope