Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -15,19 +15,9 @@ import anthropic
|
|
| 15 |
from fastapi import FastAPI, HTTPException
|
| 16 |
from fastapi.middleware.cors import CORSMiddleware
|
| 17 |
from fastapi.responses import StreamingResponse
|
|
|
|
| 18 |
from pydantic import BaseModel
|
| 19 |
-
from
|
| 20 |
-
from reportlab.lib.pagesizes import A4
|
| 21 |
-
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
| 22 |
-
from reportlab.lib.units import mm
|
| 23 |
-
from reportlab.platypus import (
|
| 24 |
-
HRFlowable,
|
| 25 |
-
Paragraph,
|
| 26 |
-
SimpleDocTemplate,
|
| 27 |
-
Spacer,
|
| 28 |
-
Table,
|
| 29 |
-
TableStyle,
|
| 30 |
-
)
|
| 31 |
|
| 32 |
# ββ App Setup βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 33 |
app = FastAPI(
|
|
@@ -313,106 +303,72 @@ Root Causes:\n{root_causes}""",
|
|
| 313 |
|
| 314 |
# ββ PDF Generator βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 315 |
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
|
| 321 |
|
| 322 |
def build_pdf(analysis: FullAnalysis, name: str, role: str) -> bytes:
|
| 323 |
-
"""
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
rightMargin=24 * mm, leftMargin=24 * mm,
|
| 334 |
-
topMargin=24 * mm, bottomMargin=24 * mm,
|
| 335 |
-
)
|
| 336 |
-
styles = getSampleStyleSheet()
|
| 337 |
-
|
| 338 |
-
s_title = ParagraphStyle("t", parent=styles["Normal"],
|
| 339 |
-
fontSize=20, fontName="Helvetica-Bold", textColor=INK, spaceAfter=3)
|
| 340 |
-
s_meta = ParagraphStyle("m", parent=styles["Normal"],
|
| 341 |
-
fontSize=10, fontName="Helvetica", textColor=GRAY, spaceAfter=14)
|
| 342 |
-
s_section_head = ParagraphStyle("sh", parent=styles["Normal"],
|
| 343 |
-
fontSize=11, fontName="Helvetica-Bold", textColor=INK,
|
| 344 |
-
spaceBefore=20, spaceAfter=2)
|
| 345 |
-
s_section_num = ParagraphStyle("sn", parent=styles["Normal"],
|
| 346 |
-
fontSize=10, fontName="Helvetica", textColor=GRAY, spaceAfter=8)
|
| 347 |
-
s_h2 = ParagraphStyle("h2", parent=styles["Normal"],
|
| 348 |
-
fontSize=10, fontName="Helvetica-Bold", textColor=INK,
|
| 349 |
-
spaceBefore=10, spaceAfter=3)
|
| 350 |
-
s_h3 = ParagraphStyle("h3", parent=styles["Normal"],
|
| 351 |
-
fontSize=10, fontName="Helvetica-Bold", textColor=INK2,
|
| 352 |
-
spaceBefore=7, spaceAfter=2)
|
| 353 |
-
s_body = ParagraphStyle("b", parent=styles["Normal"],
|
| 354 |
-
fontSize=10, fontName="Helvetica", textColor=INK2,
|
| 355 |
-
leading=15, spaceAfter=3)
|
| 356 |
-
s_bullet = ParagraphStyle("bl", parent=styles["Normal"],
|
| 357 |
-
fontSize=10, fontName="Helvetica", textColor=INK2,
|
| 358 |
-
leading=15, spaceAfter=2, leftIndent=12, firstLineIndent=-12)
|
| 359 |
-
s_footer = ParagraphStyle("f", parent=styles["Normal"],
|
| 360 |
-
fontSize=8, fontName="Helvetica", textColor=GRAY, alignment=1)
|
| 361 |
-
|
| 362 |
-
SECTIONS = [
|
| 363 |
-
("Problem Analysis", analysis.problem_statement),
|
| 364 |
-
("Root Cause", analysis.root_causes),
|
| 365 |
-
("Solutions", analysis.solutions),
|
| 366 |
-
("Action Plan", analysis.action_plan),
|
| 367 |
-
("Reflection", analysis.thinking_feedback),
|
| 368 |
]
|
| 369 |
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
out.append(Paragraph(strip_md(line[4:]), s_h3))
|
| 380 |
-
elif line.startswith("- ") or line.startswith("* "):
|
| 381 |
-
out.append(Paragraph(f"β {strip_md(line[2:])}", s_bullet))
|
| 382 |
-
elif re.match(r"^\d+\.", line):
|
| 383 |
-
out.append(Paragraph(strip_md(line), s_body))
|
| 384 |
-
else:
|
| 385 |
-
out.append(Paragraph(strip_md(line), s_body))
|
| 386 |
-
return out
|
| 387 |
-
|
| 388 |
-
story = []
|
| 389 |
-
|
| 390 |
-
# Header
|
| 391 |
-
story.append(Paragraph("Problem Analysis", s_title))
|
| 392 |
-
story.append(Paragraph(
|
| 393 |
-
f"{name} Β· {datetime.now().strftime('%d %B %Y')}",
|
| 394 |
-
s_meta,
|
| 395 |
-
))
|
| 396 |
-
story.append(HRFlowable(width=W, thickness=0.5, color=LGRAY, spaceAfter=4))
|
| 397 |
-
|
| 398 |
-
# Sections
|
| 399 |
-
for i, (title, text) in enumerate(SECTIONS, 1):
|
| 400 |
-
story.append(Spacer(1, 4))
|
| 401 |
-
story.append(Paragraph(title, s_section_head))
|
| 402 |
-
story.append(HRFlowable(width=W, thickness=0.5, color=LGRAY, spaceAfter=6))
|
| 403 |
-
story += render_block(text)
|
| 404 |
-
|
| 405 |
-
# Footer
|
| 406 |
-
story.append(Spacer(1, 20))
|
| 407 |
-
story.append(HRFlowable(width=W, thickness=0.5, color=LGRAY, spaceAfter=6))
|
| 408 |
-
story.append(Paragraph(
|
| 409 |
-
f"Problem Solver Β· {datetime.now().strftime('%d %B %Y')}",
|
| 410 |
-
s_footer,
|
| 411 |
-
))
|
| 412 |
-
|
| 413 |
-
doc.build(story)
|
| 414 |
-
buf.seek(0)
|
| 415 |
-
return buf.read()
|
| 416 |
|
| 417 |
|
| 418 |
# ββ API Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 15 |
from fastapi import FastAPI, HTTPException
|
| 16 |
from fastapi.middleware.cors import CORSMiddleware
|
| 17 |
from fastapi.responses import StreamingResponse
|
| 18 |
+
from jinja2 import Template
|
| 19 |
from pydantic import BaseModel
|
| 20 |
+
from weasyprint import HTML
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
# ββ App Setup βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 23 |
app = FastAPI(
|
|
|
|
| 303 |
|
| 304 |
# ββ PDF Generator βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 305 |
|
| 306 |
+
TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), "report_template.html")
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def md_to_html(text: str) -> str:
|
| 310 |
+
"""Convert basic markdown to clean HTML for the PDF template."""
|
| 311 |
+
lines = text.splitlines()
|
| 312 |
+
html_parts = []
|
| 313 |
+
in_ul = False
|
| 314 |
+
|
| 315 |
+
for line in lines:
|
| 316 |
+
line = line.strip()
|
| 317 |
+
|
| 318 |
+
# Close open list if needed
|
| 319 |
+
if in_ul and not (line.startswith("- ") or line.startswith("* ")):
|
| 320 |
+
html_parts.append("</ul>")
|
| 321 |
+
in_ul = False
|
| 322 |
+
|
| 323 |
+
if not line:
|
| 324 |
+
continue
|
| 325 |
+
elif line.startswith("## "):
|
| 326 |
+
content = re.sub(r"\*\*(.*?)\*\*", r"<strong>\1</strong>", line[3:])
|
| 327 |
+
html_parts.append(f"<h2>{content}</h2>")
|
| 328 |
+
elif line.startswith("### "):
|
| 329 |
+
content = re.sub(r"\*\*(.*?)\*\*", r"<strong>\1</strong>", line[4:])
|
| 330 |
+
html_parts.append(f"<h3>{content}</h3>")
|
| 331 |
+
elif line.startswith("- ") or line.startswith("* "):
|
| 332 |
+
if not in_ul:
|
| 333 |
+
html_parts.append("<ul>")
|
| 334 |
+
in_ul = True
|
| 335 |
+
content = re.sub(r"\*\*(.*?)\*\*", r"<strong>\1</strong>", line[2:])
|
| 336 |
+
html_parts.append(f"<li>{content}</li>")
|
| 337 |
+
elif re.match(r"^\d+\.", line):
|
| 338 |
+
content = re.sub(r"\*\*(.*?)\*\*", r"<strong>\1</strong>", line)
|
| 339 |
+
html_parts.append(f"<p>{content}</p>")
|
| 340 |
+
else:
|
| 341 |
+
content = re.sub(r"\*\*(.*?)\*\*", r"<strong>\1</strong>", line)
|
| 342 |
+
html_parts.append(f"<p>{content}</p>")
|
| 343 |
+
|
| 344 |
+
if in_ul:
|
| 345 |
+
html_parts.append("</ul>")
|
| 346 |
+
|
| 347 |
+
return "\n".join(html_parts)
|
| 348 |
|
| 349 |
|
| 350 |
def build_pdf(analysis: FullAnalysis, name: str, role: str) -> bytes:
|
| 351 |
+
"""Render Jinja2 HTML template and convert to PDF via WeasyPrint."""
|
| 352 |
+
with open(TEMPLATE_PATH, "r", encoding="utf-8") as f:
|
| 353 |
+
template = Template(f.read())
|
| 354 |
+
|
| 355 |
+
sections = [
|
| 356 |
+
{"title": "Problem Analysis", "html": md_to_html(analysis.problem_statement)},
|
| 357 |
+
{"title": "Root Cause", "html": md_to_html(analysis.root_causes)},
|
| 358 |
+
{"title": "Solutions", "html": md_to_html(analysis.solutions)},
|
| 359 |
+
{"title": "Action Plan", "html": md_to_html(analysis.action_plan)},
|
| 360 |
+
{"title": "Reflection", "html": md_to_html(analysis.thinking_feedback)},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
]
|
| 362 |
|
| 363 |
+
html_str = template.render(
|
| 364 |
+
user_name=name,
|
| 365 |
+
date=datetime.now().strftime("%d %B %Y"),
|
| 366 |
+
sections=sections,
|
| 367 |
+
page_breaks={3, 4}, # start Solutions and Action Plan on new page
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
pdf_bytes = HTML(string=html_str, base_url=None).write_pdf()
|
| 371 |
+
return pdf_bytes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
|
| 373 |
|
| 374 |
# ββ API Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|