Skip to content

Decouple projection/planning data inclusion from event group concealment #2229

Description

@moveson

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 (PlanDisplayProjectedEffort), 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions