Background
The projection engine (Projection.sql, app/models/projection.rb) selects its source events with a hard filter on event group concealment:
where splits.id = #{starting_split_id}
and (event_groups.concealed is false or event_groups.concealed is null)
Because planning (PlanDisplay → ProjectedEffort), live effort projections (EffortProjectionsView), crew access arrival estimates (GatingLocationRow#projected_low_seconds_from), and projection assessments (lib/projection_assessments/runner.rb) all run on this one query, concealment currently controls two unrelated things at once: public visibility of results and eligibility as projection source data.
Those two concerns need to move independently in both directions:
- Concealed but should feed projections: A race director moving to a new course created a concealed seed event with 33 realistic efforts so that predictions work from the first minutes of race day. Concealment silently excludes the seed data, defeating the purpose (real case: Twisted Branch 100k, August 2026).
- Visible but should NOT feed projections: A public event may contain poisonous data (mass timing errors, a shortened storm year, a mis-measured course) that legitimately belongs in the public record but skews every future plan and race-day estimate on that course.
Proposal
Add a boolean to events (working name: use_for_projections) and filter relevant_event_ids on it instead of on event_groups.concealed.
- Per event, not per event group — poisonous data is usually one distance/year, and seed events are single events.
- Default
true for new events; backfill existing events with use_for_projections = not event_groups.concealed so current behavior is preserved exactly at rollout.
- No ongoing sync with concealment. After the backfill the flag stands alone; concealing or revealing an event group no longer affects the projection stream. (This is the point of the change, but worth stating: staging/test event groups that today are auto-excluded by concealment will need the flag unchecked if they are created with realistic times on a real course.)
- Expose the checkbox on the event setup form, with help text explaining the effect.
Touch points
Projection.sql relevant_event_ids CTE — replace the concealment predicate
PlanDisplay#validate_setup / CourseAnalysisMethods#event — the plan page anchors on course.visible_events.latest for start time and laps, and errors with "No events have been held on this course" when every event on the course is concealed. It should be able to anchor on a projectable-but-concealed event (or the anchoring and the data-sourcing should be separated) so a seed-only course can plan.
lib/projection_assessments/runner.rb — inherits the fix via Projection.sql; verify the assessed event itself can be concealed
- Event form +
EventParameters.permitted
Migration sequencing
Per repo convention, the db/migrate migration (+ schema.rb + erd.mmd) ships in its own PR, merged and run in production first; the behavior change follows in a second PR rebased on it.
Notes
- The plan page displays
effort_years ("from years X, Y") sourced from the projection query, so seed-event years will surface there; cosmetic, but worth a glance when seed data is in play.
- Interim workaround (until this ships): un-conceal the seed event group and name it transparently, accepting that fabricated results are publicly listed.
🤖 Generated with Claude Code
Background
The projection engine (
Projection.sql, app/models/projection.rb) selects its source events with a hard filter on event group concealment:Because planning (
PlanDisplay→ProjectedEffort), live effort projections (EffortProjectionsView), crew access arrival estimates (GatingLocationRow#projected_low_seconds_from), and projection assessments (lib/projection_assessments/runner.rb) all run on this one query, concealment currently controls two unrelated things at once: public visibility of results and eligibility as projection source data.Those two concerns need to move independently in both directions:
Proposal
Add a boolean to
events(working name:use_for_projections) and filterrelevant_event_idson it instead of onevent_groups.concealed.truefor new events; backfill existing events withuse_for_projections = not event_groups.concealedso current behavior is preserved exactly at rollout.Touch points
Projection.sqlrelevant_event_idsCTE — replace the concealment predicatePlanDisplay#validate_setup/CourseAnalysisMethods#event— the plan page anchors oncourse.visible_events.latestfor start time and laps, and errors with "No events have been held on this course" when every event on the course is concealed. It should be able to anchor on a projectable-but-concealed event (or the anchoring and the data-sourcing should be separated) so a seed-only course can plan.lib/projection_assessments/runner.rb— inherits the fix viaProjection.sql; verify the assessed event itself can be concealedEventParameters.permittedMigration sequencing
Per repo convention, the
db/migratemigration (+ schema.rb + erd.mmd) ships in its own PR, merged and run in production first; the behavior change follows in a second PR rebased on it.Notes
effort_years("from years X, Y") sourced from the projection query, so seed-event years will surface there; cosmetic, but worth a glance when seed data is in play.🤖 Generated with Claude Code