From f77f81c97b9b2c630272c97dd11d5c3d988eaa0a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 14:28:52 +0000 Subject: [PATCH] fix(sentiment): treat an empty MSN response as no votes, not a parse error MSN SentimentBrowser returns `[]` for every IDX ticker checked live (BBCA, BBRI, TLKM), so `stocks sentiment` always failed with PARSEERROR. Return empty statistics instead; the table prints a one-line notice and JSON gets `"statistics": []` with exit 0. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Ld3fDSDiBzAaQvgZJX5NKM --- src/api/msn/map.rs | 19 ++++++++++++++++--- src/output/table.rs | 4 ++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/api/msn/map.rs b/src/api/msn/map.rs index 4e3a8fc..bb3581a 100644 --- a/src/api/msn/map.rs +++ b/src/api/msn/map.rs @@ -460,9 +460,15 @@ pub(super) fn parse_sentiment( symbol: &str, raw: &[RawSentiment], ) -> Result { - let item = raw - .first() - .ok_or_else(|| IdxError::ParseError("no sentiment data".into()))?; + // MSN answers `[]` for tickers nobody has voted on (all IDX names as of + // 2026-10). That is "no votes", not a malformed response. + let Some(item) = raw.first() else { + let ticker = ticker_from_symbol(symbol).unwrap_or_default(); + return Ok(SentimentData { + symbol: normalized_symbol(symbol, &ticker), + statistics: Vec::new(), + }); + }; let stats = item .sentiment_statistics .as_ref() @@ -929,6 +935,13 @@ mod tests { ); } + #[test] + fn empty_sentiment_response_is_empty_data_not_an_error() { + let sentiment = parse_sentiment("BBCA", &[]).expect("empty sentiment is valid"); + assert_eq!(sentiment.symbol, "BBCA.JK"); + assert!(sentiment.statistics.is_empty()); + } + #[test] fn parses_msn_chart_price_only_fixture_as_synthetic_ohlc() { let raw: Vec = serde_json::from_str(include_str!( diff --git a/src/output/table.rs b/src/output/table.rs index 1b92e1b..429f92d 100644 --- a/src/output/table.rs +++ b/src/output/table.rs @@ -768,6 +768,10 @@ pub fn print_earnings(report: &EarningsReport) -> Result<(), IdxError> { } pub fn print_sentiment(data: &SentimentData) -> Result<(), IdxError> { + if data.statistics.is_empty() { + println!("No sentiment votes for {}.", data.symbol); + return Ok(()); + } let mut table = Table::new(); table .load_preset(UTF8_FULL)