Agenda Planner — Data Maintenance Guide

The Agenda Planner loads its data from two JSON files at runtime. You can update races and epithets by editing these files directly — no component changes needed.

File Locations

docs/.vitepress/theme/data/AgendaData/
├── race-list.json    ← All career races (186 entries)
└── epithets.json     ← Epithet definitions (4 entries)

These files are imported directly by the AgendaPlanner component (no longer fetched as public assets).


Updating the Race List

File: docs/.vitepress/theme/data/AgendaData/race-list.json

Each race is a JSON object with these fields:

FieldTypeDescriptionExample
raceNamestringFull English race name"Arima Kinen"
gradestring"G1", "G2", or "G3""G1"
yearstring"First Year", "Second Year", "Third Year""Third Year"
turnstringMM_HH — month (01–12) and half (01=Early, 02=Late)"12_02" (Late December)
typestring"Turf" or "Dirt""Turf"
locationstringRacecourse with direction arrow"⇒ Nakayama"
lengthstringCategory: "Short", "Mile", "Medium", "Long""Long"
lengthMstringDistance in metres"2500 m"

Adding a race

Add a new object to the array:

json
{
  "raceName": "New Race Name",
  "grade": "G2",
  "year": "Second Year",
  "turn": "05_01",
  "type": "Turf",
  "location": "⇒ Tokyo",
  "length": "Medium",
  "lengthM": "2000 m"
}

Removing a race

Delete the corresponding object from the array.

Notes

  • raceName must be unique within each year (the planner uses year|raceName as a key).
  • turn controls calendar placement. "05_01" = Early May, "05_02" = Late May.
  • grade must be exactly "G1", "G2", or "G3" for filters and styling to work.
  • length must be exactly "Short", "Mile", "Medium", or "Long" for the length filter to work.

Updating Epithets

File: docs/.vitepress/theme/data/AgendaData/epithets.json

Each epithet is a JSON object. All fields except key, reward, and note are optional — use only the ones you need.

FieldTypeDescription
keystringDisplay name of the epithet
rewardstringReward text, e.g. "2 Random Stats +10" or "Mile Straightaways Hint +1"
notestringRequirement description shown in the UI
required(string | {race, year?})[]Races that must all be planned. Strings match within year; objects can override year per-race
yearstringYear filter: "Junior", "Classic", "Senior". Omit to match any year
requiredAnyOfstring[]Races where at least requiredAnyCount must be planned
requiredAnyCountnumberHow many of requiredAnyOf are needed
requiresEpithetstring[]Other epithet keys that must ALL be completed first
requiresAnyEpithetstring[]Other epithet keys where at least ONE must be completed
requireCountobjectCount-based: plan must have ≥ count races matching filters (see sub-fields below)

requireCount sub-fields:

Sub-fieldTypeDescription
countnumber(required) Minimum number of matching races needed
surfacestringFilter by surface: "Turf" or "Dirt"
gradestringExact grade filter: "G1", "G2", or "G3"
gradeMinstringMinimum grade: "OP" means G1/G2/G3/OP all count
lengthCategorystring"standard" = distance divisible by 400m, "nonstandard" = not divisible by 400m
tracksstring[]Race location must contain one of these track names (e.g. ["Tokyo", "Nakayama"])
nameContainsstring[]Race name must contain one of these substrings (case-insensitive)

Examples

Simple — all listed races in a specific year:

json
{
  "key": "Lady",
  "required": ["Oka Sho", "Japanese Oaks", "Shuka Sho"],
  "year": "Classic",
  "reward": "2 Random Stats +10",
  "note": "Win the Oka Sho, Japanese Oaks, and Shuka Sho"
}

With epithet prerequisite:

json
{
  "key": "Heroine",
  "required": ["Queen Elizabeth II Cup"],
  "year": "Classic",
  "requiresEpithet": ["Lady"],
  "reward": "2 Random Stats +10",
  "note": "Acquire the Lady Epithet, and win the Queen Elizabeth II Cup (Classic)"
}

Year-specific race objects (same race in multiple years):

json
{
  "key": "Dirt Sprinter",
  "required": [
    { "race": "JBC Sprint", "year": "Classic" },
    { "race": "JBC Sprint", "year": "Senior" }
  ],
  "reward": "2 Random Stats +10",
  "note": "Win both JBC Sprints"
}

Any-of (pick N from a list):

json
{
  "key": "Phenomenal",
  "requiresEpithet": ["Stunning"],
  "requiredAnyOf": ["Tenno Sho (Spring)", "Takarazuka Kinen", "Japan Cup", "Tenno Sho (Autumn)", "Osaka Hai", "Arima Kinen"],
  "requiredAnyCount": 2,
  "reward": "2 Random Stats +15",
  "note": "..."
}

Count-based (win N races matching a filter):

json
{
  "key": "Dirt G1 Achiever",
  "requireCount": { "surface": "Dirt", "grade": "G1", "count": 3 },
  "reward": "2 Random Stats +10",
  "note": "Win 3 Dirt G1 Races"
}

How rewards are parsed

The component parses reward strings to calculate totals:

  • "2 Random Stats +10" → 2 × 10 = 20 stat points
  • "Mile Straightaways Hint +1" → tracked as a skill hint
  • Total stat points and earned hints are shown in the summary

How matching works

  • "First Year""Junior", "Second Year""Classic", "Third Year""Senior".
  • Each required entry must exactly match a raceName from race-list.json (case-sensitive).
  • Epithet prerequisites (requiresEpithet) are resolved in order — place prerequisite epithets before dependent ones in the array.
  • Count-based requirements count all planned races matching the filters across all years.
  • lengthCategory uses the lengthM field (e.g. "2400 m" → 2400). Standard = divisible by 400, non-standard = not divisible by 400.

Race Icons

Race icons are not maintained by the agenda planner. They come from the site's existing TerumiRaceData.json via dataService. The component automatically maps each raceName to a raceId and loads the icon from /icon/race/thum_race_rt_000_{raceId}_00.webp.

This means:

  • All G1, G2, and G3 races get icons automatically if they exist in the data.
  • If a race name in race-list.json doesn't exactly match a name in TerumiRaceData.json, it will fall back to a text-only card.