Spaces:
Configuration error
Configuration error
Download build_comparison_pdf.py from Brettapps/Trifecta-Lab: direct link, hf CLI and curl.
- Browser
- Download file 9.07 kB
-
https://huggingface.co/spaces/Brettapps/Trifecta-Lab/resolve/main/build_comparison_pdf.py
- Command line
-
hf download hf://spaces/Brettapps/Trifecta-Lab/build_comparison_pdf.py
-
curl -L -o build_comparison_pdf.py https://huggingface.co/spaces/Brettapps/Trifecta-Lab/resolve/main/build_comparison_pdf.py
9.07 kB
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """Build the trifecta comparison PDF for 2026-08-10 from _rerun_summary.json.""" | |
| from __future__ import annotations | |
| import json | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| 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, HRFlowable, | |
| ) | |
| BASE = Path(__file__).resolve().parent | |
| S = json.loads((BASE / "data" / "predictions" / "_rerun_summary.json").read_text()) | |
| MODELS = ["Model A (default)", "Variant B (form-heavy)", "Variant C (place-heavy)"] | |
| NEON = colors.HexColor("#16C47F") | |
| DARK = colors.HexColor("#0F172A") | |
| MUTED = colors.HexColor("#64748B") | |
| LIGHT = colors.HexColor("#F1F5F9") | |
| styles = getSampleStyleSheet() | |
| H1 = ParagraphStyle("H1", parent=styles["Title"], fontSize=22, textColor=DARK, spaceAfter=2, leading=26) | |
| SUB = ParagraphStyle("SUB", parent=styles["Normal"], fontSize=10, textColor=MUTED, spaceAfter=10) | |
| H2 = ParagraphStyle("H2", parent=styles["Heading2"], fontSize=14, textColor=DARK, spaceBefore=10, spaceAfter=6) | |
| BODY = ParagraphStyle("BODY", parent=styles["Normal"], fontSize=9.5, textColor=DARK, leading=13) | |
| SMALL = ParagraphStyle("SMALL", parent=styles["Normal"], fontSize=8, textColor=MUTED, leading=10) | |
| CELL = ParagraphStyle("CELL", parent=styles["Normal"], fontSize=8.5, textColor=DARK, leading=11) | |
| CELLH = ParagraphStyle("CELLH", parent=styles["Normal"], fontSize=8.5, textColor=colors.white, leading=11) | |
| def pct(a, b): | |
| return f"{100*a/b:.0f}%" if b else "0%" | |
| def build(): | |
| out = BASE / "data" / "pdfs" / "trifecta-comparison-2026-08-10.pdf" | |
| doc = SimpleDocTemplate(str(out), pagesize=A4, title="Trifecta-Bro Comparison 2026-08-10", | |
| author="Trifecta-Bro", leftMargin=16*mm, rightMargin=16*mm, | |
| topMargin=16*mm, bottomMargin=14*mm) | |
| story = [] | |
| story.append(Paragraph("Trifecta-Bro — Prediction vs Result Comparison", H1)) | |
| story.append(Paragraph( | |
| f"Date 2026-08-10 | Tracks: Dubbo, Kilcoy, Nowra | " | |
| f"Generated {datetime.now(timezone.utc):%Y-%m-%d %H:%M UTC} | " | |
| f"Engine: open-source TrifectaPredictor (LM Studio model v1 has no weights loaded)", SUB)) | |
| story.append(HRFlowable(width="100%", thickness=1.2, color=NEON, spaceAfter=8)) | |
| # Scorecard table | |
| story.append(Paragraph("Scorecard — 20 scorable races (Dubbo R7 excluded: race time only)", H2)) | |
| head = ["Metric", "Model A (default)", "Variant B (form)", "Variant C (place)"] | |
| rows = [head] | |
| def r(metric, key, denom_key): | |
| def g(m): | |
| d = S["results"][m] | |
| return f"{d[key]}/{d[denom_key]} ({pct(d[key], d[denom_key])})" | |
| rows.append([metric, g(MODELS[0]), g(MODELS[1]), g(MODELS[2])]) | |
| def r1(metric, key): | |
| rows.append([metric, str(S["results"][MODELS[0]][key]), | |
| str(S["results"][MODELS[1]][key]), str(S["results"][MODELS[2]][key])]) | |
| r("Exact trifecta hit", "exact", "races") | |
| r("Any box-of-3 (any order)", "box", "races") | |
| r("Winner picked", "winner", "races") | |
| r("Winner inside predicted top-3", "win_in_top3", "n_all") | |
| r("Full 3 in predicted top-3", "full3_in_top3", "races3") | |
| r("2-of-3 in predicted top-3", "two3_in_top3", "races3") | |
| r1("Quinella hits (2-horse results)", "quinella") | |
| t = Table(rows, colWidths=[58*mm, 38*mm, 38*mm, 38*mm]) | |
| t.setStyle(TableStyle([ | |
| ("BACKGROUND", (0,0), (-1,0), DARK), | |
| ("TEXTCOLOR", (0,0), (-1,0), colors.white), | |
| ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"), | |
| ("FONTSIZE", (0,0), (-1,-1), 8.5), | |
| ("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, LIGHT]), | |
| ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CBD5E1")), | |
| ("ALIGN", (1,0), (-1,-1), "CENTER"), | |
| ("VALIGN", (0,0), (-1,-1), "MIDDLE"), | |
| ("TOPPADDING", (0,0), (-1,-1), 3), | |
| ("BOTTOMPADDING", (0,0), (-1,-1), 3), | |
| # highlight Model A column winner/exact rows | |
| ("BACKGROUND", (1,1), (1,3), colors.HexColor("#ECFDF5")), | |
| ])) | |
| story.append(t) | |
| story.append(Spacer(1, 6)) | |
| story.append(Paragraph( | |
| "Model A (default weights) posted the best result: 4 winners and the lone exact trifecta. " | |
| "Neither alternative weighting improved the strike rate — Variant B and C each picked 3 winners.", SMALL)) | |
| # Race-by-race | |
| story.append(Paragraph("Race-by-Race — actual vs each model's primary pick", H2)) | |
| det = {m: {f"{d[0]}_R{d[1]}": d[3]["primary"] for d in S["details"][m] if d[3]} for m in MODELS} | |
| actual = { | |
| ("Dubbo","1"):"1-7-9",("Dubbo","2"):"8-3-6",("Dubbo","3"):"5-1-4",("Dubbo","4"):"4-9-6", | |
| ("Dubbo","5"):"2-8",("Dubbo","6"):"13-11-9",("Dubbo","7"):"(time)", | |
| ("Kilcoy","1"):"7-8",("Kilcoy","2"):"6-9-4",("Kilcoy","3"):"2-4",("Kilcoy","4"):"6-1-4", | |
| ("Kilcoy","5"):"5-11-9",("Kilcoy","6"):"10-4",("Kilcoy","7"):"15-9-1", | |
| ("Nowra","1"):"6-8-1",("Nowra","2"):"9-11-1",("Nowra","3"):"3-5-1",("Nowra","4"):"4-11-2", | |
| ("Nowra","5"):"8-11-3",("Nowra","6"):"7-1-6",("Nowra","7"):"2-3-9", | |
| } | |
| keys = sorted(actual.keys(), key=lambda k: (k[0], int(k[1]))) | |
| head2 = ["Track", "R", "Actual", "A", "B", "C", "Hit"] | |
| rdata = [head2] | |
| for trk, rn in keys: | |
| a = actual[(trk, rn)] | |
| pa = det[MODELS[0]].get(f"{trk}_R{rn}", "--") | |
| pb = det[MODELS[1]].get(f"{trk}_R{rn}", "--") | |
| pc = det[MODELS[2]].get(f"{trk}_R{rn}", "--") | |
| hit = "" | |
| if a != "(time)": | |
| av = [int(x) for x in a.split("-")] | |
| for m, pk in ((MODELS[0],pa),(MODELS[1],pb),(MODELS[2],pc)): | |
| if pk != "--": | |
| pv = [int(x) for x in pk.split("-")] | |
| if pv[0] == av[0]: | |
| hit += "W" if m==MODELS[0] else ("B" if m==MODELS[1] else "C") | |
| rdata.append([trk, rn, a, pa, pb, pc, hit or "-"]) | |
| t2 = Table(rdata, colWidths=[20*mm, 8*mm, 20*mm, 22*mm, 22*mm, 22*mm, 22*mm], repeatRows=1) | |
| ts = [ | |
| ("BACKGROUND", (0,0), (-1,0), DARK), | |
| ("TEXTCOLOR", (0,0), (-1,0), colors.white), | |
| ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"), | |
| ("FONTSIZE", (0,0), (-1,-1), 8), | |
| ("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, LIGHT]), | |
| ("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CBD5E1")), | |
| ("ALIGN", (1,0), (-1,-1), "CENTER"), | |
| ("TOPPADDING", (0,0), (-1,-1), 2.2), | |
| ("BOTTOMPADDING", (0,0), (-1,-1), 2.2), | |
| ] | |
| # shade hit cells | |
| for i, (trk, rn) in enumerate(keys, start=1): | |
| a = actual[(trk, rn)] | |
| if a == "(time)": | |
| ts.append(("TEXTCOLOR", (6,i), (6,i), MUTED)) | |
| t2.setStyle(TableStyle(ts)) | |
| story.append(t2) | |
| story.append(Spacer(1, 8)) | |
| # Read-through | |
| story.append(Paragraph("Read-Through", H2)) | |
| for line in [ | |
| "1. Model A nailed Dubbo R1 outright (exact trifecta 1-7-9) and found 4 winners overall — " | |
| "the strongest single-model run of the day.", | |
| "2. Re-weighting the scorer toward recent form (Variant B) or toward place%+prize (Variant C) " | |
| "did NOT lift the strike rate. The default weights already performed best.", | |
| "3. The shared weakness is the SAME across all three: across the 16 complete 3-place results, " | |
| "ZERO had all three actual trifecta runners sitting inside the predicted top-3, and only 4/16 " | |
| "had even two of three. The models rarely select the correct SET of runners — they are " | |
| "better at the front (35-45% of winners in the top-3) than at nailing the exotics.", | |
| "4. This is a data/feature limitation of the open-source heuristic (no weights loaded for the " | |
| "LM Studio model v1), not a tuning problem. Meaningful gains need either the trained " | |
| "Brettapps/trifecta-bro/v1 weights, or richer inputs (speed ratings, sectional times, " | |
| "jockey/trainer strike rates, track bias).", | |
| "5. Caveats: several pasted results are 2 numbers only (Dubbo R5; Kilcoy R1/R3/R6) and were " | |
| "judged as quinella/winner only. Dubbo R7 carried a race time with no finishing order.", | |
| ]: | |
| story.append(Paragraph("• " + line, BODY)) | |
| story.append(Spacer(1, 3)) | |
| story.append(Spacer(1, 8)) | |
| story.append(HRFlowable(width="100%", thickness=0.6, color=MUTED, spaceAfter=4)) | |
| story.append(Paragraph( | |
| "Method: predictions regenerated from the same FormFav runner data in predictions-2026-08-10.json. " | |
| "Variant B = form x1.6, place x0.8, distance x1.2, barrier x0.3, prize x0.5. " | |
| "Variant C = place x1.7, prize x1.3, track x1.2, condition x1.2, form x0.7, barrier x0.5. " | |
| "Honest backtest — only the scoring weights differ.", SMALL)) | |
| doc.build(story) | |
| print(f"wrote {out} ({out.stat().st_size} bytes)") | |
| if __name__ == "__main__": | |
| build() | |