惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
人人都是产品经理
人人都是产品经理
爱范儿
爱范儿
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
H
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
The Cloudflare Blog
S
SegmentFault 最新的问题
小众软件
小众软件
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美
The GitHub Blog
The GitHub Blog
量子位
H
Hackread – Cybersecurity News, Data Breaches, AI and More
V
V2EX
Martin Fowler
Martin Fowler
博客园 - 【当耐特】
J
Java Code Geeks
D
DataBreaches.Net
云风的 BLOG
云风的 BLOG
F
Fortinet All Blogs
Blog — PlanetScale
Blog — PlanetScale
Last Week in AI
Last Week in AI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Building a Horse Racing AI Pipeline: PostgreSQL + Claude ...
kanta13jp1 · 2026-04-28 · via DEV Community

kanta13jp1

Building a Horse Racing AI Pipeline: PostgreSQL + Claude for Automated Race Predictions

For the past six months, I've been building an AI horse racing prediction system. Not a simple "past results → prediction" model — a multi-stage pipeline: data quality management → feature engineering → Claude inference → ranked recommendations.

Here's what I've learned.

Architecture

netkeiba scrape → PostgreSQL (horse_races / horse_entries)
               → fetch_horse_racing.py (daily batch)
               → Supabase Edge Function (ai-hub: horse.predict)
               → Claude haiku (inference)
               → horse_race_predictions_ensemble (results)
               → evaluate_accuracy.ts (weekly evaluation)

Enter fullscreen mode Exit fullscreen mode

Data Quality Score (DQS)

Prediction accuracy is mostly determined by data quality. I score 15 fields to produce a DQS (0-100):

(
  CASE WHEN weight IS NOT NULL THEN 10 ELSE 0 END +
  CASE WHEN weight_diff IS NOT NULL THEN 10 ELSE 0 END +
  CASE WHEN last_3f IS NOT NULL THEN 15 ELSE 0 END +
  CASE WHEN prev_last_3f IS NOT NULL THEN 10 ELSE 0 END +
  CASE WHEN jockey_id IS NOT NULL THEN 10 ELSE 0 END +
  CASE WHEN trainer_id IS NOT NULL THEN 10 ELSE 0 END +
  CASE WHEN odds IS NOT NULL THEN 15 ELSE 0 END
  -- + 8 more fields...
) AS data_quality_score

Enter fullscreen mode Exit fullscreen mode

Entries with DQS < 60 are skipped. This single filter improved accuracy more than any model change.

Feature Engineering: Ranking Score

Eight factors, weighted by empirical contribution:

Factor Weight Rationale
Historical place rate 25% Most stable signal
Final 3F time (last_3f) 20% Late speed is predictive
Inverse of odds 15% Market wisdom
Jockey win rate 15% Jockey effect is real
Weight change 10% Condition signal
Last 3F vs previous race 10% Momentum trend
Best time record 5% Ceiling indicator

Claude Inference Prompt

I use Claude to generate explanations, not just scores:

const prompt = `
You are a horse racing prediction specialist.

[RACE INFORMATION]
<<<USER_DATA>>>
${raceInfo}
<<<END>>>

[HORSE DATA]
<<<USER_DATA>>>
${horseData}
<<<END>>>

Recommend top 3 horses considering:
1. Prioritize horses with DQS >= 70
2. Emphasize best time record and final 3F
3. Flag weight changes of ±10kg as risk factors
4. Explain each recommendation in under 100 characters

Output format: JSON
`;

Enter fullscreen mode Exit fullscreen mode

The <<<USER_DATA>>> blocks protect against prompt injection from scraped race data.

Solving the N+1 Query Problem

Initial implementation: 2 queries per race × 50 races = 100 queries per evaluation run.

// Before: N+1
for (const race of races) {
  const entries = await db.from('horse_entries').eq('race_id', race.id);
  const predictions = await db.from('predictions').eq('race_id', race.id);
}

// After: Batch queries
const raceIds = races.map(r => r.id);
const [allEntries, allPredictions] = await Promise.all([
  db.from('horse_entries').in('race_id', raceIds),
  db.from('predictions').in('race_id', raceIds),
]);

// O(1) lookup via Map
const entriesByRace = new Map(
  raceIds.map(id => [id, allEntries.filter(e => e.race_id === id)])
);

Enter fullscreen mode Exit fullscreen mode

100 queries → 3 queries. Evaluation batch went from 8 minutes to under 1 minute.

Weekly Accuracy Evaluation

type AccuracyResult = {
  total_races: number;
  top3_accuracy: number;   // % races where a placed horse was in top-3 recommendations
  rank1_accuracy: number;  // % races where rank-1 recommendation placed
  avg_dqs: number;         // Average DQS of evaluated races
};

Enter fullscreen mode Exit fullscreen mode

The evaluation runs via GitHub Actions every Sunday JST, with results stored in Supabase and surfaced in the admin dashboard.

Current Numbers

  • top3_accuracy: 52% (a placed horse appears in the top-3 predictions)
  • rank1_accuracy: 31% (vs. 20% random baseline)
  • Evaluation scope: DQS ≥ 70 races only (~60% of all races)

These numbers are for a single track type and surface. Generalization is ongoing work.

The Core Lesson

The biggest learning from six months of this project: fix your data pipeline before touching the model.

The DQS filter alone improved accuracy by 10+ percentage points. Before that, I spent weeks tuning prompt parameters and weights that had almost no effect — because the training/evaluation set was full of incomplete data.

Clean data → simple model → measure → iterate.

The AI reasoning layer (Claude) is genuinely useful for generating explanations that can be audited. But it's the last 20% of the system, not the first.