banao-tech commited on
Commit
4ff15ef
Β·
verified Β·
1 Parent(s): 14e8462

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +63 -107
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 reportlab.lib import colors
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
- def strip_md(text: str) -> str:
317
- text = re.sub(r"\*\*(.*?)\*\*", r"\1", text)
318
- text = re.sub(r"\*(.*?)\*", r"\1", text)
319
- return text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
 
322
  def build_pdf(analysis: FullAnalysis, name: str, role: str) -> bytes:
323
- """Clean black-and-white PDF report."""
324
- buf = io.BytesIO()
325
- INK = colors.HexColor("#111827")
326
- INK2 = colors.HexColor("#374151")
327
- GRAY = colors.HexColor("#6b7280")
328
- LGRAY = colors.HexColor("#d1d5db")
329
- W = A4[0] - 48 * mm
330
-
331
- doc = SimpleDocTemplate(
332
- buf, pagesize=A4,
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
- def render_block(md_text: str) -> list:
371
- out = []
372
- for line in md_text.splitlines():
373
- line = line.strip()
374
- if not line:
375
- out.append(Spacer(1, 3))
376
- elif line.startswith("## "):
377
- out.append(Paragraph(strip_md(line[3:]), s_h2))
378
- elif line.startswith("### "):
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 ─────────────────────────────────────────────────────────────