chore: capture semantic pipeline baseline
This commit is contained in:
@@ -339,3 +339,128 @@ class FiscalContextEntry(Base):
|
||||
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
# ================= AI semantic layer V1 =================
|
||||
# The AI layer is NOT a fiscal authority: it only selects among official candidates and
|
||||
# never writes to Firebird. Knowledge is versioned, provenance-tracked and never deleted.
|
||||
|
||||
|
||||
class KbProduct(Base):
|
||||
"""GLOBAL product knowledge: only facts inherent to the product (never tenant context)."""
|
||||
__tablename__ = "kb_products"
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
fingerprint: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
||||
gtin: Mapped[str | None] = mapped_column(String(16), unique=True, nullable=True, index=True)
|
||||
identity_kind: Mapped[str] = mapped_column(String(16), default="FINGERPRINT") # GTIN | FINGERPRINT | SEMANTIC | UNKNOWN
|
||||
normalized_description: Mapped[str] = mapped_column(Text, default="")
|
||||
brand: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
unit: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
||||
package_quantity: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
||||
category: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
semantic_attributes: Mapped[dict] = mapped_column(JSON, default=dict)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
class KbKnowledge(Base):
|
||||
"""Versioned knowledge rows. Unique per (product, key, value): conflicting values for the
|
||||
same key are kept side by side and surfaced as KNOWLEDGE_CONFLICT, never majority-picked."""
|
||||
__tablename__ = "kb_knowledge"
|
||||
__table_args__ = (UniqueConstraint("kb_product_id", "knowledge_key", "value", name="uq_kb_knowledge_value"),)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
kb_product_id: Mapped[int] = mapped_column(ForeignKey("kb_products.id"), index=True)
|
||||
knowledge_key: Mapped[str] = mapped_column(String(48), index=True) # candidate_ncm | validated_ncm | candidate_cest | candidate_ex_tipi | ...
|
||||
value: Mapped[str] = mapped_column(Text)
|
||||
source: Mapped[str] = mapped_column(String(32)) # OFFICIAL_DATASET | ERP_OBSERVATION | OPERATOR | AI | AI_PLUS_OFFICIAL_VALIDATION
|
||||
confidence: Mapped[float] = mapped_column(default=0.0)
|
||||
evidence: Mapped[dict] = mapped_column(JSON, default=dict)
|
||||
model: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
prompt_version: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
||||
dataset_versions: Mapped[dict] = mapped_column(JSON, default=dict)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
validated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
|
||||
class KbObservation(Base):
|
||||
"""Raw per-tenant ERP observation of a KB product (evidence aggregation only)."""
|
||||
__tablename__ = "kb_observations"
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
kb_product_id: Mapped[int] = mapped_column(ForeignKey("kb_products.id"), index=True)
|
||||
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
||||
company_key: Mapped[str] = mapped_column(String(64), index=True)
|
||||
erp_code: Mapped[str] = mapped_column(String(32))
|
||||
database_label: Mapped[str] = mapped_column(String(64))
|
||||
observed_fields: Mapped[dict] = mapped_column(JSON, default=dict)
|
||||
source: Mapped[str] = mapped_column(String(32), default="ERP_OBSERVATION")
|
||||
observed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
class TenantProductContext(Base):
|
||||
"""TENANT-specific product context: role, ST-withheld, operation profile, operator decisions.
|
||||
Never promoted to global knowledge."""
|
||||
__tablename__ = "tenant_product_context"
|
||||
__table_args__ = (UniqueConstraint("tenant_id", "company_key", "erp_code", name="uq_tenant_product_context"),)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
||||
company_key: Mapped[str] = mapped_column(String(64), index=True)
|
||||
erp_code: Mapped[str] = mapped_column(String(32))
|
||||
kb_product_id: Mapped[int | None] = mapped_column(ForeignKey("kb_products.id"), nullable=True)
|
||||
product_role: Mapped[str | None] = mapped_column(String(16), nullable=True) # RESALE | OWN_PRODUCTION | INPUT | SERVICE | UNKNOWN
|
||||
st_withheld: Mapped[str | None] = mapped_column(String(4), nullable=True) # Y | N — never AI-decided
|
||||
operation_profile: Mapped[dict] = mapped_column(JSON, default=dict)
|
||||
source: Mapped[str] = mapped_column(String(32), default="ERP_OBSERVATION")
|
||||
confidence: Mapped[float] = mapped_column(default=0.0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
class AiWorkItem(Base):
|
||||
"""Deduplicated semantic work item (one knowledge need, many pending cells)."""
|
||||
__tablename__ = "ai_work_items"
|
||||
__table_args__ = (UniqueConstraint("dedup_key", name="uq_ai_work_item_dedup"),)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
task_type: Mapped[str] = mapped_column(String(24), index=True) # INVALID_NCM | CEST | EX_TIPI | PRODUCT_ROLE
|
||||
dedup_key: Mapped[str] = mapped_column(String(160))
|
||||
question_refs: Mapped[list] = mapped_column(JSON, default=list)
|
||||
payload: Mapped[dict] = mapped_column(JSON, default=dict)
|
||||
status: Mapped[str] = mapped_column(String(32), default="PENDING", index=True) # PENDING | KB_EXACT_HIT | KB_FINGERPRINT_HIT | HIGH_CONFIDENCE_RETRIEVAL | SINGLE_OFFICIAL_CANDIDATE | LLM_SELECTION_REQUIRED | UNRESOLVED | UNRESOLVED_NO_OFFICIAL_MAPPING | CONFLICT | DONE | SKIPPED
|
||||
kb_product_id: Mapped[int | None] = mapped_column(ForeignKey("kb_products.id"), nullable=True)
|
||||
cells_covered: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
class AiRun(Base):
|
||||
"""One provider execution over a batch of work items, with budget-guard evidence."""
|
||||
__tablename__ = "ai_runs"
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
provider: Mapped[str] = mapped_column(String(64))
|
||||
model: Mapped[str] = mapped_column(String(64))
|
||||
prompt_version: Mapped[str] = mapped_column(String(32))
|
||||
work_item_ids: Mapped[list] = mapped_column(JSON, default=list)
|
||||
input_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
||||
output_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
||||
cost_usd: Mapped[float] = mapped_column(default=0.0)
|
||||
status: Mapped[str] = mapped_column(String(16), default="RUN")
|
||||
results: Mapped[list] = mapped_column(JSON, default=list)
|
||||
budget_guard: Mapped[dict] = mapped_column(JSON, default=dict)
|
||||
started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
|
||||
class AiEvalCache(Base):
|
||||
"""Persistent LLM result cache: identical (input_fingerprint, candidate_set_hash,
|
||||
prompt_version, model, dataset_versions) re-execution makes ZERO new calls."""
|
||||
__tablename__ = "ai_eval_cache"
|
||||
__table_args__ = (UniqueConstraint("input_fingerprint", "candidate_set_hash", "prompt_version", "model", "dataset_version_ncm", name="uq_ai_eval_cache"),)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
input_fingerprint: Mapped[str] = mapped_column(String(64), index=True)
|
||||
candidate_set_hash: Mapped[str] = mapped_column(String(64))
|
||||
prompt_version: Mapped[str] = mapped_column(String(32))
|
||||
model: Mapped[str] = mapped_column(String(64))
|
||||
dataset_version_ncm: Mapped[str] = mapped_column(String(64))
|
||||
phase: Mapped[str] = mapped_column(String(16))
|
||||
answer: Mapped[dict] = mapped_column(JSON)
|
||||
gold_ncm: Mapped[str | None] = mapped_column(String(8), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
Reference in New Issue
Block a user