Radioterapia-AI commited on
Commit
38ee85e
·
verified ·
1 Parent(s): ce428cb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -17
app.py CHANGED
@@ -596,9 +596,9 @@ def gerar_diagrama_png(tipo, conteudo, titulo, pal, context="annex"):
596
  )
597
 
598
  if context == "inline" and not has_decision:
599
- # ── INLINE TIPO 1: Linear simples, max 4 etapas ──
600
- etapas = etapas_raw[:4]
601
- dot.attr(rankdir='LR', size='8,1.5!')
602
  for i, et in enumerate(etapas):
603
  txt, frm = _parse_etapa(et, i, len(etapas))
604
  st = {**(node_style if i % 2 == 0 else node_light), 'shape': frm}
@@ -627,8 +627,8 @@ def gerar_diagrama_png(tipo, conteudo, titulo, pal, context="annex"):
627
  # Edges: 0→1, 1→2, 2→3(left), 2→4(right)
628
  if len(etapas) >= 2: dot.edge('n0', 'n1', **edge_style)
629
  if len(etapas) >= 3: dot.edge('n1', 'n2', **edge_style)
630
- if len(etapas) >= 4: dot.edge('n2', 'n3', **edge_style, label=' Sim', fontsize='9', fontcolor=f'#{pal["primary"]}')
631
- if len(etapas) >= 5: dot.edge('n2', 'n4', **edge_style, label=' N\u00e3o', fontsize='9', fontcolor=f'#{pal["primary"]}')
632
  # Ranks
633
  dot.body.append(' { rank=same; n0 n1 }')
634
  if len(etapas) >= 4:
@@ -663,10 +663,10 @@ def gerar_diagrama_png(tipo, conteudo, titulo, pal, context="annex"):
663
  right_idx = d1 + 2 if d1 + 2 < n else None
664
  if left_idx is not None:
665
  dot.edge(f'n{d1}', f'n{left_idx}', **edge_style,
666
- label=' Sim', fontsize='9', fontcolor=f'#{pal["primary"]}')
667
  if right_idx is not None:
668
  dot.edge(f'n{d1}', f'n{right_idx}', **edge_style,
669
- label=' N\u00e3o', fontsize='9', fontcolor=f'#{pal["primary"]}')
670
  # Ranks: centro, split esquerdo/direito
671
  if left_idx and right_idx:
672
  dot.body.append(f' {{ rank=same; n{left_idx} n{right_idx} }}')
@@ -687,9 +687,9 @@ def gerar_diagrama_png(tipo, conteudo, titulo, pal, context="annex"):
687
  # Segundo split
688
  dot.edge(f'n{prev_right}', f'n{ri}', **edge_style)
689
  dot.edge(f'n{ri}', f'n{ri+1}', **edge_style,
690
- label=' Sim', fontsize='9', fontcolor=f'#{pal["primary"]}')
691
- dot.edge(f'n{ri}', f'n{ri+2}', **edge_style,
692
  label=' N\u00e3o', fontsize='9', fontcolor=f'#{pal["primary"]}')
 
 
693
  dot.body.append(f' {{ rank=same; n{ri+1} n{ri+2} }}')
694
  break
695
  else:
@@ -897,7 +897,7 @@ def render_annex(doc, anexo, num, pal):
897
  _fill_cell_blank_lines(bc, max(1, 12 - len(itens)))
898
 
899
  # === ESCALA / TABELA ===
900
- elif tipo in ("escala", "tabela"):
901
  headers = anexo.get("colunas", [])
902
  rows_data = conteudo if isinstance(conteudo, list) else anexo.get("linhas", [])
903
  # Handle Gemini format: conteudo = [{colunas: [...], linhas: [...]}]
@@ -1104,6 +1104,51 @@ def render_annex(doc, anexo, num, pal):
1104
  for ci2 in range(nc2): set_shading(it2.cell(ri2+1,ci2), pal["tertiary"])
1105
  _fill_cell_blank_lines(bc, 4)
1106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1107
  # === TEXTO / FALLBACK ===
1108
  else:
1109
  if isinstance(conteudo, str):
@@ -1185,7 +1230,15 @@ def gerar_pop_docx(json_str, primary_color=None, logo_bytes=None, palette_overri
1185
  sn += 1; add_h1(doc, sn, "GERENCIAMENTO DE RISCO E PONTOS CRÍTICOS", pal)
1186
  riscos = secoes.get("riscos", {})
1187
  add_h2(doc, f"{sn}.1", "Riscos Assistenciais", pal)
1188
- for it in riscos.get("assistenciais", []):
 
 
 
 
 
 
 
 
1189
  if isinstance(it, dict): add_risk(doc, it.get("risco",""), it.get("barreira",""), pal)
1190
  else: add_bullet(doc, str(it), pal=pal)
1191
  add_h2(doc, f"{sn}.2", "Plano de Contingência", pal)
@@ -1209,7 +1262,7 @@ def gerar_pop_docx(json_str, primary_color=None, logo_bytes=None, palette_overri
1209
  pt = cc.paragraphs[0]
1210
  pt.paragraph_format.space_after = Pt(8)
1211
  fmt(pt, "\u26a0 ATEN\u00c7\u00c3O \u2014 Plano de Conting\u00eancia", bold=True, size=TAM_CORPO, color=pal["risk_red"])
1212
- # Itens numerados com seta
1213
  for idx, item_c in enumerate(cont_items, 1):
1214
  pi = cc.add_paragraph()
1215
  pi.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
@@ -1217,9 +1270,26 @@ def gerar_pop_docx(json_str, primary_color=None, logo_bytes=None, palette_overri
1217
  pi.paragraph_format.left_indent = Cm(0.5)
1218
  pi.paragraph_format.space_before = Pt(3)
1219
  pi.paragraph_format.space_after = Pt(3)
 
1220
  fmt(pi, f"{idx}. ", bold=True, size=TAM_SMALL, color=pal["risk_red"])
1221
- # Texto compreservado
1222
- fmt_with_meta_badges(pi, str(item_c), size=TAM_SMALL, pal=pal)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1223
 
1224
  sn += 1; add_h1(doc, sn, "REGISTROS DA QUALIDADE (EVIDÊNCIAS)", pal)
1225
  add_body(doc, "A execução deste procedimento e eventuais intercorrências devem ser "
@@ -1314,9 +1384,14 @@ def processar(json_text, json_file, c_pri, c_sec, c_ter, c_zeb, logo_file):
1314
  if not json_str.strip():
1315
  return None, "\u26a0\ufe0f Insira o c\u00f3digo.", None
1316
  jc = json_str.strip()
1317
- if jc.startswith("```"): jc = jc.split("\n",1)[1] if "\n" in jc else jc[3:]
1318
- if jc.endswith("```"): jc = jc[:-3]
1319
- jc = jc.strip()
 
 
 
 
 
1320
  ok, msg, data = validar(jc)
1321
  if not ok: return None, msg, None
1322
  pri = parse_color_input(c_pri) if c_pri else DEFAULT_UI_COLOR
 
596
  )
597
 
598
  if context == "inline" and not has_decision:
599
+ # ── INLINE TIPO 1: Linear simples, max 5 etapas ──
600
+ etapas = etapas_raw[:5]
601
+ dot.attr(rankdir='LR', size='8,2!')
602
  for i, et in enumerate(etapas):
603
  txt, frm = _parse_etapa(et, i, len(etapas))
604
  st = {**(node_style if i % 2 == 0 else node_light), 'shape': frm}
 
627
  # Edges: 0→1, 1→2, 2→3(left), 2→4(right)
628
  if len(etapas) >= 2: dot.edge('n0', 'n1', **edge_style)
629
  if len(etapas) >= 3: dot.edge('n1', 'n2', **edge_style)
630
+ if len(etapas) >= 4: dot.edge('n2', 'n3', **edge_style, label=' N\u00e3o', fontsize='9', fontcolor=f'#{pal["primary"]}')
631
+ if len(etapas) >= 5: dot.edge('n2', 'n4', **edge_style, label=' Sim', fontsize='9', fontcolor=f'#{pal["primary"]}')
632
  # Ranks
633
  dot.body.append(' { rank=same; n0 n1 }')
634
  if len(etapas) >= 4:
 
663
  right_idx = d1 + 2 if d1 + 2 < n else None
664
  if left_idx is not None:
665
  dot.edge(f'n{d1}', f'n{left_idx}', **edge_style,
666
+ label=' N\u00e3o', fontsize='9', fontcolor=f'#{pal["primary"]}')
667
  if right_idx is not None:
668
  dot.edge(f'n{d1}', f'n{right_idx}', **edge_style,
669
+ label=' Sim', fontsize='9', fontcolor=f'#{pal["primary"]}')
670
  # Ranks: centro, split esquerdo/direito
671
  if left_idx and right_idx:
672
  dot.body.append(f' {{ rank=same; n{left_idx} n{right_idx} }}')
 
687
  # Segundo split
688
  dot.edge(f'n{prev_right}', f'n{ri}', **edge_style)
689
  dot.edge(f'n{ri}', f'n{ri+1}', **edge_style,
 
 
690
  label=' N\u00e3o', fontsize='9', fontcolor=f'#{pal["primary"]}')
691
+ dot.edge(f'n{ri}', f'n{ri+2}', **edge_style,
692
+ label=' Sim', fontsize='9', fontcolor=f'#{pal["primary"]}')
693
  dot.body.append(f' {{ rank=same; n{ri+1} n{ri+2} }}')
694
  break
695
  else:
 
897
  _fill_cell_blank_lines(bc, max(1, 12 - len(itens)))
898
 
899
  # === ESCALA / TABELA ===
900
+ elif tipo in ("escala", "tabela", "referencia", "referência"):
901
  headers = anexo.get("colunas", [])
902
  rows_data = conteudo if isinstance(conteudo, list) else anexo.get("linhas", [])
903
  # Handle Gemini format: conteudo = [{colunas: [...], linhas: [...]}]
 
1104
  for ci2 in range(nc2): set_shading(it2.cell(ri2+1,ci2), pal["tertiary"])
1105
  _fill_cell_blank_lines(bc, 4)
1106
 
1107
+ # === GLOSSÁRIO / SÍMBOLOS ===
1108
+ elif tipo in ("glossario", "glossário", "simbolos", "símbolos", "glossario_visual"):
1109
+ bc.paragraphs[0].text = ""
1110
+ if isinstance(conteudo, list):
1111
+ if len(conteudo) > 0 and isinstance(conteudo[0], dict):
1112
+ # Formato tabela: [{colunas:..., linhas:...}] ou [{simbolo:..., nome:...}]
1113
+ tbl_obj = conteudo[0]
1114
+ if "colunas" in tbl_obj:
1115
+ headers = tbl_obj.get("colunas", [])
1116
+ rows_data = tbl_obj.get("linhas", [])
1117
+ if headers and rows_data:
1118
+ nc = len(headers); cw_each = (LARGURA_DXA - 900) // nc
1119
+ inner = bc.add_table(rows=1+len(rows_data), cols=nc)
1120
+ for i, h in enumerate(headers):
1121
+ c = inner.cell(0, i); set_shading(c, pal["primary"])
1122
+ set_width(c, cw_each); set_valign(c); set_margins(c, 40, 40, 60, 60)
1123
+ set_borders(c,(pal["primary"],"4"),(pal["primary"],"4"),(pal["primary"],"4"),(pal["primary"],"4"))
1124
+ c.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
1125
+ fmt(c.paragraphs[0], h, bold=True, size=TAM_SMALL, color="FFFFFF")
1126
+ for ri, rd in enumerate(rows_data):
1127
+ for ci, val in enumerate(rd):
1128
+ c = inner.cell(ri+1, ci); set_width(c, cw_each); set_valign(c)
1129
+ set_margins(c, 30, 30, 60, 60)
1130
+ set_borders(c,(pal["gray_border"],"1"),(pal["gray_border"],"1"),
1131
+ (pal["gray_border"],"1"),(pal["gray_border"],"1"))
1132
+ c.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
1133
+ fmt(c.paragraphs[0], str(val), size=TAM_SMALL, color="1A1A1A")
1134
+ if ri % 2 == 0:
1135
+ for ci in range(nc): set_shading(inner.cell(ri+1, ci), pal["tertiary"])
1136
+ else:
1137
+ # Formato dict simples: [{simbolo: "⚠", nome: "Atenção"}, ...]
1138
+ for item in conteudo:
1139
+ pi = bc.add_paragraph()
1140
+ pi.paragraph_format.left_indent = Cm(0.5)
1141
+ simb = item.get("simbolo", item.get("icone", "●"))
1142
+ nome = item.get("nome", item.get("descricao", str(item)))
1143
+ fmt(pi, f"{simb} {nome}", size=TAM_CORPO, color="1A1A1A")
1144
+ else:
1145
+ # Formato lista simples de strings: "⚠ Atenção" (símbolo + 2 espaços + nome)
1146
+ for item in conteudo:
1147
+ pi = bc.add_paragraph()
1148
+ pi.paragraph_format.left_indent = Cm(0.5)
1149
+ fmt(pi, str(item), size=TAM_CORPO, color="1A1A1A")
1150
+ _fill_cell_blank_lines(bc, max(1, 10 - len(conteudo) if isinstance(conteudo, list) else 10))
1151
+
1152
  # === TEXTO / FALLBACK ===
1153
  else:
1154
  if isinstance(conteudo, str):
 
1230
  sn += 1; add_h1(doc, sn, "GERENCIAMENTO DE RISCO E PONTOS CRÍTICOS", pal)
1231
  riscos = secoes.get("riscos", {})
1232
  add_h2(doc, f"{sn}.1", "Riscos Assistenciais", pal)
1233
+ # Ordenar riscos: Crítico primeiro, Moderado depois, demais por último
1234
+ riscos_list = riscos.get("assistenciais", [])
1235
+ def _risk_sort_key(it):
1236
+ r = it.get("risco","").lower() if isinstance(it, dict) else str(it).lower()
1237
+ if "crít" in r or "crit" in r: return 0
1238
+ if "moder" in r: return 1
1239
+ return 2
1240
+ riscos_list = sorted(riscos_list, key=_risk_sort_key)
1241
+ for it in riscos_list:
1242
  if isinstance(it, dict): add_risk(doc, it.get("risco",""), it.get("barreira",""), pal)
1243
  else: add_bullet(doc, str(it), pal=pal)
1244
  add_h2(doc, f"{sn}.2", "Plano de Contingência", pal)
 
1262
  pt = cc.paragraphs[0]
1263
  pt.paragraph_format.space_after = Pt(8)
1264
  fmt(pt, "\u26a0 ATEN\u00c7\u00c3O \u2014 Plano de Conting\u00eancia", bold=True, size=TAM_CORPO, color=pal["risk_red"])
1265
+ # Itens numerados: conceito bold + ":" + ações com seta
1266
  for idx, item_c in enumerate(cont_items, 1):
1267
  pi = cc.add_paragraph()
1268
  pi.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
 
1270
  pi.paragraph_format.left_indent = Cm(0.5)
1271
  pi.paragraph_format.space_before = Pt(3)
1272
  pi.paragraph_format.space_after = Pt(3)
1273
+ # Número bold + cor risk_red
1274
  fmt(pi, f"{idx}. ", bold=True, size=TAM_SMALL, color=pal["risk_red"])
1275
+ # Limpar texto: remover prefixos "" ou "1. " que o Gemini pode ter adicionado
1276
+ txt = str(item_c).strip()
1277
+ import re
1278
+ txt = re.sub(r'^\d+\.\s*', '', txt) # Remove "1. " prefix
1279
+ txt = re.sub(r'^[\u2192→]\s*', '', txt) # Remove "→ " prefix
1280
+ # Separar conceito (antes do primeiro ":") do resto
1281
+ if ':' in txt:
1282
+ conceito, resto = txt.split(':', 1)
1283
+ # Strip markdown bold markers **
1284
+ conceito = conceito.replace('**', '').strip()
1285
+ fmt(pi, conceito + ":", bold=True, size=TAM_SMALL, color="1A1A1A")
1286
+ # Formatar setas com espaçamento
1287
+ resto = resto.strip()
1288
+ # Normalizar setas: garantir " → " com espaços
1289
+ resto = re.sub(r'\s*[\u2192→]\s*', ' \u2192 ', resto)
1290
+ fmt_with_meta_badges(pi, " " + resto, size=TAM_SMALL, pal=pal)
1291
+ else:
1292
+ fmt_with_meta_badges(pi, txt, size=TAM_SMALL, pal=pal)
1293
 
1294
  sn += 1; add_h1(doc, sn, "REGISTROS DA QUALIDADE (EVIDÊNCIAS)", pal)
1295
  add_body(doc, "A execução deste procedimento e eventuais intercorrências devem ser "
 
1384
  if not json_str.strip():
1385
  return None, "\u26a0\ufe0f Insira o c\u00f3digo.", None
1386
  jc = json_str.strip()
1387
+ # Remover backticks de codeblock markdown (```json ... ```)
1388
+ if jc.startswith("```"):
1389
+ jc = jc.split("\n",1)[1] if "\n" in jc else jc[3:]
1390
+ jc = jc.strip()
1391
+ if jc.startswith("json"):
1392
+ jc = jc[4:].strip()
1393
+ if jc.endswith("```"):
1394
+ jc = jc[:-3].strip()
1395
  ok, msg, data = validar(jc)
1396
  if not ok: return None, msg, None
1397
  pri = parse_color_input(c_pri) if c_pri else DEFAULT_UI_COLOR