| """The CAP/ICCR endometrium checklist field list: the single source of truth. |
| |
| Generated by scripts/generate_fields.py from the confirmed field-to-concept mapping |
| (endopath.mapping.cap_checklist_coverage, #95) and the taxonomy registry (endopath.taxonomy, |
| #35). Do not edit by hand: run `PYTHONPATH=src python3 scripts/generate_fields.py` and commit |
| the result. tests/test_field_generation.py fails when this file drifts from a fresh render. |
| |
| Every consumer derives from `FIELDS` below: schema.py re-exports the enums, llm_extraction reads |
| FIELD_VALUE_SCHEMAS and ENUM_FIELDS, colpali_retrieval reads FIELD_QUERIES, and the frontend |
| renders the checklist from GET /api/fields (issue #37). |
| |
| **Standard library only, deliberately.** `scripts/modal_precompute.py` runs on a Modal image |
| carrying only colpali-engine/transformers/torch/pillow/numpy, so it cannot import pydantic. Its |
| `@app.local_entrypoint` imports `FIELD_QUERIES` from here in the local process and passes it to the |
| GPU function as an argument. A test asserts this module stays import-clean; adding a third-party |
| import here silently breaks that job. (Note `endopath/__init__.py` imports schema, so the Modal |
| container must never import `endopath.fields` directly either.) |
| |
| The value enums live here rather than in schema.py because a field's permitted values are part of |
| its definition, and schema.py's Pydantic models sit on top of that. schema.py re-exports them, so |
| `from endopath.schema import Histotype` still resolves. |
| |
| Field *order* is meaningful: it is the order Case Review renders evidence cards. |
| |
| Several value sets differ from how docs/taxonomy.md and the registry model the same concept. Those |
| differences are annotated on the fields below and reconciled separately: #81 (histotype, LVSI, |
| myometrial, cervical, nodal, and molecular value sets), #62 (pathologic_stage as a reported field |
| plus per-edition computed views), and #36 (the dedifferentiated/undifferentiated split). |
| """ |
|
|
| from __future__ import annotations |
|
|
| from dataclasses import dataclass |
| from enum import Enum |
| from typing import Any, Optional |
|
|
|
|
| class Histotype(str, Enum): |
| ENDOMETRIOID = "endometrioid" |
| MUCINOUS = "mucinous" |
| SEROUS = "serous" |
| CLEAR_CELL = "clear_cell" |
| CARCINOSARCOMA = "carcinosarcoma" |
| DEDIFFERENTIATED_UNDIFFERENTIATED = "dedifferentiated_undifferentiated" |
| MIXED = "mixed" |
| OTHER_NOS = "other_nos" |
|
|
|
|
| class LvsiStatus(str, Enum): |
| NOT_IDENTIFIED = "not_identified" |
| FOCAL = "focal" |
| SUBSTANTIAL = "substantial" |
|
|
|
|
| class MolecularClassification(str, Enum): |
| POLE_ULTRAMUTATED = "pole_ultramutated" |
| MMR_DEFICIENT = "mmr_deficient" |
| P53_ABNORMAL = "p53_abnormal" |
| NO_SPECIFIC_MOLECULAR_PROFILE = "no_specific_molecular_profile" |
| MULTIPLE_CLASSIFIER = "multiple_classifier" |
|
|
|
|
| def _enum_schema(enum_cls: type[Enum]) -> dict[str, Any]: |
| return {"type": "string", "enum": [member.value for member in enum_cls]} |
|
|
|
|
| @dataclass(frozen=True) |
| class FieldSpec: |
| """One checklist field, in every form the codebase needs it. |
| |
| `query` is the ColPali retrieval phrase, and the key under which that |
| phrase's precomputed vector is stored in data/embeddings/query_embeddings.npz. |
| Renaming `name` therefore invalidates that file; `tests/test_fields.py` |
| asserts the two agree so the break is loud rather than silent. |
| |
| `value_schema` is JSON Schema, consumed directly as the LLM tool-use |
| parameter schema (llm_extraction.CHECKLIST_TOOL) and surfaced over |
| /api/fields so the frontend can render a dropdown instead of a text box. |
| `python_enum` is set only where the value coerces back to a Python Enum. |
| """ |
|
|
| name: str |
| label: str |
| query: str |
| value_schema: dict[str, Any] |
| python_enum: Optional[type[Enum]] = None |
|
|
| @property |
| def value_type(self) -> str: |
| return self.value_schema["type"] |
|
|
| @property |
| def enum_values(self) -> Optional[list[str]]: |
| return self.value_schema.get("enum") |
|
|
|
|
| FIELDS: tuple[FieldSpec, ...] = ( |
| |
| |
| FieldSpec( |
| name="histologic_type", |
| label="Histologic type", |
| query="histologic type", |
| value_schema=_enum_schema(Histotype), |
| python_enum=Histotype, |
| ), |
| |
| |
| FieldSpec( |
| name="histologic_grade", |
| label="Histologic grade (FIGO)", |
| query="FIGO grade", |
| value_schema={"type": "string", "enum": ["1", "2", "3"]}, |
| ), |
| |
| |
| FieldSpec( |
| name="myometrial_invasion_percent", |
| label="Myometrial invasion %", |
| query="myometrial invasion depth", |
| value_schema={"type": "number", "minimum": 0, "maximum": 100}, |
| ), |
| |
| |
| FieldSpec( |
| name="cervical_stromal_invasion", |
| label="Cervical stromal invasion", |
| query="cervical stromal invasion", |
| value_schema={"type": "boolean"}, |
| ), |
| |
| |
| |
| FieldSpec( |
| name="lymphovascular_space_invasion", |
| label="LVSI", |
| query="lymphovascular space invasion", |
| value_schema=_enum_schema(LvsiStatus), |
| python_enum=LvsiStatus, |
| ), |
| |
| |
| FieldSpec( |
| name="regional_lymph_node_status", |
| label="Regional lymph node status", |
| query="regional lymph node status", |
| value_schema={"type": "string"}, |
| ), |
| |
| |
| FieldSpec( |
| name="pathologic_stage", |
| label="Pathologic stage", |
| query="pathologic stage", |
| value_schema={"type": "string"}, |
| ), |
| |
| |
| FieldSpec( |
| name="molecular_classification", |
| label="Molecular classification", |
| query="molecular classification", |
| value_schema=_enum_schema(MolecularClassification), |
| python_enum=MolecularClassification, |
| ), |
| ) |
|
|
| BY_NAME: dict[str, FieldSpec] = {spec.name: spec for spec in FIELDS} |
|
|
| FIELD_NAMES: tuple[str, ...] = tuple(spec.name for spec in FIELDS) |
|
|
| FIELD_LABELS: dict[str, str] = {spec.name: spec.label for spec in FIELDS} |
|
|
| FIELD_QUERIES: dict[str, str] = {spec.name: spec.query for spec in FIELDS} |
|
|
| FIELD_VALUE_SCHEMAS: dict[str, dict[str, Any]] = {spec.name: spec.value_schema for spec in FIELDS} |
|
|
| ENUM_FIELDS: dict[str, type[Enum]] = { |
| spec.name: spec.python_enum for spec in FIELDS if spec.python_enum is not None |
| } |
|
|