Skip to content

ui(public): wire fleetAccuracy.basis into the homepage and /fairness #9673

Description

@JSONbored

⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.

Context

GET /v1/public/stats publishes fleetAccuracy.basis — added by #9168 and computed at
src/review/public-stats.ts:600 as fleet.fleetFramingEligible ? "fleet" : "single_instance_self_report".
Its whole purpose is stated in src/orb/analytics.ts:43-49:

"Publishing those numbers under fleet framing invites a reader to treat one party's self-report as
independent corroboration of that same party's guarantee. The numbers stay published — they are
real — but basis says which of the two they are."

No UI surface reads it. Both public consumers hardcode fleet framing:

  • apps/loopover-ui/src/components/site/proof-of-power-stats.tsx:115-117 gates on
    instanceCount > 0 && accuracyPct != null — true at one instance — and line 177 renders the
    hint ending · ${instanceCount} self-hosted instance${instanceCount === 1 ? "" : "s"}, with the
    file comment at line 109 calling it "the live, fleet-wide accuracy".
  • apps/loopover-ui/src/components/site/fairness-report-page.tsx:75-79 uses the identical
    instanceCount > 0 gate, line 117 renders across N self-hosted instances, and lines 158-164
    render an unconditional methodology paragraph headed "Why the fleet number, not just our own
    repos"
    — exactly the overclaim basis exists to prevent, shown verbatim at instanceCount === 1.

The UI's hand-authored PublicStats type
(apps/loopover-ui/src/components/site/proof-of-power-stats-model.ts:31-68) does not declare basis
at all, so nothing in the app can even reach the field today. The same type has two further concrete
drifts from PublicStatsSchema:

  • accuracyTrend items are typed merged: number; closed: number; reversed: number
    (proof-of-power-stats-model.ts:70-76) but the schema declares all three
    z.number().nullable() (src/openapi/schemas.ts:170-178). fairness-report-page.tsx:242-250
    already renders week.merged != null ? … : "—" for all three — the code proves the type is wrong.
  • decidedCount?: number (proof-of-power-stats-model.ts:52) vs the schema's
    decidedCount: z.number().nullable() (src/openapi/schemas.ts:154).

This is not #9282. #9282 decides and builds the derivation mechanism (z.infer vs
openapi-typescript) plus a drift check, and explicitly says not to migrate surfaces wholesale. This
issue fixes one specific already-shipped mislabel and the two specific type errors that let it hide.

Requirements

  • Add basis: "fleet" | "single_instance_self_report" to PublicStats["fleetAccuracy"] in
    apps/loopover-ui/src/components/site/proof-of-power-stats-model.ts, declared optional
    (basis?:) with a comment matching the existing optional-field convention in that same type
    (see the #8829 fields — optional-chained at the render site comment at line 33), so an older
    backend that predates orb(public-stats): fleetAccuracy has no minimum instanceCount — at N=1 it publishes one operator's self-report as a fleet aggregate, and the anti-gaming detector it cites cannot fire #9168 does not break the render.
  • Correct accuracyTrend's item type to merged: number | null; closed: number | null; reversed: number | null and decidedCount to decidedCount?: number | null, matching
    src/openapi/schemas.ts:154 and :170-178.
  • Add a single exported pure helper to proof-of-power-stats-model.ts — signature exactly
    export function isFleetBasis(fleetAccuracy: PublicStats["fleetAccuracy"] | undefined): boolean
    returning true only when fleetAccuracy?.basis === "fleet". Both surfaces must call it; neither
    may re-derive the check inline.
  • proof-of-power-stats.tsx: when the accuracy figure is shown and isFleetBasis(...) is false,
    the tile hint must say the figure is one self-hosted instance's own self-report rather than a fleet
    aggregate. The numeric value itself must still be displayed — basis changes the label, never the
    number.
  • fairness-report-page.tsx: apply the same labelling to the "Decision accuracy" card's caption
    (line 117), and render the "Why the fleet number, not just our own repos" paragraph
    (lines 158-164) only when isFleetBasis(...) is true.
  • No other behaviour changes. Do not alter the fleetEligible numeric-eligibility gate, the
    gamingFlagsCaught card, or any endpoint.

⚠️ Required pattern: mirror the existing #9068 gamingFlagsCaught treatment already in this
codebase — fairness-report-page.tsx:126-136 and the proof-of-power-stats.tsx:175-177 comment —
where a backend-supplied disambiguating field changes the copy, never the number. What does NOT
satisfy this issue: hiding the accuracy tile entirely below the fleet-framing floor; deriving the
distinction client-side from instanceCount instead of reading basis; a differently-named or
differently-shaped helper; or fixing one of the two surfaces and leaving the other.

Deliverables

  • PublicStats["fleetAccuracy"] in proof-of-power-stats-model.ts declares basis?: "fleet" | "single_instance_self_report", decidedCount?: number | null, and accuracyTrend
    items with all three of merged/closed/reversed typed number | null.
  • isFleetBasis() is exported from proof-of-power-stats-model.ts with the exact signature
    above, with unit cases for "fleet", "single_instance_self_report", a missing basis, and
    an undefined fleetAccuracy, in apps/loopover-ui/src/components/site/proof-of-power-stats.test.tsx.
  • ProofOfPowerStats given fleetAccuracy.basis === "single_instance_self_report" renders the
    accuracy percentage AND a hint that does not claim a fleet, asserted by a new case in
    proof-of-power-stats.test.tsx.
  • FairnessReportPage given basis === "single_instance_self_report" does not render the text
    Why the fleet number, and given basis === "fleet" does render it — both asserted in
    apps/loopover-ui/src/components/site/fairness-report-page.test.tsx.
  • A regression case asserting that an accuracyTrend week with merged: null, closed: null, reversed: null still renders (as ) without a type-level or runtime break.

All Deliverables above are required in a single PR. A PR that satisfies only some of them — for
example adding basis to the type (Deliverable 1) without changing either surface's copy — does not
resolve this issue.

Test Coverage Requirements

apps/** is outside Codecov's coverage.include (vitest.config.ts), so codecov/patch does not
gate this change; real tests are still required and npm run ui:test is a required CI check. Both
arms of isFleetBasis() and of every conditional it newly guards must be tested — "fleet" and
"single_instance_self_report", plus the absent-field arm that an older backend produces. The
single_instance_self_report render assertion on each surface is the named regression test.

Expected Outcome

At fewer than FLEET_FRAMING_MIN_INSTANCES registered instances, the homepage tile and the
/fairness page disclose that the accuracy figure is one operator's self-report rather than a fleet
aggregate — the exact distinction src/orb/analytics.ts:43-49 says the field exists to publish — and
the hand-typed PublicStats stops contradicting PublicStatsSchema on three fields.

Links & Resources

  • src/openapi/schemas.ts:143-166 (PublicStatsSchema.fleetAccuracy), :169-178 (accuracyTrend)
  • src/review/public-stats.ts:277-283, :600
  • src/orb/analytics.ts:43-49, :155-163, :402
  • apps/loopover-ui/src/components/site/proof-of-power-stats-model.ts:29-76
  • apps/loopover-ui/src/components/site/proof-of-power-stats.tsx:109-188
  • apps/loopover-ui/src/components/site/fairness-report-page.tsx:73-79, :110-122, :150-165
  • Related (mechanism, not this bug): ui: derive API response types from the backend's real zod/OpenAPI schemas, stop hand-duplicating them #9282

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions