diff --git a/central/app/control_plane.py b/central/app/control_plane.py
index 748321b..a233811 100644
--- a/central/app/control_plane.py
+++ b/central/app/control_plane.py
@@ -76,6 +76,24 @@ def _fiscal_status(snapshot) -> str:
return "INCOMPLETE"
+def _coverage_partition(snapshot) -> dict:
+ """Expose only explicit snapshot states and label the remaining count honestly."""
+ total = int(getattr(snapshot, "total_products", 0) or 0)
+ complete = int(getattr(snapshot, "fiscally_complete", 0) or 0)
+ safe_blocked = int(getattr(snapshot, "safe_blocked", 0) or 0)
+ failed = int(getattr(snapshot, "failed_products", 0) or 0)
+ explicit = complete + safe_blocked + failed
+ residual = total - explicit
+ invalid_counts = min(total, complete, safe_blocked, failed) < 0
+ overcount = max(0, explicit - total)
+ return {
+ "unclassified_products": max(0, residual) if not invalid_counts else 0,
+ "coverage_overcount_products": overcount,
+ "coverage_invalid_counts": invalid_counts,
+ "coverage_invariant_pass": not invalid_counts and overcount == 0,
+ }
+
+
def _technical_timestamp(item):
if item["technical_health"] in {"FAILED", "DEGRADED"}:
return item.get("last_job_at") or item.get("last_seen_at")
@@ -105,6 +123,9 @@ def installation_view(installation, agent, tenant, snapshot=None, *, now=None):
if version_drift == "UNKNOWN":
version_drift = compute_version_drift(agent.agent_version)
fiscal_status = _fiscal_status(snapshot)
+ coverage_partition = _coverage_partition(snapshot)
+ version_drift_label = {"CURRENT": "Atualizada", "OUTDATED": "Desatualizada", "UNKNOWN": "Desconhecida"}.get(version_drift, str(version_drift))
+ agent_version_display = f"{agent.agent_version} · {version_drift_label}" if agent.agent_version else "Desconhecida"
return {
"agent_id": agent.id,
"installation_id": installation.installation_id,
@@ -119,7 +140,9 @@ def installation_view(installation, agent, tenant, snapshot=None, *, now=None):
"company_uf": installation.company_uf,
"company_crt": installation.company_crt,
"agent_version": agent.agent_version,
+ "agent_version_display": agent_version_display,
"version_drift": version_drift,
+ "version_drift_label": version_drift_label,
"health": health,
"health_reason": reason,
"technical_health": health,
@@ -134,6 +157,7 @@ def installation_view(installation, agent, tenant, snapshot=None, *, now=None):
"fiscally_complete": snapshot.fiscally_complete,
"safe_blocked": snapshot.safe_blocked,
"failed_products": snapshot.failed_products,
+ **coverage_partition,
"coverage_percent": snapshot.coverage_percent,
"fiscal_completion_percent": _fiscal_completion(snapshot),
"fiscal_status": fiscal_status,
@@ -362,15 +386,25 @@ def dashboard_summary(session, now=None):
for state, count in health_rows:
counts[state] = int(count)
+ snapshot_total = func.coalesce(AgentSnapshot.total_products, 0)
+ snapshot_complete = func.coalesce(AgentSnapshot.fiscally_complete, 0)
+ snapshot_blocked = func.coalesce(AgentSnapshot.safe_blocked, 0)
+ snapshot_failed = func.coalesce(AgentSnapshot.failed_products, 0)
+ snapshot_explicit = snapshot_complete + snapshot_blocked + snapshot_failed
+ snapshot_has_negative = or_(snapshot_total < 0, snapshot_complete < 0, snapshot_blocked < 0, snapshot_failed < 0)
total_row = session.execute(
select(
func.coalesce(func.sum(AgentSnapshot.total_products), 0),
func.coalesce(func.sum(AgentSnapshot.fiscally_complete), 0),
func.coalesce(func.sum(AgentSnapshot.safe_blocked), 0),
func.coalesce(func.sum(AgentSnapshot.failed_products), 0),
+ func.coalesce(func.sum(case((and_(~snapshot_has_negative, snapshot_explicit <= snapshot_total), snapshot_total - snapshot_explicit), else_=0)), 0),
+ func.coalesce(func.sum(case((and_(~snapshot_has_negative, snapshot_explicit > snapshot_total), snapshot_explicit - snapshot_total), else_=0)), 0),
+ func.coalesce(func.sum(case((snapshot_has_negative, 1), else_=0)), 0),
).select_from(Installation).outerjoin(AgentSnapshot, AgentSnapshot.installation_id == Installation.id)
).one()
- totals = dict(zip(("total_products", "fiscally_complete", "safe_blocked", "failed_products"), (int(value or 0) for value in total_row)))
+ totals = dict(zip(("total_products", "fiscally_complete", "safe_blocked", "failed_products", "unclassified_products", "coverage_overcount_products", "coverage_invalid_snapshots"), (int(value or 0) for value in total_row)))
+ totals["coverage_invariant_pass"] = totals["coverage_overcount_products"] == 0 and totals["coverage_invalid_snapshots"] == 0
open_filter = BlockedProduct.resolved_at.is_(None)
open_blockers = session.scalar(select(func.count(BlockedProduct.id)).where(open_filter)) or 0
diff --git a/central/scripts/generate_control_panel_fixture.py b/central/scripts/generate_control_panel_fixture.py
index dd2106f..86535aa 100644
--- a/central/scripts/generate_control_panel_fixture.py
+++ b/central/scripts/generate_control_panel_fixture.py
@@ -23,7 +23,12 @@ def generate_fixture(session, agent_count=100, seed=7):
last_seen = None if state == "UNKNOWN" else now - timedelta(seconds=10 if state in {"ONLINE", "DEGRADED"} else 10_000)
installation = Installation(agent_id=agent.id, tenant_id=tenant.id, installation_id=str(uuid.uuid5(uuid.NAMESPACE_DNS, f"fixture-{index}")), display_name=f"{tenant.name} - Servidor Principal", customer_code=f"F{index:03d}", hostname=f"srv-{index:03d}", company_name=f"Empresa {index:03d}", company_uf="BA", company_crt="1", last_seen_at=last_seen, last_status="IDLE")
session.add(installation); session.flush()
- snapshot = AgentSnapshot(installation_id=installation.id, total_products=5000 + index, fiscally_complete=4900 + index, safe_blocked=1 if index % 2 == 0 else 0, failed_products=1 if state == "FAILED" else 0, coverage_percent=98.0, blockers={"CFOP": index % 7, "ST": index % 5}, agent_health=state, version_drift="UNKNOWN" if agent_version is None else ("OUTDATED" if agent_version != "1.0.0rc1" else "CURRENT"), last_job_status="FAILED" if state in {"DEGRADED", "FAILED"} else "COMPLETED")
+ total_products = 5000 + index
+ safe_blocked = 1 if index % 2 == 0 else 0
+ failed_products = 1 if state == "FAILED" else 0
+ fiscally_complete = total_products - safe_blocked - failed_products
+ coverage_percent = round(fiscally_complete * 100 / total_products, 1)
+ snapshot = AgentSnapshot(installation_id=installation.id, total_products=total_products, fiscally_complete=fiscally_complete, safe_blocked=safe_blocked, failed_products=failed_products, coverage_percent=coverage_percent, blockers={"CFOP": index % 7, "ST": index % 5}, agent_health=state, version_drift="UNKNOWN" if agent_version is None else ("OUTDATED" if agent_version != "1.0.0rc1" else "CURRENT"), last_job_status="FAILED" if state in {"DEGRADED", "FAILED"} else "COMPLETED")
session.add(snapshot); created["agents"] += 1
failed = state in {"DEGRADED", "FAILED"}
diff --git a/central/static/control-panel.css b/central/static/control-panel.css
index 35f4b00..3af7efb 100644
--- a/central/static/control-panel.css
+++ b/central/static/control-panel.css
@@ -43,10 +43,14 @@ small { display: block; font-size: 12px; }
.panel-title { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
.panel-title h2 { margin-bottom: 0; }
.panel-title a { color: var(--accent); font-size: 12px; }
-.coverage { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
+.panel-title a.secondary { color: #fff; font-size: inherit; }
+.coverage { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; }
.coverage div { border-left: 3px solid var(--accent-soft); padding-left: 12px; }
.coverage strong { display: block; font-size: 23px; }
.coverage span { color: var(--muted); font-size: 12px; }
+.coverage-status { margin: 16px 0 0; color: var(--accent); font-size: 12px; font-weight: 700; }
+.registry-summary { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); color: var(--muted); font-size: 12px; }
+.registry-summary a { text-decoration: none; }
.bar-row { display: flex; justify-content: space-between; border-bottom: 1px solid var(--line); padding: 8px 0; }
.bar-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 24px; }
.list-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--line); text-decoration: none; color: inherit; }
@@ -90,6 +94,12 @@ td a { text-decoration: none; }
.event-row { display: grid; grid-template-columns: 120px 1fr auto; align-items: start; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line); }
.event-row:last-child { border-bottom: 0; }
.event-time { color: var(--muted); font-size: 12px; }
+.login-page { min-height: 100vh; display: grid; place-items: center; padding: 24px; background: var(--paper); }
+.login-card { width: min(100%, 420px); display: grid; gap: 10px; padding: 30px; background: var(--card); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow); }
+.login-card .eyebrow, .login-card h1 { margin-bottom: 2px; }
+.login-card label { margin-top: 6px; color: var(--muted); font-size: 13px; font-weight: 700; }
+.login-card input { width: 100%; }
+.login-card button { width: 100%; margin-top: 8px; }
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
.form-grid label { display: grid; gap: 5px; color: var(--muted); font-weight: 700; }
.form-grid .wide { grid-column: 1 / -1; }
@@ -120,4 +130,5 @@ td a { text-decoration: none; }
.cards, .cards.compact, .split, .bar-grid { grid-template-columns: 1fr; }
.filters label, .filters input, .filters select, .filters button { width: 100%; }
.filters button { margin-top: 2px; }
+ .login-card { padding: 22px; }
}
diff --git a/central/templates/agent_detail.html b/central/templates/agent_detail.html
index 0312cb8..516fb42 100644
--- a/central/templates/agent_detail.html
+++ b/central/templates/agent_detail.html
@@ -2,9 +2,10 @@
{% block title %}{{ detail.display_name }} | GTI Fiscal{% endblock %}
{% block content %}
{{ detail.client or "Cliente não identificado" }} / INSTALAÇÃO
{{ detail.display_name or detail.hostname or detail.installation_id }} {{ detail.hostname or "hostname indisponível" }}
Saúde técnica: {{ detail.technical_health_label }} Status fiscal: {{ detail.fiscal_status_label }}
-Produtos {{ detail.total_products }} Completos {{ detail.fiscally_complete }} Bloqueados com segurança {{ detail.safe_blocked }} Cobertura fiscal {{ "%.1f"|format(detail.fiscal_completion_percent)|replace(".", ",") }}% {{ detail.fiscally_complete }} / {{ detail.total_products }}
+Produtos {{ detail.total_products }} Completos {{ detail.fiscally_complete }} Bloqueados com segurança {{ detail.safe_blocked }} Falhas técnicas {{ detail.failed_products }} Não classificados {{ detail.unclassified_products }} Cobertura fiscal {{ "%.1f"|format(detail.fiscal_completion_percent)|replace(".", ",") }}% {{ detail.fiscally_complete }} / {{ detail.total_products }} completos
+{% if detail.coverage_invariant_pass %}Cobertura reconciliada para este servidor.
{% else %}Inconsistência de cobertura: {{ detail.coverage_overcount_products }} acima do total{% if detail.coverage_invalid_counts %}; existem contagens negativas{% endif %}.
{% endif %}
{% for name,label in [("overview","Resumo"),("fiscal","Fiscal"),("blockers","Blockers"),("jobs","Jobs"),("events","Eventos")] %}{{ label }} {% endfor %}
- Cliente {{ detail.client or "-" }} Nome de exibição {{ detail.display_name or "-" }} Hostname {{ detail.hostname or "-" }} Empresa {{ detail.company_name or detail.company_code or "-" }} UF {{ detail.company_uf or "-" }} CRT {{ detail.company_crt or "-" }} Versão do agent {{ detail.agent_version or "Desconhecida" }} · {{ detail.version_drift|title }} Último heartbeat {{ detail.last_seen_display }} · {{ detail.last_seen_age }} Firebird {{ detail.firebird_version or "-" }} · ODS {{ detail.firebird_ods or "-" }} IDs técnicos Agent {{ detail.technical_ids.agent_id }} · instalação {{ detail.technical_ids.installation_db_id }}
+ Cliente {{ detail.client or "Não informado" }} Nome de exibição {{ detail.display_name or "Não informado" }} Hostname {{ detail.hostname or "Não informado" }} Empresa {{ detail.company_name or detail.company_code or "Não informado" }} UF {{ detail.company_uf or "Não informado" }} CRT {{ detail.company_crt or "Não informado" }} Versão do agent {{ detail.agent_version_display }} Último heartbeat {{ detail.last_seen_display }} · {{ detail.last_seen_age }} Firebird {{ detail.firebird_version or "Não informado" }} · ODS {{ detail.firebird_ods or "Não informado" }} IDs técnicos Agent {{ detail.technical_ids.agent_id }} · instalação {{ detail.technical_ids.installation_db_id }}
{% for event in events[:8] %}{% else %}Nenhum evento.
{% endfor %}
Eventos operacionais Filtros aplicados no servidor.
diff --git a/central/templates/agents.html b/central/templates/agents.html
index ff1c008..22a0260 100644
--- a/central/templates/agents.html
+++ b/central/templates/agents.html
@@ -2,6 +2,6 @@
{% block title %}Agents | GTI Fiscal{% endblock %}
{% block content %}
INVENTÁRIO OPERACIONAL
Agents / Servidores Identidade do cliente primeiro; IDs técnicos ficam no detalhe.
-Instalações monitoradas Cliente Nome de exibição Hostname Saúde técnica Status fiscal Cobertura fiscal Produtos Versão Último heartbeat {% for item in result["items"] %}{{ item.client or "Cliente não identificado" }} {{ item.display_name or "-" }}ID técnico: {{ item.installation_id }} {{ item.hostname or "-" }} Saúde técnica: {{ item.technical_health_label }} Status fiscal: {{ item.fiscal_status_label }} {% if item.safe_blocked %}{{ item.safe_blocked }} bloqueados com segurança {% endif %}{{ "%.1f"|format(item.fiscal_completion_percent)|replace(".", ",") }}% {{ item.fiscally_complete }} / {{ item.total_products }} {{ item.total_products }}{{ item.failed_products }} falhas {{ item.agent_version or "Desconhecida" }}{{ item.version_drift|title }} {{ item.last_seen_display }}{{ item.last_seen_age }} {% else %}Nenhuma instalação encontrada. {% endfor %}
+Instalações monitoradas Cliente Nome de exibição Hostname Saúde técnica Status fiscal Cobertura fiscal Produtos Versão Último heartbeat {% for item in result["items"] %}{{ item.client or "Cliente não identificado" }} {{ item.display_name or "Não informado" }} {{ item.hostname or "Não informado" }} Saúde técnica: {{ item.technical_health_label }} Status fiscal: {{ item.fiscal_status_label }} {{ item.fiscally_complete }} completos · {{ item.safe_blocked }} bloqueados com segurança · {{ item.failed_products }} falhas · {{ item.unclassified_products }} não classificados {% if not item.coverage_invariant_pass %}Inconsistência: {{ item.coverage_overcount_products }} acima do total{% if item.coverage_invalid_counts %}; contagens negativas{% endif %} {% endif %}{{ "%.1f"|format(item.fiscal_completion_percent)|replace(".", ",") }}% {{ item.fiscally_complete }} / {{ item.total_products }} completos {{ item.total_products }}{{ item.failed_products }} falhas técnicas {{ item.agent_version_display }} {{ item.last_seen_display }}{{ item.last_seen_age }} {% else %}Nenhuma instalação encontrada. {% endfor %}
{% endblock %}
diff --git a/central/templates/dashboard.html b/central/templates/dashboard.html
index 0e4db17..a815c50 100644
--- a/central/templates/dashboard.html
+++ b/central/templates/dashboard.html
@@ -6,7 +6,7 @@
{% for key,label in [("agents_total","Agents total"),("online","Online"),("degraded","Degraded"),("offline","Offline"),("failed","Failed"),("unknown","Unknown")] %}{{ label }} {{ summary[key] }} {% endfor %}
- {{ summary.safe_blocked_products }} Produtos bloqueados
{{ summary.open_blockers }} Blockers abertos
{{ summary.total_products }} Produtos avaliados
{{ summary.fiscally_complete }} Completos
+ {% for key,label in [("total_products","Produtos avaliados"),("fiscally_complete","Completos"),("safe_blocked","Bloqueados com segurança"),("failed_products","Falhas técnicas"),("unclassified_products","Não classificados") ] %}
{{ summary[key] }} {{ label }}
{% endfor %}
{% if summary.coverage_invariant_pass %}Cobertura reconciliada: total = completos + bloqueados com segurança + falhas técnicas + não classificados.
{% else %}Inconsistência de cobertura: {{ summary.coverage_overcount_products }} acima do total; {{ summary.coverage_invalid_snapshots }} snapshot(s) com contagens negativas.
{% endif %}
{% for key in ["CFOP","ST","ICMS","CSOSN","NCM","OTHER"] %}{{ key }} {{ summary.open_blocker_breakdown[key] }}
{% endfor %}
diff --git a/central/templates/login.html b/central/templates/login.html
index 9f3cc0e..23545c5 100644
--- a/central/templates/login.html
+++ b/central/templates/login.html
@@ -1 +1 @@
-Login | GTI Fiscal
+Login | GTI Fiscal
diff --git a/central/tests/test_control_panel_fixture.py b/central/tests/test_control_panel_fixture.py
index 1ab708d..83cd0d6 100644
--- a/central/tests/test_control_panel_fixture.py
+++ b/central/tests/test_control_panel_fixture.py
@@ -17,6 +17,8 @@ def test_fixture_creates_one_hundred_varied_installations_and_events():
assert {row.agent_health for row in snapshots} == {"ONLINE", "DEGRADED", "OFFLINE", "FAILED", "UNKNOWN"}
assert {row.version_drift for row in snapshots} == {"CURRENT", "OUTDATED", "UNKNOWN"}
assert {row.installation_id for row in session.query(Installation).all()} == {str(uuid.uuid5(uuid.NAMESPACE_DNS, f"fixture-{index}")) for index in range(100)}
+ assert all(row.fiscally_complete + row.safe_blocked + row.failed_products == row.total_products for row in snapshots)
+ assert all(row.coverage_percent == round(row.fiscally_complete * 100 / row.total_products, 1) for row in snapshots)
blockers = session.query(BlockedProduct).all()
assert len({row.blocker_fingerprint for row in blockers}) == len(blockers)
diff --git a/central/tests/test_control_panel_pages.py b/central/tests/test_control_panel_pages.py
index 6ad3442..f8ba405 100644
--- a/central/tests/test_control_panel_pages.py
+++ b/central/tests/test_control_panel_pages.py
@@ -22,6 +22,8 @@ def _panel_client():
snapshot = session.scalar(select(AgentSnapshot).where(AgentSnapshot.installation_id == installation.id))
snapshot.total_products = 50
snapshot.fiscally_complete = 49
+ snapshot.safe_blocked = 1
+ snapshot.failed_products = 0
snapshot.coverage_percent = 98
job = Job(
agent_id=agent_id,
@@ -80,7 +82,7 @@ def test_dashboard_renders_operational_queue_labels_and_human_dates():
assert response.status_code == 200
for label in ("Agents total", "Online", "Degraded", "Offline", "Failed", "Unknown"):
assert label in response.text
- assert "Produtos bloqueados" in response.text
+ assert "Produtos com blockers abertos" in response.text
assert "Blockers abertos" in response.text
assert "ATENÇÃO NECESSÁRIA" in response.text
assert "Ver todos" in response.text
@@ -89,6 +91,30 @@ def test_dashboard_renders_operational_queue_labels_and_human_dates():
assert not re.search(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}", response.text)
+def test_dashboard_coverage_displays_reconciled_product_buckets():
+ client, _, _ = _panel_client()
+
+ response = client.get("/")
+
+ assert response.status_code == 200
+ for label in ("Produtos avaliados", "Completos", "Bloqueados com segurança", "Falhas técnicas", "Não classificados"):
+ assert label in response.text
+ assert "Produtos com blockers abertos" in response.text
+ assert "Cobertura reconciliada" in response.text
+
+
+def test_login_has_centered_card_and_explicit_field_labels():
+ client, _, _ = _panel_client()
+
+ response = client.get("/login")
+
+ assert response.status_code == 200
+ assert 'class="login-page"' in response.text
+ assert 'class="login-card"' in response.text
+ assert 'Usuário ' in response.text
+ assert 'Senha ' in response.text
+
+
def test_dashboard_lists_bounded_failed_job_rows_with_identity_error_age_and_empty_state():
client, agent_id, _ = _panel_client()
factory = client._session_factory
@@ -159,6 +185,27 @@ def test_agents_render_separate_health_and_fiscal_status_with_server_sort():
assert 'value="worst_coverage"' in response.text
+def test_agent_list_hides_technical_uuid_and_explains_remainder():
+ client, agent_id, _ = _panel_client()
+ with client._session_factory() as session:
+ installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
+ technical_uuid = installation.installation_id
+ agent = session.get(Agent, agent_id)
+ agent.agent_version = None
+ snapshot = session.scalar(select(AgentSnapshot).where(AgentSnapshot.installation_id == installation.id))
+ snapshot.version_drift = "UNKNOWN"
+ session.commit()
+
+ response = client.get("/agents")
+
+ assert response.status_code == 200
+ assert technical_uuid not in response.text
+ assert "classificados" in response.text
+ assert "bloqueados com segurança" in response.text
+ assert "falhas técnicas" in response.text
+ assert "DesconhecidaDesconhecida " not in response.text
+
+
def test_agent_list_and_detail_show_localized_last_seen_with_iso_tooltip():
client, agent_id, _ = _panel_client()
expected_iso = '2026-09-22T19:11:37+00:00'
@@ -310,6 +357,44 @@ def test_agent_detail_resolves_agent_after_first_hundred_installations():
assert "Late ERP 104" in response.text
+def test_agent_detail_reconciles_coverage_and_formats_missing_technical_fields():
+ client, agent_id, _ = _panel_client()
+ with client._session_factory() as session:
+ agent = session.get(Agent, agent_id)
+ agent.agent_version = None
+ installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
+ snapshot = session.scalar(select(AgentSnapshot).where(AgentSnapshot.installation_id == installation.id))
+ snapshot.version_drift = "UNKNOWN"
+ session.commit()
+
+ response = client.get(f"/agents/{agent_id}")
+
+ assert response.status_code == 200
+ for label in ("Produtos", "Completos", "Bloqueados com segurança", "Falhas técnicas", "Não classificados", "Cobertura fiscal"):
+ assert label in response.text
+ assert "Não informado" in response.text
+ assert "Desconhecida" in response.text
+ assert "Desconhecida · Desconhecida" not in response.text
+ assert "ODS -" not in response.text
+ context = response.text.split("Contexto da empresa ", 1)[1].split("", 1)[0]
+ assert "Reprocessar tudo" in context
+ assert not re.search(r"<(?:a|button)\b[^>]*>\s*(?:a|button)>", context)
+
+
+def test_agent_detail_reports_negative_coverage_counts_as_data_inconsistency():
+ client, agent_id, _ = _panel_client()
+ with client._session_factory() as session:
+ installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
+ snapshot = session.scalar(select(AgentSnapshot).where(AgentSnapshot.installation_id == installation.id))
+ snapshot.failed_products = -1
+ session.commit()
+
+ response = client.get(f"/agents/{agent_id}")
+
+ assert response.status_code == 200
+ assert "contagens negativas" in response.text
+
+
def test_agent_event_filters_lookahead_and_safe_event_reference_links_and_confirmation():
client, agent_id, _ = _panel_client()
factory = client._session_factory
diff --git a/central/tests/test_operational_view_models.py b/central/tests/test_operational_view_models.py
index 600ed5f..6482117 100644
--- a/central/tests/test_operational_view_models.py
+++ b/central/tests/test_operational_view_models.py
@@ -100,6 +100,70 @@ def test_dashboard_reconciles_five_health_states_and_counts_distinct_safe_produc
assert summary["online"] == 1 and summary["safe_blocked"] == 2
+def test_snapshot_coverage_exposes_unclassified_residual_and_overcount():
+ session_factory = setup_db()
+ with session_factory() as session:
+ tenant, agent, installation, snapshot = _installation(session, 1, coverage=80, blocked=1)
+ snapshot.total_products = 100
+ snapshot.fiscally_complete = 96
+ snapshot.safe_blocked = 1
+ snapshot.failed_products = 1
+ session.commit()
+
+ item = list_installations(session, page_size=5)["items"][0]
+ assert item["unclassified_products"] == 2
+ assert item["coverage_overcount_products"] == 0
+ assert item["coverage_invariant_pass"] is True
+ assert item["total_products"] == item["fiscally_complete"] + item["safe_blocked"] + item["failed_products"] + item["unclassified_products"]
+
+ snapshot.fiscally_complete = 99
+ session.commit()
+ inconsistent = list_installations(session, page_size=5)["items"][0]
+ assert inconsistent["unclassified_products"] == 0
+ assert inconsistent["coverage_overcount_products"] == 1
+ assert inconsistent["coverage_invariant_pass"] is False
+
+
+def test_dashboard_coverage_totals_reconcile_and_keep_registry_metric_separate():
+ session_factory = setup_db()
+ with session_factory() as session:
+ tenant, _, installation, snapshot = _installation(session, 1, coverage=96, blocked=1)
+ snapshot.total_products = 100
+ snapshot.fiscally_complete = 96
+ snapshot.safe_blocked = 1
+ snapshot.failed_products = 1
+ _blocker(session, tenant, product="P-OPEN", field="CFOP", index=91, installation_id=installation.id)
+ session.commit()
+
+ summary = dashboard_summary(session, now=NOW)
+
+ assert summary["total_products"] == 100
+ assert summary["fiscally_complete"] == 96
+ assert summary["safe_blocked"] == 1
+ assert summary["failed_products"] == 1
+ assert summary["unclassified_products"] == 2
+ assert summary["coverage_overcount_products"] == 0
+ assert summary["coverage_invariant_pass"] is True
+ assert summary["safe_blocked_products"] == 1
+ assert summary["total_products"] == summary["fiscally_complete"] + summary["safe_blocked"] + summary["failed_products"] + summary["unclassified_products"]
+
+
+def test_dashboard_marks_inconsistent_snapshot_partition_instead_of_clamping():
+ session_factory = setup_db()
+ with session_factory() as session:
+ _, _, _, snapshot = _installation(session, 1, coverage=100, blocked=2)
+ snapshot.total_products = 10
+ snapshot.fiscally_complete = 9
+ snapshot.safe_blocked = 2
+ snapshot.failed_products = 0
+ session.commit()
+
+ summary = dashboard_summary(session, now=NOW)
+
+ assert summary["coverage_overcount_products"] == 1
+ assert summary["coverage_invariant_pass"] is False
+
+
def test_attention_items_are_ranked_and_capped_at_ten():
session_factory = setup_db()
with session_factory() as session:
diff --git a/docs/CONTROL_PANEL_OPERATIONAL_UX_V1.md b/docs/CONTROL_PANEL_OPERATIONAL_UX_V1.md
index f676ef1..4fb7348 100644
--- a/docs/CONTROL_PANEL_OPERATIONAL_UX_V1.md
+++ b/docs/CONTROL_PANEL_OPERATIONAL_UX_V1.md
@@ -21,15 +21,32 @@ Technical health describes Agent/installation availability and processing
failures. Fiscal status and fiscal completion are separate: safe-blocked fiscal
work does not make an otherwise healthy Agent technically degraded or failed.
Agent views show product total, fiscally complete count, safe-blocked count,
-failed count, and fiscal completion percentage. The percentage is
+technical-failure count, unclassified residual, and fiscal completion percentage.
+At installation/snapshot grain, coverage is reconciled as:
+
+```text
+TOTAL_PRODUCTS = COMPLETE + SAFE_BLOCKED + FAILED_PRODUCTS + UNCLASSIFIED
+```
+
+`UNCLASSIFIED` is the positive residual `TOTAL_PRODUCTS - COMPLETE -
+SAFE_BLOCKED - FAILED_PRODUCTS`: the snapshot did not assign those products to
+one of its explicit states. It is an observability bucket, not a newly inferred
+fiscal state. If explicit category counts exceed the total or contain negative
+values, the panel shows an inconsistency instead of clamping it away. Fixture
+snapshots assign all products explicitly and therefore reconcile to zero
+unclassified products. The fiscal completion percentage is
`fiscally_complete / total_products * 100`, rounded to one decimal place and
bounded to 0–100; when the total is zero, the stored coverage percentage is
used. The fiscal status badge is `BLOCKED` when safe-blocked work exists,
`COMPLETE` when all products are complete, and otherwise `INCOMPLETE`.
-Two blocker counts must remain distinct:
+Coverage-state counts and blocker-registry counts have different sources and
+must remain distinct:
-- **Safe-blocked products**: distinct open `(tenant_id, product_code)` pairs.
+- **Safe-blocked coverage products**: sum of the Agent snapshot safe-blocked
+ count at installation grain; this participates in the coverage invariant.
+- **Products with open blockers**: distinct open `(tenant_id, product_code)`
+ pairs in the blocker registry.
- **Open blockers**: open blocker rows; one product can have multiple rows for
different fields or evidence.
diff --git a/docs/superpowers/plans/2026-09-22-control-panel-final-predeploy-fixes-v1.md b/docs/superpowers/plans/2026-09-22-control-panel-final-predeploy-fixes-v1.md
new file mode 100644
index 0000000..a78b1d1
--- /dev/null
+++ b/docs/superpowers/plans/2026-09-22-control-panel-final-predeploy-fixes-v1.md
@@ -0,0 +1,81 @@
+# Control Panel Final Pre-Deploy Fixes V1 Implementation Plan
+
+> **For agentic workers:** Execute inline; the operator mission authorizes autonomous completion. Steps use checkbox syntax for tracking.
+
+**Goal:** Reconcile fiscal coverage counts, correct the fixture gap, and apply the narrowly scoped login/Agent-list/Agent-detail presentation fixes before deploy freeze.
+
+**Architecture:** Preserve the FastAPI/Jinja2/CSS/vanilla-JS panel and fiscal/Agent behavior. Add a derived, explicitly named `unclassified_products` residual from snapshot coverage, expose a consistency flag for over-counted categories, correct only fixture counts that are known synthetic gaps, and make requested UI-only changes.
+
+**Tech Stack:** Python 3.12, FastAPI, Jinja2, SQLAlchemy, CSS, pytest, Docker Compose local runtime.
+
+## Global Constraints
+
+- Do not deploy, alter fiscal engine/requiredness, call AI, enable Firebird writes, change architecture, add SPA, modify Proxmox, or integrate GTI Gerenciador.
+- A product coverage state is counted at installation/snapshot grain; existing distinct blocker registry totals remain separately labeled.
+- Define `UNCLASSIFIED = TOTAL - COMPLETE - SAFE_BLOCKED - FAILED_PRODUCTS` only when nonnegative; show an inconsistency warning and fail `coverage_invariant_pass` when the explicit categories exceed total.
+- Do not use an unexplained generic “other” label; the residual is “Não classificados (sem estado informado)” and documentation says precisely that snapshot telemetry did not assign a known state.
+- Keep Agent percentage and technical IDs in detail/API; remove UUID only from the main Agents table.
+- Do not modify Firebird databases or run fixture generation against production/client data.
+
+---
+
+### Task 1: Prove coverage gap and add view-model/UI tests
+
+**Files:**
+- Modify `central/tests/test_operational_view_models.py`
+- Modify `central/tests/test_control_panel_fixture.py`
+- Modify `central/tests/test_control_panel_pages.py`
+- Add focused assertions to `central/tests/test_control_plane.py` only if needed.
+
+- [ ] Test coverage partition arithmetic for normal, residual, and over-counted snapshots.
+- [ ] Test dashboard sums total/complete/snapshot-safe-blocked/failed and explicit residual; require nonnegative invariant pass only when components do not exceed total.
+- [ ] Test Agent detail/list expose explicit residual and the fiscal percentage.
+- [ ] Test fixture total equals complete + safe-blocked + failed + residual; assert fixture residual zero after correction.
+- [ ] Test login form labels/container, Agent list does not contain technical UUID, detail shows missing fields as `Não informado`/`Desconhecida`, and no empty green anchor/button exists in company card.
+- [ ] Run targeted tests and capture expected failures before implementation.
+
+### Task 2: Implement reconciled coverage view-models and fixture
+
+**Files:**
+- Modify `central/app/control_plane.py`
+- Modify `central/scripts/generate_control_panel_fixture.py`
+- Modify dashboard, Agent list, and Agent detail templates.
+
+**Interfaces:**
+- Add a single helper returning `complete`, `safe_blocked`, `failed`, `unclassified`, `total`, `consistent`, and `overcount` for a snapshot.
+- Agent view adds `unclassified_products`, `coverage_invariant_pass`, `coverage_overcount_products`.
+- Dashboard returns `safe_blocked_coverage_products`, `unclassified_products`, `coverage_invariant_pass`, and `coverage_overcount_products`; retain registry `safe_blocked_products` as a separately labeled blocker metric if displayed.
+
+- [ ] Implement arithmetic with explicit consistency behavior; do not clamp or silently hide over-count.
+- [ ] Dashboard SQL aggregate sums snapshot fields and computes residual at presentation/query aggregation without loading products.
+- [ ] Fixture sets complete to `total - safe_blocked - failed_products`; set coverage percentage from complete/total rounded one decimal.
+- [ ] Add template rows/cards for safe-blocked coverage, failed technical products, and unclassified products; display inconsistency warning when needed.
+- [ ] Agent list explains the remaining percentage beside complete/blocked/failed/unclassified counts.
+- [ ] Run targeted model/fixture/page tests green.
+
+### Task 3: Minimal visual fixes and technical missing values
+
+**Files:**
+- Modify `central/templates/login.html`
+- Modify `central/templates/agents.html`
+- Modify `central/templates/agent_detail.html`
+- Modify `central/static/control-panel.css`
+- Modify `central/tests/test_control_panel_pages.py`
+
+- [ ] Add a login wrapper/card sizing and center layout, explicit `for`/`id` labels, spacing, responsive width, and existing accent colors.
+- [ ] Remove the installation UUID secondary text from the Agents table; retain it in detail technical IDs/API.
+- [ ] Replace empty/unknown technical displays with `Não informado` and `Desconhecida`; do not invent Firebird version/ODS or version drift.
+- [ ] Investigate green empty control: confirm rendered anchor is a real reprocess link with visible text; if visually empty due styles, correct styles; if action absent, remove element. Add assertion for non-empty accessible name/text.
+- [ ] Run focused page tests green.
+
+### Task 4: Documentation and final verification
+
+**Files:**
+- Modify `docs/CONTROL_PANEL_V1.md` and/or `docs/CONTROL_PANEL_OPERATIONAL_UX_V1.md` only if required to define the residual precisely.
+
+- [ ] Document that unclassified is the snapshot residual without a supplied operational state, not a fiscal rule.
+- [ ] Run exactly `python -m pytest tests -q`, `python -m pytest central/tests -q`, and `python -m pytest agent/tests -q`.
+- [ ] Rebuild only local Compose `fiscal-hub`; verify PostgreSQL healthy and Hub `/health` 200.
+- [ ] Smoke `/login`, `/`, `/agents`, `/agents/{id}`; check coverage reconciliation, login, UUID removal, visible reprocess control, and missing technical values.
+- [ ] Measure route latency and record maximum.
+- [ ] Verify flags, no deploy/Firebird operations, diff check, commits, and clean worktree; report exact requested final fields.