From 22b6dabaeb975f21fabda52847b4dbbcf7f89159 Mon Sep 17 00:00:00 2001 From: hermes Date: Wed, 14 Oct 2026 08:52:13 +0000 Subject: [PATCH] feat(cli): agent-native output, stdin symbols, and exit-code classes Phase 1 of docs/DESIGN.md: - Output format resolves -o > IDX_OUTPUT > general.output > auto; auto is a table on a terminal and JSON when stdout is piped or captured. `config init` now writes output = "auto". - JSON is pretty only on a terminal and compact otherwise. New `-o jsonl` prints one array item per line with the same key order. - No ANSI color when stdout is not a terminal (direct owo-colors calls now go through output::style). - `stocks quote -` and `stocks compare -` read symbols from stdin. - Exit codes by class: 2 invalid input/config, 3 not found or cache miss, 4 upstream unavailable, 5 unsupported, 1 other. Golden changes are limited to those documented kinds and were verified mechanically: JSON goldens are semantically identical, tables differ only by removed ANSI codes and the column padding comfy-table had reserved for them (cell contents identical), exit codes match each error's class, and help/completions only gain the new --output values. 17 new golden cases cover auto, jsonl, stdin, and the new errors. tests/cli.rs now pins IDX_OUTPUT=table in its harness (its tests assert table text and run piped) and parses discover JSON instead of matching pretty-printed whitespace. Live A/B against main: 16/16 stocks cases identical; ownership discover got IDX 403 on both binaries, now exiting 4 instead of 1 as intended. Co-Authored-By: Claude Opus 5.5 --- Cargo.toml | 2 +- README.md | 26 ++- docs/ARCHITECTURE.md | 14 +- docs/DESIGN.md | 8 +- src/cli/cache.rs | 4 +- src/cli/mod.rs | 12 +- src/cli/ownership.rs | 37 ++-- src/cli/stocks.rs | 49 ++++- src/config.rs | 38 ++-- src/error.rs | 17 +- src/main.rs | 31 +-- src/output/json.rs | 40 +++- src/output/mod.rs | 97 +++++++--- src/output/style.rs | 41 ++++ src/output/table.rs | 2 +- tests/cli.rs | 33 ++-- tests/golden.rs | 31 ++- tests/golden/auto-format-env-table.golden | 8 + tests/golden/auto-format-piped.golden | 4 + tests/golden/cache-clear.json.golden | 5 +- tests/golden/cache-info-empty.json.golden | 8 +- tests/golden/cases.toml | 61 +++++- tests/golden/completions-bash.golden | 176 +++++++++--------- .../config-output-table-pins-format.golden | 8 + tests/golden/config-set-output-invalid.golden | 4 + tests/golden/config-set-output-jsonl.golden | 4 + .../error-compare-total-failure.json.golden | 8 +- .../error-compare-total-failure.table.golden | 2 +- .../error-fetch-bing-unsupported.json.golden | 8 +- .../error-fetch-bing-unsupported.table.golden | 2 +- ...ror-industry-only-fundamentals.json.golden | 6 +- tests/golden/error-invalid-output-env.golden | 4 + .../golden/error-invalid-provider.json.golden | 8 +- .../error-invalid-provider.table.golden | 2 +- .../error-offline-cache-miss.json.golden | 8 +- .../error-offline-cache-miss.table.golden | 2 +- .../error-offline-no-cache-flags.json.golden | 8 +- .../error-offline-no-cache-flags.table.golden | 2 +- .../golden/error-provider-failure.json.golden | 8 +- .../error-provider-failure.table.golden | 2 +- tests/golden/error-screen-region.json.golden | 8 +- tests/golden/error-screen-region.jsonl.golden | 4 + tests/golden/error-screen-region.table.golden | 2 +- tests/golden/error-stdin-empty.json.golden | 4 + tests/golden/error-stdin-empty.table.golden | 4 + ...rror-yahoo-profile-unsupported.json.golden | 8 +- ...ror-yahoo-profile-unsupported.table.golden | 2 +- tests/golden/help-cache.golden | 2 +- tests/golden/help-config.golden | 2 +- tests/golden/help-ownership.golden | 4 +- tests/golden/help-root.golden | 4 +- tests/golden/help-stocks.golden | 2 +- tests/golden/ownership-changes.json.golden | 107 +---------- tests/golden/ownership-changes.jsonl.golden | 16 ++ tests/golden/ownership-changes.table.golden | 58 +++--- .../ownership-concentration.json.golden | 43 +---- .../ownership-cross-holders.json.golden | 173 +---------------- tests/golden/ownership-entity.json.golden | 24 +-- tests/golden/ownership-entity.table.golden | 4 +- tests/golden/ownership-graph-dot.json.golden | 56 +----- tests/golden/ownership-graph.json.golden | 56 +----- .../ownership-import-archive-txt.json.golden | 9 +- .../golden/ownership-import-xlsx.json.golden | 9 +- tests/golden/ownership-releases.json.golden | 27 +-- .../golden/ownership-resolve-map.json.golden | 6 +- .../ownership-resolve-merge.json.golden | 9 +- tests/golden/ownership-search.json.golden | 9 +- .../golden/ownership-ticker-empty.json.golden | 8 +- .../ownership-ticker-empty.table.golden | 2 +- .../golden/ownership-ticker-ksei.json.golden | 65 +------ .../golden/ownership-ticker-ksei.table.golden | 4 +- tests/golden/ownership-ticker.json.golden | 65 +------ tests/golden/ownership-ticker.table.golden | 4 +- tests/golden/stocks-compare-stdin.json.golden | 4 + .../golden/stocks-compare-stdin.table.golden | 23 +++ tests/golden/stocks-compare.json.golden | 79 +------- tests/golden/stocks-compare.table.golden | 40 ++-- ...tocks-earnings-forecast-annual.json.golden | 20 +- ...cks-earnings-history-quarterly.json.golden | 20 +- tests/golden/stocks-earnings.json.golden | 54 +----- .../stocks-financials-income.json.golden | 19 +- tests/golden/stocks-financials.json.golden | 34 +--- tests/golden/stocks-fundamental.json.golden | 39 +--- tests/golden/stocks-fundamental.jsonl.golden | 4 + tests/golden/stocks-fundamental.table.golden | 84 ++++----- tests/golden/stocks-growth.json.golden | 10 +- tests/golden/stocks-growth.table.golden | 20 +- tests/golden/stocks-history-msn.json.golden | 27 +-- tests/golden/stocks-history-msn.table.golden | 2 +- tests/golden/stocks-history.json.golden | 27 +-- tests/golden/stocks-history.jsonl.golden | 6 + tests/golden/stocks-history.table.golden | 2 +- tests/golden/stocks-insights.json.golden | 14 +- tests/golden/stocks-news.json.golden | 13 +- tests/golden/stocks-profile.json.golden | 25 +-- tests/golden/stocks-quote-multi.json.golden | 33 +--- tests/golden/stocks-quote-multi.jsonl.golden | 5 + .../stocks-quote-offline-warm.json.golden | 18 +- tests/golden/stocks-quote-stdin.json.golden | 4 + tests/golden/stocks-quote-stdin.table.golden | 10 + tests/golden/stocks-quote.json.golden | 18 +- tests/golden/stocks-risk.json.golden | 10 +- tests/golden/stocks-risk.table.golden | 24 +-- .../stocks-screen-high-volume.json.golden | 33 +--- .../stocks-screen-large-cap.json.golden | 33 +--- tests/golden/stocks-screen-worst.json.golden | 33 +--- tests/golden/stocks-screen.json.golden | 33 +--- tests/golden/stocks-sentiment.json.golden | 18 +- tests/golden/stocks-technical.json.golden | 26 +-- tests/golden/stocks-technical.table.golden | 44 ++--- .../stocks-valuation-nonfinite.json.golden | 17 +- .../stocks-valuation-nonfinite.table.golden | 36 ++-- tests/golden/stocks-valuation.json.golden | 17 +- tests/golden/stocks-valuation.table.golden | 36 ++-- tests/golden/yahoo-fundamental.json.golden | 39 +--- tests/golden/yahoo-fundamental.table.golden | 84 ++++----- tests/golden/yahoo-history.json.golden | 27 +-- tests/golden/yahoo-history.table.golden | 2 +- tests/golden/yahoo-quote.json.golden | 18 +- 119 files changed, 919 insertions(+), 1895 deletions(-) create mode 100644 src/output/style.rs create mode 100644 tests/golden/auto-format-env-table.golden create mode 100644 tests/golden/auto-format-piped.golden create mode 100644 tests/golden/config-output-table-pins-format.golden create mode 100644 tests/golden/config-set-output-invalid.golden create mode 100644 tests/golden/config-set-output-jsonl.golden create mode 100644 tests/golden/error-invalid-output-env.golden create mode 100644 tests/golden/error-screen-region.jsonl.golden create mode 100644 tests/golden/error-stdin-empty.json.golden create mode 100644 tests/golden/error-stdin-empty.table.golden create mode 100644 tests/golden/ownership-changes.jsonl.golden create mode 100644 tests/golden/stocks-compare-stdin.json.golden create mode 100644 tests/golden/stocks-compare-stdin.table.golden create mode 100644 tests/golden/stocks-fundamental.jsonl.golden create mode 100644 tests/golden/stocks-history.jsonl.golden create mode 100644 tests/golden/stocks-quote-multi.jsonl.golden create mode 100644 tests/golden/stocks-quote-stdin.json.golden create mode 100644 tests/golden/stocks-quote-stdin.table.golden diff --git a/Cargo.toml b/Cargo.toml index c2a7f1a..0cac163 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ clap = { version = "4", features = ["derive"] } clap_complete = "4" ureq = { version = "3", features = ["json", "cookies"] } serde = { version = "1", features = ["derive"] } -serde_json = "1" +serde_json = { version = "1", features = ["raw_value"] } comfy-table = "7" owo-colors = "4" toml = "0.8" diff --git a/README.md b/README.md index 6f2cde1..ce7de8d 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,11 @@ idx ownership changes --from 2026-07-31 --to 2026-08-31 ### Output -- Human-readable tables on stdout, JSON with `-o json`, errors on stderr +- Tables when you run idx in a terminal; compact JSON when its output is piped + or captured, so scripts and AI agents get structured data with no flag +- `-o table|json|jsonl` (or `IDX_OUTPUT`, or `general.output` in the config) + overrides that; `jsonl` prints one array item per line +- Errors go to stderr, as one JSON object in JSON modes ## Configuration @@ -133,12 +137,26 @@ Precedence order: 3. Config file 4. Built-in defaults -## Agent-friendly usage +## Scripting and agents -- Use `idx --help` and subcommand help for discoverability -- Use `-o json` / `--output json` for structured output +idx composes with `jq`, Python, and other shell tools: + +```bash +idx stocks quote BBCA BBRI | jq -r '.[] | "\(.symbol) \(.price)"' +idx -o jsonl stocks history BBCA --period 1y | tail -5 +idx stocks screen --filter low-pe | jq -r '.[].symbol' | idx stocks compare - +``` + +- stdout carries only data; warnings and errors go to stderr +- `-` reads symbols from stdin for `stocks quote` and `stocks compare` +- No color codes when output is not a terminal +- Exit codes: `0` success, `2` invalid input or config, `3` not found or no + cached data, `4` upstream unavailable (retry later), `5` unsupported, `1` other - `idx completions ` generates shell completions +If you ran `idx config init` before v0.3, your config pins `output = "table"`; +change it to `auto` with `idx config set general.output auto`. + ## Documentation - [docs/INSTALL.md](docs/INSTALL.md): install script options diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e16dec0..030e959 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -144,11 +144,21 @@ CLI flags > environment variables > config file > defaults ## Error Strategy - `IdxError` enum (thiserror) with structured error codes - Table mode: human-readable error on stderr -- JSON mode: `{"error": true, "code": "...", "message": "..."}` -- Exit code 0 on success, non-zero on failure +- JSON and JSONL modes: `{"error": true, "code": "...", "message": "..."}` on stderr, stdout empty +- Exit codes by class: `0` success, `2` `INVALIDINPUT`/`CONFIGERROR` and clap usage + errors, `3` `SYMBOLNOTFOUND`/`NOMARKETDATA`/`CACHEMISS`, `4` `HTTP`/`RATELIMITED`/ + `PROVIDERUNAVAILABLE`/`OFFLINE`/`AUTHERROR`, `5` `UNSUPPORTED`, `1` everything else - A totally failed `stocks compare` emits one structured error in JSON mode; per-symbol warnings are retained for partial success, not printed ahead of a total-failure error. ## CLI Output and Offline Cache +- Output format resolves as `-o` flag > `IDX_OUTPUT` > `general.output` > `auto`. + `auto` is a table when stdout is a terminal and JSON otherwise + (`std::io::IsTerminal`). +- JSON is pretty-printed only on a terminal; piped output is compact. `jsonl` + prints each array item on its own line with the same key order as `json`, + and a single object as one line. +- ANSI styling goes through `output::style` and is disabled when stdout is not a + terminal or color is turned off. - `version`, `cache info`, `cache clear`, and `ownership resolve map|merge` emit JSON objects when JSON output is selected. - `stocks quote` and `stocks compare` require at least one symbol. diff --git a/docs/DESIGN.md b/docs/DESIGN.md index b126234..fbe9c3f 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -1,6 +1,6 @@ # System Design: idx-core and its frontends -**Status:** Accepted 2026-10-14. Implementation on `refactor/idx-core`; nothing implemented yet. +**Status:** Accepted 2026-10-14. Implementation on `refactor/idx-core`; phases 0-1 done. ## Goal @@ -164,8 +164,8 @@ is another 20-30% smaller than pretty JSON. Already true. - No ANSI color when stdout is not a terminal. Today some table headers emit bold codes even when piped. -- Commands that take symbols accept `-` to read them from stdin, one per line or - whitespace-separated: +- `stocks quote` and `stocks compare` accept `-` to read symbols from stdin, + separated by whitespace or commas: `idx stocks screen --filter low-pe | jq -r '.[].symbol' | idx stocks compare -` - Exit codes by error class (today every error exits `1`): @@ -175,7 +175,7 @@ is another 20-30% smaller than pretty JSON. | 1 | other failure | `IO`, `DATABASEERROR`, `PARSEERROR`, ... | | 2 | invalid usage or input | clap errors, `INVALIDINPUT`, `CONFIGERROR` | | 3 | not found or no data | `SYMBOLNOTFOUND`, `NOMARKETDATA`, `CACHEMISS` | - | 4 | upstream unavailable, retry later | `HTTP`, `RATELIMITED`, `PROVIDERUNAVAILABLE`, `OFFLINE` | + | 4 | upstream unavailable, retry later | `HTTP`, `RATELIMITED`, `PROVIDERUNAVAILABLE`, `OFFLINE`, `AUTHERROR` | | 5 | unsupported | `UNSUPPORTED` | - JSON keeps today's top-level shape: no `{data, meta}` wrapper, so `jq` paths diff --git a/src/cli/cache.rs b/src/cli/cache.rs index 3a8ae31..8b190a7 100644 --- a/src/cli/cache.rs +++ b/src/cli/cache.rs @@ -25,7 +25,7 @@ pub fn handle(cmd: &CacheCmd, format: &OutputFormat) -> Result<(), IdxError> { match &cmd.command { CacheSubcommand::Info => { let info = cache.info()?; - if matches!(format, OutputFormat::Json) { + if format.is_json() { return json::print_json(&serde_json::json!({ "path": info.path, "files": info.files, @@ -52,7 +52,7 @@ pub fn handle(cmd: &CacheCmd, format: &OutputFormat) -> Result<(), IdxError> { } CacheSubcommand::Clear => { let (removed, failed) = cache.clear()?; - if matches!(format, OutputFormat::Json) { + if format.is_json() { return json::print_json(&serde_json::json!({ "removed": removed, "failed": failed, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index d645bd6..4a77ca7 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -6,7 +6,7 @@ pub mod stocks; use clap::{Parser, Subcommand, ValueEnum}; -use crate::output::OutputFormat; +use crate::output::OutputSetting; #[derive(Debug, Clone, ValueEnum)] pub enum Shell { @@ -23,8 +23,14 @@ pub enum Shell { long_about = "idx-cli is a Rust command-line tool for Indonesian stock market (IDX) analysis.\nIt supports quote lookup, historical data, local caching, and output formats for both humans and AI agents." )] pub struct Cli { - #[arg(short, long, value_enum, global = true, help = "Output format")] - pub output: Option, + #[arg( + short, + long, + value_enum, + global = true, + help = "Output format [default: auto = table on a terminal, json when piped]" + )] + pub output: Option, #[arg(long, global = true, help = "Disable colored output")] pub no_color: bool, #[arg(short, long, global = true, help = "Suppress non-essential output")] diff --git a/src/cli/ownership.rs b/src/cli/ownership.rs index cf4f369..eeadaea 100644 --- a/src/cli/ownership.rs +++ b/src/cli/ownership.rs @@ -2,16 +2,15 @@ use std::collections::HashSet; use std::fs; use std::path::{Path, PathBuf}; +use crate::output::style::Paint; use chrono::Utc; use clap::{Args, Subcommand}; use comfy_table::{Cell, ContentArrangement, Table, presets::UTF8_FULL}; -use owo_colors::OwoColorize; use serde::Serialize; use sha2::{Digest, Sha256}; use crate::config::IdxConfig; use crate::error::IdxError; -use crate::output::OutputFormat; use crate::output::json; use crate::output::table::format_idr; use crate::ownership::types::{ @@ -219,7 +218,7 @@ fn handle_sync(args: &SyncArgs, config: &IdxConfig) -> Result<(), IdxError> { let db_path = db::db_path(config)?; let result = snapshot::sync_snapshot(&manifest_source, &db_path, args.force)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&result); } @@ -269,7 +268,7 @@ fn handle_discover(args: &DiscoverArgs, config: &IdxConfig) -> Result<(), IdxErr let family = parse_discovery_family(&args.family)?; let reports = remote::discover_idx_ownership_reports(family, args.limit)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&reports); } @@ -337,7 +336,7 @@ fn handle_ticker(args: &TickerArgs, config: &IdxConfig) -> Result<(), IdxError> ))); } - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&data); } @@ -407,7 +406,7 @@ fn handle_entity(args: &EntityArgs, config: &IdxConfig) -> Result<(), IdxError> let data = db::query_entity_holdings(&conn, entity.0)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&data); } @@ -440,7 +439,7 @@ fn handle_search(args: &SearchArgs, config: &IdxConfig) -> Result<(), IdxError> let conn = db::open_db(config)?; let entities = search::fts_search(&conn, &args.query, args.limit)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&entities); } @@ -475,7 +474,7 @@ fn handle_cross_holders(args: &CrossHolderArgs, config: &IdxConfig) -> Result<() let conn = db::open_db(config)?; let rows = db::query_cross_holders(&conn, args.min_tickers, args.top)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&rows); } @@ -509,7 +508,7 @@ fn handle_concentration(args: &ConcentrationArgs, config: &IdxConfig) -> Result< let rows = db::query_concentration(&conn, &by, args.top)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&rows); } @@ -551,14 +550,14 @@ fn handle_flow(args: &FlowArgs, config: &IdxConfig) -> Result<(), IdxError> { let flow = db::query_bing_flow(&conn, ticker_id)?; let Some(flow) = flow else { - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&Option::::None); } println!("No institutional flow data available for {symbol}."); return Ok(()); }; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&flow); } @@ -599,7 +598,7 @@ fn handle_graph(args: &GraphArgs, config: &IdxConfig) -> Result<(), IdxError> { let conn = db::open_db(config)?; let (nodes, edges) = graph::query_ownership_graph(&conn, &args.root, args.depth)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { #[derive(Serialize)] struct GraphOutput<'a> { nodes: &'a [crate::ownership::types::GraphNode], @@ -630,7 +629,7 @@ fn handle_changes(args: &ChangesArgs, config: &IdxConfig) -> Result<(), IdxError let conn = db::open_db(config)?; let changes = db::query_changes(&conn, &args.from, &args.to)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&changes); } @@ -679,7 +678,7 @@ fn handle_resolve(args: &ResolveArgs, config: &IdxConfig) -> Result<(), IdxError ResolveCommand::Unresolved { limit } => { let rows = db::list_unresolved(&conn, *limit)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&rows); } @@ -718,7 +717,7 @@ fn handle_resolve(args: &ResolveArgs, config: &IdxConfig) -> Result<(), IdxError } ResolveCommand::Map { alias, entity } => { db::manual_map(&conn, alias, entity)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&serde_json::json!({ "action": "mapped", "alias": alias, @@ -753,7 +752,7 @@ fn handle_resolve(args: &ResolveArgs, config: &IdxConfig) -> Result<(), IdxError db::merge_entities(&conn, *keep, *merge)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&serde_json::json!({ "action": "merged", "keep": keep, @@ -813,7 +812,7 @@ fn handle_import(args: &ImportArgs, config: &IdxConfig) -> Result<(), IdxError> source_url: import_input.source_url, }; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&result); } @@ -897,7 +896,7 @@ fn handle_import(args: &ImportArgs, config: &IdxConfig) -> Result<(), IdxError> source_url: release.source_url.clone(), }; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&result); } @@ -915,7 +914,7 @@ fn handle_releases(config: &IdxConfig) -> Result<(), IdxError> { let conn = db::open_db(config)?; let releases = db::query_releases(&conn)?; - if matches!(config.output, OutputFormat::Json) { + if config.output.is_json() { return json::print_json(&releases); } diff --git a/src/cli/stocks.rs b/src/cli/stocks.rs index a596706..430c1ce 100644 --- a/src/cli/stocks.rs +++ b/src/cli/stocks.rs @@ -1,4 +1,5 @@ use std::cmp::Ordering; +use std::io::Read; use clap::{Args, Subcommand, ValueEnum}; use serde::{Serialize, de::DeserializeOwned}; @@ -50,6 +51,34 @@ const SCREENER_FILTERS: &[&str] = &[ const SCREENER_REGIONS: &[&str] = &["id", "us", "sg", "hk", "jp"]; +/// Splits comma-separated symbol arguments; `-` reads symbols from stdin, +/// separated by whitespace or commas. +fn expand_symbols(args: &[String]) -> Result, IdxError> { + let mut symbols = Vec::new(); + for arg in args { + if arg == "-" { + let mut input = String::new(); + std::io::stdin() + .read_to_string(&mut input) + .map_err(|e| IdxError::Io(e.to_string()))?; + let from_stdin: Vec = input + .split(|c: char| c.is_whitespace() || c == ',') + .filter(|s| !s.is_empty()) + .map(String::from) + .collect(); + if from_stdin.is_empty() { + return Err(IdxError::InvalidInput( + "no symbols read from stdin".to_string(), + )); + } + symbols.extend(from_stdin); + } else { + symbols.extend(arg.split(',').map(String::from)); + } + } + Ok(symbols) +} + fn cache_bucket(provider: ProviderKind, key: &str) -> String { format!("{}-{key}", provider.as_str()) } @@ -124,10 +153,10 @@ pub struct StocksCmd { pub enum StocksSubcommand { #[command( about = "Get real-time stock quotes", - after_help = "Examples:\n idx stocks quote BBCA\n idx stocks quote BBCA,BBRI,BMRI\n idx -o json stocks quote BBCA" + after_help = "Examples:\n idx stocks quote BBCA\n idx stocks quote BBCA,BBRI,BMRI\n idx -o json stocks quote BBCA\n echo BBCA BBRI | idx stocks quote -" )] Quote { - /// One or more symbols, comma-separated or space-separated. + /// One or more symbols, comma-separated or space-separated; `-` reads them from stdin. #[arg(required = true)] symbols: Vec, }, @@ -228,10 +257,10 @@ pub enum StocksSubcommand { }, #[command( about = "Compare fundamentals across stocks", - after_help = "Examples:\n idx stocks compare BBCA BBRI BMRI\n idx stocks compare BBCA,BBRI,BMRI\n idx -o json stocks compare BBCA,BBRI" + after_help = "Examples:\n idx stocks compare BBCA BBRI BMRI\n idx stocks compare BBCA,BBRI,BMRI\n idx -o json stocks compare BBCA,BBRI\n idx stocks screen --filter low-pe | jq -r '.[].symbol' | idx stocks compare -" )] Compare { - /// One or more symbols, comma-separated or space-separated. + /// One or more symbols, comma-separated or space-separated; `-` reads them from stdin. #[arg(required = true)] symbols: Vec, }, @@ -257,8 +286,8 @@ pub fn handle( StocksSubcommand::Quote { symbols } => { let quote_bucket = cache_bucket(provider.kind(), "quote"); let mut quotes = Vec::new(); - for sym in symbols.iter().flat_map(|s| s.split(',')) { - let resolved = crate::api::resolve_symbol(sym, &config.exchange)?; + for sym in expand_symbols(symbols)? { + let resolved = crate::api::resolve_symbol(&sym, &config.exchange)?; if !no_cache && let Some(q) = cache.get("e_bucket, &resolved)? { quotes.push(q); continue; @@ -305,7 +334,7 @@ pub fn handle( history_provider(provider.kind(), history_mode, verbose)?; if matches!(history_mode, HistoryProviderKind::Auto) && history_source != provider.kind() - && !matches!(config.output, crate::output::OutputFormat::Json) + && !config.output.is_json() { runtime::info(format!( "history provider fallback active ({} -> {})", @@ -373,7 +402,7 @@ pub fn handle( history_provider(provider.kind(), history_mode, verbose)?; if matches!(history_mode, HistoryProviderKind::Auto) && history_source != provider.kind() - && !matches!(config.output, crate::output::OutputFormat::Json) + && !config.output.is_json() { runtime::info(format!( "history provider fallback active ({} -> {})", @@ -649,8 +678,8 @@ pub fn handle( let mut reports: Vec = Vec::new(); let mut failures = Vec::new(); - for sym in symbols.iter().flat_map(|s| s.split(',')) { - let resolved = crate::api::resolve_symbol(sym, &config.exchange)?; + for sym in expand_symbols(symbols)? { + let resolved = crate::api::resolve_symbol(&sym, &config.exchange)?; match fetch_fundamental_analysis_report( &cache, provider.market(), diff --git a/src/config.rs b/src/config.rs index a0f55d3..b7b3ba9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use std::fs; +use std::io::IsTerminal; use std::path::PathBuf; use clap::ValueEnum; @@ -7,7 +8,7 @@ use serde::Deserialize; use crate::cli::Cli; use crate::error::IdxError; -use crate::output::OutputFormat; +use crate::output::{OutputFormat, OutputSetting}; #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] @@ -92,7 +93,7 @@ struct FileGeneral { provider: Option, history_provider: Option, exchange: Option, - output: Option, + output: Option, color: Option, } @@ -108,7 +109,7 @@ impl Default for IdxConfig { provider: ProviderKind::Msn, history_provider: HistoryProviderKind::Auto, exchange: "JK".to_string(), - output: OutputFormat::Table, + output: OutputSetting::Auto.resolve(stdout_is_terminal()), no_color: false, quote_ttl: 300, fundamental_ttl: 3600, @@ -130,15 +131,12 @@ impl IdxConfig { cfg.exchange = exchange; } if let Ok(output) = std::env::var("IDX_OUTPUT") { - cfg.output = if output.eq_ignore_ascii_case("json") { - OutputFormat::Json - } else if output.eq_ignore_ascii_case("table") { - OutputFormat::Table - } else { - return Err(IdxError::ConfigError(format!( - "invalid IDX_OUTPUT value: '{output}', expected 'json' or 'table'" - ))); - }; + let setting = OutputSetting::parse(&output).ok_or_else(|| { + IdxError::ConfigError(format!( + "invalid IDX_OUTPUT value: '{output}', expected 'auto', 'table', 'json', or 'jsonl'" + )) + })?; + cfg.output = setting.resolve(stdout_is_terminal()); } if let Ok(no_color) = std::env::var("IDX_NO_COLOR") { cfg.no_color = no_color == "1" || no_color.eq_ignore_ascii_case("true"); @@ -151,9 +149,9 @@ impl IdxConfig { } if let Some(output) = cli.output { - cfg.output = output; + cfg.output = output.resolve(stdout_is_terminal()); } - cfg.no_color = cfg.no_color || cli.no_color; + cfg.no_color = cfg.no_color || cli.no_color || !stdout_is_terminal(); Ok(cfg) } @@ -176,7 +174,7 @@ impl IdxConfig { cfg.exchange = exchange; } if let Some(output) = general.output { - cfg.output = output; + cfg.output = output.resolve(stdout_is_terminal()); } if let Some(color) = general.color { cfg.no_color = !color; @@ -216,8 +214,12 @@ fn parse_env_ttl(name: &str) -> Result, IdxError> { Ok(Some(parsed)) } +pub fn stdout_is_terminal() -> bool { + std::io::stdout().is_terminal() +} + pub fn default_config_toml() -> String { - "[general]\nprovider = \"msn\"\nhistory_provider = \"auto\"\nexchange = \"JK\"\noutput = \"table\"\ncolor = true\n\n[cache]\nquote_ttl = 300\nfundamental_ttl = 3600\n".to_string() + "[general]\nprovider = \"msn\"\nhistory_provider = \"auto\"\nexchange = \"JK\"\noutput = \"auto\"\ncolor = true\n\n[cache]\nquote_ttl = 300\nfundamental_ttl = 3600\n".to_string() } pub fn config_path() -> Result { @@ -292,9 +294,9 @@ fn normalize_config_value(key: &str, value: &str) -> Result { - if !value.eq_ignore_ascii_case("table") && !value.eq_ignore_ascii_case("json") { + if OutputSetting::parse(value).is_none() { return Err(IdxError::InvalidInput(format!( - "invalid output format '{value}': expected 'table' or 'json'" + "invalid output format '{value}': expected 'auto', 'table', 'json', or 'jsonl'" ))); } Ok(toml::Value::String(value.to_ascii_lowercase())) diff --git a/src/error.rs b/src/error.rs index 37ed148..7df9dc2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -76,8 +76,23 @@ impl IdxError { } } + /// Exit status by error class, so scripts can branch without parsing + /// stderr. Clap usage errors also exit 2. See docs/DESIGN.md. pub fn exit_code(&self) -> i32 { - 1 + match self.code() { + ErrorCode::InvalidInput | ErrorCode::ConfigError => 2, + ErrorCode::SymbolNotFound | ErrorCode::NoMarketData | ErrorCode::CacheMiss => 3, + ErrorCode::Http + | ErrorCode::RateLimited + | ErrorCode::ProviderUnavailable + | ErrorCode::Offline + | ErrorCode::AuthError => 4, + ErrorCode::Unsupported => 5, + ErrorCode::Io + | ErrorCode::ParseError + | ErrorCode::DatabaseError + | ErrorCode::PdfParseError => 1, + } } } diff --git a/src/main.rs b/src/main.rs index 499195f..0d38ea5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,7 @@ use crate::api::default_provider; use crate::cli::{Cli, Commands}; use crate::config::IdxConfig; use crate::error::IdxError; -use crate::output::{OutputFormat, emit_error}; +use crate::output::{OutputFormat, OutputSetting, emit_error}; fn main() { if let Err(err) = run() { @@ -62,16 +62,19 @@ fn run() -> Result<(), IdxError> { return Err(err); } }; + output::json::configure(config.output, config::stdout_is_terminal()); + output::style::set_color(!config.no_color); match &cli.command { - Commands::Version => match config.output { - OutputFormat::Json => { + Commands::Version => { + if config.output.is_json() { output::json::print_json(&serde_json::json!({ "version": env!("CARGO_PKG_VERSION") }))?; + } else { + println!("{}", env!("CARGO_PKG_VERSION")); } - OutputFormat::Table => println!("{}", env!("CARGO_PKG_VERSION")), - }, + } Commands::Completions { shell } => { let mut cmd = Cli::command(); let name = cmd.get_name().to_owned(); @@ -119,13 +122,15 @@ fn run() -> Result<(), IdxError> { Ok(()) } +/// Output format for errors raised before the configuration has loaded. fn bootstrap_output_format(cli: &Cli) -> OutputFormat { - if let Some(output) = cli.output { - return output; - } - - match std::env::var("IDX_OUTPUT") { - Ok(value) if value.eq_ignore_ascii_case("json") => OutputFormat::Json, - _ => OutputFormat::Table, - } + let setting = cli + .output + .or_else(|| { + std::env::var("IDX_OUTPUT") + .ok() + .and_then(|value| OutputSetting::parse(&value)) + }) + .unwrap_or_default(); + setting.resolve(config::stdout_is_terminal()) } diff --git a/src/output/json.rs b/src/output/json.rs index 9c094f0..1b8d223 100644 --- a/src/output/json.rs +++ b/src/output/json.rs @@ -1,10 +1,44 @@ +use std::sync::atomic::{AtomicU8, Ordering}; + use serde::Serialize; use crate::error::IdxError; +const PRETTY: u8 = 0; +const COMPACT: u8 = 1; +const LINES: u8 = 2; + +static STYLE: AtomicU8 = AtomicU8::new(PRETTY); + +/// Pretty JSON only for a human at a terminal; compact for pipes and +/// captured output; JSON Lines when requested. +pub fn configure(format: super::OutputFormat, stdout_is_terminal: bool) { + let style = match format { + super::OutputFormat::Jsonl => LINES, + _ if stdout_is_terminal => PRETTY, + _ => COMPACT, + }; + STYLE.store(style, Ordering::Relaxed); +} + pub fn print_json(value: &T) -> Result<(), IdxError> { - let out = - serde_json::to_string_pretty(value).map_err(|e| IdxError::ParseError(e.to_string()))?; - println!("{out}"); + let to_err = |e: serde_json::Error| IdxError::ParseError(e.to_string()); + match STYLE.load(Ordering::Relaxed) { + PRETTY => println!("{}", serde_json::to_string_pretty(value).map_err(to_err)?), + COMPACT => println!("{}", serde_json::to_string(value).map_err(to_err)?), + _ => { + // Split an array into one line per item without re-serializing, + // so each line keeps the same key order as `-o json`. + let compact = serde_json::to_string(value).map_err(to_err)?; + match serde_json::from_str::>(&compact) { + Ok(items) => { + for item in items { + println!("{}", item.get()); + } + } + Err(_) => println!("{compact}"), + } + } + } Ok(()) } diff --git a/src/output/mod.rs b/src/output/mod.rs index d82af17..4559a2a 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -1,6 +1,9 @@ pub mod json; +pub mod style; pub mod table; +use std::io::IsTerminal; + use chrono::NaiveDate; use clap::ValueEnum; use serde::{Deserialize, Serialize}; @@ -13,12 +16,48 @@ use crate::api::types::{ }; use crate::error::IdxError; +/// Output format requested by the user (flag, `IDX_OUTPUT`, or config file). #[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, serde::Deserialize, Default)] #[serde(rename_all = "lowercase")] +pub enum OutputSetting { + // Table on a terminal, JSON when stdout is piped or captured. + #[default] + Auto, + Table, + Json, + // JSON Lines: one array item per line. + Jsonl, +} + +impl OutputSetting { + pub fn parse(value: &str) -> Option { + ::from_str(value, true).ok() + } + + pub fn resolve(self, stdout_is_terminal: bool) -> OutputFormat { + match self { + Self::Auto if stdout_is_terminal => OutputFormat::Table, + Self::Auto | Self::Json => OutputFormat::Json, + Self::Table => OutputFormat::Table, + Self::Jsonl => OutputFormat::Jsonl, + } + } +} + +/// Resolved output format used by renderers. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum OutputFormat { #[default] Table, Json, + Jsonl, +} + +impl OutputFormat { + /// True for every machine-readable format (JSON and JSON Lines). + pub fn is_json(self) -> bool { + matches!(self, Self::Json | Self::Jsonl) + } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -56,7 +95,7 @@ pub fn render_quotes( ) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_quotes(quotes, no_color), - OutputFormat::Json => json::print_json(quotes), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(quotes), } } @@ -67,7 +106,7 @@ pub fn render_history( ) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_history(symbol, history), - OutputFormat::Json => json::print_json(history), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(history), } } @@ -78,7 +117,7 @@ pub fn render_technical( ) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_technical(report, no_color), - OutputFormat::Json => json::print_json(report), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report), } } @@ -90,7 +129,7 @@ pub fn render_growth( ) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_growth(symbol, report, no_color), - OutputFormat::Json => json::print_json(report), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report), } } @@ -102,7 +141,7 @@ pub fn render_valuation( ) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_valuation(symbol, report, no_color), - OutputFormat::Json => json::print_json(report), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report), } } @@ -114,7 +153,7 @@ pub fn render_risk( ) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_risk(symbol, report, no_color), - OutputFormat::Json => json::print_json(report), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report), } } @@ -125,7 +164,7 @@ pub fn render_fundamental( ) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_fundamental(report, no_color), - OutputFormat::Json => json::print_json(report), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report), } } @@ -136,14 +175,14 @@ pub fn render_compare( ) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_compare(reports, no_color), - OutputFormat::Json => json::print_json(reports), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(reports), } } pub fn render_profile(profile: &CompanyProfile, format: &OutputFormat) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_profile(profile), - OutputFormat::Json => json::print_json(profile), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(profile), } } @@ -153,35 +192,35 @@ pub fn render_financials( ) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_financials(financials), - OutputFormat::Json => json::print_json(financials), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(financials), } } pub fn render_earnings(report: &EarningsReport, format: &OutputFormat) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_earnings(report), - OutputFormat::Json => json::print_json(report), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(report), } } pub fn render_sentiment(data: &SentimentData, format: &OutputFormat) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_sentiment(data), - OutputFormat::Json => json::print_json(data), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(data), } } pub fn render_insights(data: &InsightData, format: &OutputFormat) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_insights(data), - OutputFormat::Json => json::print_json(data), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(data), } } pub fn render_news(items: &[NewsItem], format: &OutputFormat) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_news(items), - OutputFormat::Json => json::print_json(items), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(items), } } @@ -192,23 +231,25 @@ pub fn render_screener( ) -> Result<(), IdxError> { match format { OutputFormat::Table => table::print_quotes(quotes, no_color), - OutputFormat::Json => json::print_json(quotes), + OutputFormat::Json | OutputFormat::Jsonl => json::print_json(quotes), } } pub fn emit_error(err: &IdxError, format: &OutputFormat) { - match format { - OutputFormat::Table => eprintln!("Error: {err}"), - OutputFormat::Json => { - let payload = serde_json::json!({ - "error": true, - "code": format!("{:?}", err.code()).to_uppercase(), - "message": err.to_string() - }); - eprintln!( - "{}", - serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".to_string()) - ); - } + if !format.is_json() { + eprintln!("Error: {err}"); + return; } + let payload = serde_json::json!({ + "error": true, + "code": format!("{:?}", err.code()).to_uppercase(), + "message": err.to_string() + }); + let pretty = *format == OutputFormat::Json && std::io::stderr().is_terminal(); + let rendered = if pretty { + serde_json::to_string_pretty(&payload) + } else { + serde_json::to_string(&payload) + }; + eprintln!("{}", rendered.unwrap_or_else(|_| "{}".to_string())); } diff --git a/src/output/style.rs b/src/output/style.rs new file mode 100644 index 0000000..ee72e50 --- /dev/null +++ b/src/output/style.rs @@ -0,0 +1,41 @@ +//! Terminal styling that turns itself off when color is disabled or stdout is +//! not a terminal, so piped output never carries ANSI escape codes. + +use std::fmt::Display; +use std::sync::atomic::{AtomicBool, Ordering}; + +use owo_colors::OwoColorize; + +static COLOR: AtomicBool = AtomicBool::new(true); + +pub fn set_color(enabled: bool) { + COLOR.store(enabled, Ordering::Relaxed); +} + +fn paint(value: T, styled: impl FnOnce(&T) -> String) -> String { + if COLOR.load(Ordering::Relaxed) { + styled(&value) + } else { + value.to_string() + } +} + +pub trait Paint: Display + Sized { + fn bold(self) -> String { + paint(self, |v| OwoColorize::bold(v).to_string()) + } + fn green(self) -> String { + paint(self, |v| OwoColorize::green(v).to_string()) + } + fn red(self) -> String { + paint(self, |v| OwoColorize::red(v).to_string()) + } + fn yellow(self) -> String { + paint(self, |v| OwoColorize::yellow(v).to_string()) + } + fn blue(self) -> String { + paint(self, |v| OwoColorize::blue(v).to_string()) + } +} + +impl Paint for T {} diff --git a/src/output/table.rs b/src/output/table.rs index 4257345..2f26f94 100644 --- a/src/output/table.rs +++ b/src/output/table.rs @@ -1,5 +1,5 @@ +use crate::output::style::Paint; use comfy_table::{Cell, Color, ContentArrangement, Table, presets::UTF8_FULL}; -use owo_colors::OwoColorize; use crate::analysis::fundamental::{FundamentalReport, GrowthReport, RiskReport, ValuationReport}; use crate::analysis::signals::Signal; diff --git a/tests/cli.rs b/tests/cli.rs index 13f95f5..5e3f34d 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -73,7 +73,10 @@ fn candidate_is_idx_binary(path: &Path) -> bool { return false; } - let Ok(output) = std::process::Command::new(path).arg("version").output() else { + let Ok(output) = std::process::Command::new(path) + .args(["-o", "table", "version"]) + .output() + else { return false; }; @@ -111,6 +114,10 @@ fn bin_with_root(root: &Path) -> Command { let mut cmd = bin(); cmd.current_dir(env!("CARGO_MANIFEST_DIR")); + // Tests run with piped stdout, where the default (auto) format is JSON. + // Pin table so tests that assert table text keep doing so; `-o` and + // per-test IDX_OUTPUT still override this. + cmd.env("IDX_OUTPUT", "table"); cmd.env("XDG_CONFIG_HOME", &config_home); cmd.env("XDG_CACHE_HOME", &cache_home); cmd @@ -1271,18 +1278,18 @@ fn ownership_discover_json_includes_status() { }"#; let json_url = spawn_single_response_server("application/json", body.to_string()); - test_bin("ownership-discover-json-status") - .env("IDX_CURL_IMPERSONATE_BIN", "curl") - .env("IDX_OWNERSHIP_ANNOUNCEMENT_API_URL", &json_url) - .env("IDX_OWNERSHIP_DATA_PAGE_URL", UNREACHABLE_DATA_PAGE_URL) - .env( - "IDX_OWNERSHIP_ANNOUNCEMENT_PAGE_URL", - "http://127.0.0.1/pengumuman", - ) - .args(["-o", "json", "ownership", "discover", "--limit", "1"]) - .assert() - .success() - .stdout(predicate::str::contains("\"status\": \"supported\"")); + let discovered = run_success_json( + test_bin("ownership-discover-json-status") + .env("IDX_CURL_IMPERSONATE_BIN", "curl") + .env("IDX_OWNERSHIP_ANNOUNCEMENT_API_URL", &json_url) + .env("IDX_OWNERSHIP_DATA_PAGE_URL", UNREACHABLE_DATA_PAGE_URL) + .env( + "IDX_OWNERSHIP_ANNOUNCEMENT_PAGE_URL", + "http://127.0.0.1/pengumuman", + ) + .args(["-o", "json", "ownership", "discover", "--limit", "1"]), + ); + assert_eq!(discovered[0]["status"], "supported"); } #[test] diff --git a/tests/golden.rs b/tests/golden.rs index 884abf2..d6c5aba 100644 --- a/tests/golden.rs +++ b/tests/golden.rs @@ -10,8 +10,9 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fs; +use std::io::Write; use std::path::{Path, PathBuf}; -use std::process::Command; +use std::process::{Command, Stdio}; use std::sync::OnceLock; use serde::Deserialize; @@ -40,6 +41,9 @@ struct Case { /// Commands run first in the same environment; their output is ignored. #[serde(default)] before: Vec>, + /// Text piped to the command's stdin. + #[serde(default)] + stdin: Option, } fn default_formats() -> Vec { @@ -56,6 +60,15 @@ fn work_dir() -> PathBuf { /// Runs idx with an isolated home under `root` and only the given IDX_* env. fn idx(root: &Path, args: &[String], env: &BTreeMap) -> std::process::Output { + idx_with_stdin(root, args, env, None) +} + +fn idx_with_stdin( + root: &Path, + args: &[String], + env: &BTreeMap, + stdin: Option<&str>, +) -> std::process::Output { let mut cmd = Command::new(IDX); cmd.current_dir(REPO).args(args); for (key, _) in std::env::vars_os() { @@ -68,8 +81,17 @@ fn idx(root: &Path, args: &[String], env: &BTreeMap) -> std::pro .env("XDG_CACHE_HOME", root.join("cache")) .env("XDG_DATA_HOME", root.join("data")) .env("IDX_USE_MOCK_PROVIDER", "1") - .envs(env); - cmd.output().expect("run idx") + .envs(env) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + let mut child = cmd.spawn().expect("spawn idx"); + let mut pipe = child.stdin.take().expect("stdin pipe"); + if let Some(input) = stdin { + pipe.write_all(input.as_bytes()).expect("write stdin"); + } + drop(pipe); + child.wait_with_output().expect("run idx") } /// Fixture ownership DB: the above-1% XLSX (2026-08-31) plus two KSEI archive @@ -161,7 +183,8 @@ fn cli_output_matches_golden_files() { idx(&root, before, &case.env); } - let actual = render(&root, &idx(&root, &args, &case.env)); + let output = idx_with_stdin(&root, &args, &case.env, case.stdin.as_deref()); + let actual = render(&root, &output); let path = golden_dir().join(format!("{name}.golden")); if update { fs::write(&path, &actual).expect("write golden"); diff --git a/tests/golden/auto-format-env-table.golden b/tests/golden/auto-format-env-table.golden new file mode 100644 index 0000000..6102d71 --- /dev/null +++ b/tests/golden/auto-format-env-table.golden @@ -0,0 +1,8 @@ +exit: 0 +--- stdout +┌─────────┬───────┬──────┬────────┬────────────┬───────────────────────┬────────────┐ +│ SYMBOL ┆ PRICE ┆ CHG ┆ CHG% ┆ VOLUME ┆ MKT CAP ┆ 52W RANGE │ +╞═════════╪═══════╪══════╪════════╪════════════╪═══════════════════════╪════════════╡ +│ BBCA.JK ┆ 9,875 ┆ +117 ┆ +1.20% ┆ 12,300,000 ┆ 1,215,200,000,000,000 ┆ ████████░░ │ +└─────────┴───────┴──────┴────────┴────────────┴───────────────────────┴────────────┘ +--- stderr diff --git a/tests/golden/auto-format-piped.golden b/tests/golden/auto-format-piped.golden new file mode 100644 index 0000000..9df5da7 --- /dev/null +++ b/tests/golden/auto-format-piped.golden @@ -0,0 +1,4 @@ +exit: 0 +--- stdout +[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null},{"symbol":"BBRI.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}] +--- stderr diff --git a/tests/golden/cache-clear.json.golden b/tests/golden/cache-clear.json.golden index ac869ff..d8fca10 100644 --- a/tests/golden/cache-clear.json.golden +++ b/tests/golden/cache-clear.json.golden @@ -1,7 +1,4 @@ exit: 0 --- stdout -{ - "failed": [], - "removed": 0 -} +{"failed":[],"removed":0} --- stderr diff --git a/tests/golden/cache-info-empty.json.golden b/tests/golden/cache-info-empty.json.golden index eb5a389..e68ed73 100644 --- a/tests/golden/cache-info-empty.json.golden +++ b/tests/golden/cache-info-empty.json.golden @@ -1,10 +1,4 @@ exit: 0 --- stdout -{ - "files": 0, - "newest": null, - "oldest": null, - "path": "/cache/idx", - "size_bytes": 0 -} +{"files":0,"newest":null,"oldest":null,"path":"/cache/idx","size_bytes":0} --- stderr diff --git a/tests/golden/cases.toml b/tests/golden/cases.toml index f81a4f0..2a44511 100644 --- a/tests/golden/cases.toml +++ b/tests/golden/cases.toml @@ -1,9 +1,11 @@ # Golden output cases for tests/golden.rs. # # Each case runs once per entry in `formats` (default: table and json) with -# `-o ` prepended; `formats = []` runs the args as given. Every run uses -# the mock provider (IDX_USE_MOCK_PROVIDER=1) and an isolated home directory. -# `ownership = true` seeds the fixture DB; `before` runs setup commands first. +# `-o ` prepended; `formats = []` runs the args as given, which means +# the auto format: stdout is a pipe here, so auto resolves to compact JSON. +# Every run uses the mock provider (IDX_USE_MOCK_PROVIDER=1) and an isolated +# home directory. `ownership = true` seeds the fixture DB; `before` runs setup +# commands first; `stdin` is piped to the command. # # `idx version` is excluded on purpose: its output changes with every release. @@ -62,6 +64,22 @@ name = "config-set" args = ["config", "set", "general.provider", "yahoo"] formats = [] +[[case]] +name = "config-set-output-jsonl" +args = ["config", "set", "general.output", "jsonl"] +formats = [] + +[[case]] +name = "config-set-output-invalid" +args = ["config", "set", "general.output", "yaml"] +formats = [] + +[[case]] +name = "config-output-table-pins-format" +args = ["stocks", "quote", "BBCA"] +formats = [] +before = [["config", "set", "general.output", "table"]] + [[case]] name = "cache-info-empty" args = ["cache", "info"] @@ -78,10 +96,12 @@ args = ["stocks", "quote", "BBCA"] [[case]] name = "stocks-quote-multi" +formats = ["table", "json", "jsonl"] args = ["stocks", "quote", "BBCA,BBRI"] [[case]] name = "stocks-history" +formats = ["table", "json", "jsonl"] args = ["stocks", "history", "BBCA"] [[case]] @@ -111,6 +131,7 @@ args = ["stocks", "risk", "BBCA"] [[case]] name = "stocks-fundamental" +formats = ["table", "json", "jsonl"] args = ["stocks", "fundamental", "BBCA"] [[case]] @@ -169,6 +190,38 @@ args = ["stocks", "screen", "--filter", "high-volume"] name = "stocks-screen-large-cap" args = ["stocks", "screen", "--filter", "large-cap"] +[[case]] +name = "auto-format-piped" +args = ["stocks", "quote", "BBCA,BBRI"] +formats = [] + +[[case]] +name = "auto-format-env-table" +args = ["stocks", "quote", "BBCA"] +formats = [] +env = { IDX_OUTPUT = "table" } + +[[case]] +name = "stocks-quote-stdin" +args = ["stocks", "quote", "-"] +stdin = "BBCA\nBBRI\n" + +[[case]] +name = "stocks-compare-stdin" +args = ["stocks", "compare", "-"] +stdin = "BBCA, BBRI" + +[[case]] +name = "error-stdin-empty" +args = ["stocks", "quote", "-"] +stdin = "" + +[[case]] +name = "error-invalid-output-env" +args = ["stocks", "quote", "BBCA"] +formats = [] +env = { IDX_OUTPUT = "yaml" } + [[case]] name = "stocks-quote-offline-warm" args = ["--offline", "stocks", "quote", "BBCA"] @@ -214,6 +267,7 @@ env = { IDX_MOCK_MSN_KEYRATIOS_FIXTURE = "tests/fixtures/msn_keyratios_industry_ [[case]] name = "error-screen-region" +formats = ["table", "json", "jsonl"] args = ["stocks", "screen", "--region", "eu"] [[case]] @@ -298,6 +352,7 @@ ownership = true [[case]] name = "ownership-changes" +formats = ["table", "json", "jsonl"] args = ["ownership", "changes", "--from", "2026-01-30", "--to", "2026-02-27"] ownership = true diff --git a/tests/golden/completions-bash.golden b/tests/golden/completions-bash.golden index 5cee459..6439682 100644 --- a/tests/golden/completions-bash.golden +++ b/tests/golden/completions-bash.golden @@ -446,11 +446,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -468,11 +468,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -490,11 +490,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -568,11 +568,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -590,11 +590,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -612,11 +612,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -634,11 +634,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -740,11 +740,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -762,11 +762,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -784,11 +784,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1436,11 +1436,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1466,11 +1466,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1496,11 +1496,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1526,11 +1526,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1556,11 +1556,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1578,11 +1578,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1600,11 +1600,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1630,11 +1630,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1916,11 +1916,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1938,11 +1938,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -1960,11 +1960,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2052,11 +2052,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2074,11 +2074,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2100,11 +2100,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2126,11 +2126,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2152,11 +2152,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2178,11 +2178,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2200,11 +2200,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2222,11 +2222,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2244,11 +2244,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2270,11 +2270,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2292,11 +2292,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2314,11 +2314,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2586,11 +2586,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2608,11 +2608,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2634,11 +2634,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2656,11 +2656,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2678,11 +2678,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2700,11 +2700,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2734,11 +2734,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2756,11 +2756,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2782,11 +2782,11 @@ _idx() { return 0 ;; --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2804,11 +2804,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) @@ -2826,11 +2826,11 @@ _idx() { fi case "${prev}" in --output) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; -o) - COMPREPLY=($(compgen -W "table json" -- "${cur}")) + COMPREPLY=($(compgen -W "auto table json jsonl" -- "${cur}")) return 0 ;; *) diff --git a/tests/golden/config-output-table-pins-format.golden b/tests/golden/config-output-table-pins-format.golden new file mode 100644 index 0000000..6102d71 --- /dev/null +++ b/tests/golden/config-output-table-pins-format.golden @@ -0,0 +1,8 @@ +exit: 0 +--- stdout +┌─────────┬───────┬──────┬────────┬────────────┬───────────────────────┬────────────┐ +│ SYMBOL ┆ PRICE ┆ CHG ┆ CHG% ┆ VOLUME ┆ MKT CAP ┆ 52W RANGE │ +╞═════════╪═══════╪══════╪════════╪════════════╪═══════════════════════╪════════════╡ +│ BBCA.JK ┆ 9,875 ┆ +117 ┆ +1.20% ┆ 12,300,000 ┆ 1,215,200,000,000,000 ┆ ████████░░ │ +└─────────┴───────┴──────┴────────┴────────────┴───────────────────────┴────────────┘ +--- stderr diff --git a/tests/golden/config-set-output-invalid.golden b/tests/golden/config-set-output-invalid.golden new file mode 100644 index 0000000..4260d21 --- /dev/null +++ b/tests/golden/config-set-output-invalid.golden @@ -0,0 +1,4 @@ +exit: 2 +--- stdout +--- stderr +{"code":"INVALIDINPUT","error":true,"message":"invalid input: invalid output format 'yaml': expected 'auto', 'table', 'json', or 'jsonl'"} diff --git a/tests/golden/config-set-output-jsonl.golden b/tests/golden/config-set-output-jsonl.golden new file mode 100644 index 0000000..aecf1eb --- /dev/null +++ b/tests/golden/config-set-output-jsonl.golden @@ -0,0 +1,4 @@ +exit: 0 +--- stdout +ok +--- stderr diff --git a/tests/golden/error-compare-total-failure.json.golden b/tests/golden/error-compare-total-failure.json.golden index 9c72e99..485ff72 100644 --- a/tests/golden/error-compare-total-failure.json.golden +++ b/tests/golden/error-compare-total-failure.json.golden @@ -1,8 +1,4 @@ -exit: 1 +exit: 4 --- stdout --- stderr -{ - "code": "PROVIDERUNAVAILABLE", - "error": true, - "message": "provider unavailable" -} +{"code":"PROVIDERUNAVAILABLE","error":true,"message":"provider unavailable"} diff --git a/tests/golden/error-compare-total-failure.table.golden b/tests/golden/error-compare-total-failure.table.golden index fc7d924..711b361 100644 --- a/tests/golden/error-compare-total-failure.table.golden +++ b/tests/golden/error-compare-total-failure.table.golden @@ -1,4 +1,4 @@ -exit: 1 +exit: 4 --- stdout --- stderr Error: provider unavailable diff --git a/tests/golden/error-fetch-bing-unsupported.json.golden b/tests/golden/error-fetch-bing-unsupported.json.golden index 1c73d67..5160a6b 100644 --- a/tests/golden/error-fetch-bing-unsupported.json.golden +++ b/tests/golden/error-fetch-bing-unsupported.json.golden @@ -1,8 +1,4 @@ -exit: 1 +exit: 5 --- stdout --- stderr -{ - "code": "UNSUPPORTED", - "error": true, - "message": "unsupported: --fetch-bing import is not implemented yet" -} +{"code":"UNSUPPORTED","error":true,"message":"unsupported: --fetch-bing import is not implemented yet"} diff --git a/tests/golden/error-fetch-bing-unsupported.table.golden b/tests/golden/error-fetch-bing-unsupported.table.golden index bf6ba34..0beac40 100644 --- a/tests/golden/error-fetch-bing-unsupported.table.golden +++ b/tests/golden/error-fetch-bing-unsupported.table.golden @@ -1,4 +1,4 @@ -exit: 1 +exit: 5 --- stdout --- stderr Error: unsupported: --fetch-bing import is not implemented yet diff --git a/tests/golden/error-industry-only-fundamentals.json.golden b/tests/golden/error-industry-only-fundamentals.json.golden index d8fb2a5..174e400 100644 --- a/tests/golden/error-industry-only-fundamentals.json.golden +++ b/tests/golden/error-industry-only-fundamentals.json.golden @@ -1,8 +1,4 @@ exit: 1 --- stdout --- stderr -{ - "code": "PARSEERROR", - "error": true, - "message": "parse error: unsupported: company fundamentals unavailable from MSN; industry fallback is disabled" -} +{"code":"PARSEERROR","error":true,"message":"parse error: unsupported: company fundamentals unavailable from MSN; industry fallback is disabled"} diff --git a/tests/golden/error-invalid-output-env.golden b/tests/golden/error-invalid-output-env.golden new file mode 100644 index 0000000..ff8cf52 --- /dev/null +++ b/tests/golden/error-invalid-output-env.golden @@ -0,0 +1,4 @@ +exit: 2 +--- stdout +--- stderr +{"code":"CONFIGERROR","error":true,"message":"config error: invalid IDX_OUTPUT value: 'yaml', expected 'auto', 'table', 'json', or 'jsonl'"} diff --git a/tests/golden/error-invalid-provider.json.golden b/tests/golden/error-invalid-provider.json.golden index cb67653..7e9cef0 100644 --- a/tests/golden/error-invalid-provider.json.golden +++ b/tests/golden/error-invalid-provider.json.golden @@ -1,8 +1,4 @@ -exit: 1 +exit: 2 --- stdout --- stderr -{ - "code": "CONFIGERROR", - "error": true, - "message": "config error: invalid provider 'bogus' (expected yahoo or msn)" -} +{"code":"CONFIGERROR","error":true,"message":"config error: invalid provider 'bogus' (expected yahoo or msn)"} diff --git a/tests/golden/error-invalid-provider.table.golden b/tests/golden/error-invalid-provider.table.golden index 8b5ee37..37464ed 100644 --- a/tests/golden/error-invalid-provider.table.golden +++ b/tests/golden/error-invalid-provider.table.golden @@ -1,4 +1,4 @@ -exit: 1 +exit: 2 --- stdout --- stderr Error: config error: invalid provider 'bogus' (expected yahoo or msn) diff --git a/tests/golden/error-offline-cache-miss.json.golden b/tests/golden/error-offline-cache-miss.json.golden index 6006ad7..44b093f 100644 --- a/tests/golden/error-offline-cache-miss.json.golden +++ b/tests/golden/error-offline-cache-miss.json.golden @@ -1,8 +1,4 @@ -exit: 1 +exit: 3 --- stdout --- stderr -{ - "code": "CACHEMISS", - "error": true, - "message": "cache miss: msn-quote/BBCA.JK" -} +{"code":"CACHEMISS","error":true,"message":"cache miss: msn-quote/BBCA.JK"} diff --git a/tests/golden/error-offline-cache-miss.table.golden b/tests/golden/error-offline-cache-miss.table.golden index 27e5af2..2b09a39 100644 --- a/tests/golden/error-offline-cache-miss.table.golden +++ b/tests/golden/error-offline-cache-miss.table.golden @@ -1,4 +1,4 @@ -exit: 1 +exit: 3 --- stdout --- stderr Error: cache miss: msn-quote/BBCA.JK diff --git a/tests/golden/error-offline-no-cache-flags.json.golden b/tests/golden/error-offline-no-cache-flags.json.golden index 8d9b175..6c243ed 100644 --- a/tests/golden/error-offline-no-cache-flags.json.golden +++ b/tests/golden/error-offline-no-cache-flags.json.golden @@ -1,8 +1,4 @@ -exit: 1 +exit: 2 --- stdout --- stderr -{ - "code": "INVALIDINPUT", - "error": true, - "message": "invalid input: cannot combine --offline with --no-cache" -} +{"code":"INVALIDINPUT","error":true,"message":"invalid input: cannot combine --offline with --no-cache"} diff --git a/tests/golden/error-offline-no-cache-flags.table.golden b/tests/golden/error-offline-no-cache-flags.table.golden index 0dcead6..10a92aa 100644 --- a/tests/golden/error-offline-no-cache-flags.table.golden +++ b/tests/golden/error-offline-no-cache-flags.table.golden @@ -1,4 +1,4 @@ -exit: 1 +exit: 2 --- stdout --- stderr Error: invalid input: cannot combine --offline with --no-cache diff --git a/tests/golden/error-provider-failure.json.golden b/tests/golden/error-provider-failure.json.golden index 9c72e99..485ff72 100644 --- a/tests/golden/error-provider-failure.json.golden +++ b/tests/golden/error-provider-failure.json.golden @@ -1,8 +1,4 @@ -exit: 1 +exit: 4 --- stdout --- stderr -{ - "code": "PROVIDERUNAVAILABLE", - "error": true, - "message": "provider unavailable" -} +{"code":"PROVIDERUNAVAILABLE","error":true,"message":"provider unavailable"} diff --git a/tests/golden/error-provider-failure.table.golden b/tests/golden/error-provider-failure.table.golden index fc7d924..711b361 100644 --- a/tests/golden/error-provider-failure.table.golden +++ b/tests/golden/error-provider-failure.table.golden @@ -1,4 +1,4 @@ -exit: 1 +exit: 4 --- stdout --- stderr Error: provider unavailable diff --git a/tests/golden/error-screen-region.json.golden b/tests/golden/error-screen-region.json.golden index e3ad825..8cf7204 100644 --- a/tests/golden/error-screen-region.json.golden +++ b/tests/golden/error-screen-region.json.golden @@ -1,8 +1,4 @@ -exit: 1 +exit: 2 --- stdout --- stderr -{ - "code": "INVALIDINPUT", - "error": true, - "message": "invalid input: invalid screener region 'eu' (expected one of: id, us, sg, hk, jp)" -} +{"code":"INVALIDINPUT","error":true,"message":"invalid input: invalid screener region 'eu' (expected one of: id, us, sg, hk, jp)"} diff --git a/tests/golden/error-screen-region.jsonl.golden b/tests/golden/error-screen-region.jsonl.golden new file mode 100644 index 0000000..8cf7204 --- /dev/null +++ b/tests/golden/error-screen-region.jsonl.golden @@ -0,0 +1,4 @@ +exit: 2 +--- stdout +--- stderr +{"code":"INVALIDINPUT","error":true,"message":"invalid input: invalid screener region 'eu' (expected one of: id, us, sg, hk, jp)"} diff --git a/tests/golden/error-screen-region.table.golden b/tests/golden/error-screen-region.table.golden index 39ac5be..4b13a9e 100644 --- a/tests/golden/error-screen-region.table.golden +++ b/tests/golden/error-screen-region.table.golden @@ -1,4 +1,4 @@ -exit: 1 +exit: 2 --- stdout --- stderr Error: invalid input: invalid screener region 'eu' (expected one of: id, us, sg, hk, jp) diff --git a/tests/golden/error-stdin-empty.json.golden b/tests/golden/error-stdin-empty.json.golden new file mode 100644 index 0000000..512e7c8 --- /dev/null +++ b/tests/golden/error-stdin-empty.json.golden @@ -0,0 +1,4 @@ +exit: 2 +--- stdout +--- stderr +{"code":"INVALIDINPUT","error":true,"message":"invalid input: no symbols read from stdin"} diff --git a/tests/golden/error-stdin-empty.table.golden b/tests/golden/error-stdin-empty.table.golden new file mode 100644 index 0000000..9b22c0f --- /dev/null +++ b/tests/golden/error-stdin-empty.table.golden @@ -0,0 +1,4 @@ +exit: 2 +--- stdout +--- stderr +Error: invalid input: no symbols read from stdin diff --git a/tests/golden/error-yahoo-profile-unsupported.json.golden b/tests/golden/error-yahoo-profile-unsupported.json.golden index 237c99d..12d072c 100644 --- a/tests/golden/error-yahoo-profile-unsupported.json.golden +++ b/tests/golden/error-yahoo-profile-unsupported.json.golden @@ -1,8 +1,4 @@ -exit: 1 +exit: 5 --- stdout --- stderr -{ - "code": "UNSUPPORTED", - "error": true, - "message": "unsupported: BBCA.JK: command requires --provider msn" -} +{"code":"UNSUPPORTED","error":true,"message":"unsupported: BBCA.JK: command requires --provider msn"} diff --git a/tests/golden/error-yahoo-profile-unsupported.table.golden b/tests/golden/error-yahoo-profile-unsupported.table.golden index 762324c..c3789d8 100644 --- a/tests/golden/error-yahoo-profile-unsupported.table.golden +++ b/tests/golden/error-yahoo-profile-unsupported.table.golden @@ -1,4 +1,4 @@ -exit: 1 +exit: 5 --- stdout --- stderr Error: unsupported: BBCA.JK: command requires --provider msn diff --git a/tests/golden/help-cache.golden b/tests/golden/help-cache.golden index 40f2b4f..27f05b0 100644 --- a/tests/golden/help-cache.golden +++ b/tests/golden/help-cache.golden @@ -10,7 +10,7 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -o, --output Output format [possible values: table, json] + -o, --output Output format [default: auto = table on a terminal, json when piped] [possible values: auto, table, json, jsonl] --no-color Disable colored output -q, --quiet Suppress non-essential output -v, --verbose... Increase verbosity (-v, -vv) diff --git a/tests/golden/help-config.golden b/tests/golden/help-config.golden index 485641f..e594fbc 100644 --- a/tests/golden/help-config.golden +++ b/tests/golden/help-config.golden @@ -12,7 +12,7 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -o, --output Output format [possible values: table, json] + -o, --output Output format [default: auto = table on a terminal, json when piped] [possible values: auto, table, json, jsonl] --no-color Disable colored output -q, --quiet Suppress non-essential output -v, --verbose... Increase verbosity (-v, -vv) diff --git a/tests/golden/help-ownership.golden b/tests/golden/help-ownership.golden index 44bb1e8..54501d7 100644 --- a/tests/golden/help-ownership.golden +++ b/tests/golden/help-ownership.golden @@ -27,9 +27,9 @@ Commands: Options: -o, --output - Output format + Output format [default: auto = table on a terminal, json when piped] - [possible values: table, json] + [possible values: auto, table, json, jsonl] --no-color Disable colored output diff --git a/tests/golden/help-root.golden b/tests/golden/help-root.golden index 6095643..2814cde 100644 --- a/tests/golden/help-root.golden +++ b/tests/golden/help-root.golden @@ -16,9 +16,9 @@ Commands: Options: -o, --output - Output format + Output format [default: auto = table on a terminal, json when piped] - [possible values: table, json] + [possible values: auto, table, json, jsonl] --no-color Disable colored output diff --git a/tests/golden/help-stocks.golden b/tests/golden/help-stocks.golden index 1c51546..8210119 100644 --- a/tests/golden/help-stocks.golden +++ b/tests/golden/help-stocks.golden @@ -23,7 +23,7 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -o, --output Output format [possible values: table, json] + -o, --output Output format [default: auto = table on a terminal, json when piped] [possible values: auto, table, json, jsonl] --no-color Disable colored output -q, --quiet Suppress non-essential output -v, --verbose... Increase verbosity (-v, -vv) diff --git a/tests/golden/ownership-changes.json.golden b/tests/golden/ownership-changes.json.golden index 138b1cb..78e42e5 100644 --- a/tests/golden/ownership-changes.json.golden +++ b/tests/golden/ownership-changes.json.golden @@ -1,109 +1,4 @@ exit: 0 --- stdout -[ - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE LOCAL ID", - "change_type": "decreased", - "old_bps": 2400, - "new_bps": 2334, - "delta_bps": -66 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE LOCAL IS", - "change_type": "increased", - "old_bps": 75, - "new_bps": 129, - "delta_bps": 54 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE LOCAL MF", - "change_type": "increased", - "old_bps": 117, - "new_bps": 152, - "delta_bps": 35 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE FOREIGN MF", - "change_type": "increased", - "old_bps": 307, - "new_bps": 337, - "delta_bps": 30 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE FOREIGN OT", - "change_type": "decreased", - "old_bps": 91, - "new_bps": 64, - "delta_bps": -27 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE FOREIGN PF", - "change_type": "decreased", - "old_bps": 76, - "new_bps": 56, - "delta_bps": -20 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE FOREIGN CP", - "change_type": "increased", - "old_bps": 72, - "new_bps": 86, - "delta_bps": 14 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE LOCAL CP", - "change_type": "decreased", - "old_bps": 6481, - "new_bps": 6467, - "delta_bps": -14 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE LOCAL SC", - "change_type": "decreased", - "old_bps": 36, - "new_bps": 29, - "delta_bps": -7 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE FOREIGN IB", - "change_type": "decreased", - "old_bps": 267, - "new_bps": 263, - "delta_bps": -4 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE FOREIGN SC", - "change_type": "increased", - "old_bps": 51, - "new_bps": 55, - "delta_bps": 4 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE LOCAL PF", - "change_type": "increased", - "old_bps": 17, - "new_bps": 20, - "delta_bps": 3 - }, - { - "ticker_code": "AADI", - "entity_name": "KSEI AGGREGATE FOREIGN ID", - "change_type": "decreased", - "old_bps": 3, - "new_bps": 2, - "delta_bps": -1 - } -] +[{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL ID","change_type":"decreased","old_bps":2400,"new_bps":2334,"delta_bps":-66},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL IS","change_type":"increased","old_bps":75,"new_bps":129,"delta_bps":54},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL MF","change_type":"increased","old_bps":117,"new_bps":152,"delta_bps":35},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN MF","change_type":"increased","old_bps":307,"new_bps":337,"delta_bps":30},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN OT","change_type":"decreased","old_bps":91,"new_bps":64,"delta_bps":-27},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN PF","change_type":"decreased","old_bps":76,"new_bps":56,"delta_bps":-20},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN CP","change_type":"increased","old_bps":72,"new_bps":86,"delta_bps":14},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL CP","change_type":"decreased","old_bps":6481,"new_bps":6467,"delta_bps":-14},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL SC","change_type":"decreased","old_bps":36,"new_bps":29,"delta_bps":-7},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN IB","change_type":"decreased","old_bps":267,"new_bps":263,"delta_bps":-4},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN SC","change_type":"increased","old_bps":51,"new_bps":55,"delta_bps":4},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL PF","change_type":"increased","old_bps":17,"new_bps":20,"delta_bps":3},{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN ID","change_type":"decreased","old_bps":3,"new_bps":2,"delta_bps":-1}] --- stderr diff --git a/tests/golden/ownership-changes.jsonl.golden b/tests/golden/ownership-changes.jsonl.golden new file mode 100644 index 0000000..a158f77 --- /dev/null +++ b/tests/golden/ownership-changes.jsonl.golden @@ -0,0 +1,16 @@ +exit: 0 +--- stdout +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL ID","change_type":"decreased","old_bps":2400,"new_bps":2334,"delta_bps":-66} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL IS","change_type":"increased","old_bps":75,"new_bps":129,"delta_bps":54} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL MF","change_type":"increased","old_bps":117,"new_bps":152,"delta_bps":35} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN MF","change_type":"increased","old_bps":307,"new_bps":337,"delta_bps":30} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN OT","change_type":"decreased","old_bps":91,"new_bps":64,"delta_bps":-27} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN PF","change_type":"decreased","old_bps":76,"new_bps":56,"delta_bps":-20} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN CP","change_type":"increased","old_bps":72,"new_bps":86,"delta_bps":14} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL CP","change_type":"decreased","old_bps":6481,"new_bps":6467,"delta_bps":-14} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL SC","change_type":"decreased","old_bps":36,"new_bps":29,"delta_bps":-7} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN IB","change_type":"decreased","old_bps":267,"new_bps":263,"delta_bps":-4} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN SC","change_type":"increased","old_bps":51,"new_bps":55,"delta_bps":4} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE LOCAL PF","change_type":"increased","old_bps":17,"new_bps":20,"delta_bps":3} +{"ticker_code":"AADI","entity_name":"KSEI AGGREGATE FOREIGN ID","change_type":"decreased","old_bps":3,"new_bps":2,"delta_bps":-1} +--- stderr diff --git a/tests/golden/ownership-changes.table.golden b/tests/golden/ownership-changes.table.golden index 7f08993..5f35d3c 100644 --- a/tests/golden/ownership-changes.table.golden +++ b/tests/golden/ownership-changes.table.golden @@ -1,32 +1,32 @@ exit: 0 --- stdout -┌────────┬───────────────────────────┬─────────────────────┬────────┬────────┬────────┐ -│ TICKER ┆ ENTITY ┆ CHANGE ┆ OLD% ┆ NEW% ┆ DELTA% │ -╞════════╪═══════════════════════════╪═════════════════════╪════════╪════════╪════════╡ -│ AADI ┆ KSEI AGGREGATE LOCAL ID ┆ DECREASED ┆ 24.00% ┆ 23.34% ┆ -0.66% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE LOCAL IS ┆ INCREASED ┆ 0.75% ┆ 1.29% ┆ +0.54% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE LOCAL MF ┆ INCREASED ┆ 1.17% ┆ 1.52% ┆ +0.35% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE FOREIGN MF ┆ INCREASED ┆ 3.07% ┆ 3.37% ┆ +0.30% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE FOREIGN OT ┆ DECREASED ┆ 0.91% ┆ 0.64% ┆ -0.27% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE FOREIGN PF ┆ DECREASED ┆ 0.76% ┆ 0.56% ┆ -0.20% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE FOREIGN CP ┆ INCREASED ┆ 0.72% ┆ 0.86% ┆ +0.14% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE LOCAL CP ┆ DECREASED ┆ 64.81% ┆ 64.67% ┆ -0.14% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE LOCAL SC ┆ DECREASED ┆ 0.36% ┆ 0.29% ┆ -0.07% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE FOREIGN IB ┆ DECREASED ┆ 2.67% ┆ 2.63% ┆ -0.04% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE FOREIGN SC ┆ INCREASED ┆ 0.51% ┆ 0.55% ┆ +0.04% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE LOCAL PF ┆ INCREASED ┆ 0.17% ┆ 0.20% ┆ +0.03% │ -├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ -│ AADI ┆ KSEI AGGREGATE FOREIGN ID ┆ DECREASED ┆ 0.03% ┆ 0.02% ┆ -0.01% │ -└────────┴───────────────────────────┴─────────────────────┴────────┴────────┴────────┘ +┌────────┬───────────────────────────┬───────────┬────────┬────────┬────────┐ +│ TICKER ┆ ENTITY ┆ CHANGE ┆ OLD% ┆ NEW% ┆ DELTA% │ +╞════════╪═══════════════════════════╪═══════════╪════════╪════════╪════════╡ +│ AADI ┆ KSEI AGGREGATE LOCAL ID ┆ DECREASED ┆ 24.00% ┆ 23.34% ┆ -0.66% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE LOCAL IS ┆ INCREASED ┆ 0.75% ┆ 1.29% ┆ +0.54% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE LOCAL MF ┆ INCREASED ┆ 1.17% ┆ 1.52% ┆ +0.35% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE FOREIGN MF ┆ INCREASED ┆ 3.07% ┆ 3.37% ┆ +0.30% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE FOREIGN OT ┆ DECREASED ┆ 0.91% ┆ 0.64% ┆ -0.27% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE FOREIGN PF ┆ DECREASED ┆ 0.76% ┆ 0.56% ┆ -0.20% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE FOREIGN CP ┆ INCREASED ┆ 0.72% ┆ 0.86% ┆ +0.14% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE LOCAL CP ┆ DECREASED ┆ 64.81% ┆ 64.67% ┆ -0.14% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE LOCAL SC ┆ DECREASED ┆ 0.36% ┆ 0.29% ┆ -0.07% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE FOREIGN IB ┆ DECREASED ┆ 2.67% ┆ 2.63% ┆ -0.04% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE FOREIGN SC ┆ INCREASED ┆ 0.51% ┆ 0.55% ┆ +0.04% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE LOCAL PF ┆ INCREASED ┆ 0.17% ┆ 0.20% ┆ +0.03% │ +├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ +│ AADI ┆ KSEI AGGREGATE FOREIGN ID ┆ DECREASED ┆ 0.03% ┆ 0.02% ┆ -0.01% │ +└────────┴───────────────────────────┴───────────┴────────┴────────┴────────┘ --- stderr diff --git a/tests/golden/ownership-concentration.json.golden b/tests/golden/ownership-concentration.json.golden index 2c5afc3..35cfcd4 100644 --- a/tests/golden/ownership-concentration.json.golden +++ b/tests/golden/ownership-concentration.json.golden @@ -1,45 +1,4 @@ exit: 0 --- stdout -[ - [ - "AALI", - { - "top1_bps": 7968, - "top3_bps": 7968, - "hhi": 6348, - "free_float_bps": 2032, - "holder_count": 1 - } - ], - [ - "BBCA", - { - "top1_bps": 5494, - "top3_bps": 5716, - "hhi": 3021, - "free_float_bps": 4182, - "holder_count": 4 - } - ], - [ - "AADI", - { - "top1_bps": 4110, - "top3_bps": 6230, - "hhi": 2009, - "free_float_bps": 2030, - "holder_count": 10 - } - ], - [ - "ABBA", - { - "top1_bps": 283, - "top3_bps": 418, - "hhi": 9, - "free_float_bps": 9582, - "holder_count": 2 - } - ] -] +[["AALI",{"top1_bps":7968,"top3_bps":7968,"hhi":6348,"free_float_bps":2032,"holder_count":1}],["BBCA",{"top1_bps":5494,"top3_bps":5716,"hhi":3021,"free_float_bps":4182,"holder_count":4}],["AADI",{"top1_bps":4110,"top3_bps":6230,"hhi":2009,"free_float_bps":2030,"holder_count":10}],["ABBA",{"top1_bps":283,"top3_bps":418,"hhi":9,"free_float_bps":9582,"holder_count":2}]] --- stderr diff --git a/tests/golden/ownership-cross-holders.json.golden b/tests/golden/ownership-cross-holders.json.golden index 9c4685e..3a1e2a6 100644 --- a/tests/golden/ownership-cross-holders.json.golden +++ b/tests/golden/ownership-cross-holders.json.golden @@ -1,175 +1,4 @@ exit: 0 --- stdout -[ - { - "entity": { - "id": 11, - "canonical_name": "ASTRA INTERNATIONAL", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 7968 - }, - { - "entity": { - "id": 14, - "canonical_name": "DWIMURIA INVESTAMA ANDALAN", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 5494 - }, - { - "entity": { - "id": 4, - "canonical_name": "ADARO STRATEGIC INVESTMENTS", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 4110 - }, - { - "entity": { - "id": 5, - "canonical_name": "ALAMTRI RESOURCES INDONESIA TBK PT", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 1537 - }, - { - "entity": { - "id": 7, - "canonical_name": "GARIBALDI THOHIR", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 583 - }, - { - "entity": { - "id": 2, - "canonical_name": "SARATOGA INVESTAMA SEDAYA", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 438 - }, - { - "entity": { - "id": 9, - "canonical_name": "EDWIN SOERYADJAYA", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 352 - }, - { - "entity": { - "id": 8, - "canonical_name": "IR. T. PERMADI RACHMAT", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 348 - }, - { - "entity": { - "id": 13, - "canonical_name": "YAYASAN ABDI BANGSA", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 283 - }, - { - "entity": { - "id": 10, - "canonical_name": "SANDIAGA SALAHUDDIN UNO", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 204 - }, - { - "entity": { - "id": 1, - "canonical_name": "PERSADA CAPITAL INVESTAMA", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 154 - }, - { - "entity": { - "id": 12, - "canonical_name": "YAYASAN BINA SEJAHTERA WARGA BULOG", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 135 - }, - { - "entity": { - "id": 6, - "canonical_name": "FTAG INVESTMENT BANK", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 129 - }, - { - "entity": { - "id": 17, - "canonical_name": "ANTHONI SALIM", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 115 - }, - { - "entity": { - "id": 3, - "canonical_name": "TRINUGRAHA THOHIR", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 115 - }, - { - "entity": { - "id": 16, - "canonical_name": "TRICIPTA MANDHALA GUMILANG", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 107 - }, - { - "entity": { - "id": 15, - "canonical_name": "CATURGUWIRATNA SUMAPALA", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "total_bps": 102 - } -] +[{"entity":{"id":11,"canonical_name":"ASTRA INTERNATIONAL","entity_type":null,"country":null},"ticker_count":1,"total_bps":7968},{"entity":{"id":14,"canonical_name":"DWIMURIA INVESTAMA ANDALAN","entity_type":null,"country":null},"ticker_count":1,"total_bps":5494},{"entity":{"id":4,"canonical_name":"ADARO STRATEGIC INVESTMENTS","entity_type":null,"country":null},"ticker_count":1,"total_bps":4110},{"entity":{"id":5,"canonical_name":"ALAMTRI RESOURCES INDONESIA TBK PT","entity_type":null,"country":null},"ticker_count":1,"total_bps":1537},{"entity":{"id":7,"canonical_name":"GARIBALDI THOHIR","entity_type":null,"country":null},"ticker_count":1,"total_bps":583},{"entity":{"id":2,"canonical_name":"SARATOGA INVESTAMA SEDAYA","entity_type":null,"country":null},"ticker_count":1,"total_bps":438},{"entity":{"id":9,"canonical_name":"EDWIN SOERYADJAYA","entity_type":null,"country":null},"ticker_count":1,"total_bps":352},{"entity":{"id":8,"canonical_name":"IR. T. PERMADI RACHMAT","entity_type":null,"country":null},"ticker_count":1,"total_bps":348},{"entity":{"id":13,"canonical_name":"YAYASAN ABDI BANGSA","entity_type":null,"country":null},"ticker_count":1,"total_bps":283},{"entity":{"id":10,"canonical_name":"SANDIAGA SALAHUDDIN UNO","entity_type":null,"country":null},"ticker_count":1,"total_bps":204},{"entity":{"id":1,"canonical_name":"PERSADA CAPITAL INVESTAMA","entity_type":null,"country":null},"ticker_count":1,"total_bps":154},{"entity":{"id":12,"canonical_name":"YAYASAN BINA SEJAHTERA WARGA BULOG","entity_type":null,"country":null},"ticker_count":1,"total_bps":135},{"entity":{"id":6,"canonical_name":"FTAG INVESTMENT BANK","entity_type":null,"country":null},"ticker_count":1,"total_bps":129},{"entity":{"id":17,"canonical_name":"ANTHONI SALIM","entity_type":null,"country":null},"ticker_count":1,"total_bps":115},{"entity":{"id":3,"canonical_name":"TRINUGRAHA THOHIR","entity_type":null,"country":null},"ticker_count":1,"total_bps":115},{"entity":{"id":16,"canonical_name":"TRICIPTA MANDHALA GUMILANG","entity_type":null,"country":null},"ticker_count":1,"total_bps":107},{"entity":{"id":15,"canonical_name":"CATURGUWIRATNA SUMAPALA","entity_type":null,"country":null},"ticker_count":1,"total_bps":102}] --- stderr diff --git a/tests/golden/ownership-entity.json.golden b/tests/golden/ownership-entity.json.golden index ece9e47..7084f9f 100644 --- a/tests/golden/ownership-entity.json.golden +++ b/tests/golden/ownership-entity.json.golden @@ -1,26 +1,4 @@ exit: 0 --- stdout -{ - "entity": { - "id": 17, - "canonical_name": "ANTHONI SALIM", - "entity_type": null, - "country": null - }, - "ticker_count": 1, - "holdings": [ - { - "ticker": { - "id": 4, - "code": "BBCA", - "name": "BANK CENTRAL ASIA Tbk", - "sector": null - }, - "source": "ksei", - "shares": 1416306835, - "percentage_bps": 115, - "report_date": "2026-08-31" - } - ] -} +{"entity":{"id":17,"canonical_name":"ANTHONI SALIM","entity_type":null,"country":null},"ticker_count":1,"holdings":[{"ticker":{"id":4,"code":"BBCA","name":"BANK CENTRAL ASIA Tbk","sector":null},"source":"ksei","shares":1416306835,"percentage_bps":115,"report_date":"2026-08-31"}]} --- stderr diff --git a/tests/golden/ownership-entity.table.golden b/tests/golden/ownership-entity.table.golden index 3fb0cae..3969857 100644 --- a/tests/golden/ownership-entity.table.golden +++ b/tests/golden/ownership-entity.table.golden @@ -1,10 +1,10 @@ exit: 0 --- stdout -Entity: ANTHONI SALIM +Entity: ANTHONI SALIM ┌────────┬────────┬───────────────┬───────┬─────────────┐ │ TICKER ┆ SOURCE ┆ SHARES ┆ % ┆ REPORT_DATE │ ╞════════╪════════╪═══════════════╪═══════╪═════════════╡ │ BBCA ┆ KSEI ┆ 1,416,306,835 ┆ 1.15% ┆ 2026-08-31 │ └────────┴────────┴───────────────┴───────┴─────────────┘ -Total tickers: 1 +Total tickers: 1 --- stderr diff --git a/tests/golden/ownership-graph-dot.json.golden b/tests/golden/ownership-graph-dot.json.golden index ffebccb..83e2349 100644 --- a/tests/golden/ownership-graph-dot.json.golden +++ b/tests/golden/ownership-graph-dot.json.golden @@ -1,58 +1,4 @@ exit: 0 --- stdout -{ - "nodes": [ - { - "id": "entity:14", - "label": "DWIMURIA INVESTAMA ANDALAN", - "node_type": "entity" - }, - { - "id": "entity:15", - "label": "CATURGUWIRATNA SUMAPALA", - "node_type": "entity" - }, - { - "id": "entity:16", - "label": "TRICIPTA MANDHALA GUMILANG", - "node_type": "entity" - }, - { - "id": "entity:17", - "label": "ANTHONI SALIM", - "node_type": "entity" - }, - { - "id": "ticker:BBCA", - "label": "BBCA", - "node_type": "ticker" - } - ], - "edges": [ - { - "from": "entity:17", - "to": "ticker:BBCA", - "percentage_bps": 115, - "source": "ksei" - }, - { - "from": "entity:15", - "to": "ticker:BBCA", - "percentage_bps": 102, - "source": "ksei" - }, - { - "from": "entity:14", - "to": "ticker:BBCA", - "percentage_bps": 5494, - "source": "ksei" - }, - { - "from": "entity:16", - "to": "ticker:BBCA", - "percentage_bps": 107, - "source": "ksei" - } - ] -} +{"nodes":[{"id":"entity:14","label":"DWIMURIA INVESTAMA ANDALAN","node_type":"entity"},{"id":"entity:15","label":"CATURGUWIRATNA SUMAPALA","node_type":"entity"},{"id":"entity:16","label":"TRICIPTA MANDHALA GUMILANG","node_type":"entity"},{"id":"entity:17","label":"ANTHONI SALIM","node_type":"entity"},{"id":"ticker:BBCA","label":"BBCA","node_type":"ticker"}],"edges":[{"from":"entity:17","to":"ticker:BBCA","percentage_bps":115,"source":"ksei"},{"from":"entity:15","to":"ticker:BBCA","percentage_bps":102,"source":"ksei"},{"from":"entity:14","to":"ticker:BBCA","percentage_bps":5494,"source":"ksei"},{"from":"entity:16","to":"ticker:BBCA","percentage_bps":107,"source":"ksei"}]} --- stderr diff --git a/tests/golden/ownership-graph.json.golden b/tests/golden/ownership-graph.json.golden index ffebccb..83e2349 100644 --- a/tests/golden/ownership-graph.json.golden +++ b/tests/golden/ownership-graph.json.golden @@ -1,58 +1,4 @@ exit: 0 --- stdout -{ - "nodes": [ - { - "id": "entity:14", - "label": "DWIMURIA INVESTAMA ANDALAN", - "node_type": "entity" - }, - { - "id": "entity:15", - "label": "CATURGUWIRATNA SUMAPALA", - "node_type": "entity" - }, - { - "id": "entity:16", - "label": "TRICIPTA MANDHALA GUMILANG", - "node_type": "entity" - }, - { - "id": "entity:17", - "label": "ANTHONI SALIM", - "node_type": "entity" - }, - { - "id": "ticker:BBCA", - "label": "BBCA", - "node_type": "ticker" - } - ], - "edges": [ - { - "from": "entity:17", - "to": "ticker:BBCA", - "percentage_bps": 115, - "source": "ksei" - }, - { - "from": "entity:15", - "to": "ticker:BBCA", - "percentage_bps": 102, - "source": "ksei" - }, - { - "from": "entity:14", - "to": "ticker:BBCA", - "percentage_bps": 5494, - "source": "ksei" - }, - { - "from": "entity:16", - "to": "ticker:BBCA", - "percentage_bps": 107, - "source": "ksei" - } - ] -} +{"nodes":[{"id":"entity:14","label":"DWIMURIA INVESTAMA ANDALAN","node_type":"entity"},{"id":"entity:15","label":"CATURGUWIRATNA SUMAPALA","node_type":"entity"},{"id":"entity:16","label":"TRICIPTA MANDHALA GUMILANG","node_type":"entity"},{"id":"entity:17","label":"ANTHONI SALIM","node_type":"entity"},{"id":"ticker:BBCA","label":"BBCA","node_type":"ticker"}],"edges":[{"from":"entity:17","to":"ticker:BBCA","percentage_bps":115,"source":"ksei"},{"from":"entity:15","to":"ticker:BBCA","percentage_bps":102,"source":"ksei"},{"from":"entity:14","to":"ticker:BBCA","percentage_bps":5494,"source":"ksei"},{"from":"entity:16","to":"ticker:BBCA","percentage_bps":107,"source":"ksei"}]} --- stderr diff --git a/tests/golden/ownership-import-archive-txt.json.golden b/tests/golden/ownership-import-archive-txt.json.golden index 0717683..58baa64 100644 --- a/tests/golden/ownership-import-archive-txt.json.golden +++ b/tests/golden/ownership-import-archive-txt.json.golden @@ -1,11 +1,4 @@ exit: 0 --- stdout -{ - "action": "imported", - "inserted_rows": 18, - "ticker_count": 1, - "as_of_date": "2026-02-27", - "sha256": "7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547", - "source_url": null -} +{"action":"imported","inserted_rows":18,"ticker_count":1,"as_of_date":"2026-02-27","sha256":"7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547","source_url":null} --- stderr diff --git a/tests/golden/ownership-import-xlsx.json.golden b/tests/golden/ownership-import-xlsx.json.golden index 30fb0d3..30245c2 100644 --- a/tests/golden/ownership-import-xlsx.json.golden +++ b/tests/golden/ownership-import-xlsx.json.golden @@ -1,11 +1,4 @@ exit: 0 --- stdout -{ - "action": "imported", - "inserted_rows": 17, - "ticker_count": 4, - "as_of_date": "2026-08-31", - "sha256": "7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3", - "source_url": null -} +{"action":"imported","inserted_rows":17,"ticker_count":4,"as_of_date":"2026-08-31","sha256":"7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3","source_url":null} --- stderr diff --git a/tests/golden/ownership-releases.json.golden b/tests/golden/ownership-releases.json.golden index a555050..6145fee 100644 --- a/tests/golden/ownership-releases.json.golden +++ b/tests/golden/ownership-releases.json.golden @@ -1,29 +1,4 @@ exit: 0 --- stdout -[ - { - "id": 1, - "source_url": null, - "sha256": "7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3", - "as_of_date": "2026-08-31", - "row_count": 17, - "imported_at": 0 - }, - { - "id": 3, - "source_url": null, - "sha256": "7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547", - "as_of_date": "2026-02-27", - "row_count": 18, - "imported_at": 0 - }, - { - "id": 2, - "source_url": null, - "sha256": "efb3d8fb19c9e0d803dc1b1734c8389356605d0f78d73db6729fb76d3d75753d", - "as_of_date": "2026-01-30", - "row_count": 18, - "imported_at": 0 - } -] +[{"id":1,"source_url":null,"sha256":"7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3","as_of_date":"2026-08-31","row_count":17,"imported_at":0},{"id":3,"source_url":null,"sha256":"7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547","as_of_date":"2026-02-27","row_count":18,"imported_at":0},{"id":2,"source_url":null,"sha256":"efb3d8fb19c9e0d803dc1b1734c8389356605d0f78d73db6729fb76d3d75753d","as_of_date":"2026-01-30","row_count":18,"imported_at":0}] --- stderr diff --git a/tests/golden/ownership-resolve-map.json.golden b/tests/golden/ownership-resolve-map.json.golden index baf20c6..daa7080 100644 --- a/tests/golden/ownership-resolve-map.json.golden +++ b/tests/golden/ownership-resolve-map.json.golden @@ -1,8 +1,4 @@ exit: 0 --- stdout -{ - "action": "mapped", - "alias": "SALIM ANTHONI", - "entity": "ANTHONI SALIM" -} +{"action":"mapped","alias":"SALIM ANTHONI","entity":"ANTHONI SALIM"} --- stderr diff --git a/tests/golden/ownership-resolve-merge.json.golden b/tests/golden/ownership-resolve-merge.json.golden index 6ea677b..9e65e1c 100644 --- a/tests/golden/ownership-resolve-merge.json.golden +++ b/tests/golden/ownership-resolve-merge.json.golden @@ -1,11 +1,4 @@ exit: 0 --- stdout -{ - "action": "merged", - "alias_updates": 1, - "bing_holdings_updates": 0, - "keep": 1, - "ksei_holdings_updates": 1, - "merge": 2 -} +{"action":"merged","alias_updates":1,"bing_holdings_updates":0,"keep":1,"ksei_holdings_updates":1,"merge":2} --- stderr diff --git a/tests/golden/ownership-search.json.golden b/tests/golden/ownership-search.json.golden index 81ef014..664aadc 100644 --- a/tests/golden/ownership-search.json.golden +++ b/tests/golden/ownership-search.json.golden @@ -1,11 +1,4 @@ exit: 0 --- stdout -[ - { - "id": 17, - "canonical_name": "ANTHONI SALIM", - "entity_type": null, - "country": null - } -] +[{"id":17,"canonical_name":"ANTHONI SALIM","entity_type":null,"country":null}] --- stderr diff --git a/tests/golden/ownership-ticker-empty.json.golden b/tests/golden/ownership-ticker-empty.json.golden index f165894..57c52fa 100644 --- a/tests/golden/ownership-ticker-empty.json.golden +++ b/tests/golden/ownership-ticker-empty.json.golden @@ -1,8 +1,4 @@ -exit: 1 +exit: 3 --- stdout --- stderr -{ - "code": "SYMBOLNOTFOUND", - "error": true, - "message": "symbol not found: BBCA" -} +{"code":"SYMBOLNOTFOUND","error":true,"message":"symbol not found: BBCA"} diff --git a/tests/golden/ownership-ticker-empty.table.golden b/tests/golden/ownership-ticker-empty.table.golden index b92ace9..8470d72 100644 --- a/tests/golden/ownership-ticker-empty.table.golden +++ b/tests/golden/ownership-ticker-empty.table.golden @@ -1,4 +1,4 @@ -exit: 1 +exit: 3 --- stdout --- stderr Error: symbol not found: BBCA diff --git a/tests/golden/ownership-ticker-ksei.json.golden b/tests/golden/ownership-ticker-ksei.json.golden index de19ce7..cbe8b50 100644 --- a/tests/golden/ownership-ticker-ksei.json.golden +++ b/tests/golden/ownership-ticker-ksei.json.golden @@ -1,67 +1,4 @@ exit: 0 --- stdout -{ - "ticker": { - "id": 4, - "code": "BBCA", - "name": "BANK CENTRAL ASIA Tbk", - "sector": null - }, - "ksei_as_of": "2026-08-31", - "bing_as_of": null, - "holders": [ - { - "rank": 1, - "source": "ksei", - "name": "DWIMURIA INVESTAMA ANDALAN", - "entity_id": 14, - "investor_type": "CORPORATE", - "locality": "Local", - "shares": 67729950000, - "percentage_bps": 5494, - "signal": null - }, - { - "rank": 2, - "source": "ksei", - "name": "ANTHONI SALIM", - "entity_id": 17, - "investor_type": "INDIVIDUAL", - "locality": "Local", - "shares": 1416306835, - "percentage_bps": 115, - "signal": null - }, - { - "rank": 3, - "source": "ksei", - "name": "TRICIPTA MANDHALA GUMILANG", - "entity_id": 16, - "investor_type": "CORPORATE", - "locality": "Local", - "shares": 1313250000, - "percentage_bps": 107, - "signal": null - }, - { - "rank": 4, - "source": "ksei", - "name": "CATURGUWIRATNA SUMAPALA", - "entity_id": 15, - "investor_type": "CORPORATE", - "locality": "Local", - "shares": 1261750000, - "percentage_bps": 102, - "signal": null - } - ], - "concentration": { - "top1_bps": 5494, - "top3_bps": 5716, - "hhi": 3021, - "free_float_bps": 4182, - "holder_count": 4 - }, - "flow": null -} +{"ticker":{"id":4,"code":"BBCA","name":"BANK CENTRAL ASIA Tbk","sector":null},"ksei_as_of":"2026-08-31","bing_as_of":null,"holders":[{"rank":1,"source":"ksei","name":"DWIMURIA INVESTAMA ANDALAN","entity_id":14,"investor_type":"CORPORATE","locality":"Local","shares":67729950000,"percentage_bps":5494,"signal":null},{"rank":2,"source":"ksei","name":"ANTHONI SALIM","entity_id":17,"investor_type":"INDIVIDUAL","locality":"Local","shares":1416306835,"percentage_bps":115,"signal":null},{"rank":3,"source":"ksei","name":"TRICIPTA MANDHALA GUMILANG","entity_id":16,"investor_type":"CORPORATE","locality":"Local","shares":1313250000,"percentage_bps":107,"signal":null},{"rank":4,"source":"ksei","name":"CATURGUWIRATNA SUMAPALA","entity_id":15,"investor_type":"CORPORATE","locality":"Local","shares":1261750000,"percentage_bps":102,"signal":null}],"concentration":{"top1_bps":5494,"top3_bps":5716,"hhi":3021,"free_float_bps":4182,"holder_count":4},"flow":null} --- stderr diff --git a/tests/golden/ownership-ticker-ksei.table.golden b/tests/golden/ownership-ticker-ksei.table.golden index e59444a..b8e1931 100644 --- a/tests/golden/ownership-ticker-ksei.table.golden +++ b/tests/golden/ownership-ticker-ksei.table.golden @@ -1,6 +1,6 @@ exit: 0 --- stdout -Ownership: BBCA KSEI=2026-08-31 Bing=- +Ownership: BBCA KSEI=2026-08-31 Bing=- ┌──────┬────────┬────────────────────────────┬────────────┬─────┬────────────────┬────────┬────────┐ │ RANK ┆ SOURCE ┆ INVESTOR ┆ TYPE ┆ L/F ┆ SHARES ┆ % ┆ SIGNAL │ ╞══════╪════════╪════════════════════════════╪════════════╪═════╪════════════════╪════════╪════════╡ @@ -12,5 +12,5 @@ exit: 0 ├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ │ 4 ┆ KSEI ┆ CATURGUWIRATNA SUMAPALA ┆ CORPORATE ┆ L ┆ 1,261,750,000 ┆ 1.02% ┆ - │ └──────┴────────┴────────────────────────────┴────────────┴─────┴────────────────┴────────┴────────┘ -Concentration: top1=54.94% top3=57.16% hhi=3021 free_float=41.82% holders=4 +Concentration: top1=54.94% top3=57.16% hhi=3021 free_float=41.82% holders=4 --- stderr diff --git a/tests/golden/ownership-ticker.json.golden b/tests/golden/ownership-ticker.json.golden index de19ce7..cbe8b50 100644 --- a/tests/golden/ownership-ticker.json.golden +++ b/tests/golden/ownership-ticker.json.golden @@ -1,67 +1,4 @@ exit: 0 --- stdout -{ - "ticker": { - "id": 4, - "code": "BBCA", - "name": "BANK CENTRAL ASIA Tbk", - "sector": null - }, - "ksei_as_of": "2026-08-31", - "bing_as_of": null, - "holders": [ - { - "rank": 1, - "source": "ksei", - "name": "DWIMURIA INVESTAMA ANDALAN", - "entity_id": 14, - "investor_type": "CORPORATE", - "locality": "Local", - "shares": 67729950000, - "percentage_bps": 5494, - "signal": null - }, - { - "rank": 2, - "source": "ksei", - "name": "ANTHONI SALIM", - "entity_id": 17, - "investor_type": "INDIVIDUAL", - "locality": "Local", - "shares": 1416306835, - "percentage_bps": 115, - "signal": null - }, - { - "rank": 3, - "source": "ksei", - "name": "TRICIPTA MANDHALA GUMILANG", - "entity_id": 16, - "investor_type": "CORPORATE", - "locality": "Local", - "shares": 1313250000, - "percentage_bps": 107, - "signal": null - }, - { - "rank": 4, - "source": "ksei", - "name": "CATURGUWIRATNA SUMAPALA", - "entity_id": 15, - "investor_type": "CORPORATE", - "locality": "Local", - "shares": 1261750000, - "percentage_bps": 102, - "signal": null - } - ], - "concentration": { - "top1_bps": 5494, - "top3_bps": 5716, - "hhi": 3021, - "free_float_bps": 4182, - "holder_count": 4 - }, - "flow": null -} +{"ticker":{"id":4,"code":"BBCA","name":"BANK CENTRAL ASIA Tbk","sector":null},"ksei_as_of":"2026-08-31","bing_as_of":null,"holders":[{"rank":1,"source":"ksei","name":"DWIMURIA INVESTAMA ANDALAN","entity_id":14,"investor_type":"CORPORATE","locality":"Local","shares":67729950000,"percentage_bps":5494,"signal":null},{"rank":2,"source":"ksei","name":"ANTHONI SALIM","entity_id":17,"investor_type":"INDIVIDUAL","locality":"Local","shares":1416306835,"percentage_bps":115,"signal":null},{"rank":3,"source":"ksei","name":"TRICIPTA MANDHALA GUMILANG","entity_id":16,"investor_type":"CORPORATE","locality":"Local","shares":1313250000,"percentage_bps":107,"signal":null},{"rank":4,"source":"ksei","name":"CATURGUWIRATNA SUMAPALA","entity_id":15,"investor_type":"CORPORATE","locality":"Local","shares":1261750000,"percentage_bps":102,"signal":null}],"concentration":{"top1_bps":5494,"top3_bps":5716,"hhi":3021,"free_float_bps":4182,"holder_count":4},"flow":null} --- stderr diff --git a/tests/golden/ownership-ticker.table.golden b/tests/golden/ownership-ticker.table.golden index e59444a..b8e1931 100644 --- a/tests/golden/ownership-ticker.table.golden +++ b/tests/golden/ownership-ticker.table.golden @@ -1,6 +1,6 @@ exit: 0 --- stdout -Ownership: BBCA KSEI=2026-08-31 Bing=- +Ownership: BBCA KSEI=2026-08-31 Bing=- ┌──────┬────────┬────────────────────────────┬────────────┬─────┬────────────────┬────────┬────────┐ │ RANK ┆ SOURCE ┆ INVESTOR ┆ TYPE ┆ L/F ┆ SHARES ┆ % ┆ SIGNAL │ ╞══════╪════════╪════════════════════════════╪════════════╪═════╪════════════════╪════════╪════════╡ @@ -12,5 +12,5 @@ exit: 0 ├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ │ 4 ┆ KSEI ┆ CATURGUWIRATNA SUMAPALA ┆ CORPORATE ┆ L ┆ 1,261,750,000 ┆ 1.02% ┆ - │ └──────┴────────┴────────────────────────────┴────────────┴─────┴────────────────┴────────┴────────┘ -Concentration: top1=54.94% top3=57.16% hhi=3021 free_float=41.82% holders=4 +Concentration: top1=54.94% top3=57.16% hhi=3021 free_float=41.82% holders=4 --- stderr diff --git a/tests/golden/stocks-compare-stdin.json.golden b/tests/golden/stocks-compare-stdin.json.golden new file mode 100644 index 0000000..157f866 --- /dev/null +++ b/tests/golden/stocks-compare-stdin.json.golden @@ -0,0 +1,4 @@ +exit: 0 +--- stdout +[{"symbol":"BBCA.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"},{"symbol":"BBRI.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"}] +--- stderr diff --git a/tests/golden/stocks-compare-stdin.table.golden b/tests/golden/stocks-compare-stdin.table.golden new file mode 100644 index 0000000..31710bd --- /dev/null +++ b/tests/golden/stocks-compare-stdin.table.golden @@ -0,0 +1,23 @@ +exit: 0 +--- stdout +Fundamental Comparison +┌────────────────┬───────────────┬───────────────┐ +│ METRIC ┆ BBCA.JK ┆ BBRI.JK │ +╞════════════════╪═══════════════╪═══════════════╡ +│ Symbol ┆ BBCA.JK ┆ BBRI.JK │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ mixed ┆ mixed │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Growth ┆ mixed ┆ mixed │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Valuation ┆ expensive ┆ expensive │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Risk ┆ moderate risk ┆ moderate risk │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ P/E ┆ 25.40 ┆ 25.40 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ROE ┆ +19.80% ┆ +19.80% │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Revenue Growth ┆ +8.10% ┆ +8.10% │ +└────────────────┴───────────────┴───────────────┘ +--- stderr diff --git a/tests/golden/stocks-compare.json.golden b/tests/golden/stocks-compare.json.golden index c5cf145..157f866 100644 --- a/tests/golden/stocks-compare.json.golden +++ b/tests/golden/stocks-compare.json.golden @@ -1,81 +1,4 @@ exit: 0 --- stdout -[ - { - "symbol": "BBCA.JK", - "growth": { - "revenue_growth": 0.081, - "earnings_growth": 0.121, - "revenue_growth_pct": 8.1, - "earnings_growth_pct": 12.1, - "revenue_signal": "slow", - "earnings_signal": "moderate", - "overall_signal": "mixed" - }, - "valuation": { - "pe_trailing": 25.4, - "pe_forward": null, - "pb": 4.6, - "roe": 0.198, - "roe_pct": 19.8, - "net_margin": 0.324, - "net_margin_pct": 32.4, - "ev_ebitda": null, - "pe_signal": "premium", - "pb_signal": "expensive", - "roe_signal": "strong", - "margin_signal": "excellent", - "ev_ebitda_signal": "no data", - "overall_signal": "expensive" - }, - "risk": { - "debt_to_equity": 0.42, - "current_ratio": 1.18, - "roa": 0.031, - "roa_pct": 3.1, - "de_signal": "conservative", - "current_ratio_signal": "tight", - "overall_signal": "moderate risk" - }, - "overall_signal": "mixed" - }, - { - "symbol": "BBRI.JK", - "growth": { - "revenue_growth": 0.081, - "earnings_growth": 0.121, - "revenue_growth_pct": 8.1, - "earnings_growth_pct": 12.1, - "revenue_signal": "slow", - "earnings_signal": "moderate", - "overall_signal": "mixed" - }, - "valuation": { - "pe_trailing": 25.4, - "pe_forward": null, - "pb": 4.6, - "roe": 0.198, - "roe_pct": 19.8, - "net_margin": 0.324, - "net_margin_pct": 32.4, - "ev_ebitda": null, - "pe_signal": "premium", - "pb_signal": "expensive", - "roe_signal": "strong", - "margin_signal": "excellent", - "ev_ebitda_signal": "no data", - "overall_signal": "expensive" - }, - "risk": { - "debt_to_equity": 0.42, - "current_ratio": 1.18, - "roa": 0.031, - "roa_pct": 3.1, - "de_signal": "conservative", - "current_ratio_signal": "tight", - "overall_signal": "moderate risk" - }, - "overall_signal": "mixed" - } -] +[{"symbol":"BBCA.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"},{"symbol":"BBRI.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"}] --- stderr diff --git a/tests/golden/stocks-compare.table.golden b/tests/golden/stocks-compare.table.golden index c913fbc..31710bd 100644 --- a/tests/golden/stocks-compare.table.golden +++ b/tests/golden/stocks-compare.table.golden @@ -1,23 +1,23 @@ exit: 0 --- stdout -Fundamental Comparison -┌────────────────┬─────────────────────────┬─────────────────────────┐ -│ METRIC ┆ BBCA.JK ┆ BBRI.JK │ -╞════════════════╪═════════════════════════╪═════════════════════════╡ -│ Symbol ┆ BBCA.JK ┆ BBRI.JK │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ mixed ┆ mixed │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Growth ┆ mixed ┆ mixed │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Valuation ┆ expensive ┆ expensive │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Risk ┆ moderate risk ┆ moderate risk │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ P/E ┆ 25.40 ┆ 25.40 │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ROE ┆ +19.80% ┆ +19.80% │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Revenue Growth ┆ +8.10% ┆ +8.10% │ -└────────────────┴─────────────────────────┴─────────────────────────┘ +Fundamental Comparison +┌────────────────┬───────────────┬───────────────┐ +│ METRIC ┆ BBCA.JK ┆ BBRI.JK │ +╞════════════════╪═══════════════╪═══════════════╡ +│ Symbol ┆ BBCA.JK ┆ BBRI.JK │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ mixed ┆ mixed │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Growth ┆ mixed ┆ mixed │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Valuation ┆ expensive ┆ expensive │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Risk ┆ moderate risk ┆ moderate risk │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ P/E ┆ 25.40 ┆ 25.40 │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ROE ┆ +19.80% ┆ +19.80% │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Revenue Growth ┆ +8.10% ┆ +8.10% │ +└────────────────┴───────────────┴───────────────┘ --- stderr diff --git a/tests/golden/stocks-earnings-forecast-annual.json.golden b/tests/golden/stocks-earnings-forecast-annual.json.golden index ca4b219..e6dac47 100644 --- a/tests/golden/stocks-earnings-forecast-annual.json.golden +++ b/tests/golden/stocks-earnings-forecast-annual.json.golden @@ -1,22 +1,4 @@ exit: 0 --- stdout -{ - "symbol": "BBCA.JK", - "eps_last_year": 1200.0, - "revenue_last_year": 100000000000.0, - "forecast": [ - { - "eps_actual": null, - "eps_forecast": 1300.0, - "eps_surprise": null, - "eps_surprise_pct": null, - "revenue_actual": null, - "revenue_forecast": 110000000000.0, - "revenue_surprise": null, - "earning_release_date": "2026-03-15", - "period_type": "2026" - } - ], - "history": [] -} +{"symbol":"BBCA.JK","eps_last_year":1200.0,"revenue_last_year":100000000000.0,"forecast":[{"eps_actual":null,"eps_forecast":1300.0,"eps_surprise":null,"eps_surprise_pct":null,"revenue_actual":null,"revenue_forecast":110000000000.0,"revenue_surprise":null,"earning_release_date":"2026-03-15","period_type":"2026"}],"history":[]} --- stderr diff --git a/tests/golden/stocks-earnings-history-quarterly.json.golden b/tests/golden/stocks-earnings-history-quarterly.json.golden index 2c8eef8..f713693 100644 --- a/tests/golden/stocks-earnings-history-quarterly.json.golden +++ b/tests/golden/stocks-earnings-history-quarterly.json.golden @@ -1,22 +1,4 @@ exit: 0 --- stdout -{ - "symbol": "BBCA.JK", - "eps_last_year": 1200.0, - "revenue_last_year": 100000000000.0, - "forecast": [], - "history": [ - { - "eps_actual": 320.0, - "eps_forecast": null, - "eps_surprise": 5.0, - "eps_surprise_pct": 1.5, - "revenue_actual": 27000000000.0, - "revenue_forecast": null, - "revenue_surprise": 200000000.0, - "earning_release_date": "2025-11-20", - "period_type": "Q42025" - } - ] -} +{"symbol":"BBCA.JK","eps_last_year":1200.0,"revenue_last_year":100000000000.0,"forecast":[],"history":[{"eps_actual":320.0,"eps_forecast":null,"eps_surprise":5.0,"eps_surprise_pct":1.5,"revenue_actual":27000000000.0,"revenue_forecast":null,"revenue_surprise":200000000.0,"earning_release_date":"2025-11-20","period_type":"Q42025"}]} --- stderr diff --git a/tests/golden/stocks-earnings.json.golden b/tests/golden/stocks-earnings.json.golden index f9fcc31..9085b29 100644 --- a/tests/golden/stocks-earnings.json.golden +++ b/tests/golden/stocks-earnings.json.golden @@ -1,56 +1,4 @@ exit: 0 --- stdout -{ - "symbol": "BBCA.JK", - "eps_last_year": 1200.0, - "revenue_last_year": 100000000000.0, - "forecast": [ - { - "eps_actual": null, - "eps_forecast": 1300.0, - "eps_surprise": null, - "eps_surprise_pct": null, - "revenue_actual": null, - "revenue_forecast": 110000000000.0, - "revenue_surprise": null, - "earning_release_date": "2026-03-15", - "period_type": "2026" - }, - { - "eps_actual": null, - "eps_forecast": 330.0, - "eps_surprise": null, - "eps_surprise_pct": null, - "revenue_actual": null, - "revenue_forecast": 28000000000.0, - "revenue_surprise": null, - "earning_release_date": "2026-04-20", - "period_type": "Q12026" - } - ], - "history": [ - { - "eps_actual": 1250.0, - "eps_forecast": null, - "eps_surprise": 20.0, - "eps_surprise_pct": 1.6, - "revenue_actual": 105000000000.0, - "revenue_forecast": null, - "revenue_surprise": 500000000.0, - "earning_release_date": "2025-03-15", - "period_type": "2025" - }, - { - "eps_actual": 320.0, - "eps_forecast": null, - "eps_surprise": 5.0, - "eps_surprise_pct": 1.5, - "revenue_actual": 27000000000.0, - "revenue_forecast": null, - "revenue_surprise": 200000000.0, - "earning_release_date": "2025-11-20", - "period_type": "Q42025" - } - ] -} +{"symbol":"BBCA.JK","eps_last_year":1200.0,"revenue_last_year":100000000000.0,"forecast":[{"eps_actual":null,"eps_forecast":1300.0,"eps_surprise":null,"eps_surprise_pct":null,"revenue_actual":null,"revenue_forecast":110000000000.0,"revenue_surprise":null,"earning_release_date":"2026-03-15","period_type":"2026"},{"eps_actual":null,"eps_forecast":330.0,"eps_surprise":null,"eps_surprise_pct":null,"revenue_actual":null,"revenue_forecast":28000000000.0,"revenue_surprise":null,"earning_release_date":"2026-04-20","period_type":"Q12026"}],"history":[{"eps_actual":1250.0,"eps_forecast":null,"eps_surprise":20.0,"eps_surprise_pct":1.6,"revenue_actual":105000000000.0,"revenue_forecast":null,"revenue_surprise":500000000.0,"earning_release_date":"2025-03-15","period_type":"2025"},{"eps_actual":320.0,"eps_forecast":null,"eps_surprise":5.0,"eps_surprise_pct":1.5,"revenue_actual":27000000000.0,"revenue_forecast":null,"revenue_surprise":200000000.0,"earning_release_date":"2025-11-20","period_type":"Q42025"}]} --- stderr diff --git a/tests/golden/stocks-financials-income.json.golden b/tests/golden/stocks-financials-income.json.golden index 83cd079..cfa9dde 100644 --- a/tests/golden/stocks-financials-income.json.golden +++ b/tests/golden/stocks-financials-income.json.golden @@ -1,21 +1,4 @@ exit: 0 --- stdout -{ - "instrument": { - "id": "bn91jc", - "symbol": "BBCA.JK", - "name": "Bank Central Asia Tbk PT" - }, - "balance_sheet": null, - "cash_flow": null, - "income_statement": { - "values": { - "netIncome": 400000000.0, - "totalRevenue": 1000000000.0 - }, - "currency": "IDR", - "report_date": "2025-12-31", - "end_date": "2025-12-31" - } -} +{"instrument":{"id":"bn91jc","symbol":"BBCA.JK","name":"Bank Central Asia Tbk PT"},"balance_sheet":null,"cash_flow":null,"income_statement":{"values":{"netIncome":400000000.0,"totalRevenue":1000000000.0},"currency":"IDR","report_date":"2025-12-31","end_date":"2025-12-31"}} --- stderr diff --git a/tests/golden/stocks-financials.json.golden b/tests/golden/stocks-financials.json.golden index 34bab31..a935f13 100644 --- a/tests/golden/stocks-financials.json.golden +++ b/tests/golden/stocks-financials.json.golden @@ -1,36 +1,4 @@ exit: 0 --- stdout -{ - "instrument": { - "id": "bn91jc", - "symbol": "BBCA.JK", - "name": "Bank Central Asia Tbk PT" - }, - "balance_sheet": { - "values": { - "cashAndShortTermInvestments": 500000000.0, - "totalEquity": 900000000.0 - }, - "currency": "IDR", - "report_date": "2025-12-31", - "end_date": "2025-12-31" - }, - "cash_flow": { - "values": { - "operatingCashFlow": 600000000.0 - }, - "currency": "IDR", - "report_date": "2025-12-31", - "end_date": "2025-12-31" - }, - "income_statement": { - "values": { - "netIncome": 400000000.0, - "totalRevenue": 1000000000.0 - }, - "currency": "IDR", - "report_date": "2025-12-31", - "end_date": "2025-12-31" - } -} +{"instrument":{"id":"bn91jc","symbol":"BBCA.JK","name":"Bank Central Asia Tbk PT"},"balance_sheet":{"values":{"cashAndShortTermInvestments":500000000.0,"totalEquity":900000000.0},"currency":"IDR","report_date":"2025-12-31","end_date":"2025-12-31"},"cash_flow":{"values":{"operatingCashFlow":600000000.0},"currency":"IDR","report_date":"2025-12-31","end_date":"2025-12-31"},"income_statement":{"values":{"netIncome":400000000.0,"totalRevenue":1000000000.0},"currency":"IDR","report_date":"2025-12-31","end_date":"2025-12-31"}} --- stderr diff --git a/tests/golden/stocks-fundamental.json.golden b/tests/golden/stocks-fundamental.json.golden index a24505b..b04537b 100644 --- a/tests/golden/stocks-fundamental.json.golden +++ b/tests/golden/stocks-fundamental.json.golden @@ -1,41 +1,4 @@ exit: 0 --- stdout -{ - "symbol": "BBCA.JK", - "growth": { - "revenue_growth": 0.081, - "earnings_growth": 0.121, - "revenue_growth_pct": 8.1, - "earnings_growth_pct": 12.1, - "revenue_signal": "slow", - "earnings_signal": "moderate", - "overall_signal": "mixed" - }, - "valuation": { - "pe_trailing": 25.4, - "pe_forward": null, - "pb": 4.6, - "roe": 0.198, - "roe_pct": 19.8, - "net_margin": 0.324, - "net_margin_pct": 32.4, - "ev_ebitda": null, - "pe_signal": "premium", - "pb_signal": "expensive", - "roe_signal": "strong", - "margin_signal": "excellent", - "ev_ebitda_signal": "no data", - "overall_signal": "expensive" - }, - "risk": { - "debt_to_equity": 0.42, - "current_ratio": 1.18, - "roa": 0.031, - "roa_pct": 3.1, - "de_signal": "conservative", - "current_ratio_signal": "tight", - "overall_signal": "moderate risk" - }, - "overall_signal": "mixed" -} +{"symbol":"BBCA.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"} --- stderr diff --git a/tests/golden/stocks-fundamental.jsonl.golden b/tests/golden/stocks-fundamental.jsonl.golden new file mode 100644 index 0000000..b04537b --- /dev/null +++ b/tests/golden/stocks-fundamental.jsonl.golden @@ -0,0 +1,4 @@ +exit: 0 +--- stdout +{"symbol":"BBCA.JK","growth":{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"},"valuation":{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"},"risk":{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"} +--- stderr diff --git a/tests/golden/stocks-fundamental.table.golden b/tests/golden/stocks-fundamental.table.golden index d697200..f4c8139 100644 --- a/tests/golden/stocks-fundamental.table.golden +++ b/tests/golden/stocks-fundamental.table.golden @@ -1,49 +1,49 @@ exit: 0 --- stdout -Fundamental Analysis for BBCA.JK +Fundamental Analysis for BBCA.JK -Growth Analysis for BBCA.JK -┌─────────────────┬─────────┬────────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞═════════════════╪═════════╪════════════════════╡ -│ Revenue Growth ┆ +8.10% ┆ slow │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Earnings Growth ┆ +12.10% ┆ moderate │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ - ┆ mixed │ -└─────────────────┴─────────┴────────────────────┘ +Growth Analysis for BBCA.JK +┌─────────────────┬─────────┬──────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞═════════════════╪═════════╪══════════╡ +│ Revenue Growth ┆ +8.10% ┆ slow │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ +│ Earnings Growth ┆ +12.10% ┆ moderate │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ - ┆ mixed │ +└─────────────────┴─────────┴──────────┘ -Valuation Analysis for BBCA.JK -┌────────────────┬─────────┬─────────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞════════════════╪═════════╪═════════════════════╡ -│ P/E (Trailing) ┆ 25.40 ┆ premium │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ P/E (Forward) ┆ - ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Price/Book ┆ 4.60 ┆ expensive │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ROE ┆ +19.80% ┆ strong │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Net Margin ┆ +32.40% ┆ excellent │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ EV/EBITDA ┆ - ┆ no data │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ - ┆ expensive │ -└────────────────┴─────────┴─────────────────────┘ +Valuation Analysis for BBCA.JK +┌────────────────┬─────────┬───────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞════════════════╪═════════╪═══════════╡ +│ P/E (Trailing) ┆ 25.40 ┆ premium │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ P/E (Forward) ┆ - ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Price/Book ┆ 4.60 ┆ expensive │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ ROE ┆ +19.80% ┆ strong │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Net Margin ┆ +32.40% ┆ excellent │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ EV/EBITDA ┆ - ┆ no data │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ - ┆ expensive │ +└────────────────┴─────────┴───────────┘ -Risk Analysis for BBCA.JK -┌───────────────┬────────┬─────────────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞═══════════════╪════════╪═════════════════════════╡ -│ Debt/Equity ┆ 0.42 ┆ conservative │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Current Ratio ┆ 1.18 ┆ tight │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ROA ┆ +3.10% ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ - ┆ moderate risk │ -└───────────────┴────────┴─────────────────────────┘ +Risk Analysis for BBCA.JK +┌───────────────┬────────┬───────────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞═══════════════╪════════╪═══════════════╡ +│ Debt/Equity ┆ 0.42 ┆ conservative │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Current Ratio ┆ 1.18 ┆ tight │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ROA ┆ +3.10% ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ - ┆ moderate risk │ +└───────────────┴────────┴───────────────┘ -Overall Signal: mixed +Overall Signal: mixed --- stderr diff --git a/tests/golden/stocks-growth.json.golden b/tests/golden/stocks-growth.json.golden index d2327bf..bcd7ccd 100644 --- a/tests/golden/stocks-growth.json.golden +++ b/tests/golden/stocks-growth.json.golden @@ -1,12 +1,4 @@ exit: 0 --- stdout -{ - "revenue_growth": 0.081, - "earnings_growth": 0.121, - "revenue_growth_pct": 8.1, - "earnings_growth_pct": 12.1, - "revenue_signal": "slow", - "earnings_signal": "moderate", - "overall_signal": "mixed" -} +{"revenue_growth":0.081,"earnings_growth":0.121,"revenue_growth_pct":8.1,"earnings_growth_pct":12.1,"revenue_signal":"slow","earnings_signal":"moderate","overall_signal":"mixed"} --- stderr diff --git a/tests/golden/stocks-growth.table.golden b/tests/golden/stocks-growth.table.golden index a26e3a1..fdfc0c7 100644 --- a/tests/golden/stocks-growth.table.golden +++ b/tests/golden/stocks-growth.table.golden @@ -1,13 +1,13 @@ exit: 0 --- stdout -Growth Analysis for BBCA.JK -┌─────────────────┬─────────┬────────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞═════════════════╪═════════╪════════════════════╡ -│ Revenue Growth ┆ +8.10% ┆ slow │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Earnings Growth ┆ +12.10% ┆ moderate │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ - ┆ mixed │ -└─────────────────┴─────────┴────────────────────┘ +Growth Analysis for BBCA.JK +┌─────────────────┬─────────┬──────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞═════════════════╪═════════╪══════════╡ +│ Revenue Growth ┆ +8.10% ┆ slow │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ +│ Earnings Growth ┆ +12.10% ┆ moderate │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ - ┆ mixed │ +└─────────────────┴─────────┴──────────┘ --- stderr diff --git a/tests/golden/stocks-history-msn.json.golden b/tests/golden/stocks-history-msn.json.golden index e6eb3e6..faec72d 100644 --- a/tests/golden/stocks-history-msn.json.golden +++ b/tests/golden/stocks-history-msn.json.golden @@ -1,29 +1,4 @@ exit: 0 --- stdout -[ - { - "date": "2026-01-14", - "open": 8000, - "high": 8000, - "low": 8000, - "close": 8000, - "volume": 0 - }, - { - "date": "2026-01-15", - "open": 8075, - "high": 8075, - "low": 8075, - "close": 8075, - "volume": 0 - }, - { - "date": "2026-01-16", - "open": 8025, - "high": 8025, - "low": 8025, - "close": 8025, - "volume": 0 - } -] +[{"date":"2026-01-14","open":8000,"high":8000,"low":8000,"close":8000,"volume":0},{"date":"2026-01-15","open":8075,"high":8075,"low":8075,"close":8075,"volume":0},{"date":"2026-01-16","open":8025,"high":8025,"low":8025,"close":8025,"volume":0}] --- stderr diff --git a/tests/golden/stocks-history-msn.table.golden b/tests/golden/stocks-history-msn.table.golden index b47c365..fef3c67 100644 --- a/tests/golden/stocks-history-msn.table.golden +++ b/tests/golden/stocks-history-msn.table.golden @@ -1,6 +1,6 @@ exit: 0 --- stdout -History for BBCA.JK +History for BBCA.JK ┌────────────┬───────┬───────┬───────┬───────┬────────┐ │ DATE ┆ OPEN ┆ HIGH ┆ LOW ┆ CLOSE ┆ VOLUME │ ╞════════════╪═══════╪═══════╪═══════╪═══════╪════════╡ diff --git a/tests/golden/stocks-history.json.golden b/tests/golden/stocks-history.json.golden index 423e9c6..317447c 100644 --- a/tests/golden/stocks-history.json.golden +++ b/tests/golden/stocks-history.json.golden @@ -1,29 +1,4 @@ exit: 0 --- stdout -[ - { - "date": "2024-03-01", - "open": 9800, - "high": 9900, - "low": 9750, - "close": 9875, - "volume": 12300000 - }, - { - "date": "2024-03-02", - "open": 9850, - "high": 9920, - "low": 9800, - "close": 9880, - "volume": 11000000 - }, - { - "date": "2024-03-03", - "open": 9860, - "high": 9950, - "low": 9820, - "close": 9925, - "volume": 14000000 - } -] +[{"date":"2024-03-01","open":9800,"high":9900,"low":9750,"close":9875,"volume":12300000},{"date":"2024-03-02","open":9850,"high":9920,"low":9800,"close":9880,"volume":11000000},{"date":"2024-03-03","open":9860,"high":9950,"low":9820,"close":9925,"volume":14000000}] --- stderr diff --git a/tests/golden/stocks-history.jsonl.golden b/tests/golden/stocks-history.jsonl.golden new file mode 100644 index 0000000..34aaf68 --- /dev/null +++ b/tests/golden/stocks-history.jsonl.golden @@ -0,0 +1,6 @@ +exit: 0 +--- stdout +{"date":"2024-03-01","open":9800,"high":9900,"low":9750,"close":9875,"volume":12300000} +{"date":"2024-03-02","open":9850,"high":9920,"low":9800,"close":9880,"volume":11000000} +{"date":"2024-03-03","open":9860,"high":9950,"low":9820,"close":9925,"volume":14000000} +--- stderr diff --git a/tests/golden/stocks-history.table.golden b/tests/golden/stocks-history.table.golden index 50e34a6..5ede18b 100644 --- a/tests/golden/stocks-history.table.golden +++ b/tests/golden/stocks-history.table.golden @@ -1,6 +1,6 @@ exit: 0 --- stdout -History for BBCA.JK +History for BBCA.JK ┌────────────┬───────┬───────┬───────┬───────┬────────────┐ │ DATE ┆ OPEN ┆ HIGH ┆ LOW ┆ CLOSE ┆ VOLUME │ ╞════════════╪═══════╪═══════╪═══════╪═══════╪════════════╡ diff --git a/tests/golden/stocks-insights.json.golden b/tests/golden/stocks-insights.json.golden index 0050c83..4249c18 100644 --- a/tests/golden/stocks-insights.json.golden +++ b/tests/golden/stocks-insights.json.golden @@ -1,16 +1,4 @@ exit: 0 --- stdout -{ - "id": "bn91jc", - "symbol": "BBCA.JK", - "summary": "Mixed analyst signals for Bank Central Asia Tbk PT: 1 positive, 1 negative, 1 neutral.", - "highlights": [ - "P/E ratio: Earnings data unavailable, can't compare P/E ratio with peers", - "Analyst price target: Analysts forecast more than 20% upside for 12-month price target" - ], - "risks": [ - "Quarterly Revenue YoY Growth: Revenue grew worse than peers" - ], - "last_updated": "2026-03-26T04:14:57.9197955Z" -} +{"id":"bn91jc","symbol":"BBCA.JK","summary":"Mixed analyst signals for Bank Central Asia Tbk PT: 1 positive, 1 negative, 1 neutral.","highlights":["P/E ratio: Earnings data unavailable, can't compare P/E ratio with peers","Analyst price target: Analysts forecast more than 20% upside for 12-month price target"],"risks":["Quarterly Revenue YoY Growth: Revenue grew worse than peers"],"last_updated":"2026-03-26T04:14:57.9197955Z"} --- stderr diff --git a/tests/golden/stocks-news.json.golden b/tests/golden/stocks-news.json.golden index 71cf540..647f530 100644 --- a/tests/golden/stocks-news.json.golden +++ b/tests/golden/stocks-news.json.golden @@ -1,15 +1,4 @@ exit: 0 --- stdout -[ - { - "id": "news-1", - "symbol": "BBCA.JK", - "title": "BCA reports steady growth", - "url": "https://example.com/news/bca-growth", - "description": "BCA delivered steady quarterly growth across lending and deposits.", - "provider": "Contoso News", - "published_at": "2026-03-20T10:00:00Z", - "read_time_min": 3 - } -] +[{"id":"news-1","symbol":"BBCA.JK","title":"BCA reports steady growth","url":"https://example.com/news/bca-growth","description":"BCA delivered steady quarterly growth across lending and deposits.","provider":"Contoso News","published_at":"2026-03-20T10:00:00Z","read_time_min":3}] --- stderr diff --git a/tests/golden/stocks-profile.json.golden b/tests/golden/stocks-profile.json.golden index afafd3f..4822115 100644 --- a/tests/golden/stocks-profile.json.golden +++ b/tests/golden/stocks-profile.json.golden @@ -1,27 +1,4 @@ exit: 0 --- stdout -{ - "id": "feee16af-c94c-465d-a59d-a9727c22bd0e", - "symbol": "BBCA", - "short_name": "Bank Cntrl Asia", - "long_name": "PT Bank Central Asia Tbk", - "description": "PT Bank Central Asia Tbk (the Bank) is an Indonesia-based commercial bank.", - "sector": "Financials", - "industry": "Banking Services", - "website": "https://www.bca.co.id/", - "employees": 27937, - "address": "Menara BCA, Grand, Jl. MH Thamrin No. 1", - "city": "JAKARTA PUSAT", - "country": "Indonesia", - "phone": "+62 21 23588000", - "officers": [ - { - "name": "Jahja Setiaatmadja", - "title": "President Director", - "age": 69, - "year_born": 1955, - "total_pay": 12000000000 - } - ] -} +{"id":"feee16af-c94c-465d-a59d-a9727c22bd0e","symbol":"BBCA","short_name":"Bank Cntrl Asia","long_name":"PT Bank Central Asia Tbk","description":"PT Bank Central Asia Tbk (the Bank) is an Indonesia-based commercial bank.","sector":"Financials","industry":"Banking Services","website":"https://www.bca.co.id/","employees":27937,"address":"Menara BCA, Grand, Jl. MH Thamrin No. 1","city":"JAKARTA PUSAT","country":"Indonesia","phone":"+62 21 23588000","officers":[{"name":"Jahja Setiaatmadja","title":"President Director","age":69,"year_born":1955,"total_pay":12000000000}]} --- stderr diff --git a/tests/golden/stocks-quote-multi.json.golden b/tests/golden/stocks-quote-multi.json.golden index 6095d47..9df5da7 100644 --- a/tests/golden/stocks-quote-multi.json.golden +++ b/tests/golden/stocks-quote-multi.json.golden @@ -1,35 +1,4 @@ exit: 0 --- stdout -[ - { - "symbol": "BBCA.JK", - "price": 9875, - "change": 117, - "change_pct": 1.2, - "volume": 12300000, - "market_cap": 1215200000000000, - "week52_high": 10250, - "week52_low": 7800, - "week52_position": 0.8469387755102041, - "range_signal": "upper", - "prev_close": 9758, - "avg_volume": 10000000, - "as_of": null - }, - { - "symbol": "BBRI.JK", - "price": 9875, - "change": 117, - "change_pct": 1.2, - "volume": 12300000, - "market_cap": 1215200000000000, - "week52_high": 10250, - "week52_low": 7800, - "week52_position": 0.8469387755102041, - "range_signal": "upper", - "prev_close": 9758, - "avg_volume": 10000000, - "as_of": null - } -] +[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null},{"symbol":"BBRI.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}] --- stderr diff --git a/tests/golden/stocks-quote-multi.jsonl.golden b/tests/golden/stocks-quote-multi.jsonl.golden new file mode 100644 index 0000000..36b651b --- /dev/null +++ b/tests/golden/stocks-quote-multi.jsonl.golden @@ -0,0 +1,5 @@ +exit: 0 +--- stdout +{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null} +{"symbol":"BBRI.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null} +--- stderr diff --git a/tests/golden/stocks-quote-offline-warm.json.golden b/tests/golden/stocks-quote-offline-warm.json.golden index bfcbbaf..5f15d77 100644 --- a/tests/golden/stocks-quote-offline-warm.json.golden +++ b/tests/golden/stocks-quote-offline-warm.json.golden @@ -1,20 +1,4 @@ exit: 0 --- stdout -[ - { - "symbol": "BBCA.JK", - "price": 9875, - "change": 117, - "change_pct": 1.2, - "volume": 12300000, - "market_cap": 1215200000000000, - "week52_high": 10250, - "week52_low": 7800, - "week52_position": 0.8469387755102041, - "range_signal": "upper", - "prev_close": 9758, - "avg_volume": 10000000, - "as_of": null - } -] +[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}] --- stderr diff --git a/tests/golden/stocks-quote-stdin.json.golden b/tests/golden/stocks-quote-stdin.json.golden new file mode 100644 index 0000000..9df5da7 --- /dev/null +++ b/tests/golden/stocks-quote-stdin.json.golden @@ -0,0 +1,4 @@ +exit: 0 +--- stdout +[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null},{"symbol":"BBRI.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}] +--- stderr diff --git a/tests/golden/stocks-quote-stdin.table.golden b/tests/golden/stocks-quote-stdin.table.golden new file mode 100644 index 0000000..99eb4e0 --- /dev/null +++ b/tests/golden/stocks-quote-stdin.table.golden @@ -0,0 +1,10 @@ +exit: 0 +--- stdout +┌─────────┬───────┬──────┬────────┬────────────┬───────────────────────┬────────────┐ +│ SYMBOL ┆ PRICE ┆ CHG ┆ CHG% ┆ VOLUME ┆ MKT CAP ┆ 52W RANGE │ +╞═════════╪═══════╪══════╪════════╪════════════╪═══════════════════════╪════════════╡ +│ BBCA.JK ┆ 9,875 ┆ +117 ┆ +1.20% ┆ 12,300,000 ┆ 1,215,200,000,000,000 ┆ ████████░░ │ +├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ BBRI.JK ┆ 9,875 ┆ +117 ┆ +1.20% ┆ 12,300,000 ┆ 1,215,200,000,000,000 ┆ ████████░░ │ +└─────────┴───────┴──────┴────────┴────────────┴───────────────────────┴────────────┘ +--- stderr diff --git a/tests/golden/stocks-quote.json.golden b/tests/golden/stocks-quote.json.golden index bfcbbaf..5f15d77 100644 --- a/tests/golden/stocks-quote.json.golden +++ b/tests/golden/stocks-quote.json.golden @@ -1,20 +1,4 @@ exit: 0 --- stdout -[ - { - "symbol": "BBCA.JK", - "price": 9875, - "change": 117, - "change_pct": 1.2, - "volume": 12300000, - "market_cap": 1215200000000000, - "week52_high": 10250, - "week52_low": 7800, - "week52_position": 0.8469387755102041, - "range_signal": "upper", - "prev_close": 9758, - "avg_volume": 10000000, - "as_of": null - } -] +[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}] --- stderr diff --git a/tests/golden/stocks-risk.json.golden b/tests/golden/stocks-risk.json.golden index 0cce202..fd4d313 100644 --- a/tests/golden/stocks-risk.json.golden +++ b/tests/golden/stocks-risk.json.golden @@ -1,12 +1,4 @@ exit: 0 --- stdout -{ - "debt_to_equity": 0.42, - "current_ratio": 1.18, - "roa": 0.031, - "roa_pct": 3.1, - "de_signal": "conservative", - "current_ratio_signal": "tight", - "overall_signal": "moderate risk" -} +{"debt_to_equity":0.42,"current_ratio":1.18,"roa":0.031,"roa_pct":3.1,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"} --- stderr diff --git a/tests/golden/stocks-risk.table.golden b/tests/golden/stocks-risk.table.golden index 9888a27..6360460 100644 --- a/tests/golden/stocks-risk.table.golden +++ b/tests/golden/stocks-risk.table.golden @@ -1,15 +1,15 @@ exit: 0 --- stdout -Risk Analysis for BBCA.JK -┌───────────────┬────────┬─────────────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞═══════════════╪════════╪═════════════════════════╡ -│ Debt/Equity ┆ 0.42 ┆ conservative │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Current Ratio ┆ 1.18 ┆ tight │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ROA ┆ +3.10% ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ - ┆ moderate risk │ -└───────────────┴────────┴─────────────────────────┘ +Risk Analysis for BBCA.JK +┌───────────────┬────────┬───────────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞═══════════════╪════════╪═══════════════╡ +│ Debt/Equity ┆ 0.42 ┆ conservative │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Current Ratio ┆ 1.18 ┆ tight │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ROA ┆ +3.10% ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ - ┆ moderate risk │ +└───────────────┴────────┴───────────────┘ --- stderr diff --git a/tests/golden/stocks-screen-high-volume.json.golden b/tests/golden/stocks-screen-high-volume.json.golden index a822087..191abda 100644 --- a/tests/golden/stocks-screen-high-volume.json.golden +++ b/tests/golden/stocks-screen-high-volume.json.golden @@ -1,35 +1,4 @@ exit: 0 --- stdout -[ - { - "symbol": "BBRI.JK", - "price": 5100, - "change": 210, - "change_pct": 4.3, - "volume": 22200000, - "market_cap": 780000000000000, - "week52_high": 5400, - "week52_low": 3900, - "week52_position": 0.8, - "range_signal": "upper", - "prev_close": 4890, - "avg_volume": 18000000, - "as_of": null - }, - { - "symbol": "BBCA.JK", - "price": 9875, - "change": 117, - "change_pct": 1.2, - "volume": 12300000, - "market_cap": 1215200000000000, - "week52_high": 10500, - "week52_low": 8000, - "week52_position": 0.75, - "range_signal": "upper", - "prev_close": 9758, - "avg_volume": 10000000, - "as_of": null - } -] +[{"symbol":"BBRI.JK","price":5100,"change":210,"change_pct":4.3,"volume":22200000,"market_cap":780000000000000,"week52_high":5400,"week52_low":3900,"week52_position":0.8,"range_signal":"upper","prev_close":4890,"avg_volume":18000000,"as_of":null},{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10500,"week52_low":8000,"week52_position":0.75,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}] --- stderr diff --git a/tests/golden/stocks-screen-large-cap.json.golden b/tests/golden/stocks-screen-large-cap.json.golden index 94ac385..5303d7b 100644 --- a/tests/golden/stocks-screen-large-cap.json.golden +++ b/tests/golden/stocks-screen-large-cap.json.golden @@ -1,35 +1,4 @@ exit: 0 --- stdout -[ - { - "symbol": "BBCA.JK", - "price": 9875, - "change": 117, - "change_pct": 1.2, - "volume": 12300000, - "market_cap": 1215200000000000, - "week52_high": 10500, - "week52_low": 8000, - "week52_position": 0.75, - "range_signal": "upper", - "prev_close": 9758, - "avg_volume": 10000000, - "as_of": null - }, - { - "symbol": "BBRI.JK", - "price": 5100, - "change": 210, - "change_pct": 4.3, - "volume": 22200000, - "market_cap": 780000000000000, - "week52_high": 5400, - "week52_low": 3900, - "week52_position": 0.8, - "range_signal": "upper", - "prev_close": 4890, - "avg_volume": 18000000, - "as_of": null - } -] +[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10500,"week52_low":8000,"week52_position":0.75,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null},{"symbol":"BBRI.JK","price":5100,"change":210,"change_pct":4.3,"volume":22200000,"market_cap":780000000000000,"week52_high":5400,"week52_low":3900,"week52_position":0.8,"range_signal":"upper","prev_close":4890,"avg_volume":18000000,"as_of":null}] --- stderr diff --git a/tests/golden/stocks-screen-worst.json.golden b/tests/golden/stocks-screen-worst.json.golden index 94ac385..5303d7b 100644 --- a/tests/golden/stocks-screen-worst.json.golden +++ b/tests/golden/stocks-screen-worst.json.golden @@ -1,35 +1,4 @@ exit: 0 --- stdout -[ - { - "symbol": "BBCA.JK", - "price": 9875, - "change": 117, - "change_pct": 1.2, - "volume": 12300000, - "market_cap": 1215200000000000, - "week52_high": 10500, - "week52_low": 8000, - "week52_position": 0.75, - "range_signal": "upper", - "prev_close": 9758, - "avg_volume": 10000000, - "as_of": null - }, - { - "symbol": "BBRI.JK", - "price": 5100, - "change": 210, - "change_pct": 4.3, - "volume": 22200000, - "market_cap": 780000000000000, - "week52_high": 5400, - "week52_low": 3900, - "week52_position": 0.8, - "range_signal": "upper", - "prev_close": 4890, - "avg_volume": 18000000, - "as_of": null - } -] +[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10500,"week52_low":8000,"week52_position":0.75,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null},{"symbol":"BBRI.JK","price":5100,"change":210,"change_pct":4.3,"volume":22200000,"market_cap":780000000000000,"week52_high":5400,"week52_low":3900,"week52_position":0.8,"range_signal":"upper","prev_close":4890,"avg_volume":18000000,"as_of":null}] --- stderr diff --git a/tests/golden/stocks-screen.json.golden b/tests/golden/stocks-screen.json.golden index a822087..191abda 100644 --- a/tests/golden/stocks-screen.json.golden +++ b/tests/golden/stocks-screen.json.golden @@ -1,35 +1,4 @@ exit: 0 --- stdout -[ - { - "symbol": "BBRI.JK", - "price": 5100, - "change": 210, - "change_pct": 4.3, - "volume": 22200000, - "market_cap": 780000000000000, - "week52_high": 5400, - "week52_low": 3900, - "week52_position": 0.8, - "range_signal": "upper", - "prev_close": 4890, - "avg_volume": 18000000, - "as_of": null - }, - { - "symbol": "BBCA.JK", - "price": 9875, - "change": 117, - "change_pct": 1.2, - "volume": 12300000, - "market_cap": 1215200000000000, - "week52_high": 10500, - "week52_low": 8000, - "week52_position": 0.75, - "range_signal": "upper", - "prev_close": 9758, - "avg_volume": 10000000, - "as_of": null - } -] +[{"symbol":"BBRI.JK","price":5100,"change":210,"change_pct":4.3,"volume":22200000,"market_cap":780000000000000,"week52_high":5400,"week52_low":3900,"week52_position":0.8,"range_signal":"upper","prev_close":4890,"avg_volume":18000000,"as_of":null},{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.2,"volume":12300000,"market_cap":1215200000000000,"week52_high":10500,"week52_low":8000,"week52_position":0.75,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}] --- stderr diff --git a/tests/golden/stocks-sentiment.json.golden b/tests/golden/stocks-sentiment.json.golden index e4d6d4a..0aca7de 100644 --- a/tests/golden/stocks-sentiment.json.golden +++ b/tests/golden/stocks-sentiment.json.golden @@ -1,20 +1,4 @@ exit: 0 --- stdout -{ - "symbol": "BBCA.JK", - "statistics": [ - { - "time_range": "1D", - "bullish": 10, - "bearish": 2, - "neutral": 3 - }, - { - "time_range": "1W", - "bullish": 25, - "bearish": 5, - "neutral": 7 - } - ] -} +{"symbol":"BBCA.JK","statistics":[{"time_range":"1D","bullish":10,"bearish":2,"neutral":3},{"time_range":"1W","bullish":25,"bearish":5,"neutral":7}]} --- stderr diff --git a/tests/golden/stocks-technical.json.golden b/tests/golden/stocks-technical.json.golden index 3e587b3..d0b773d 100644 --- a/tests/golden/stocks-technical.json.golden +++ b/tests/golden/stocks-technical.json.golden @@ -1,28 +1,4 @@ exit: 0 --- stdout -{ - "symbol": "BBCA.JK", - "as_of": "2024-03-03", - "current_price": 9925, - "sma20": null, - "sma50": null, - "sma200": null, - "rsi14": null, - "macd": { - "line": null, - "signal": null, - "histogram": null - }, - "volume": { - "current": 14000000, - "average20": null, - "ratio20": null - }, - "signals": { - "rsi": "Neutral", - "macd": "Neutral", - "trend": "Neutral", - "overall": "Neutral" - } -} +{"symbol":"BBCA.JK","as_of":"2024-03-03","current_price":9925,"sma20":null,"sma50":null,"sma200":null,"rsi14":null,"macd":{"line":null,"signal":null,"histogram":null},"volume":{"current":14000000,"average20":null,"ratio20":null},"signals":{"rsi":"Neutral","macd":"Neutral","trend":"Neutral","overall":"Neutral"}} --- stderr diff --git a/tests/golden/stocks-technical.table.golden b/tests/golden/stocks-technical.table.golden index d1099d0..2bed5e0 100644 --- a/tests/golden/stocks-technical.table.golden +++ b/tests/golden/stocks-technical.table.golden @@ -1,26 +1,26 @@ exit: 0 --- stdout -Technical Analysis for BBCA.JK (2024-03-03) -┌───────────────────┬─────────────────────────────────────────────────────┬───────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞═══════════════════╪═════════════════════════════════════════════════════╪═══════════════════╡ -│ Current Price ┆ 9,925 ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ SMA 20 ┆ - ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ SMA 50 ┆ - ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ SMA 200 ┆ - ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ RSI (14) ┆ - ┆ Neutral │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ MACD (12,26,9) ┆ -/-/- ┆ Neutral │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Trend ┆ Trend unavailable (need at least 200 daily candles) ┆ Neutral │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Volume Ratio (20) ┆ - ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall Signal ┆ - ┆ NEUTRAL │ -└───────────────────┴─────────────────────────────────────────────────────┴───────────────────┘ +Technical Analysis for BBCA.JK (2024-03-03) +┌───────────────────┬─────────────────────────────────────────────────────┬─────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞═══════════════════╪═════════════════════════════════════════════════════╪═════════╡ +│ Current Price ┆ 9,925 ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ +│ SMA 20 ┆ - ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ +│ SMA 50 ┆ - ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ +│ SMA 200 ┆ - ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ +│ RSI (14) ┆ - ┆ Neutral │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ +│ MACD (12,26,9) ┆ -/-/- ┆ Neutral │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ +│ Trend ┆ Trend unavailable (need at least 200 daily candles) ┆ Neutral │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ +│ Volume Ratio (20) ┆ - ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ +│ Overall Signal ┆ - ┆ NEUTRAL │ +└───────────────────┴─────────────────────────────────────────────────────┴─────────┘ --- stderr info: history provider fallback active (msn -> yahoo) diff --git a/tests/golden/stocks-valuation-nonfinite.json.golden b/tests/golden/stocks-valuation-nonfinite.json.golden index ef8159f..d5761cf 100644 --- a/tests/golden/stocks-valuation-nonfinite.json.golden +++ b/tests/golden/stocks-valuation-nonfinite.json.golden @@ -1,19 +1,4 @@ exit: 0 --- stdout -{ - "pe_trailing": null, - "pe_forward": null, - "pb": 4.6, - "roe": 0.198, - "roe_pct": 19.8, - "net_margin": 0.442, - "net_margin_pct": 44.2, - "ev_ebitda": null, - "pe_signal": "no data", - "pb_signal": "expensive", - "roe_signal": "strong", - "margin_signal": "excellent", - "ev_ebitda_signal": "no data", - "overall_signal": "expensive" -} +{"pe_trailing":null,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.442,"net_margin_pct":44.2,"ev_ebitda":null,"pe_signal":"no data","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"} --- stderr diff --git a/tests/golden/stocks-valuation-nonfinite.table.golden b/tests/golden/stocks-valuation-nonfinite.table.golden index 6ccd5d2..9626075 100644 --- a/tests/golden/stocks-valuation-nonfinite.table.golden +++ b/tests/golden/stocks-valuation-nonfinite.table.golden @@ -1,21 +1,21 @@ exit: 0 --- stdout -Valuation Analysis for BBCA.JK -┌────────────────┬─────────┬─────────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞════════════════╪═════════╪═════════════════════╡ -│ P/E (Trailing) ┆ - ┆ no data │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ P/E (Forward) ┆ - ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Price/Book ┆ 4.60 ┆ expensive │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ROE ┆ +19.80% ┆ strong │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Net Margin ┆ +44.20% ┆ excellent │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ EV/EBITDA ┆ - ┆ no data │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ - ┆ expensive │ -└────────────────┴─────────┴─────────────────────┘ +Valuation Analysis for BBCA.JK +┌────────────────┬─────────┬───────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞════════════════╪═════════╪═══════════╡ +│ P/E (Trailing) ┆ - ┆ no data │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ P/E (Forward) ┆ - ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Price/Book ┆ 4.60 ┆ expensive │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ ROE ┆ +19.80% ┆ strong │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Net Margin ┆ +44.20% ┆ excellent │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ EV/EBITDA ┆ - ┆ no data │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ - ┆ expensive │ +└────────────────┴─────────┴───────────┘ --- stderr diff --git a/tests/golden/stocks-valuation.json.golden b/tests/golden/stocks-valuation.json.golden index f795bfb..3cdc063 100644 --- a/tests/golden/stocks-valuation.json.golden +++ b/tests/golden/stocks-valuation.json.golden @@ -1,19 +1,4 @@ exit: 0 --- stdout -{ - "pe_trailing": 25.4, - "pe_forward": null, - "pb": 4.6, - "roe": 0.198, - "roe_pct": 19.8, - "net_margin": 0.324, - "net_margin_pct": 32.4, - "ev_ebitda": null, - "pe_signal": "premium", - "pb_signal": "expensive", - "roe_signal": "strong", - "margin_signal": "excellent", - "ev_ebitda_signal": "no data", - "overall_signal": "expensive" -} +{"pe_trailing":25.4,"pe_forward":null,"pb":4.6,"roe":0.198,"roe_pct":19.8,"net_margin":0.324,"net_margin_pct":32.4,"ev_ebitda":null,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"strong","margin_signal":"excellent","ev_ebitda_signal":"no data","overall_signal":"expensive"} --- stderr diff --git a/tests/golden/stocks-valuation.table.golden b/tests/golden/stocks-valuation.table.golden index 2a0666c..5964aa0 100644 --- a/tests/golden/stocks-valuation.table.golden +++ b/tests/golden/stocks-valuation.table.golden @@ -1,21 +1,21 @@ exit: 0 --- stdout -Valuation Analysis for BBCA.JK -┌────────────────┬─────────┬─────────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞════════════════╪═════════╪═════════════════════╡ -│ P/E (Trailing) ┆ 25.40 ┆ premium │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ P/E (Forward) ┆ - ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Price/Book ┆ 4.60 ┆ expensive │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ROE ┆ +19.80% ┆ strong │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Net Margin ┆ +32.40% ┆ excellent │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ EV/EBITDA ┆ - ┆ no data │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ - ┆ expensive │ -└────────────────┴─────────┴─────────────────────┘ +Valuation Analysis for BBCA.JK +┌────────────────┬─────────┬───────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞════════════════╪═════════╪═══════════╡ +│ P/E (Trailing) ┆ 25.40 ┆ premium │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ P/E (Forward) ┆ - ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Price/Book ┆ 4.60 ┆ expensive │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ ROE ┆ +19.80% ┆ strong │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Net Margin ┆ +32.40% ┆ excellent │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ EV/EBITDA ┆ - ┆ no data │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ - ┆ expensive │ +└────────────────┴─────────┴───────────┘ --- stderr diff --git a/tests/golden/yahoo-fundamental.json.golden b/tests/golden/yahoo-fundamental.json.golden index 8857e28..d463687 100644 --- a/tests/golden/yahoo-fundamental.json.golden +++ b/tests/golden/yahoo-fundamental.json.golden @@ -1,41 +1,4 @@ exit: 0 --- stdout -{ - "symbol": "BBCA.JK", - "growth": { - "revenue_growth": 0.118, - "earnings_growth": 0.121, - "revenue_growth_pct": 11.8, - "earnings_growth_pct": 12.1, - "revenue_signal": "moderate", - "earnings_signal": "moderate", - "overall_signal": "growing" - }, - "valuation": { - "pe_trailing": 25.4, - "pe_forward": 23.1, - "pb": 4.6, - "roe": 0.202, - "roe_pct": 20.2, - "net_margin": 0.385, - "net_margin_pct": 38.5, - "ev_ebitda": 21.28, - "pe_signal": "premium", - "pb_signal": "expensive", - "roe_signal": "excellent", - "margin_signal": "excellent", - "ev_ebitda_signal": "expensive", - "overall_signal": "expensive" - }, - "risk": { - "debt_to_equity": 18.5, - "current_ratio": 1.21, - "roa": 0.038, - "roa_pct": 3.8, - "de_signal": "conservative", - "current_ratio_signal": "tight", - "overall_signal": "moderate risk" - }, - "overall_signal": "mixed" -} +{"symbol":"BBCA.JK","growth":{"revenue_growth":0.118,"earnings_growth":0.121,"revenue_growth_pct":11.8,"earnings_growth_pct":12.1,"revenue_signal":"moderate","earnings_signal":"moderate","overall_signal":"growing"},"valuation":{"pe_trailing":25.4,"pe_forward":23.1,"pb":4.6,"roe":0.202,"roe_pct":20.2,"net_margin":0.385,"net_margin_pct":38.5,"ev_ebitda":21.28,"pe_signal":"premium","pb_signal":"expensive","roe_signal":"excellent","margin_signal":"excellent","ev_ebitda_signal":"expensive","overall_signal":"expensive"},"risk":{"debt_to_equity":18.5,"current_ratio":1.21,"roa":0.038,"roa_pct":3.8,"de_signal":"conservative","current_ratio_signal":"tight","overall_signal":"moderate risk"},"overall_signal":"mixed"} --- stderr diff --git a/tests/golden/yahoo-fundamental.table.golden b/tests/golden/yahoo-fundamental.table.golden index 651349a..13cfbad 100644 --- a/tests/golden/yahoo-fundamental.table.golden +++ b/tests/golden/yahoo-fundamental.table.golden @@ -1,49 +1,49 @@ exit: 0 --- stdout -Fundamental Analysis for BBCA.JK +Fundamental Analysis for BBCA.JK -Growth Analysis for BBCA.JK -┌─────────────────┬─────────┬────────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞═════════════════╪═════════╪════════════════════╡ -│ Revenue Growth ┆ +11.80% ┆ moderate │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Earnings Growth ┆ +12.10% ┆ moderate │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ - ┆ growing │ -└─────────────────┴─────────┴────────────────────┘ +Growth Analysis for BBCA.JK +┌─────────────────┬─────────┬──────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞═════════════════╪═════════╪══════════╡ +│ Revenue Growth ┆ +11.80% ┆ moderate │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ +│ Earnings Growth ┆ +12.10% ┆ moderate │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ - ┆ growing │ +└─────────────────┴─────────┴──────────┘ -Valuation Analysis for BBCA.JK -┌────────────────┬─────────┬─────────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞════════════════╪═════════╪═════════════════════╡ -│ P/E (Trailing) ┆ 25.40 ┆ premium │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ P/E (Forward) ┆ 23.10 ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Price/Book ┆ 4.60 ┆ expensive │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ROE ┆ +20.20% ┆ excellent │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Net Margin ┆ +38.50% ┆ excellent │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ EV/EBITDA ┆ 21.28 ┆ expensive │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ - ┆ expensive │ -└────────────────┴─────────┴─────────────────────┘ +Valuation Analysis for BBCA.JK +┌────────────────┬─────────┬───────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞════════════════╪═════════╪═══════════╡ +│ P/E (Trailing) ┆ 25.40 ┆ premium │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ P/E (Forward) ┆ 23.10 ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Price/Book ┆ 4.60 ┆ expensive │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ ROE ┆ +20.20% ┆ excellent │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Net Margin ┆ +38.50% ┆ excellent │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ EV/EBITDA ┆ 21.28 ┆ expensive │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ - ┆ expensive │ +└────────────────┴─────────┴───────────┘ -Risk Analysis for BBCA.JK -┌───────────────┬────────┬─────────────────────────┐ -│ METRIC ┆ VALUE ┆ SIGNAL │ -╞═══════════════╪════════╪═════════════════════════╡ -│ Debt/Equity ┆ 18.50 ┆ conservative │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Current Ratio ┆ 1.21 ┆ tight │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ ROA ┆ +3.80% ┆ - │ -├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ -│ Overall ┆ - ┆ moderate risk │ -└───────────────┴────────┴─────────────────────────┘ +Risk Analysis for BBCA.JK +┌───────────────┬────────┬───────────────┐ +│ METRIC ┆ VALUE ┆ SIGNAL │ +╞═══════════════╪════════╪═══════════════╡ +│ Debt/Equity ┆ 18.50 ┆ conservative │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Current Ratio ┆ 1.21 ┆ tight │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ ROA ┆ +3.80% ┆ - │ +├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ +│ Overall ┆ - ┆ moderate risk │ +└───────────────┴────────┴───────────────┘ -Overall Signal: mixed +Overall Signal: mixed --- stderr diff --git a/tests/golden/yahoo-history.json.golden b/tests/golden/yahoo-history.json.golden index 423e9c6..317447c 100644 --- a/tests/golden/yahoo-history.json.golden +++ b/tests/golden/yahoo-history.json.golden @@ -1,29 +1,4 @@ exit: 0 --- stdout -[ - { - "date": "2024-03-01", - "open": 9800, - "high": 9900, - "low": 9750, - "close": 9875, - "volume": 12300000 - }, - { - "date": "2024-03-02", - "open": 9850, - "high": 9920, - "low": 9800, - "close": 9880, - "volume": 11000000 - }, - { - "date": "2024-03-03", - "open": 9860, - "high": 9950, - "low": 9820, - "close": 9925, - "volume": 14000000 - } -] +[{"date":"2024-03-01","open":9800,"high":9900,"low":9750,"close":9875,"volume":12300000},{"date":"2024-03-02","open":9850,"high":9920,"low":9800,"close":9880,"volume":11000000},{"date":"2024-03-03","open":9860,"high":9950,"low":9820,"close":9925,"volume":14000000}] --- stderr diff --git a/tests/golden/yahoo-history.table.golden b/tests/golden/yahoo-history.table.golden index 59954ee..b20b772 100644 --- a/tests/golden/yahoo-history.table.golden +++ b/tests/golden/yahoo-history.table.golden @@ -1,6 +1,6 @@ exit: 0 --- stdout -History for BBCA.JK +History for BBCA.JK ┌────────────┬───────┬───────┬───────┬───────┬────────────┐ │ DATE ┆ OPEN ┆ HIGH ┆ LOW ┆ CLOSE ┆ VOLUME │ ╞════════════╪═══════╪═══════╪═══════╪═══════╪════════════╡ diff --git a/tests/golden/yahoo-quote.json.golden b/tests/golden/yahoo-quote.json.golden index ead2991..74ddf4e 100644 --- a/tests/golden/yahoo-quote.json.golden +++ b/tests/golden/yahoo-quote.json.golden @@ -1,20 +1,4 @@ exit: 0 --- stdout -[ - { - "symbol": "BBCA.JK", - "price": 9875, - "change": 117, - "change_pct": 1.1990161918425908, - "volume": 12300000, - "market_cap": 1215200000000000, - "week52_high": 10250, - "week52_low": 7800, - "week52_position": 0.8469387755102041, - "range_signal": "upper", - "prev_close": 9758, - "avg_volume": 10000000, - "as_of": null - } -] +[{"symbol":"BBCA.JK","price":9875,"change":117,"change_pct":1.1990161918425908,"volume":12300000,"market_cap":1215200000000000,"week52_high":10250,"week52_low":7800,"week52_position":0.8469387755102041,"range_signal":"upper","prev_close":9758,"avg_volume":10000000,"as_of":null}] --- stderr