Files
fourge-portal/docs/generate-checked-sites-audit-pdf.py
T
Krao Hasanee 04e0911e9f fix: crash bugs in TeamInvoices + faster load
Bug fixes:
- TeamInvoices: add useAuth import/currentUser (invoice-create crash)
- TeamInvoices: setChartYear -> setExportYear (year-dropdown crash)
- TeamDashboard: drop redundant setState-in-effect (cascading renders)
- Companies: delete dead _UnusedClientCompanies (illegal hook calls)
- annotate intentional empty PDF-fallback catches

Load speed:
- preconnect/dns-prefetch to Supabase origin
- lazy-load heic-to in Converters: page chunk 2737KB -> 9KB
- split recharts into its own 'charts' vendor chunk

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:59:11 -04:00

363 lines
10 KiB
Python

from pathlib import Path
import json
from reportlab.lib import colors
from reportlab.lib.enums import TA_LEFT
from reportlab.lib.pagesizes import landscape, legal
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import (
BaseDocTemplate,
Frame,
LongTable,
PageBreak,
PageTemplate,
Paragraph,
Spacer,
Table,
TableStyle,
)
DOCS_DIR = Path(__file__).resolve().parent
INPUT_PATH = DOCS_DIR / "checked-sites-revision-audit-2026-06-04.json"
OUTPUT_PATH = DOCS_DIR / "checked-sites-revision-audit-2026-06-04.pdf"
BG = colors.HexColor("#F5F1E8")
TEXT = colors.HexColor("#231D14")
MUTED = colors.HexColor("#6B6255")
ACCENT = colors.HexColor("#B8831F")
ACCENT_SOFT = colors.HexColor("#F1E3C2")
OK = colors.HexColor("#1F8A4C")
OK_SOFT = colors.HexColor("#E5F4EB")
WARN = colors.HexColor("#B45309")
WARN_SOFT = colors.HexColor("#F7E7D7")
REVIEW = colors.HexColor("#2563EB")
REVIEW_SOFT = colors.HexColor("#E6EEFF")
TODO = colors.HexColor("#7C3AED")
TODO_SOFT = colors.HexColor("#F0E8FF")
LINE = colors.HexColor("#D7CFC1")
def page_template(canvas, doc):
canvas.saveState()
canvas.setFillColor(BG)
canvas.rect(0, 0, doc.pagesize[0], doc.pagesize[1], fill=1, stroke=0)
canvas.setFillColor(MUTED)
canvas.setFont("Helvetica", 9)
canvas.drawRightString(doc.pagesize[0] - 36, 20, f"Page {doc.page}")
canvas.restoreState()
def pill(text, fg, bg):
return Paragraph(
(
f'<para alignment="center" backColor="{bg}" borderColor="{fg}" '
f'borderWidth="0.7" borderPadding="4" textColor="{fg}"><b>{escape(text)}</b></para>'
),
styles["pill"],
)
def escape(value):
return (
str(value or "")
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
)
def status_pill(value):
value = value or "—"
if value == "client_review":
return pill(value, REVIEW, REVIEW_SOFT)
if value == "client_approved":
return pill(value, OK, OK_SOFT)
if value == "not_started":
return pill(value, TODO, TODO_SOFT)
if value == "in_progress":
return pill(value, ACCENT, ACCENT_SOFT)
if value == "Yes":
return pill(value, OK, OK_SOFT)
if value == "No":
return pill(value, WARN, WARN_SOFT)
return pill(value, MUTED, colors.white)
def text_para(value, style_name="cell"):
return Paragraph(escape(value), styles[style_name])
def build_summary_cards(rows):
existing_rows = [row for row in rows if row.get("exists", True) is not False]
active_rows = [row for row in existing_rows if row.get("hasActiveRevision") == "Yes"]
billed_rows = [row for row in existing_rows if row.get("r00Billed") != "No"]
unbilled_rows = [row for row in existing_rows if row.get("r00Billed") == "No"]
cards = [
("Checked Sites", str(len(existing_rows)), "Sites found in the database from your checked list."),
("Active Revisions", str(len(active_rows)), "Checked sites that now have an active R01+."),
("R00 Billed", str(len(billed_rows)), "Checked sites where the new-book unit is already billed."),
("R00 Unbilled", str(len(unbilled_rows)), "Checked sites where R00 is done but not billed yet."),
]
card_rows = []
for label, value, subtext in cards:
card_rows.append(
Table(
[
[Paragraph(label.upper(), styles["card_label"])],
[Paragraph(value, styles["card_value"])],
[Paragraph(subtext, styles["card_sub"])],
],
colWidths=[3.0 * inch],
style=TableStyle(
[
("BACKGROUND", (0, 0), (-1, -1), colors.white),
("BOX", (0, 0), (-1, -1), 0.8, LINE),
("INNERPADDING", (0, 0), (-1, -1), 10),
("ROUNDEDCORNERS", [10, 10, 10, 10]),
]
),
)
)
return Table([card_rows], colWidths=[3.1 * inch] * 4, hAlign="LEFT")
def build_table(rows, title, intro):
flow = [Paragraph(title, styles["section_title"]), Paragraph(intro, styles["small"]), Spacer(1, 10)]
header = [
text_para("Site", "th"),
text_para("DB Title", "th"),
text_para("Internal #", "th"),
text_para("R00 Done", "th"),
text_para("R00 Billed", "th"),
text_para("Active R#", "th"),
text_para("Active Status", "th"),
text_para("Completed Revisions", "th"),
text_para("Billed Versions", "th"),
text_para("Version Summary", "th"),
]
data = [header]
for row in rows:
data.append(
[
text_para(row.get("site", ""), "cell_mono"),
text_para(row.get("dbTitle", "")),
text_para(row.get("internalTaskNumber", ""), "cell_mono"),
status_pill(row.get("r00Completed", "No")),
status_pill(row.get("r00Billed", "No")),
text_para(row.get("activeRevision", "—"), "cell_mono"),
status_pill(row.get("activeStatus", "—")),
text_para(row.get("completedRevisions", "—")),
text_para(row.get("billedVersions", "—")),
text_para(row.get("versionSummary", "—")),
]
)
table = LongTable(
data,
colWidths=[
0.78 * inch,
1.7 * inch,
0.72 * inch,
0.75 * inch,
0.85 * inch,
0.6 * inch,
1.0 * inch,
1.45 * inch,
1.2 * inch,
2.15 * inch,
],
repeatRows=1,
)
table.setStyle(
TableStyle(
[
("BACKGROUND", (0, 0), (-1, 0), colors.white),
("BOX", (0, 0), (-1, -1), 0.8, LINE),
("INNERGRID", (0, 0), (-1, -1), 0.4, LINE),
("VALIGN", (0, 0), (-1, -1), "TOP"),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("RIGHTPADDING", (0, 0), (-1, -1), 6),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#FBF8F2")]),
]
)
)
flow.append(table)
return flow
styles = getSampleStyleSheet()
styles.add(
ParagraphStyle(
name="title_main",
fontName="Helvetica-Bold",
fontSize=24,
leading=28,
textColor=TEXT,
alignment=TA_LEFT,
)
)
styles.add(
ParagraphStyle(
name="eyebrow",
fontName="Helvetica-Bold",
fontSize=9,
leading=11,
textColor=MUTED,
)
)
styles.add(
ParagraphStyle(
name="subtitle",
fontName="Helvetica",
fontSize=11,
leading=15,
textColor=MUTED,
)
)
styles.add(
ParagraphStyle(
name="section_title",
fontName="Helvetica-Bold",
fontSize=15,
leading=18,
textColor=TEXT,
spaceAfter=2,
)
)
styles.add(
ParagraphStyle(
name="small",
fontName="Helvetica",
fontSize=9,
leading=12,
textColor=MUTED,
)
)
styles.add(
ParagraphStyle(
name="card_label",
fontName="Helvetica-Bold",
fontSize=8,
leading=10,
textColor=MUTED,
)
)
styles.add(
ParagraphStyle(
name="card_value",
fontName="Helvetica-Bold",
fontSize=24,
leading=26,
textColor=TEXT,
)
)
styles.add(
ParagraphStyle(
name="card_sub",
fontName="Helvetica",
fontSize=8.5,
leading=11,
textColor=MUTED,
)
)
styles.add(
ParagraphStyle(
name="th",
fontName="Helvetica-Bold",
fontSize=8,
leading=10,
textColor=MUTED,
)
)
styles.add(
ParagraphStyle(
name="cell",
fontName="Helvetica",
fontSize=8.2,
leading=10.5,
textColor=TEXT,
)
)
styles.add(
ParagraphStyle(
name="cell_mono",
fontName="Courier",
fontSize=8.1,
leading=10.3,
textColor=TEXT,
)
)
styles.add(
ParagraphStyle(
name="pill",
fontName="Helvetica-Bold",
fontSize=7.5,
leading=9,
alignment=1,
)
)
def main():
rows = json.loads(INPUT_PATH.read_text())
existing_rows = [row for row in rows if row.get("exists", True) is not False]
active_rows = [row for row in existing_rows if row.get("hasActiveRevision") == "Yes"]
no_active_rows = [row for row in existing_rows if row.get("hasActiveRevision") != "Yes"]
doc = BaseDocTemplate(
str(OUTPUT_PATH),
pagesize=landscape(legal),
leftMargin=36,
rightMargin=36,
topMargin=28,
bottomMargin=28,
)
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id="main")
doc.addPageTemplates([PageTemplate(id="default", frames=[frame], onPage=page_template)])
story = [
Paragraph("FOURGE PORTAL AUDIT", styles["eyebrow"]),
Spacer(1, 4),
Paragraph("Checked Sites Revision Audit", styles["title_main"]),
Spacer(1, 6),
Paragraph(
"This report treats your checkmark as “R00 was completed,” then separates that from billing and from any newer active revisions. "
"It is designed to make client-billing comparison easier at a glance.",
styles["subtitle"],
),
Spacer(1, 18),
build_summary_cards(rows),
Spacer(1, 18),
]
story.extend(
build_table(
active_rows,
"Checked Sites With Active Revisions",
"These are the sites where R00 is done, but there is still a newer revision active or awaiting billing.",
)
)
story.append(PageBreak())
story.extend(
build_table(
no_active_rows,
"Checked Sites With No Active Revision",
"These are the checked sites that currently do not have a newer active revision.",
)
)
doc.build(story)
print(OUTPUT_PATH)
if __name__ == "__main__":
main()