git-subtree-dir: fiscal git-subtree-mainline:d95165fc8fgit-subtree-split:102ba84500
81 lines
12 KiB
HTML
81 lines
12 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Resumo | GTI Fiscal{% endblock %}
|
||
{% block content %}
|
||
<div class="page-heading">
|
||
<div><p class="eyebrow">OPERAÇÃO CENTRAL</p><h1>Qual cliente precisa de atenção?</h1><p class="muted">Somente instalações ativas. Histórico permanece disponível nas listagens.</p></div>
|
||
<span class="live-dot">Atualizado pelo snapshot</span>
|
||
</div>
|
||
<section class="cards" aria-label="Saúde dos servidores">
|
||
{% for key,label,health in [("agents_total","Servidores",none),("online","Online","ONLINE"),("degraded","Atenção","DEGRADED"),("offline","Offline","OFFLINE"),("failed","Falhou","FAILED"),("unknown","Não informado","UNKNOWN")] %}
|
||
<a class="metric metric-link" href="/agents?scope=active{% if health %}&health={{ health }}{% endif %}" aria-label="{{ label }}: {{ summary[key] }}. Ver servidores{% if health %} filtrados{% endif %}">
|
||
<span>{{ label }}</span><strong>{{ summary[key] }}</strong><small>Ver servidores <span aria-hidden="true">→</span></small>
|
||
</a>
|
||
{% endfor %}
|
||
</section>
|
||
<section class="split overview-split" id="fontes">
|
||
<article class="panel">
|
||
<div class="panel-title"><div><h2>Fontes de produtos</h2><p class="muted">Consultas feitas somente pelo Hub, dentro do limite gratuito.</p></div></div>
|
||
{% for src in sources %}<div class="list-row"><span><b>{{ src.provider }}</b> <em class="badge {{ '' if src.status == 'ONLINE' else 'critical' }}">{{ src.status_label }}</em>
|
||
<small>{% if src.limit %}{{ src.used }} / {{ src.limit }} consultas hoje · restam {{ src.remaining if src.remaining is not none else "—" }}{% if src.reset_at %} · renova {{ src.reset_at.strftime("%d/%m %H:%M") }} UTC{% endif %}{% elif src.rate_per_minute %}{{ src.rate_per_minute }} consultas/min (limite documentado) · {{ src.used }} hoje{% else %}{{ src.used }} consultas hoje{% endif %}
|
||
{% if src.latency_ms %} · {{ src.latency_ms }} ms{% endif %}{% if src.cache_hit_rate is not none %} · cache {{ src.cache_hit_rate }}%{% endif %}
|
||
{% if src.last_success_at %} · último sucesso {{ src.last_success_at.strftime("%d/%m %H:%M") }}{% endif %}{% if src.last_error %} · último erro: {{ src.last_error }}{% endif %}
|
||
· hoje: {{ src.found_today }} achados{% if not src.identity_only %}, {{ src.ncm_found_today }} com NCM{% else %} (só identidade){% endif %}, {{ src.cache_hits }} do cache, {{ src.rate_limited }} limitados{% if src.next_reset %} · próximo reset {{ src.next_reset.strftime("%d/%m %H:%M") }} UTC{% endif %}
|
||
· configurado: {{ "sim" if src.configured else "não" }}</small></span></div>{% endfor %}
|
||
</article>
|
||
<article class="panel">
|
||
<div class="panel-title"><div><h2>Fontes oficiais</h2><p class="muted">Bases fiscais usadas para validar qualquer preenchimento.</p></div></div>
|
||
{% for o in official %}<div class="list-row"><span><b>{{ o.label }}</b> <em class="badge {{ '' if o.active and not o.error else 'critical' }}">{{ o.status }}</em>
|
||
<small>{% if o.active %}{{ o.records }} registros · {{ o.version }} · {{ o.sha256 }}{% if o.last_update %} · baixada {{ o.last_update.strftime("%d/%m/%Y") }}{% endif %}{% endif %}{% if o.auto_refresh %} · atualização automática diária{% endif %}{% if o.error %} · {{ o.error }}{% endif %}</small></span></div>{% endfor %}
|
||
</article>
|
||
</section>
|
||
<section class="panel" id="kb-resumo"><div class="panel-title"><div><h2>Base de conhecimento de produtos</h2><p class="muted">Evidência local, histórica e externa; nada vira verdade fiscal sem regra de promoção.</p></div></div>
|
||
<div class="coverage"><div class="coverage-item"><strong>{{ kb.products }}</strong><span>Produtos conhecidos</span></div><div class="coverage-item"><strong>{{ kb.verified }}</strong><span>GTINs verificados</span></div><div class="coverage-item"><strong>{{ kb.corroborated }}</strong><span>Corroborados</span></div><div class="coverage-item"><strong>{{ kb.conflicts }}</strong><span>Conflitos</span></div><div class="coverage-item"><strong>{{ kb.hits_today }}</strong><span>Resolvidos pela base hoje</span></div></div>
|
||
<form method="get" action="/" class="filters compact-filters"><label>Buscar produto na base (EAN ou nome)<input name="kb_q" value="{{ kb_q or '' }}" placeholder="ex.: COCA COLA LATA 350"></label><button class="secondary" type="submit">Buscar</button>{% if kb_q %}<small class="muted">{{ kb.search.total }} resultado(s) em toda a base{% if kb.search.pages > 1 %} · página {{ kb.search.page }}/{{ kb.search.pages }}{% endif %}</small>{% endif %}</form>
|
||
{% for c in kb.search.results %}<div class="list-row"><span><b>{{ c.gtin }}</b> {{ c.description }} · <em class="badge">{{ c.status }}</em>
|
||
<small>{% for x in c.candidates %}NCM {{ x.ncm }} — <i>{{ x.official_text or "fora do dataset oficial vigente" }}</i> ({{ x.clients|length }} cliente(s), {{ x.documents }} doc.{% if x.providers %}, {{ x.providers|join("/") }}{% endif %}{% if x.attested %}, decidido por {{ x.attested }}{% endif %}){% if is_admin and x.official_text and c.status != "VERIFIED_KB" %}
|
||
<form method="post" action="/kb/attest" style="display:inline"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="gtin" value="{{ c.gtin_key }}"><input type="hidden" name="ncm" value="{{ x.ncm }}"><button class="secondary" type="submit" title="Decisão fiscal de um administrador: este é o NCM correto deste EAN">Confirmar este NCM</button></form>{% endif %}<br>{% endfor %}</small></span></div>{% endfor %}
|
||
{% if kb_q and kb.search.total == 0 %}<p class="empty-state">Nenhum produto encontrado para "{{ kb_q }}".</p>{% endif %}
|
||
{% if kb.search.pages > 1 %}<nav class="pagination" aria-label="Paginação da busca na base">{% if kb.search.page > 1 %}<a href="/?kb_q={{ kb_q|urlencode }}&kb_page={{ kb.search.page - 1 }}#kb-resumo">Anterior</a>{% endif %}<span>Página {{ kb.search.page }} de {{ kb.search.pages }}</span>{% if kb.search.page < kb.search.pages %}<a href="/?kb_q={{ kb_q|urlencode }}&kb_page={{ kb.search.page + 1 }}#kb-resumo">Próxima</a>{% endif %}</nav>{% endif %}
|
||
{% for c in kb.recent_conflicts %}<div class="list-row"><span><b>{{ c.gtin }}</b> {{ c.description }}<small>NCMs divergentes, nada é preenchido: {% for x in c.candidates %}{{ x.ncm }} ({{ x.clients|length }} cliente(s), {{ x.documents }} doc.{% if x.providers %}, {{ x.providers|join("/") }}{% endif %}){% if not loop.last %} × {% endif %}{% endfor %}</small></span></div>{% endfor %}</section>
|
||
<section class="panel" id="enriquecimento-resumo"><div class="panel-title"><div><h2>Enriquecimento de produtos novos</h2><p class="muted">Cobertura medida sobre os produtos novos acompanhados (o catálogo histórico não entra).</p></div></div>
|
||
{% if enrich_metrics.tracked %}<div class="coverage">{% for key,label in [("GTIN_VALID_RATE","Com GTIN válido"),("KB_HIT_RATE","Resolvidos pela base"),("PRODUCT_FOUND_RATE","Produto identificado"),("NCM_AUTO_FILL_SAFE_RATE","NCM preenchível com segurança"),("AMBIGUOUS_RATE","Ambíguos"),("CONFLICT_RATE","Em conflito")] %}<div class="coverage-item"><strong>{{ enrich_metrics[key] if enrich_metrics[key] is not none else "—" }}{% if enrich_metrics[key] is not none %}%{% endif %}</strong><span>{{ label }}</span></div>{% endfor %}</div>{% else %}<p class="empty-state">Nenhum produto novo acompanhado ainda.</p>{% endif %}</section>
|
||
<section class="split overview-split">
|
||
<article class="panel">
|
||
<div class="panel-title"><h2>Cobertura dos produtos</h2><a href="/agents?scope=active">Ver servidores</a></div>
|
||
<div class="coverage">
|
||
{% for key,label,url in [("total_products","Produtos avaliados","/agents?scope=active"),("fiscally_complete","Completos","/agents?scope=active&sort=worst_coverage"),("safe_blocked","Bloqueados com segurança","/blockers?scope=active&status=OPEN"),("failed_products","Falhas técnicas","/agents?scope=active&health=FAILED"),("unclassified_products","Não classificados","/agents?scope=active&sort=worst_coverage")] %}
|
||
<a class="coverage-item" href="{{ url|safe }}" aria-label="{{ label }}: {{ summary[key] }}. Abrir listagem"><strong>{{ summary[key] }}</strong><span>{{ label }}</span></a>
|
||
{% endfor %}
|
||
</div>
|
||
{% if summary.coverage_invariant_pass %}<p class="coverage-status">Cobertura reconciliada: total = completos + bloqueados + falhas + não classificados.</p>{% else %}<p class="coverage-status error">Inconsistência de cobertura: {{ summary.coverage_overcount_products }} acima do total; {{ summary.coverage_invalid_snapshots }} snapshot(s) com contagens negativas.</p>{% endif %}
|
||
<div class="registry-summary"><a href="/blockers?scope=active&status=OPEN">Produtos com blockers abertos: <strong>{{ summary.safe_blocked_products }}</strong></a><span>Blockers abertos: <strong>{{ summary.open_blockers }}</strong></span></div>
|
||
</article>
|
||
<article class="panel">
|
||
<div class="panel-title"><h2>Breakdown dos blockers</h2><a href="/blockers?scope=active&status=OPEN">Ver blockers</a></div>
|
||
{% for key in ["NCM","CEST","CFOP","ST","ICMS","CSOSN","PIS","COFINS","RTC","OTHER"] %}
|
||
{% if summary.open_blocker_breakdown[key] > 0 %}<a class="bar-row bar-link" href="/blockers?scope=active&status=OPEN&field={{ key }}" aria-label="{{ key }}: {{ summary.open_blocker_breakdown[key] }} blockers abertos"><span>{{ key }}</span><b>{{ summary.open_blocker_breakdown[key] }}</b><span aria-hidden="true">→</span></a>
|
||
{% else %}<div class="bar-row muted"><span>{{ key }}</span><b>0</b></div>{% endif %}
|
||
{% endfor %}
|
||
</article>
|
||
</section>
|
||
<section class="panel attention">
|
||
<div class="panel-title"><div><p class="eyebrow">FILA DE AÇÃO</p><h2>Atenção necessária</h2></div><a href="/blockers?scope=active&status=OPEN">Ver blockers</a></div>
|
||
{% for item in summary.attention_items %}
|
||
<a class="list-row attention-row" href="{{ item.link }}"><span><b>{{ item.client or "Cliente não identificado" }}</b><small>{{ item.hostname or item.display_name or "Servidor sem nome" }} · {{ item.problem }}</small>{% if item.group_count %}<small>{{ item.products_affected }} produtos afetados · {{ item.group_count }} blockers abertos · Ver blockers →</small>{% endif %}</span><em class="badge {{ 'priority-' ~ item.priority|lower if item.priority else item.technical_health|lower }}">{{ item.age }}</em></a>
|
||
{% else %}<p class="empty-state">Nenhum item requer atenção imediata.</p>{% endfor %}
|
||
</section>
|
||
<section class="split jobs-overview">
|
||
<article class="panel">
|
||
<div class="panel-title"><h2>Jobs com falha</h2><a href="/jobs?scope=active&status=FAILED">Ver jobs</a></div>
|
||
{% if summary.failed_jobs %}<p class="metric-inline"><strong>{{ summary.failed_jobs }}</strong> jobs aguardam investigação.</p>
|
||
<ul class="failed-jobs" aria-label="Falhas recentes (máximo 10)">
|
||
{% for job in summary.recent_failed_jobs %}<li class="failed-job"><div class="failed-job-main"><b>{{ job.client or "Cliente não identificado" }}</b><small>{{ job.display_name or "Instalação" }} · {{ job.hostname or "Servidor não informado" }}</small><span class="failed-job-kind">{{ job.kind_label }}</span><small class="error-text job-error-summary" title="{{ job.error or '' }}">{{ job.error or "Sem detalhe de erro" }}</small></div><div class="failed-job-side"><span title="{{ job.finished_at }}">{{ job.finished_age }}</span><small>{{ job.finished_display }}</small><a href="/jobs?scope=active&status=FAILED&job_id={{ job.job_id }}">Ver detalhes</a></div></li>{% endfor %}
|
||
</ul>{% else %}<p class="empty-state">Nenhum job com falha.</p>{% endif %}
|
||
</article>
|
||
<article class="panel">
|
||
<div class="panel-title"><h2>Agents desatualizados</h2><a href="/agents?scope=active&version=outdated">Ver servidores</a></div>
|
||
{% for item in summary.outdated_agents[:5] %}<a class="list-row" href="/agents/{{ item.agent_id }}"><span><b>{{ item.client or "Cliente não identificado" }}</b><small>{{ item.display_name or "Instalação" }} · {{ item.hostname or "Servidor não informado" }}</small><small>{{ item.agent_version or "Versão não informada" }} → atualização disponível</small></span><em class="badge warning">Desatualizado</em></a>{% else %}<p class="empty-state">Nenhum Agent desatualizado.</p>{% endfor %}
|
||
</article>
|
||
</section>
|
||
{% endblock %}
|