Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"""
|
| 2 |
Dashboard GLiNER2 β AnΓ‘lise de Projetos/Tarefas
|
| 3 |
-
489 registos reais | Gradio
|
| 4 |
"""
|
| 5 |
|
| 6 |
import gradio as gr
|
|
@@ -9,598 +9,279 @@ import plotly.graph_objects as go
|
|
| 9 |
from plotly.subplots import make_subplots
|
| 10 |
import json
|
| 11 |
from collections import Counter
|
|
|
|
| 12 |
import warnings
|
|
|
|
| 13 |
warnings.filterwarnings("ignore")
|
| 14 |
|
| 15 |
-
# βββ
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
stats = json.load(f)
|
| 23 |
|
| 24 |
-
with open(
|
| 25 |
estruturados = json.load(f)
|
| 26 |
|
| 27 |
-
#
|
|
|
|
|
|
|
|
|
|
| 28 |
df["COLABORADOR"] = df["COLABORADOR"].fillna("").str.strip().str.title()
|
| 29 |
|
| 30 |
-
#
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
-
# Paleta de cores
|
| 38 |
CORES_FASE = {
|
| 39 |
"pendente_validacao": "#F18F01",
|
| 40 |
-
"concluido":
|
| 41 |
-
"faturado":
|
| 42 |
-
"em_progresso":
|
| 43 |
-
"cancelado":
|
| 44 |
}
|
| 45 |
|
| 46 |
FASE_LABELS = {
|
| 47 |
"pendente_validacao": "Pendente ValidaΓ§Γ£o",
|
| 48 |
-
"concluido":
|
| 49 |
-
"faturado":
|
| 50 |
-
"em_progresso":
|
| 51 |
-
"cancelado":
|
| 52 |
}
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
def top_counter(series, n=15):
|
| 55 |
all_vals = []
|
| 56 |
for v in series.dropna():
|
| 57 |
-
all_vals.extend([x.strip() for x in str(v).split(
|
| 58 |
return dict(Counter(all_vals).most_common(n))
|
| 59 |
|
| 60 |
-
# βββ
|
|
|
|
|
|
|
|
|
|
| 61 |
def fig_visao_geral():
|
|
|
|
| 62 |
fig = make_subplots(
|
| 63 |
-
rows=2,
|
|
|
|
| 64 |
subplot_titles=[
|
| 65 |
-
"Fases dos Projetos
|
| 66 |
-
"
|
| 67 |
"Projetos por Colaborador",
|
| 68 |
-
"AΓ§Γ΅es nas ObservaΓ§Γ΅es
|
| 69 |
-
"Tipos de Rede ExtraΓdos (NER)",
|
| 70 |
-
"Semanas de ReferΓͺncia (NER)",
|
| 71 |
-
],
|
| 72 |
-
specs=[
|
| 73 |
-
[{"type": "pie"}, {"type": "bar"}, {"type": "bar"}],
|
| 74 |
-
[{"type": "bar"}, {"type": "bar"}, {"type": "bar"}],
|
| 75 |
],
|
| 76 |
-
vertical_spacing=0.18,
|
| 77 |
-
horizontal_spacing=0.10,
|
| 78 |
)
|
| 79 |
|
| 80 |
-
# 1 β Fases (donut)
|
| 81 |
fases = stats["fases_classificadas"]
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
# 2 β Tipo de projeto
|
| 93 |
-
tipo_counts = df[col_tipo].value_counts().head(10)
|
| 94 |
-
fig.add_trace(go.Bar(
|
| 95 |
-
x=tipo_counts.values, y=tipo_counts.index,
|
| 96 |
-
orientation="h", marker_color="#2E86AB",
|
| 97 |
-
text=tipo_counts.values, textposition="outside",
|
| 98 |
-
showlegend=False,
|
| 99 |
-
), row=1, col=2)
|
| 100 |
-
|
| 101 |
-
# 3 β Colaborador
|
| 102 |
-
colab_counts = df[col_colab].replace("", pd.NA).dropna().value_counts().head(8)
|
| 103 |
-
fig.add_trace(go.Bar(
|
| 104 |
-
x=colab_counts.values, y=colab_counts.index,
|
| 105 |
-
orientation="h", marker_color="#A23B72",
|
| 106 |
-
text=colab_counts.values, textposition="outside",
|
| 107 |
-
showlegend=False,
|
| 108 |
-
), row=1, col=3)
|
| 109 |
-
|
| 110 |
-
# 4 β AΓ§Γ΅es
|
| 111 |
-
acoes = stats["acoes_observacoes"]
|
| 112 |
-
top_acoes = dict(list(acoes.items())[:8])
|
| 113 |
-
fig.add_trace(go.Bar(
|
| 114 |
-
x=list(top_acoes.values()), y=list(top_acoes.keys()),
|
| 115 |
-
orientation="h", marker_color="#F18F01",
|
| 116 |
-
text=list(top_acoes.values()), textposition="outside",
|
| 117 |
-
showlegend=False,
|
| 118 |
-
), row=2, col=1)
|
| 119 |
-
|
| 120 |
-
# 5 β Tipos de rede
|
| 121 |
-
rede = stats["tipos_rede_extraidos"]
|
| 122 |
-
top_rede = dict(list(rede.items())[:8])
|
| 123 |
-
fig.add_trace(go.Bar(
|
| 124 |
-
x=list(top_rede.keys()), y=list(top_rede.values()),
|
| 125 |
-
marker_color="#44BBA4",
|
| 126 |
-
text=list(top_rede.values()), textposition="outside",
|
| 127 |
-
showlegend=False,
|
| 128 |
-
), row=2, col=2)
|
| 129 |
-
|
| 130 |
-
# 6 β Semanas
|
| 131 |
-
semanas = stats["semanas_referencia"]
|
| 132 |
-
top_sem = dict(list(semanas.items())[:10])
|
| 133 |
-
fig.add_trace(go.Bar(
|
| 134 |
-
x=list(top_sem.keys()), y=list(top_sem.values()),
|
| 135 |
-
marker_color="#C73E1D",
|
| 136 |
-
text=list(top_sem.values()), textposition="outside",
|
| 137 |
-
showlegend=False,
|
| 138 |
-
), row=2, col=3)
|
| 139 |
-
|
| 140 |
-
fig.update_layout(
|
| 141 |
-
height=700,
|
| 142 |
-
title_text=f"<b>Dashboard GLiNER2 β {stats['total_registos']} Projetos Analisados</b>",
|
| 143 |
-
title_font_size=16,
|
| 144 |
-
paper_bgcolor="#f8f9fa",
|
| 145 |
-
plot_bgcolor="#ffffff",
|
| 146 |
-
margin=dict(t=80, b=30, l=10, r=10),
|
| 147 |
)
|
| 148 |
-
fig.update_xaxes(showgrid=False)
|
| 149 |
-
fig.update_yaxes(showgrid=False)
|
| 150 |
-
return fig
|
| 151 |
|
|
|
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
|
|
|
|
|
|
| 160 |
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
)
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
fig.add_trace(
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
marker_color="#555555", text=col_c.values, textposition="outside", showlegend=False,
|
| 178 |
-
), row=1, col=2)
|
| 179 |
-
|
| 180 |
-
fig.update_layout(
|
| 181 |
-
height=420,
|
| 182 |
-
title_text=f"<b>Fase: {fase_sel}</b> β {len(sub)} projetos",
|
| 183 |
-
title_font_size=14,
|
| 184 |
-
paper_bgcolor="#f8f9fa", plot_bgcolor="#ffffff",
|
| 185 |
-
margin=dict(t=70, b=20, l=10, r=10),
|
| 186 |
)
|
| 187 |
-
fig.update_xaxes(showgrid=False)
|
| 188 |
-
fig.update_yaxes(showgrid=False)
|
| 189 |
-
return fig
|
| 190 |
|
|
|
|
| 191 |
|
| 192 |
-
|
| 193 |
-
if fase_sel == "Todas":
|
| 194 |
-
sub = df[df["FASE_CLASSIFICADA"].notna()]
|
| 195 |
-
else:
|
| 196 |
-
chave = {v: k for k, v in FASE_LABELS.items()}.get(fase_sel, fase_sel.lower().replace(" ", "_"))
|
| 197 |
-
sub = df[df["FASE_CLASSIFICADA"] == chave]
|
| 198 |
|
| 199 |
-
cols = ["SUB-CIP", "PROJETO", col_tipo, col_status, col_colab,
|
| 200 |
-
"FASE_CLASSIFICADA", "NER_OBS_acao", "NER_OBS_semana"]
|
| 201 |
-
return sub[cols].head(100).fillna("-").rename(columns={
|
| 202 |
-
col_tipo: "TIPO", col_status: "STATUS", col_colab: "COLABORADOR",
|
| 203 |
-
"FASE_CLASSIFICADA": "FASE (GLiNER2)",
|
| 204 |
-
"NER_OBS_acao": "AΓΓO (NER)", "NER_OBS_semana": "SEMANA (NER)"
|
| 205 |
-
})
|
| 206 |
|
|
|
|
|
|
|
|
|
|
| 207 |
|
| 208 |
-
# βββ ABA 3: EXPLORADOR NER βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 209 |
COL_NER_MAP = {
|
| 210 |
-
"AΓ§Γ£o (ObservaΓ§Γ΅es)":
|
| 211 |
-
"Semana (ObservaΓ§Γ΅es)":
|
| 212 |
-
"
|
| 213 |
-
"Entidade Externa": "NER_OBS_entidade_externa",
|
| 214 |
-
"Tipo de Rede (DesignaΓ§Γ£o)": "NER_DESIGN_tipo_rede",
|
| 215 |
-
"Tipo de Trabalho": "NER_DESIGN_tipo_trabalho",
|
| 216 |
-
"Cliente/Entidade": "NER_DESIGN_cliente_entidade",
|
| 217 |
-
"CΓ³digo do Projeto": "NER_DESIGN_codigo_projeto",
|
| 218 |
}
|
| 219 |
|
|
|
|
| 220 |
def explorar_ner(coluna_ner, top_n):
|
| 221 |
-
|
|
|
|
|
|
|
| 222 |
all_vals = []
|
|
|
|
| 223 |
for v in df[col].dropna():
|
| 224 |
all_vals.extend([x.strip() for x in str(v).split(";") if x.strip()])
|
|
|
|
| 225 |
counter = Counter(all_vals)
|
| 226 |
top = dict(counter.most_common(int(top_n)))
|
| 227 |
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
x=list(top.values()), y=list(top.keys()),
|
| 235 |
-
orientation="h",
|
| 236 |
-
marker=dict(color=list(top.values()), colorscale="Blues", showscale=False),
|
| 237 |
-
text=list(top.values()), textposition="outside",
|
| 238 |
-
))
|
| 239 |
-
fig.update_layout(
|
| 240 |
-
height=max(350, int(top_n) * 30),
|
| 241 |
-
title_text=f"<b>Top {top_n} β {coluna_ner}</b>",
|
| 242 |
-
title_font_size=14,
|
| 243 |
-
paper_bgcolor="#f8f9fa", plot_bgcolor="#ffffff",
|
| 244 |
-
margin=dict(t=60, b=20, l=200, r=60),
|
| 245 |
-
yaxis=dict(autorange="reversed"),
|
| 246 |
)
|
| 247 |
-
fig.update_xaxes(showgrid=False)
|
| 248 |
-
fig.update_yaxes(showgrid=False)
|
| 249 |
|
| 250 |
-
|
| 251 |
-
|
|
|
|
|
|
|
| 252 |
return fig, sub
|
| 253 |
|
| 254 |
|
| 255 |
-
# βββ
|
|
|
|
|
|
|
|
|
|
| 256 |
def tabela_json():
|
|
|
|
| 257 |
rows = []
|
|
|
|
| 258 |
for item in estruturados:
|
|
|
|
| 259 |
sub_cip = item.get("sub_cip", "")
|
|
|
|
| 260 |
tarefas = item.get("extracao_gliner2", {}).get("tarefa", [])
|
| 261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
rows.append({
|
| 263 |
-
"SUB-CIP":
|
| 264 |
-
"CΓ³digo
|
| 265 |
-
"
|
| 266 |
-
"Colaborador":
|
| 267 |
-
"Status":
|
| 268 |
-
"AΓ§Γ£o/ObservaΓ§Γ£o": t.get("acao_observacao") or "-",
|
| 269 |
-
"Semana Ref.": t.get("semana_referencia") or "-",
|
| 270 |
})
|
|
|
|
| 271 |
return pd.DataFrame(rows)
|
| 272 |
|
| 273 |
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
tarefas = item.get("extracao_gliner2", {}).get("tarefa", [])
|
| 278 |
-
if tarefas:
|
| 279 |
-
rows.append(tarefas[0])
|
| 280 |
-
df_j = pd.DataFrame(rows)
|
| 281 |
-
|
| 282 |
-
fig = make_subplots(rows=1, cols=2,
|
| 283 |
-
subplot_titles=["Colaboradores (JSON)", "Status (JSON)"])
|
| 284 |
-
|
| 285 |
-
if "colaborador" in df_j and df_j["colaborador"].notna().any():
|
| 286 |
-
co = df_j["colaborador"].dropna().str.title().value_counts().head(8)
|
| 287 |
-
fig.add_trace(go.Bar(x=co.index, y=co.values,
|
| 288 |
-
marker_color="#A23B72", text=co.values, textposition="outside",
|
| 289 |
-
showlegend=False), row=1, col=1)
|
| 290 |
-
|
| 291 |
-
if "status" in df_j and df_j["status"].notna().any():
|
| 292 |
-
st = df_j["status"].dropna().value_counts().head(8)
|
| 293 |
-
fig.add_trace(go.Bar(x=st.index, y=st.values,
|
| 294 |
-
marker_color="#2E86AB", text=st.values, textposition="outside",
|
| 295 |
-
showlegend=False), row=1, col=2)
|
| 296 |
-
|
| 297 |
-
fig.update_layout(height=350, paper_bgcolor="#f8f9fa",
|
| 298 |
-
plot_bgcolor="#ffffff", margin=dict(t=50, b=20))
|
| 299 |
-
fig.update_xaxes(showgrid=False, tickangle=30)
|
| 300 |
-
fig.update_yaxes(showgrid=False)
|
| 301 |
-
return fig
|
| 302 |
|
|
|
|
| 303 |
|
| 304 |
-
#
|
| 305 |
-
def fig_mapeamento():
|
| 306 |
-
status_counts = stats["status_original"]
|
| 307 |
-
mapa = stats["mapeamento_status_fase"]
|
| 308 |
-
|
| 309 |
-
status_list = list(status_counts.keys())
|
| 310 |
-
count_list = [status_counts[s] for s in status_list]
|
| 311 |
-
fase_list = [mapa.get(s, "N/A") for s in status_list]
|
| 312 |
-
cores = [CORES_FASE.get(f, "#888888") for f in fase_list]
|
| 313 |
-
labels_fase = [FASE_LABELS.get(f, f) for f in fase_list]
|
| 314 |
-
|
| 315 |
-
fig = go.Figure()
|
| 316 |
-
fig.add_trace(go.Bar(
|
| 317 |
-
x=count_list,
|
| 318 |
-
y=status_list,
|
| 319 |
-
orientation="h",
|
| 320 |
-
marker_color=cores,
|
| 321 |
-
text=[f"{lf} ({n})" for lf, n in zip(labels_fase, count_list)],
|
| 322 |
-
textposition="inside",
|
| 323 |
-
insidetextanchor="middle",
|
| 324 |
-
textfont=dict(color="white", size=10),
|
| 325 |
-
showlegend=False,
|
| 326 |
-
hovertemplate="<b>%{y}</b><br>Fase: %{text}<extra></extra>",
|
| 327 |
-
))
|
| 328 |
-
|
| 329 |
-
for k, v in CORES_FASE.items():
|
| 330 |
-
fig.add_trace(go.Bar(
|
| 331 |
-
name=FASE_LABELS.get(k, k), x=[None], y=[None],
|
| 332 |
-
marker_color=v, showlegend=True
|
| 333 |
-
))
|
| 334 |
-
|
| 335 |
-
fig.update_layout(
|
| 336 |
-
height=560,
|
| 337 |
-
title_text="<b>Status Original β Fase (GLiNER2) β 18 status, 489 projetos</b>",
|
| 338 |
-
title_font_size=14,
|
| 339 |
-
paper_bgcolor="#f8f9fa", plot_bgcolor="#ffffff",
|
| 340 |
-
xaxis=dict(title="NΓΊmero de Projetos"),
|
| 341 |
-
yaxis=dict(autorange="reversed"),
|
| 342 |
-
margin=dict(t=60, b=40, l=260, r=20),
|
| 343 |
-
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
| 344 |
-
)
|
| 345 |
-
return fig
|
| 346 |
|
|
|
|
| 347 |
|
| 348 |
-
|
| 349 |
-
def fig_temporal():
|
| 350 |
-
# DistribuiΓ§Γ£o de aΓ§Γ΅es por semana
|
| 351 |
-
df_sem = df[df["NER_OBS_semana"].notna() & df["NER_OBS_acao"].notna()].copy()
|
| 352 |
-
|
| 353 |
-
fig = make_subplots(rows=1, cols=2,
|
| 354 |
-
subplot_titles=["AΓ§Γ΅es por Semana de ReferΓͺncia", "Entidades Externas Identificadas"])
|
| 355 |
-
|
| 356 |
-
if not df_sem.empty:
|
| 357 |
-
sem_acao = df_sem.groupby(["NER_OBS_semana", "NER_OBS_acao"]).size().reset_index(name="n")
|
| 358 |
-
top_semanas = df_sem["NER_OBS_semana"].value_counts().head(8).index.tolist()
|
| 359 |
-
sem_acao = sem_acao[sem_acao["NER_OBS_semana"].isin(top_semanas)]
|
| 360 |
-
for acao in sem_acao["NER_OBS_acao"].unique()[:5]:
|
| 361 |
-
sub_a = sem_acao[sem_acao["NER_OBS_acao"] == acao]
|
| 362 |
-
fig.add_trace(go.Bar(
|
| 363 |
-
x=sub_a["NER_OBS_semana"], y=sub_a["n"],
|
| 364 |
-
name=acao, showlegend=True,
|
| 365 |
-
), row=1, col=1)
|
| 366 |
-
|
| 367 |
-
entidades = stats.get("entidades_externas", {})
|
| 368 |
-
top_ent = dict(list(entidades.items())[:10])
|
| 369 |
-
if top_ent:
|
| 370 |
-
fig.add_trace(go.Bar(
|
| 371 |
-
x=list(top_ent.values()), y=list(top_ent.keys()),
|
| 372 |
-
orientation="h", marker_color="#44BBA4",
|
| 373 |
-
text=list(top_ent.values()), textposition="outside",
|
| 374 |
-
showlegend=False,
|
| 375 |
-
), row=1, col=2)
|
| 376 |
-
|
| 377 |
-
fig.update_layout(
|
| 378 |
-
height=420, barmode="stack",
|
| 379 |
-
title_text="<b>AnΓ‘lise Temporal e Entidades Externas (NER)</b>",
|
| 380 |
-
title_font_size=14,
|
| 381 |
-
paper_bgcolor="#f8f9fa", plot_bgcolor="#ffffff",
|
| 382 |
-
margin=dict(t=70, b=30, l=10, r=10),
|
| 383 |
-
)
|
| 384 |
-
fig.update_xaxes(showgrid=False)
|
| 385 |
-
fig.update_yaxes(showgrid=False)
|
| 386 |
-
return fig
|
| 387 |
|
|
|
|
| 388 |
|
| 389 |
-
|
| 390 |
-
EXPLICACAO_HTML = """
|
| 391 |
-
<div style="font-family: 'Segoe UI', Arial, sans-serif; max-width: 920px; margin: 0 auto; color: #1a1a2e;">
|
| 392 |
-
|
| 393 |
-
<div style="background: linear-gradient(135deg, #1a5276, #2E86AB); border-radius: 12px; padding: 28px 32px; margin-bottom: 28px;">
|
| 394 |
-
<h1 style="color: white; margin: 0 0 8px 0; font-size: 1.8em;">π€ GLiNER2</h1>
|
| 395 |
-
<p style="color: #d6eaf8; margin: 0; font-size: 1.05em;">
|
| 396 |
-
<b>Generalist and Lightweight Named Entity Recognition 2</b><br>
|
| 397 |
-
Um modelo de IA compacto que extrai informaΓ§Γ£o estruturada de texto livre β sem GPU, sem API, sem custos.
|
| 398 |
-
</p>
|
| 399 |
-
</div>
|
| 400 |
-
|
| 401 |
-
<h2 style="color: #1a5276; border-bottom: 3px solid #2E86AB; padding-bottom: 6px;">π O Problema que o GLiNER2 Resolve</h2>
|
| 402 |
-
<p>
|
| 403 |
-
O ficheiro de projetos contΓ©m <b>489 registos reais</b> com campos de texto livre como
|
| 404 |
-
<em>DesignaΓ§Γ£o do Projeto</em> e <em>ObservaΓ§Γ΅es</em>. Estes campos guardam informaΓ§Γ£o crΓtica β
|
| 405 |
-
tipo de rede, aΓ§Γ΅es pendentes, entidades externas, semanas de referΓͺncia β mas numa forma
|
| 406 |
-
impossΓvel de analisar diretamente com ferramentas tradicionais.
|
| 407 |
-
</p>
|
| 408 |
-
<div style="background: #fff3cd; border-left: 5px solid #ffc107; padding: 14px 18px; border-radius: 6px; margin: 16px 0;">
|
| 409 |
-
<b>Exemplo real do ficheiro:</b><br>
|
| 410 |
-
<code>"ATT VALID 17/02 PUSH MAIL ATT INFO MAIRIE 06/02 //"</code><br>
|
| 411 |
-
<span style="color:#856404;">β Sem GLiNER2: apenas texto. Com GLiNER2: <b>AΓ§Γ£o=ATT VALID</b>, <b>Data=17/02</b>, <b>Entidade=MAIRIE</b></span>
|
| 412 |
-
</div>
|
| 413 |
-
<div style="background: #d1ecf1; border-left: 5px solid #17a2b8; padding: 14px 18px; border-radius: 6px; margin: 16px 0;">
|
| 414 |
-
<b>Nota tΓ©cnica β Separador do ficheiro:</b><br>
|
| 415 |
-
O ficheiro CSV usa <b>ponto-e-vΓrgula (;)</b> como separador e codificaΓ§Γ£o <b>latin-1</b>.
|
| 416 |
-
Alguns campos (ex: "DATA LIMITE SLA") contΓͺm quebras de linha internas que faziam o pandas
|
| 417 |
-
interpretar 1199 pseudo-linhas em vez das <b>489 reais</b>. O dashboard usa a leitura correta
|
| 418 |
-
com filtro por padrΓ£o SUB-CIP (RBPT.YYYY.NNN.NNNN).
|
| 419 |
-
</div>
|
| 420 |
-
|
| 421 |
-
<h2 style="color: #1a5276; border-bottom: 3px solid #2E86AB; padding-bottom: 6px; margin-top: 28px;">βοΈ Como Funciona: Os 3 Modos Aplicados</h2>
|
| 422 |
-
|
| 423 |
-
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; margin: 16px 0;">
|
| 424 |
-
<div style="background: #d1ecf1; border-radius: 10px; padding: 16px;">
|
| 425 |
-
<h3 style="color: #0c5460; margin-top: 0;">π Modo 1<br>NER nas DesignaΓ§Γ΅es</h3>
|
| 426 |
-
<p style="font-size: 0.92em; color: #333;">
|
| 427 |
-
Extrai o <b>tipo de rede</b> (BTHD, RAMI, ART 2 3...), o <b>tipo de trabalho</b> (TVX, GC, ETU...),
|
| 428 |
-
o <b>cliente</b> (ENEDIS, ORANGE) e o <b>cΓ³digo do projeto</b> (VAN500664...) de textos como
|
| 429 |
-
<em>"L, AO, 7C - 576 BTHD TVX GC DROIT AU TERRAIN"</em>.
|
| 430 |
-
</p>
|
| 431 |
-
<p style="font-size: 0.88em; color: #0c5460;"><b>Resultado:</b> BTHD domina com 73 projetos; ART 2 3 com 69.</p>
|
| 432 |
-
</div>
|
| 433 |
-
<div style="background: #d1ecf1; border-radius: 10px; padding: 16px;">
|
| 434 |
-
<h3 style="color: #0c5460; margin-top: 0;">π Modo 2<br>NER nas ObservaΓ§Γ΅es</h3>
|
| 435 |
-
<p style="font-size: 0.92em; color: #333;">
|
| 436 |
-
Extrai <b>aΓ§Γ΅es</b> (VALIDADO, ATT VALID, RELANCE), <b>semanas</b> (S07, S39),
|
| 437 |
-
<b>datas</b> (17/02) e <b>entidades externas</b> (MAIRIE, PMV, Sogetrel)
|
| 438 |
-
de notas humanas confusas e abreviadas.
|
| 439 |
-
</p>
|
| 440 |
-
<p style="font-size: 0.88em; color: #0c5460;"><b>Resultado:</b> "VALIDADO" lidera (163x); "ATT VALID" em 90 registos.</p>
|
| 441 |
-
</div>
|
| 442 |
-
<div style="background: #d1ecf1; border-radius: 10px; padding: 16px;">
|
| 443 |
-
<h3 style="color: #0c5460; margin-top: 0;">π·οΈ Modo 3<br>ClassificaΓ§Γ£o de Status</h3>
|
| 444 |
-
<p style="font-size: 0.92em; color: #333;">
|
| 445 |
-
Agrupa os <b>18 status distintos</b> em 5 fases de ciclo de vida
|
| 446 |
-
sem qualquer regra manual β o modelo lΓͺ o texto e decide semanticamente.
|
| 447 |
-
</p>
|
| 448 |
-
<p style="font-size: 0.88em; color: #0c5460;"><b>Resultado:</b> 43% pendentes de validaΓ§Γ£o; 29% concluΓdos.</p>
|
| 449 |
-
</div>
|
| 450 |
-
</div>
|
| 451 |
-
|
| 452 |
-
<h2 style="color: #1a5276; border-bottom: 3px solid #2E86AB; padding-bottom: 6px; margin-top: 28px;">π Resultados Obtidos</h2>
|
| 453 |
-
<table style="width:100%; border-collapse: collapse; font-size: 0.95em;">
|
| 454 |
-
<thead>
|
| 455 |
-
<tr style="background: #2E86AB; color: white;">
|
| 456 |
-
<th style="padding: 10px; text-align: left;">MΓ©trica</th>
|
| 457 |
-
<th style="padding: 10px; text-align: center;">Valor</th>
|
| 458 |
-
<th style="padding: 10px; text-align: left;">Significado</th>
|
| 459 |
-
</tr>
|
| 460 |
-
</thead>
|
| 461 |
-
<tbody>
|
| 462 |
-
<tr style="background: #f0f7ff;"><td style="padding: 9px 10px;">Total de registos reais</td><td style="padding: 9px 10px; text-align: center; font-weight: bold; color: #2E86AB;">489</td><td style="padding: 9px 10px;">Filtrado por padrΓ£o SUB-CIP vΓ‘lido</td></tr>
|
| 463 |
-
<tr><td style="padding: 9px 10px;">DesignaΓ§Γ΅es ΓΊnicas processadas</td><td style="padding: 9px 10px; text-align: center; font-weight: bold; color: #2E86AB;">62</td><td style="padding: 9px 10px;">Tipos de projeto distintos</td></tr>
|
| 464 |
-
<tr style="background: #f0f7ff;"><td style="padding: 9px 10px;">ObservaΓ§Γ΅es ΓΊnicas processadas</td><td style="padding: 9px 10px; text-align: center; font-weight: bold; color: #2E86AB;">232</td><td style="padding: 9px 10px;">Notas ΓΊnicas analisadas pelo NER</td></tr>
|
| 465 |
-
<tr><td style="padding: 9px 10px;">Registos com aΓ§Γ£o identificada</td><td style="padding: 9px 10px; text-align: center; font-weight: bold; color: #F18F01;">163+ (VALIDADO)</td><td style="padding: 9px 10px;">AΓ§Γ΅es operacionais extraΓdas</td></tr>
|
| 466 |
-
<tr style="background: #f0f7ff;"><td style="padding: 9px 10px;">Projetos com fase classificada</td><td style="padding: 9px 10px; text-align: center; font-weight: bold; color: #44BBA4;">489 (100%)</td><td style="padding: 9px 10px;">Todos os status mapeados em fases</td></tr>
|
| 467 |
-
<tr><td style="padding: 9px 10px;">Novas colunas adicionadas ao CSV</td><td style="padding: 9px 10px; text-align: center; font-weight: bold; color: #A23B72;">12</td><td style="padding: 9px 10px;">Dados estruturados prontos para BI</td></tr>
|
| 468 |
-
</tbody>
|
| 469 |
-
</table>
|
| 470 |
-
|
| 471 |
-
<h2 style="color: #1a5276; border-bottom: 3px solid #2E86AB; padding-bottom: 6px; margin-top: 28px;">β‘ Por que GLiNER2 e nΓ£o outra abordagem?</h2>
|
| 472 |
-
<table style="width:100%; border-collapse: collapse; font-size: 0.93em;">
|
| 473 |
-
<thead>
|
| 474 |
-
<tr style="background: #2E86AB; color: white;">
|
| 475 |
-
<th style="padding: 10px;">Abordagem</th>
|
| 476 |
-
<th style="padding: 10px;">LimitaΓ§Γ£o</th>
|
| 477 |
-
<th style="padding: 10px;">GLiNER2</th>
|
| 478 |
-
</tr>
|
| 479 |
-
</thead>
|
| 480 |
-
<tbody>
|
| 481 |
-
<tr style="background: #f8d7da;"><td style="padding: 9px 10px; font-weight: bold;">ExpressΓ΅es Regulares</td><td style="padding: 9px 10px;">Quebram com qualquer variaΓ§Γ£o no texto</td><td style="padding: 9px 10px; color: #155724;">β
Entende semΓ’ntica, nΓ£o padrΓ΅es rΓgidos</td></tr>
|
| 482 |
-
<tr style="background: #fff3cd;"><td style="padding: 9px 10px; font-weight: bold;">ChatGPT / Claude (API)</td><td style="padding: 9px 10px;">Custo elevado + dados enviados para a nuvem</td><td style="padding: 9px 10px; color: #155724;">β
100% local, gratuito, privado</td></tr>
|
| 483 |
-
<tr style="background: #f8d7da;"><td style="padding: 9px 10px; font-weight: bold;">SpaCy / NER clΓ‘ssico</td><td style="padding: 9px 10px;">SΓ³ reconhece Pessoa/Local/Org genΓ©ricos</td><td style="padding: 9px 10px; color: #155724;">β
Schema personalizado em linguagem natural</td></tr>
|
| 484 |
-
<tr style="background: #fff3cd;"><td style="padding: 9px 10px; font-weight: bold;">LLMs locais (Llama 7B+)</td><td style="padding: 9px 10px;">Requer GPU potente; lento em CPU</td><td style="padding: 9px 10px; color: #155724;">β
205M parΓ’metros, rΓ‘pido em CPU</td></tr>
|
| 485 |
-
</tbody>
|
| 486 |
-
</table>
|
| 487 |
-
|
| 488 |
-
<div style="background: linear-gradient(135deg, #d4edda, #c3e6cb); border-radius: 10px; padding: 20px 24px; margin-top: 28px;">
|
| 489 |
-
<h3 style="color: #155724; margin-top: 0;">π― ConclusΓ£o</h3>
|
| 490 |
-
<p style="color: #155724; margin: 0; font-size: 1em;">
|
| 491 |
-
O GLiNER2 transformou o ficheiro CSV de <b>489 projetos</b> de um depΓ³sito de texto livre
|
| 492 |
-
numa <b>base de dados estruturada e analisΓ‘vel</b>. As 12 novas colunas geradas estΓ£o prontas
|
| 493 |
-
para alimentar qualquer ferramenta de Business Intelligence (Power BI, Tableau, Excel)
|
| 494 |
-
β tudo processado localmente, sem qualquer custo de API.
|
| 495 |
-
</p>
|
| 496 |
-
</div>
|
| 497 |
-
|
| 498 |
-
<div style="margin-top: 20px; padding: 12px 16px; background: #e8f4fd; border-radius: 8px; font-size: 0.88em; color: #1a5276;">
|
| 499 |
-
<b>ReferΓͺncias:</b>
|
| 500 |
-
Zaratiana et al. (2025) β <em>GLiNER2: An Efficient Multi-Task Information Extraction System</em>.
|
| 501 |
-
arXiv:2507.18546 Β·
|
| 502 |
-
<a href="https://github.com/fastino-ai/GLiNER2" target="_blank" style="color:#2E86AB;">github.com/fastino-ai/GLiNER2</a>
|
| 503 |
-
</div>
|
| 504 |
-
</div>
|
| 505 |
-
"""
|
| 506 |
|
| 507 |
-
|
| 508 |
-
THEME = gr.themes.Soft(
|
| 509 |
-
primary_hue="blue",
|
| 510 |
-
secondary_hue="orange",
|
| 511 |
-
font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "sans-serif"],
|
| 512 |
-
)
|
| 513 |
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
.tab-nav button { font-size: 14px !important; font-weight: 600 !important; }
|
| 520 |
-
footer { display: none !important; }
|
| 521 |
-
""",
|
| 522 |
-
) as demo:
|
| 523 |
-
|
| 524 |
-
gr.HTML("""
|
| 525 |
-
<div style="background: linear-gradient(135deg, #1a5276 0%, #2E86AB 100%);
|
| 526 |
-
padding: 22px 32px; border-radius: 12px; margin-bottom: 8px;">
|
| 527 |
-
<h1 style="color: white; margin: 0; font-size: 1.9em; font-family: Inter, sans-serif;">
|
| 528 |
-
π¬ GLiNER2 Dashboard
|
| 529 |
-
</h1>
|
| 530 |
-
<p style="color: #d6eaf8; margin: 6px 0 0 0; font-size: 1.05em;">
|
| 531 |
-
AnΓ‘lise de <b>489 Projetos/Tarefas</b> com ExtraΓ§Γ£o de InformaΓ§Γ£o por InteligΓͺncia Artificial
|
| 532 |
-
| Separador: <code>;</code> | Encoding: <code>latin-1</code>
|
| 533 |
-
</p>
|
| 534 |
-
</div>
|
| 535 |
-
""")
|
| 536 |
|
| 537 |
-
|
| 538 |
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
demo.load(fn=fig_visao_geral, outputs=plot_geral)
|
| 543 |
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
value="Todas", label="Filtrar por Fase",
|
| 549 |
)
|
| 550 |
-
plot_fase = gr.Plot(show_label=False)
|
| 551 |
-
tbl_fase = gr.Dataframe(label="Registos da Fase", wrap=True, interactive=False)
|
| 552 |
-
fase_dd.change(fn=fig_fases, inputs=fase_dd, outputs=plot_fase)
|
| 553 |
-
fase_dd.change(fn=tabela_fases, inputs=fase_dd, outputs=tbl_fase)
|
| 554 |
-
demo.load(fn=fig_fases, inputs=fase_dd, outputs=plot_fase)
|
| 555 |
-
demo.load(fn=tabela_fases, inputs=fase_dd, outputs=tbl_fase)
|
| 556 |
|
| 557 |
-
with gr.Tab("
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
tbl_ner = gr.Dataframe(label="Registos com esta entidade", wrap=True, interactive=False)
|
| 567 |
-
ner_dd.change(fn=explorar_ner, inputs=[ner_dd, top_n], outputs=[plot_ner, tbl_ner])
|
| 568 |
-
top_n.change(fn=explorar_ner, inputs=[ner_dd, top_n], outputs=[plot_ner, tbl_ner])
|
| 569 |
-
demo.load(fn=explorar_ner, inputs=[ner_dd, top_n], outputs=[plot_ner, tbl_ner])
|
| 570 |
-
|
| 571 |
-
with gr.Tab("π ExtraΓ§Γ£o JSON"):
|
| 572 |
-
gr.Markdown("""
|
| 573 |
-
### ExtraΓ§Γ£o Estruturada (JSON) pelo GLiNER2
|
| 574 |
-
O modelo leu o texto completo de cada projeto e extraiu automaticamente um objeto JSON
|
| 575 |
-
com **cΓ³digo do projeto**, **tipo de rede**, **colaborador**, **status**, **aΓ§Γ£o** e **semana**.
|
| 576 |
-
""")
|
| 577 |
-
plot_json = gr.Plot(show_label=False)
|
| 578 |
-
tbl_json = gr.Dataframe(label="ExtraΓ§Γ£o Estruturada (30 amostras)", wrap=True, interactive=False)
|
| 579 |
-
demo.load(fn=fig_json_resumo, outputs=plot_json)
|
| 580 |
-
demo.load(fn=tabela_json, outputs=tbl_json)
|
| 581 |
-
|
| 582 |
-
with gr.Tab("πΊοΈ Status β Fase"):
|
| 583 |
-
gr.Markdown("""
|
| 584 |
-
### Mapeamento Inteligente: 18 Status β 5 Fases
|
| 585 |
-
O GLiNER2 classificou semanticamente todos os status do ficheiro em fases de ciclo de vida.
|
| 586 |
-
""")
|
| 587 |
-
plot_mapa = gr.Plot(show_label=False)
|
| 588 |
-
demo.load(fn=fig_mapeamento, outputs=plot_mapa)
|
| 589 |
-
|
| 590 |
-
with gr.Tab("π
AnΓ‘lise Temporal"):
|
| 591 |
-
gr.Markdown("### AΓ§Γ΅es por semana e entidades externas identificadas pelo NER")
|
| 592 |
-
plot_temp = gr.Plot(show_label=False)
|
| 593 |
-
demo.load(fn=fig_temporal, outputs=plot_temp)
|
| 594 |
-
|
| 595 |
-
with gr.Tab("π€ O que Γ© o GLiNER2?"):
|
| 596 |
-
gr.HTML(EXPLICACAO_HTML)
|
| 597 |
-
|
| 598 |
-
gr.HTML("""
|
| 599 |
-
<div style="text-align: center; padding: 10px; color: #888; font-size: 0.85em; margin-top: 6px;">
|
| 600 |
-
GLiNER2 Dashboard Β· <b>489 projetos reais</b> Β· Modelo: urchade/gliner_medium-v2.1 Β·
|
| 601 |
-
Separador: <code>;</code> Β· Encoding: <code>latin-1</code> Β· Processado 100% localmente
|
| 602 |
-
</div>
|
| 603 |
-
""")
|
| 604 |
|
| 605 |
if __name__ == "__main__":
|
| 606 |
-
demo.launch(
|
|
|
|
| 1 |
"""
|
| 2 |
Dashboard GLiNER2 β AnΓ‘lise de Projetos/Tarefas
|
| 3 |
+
489 registos reais | Gradio | MΓΊltiplas abas interativas
|
| 4 |
"""
|
| 5 |
|
| 6 |
import gradio as gr
|
|
|
|
| 9 |
from plotly.subplots import make_subplots
|
| 10 |
import json
|
| 11 |
from collections import Counter
|
| 12 |
+
from pathlib import Path
|
| 13 |
import warnings
|
| 14 |
+
|
| 15 |
warnings.filterwarnings("ignore")
|
| 16 |
|
| 17 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 18 |
+
# CAMINHOS PORTΓTEIS
|
| 19 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 20 |
+
|
| 21 |
+
BASE = Path(__file__).parent
|
| 22 |
+
|
| 23 |
+
CSV_FILE = BASE / "gliner2_resultado_489.csv"
|
| 24 |
+
STAT_FILE = BASE / "gliner2_estatisticas_489.json"
|
| 25 |
+
JSON_FILE = BASE / "gliner2_extracao_489.json"
|
| 26 |
+
|
| 27 |
+
if not CSV_FILE.exists():
|
| 28 |
+
raise FileNotFoundError(f"CSV nΓ£o encontrado: {CSV_FILE}")
|
| 29 |
+
|
| 30 |
+
if not STAT_FILE.exists():
|
| 31 |
+
raise FileNotFoundError(f"JSON estatΓsticas nΓ£o encontrado: {STAT_FILE}")
|
| 32 |
|
| 33 |
+
if not JSON_FILE.exists():
|
| 34 |
+
raise FileNotFoundError(f"JSON extraΓ§Γ£o nΓ£o encontrado: {JSON_FILE}")
|
| 35 |
|
| 36 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 37 |
+
# CARREGAR DADOS
|
| 38 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 39 |
+
|
| 40 |
+
df = pd.read_csv(
|
| 41 |
+
CSV_FILE,
|
| 42 |
+
sep=";",
|
| 43 |
+
encoding="latin-1",
|
| 44 |
+
on_bad_lines="skip"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
with open(STAT_FILE, encoding="utf-8") as f:
|
| 48 |
stats = json.load(f)
|
| 49 |
|
| 50 |
+
with open(JSON_FILE, encoding="utf-8") as f:
|
| 51 |
estruturados = json.load(f)
|
| 52 |
|
| 53 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 54 |
+
# NORMALIZAΓΓO
|
| 55 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 56 |
+
|
| 57 |
df["COLABORADOR"] = df["COLABORADOR"].fillna("").str.strip().str.title()
|
| 58 |
|
| 59 |
+
# procurar colunas automaticamente
|
| 60 |
+
def find_col(keyword):
|
| 61 |
+
for c in df.columns:
|
| 62 |
+
if keyword.upper() in c.upper():
|
| 63 |
+
return c
|
| 64 |
+
raise ValueError(f"Coluna contendo '{keyword}' nΓ£o encontrada")
|
| 65 |
+
|
| 66 |
+
col_designacao = find_col("DESIGNA")
|
| 67 |
+
col_obs = find_col("OBSERVA")
|
| 68 |
+
col_status = "RB STATUS"
|
| 69 |
+
col_tipo = "TIPO"
|
| 70 |
+
col_colab = "COLABORADOR"
|
| 71 |
+
|
| 72 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 73 |
+
# CORES
|
| 74 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 75 |
|
|
|
|
| 76 |
CORES_FASE = {
|
| 77 |
"pendente_validacao": "#F18F01",
|
| 78 |
+
"concluido": "#2E86AB",
|
| 79 |
+
"faturado": "#44BBA4",
|
| 80 |
+
"em_progresso": "#A23B72",
|
| 81 |
+
"cancelado": "#C73E1D",
|
| 82 |
}
|
| 83 |
|
| 84 |
FASE_LABELS = {
|
| 85 |
"pendente_validacao": "Pendente ValidaΓ§Γ£o",
|
| 86 |
+
"concluido": "ConcluΓdo",
|
| 87 |
+
"faturado": "Faturado",
|
| 88 |
+
"em_progresso": "Em Progresso",
|
| 89 |
+
"cancelado": "Cancelado",
|
| 90 |
}
|
| 91 |
|
| 92 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 93 |
+
# UTIL
|
| 94 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 95 |
+
|
| 96 |
def top_counter(series, n=15):
|
| 97 |
all_vals = []
|
| 98 |
for v in series.dropna():
|
| 99 |
+
all_vals.extend([x.strip() for x in str(v).split(";") if x.strip()])
|
| 100 |
return dict(Counter(all_vals).most_common(n))
|
| 101 |
|
| 102 |
+
# βββββββοΏ½οΏ½οΏ½βββββββββββββββββββββββββββββββββββββ
|
| 103 |
+
# FIGURA PRINCIPAL
|
| 104 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 105 |
+
|
| 106 |
def fig_visao_geral():
|
| 107 |
+
|
| 108 |
fig = make_subplots(
|
| 109 |
+
rows=2,
|
| 110 |
+
cols=2,
|
| 111 |
subplot_titles=[
|
| 112 |
+
"Fases dos Projetos",
|
| 113 |
+
"Tipos de Projeto",
|
| 114 |
"Projetos por Colaborador",
|
| 115 |
+
"AΓ§Γ΅es nas ObservaΓ§Γ΅es",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
],
|
|
|
|
|
|
|
| 117 |
)
|
| 118 |
|
|
|
|
| 119 |
fases = stats["fases_classificadas"]
|
| 120 |
+
|
| 121 |
+
fig.add_trace(
|
| 122 |
+
go.Pie(
|
| 123 |
+
labels=[FASE_LABELS.get(k, k) for k in fases],
|
| 124 |
+
values=list(fases.values()),
|
| 125 |
+
hole=0.4,
|
| 126 |
+
),
|
| 127 |
+
row=1,
|
| 128 |
+
col=1,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
)
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
+
tipo_counts = df[col_tipo].value_counts().head(10)
|
| 132 |
|
| 133 |
+
fig.add_trace(
|
| 134 |
+
go.Bar(
|
| 135 |
+
x=tipo_counts.values,
|
| 136 |
+
y=tipo_counts.index,
|
| 137 |
+
orientation="h",
|
| 138 |
+
),
|
| 139 |
+
row=1,
|
| 140 |
+
col=2,
|
| 141 |
+
)
|
| 142 |
|
| 143 |
+
colab_counts = df[col_colab].replace("", pd.NA).dropna().value_counts().head(8)
|
| 144 |
+
|
| 145 |
+
fig.add_trace(
|
| 146 |
+
go.Bar(
|
| 147 |
+
x=colab_counts.values,
|
| 148 |
+
y=colab_counts.index,
|
| 149 |
+
orientation="h",
|
| 150 |
+
),
|
| 151 |
+
row=2,
|
| 152 |
+
col=1,
|
| 153 |
)
|
| 154 |
+
|
| 155 |
+
acoes = stats["acoes_observacoes"]
|
| 156 |
+
|
| 157 |
+
fig.add_trace(
|
| 158 |
+
go.Bar(
|
| 159 |
+
x=list(acoes.values())[:10],
|
| 160 |
+
y=list(acoes.keys())[:10],
|
| 161 |
+
orientation="h",
|
| 162 |
+
),
|
| 163 |
+
row=2,
|
| 164 |
+
col=2,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
)
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
+
fig.update_layout(height=650)
|
| 168 |
|
| 169 |
+
return fig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 173 |
+
# EXPLORADOR NER
|
| 174 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 175 |
|
|
|
|
| 176 |
COL_NER_MAP = {
|
| 177 |
+
"AΓ§Γ£o (ObservaΓ§Γ΅es)": "NER_OBS_acao",
|
| 178 |
+
"Semana (ObservaΓ§Γ΅es)": "NER_OBS_semana",
|
| 179 |
+
"Entidade": "NER_OBS_entidade_externa",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
}
|
| 181 |
|
| 182 |
+
|
| 183 |
def explorar_ner(coluna_ner, top_n):
|
| 184 |
+
|
| 185 |
+
col = COL_NER_MAP[coluna_ner]
|
| 186 |
+
|
| 187 |
all_vals = []
|
| 188 |
+
|
| 189 |
for v in df[col].dropna():
|
| 190 |
all_vals.extend([x.strip() for x in str(v).split(";") if x.strip()])
|
| 191 |
+
|
| 192 |
counter = Counter(all_vals)
|
| 193 |
top = dict(counter.most_common(int(top_n)))
|
| 194 |
|
| 195 |
+
fig = go.Figure(
|
| 196 |
+
go.Bar(
|
| 197 |
+
x=list(top.values()),
|
| 198 |
+
y=list(top.keys()),
|
| 199 |
+
orientation="h",
|
| 200 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
)
|
|
|
|
|
|
|
| 202 |
|
| 203 |
+
fig.update_layout(height=400)
|
| 204 |
+
|
| 205 |
+
sub = df[df[col].notna()].head(50)
|
| 206 |
+
|
| 207 |
return fig, sub
|
| 208 |
|
| 209 |
|
| 210 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 211 |
+
# JSON ESTRUTURADO
|
| 212 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 213 |
+
|
| 214 |
def tabela_json():
|
| 215 |
+
|
| 216 |
rows = []
|
| 217 |
+
|
| 218 |
for item in estruturados:
|
| 219 |
+
|
| 220 |
sub_cip = item.get("sub_cip", "")
|
| 221 |
+
|
| 222 |
tarefas = item.get("extracao_gliner2", {}).get("tarefa", [])
|
| 223 |
+
|
| 224 |
+
if tarefas:
|
| 225 |
+
t = tarefas[0]
|
| 226 |
+
else:
|
| 227 |
+
t = {}
|
| 228 |
+
|
| 229 |
rows.append({
|
| 230 |
+
"SUB-CIP": sub_cip,
|
| 231 |
+
"CΓ³digo": t.get("codigo_projeto"),
|
| 232 |
+
"Rede": t.get("tipo_rede"),
|
| 233 |
+
"Colaborador": t.get("colaborador"),
|
| 234 |
+
"Status": t.get("status"),
|
|
|
|
|
|
|
| 235 |
})
|
| 236 |
+
|
| 237 |
return pd.DataFrame(rows)
|
| 238 |
|
| 239 |
|
| 240 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 241 |
+
# APP GRADIO
|
| 242 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
+
with gr.Blocks(title="GLiNER2 Dashboard") as demo:
|
| 245 |
|
| 246 |
+
gr.Markdown("# π¬ GLiNER2 Dashboard")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
|
| 248 |
+
with gr.Tabs():
|
| 249 |
|
| 250 |
+
with gr.Tab("π VisΓ£o Geral"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
|
| 252 |
+
plot_geral = gr.Plot()
|
| 253 |
|
| 254 |
+
demo.load(fn=fig_visao_geral, outputs=plot_geral)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
+
with gr.Tab("π Explorador NER"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
+
ner_dd = gr.Dropdown(
|
| 259 |
+
choices=list(COL_NER_MAP.keys()),
|
| 260 |
+
value="AΓ§Γ£o (ObservaΓ§Γ΅es)",
|
| 261 |
+
label="Entidade NER"
|
| 262 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
+
top_n = gr.Slider(5, 20, value=10)
|
| 265 |
|
| 266 |
+
plot_ner = gr.Plot()
|
| 267 |
+
|
| 268 |
+
tbl_ner = gr.Dataframe()
|
|
|
|
| 269 |
|
| 270 |
+
ner_dd.change(
|
| 271 |
+
fn=explorar_ner,
|
| 272 |
+
inputs=[ner_dd, top_n],
|
| 273 |
+
outputs=[plot_ner, tbl_ner],
|
|
|
|
| 274 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
+
with gr.Tab("π JSON Estruturado"):
|
| 277 |
+
|
| 278 |
+
tbl_json = gr.Dataframe()
|
| 279 |
+
|
| 280 |
+
demo.load(fn=tabela_json, outputs=tbl_json)
|
| 281 |
+
|
| 282 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 283 |
+
# RUN
|
| 284 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
if __name__ == "__main__":
|
| 287 |
+
demo.launch()
|