Dumb OMO commit
This commit is contained in:
@@ -188,3 +188,24 @@ _Auto-scaffolded by /start-work. Append new entries below - never overwrite._
|
||||
DB column — a direct hand-written match keeps the DB layer decoupled
|
||||
from `news_core`'s serde attribute and makes the two directions
|
||||
symmetric and easy to audit).
|
||||
|
||||
## F2/F3 final-wave fixes — cold-start percentile rule
|
||||
|
||||
- **Cold-start rule for trailing-window percentile ranks**: the draft specifies
|
||||
a cold-start fallback only for the relevance *score* (BM25-only until 20
|
||||
feedback samples; `.omo/drafts/news-triage.md:45`), not for percentile ranks.
|
||||
When `PercentileTracker::percentile_rank` returns `StoreError::InsufficientData`
|
||||
because the trailing 7-day window is empty, the scheduler maps it to `0.0`
|
||||
with a one-line `tracing::info!` log.
|
||||
- **Rationale**: conservative gate behavior. With percentile ranks at 0.0, no
|
||||
story can meet the notify threshold (relevance @ p90) or digest band
|
||||
[75, 90) until the window has data, preventing bogus batch-local
|
||||
percentiles from driving decisions. The breaking-news bypass lane
|
||||
(`source_count >= 2` or importance @ p99) is unaffected: corroboration bypass
|
||||
is percentile-independent, and the importance percentile uses its own
|
||||
distribution.
|
||||
- **JSON boundary for blocklist veto**: `StoryRow.relevance` is `Option<f64>`,
|
||||
mapped with `relevance.is_finite().then_some(relevance)`. This keeps the
|
||||
`f64::NEG_INFINITY` sentinel inside the backend scorer while serializing
|
||||
honestly as JSON `null` at the API boundary. `importance` remains `f64`
|
||||
because it is finite by construction (multi-hot arithmetic, no veto path).
|
||||
|
||||
@@ -664,3 +664,24 @@ Key gotchas that would otherwise silently corrupt the index:
|
||||
|
||||
- **Do not auto-apply snippets**. The README is operator-facing; the QA used
|
||||
scratch copies in `/tmp/opencode`. No real estate file was touched.
|
||||
|
||||
## F2/F3 final-wave fixes
|
||||
|
||||
- **Trailing-window percentile ranks must live in one place**: the original
|
||||
batch-local `percentile_rank` helper was duplicated across `cycle.rs`,
|
||||
`api/stories.rs`, and `news-cli/src/replay.rs`. Centralizing it in
|
||||
`news-store::PercentileTracker::percentile_rank` removes the duplication and
|
||||
guarantees the scheduler, API, and replay harness all rank against the same
|
||||
trailing-7-day distribution.
|
||||
- **Use a stable empirical-CDF formula**: `(below * 100 + equal * 50) / count`
|
||||
(implemented via `f64::mul_add`) avoids the small floating-point drift that
|
||||
`(below + equal / 2.0) / count * 100.0` introduces for values like 5.5/10.
|
||||
- **Model non-finite f64 honestly at the JSON boundary**: the backend keeps
|
||||
`f64::NEG_INFINITY` as the blocklist-veto sentinel, but the API serializes it
|
||||
as `null` by making `StoryRow.relevance` an `Option<f64>` mapped with
|
||||
`is_finite().then_some(...)`. The frontend mirrors the option and renders
|
||||
`—` so the entire story list no longer fails when a blocklisted item is
|
||||
present.
|
||||
- **Host-target tests for the frontend DTO are viable**: `news-web` can host-run
|
||||
small `serde_json::from_str` regression tests because they only exercise the
|
||||
`serde`-derived DTOs and do not depend on `gloo-net` or Leptos runtime APIs.
|
||||
|
||||
Reference in New Issue
Block a user