| """Field coverage: a project's target dictionary and the record types that fulfil it (#237). |
| |
| The coverage view (the Field coverage page) shows every field of a project's target dictionary, coloured by |
| the record type that sources it and metered by how full it is in the confirmed dataset. That mapping is |
| DB-backed here rather than hardcoded in the frontend, so a different project or dictionary is a different |
| format to review. The record types replace the old hardcoded source strings. |
| |
| `seed_coverage` loads the TCGA-UCEC REDCap seed (`coverage_data`, generated and de-identified) into the DB |
| once. `build_coverage` reads it back as the API payload; the live fill is still computed in the browser |
| from `GET /api/dashboard` and `GET /api/longitudinal/cohort`, so this module stays a thin schema read. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from sqlalchemy import Connection |
|
|
| from endopath import coverage_data, coverage_extractions, storage |
|
|
|
|
| def seed_coverage(conn: Connection, project: str = coverage_data.PROJECT_ID) -> None: |
| """Seed the project's record types and target-dictionary coverage from the generated seed, once, then |
| load the populated record-type extractions. Each step is a no-op if already seeded.""" |
| if not storage.has_coverage(conn, project): |
| for order, (record_type_id, name, blurb) in enumerate(coverage_data.RECORD_TYPES): |
| storage.upsert_record_type( |
| conn, |
| project=project, |
| record_type_id=record_type_id, |
| name=name, |
| source_class=record_type_id, |
| blurb=blurb, |
| sort_order=order, |
| ) |
| for order, (name, label, section, record_type_id, fill_via, fill_key) in enumerate( |
| coverage_data.FIELDS |
| ): |
| storage.upsert_coverage_field( |
| conn, |
| project=project, |
| dictionary_id=coverage_data.DICTIONARY_ID, |
| name=name, |
| label=label, |
| section=section, |
| record_type_id=record_type_id, |
| fill_via=fill_via, |
| fill_key=fill_key, |
| sort_order=order, |
| ) |
| conn.commit() |
| seed_extractions(conn, project) |
|
|
|
|
| def seed_extractions(conn: Connection, project: str = coverage_extractions.PROJECT_ID) -> None: |
| """Load each record type's committed, in-conversation sample into its extracted dictionary, once per |
| record type. A no-op for a record type that already carries extraction rows.""" |
| for record_type_id, cases in coverage_extractions.RECORD_TYPE_EXTRACTIONS.items(): |
| if storage.has_record_type_extractions(conn, project, record_type_id): |
| continue |
| for barcode, fields in cases.items(): |
| for order, (field, (value, evidence)) in enumerate(fields.items()): |
| storage.upsert_record_type_extraction( |
| conn, |
| project=project, |
| record_type_id=record_type_id, |
| case_barcode=barcode, |
| field=field, |
| value=value, |
| evidence=evidence, |
| sort_order=order, |
| ) |
| conn.commit() |
|
|
|
|
| def build_coverage(conn: Connection, project: str) -> dict: |
| """The Field coverage payload for a project: its record types, every target-dictionary field with the |
| record type that fulfils it and its fill config, and each record type's populated sample extractions |
| (the extracted dictionary in action). Empty `record_types`/`fields` when unseeded.""" |
| fields = [ |
| { |
| "name": row["name"], |
| "label": row["label"], |
| "section": row["section"], |
| "source": row["record_type_id"], |
| "fill": {"via": row["fill_via"], "key": row["fill_key"]} if row["fill_via"] else None, |
| } |
| for row in storage.list_coverage_fields(conn, project) |
| ] |
| record_types = storage.list_record_types(conn, project) |
| extractions: dict[str, list[dict]] = {} |
| for rt in record_types: |
| rows = storage.list_record_type_extractions(conn, project, rt["record_type_id"]) |
| if rows: |
| extractions[rt["record_type_id"]] = [ |
| { |
| "case": row["case_barcode"], |
| "field": row["field"], |
| "value": row["value"], |
| "evidence": row["evidence"], |
| } |
| for row in rows |
| ] |
| return { |
| "dictionary_id": coverage_data.DICTIONARY_ID, |
| "record_types": record_types, |
| "fields": fields, |
| "extractions": extractions, |
| } |
|
|