feat: audited-decision gate for PIS/COFINS and CFOP_ENTRADA NOT_APPLICABLE/OPERATION_TIME
Missao 2 (fechamento de lacunas fiscais, read-only + UI/engine gated): - field_contract: pis_cofins_profile now also accepts the literal sentinels NOT_APPLICABLE/OPERATION_TIME, resolving to NA only from an audited FiscalContextEntry - never inferred from emission statistics. CFOP_ENTRADA under an already-decided entry_scope=MIXED_OR_DYNAMIC now resolves to NA instead of MANUAL, so it stops blocking product completeness and stops re-asking an already-answered question. Regression tests prove: no ADMIN decision => unchanged old behavior; decision present => gated bucket, no write. - context_keys: validate_entry accepts the two sentinels for pis_cofins_profile. - Contexto Fiscal UI: PIS/COFINS and CFOP_ENTRADA cards show the Bloco 2 cross-database evidence (5 installations, CRT 1 and 3: emission never reads the PIS/COFINS cadastral fields anywhere) and a link to an ADMIN-facing, anonymized dossie-for-the-accountant; entry_scope is now a dropdown. - release/tests: decoupled the Hub's GTI_FISCAL_VERSION from the Agent's own version (test_versions_are_coherent_across_hub_and_packages vs the new test_agent_version_is_internally_coherent) - a Hub-only release must not require touching, rebuilding or republishing the Agent. - New read-only evidence: docs/runs/GTI_dryrun_universe_summary.json (527 products, Agent 2's real database), docs/runs/BLOCO2_PIS_COFINS_CROSS_DB_V1.json (5 installations), docs/fiscal_completeness_matrix.md, docs/dossie_contador.md (internal, real names) + central/static/docs/dossie_contador.md (public, anonymized - no CNPJ/razao social/product names), docs/AGENT2_UPGRADE_RUNBOOK_V1.md. - CLAUDE.md: recorded the permanent fiscal-mission rules (no AI/scraping, no Firebird schema changes, deploy only via deploy_hub.ps1, never reenroll, no remote access to client Agent hosts, no client data in unauthenticated static routes). No schema/migration change. Zero Firebird writes (new scripts never open a write connection). Hub only - Agent code untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -79,3 +79,33 @@ guarded executor (path allowlist, company-context guard, schema/type/length guar
|
||||
|
||||
## Production deploy rule (Hub)
|
||||
Normal deploy = `powershell -ExecutionPolicy Bypass -File .\scripts\deploy_hub.ps1 [-PublishAgent -Version X]` (docs/DEPLOY_HUB.md). Do not recreate ad-hoc deploy scripts (deploy115.py etc.) and do not improvise SSH/docker against 10.0.10.117. The script handles VPN/SSH retry, backup, upload, migrations, rebuild, health, release publish, download verification, and automatic rollback; the real error is in `logs/last_deploy_result.json` and the log.
|
||||
|
||||
## Permanent mission rules (fiscal completion missions, recorded 2026-09-28)
|
||||
|
||||
- No AI/LLM/embeddings, no paid API, no scraping — every rule/decision is derived from real ERP
|
||||
evidence or an explicit, audited operator/ADMIN decision (`FiscalContextEntry`), never inferred
|
||||
or guessed.
|
||||
- No Firebird triggers or schema changes, ever, for any discovery/completion mission. All queries
|
||||
against any Firebird database (client, lab copy, or disposable test DB) are read-only unless the
|
||||
exact path is on an explicit, mission-scoped write allowlist (see above) — writes still go only
|
||||
through the guarded executor.
|
||||
- Deploy only via `scripts\deploy_hub.ps1` (see "Production deploy rule" above) — never ad-hoc
|
||||
SSH/docker/psql against 10.0.10.117.
|
||||
- If the Agent (agent/) changes in a way that affects its runtime behavior: bump the single-source
|
||||
version, build a new installer, validate it in a fresh Windows Sandbox (install, upgrade in-place,
|
||||
identity preserved), and never overwrite an already-published release. A pure version-label sync
|
||||
bump (release/VERSION, pyproject.toml files, the `AGENT_VERSION` default string) that changes no
|
||||
Agent behavior is not itself an "Agent change" requiring a new installer.
|
||||
- Never reenroll an existing installation (never mint a new `installation_id`/token for a machine
|
||||
that already has one) — an in-place upgrade always preserves identity and local SQLite state.
|
||||
- No remote-execution channel to any client's Agent host, ever (by design — see
|
||||
`docs/rtc_auto_apply.md`). An Agent version upgrade on a real client machine is always performed
|
||||
by a human operator on-site; the assistant never attempts RDP/WinRM/SSH/PowerShell-remoting into
|
||||
a client host, and never asks for or accepts a production `AGENT_TOKEN`/admin session credential
|
||||
pasted in chat to work around that — live validation of a real installation, when needed, comes
|
||||
from the operator (a panel screenshot, or a value they read and paste themselves).
|
||||
- No real client/production data (CNPJ, razão social, product names/descriptions, per-product NCM)
|
||||
is ever published in a location a non-admin can reach (e.g. `central/static/`). Any evidence
|
||||
artifact referenced from a UI card must either be served through an authenticated ADMIN-only
|
||||
route, or be anonymized (generic client labels, no names/documents/descriptions) before being
|
||||
published as a static file.
|
||||
|
||||
@@ -61,11 +61,13 @@ def validate_entry(entry):
|
||||
float(value.replace(",", "."))
|
||||
except ValueError:
|
||||
return None, f"{key} must be numeric"
|
||||
if key == "pis_cofins_profile":
|
||||
if key == "pis_cofins_profile" and value.upper() not in ("NOT_APPLICABLE", "OPERATION_TIME"):
|
||||
try:
|
||||
prof = json.loads(value)
|
||||
except ValueError:
|
||||
return None, "pis_cofins_profile must be JSON"
|
||||
return None, "pis_cofins_profile must be JSON, or exactly NOT_APPLICABLE / OPERATION_TIME"
|
||||
if not isinstance(prof, dict) or not prof or set(prof) - PROFILE_KEYS:
|
||||
return None, f"pis_cofins_profile keys must be a non-empty subset of {sorted(PROFILE_KEYS)}"
|
||||
elif key == "pis_cofins_profile":
|
||||
value = value.upper()
|
||||
return {"scope": scope, "scope_key": scope_key if scope != "COMPANY" else "", "key": key, "value": value, "reason": (entry.get("reason") or "").strip() or None}, None
|
||||
|
||||
@@ -369,7 +369,10 @@ def resolve_product(p, d, company, refs, ev, ctx, manual_ncm=None):
|
||||
if valid_e:
|
||||
out["CFOP_ENTRADA"] = _r("CFOP_ENTRADA", ALREADY, cfe, reason="DECLARED_VALID_OPERATION_DEPENDENT_MIXED_SCOPE")
|
||||
else:
|
||||
out["CFOP_ENTRADA"] = _r("CFOP_ENTRADA", MANUAL, cfe, reason="CFOP_ENTRADA_OPERATION_DEPENDENT_MIXED_SCOPE")
|
||||
# Audited ADMIN decision (entry_scope=MIXED_OR_DYNAMIC) already says no static CFOP_ENTRADA is
|
||||
# ever correct here: NA (never MANUAL) so this doesn't block product completeness and never
|
||||
# raises a spurious "answer entry_scope" question — the scope question was already answered.
|
||||
out["CFOP_ENTRADA"] = _r("CFOP_ENTRADA", NA, cfe, reason="CFOP_ENTRADA_OPERATION_DEPENDENT_MIXED_SCOPE")
|
||||
else:
|
||||
if scope_ctx in ("INTRA", "INTER") and role in ("RESALE", "OWN_PRODUCTION", "INPUT") and st != "UNKNOWN" and not (st == "SUBJECT" and withheld is None):
|
||||
first = "1" if scope_ctx == "INTRA" else "2"
|
||||
@@ -470,13 +473,23 @@ def resolve_product(p, d, company, refs, ev, ctx, manual_ncm=None):
|
||||
|
||||
# ---------------- PIS / COFINS ----------------
|
||||
prof_raw, prof_scope = ctx.get("pis_cofins_profile", code, group)
|
||||
import json as _json
|
||||
try:
|
||||
prof = _json.loads(prof_raw) if prof_raw else {}
|
||||
except ValueError:
|
||||
prof = {}
|
||||
for field, pkey, dom in (("COD_ENTRADA_PIS", "pis_in_cst", "cst_pis"), ("COD_SAIDA_PIS", "pis_out_cst", "cst_pis"), ("ALIQ_PIS", "pis_rate", None),
|
||||
("COD_ENTRADA_COFINS", "cofins_in_cst", "cst_cofins"), ("COD_SAIDA_COFINS", "cofins_out_cst", "cst_cofins"), ("ALIQ_COFINS", "cofins_rate", None)):
|
||||
PIS_COFINS_FIELDS = ("COD_ENTRADA_PIS", "pis_in_cst", "cst_pis"), ("COD_SAIDA_PIS", "pis_out_cst", "cst_pis"), ("ALIQ_PIS", "pis_rate", None), \
|
||||
("COD_ENTRADA_COFINS", "cofins_in_cst", "cst_cofins"), ("COD_SAIDA_COFINS", "cofins_out_cst", "cst_cofins"), ("ALIQ_COFINS", "cofins_rate", None)
|
||||
if prof_raw in ("NOT_APPLICABLE", "OPERATION_TIME"):
|
||||
# Audited ADMIN decision only (Missão 2 / Bloco 2.4): an operator explicitly recorded, in FiscalContextEntry,
|
||||
# that this company/product's PIS/COFINS is never taken from the cadastral fields (real evidence: SGV emission
|
||||
# never reads them) or varies per operation. Never inferred automatically from emission statistics -
|
||||
# absent this exact recorded value, the field stays MANUAL (see the final `else` below), unchanged.
|
||||
for field, _pkey, _dom in PIS_COFINS_FIELDS:
|
||||
out[field] = _r(field, NA, cur[field], reason="ADMIN_DECISION_PIS_COFINS_" + prof_raw)
|
||||
prof = None
|
||||
else:
|
||||
import json as _json
|
||||
try:
|
||||
prof = _json.loads(prof_raw) if prof_raw else {}
|
||||
except ValueError:
|
||||
prof = {}
|
||||
for field, pkey, dom in ([] if prof is None else PIS_COFINS_FIELDS):
|
||||
v = cur[field]
|
||||
want = _s(prof.get(pkey)) if prof else None
|
||||
if want is not None and dom and want not in refs[dom]:
|
||||
|
||||
@@ -23,20 +23,28 @@ DECISIONS = [
|
||||
"key": "pis_cofins_profile", "scope": "COMPANY", "title": "Perfil de PIS/COFINS (código e alíquota de entrada/saída)",
|
||||
"evidence": "34.946 itens de NFC-e emitidos; 3.336 (9,5%) carregam um CST de PIS/COFINS na nota, sempre o código 01 - mas os "
|
||||
"mesmos produtos têm os campos de cadastro (código de saída, alíquota) sempre em branco no SGV, incluindo os poucos "
|
||||
"produtos que TÊM esses campos preenchidos manualmente (a nota emitida ignora o valor cadastrado). Isso indica que a "
|
||||
"emissão não lê esse cadastro para este cliente - mas não é conclusivo o bastante para marcar os 6 campos como "
|
||||
"não aplicável sem confirmação humana.", "suggestion": None,
|
||||
"produtos que TÊM esses campos preenchidos manualmente (a nota emitida ignora o valor cadastrado). Reprocessado em "
|
||||
"2026-09-28 (Missão 2 / Bloco 2.1): os 3.336 itens estão concentrados quase inteiramente em 2022 (3.336 de 3.669 "
|
||||
"itens daquele ano; 0% em 2018 e em 2023-2026) - coincide com a mesma janela já sinalizada como anômala em outra "
|
||||
"auditoria (drift de identidade de produto). O mesmo padrão de cadastro nunca preenchido na emissão se repete, sem "
|
||||
"exceção, nos outros 4 bancos auditados (CONSTRUCAO, MMMODAS, RINCAO CRT=3, UNIAO): 0% de itens com CST de "
|
||||
"PIS/COFINS em todo o histórico, em todos os anos. Não é um comportamento específico deste cliente. "
|
||||
"Ver docs/dossie_contador.md e docs/runs/BLOCO2_PIS_COFINS_CROSS_DB_V1.json.", "suggestion": None,
|
||||
"affected_fields": ["COD_ENTRADA_PIS", "COD_SAIDA_PIS", "ALIQ_PIS", "COD_ENTRADA_COFINS", "COD_SAIDA_COFINS", "ALIQ_COFINS"],
|
||||
"confidence": "Baixa para um perfil único da empresa; considerar tratar como não aplicável ao cadastro (pendente de confirmação contábil)",
|
||||
"measured_at": "2026-09-27",
|
||||
"confidence": "Baixa para um perfil único da empresa; considerar NOT_APPLICABLE ou OPERATION_TIME (pendente de confirmação contábil)",
|
||||
"measured_at": "2026-09-28",
|
||||
"value_hint": 'JSON de perfil (ex.: {"pis_out_cst":"49","pis_in_cst":"98","pis_rate":"0","cofins_out_cst":"49","cofins_in_cst":"98","cofins_rate":"0"}) '
|
||||
'OU exatamente NOT_APPLICABLE (nunca é lido pelo cadastro) OU OPERATION_TIME (varia por operação) - nunca inferido automaticamente.',
|
||||
"dossier_link": "/static/docs/dossie_contador.md",
|
||||
},
|
||||
{
|
||||
"key": "entry_scope", "scope": "COMPANY", "title": "CFOP de entrada padrão (compras dentro/fora do estado)",
|
||||
"evidence": "Apenas 12 documentos de compra reais no ERP para 526 produtos; 11 sem CFOP preenchido, 1 com CFOP intra-estadual. "
|
||||
"Evidência real insuficiente para qualquer sugestão - o CFOP de entrada normalmente é definido pela operação/documento "
|
||||
"de compra, não pelo cadastro do produto.", "suggestion": None,
|
||||
"affected_fields": ["CFOP_ENTRADA"], "confidence": "Muito baixa - recomendação: tratar como definido no momento da operação, não como cadastro obrigatório",
|
||||
"measured_at": "2026-09-27",
|
||||
"de compra, não pelo cadastro do produto. Ver docs/dossie_contador.md.", "suggestion": None,
|
||||
"affected_fields": ["CFOP_ENTRADA"], "confidence": "Muito baixa - recomendação: MIXED_OR_DYNAMIC (definido na operação, não bloqueia o produto)",
|
||||
"measured_at": "2026-09-27", "allowed_values": ["INTRA", "INTER", "MIXED_OR_DYNAMIC"],
|
||||
"dossier_link": "/static/docs/dossie_contador.md",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -4,5 +4,5 @@ kept identical to this) and what an AgentRelease row's `version` column holds
|
||||
once published — never a second, independent version number. Fiscal semantics
|
||||
are versioned separately (ENGINE_VERSION/DECISION_VERSION in classification.py,
|
||||
DECISION_ENGINE_VERSION etc.) and are NOT changed by a release bump."""
|
||||
GTI_FISCAL_VERSION = "1.3.9"
|
||||
GTI_FISCAL_VERSION = "1.3.10"
|
||||
API_COMPAT_MAJOR_MINOR = "1.0" # an Agent 1.0.x/1.1.x talks to any Hub 1.0.x
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "gti-fiscal-hub"
|
||||
version = "1.3.9"
|
||||
version = "1.3.10"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"fastapi>=0.115,<1",
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# Dossiê para o contador (2026-09-28)
|
||||
|
||||
Este documento não afirma nenhuma regra fiscal. Cada pergunta mostra a evidência real medida
|
||||
(somente leitura, sem gravação em nenhum banco) e qual clique na tela "Contexto Fiscal" do painel
|
||||
a resposta libera. Identificadores de cliente foram substituídos por rótulos genéricos
|
||||
(Cliente A–E); nenhum nome de produto, CNPJ ou razão social aparece aqui.
|
||||
|
||||
## (a) Em CRT=1, o PIS/COFINS por item segue o cadastro do produto ou a operação/regime?
|
||||
|
||||
**Evidência**: em 5 instalações auditadas (Cliente A, CRT 1; Clientes B/C/E, CRT 1; Cliente D,
|
||||
CRT 3), o cadastro do produto **nunca** tem os campos de PIS/COFINS usados pela emissão: mesmo
|
||||
quando a nota fiscal emitida traz um CST de PIS/COFINS preenchido, o cadastro está em branco para
|
||||
o mesmo produto (23 casos confirmados no Cliente A). Nos Clientes B, C, D e E, a emissão nunca
|
||||
preenche o CST de PIS/COFINS (0% dos itens, em todos os anos de histórico disponíveis). Isso indica
|
||||
que a emissão não lê o cadastro — mas não decide se o valor correto é "não aplicável", "por
|
||||
operação" ou um perfil fixo por empresa. **Libera**: card "Perfil de PIS/COFINS" → escolher
|
||||
`NOT_APPLICABLE`, `OPERATION_TIME` ou um perfil JSON de 6 campos.
|
||||
|
||||
## (b) O CST 01 emitido em ~9,5% dos itens é intencional?
|
||||
|
||||
**Evidência**: dos itens de NFC-e emitidos pelo Cliente A, 9,5% trazem CST de PIS/COFINS = "01" —
|
||||
mas esses itens estão concentrados quase inteiramente no ano de 2022 (mais de 90% dos itens
|
||||
daquele ano); nos demais anos com dados, a taxa é 0%. O ano de 2022 já havia sido sinalizado em
|
||||
outra auditoria interna como um período com problema de integridade de dados. A leitura mais
|
||||
provável é que **não é uma política atual deliberada** — é um comportamento isolado de um período
|
||||
específico, possivelmente uma versão antiga do software de emissão ou uma configuração temporária.
|
||||
Não decidimos isso por evidência insuficiente para confirmar a causa exata; a pergunta é se o
|
||||
contador reconhece esse padrão de 2022.
|
||||
|
||||
## (c) CFOP_ENTRADA no cadastro do produto é necessário ou é sempre definido na nota de compra?
|
||||
|
||||
**Evidência**:
|
||||
- **Schema do banco**: a coluna é opcional no próprio banco de dados (sem obrigatoriedade, sem
|
||||
valor padrão, sem regra de validação) — o sistema nunca exige um valor aqui a nível de banco.
|
||||
- **Uso real é muito diferente entre clientes**: em dois clientes o campo está praticamente sempre
|
||||
em branco; em outros três está preenchido em quase 100% dos produtos, com valores plausíveis —
|
||||
ou seja, alguns clientes efetivamente mantêm esse cadastro, outros não.
|
||||
- Isso mostra que não existe uma regra universal: depende de como cada loja opera. Por isso o motor
|
||||
já trata isso como uma decisão por instalação (`entry_scope`), nunca uma regra fixa do sistema.
|
||||
- **Não verificado ainda** (requer acesso à tela de entrada de nota do sistema, que não fizemos):
|
||||
se a tela de lançamento de nota de compra lê o CFOP_ENTRADA do cadastro como sugestão inicial, ou
|
||||
sempre pede a digitação manual. Roteiro de 5 passos para teste manual:
|
||||
1. Anote o CFOP_ENTRADA atual de um produto de teste (ou deixe em branco).
|
||||
2. Abra o cadastro do produto e grave um CFOP_ENTRADA diferente (ex.: 1102).
|
||||
3. Lance uma nota de entrada (compra) desse mesmo produto.
|
||||
4. Veja se o CFOP do item da nota já vem preenchido com o valor do cadastro, ou em branco.
|
||||
5. Repita com CFOP_ENTRADA em branco no cadastro e confirme se a nota exige digitação manual.
|
||||
- **Libera**: card "CFOP de entrada padrão" → `INTRA`, `INTER` ou `MIXED_OR_DYNAMIC` (este último
|
||||
não bloqueia mais a completude do produto).
|
||||
|
||||
## (d) O que mais o levantamento deixou como decisão pendente com evidência insuficiente?
|
||||
|
||||
- **Origem da mercadoria** (nacional/importado): nenhuma fonte automática existe para decidir isso
|
||||
— a grande maioria dos produtos não tem nenhuma evidência. Só pode ser respondido produto a
|
||||
produto ou por grupo, nunca por uma regra geral.
|
||||
- **Tratamento de ICMS** para um pequeno grupo de produtos cujo código fiscal está em branco —
|
||||
parte é cadastro de teste/incompleto, mas alguns produtos reais também aparecem.
|
||||
- Um pequeno número de produtos com o código fiscal (CSOSN) em branco hoje. Nenhuma simulação
|
||||
escreve nada; um valor padrão só se aplica de fato após a confirmação do "Tratamento padrão de
|
||||
ICMS" no card correspondente, e mesmo assim o motor consulta o tratamento antes de propor o
|
||||
código (nunca aplica um padrão cegamente).
|
||||
|
||||
## Como usar este dossiê
|
||||
|
||||
Cada resposta é dada uma única vez, no painel ("Contexto Fiscal", aba do Agent), nunca escrevendo
|
||||
nada no ERP diretamente — a decisão fica registrada e auditada, e só depois disso o motor passa a
|
||||
considerar o campo resolvido (ou explicitamente não-bloqueante). Nenhuma decisão aqui é assumida
|
||||
por padrão.
|
||||
@@ -14,11 +14,11 @@
|
||||
{% for d in fiscal_context %}<article class="panel" style="margin-top:12px"><div class="panel-title"><h3>{{ d.title }}</h3><span class="badge">Escopo: {{ {"COMPANY":"Empresa","GROUP":"Grupo","PRODUCT":"Produto"}[d.scope] }}</span></div>
|
||||
<dl class="facts"><dt>Valor atual</dt><dd>{{ d.current_value or "Não confirmado" }}{% if d.last_changed %} <small class="muted">· alterado em {{ d.last_changed }} por {{ d.last_changed_by }}</small>{% endif %}</dd>
|
||||
<dt>Sugestão</dt><dd>{{ d.suggestion or "Sem sugestão automática — evidência insuficiente" }}</dd>
|
||||
<dt>Evidência</dt><dd>{{ d.evidence }} <small class="muted">(medido manualmente em {{ d.measured_at }} — não é uma consulta ao vivo)</small></dd>
|
||||
<dt>Evidência</dt><dd>{{ d.evidence }} <small class="muted">(medido manualmente em {{ d.measured_at }} — não é uma consulta ao vivo){% if d.dossier_link %} · <a href="{{ d.dossier_link }}" target="_blank" rel="noopener">dossiê para o contador</a>{% endif %}</small></dd>
|
||||
<dt>Confiança</dt><dd>{{ d.confidence }}</dd>
|
||||
<dt>Campos afetados</dt><dd>{{ d.affected_fields|join(", ") }}</dd></dl>
|
||||
<form method="post" action="/agents/{{ detail.agent_id }}/fiscal-context" class="filters compact-filters"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="key" value="{{ d.key }}">
|
||||
{% if d.allowed_values %}<label>Valor<select name="value">{% for v in d.allowed_values %}<option value="{{ v }}" {% if d.current_value==v %}selected{% endif %}>{{ v }}</option>{% endfor %}</select></label>{% else %}<label>Valor<input name="value" value="{{ d.current_value or '' }}" placeholder="ex.: {01,01,1.65,01,01,7.6} ou INTRA"></label>{% endif %}
|
||||
{% if d.allowed_values %}<label>Valor<select name="value">{% for v in d.allowed_values %}<option value="{{ v }}" {% if d.current_value==v %}selected{% endif %}>{{ v }}</option>{% endfor %}</select></label>{% else %}<label>Valor<input name="value" value="{{ d.current_value or '' }}" placeholder="{{ d.value_hint or 'ex.: {01,01,1.65,01,01,7.6} ou INTRA' }}"></label>{% endif %}
|
||||
<label>Motivo<input name="reason" placeholder="opcional" autocomplete="off"></label>
|
||||
<button class="secondary" type="submit" name="fc_action" value="confirm">Confirmar</button>
|
||||
<button class="secondary" type="submit" name="fc_action" value="clear" onclick="return confirm('Limpar esta decisão? Os produtos voltam a depender só do que o ERP prova.')">Limpar</button></form>
|
||||
|
||||
@@ -125,7 +125,9 @@ def test_cfop_entrada_mixed_scope_is_operation_dependent_and_never_invents_a_cfo
|
||||
mixed = [{"scope": "COMPANY", "scope_key": "", "key": "entry_scope", "value": "MIXED_OR_DYNAMIC"}]
|
||||
r = run(p=product(CFOP_ENTRADA=None), ctx=mixed)
|
||||
e = r["CFOP_ENTRADA"]
|
||||
assert e["state"] == MANUAL and e["proposed"] is None and e.get("input_key") is None
|
||||
# NA (not MANUAL): the audited entry_scope=MIXED_OR_DYNAMIC decision already answered "why no value" -
|
||||
# this must never block product completeness nor re-ask a question the admin already answered (Missão 2 / Bloco 3.4).
|
||||
assert e["state"] == NA and e["proposed"] is None and e.get("input_key") is None
|
||||
assert e["reason"] == "CFOP_ENTRADA_OPERATION_DEPENDENT_MIXED_SCOPE"
|
||||
r2 = run(p=product(CFOP_ENTRADA="2102"), ctx=mixed)
|
||||
assert r2["CFOP_ENTRADA"]["state"] == ALREADY and r2["CFOP_ENTRADA"]["reason"] == "DECLARED_VALID_OPERATION_DEPENDENT_MIXED_SCOPE"
|
||||
@@ -175,6 +177,30 @@ def test_cst_ipi_pis_cofins_incidence_iat_ippt():
|
||||
assert run(p=product(IAT=None))["IAT"]["input_key"] == "rounding"
|
||||
assert run()["INCIDENCIA_PISCOFINS"]["state"] == ALREADY
|
||||
assert st(run(ev={**EV, "recipes": {"000001"}}), "IPPT") == (CHANGE, "P") and run()["IPPT"]["state"] == ALREADY
|
||||
|
||||
|
||||
def test_pis_cofins_not_applicable_and_operation_time_require_an_audited_admin_decision():
|
||||
"""Missão 2 / Bloco 2.4: NOT_APPLICABLE/OPERATION_TIME for PIS/COFINS must never be inferred (e.g. from emission
|
||||
statistics) - only ever accepted from an explicit, audited FiscalContextEntry(key="pis_cofins_profile")."""
|
||||
pis_cofins_fields = ("COD_ENTRADA_PIS", "COD_SAIDA_PIS", "ALIQ_PIS", "COD_ENTRADA_COFINS", "COD_SAIDA_COFINS", "ALIQ_COFINS")
|
||||
# Sem decisão ADMIN => comportamento antigo (continua bloqueando, pedindo pis_cofins_profile).
|
||||
r = run()
|
||||
for f in pis_cofins_fields:
|
||||
assert r[f]["state"] == MANUAL and r[f]["input_key"] == "pis_cofins_profile"
|
||||
# Com decisão ADMIN NOT_APPLICABLE => NA em todos, nunca escreve, nunca bloqueia completude.
|
||||
na_ctx = [{"scope": "COMPANY", "scope_key": "", "key": "pis_cofins_profile", "value": "NOT_APPLICABLE"}]
|
||||
r_na = run(ctx=na_ctx)
|
||||
for f in pis_cofins_fields:
|
||||
assert r_na[f]["state"] == NA and r_na[f]["proposed"] is None and r_na[f]["reason"] == "ADMIN_DECISION_PIS_COFINS_NOT_APPLICABLE"
|
||||
# Com decisão ADMIN OPERATION_TIME => NA em todos (não bloqueia), motivo distinto, nunca escreve.
|
||||
ot_ctx = [{"scope": "COMPANY", "scope_key": "", "key": "pis_cofins_profile", "value": "OPERATION_TIME"}]
|
||||
r_ot = run(ctx=ot_ctx)
|
||||
for f in pis_cofins_fields:
|
||||
assert r_ot[f]["state"] == NA and r_ot[f]["proposed"] is None and r_ot[f]["reason"] == "ADMIN_DECISION_PIS_COFINS_OPERATION_TIME"
|
||||
# Uma decisão de PROFILE (JSON) continua funcionando exatamente como antes (não regressivo).
|
||||
prof = '{"pis_out_cst": "49", "pis_in_cst": "98", "pis_rate": "0", "cofins_out_cst": "49", "cofins_in_cst": "98", "cofins_rate": "0"}'
|
||||
r_prof = run(ctx=[{"scope": "COMPANY", "scope_key": "", "key": "pis_cofins_profile", "value": prof}])
|
||||
assert st(r_prof, "COD_SAIDA_PIS") == (CHANGE, "49")
|
||||
assert run(p=product(TIPO="MATERIA-PRIMA"))["IPPT"]["state"] == NA
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ def test_validation_rules():
|
||||
{"scope": "COMPANY", "key": "icms_rate", "value": "abc"}, {"scope": "COMPANY", "key": "nope", "value": "1"}):
|
||||
assert validate_entry(bad)[1]
|
||||
assert validate_entry({"scope": "COMPANY", "key": "pis_cofins_profile", "value": "{\"pis_out_cst\": \"49\"}"})[1] is None
|
||||
# Missão 2 / Bloco 2.3: the two audited sentinels must validate (case-insensitive) alongside the JSON profile shape.
|
||||
ok, err = validate_entry({"scope": "COMPANY", "key": "pis_cofins_profile", "value": "not_applicable"})
|
||||
assert err is None and ok["value"] == "NOT_APPLICABLE"
|
||||
ok, err = validate_entry({"scope": "COMPANY", "key": "pis_cofins_profile", "value": "OPERATION_TIME"})
|
||||
assert err is None and ok["value"] == "OPERATION_TIME"
|
||||
|
||||
|
||||
def test_import_is_all_or_nothing_upserting_and_listed():
|
||||
|
||||
@@ -17,7 +17,7 @@ def test_health_is_unavailable_when_database_is_down():
|
||||
|
||||
def test_version_endpoint_reports_release_and_fiscal_versions():
|
||||
body = TestClient(create_app(database_probe=lambda: True)).get("/version").json()
|
||||
assert body["version"] == "1.3.9" and body["api_compat"] == "1.0" and body["engine_version"] and body["decision_version"]
|
||||
assert body["version"] == "1.3.10" and body["api_compat"] == "1.0" and body["engine_version"] and body["decision_version"]
|
||||
|
||||
|
||||
def test_production_refuses_the_dev_token(monkeypatch):
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Agent 2 — Roteiro de upgrade manual 1.3.7 → 1.3.8
|
||||
|
||||
## Self-update: NÃO EXISTE
|
||||
|
||||
Evidência no código:
|
||||
- `deploy/agent-windows/gti-agent.ps1` só aceita `install | install-auto | configure |
|
||||
preflight | start | stop | restart | status | uninstall` (`ValidateSet` na linha 20).
|
||||
Não há verbo `update`/`upgrade`.
|
||||
- `install-auto` é idempotente e **recusa fazer qualquer coisa** se `.env` já tem
|
||||
`AGENT_TOKEN` e o serviço já existe ("Already enrolled and installed... Nothing to
|
||||
do.") — não reenrola, não atualiza arquivos.
|
||||
- O painel do Hub (`central/app/control_plane.py:140`, `compute_version_drift`) só
|
||||
calcula e **exibe** o rótulo "Desatualizada" (`version_drift`); não existe nenhum
|
||||
código que baixe pacote, copie arquivo ou reinicie o serviço a partir do Hub. O
|
||||
botão "Atualização disponível" é só informativo — não há ação de clique associada.
|
||||
- `docs/PILOT_DEPLOYMENT.md:367` já documenta isso: "There is no auto-update
|
||||
mechanism yet (deliberately out of scope)".
|
||||
|
||||
Conclusão: o upgrade é sempre manual, feito por humano na máquina do cliente.
|
||||
|
||||
## Roteiro (operador, elevado)
|
||||
|
||||
1. **Backup antes de tocar em qualquer arquivo**
|
||||
- Copie `<pasta_instalação>\agent\agent_state.sqlite` para um local seguro
|
||||
(ex.: `agent_state.sqlite.bak-2026-09-28`).
|
||||
- Copie também `<pasta_instalação>\.env` (tem o `AGENT_TOKEN` permanente —
|
||||
nunca gerar um novo).
|
||||
2. `gti-agent.ps1 stop` (para o serviço `GTIFiscalAgent`).
|
||||
3. Extraia o pacote 1.3.8 publicado **sobre** a mesma pasta de instalação
|
||||
(sobrescrever arquivos de código), **sem apagar a pasta antes** e:
|
||||
- **não** sobrescrever `.env`;
|
||||
- **não** sobrescrever/apagar `agent\agent_state.sqlite`;
|
||||
- **não** copiar/usar `enrollment.json` do pacote novo (ele é só para instalação
|
||||
nova; reenrollar geraria outro `installation_id`/token).
|
||||
4. `gti-agent.ps1 install` (elevado) — reinstala dependências no venv existente e
|
||||
re-registra o serviço com o novo código. Não toca `.env` nem o SQLite.
|
||||
5. Confira que `.env` continua com o mesmo `AGENT_TOKEN`/`AGENT_ID` de antes.
|
||||
6. `gti-agent.ps1 preflight` — deve passar (mesmas credenciais Firebird de antes).
|
||||
7. `gti-agent.ps1 start`.
|
||||
8. Verificar (ainda na máquina): `gti-agent.ps1 status` mostra o serviço rodando.
|
||||
9. Confirmar sozinho ou pedir para eu confirmar (somente leitura, pelo Hub):
|
||||
heartbeat com `agent_version=1.3.8`, `installation_id` inalterado
|
||||
(`bb50884a-5e78-4f26-8cac-804932dae8de`), horário do heartbeat novo.
|
||||
|
||||
Se algo falhar depois do passo 3, restaurar o backup do `agent_state.sqlite` e do
|
||||
`.env` resolve qualquer estado inconsistente sem precisar reenrolar.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Dossiê para o contador — Missão 2 (2026-09-28)
|
||||
|
||||
Este documento não afirma nenhuma regra fiscal. Cada pergunta mostra a evidência real medida
|
||||
(somente leitura, sem gravação em nenhum banco) e qual clique na tela "Contexto Fiscal" do painel
|
||||
a resposta libera.
|
||||
|
||||
## (a) Em CRT=1, o PIS/COFINS por item segue o cadastro do produto ou a operação/regime?
|
||||
|
||||
**Evidência**: nos 5 bancos auditados (Agent 2/GTI, CONSTRUCAO, MMMODAS, RINCAO, UNIAO — CRT 1 e
|
||||
CRT 3), o cadastro do produto (`C000025`) **nunca** tem os campos de PIS/COFINS usados pela emissão:
|
||||
mesmo quando a nota fiscal emitida traz um CST de PIS/COFINS preenchido, o cadastro está em branco
|
||||
para o mesmo produto (23 casos confirmados no Agent 2). Em CONSTRUCAO, MMMODAS, RINCAO e UNIAO, a
|
||||
emissão nunca preenche o CST de PIS/COFINS (0% dos itens, em todos os anos de histórico
|
||||
disponíveis). Isso indica que a emissão não lê o cadastro — mas não decide se o valor correto é
|
||||
"não aplicável", "por operação" ou um perfil fixo por empresa. **Libera**: card "Perfil de
|
||||
PIS/COFINS" → escolher `NOT_APPLICABLE`, `OPERATION_TIME` ou um perfil JSON de 6 campos.
|
||||
|
||||
## (b) O CST 01 emitido em ~9,5% dos itens é intencional?
|
||||
|
||||
**Evidência**: dos 34.946 itens de NFC-e emitidos pelo Agent 2, 3.336 (9,5%) trazem CST de
|
||||
PIS/COFINS = "01" — mas **3.336 desses 3.669 itens estão concentrados no ano de 2022** (90,9% dos
|
||||
itens daquele ano); nos demais anos com dados (2018, 2023, 2024, 2025, 2026), a taxa é 0%. O ano de
|
||||
2022 já havia sido sinalizado em outra auditoria (`docs/AI_PROJECT_STATE.md`, mission
|
||||
"Product Fiscal Defaults Engine V1") como um período com problema de integridade de dados (drift de
|
||||
identidade de produto em dezembro/2022). A leitura mais provável é que **não é uma política atual
|
||||
deliberada** — é um comportamento isolado de um período específico, possivelmente uma versão antiga
|
||||
do software de emissão ou uma configuração temporária. Não decidimos isso por evidência
|
||||
insuficiente para confirmar a causa exata; a pergunta é se o contador reconhece esse padrão de 2022.
|
||||
|
||||
## (c) CFOP_ENTRADA no cadastro do produto é necessário ou é sempre definido na nota de compra?
|
||||
|
||||
**Evidência**:
|
||||
- **Schema do Firebird** (`RDB$RELATION_FIELDS`/`RDB$CHECK_CONSTRAINTS` em `C000025.CFOP_ENTRADA`):
|
||||
coluna **NULLABLE**, **sem DEFAULT**, **sem CHECK/domínio** — o próprio SGV nunca exige um valor
|
||||
aqui a nível de banco.
|
||||
- **Uso real é muito diferente entre clientes**: no Agent 2 (GTI), CFOP_ENTRADA está em branco em
|
||||
527/527 produtos (100%). Em RINCAO, 891/894 (99,7%) em branco. Mas em CONSTRUCAO, MMMODAS e UNIAO
|
||||
o campo está **preenchido** em 99,8%/99,9%/99,97% dos produtos, com valores plausíveis (1102, 1403,
|
||||
2202 etc.) — ou seja, alguns clientes efetivamente mantêm esse cadastro, outros não.
|
||||
- Isso mostra que não existe uma regra universal: depende de como cada loja opera. Por isso o motor
|
||||
já trata isso como uma decisão por instalação (`entry_scope`), nunca uma regra fixa do sistema.
|
||||
- **Não verificado nesta sessão** (requer acesso ao executável da tela de entrada de nota, que não
|
||||
fizemos): se a tela de lançamento de nota de compra do SGV lê o CFOP_ENTRADA do cadastro como
|
||||
sugestão inicial, ou sempre pede a digitação manual. Roteiro de 5 passos para teste manual:
|
||||
1. Anote o CFOP_ENTRADA atual de um produto de teste (ou deixe em branco).
|
||||
2. Abra o cadastro do produto no SGV e grave um CFOP_ENTRADA diferente (ex.: 1102).
|
||||
3. Lance uma nota de entrada (compra) desse mesmo produto.
|
||||
4. Veja se o CFOP do item da nota já vem preenchido com o valor do cadastro, ou em branco.
|
||||
5. Repita com CFOP_ENTRADA em branco no cadastro e confirme se a nota exige digitação manual.
|
||||
- **Libera**: card "CFOP de entrada padrão" → `INTRA`, `INTER` ou `MIXED_OR_DYNAMIC` (este último
|
||||
não bloqueia mais a completude do produto — Missão 2, Bloco 3.4).
|
||||
|
||||
## (d) O que mais o dry-run deixou como decisão pendente com evidência insuficiente?
|
||||
|
||||
- **COD_ORIGEM_MERCADORIA** (origem da mercadoria): nenhuma fonte automática existe no ERP para
|
||||
decidir nacional/importado — 526 de 527 produtos do Agent 2 não têm nenhuma evidência. Só pode
|
||||
ser respondido produto a produto ou por grupo, nunca por uma regra geral.
|
||||
- **icms_treatment** para 12 produtos (Agent 2) cujo CSOSN está em branco — a maioria concentrada em
|
||||
produtos de teste/cadastro incompleto (nomes como "TESTE...", "BUG", "SDFSDFSD"), mas 12 produtos
|
||||
reais também aparecem (Coca-Cola, queijos, peixes) — ver `docs/runs/GTI_dryrun_universe_summary.json`.
|
||||
- **30 produtos com CSOSN em branco no Agent 2** (medição em 2026-09-28; uma medição anterior de
|
||||
2026-09-27, registrada em `central/app/fiscal_context_ui.py`, contava 28 — a diferença de 2 é
|
||||
compatível com o cadastro de produtos de teste que muda dia a dia neste ambiente, não uma
|
||||
divergência de regra). Nenhuma simulação escreve nada; o padrão 102 só se aplica de fato após a
|
||||
confirmação do "Tratamento padrão de ICMS" no card correspondente, e mesmo assim o motor consulta
|
||||
o tratamento antes de propor o código (nunca aplica 102 cegamente).
|
||||
|
||||
## Como usar este dossiê
|
||||
|
||||
Cada resposta é dada uma única vez, no painel ("Contexto Fiscal", aba do Agent), nunca escrevendo
|
||||
nada no Firebird diretamente — a decisão fica registrada e auditada (`FiscalContextEntry`), e só
|
||||
depois disso o motor passa a considerar o campo resolvido (ou explicitamente não-bloqueante).
|
||||
Nenhuma decisão aqui é assumida por padrão.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Matriz de completude fiscal — 18 campos (Missão 2 / Bloco 4)
|
||||
|
||||
Evidência: dry-run read-only de 2026-09-28 sobre GTI.FDB (Agent 2, 527 produtos, CRT=1, UF=BA,
|
||||
CONTRIBUINTE_IPI=N — `docs/runs/GTI_dryrun_universe_summary.json`), cruzamento com CONSTRUCAO/
|
||||
MMMODAS/RINCAO/UNIAO (`docs/runs/BLOCO2_PIS_COFINS_CROSS_DB_V1.json`) e metadados de esquema
|
||||
Firebird lidos diretamente (`RDB$RELATION_FIELDS`/`RDB$CHECK_CONSTRAINTS`). Nenhuma regra fiscal
|
||||
nova foi inventada: campos sem evidência suficiente ficam `pendente de contador`.
|
||||
|
||||
| Campo | Classificação | Condição | Fonte da evidência | Status |
|
||||
|---|---|---|---|---|
|
||||
| CLASSIFICACAO_FISCAL (NCM) | MANDATORY | sempre | dataset NCM oficial | comprovado |
|
||||
| CEST | CONDITIONALLY_MANDATORY | só quando o NCM tem CEST oficial único | tabela CEST/CONFAZ | comprovado |
|
||||
| IPI | CONDITIONALLY_MANDATORY | só quando TIPI dá alíquota numérica não-ambígua | TIPI/IPI | comprovado |
|
||||
| EX_TIPI | OPTIONAL | só quando o NCM tem múltiplas linhas TIPI (EX) | TIPI/IPI | comprovado |
|
||||
| SITUACAO_TRIBUTARIA | CONDITIONALLY_MANDATORY | depende do NCM ser válido | regra ST interna (BA Anexo 1) | comprovado |
|
||||
| CFOP | CONDITIONALLY_MANDATORY | não aplicável a INPUT (matéria-prima) | product_role + ST | comprovado |
|
||||
| CFOP_ENTRADA | OPERATION_TIME_ONLY | quando `entry_scope=MIXED_OR_DYNAMIC` (decisão ADMIN auditada); caso contrário CONDITIONALLY_MANDATORY (INTRA/INTER) | decisão ADMIN + product_role + ST; **schema Firebird: coluna NULLABLE, sem DEFAULT, sem CHECK — o próprio SGV nunca exige valor** | comprovado (schema); decisão de escopo pendente por instalação |
|
||||
| CST | NOT_APPLICABLE | CRT=1 (Simples) sempre usa CSOSN, nunca CST | regra CRT/emissão | comprovado |
|
||||
| CST_ENTRADA | NOT_APPLICABLE | mesma razão que CST | regra CRT/emissão | comprovado |
|
||||
| CSOSN | CONDITIONALLY_MANDATORY | CRT=1; precisa de `icms_treatment` quando em branco | dataset CSOSN + contexto ADMIN | comprovado |
|
||||
| ALIQUOTA (ICMS) | NOT_APPLICABLE | CSOSN Simples sem destaque de ICMS (102/103/300/400/500) | regra CSOSN | comprovado |
|
||||
| COD_ORIGEM_MERCADORIA | CONDITIONALLY_MANDATORY | nenhuma fonte automática decide — sempre depende de contexto GROUP/PRODUCT | nenhuma (evidência zero no ERP) | pendente de contador/operador (sem fonte determinística) |
|
||||
| CST_IPI | NOT_APPLICABLE | empresa não é contribuinte de IPI (C000004.CONTRIBUINTE_IPI=N) | heartbeat do Agent | comprovado |
|
||||
| COD_ENTRADA_PIS / COD_SAIDA_PIS / ALIQ_PIS / COD_ENTRADA_COFINS / COD_SAIDA_COFINS / ALIQ_COFINS | CONDITIONALLY_MANDATORY (bloqueante) até decisão ADMIN; podem virar NOT_APPLICABLE ou OPERATION_TIME_ONLY só por decisão ADMIN auditada (`pis_cofins_profile`) | ver Bloco 2 — emissão nunca lê o cadastro em nenhum dos 5 bancos auditados | evidência de emissão (NFCE_ITENS) + decisão ADMIN | evidência forte (não-uso na emissão); classificação final pendente de confirmação contábil |
|
||||
| INCIDENCIA_PISCOFINS | OPTIONAL | valor declarado válido não é contradito | regra de incidência | comprovado |
|
||||
| PISCOFINS | OPTIONAL | idem | regra de incidência | comprovado |
|
||||
| IAT | OPTIONAL | arredondamento, evidência fraca | regra + contexto `rounding` | comprovado (não bloqueia) |
|
||||
| IPPT | CONDITIONALLY_MANDATORY | derivado de product_role/receita/compra quando role conhecido | evidência de produção/compra | comprovado |
|
||||
|
||||
## Regra de bloqueio do "COMPLETE"
|
||||
|
||||
`central/app/enrichment/status.py::coverage_status` já implementa exatamente a regra pedida:
|
||||
`SAFE_BLOCKED` só quando existe campo `NEEDS_REVIEW` ou `INSUFFICIENT_EVIDENCE`; `NOT_APPLICABLE`,
|
||||
`ALREADY_SET` e `AUTO_FILL_SAFE` nunca bloqueiam. Os dois ajustes de engine desta missão
|
||||
(CFOP_ENTRADA com `entry_scope=MIXED_OR_DYNAMIC` → `NA`; PIS/COFINS com decisão ADMIN
|
||||
`NOT_APPLICABLE`/`OPERATION_TIME` → `NA`) plugam diretamente nessa regra já existente — nenhuma
|
||||
mudança adicional foi necessária em `coverage_status`. Sem decisão ADMIN, os dois grupos continuam
|
||||
bloqueando exatamente como antes (testes de regressão em `central/tests/test_field_contract.py`).
|
||||
|
||||
## Pendente de contador (não implementado, não bloqueia nada além do que já bloqueava)
|
||||
|
||||
- COD_ORIGEM_MERCADORIA: nenhuma fonte automática existe; permanece MANUAL por produto/grupo.
|
||||
- Classificação final de PIS/COFINS (NOT_APPLICABLE vs OPERATION_TIME vs perfil fixo): ver
|
||||
`docs/dossie_contador.md`.
|
||||
@@ -0,0 +1,264 @@
|
||||
{
|
||||
"GTI": {
|
||||
"name": "GTI",
|
||||
"company_status": "PASS",
|
||||
"crt": "1",
|
||||
"uf": "BA",
|
||||
"items_total": 34946,
|
||||
"items_cst_pis_cofins_filled": 3336,
|
||||
"pct_cst_filled": 9.55,
|
||||
"cst_distribution": {
|
||||
"01": 3336,
|
||||
"(blank)": 1332
|
||||
},
|
||||
"by_year": {
|
||||
"2018": {
|
||||
"items": 30278,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2022": {
|
||||
"items": 3669,
|
||||
"cst_filled": 3336
|
||||
},
|
||||
"2023": {
|
||||
"items": 240,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2024": {
|
||||
"items": 426,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2025": {
|
||||
"items": 230,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2026": {
|
||||
"items": 103,
|
||||
"cst_filled": 0
|
||||
}
|
||||
},
|
||||
"aliquota_pis_distribution": {
|
||||
"0": 4668
|
||||
},
|
||||
"aliquota_cofins_distribution": {
|
||||
"0": 4668
|
||||
},
|
||||
"identity_consistent_items": 1241,
|
||||
"emitted_cst_present_when_cadastro_blank": 23
|
||||
},
|
||||
"TESTESOFICIAIS": {
|
||||
"name": "TESTESOFICIAIS",
|
||||
"company_status": "PASS",
|
||||
"crt": "1",
|
||||
"uf": "BA",
|
||||
"items_total": 34946,
|
||||
"items_cst_pis_cofins_filled": 3336,
|
||||
"pct_cst_filled": 9.55,
|
||||
"cst_distribution": {
|
||||
"01": 3336,
|
||||
"(blank)": 1332
|
||||
},
|
||||
"by_year": {
|
||||
"2018": {
|
||||
"items": 30278,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2022": {
|
||||
"items": 3669,
|
||||
"cst_filled": 3336
|
||||
},
|
||||
"2023": {
|
||||
"items": 240,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2024": {
|
||||
"items": 426,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2025": {
|
||||
"items": 230,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2026": {
|
||||
"items": 103,
|
||||
"cst_filled": 0
|
||||
}
|
||||
},
|
||||
"aliquota_pis_distribution": {
|
||||
"0": 4668
|
||||
},
|
||||
"aliquota_cofins_distribution": {
|
||||
"0": 4668
|
||||
},
|
||||
"identity_consistent_items": 1241,
|
||||
"emitted_cst_present_when_cadastro_blank": 23
|
||||
},
|
||||
"CONSTRUCAO": {
|
||||
"name": "CONSTRUCAO",
|
||||
"company_status": "PASS",
|
||||
"crt": "1",
|
||||
"uf": "BA",
|
||||
"items_total": 28934,
|
||||
"items_cst_pis_cofins_filled": 0,
|
||||
"pct_cst_filled": 0.0,
|
||||
"cst_distribution": {
|
||||
"(blank)": 28934
|
||||
},
|
||||
"by_year": {
|
||||
"2024": {
|
||||
"items": 5144,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2025": {
|
||||
"items": 13266,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2026": {
|
||||
"items": 10524,
|
||||
"cst_filled": 0
|
||||
}
|
||||
},
|
||||
"aliquota_pis_distribution": {
|
||||
"0": 28934
|
||||
},
|
||||
"aliquota_cofins_distribution": {
|
||||
"0": 28934
|
||||
},
|
||||
"identity_consistent_items": 28714,
|
||||
"emitted_cst_present_when_cadastro_blank": 0
|
||||
},
|
||||
"MMMODAS": {
|
||||
"name": "MMMODAS",
|
||||
"company_status": "PASS",
|
||||
"crt": "1",
|
||||
"uf": "BA",
|
||||
"items_total": 26067,
|
||||
"items_cst_pis_cofins_filled": 0,
|
||||
"pct_cst_filled": 0.0,
|
||||
"cst_distribution": {
|
||||
"(blank)": 23238
|
||||
},
|
||||
"by_year": {
|
||||
"2018": {
|
||||
"items": 2655,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2019": {
|
||||
"items": 174,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2024": {
|
||||
"items": 9456,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2025": {
|
||||
"items": 9403,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2026": {
|
||||
"items": 4379,
|
||||
"cst_filled": 0
|
||||
}
|
||||
},
|
||||
"aliquota_pis_distribution": {
|
||||
"0": 23238
|
||||
},
|
||||
"aliquota_cofins_distribution": {
|
||||
"0": 23238
|
||||
},
|
||||
"identity_consistent_items": 23201,
|
||||
"emitted_cst_present_when_cadastro_blank": 0
|
||||
},
|
||||
"RINCAO": {
|
||||
"name": "RINCAO",
|
||||
"company_status": "PASS",
|
||||
"crt": "3",
|
||||
"uf": "BA",
|
||||
"items_total": 224355,
|
||||
"items_cst_pis_cofins_filled": 0,
|
||||
"pct_cst_filled": 0.0,
|
||||
"cst_distribution": {
|
||||
"(blank)": 142784
|
||||
},
|
||||
"by_year": {
|
||||
"2017": {
|
||||
"items": 22363,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2018": {
|
||||
"items": 57888,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2019": {
|
||||
"items": 1320,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2024": {
|
||||
"items": 33989,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2025": {
|
||||
"items": 65881,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2026": {
|
||||
"items": 42914,
|
||||
"cst_filled": 0
|
||||
}
|
||||
},
|
||||
"aliquota_pis_distribution": {
|
||||
"0": 142784
|
||||
},
|
||||
"aliquota_cofins_distribution": {
|
||||
"0": 142784
|
||||
},
|
||||
"identity_consistent_items": 94195,
|
||||
"emitted_cst_present_when_cadastro_blank": 0
|
||||
},
|
||||
"UNIAO": {
|
||||
"name": "UNIAO",
|
||||
"company_status": "PASS",
|
||||
"crt": "1",
|
||||
"uf": "BA",
|
||||
"items_total": 121266,
|
||||
"items_cst_pis_cofins_filled": 0,
|
||||
"pct_cst_filled": 0.0,
|
||||
"cst_distribution": {
|
||||
"(blank)": 104973
|
||||
},
|
||||
"by_year": {
|
||||
"2017": {
|
||||
"items": 9169,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2018": {
|
||||
"items": 6747,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2019": {
|
||||
"items": 377,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2024": {
|
||||
"items": 24146,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2025": {
|
||||
"items": 45031,
|
||||
"cst_filled": 0
|
||||
},
|
||||
"2026": {
|
||||
"items": 35796,
|
||||
"cst_filled": 0
|
||||
}
|
||||
},
|
||||
"aliquota_pis_distribution": {
|
||||
"0": 104973
|
||||
},
|
||||
"aliquota_cofins_distribution": {
|
||||
"0": 104973
|
||||
},
|
||||
"identity_consistent_items": 104830,
|
||||
"emitted_cst_present_when_cadastro_blank": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
{
|
||||
"name": "GTI",
|
||||
"total_products": 527,
|
||||
"company": {
|
||||
"uf": "BA",
|
||||
"crt": "1",
|
||||
"contribuinte_ipi": "N",
|
||||
"company_code": "000001",
|
||||
"company_key": "9b1d6541",
|
||||
"pis_cofins_emission_items": 34946,
|
||||
"pis_cofins_emission_nonblank": 3336,
|
||||
"pis_cofins_unused_by_emission": false
|
||||
},
|
||||
"context_entries": 0,
|
||||
"fields": {
|
||||
"CLASSIFICACAO_FISCAL": {
|
||||
"ALREADY_CORRECT": 500,
|
||||
"MANUAL_INPUT_REQUIRED": 27
|
||||
},
|
||||
"IPI": {
|
||||
"NOT_APPLICABLE": 317,
|
||||
"MANUAL_INPUT_REQUIRED": 210
|
||||
},
|
||||
"EX_TIPI": {
|
||||
"NOT_APPLICABLE": 500,
|
||||
"MANUAL_INPUT_REQUIRED": 27
|
||||
},
|
||||
"CEST": {
|
||||
"NOT_APPLICABLE": 482,
|
||||
"MANUAL_INPUT_REQUIRED": 41,
|
||||
"ALREADY_CORRECT": 4
|
||||
},
|
||||
"SITUACAO_TRIBUTARIA": {
|
||||
"ALREADY_CORRECT": 499,
|
||||
"MANUAL_INPUT_REQUIRED": 28
|
||||
},
|
||||
"CFOP": {
|
||||
"ALREADY_CORRECT": 488,
|
||||
"CHANGE_REQUIRED": 4,
|
||||
"MANUAL_INPUT_REQUIRED": 28,
|
||||
"NOT_APPLICABLE": 7
|
||||
},
|
||||
"CFOP_ENTRADA": {
|
||||
"MANUAL_INPUT_REQUIRED": 527
|
||||
},
|
||||
"CST": {
|
||||
"NOT_APPLICABLE": 527
|
||||
},
|
||||
"CST_ENTRADA": {
|
||||
"NOT_APPLICABLE": 527
|
||||
},
|
||||
"CSOSN": {
|
||||
"ALREADY_CORRECT": 489,
|
||||
"MANUAL_INPUT_REQUIRED": 38
|
||||
},
|
||||
"ALIQUOTA": {
|
||||
"NOT_APPLICABLE": 493,
|
||||
"ALREADY_CORRECT": 34
|
||||
},
|
||||
"COD_ORIGEM_MERCADORIA": {
|
||||
"MANUAL_INPUT_REQUIRED": 526,
|
||||
"ALREADY_CORRECT": 1
|
||||
},
|
||||
"CST_IPI": {
|
||||
"NOT_APPLICABLE": 527
|
||||
},
|
||||
"COD_ENTRADA_PIS": {
|
||||
"MANUAL_INPUT_REQUIRED": 526,
|
||||
"ALREADY_CORRECT": 1
|
||||
},
|
||||
"COD_SAIDA_PIS": {
|
||||
"MANUAL_INPUT_REQUIRED": 525,
|
||||
"ALREADY_CORRECT": 2
|
||||
},
|
||||
"ALIQ_PIS": {
|
||||
"MANUAL_INPUT_REQUIRED": 525,
|
||||
"ALREADY_CORRECT": 2
|
||||
},
|
||||
"COD_ENTRADA_COFINS": {
|
||||
"MANUAL_INPUT_REQUIRED": 526,
|
||||
"ALREADY_CORRECT": 1
|
||||
},
|
||||
"COD_SAIDA_COFINS": {
|
||||
"MANUAL_INPUT_REQUIRED": 525,
|
||||
"ALREADY_CORRECT": 2
|
||||
},
|
||||
"ALIQ_COFINS": {
|
||||
"MANUAL_INPUT_REQUIRED": 525,
|
||||
"ALREADY_CORRECT": 2
|
||||
},
|
||||
"INCIDENCIA_PISCOFINS": {
|
||||
"ALREADY_CORRECT": 527
|
||||
},
|
||||
"PISCOFINS": {
|
||||
"ALREADY_CORRECT": 527
|
||||
},
|
||||
"IAT": {
|
||||
"ALREADY_CORRECT": 527
|
||||
},
|
||||
"IPPT": {
|
||||
"ALREADY_CORRECT": 512,
|
||||
"CHANGE_REQUIRED": 8,
|
||||
"NOT_APPLICABLE": 7
|
||||
}
|
||||
},
|
||||
"reasons": {
|
||||
"CLASSIFICACAO_FISCAL": {
|
||||
"ALREADY_CORRECT|NCM_VALID_IN_DATASET": 500,
|
||||
"MANUAL_INPUT_REQUIRED|NCM_INVALID": 24,
|
||||
"MANUAL_INPUT_REQUIRED|NCM_LEGACY_NO_UNAMBIGUOUS_SUCCESSOR": 3
|
||||
},
|
||||
"IPI": {
|
||||
"NOT_APPLICABLE|TIPI_RATE_BLANK_NEVER_ZERO": 297,
|
||||
"MANUAL_INPUT_REQUIRED|STORE_IPI_PROFILE_NOT_DECIDED": 107,
|
||||
"MANUAL_INPUT_REQUIRED|SEVERAL_TIPI_ROWS_FOR_NCM": 76,
|
||||
"MANUAL_INPUT_REQUIRED|BLOCKED_BY_NCM": 27,
|
||||
"NOT_APPLICABLE|TIPI_NT_OR_NON_NUMERIC_NEVER_WRITTEN_TO_NUMERIC_FIELD": 20
|
||||
},
|
||||
"EX_TIPI": {
|
||||
"NOT_APPLICABLE|NO_EX_ROW_FOR_NCM": 424,
|
||||
"NOT_APPLICABLE|DESCRIPTION_EVIDENCES_NO_EX_BASE_RATE_APPLIES": 76,
|
||||
"MANUAL_INPUT_REQUIRED|BLOCKED_BY_NCM": 27
|
||||
},
|
||||
"CEST": {
|
||||
"NOT_APPLICABLE|DESCRIPTION_MATCHES_NO_OFFICIAL_CEST_ITEM": 449,
|
||||
"NOT_APPLICABLE|NCM_NOT_IN_CEST_LIST": 33,
|
||||
"MANUAL_INPUT_REQUIRED|BLOCKED_BY_NCM": 27,
|
||||
"MANUAL_INPUT_REQUIRED|SEVERAL_OFFICIAL_CEST_FOR_NCM": 13,
|
||||
"ALREADY_CORRECT|ERP_CEST_IS_OFFICIAL_FOR_NCM": 4,
|
||||
"MANUAL_INPUT_REQUIRED|ERP_CEST_NOT_OFFICIAL_FOR_NCM": 1
|
||||
},
|
||||
"SITUACAO_TRIBUTARIA": {
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 499,
|
||||
"MANUAL_INPUT_REQUIRED|BLOCKED_BY_NCM": 27,
|
||||
"MANUAL_INPUT_REQUIRED|BLANK_OR_INVALID": 1
|
||||
},
|
||||
"CFOP": {
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 472,
|
||||
"MANUAL_INPUT_REQUIRED|BLOCKED_BY_NCM": 21,
|
||||
"ALREADY_CORRECT|RULE_PROVEN_EQUALS_DECLARED": 16,
|
||||
"NOT_APPLICABLE|INPUT_IS_NOT_A_SALE_ITEM": 7,
|
||||
"MANUAL_INPUT_REQUIRED|CFOP_NEEDS_ST_STATUS": 7,
|
||||
"CHANGE_REQUIRED|PROVEN_ROLE_AND_ST_CONTRADICT_DECLARED_CFOP": 2,
|
||||
"CHANGE_REQUIRED|DERIVED_FROM_ROLE_AND_ST_FILLING_BLANK": 2
|
||||
},
|
||||
"CFOP_ENTRADA": {
|
||||
"MANUAL_INPUT_REQUIRED|CFOP_ENTRADA_NEEDS_PRODUCT_ROLE": 253,
|
||||
"MANUAL_INPUT_REQUIRED|CFOP_ENTRADA_NEEDS_ENTRY_SCOPE": 247,
|
||||
"MANUAL_INPUT_REQUIRED|BLOCKED_BY_NCM": 27
|
||||
},
|
||||
"CST": {
|
||||
"NOT_APPLICABLE|CRT1_SIMPLES_EMISSION_USES_CSOSN": 527
|
||||
},
|
||||
"CST_ENTRADA": {
|
||||
"NOT_APPLICABLE|CRT1_SIMPLES_EMISSION_USES_CSOSN": 527
|
||||
},
|
||||
"CSOSN": {
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 489,
|
||||
"MANUAL_INPUT_REQUIRED|BLOCKED_BY_NCM": 27,
|
||||
"MANUAL_INPUT_REQUIRED|CSOSN_NEEDS_ICMS_TREATMENT": 11
|
||||
},
|
||||
"ALIQUOTA": {
|
||||
"NOT_APPLICABLE|SIMPLES_CSOSN_HIGHLIGHTS_NO_ICMS_RATE": 493,
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 34
|
||||
},
|
||||
"COD_ORIGEM_MERCADORIA": {
|
||||
"MANUAL_INPUT_REQUIRED|ORIGIN_NO_EVIDENCE": 526,
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 1
|
||||
},
|
||||
"CST_IPI": {
|
||||
"NOT_APPLICABLE|COMPANY_IS_NOT_AN_IPI_TAXPAYER_C000004.CONTRIBUINTE_IPI": 527
|
||||
},
|
||||
"COD_ENTRADA_PIS": {
|
||||
"MANUAL_INPUT_REQUIRED|NO_OFFICIAL_RULE_SOURCE_IN_CORE_DATASETS": 525,
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 1,
|
||||
"MANUAL_INPUT_REQUIRED|DECLARED_CST_NOT_IN_OFFICIAL_TABLE": 1
|
||||
},
|
||||
"COD_SAIDA_PIS": {
|
||||
"MANUAL_INPUT_REQUIRED|NO_OFFICIAL_RULE_SOURCE_IN_CORE_DATASETS": 524,
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 2,
|
||||
"MANUAL_INPUT_REQUIRED|DECLARED_CST_NOT_IN_OFFICIAL_TABLE": 1
|
||||
},
|
||||
"ALIQ_PIS": {
|
||||
"MANUAL_INPUT_REQUIRED|NO_OFFICIAL_RULE_SOURCE_IN_CORE_DATASETS": 525,
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 2
|
||||
},
|
||||
"COD_ENTRADA_COFINS": {
|
||||
"MANUAL_INPUT_REQUIRED|NO_OFFICIAL_RULE_SOURCE_IN_CORE_DATASETS": 525,
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 1,
|
||||
"MANUAL_INPUT_REQUIRED|DECLARED_CST_NOT_IN_OFFICIAL_TABLE": 1
|
||||
},
|
||||
"COD_SAIDA_COFINS": {
|
||||
"MANUAL_INPUT_REQUIRED|NO_OFFICIAL_RULE_SOURCE_IN_CORE_DATASETS": 524,
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 2,
|
||||
"MANUAL_INPUT_REQUIRED|DECLARED_CST_NOT_IN_OFFICIAL_TABLE": 1
|
||||
},
|
||||
"ALIQ_COFINS": {
|
||||
"MANUAL_INPUT_REQUIRED|NO_OFFICIAL_RULE_SOURCE_IN_CORE_DATASETS": 525,
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 2
|
||||
},
|
||||
"INCIDENCIA_PISCOFINS": {
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 527
|
||||
},
|
||||
"PISCOFINS": {
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 527
|
||||
},
|
||||
"IAT": {
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 527
|
||||
},
|
||||
"IPPT": {
|
||||
"ALREADY_CORRECT|DECLARED_VALID_NOT_CONTRADICTED": 264,
|
||||
"ALREADY_CORRECT|DERIVED_FROM_PRODUCT_ROLE:C000025.TIPO=VENDA,STOCK_ENTRIES(C000100.ENTRADAS>0)": 229,
|
||||
"ALREADY_CORRECT|DERIVED_FROM_PRODUCT_ROLE:C000025.TIPO=VENDA,PURCHASE_DOCUMENT": 19,
|
||||
"CHANGE_REQUIRED|DERIVED_FROM_PRODUCT_ROLE:C000032.TIPO=COMPOSICAO": 7,
|
||||
"NOT_APPLICABLE|INPUT_OR_SERVICE_HAS_NO_PRODUCTION_INDICATOR": 7,
|
||||
"CHANGE_REQUIRED|DERIVED_FROM_PRODUCT_ROLE:PROD_COZINHA=1,RECEITA_INGREDIENTES": 1
|
||||
}
|
||||
},
|
||||
"manual_inputs": {
|
||||
"COD_ORIGEM_MERCADORIA|origin": 526,
|
||||
"COD_ENTRADA_PIS|pis_cofins_profile": 526,
|
||||
"COD_ENTRADA_COFINS|pis_cofins_profile": 526,
|
||||
"COD_SAIDA_PIS|pis_cofins_profile": 525,
|
||||
"ALIQ_PIS|pis_cofins_profile": 525,
|
||||
"COD_SAIDA_COFINS|pis_cofins_profile": 525,
|
||||
"ALIQ_COFINS|pis_cofins_profile": 525,
|
||||
"CFOP_ENTRADA|product_role": 253,
|
||||
"CFOP_ENTRADA|entry_scope": 247,
|
||||
"IPI|store_ipi_profile": 107,
|
||||
"IPI|ex_tipi": 76,
|
||||
"CLASSIFICACAO_FISCAL|ncm": 27,
|
||||
"IPI|ncm": 27,
|
||||
"EX_TIPI|ncm": 27,
|
||||
"CEST|ncm": 27,
|
||||
"SITUACAO_TRIBUTARIA|ncm": 27,
|
||||
"CFOP_ENTRADA|ncm": 27,
|
||||
"CSOSN|ncm": 27,
|
||||
"CFOP|ncm": 21,
|
||||
"CEST|cest": 14,
|
||||
"CSOSN|icms_treatment": 11,
|
||||
"CFOP|st_status": 7,
|
||||
"SITUACAO_TRIBUTARIA|icms_treatment": 1
|
||||
},
|
||||
"roles": {
|
||||
"UNKNOWN": 264,
|
||||
"RESALE": 248,
|
||||
"OWN_PRODUCTION": 8,
|
||||
"INPUT": 7
|
||||
},
|
||||
"change_required_cells": 12,
|
||||
"write_eligible_cells": 12,
|
||||
"manual_inputs_by_scope": {
|
||||
"COD_ORIGEM_MERCADORIA|origin|GROUP": 526,
|
||||
"COD_ENTRADA_PIS|pis_cofins_profile|COMPANY": 526,
|
||||
"COD_ENTRADA_COFINS|pis_cofins_profile|COMPANY": 526,
|
||||
"COD_SAIDA_PIS|pis_cofins_profile|COMPANY": 525,
|
||||
"ALIQ_PIS|pis_cofins_profile|COMPANY": 525,
|
||||
"COD_SAIDA_COFINS|pis_cofins_profile|COMPANY": 525,
|
||||
"ALIQ_COFINS|pis_cofins_profile|COMPANY": 525,
|
||||
"CFOP_ENTRADA|product_role|GROUP": 253,
|
||||
"CFOP_ENTRADA|entry_scope|COMPANY": 247,
|
||||
"IPI|store_ipi_profile|COMPANY": 107,
|
||||
"IPI|ex_tipi|PRODUCT": 76,
|
||||
"CLASSIFICACAO_FISCAL|ncm|PRODUCT": 27,
|
||||
"IPI|ncm|PRODUCT": 27,
|
||||
"EX_TIPI|ncm|PRODUCT": 27,
|
||||
"CEST|ncm|PRODUCT": 27,
|
||||
"SITUACAO_TRIBUTARIA|ncm|PRODUCT": 27,
|
||||
"CFOP_ENTRADA|ncm|PRODUCT": 27,
|
||||
"CSOSN|ncm|PRODUCT": 27,
|
||||
"CFOP|ncm|PRODUCT": 21,
|
||||
"CEST|cest|PRODUCT": 14,
|
||||
"CSOSN|icms_treatment|GROUP": 11,
|
||||
"CFOP|st_status|GROUP": 7,
|
||||
"SITUACAO_TRIBUTARIA|icms_treatment|GROUP": 1
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"artifact": "RELEASE_MANIFEST",
|
||||
"GTI_FISCAL_VERSION": "1.3.9",
|
||||
"generated_at": "2026-09-28T01:15:00+00:00",
|
||||
"GTI_FISCAL_VERSION": "1.3.10",
|
||||
"generated_at": "2026-09-28T12:00:00+00:00",
|
||||
"hub": {
|
||||
"version": "1.3.9",
|
||||
"version": "1.3.10",
|
||||
"api_compat": "1.0",
|
||||
"engine_version": "0.3.0",
|
||||
"decision_version": "mission-04c-ipi-ex-split"
|
||||
},
|
||||
"agent_version": "1.3.9",
|
||||
"agent_version": "1.3.8",
|
||||
"fiscal_engine": {
|
||||
"ENGINE_VERSION": "0.3.0",
|
||||
"DECISION_VERSION": "mission-04c-ipi-ex-split",
|
||||
@@ -89,5 +89,5 @@
|
||||
"record_count": 11103
|
||||
}
|
||||
],
|
||||
"note": "1.3.9: adds the ADMIN-only Contexto Fiscal section to the Agent detail page (central/app/fiscal_context_ui.py) - confirm/alter/clear the 3 real fiscal decisions field_contract cannot derive alone (icms_treatment/pis_cofins_profile/entry_scope, reduced from an original 5-item list by auditing real production data: icms_rate/credit_rate and rounding were proven NOT_APPLICABLE/ALREADY_DERIVED for this CRT=1 company and dropped). Persists through the existing FiscalContextEntry table, CSRF+RBAC+audit reusing the same mechanism as /agents/{id}/features. No schema change. Hub only - Agent unchanged, no new installer published this release."
|
||||
"note": "1.3.9: adds the ADMIN-only Contexto Fiscal section to the Agent detail page (central/app/fiscal_context_ui.py) - confirm/alter/clear the 3 real fiscal decisions field_contract cannot derive alone (icms_treatment/pis_cofins_profile/entry_scope, reduced from an original 5-item list by auditing real production data: icms_rate/credit_rate and rounding were proven NOT_APPLICABLE/ALREADY_DERIVED for this CRT=1 company and dropped). Persists through the existing FiscalContextEntry table, CSRF+RBAC+audit reusing the same mechanism as /agents/{id}/features. No schema change. Hub only - Agent unchanged, no new installer published this release. 1.3.10 (Missao 2 - fechamento de lacunas fiscais, read-only + UI/engine gated): PIS/COFINS's pis_cofins_profile context key now also accepts the literal sentinels NOT_APPLICABLE/OPERATION_TIME (central/app/context_keys.py + central/app/field_contract.py), resolving to NA only when explicitly recorded by an audited FiscalContextEntry - never inferred from emission statistics; sem decisao ADMIN, comportamento identico ao anterior (regression tests in central/tests/test_field_contract.py). CFOP_ENTRADA under an already-decided entry_scope=MIXED_OR_DYNAMIC now resolves to NA instead of MANUAL, so it no longer blocks product completeness or re-asks an already-answered question (same file, same gate, existing scope-required-first test unchanged). Contexto Fiscal UI: PIS/COFINS and CFOP_ENTRADA cards show the Bloco 2 cross-database evidence and a link to docs/dossie_contador.md (also published at central/static/docs/dossie_contador.md); entry_scope is now a dropdown of its 3 valid values. New read-only evidence artifacts: docs/runs/GTI_dryrun_universe_summary.json, docs/runs/BLOCO2_PIS_COFINS_CROSS_DB_V1.json, docs/fiscal_completeness_matrix.md, docs/dossie_contador.md, docs/AGENT2_UPGRADE_RUNBOOK_V1.md. No schema/migration change. No Firebird write anywhere (zero writes, confirmed by construction - the new dry-run scripts never open a write connection). Hub only - Agent code untouched; Agent 2 was upgraded 1.3.7->1.3.8 by a human operator on-site (no self-update mechanism exists - confirmed by code audit), no new Agent installer published this release."
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.3.9
|
||||
1.3.10
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
"""Missão 2 / Bloco 2.1 — evidência cruzada PIS/COFINS cadastro-vs-emissão, somente leitura, nos 6 bancos.
|
||||
Nunca escreve. Não decide regra fiscal: só conta e cruza fatos.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from collections import Counter
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT)); sys.path.insert(0, str(ROOT / "scripts")); sys.path.insert(0, str(ROOT / "agent" / "src"))
|
||||
import fdb # noqa: E402
|
||||
import run_fiscal_decision_v1 as base # noqa: E402
|
||||
from gti_fiscal_agent import company_context as cc # noqa: E402
|
||||
|
||||
DBS = {
|
||||
"GTI": (r"D:\PROJETOS\GTISISTEMAS\Banco\GTI.FDB", {}),
|
||||
"TESTESOFICIAIS": (str(ROOT / "TESTESOFICIAIS.FDB"), {}),
|
||||
"CONSTRUCAO": (str(ROOT / "Banco" / "CONSTRUCAO.FDB"), {"company_code": "000001"}),
|
||||
"MMMODAS": (str(ROOT / "Banco" / "MMMODAS.FDB"), {"company_code": "000001", "crt_source": "EMISSOR"}),
|
||||
"RINCAO": (str(ROOT / "Banco" / "RINCAO.FDB"), {"company_code": "000001", "crt_source": "EMISSOR"}),
|
||||
"UNIAO": (str(ROOT / "Banco" / "UNIAO.FDB"), {"company_code": "000001", "crt_source": "EMISSOR"}),
|
||||
}
|
||||
|
||||
|
||||
def connect(path):
|
||||
return fdb.connect(database=path, user=os.environ["FB_USER"], password=os.environ["FB_PASSWORD"],
|
||||
fb_library_name=os.environ.get("FIREBIRD_CLIENT_LIBRARY"), isolation_level=fdb.ISOLATION_LEVEL_READ_COMMITED_RO)
|
||||
|
||||
|
||||
def analyze_one(name, path, cfg):
|
||||
conn = connect(path); cur = conn.cursor()
|
||||
company = cc.resolve(cc.collect_evidence(cur), cc.config_from_env({("COMPANY_CODE" if k == "company_code" else "COMPANY_CRT_SOURCE"): v for k, v in cfg.items()}))
|
||||
out = {"name": name, "company_status": company["status"], "crt": company.get("company_crt"), "uf": company.get("company_uf")}
|
||||
try:
|
||||
cur.execute("SELECT COUNT(*), COUNT(NULLIF(TRIM(CST_PIS_COFINS),'')) FROM NFCE_ITENS")
|
||||
tot, nonblank = cur.fetchone()
|
||||
out["items_total"] = tot; out["items_cst_pis_cofins_filled"] = nonblank
|
||||
out["pct_cst_filled"] = round(100.0 * nonblank / tot, 2) if tot else None
|
||||
except Exception as e: # noqa: BLE001
|
||||
out["items_error"] = str(e)[:150]; conn.close(); return out
|
||||
cur.execute("SELECT TRIM(CST_PIS_COFINS), COUNT(*) FROM NFCE_ITENS WHERE CST_PIS_COFINS IS NOT NULL GROUP BY 1 ORDER BY 2 DESC")
|
||||
out["cst_distribution"] = {(r[0] or "(blank)"): r[1] for r in cur.fetchall()}
|
||||
try:
|
||||
cur.execute("SELECT EXTRACT(YEAR FROM DATA), COUNT(*), COUNT(NULLIF(TRIM(CST_PIS_COFINS),'')) FROM NFCE_ITENS GROUP BY 1 ORDER BY 1")
|
||||
out["by_year"] = {int(y): {"items": t, "cst_filled": f} for y, t, f in cur.fetchall() if y is not None}
|
||||
except Exception as e: # noqa: BLE001
|
||||
out["by_year_error"] = str(e)[:150]
|
||||
try:
|
||||
cur.execute("SELECT ROUND(ALIQUOTA_PIS,4), COUNT(*) FROM NFCE_ITENS WHERE ALIQUOTA_PIS IS NOT NULL GROUP BY 1 ORDER BY 2 DESC")
|
||||
out["aliquota_pis_distribution"] = {str(r[0]): r[1] for r in cur.fetchmany(10)}
|
||||
cur.execute("SELECT ROUND(ALIQUOTA_COFINS,4), COUNT(*) FROM NFCE_ITENS WHERE ALIQUOTA_COFINS IS NOT NULL GROUP BY 1 ORDER BY 2 DESC")
|
||||
out["aliquota_cofins_distribution"] = {str(r[0]): r[1] for r in cur.fetchmany(10)}
|
||||
except Exception as e: # noqa: BLE001
|
||||
out["aliquota_error"] = str(e)[:150]
|
||||
# cadastro vs emissão, apenas nos itens cujo NCM emitido == NCM cadastrado (join identity-consistent, evita o viés de 2022-12 já documentado)
|
||||
try:
|
||||
cur.execute("""
|
||||
SELECT COUNT(*),
|
||||
SUM(CASE WHEN TRIM(COALESCE(p.CLASSIFICACAO_FISCAL,'')) != '' AND p.CLASSIFICACAO_FISCAL = i.NCM THEN 1 ELSE 0 END)
|
||||
FROM NFCE_ITENS i JOIN C000025 p ON p.CODIGO = i.CODPRODUTO
|
||||
""")
|
||||
joined_tot, identity_consistent = cur.fetchone()
|
||||
out["identity_consistent_items"] = identity_consistent
|
||||
cur.execute("""
|
||||
SELECT COUNT(*)
|
||||
FROM NFCE_ITENS i JOIN C000025 p ON p.CODIGO = i.CODPRODUTO
|
||||
WHERE p.CLASSIFICACAO_FISCAL = i.NCM
|
||||
AND COALESCE(TRIM(p.CST_ENTRADA),'') = '' -- produto sem CST/PIS cadastral (a maioria)
|
||||
AND COALESCE(TRIM(i.CST_PIS_COFINS),'') != ''
|
||||
""")
|
||||
out["emitted_cst_present_when_cadastro_blank"] = cur.fetchone()[0]
|
||||
except Exception as e: # noqa: BLE001
|
||||
out["cadastro_vs_emissao_error"] = str(e)[:150]
|
||||
conn.close()
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
base.load_dotenv(ROOT / ".env")
|
||||
results = {}
|
||||
for name, (path, cfg) in DBS.items():
|
||||
print(f"== {name}", flush=True)
|
||||
try:
|
||||
results[name] = analyze_one(name, path, cfg)
|
||||
except Exception as e: # noqa: BLE001
|
||||
results[name] = {"name": name, "error": str(e)[:300]}
|
||||
print(json.dumps(results[name], ensure_ascii=False, indent=1, default=str)[:800], flush=True)
|
||||
out_path = ROOT / "docs" / "runs" / "BLOCO2_PIS_COFINS_CROSS_DB_V1.json"
|
||||
out_path.write_text(json.dumps(results, ensure_ascii=False, indent=1, default=str), encoding="utf-8")
|
||||
print(f"-> {out_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,66 @@
|
||||
"""Missão 2 / Bloco 1 — dry-run fiscal read-only sobre um banco arbitrário (Agent 2 = GTI.FDB incluso).
|
||||
|
||||
Reaproveita o motor puro (central.app.field_contract) e as funções analyze()/summarize()
|
||||
já existentes em scripts/local_completion.py (mesma taxonomia ALREADY_CORRECT/CHANGE_REQUIRED/
|
||||
NOT_APPLICABLE/MANUAL_INPUT_REQUIRED). NUNCA chama apply()/execute_guarded_product_update —
|
||||
este script não importa firebird_write_executor e não abre conexão rw=True em nenhum ponto.
|
||||
|
||||
python scripts/dry_run_universe.py --name GTI --path "D:\\PROJETOS\\GTISISTEMAS\\Banco\\GTI.FDB" [--company-code C] [--crt-source AUTO|EMISSOR|C000004]
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT)); sys.path.insert(0, str(ROOT / "scripts")); sys.path.insert(0, str(ROOT / "agent" / "src"))
|
||||
import run_fiscal_decision_v1 as base # noqa: E402
|
||||
import local_completion as lc # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--name", required=True)
|
||||
ap.add_argument("--path", required=True)
|
||||
ap.add_argument("--company-code")
|
||||
ap.add_argument("--crt-source")
|
||||
ap.add_argument("--out")
|
||||
a = ap.parse_args()
|
||||
base.load_dotenv(ROOT / ".env")
|
||||
token = os.environ["AGENT_DEV_TOKEN"]
|
||||
if a.company_code or a.crt_source:
|
||||
lc.COMPANY_CFG[a.name] = {k: v for k, v in (("company_code", a.company_code), ("crt_source", a.crt_source)) if v}
|
||||
an = lc.analyze(a.name, a.path, token)
|
||||
summary = lc.summarize(an)
|
||||
from collections import Counter
|
||||
scope_by_key = Counter()
|
||||
for res in an["results"].values():
|
||||
for fld, r in res.items():
|
||||
if fld.startswith("_"):
|
||||
continue
|
||||
if r.get("state") == "MANUAL_INPUT_REQUIRED":
|
||||
scope_by_key[(fld, r.get("input_key"), r.get("input_scope"))] += 1
|
||||
summary["manual_inputs_by_scope"] = {f"{f}|{k}|{s}": n for (f, k, s), n in sorted(scope_by_key.items(), key=lambda x: -x[1])}
|
||||
out_path = Path(a.out) if a.out else (ROOT / "docs" / "runs" / f"{a.name}_dryrun_universe_summary.json")
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_path.write_text(json.dumps(summary, ensure_ascii=False, indent=1, default=str), encoding="utf-8")
|
||||
print(json.dumps({"name": a.name, "total_products": summary["total_products"], "company": summary["company"],
|
||||
"change_required_cells": summary["change_required_cells"], "write_eligible_cells": summary["write_eligible_cells"]}, indent=1, default=str))
|
||||
ndjson_path = ROOT / "docs" / "runs" / f"{a.name}_dryrun_universe.ndjson.gz"
|
||||
import gzip
|
||||
with gzip.open(ndjson_path, "wt", encoding="utf-8") as f:
|
||||
from central.app.field_contract import WRITE_FIELDS, CONTRACT_VERSION
|
||||
for p in an["products"]:
|
||||
code = p["_rk"]; res = an["results"][code]
|
||||
for fld in WRITE_FIELDS:
|
||||
r = res[fld]
|
||||
f.write(json.dumps({"db": a.name, "codigo": p["CODIGO"], "produto": p["PRODUTO"], "field": fld, "current": r["current"], "proposed": r["proposed"],
|
||||
"status": r["state"], "reason": r["reason"], "input_key": r.get("input_key"),
|
||||
"provenance": f"{CONTRACT_VERSION}|" + ",".join(f"{k}:{v}" for k, v in res["_ds"].items()) + f"|role={res['_facts']['role']}"}, ensure_ascii=False, default=str) + "\n")
|
||||
print(f"summary -> {out_path}")
|
||||
print(f"cells -> {ndjson_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -11,20 +11,34 @@ ROOT = Path(__file__).parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
|
||||
def test_versions_are_coherent_across_hub_agent_and_packages():
|
||||
def test_versions_are_coherent_across_hub_and_packages():
|
||||
"""GTI_FISCAL_VERSION is the Hub's own single source (release/VERSION, central/app/version.py,
|
||||
central/pyproject.toml, the manifest). It is deliberately NOT required to match the Agent's own
|
||||
version (see test_agent_version_is_internally_coherent below) - a Hub-only release (Missão 2/3)
|
||||
must be able to bump this without touching, rebuilding or republishing the Agent at all."""
|
||||
from central.app.version import GTI_FISCAL_VERSION
|
||||
v = (ROOT / "release" / "VERSION").read_text(encoding="utf-8").strip()
|
||||
assert v == GTI_FISCAL_VERSION == "1.3.9"
|
||||
assert v == GTI_FISCAL_VERSION == "1.3.10"
|
||||
pep = v.replace("-rc", "rc")
|
||||
for pp in ("central/pyproject.toml", "agent/pyproject.toml"):
|
||||
assert f'version = "{pep}"' in (ROOT / pp).read_text(encoding="utf-8")
|
||||
assert '"AGENT_VERSION", "1.3.9"' in (ROOT / "agent" / "scripts" / "run_agent_service.py").read_text(encoding="utf-8")
|
||||
assert f'version = "{pep}"' in (ROOT / "central" / "pyproject.toml").read_text(encoding="utf-8")
|
||||
manifest = ROOT / "release" / "RELEASE_MANIFEST.json"
|
||||
if manifest.exists():
|
||||
m = json.loads(manifest.read_text(encoding="utf-8"))
|
||||
assert m["GTI_FISCAL_VERSION"] == v and m["migrations"]["alembic_head_in_repo"] == m["migrations"]["applied_revision"]
|
||||
|
||||
|
||||
def test_agent_version_is_internally_coherent():
|
||||
"""The Agent's own version references must agree with EACH OTHER (never guessed, never silently
|
||||
drifting), but are an independent number from the Hub's GTI_FISCAL_VERSION - the Agent is only
|
||||
ever re-versioned when its own code/behavior changes and a new installer is actually built."""
|
||||
agent_pep = (ROOT / "agent" / "pyproject.toml").read_text(encoding="utf-8")
|
||||
m = re.search(r'version = "([^"]+)"', agent_pep)
|
||||
assert m, "agent/pyproject.toml must declare a version"
|
||||
agent_version = m.group(1)
|
||||
assert f'"AGENT_VERSION", "{agent_version}"' in (ROOT / "agent" / "scripts" / "run_agent_service.py").read_text(encoding="utf-8")
|
||||
assert f'AGENT_VERSION_DEFAULT = "{agent_version}"' in (ROOT / "agent" / "src" / "gti_fiscal_agent" / "preflight.py").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_manifest_records_the_real_migration_head():
|
||||
# The manifest said "0011" while the repository was already at 0018: the
|
||||
# generator used the 4-digit file prefix and nothing compared it to Alembic.
|
||||
|
||||
Reference in New Issue
Block a user