Trifecta-Lab / src /build_booklet.py
Brettapps's picture
Upload folder using huggingface_hub (part 21)
e23172f verified
Raw History Blame Contribute Delete
6.36 kB
"""Generate PDF booklet for Trifecta-Bro predictions."""
from __future__ import annotations
from pathlib import Path
from typing import Any
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from src.prediction_model import Race, Runner, TrifectaPredictor
TITLE = "Trifecta-Bro"
SUBTITLE = "Australian Gallops Prediction Booklet"
def build_booklet(races: list[dict[str, Any]], output_path: Path) -> Path:
predictor = TrifectaPredictor()
doc = SimpleDocTemplate(
str(output_path),
pagesize=A4,
title=TITLE,
author="Trifecta-Bro",
)
styles = getSampleStyleSheet()
title_style = ParagraphStyle(
"TBTitle",
parent=styles["Title"],
fontSize=22,
textColor=colors.HexColor("#111827"),
spaceAfter=4,
)
subtitle_style = ParagraphStyle(
"TBSubtitle",
parent=styles["Normal"],
fontSize=10,
textColor=colors.HexColor("#6B7280"),
spaceAfter=12,
)
heading_style = ParagraphStyle(
"TBHeading",
parent=styles["Heading2"],
fontSize=14,
textColor=colors.HexColor("#0F172A"),
spaceAfter=6,
)
body_style = ParagraphStyle(
"TBBody",
parent=styles["Normal"],
fontSize=9,
leading=12,
)
story: list[Any] = [
Paragraph(TITLE, title_style),
Paragraph(SUBTITLE, subtitle_style),
Spacer(1, 4 * mm),
]
for race_payload in races:
race = _to_race(race_payload)
story.append(Paragraph(f"Race {race.race_number}: {race.race_name}", heading_style))
meta = Table(
[
["Distance", race.distance, "Condition", race.condition],
["Weather", race.weather or "", "Class", race.race_class],
["Prize", f"${race.prize_money}", "Runners", str(race.number_of_runners)],
],
colWidths=[28 * mm, 45 * mm, 28 * mm, 45 * mm],
)
meta.setStyle(TableStyle([
("FONTNAME", (0, 0), (-1, -1), "Helvetica"),
("FONTSIZE", (0, 0), (-1, -1), 8),
("BACKGROUND", (0, 0), (0, -1), colors.HexColor("#F3F4F6")),
("BACKGROUND", (2, 0), (2, -1), colors.HexColor("#F3F4F6")),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("RIGHTPADDING", (0, 0), (-1, -1), 6),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#E5E7EB")),
]))
story.append(meta)
story.append(Spacer(1, 4 * mm))
prediction = predictor.predict(race)
if "error" in prediction:
story.append(Paragraph(f"Prediction unavailable: {prediction['error']}", body_style))
story.append(Spacer(1, 6 * mm))
continue
trifecta_table = Table(
[
["Primary", prediction.get("primary", "")],
["Secondary", prediction.get("secondary") or "-"],
["Value", prediction.get("value") or "-"],
],
colWidths=[32 * mm, 114 * mm],
)
trifecta_table.setStyle(TableStyle([
("FONTNAME", (0, 0), (-1, -1), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("BACKGROUND", (0, 0), (0, -1), colors.HexColor("#0F172A")),
("TEXTCOLOR", (0, 0), (0, -1), colors.white),
("LEFTPADDING", (0, 0), (-1, -1), 6),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#E5E7EB")),
]))
story.append(trifecta_table)
story.append(Spacer(1, 4 * mm))
runner_rows = [[
"#", "Horse", "Form", "Score", "Win%", "Place%", "Bar", "Jockey"
]]
for item in prediction.get("top3", []):
runner_rows.append([
str(item["number"]),
item["name"],
"",
str(item["score"]),
f"{item['win_prob']:.0f}%",
f"{item['place_prob']:.0f}%",
"",
"",
])
runner_table = Table(runner_rows, repeatRows=1)
runner_table.setStyle(TableStyle([
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 8),
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#F3F4F6")),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#E5E7EB")),
("LEFTPADDING", (0, 0), (-1, -1), 5),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
]))
story.append(runner_table)
story.append(Spacer(1, 6 * mm))
doc.build(story)
return output_path
def _to_race(payload: dict[str, Any]) -> Race:
form = payload.get("form", {})
runners = []
for r in form.get("runners", []):
runners.append(Runner(
number=r.get("number", 0),
name=r.get("name", ""),
jockey=r.get("jockey", ""),
trainer=r.get("trainer", ""),
weight=r.get("weight"),
barrier=r.get("barrier"),
form=r.get("form", ""),
last20Starts=r.get("last20Starts", ""),
careerPrizeMoney=r.get("careerPrizeMoney", "$0"),
scratched=bool(r.get("scratched", False)),
stats=r.get("stats", {}),
))
return Race(
date=payload.get("date", ""),
track=payload.get("track", ""),
track_slug=payload.get("track_slug", ""),
race_number=str(payload.get("race_number", "")),
race_name=payload.get("race_name", ""),
distance=payload.get("distance", ""),
condition=payload.get("condition", ""),
weather=payload.get("weather", ""),
race_class=payload.get("race_class", ""),
start_time=payload.get("start_time", ""),
prize_money=payload.get("prize_money", ""),
number_of_runners=int(payload.get("number_of_runners", 0) or 0),
runners=runners,
)