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:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user