worldcup-predictor-hack / backfill_learnings.py
World Cup Agent
Cursor
Backfill past reflections with terse learnings format.
4d3acef
Raw History Blame Contribute Delete
3.61 kB
"""Regenerate terse evaluations and rebuild strategy for past reflections."""
import json
from pathlib import Path
from dotenv import load_dotenv
import gemini
load_dotenv()
WORK_DIR = Path(__file__).parent
RESULTS_PATH = WORK_DIR / "results.json"
STRATEGY_PATH = WORK_DIR / "strategy.md"
def _eval_prompt(entry: dict) -> str:
home, away = entry["home"], entry["away"]
match = entry["match"]
prediction = entry.get("prediction", {})
result = entry["result"]
pick = prediction.get("pick", "home")
correct = pick == result["winner"]
research_summary = ""
for svc in entry.get("services_used", []):
research_summary += f"\n- {svc['source']} (${svc['cost']:.4f}): {svc.get('reason', '')}"
mistake_section = "" if correct else """MISTAKE:
- One sentence: what the pick got wrong.
- One sentence: the specific reasoning error.
"""
return f"""Write a terse post-match learning note for a prediction agent.
Match: {match}
Result: {home} {result['home_score']}-{result['away_score']} {away} (winner: {result['winner']})
Predicted: {pick} (confidence {prediction.get('confidence')}/10) — {'CORRECT' if correct else 'WRONG'}
Reasoning given: {prediction.get('reasoning')}
Research purchased (${entry.get('research_cost', 0):.4f}):{research_summary if research_summary else " none"}
Rules:
- Plain text only. No preamble, praise, or filler ("Here is...", "honest evaluation", etc.).
- No markdown headers, bold, or horizontal rules.
- Max 120 words.
- Be blunt and specific. Name the exact mistake, not abstract narratives.
Format exactly:
{mistake_section}DATA:
- useful: [source] — one line on what it told us that mattered
- useless: [source] — one line on why it didn't help
List only endpoints that were actually purchased. 2-5 bullets total across useful/useless."""
def _reflect_prompt(strategy: str, evaluation: str, match: str) -> str:
return f"""Update the agent's strategy document. Write like engineering notes — terse, direct, no marketing language or dramatic rule names.
Current strategy:
{strategy if strategy else "(empty — first match)"}
Latest match note ({match}):
{evaluation}
Rewrite with EXACTLY ONE rule change from this match. Keep still-valid existing rules.
Format:
# Strategy
## Rules
- MATCH: one sentence rule
## Data sources
- use: source type — why (MATCH)
- skip: source type — why (MATCH)
Max 250 words. Return ONLY the document."""
def main() -> None:
results = json.loads(RESULTS_PATH.read_text())
reflected = sorted(
[r for r in results if r.get("reflected")],
key=lambda r: r.get("reflected_at") or "",
)
if not reflected:
print("No reflected entries to backfill.")
return
strategy = ""
by_match = {r["match"]: r for r in results}
for entry in reflected:
match = entry["match"]
print(f"Regenerating evaluation: {match}")
evaluation = gemini.generate(_eval_prompt(entry)).strip()
entry["evaluation"] = evaluation
by_match[match]["evaluation"] = evaluation
print(f" → {len(evaluation)} chars")
print(f"Rebuilding strategy after: {match}")
strategy = gemini.generate(_reflect_prompt(strategy, evaluation, match)).strip()
entry["strategy_after"] = strategy
by_match[match]["strategy_after"] = strategy
STRATEGY_PATH.write_text(strategy + "\n")
RESULTS_PATH.write_text(json.dumps(results, indent=2))
print(f"\nDone. Updated {len(reflected)} reflections and strategy.md ({len(strategy)} chars).")
if __name__ == "__main__":
main()