Missão 4 / Bloco E.1-E.2, on feature/cfop-ippt-write only (never main):
- Migration 0033_cfop_ippt_write_mode: Installation.cfop_ippt_write_mode
(DISABLED|DRY_RUN|APPROVAL_REQUIRED|ENABLED, DEFAULT 'DISABLED' for every
installation, existing and new) + append-only cfop_ippt_write_audits table.
- central/app/cfop_ippt_audit.py mirrors rtc_audit.py for this narrower
capability: job_authorizations(installation) (delivered inside the
authenticated job/heartbeat authorizations, never a local Agent switch) and
record_cfop_ippt_write_audits (persists applied/aborted cells; only CFOP/
IPPT are ever accepted, everything else silently ignored).
- central/app/web.py: POST /agents/{id}/cfop-ippt-write - ADMIN only (SSO
Gerenciador), CSRF, typing GRAVAR required for any mode other than
DISABLED, every change audited (CFOP_IPPT_WRITE_MODE). Small toggle UI in
agent_detail.html next to Contexto Fiscal.
- Wired into the existing job/heartbeat authorization payload and job
completion handling in central/app/api.py, alongside (never mixed with)
the RTC wiring.
- 18 new tests (central/tests/test_cfop_ippt_write_plumbing.py): default
DISABLED everywhere (job payload AND /v1/agents/me/authorizations); a
forged completion payload can never change the mode (read fresh from the
Installation row, never from Agent-supplied JSON); PADRAO role gets 403
with no audit row; ADMIN + wrong/missing CSRF is refused; ADMIN without
typing GRAVAR is refused (422); disabling never requires GRAVAR; invalid
mode is refused; the audit recorder only accepts CFOP/IPPT, silently
drops any other field (e.g. a stray CCLASSTRIB row).
- Two existing RTC tests updated for the additive authorizations-dict key
(cfop_ippt_write_mode) they now also carry.
Still NOT wired into agent/scripts/run_agent_service.py::execute_job - no
Agent code calls select_candidates/apply_candidates yet, so nothing writes
to Firebird from this branch. That wiring, an Agent version bump, a new
installer and Windows Sandbox validation are the next step, deliberately
left for after review (see docs/CFOP_IPPT_WRITE_CAPABILITY_V1.md) - this
mission explicitly asked not to publish this as an active release yet.
FULL_SUITE on this branch: 1 known, expected failure -
test_manifest_records_the_real_migration_head - because this branch adds
migration 0033 that was never merged to master/deployed; RELEASE_MANIFEST.json
intentionally still describes master/production (head 0032). Every other
test passes, including all CFOP/IPPT and RTC-adjacent tests.
REAL_PRODUCT_FISCAL_WRITES=0 (no write path is reachable from any Agent
code on any branch).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
960 lines
62 KiB
Python
960 lines
62 KiB
Python
"""PostgreSQL records owned by the Fiscal Hub."""
|
|
|
|
from datetime import datetime
|
|
from sqlalchemy import Boolean, CheckConstraint, DateTime, ForeignKey, Integer, JSON, String, Text, UniqueConstraint, func
|
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
class Dataset(Base):
|
|
__tablename__ = "datasets"
|
|
__table_args__ = (UniqueConstraint("provider", "sha256", name="uq_dataset_provider_hash"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
kind: Mapped[str] = mapped_column(String(32), index=True)
|
|
provider: Mapped[str] = mapped_column(String(128))
|
|
sha256: Mapped[str] = mapped_column(String(64))
|
|
raw_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
version_label: Mapped[str] = mapped_column(String(256))
|
|
act: Mapped[str] = mapped_column(Text)
|
|
source_url: Mapped[str] = mapped_column(Text)
|
|
downloaded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
status: Mapped[str] = mapped_column(String(32), index=True)
|
|
record_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
|
|
|
|
class NcmEntry(Base):
|
|
__tablename__ = "ncm_entries"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "code", "valid_from", "valid_to", name="uq_ncm_versioned_entry"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
code: Mapped[str] = mapped_column(String(16), index=True)
|
|
description: Mapped[str] = mapped_column(Text)
|
|
valid_from: Mapped[str] = mapped_column(String(32))
|
|
valid_to: Mapped[str] = mapped_column(String(32))
|
|
|
|
|
|
class LegacyNcmEntry(Base):
|
|
__tablename__ = "legacy_ncm_entries"
|
|
code: Mapped[str] = mapped_column(String(16), primary_key=True)
|
|
source: Mapped[str] = mapped_column(Text)
|
|
source_date: Mapped[str] = mapped_column(String(32))
|
|
reason: Mapped[str] = mapped_column(Text)
|
|
successor_code: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
|
|
|
|
class CestEntry(Base):
|
|
__tablename__ = "cest_entries"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "code", name="uq_cest_versioned_entry"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
code: Mapped[str] = mapped_column(String(16), index=True)
|
|
description: Mapped[str] = mapped_column(Text)
|
|
annex: Mapped[str] = mapped_column(String(8))
|
|
|
|
|
|
class CestNcmEntry(Base):
|
|
__tablename__ = "cest_ncm_entries"
|
|
__table_args__ = (UniqueConstraint("cest_entry_id", "ncm_code", name="uq_cest_ncm_link"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
cest_entry_id: Mapped[int] = mapped_column(ForeignKey("cest_entries.id"), index=True)
|
|
ncm_code: Mapped[str] = mapped_column(String(16), index=True)
|
|
|
|
|
|
class TipiEntry(Base):
|
|
__tablename__ = "tipi_entries"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
ncm: Mapped[str] = mapped_column(String(8), index=True)
|
|
ex: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
description: Mapped[str] = mapped_column(Text)
|
|
ipi_rate: Mapped[float | None] = mapped_column(nullable=True)
|
|
ipi_rate_text: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
source_row: Mapped[int] = mapped_column(Integer)
|
|
metadata_json: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
|
|
|
|
class BaStMvaEntry(Base):
|
|
__tablename__ = "ba_st_mva_entries"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
item: Mapped[str] = mapped_column(String(32))
|
|
segment: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
cest: Mapped[str | None] = mapped_column(String(16), index=True, nullable=True)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
mva_original: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
mva_adjusted: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
agreement_info: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
source_page: Mapped[int] = mapped_column(Integer)
|
|
metadata_json: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
|
|
class BaStMvaNcmSh(Base):
|
|
__tablename__ = "ba_st_mva_ncm_sh"
|
|
__table_args__ = (UniqueConstraint("entry_id", "ncm_sh", name="uq_ba_st_mva_entry_ncm_sh"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
entry_id: Mapped[int] = mapped_column(ForeignKey("ba_st_mva_entries.id"), index=True)
|
|
ncm_sh: Mapped[str] = mapped_column(String(32), index=True)
|
|
metadata_json: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
|
|
|
|
class FiscalCodeEntry(Base):
|
|
"""Small national fiscal code domains sourced from the official NF-e MOC
|
|
manual: ORIGEM_MERCADORIA, CST_ICMS, CSOSN, CST_PIS, CST_COFINS."""
|
|
__tablename__ = "fiscal_code_entries"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "domain", "code", name="uq_fiscal_code_entry"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
domain: Mapped[str] = mapped_column(String(32), index=True)
|
|
code: Mapped[str] = mapped_column(String(8), index=True)
|
|
description: Mapped[str] = mapped_column(Text)
|
|
source_section: Mapped[str] = mapped_column(String(96))
|
|
metadata_json: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
|
|
|
|
class CfopEntry(Base):
|
|
"""CFOP (Código Fiscal de Operações e Prestações) — Portal Nacional NF-e."""
|
|
__tablename__ = "cfop_entries"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "code", name="uq_cfop_entry"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
code: Mapped[str] = mapped_column(String(8), index=True)
|
|
title: Mapped[str] = mapped_column(Text)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
effective_from: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
effective_to: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
ind_nfe: Mapped[str | None] = mapped_column(String(4), nullable=True)
|
|
ind_comunica: Mapped[str | None] = mapped_column(String(4), nullable=True)
|
|
ind_transp: Mapped[str | None] = mapped_column(String(4), nullable=True)
|
|
ind_devol: Mapped[str | None] = mapped_column(String(4), nullable=True)
|
|
ind_retor: Mapped[str | None] = mapped_column(String(4), nullable=True)
|
|
ind_anula: Mapped[str | None] = mapped_column(String(4), nullable=True)
|
|
ind_remes: Mapped[str | None] = mapped_column(String(4), nullable=True)
|
|
ind_comb: Mapped[str | None] = mapped_column(String(4), nullable=True)
|
|
ind_exc_ibscbs: Mapped[str | None] = mapped_column(String(4), nullable=True)
|
|
|
|
|
|
class FcpRate(Base):
|
|
"""FCP (Fundo de Combate à Pobreza) alíquotas por UF — Portal Nacional NF-e."""
|
|
__tablename__ = "fcp_rates"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "uf", name="uq_fcp_rate_uf"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
uf_code: Mapped[str] = mapped_column(String(4))
|
|
uf: Mapped[str] = mapped_column(String(4), index=True)
|
|
uf_name: Mapped[str] = mapped_column(String(64))
|
|
aliq_1: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
aliq_2: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
aliq_3: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
observacao: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
|
|
class AnpProductCode(Base):
|
|
"""cProdANP — official code list used by NF-e, published on the Portal Nacional NF-e."""
|
|
__tablename__ = "anp_product_codes"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "cprod_anp", name="uq_anp_product_code"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
cprod_anp: Mapped[str] = mapped_column(String(16), index=True)
|
|
|
|
|
|
class NcmUtribEntry(Base):
|
|
"""NCM x uTrib (unidade tributável de comércio exterior) — Portal Nacional NF-e."""
|
|
__tablename__ = "ncm_utrib_entries"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "ncm", name="uq_ncm_utrib_entry"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
ncm: Mapped[str] = mapped_column(String(16), index=True)
|
|
effective_from: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
effective_to: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
utrib_abbreviation: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
utrib_description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
|
|
class RtcBiofuelIndexEntry(Base):
|
|
"""RTC pBioObrigatorio (índice de biocombustível) by cProdANP — Portal Nacional NF-e."""
|
|
__tablename__ = "rtc_biofuel_index_entries"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
cprod_anp: Mapped[str | None] = mapped_column(String(16), index=True, nullable=True)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
p_bio_percent: Mapped[float | None] = mapped_column(nullable=True)
|
|
effective_from: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
effective_to: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
is_historical: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
|
|
|
|
class RtcCredPresEntry(Base):
|
|
"""RTC cCredPres (crédito presumido IBS/CBS), LC 214/2025 — Portal Nacional NF-e.
|
|
Keeps every official field distinctly so future automatic determination
|
|
doesn't require re-parsing the source."""
|
|
__tablename__ = "rtc_cred_pres_entries"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "code", name="uq_rtc_cred_pres_code"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
code: Mapped[str] = mapped_column(String(8), index=True)
|
|
description: Mapped[str] = mapped_column(Text)
|
|
legal_basis: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
apropria_via_nf: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
apropria_via_evento: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
ind_deduz_cred_pres: Mapped[str | None] = mapped_column(String(8), nullable=True)
|
|
ind_g_cbs_cred_pres: Mapped[str | None] = mapped_column(String(8), nullable=True)
|
|
ind_g_ibs_cred_pres: Mapped[str | None] = mapped_column(String(8), nullable=True)
|
|
aliquota_cbs_text: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
aliquota_ibs_text: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
p_aliq_cred_pres_cbs: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
p_aliq_cred_pres_ibs: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
p_red_transicao_ibs: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
# Free text in some rows (e.g. "cClass de alíquota zero"), not always the numeric code.
|
|
cclass_nota_referenciada: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
d_ini_vig_cbs: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
d_fim_vig_cbs: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
d_ini_vig_ibs: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
d_fim_vig_ibs: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
|
|
|
|
class Tenant(Base):
|
|
"""A client. GTI Gerenciador (the panel) is the single source of truth —
|
|
see central/app/client_sync.py. `source='GTI_GERENCIADOR'` rows are
|
|
upserted by that sync, keyed by `external_client_id` (Gerenciador's
|
|
CLIENTE.ID, the stable technical PK — CLIENTE.CODIGO is editable and
|
|
never used as identity, same reasoning as FUNCIONARIO_PAINEL.ID in SSO).
|
|
`source='LOCAL'` rows predate this sync (dev/test tenants) and are never
|
|
offered for a new installation."""
|
|
__tablename__ = "tenants"
|
|
__table_args__ = (UniqueConstraint("source", "external_client_id", name="uq_tenant_external_identity"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
name: Mapped[str] = mapped_column(String(128), unique=True)
|
|
source: Mapped[str] = mapped_column(String(16), default="LOCAL", server_default="LOCAL") # LOCAL | GTI_GERENCIADOR
|
|
external_client_id: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
|
external_code: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
external_cnpj: Mapped[str | None] = mapped_column(String(20), nullable=True) # digits only, for the client selector search
|
|
active: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")
|
|
synced_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
|
|
class Agent(Base):
|
|
__tablename__ = "agents"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True) # company_id
|
|
name: Mapped[str] = mapped_column(String(128))
|
|
token_hash: Mapped[str] = mapped_column(String(64), unique=True)
|
|
enabled: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")
|
|
last_seen_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_status: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
schema_fingerprint: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
agent_version: Mapped[str | None] = mapped_column(String(32), 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(), onupdate=func.now())
|
|
|
|
|
|
class Installation(Base):
|
|
__tablename__ = "installations"
|
|
__table_args__ = (UniqueConstraint("installation_id", name="uq_installation_identity"), UniqueConstraint("agent_id", name="uq_installation_agent"))
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
agent_id: Mapped[int] = mapped_column(ForeignKey("agents.id"), index=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
installation_id: Mapped[str] = mapped_column(String(36), index=True)
|
|
display_name: Mapped[str] = mapped_column(String(160), default="", server_default="")
|
|
customer_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
|
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
hostname: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True)
|
|
windows_version: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
machine_fingerprint: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
company_code: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
company_name: Mapped[str | None] = mapped_column(String(160), nullable=True)
|
|
company_cnpj_masked: Mapped[str | None] = mapped_column(String(24), nullable=True)
|
|
company_uf: Mapped[str | None] = mapped_column(String(2), nullable=True)
|
|
company_crt: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
company_contribuinte_ipi: Mapped[str | None] = mapped_column(String(1), nullable=True) # C000004.CONTRIBUINTE_IPI, same auto-discovery as company_crt
|
|
firebird_version: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
firebird_ods: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
database_identifier: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
capabilities: Mapped[dict] = mapped_column(JSON, default=dict, server_default="{}")
|
|
fiscal_engine_version: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
dataset_versions: Mapped[dict] = mapped_column(JSON, default=dict, server_default="{}")
|
|
started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_seen_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
|
|
last_status: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
# Central execution control (no local .env toggle): the Hub decides whether this
|
|
# installation's Agent executes jobs, and how often the automatic incremental sync runs.
|
|
processing_enabled: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")
|
|
sync_interval_seconds: Mapped[int] = mapped_column(Integer, default=1800, server_default="1800")
|
|
last_sync_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_sync_changed: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
# RTC auto-apply is authorized centrally per installation and is OFF until an operator explicitly turns it on (the Agent has its own second switch).
|
|
rtc_auto_apply_authorized: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false") # legacy (0027); rtc_write_mode is the source of truth
|
|
rtc_write_mode: Mapped[str] = mapped_column(String(24), default="DISABLED", server_default="DISABLED") # DISABLED | DRY_RUN | APPROVAL_REQUIRED | ENABLED
|
|
# Same model as rtc_write_mode, an entirely separate switch: authorizes ONLY the CFOP_IPPT_WRITE_V1
|
|
# capability (gti_fiscal_agent.cfop_ippt_auto_apply), never RTC. DISABLED by default for every
|
|
# installation - an operator must explicitly turn it on, per installation.
|
|
cfop_ippt_write_mode: Mapped[str] = mapped_column(String(24), default="DISABLED", server_default="DISABLED") # DISABLED | DRY_RUN | APPROVAL_REQUIRED | ENABLED
|
|
# Per-installation FUNCTIONS (central, no local switch). Existing installations keep working exactly as before (corrector on, features configured);
|
|
# a NEW installation starts WAITING_FEATURE_CONFIGURATION (features_configured=False): it heartbeats but processes nothing until an admin chooses.
|
|
corrector_enabled: Mapped[bool] = mapped_column(Boolean, default=False, server_default="true")
|
|
enrichment_mode: Mapped[str] = mapped_column(String(16), default="DISABLED", server_default="DISABLED") # DISABLED | DRY_RUN | ENABLED
|
|
features_configured: Mapped[bool] = mapped_column(Boolean, default=False, server_default="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(), onupdate=func.now())
|
|
|
|
|
|
class AgentSnapshot(Base):
|
|
__tablename__ = "agent_snapshots"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
installation_id: Mapped[int] = mapped_column(ForeignKey("installations.id"), unique=True, index=True)
|
|
total_products: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
fiscally_complete: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
safe_blocked: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
failed_products: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
coverage_percent: Mapped[float] = mapped_column(default=0.0, server_default="0")
|
|
blockers: Mapped[dict] = mapped_column(JSON, default=dict, server_default="{}")
|
|
last_job_status: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
last_job_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_successful_job_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
agent_health: Mapped[str] = mapped_column(String(16), default="UNKNOWN", server_default="UNKNOWN", index=True)
|
|
health_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
version_drift: Mapped[str] = mapped_column(String(16), default="UNKNOWN", server_default="UNKNOWN", index=True)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), index=True)
|
|
|
|
|
|
class AgentEvent(Base):
|
|
__tablename__ = "agent_events"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
installation_id: Mapped[int] = mapped_column(ForeignKey("installations.id"), index=True)
|
|
agent_id: Mapped[int] = mapped_column(ForeignKey("agents.id"), index=True)
|
|
severity: Mapped[str] = mapped_column(String(16), index=True)
|
|
event_type: Mapped[str] = mapped_column(String(48), index=True)
|
|
message: Mapped[str] = mapped_column(Text)
|
|
structured_context: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
job_id: Mapped[int | None] = mapped_column(ForeignKey("jobs.id"), nullable=True, index=True)
|
|
product_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
|
blocker_id: Mapped[int | None] = mapped_column(ForeignKey("blocked_products.id"), nullable=True, index=True)
|
|
is_audit: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false", index=True)
|
|
occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class AdminUser(Base):
|
|
"""Human principal of the control panel.
|
|
|
|
LOCAL: break-glass account with a password (create_admin.py).
|
|
GTI_GERENCIADOR: collaborator provisioned by SSO (central/app/sso.py) — no
|
|
password at all; identified by the panel's immutable subject.
|
|
"""
|
|
__tablename__ = "admin_users"
|
|
__table_args__ = (
|
|
UniqueConstraint("external_subject", name="uq_admin_users_external_subject"),
|
|
CheckConstraint(
|
|
"(auth_source = 'LOCAL' AND password_hash IS NOT NULL AND external_subject IS NULL)"
|
|
" OR (auth_source = 'GTI_GERENCIADOR' AND password_hash IS NULL AND external_subject IS NOT NULL)",
|
|
name="ck_admin_users_auth_source",
|
|
),
|
|
)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
username: Mapped[str] = mapped_column(String(128), unique=True, index=True)
|
|
password_hash: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
auth_source: Mapped[str] = mapped_column(String(16), default="LOCAL", server_default="LOCAL")
|
|
external_subject: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
display_name: Mapped[str | None] = mapped_column(String(160), nullable=True)
|
|
external_role: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
|
|
class SsoUsedAssertion(Base):
|
|
"""jti of every accepted SSO assertion, kept past its expiry to reject replays."""
|
|
__tablename__ = "sso_used_assertions"
|
|
jti: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
subject: Mapped[str] = mapped_column(String(128))
|
|
used_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
|
|
|
|
|
|
class WebSession(Base):
|
|
__tablename__ = "web_sessions"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
admin_user_id: Mapped[int] = mapped_column(ForeignKey("admin_users.id"), index=True)
|
|
token_hash: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
|
csrf_hash: Mapped[str] = mapped_column(String(64))
|
|
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
user_agent: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
|
remote_addr: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
|
|
|
|
class AuditEvent(Base):
|
|
__tablename__ = "audit_events"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
admin_user_id: Mapped[int | None] = mapped_column(ForeignKey("admin_users.id"), nullable=True, index=True)
|
|
tenant_id: Mapped[int | None] = mapped_column(ForeignKey("tenants.id"), nullable=True, index=True)
|
|
action: Mapped[str] = mapped_column(String(64), index=True)
|
|
target_type: Mapped[str] = mapped_column(String(64))
|
|
target_id: Mapped[str] = mapped_column(String(128))
|
|
context: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
severity: Mapped[str] = mapped_column(String(16), default="INFO", server_default="INFO")
|
|
occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class AgentRelease(Base):
|
|
"""A published build of the Windows Agent package. The universal binary/zip is
|
|
shared by every installation; storage_path points at it on the Hub's local
|
|
release storage (never inside PostgreSQL)."""
|
|
__tablename__ = "agent_releases"
|
|
__table_args__ = (UniqueConstraint("version", "platform", "architecture", name="uq_agent_release_version"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
version: Mapped[str] = mapped_column(String(32), index=True)
|
|
platform: Mapped[str] = mapped_column(String(16), default="windows", server_default="windows")
|
|
architecture: Mapped[str] = mapped_column(String(16), default="x64", server_default="x64")
|
|
filename: Mapped[str] = mapped_column(String(255))
|
|
size_bytes: Mapped[int] = mapped_column(Integer)
|
|
sha256: Mapped[str] = mapped_column(String(64), index=True)
|
|
storage_path: Mapped[str] = mapped_column(String(512))
|
|
status: Mapped[str] = mapped_column(String(16), default="DRAFT", server_default="DRAFT", index=True) # DRAFT | PUBLISHED | REVOKED
|
|
release_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
created_by: Mapped[int | None] = mapped_column(ForeignKey("admin_users.id"), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
published_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
|
|
class AgentEnrollment(Base):
|
|
"""A pending (or completed) Agent installation: created by an operator in the
|
|
control panel before any real Agent exists. Carries two distinct, hashed,
|
|
single-use, short-TTL secrets — a download token (for the shareable package
|
|
link) and an enrollment token (embedded in the bundle, consumed once by
|
|
POST /v1/agents/enroll). Never stores either secret in plaintext."""
|
|
__tablename__ = "agent_enrollments"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
enrollment_uuid: Mapped[str] = mapped_column(String(36), unique=True, index=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
display_name: Mapped[str] = mapped_column(String(160))
|
|
expected_hostname: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
release_id: Mapped[int] = mapped_column(ForeignKey("agent_releases.id"), index=True)
|
|
status: Mapped[str] = mapped_column(String(16), default="PENDING", server_default="PENDING", index=True)
|
|
# PENDING | LINK_CREATED | DOWNLOADED | ENROLLED | EXPIRED | REVOKED | FAILED
|
|
created_by: Mapped[int | None] = mapped_column(ForeignKey("admin_users.id"), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
download_token_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
download_token_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
download_max_uses: Mapped[int] = mapped_column(Integer, default=1, server_default="1")
|
|
download_uses: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
downloaded_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
enrollment_token_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
enrollment_token_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
enrollment_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
# The install code typed by the operator into the universal EXE installer.
|
|
# Only its SHA-256 is stored (normalize → digest), never the plaintext.
|
|
# A new code (rotation) rewrites these three fields; the previous code
|
|
# becomes unusable the moment a new one is generated.
|
|
install_code_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
install_code_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
install_code_attempts: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
install_code_created_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
machine_fingerprint_seen: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
hostname_seen: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
hostname_mismatch: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
|
agent_id: Mapped[int | None] = mapped_column(ForeignKey("agents.id"), nullable=True, index=True)
|
|
# Functions chosen when the installation was created (copied to the Installation at enrollment). Nothing chosen => WAITING_FEATURE_CONFIGURATION.
|
|
features_selected: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
|
feature_corrector: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
|
feature_enrichment_mode: Mapped[str] = mapped_column(String(16), default="DISABLED", server_default="DISABLED")
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
|
|
class Job(Base):
|
|
__tablename__ = "jobs"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
agent_id: Mapped[int] = mapped_column(ForeignKey("agents.id"), index=True)
|
|
kind: Mapped[str] = mapped_column(String(32), index=True) # REPROCESS_ALL | REPROCESS_PRODUCTS
|
|
status: Mapped[str] = mapped_column(String(16), index=True, default="PENDING", server_default="PENDING")
|
|
payload: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
attempts: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
result_summary: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
|
|
class BlockedProduct(Base):
|
|
"""Deduplicated safe exception for one tenant/product/field/input."""
|
|
__tablename__ = "blocked_products"
|
|
__table_args__ = (UniqueConstraint("blocker_fingerprint", name="uq_blocked_product_fingerprint"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
installation_id: Mapped[int | None] = mapped_column(ForeignKey("installations.id"), nullable=True, index=True)
|
|
company: Mapped[str] = mapped_column(String(128), index=True)
|
|
product_code: Mapped[str] = mapped_column(String(64), index=True)
|
|
gtin: Mapped[str | None] = mapped_column(String(16), nullable=True, index=True)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
blocker_type: Mapped[str] = mapped_column(String(48), index=True)
|
|
field: Mapped[str] = mapped_column(String(64), index=True)
|
|
reason_code: Mapped[str] = mapped_column(String(96))
|
|
evidence_summary: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
input_fingerprint: Mapped[str] = mapped_column(String(64), index=True)
|
|
blocker_fingerprint: Mapped[str] = mapped_column(String(64))
|
|
first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
retry_count: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
next_retry_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
resolution_source: Mapped[str | None] = mapped_column(String(48), nullable=True)
|
|
|
|
|
|
class ClassificationRequest(Base):
|
|
__tablename__ = "classification_requests"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
agent_id: Mapped[int] = mapped_column(ForeignKey("agents.id"), index=True)
|
|
fingerprint: Mapped[str] = mapped_column(String(64), unique=True)
|
|
payload: Mapped[dict] = mapped_column(JSON)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
|
|
class ClassificationResult(Base):
|
|
__tablename__ = "classification_results"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
request_id: Mapped[int] = mapped_column(ForeignKey("classification_requests.id"), unique=True)
|
|
result: Mapped[dict] = mapped_column(JSON)
|
|
|
|
|
|
class ClassificationCache(Base):
|
|
__tablename__ = "classification_cache"
|
|
fingerprint: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
result: Mapped[dict] = mapped_column(JSON)
|
|
|
|
|
|
class RtcCstIbsCbsEntry(Base):
|
|
"""RTC CST-IBS/CBS (sheet 'CST ...' of the official cClassTrib workbook)."""
|
|
__tablename__ = "rtc_cst_ibscbs_entries"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "cst", name="uq_rtc_cst_ibscbs"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
cst: Mapped[str] = mapped_column(String(8), index=True)
|
|
description: Mapped[str] = mapped_column(Text)
|
|
indicators: Mapped[dict] = mapped_column(JSON)
|
|
|
|
|
|
class RtcCClassTribEntry(Base):
|
|
"""RTC cClassTrib (sheet 'cClass ...'). Every official column is kept:
|
|
key fields typed, all ind_* flags in `indicators` keyed by official header."""
|
|
__tablename__ = "rtc_cclasstrib_entries"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "cclasstrib", name="uq_rtc_cclasstrib"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
cst: Mapped[str] = mapped_column(String(8), index=True)
|
|
cst_description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
cclasstrib: Mapped[str] = mapped_column(String(8), index=True)
|
|
name: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
lc_redacao: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
lc_214: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
regulamento_cbs: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
regulamento_ibs: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
tipo_aliquota: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
p_red_ibs: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
p_red_cbs: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
d_ini_vig: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
d_fim_vig: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
data_atualizacao: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
anexo: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
link: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
indicators: Mapped[dict] = mapped_column(JSON)
|
|
|
|
|
|
class RtcCbsRateEntry(Base):
|
|
"""RTC CBS rate per year (IT 2026.002). rate is NULL when the official
|
|
text has no number yet (e.g. 'aguarda legislação')."""
|
|
__tablename__ = "rtc_cbs_rate_entries"
|
|
__table_args__ = (UniqueConstraint("dataset_id", "year", name="uq_rtc_cbs_rate_year"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
dataset_id: Mapped[int] = mapped_column(ForeignKey("datasets.id"), index=True)
|
|
year: Mapped[int] = mapped_column(Integer, index=True)
|
|
rate_text: Mapped[str] = mapped_column(Text)
|
|
rate: Mapped[float | None] = mapped_column(nullable=True)
|
|
|
|
|
|
class FiscalContextEntry(Base):
|
|
"""Operator-supplied fiscal fact (product role, origin, ST retention, treatment, ...). Hierarchy: PRODUCT > GROUP > COMPANY."""
|
|
__tablename__ = "fiscal_context_entries"
|
|
__table_args__ = (UniqueConstraint("tenant_id", "company_key", "scope", "scope_key", "key", name="uq_fiscal_context_entry"),)
|
|
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)
|
|
scope: Mapped[str] = mapped_column(String(8))
|
|
scope_key: Mapped[str] = mapped_column(String(64), default="", server_default="")
|
|
key: Mapped[str] = mapped_column(String(48))
|
|
value: Mapped[str] = mapped_column(Text)
|
|
source: Mapped[str] = mapped_column(String(16), default="OPERATOR", server_default="OPERATOR")
|
|
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)
|
|
status: Mapped[str] = mapped_column(String(24), default="OBSERVED", server_default="OBSERVED", index=True) # OBSERVED | CORROBORATED | EXTERNAL_CORROBORATED | VERIFIED_KB | CONFLICT | STALE
|
|
|
|
|
|
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())
|
|
|
|
|
|
class AiUsageLedger(Base):
|
|
"""Central persistent usage ledger reserved before any future provider call."""
|
|
__tablename__ = "ai_usage_ledger"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
provider: Mapped[str] = mapped_column(String(64))
|
|
model: Mapped[str] = mapped_column(String(64))
|
|
run_id: Mapped[str] = mapped_column(String(128), index=True)
|
|
tenant: Mapped[str] = mapped_column(String(128), index=True)
|
|
items: Mapped[int] = mapped_column(Integer, default=0)
|
|
input_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
|
output_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
|
cost: Mapped[float] = mapped_column(default=0.0)
|
|
timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class ExternalProductCache(Base):
|
|
"""Versioned GTIN cache; legal/provider flags remain explicit metadata."""
|
|
__tablename__ = "external_product_cache"
|
|
__table_args__ = (UniqueConstraint("gtin", "provider", "provider_record_version", name="uq_external_product_cache_record"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
gtin: Mapped[str] = mapped_column(String(16), index=True)
|
|
provider: Mapped[str] = mapped_column(String(64))
|
|
provider_record_version: Mapped[str] = mapped_column(String(64))
|
|
retrieved_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
raw_hash: Mapped[str] = mapped_column(String(64))
|
|
normalized_data: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
cache_allowed: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
redistribution_allowed: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
|
|
|
|
class RtcWriteAudit(Base):
|
|
"""Append-only record of every attempted C000025.CCLASSTRIB write (applied or aborted). Holds the old value, so each change is revertible by hand."""
|
|
__tablename__ = "rtc_write_audits"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
installation_id: Mapped[int] = mapped_column(ForeignKey("installations.id"), index=True)
|
|
agent_id: Mapped[int] = mapped_column(ForeignKey("agents.id"), index=True)
|
|
job_id: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
|
client: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
product_code: Mapped[str] = mapped_column(String(64), index=True)
|
|
field: Mapped[str] = mapped_column(String(32))
|
|
outcome: Mapped[str] = mapped_column(String(16), index=True)
|
|
reason: Mapped[str | None] = mapped_column(String(96), nullable=True)
|
|
old_value: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
new_value: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
rule_id: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
evidence: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
official_source_version: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
local_catalog_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
written_at: Mapped[str | None] = mapped_column(String(40), nullable=True)
|
|
rule_version: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
company_crt: Mapped[str | None] = mapped_column(String(8), nullable=True)
|
|
authorization_origin: Mapped[str | None] = mapped_column(String(48), nullable=True)
|
|
description_snapshot: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
installation_uuid: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
rtc_status: Mapped[str | None] = mapped_column(String(24), nullable=True)
|
|
local_catalog_state: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
action: Mapped[str | None] = mapped_column(String(16), nullable=True) # APPLY | ROLLBACK
|
|
rollback_state: Mapped[str | None] = mapped_column(String(16), nullable=True) # REQUESTED | DONE | BLOCKED (on the original APPLY record)
|
|
rollback_of: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
|
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class CfopIpptWriteAudit(Base):
|
|
"""Append-only record of every attempted C000025.CFOP/IPPT write (applied or aborted) under the
|
|
CFOP_IPPT_WRITE_V1 capability. Holds the old value, so each change is revertible by hand."""
|
|
__tablename__ = "cfop_ippt_write_audits"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
installation_id: Mapped[int] = mapped_column(ForeignKey("installations.id"), index=True)
|
|
agent_id: Mapped[int] = mapped_column(ForeignKey("agents.id"), index=True)
|
|
job_id: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
|
product_code: Mapped[str] = mapped_column(String(64), index=True)
|
|
field: Mapped[str] = mapped_column(String(16)) # CFOP | IPPT only
|
|
outcome: Mapped[str] = mapped_column(String(16), index=True) # APPLIED | ABORTED | FAILED | ROLLED_BACK
|
|
reason: Mapped[str | None] = mapped_column(String(96), nullable=True)
|
|
old_value: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
new_value: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
action: Mapped[str | None] = mapped_column(String(16), nullable=True) # APPLY | ROLLBACK
|
|
rollback_of: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
|
written_at: Mapped[str | None] = mapped_column(String(40), nullable=True)
|
|
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class RtcCandidate(Base):
|
|
"""Server-side preview of the products the Agent could fill (R3 only). Rows are proposals; only the Agent's re-derivation at write time decides."""
|
|
__tablename__ = "rtc_candidates"
|
|
__table_args__ = (UniqueConstraint("installation_id", "product_code", name="uq_rtc_candidate_product"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
installation_id: Mapped[int] = mapped_column(ForeignKey("installations.id"), index=True)
|
|
product_code: Mapped[str] = mapped_column(String(64))
|
|
description: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
ncm: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
current_value: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
proposed: Mapped[str] = mapped_column(String(16))
|
|
cst_ibscbs: Mapped[str | None] = mapped_column(String(8), nullable=True)
|
|
rule_id: Mapped[str] = mapped_column(String(32))
|
|
basis: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(16), default="PENDING", server_default="PENDING", index=True) # PENDING | APPROVED | REJECTED | APPLIED | STALE
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
|
|
class ProductEnrichment(Base):
|
|
"""Per (installation, product) state of the new-product enrichment. Historical products never get a row (no mass provider use)."""
|
|
__tablename__ = "product_enrichments"
|
|
__table_args__ = (UniqueConstraint("installation_id", "product_code", name="uq_product_enrichment"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
installation_id: Mapped[int] = mapped_column(ForeignKey("installations.id"), index=True)
|
|
product_code: Mapped[str] = mapped_column(String(64))
|
|
gtin: Mapped[str | None] = mapped_column(String(16), nullable=True, index=True)
|
|
description: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
status: Mapped[str] = mapped_column(String(32), default="PENDING", server_default="PENDING", index=True)
|
|
# PENDING | RESOLVED | APPLIED | PARTIAL | NOT_RESOLVED | CONFLICT | PENDING_PROVIDER_QUOTA | PENDING_PROVIDERS_OFFLINE | INVALID_GTIN
|
|
decisions: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
fields_applied: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
human_overridden: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
attempts: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
next_attempt_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
|
|
latency_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
reevaluate: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false", index=True) # set by panel / REPROCESS / SYNC / KB change; consumed by the Agent
|
|
last_attempt_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
ui_status: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) # human-facing state (never a silent product)
|
|
reason_code: Mapped[str | None] = mapped_column(String(48), nullable=True)
|
|
detected_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 ProductFieldAudit(Base):
|
|
"""Append-only audit of every field the enrichment wrote (or refused to). Never holds secrets."""
|
|
__tablename__ = "product_field_audits"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
|
|
installation_id: Mapped[int] = mapped_column(ForeignKey("installations.id"), index=True)
|
|
agent_id: Mapped[int] = mapped_column(ForeignKey("agents.id"), index=True)
|
|
product_code: Mapped[str] = mapped_column(String(64), index=True)
|
|
gtin: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
description_snapshot: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
field: Mapped[str] = mapped_column(String(48))
|
|
old_value: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
new_value: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
rule: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
kb_status: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
evidence: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
dataset_versions: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
authorization_origin: Mapped[str | None] = mapped_column(String(48), nullable=True)
|
|
job_id: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
outcome: Mapped[str] = mapped_column(String(24), default="APPLIED", server_default="APPLIED") # APPLIED | NO_WRITE_REQUIRED | ABORTED | HUMAN_OVERRIDDEN
|
|
reason: Mapped[str | None] = mapped_column(String(96), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
|
|
class ProviderState(Base):
|
|
"""Live state of each external product provider (owned by the Hub: quota belongs to the Hub, never to Agents)."""
|
|
__tablename__ = "provider_states"
|
|
provider: Mapped[str] = mapped_column(String(32), primary_key=True)
|
|
configured: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
|
|
status: Mapped[str] = mapped_column(String(16), default="NOT_CONFIGURED", server_default="NOT_CONFIGURED") # ONLINE | DEGRADED | RATE_LIMITED | AUTH_ERROR | OFFLINE | NOT_CONFIGURED
|
|
quota_kind: Mapped[str] = mapped_column(String(16), default="DOCUMENTED_RATE", server_default="DOCUMENTED_RATE") # REMOTE_QUOTA | LOCAL_ESTIMATE | DOCUMENTED_RATE
|
|
daily_limit: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
remaining: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
reset_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
rate_per_minute: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
circuit_open_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
consecutive_failures: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
last_request_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_success_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_error: Mapped[str | None] = mapped_column(String(160), nullable=True)
|
|
last_error_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_latency_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
|
|
class ProviderUsageDaily(Base):
|
|
__tablename__ = "provider_usage_daily"
|
|
__table_args__ = (UniqueConstraint("provider", "day", name="uq_provider_usage_day"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
provider: Mapped[str] = mapped_column(String(32), index=True)
|
|
day: Mapped[str] = mapped_column(String(10), index=True)
|
|
requests: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
success: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
not_found: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
errors: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
rate_limited: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
timeouts: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
cache_hits: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
cache_misses: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
found: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
ncm_found: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
kb_hits: Mapped[int] = mapped_column(Integer, default=0, server_default="0")
|
|
|
|
|
|
class GtinLookupCache(Base):
|
|
"""Positive and negative (NOT_FOUND) provider results per GTIN, with TTL. Single place that spares quota across all installations."""
|
|
__tablename__ = "gtin_lookup_cache"
|
|
__table_args__ = (UniqueConstraint("gtin", "provider", name="uq_gtin_lookup_cache"),)
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
gtin: Mapped[str] = mapped_column(String(16), index=True)
|
|
provider: Mapped[str] = mapped_column(String(32))
|
|
found: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
data: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
fetched_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
|
|
|
|
|
|
class SourceRefreshRun(Base):
|
|
"""Observability of official fiscal sources (NCM Siscomex, ...): one row per refresh attempt."""
|
|
__tablename__ = "source_refresh_runs"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
source: Mapped[str] = mapped_column(String(32), index=True)
|
|
source_url: Mapped[str] = mapped_column(Text)
|
|
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)
|
|
status: Mapped[str] = mapped_column(String(16), default="RUNNING", server_default="RUNNING") # RUNNING | UPDATED | UNCHANGED | FAILED
|
|
sha256: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
version_label: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
record_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
error: Mapped[str | None] = mapped_column(String(300), nullable=True)
|
|
|
|
|
|
class KbPromotionAudit(Base):
|
|
"""Why a GTIN/NCM became (or stopped being) trustworthy: statuses, evidence families, source references and the official dataset hash at that moment."""
|
|
__tablename__ = "kb_promotion_audits"
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
kb_product_id: Mapped[int] = mapped_column(ForeignKey("kb_products.id"), index=True)
|
|
gtin: Mapped[str | None] = mapped_column(String(16), nullable=True, index=True)
|
|
field: Mapped[str] = mapped_column(String(24), default="ncm", server_default="ncm")
|
|
value: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
old_status: Mapped[str | None] = mapped_column(String(24), nullable=True)
|
|
new_status: Mapped[str] = mapped_column(String(24))
|
|
families: Mapped[list] = mapped_column(JSON, default=list)
|
|
sources: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
official_dataset: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
rule_version: Mapped[str] = mapped_column(String(32))
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|