mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
feat(ownership): import IDX above-1% XLSX holder register
Since June 2026 IDX publishes the monthly "Pemegang Saham di Atas 1%" list as an XLSX workbook on the Data Kepemilikan Saham page instead of a PDF announcement. Add a parser for it and accept `.xlsx` in `ownership import --file`. - src/ownership/xlsx.rs: zip + quick-xml reader (shared strings incl. rich text, sheet cells), exact 12-column header match so the daily above-5% and investor-type/classification workbooks are rejected, Excel serial dates, numeric shares, percent -> bps with float-noise rounding. Builds KseiHoldingDraft directly and reuses the entity normalizers, so investor types stay uppercase like the PDF path. - Fixtures: trimmed excerpts of the real 2026-08-31 above-1% and 2026-09-23 above-5% workbooks. - Unit + CLI tests: import, ticker, releases, above-5% rejection. - Docs: ARCHITECTURE parser pipeline, AGENTS ownership inputs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
This commit is contained in:
parent
5bf3e5d5e3
commit
2c85acb829
9 changed files with 539 additions and 10 deletions
|
|
@ -17,13 +17,13 @@ use crate::output::table::format_idr;
|
|||
use crate::ownership::types::{
|
||||
ChangeType, FlowSignal, HolderRow, KseiHolding, OwnershipRelease, OwnershipSource,
|
||||
};
|
||||
use crate::ownership::{archive, db, entities, graph, parser, remote, search, snapshot};
|
||||
use crate::ownership::{archive, db, entities, graph, parser, remote, search, snapshot, xlsx};
|
||||
use crate::runtime;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[command(
|
||||
about = "Ownership intelligence (KSEI + Bing)",
|
||||
long_about = "Ownership intelligence (KSEI + Bing).\n\nPreferred bootstrap path:\n 1. Run `idx ownership sync` to install or refresh a maintained SQLite snapshot.\n 2. If no snapshot manifest is available, run `idx ownership discover` and then `idx ownership import --url <pdf-url>`.\n 3. Local `.pdf`, `.zip`, and `.txt` imports remain available for manual or fallback workflows.",
|
||||
long_about = "Ownership intelligence (KSEI + Bing).\n\nPreferred bootstrap path:\n 1. Run `idx ownership sync` to install or refresh a maintained SQLite snapshot.\n 2. If no snapshot manifest is available, run `idx ownership discover` and then `idx ownership import --url <pdf-url>`.\n 3. Local `.xlsx`, `.pdf`, `.zip`, and `.txt` imports remain available for manual or fallback workflows.",
|
||||
after_help = "Examples:\n idx ownership sync\n idx ownership sync --manifest /path/to/ownership-snapshot-manifest.json\n idx ownership discover --limit 1\n idx ownership import --url <pdf-url>\n idx ownership releases"
|
||||
)]
|
||||
pub struct OwnershipCmd {
|
||||
|
|
@ -41,7 +41,7 @@ pub enum OwnershipCommand {
|
|||
#[command(
|
||||
about = "Import ownership data directly from source files",
|
||||
long_about = "Import ownership data directly from source files.\n\nPrefer `idx ownership sync` for normal bootstrap/update flows. Use `import` when you need a direct PDF import from IDX discovery output, a local PDF, or a local KSEI archive fallback file.",
|
||||
after_help = "Examples:\n idx ownership import --url <pdf-url-from-discover>\n idx ownership import --file /path/to/ksei.pdf\n idx ownership import --file /path/to/BalanceposEfek20260227.zip"
|
||||
after_help = "Examples:\n idx ownership import --url <pdf-url-from-discover>\n idx ownership import --file /path/to/peng-2026-08-00017-satu-persen.xlsx\n idx ownership import --file /path/to/ksei.pdf\n idx ownership import --file /path/to/BalanceposEfek20260227.zip"
|
||||
)]
|
||||
Import(ImportArgs),
|
||||
#[command(
|
||||
|
|
@ -87,7 +87,7 @@ pub struct ImportArgs {
|
|||
/// Direct URL to a remote ownership PDF, typically from `ownership discover`.
|
||||
#[arg(long)]
|
||||
pub url: Option<String>,
|
||||
/// Path to a local ownership file: PDF (primary), ZIP/TXT archive (fallback).
|
||||
/// Path to a local ownership file: above-1% XLSX or PDF (primary), ZIP/TXT archive (fallback).
|
||||
#[arg(long)]
|
||||
pub file: Option<PathBuf>,
|
||||
/// Fetch Bing institutional data for these symbols.
|
||||
|
|
@ -836,6 +836,7 @@ fn handle_import(args: &ImportArgs, config: &IdxConfig) -> Result<(), IdxError>
|
|||
drafts
|
||||
}
|
||||
ImportInputFormat::Archive => archive::parse_balancepos_file(&import_input.import_path)?,
|
||||
ImportInputFormat::Xlsx => xlsx::parse_above1_xlsx_file(&import_input.import_path)?,
|
||||
};
|
||||
|
||||
let mut holdings = Vec::with_capacity(drafts.len());
|
||||
|
|
@ -977,6 +978,7 @@ struct OwnershipImportResult {
|
|||
enum ImportInputFormat {
|
||||
Pdf,
|
||||
Archive,
|
||||
Xlsx,
|
||||
}
|
||||
|
||||
fn resolve_import_input(args: &ImportArgs) -> Result<Option<ResolvedImportInput>, IdxError> {
|
||||
|
|
@ -1018,11 +1020,12 @@ fn detect_local_import_format(path: &Path) -> Result<ImportInputFormat, IdxError
|
|||
.as_deref()
|
||||
{
|
||||
Some("pdf") => Ok(ImportInputFormat::Pdf),
|
||||
Some("xlsx") if xlsx::supports_xlsx_file(path) => Ok(ImportInputFormat::Xlsx),
|
||||
Some("zip") | Some("txt") if archive::supports_local_archive_file(path) => {
|
||||
Ok(ImportInputFormat::Archive)
|
||||
}
|
||||
_ => Err(IdxError::InvalidInput(format!(
|
||||
"unsupported local ownership file {}; expected a .pdf, .zip, or .txt input",
|
||||
"unsupported local ownership file {}; expected a .xlsx, .pdf, .zip, or .txt input",
|
||||
path.display()
|
||||
))),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue