Spaces:
Running
Running
Update app.py
Browse files
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
|
| 600 |
-
etapas = etapas_raw[:
|
| 601 |
-
dot.attr(rankdir='LR', size='8,
|
| 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='
|
| 631 |
-
if len(etapas) >= 5: dot.edge('n2', 'n4', **edge_style, label='
|
| 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='
|
| 667 |
if right_idx is not None:
|
| 668 |
dot.edge(f'n{d1}', f'n{right_idx}', **edge_style,
|
| 669 |
-
label='
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 1222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1318 |
-
if jc.
|
| 1319 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|