feat(fifa17): own club/stats/{year,consumables} in Rust (Core-accurate)

Migrate the MY CLUB stat set from the Python proxy to a Rust handler computing
Core-accurate counts: player tiers + rare from the collection, staff/consumable
families from catalog kind+subtype, per-nation buckets via the reverse entity
resolver. Faithful port of fut_club_stats.py (VOCAB + global_counts +
context_rows). Unlike the oracle (stale profile + synthetic consumable shelf),
this reflects the real imported content (incl. the content-gap consumables/staff).
Fail-closed 503 on Core error. club/stats/country|league|team sub-screens remain
Python (documented). Adds adapter club_stats module (5 tests), host handler +
classify arm + resolver subtype_of/rareflag_of, ownership + integration tests;
reachability tool splits club/stats global(migrated) vs context(residual).
This commit is contained in:
funman300
2026-08-15 17:56:37 +00:00
parent 979e71fbea
commit 71fcf5e251
5 changed files with 477 additions and 8 deletions
+37
View File
@@ -1434,3 +1434,40 @@ fn hub_counts_players_from_core_no_python() {
);
assert_eq!(rec.lock().len(), 0, "hub never reaches Python");
}
/// `GET /club/stats/year` is served from Rust with Core-accurate player tier
/// counts (contextId 1 global bucket), never reaching Python.
#[test]
fn club_stats_year_counts_tiers_from_core_no_python() {
let items = vec![
item("oc1", "card_a", 90, "ST", "Brazil", "La Liga", "Barcelona"), // gold
item("oc2", "card_b", 70, "CM", "Spain", "La Liga", "Real Madrid"), // silver
item("oc3", "card_c", 60, "CB", "France", "Ligue 1", "PSG"), // bronze
];
let core = Arc::new(FakeCore::new(items, 3));
let (py_url, rec) = spawn_mock_python();
let server = build_server(core, &py_url, None);
let resp = server.handle("GET", "/ut/game/fifa17/club/stats/year", &[], b"");
assert_eq!(resp.status, 200);
let body: Value = serde_json::from_slice(&resp.body).unwrap();
let g: std::collections::HashMap<String, i64> = body["stat"]
.as_array()
.unwrap()
.iter()
.filter(|r| r["contextId"] == 1)
.map(|r| {
(
r["type"].as_str().unwrap().to_string(),
r["typeValue"].as_i64().unwrap(),
)
})
.collect();
assert_eq!(g["players"], 3);
assert_eq!(g["playersGold"], 1);
assert_eq!(g["playersSilver"], 1);
assert_eq!(g["playersBronze"], 1);
assert_eq!(g["consumables"], 0);
assert_eq!(g["staff"], 0);
assert_eq!(rec.lock().len(), 0, "club/stats never reaches Python");
}