mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix(core): version the broker summary cache bucket
The cache stores normalized summaries, so one written before buy_value and sell_value existed read back without them: bandar gave null readings (the table said "No trades.") for a week, and an investor-filtered entry lacked its totals. Summaries now cache under ipot-broker-v2, so an old-shape entry is never served; offline it is a cache miss, online it is fetched again. A CLI test seeds an old entry and checks both, in JSON and table output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
parent
5f64319157
commit
b137dbf540
2 changed files with 73 additions and 2 deletions
|
|
@ -2933,3 +2933,69 @@ fn bandar_reads_one_investor_side_against_its_own_trading() {
|
|||
"{table}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bandar_never_reads_a_broker_summary_cached_in_the_old_shape() {
|
||||
// A summary cached before buy_value/sell_value existed: fresh for a
|
||||
// week, with trades, but no denominators for a reading.
|
||||
let root = test_env_dir("bandar-legacy-cache");
|
||||
let bucket = root.join("cache").join("idx").join("ipot-broker");
|
||||
fs::create_dir_all(&bucket).unwrap();
|
||||
let flow = |code: &str, value: i64| {
|
||||
serde_json::json!({"rank": 1, "code": code, "name": null, "kind": null,
|
||||
"lots": value / 100, "value": value, "average_price": 1})
|
||||
};
|
||||
let legacy = serde_json::json!({
|
||||
"fetched_at": chrono::Utc::now().to_rfc3339(),
|
||||
"ttl_secs": 7 * 24 * 3600,
|
||||
"schema_version": 1,
|
||||
"data": {
|
||||
"symbol": "BBCA.JK", "from": "2026-10-16", "to": "2026-10-16",
|
||||
"investor": "all", "board": "all",
|
||||
"buyers": [flow("AA", 60_000)], "sellers": [flow("BB", 40_000)],
|
||||
"total_value": 100_000, "total_lots": 1_000, "average_price": 100,
|
||||
"foreign_net_value": null, "rounded": false
|
||||
}
|
||||
});
|
||||
fs::write(
|
||||
bucket.join("BBCA-2026-10-16-2026-10-16-all-all.json"),
|
||||
legacy.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Offline, the old entry is not served as a complete summary.
|
||||
run_error_json(
|
||||
mock_bin_with_root(&root).args([
|
||||
"-o",
|
||||
"json",
|
||||
"--offline",
|
||||
"stocks",
|
||||
"bandar",
|
||||
"BBCA",
|
||||
"--date",
|
||||
"2026-10-16",
|
||||
]),
|
||||
"CACHEMISS",
|
||||
);
|
||||
// Online, the summary is fetched again and read in full.
|
||||
let reports = run_success_json(mock_bin_with_root(&root).args([
|
||||
"-o",
|
||||
"json",
|
||||
"stocks",
|
||||
"bandar",
|
||||
"BBCA",
|
||||
"--date",
|
||||
"2026-10-16",
|
||||
]));
|
||||
assert_eq!(reports[0]["imbalance_bps"], -2425);
|
||||
let table = run_success_stdout(mock_bin_with_root(&root).args([
|
||||
"--no-color",
|
||||
"stocks",
|
||||
"bandar",
|
||||
"BBCA",
|
||||
"--date",
|
||||
"2026-10-16",
|
||||
]));
|
||||
assert!(!table.contains("No trades."), "{table}");
|
||||
assert!(table.contains("big distribution"), "{table}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ use crate::error::IdxError;
|
|||
use types::{BrokerBoard, BrokerInvestor, BrokerQuery, BrokerRange, BrokerSummary};
|
||||
|
||||
const SOURCE: &str = "ipot";
|
||||
/// Cache bucket for normalized summaries, versioned with their shape: a
|
||||
/// summary cached before `buy_value`/`sell_value` existed would read back
|
||||
/// without them (and an investor-filtered one without its totals), so a new
|
||||
/// shape gets a new bucket rather than serving such entries.
|
||||
const CACHE_BUCKET: &str = "ipot-broker-v2";
|
||||
/// Longest range one summary may cover.
|
||||
pub const MAX_RANGE_DAYS: i64 = 366;
|
||||
/// Calendar days searched back for the latest session with trades.
|
||||
|
|
@ -98,7 +103,7 @@ impl Broker<'_> {
|
|||
cached_fetch(
|
||||
&self.idx.cache,
|
||||
CacheSpec {
|
||||
bucket: "ipot-broker",
|
||||
bucket: CACHE_BUCKET,
|
||||
key: &key,
|
||||
ttl_secs: if query.to < today {
|
||||
TTL_CLOSED_SECS
|
||||
|
|
@ -146,7 +151,7 @@ impl Broker<'_> {
|
|||
}
|
||||
if policy == CachePolicy::Offline {
|
||||
return Err(IdxError::CacheMiss(format!(
|
||||
"ipot-broker/{resolved} (no cached session in the last {LATEST_LOOKBACK_DAYS} days)"
|
||||
"{CACHE_BUCKET}/{resolved} (no cached session in the last {LATEST_LOOKBACK_DAYS} days)"
|
||||
)));
|
||||
}
|
||||
Err(IdxError::NoMarketData(format!(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue