DEV Community

kanta13jp1
kanta13jp1

Posted on

Flutter Horse Racing AI Dashboard: EF Hub Actions + NO_AUTH Zone + Promise.all Timeout Fix

Flutter Horse Racing AI Dashboard: EF Hub Actions + NO_AUTH Zone + Promise.all Timeout Fix

What Shipped

自分株式会社 added a fully automated horse racing prediction pipeline — from race data ingestion to AI prediction to a 3-tab Flutter UI with hit-rate dashboard.

Two architectural highlights worth sharing: the NO_AUTH action zone for service-to-service calls, and the Promise.all timeout fix for bulk race queries.


Pipeline Architecture

[JRA Data Fetch] → [tools-hub EF: horseracing.today]
       ↓
[AI Prediction]  → [tools-hub EF: horseracing.predict_all]
       ↓
[Store Results]  → [Supabase: horse_results table]
       ↓
[Flutter UI]     → [horse_racing_predictor_page.dart]
Enter fullscreen mode Exit fullscreen mode

All four actions live in a single tools-hub Edge Function — no new EF deployments needed, staying within the 50-EF hard cap.


Edge Function: Action Routing

// tools-hub/index.ts
switch (action) {
  case 'horseracing.today':
    return await getHorseRacingToday(supabase);
  case 'horseracing.predict_all':
    return await predictAllRaces(supabase, body);
  case 'horseracing.predictions':
    return await getPredictions(supabase, body);
  case 'horseracing.accuracy':
    return await getAccuracyStats(supabase);
}
Enter fullscreen mode Exit fullscreen mode

Action routing inside one EF = zero extra cold starts, one CORS config, one deploy step.


NO_AUTH Zone for GitHub Actions

horseracing.predict_all is called from a GitHub Actions scheduled workflow — no user JWT available. The initial implementation put it in the auth zone, causing 401 errors on every scheduled run.

Fix: add the action to the NO_AUTH_ACTIONS list:

const NO_AUTH_ACTIONS = ['horseracing.today', 'horseracing.predictions'];
Enter fullscreen mode Exit fullscreen mode

Actions in this list bypass JWT validation. They're still protected by Supabase's service-role key requirement at the API gateway level — no user can call them directly without the service key. This is the right pattern for:

  • GitHub Actions → Supabase EF (no user in context)
  • Cron jobs
  • Server-to-server calls

Promise.all Timeout Fix

Initial implementation fetched all race results in a single SELECT:

// Before: single bulk query → timeout on large datasets
const { data } = await supabase.from('horse_results').select('*');
Enter fullscreen mode Exit fullscreen mode

On days with many races, this hit Deno's 30-second timeout. Fix: parallel queries per race:

// After: parallel per-race queries
const results = await Promise.all(
  raceIds.map(id =>
    supabase.from('horse_results').select('*').eq('race_id', id)
  )
);
Enter fullscreen mode Exit fullscreen mode

Promise.all runs all queries concurrently. Each query is small (one race's data), so all complete within the timeout. The total wall-clock time is bounded by the slowest single query, not the sum.


Flutter: 3-Tab Layout

Tab Content
Today's Races ExpansionTile per race, grade color badge
Prediction History Past AI predictions with date filter
Accuracy Hit rate %, total count, top payout

Grade Color Badges

Color _gradeColor(String grade) {
  return switch (grade) {
    'G1' => Colors.red.shade700,
    'G2' => Colors.blue.shade700,
    'G3' => Colors.green.shade700,
    _    => Colors.grey.shade600,
  };
}
Enter fullscreen mode Exit fullscreen mode

Dart 3 switch expression — no break statements, exhaustive.

Payout Formatting

final fmt = NumberFormat('#,###');
Text(${fmt.format(payout)}')
Enter fullscreen mode Exit fullscreen mode

NumberFormat('#,###') from package:intl adds comma separators. ¥1,234,500 is readable; ¥1234500 is not.


SQLSTATE 42P10: Missing UNIQUE Constraint

During deploy, ai_university_content UPSERT failed with:

SQLSTATE 42P10: there is no unique or exclusion constraint matching the ON CONFLICT specification
Enter fullscreen mode Exit fullscreen mode

The ON CONFLICT (provider, category) DO UPDATE clause requires a UNIQUE constraint on those columns. The original migration didn't add it.

Fix:

ALTER TABLE ai_university_content
  ADD CONSTRAINT ai_university_content_provider_category_key
  UNIQUE (provider, category);
Enter fullscreen mode Exit fullscreen mode

Rule: every ON CONFLICT (col1, col2) needs UNIQUE (col1, col2) or a partial unique index.


Summary

Pattern When to Use
NO_AUTH_ACTIONS list GitHub Actions / cron → EF (no user JWT)
Promise.all per-ID Bulk queries that time out as a single SELECT
Action routing in hub EF New features without new EF deployments
ON CONFLICT + UNIQUE Always add the constraint before the migration runs

Try it: 自分株式会社

buildinpublic #Flutter #Supabase #Dart #AI

Top comments (0)