export type FieldStatus = "confirmed" | "needs_review" | "flagged"; // One project ("repo") a team labels: a dataset scoped to its own worklist. Served by // GET /api/projects; an admin creates one through POST /api/projects. export interface Project { project_id: string; name: string; case_count: number; } // GET /api/projects: every project, plus the id the app opens when the reviewer has chosen none. export interface ProjectList { projects: Project[]; default: string; } // One row of GET /api/fields, mirroring endopath.fields.FieldSpec. `enum_values` // is non-null for the fields whose value is drawn from a closed vocabulary, so // the edit path can render a dropdown rather than a free-text box. export interface FieldSpec { name: string; label: string; value_type: "string" | "number" | "boolean"; enum_values: string[] | null; } export type CaseStatus = | "queued" | "processing" | "ready_for_review" | "in_review" | "confirmed" | "exported" | "flagged"; export interface EvidenceSpan { quote: string; char_start: number | null; char_end: number | null; page_number: number | null; source: string; } export interface ChecklistField { value: unknown; confidence: number | null; evidence: EvidenceSpan | null; status: FieldStatus; applicable: boolean; not_applicable_reason: string | null; search_log: string[]; } export interface WorklistRow { case_barcode: string; patient_filename: string | null; status: CaseStatus; } // One field a reviewer routed to the licensed account's queue (#161). export interface Escalation { case_barcode: string; field_name: string; value: unknown; escalated_by: string; role: string; note: string | null; escalated_at: string; } // One recorded LLM call (#221): metadata only, no prompt or response text. `model` is what the call site // requested; `response_model` is what the API says handled it; `request_id` is the Anthropic-issued id. export interface ModelCall { id: string; provider: string; model: string | null; response_model: string | null; request_id: string | null; input_tokens: number | null; output_tokens: number | null; cache_creation_input_tokens: number | null; cache_read_input_tokens: number | null; latency_ms: number | null; context: string | null; created_at: string; } export interface TraceSummary { call_count: number; input_tokens: number; output_tokens: number; cache_creation_input_tokens: number; cache_read_input_tokens: number; models: string[]; providers: string[]; mean_latency_ms: number | null; } export interface TraceResponse { calls: ModelCall[]; summary: TraceSummary; } // Field coverage (#237): a project's record types and its target-dictionary fields, DB-backed. `source` // is the record type that fulfils the field; `fill` names how its live confirmed fill is measured. export interface CoverageRecordType { record_type_id: string; name: string; source_class: string; blurb: string | null; } export interface CoverageField { name: string; label: string; section: string | null; source: string; fill: { via: "checklist" | "outcome"; key: string } | null; } export interface CoverageExtraction { case: string; field: string; value: string; evidence: string | null; } export interface CoverageResponse { dictionary_id: string; record_types: CoverageRecordType[]; fields: CoverageField[]; // record_type_id -> its populated sample extractions (the extracted dictionary in action). extractions: Record; } export interface CaseDetail { case_barcode: string; patient_filename: string | null; report_date: string | null; staging_edition: string; status: CaseStatus; page_count: number; fields: Record; } export interface VisualEvidence { evidence: EvidenceSpan; score: number; } // A selectable text source (issue #90): how the ingest path reads a report. export interface TextSourceOption { id: string; label: string; } // One variable of a target data dictionary. `permitted_values` is null for an // open vocabulary; `licence_required` marks a variable a licence gates. export interface DictionaryVariable { name: string; label: string | null; permitted_values: string[] | null; licence_required: boolean; } export interface DataDictionary { id: string; title: string; variables: DictionaryVariable[]; } export type ReportSourceKind = "prepared_corpus" | "folder"; export interface IngestionRunRequest { source: { kind: ReportSourceKind; folder_path?: string | null }; dictionary?: DataDictionary | null; text_source?: string | null; } // One enqueued follow-up job: report induction (#92) or dictionary // compilation (#91). The worker moves status enqueued -> running -> done or // failed, stamping started_at, finished_at, and any error as it drains it. export interface IngestionJob { id: string; kind: string; ticket: string; status: string; dictionary_id: string; source_kind: string; case_count: number; variable_count: number; error?: string | null; created_at: string; started_at?: string | null; finished_at?: string | null; } // A dictionary a reviewer can open on the mapping screen (#109): the built-in // CAP checklist, or a submitted one, flagged with whether its crosswalk has been // drafted yet. export interface DictionaryOption { id: string; title: string; builtin: boolean; ready: boolean; } export interface IngestionRunResult { ingested: number; failures: number; seeded: { queued: number; flagged: number }; source: { kind: ReportSourceKind; folder_path?: string | null }; text_source: TextSourceOption; dictionary: { id: string; title: string; variable_count: number; licence_required_variables: string[]; }; enqueued: IngestionJob[]; } export interface FieldSummary { field_name: string; total: number; confirmed: number; needs_review: number; flagged: number; avg_confidence: number | null; } // The cohort dashboard (#100, prd.md sections 8.1, 4.3, 4.5, 4.7). Beyond the completion summary it // carries, per field, the confirmed-value and coded-absence-reason distributions as distinct series, the // absence-versus-grade association, and, per case, the absent variables a re-review would order slides to // recover. The coded reasons come from the section 4.3 vocabulary (AbsentReason). export interface ValueCount { value: string; count: number; } export interface AbsenceCount { reason: AbsentReason; count: number; } // One stratum of the absence-versus-grade contingency: how many cases carry the field present versus // coded-absent within that FIGO-grade band. export interface AssociationRow { stratum: string; present: number; absent: number; total: number; absent_rate: number; } // Whether a field's absence tracks the grade stratum (prd.md section 4.5). `signal` fires when the // absence concentrates in one known stratum beyond the other; `detail` names it. export interface FieldAssociation { stratifier: string; rows: AssociationRow[]; signal: boolean; detail: string | null; } // One parsed component of a reported stage (T / N / M), as its own distribution (#165 follow-on), so the // tile can separate the combined-string "tabs" beside the folded FIGO stage. export interface FieldComponentDistribution { dimension: string; values: ValueCount[]; } export interface FieldDistribution { field_name: string; label: string; // The JSON-schema type of the field's value: "number"/"integer" render as a histogram, everything // else as a categorical bar chart (#100). value_type: string; confirmed: number; pending: number; absent: number; total: number; values: ValueCount[]; absence: AbsenceCount[]; association: FieldAssociation; // Parsed sub-distributions (T/N/M for the reported stage); empty for every other field. components: FieldComponentDistribution[]; } export interface RequiresSlidesVariable { field_name: string; label: string; } // One case's slide order (prd.md section 4.7): the absent variables recovered from the glass slides, the // ones filled by a join instead, and the FIGO 2023 determinability verdict (#35) for context. export interface RequiresSlidesCase { case_barcode: string; verdict: string | null; // Whether the verdict rests only on confirmed values or on at least one unreviewed model draft (#224), so // a draft-based projection renders provisional rather than as established fact. verdict_basis: "confirmed" | "draft" | null; stage: string | null; variables: RequiresSlidesVariable[]; count: number; join_variables: RequiresSlidesVariable[]; } export interface DashboardStratifier { field_name: string; label: string; strata: string[]; } export interface DashboardSummary { total_cases: number; done_cases: number; completion_rate: number; fields: FieldSummary[]; absence_vocabulary: AbsentReason[]; stratifier: DashboardStratifier; field_distributions: FieldDistribution[]; requires_slides: RequiresSlidesCase[]; requires_slides_case_count: number; } export interface ProvenanceRow { case_barcode: string; field_name: string; value: unknown; confidence: number | null; status: FieldStatus; applicable: boolean; not_applicable_reason: string | null; evidence_quote: string | null; evidence_page: number | null; evidence_source: string | null; } // The cohort export (#101, prd.md sections 3, 8.1). The column spec is the // canonical concept list (#91): one value column per concept, each paired with // a parallel absent-reason column so an absent value carries a coded reason // rather than a blank. The coded reasons come from prd.md section 4.3. export type AbsentReason = | "not_applicable" | "not_stated" | "indeterminate" | "determined_by_join" | "constrained"; export type ExportColumnKind = "identifier" | "value" | "absent_reason"; export interface ExportColumn { key: string; label: string; kind: ExportColumnKind; // Value columns carry the concept metadata; absent-reason columns carry `of`. concept_id?: string; category?: string; assertion_type?: AssertionType; cardinality?: Cardinality; cited_to?: string; derived?: DerivedRule | null; absent_reason_key?: string; of?: string; // Set on the outcome columns joined from the GDC clinical record rather than read from the report (#176). sourced_via?: "join"; join_source?: string; note?: string; } export interface ExportBundle { target_schema: { name: string; concept_count: number; schema_checksum: string }; staging_edition: { selected: string; per_case_editions: string[] }; generated_at: string; rule_versions: { dependency_graph_checksum: string; named_rules: string[] }; model_versions: { extraction: string }; coding_systems: { stage: string; value_sets: string }; sources: { case_barcode: string; source_document: string | null; evidence_checksum: string }[]; reproduction: string; } export interface ExportFile { filename: string; media_type: string; content?: string; content_base64?: string; } export interface ExportResult { columns: ExportColumn[]; rows: Record[]; provenance: ProvenanceRow[]; bundle: ExportBundle; files: { csv: ExportFile; json: ExportFile; xlsx: ExportFile }; } // A FIGO edition the Export target-schema selector offers (#40), from GET /api/staging_editions. export interface StagingEditionOption { id: string; label: string; } // One case's projected FIGO stage under a chosen edition (#40), carried on the export row under // `figo_stage__projection`. A determined code, a constrained candidate set, or an indeterminate reason, // each with its confidence and the derivation that produced it. export interface StageProjection { kind: "determined" | "constrained" | "indeterminate"; code?: string; modifier?: string | null; candidates?: string[]; reason?: string; system_id?: string | null; confidence?: number | null; derivation?: { rule: string; edition: string; inputs: string[] }; } // The registry browser (#94), served by GET /api/concepts. Its concept spine is // mapping.concept_list() (#91) with the taxonomy registry (#35) joined on: // evidence resolved from the citation, dependency relationships from the graph, // and the decisions that name each concept. export type AssertionType = "asserted" | "derived"; export type Cardinality = "exactly_one" | "zero_or_one" | "zero_or_more"; export type RuleKind = "formula" | "threshold" | "membership" | "algorithm"; export interface DerivedRule { inputs: string[]; rule: string; rule_kind: RuleKind; } export interface ConceptSource { source_id: string; title: string | null; year: string | null; url: string | null; license: string | null; } // Resolved from the concept's `cited_to`. An extraction carries a page-anchored // verbatim span and its source document; a doc_section is prose-only, the "open" // evidence state (no value-level extraction row yet, #29). export interface ConceptEvidence { kind: "extraction" | "doc_section" | "unresolved"; cited_to: string; prose_only: boolean; doc_section?: string; extraction_id?: string; source_id?: string; page?: number | null; verbatim_text?: string | null; extracted_by?: string | null; note?: string | null; source?: ConceptSource | null; } export interface ConceptRef { concept_id: string; label: string; relation: string; } export interface ConceptRelationships { computed_from: ConceptRef[]; feeds_into: ConceptRef[]; gated_by: ConceptRef[]; gates: ConceptRef[]; } export interface ConceptDecision { decision_id: string; date: string | null; question: string | null; decision: string | null; rationale: string | null; decided_in: string | null; } // The SKOS predicate of a crosswalk edge, plus `conflict` for two standards that state // disagreeing rules for the same question (#59, #85). export type CrosswalkKind = | "exactMatch" | "broadMatch" | "narrowMatch" | "relatedMatch" | "noMatch" | "conflict"; // One end of a crosswalk edge: a standard's short name and the value's label. export interface CrosswalkSide { standard: string; label: string; } // One cross-standard mapping for a concept's clinical dimension (#59), from crosswalk.csv. It reads // as ` ` -> ` ` with a kind tag; `conflict` edges // sort first and carry the rationale for the disagreement. export interface CrosswalkEdge { from: CrosswalkSide; to: CrosswalkSide; kind: CrosswalkKind; certainty: "certain" | "uncertain"; rationale: string; } export interface RegistryConcept { concept_id: string; category: string; label: string; assertion_type: AssertionType; cardinality: Cardinality; derived: DerivedRule | null; cited_to: string; evidence: ConceptEvidence; relationships: ConceptRelationships; decisions: ConceptDecision[]; crosswalk: CrosswalkEdge[]; } export interface RegistryCategory { id: string; concept_count: number; open_count: number; } export interface ConceptRegistry { categories: RegistryCategory[]; concepts: RegistryConcept[]; } // The crosswalk mapping-review surface (#95), served by GET /api/mapping. The // edges are crosswalk.reference_crosswalk() (the committed CAP-checklist // reference instance in the #93 edge contract), joined to concept labels and // categories and to the confirmations a reviewer has recorded. // One candidate resolution the maieutic loop offers for a queued edge, and that // POST /api/mapping/confirm accepts. `concept_id` is null for an out-of-scope // (unresolved) resolution. export interface MappingResolutionCandidate { label: string; concept_id: string | null; relation: string; tradeoff: string; } // The maieutic draft for one queued edge: the question plus two candidates. export interface MappingReviewDraft { question: string; candidates: MappingResolutionCandidate[]; } // How a reviewer decided an edge (#103). `confirmed` resolves it; `flagged` and // `deferred` park it as an open concern without resolving it. export type MappingDisposition = "confirmed" | "flagged" | "deferred"; // How the confirmed answer was reached, for the decision ledger (#103). export type MappingDecidedVia = "candidate" | "written" | "chat" | "flag" | "defer"; // Whether an edge reads as settled or needs the reviewer's judgment (#103), // directed by field type and state rather than a confidence number. export type MappingAttention = "confident" | "needs_judgment"; export interface MappingConfirmation { mapping_id: string; source_field: string; concept_id: string | null; relation: string; predicate: string; confirmed_by: string; role: string; note: string | null; licence_required: boolean; confirmed_at: string; disposition: MappingDisposition; decided_via: MappingDecidedVia | null; question: string | null; options_json: string | null; } // One drafted edge from a source field to a canonical concept. `is_unresolved` // routes the edge to its own lane; `queued` (unresolved or low-confidence) marks // the reviewer work the maieutic loop resolves. `attention` directs the eye by // field type and state; the confidence number stays off-screen (#103). // One canonical concept a source field also resolves to under a confirmed split // (#103 follow-on), beside the field's headline concept. export interface MappingEdgeComponent { concept_id: string; concept_label: string; relation: string; predicate: string; category: string | null; } export interface MappingEdge { mapping_id: string; source_std: string; source_field: string; source_label: string; concept_id: string | null; concept_label: string | null; category: string | null; relation: string; predicate: string; confidence: number; note: string; is_unresolved: boolean; queued: boolean; attention: MappingAttention; review: MappingReviewDraft | null; confirmation: MappingConfirmation | null; // Present and non-empty when the field was confirmed as a split. components: MappingEdgeComponent[]; } // One decision-ledger record (#103): the question a decision answered, the // options weighed, the chosen or edited answer, who confirmed it, when, and // where it is filed. One record per decided edge; amending replaces it. export interface MappingDecision { mapping_id: string; source_field: string; source_label: string; disposition: MappingDisposition; decided_via: MappingDecidedVia | null; question: string | null; options: string[]; concept_id: string | null; concept_label: string | null; relation: string; predicate: string; note: string | null; confirmed_by: string; role: string; confirmed_at: string; filed_to: { edge: string; table: string }; } // One component concept a proposed split resolves the field to (#103 follow-on). export interface MappingProposalComponent { concept_id: string; relation: string; concept_label: string; } // One remap the edge chat co-pilot proposes for the open edge (#103), which the // reviewer applies in a click. `components` is empty for a plain re-target and // carries the primitives when the proposal splits the field across several // concepts (with `concept_id`/`relation` as the derived headline). export interface MappingProposal { summary: string; concept_id: string | null; relation: string; concept_label: string | null; components: MappingProposalComponent[]; } // One prior turn of an edge chat, sent so a follow-up carries its context. export interface MappingChatTurn { role: "user" | "assistant"; text: string; } export interface MappingChatRequest { mapping_id: string; question: string; history: MappingChatTurn[]; } // The engine's reply about one edge (#103): a concise answer and, when the // message points at a better resolution, one remap the reviewer can apply. export interface MappingChatResponse { mapping_id: string; reply: string; proposal: MappingProposal | null; } export interface MappingConcept { concept_id: string; label: string; category: string; assertion_type: AssertionType; } export interface MappingSourceField { name: string; label: string; licence_required: boolean; } // One row of the confirmed field set (#96 reads this). A queued edge carries its // reviewer and time; a high-confidence candidate is ratified by the whole-mapping // confirmation. export interface MappingFieldSetRow { source_field: string; source_label: string; concept_id: string | null; relation: string; predicate: string; licence_required: boolean; confirmed_by: string | null; confirmed_at: string | null; origin: "confirmed" | "candidate"; } // The case-review confirmation surface (#98), served by GET /api/cases/{barcode}/review. The record is // the stored per-case checklist; each field carries its B1 validation block (#97) and the concept it // answers, and the payload joins the decision ledger. Every value is served here, never hardcoded. export type FieldCardKind = "confirmed" | "derived" | "not_applicable"; export type FieldAttention = "confident" | "needs_judgment"; export type FieldPromotion = "confirmed_eligible" | "needs_review"; export type FieldExtractionState = | "present" | "not_applicable" | "not_stated" | "indeterminate" | "determined_by_join" | "constrained"; // One B1 shape check (#97): a type, enum, cardinality, or state check and its outcome. export interface ValidationCheck { name: "type" | "enum" | "cardinality" | "state"; status: "pass" | "fail" | "not_applicable"; detail: string; } // The recompute of a derived concept (#97), with a human label per primitive input concept. export interface FieldDerivation { status: | "not_derived" | "matched" | "mismatched" | "inputs_absent" | "rule_not_implemented" | "not_checked"; rule: string | null; inputs: string[]; input_labels: { concept_id: string; label: string }[]; recomputed: unknown; extracted: unknown; detail: string; } // One field's #97 validation block: the shape checks, the derivation recompute, and the promotion it // earns. The confirmation card renders this beside the value. export interface FieldValidation { concept_id: string; assertion_type: AssertionType; cardinality: Cardinality; state: FieldExtractionState; value: unknown; evidence: EvidenceSpan | null; checks: ValidationCheck[]; derivation: FieldDerivation; promotion: FieldPromotion; reasons: string[]; } // One decision-ledger record for a case field (#98): the value filed, how it was reached, who filed it // and when, and where it is filed. One record per decided field; amending replaces it. export interface CaseFieldDecision { field_name: string; label: string; value: unknown; status: FieldStatus; disposition: string; decided_via: string | null; question: string | null; options: string[]; note: string | null; confirmed_by: string; role: string; confirmed_at: string; filed_to: { field: string; table: string }; } // One record card on the confirmation surface: a checklist field with its value, evidence, the concept // it answers, its validation block, the card archetype that renders it, and its recorded decision. export interface CaseFieldBlock { field_name: string; label: string; value: unknown; value_type: "string" | "number" | "boolean"; enum_values: string[] | null; evidence: EvidenceSpan | null; status: FieldStatus; applicable: boolean; not_applicable_reason: string | null; concept_id: string | null; concept_label: string | null; assertion_type: AssertionType | null; card_kind: FieldCardKind; attention: FieldAttention; validation: FieldValidation; decision: CaseFieldDecision | null; } export interface CaseReviewSection { title: string; fields: CaseFieldBlock[]; } export interface CaseReviewData { case_barcode: string; patient_filename: string | null; report_date: string | null; staging_edition: string; status: CaseStatus; page_count: number; sections: CaseReviewSection[]; progress: { confirmed: number; applicable: number; confirmed_eligible: number; total: number }; ledger: CaseFieldDecision[]; } // The reviewer-session context sent with a confirm or edit (#98), drawn from the session so identity is // not re-entered per value. A ledger row is written only when confirmer and role are present. export interface FieldDecisionContext { confirmer: string; role: string; note?: string | null; decided_via?: string | null; question?: string | null; options?: string[] | null; } // One fact the value chat co-pilot rested its answer on (#98). export interface FieldChatEvidence { fact: string; source: string; } export interface FieldChatTurn { role: "user" | "assistant"; text: string; } export interface FieldChatRequest { question: string; history: FieldChatTurn[]; } // The engine's answer about one value (#98): its reasoning, the evidence it read, and, when the question // points at a correction, the value the reviewer can file. export interface FieldChatResponse { field_name: string; answer: string; reasoning: string; evidence: FieldChatEvidence[]; suggested_value: unknown; suggested_state: string | null; } export interface MappingReview { source_std: string; source: { std: string; title: string; version: string }; threshold: number; categories: string[]; concepts: MappingConcept[]; source_fields: MappingSourceField[]; edges: MappingEdge[]; queue: { total: number; resolved: number; remaining: number; parked: number }; confirmed: boolean; field_set: MappingFieldSetRow[] | null; ledger: MappingDecision[]; } export interface MappingConfirmRequest { mapping_id: string; concept_id: string | null; relation: string; confirmer: string; role: string; note?: string | null; licence_required?: boolean; disposition?: MappingDisposition; decided_via?: MappingDecidedVia | null; question?: string | null; options?: string[] | null; // When present, splits the field across these component concepts, with // concept_id/relation as the derived headline (#103 follow-on). components?: { concept_id: string; relation: string }[]; }