ishaq101's picture sofhiaazzhr's picture
/feat knowledge management (#20)
b68816f
Raw History Blame
3.49 kB
"""What the MODEL is asked to return.
Deliberately separate from `models.py`. The model never supplies `doc_id` (we
know it), never sets `extraction_status`, and never sets the conflict or diff
fields — validation owns those. **Asking a model for fields it cannot know is an
invitation to fabricate**, so the request schema is narrower than the stored one.
v2 narrowed it further, and every removal has a reason of that kind:
- **ids and links** (`term_id`, `formula_id`, `rule_id`, `domain_id`,
`defining_formula_id`, `formula_ids`, `term_ids`, `variables[].term_id`) are
minted deterministically. A model cannot produce a stable identifier — it has
no memory of what it emitted last run — and `rule_id` being model-written was
a real defect, not a style choice.
- **`domain` / `company`** were never in any prompt's field guide, so the model
filled them purely by copying the worked examples, and neither was
span-guarded — a wrong value was undetectable.
- **`formula_latex` on the glossary draft** duplicated the formula branch. One
truth, one place; the glossary now references it.
- **`interpretation`** moved to the Interpretation Pack, reached via `term_id`.
"""
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, Field
from ..models import SubdomainEnum
RuleTypeDraft = Literal["interpretation", "calculation"]
class ProvenanceDraft(BaseModel):
section_no: str | None = None
page: int
span: str
class GlossaryDraft(BaseModel):
term: str
full_name: str | None = None
# The wording exactly as the document writes it, even when "wrong" — the
# standard heads its section "Physical of Availability (PA)". Surfacing the
# discrepancy is the point; normalising it hides a decision the expert owns.
#
# Still requested, though the stored value is taken deterministically from
# the section heading: the model's answer is the fallback when no evidence
# chunk carries a heading naming the term.
source_wording: str | None = None
definition: str | None = None
subdomain_tags: list[SubdomainEnum] = Field(default_factory=list)
provenance: ProvenanceDraft
class RuleDraft(BaseModel):
# `rule_id` is NOT requested — it is minted from the document position. The
# model previously wrote it as SCREAMING_SNAKE_CASE, which meant the same
# rule could come back under a different id on the next run.
rule_type: RuleTypeDraft = "calculation"
statement: str | None = None
condition: str | None = None
consequence: str | None = None
provenance: ProvenanceDraft
class VariableDraft(BaseModel):
symbol: str
meaning: str | None = None
class FormulaDraft(BaseModel):
name: str | None = None
formula_latex: str | None = None
variables: list[VariableDraft] = Field(default_factory=list)
unit: str | None = None
provenance: ProvenanceDraft
class SummaryDraft(BaseModel):
title: str | None = None
# Located, not composed: the document's own statement of purpose, copied
# verbatim, so the span check can prove it. See prompts/summary.txt.
purpose_verbatim: str | None = None
key_parameters: list[str] = Field(default_factory=list)
provenance: ProvenanceDraft
DRAFTS = {
"glossary": GlossaryDraft,
"rule": RuleDraft,
"formula": FormulaDraft,
"summary": SummaryDraft,
}
def schema_for(branch: str) -> dict:
return DRAFTS[branch].model_json_schema()