roundb commited on
Commit
c7e4dc8
·
verified ·
1 Parent(s): 0200763

Upload 13 files

Browse files
app.py ADDED
@@ -0,0 +1,1003 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ app.py — CIRCET SLA (dash.R → Python + Gradio) + RAG NVIDIA NIM
3
+ ==============================================================
4
+
5
+ ✅ O que foi pedido e foi feito:
6
+ - Mantive o layout do 2º script (header, CSS, tabela, KPIs, exportação, legenda) SEM alterações.
7
+ - Apenas ADICIONEI uma nova aba "Assistente IA (RAG)" igual ao estilo do 1º script.
8
+ - O RAG aqui é o mesmo conceito do 1º script: contexto estruturado (CONTEXTO_RAG) + LLM via NVIDIA NIM.
9
+ - NÃO mexi no design do dashboard. Só encapsulei o dashboard numa Tab (sem mudar o conteúdo).
10
+
11
+ Requisitos:
12
+ pip install gradio pandas numpy openai
13
+
14
+ Estrutura esperada:
15
+ ./upload/
16
+ tarefasss_datas_corrigidas_final.csv
17
+ emcurso.csv
18
+ licenciamento.csv
19
+ validado.csv
20
+ ./output/ (criado automaticamente)
21
+
22
+ Chave NVIDIA:
23
+ - Pode vir de env: NVIDIA_API_KEY
24
+ - Ou ser inserida no campo na aba do assistente
25
+ """
26
+
27
+ import os
28
+ import datetime
29
+ import pandas as pd
30
+ import numpy as np
31
+ import gradio as gr
32
+ from openai import OpenAI
33
+
34
+
35
+ # ── Paths ──────────────────────────────────────────────────────────────────────
36
+ BASE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'upload')
37
+ BASE = os.path.abspath(BASE)
38
+ OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'output')
39
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
40
+
41
+
42
+ # ── Chave API NVIDIA NIM ───────────────────────────────────────────────────────
43
+ NVIDIA_API_KEY_ENV = os.environ.get('NVIDIA_API_KEY', '').strip()
44
+
45
+ MODELOS_NVIDIA = [
46
+ "meta/llama-3.3-70b-instruct",
47
+ "meta/llama-3.1-70b-instruct",
48
+ "meta/llama-3.1-8b-instruct",
49
+ "mistralai/mistral-7b-instruct-v0.3",
50
+ "mistralai/mixtral-8x7b-instruct-v0.1",
51
+ "microsoft/phi-3-mini-128k-instruct",
52
+ "google/gemma-2-9b-it",
53
+ ]
54
+
55
+ PERGUNTAS_SUGERIDAS = [
56
+ "📊 Faz um resumo executivo completo do estado actual do portfolio SLA",
57
+ "🚨 Quais são os 3 tipos de tarefa mais críticos neste momento e que acções recomendas?",
58
+ "📈 Compara o desempenho SLA entre as categorias Em Curso, Licenciamento e Validado",
59
+ "⚠️ Quais os projectos em curso com maior risco de incumprimento SLA nas próximas semanas?",
60
+ "🔴 Identifica os gargalos operacionais cruzando tipo de tarefa com status actual",
61
+ "📉 Qual o tipo de tarefa com maior taxa de incumprimento e qual o % SLA médio?",
62
+ "📈 Qual o tipo de tarefa com melhor desempenho SLA? O que pode explicar esse resultado?",
63
+ "📊 Faz uma tabela comparativa de todos os tipos com: total, excedidos, taxa e % SLA médio",
64
+ "⏰ Lista os 10 projectos com maior desvio SLA e o número de dias de atraso",
65
+ "📅 Analisa as adjudicações por mês: há meses com maior volume e pior desempenho?",
66
+ "💡 Que 3 acções correctivas prioritárias recomendas para melhorar a taxa de cumprimento global?",
67
+ ]
68
+
69
+
70
+ def criar_cliente_nvidia(api_key: str) -> OpenAI:
71
+ """Cliente OpenAI compatível com a API NVIDIA NIM."""
72
+ return OpenAI(
73
+ base_url="https://integrate.api.nvidia.com/v1",
74
+ api_key=api_key
75
+ )
76
+
77
+
78
+ # ── SLA fixo por TIPO (tabela da imagem) ──────────────────────────────────────
79
+ SLA_MAP = {
80
+ 'ART 2 3' : 30,
81
+ 'RAMI' : 30,
82
+ 'CUIVRE' : 30,
83
+ 'R?COLEMENTS' : 0,
84
+ 'PAR' : 10,
85
+ 'FIBRE' : 30,
86
+ 'RACCO' : 30,
87
+ 'IMMO' : 30,
88
+ 'DESSAT' : 10,
89
+ 'DISSIM - POI1': 30,
90
+ 'DISSIM - POI2': 15,
91
+ 'DISSIM - POI3': 15,
92
+ 'DISSIM' : 30,
93
+ }
94
+
95
+ TIPO_ORDER = [
96
+ 'ART 2 3', 'RAMI', 'CUIVRE', 'R?COLEMENTS', 'PAR', 'FIBRE',
97
+ 'RACCO', 'IMMO', 'DESSAT', 'DISSIM - POI1', 'DISSIM - POI2',
98
+ 'DISSIM - POI3', 'DISSIM'
99
+ ]
100
+
101
+ TIPO_LABEL = {
102
+ 'ART 2 3' : 'ART 2 3',
103
+ 'RAMI' : 'RAMI',
104
+ 'CUIVRE' : 'CUIVRE',
105
+ 'R?COLEMENTS' : 'RÉCOLEMENTS',
106
+ 'PAR' : 'PAR',
107
+ 'FIBRE' : 'FIBRE',
108
+ 'RACCO' : 'RACCO',
109
+ 'IMMO' : 'IMMO',
110
+ 'DESSAT' : 'DESSAT',
111
+ 'DISSIM - POI1': 'DISSIM - POI1',
112
+ 'DISSIM - POI2': 'DISSIM - POI2',
113
+ 'DISSIM - POI3': 'DISSIM - POI3',
114
+ 'DISSIM' : 'DISSIM',
115
+ }
116
+
117
+ # ── Cores por categoria ────────────────────────────────────────────────────────
118
+ CAT_CORES = {
119
+ 'EM CURSO' : ('#0D47A1', '#1565C0'),
120
+ 'LICENCIAMENTO': ('#4A148C', '#6A1B9A'),
121
+ 'VALIDADO' : ('#1B5E20', '#2E7D32'),
122
+ 'GLOBAL' : ('#212121', '#37474F'),
123
+ }
124
+
125
+ # ── Leitura dos CSVs de categoria ───────��─────────────────────────────────────
126
+ def ler_status_csv(path):
127
+ for enc in ('utf-8', 'latin-1', 'cp1252'):
128
+ try:
129
+ result = []
130
+ with open(path, encoding=enc) as f:
131
+ for line in f:
132
+ line = line.strip()
133
+ if not line:
134
+ continue
135
+ parts = line.split(',')
136
+ if len(parts) >= 3:
137
+ result.append(parts[2].strip())
138
+ elif len(parts) == 2:
139
+ result.append(parts[1].strip())
140
+ return [s for s in result if s]
141
+ except UnicodeDecodeError:
142
+ continue
143
+ except FileNotFoundError:
144
+ return []
145
+ return []
146
+
147
+ EM_CURSO_STATUS = set(ler_status_csv(os.path.join(BASE, 'emcurso.csv')))
148
+ VALIDADO_STATUS = set(ler_status_csv(os.path.join(BASE, 'validado.csv')))
149
+ LICENCIAMENTO_STATUS = set(ler_status_csv(os.path.join(BASE, 'licenciamento.csv')))
150
+
151
+ STATUS_EXTRA_MAP = {
152
+ '02.1 PROJETO POR ADJUDICAR' : 'EM CURSO',
153
+ '02.10 PRE VALIDA??O PROJETO' : 'EM CURSO',
154
+ '02.2 PROJETO EM CURSO' : 'EM CURSO',
155
+ '02.3 PROJETO PENDENTE CLIENTE': 'EM CURSO',
156
+ '02.4 AGUARDA DEVIS' : 'LICENCIAMENTO',
157
+ '02.6 AGUARDA PMV+DT' : 'LICENCIAMENTO',
158
+ '04 VALIDA??O ORANGE' : 'EM CURSO',
159
+ '05 PROJETO VALIDADO' : 'VALIDADO',
160
+ '06 TRABALHOS EM CURSO' : 'EM CURSO',
161
+ '10 CANCELADO' : 'VALIDADO',
162
+ '11 FATURADO' : 'VALIDADO',
163
+ '8.3 AGUARDA RT' : 'VALIDADO',
164
+ }
165
+
166
+ def get_categoria(status: str) -> str:
167
+ s = str(status).strip()
168
+ if s in VALIDADO_STATUS: return 'VALIDADO'
169
+ if s in LICENCIAMENTO_STATUS: return 'LICENCIAMENTO'
170
+ if s in EM_CURSO_STATUS: return 'EM CURSO'
171
+ return STATUS_EXTRA_MAP.get(s, 'GLOBAL')
172
+
173
+ def calcular_faixa(pct):
174
+ if pd.isna(pct): return 'N/A'
175
+ if pct < 50: return '< 50 %'
176
+ elif pct <= 75: return '50 % < X ≤ 75 %'
177
+ elif pct <= 100: return '75 % < X ≤ 100 %'
178
+ else: return '> 100 %'
179
+
180
+ # ── Carregar e processar dados (lógica do dash.R) ─────────────────────────────
181
+ def carregar_dados(caminho_csv: str) -> pd.DataFrame:
182
+ df_raw = pd.read_csv(caminho_csv, sep=';', encoding='latin-1', on_bad_lines='skip')
183
+ df_raw.rename(columns={df_raw.columns[9]: 'TEMPO_EXECUCAO'}, inplace=True)
184
+
185
+ dftipoadj = df_raw[[
186
+ 'SUB-CIP', 'PROJETO', 'TIPO', 'RB STATUS',
187
+ 'TEMPO_EXECUCAO', 'DATA_ADJ_CLIENTE'
188
+ ]].copy()
189
+
190
+ dd = dftipoadj[dftipoadj['TIPO'].isin(list(SLA_MAP.keys()))].copy()
191
+ dd = dd.sort_values('DATA_ADJ_CLIENTE').reset_index(drop=True)
192
+
193
+ dd['DATA_ADJ_CLIENTE'] = pd.to_datetime(
194
+ dd['DATA_ADJ_CLIENTE'], format='%d/%m/%Y', errors='coerce'
195
+ )
196
+ dd['TEMPO_EXECUCAO'] = pd.to_numeric(
197
+ dd['TEMPO_EXECUCAO'].astype(str).str.strip(), errors='coerce'
198
+ )
199
+
200
+ hoje = pd.Timestamp.today().normalize()
201
+ dd['DATA_PREVISTA'] = dd['DATA_ADJ_CLIENTE'] + pd.to_timedelta(dd['TEMPO_EXECUCAO'], unit='D')
202
+ dd['ATUAL'] = (hoje - dd['DATA_PREVISTA']).dt.days
203
+ dd['DIFDIAS'] = dd['TEMPO_EXECUCAO'] - dd['ATUAL']
204
+
205
+ dd['SLA_FIXO'] = dd['TIPO'].map(SLA_MAP)
206
+ dd['TIPO_LABEL'] = dd['TIPO'].map(TIPO_LABEL).fillna(dd['TIPO'])
207
+ dd['CATEGORIA'] = dd['RB STATUS'].apply(get_categoria)
208
+
209
+ dd['PCT_SLA'] = np.where(
210
+ (dd['SLA_FIXO'] > 0) & (dd['TEMPO_EXECUCAO'] >= 0),
211
+ (dd['TEMPO_EXECUCAO'] / dd['SLA_FIXO'] * 100).round(1),
212
+ np.nan
213
+ )
214
+ dd['FAIXA_SLA'] = dd['PCT_SLA'].apply(calcular_faixa)
215
+ dd['DATA_CALCULO'] = hoje.strftime('%Y-%m-%d')
216
+ return dd
217
+
218
+ # Carregar dados na inicialização
219
+ CSV_PATH = os.path.join(BASE, 'tarefasss_datas_corrigidas_final.csv')
220
+ DF_GLOBAL = carregar_dados(CSV_PATH)
221
+
222
+ # ── Construir tabela pivot para uma categoria ──────────────────────────────────
223
+ def build_pivot(df: pd.DataFrame, categoria: str) -> pd.DataFrame:
224
+ if categoria == 'GLOBAL':
225
+ sub = df.copy()
226
+ else:
227
+ sub = df[df['CATEGORIA'] == categoria].copy()
228
+
229
+ rows = []
230
+ for tipo in TIPO_ORDER:
231
+ sub_t = sub[sub['TIPO'] == tipo]
232
+ row = {
233
+ 'TIPOS' : TIPO_LABEL.get(tipo, tipo),
234
+ 'SLA [dias]' : SLA_MAP.get(tipo, 0),
235
+ '< 50 % [uni]' : int((sub_t['FAIXA_SLA'] == '< 50 %').sum()),
236
+ '50 % < X ≤ 75 % [uni]' : int((sub_t['FAIXA_SLA'] == '50 % < X ≤ 75 %').sum()),
237
+ '75 % < X ≤ 100 % [uni]' : int((sub_t['FAIXA_SLA'] == '75 % < X ≤ 100 %').sum()),
238
+ '> 100 % [uni]' : int((sub_t['FAIXA_SLA'] == '> 100 %').sum()),
239
+ 'TOTAL' : len(sub_t),
240
+ }
241
+ rows.append(row)
242
+
243
+ return pd.DataFrame(rows)
244
+
245
+ # ── Estatísticas de resumo ─────────────────────────────────────────────────────
246
+ def get_stats(categoria: str) -> dict:
247
+ if categoria == 'GLOBAL':
248
+ sub = DF_GLOBAL.copy()
249
+ else:
250
+ sub = DF_GLOBAL[DF_GLOBAL['CATEGORIA'] == categoria]
251
+
252
+ total = len(sub)
253
+ validos = sub[sub['SLA_FIXO'] > 0]
254
+ dentro = int((validos['FAIXA_SLA'].isin(['< 50 %', '50 % < X ≤ 75 %', '75 % < X ≤ 100 %'])).sum())
255
+ excedido = int((validos['FAIXA_SLA'] == '> 100 %').sum())
256
+ pct_ok = round(dentro / len(validos) * 100, 1) if len(validos) > 0 else 0.0
257
+
258
+ return {'total': total, 'dentro': dentro, 'excedido': excedido, 'pct_ok': pct_ok}
259
+
260
+ # ── Renderizar tabela HTML com design profissional ────────────────────────────
261
+ def render_html_table(pivot: pd.DataFrame, categoria: str) -> str:
262
+ cor_dark, cor_mid = CAT_CORES.get(categoria, ('#212121', '#37474F'))
263
+
264
+ faixa_header_bg = ['#1B5E20', '#E65100', '#BF360C', '#B71C1C']
265
+ faixa_cell = [
266
+ ('#E8F5E9', '#1B5E20'),
267
+ ('#FFF8E1', '#E65100'),
268
+ ('#FBE9E7', '#BF360C'),
269
+ ('#FFEBEE', '#B71C1C'),
270
+ ]
271
+ faixa_cols = [
272
+ '< 50 % [uni]',
273
+ '50 % < X ≤ 75 % [uni]',
274
+ '75 % < X ≤ 100 % [uni]',
275
+ '> 100 % [uni]',
276
+ ]
277
+ faixa_labels = [
278
+ '&lt; 50&nbsp;%',
279
+ '50&nbsp;% &lt; X ≤ 75&nbsp;%',
280
+ '75&nbsp;% &lt; X ≤ 100&nbsp;%',
281
+ '&gt; 100&nbsp;%',
282
+ ]
283
+
284
+ html = f"""
285
+ <style>
286
+ .sla-wrap {{
287
+ font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
288
+ font-size: 13px;
289
+ }}
290
+ .sla-table {{
291
+ border-collapse: separate;
292
+ border-spacing: 0;
293
+ width: 100%;
294
+ border-radius: 10px;
295
+ overflow: hidden;
296
+ box-shadow: 0 4px 20px rgba(0,0,0,0.10);
297
+ }}
298
+ .sla-table thead tr th {{
299
+ padding: 11px 16px;
300
+ font-weight: 700;
301
+ letter-spacing: 0.4px;
302
+ font-size: 12px;
303
+ text-transform: uppercase;
304
+ border-bottom: 2px solid rgba(255,255,255,0.18);
305
+ color: #ffffff !important;
306
+ }}
307
+ .sla-table th.th-tipo {{
308
+ background: {cor_dark};
309
+ color: #ffffff !important;
310
+ text-align: left;
311
+ min-width: 150px;
312
+ border-right: 1px solid rgba(255,255,255,0.15);
313
+ }}
314
+ .sla-table th.th-sla {{
315
+ background: {cor_mid};
316
+ color: #ffffff !important;
317
+ text-align: center;
318
+ width: 80px;
319
+ border-right: 1px solid rgba(255,255,255,0.15);
320
+ }}
321
+ .sla-table th.th-total {{
322
+ background: {cor_dark};
323
+ color: #ffffff !important;
324
+ text-align: center;
325
+ width: 70px;
326
+ }}
327
+ .sla-table tbody tr {{
328
+ transition: background 0.15s;
329
+ }}
330
+ .sla-table tbody tr:nth-child(even) td {{
331
+ background-color: #f7f9fc;
332
+ }}
333
+ .sla-table tbody tr:nth-child(odd) td {{
334
+ background-color: #ffffff;
335
+ }}
336
+ .sla-table tbody tr:hover td {{
337
+ background-color: #eef2ff !important;
338
+ }}
339
+ .sla-table td {{
340
+ padding: 9px 16px;
341
+ border-bottom: 1px solid #e8eaf0;
342
+ vertical-align: middle;
343
+ }}
344
+ .sla-table td.td-tipo {{
345
+ font-weight: 600;
346
+ color: #1a1a2e;
347
+ border-left: 4px solid {cor_mid};
348
+ text-align: left;
349
+ background-color: inherit;
350
+ }}
351
+ .sla-table td.td-sla {{
352
+ text-align: center;
353
+ color: #546e7a;
354
+ font-style: italic;
355
+ font-size: 12px;
356
+ }}
357
+ .sla-table td.td-faixa {{
358
+ text-align: center;
359
+ }}
360
+ .sla-table td.td-total {{
361
+ text-align: center;
362
+ font-weight: 800;
363
+ font-size: 14px;
364
+ color: {cor_dark};
365
+ background-color: #f0f4ff !important;
366
+ border-left: 2px solid #c5cae9;
367
+ }}
368
+ .badge {{
369
+ display: inline-flex;
370
+ align-items: center;
371
+ justify-content: center;
372
+ min-width: 36px;
373
+ height: 26px;
374
+ padding: 0 10px;
375
+ border-radius: 20px;
376
+ font-weight: 700;
377
+ font-size: 13px;
378
+ line-height: 1;
379
+ }}
380
+ .badge-zero {{
381
+ color: #bdbdbd;
382
+ font-size: 16px;
383
+ font-weight: 400;
384
+ }}
385
+ .sub-label {{
386
+ display: block;
387
+ font-size: 10px;
388
+ font-weight: 400;
389
+ opacity: 0.85;
390
+ margin-top: 2px;
391
+ text-transform: none;
392
+ letter-spacing: 0;
393
+ color: #ffffff !important;
394
+ }}
395
+ </style>
396
+ <div class="sla-wrap">
397
+ <table class="sla-table">
398
+ <thead>
399
+ <tr>
400
+ <th class="th-tipo">TIPOS</th>
401
+ <th class="th-sla">SLA<span class="sub-label">[dias]</span></th>
402
+ """
403
+ for label, bg in zip(faixa_labels, faixa_header_bg):
404
+ html += (f' <th style="background:{bg};color:#ffffff !important;'
405
+ f'text-align:center;min-width:90px;">'
406
+ f'{label}<span class="sub-label">[uni]</span></th>\n')
407
+
408
+ html += ' <th class="th-total">TOTAL</th>\n </tr>\n </thead>\n <tbody>\n'
409
+
410
+ for _, row in pivot.iterrows():
411
+ sla_val = row['SLA [dias]']
412
+ sla_str = str(int(sla_val)) if sla_val > 0 else '—'
413
+ html += f' <tr>\n <td class="td-tipo">{row["TIPOS"]}</td>\n'
414
+ html += f' <td class="td-sla">{sla_str}</td>\n'
415
+
416
+ for col, (bg, fg) in zip(faixa_cols, faixa_cell):
417
+ val = int(row[col])
418
+ if val == 0:
419
+ html += ' <td class="td-faixa"><span class="badge badge-zero">—</span></td>\n'
420
+ else:
421
+ html += (f' <td class="td-faixa">'
422
+ f'<span class="badge" style="background:{bg};color:{fg};">{val}</span>'
423
+ f'</td>\n')
424
+
425
+ total = int(row['TOTAL'])
426
+ html += f' <td class="td-total">{total}</td>\n </tr>\n'
427
+
428
+ html += ' </tbody>\n</table>\n</div>'
429
+ return html
430
+
431
+ # ── Renderizar KPI cards HTML ─────────────────────────────────────────────────
432
+ def render_kpi_html(stats: dict, categoria: str) -> str:
433
+ cor_dark, cor_mid = CAT_CORES.get(categoria, ('#212121', '#37474F'))
434
+ pct = stats['pct_ok']
435
+ if pct >= 80:
436
+ taxa_cor, taxa_bg = '#1B5E20', '#E8F5E9'
437
+ elif pct >= 60:
438
+ taxa_cor, taxa_bg = '#E65100', '#FFF8E1'
439
+ else:
440
+ taxa_cor, taxa_bg = '#B71C1C', '#FFEBEE'
441
+
442
+ html = f"""
443
+ <style>
444
+ .kpi-grid {{
445
+ display: grid;
446
+ grid-template-columns: 1fr 1fr;
447
+ gap: 14px;
448
+ font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
449
+ }}
450
+ .kpi-card {{
451
+ border-radius: 12px;
452
+ padding: 18px 16px 14px;
453
+ text-align: center;
454
+ box-shadow: 0 2px 10px rgba(0,0,0,0.08);
455
+ border-top: 4px solid;
456
+ }}
457
+ .kpi-label {{
458
+ font-size: 11px;
459
+ font-weight: 600;
460
+ text-transform: uppercase;
461
+ letter-spacing: 0.8px;
462
+ margin-bottom: 8px;
463
+ opacity: 0.75;
464
+ }}
465
+ .kpi-value {{
466
+ font-size: 36px;
467
+ font-weight: 800;
468
+ line-height: 1;
469
+ }}
470
+ .kpi-sub {{
471
+ font-size: 11px;
472
+ margin-top: 6px;
473
+ opacity: 0.6;
474
+ }}
475
+ </style>
476
+ <div class="kpi-grid">
477
+ <div class="kpi-card" style="background:#F3F4F6;border-color:{cor_mid};color:{cor_dark};">
478
+ <div class="kpi-label">Total de Tarefas</div>
479
+ <div class="kpi-value">{stats['total']}</div>
480
+ <div class="kpi-sub">registos processados</div>
481
+ </div>
482
+ <div class="kpi-card" style="background:#E8F5E9;border-color:#2E7D32;color:#1B5E20;">
483
+ <div class="kpi-label">Dentro do SLA</div>
484
+ <div class="kpi-value">{stats['dentro']}</div>
485
+ <div class="kpi-sub">≤ 100&nbsp;% SLA</div>
486
+ </div>
487
+ <div class="kpi-card" style="background:#FFEBEE;border-color:#C62828;color:#B71C1C;">
488
+ <div class="kpi-label">SLA Excedido</div>
489
+ <div class="kpi-value">{stats['excedido']}</div>
490
+ <div class="kpi-sub">&gt; 100&nbsp;% SLA</div>
491
+ </div>
492
+ <div class="kpi-card" style="background:{taxa_bg};border-color:{taxa_cor};color:{taxa_cor};">
493
+ <div class="kpi-label">Taxa de Cumprimento</div>
494
+ <div class="kpi-value">{pct}&nbsp;%</div>
495
+ <div class="kpi-sub">tarefas dentro do SLA</div>
496
+ </div>
497
+ </div>
498
+ """
499
+ return html
500
+
501
+ # ── Exportações ────────────────────────────────────────────────────────────────
502
+ def exportar_csv_pivot(categoria: str) -> str:
503
+ pivot = build_pivot(DF_GLOBAL, categoria)
504
+ ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
505
+ nome = f"sla_pivot_{categoria.lower().replace(' ', '_')}_{ts}.csv"
506
+ path = os.path.join(OUTPUT_DIR, nome)
507
+ pivot.to_csv(path, index=False, encoding='utf-8-sig', sep=';')
508
+ return path
509
+
510
+ def exportar_csv_fact(categoria: str) -> str:
511
+ sub = DF_GLOBAL.copy() if categoria == 'GLOBAL' else DF_GLOBAL[DF_GLOBAL['CATEGORIA'] == categoria].copy()
512
+ fact = sub[[
513
+ 'SUB-CIP', 'PROJETO', 'TIPO', 'TIPO_LABEL', 'RB STATUS', 'CATEGORIA',
514
+ 'DATA_ADJ_CLIENTE', 'DATA_PREVISTA', 'TEMPO_EXECUCAO',
515
+ 'ATUAL', 'DIFDIAS', 'SLA_FIXO', 'PCT_SLA', 'FAIXA_SLA', 'DATA_CALCULO'
516
+ ]].copy()
517
+ fact['DATA_ADJ_CLIENTE'] = fact['DATA_ADJ_CLIENTE'].dt.strftime('%d/%m/%Y')
518
+ fact['DATA_PREVISTA'] = fact['DATA_PREVISTA'].dt.strftime('%d/%m/%Y')
519
+ ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
520
+ nome = f"sla_fact_{categoria.lower().replace(' ', '_')}_{ts}.csv"
521
+ path = os.path.join(OUTPUT_DIR, nome)
522
+ fact.to_csv(path, index=False, encoding='utf-8-sig', sep=';')
523
+ return path
524
+
525
+ # ── Actualizar vista principal ─────────────────────────────────────────────────
526
+ def atualizar_vista(categoria: str):
527
+ pivot = build_pivot(DF_GLOBAL, categoria)
528
+ tabela_html = render_html_table(pivot, categoria)
529
+ stats = get_stats(categoria)
530
+ kpi_html = render_kpi_html(stats, categoria)
531
+ return tabela_html, kpi_html
532
+
533
+
534
+ # ═══════════════════════════════════════════════════════════════════════════════
535
+ # ── RAG (igual ao 1º script): contexto estruturado + LLM NVIDIA ───────────────
536
+ # ═══════════════════════════════════════════════════════════════════════════════
537
+ def gerar_contexto_rag() -> str:
538
+ """
539
+ Contexto estruturado dos dados do dashboard.
540
+ (Mesmo estilo do 1º script: rico, com cruzamentos, riscos, aging, tendências)
541
+ """
542
+ hoje_ts = pd.Timestamp.today().normalize()
543
+ hoje_str = hoje_ts.strftime('%d/%m/%Y')
544
+ df = DF_GLOBAL.copy()
545
+ linhas = []
546
+
547
+ linhas.append("=" * 70)
548
+ linhas.append("DASHBOARD SLA — CONTEXTO COMPLETO (CIRCET)")
549
+ linhas.append("=" * 70)
550
+ linhas.append(f"Data de referência : {hoje_str}")
551
+ linhas.append(f"Total de registos : {len(df)}")
552
+ linhas.append(f"Tipos de tarefa : {', '.join(sorted(df['TIPO_LABEL'].unique()))}")
553
+ linhas.append(f"Categorias activas : EM CURSO ({(df['CATEGORIA']=='EM CURSO').sum()}) | "
554
+ f"LICENCIAMENTO ({(df['CATEGORIA']=='LICENCIAMENTO').sum()}) | "
555
+ f"VALIDADO ({(df['CATEGORIA']=='VALIDADO').sum()}) | "
556
+ f"GLOBAL ({(df['CATEGORIA']=='GLOBAL').sum()})")
557
+ linhas.append("")
558
+
559
+ # KPIs por categoria
560
+ linhas.append("-" * 70)
561
+ linhas.append("KPIs EXECUTIVOS (por categoria)")
562
+ linhas.append("-" * 70)
563
+ for cat in ['GLOBAL', 'EM CURSO', 'LICENCIAMENTO', 'VALIDADO']:
564
+ stats = get_stats(cat)
565
+ sub = df if cat == 'GLOBAL' else df[df['CATEGORIA'] == cat]
566
+ n_sla = sub[sub['SLA_FIXO'] > 0]
567
+ pct_medio = round(n_sla['PCT_SLA'].mean(), 1) if len(n_sla) > 0 else 0
568
+ linhas.append(f" [{cat}] Total={stats['total']} | Dentro SLA={stats['dentro']} ({stats['pct_ok']}%) | "
569
+ f"Excedido={stats['excedido']} | %SLA médio={pct_medio}%")
570
+ linhas.append("")
571
+
572
+ # Risco por tipo
573
+ linhas.append("-" * 70)
574
+ linhas.append("ANÁLISE DE RISCO — TIPOS ORDENADOS POR TAXA DE INCUMPRIMENTO")
575
+ linhas.append("-" * 70)
576
+ risco = df[df['SLA_FIXO'] > 0].groupby('TIPO_LABEL').agg(
577
+ total=('TIPO_LABEL', 'count'),
578
+ excedido=('FAIXA_SLA', lambda x: (x == '> 100 %').sum()),
579
+ pct_medio=('PCT_SLA', 'mean')
580
+ ).reset_index()
581
+ risco['taxa_exc'] = (risco['excedido'] / risco['total'] * 100).round(1)
582
+ risco['pct_medio'] = risco['pct_medio'].round(1)
583
+ risco = risco.sort_values('taxa_exc', ascending=False)
584
+ for _, r in risco.iterrows():
585
+ nivel = "CRITICO" if r['taxa_exc'] >= 70 else ("ALTO" if r['taxa_exc'] >= 40 else ("MEDIO" if r['taxa_exc'] >= 20 else "BAIXO"))
586
+ linhas.append(
587
+ f" [{nivel}] {r['TIPO_LABEL']:<20} | Excedido: {r['excedido']}/{r['total']} ({r['taxa_exc']}%) | %SLA médio: {r['pct_medio']}%"
588
+ )
589
+ linhas.append("")
590
+
591
+ # Aging (top 20)
592
+ linhas.append("-" * 70)
593
+ linhas.append("AGING — TOP 20 PROJECTOS COM MAIOR EXCESSO DE SLA")
594
+ linhas.append("-" * 70)
595
+ excedidos = df[df['FAIXA_SLA'] == '> 100 %'].copy()
596
+ excedidos = excedidos.sort_values('PCT_SLA', ascending=False).head(20)
597
+ for _, row in excedidos.iterrows():
598
+ data_adj = row['DATA_ADJ_CLIENTE'].strftime('%d/%m/%Y') if pd.notna(row['DATA_ADJ_CLIENTE']) else 'N/D'
599
+ dias_atraso = int(row['ATUAL']) if pd.notna(row['ATUAL']) else 0
600
+ linhas.append(
601
+ f" {row['PROJETO']:<14} [{row['CATEGORIA']:<13}] "
602
+ f"Tipo: {row['TIPO_LABEL']:<16} Status: {row['RB STATUS']:<30} "
603
+ f"% SLA: {row['PCT_SLA']:>6}% | Atraso: {dias_atraso:>4}d | Adj: {data_adj}"
604
+ )
605
+ linhas.append("")
606
+
607
+ # Gargalos: tipo x status em excedidos
608
+ linhas.append("-" * 70)
609
+ linhas.append("GARGALOS — TIPO × STATUS (excedidos)")
610
+ linhas.append("-" * 70)
611
+ cross = df[df['FAIXA_SLA'] == '> 100 %'].groupby(['TIPO_LABEL', 'RB STATUS']).size().reset_index(name='n_excedidos')
612
+ cross = cross.sort_values('n_excedidos', ascending=False).head(25)
613
+ for _, r in cross.iterrows():
614
+ linhas.append(f" {r['TIPO_LABEL']:<20} + {r['RB STATUS']:<35} → {r['n_excedidos']} excedidos")
615
+ linhas.append("")
616
+
617
+ # Volume por mês (últimos 12)
618
+ linhas.append("-" * 70)
619
+ linhas.append("ADJUDICAÇÕES POR MÊS (últimos 12 meses disponíveis)")
620
+ linhas.append("-" * 70)
621
+ df_datas = df[df['DATA_ADJ_CLIENTE'].notna()].copy()
622
+ if len(df_datas) > 0:
623
+ df_datas['MES_ADJ'] = df_datas['DATA_ADJ_CLIENTE'].dt.to_period('M')
624
+ mes_counts = df_datas.groupby('MES_ADJ').agg(
625
+ total=('PROJETO', 'count'),
626
+ excedido=('FAIXA_SLA', lambda x: (x == '> 100 %').sum())
627
+ ).reset_index().sort_values('MES_ADJ', ascending=False).head(12)
628
+
629
+ for _, r in mes_counts.iterrows():
630
+ taxa = round(r['excedido'] / r['total'] * 100, 1) if r['total'] > 0 else 0
631
+ linhas.append(f" {str(r['MES_ADJ']):<10} | Adjudicados: {r['total']:3d} | Excedidos: {r['excedido']:3d} ({taxa}%)")
632
+ else:
633
+ linhas.append(" (sem datas válidas em DATA_ADJ_CLIENTE)")
634
+ linhas.append("")
635
+
636
+ # Resumo executivo automático
637
+ linhas.append("-" * 70)
638
+ linhas.append("RESUMO EXECUTIVO AUTOMÁTICO")
639
+ linhas.append("-" * 70)
640
+ total_g = len(df)
641
+ exc_g = int((df['FAIXA_SLA'] == '> 100 %').sum())
642
+ taxa_g = round(exc_g / total_g * 100, 1) if total_g > 0 else 0.0
643
+ tipo_pior = risco.iloc[0]['TIPO_LABEL'] if len(risco) > 0 else 'N/D'
644
+ taxa_pior = risco.iloc[0]['taxa_exc'] if len(risco) > 0 else 0
645
+ linhas.append(f" Portfolio total : {total_g} tarefas")
646
+ linhas.append(f" Taxa incumprimento : {taxa_g}% ({exc_g} tarefas com SLA > 100%)")
647
+ linhas.append(f" Tipo mais crítico : {tipo_pior} ({taxa_pior}% de incumprimento)")
648
+ linhas.append("")
649
+
650
+ return "\n".join(linhas)
651
+
652
+ # Pré-calcular o contexto
653
+ CONTEXTO_RAG = gerar_contexto_rag()
654
+
655
+ def responder_pergunta(pergunta: str, historico: list, api_key: str, modelo: str) -> tuple:
656
+ if not api_key or not api_key.strip():
657
+ historico = historico + [
658
+ {"role": "user", "content": pergunta},
659
+ {"role": "assistant", "content": "Por favor, insira a sua chave API da NVIDIA NIM no campo acima para usar o assistente."}
660
+ ]
661
+ return historico, ""
662
+
663
+ if not pergunta or not pergunta.strip():
664
+ return historico, ""
665
+
666
+ try:
667
+ client = criar_cliente_nvidia(api_key.strip())
668
+
669
+ system_prompt = f"""Você é um Gestor de Projecto Sénior com mais de 15 anos de experiência em gestão de portfolios de telecomunicações, especializado em controlo SLA, análise de risco operacional e reporte executivo.
670
+ O seu papel é analisar os dados reais do dashboard SLA e responder com rigor. Interprete números, identifique padrões, riscos e proponha acções.
671
+ Responda SEMPRE em português europeu (Portugal), linguagem profissional e directa. Use números exactos sempre que possível.
672
+ --- DADOS DO DASHBOARD SLA ---
673
+ {CONTEXTO_RAG}
674
+ --- FIM DOS DADOS ---
675
+ Regras:
676
+ - Estruture: Situação → Análise → Recomendação (quando aplicável)
677
+ - Use tabelas markdown para comparações com 3+ itens
678
+ - Classifique risco: 🔴 CRÍTICO (≥70%) | 🟠 ALTO (40-69%) | 🟡 MÉDIO (20-39%) | 🟢 BAIXO (<20%)
679
+ - Se não houver dados para responder, diga claramente o que falta
680
+ """
681
+
682
+ messages = [{"role": "system", "content": system_prompt}]
683
+
684
+ for msg in historico[-10:]:
685
+ messages.append({"role": msg["role"], "content": msg["content"]})
686
+
687
+ messages.append({"role": "user", "content": pergunta})
688
+
689
+ response = client.chat.completions.create(
690
+ model=modelo,
691
+ messages=messages,
692
+ temperature=0.2,
693
+ max_tokens=1500,
694
+ )
695
+
696
+ resposta = response.choices[0].message.content
697
+
698
+ historico = historico + [
699
+ {"role": "user", "content": pergunta},
700
+ {"role": "assistant", "content": resposta}
701
+ ]
702
+ return historico, ""
703
+
704
+ except Exception as e:
705
+ erro = str(e)
706
+ if "401" in erro or "Unauthorized" in erro or "invalid_api_key" in erro.lower():
707
+ msg_erro = "❌ Chave API inválida ou sem autorização. Verifique a sua chave NVIDIA NIM."
708
+ elif "429" in erro or "rate_limit" in erro.lower():
709
+ msg_erro = "⏳ Limite de pedidos atingido. Aguarde alguns segundos e tente novamente."
710
+ elif "model" in erro.lower() and "not found" in erro.lower():
711
+ msg_erro = f"❌ Modelo '{modelo}' não encontrado. Tente selecionar outro modelo."
712
+ else:
713
+ msg_erro = f"❌ Erro ao contactar a API NVIDIA NIM: {erro}"
714
+
715
+ historico = historico + [
716
+ {"role": "user", "content": pergunta},
717
+ {"role": "assistant", "content": msg_erro}
718
+ ]
719
+ return historico, ""
720
+
721
+ def limpar_chat():
722
+ return [], ""
723
+
724
+ def perguntas_rapidas(pergunta_selecionada: str) -> str:
725
+ return pergunta_selecionada
726
+
727
+
728
+ # ── CSS global (IGUAL AO SEU 2º SCRIPT) ────────────────────────────────────────
729
+ CSS = """
730
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
731
+
732
+ body, .gradio-container {
733
+ font-family: 'Inter', 'Segoe UI', Arial, sans-serif !important;
734
+ background: #F0F2F8 !important;
735
+ }
736
+ .gradio-container {
737
+ max-width: 1280px !important;
738
+ margin: 0 auto !important;
739
+ padding: 0 !important;
740
+ }
741
+ .app-header {
742
+ background: linear-gradient(135deg, #0D47A1 0%, #1565C0 50%, #1976D2 100%);
743
+ padding: 28px 36px 22px;
744
+ border-radius: 0 0 16px 16px;
745
+ margin-bottom: 24px;
746
+ box-shadow: 0 4px 20px rgba(13,71,161,0.25);
747
+ }
748
+ .app-header * {
749
+ color: #ffffff !important;
750
+ }
751
+ .app-header h1 {
752
+ margin: 0 0 6px;
753
+ font-size: 26px;
754
+ font-weight: 800;
755
+ letter-spacing: -0.3px;
756
+ color: #ffffff !important;
757
+ }
758
+ .app-header p {
759
+ margin: 0;
760
+ font-size: 13px;
761
+ font-weight: 400;
762
+ color: #ffffff !important;
763
+ opacity: 0.92;
764
+ }
765
+ .cat-selector label {
766
+ font-weight: 700 !important;
767
+ font-size: 12px !important;
768
+ text-transform: uppercase !important;
769
+ letter-spacing: 0.6px !important;
770
+ color: #546e7a !important;
771
+ margin-bottom: 8px !important;
772
+ }
773
+ .cat-selector .wrap { gap: 10px !important; }
774
+ .cat-selector input[type=radio] + span {
775
+ border-radius: 8px !important;
776
+ padding: 9px 22px !important;
777
+ font-weight: 600 !important;
778
+ font-size: 13px !important;
779
+ border: 2px solid #e0e0e0 !important;
780
+ background: white !important;
781
+ color: #37474F !important;
782
+ transition: all 0.2s !important;
783
+ cursor: pointer !important;
784
+ }
785
+ .cat-selector input[type=radio]:checked + span {
786
+ background: #0D47A1 !important;
787
+ color: white !important;
788
+ border-color: #0D47A1 !important;
789
+ box-shadow: 0 4px 12px rgba(13,71,161,0.3) !important;
790
+ }
791
+ .btn-export {
792
+ border-radius: 8px !important;
793
+ font-weight: 600 !important;
794
+ font-size: 13px !important;
795
+ padding: 10px 20px !important;
796
+ transition: all 0.2s !important;
797
+ }
798
+ .export-section {
799
+ background: white;
800
+ border-radius: 12px;
801
+ padding: 20px 24px;
802
+ box-shadow: 0 2px 10px rgba(0,0,0,0.06);
803
+ margin-top: 16px;
804
+ }
805
+ .legenda-bar {
806
+ display: flex;
807
+ gap: 20px;
808
+ align-items: center;
809
+ background: white;
810
+ border-radius: 10px;
811
+ padding: 12px 20px;
812
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
813
+ margin-top: 16px;
814
+ flex-wrap: wrap;
815
+ }
816
+ footer { display: none !important; }
817
+ .gr-panel, .gr-box { border-radius: 12px !important; }
818
+ """
819
+
820
+ # ── Interface Gradio ───────────────────────────────────────────────────────────
821
+ CATEGORIAS = ['EM CURSO', 'LICENCIAMENTO', 'VALIDADO', 'GLOBAL']
822
+ DATA_REF = pd.Timestamp.today().strftime('%d/%m/%Y')
823
+ N_TOTAL = len(DF_GLOBAL)
824
+
825
+ with gr.Blocks(title="CIRCET SLA") as demo:
826
+
827
+ # ── Header — IGUAL AO SEU ────────────────────────────────────────────────
828
+ gr.HTML(f"""
829
+ <div class="app-header" style="
830
+ background: linear-gradient(135deg, #0D47A1 0%, #1565C0 50%, #1976D2 100%);
831
+ padding: 28px 36px 22px;
832
+ border-radius: 0 0 16px 16px;
833
+ margin-bottom: 24px;
834
+ box-shadow: 0 4px 20px rgba(13,71,161,0.25);
835
+ ">
836
+ <h1 style="margin:0 0 6px;font-size:26px;font-weight:800;letter-spacing:-0.3px;
837
+ color:#ffffff !important;font-family:'Inter','Segoe UI',Arial,sans-serif;">
838
+ 📊 CIRCET SLA — Acompanhamento de Tarefas
839
+ </h1>
840
+ <p style="margin:0;font-size:13px;font-weight:400;
841
+ color:#ffffff !important;opacity:0.92;
842
+ font-family:'Inter','Segoe UI',Arial,sans-serif;">
843
+ Controlo SLA por tipo de tarefa &nbsp;·&nbsp; Distribuição por faixas de percentagem
844
+ &nbsp;·&nbsp; {N_TOTAL} registos &nbsp;·&nbsp; Referência: {DATA_REF}
845
+ </p>
846
+ </div>
847
+ """)
848
+
849
+ # ── Tabs (apenas para adicionar RAG sem mexer no layout do dashboard) ─────
850
+ with gr.Tabs():
851
+
852
+ # ── TAB 1: Dashboard (conteúdo IGUAL AO SEU) ────────────────────────
853
+ with gr.Tab("📊 Dashboard SLA"):
854
+
855
+ # Selector de categoria
856
+ with gr.Row():
857
+ cat_selector = gr.Radio(
858
+ choices=CATEGORIAS,
859
+ value='EM CURSO',
860
+ label='Categoria',
861
+ interactive=True,
862
+ elem_classes=['cat-selector'],
863
+ )
864
+
865
+ # Tabela + KPIs
866
+ with gr.Row(equal_height=True):
867
+ with gr.Column(scale=4):
868
+ tabela_out = gr.HTML()
869
+ with gr.Column(scale=1, min_width=220):
870
+ kpi_out = gr.HTML()
871
+
872
+ # Secção de exportação
873
+ gr.HTML('<div class="export-section"><b style="font-size:13px;color:#37474F;'
874
+ 'text-transform:uppercase;letter-spacing:0.6px;">⬇ Exportar Dados</b></div>')
875
+
876
+ with gr.Row():
877
+ with gr.Column(scale=1):
878
+ gr.Markdown("**Pivot da categoria** — distribuição por faixas SLA")
879
+ btn_pivot = gr.Button("⬇ CSV — Tabela Pivot", variant="secondary", elem_classes=["btn-export"])
880
+ file_pivot = gr.File(label="", show_label=False)
881
+
882
+ with gr.Column(scale=1):
883
+ gr.Markdown("**Dados calculados completos** — todos os campos do dash.R")
884
+ btn_fact = gr.Button("⬇ CSV — Dados Calculados", variant="secondary", elem_classes=["btn-export"])
885
+ file_fact = gr.File(label="", show_label=False)
886
+
887
+ # Legenda
888
+ gr.HTML("""
889
+ <div class="legenda-bar">
890
+ <span style="font-size:12px;font-weight:700;color:#546e7a;text-transform:uppercase;letter-spacing:0.5px;">Legenda:</span>
891
+ <span style="display:inline-flex;align-items:center;gap:6px;font-size:13px;">
892
+ <span style="width:14px;height:14px;border-radius:50%;background:#2E7D32;display:inline-block;"></span>
893
+ <b style="color:#1B5E20">&lt; 50 %</b> — Dentro do prazo
894
+ </span>
895
+ <span style="display:inline-flex;align-items:center;gap:6px;font-size:13px;">
896
+ <span style="width:14px;height:14px;border-radius:50%;background:#E65100;display:inline-block;"></span>
897
+ <b style="color:#E65100">50 % &lt; X ≤ 75 %</b> — Atenção
898
+ </span>
899
+ <span style="display:inline-flex;align-items:center;gap:6px;font-size:13px;">
900
+ <span style="width:14px;height:14px;border-radius:50%;background:#BF360C;display:inline-block;"></span>
901
+ <b style="color:#BF360C">75 % &lt; X ≤ 100 %</b> — Crítico
902
+ </span>
903
+ <span style="display:inline-flex;align-items:center;gap:6px;font-size:13px;">
904
+ <span style="width:14px;height:14px;border-radius:50%;background:#B71C1C;display:inline-block;"></span>
905
+ <b style="color:#B71C1C">&gt; 100 %</b> — SLA excedido
906
+ </span>
907
+ </div>
908
+ """)
909
+
910
+ # Eventos do dashboard
911
+ cat_selector.change(fn=atualizar_vista, inputs=cat_selector, outputs=[tabela_out, kpi_out])
912
+ demo.load(fn=lambda: atualizar_vista('EM CURSO'), outputs=[tabela_out, kpi_out])
913
+ btn_pivot.click(fn=exportar_csv_pivot, inputs=cat_selector, outputs=file_pivot)
914
+ btn_fact.click(fn=exportar_csv_fact, inputs=cat_selector, outputs=file_fact)
915
+
916
+ # ── TAB 2: Assistente IA (RAG) ───────────────────────────────────────
917
+ with gr.Tab("Assistente IA (RAG)"):
918
+
919
+ gr.HTML("""
920
+ <div style="background:linear-gradient(135deg,#0D47A1 0%,#1565C0 50%,#1976D2 100%);
921
+ border-radius:10px;padding:16px 20px;margin-bottom:16px;">
922
+ <h3 style="margin:0 0 6px;color:#fff;font-size:16px;font-weight:700;">
923
+ Assistente RAG — NVIDIA NIM
924
+ </h3>
925
+ <p style="margin:0;color:rgba(255,255,255,0.85);font-size:13px;">
926
+ Faça perguntas em linguagem natural sobre os dados do dashboard SLA.
927
+ </p>
928
+ </div>
929
+ """)
930
+
931
+ with gr.Row():
932
+ with gr.Column(scale=3):
933
+ api_key_input = gr.Textbox(
934
+ label="🔑 Chave API NVIDIA NIM",
935
+ placeholder="nvapi-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
936
+ value=NVIDIA_API_KEY_ENV,
937
+ type="password",
938
+ info="Pode vir de env NVIDIA_API_KEY ou ser inserida manualmente."
939
+ )
940
+ with gr.Column(scale=2):
941
+ modelo_selector = gr.Dropdown(
942
+ choices=MODELOS_NVIDIA,
943
+ value="meta/llama-3.3-70b-instruct",
944
+ label="🧠 Modelo NVIDIA NIM"
945
+ )
946
+
947
+ with gr.Row():
948
+ perguntas_dropdown = gr.Dropdown(
949
+ choices=PERGUNTAS_SUGERIDAS,
950
+ label="💡 Perguntas sugeridas (clique para usar)",
951
+ value=None,
952
+ interactive=True,
953
+ )
954
+
955
+ chatbot = gr.Chatbot(
956
+ label="Conversa com o Assistente SLA",
957
+ height=480,
958
+ placeholder="<div style='text-align:center;padding:40px;color:#9e9e9e;'>"
959
+ "<b>Assistente SLA com NVIDIA NIM</b><br>"
960
+ "<span style='font-size:13px;'>Insira a sua chave e faça uma pergunta</span>"
961
+ "</div>"
962
+ )
963
+
964
+ with gr.Row():
965
+ pergunta_input = gr.Textbox(
966
+ label="",
967
+ placeholder="Ex: Quais os tipos mais críticos? Qual a taxa global? Onde estão os gargalos?",
968
+ lines=2,
969
+ scale=5,
970
+ show_label=False,
971
+ )
972
+ with gr.Column(scale=1, min_width=120):
973
+ btn_enviar = gr.Button("Enviar ▶", variant="primary", size="lg")
974
+ btn_limpar = gr.Button("🗑 Limpar", variant="secondary")
975
+
976
+ with gr.Accordion("ℹ️ Ver contexto RAG (dados enviados ao modelo)", open=False):
977
+ gr.Textbox(
978
+ value=CONTEXTO_RAG,
979
+ label="Contexto estruturado dos dados (enviado ao modelo)",
980
+ lines=20,
981
+ interactive=False,
982
+ )
983
+
984
+ btn_enviar.click(
985
+ fn=responder_pergunta,
986
+ inputs=[pergunta_input, chatbot, api_key_input, modelo_selector],
987
+ outputs=[chatbot, pergunta_input],
988
+ )
989
+ pergunta_input.submit(
990
+ fn=responder_pergunta,
991
+ inputs=[pergunta_input, chatbot, api_key_input, modelo_selector],
992
+ outputs=[chatbot, pergunta_input],
993
+ )
994
+ btn_limpar.click(fn=limpar_chat, outputs=[chatbot, pergunta_input])
995
+ perguntas_dropdown.change(fn=perguntas_rapidas, inputs=perguntas_dropdown, outputs=pergunta_input)
996
+
997
+
998
+ if __name__ == "__main__":
999
+ demo.launch(
1000
+ css=CSS,
1001
+ theme=gr.themes.Base(),
1002
+ allowed_paths=[OUTPUT_DIR, BASE],
1003
+ )
emcurso.csv ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ EM CURSO,1,01.1 SURVEY EM AGENDAMENTO
2
+ EM CURSO,1,01.2 SURVEY PENDENTE CLIENTE
3
+ EM CURSO,1,01.3 SURVEY EM CURSO
4
+ EM CURSO,1,02.1 PROJETO PENDENTE CLIENTE
5
+ EM CURSO,1,02.2 PROJETO POR ADJUDICAR
6
+ EM CURSO,1,02.3 PROJETO EM CURSO
7
+ EM CURSO,1,03 POR INICIAR CQ
8
+ EM CURSO,1,03.1 CQ EM CURSO
9
+ EM CURSO,1,03.2 CQ TERMINADO
10
+ EM CURSO,1,03.3 CQ SOGETREL
11
+ EM CURSO,1,04 PRE VALIDA��O PROJETO
12
+ EM CURSO,1,05 SUIVI PROJETO
13
+
emcurso_d.csv ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TIPOS;SLA [dias];< 50 % [uni]; 50 % < X < 75 % [uni];75 % < X < 100 % [uni];> 100 % [uni];TOTAL
2
+ ART 2 3;30;;;;;
3
+ RAMI;30;;;;;
4
+ CUIVRE;30;;;;;
5
+ R�COLEMENTS;0;;;;;
6
+ PAR;10;;;;;
7
+ FIBRE;30;;;;;
8
+ RACCO;30;;;;;
9
+ IMMO;30;;;;;
10
+ DESSAT;10;;;;;
11
+ DISSIM - POI1;30;;;;;
12
+ DISSIM - POI2;15;;;;;
13
+ DISSIM - POI3;15;;;;;
14
+ DISSIM;30;;;;;
emcursoprojecto.csv ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ EM CURSO PROJETO;01 POR INICIAR SURVEY
2
+ EM CURSO PROJETO;01.1 SURVEY EM AGENDAMENTO
3
+ EM CURSO PROJETO;01.2 SURVEY PENDENTE CLIENTE
4
+ EM CURSO PROJETO;01.3 SURVEY EM CURSO
5
+ EM CURSO PROJETO;02 POR INICAR PROJETO
6
+ EM CURSO PROJETO;02.1 PROJETO PENDENTE CLIENTE
7
+ EM CURSO PROJETO;02.2 PROJETO POR ADJUDICAR
8
+ EM CURSO PROJETO;02.3 PROJETO EM CURSO
9
+ EM CURSO PROJETO;03 POR INICIAR CQ
10
+ EM CURSO PROJETO;03.1 CQ EM CURSO
11
+ EM CURSO PROJETO;03.2 CQ TERMINADO
12
+ EM CURSO PROJETO;03.3 CQ SOGETREL
13
+ EM CURSO PROJETO;04 PRE VALIDA��O PROJETO
14
+ EM CURSO PROJETO;05 SUIVI PROJETO
emcursoprojecto_d.csv ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TIPOS;SLA [dias];< 50 % [uni]; 50 % < X < 75 % [uni];75 % < X < 100 % [uni];> 100 % [uni];TOTAL
2
+ ART 2 3;30;;;;;
3
+ RAMI;30;;;;;
4
+ CUIVRE;30;;;;;
5
+ R�COLEMENTS;0;;;;;
6
+ PAR;10;;;;;
7
+ FIBRE;30;;;;;
8
+ RACCO;30;;;;;
9
+ IMMO;30;;;;;
10
+ DESSAT;10;;;;;
11
+ DISSIM - POI1;30;;;;;
12
+ DISSIM - POI2;15;;;;;
13
+ DISSIM - POI3;15;;;;;
14
+ DISSIM;30;;;;;
global.csv ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SITUA��O GLOBAL;STATUS
2
+ GLOBAL;01 POR INICIAR SURVEY
3
+ GLOBAL;01.1 SURVEY EM AGENDAMENTO
4
+ GLOBAL;01.2 SURVEY PENDENTE CLIENTE
5
+ GLOBAL;01.3 SURVEY EM CURSO
6
+ GLOBAL;01.4 SURVEY CANCELADO
7
+ GLOBAL;02 POR INICAR PROJETO
8
+ GLOBAL;02.1 PROJETO PENDENTE CLIENTE
9
+ GLOBAL;02.2 PROJETO POR ADJUDICAR
10
+ GLOBAL;02.3 PROJETO EM CURSO
11
+ GLOBAL;03 POR INICIAR CQ
12
+ GLOBAL;03.1 CQ EM CURSO
13
+ GLOBAL;03.2 CQ TERMINADO
14
+ GLOBAL;03.3 CQ SOGETREL
15
+ GLOBAL;04 PRE VALIDA��O PROJETO
16
+ GLOBAL;05 SUIVI PROJETO
17
+ GLOBAL;06 POR INICIAR LICENCIAMENTOS
18
+ GLOBAL;06.1 LICENCIAMENTO POR ADJUDICAR
19
+ GLOBAL;06.2 AGUARDA DEVIS
20
+ GLOBAL;06.3 DEVIS OK
21
+ GLOBAL;06.4 AGUARDA PMV+DT
22
+ GLOBAL;06.5 PMV + DT OK
23
+ GLOBAL;06.6 AGUARDA CRVT
24
+ GLOBAL;06.7 CRVT OK
25
+ GLOBAL;07 VALIDA��O ORANGE
26
+ GLOBAL;08 PROJETO VALIDADO
27
+ GLOBAL;09 TRABALHOS EM CURSO
28
+ GLOBAL;10 TRABALHOS TERMINADOS
29
+ GLOBAL;11 POR INICIAR CADASTRO
30
+ GLOBAL;11.1 AGUARDA RT
31
+ GLOBAL;11.2 CADASTRO POR ADJUDICAR
32
+ GLOBAL;11.3 CADASTRO EM CURSO
33
+ GLOBAL;11.4 CADASTRO TERMINADO
34
+ GLOBAL;11.5 CADASTRO VALIDADO
35
+ GLOBAL;12 CANCELADO
36
+ GLOBAL;13 FATURADO
37
+ GLOBAL;14 DOSSIER CONCLUIDO
global_d.csv ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TIPOS;SLA [dias];< 50 % [uni]; 50 % < X < 75 % [uni];75 % < X < 100 % [uni];> 100 % [uni];TOTAL
2
+ ART 2 3;30;;;;;
3
+ RAMI;30;;;;;
4
+ CUIVRE;30;;;;;
5
+ R�COLEMENTS;0;;;;;
6
+ PAR;10;;;;;
7
+ FIBRE;30;;;;;
8
+ RACCO;30;;;;;
9
+ IMMO;30;;;;;
10
+ DESSAT;10;;;;;
11
+ DISSIM - POI1;30;;;;;
12
+ DISSIM - POI2;15;;;;;
13
+ DISSIM - POI3;15;;;;;
14
+ DISSIM;30;;;;;
licenciamento.csv ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ LICENCIAMENTO;06 POR INICIAR LICENCIAMENTOS
2
+ LICENCIAMENTO;06.1 LICENCIAMENTO POR ADJUDICAR
3
+ LICENCIAMENTO;06.2 AGUARDA DEVIS
4
+ LICENCIAMENTO;06.3 DEVIS OK
5
+ LICENCIAMENTO;06.4 AGUARDA PMV+DT
6
+ LICENCIAMENTO;06.5 PMV + DT OK
7
+ LICENCIAMENTO;06.6 AGUARDA CRVT
8
+ LICENCIAMENTO;06.7 CRVT OK
9
+ LICENCIAMENTO;06.7 CRVT OK
licenciamento_d.csv ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TIPOS;SLA [dias];< 50 % [uni]; 50 % < X < 75 % [uni];75 % < X < 100 % [uni];> 100 % [uni];TOTAL
2
+ ART 2 3;30;;;;;
3
+ RAMI;30;;;;;
4
+ CUIVRE;30;;;;;
5
+ R�COLEMENTS;0;;;;;
6
+ PAR;10;;;;;
7
+ FIBRE;30;;;;;
8
+ RACCO;30;;;;;
9
+ IMMO;30;;;;;
10
+ DESSAT;10;;;;;
11
+ DISSIM - POI1;30;;;;;
12
+ DISSIM - POI2;15;;;;;
13
+ DISSIM - POI3;15;;;;;
14
+ DISSIM;30;;;;;
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ pandas
3
+ numpy
4
+ openai
tarefasss_datas_corrigidas_final.csv ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SUB-CIP;PROJETO;DESIGNA??O DO PROJETO;TIPO;RB STATUS;COLABORADOR;OBSERVA??ES;DATA_ADJ_CLIENTE;DATA_LIMITE_SLA;TEMPO_EXECU������O_[DIAS];% SLA;DATA DE ENTREGA V0;DATA 1? RE-ENTREGA V1;DATA 2? RE-ENTREGA V2;DATA 3? RE-ENTREGA V3;DATA DA VALIDA??O;Qtd de Vers?es;Taxa de Rejei??o VERS?ES;Taxa de Aceita??o VERS?ES;Qtd de Revis?es;?ndice de Qualidade;COLABORADOR PROD.;DATA INICIO;DATA FIM;COLABORADOR QC;DATA INICIO_1;DATA FIM_2;% ERROS VISUAIS;% ERROS MACRO,;TAXA APROVA??O INTERNA;COLABORADOR PROD._3;DATA INICIO_4;DATA FIM_5;COLABORADOR QC_6;DATA INICIO_7;DATA FIM_8;% ERROS VISUAIS_9;% ERROS MACRO,_10;TAXA ERRO GLOBAL;COLABORADOR PROD._11;DATA INICIO_12;DATA FIM_13;COLABORADOR QC_14;DATA INICIO_15;DATA FIM_16;% ERROS VISUAIS_17;% ERROS MACRO,_18;TAXA ERRO GLOBAL_19;Resposta Cliente;DATA ;COLABORADOR PROD._20;DATA INICIO_21;DATA FIM_22;COLABORADOR QC_23;DATA INICIO_24;DATA FIM_25;% ERROS VISUAIS_26;% ERROS MACRO,_27;TAXA ERRO GLOBAL_28;COLABORADOR PROD._29;DATA INICIO_30;DATA FIM_31;COLABORADOR QC_32;DATA INICIO_33;DATA FIM_34;% ERROS VISUAIS_35;% ERROS MACRO,_36;TAXA ERRO GLOBAL_37;COLABORADOR PROD._38;DATA INICIO_39;DATA FIM_40;COLABORADOR QC_41;DATA INICIO_42;DATA FIM_43;% ERROS VISUAIS_44;% ERROS MACRO,_45;TAXA ERRO GLOBAL_46;Resposta Cliente_47;DATE;COLABORADOR PROD._48;DATA INICIO_49;DATA FIM_50;COLABORADOR QC_51;DATA INICIO_52;DATA FIM_53;% ERROS VISUAIS_54;% ERROS MACRO,_55;TAXA ERRO GLOBAL_56;COLABORADOR PROD._57;DATA INICIO_58;DATA FIM_59;COLABORADOR QC_60;DATA INICIO_61;DATA FIM_62;% ERROS VISUAIS_63;% ERROS MACRO,_64;TAXA ERRO GLOBAL_65;DESIGN BY;START DATE;COMPLETE DATE;QC DONE BY;START DATE_66;COMPLETE DATE_67;% ERROS VISUAIS_68;% ERROS MACRO,_69;TAXA ERRO GLOBAL_70;Resposta Cliente_71;DATE_72;COLABORADOR PROD._73;DATA INICIO_74;DATA FIM_75;COLABORADOR QC_76;DATA INICIO_77;DATA FIM_78;% ERROS VISUAIS_79;% ERROS MACRO,_80;TAXA ERRO GLOBAL_81;COLABORADOR PROD._82;DATA INICIO_83;DATA FIM_84;COLABORADOR QC_85;DATA INICIO_86;DATA FIM_87;% ERROS VISUAIS_88;% ERROS MACRO,_89;TAXA ERRO GLOBAL_90;DESIGN BY_91;START DATE_92;COMPLETE DATE_93;QC DONE BY_94;START DATE_95;COMPLETE DATE_96;% ERROS VISUAIS_97;% ERROS MACRO,_98;TAXA ERRO GLOBAL_99;Resposta Cliente_100;DATE_101
2
+ RBPT.2026.006.001;PON600009;RF, DF, FU - 376 FO TVX NON FACT;CUIVRE;03 POR INICIAR CQ;BENCOST;"DRE: 19/03 // RECEBIDO DA BENCOST A 26/02
3
+ ADJUDICADO � BENCOST A 23/02";02/02/2026;04/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4
+ RBPT.2026.006.002;BES600083;L, AO, 9I - 520_CM FIBRE PIM DA IMMO;RAMI;02.3 PROJETO EM CURSO;DIOGO SANTOS;DRE:17/03 // FAZER CRVT;02/02/2026;04/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
5
+ RBPT.2026.006.003;SCX600031;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;03.2 CQ TERMINADO;BENCOST;"DRE: 16/03 // RECEBIDO DA BENCOST A 24/02
6
+ ADJUDICADO � BENCOST A 23/02";03/02/2026;05/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
7
+ RBPT.2026.006.004;PON600013;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;"DRE: 20/03 // D�VIDA CARLOS
8
+ ADJUDICADO � BENCOST A 23/02";06/02/2026;08/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
9
+ RBPT.2026.006.005;STR600078;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE: 25/03 // ADJUDICADO � BENCOST A 26/02;06/02/2026;08/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
10
+ RBPT.2026.006.006;PON600014;RF, DF, FU - 376 FO TVX NON FACT;CUIVRE;03.2 CQ TERMINADO;BENCOST;"DRE: 18/03 // RECEBIDO DA BENCOST A 24/02
11
+ ADJUDICADO � BENCOST A 23/02";09/02/2026;11/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
12
+ RBPT.2026.006.007;SCX600033;RF, DF, FV - 376 FO TVX FACT;CUIVRE;02.3 PROJETO EM CURSO;BENCOST;DRE:20/03 // ADJUDICADO � BENCOST A 23/02;09/02/2026;11/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
13
+ RBPT.2026.006.008;STR600080 ;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE: 26/03;10/02/2026;12/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
14
+ RBPT.2026.006.009;STR600079 ;" L, CU, CL - 011 CU REHAUSSE TRAPPES";ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE: 26/03;10/02/2026;12/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
15
+ RBPT.2026.006.010;BES600112;RF, DF, FV - 376 FO TVX FACT;CUIVRE;01.2 SURVEY PENDENTE CLIENTE;;"FOMOS CONTACTADOS PELA SENHORA, QUE MANDOU FOTOS A 23/02
16
+ LIGAR A 23/02 ";13/02/2026;15/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
17
+ RBPT.2026.006.011;PON600023 ;RF, DF, FV - 376 FO TVX FACT;CUIVRE;03 POR INICIAR CQ;BENCOST;DRE: 20/03 // ADJUDICADO � BENCOST A 23/02;13/02/2026;15/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
18
+ RBPT.2026.006.012;LUN500077 ;L, AO, 9J - 070 TVX INFRA SITE CLIENT E;CUIVRE;03 POR INICIAR CQ;BENCOST;"DRE: 19/03 // RECEBIDO DA BENCOST A 02/03
19
+ ADJUDICADO � BENCOST A 23/02";13/02/2026;15/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
20
+ RBPT.2026.006.013;VAN600007;RF, DF, FV - 376 FO TVX FACT;CUIVRE;03 POR INICIAR CQ;BENCOST;"DRE: 25/03 // RECEBIDO DA BENCOST A 02/03
21
+ ADJUDICADO � BENCOST A 26/02";13/02/2026;15/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
22
+ RBPT.2026.006.014;BAR600041 ;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;01.3 SURVEY EM CURSO;;DRE: 27/03 // Vincent Schmitt 0667309869, podemos ir ao local sem agendamento, os trabalhos s� v�o come�ar na semana 11;13/02/2026;15/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
23
+ RBPT.2026.006.015;ALL600112 ;RF, DF, FU - 376 FO TVX NON FACT;CUIVRE;02.3 PROJETO EM CURSO;BENCOST;DRE: 23/02 // ADJUDICADO � BENCOST A 26/02;13/02/2026;15/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24
+ RBPT.2026.006.016;MTZ600072 ;L, AO, RY - 070 CU NON FACTURE DPCMT OUVRAGE;CUIVRE;02.3 PROJETO EM CURSO;BENCOST;DRE: 24/03 // ADJUDICADO � BENCOST A 26/02;13/02/2026;15/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25
+ RBPT.2026.006.017;PAM500021 ;L, AO, IU - #166#CMF COLONNE MONTANTE FIBRE;IMMO;02.3 PROJETO EM CURSO;BENCOST;"DRE: 26/03 // Cliente: 1� IDA - CRVT FEITO+ FOTOS E DATA DE TRAVAUX (2� E/OU 3� IDA, CRVT EM CADA IDA)
26
+ ADJUDICADO � BENCOST A 26/02";13/02/2026;15/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
27
+ RBPT.2026.006.018;BES600115 ;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;03.2 CQ TERMINADO;BENCOST;"DRE: 19/03 // RECEBIDO DA BENCOST A 24/02
28
+ ADJUDICADO � BENCOST A 23/02";16/02/2026;18/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
29
+ RBPT.2026.006.019;BAR400128;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE: 27/03 // URGENTE;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
30
+ RBPT.2026.006.020;BAR500078;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:27/03;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
31
+ RBPT.2026.006.021;BAR500143;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:27/03;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
32
+ RBPT.2026.006.022;LUN500074;L, CU, BJ - 011 CU COORDIN RECURRENT;ART 2 3;01.2 SURVEY PENDENTE CLIENTE;;PEDIDO PLANOS A 19/02 - DRE:19/03;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
33
+ RBPT.2026.006.023;MAX500180;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:26/03 // ADJUDICADO � BENCOST A 26/02;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
34
+ RBPT.2026.006.024;MAX600025;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:26/03 // ADJUDICADO � A 26/02;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
35
+ RBPT.2026.006.025;PAM500025;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:26/03 // PRIORIT�RIO // ADJUDICADO � BENCOST A 26/02;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
36
+ RBPT.2026.006.026;PAM500091;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;01.1 SURVEY EM AGENDAMENTO;;;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
37
+ RBPT.2026.006.027;PAM500107;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:26/03 // ADJUDICADO � BENCOST A 26/02;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
38
+ RBPT.2026.006.028;VAN400424;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:26/03 // ADJUDICADO � BENCOST A 26/02;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
39
+ RBPT.2026.006.029;VAN500356;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:27/03;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
40
+ RBPT.2026.006.030;VAN500427;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:23/03 // ADJUDICADO � BENCOST A 26/02;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
41
+ RBPT.2026.006.031;VER500025;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:24/03 // ADJUDICADO � BENCOST A 26/02;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
42
+ RBPT.2026.006.032;VER500028;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:26/03 // ADJUDICADO � BENCOST A 26/02;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
43
+ RBPT.2026.006.033;VER500057;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;02.3 PROJETO EM CURSO;BENCOST;DRE:26/03 // ADJUDICADO � BENCOST A 26/02;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
44
+ RBPT.2026.006.034;LUN600007;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;01.2 SURVEY PENDENTE CLIENTE;;PEDIDO PLANOS A 20/02 - DRE:20/03;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
45
+ RBPT.2026.006.035;LUN600009;L, CU, CH - 011 CU COORD ART 323-25;ART 2 3;01.2 SURVEY PENDENTE CLIENTE;;PEDIDO PLANOS A 20/02 - DRE:05/03;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
46
+ RBPT.2026.006.036;MTZ600223;L, AO, RY - 070 CU NON FACTURE DPCMT OUVRAGE;CUIVRE;01.2 SURVEY PENDENTE CLIENTE;;DRE: 27/02 //PEDIDO O CONTACTO A 20/02 PARA FAZERMOS VT // M�RIO TIROU AS FOTOS A 24/02;19/02/2026;21/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
47
+ RBPT.2026.006.037;SCX600045 ;RF, DF, FV - 376 FO TVX FACT;CUIVRE;01 POR INICIAR SURVEY;;;23/02/2026;25/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
48
+ RBPT.2026.006.038;ALL600164 ;RF, DF, FV - 376 FO TVX FACT;CUIVRE;01 POR INICIAR SURVEY;;;23/02/2026;25/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
49
+ RBPT.2026.006.039;SCX600051 ;RF, DF, FV - 376 FO TVX FACT;CUIVRE;01 POR INICIAR SURVEY;;;23/02/2026;25/03/2026;;0;;;;;;;;;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
validado.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ FINALIZADO;08 PROJETO VALIDADO
2
+ FINALIZADO;11.5 CADASTRO VALIDADO
3
+ FINALIZADO;13 FATURADO
validado_d.csv ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TIPOS;SLA [dias];< 50 % [uni]; 50 % < X < 75 % [uni];75 % < X < 100 % [uni];> 100 % [uni];TOTAL
2
+ ART 2 3;30;;;;;
3
+ RAMI;30;;;;;
4
+ CUIVRE;30;;;;;
5
+ R�COLEMENTS;0;;;;;
6
+ PAR;10;;;;;
7
+ FIBRE;30;;;;;
8
+ RACCO;30;;;;;
9
+ IMMO;30;;;;;
10
+ DESSAT;10;;;;;
11
+ DISSIM - POI1;30;;;;;
12
+ DISSIM - POI2;15;;;;;
13
+ DISSIM - POI3;15;;;;;
14
+ DISSIM;30;;;;;