mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix: handle missing scrip column in April 2026 KSEI PDF
The April 2026 IDX ownership PDF omits the zero-valued scrip column for some holders, causing the parser to leave holdings_scripless/scrip as empty strings. This broke normalize_ksei_row which called parse_id_number on empty input. Two-layer fix: - Parser: pop_numeric_tail handles 2-column (missing scrip) rows by defaulting scrip to "0" - Normalizer: parse_id_number_or_zero backstop treats empty component share fields as 0 while still requiring total_shares Also includes AGENTS.md refresh, formatting cleanup (rustfmt), and version bump to 0.2.2. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c94c826307
commit
04eaa75882
10 changed files with 286 additions and 144 deletions
|
|
@ -28,6 +28,14 @@ pub fn parse_id_number(s: &str) -> Result<i64, IdxError> {
|
|||
.map_err(|e| IdxError::ParseError(format!("failed to parse Indonesian number '{s}': {e}")))
|
||||
}
|
||||
|
||||
fn parse_id_number_or_zero(s: &str) -> Result<i64, IdxError> {
|
||||
if s.trim().is_empty() {
|
||||
Ok(0)
|
||||
} else {
|
||||
parse_id_number(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse Indonesian locale percentage to basis points (i64).
|
||||
/// "54,94" → 5494, "0,00" → 0, "100,00" → 10000
|
||||
pub fn parse_id_percentage(s: &str) -> Result<i64, IdxError> {
|
||||
|
|
@ -141,8 +149,8 @@ pub fn normalize_ksei_row(raw: &KseiRawRow) -> Result<KseiHoldingDraft, IdxError
|
|||
locality,
|
||||
nationality: optional_string(&raw.nationality),
|
||||
domicile: optional_string(&raw.domicile),
|
||||
holdings_scripless: parse_id_number(&raw.holdings_scripless)?,
|
||||
holdings_scrip: parse_id_number(&raw.holdings_scrip)?,
|
||||
holdings_scripless: parse_id_number_or_zero(&raw.holdings_scripless)?,
|
||||
holdings_scrip: parse_id_number_or_zero(&raw.holdings_scrip)?,
|
||||
total_shares: parse_id_number(&raw.total_holding_shares)?,
|
||||
percentage_bps: parse_id_percentage(&raw.percentage)?,
|
||||
report_date: parse_ksei_date(&raw.date)?,
|
||||
|
|
@ -297,9 +305,10 @@ mod tests {
|
|||
use rusqlite::Connection;
|
||||
|
||||
use crate::ownership::entities::{
|
||||
normalize_name, parse_id_number, parse_id_percentage, parse_ksei_date, resolve_entity,
|
||||
normalize_ksei_row, normalize_name, parse_id_number, parse_id_percentage, parse_ksei_date,
|
||||
resolve_entity,
|
||||
};
|
||||
use crate::ownership::types::OwnershipSource;
|
||||
use crate::ownership::types::{KseiRawRow, OwnershipSource};
|
||||
|
||||
#[test]
|
||||
fn test_parse_id_number() {
|
||||
|
|
@ -401,4 +410,47 @@ mod tests {
|
|||
|
||||
assert_eq!(id1, id2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_ksei_row_defaults_empty_component_shares_to_zero() {
|
||||
let raw = KseiRawRow {
|
||||
date: "27-Apr-2026".to_string(),
|
||||
share_code: "AADI".to_string(),
|
||||
issuer_name: "ADARO ANDALAN INDONESIA Tbk".to_string(),
|
||||
investor_name: "ADARO STRATEGIC INVESTMENTS".to_string(),
|
||||
investor_type: "CP".to_string(),
|
||||
local_foreign: "D".to_string(),
|
||||
nationality: "INDONESIA".to_string(),
|
||||
domicile: String::new(),
|
||||
holdings_scripless: String::new(),
|
||||
holdings_scrip: String::new(),
|
||||
total_holding_shares: "3.200.142.830".to_string(),
|
||||
percentage: "66,18".to_string(),
|
||||
};
|
||||
|
||||
let draft = normalize_ksei_row(&raw).expect("empty component shares should normalize");
|
||||
assert_eq!(draft.holdings_scripless, 0);
|
||||
assert_eq!(draft.holdings_scrip, 0);
|
||||
assert_eq!(draft.total_shares, 3_200_142_830);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_ksei_row_still_requires_total_shares() {
|
||||
let raw = KseiRawRow {
|
||||
date: "27-Apr-2026".to_string(),
|
||||
share_code: "AADI".to_string(),
|
||||
issuer_name: "ADARO ANDALAN INDONESIA Tbk".to_string(),
|
||||
investor_name: "ADARO STRATEGIC INVESTMENTS".to_string(),
|
||||
investor_type: "CP".to_string(),
|
||||
local_foreign: "D".to_string(),
|
||||
nationality: "INDONESIA".to_string(),
|
||||
domicile: String::new(),
|
||||
holdings_scripless: String::new(),
|
||||
holdings_scrip: String::new(),
|
||||
total_holding_shares: String::new(),
|
||||
percentage: "66,18".to_string(),
|
||||
};
|
||||
|
||||
assert!(normalize_ksei_row(&raw).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue