4.8 KiB
4.8 KiB
Code Review: Briefing UX & Reason Labels
Summary
This feature adds a reason label system to tidalDB's query response types. Every ranked result now carries one or more ReasonLabel values explaining why it was surfaced. The implementation spans 5 modified source files, 1 new integration test file, and touches both the RETRIEVE and SEARCH pipelines.
Files Changed
| File | Change |
|---|---|
tidal/src/ranking/reason.rs |
New module: ReasonCode enum (19 variants), ReasonLabel struct, select_top_reasons(), reason_for_sort(), reason_for_strategy() helpers, 7 unit tests |
tidal/src/ranking/executor/mod.rs |
compute_raw_score() returns (f64, Vec<(String, f64)>) tuple; score_inner and score_personalized populate signal_snapshot from boost values |
tidal/src/query/executor/mod.rs |
Stage 3 reason tagging block after scoring: tags strategy, sort-mode, cohort, and exploration reasons |
tidal/src/query/search/executor/pipeline.rs |
Search pipeline reason tagging: tags BM25 (TextRelevance), ANN (SemanticMatch), sort-mode, and scope reasons |
tidal/tests/p1_reason_labels.rs |
10 integration tests covering trending, hidden_gems, hot, controversial, following, weight ordering, cap at 3, signal snapshot, text search, top_week |
Correctness
PASS. The implementation correctly:
- Populates
signal_snapshotduringcompute_raw_score()by collecting boost signal values> 0.0. This fixes the previously emptysignal_snapshotfield. - Tags reasons from two independent sources: sort mode (
reason_for_sort) and candidate strategy (reason_for_strategy). Both are exhaustivematchblocks with no missing arms. - Applies dominance threshold (0.10) and max cap (3) via
select_top_reasons()before writing toRetrieveResultandSearchResultItem. - Tags exploration-injected candidates with
ExplorationBudgetusing the correct heuristic (score == 0.0 and empty reasons). - Handles search-specific reasons: BM25 results get
TextRelevancewith weight proportional to BM25 score (clamped to [0.1, 1.0]), ANN results getSemanticMatchwith fixed weight 0.6. - Tags scope-derived reasons in the search pipeline (
WithinScope::Trending->TrendingGlobal, etc.).
Architecture
PASS. The design follows the existing pipeline architecture:
- Reasons are populated during scoring, not post-hoc -- matching the spec's requirement.
- The
ReasonCodeenum is closed and serde-serializable withsnake_casenaming. - Helper functions
reason_for_sort()andreason_for_strategy()are pure mapping functions with no side effects. - The
select_top_reasons()function is a clean filter-sort-take pipeline. - Signal snapshot collection is integrated into the existing boost computation loop with zero additional ledger reads.
Performance
PASS. Overhead is minimal:
compute_raw_scorecollects snapshot values in the existing boost loop -- no additional signal reads.- Reason tagging in Stage 3 is O(n) over scored candidates with constant-time reason construction.
select_top_reasons()sorts a vec of at most ~5 elements per candidate.- No heap allocations on the hot path beyond the Vec pushes (which are pre-allocated via
with_capacity).
Test Coverage
PASS. Comprehensive coverage across unit and integration tests:
- 7 unit tests in
ranking/reason.rs: serde roundtrip for all 19 codes, label construction, threshold filtering, cap enforcement, empty input, all-below-threshold. - 10 integration tests in
p1_reason_labels.rs: trending (TrendingGlobal), hidden_gems (HiddenGem), hot (TrendingGlobal), controversial (Controversial), following (FollowedCreator), weight ordering, max 3 cap, signal snapshot population, text search (TextRelevance), top_week (TopInWindow). - 1352 lib tests pass with no regressions.
- Existing test suites (m3-m8 UAT, session durability, etc.) unaffected.
Issues Found
None. The implementation is clean, well-documented, and matches the spec.
Minor Observations (non-blocking)
ReasonLabeldoes not implementSerialize/Deserialize: TheReasonCodeenum has serde derives, butReasonLabelitself does not. This is fine for the engine layer (the server serializesRetrieveResultwhich contains the label data), but adding serde derives toReasonLabelwould make it directly serializable for debugging/logging.- Reason weight normalization: Reason weights are absolute values (e.g., 1.0 for sort, 0.9 for strategy, 0.8 for cohort). They are not normalized relative to the item's total score. This means weights represent "confidence of attribution" rather than "fraction of score explained." The spec does not require normalization, so this is consistent.
Verdict
APPROVE -- The implementation is complete, correct, well-tested, and matches the spec. All 7 SDLC tasks are done with full test coverage.