EventCategory Cascading Inheritance
Overview
EventCategory rows can inherit shared attributes from a parent definition rather than duplicating every value. A national body’s "Elite Men" category — defined once on the organisational Category — flows down through every series and every event in those series, unless a series or an event explicitly overrides the value at its own layer.
This page describes the inheritance model, the parentage states an event-level row can be in, and the per-field origin surface the API exposes so an operator UI can render "inherited from …" badges and a "revert to inherited" action.
The three-tier model
Category (organisation-wide standard)
▲
│ sourceCategory
│
EventCategory (series) (series set, event null)
▲
│ seriesCategory
│
EventCategory (event) (event set)
-
Category— defined by a national / sanctioning body. Holds the canonicalname,minAge,maxAge,genderfor a category like "Elite Men U23". -
EventCategory(series-level) — points at aCategoryviasourceCategory, and may override any inherited value (e.g. relax the age range for a development series). -
EventCategory(event-level) — points at the series-level row viaseriesCategory, and may override any inherited value (e.g. open the entry for a single race weekend).
sourceCategory and seriesCategory are not duplicates: sourceCategory reaches up to the organisational Category; seriesCategory reaches up to the series-level EventCategory.
Parentage rule for event-level rows
An event-level EventCategory is in exactly one of three states:
| State | seriesCategory | sourceCategory | Meaning |
|---|---|---|---|
series-linked |
set |
null |
Template reached via the series-level row’s own |
standalone-standard |
null |
set |
Event not part of a series; inherits directly from the national |
ad-hoc |
null |
null |
Event-only custom category with no upstream template |
Setting both seriesCategory and sourceCategory on the same event-level row is rejected by:
-
the bean-validation
@AssertTrue isValidParentage()on the entity, and -
the
ec_event_parent_exclusiveCHECKconstraint onevent_categoryin the database.
Series-level rows are not subject to this exclusivity — they always have sourceCategory set (or null if ad-hoc at series level), and never have seriesCategory.
Cascade depth per field
| Field | Layers | Notes |
|---|---|---|
|
3-layer |
Cascades through Category. The label shown to participants. |
|
3-layer |
Age boundaries, often defined nationally. |
|
3-layer |
|
|
2-layer |
Event-level row inherits the pointer from |
|
2-layer |
Commercial. Series default; events override for one-off pricing. |
|
2-layer |
Boolean policy flags. Null means "inherit"; explicit |
|
2-layer |
Number subtype pools (WS2 / WS3). Null still falls through to |
"Elite Men" worked example
National body publishes a Category with name = "Elite Men", minAge = 19, gender = MALE.
Series A is built on it: a series-level EventCategory with sourceCategory = "Elite Men" and no local overrides.
Each of the 8 events in Series A has an event-level EventCategory with seriesCategory = (Series A’s Elite Men row) and no local overrides.
| Read on… | minAge | minAgeOrigin | Why |
|---|---|---|---|
Category "Elite Men" |
19 |
LOCAL (on Category — represented as the column itself) |
Defined on the organisation |
Series-level "Elite Men" row |
19 |
CATEGORY |
Inherited from |
Event-level "Elite Men" row |
19 |
CATEGORY |
Cascaded through |
Series B wants U21 athletes too — it overrides on the series-level row: minAge = 16. Event-level rows that don’t override locally now report:
| Read on… | minAge | minAgeOrigin | Why |
|---|---|---|---|
Series-level "Elite Men" row (Series B) |
16 |
LOCAL |
Override at the series |
Event-level "Elite Men" row in Series B |
16 |
SERIES |
Inherited from `seriesCategory’s local override |
A single event in Series B raises the floor back to 19 by setting minAge = 19 on its event-level row directly. That row reports minAgeOrigin = LOCAL and minAgeLocal = 19.
Origin surface on the API
EventCategoryDTO exposes three properties per inheritable field:
| Property | Read / write | Meaning |
|---|---|---|
|
read + write |
Effective (cascaded) value — what the system uses for validation, display, and scoring. On write, sets the local column. |
|
read-only |
The raw column value on this row. |
|
read-only |
|
|
The current API keeps |
Operator rules of thumb
-
To change a value for every event in a series — set it on the series-level row. Don’t edit each event row individually.
-
To revert an event-level override and re-inherit — clear the value (
null) on the event-level row. The DTO’sxxxLocalwill then benullandxxxOriginwill report whichever upstream layer the value comes from. -
"Standard" eligibility —
isOrganisationStandard()andisSeriesStandard()(used to decide records / leaderboard eligibility) require the row to have no inheritable overrides. Setting any ofminAge,maxAge,gender,product,entryCategory,raceCategory,defaultNumberSubType,defaultNumberSubType2locally disqualifies the row. -
nameis intentionally excluded from the "standard" check — a custom event label doesn’t disqualify the row from comparison. -
Ad-hoc rows — when both
seriesCategoryandsourceCategoryare null on an event-level row, theisAdHoc()helper returnstrue. Ad-hoc categories never inherit anything; their fields stand alone.
See also
-
Event Management entities — the catalogue of
EventCategoryand related entities.