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:
| Field | Type | Description | Example |
|---|---|---|---|
raceName | string | Full English race name | "Arima Kinen" |
grade | string | "G1", "G2", or "G3" | "G1" |
year | string | "First Year", "Second Year", "Third Year" | "Third Year" |
turn | string | MM_HH — month (01–12) and half (01=Early, 02=Late) | "12_02" (Late December) |
type | string | "Turf" or "Dirt" | "Turf" |
location | string | Racecourse with direction arrow | "⇒ Nakayama" |
length | string | Category: "Short", "Mile", "Medium", "Long" | "Long" |
lengthM | string | Distance in metres | "2500 m" |
Adding a race
Add a new object to the array:
{
"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
raceNamemust be unique within eachyear(the planner usesyear|raceNameas a key).turncontrols calendar placement."05_01"= Early May,"05_02"= Late May.grademust be exactly"G1","G2", or"G3"for filters and styling to work.lengthmust 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.
| Field | Type | Description |
|---|---|---|
key | string | Display name of the epithet |
reward | string | Reward text, e.g. "2 Random Stats +10" or "Mile Straightaways Hint +1" |
note | string | Requirement 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 |
year | string | Year filter: "Junior", "Classic", "Senior". Omit to match any year |
requiredAnyOf | string[] | Races where at least requiredAnyCount must be planned |
requiredAnyCount | number | How many of requiredAnyOf are needed |
requiresEpithet | string[] | Other epithet keys that must ALL be completed first |
requiresAnyEpithet | string[] | Other epithet keys where at least ONE must be completed |
requireCount | object | Count-based: plan must have ≥ count races matching filters (see sub-fields below) |
requireCount sub-fields:
| Sub-field | Type | Description |
|---|---|---|
count | number | (required) Minimum number of matching races needed |
surface | string | Filter by surface: "Turf" or "Dirt" |
grade | string | Exact grade filter: "G1", "G2", or "G3" |
gradeMin | string | Minimum grade: "OP" means G1/G2/G3/OP all count |
lengthCategory | string | "standard" = distance divisible by 400m, "nonstandard" = not divisible by 400m |
tracks | string[] | Race location must contain one of these track names (e.g. ["Tokyo", "Nakayama"]) |
nameContains | string[] | Race name must contain one of these substrings (case-insensitive) |
Examples
Simple — all listed races in a specific year:
{
"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:
{
"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):
{
"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):
{
"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):
{
"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
requiredentry must exactly match araceNamefromrace-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.
lengthCategoryuses thelengthMfield (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.jsondoesn't exactly match a name inTerumiRaceData.json, it will fall back to a text-only card.