@danhachuel/thunderbolt 0.5.54 → 0.5.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/main.py +19 -14
- package/package.json +1 -1
- package/scripts/streamlit_bootstrap.py +27 -2
package/app/main.py
CHANGED
|
@@ -1549,20 +1549,25 @@ def render_growth_youtube():
|
|
|
1549
1549
|
else:
|
|
1550
1550
|
st.button("BAIXAR ANALISE COMPLETA", disabled=True, use_container_width=True, key="growth_download_disabled")
|
|
1551
1551
|
st.subheader("Dashboard de Growth")
|
|
1552
|
-
st.caption("Vermelho: 0–30 · Amarelo: 31–69 · Verde: 70–100.
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1552
|
+
st.caption("Visão híbrida: os 3 pilares críticos em destaque e os 5 pilares operacionais abaixo. Vermelho: 0–30 · Amarelo: 31–69 · Verde: 70–100.")
|
|
1553
|
+
source_names = {"public": "PÚBLICO", "paligemma": "PALIGEMMA", "estimated": "DADO ESTIMADO", "unavailable": "INDISPONÍVEL", "youtube_analytics_oauth": "ANALYTICS OAUTH"}
|
|
1554
|
+
featured, secondary = metrics[:3], metrics[3:]
|
|
1555
|
+
|
|
1556
|
+
def growth_card(metric: dict[str, Any], featured_card: bool = False) -> str:
|
|
1557
|
+
score = int(metric.get("score", 0))
|
|
1558
|
+
source = source_names.get(str(metric.get("source", "unknown")), "A VERIFICAR")
|
|
1559
|
+
height = "min-height:238px" if featured_card else "min-height:190px"
|
|
1560
|
+
return (
|
|
1561
|
+
f'<div style="{height};border:1px solid #2b3b52;border-radius:14px;padding:18px;background:linear-gradient(145deg,#111c2b,#0b121c);box-shadow:0 8px 22px rgba(0,0,0,.16);">'
|
|
1562
|
+
f'<div style="font-size:11px;font-weight:800;letter-spacing:.08em;color:#7dd3fc;text-transform:uppercase;">{source}</div>'
|
|
1563
|
+
f'<div style="font-size:{"18px" if featured_card else "15px"};font-weight:750;color:#f8fafc;margin-top:9px;min-height:40px;">{metric.get("label", "Métrica")}</div>'
|
|
1564
|
+
f'<div style="display:flex;align-items:baseline;gap:5px;margin-top:7px;"><span style="font-size:{"42px" if featured_card else "32px"};font-weight:850;color:{_growth_score_color(score)};">{score}</span><span style="font-size:13px;color:#94a3b8;">/100</span></div>'
|
|
1565
|
+
f'<div style="border-top:1px solid #263447;margin-top:10px;padding-top:10px;font-size:13px;color:#e2e8f0;"><b>KPI observado</b><br>{metric.get("value", "—")}</div>'
|
|
1566
|
+
f'<div style="font-size:11px;color:#94a3b8;margin-top:9px;line-height:1.35;">{metric.get("diagnosis", "")[:150]}</div></div>'
|
|
1567
|
+
)
|
|
1568
|
+
|
|
1569
|
+
st.markdown('<div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;margin:14px 0 18px;">' + "".join(growth_card(metric, True) for metric in featured) + '</div>', unsafe_allow_html=True)
|
|
1570
|
+
st.markdown('<div style="display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;margin:0 0 18px;">' + "".join(growth_card(metric) for metric in secondary) + '</div>', unsafe_allow_html=True)
|
|
1566
1571
|
st.divider()
|
|
1567
1572
|
history_tab, dashboard_tab = st.tabs(["Últimas análises", "Leitura estratégica"])
|
|
1568
1573
|
with dashboard_tab:
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import io
|
|
5
|
+
import logging
|
|
4
6
|
import os
|
|
5
7
|
import signal
|
|
6
8
|
import sys
|
|
9
|
+
import time
|
|
7
10
|
|
|
8
11
|
# Este ficheiro é o primeiro módulo Python executado pelo launcher. As
|
|
9
12
|
# variáveis e os streams precisam de ser corrigidos antes de qualquer import
|
|
@@ -14,6 +17,12 @@ os.environ["PYTHONLEGACYWINDOWSSTDIO"] = "1"
|
|
|
14
17
|
os.environ["CLICK_NO_WIN_CONSOLE"] = "1"
|
|
15
18
|
|
|
16
19
|
|
|
20
|
+
# O shutdown do Streamlit pode emitir tracebacks assíncronos depois de SIGINT.
|
|
21
|
+
# Estes níveis só afectam a saída de encerramento, não os logs da aplicação.
|
|
22
|
+
logging.getLogger("uvicorn").setLevel(logging.CRITICAL)
|
|
23
|
+
logging.getLogger("streamlit").setLevel(logging.CRITICAL)
|
|
24
|
+
|
|
25
|
+
|
|
17
26
|
def _utf8_stream(stream: object) -> object:
|
|
18
27
|
if os.name != "nt":
|
|
19
28
|
return stream
|
|
@@ -30,10 +39,26 @@ sys.stdout = _utf8_stream(sys.stdout)
|
|
|
30
39
|
sys.stderr = _utf8_stream(sys.stderr)
|
|
31
40
|
|
|
32
41
|
|
|
42
|
+
_shutdown_started = False
|
|
43
|
+
|
|
44
|
+
|
|
33
45
|
def _custom_sigint_handler(signum: int, frame: object) -> None:
|
|
34
|
-
"""
|
|
46
|
+
"""Parar o loop, dar uma pequena janela de drenagem e sair sem traceback."""
|
|
47
|
+
global _shutdown_started
|
|
48
|
+
if _shutdown_started:
|
|
49
|
+
return
|
|
50
|
+
_shutdown_started = True
|
|
35
51
|
print("\nEncerrando servidor...", file=sys.stderr, flush=True)
|
|
36
|
-
|
|
52
|
+
try:
|
|
53
|
+
loop = asyncio.get_running_loop()
|
|
54
|
+
except RuntimeError:
|
|
55
|
+
loop = None
|
|
56
|
+
if loop is not None and not loop.is_closed():
|
|
57
|
+
loop.call_soon_threadsafe(loop.stop)
|
|
58
|
+
# O processo pai (cli.mjs) encerra proxy e workers quando o filho termina.
|
|
59
|
+
# os._exit evita que callbacks tardios do Streamlit imprimam tracebacks.
|
|
60
|
+
time.sleep(0.5)
|
|
61
|
+
os._exit(0)
|
|
37
62
|
|
|
38
63
|
|
|
39
64
|
_original_signal = signal.signal
|