Every recommendation IJSEdge shows you is arithmetic, not opinion. Here's exactly how we compute it — formulas, thresholds, sample sizes, and the things we deliberately don't do.
For every element a skater landed, we compute how it scored vs how the same element scored across the rest of the field in the same event. That delta, multiplied by the element's base value, is the number of points the skater either captured or left on the table for that one element.
Field median is computed on every skater in the same event who attempted the same planned element. We use the median (not mean) because a single outlier judge or a single fall-and-recover skate would skew the average; the median absorbs both.
| Element | Base | Yours GOE | Field median GOE | Δ × base |
|---|---|---|---|---|
| Single Axel | 1.10 | 0.00 | 0.00 | 0.00 |
| Double Loop | 1.70 | 0.00 | −0.27 | +0.46 |
| Change Foot Comb. Spin | 1.60 | 0.00 | +0.16 | −0.26 |
| Double Flip | 0.50 | −0.08 | −0.19 | +0.06 |
| Step Sequence | 1.50 | −0.05 | 0.00 | −0.08 |
| Double Salchow | 1.43 | −0.26 | 0.00 | −0.37 |
| Fly. Change Foot Camel Spin | 1.50 | −0.30 | 0.00 | −0.45 |
| Hidden gain potential = Σ Δ × base | −0.64 | |||
Read this as: across these elements, the skater scored 0.64 points below the field median. That gap is the "hidden gain" — the point pool the next-level-up version of this same program is sitting on.
For each element, the ROI engine considers six concrete things the skater could do and estimates the point gain range for each. Recommendations are ranked by gain and tagged with confidence + risk before they surface.
Pure function, no opinion. The ROI engine reads the protocol breakdown and applies these rules deterministically — the same inputs always produce the same recommendations. Implementation: src/lib/analytics/element-roi.ts.
The verdict is a composite of three measurable things across the skater's last several scored skates at the current level:
The composite drops the skater into one of five buckets:
likely_ready — strong, stable signal across the window. Worth a conversation with the coach.almost_ready — promising but not settled. Watch the blockers and revisit.technically_ready_but_inconsistent — capability shown, but spread is too wide. Consistency, not ability, is the discussion.not_yet_ready — room to grow. The page lists the specific blockers (e.g. a missing required element).insufficient_data — fewer than 3 scored skates with a percentile. We won't guess.This is a signal, not coaching advice. Every verdict ships with its evidence and explicit blockers, and the page consistently points the conversation back to the actual coach.
The Clean Skate Index summarizes how cleanly one program executed on a 0-to-100 scale. 100 is "no calls, no falls, every element credited" — a real Senior-level performance can run in the high 80s. The index drops for:
Every CSI score ships with the underlying reasons — you can always click through and see the per-element list of what was called and what each call cost. The parser handles both the prose form ("(under-rotated)") that USFS IJS protocols use and the bracket-suffix form ("2A<") that some secondary sources export.
Implementation: src/lib/analytics/clean-skate-index.ts.
Within-event percentile ranks a skater against the field at the same competition + segment — exact peers, same panel, same date. This is the most defensible comparison and is what we lead with everywhere.
Cross-event percentile is broader: we compute the percentile at each event the skater competed in, then average across recent skates. It absorbs the "a weak field one week, a strong field the next" problem better than raw scores do.
Tie handling: when two skaters land on the same placement (rare for IJS, frequent for older protocols), both receive the higher percentile rank. Small fields: percentiles from fields under 6 skaters are flagged with a small-field warning so a 3-of-3 placement doesn't read as "33rd percentile" without context.
Every IJS protocol ships nine judge columns per element. We import all of them and surface the spread (max − min across the panel) on each element + each component. A 4-point spread on one element means the panel disagreed; a flat 0-spread means they all saw the same skate.
We never call this "judge bias." The framing is panel spread — a measurement, not an accusation. Individual judges are not characterized; we are not competent to do so and the data does not support that read.
Every Element ROI recommendation carries a confidence tag and a risk tag:
high / medium / low. Driven by sample size — a "clean up the 3Lz rotation" call after 9 attempts is high-confidence; after 2 attempts it's low.low / medium / high. Reflects how much the change could hurt if it doesn't work — a GOE-improvement call is low risk; an upgrade to a harder jump is high risk.Recommendations with high-confidence + low-risk surface at the top. Speculative ones (low-confidence + high-risk) are visible but explicitly labelled — so the reader can decide for themselves how much weight to give each.
IJSEdge does not:
Primary source: official USFS IJS leaderboards (the CAT…SEG…html pages generated by ijsLive during a competition). Every element, GOE, component score, and deduction comes from there — we don't synthesize.
Secondary sources: for competitions not published on the USFS IJS feed (smaller events, international, summer comps), we ingest the flat structure (placements, totals) from public secondary sources and parse element breakdowns where available.
Rate limits are conservative across every upstream fetcher. All imports are idempotent on a deterministic (competition, event, skater) hash so re-running an import doesn't duplicate.
Supersession rule: when multiple sources have the same competition, the official IJS row always wins. Secondary sources fill gaps, never overwrite authoritative data.
Cadence: the homepage poller runs every few minutes during competition weekends. New competitions appear within minutes of the result page being published. Off-season cadence drops to daily.