Skip to main content

Issue Rules

Issue Rules turn incoming race events (laps, flags, fuel samples, weather samples) into auto-created board cards so the race engineer doesn't have to manually log "yellow flag, lap 14" or "rain detected, switch to wets?" while juggling the rest of the stand.

Each rule pairs an event type + a condition with an action template. When the condition matches, the action runs — today the only action type is card, which drops a row on the Jobs & Issues board.

Source:

  • API route: apps/api/src/routes/issueRules.ts
  • Schema: issue_rules table in apps/api/src/db/schema.ts

See API → /issue-rules for the wire shape and Swimlanes for the lane vocabulary the actions target.

Rule shape

A rule row carries:

FieldTypeNotes
nametextHuman-readable label shown in the admin grid
descriptiontext?Hover-help / changelog free-form text
eventTypeenumlap · flag · fuel · weather
conditionsjsonb{ kind, … } — see the catalogue below
actionjsonb{ type: "card", swimlane, priority, titleTemplate, bodyTemplate? }
enabledboolDisable without deleting
lastFiredAt, fireCounttimestamps + counterTuning aid in the admin UI

Supported event types

Event typeFires onTypical condition kinds
lapEvery new /laps rowlapTimeDeltaMs, pitIn, fuelKgBelow, trackStatus
flagTrack-status change between consecutive lapstrackStatus
fuelPer-lap fuel sample (decreasing or increasing direction)fuelKgBelow
weatherEvery /weather insertrainStart, trackTempBelowC, trackTempAboveC

The eventType doesn't restrict which kind you can pair it with — it's a hint to the runtime about which inserts should re-evaluate the rule. Pick the type that matches the data producer.

Condition kinds

Each conditions blob has a discriminator field kind. The value-bearing fields after that depend on the kind.

kindExtra fieldsMeaning
trackStatusvalue ("green", "yellow", "red", "sc", "vsc", "fcy")Lap or flag-change carries the matching track status
lapTimeDeltaMsop (">=", "<=", ">", "<"), value (ms)Current lap time vs. previous lap, in milliseconds
fuelKgBelowvalue (kg)Fuel-remaining drops below value
pitIn(none)Lap is flagged pitIn: true
rainStart(none)First weather sample where skyStatus (or precipitation flag) indicates rain
trackTempBelowCvalue (°C)Weather sample with trackTempC < value
trackTempAboveCvalue (°C)Weather sample with trackTempC > value

Conditions are stored as raw JSON, so adding a new kind is a producer-side change — the route accepts arbitrary objects under conditions.

Action templating

The action block is currently hardcoded to type: "card". When the rule fires it inserts a board_cards row with:

Card fieldSource
titletitleTemplate rendered with the event payload
bodybodyTemplate rendered with the event payload
swimlaneaction.swimlane (matches a row in /swimlanes)
priorityaction.priority (low · normal · high · critical)
statusAlways todo for rule-created cards

Templates use the {{token}} syntax. Tokens are resolved against the event payload that triggered the rule. Common tokens:

  • {{lapNumber}} — current lap (lap / flag / fuel events)
  • {{lapTimeMs}} — current lap's time in milliseconds
  • {{deltaMs}} — lap-time delta to previous lap (only when lapTimeDeltaMs matched)
  • {{trackStatus}}"green" | "yellow" | …
  • {{fuelRemainingKg}} — fuel chain output (when computed)
  • {{airTempC}} / {{trackTempC}} — weather sample values

Tokens that aren't present on the event payload render as their literal text (e.g. {{lapNumber}} on a weather event).

The four seeded rules

make seed installs these rules so a freshly seeded account has end-to-end coverage out of the box. They live in apps/api/src/db/seed.ts and are good copy-paste templates:

NameEventConditionAction
Yellow flag detectedflag{ kind: "trackStatus", value: "yellow" }Strategy / high — title Yellow flag on lap {{lapNumber}}, body warns about pit-window shrinking
Lap time spike (+2s)lap{ kind: "lapTimeDeltaMs", op: ">=", value: 2000 }Engineering / high — title Lap {{lapNumber}} spike — investigate, asks for telemetry / off-track / driver request
Pit-in eventlap{ kind: "pitIn" }Mechanical / normal — title Pit stop after lap {{lapNumber}}
Rain startweather{ kind: "rainStart" }Strategy / critical — title Rain detected — switch to wets?, body notes track temp + tyre swap window

Disable any of them in the admin UI without deleting (sets enabled: false). Re-enable later without losing the configuration.

Authoring a rule

/admin/issue-rules is the in-app surface (route registered under the Admin section once the editor lands; until then, author rules via direct API calls). The wire shape is:

curl -X POST http://localhost:6161/issue-rules \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Critical fuel low",
"eventType": "fuel",
"conditions": { "kind": "fuelKgBelow", "value": 5 },
"action": {
"type": "card",
"swimlane": "Strategy",
"priority": "critical",
"titleTemplate": "Fuel critical on lap {{lapNumber}}",
"bodyTemplate": "Fuel chain reports {{fuelRemainingKg}} kg remaining. Recommend pit-in next opportunity."
}
}'

What's coming

  • Custom action types — beyond card, future actions could fire a webhook, send an email, or open an issue against a workflow in Issue Workflows.
  • Compound conditionsAND / OR of multiple kind rules. Today each rule has one condition.
  • Deduplication window — repeated firings on consecutive laps produce N cards. A future enhancement would let rules declare a cool-down (e.g. "don't re-fire for 5 laps").
  • Rule simulator — replay a session's lap / flag / weather feed against a rule to see how often it fires before enabling in production.