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:
Rasyidan Akbar F. 2026-04-06 19:15:19 +07:00
commit 04eaa75882
10 changed files with 286 additions and 144 deletions

View file

@ -77,7 +77,8 @@ fn candidate_is_idx_binary(path: &Path) -> bool {
return false;
};
output.status.success() && String::from_utf8_lossy(&output.stdout).trim() == env!("CARGO_PKG_VERSION")
output.status.success()
&& String::from_utf8_lossy(&output.stdout).trim() == env!("CARGO_PKG_VERSION")
}
#[cfg(unix)]
@ -1062,7 +1063,9 @@ fn ownership_releases_uses_xdg_data_home_default_db_path() {
.args(["ownership", "releases"])
.assert()
.success()
.stdout(predicate::str::contains("No ownership releases imported yet."));
.stdout(predicate::str::contains(
"No ownership releases imported yet.",
));
assert!(
data_home.join("idx").join("ownership.db").exists(),
@ -1584,7 +1587,15 @@ fn ownership_import_file_zip_archive_supports_releases_ticker_and_changes() {
.stdout(predicate::str::contains("KSEI AGGREGATE FOREIGN MF"));
let ticker_output = bin_with_root(&root)
.args(["-o", "json", "ownership", "ticker", "AADI", "--source", "ksei"])
.args([
"-o",
"json",
"ownership",
"ticker",
"AADI",
"--source",
"ksei",
])
.output()
.expect("ownership ticker json output");
assert!(ticker_output.status.success());
@ -1860,7 +1871,9 @@ fn invalid_quote_ttl_env_returns_non_zero() {
.args(["version"])
.assert()
.failure()
.stderr(predicate::str::contains("invalid IDX_CACHE_QUOTE_TTL value"));
.stderr(predicate::str::contains(
"invalid IDX_CACHE_QUOTE_TTL value",
));
}
#[test]