5.2 KiB
Design: Personalization Correctness Verification
Overview
This feature adds a comprehensive integration test suite (tidal/tests/pg1_personalization_correctness.rs) that mathematically verifies every link in the personalization loop. No production code changes are required — this is a pure verification feature.
Architecture
pg1_personalization_correctness.rs
├── mod decay_tests
│ ├── single_event_decay_matches_analytical
│ ├── multi_event_decay_matches_analytical
│ ├── out_of_order_converges_to_in_order
│ └── decay_score_is_zero_after_many_half_lives
├── mod windowed_count_tests
│ ├── one_hour_window_exact_count
│ ├── velocity_equals_count_over_duration
│ └── all_time_count_is_monotonic
├── mod preference_vector_tests
│ ├── ema_update_matches_manual_computation
│ ├── adaptive_learning_rate_decays
│ └── dimension_mismatch_is_noop
├── mod ranking_reactivity_tests
│ ├── signal_immediately_visible_in_retrieve
│ └── signal_immediately_visible_in_search
├── mod score_ordering_tests
│ ├── more_engagement_ranks_higher
│ ├── interaction_boost_is_additive
│ ├── boost_increases_score_linearly
│ ├── penalty_decreases_score_linearly
│ └── no_nan_or_infinity_in_scores
├── mod gate_tests
│ ├── below_threshold_excluded
│ └── at_threshold_included
└── mod diversity_tests
└── max_per_creator_enforced
Test Schema
All tests share a common schema builder helper:
fn test_schema() -> Schema {
let mut b = SchemaBuilder::new();
let _ = b.signal("view", EntityKind::Item,
DecaySpec::Exponential { half_life: Duration::from_secs(7 * 24 * 3600) })
.windows(&[Window::OneHour, Window::TwentyFourHours, Window::AllTime])
.velocity(true)
.add();
let _ = b.signal("like", EntityKind::Item,
DecaySpec::Exponential { half_life: Duration::from_secs(14 * 24 * 3600) })
.windows(&[Window::AllTime])
.velocity(false)
.add();
let _ = b.signal("share", EntityKind::Item,
DecaySpec::Exponential { half_life: Duration::from_secs(3 * 24 * 3600) })
.windows(&[Window::TwentyFourHours, Window::AllTime])
.velocity(true)
.add();
let _ = b.signal("completion", EntityKind::Item,
DecaySpec::Exponential { half_life: Duration::from_secs(30 * 24 * 3600) })
.windows(&[Window::AllTime])
.velocity(false)
.add();
b.build().unwrap()
}
Analytical Reference Implementation
The test file includes a pure-Rust analytical decay calculator used as the oracle:
/// Compute expected decay score analytically.
/// events: Vec<(timestamp_ns, weight)>, sorted chronologically.
/// query_time_ns: the time at which the score is evaluated.
/// lambda: decay rate (ln(2) / half_life_secs).
fn analytical_decay_score(events: &[(u64, f64)], query_time_ns: u64, lambda: f64) -> f64 {
events.iter().fold(0.0, |acc, &(ts, w)| {
let dt = (query_time_ns.saturating_sub(ts)) as f64 / 1e9;
acc + w * (-lambda * dt).exp()
})
}
This differs from the incremental implementation (HotSignalState::on_signal + current_score) by computing from first principles — the two must agree within 1e-10 relative error.
Reactivity Test Design
1. Open ephemeral TidalDb with test schema + "for_you" profile
2. Write 10 items with metadata
3. Record view signals for items 1-5 (established baseline)
4. let t0 = Instant::now()
5. db.signal("like", item_1, 10.0, Timestamp::now()) // big boost
6. let results = db.retrieve(&Retrieve::builder()
.profile("for_you")
.for_user(user_1)
.limit(10)
.build())
7. assert item_1 is in top 3
8. assert t0.elapsed() < Duration::from_millis(100)
The 100ms budget is generous — in-memory path is typically < 1ms. The budget accounts for worst-case WAL append and any scheduling jitter.
Score Ordering Test Design
For monotonicity verification:
- Create two items A and B with identical metadata.
- Record 10 views for A, 5 views for B.
- Retrieve with
for_youprofile. - Assert A ranks above B (more views = higher score when all else is equal).
For interaction boost:
- Create items from creator_1 and creator_2.
- Record interaction between user and creator_1.
- Retrieve for user — items from creator_1 should rank higher than equivalent items from creator_2.
Gate Test Design
- Create profile with gate:
{ signal: "view", agg: Value, window: AllTime, min_threshold: 5.0 }. - Write items: A with 10 views, B with 3 views, C with 5 views.
- Retrieve — A and C should appear, B should not.
Diversity Test Design
- Create 25 items across 5 creators with varying engagement levels.
- Use
trendingbuiltin profile which has max_per_creator=1. - Retrieve with limit 10.
- Assert diversity is enforced: max per creator <= 5, at least 2 creators represented.
Files Changed
| File | Change |
|---|---|
tidal/tests/pg1_personalization_correctness.rs |
New integration test file (all tests) |
No Production Code Changes
This feature is purely additive — new test file only. No changes to tidal/src/ are required.