Veredus Analytics

Changelog

Changes to the computed dataset, in the open: what changed, why, what we measured, and what to do if you consume the affected fields. Numbers are as recorded at fix time.

officialRating is now the declared mark for the race (2026-09)

What changed. officialRating on every runner row is now the official rating the horse is declared to run off in that race — the value on the racecard and the result. Until this change it was the mark the horse carried in its previous run. weightToRatingRatio, which divides carried weight by this field, moves with it. Nothing else changes: orVsField, fieldOrAvg, vprMinusOr, ratingDifference and the handicap-position fields always used the declared mark and are untouched.

Why. The previous-run mark is code-specific and often empty: a horse switching from a flat handicap to a hurdle race carried its flat mark, and any horse whose last run was a maiden, bumper or beginners chase carried 0. On a mixed-code Irish handicap hurdle at Listowel on 20 September, the field showed as half unrated when every runner had a mark between 96 and 120. The declared mark is published at declaration, so it is point-in-time safe and needs no proxy.

What consumers should do. Treat officialRating as the current mark from now on, with 0 meaning genuinely unrated (a maiden, a bumper, a beginners chase). If your model used the field as a "last known mark" proxy, retrain on the patched archive: the whole historical dataset was rewritten from the raw racecard and result rows, and the live pipeline computes the same value.

One product, four plans; the API tier paused (2026-09)

What changed. Veredus is now sold as one thing: the research platform in the browser. Four monthly plans — Starter, Insight, Pro and Max — differ only in how far back you can browse (past results, individual form lines, VPR history charts). Every plan gets the identical analytics on live and upcoming racecards: the full feature set, VPR and its confidence bands, all computed from the full archive to 1988. We never compute a narrower version of the analytics for a cheaper plan. Max adds an AI research assistant and goes on sale once that assistant has finished its closed trial.

Existing subscribers. Nobody is grandfathered and nobody is surprised: each existing subscriber receives an email at least 14 days before their next renewal explaining that their plan becomes Starter at that renewal, at the same £14.99, with one-click upgrades to Insight or Pro. Until that date nothing changes.

The API tier. Our data-licensing conversation is paused (amicably), so the developer API stays off sale and its public pages, sample endpoints and documentation are withdrawn rather than left half-open. The register-interest list is kept; the API returns when the platform's revenue funds the licence. Blog posts that mention it are left as written — they are a dated record, not marketing.

What consumers should do. Nothing changes in the computed data. Terms of service and a privacy policy are now published at /terms and /privacy.

Breeding own-career fields populated across the full historical dataset (2026-09)

What changed. The sire, dam and damsire own racing career groups — win and place rates by surface, going and course, the RPR-derived preferred distance and going preference, and their run counts — now carry real values wherever the parent raced in Britain or Ireland from 1988: about 70% of runner rows for the sire, half for the dam and 40% for the damsire. The rest stay 0 with a 0 run count, because the parent never raced here. sireAvgVpr, damAvgVpr and damsireAvgVpr are now the parent's own VPR average (null when the parent has no rated run) — until this change they duplicated the progeny average, which lives on sireProgenyAvgVpr and is unchanged.

Why. Our data feed identifies an animal by one number and changes only the prefix with the role it appears in — a runner, a sire, a dam, a damsire. The own-career lookup compared the sire-form id against runner-form ids, so it never found a parent's own record and every field in these groups was 0 on every row. Translating the id first fixes the live pipeline; the archive was then patched by the same computation the pipeline runs, validated row-for-row against live-computed races before the write. One deliberate semantic choice while the fields were still empty: the RPR-derived fields (average RPR, preferred distance, going preference) ignore runs with no published rating, matching the horse's own RPR fields.

What to do. Re-fetch any history you hold. Pre-off snapshots taken before the fix keep their zeros and the old *AvgVpr meaning — they are an immutable record of what was published at the time. Rows of declared horses that were then withdrawn (nonRunner) are covered as well, as of their racecard's off time.

Non-runners are now flagged on completed races (2026-09)

What changed. Every runner row gains nonRunner. When a horse is declared, its pre-race row is computed; if it is then withdrawn, that row used to sit on the completed race indistinguishable from the horses that ran. The final post-race compute now reconciles the race against the result and flags those rows. The row is kept — it is the last pre-race picture of a declared horse — so a completed race still lists every declared horse. The flag is always false on pre-race rows.

What to do. Filter on nonRunner when you want the horses that took part; the existing position of 0 cannot tell a non-runner from a horse that finished outside the top nine. Existing rows since live racecard ingestion began were reconciled the same way in one pass.

Context fields backfilled across the full historical dataset (2026-09)

What changed. The 44 context fields introduced below — the field-relative family, the handicap-position family and the sample-count denominators — are now populated on every runner row back to 1988, not only on races run since the feature shipped. The values were computed by the same logic the live pipeline uses and validated row-for-row against the live-computed races before the write.

What to do. Re-fetch any history you hold. One honest note surfaced by the new denominators: the sire, dam and damsire own-career rates (and so their run counts) came back 0 across the archive. That turned out to be an id-matching defect, not missing data — fixed and backfilled in the entry above. The progeny rates were unaffected throughout.

The context release — 44 new fields put every runner in the context of its race (2026-09)

What changed. Three new field families join every runner row. In this race: vprVsField (VPR minus the field median), vprVsTop, vprRankInField, ratedRunnerCount, fieldVprCoverage, fieldOrAvg, orVsField, weightVsFieldAvg, lastRunDaysVsField, and the race-level raceVprSpread and runnersWithin5LbOfTop (also on the race payload). Handicap position: vprMinusOr, avgVprMinusOrLast3, orVsLastWin, orTrajectory, orVsCareerHigh, bestVprCareer and daysSinceBestVpr. Denominators: 26 sample-count fields (jockeyCourseRuns, sireTurfRuns, …) beside the rates they qualify. The per-runner feature count moves from 180 to 224. On the web: race pages lead with a three-panel comparison of the field, the daily insights page gains a "Most competitive races" section, and horse profiles chart VPR against the official rating run by run.

Why. Every one of the previous 180 features described a horse in isolation; the question that actually matters — how does this horse stand in this race? — needed the field joined back together by every consumer separately. The definitions were frozen against a full-archive analysis first, and two of its findings shaped the family: the field reference is the median because a mean flips the sign for 11% of runners via low-confidence outliers, and vprMinusOr typically sits around −15 lb (VPR measures typical recent performance; the official rating calibrates to best form) — details on the methodology page and the full story on the blog.

What to do. The new families are nullable end to end — null means the input is absent, never a sentinel (unlike the legacy −999 delta fields). Rows computed before this release carry null context fields and 0 denominators until recomputed; new computes populate them for provisional and final rows alike. Treat the sample counts as required reading beside their rates, and read vprMinusOr comparatively across a field rather than as an absolute verdict.

Nine outline traces re-measured onto racing circuits; Lingfield's all-weather oval joins the dataset (2026-08)

What changed. Nine records in the open course topology dataset carried traces of a venue or course-grounds outline rather than the racing circuit itself — the backlog flagged in the turn-direction release below: Bath, Kempton (AW), Chelmsford (AW), Ffos Las, Fairyhouse, Galway, Newcastle (AW), Southwell (AW) and Wolverhampton (AW). Each was re-measured onto its mapped racing circuit and reviewed three ways before commit: circuit length against the officially published figure, signed turn sweep against the course's curated hand (a closed lap should approach ±360°), and elevation-profile shape against the course's documented topography. Lingfield's all-weather oval also received its own 1,950 m trace — the first sibling record measured in its own right under the attribution rule — and Southwell's turf jumps oval followed, traced inside the all-weather track, taking the dataset to 86 courses; both venues now carry measured profiles for turf and all-weather alike. Salisbury, which has no closed racing circuit (a long straight into a top loop), is now marked open by review, and Bellewstown's outline-only trace keeps a caveat until OpenStreetMap carries its racing line.

What to do. Re-fetch the dataset. For the nine re-measured records every measured field changes — circuit lengths drop to their true values (Kempton AW 5,542 m → 2,256 m is the largest move), and turns, straights, climb, undulation and elevation profiles are all re-derived, with startM survey origins moving accordingly. Lingfield's all-weather record appears for the first time; treat Salisbury as an open course (isClosedLoop=false). The review method and per-course reasoning are on the methodology page.

Topology turn directions oriented to racing direction; York re-measured (2026-08)

What changed. The sign of angleDeg in the open course topology dataset (and the Left/Right column on course pages) previously reflected the direction the OpenStreetMap trace happened to be drawn in, not the direction horses race — a reader comparing it with a course's documented hand would find them disagreeing on 15 of the 52 single-hand courses then measured, York included. Every trace is now oriented to the course's curated racing direction before turns are measured, so a negative angle reliably means a left-hand bend as the horse meets it; courses with no single hand keep their as-drawn order. York was additionally re-measured against the inner ring of its mapped track (3,010 m, replacing the 5,431 m outer band outline), which also removes a spurious "tight" turn where the outline wrapped a chute end. The method is documented on the methodology page.

What to do. If you consume turns[].angleDeg or derived handedness, re-fetch the dataset — signs are now trustworthy and York's turn set, circuit length, and elevation profile have changed. startM values now run in racing direction from the survey origin. A handful of courses whose traces are venue outlines rather than racing lines remain on the re-measurement backlog; their turn lists should be treated with caution until re-picked.

API subscriptions paused for new sign-ups (2026-08)

We're rebuilding the API's data foundations so the feed we sell is built entirely on data we source and compute ourselves — the same independence standard as VPR and the open topology dataset. Docs stay public; register interest from your account page and you'll be first in when early access opens. The Insights web subscription is unaffected.

VPR replaces the vendor ratings — 12 fields retired, 13 introduced (2026-08)

What changed. The Racing Post Rating and Topspeed fields are retired from every API response and web surface: prevRpr, prevTsr, avgRprLast3, bestRprLast5, improvingRpr, rprTrend, the six connection averages (jockeyAvgRpr damsireAvgRpr), and the raw rpr/tsr columns on race runners. In their place: the VPR familyvpr, vprConfidence, prevVpr, avgVprLast3, bestVprLast5, improvingVpr, vprTrend and the six …AvgVpr averages. The per-runner feature count moves from 179 to 180.

Why. Our data supplier withdrew redistribution rights for the proprietary ratings (the feed has served them empty since the ruling), so we built our own figure and took the opportunity to document it to a standard no licensed rating could be: VPR — the Veredus Performance Rating — an owned, openly-methodologised rating of every GB & IRE performance back to 1988. The complete method — sources, formula, measured scales, the drift investigation, validation and coverage — is published on the methodology page.

What to do. The VPR fields are nullable end to end: null means no rated history — never a sentinel 0, unlike the retired fields. Treat vprConfidence as evidence accounting (run count, anchor quality, recency), and note that VPR is not RPR — values on a given horse will differ. Historical analytics rows carry VPR throughout the archive, computed strictly point-in-time.

Beaten-lengths features: recomputed on true decimal margins (2026-08)

What changed. prevBeatenLengths, avgBeatenLast3 and bestBeatenLast5 are now computed from decimal beaten margins (nose = 0.05, short head = 0.10, … exactly as the photo finish recorded them) instead of whole-length integers. They are also now honest about missing data: where no margin is known — a non-finisher, or an archive row that never carried one — the value is null, never a fake 0. A companion field, beatenLengthsLowPrecision, is true when any contributing run comes from the pre-2005 archive, which only records whole lengths.

Why. When we replaced our margin ingestion we audited the archive and found two defects in the inputs these features had been computed on. First, margins had been stored rounded to whole lengths, so a nose beat and a half-length beat looked identical. Second — and much worse — 2.01 million archive rows stored a margin of 0 that actually meant "not recorded": nearly two-thirds of beaten runners looked like they had dead-heated. Features averaged over those inputs were systematically pulled towards zero.

The shift, measured. After re-pulling every reachable race (2005 onwards) with fractional margins and recomputing the full archive: rows showing a previous-run margin of exactly 0 fell from 72.2% to 9.9% — the survivors are genuine wins — while close finishes under a length rose from 235k rows to 1.17M. bestBeatenLast5 = 0 fell from 95.6% to 32.4%. The mean known previous-run margin moved from 2.43 to 4.10 lengths: the old figures weren't just imprecise, they were systematically compressed towards zero by fabricated dead-heats.

What to do. Treat null as "unknown" — it is not zero — and use beatenLengthsLowPrecision to decide whether a figure is precise enough for your use. The pre-2005 archive remains whole-length precision permanently: we would rather flag coarse data than invent precision.