from datetime import datetime from pathlib import Path from typing import Literal, Self import yaml from pydantic import BaseModel, Field, ConfigDict # Stub locale di _YamlModel. In the reference implementation questa base vive in # mschema/models.py; qui la si replica perche' SessionManifest ha bisogno di # populate_by_name=True (db_schema usa l'alias "schema"), che la base mschema non # imposta. Identici to_yaml/from_yaml della controparte mschema. class _YamlModel(BaseModel): model_config = ConfigDict(populate_by_name=True) def to_yaml(self, path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) data = self.model_dump(by_alias=True, mode="json", exclude_defaults=False) path.write_text( yaml.safe_dump(data, sort_keys=False, allow_unicode=True, width=120) ) @classmethod def from_yaml(cls, path: Path) -> Self: raw = yaml.safe_load(path.read_text()) return cls.model_validate(raw) class SessionManifest(_YamlModel): id: str created_at: datetime status: Literal["open", "closed", "finalized"] = "open" question: str database: str db_schema: str = Field(alias="schema") # D12/D15: autore della sessione (auth) e versione del workflow usato. author: str | None = None summary: str | None = None updated_at: datetime | None = None updated_by: str | None = None schema_version: int | None = None provider: str | None = None model: str | None = None thinking: str | None = None name: str | None = None archived: bool = False group: str | None = None class Candidate(BaseModel): kind: Literal["table", "column"] name: str signals: dict = {} evidence: list[str] = [] decision: Literal["promoted", "excluded", "pending"] = "pending" decision_seq: int | None = None # D14a: valori citati nella domanda ancorati a questa colonna/tabella. grounded_values: list[dict] = [] class Join(BaseModel): from_: str = Field(alias="from") to: str source: str = "" decision: Literal["promoted", "excluded", "pending"] = "promoted" decision_seq: int | None = None model_config = {"populate_by_name": True} class ExcludedItem(BaseModel): kind: Literal["table", "column"] name: str decision_seq: int | None = None class SchemaLinking(BaseModel): question: str candidates: list[Candidate] = [] joins: list[Join] = [] excluded: list[ExcludedItem] = [] open_questions: list[str] = [] # D14b: formule di concetto approvate, parte dello schema-linking. concept_formulas: list[dict] = [] model_config = {"extra": "forbid"}