diff --git a/.gitignore b/.gitignore index a65fefc7..d5792166 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ Carthage/Build/ # Data *.csv +!crates/libfmp/tests/fixtures/*.csv *.h5 *.hdf5 *.pickle diff --git a/Cargo.lock b/Cargo.lock index 60ccac25..1903f3dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,6 +174,27 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + [[package]] name = "deranged" version = "0.5.8" @@ -711,6 +732,7 @@ version = "1.1.0" dependencies = [ "bytes", "chrono", + "csv", "http", "libc", "reqwest", @@ -1344,6 +1366,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "same-file" version = "1.0.6" diff --git a/Cargo.toml b/Cargo.toml index f3dd1927..6b265f6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ license = "MIT" [workspace.dependencies] bytes = "1.12.1" chrono = { version = "0.4.45", default-features = false, features = ["std"] } +csv = "1.4" http = "1.5.0" libc = "0.2.189" libfmp = { path = "crates/libfmp" } diff --git a/crates/fmp-py-gen/src/bin/gen_go/methods.rs b/crates/fmp-py-gen/src/bin/gen_go/methods.rs index e640a178..bd94907d 100644 --- a/crates/fmp-py-gen/src/bin/gen_go/methods.rs +++ b/crates/fmp-py-gen/src/bin/gen_go/methods.rs @@ -69,6 +69,7 @@ impl QueryPlan { #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum ResponseKind { Rows(String), + CsvRows(String), Single(String), Dynamic, Binary(Vec), @@ -270,7 +271,9 @@ impl Context<'_> { let response = match (&endpoint.response_model, &wire.contract) { (_, Contract::Binary(types)) if endpoint.binary => ResponseKind::Binary(types.clone()), (None, Contract::Rows) if endpoint.dynamic => ResponseKind::Dynamic, - (Some(model), Contract::Rows) => { + (Some(model), contract @ (Contract::Rows | Contract::CsvRows)) + if !(endpoint.single && *contract == Contract::CsvRows) => + { let owner = self.model_owner.get(&model.name).ok_or_else(|| { format!("response model `{}` is not a discovered struct", model.name) })?; @@ -281,7 +284,9 @@ impl Context<'_> { model.name )); } - if endpoint.single { + if *contract == Contract::CsvRows { + ResponseKind::CsvRows(go_name(&model.name)) + } else if endpoint.single { ResponseKind::Single(go_name(&model.name)) } else { ResponseKind::Rows(go_name(&model.name)) diff --git a/crates/fmp-py-gen/src/bin/gen_go/render.rs b/crates/fmp-py-gen/src/bin/gen_go/render.rs index 09ef28f6..4f65b1c4 100644 --- a/crates/fmp-py-gen/src/bin/gen_go/render.rs +++ b/crates/fmp-py-gen/src/bin/gen_go/render.rs @@ -336,7 +336,9 @@ fn render_method(namespace: &NamespacePlan, method: &MethodPlan, out: &mut Strin .as_ref() .map_or(String::new(), |query| format!(", q {query}")); let (result, zero) = match &method.response { - ResponseKind::Rows(model) => (format!("[]{model}"), "nil".to_string()), + ResponseKind::Rows(model) | ResponseKind::CsvRows(model) => { + (format!("[]{model}"), "nil".to_string()) + } ResponseKind::Single(model) => (model.clone(), format!("{model}{{}}")), ResponseKind::Dynamic => ("[]jsontext.Value".to_string(), "nil".to_string()), ResponseKind::Binary(_) => ("BinaryPayload".to_string(), "BinaryPayload{}".to_string()), @@ -368,6 +370,12 @@ fn render_method(namespace: &NamespacePlan, method: &MethodPlan, out: &mut Strin types.join(", ") ); } + ResponseKind::CsvRows(model) => { + let _ = writeln!( + out, + "\treturn getCSV[{model}](ctx, n.client, {id:?}, {path:?}, {params})\n}}\n" + ); + } ResponseKind::Rows(_) | ResponseKind::Single(_) | ResponseKind::Dynamic => { let _ = writeln!( out, diff --git a/crates/fmp-py-gen/src/bin/gen_go/tests.rs b/crates/fmp-py-gen/src/bin/gen_go/tests.rs index 4ba383ae..f2e78e0a 100644 --- a/crates/fmp-py-gen/src/bin/gen_go/tests.rs +++ b/crates/fmp-py-gen/src/bin/gen_go/tests.rs @@ -196,6 +196,30 @@ fn nested_namespaces_embed_children_by_registry_path() { ); } +/// Bulk descriptors use `EndpointSpec::get_csv`, so their methods decode +/// through `getCSV` instead of `getJSON` (ADR 0035). +#[test] +fn csv_bulk_endpoints_render_through_get_csv() { + let files = + generate_domains(&domain_set(&["bulk", "company"])).expect("bulk generates with company"); + let (_, bulk) = files + .iter() + .find(|(name, _)| name == "bulk.go") + .expect("bulk.go is rendered"); + assert!( + bulk.contains( + "return getCSV[BulkStockRating](ctx, n.client, \"rating-bulk\", \"rating-bulk\", nil)" + ), + "{bulk}" + ); + assert!( + bulk.contains( + "return getCSV[CompanyProfile](ctx, n.client, \"profile-bulk\", \"profile-bulk\", params)" + ), + "{bulk}" + ); +} + #[test] fn cross_domain_models_require_their_owner_to_be_generated() { let error = diff --git a/crates/fmp-py-gen/src/registry/wire/mod.rs b/crates/fmp-py-gen/src/registry/wire/mod.rs index 06d834e5..c94b2ed0 100644 --- a/crates/fmp-py-gen/src/registry/wire/mod.rs +++ b/crates/fmp-py-gen/src/registry/wire/mod.rs @@ -66,6 +66,8 @@ impl fmt::Display for HttpMethod { pub enum Contract { /// A JSON array of rows. Rows, + /// A headed CSV body, one row per record (ADR 0035). + CsvRows, /// A binary body with the listed acceptable content types. Binary(Vec), } @@ -273,7 +275,8 @@ mod tests { assert!(!wire.id.is_empty(), "{}", wire.method); assert_eq!(wire.http_method, HttpMethod::Get, "{}", wire.method); match (&wire.contract, endpoint.binary) { - (Contract::Rows, false) | (Contract::Binary(_), true) => {} + (Contract::Rows | Contract::CsvRows, false) + | (Contract::Binary(_), true) => {} (contract, binary) => { panic!( "{}: contract {contract:?} vs binary = {binary}", @@ -510,11 +513,19 @@ mod tests { pub fn explicit(query: CustomQuery) -> EndpointSpec { EndpointSpec::with_response(HttpMethod::Get, "x", "x-path", query, ResponseContract::binary(&["a/b"])) } + pub fn bulk() -> EndpointSpec<(), Vec> { + EndpointSpec::get_csv("dcf-bulk", "dcf-bulk", ()) + } + pub fn explicit_csv() -> EndpointSpec<(), Vec> { + EndpointSpec::with_response(HttpMethod::Get, "y", "y-path", (), ResponseContract::csv()) + } impl Client { pub async fn quotes(&self) -> Result> { self.execute("es()).await } pub async fn custom(&self, query: CustomQuery) -> Result> { self.execute(&custom(query)).await } pub async fn xlsx(&self, query: CustomQuery) -> Result { self.execute(&xlsx(query)).await } pub async fn explicit(&self, query: CustomQuery) -> Result { self.execute(&explicit(query)).await } + pub async fn bulk(&self) -> Result> { self.execute(&bulk()).await } + pub async fn explicit_csv(&self) -> Result> { self.execute(&explicit_csv()).await } } "#; let surface = surface(&[("dcf.rs", source)]); @@ -548,6 +559,12 @@ mod tests { Contract::Binary(vec!["a/b".to_owned()]) ); assert_eq!(surface.endpoints["explicit"].relative_path, "x-path"); + assert_eq!(surface.endpoints["bulk"].contract, Contract::CsvRows); + assert_eq!(surface.endpoints["bulk"].relative_path, "dcf-bulk"); + assert_eq!( + surface.endpoints["explicit_csv"].contract, + Contract::CsvRows + ); } #[test] diff --git a/crates/fmp-py-gen/src/registry/wire/resolve.rs b/crates/fmp-py-gen/src/registry/wire/resolve.rs index 39047e28..433d41ef 100644 --- a/crates/fmp-py-gen/src/registry/wire/resolve.rs +++ b/crates/fmp-py-gen/src/registry/wire/resolve.rs @@ -297,6 +297,13 @@ impl Evaluator<'_> { contract: Contract::Binary(strings(3, "content types")?), metadata: None, }), + "get_csv" => Ok(Spec { + id: string(0, "id")?, + relative_path: string(1, "path")?, + http_method: HttpMethod::Get, + contract: Contract::CsvRows, + metadata: None, + }), "new" => Ok(Spec { http_method: http_method(0)?, id: string(1, "id")?, @@ -315,7 +322,7 @@ impl Evaluator<'_> { } } - /// Reads `ResponseContract::json()` or `ResponseContract::binary(list)`. + /// Reads `ResponseContract::json()`, `::csv()`, or `::binary(list)`. fn response_contract( &self, expr: Option<&Expr>, @@ -323,7 +330,7 @@ impl Evaluator<'_> { ) -> Result { let unsupported = |expr: Option<&Expr>| { format!( - "response contract is not `ResponseContract::json()` or `::binary(..)`: {}", + "response contract is not `ResponseContract::json()`, `::csv()` or `::binary(..)`: {}", expr.map(text).unwrap_or_default() ) }; @@ -343,6 +350,9 @@ impl Evaluator<'_> { ([head, kind], None) if head == "ResponseContract" && kind == "json" => { Ok(Contract::Rows) } + ([head, kind], None) if head == "ResponseContract" && kind == "csv" => { + Ok(Contract::CsvRows) + } ([head, kind], Some(list)) if head == "ResponseContract" && kind == "binary" => { match self.eval(list, &BTreeMap::new(), module) { Value::Strs(values) => Ok(Contract::Binary(values)), diff --git a/crates/fmp-py/python/fmp/_native/__init__.pyi b/crates/fmp-py/python/fmp/_native/__init__.pyi index 0c09d90f..93a96d76 100644 --- a/crates/fmp-py/python/fmp/_native/__init__.pyi +++ b/crates/fmp-py/python/fmp/_native/__init__.pyi @@ -172,7 +172,9 @@ class FmpClient: the default host the constructor raises `FmpConfigError` naming `FMP_API_KEY`. `timeout` and `connect_timeout` are positive finite numbers of seconds. - `max_response_body_bytes` bounds each buffered response. Authenticated + `max_response_body_bytes` bounds each buffered response; when omitted, + `client.bulk` methods buffer up to 256 MiB and every other method up to + 64 MiB. Authenticated non-loopback HTTP requires `danger_allow_insecure_authentication=True`. Redirects are either disabled or restricted to the same origin. diff --git a/crates/fmp-py/python/fmp/_native/bulk/balance/__init__.pyi b/crates/fmp-py/python/fmp/_native/bulk/balance/__init__.pyi index 9a043edb..7a18fdf1 100644 --- a/crates/fmp-py/python/fmp/_native/bulk/balance/__init__.pyi +++ b/crates/fmp-py/python/fmp/_native/bulk/balance/__init__.pyi @@ -92,111 +92,115 @@ class BulkBalanceSheetStatement: @property def fiscal_year(self) -> builtins.str: ... @property - def cash_and_cash_equivalents(self) -> builtins.str: ... + def cash_and_cash_equivalents(self) -> typing.Optional[builtins.str]: ... @property - def short_term_investments(self) -> builtins.str: ... + def short_term_investments(self) -> typing.Optional[builtins.str]: ... @property - def cash_and_short_term_investments(self) -> builtins.str: ... + def cash_and_short_term_investments(self) -> typing.Optional[builtins.str]: ... @property - def net_receivables(self) -> builtins.str: ... + def net_receivables(self) -> typing.Optional[builtins.str]: ... @property - def accounts_receivables(self) -> builtins.str: ... + def accounts_receivables(self) -> typing.Optional[builtins.str]: ... @property - def other_receivables(self) -> builtins.str: ... + def other_receivables(self) -> typing.Optional[builtins.str]: ... @property - def inventory(self) -> builtins.str: ... + def inventory(self) -> typing.Optional[builtins.str]: ... @property - def prepaids(self) -> builtins.str: ... + def prepaids(self) -> typing.Optional[builtins.str]: ... @property - def other_current_assets(self) -> builtins.str: ... + def other_current_assets(self) -> typing.Optional[builtins.str]: ... @property - def total_current_assets(self) -> builtins.str: ... + def total_current_assets(self) -> typing.Optional[builtins.str]: ... @property - def property_plant_equipment_net(self) -> builtins.str: ... + def property_plant_equipment_net(self) -> typing.Optional[builtins.str]: ... @property - def goodwill(self) -> builtins.str: ... + def goodwill(self) -> typing.Optional[builtins.str]: ... @property - def intangible_assets(self) -> builtins.str: ... + def intangible_assets(self) -> typing.Optional[builtins.str]: ... @property - def goodwill_and_intangible_assets(self) -> builtins.str: ... + def goodwill_and_intangible_assets(self) -> typing.Optional[builtins.str]: ... @property - def long_term_investments(self) -> builtins.str: ... + def long_term_investments(self) -> typing.Optional[builtins.str]: ... @property - def tax_assets(self) -> builtins.str: ... + def tax_assets(self) -> typing.Optional[builtins.str]: ... @property - def other_non_current_assets(self) -> builtins.str: ... + def other_non_current_assets(self) -> typing.Optional[builtins.str]: ... @property - def total_non_current_assets(self) -> builtins.str: ... + def total_non_current_assets(self) -> typing.Optional[builtins.str]: ... @property - def other_assets(self) -> builtins.str: ... + def other_assets(self) -> typing.Optional[builtins.str]: ... @property - def total_assets(self) -> builtins.str: ... + def total_assets(self) -> typing.Optional[builtins.str]: ... @property - def total_payables(self) -> builtins.str: ... + def total_payables(self) -> typing.Optional[builtins.str]: ... @property - def account_payables(self) -> builtins.str: ... + def account_payables(self) -> typing.Optional[builtins.str]: ... @property - def other_payables(self) -> builtins.str: ... + def other_payables(self) -> typing.Optional[builtins.str]: ... @property - def accrued_expenses(self) -> builtins.str: ... + def accrued_expenses(self) -> typing.Optional[builtins.str]: ... @property - def short_term_debt(self) -> builtins.str: ... + def short_term_debt(self) -> typing.Optional[builtins.str]: ... @property - def capital_lease_obligations_current(self) -> builtins.str: ... + def capital_lease_obligations_current(self) -> typing.Optional[builtins.str]: ... @property - def tax_payables(self) -> builtins.str: ... + def tax_payables(self) -> typing.Optional[builtins.str]: ... @property - def deferred_revenue(self) -> builtins.str: ... + def deferred_revenue(self) -> typing.Optional[builtins.str]: ... @property - def other_current_liabilities(self) -> builtins.str: ... + def other_current_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def total_current_liabilities(self) -> builtins.str: ... + def total_current_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def long_term_debt(self) -> builtins.str: ... + def long_term_debt(self) -> typing.Optional[builtins.str]: ... @property - def capital_lease_obligations_non_current(self) -> builtins.str: ... + def capital_lease_obligations_non_current( + self, + ) -> typing.Optional[builtins.str]: ... @property - def deferred_revenue_non_current(self) -> builtins.str: ... + def deferred_revenue_non_current(self) -> typing.Optional[builtins.str]: ... @property - def deferred_tax_liabilities_non_current(self) -> builtins.str: ... + def deferred_tax_liabilities_non_current(self) -> typing.Optional[builtins.str]: ... @property - def other_non_current_liabilities(self) -> builtins.str: ... + def other_non_current_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def total_non_current_liabilities(self) -> builtins.str: ... + def total_non_current_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def other_liabilities(self) -> builtins.str: ... + def other_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def capital_lease_obligations(self) -> builtins.str: ... + def capital_lease_obligations(self) -> typing.Optional[builtins.str]: ... @property - def total_liabilities(self) -> builtins.str: ... + def total_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def treasury_stock(self) -> builtins.str: ... + def treasury_stock(self) -> typing.Optional[builtins.str]: ... @property - def preferred_stock(self) -> builtins.str: ... + def preferred_stock(self) -> typing.Optional[builtins.str]: ... @property - def common_stock(self) -> builtins.str: ... + def common_stock(self) -> typing.Optional[builtins.str]: ... @property - def retained_earnings(self) -> builtins.str: ... + def retained_earnings(self) -> typing.Optional[builtins.str]: ... @property - def additional_paid_in_capital(self) -> builtins.str: ... + def additional_paid_in_capital(self) -> typing.Optional[builtins.str]: ... @property - def accumulated_other_comprehensive_income_loss(self) -> builtins.str: ... + def accumulated_other_comprehensive_income_loss( + self, + ) -> typing.Optional[builtins.str]: ... @property - def other_total_stockholders_equity(self) -> builtins.str: ... + def other_total_stockholders_equity(self) -> typing.Optional[builtins.str]: ... @property - def total_stockholders_equity(self) -> builtins.str: ... + def total_stockholders_equity(self) -> typing.Optional[builtins.str]: ... @property - def total_equity(self) -> builtins.str: ... + def total_equity(self) -> typing.Optional[builtins.str]: ... @property - def minority_interest(self) -> builtins.str: ... + def minority_interest(self) -> typing.Optional[builtins.str]: ... @property - def total_liabilities_and_total_equity(self) -> builtins.str: ... + def total_liabilities_and_total_equity(self) -> typing.Optional[builtins.str]: ... @property - def total_investments(self) -> builtins.str: ... + def total_investments(self) -> typing.Optional[builtins.str]: ... @property - def total_debt(self) -> builtins.str: ... + def total_debt(self) -> typing.Optional[builtins.str]: ... @property - def net_debt(self) -> builtins.str: ... + def net_debt(self) -> typing.Optional[builtins.str]: ... @property def period(self) -> typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... @@ -211,59 +215,59 @@ class BulkBalanceSheetStatement: accepted_date: datetime.datetime, fiscal_year: builtins.str, period: typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"], - cash_and_cash_equivalents: builtins.str, - short_term_investments: builtins.str, - cash_and_short_term_investments: builtins.str, - net_receivables: builtins.str, - accounts_receivables: builtins.str, - other_receivables: builtins.str, - inventory: builtins.str, - prepaids: builtins.str, - other_current_assets: builtins.str, - total_current_assets: builtins.str, - property_plant_equipment_net: builtins.str, - goodwill: builtins.str, - intangible_assets: builtins.str, - goodwill_and_intangible_assets: builtins.str, - long_term_investments: builtins.str, - tax_assets: builtins.str, - other_non_current_assets: builtins.str, - total_non_current_assets: builtins.str, - other_assets: builtins.str, - total_assets: builtins.str, - total_payables: builtins.str, - account_payables: builtins.str, - other_payables: builtins.str, - accrued_expenses: builtins.str, - short_term_debt: builtins.str, - capital_lease_obligations_current: builtins.str, - tax_payables: builtins.str, - deferred_revenue: builtins.str, - other_current_liabilities: builtins.str, - total_current_liabilities: builtins.str, - long_term_debt: builtins.str, - capital_lease_obligations_non_current: builtins.str, - deferred_revenue_non_current: builtins.str, - deferred_tax_liabilities_non_current: builtins.str, - other_non_current_liabilities: builtins.str, - total_non_current_liabilities: builtins.str, - other_liabilities: builtins.str, - capital_lease_obligations: builtins.str, - total_liabilities: builtins.str, - treasury_stock: builtins.str, - preferred_stock: builtins.str, - common_stock: builtins.str, - retained_earnings: builtins.str, - additional_paid_in_capital: builtins.str, - accumulated_other_comprehensive_income_loss: builtins.str, - other_total_stockholders_equity: builtins.str, - total_stockholders_equity: builtins.str, - total_equity: builtins.str, - minority_interest: builtins.str, - total_liabilities_and_total_equity: builtins.str, - total_investments: builtins.str, - total_debt: builtins.str, - net_debt: builtins.str, + cash_and_cash_equivalents: typing.Optional[builtins.str], + short_term_investments: typing.Optional[builtins.str], + cash_and_short_term_investments: typing.Optional[builtins.str], + net_receivables: typing.Optional[builtins.str], + accounts_receivables: typing.Optional[builtins.str], + other_receivables: typing.Optional[builtins.str], + inventory: typing.Optional[builtins.str], + prepaids: typing.Optional[builtins.str], + other_current_assets: typing.Optional[builtins.str], + total_current_assets: typing.Optional[builtins.str], + property_plant_equipment_net: typing.Optional[builtins.str], + goodwill: typing.Optional[builtins.str], + intangible_assets: typing.Optional[builtins.str], + goodwill_and_intangible_assets: typing.Optional[builtins.str], + long_term_investments: typing.Optional[builtins.str], + tax_assets: typing.Optional[builtins.str], + other_non_current_assets: typing.Optional[builtins.str], + total_non_current_assets: typing.Optional[builtins.str], + other_assets: typing.Optional[builtins.str], + total_assets: typing.Optional[builtins.str], + total_payables: typing.Optional[builtins.str], + account_payables: typing.Optional[builtins.str], + other_payables: typing.Optional[builtins.str], + accrued_expenses: typing.Optional[builtins.str], + short_term_debt: typing.Optional[builtins.str], + capital_lease_obligations_current: typing.Optional[builtins.str], + tax_payables: typing.Optional[builtins.str], + deferred_revenue: typing.Optional[builtins.str], + other_current_liabilities: typing.Optional[builtins.str], + total_current_liabilities: typing.Optional[builtins.str], + long_term_debt: typing.Optional[builtins.str], + capital_lease_obligations_non_current: typing.Optional[builtins.str], + deferred_revenue_non_current: typing.Optional[builtins.str], + deferred_tax_liabilities_non_current: typing.Optional[builtins.str], + other_non_current_liabilities: typing.Optional[builtins.str], + total_non_current_liabilities: typing.Optional[builtins.str], + other_liabilities: typing.Optional[builtins.str], + capital_lease_obligations: typing.Optional[builtins.str], + total_liabilities: typing.Optional[builtins.str], + treasury_stock: typing.Optional[builtins.str], + preferred_stock: typing.Optional[builtins.str], + common_stock: typing.Optional[builtins.str], + retained_earnings: typing.Optional[builtins.str], + additional_paid_in_capital: typing.Optional[builtins.str], + accumulated_other_comprehensive_income_loss: typing.Optional[builtins.str], + other_total_stockholders_equity: typing.Optional[builtins.str], + total_stockholders_equity: typing.Optional[builtins.str], + total_equity: typing.Optional[builtins.str], + minority_interest: typing.Optional[builtins.str], + total_liabilities_and_total_equity: typing.Optional[builtins.str], + total_investments: typing.Optional[builtins.str], + total_debt: typing.Optional[builtins.str], + net_debt: typing.Optional[builtins.str], ) -> BulkBalanceSheetStatement: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... @@ -345,107 +349,121 @@ class BulkBalanceSheetStatementGrowth: @property def reported_currency(self) -> builtins.str: ... @property - def growth_cash_and_cash_equivalents(self) -> builtins.str: ... + def growth_cash_and_cash_equivalents(self) -> typing.Optional[builtins.str]: ... @property - def growth_short_term_investments(self) -> builtins.str: ... + def growth_short_term_investments(self) -> typing.Optional[builtins.str]: ... @property - def growth_cash_and_short_term_investments(self) -> builtins.str: ... + def growth_cash_and_short_term_investments( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_net_receivables(self) -> builtins.str: ... + def growth_net_receivables(self) -> typing.Optional[builtins.str]: ... @property - def growth_inventory(self) -> builtins.str: ... + def growth_inventory(self) -> typing.Optional[builtins.str]: ... @property - def growth_other_current_assets(self) -> builtins.str: ... + def growth_other_current_assets(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_current_assets(self) -> builtins.str: ... + def growth_total_current_assets(self) -> typing.Optional[builtins.str]: ... @property - def growth_property_plant_equipment_net(self) -> builtins.str: ... + def growth_property_plant_equipment_net(self) -> typing.Optional[builtins.str]: ... @property - def growth_goodwill(self) -> builtins.str: ... + def growth_goodwill(self) -> typing.Optional[builtins.str]: ... @property - def growth_intangible_assets(self) -> builtins.str: ... + def growth_intangible_assets(self) -> typing.Optional[builtins.str]: ... @property - def growth_goodwill_and_intangible_assets(self) -> builtins.str: ... + def growth_goodwill_and_intangible_assets( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_long_term_investments(self) -> builtins.str: ... + def growth_long_term_investments(self) -> typing.Optional[builtins.str]: ... @property - def growth_tax_assets(self) -> builtins.str: ... + def growth_tax_assets(self) -> typing.Optional[builtins.str]: ... @property - def growth_other_non_current_assets(self) -> builtins.str: ... + def growth_other_non_current_assets(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_non_current_assets(self) -> builtins.str: ... + def growth_total_non_current_assets(self) -> typing.Optional[builtins.str]: ... @property - def growth_other_assets(self) -> builtins.str: ... + def growth_other_assets(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_assets(self) -> builtins.str: ... + def growth_total_assets(self) -> typing.Optional[builtins.str]: ... @property - def growth_account_payables(self) -> builtins.str: ... + def growth_account_payables(self) -> typing.Optional[builtins.str]: ... @property - def growth_short_term_debt(self) -> builtins.str: ... + def growth_short_term_debt(self) -> typing.Optional[builtins.str]: ... @property - def growth_tax_payables(self) -> builtins.str: ... + def growth_tax_payables(self) -> typing.Optional[builtins.str]: ... @property - def growth_deferred_revenue(self) -> builtins.str: ... + def growth_deferred_revenue(self) -> typing.Optional[builtins.str]: ... @property - def growth_other_current_liabilities(self) -> builtins.str: ... + def growth_other_current_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_current_liabilities(self) -> builtins.str: ... + def growth_total_current_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def growth_long_term_debt(self) -> builtins.str: ... + def growth_long_term_debt(self) -> typing.Optional[builtins.str]: ... @property - def growth_deferred_revenue_non_current(self) -> builtins.str: ... + def growth_deferred_revenue_non_current(self) -> typing.Optional[builtins.str]: ... @property - def growth_deferred_tax_liabilities_non_current(self) -> builtins.str: ... + def growth_deferred_tax_liabilities_non_current( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_other_non_current_liabilities(self) -> builtins.str: ... + def growth_other_non_current_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_non_current_liabilities(self) -> builtins.str: ... + def growth_total_non_current_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def growth_other_liabilities(self) -> builtins.str: ... + def growth_other_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_liabilities(self) -> builtins.str: ... + def growth_total_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def growth_preferred_stock(self) -> builtins.str: ... + def growth_preferred_stock(self) -> typing.Optional[builtins.str]: ... @property - def growth_common_stock(self) -> builtins.str: ... + def growth_common_stock(self) -> typing.Optional[builtins.str]: ... @property - def growth_retained_earnings(self) -> builtins.str: ... + def growth_retained_earnings(self) -> typing.Optional[builtins.str]: ... @property - def growth_accumulated_other_comprehensive_income_loss(self) -> builtins.str: ... + def growth_accumulated_other_comprehensive_income_loss( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_other_total_stockholders_equity(self) -> builtins.str: ... + def growth_other_total_stockholders_equity( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_total_stockholders_equity(self) -> builtins.str: ... + def growth_total_stockholders_equity(self) -> typing.Optional[builtins.str]: ... @property - def growth_minority_interest(self) -> builtins.str: ... + def growth_minority_interest(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_equity(self) -> builtins.str: ... + def growth_total_equity(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_liabilities_and_stockholders_equity(self) -> builtins.str: ... + def growth_total_liabilities_and_stockholders_equity( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_total_investments(self) -> builtins.str: ... + def growth_total_investments(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_debt(self) -> builtins.str: ... + def growth_total_debt(self) -> typing.Optional[builtins.str]: ... @property - def growth_net_debt(self) -> builtins.str: ... + def growth_net_debt(self) -> typing.Optional[builtins.str]: ... @property - def growth_accounts_receivables(self) -> builtins.str: ... + def growth_accounts_receivables(self) -> typing.Optional[builtins.str]: ... @property - def growth_other_receivables(self) -> builtins.str: ... + def growth_other_receivables(self) -> typing.Optional[builtins.str]: ... @property - def growth_prepaids(self) -> builtins.str: ... + def growth_prepaids(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_payables(self) -> builtins.str: ... + def growth_total_payables(self) -> typing.Optional[builtins.str]: ... @property - def growth_other_payables(self) -> builtins.str: ... + def growth_other_payables(self) -> typing.Optional[builtins.str]: ... @property - def growth_accrued_expenses(self) -> builtins.str: ... + def growth_accrued_expenses(self) -> typing.Optional[builtins.str]: ... @property - def growth_capital_lease_obligations_current(self) -> builtins.str: ... + def growth_capital_lease_obligations_current( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_additional_paid_in_capital(self) -> builtins.str: ... + def growth_additional_paid_in_capital(self) -> typing.Optional[builtins.str]: ... @property - def growth_treasury_stock(self) -> builtins.str: ... + def growth_treasury_stock(self) -> typing.Optional[builtins.str]: ... @property def period(self) -> typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... @@ -457,57 +475,59 @@ class BulkBalanceSheetStatementGrowth: fiscal_year: builtins.str, period: typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"], reported_currency: builtins.str, - growth_cash_and_cash_equivalents: builtins.str, - growth_short_term_investments: builtins.str, - growth_cash_and_short_term_investments: builtins.str, - growth_net_receivables: builtins.str, - growth_inventory: builtins.str, - growth_other_current_assets: builtins.str, - growth_total_current_assets: builtins.str, - growth_property_plant_equipment_net: builtins.str, - growth_goodwill: builtins.str, - growth_intangible_assets: builtins.str, - growth_goodwill_and_intangible_assets: builtins.str, - growth_long_term_investments: builtins.str, - growth_tax_assets: builtins.str, - growth_other_non_current_assets: builtins.str, - growth_total_non_current_assets: builtins.str, - growth_other_assets: builtins.str, - growth_total_assets: builtins.str, - growth_account_payables: builtins.str, - growth_short_term_debt: builtins.str, - growth_tax_payables: builtins.str, - growth_deferred_revenue: builtins.str, - growth_other_current_liabilities: builtins.str, - growth_total_current_liabilities: builtins.str, - growth_long_term_debt: builtins.str, - growth_deferred_revenue_non_current: builtins.str, - growth_deferred_tax_liabilities_non_current: builtins.str, - growth_other_non_current_liabilities: builtins.str, - growth_total_non_current_liabilities: builtins.str, - growth_other_liabilities: builtins.str, - growth_total_liabilities: builtins.str, - growth_preferred_stock: builtins.str, - growth_common_stock: builtins.str, - growth_retained_earnings: builtins.str, - growth_accumulated_other_comprehensive_income_loss: builtins.str, - growth_other_total_stockholders_equity: builtins.str, - growth_total_stockholders_equity: builtins.str, - growth_minority_interest: builtins.str, - growth_total_equity: builtins.str, - growth_total_liabilities_and_stockholders_equity: builtins.str, - growth_total_investments: builtins.str, - growth_total_debt: builtins.str, - growth_net_debt: builtins.str, - growth_accounts_receivables: builtins.str, - growth_other_receivables: builtins.str, - growth_prepaids: builtins.str, - growth_total_payables: builtins.str, - growth_other_payables: builtins.str, - growth_accrued_expenses: builtins.str, - growth_capital_lease_obligations_current: builtins.str, - growth_additional_paid_in_capital: builtins.str, - growth_treasury_stock: builtins.str, + growth_cash_and_cash_equivalents: typing.Optional[builtins.str], + growth_short_term_investments: typing.Optional[builtins.str], + growth_cash_and_short_term_investments: typing.Optional[builtins.str], + growth_net_receivables: typing.Optional[builtins.str], + growth_inventory: typing.Optional[builtins.str], + growth_other_current_assets: typing.Optional[builtins.str], + growth_total_current_assets: typing.Optional[builtins.str], + growth_property_plant_equipment_net: typing.Optional[builtins.str], + growth_goodwill: typing.Optional[builtins.str], + growth_intangible_assets: typing.Optional[builtins.str], + growth_goodwill_and_intangible_assets: typing.Optional[builtins.str], + growth_long_term_investments: typing.Optional[builtins.str], + growth_tax_assets: typing.Optional[builtins.str], + growth_other_non_current_assets: typing.Optional[builtins.str], + growth_total_non_current_assets: typing.Optional[builtins.str], + growth_other_assets: typing.Optional[builtins.str], + growth_total_assets: typing.Optional[builtins.str], + growth_account_payables: typing.Optional[builtins.str], + growth_short_term_debt: typing.Optional[builtins.str], + growth_tax_payables: typing.Optional[builtins.str], + growth_deferred_revenue: typing.Optional[builtins.str], + growth_other_current_liabilities: typing.Optional[builtins.str], + growth_total_current_liabilities: typing.Optional[builtins.str], + growth_long_term_debt: typing.Optional[builtins.str], + growth_deferred_revenue_non_current: typing.Optional[builtins.str], + growth_deferred_tax_liabilities_non_current: typing.Optional[builtins.str], + growth_other_non_current_liabilities: typing.Optional[builtins.str], + growth_total_non_current_liabilities: typing.Optional[builtins.str], + growth_other_liabilities: typing.Optional[builtins.str], + growth_total_liabilities: typing.Optional[builtins.str], + growth_preferred_stock: typing.Optional[builtins.str], + growth_common_stock: typing.Optional[builtins.str], + growth_retained_earnings: typing.Optional[builtins.str], + growth_accumulated_other_comprehensive_income_loss: typing.Optional[ + builtins.str + ], + growth_other_total_stockholders_equity: typing.Optional[builtins.str], + growth_total_stockholders_equity: typing.Optional[builtins.str], + growth_minority_interest: typing.Optional[builtins.str], + growth_total_equity: typing.Optional[builtins.str], + growth_total_liabilities_and_stockholders_equity: typing.Optional[builtins.str], + growth_total_investments: typing.Optional[builtins.str], + growth_total_debt: typing.Optional[builtins.str], + growth_net_debt: typing.Optional[builtins.str], + growth_accounts_receivables: typing.Optional[builtins.str], + growth_other_receivables: typing.Optional[builtins.str], + growth_prepaids: typing.Optional[builtins.str], + growth_total_payables: typing.Optional[builtins.str], + growth_other_payables: typing.Optional[builtins.str], + growth_accrued_expenses: typing.Optional[builtins.str], + growth_capital_lease_obligations_current: typing.Optional[builtins.str], + growth_additional_paid_in_capital: typing.Optional[builtins.str], + growth_treasury_stock: typing.Optional[builtins.str], ) -> BulkBalanceSheetStatementGrowth: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... diff --git a/crates/fmp-py/python/fmp/_native/bulk/cash_flow/__init__.pyi b/crates/fmp-py/python/fmp/_native/bulk/cash_flow/__init__.pyi index 13cb0a56..a83ff1e1 100644 --- a/crates/fmp-py/python/fmp/_native/bulk/cash_flow/__init__.pyi +++ b/crates/fmp-py/python/fmp/_native/bulk/cash_flow/__init__.pyi @@ -78,83 +78,91 @@ class BulkCashFlowStatement: @property def fiscal_year(self) -> builtins.str: ... @property - def net_income(self) -> builtins.str: ... + def net_income(self) -> typing.Optional[builtins.str]: ... @property - def depreciation_and_amortization(self) -> builtins.str: ... + def depreciation_and_amortization(self) -> typing.Optional[builtins.str]: ... @property - def deferred_income_tax(self) -> builtins.str: ... + def deferred_income_tax(self) -> typing.Optional[builtins.str]: ... @property - def stock_based_compensation(self) -> builtins.str: ... + def stock_based_compensation(self) -> typing.Optional[builtins.str]: ... @property - def change_in_working_capital(self) -> builtins.str: ... + def change_in_working_capital(self) -> typing.Optional[builtins.str]: ... @property - def accounts_receivables(self) -> builtins.str: ... + def accounts_receivables(self) -> typing.Optional[builtins.str]: ... @property - def inventory(self) -> builtins.str: ... + def inventory(self) -> typing.Optional[builtins.str]: ... @property - def accounts_payables(self) -> builtins.str: ... + def accounts_payables(self) -> typing.Optional[builtins.str]: ... @property - def other_working_capital(self) -> builtins.str: ... + def other_working_capital(self) -> typing.Optional[builtins.str]: ... @property - def other_non_cash_items(self) -> builtins.str: ... + def other_non_cash_items(self) -> typing.Optional[builtins.str]: ... @property - def net_cash_provided_by_operating_activities(self) -> builtins.str: ... + def net_cash_provided_by_operating_activities( + self, + ) -> typing.Optional[builtins.str]: ... @property - def investments_in_property_plant_and_equipment(self) -> builtins.str: ... + def investments_in_property_plant_and_equipment( + self, + ) -> typing.Optional[builtins.str]: ... @property - def acquisitions_net(self) -> builtins.str: ... + def acquisitions_net(self) -> typing.Optional[builtins.str]: ... @property - def purchases_of_investments(self) -> builtins.str: ... + def purchases_of_investments(self) -> typing.Optional[builtins.str]: ... @property - def sales_maturities_of_investments(self) -> builtins.str: ... + def sales_maturities_of_investments(self) -> typing.Optional[builtins.str]: ... @property - def other_investing_activities(self) -> builtins.str: ... + def other_investing_activities(self) -> typing.Optional[builtins.str]: ... @property - def net_cash_provided_by_investing_activities(self) -> builtins.str: ... + def net_cash_provided_by_investing_activities( + self, + ) -> typing.Optional[builtins.str]: ... @property - def net_debt_issuance(self) -> builtins.str: ... + def net_debt_issuance(self) -> typing.Optional[builtins.str]: ... @property - def long_term_net_debt_issuance(self) -> builtins.str: ... + def long_term_net_debt_issuance(self) -> typing.Optional[builtins.str]: ... @property - def short_term_net_debt_issuance(self) -> builtins.str: ... + def short_term_net_debt_issuance(self) -> typing.Optional[builtins.str]: ... @property - def net_stock_issuance(self) -> builtins.str: ... + def net_stock_issuance(self) -> typing.Optional[builtins.str]: ... @property - def net_common_stock_issuance(self) -> builtins.str: ... + def net_common_stock_issuance(self) -> typing.Optional[builtins.str]: ... @property - def common_stock_issuance(self) -> builtins.str: ... + def common_stock_issuance(self) -> typing.Optional[builtins.str]: ... @property - def common_stock_repurchased(self) -> builtins.str: ... + def common_stock_repurchased(self) -> typing.Optional[builtins.str]: ... @property - def net_preferred_stock_issuance(self) -> builtins.str: ... + def net_preferred_stock_issuance(self) -> typing.Optional[builtins.str]: ... @property - def net_dividends_paid(self) -> builtins.str: ... + def net_dividends_paid(self) -> typing.Optional[builtins.str]: ... @property - def common_dividends_paid(self) -> builtins.str: ... + def common_dividends_paid(self) -> typing.Optional[builtins.str]: ... @property - def preferred_dividends_paid(self) -> builtins.str: ... + def preferred_dividends_paid(self) -> typing.Optional[builtins.str]: ... @property - def other_financing_activities(self) -> builtins.str: ... + def other_financing_activities(self) -> typing.Optional[builtins.str]: ... @property - def net_cash_provided_by_financing_activities(self) -> builtins.str: ... + def net_cash_provided_by_financing_activities( + self, + ) -> typing.Optional[builtins.str]: ... @property - def effect_of_forex_changes_on_cash(self) -> builtins.str: ... + def effect_of_forex_changes_on_cash(self) -> typing.Optional[builtins.str]: ... @property - def net_change_in_cash(self) -> builtins.str: ... + def net_change_in_cash(self) -> typing.Optional[builtins.str]: ... @property - def cash_at_end_of_period(self) -> builtins.str: ... + def cash_at_end_of_period(self) -> typing.Optional[builtins.str]: ... @property - def cash_at_beginning_of_period(self) -> builtins.str: ... + def cash_at_beginning_of_period(self) -> typing.Optional[builtins.str]: ... @property - def operating_cash_flow(self) -> builtins.str: ... + def operating_cash_flow(self) -> typing.Optional[builtins.str]: ... @property - def capital_expenditure(self) -> builtins.str: ... + def capital_expenditure(self) -> typing.Optional[builtins.str]: ... @property - def free_cash_flow(self) -> builtins.str: ... + def free_cash_flow(self) -> typing.Optional[builtins.str]: ... @property - def income_taxes_paid(self) -> builtins.str: ... + def income_taxes_paid(self) -> typing.Optional[builtins.str]: ... @property - def interest_paid(self) -> builtins.str: ... + def interest_paid(self) -> typing.Optional[builtins.str]: ... @property def period(self) -> typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... @@ -169,45 +177,45 @@ class BulkCashFlowStatement: accepted_date: datetime.datetime, fiscal_year: builtins.str, period: typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"], - net_income: builtins.str, - depreciation_and_amortization: builtins.str, - deferred_income_tax: builtins.str, - stock_based_compensation: builtins.str, - change_in_working_capital: builtins.str, - accounts_receivables: builtins.str, - inventory: builtins.str, - accounts_payables: builtins.str, - other_working_capital: builtins.str, - other_non_cash_items: builtins.str, - net_cash_provided_by_operating_activities: builtins.str, - investments_in_property_plant_and_equipment: builtins.str, - acquisitions_net: builtins.str, - purchases_of_investments: builtins.str, - sales_maturities_of_investments: builtins.str, - other_investing_activities: builtins.str, - net_cash_provided_by_investing_activities: builtins.str, - net_debt_issuance: builtins.str, - long_term_net_debt_issuance: builtins.str, - short_term_net_debt_issuance: builtins.str, - net_stock_issuance: builtins.str, - net_common_stock_issuance: builtins.str, - common_stock_issuance: builtins.str, - common_stock_repurchased: builtins.str, - net_preferred_stock_issuance: builtins.str, - net_dividends_paid: builtins.str, - common_dividends_paid: builtins.str, - preferred_dividends_paid: builtins.str, - other_financing_activities: builtins.str, - net_cash_provided_by_financing_activities: builtins.str, - effect_of_forex_changes_on_cash: builtins.str, - net_change_in_cash: builtins.str, - cash_at_end_of_period: builtins.str, - cash_at_beginning_of_period: builtins.str, - operating_cash_flow: builtins.str, - capital_expenditure: builtins.str, - free_cash_flow: builtins.str, - income_taxes_paid: builtins.str, - interest_paid: builtins.str, + net_income: typing.Optional[builtins.str], + depreciation_and_amortization: typing.Optional[builtins.str], + deferred_income_tax: typing.Optional[builtins.str], + stock_based_compensation: typing.Optional[builtins.str], + change_in_working_capital: typing.Optional[builtins.str], + accounts_receivables: typing.Optional[builtins.str], + inventory: typing.Optional[builtins.str], + accounts_payables: typing.Optional[builtins.str], + other_working_capital: typing.Optional[builtins.str], + other_non_cash_items: typing.Optional[builtins.str], + net_cash_provided_by_operating_activities: typing.Optional[builtins.str], + investments_in_property_plant_and_equipment: typing.Optional[builtins.str], + acquisitions_net: typing.Optional[builtins.str], + purchases_of_investments: typing.Optional[builtins.str], + sales_maturities_of_investments: typing.Optional[builtins.str], + other_investing_activities: typing.Optional[builtins.str], + net_cash_provided_by_investing_activities: typing.Optional[builtins.str], + net_debt_issuance: typing.Optional[builtins.str], + long_term_net_debt_issuance: typing.Optional[builtins.str], + short_term_net_debt_issuance: typing.Optional[builtins.str], + net_stock_issuance: typing.Optional[builtins.str], + net_common_stock_issuance: typing.Optional[builtins.str], + common_stock_issuance: typing.Optional[builtins.str], + common_stock_repurchased: typing.Optional[builtins.str], + net_preferred_stock_issuance: typing.Optional[builtins.str], + net_dividends_paid: typing.Optional[builtins.str], + common_dividends_paid: typing.Optional[builtins.str], + preferred_dividends_paid: typing.Optional[builtins.str], + other_financing_activities: typing.Optional[builtins.str], + net_cash_provided_by_financing_activities: typing.Optional[builtins.str], + effect_of_forex_changes_on_cash: typing.Optional[builtins.str], + net_change_in_cash: typing.Optional[builtins.str], + cash_at_end_of_period: typing.Optional[builtins.str], + cash_at_beginning_of_period: typing.Optional[builtins.str], + operating_cash_flow: typing.Optional[builtins.str], + capital_expenditure: typing.Optional[builtins.str], + free_cash_flow: typing.Optional[builtins.str], + income_taxes_paid: typing.Optional[builtins.str], + interest_paid: typing.Optional[builtins.str], ) -> BulkCashFlowStatement: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... @@ -275,79 +283,91 @@ class BulkCashFlowStatementGrowth: @property def reported_currency(self) -> builtins.str: ... @property - def growth_net_income(self) -> builtins.str: ... + def growth_net_income(self) -> typing.Optional[builtins.str]: ... @property - def growth_depreciation_and_amortization(self) -> builtins.str: ... + def growth_depreciation_and_amortization(self) -> typing.Optional[builtins.str]: ... @property - def growth_deferred_income_tax(self) -> builtins.str: ... + def growth_deferred_income_tax(self) -> typing.Optional[builtins.str]: ... @property - def growth_stock_based_compensation(self) -> builtins.str: ... + def growth_stock_based_compensation(self) -> typing.Optional[builtins.str]: ... @property - def growth_change_in_working_capital(self) -> builtins.str: ... + def growth_change_in_working_capital(self) -> typing.Optional[builtins.str]: ... @property - def growth_accounts_receivables(self) -> builtins.str: ... + def growth_accounts_receivables(self) -> typing.Optional[builtins.str]: ... @property - def growth_inventory(self) -> builtins.str: ... + def growth_inventory(self) -> typing.Optional[builtins.str]: ... @property - def growth_accounts_payables(self) -> builtins.str: ... + def growth_accounts_payables(self) -> typing.Optional[builtins.str]: ... @property - def growth_other_working_capital(self) -> builtins.str: ... + def growth_other_working_capital(self) -> typing.Optional[builtins.str]: ... @property - def growth_other_non_cash_items(self) -> builtins.str: ... + def growth_other_non_cash_items(self) -> typing.Optional[builtins.str]: ... @property - def growth_net_cash_provided_by_operating_activities(self) -> builtins.str: ... + def growth_net_cash_provided_by_operating_activities( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_investments_in_property_plant_and_equipment(self) -> builtins.str: ... + def growth_investments_in_property_plant_and_equipment( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_acquisitions_net(self) -> builtins.str: ... + def growth_acquisitions_net(self) -> typing.Optional[builtins.str]: ... @property - def growth_purchases_of_investments(self) -> builtins.str: ... + def growth_purchases_of_investments(self) -> typing.Optional[builtins.str]: ... @property - def growth_sales_maturities_of_investments(self) -> builtins.str: ... + def growth_sales_maturities_of_investments( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_other_investing_activities(self) -> builtins.str: ... + def growth_other_investing_activities(self) -> typing.Optional[builtins.str]: ... @property - def growth_net_cash_used_for_investing_activities(self) -> builtins.str: ... + def growth_net_cash_used_for_investing_activities( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_debt_repayment(self) -> builtins.str: ... + def growth_debt_repayment(self) -> typing.Optional[builtins.str]: ... @property - def growth_common_stock_issued(self) -> builtins.str: ... + def growth_common_stock_issued(self) -> typing.Optional[builtins.str]: ... @property - def growth_common_stock_repurchased(self) -> builtins.str: ... + def growth_common_stock_repurchased(self) -> typing.Optional[builtins.str]: ... @property - def growth_dividends_paid(self) -> builtins.str: ... + def growth_dividends_paid(self) -> typing.Optional[builtins.str]: ... @property - def growth_other_financing_activities(self) -> builtins.str: ... + def growth_other_financing_activities(self) -> typing.Optional[builtins.str]: ... @property - def growth_net_cash_used_provided_by_financing_activities(self) -> builtins.str: ... + def growth_net_cash_used_provided_by_financing_activities( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_effect_of_forex_changes_on_cash(self) -> builtins.str: ... + def growth_effect_of_forex_changes_on_cash( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_net_change_in_cash(self) -> builtins.str: ... + def growth_net_change_in_cash(self) -> typing.Optional[builtins.str]: ... @property - def growth_cash_at_end_of_period(self) -> builtins.str: ... + def growth_cash_at_end_of_period(self) -> typing.Optional[builtins.str]: ... @property - def growth_cash_at_beginning_of_period(self) -> builtins.str: ... + def growth_cash_at_beginning_of_period(self) -> typing.Optional[builtins.str]: ... @property - def growth_operating_cash_flow(self) -> builtins.str: ... + def growth_operating_cash_flow(self) -> typing.Optional[builtins.str]: ... @property - def growth_capital_expenditure(self) -> builtins.str: ... + def growth_capital_expenditure(self) -> typing.Optional[builtins.str]: ... @property - def growth_free_cash_flow(self) -> builtins.str: ... + def growth_free_cash_flow(self) -> typing.Optional[builtins.str]: ... @property - def growth_net_debt_issuance(self) -> builtins.str: ... + def growth_net_debt_issuance(self) -> typing.Optional[builtins.str]: ... @property - def growth_long_term_net_debt_issuance(self) -> builtins.str: ... + def growth_long_term_net_debt_issuance(self) -> typing.Optional[builtins.str]: ... @property - def growth_short_term_net_debt_issuance(self) -> builtins.str: ... + def growth_short_term_net_debt_issuance(self) -> typing.Optional[builtins.str]: ... @property - def growth_net_stock_issuance(self) -> builtins.str: ... + def growth_net_stock_issuance(self) -> typing.Optional[builtins.str]: ... @property - def growth_preferred_dividends_paid(self) -> builtins.str: ... + def growth_preferred_dividends_paid(self) -> typing.Optional[builtins.str]: ... @property - def growth_income_taxes_paid(self) -> builtins.str: ... + def growth_income_taxes_paid(self) -> typing.Optional[builtins.str]: ... @property - def growth_interest_paid(self) -> builtins.str: ... + def growth_interest_paid(self) -> typing.Optional[builtins.str]: ... @property def period(self) -> typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... @@ -359,43 +379,47 @@ class BulkCashFlowStatementGrowth: fiscal_year: builtins.str, period: typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"], reported_currency: builtins.str, - growth_net_income: builtins.str, - growth_depreciation_and_amortization: builtins.str, - growth_deferred_income_tax: builtins.str, - growth_stock_based_compensation: builtins.str, - growth_change_in_working_capital: builtins.str, - growth_accounts_receivables: builtins.str, - growth_inventory: builtins.str, - growth_accounts_payables: builtins.str, - growth_other_working_capital: builtins.str, - growth_other_non_cash_items: builtins.str, - growth_net_cash_provided_by_operating_activities: builtins.str, - growth_investments_in_property_plant_and_equipment: builtins.str, - growth_acquisitions_net: builtins.str, - growth_purchases_of_investments: builtins.str, - growth_sales_maturities_of_investments: builtins.str, - growth_other_investing_activities: builtins.str, - growth_net_cash_used_for_investing_activities: builtins.str, - growth_debt_repayment: builtins.str, - growth_common_stock_issued: builtins.str, - growth_common_stock_repurchased: builtins.str, - growth_dividends_paid: builtins.str, - growth_other_financing_activities: builtins.str, - growth_net_cash_used_provided_by_financing_activities: builtins.str, - growth_effect_of_forex_changes_on_cash: builtins.str, - growth_net_change_in_cash: builtins.str, - growth_cash_at_end_of_period: builtins.str, - growth_cash_at_beginning_of_period: builtins.str, - growth_operating_cash_flow: builtins.str, - growth_capital_expenditure: builtins.str, - growth_free_cash_flow: builtins.str, - growth_net_debt_issuance: builtins.str, - growth_long_term_net_debt_issuance: builtins.str, - growth_short_term_net_debt_issuance: builtins.str, - growth_net_stock_issuance: builtins.str, - growth_preferred_dividends_paid: builtins.str, - growth_income_taxes_paid: builtins.str, - growth_interest_paid: builtins.str, + growth_net_income: typing.Optional[builtins.str], + growth_depreciation_and_amortization: typing.Optional[builtins.str], + growth_deferred_income_tax: typing.Optional[builtins.str], + growth_stock_based_compensation: typing.Optional[builtins.str], + growth_change_in_working_capital: typing.Optional[builtins.str], + growth_accounts_receivables: typing.Optional[builtins.str], + growth_inventory: typing.Optional[builtins.str], + growth_accounts_payables: typing.Optional[builtins.str], + growth_other_working_capital: typing.Optional[builtins.str], + growth_other_non_cash_items: typing.Optional[builtins.str], + growth_net_cash_provided_by_operating_activities: typing.Optional[builtins.str], + growth_investments_in_property_plant_and_equipment: typing.Optional[ + builtins.str + ], + growth_acquisitions_net: typing.Optional[builtins.str], + growth_purchases_of_investments: typing.Optional[builtins.str], + growth_sales_maturities_of_investments: typing.Optional[builtins.str], + growth_other_investing_activities: typing.Optional[builtins.str], + growth_net_cash_used_for_investing_activities: typing.Optional[builtins.str], + growth_debt_repayment: typing.Optional[builtins.str], + growth_common_stock_issued: typing.Optional[builtins.str], + growth_common_stock_repurchased: typing.Optional[builtins.str], + growth_dividends_paid: typing.Optional[builtins.str], + growth_other_financing_activities: typing.Optional[builtins.str], + growth_net_cash_used_provided_by_financing_activities: typing.Optional[ + builtins.str + ], + growth_effect_of_forex_changes_on_cash: typing.Optional[builtins.str], + growth_net_change_in_cash: typing.Optional[builtins.str], + growth_cash_at_end_of_period: typing.Optional[builtins.str], + growth_cash_at_beginning_of_period: typing.Optional[builtins.str], + growth_operating_cash_flow: typing.Optional[builtins.str], + growth_capital_expenditure: typing.Optional[builtins.str], + growth_free_cash_flow: typing.Optional[builtins.str], + growth_net_debt_issuance: typing.Optional[builtins.str], + growth_long_term_net_debt_issuance: typing.Optional[builtins.str], + growth_short_term_net_debt_issuance: typing.Optional[builtins.str], + growth_net_stock_issuance: typing.Optional[builtins.str], + growth_preferred_dividends_paid: typing.Optional[builtins.str], + growth_income_taxes_paid: typing.Optional[builtins.str], + growth_interest_paid: typing.Optional[builtins.str], ) -> BulkCashFlowStatementGrowth: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... diff --git a/crates/fmp-py/python/fmp/_native/bulk/eod/__init__.pyi b/crates/fmp-py/python/fmp/_native/bulk/eod/__init__.pyi index 6aa72f7a..dfb9c7f4 100644 --- a/crates/fmp-py/python/fmp/_native/bulk/eod/__init__.pyi +++ b/crates/fmp-py/python/fmp/_native/bulk/eod/__init__.pyi @@ -28,29 +28,29 @@ class BulkEodBar: @property def date(self) -> datetime.date: ... @property - def open(self) -> builtins.str: ... + def open(self) -> typing.Optional[builtins.str]: ... @property - def low(self) -> builtins.str: ... + def low(self) -> typing.Optional[builtins.str]: ... @property - def high(self) -> builtins.str: ... + def high(self) -> typing.Optional[builtins.str]: ... @property - def close(self) -> builtins.str: ... + def close(self) -> typing.Optional[builtins.str]: ... @property - def adj_close(self) -> builtins.str: ... + def adj_close(self) -> typing.Optional[builtins.str]: ... @property - def volume(self) -> builtins.str: ... + def volume(self) -> typing.Optional[builtins.str]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... def __new__( cls, *, symbol: builtins.str, date: datetime.date, - open: builtins.str, - low: builtins.str, - high: builtins.str, - close: builtins.str, - adj_close: builtins.str, - volume: builtins.str, + open: typing.Optional[builtins.str], + low: typing.Optional[builtins.str], + high: typing.Optional[builtins.str], + close: typing.Optional[builtins.str], + adj_close: typing.Optional[builtins.str], + volume: typing.Optional[builtins.str], ) -> BulkEodBar: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... diff --git a/crates/fmp-py/python/fmp/_native/bulk/income/__init__.pyi b/crates/fmp-py/python/fmp/_native/bulk/income/__init__.pyi index bae6c9ef..b13e43a7 100644 --- a/crates/fmp-py/python/fmp/_native/bulk/income/__init__.pyi +++ b/crates/fmp-py/python/fmp/_native/bulk/income/__init__.pyi @@ -70,67 +70,75 @@ class BulkIncomeStatement: @property def fiscal_year(self) -> builtins.str: ... @property - def revenue(self) -> builtins.str: ... + def revenue(self) -> typing.Optional[builtins.str]: ... @property - def cost_of_revenue(self) -> builtins.str: ... + def cost_of_revenue(self) -> typing.Optional[builtins.str]: ... @property - def gross_profit(self) -> builtins.str: ... + def gross_profit(self) -> typing.Optional[builtins.str]: ... @property - def research_and_development_expenses(self) -> builtins.str: ... + def research_and_development_expenses(self) -> typing.Optional[builtins.str]: ... @property - def general_and_administrative_expenses(self) -> builtins.str: ... + def general_and_administrative_expenses(self) -> typing.Optional[builtins.str]: ... @property - def selling_and_marketing_expenses(self) -> builtins.str: ... + def selling_and_marketing_expenses(self) -> typing.Optional[builtins.str]: ... @property - def selling_general_and_administrative_expenses(self) -> builtins.str: ... + def selling_general_and_administrative_expenses( + self, + ) -> typing.Optional[builtins.str]: ... @property - def other_expenses(self) -> builtins.str: ... + def other_expenses(self) -> typing.Optional[builtins.str]: ... @property - def operating_expenses(self) -> builtins.str: ... + def operating_expenses(self) -> typing.Optional[builtins.str]: ... @property - def cost_and_expenses(self) -> builtins.str: ... + def cost_and_expenses(self) -> typing.Optional[builtins.str]: ... @property - def net_interest_income(self) -> builtins.str: ... + def net_interest_income(self) -> typing.Optional[builtins.str]: ... @property - def interest_income(self) -> builtins.str: ... + def interest_income(self) -> typing.Optional[builtins.str]: ... @property - def interest_expense(self) -> builtins.str: ... + def interest_expense(self) -> typing.Optional[builtins.str]: ... @property - def depreciation_and_amortization(self) -> builtins.str: ... + def depreciation_and_amortization(self) -> typing.Optional[builtins.str]: ... @property - def ebitda(self) -> builtins.str: ... + def ebitda(self) -> typing.Optional[builtins.str]: ... @property - def ebit(self) -> builtins.str: ... + def ebit(self) -> typing.Optional[builtins.str]: ... @property - def non_operating_income_excluding_interest(self) -> builtins.str: ... + def non_operating_income_excluding_interest( + self, + ) -> typing.Optional[builtins.str]: ... @property - def operating_income(self) -> builtins.str: ... + def operating_income(self) -> typing.Optional[builtins.str]: ... @property - def total_other_income_expenses_net(self) -> builtins.str: ... + def total_other_income_expenses_net(self) -> typing.Optional[builtins.str]: ... @property - def income_before_tax(self) -> builtins.str: ... + def income_before_tax(self) -> typing.Optional[builtins.str]: ... @property - def income_tax_expense(self) -> builtins.str: ... + def income_tax_expense(self) -> typing.Optional[builtins.str]: ... @property - def net_income_from_continuing_operations(self) -> builtins.str: ... + def net_income_from_continuing_operations( + self, + ) -> typing.Optional[builtins.str]: ... @property - def net_income_from_discontinued_operations(self) -> builtins.str: ... + def net_income_from_discontinued_operations( + self, + ) -> typing.Optional[builtins.str]: ... @property - def other_adjustments_to_net_income(self) -> builtins.str: ... + def other_adjustments_to_net_income(self) -> typing.Optional[builtins.str]: ... @property - def net_income(self) -> builtins.str: ... + def net_income(self) -> typing.Optional[builtins.str]: ... @property - def net_income_deductions(self) -> builtins.str: ... + def net_income_deductions(self) -> typing.Optional[builtins.str]: ... @property - def bottom_line_net_income(self) -> builtins.str: ... + def bottom_line_net_income(self) -> typing.Optional[builtins.str]: ... @property - def eps(self) -> builtins.str: ... + def eps(self) -> typing.Optional[builtins.str]: ... @property - def eps_diluted(self) -> builtins.str: ... + def eps_diluted(self) -> typing.Optional[builtins.str]: ... @property - def weighted_average_shs_out(self) -> builtins.str: ... + def weighted_average_shs_out(self) -> typing.Optional[builtins.str]: ... @property - def weighted_average_shs_out_dil(self) -> builtins.str: ... + def weighted_average_shs_out_dil(self) -> typing.Optional[builtins.str]: ... @property def period(self) -> typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... @@ -145,37 +153,37 @@ class BulkIncomeStatement: accepted_date: datetime.datetime, fiscal_year: builtins.str, period: typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"], - revenue: builtins.str, - cost_of_revenue: builtins.str, - gross_profit: builtins.str, - research_and_development_expenses: builtins.str, - general_and_administrative_expenses: builtins.str, - selling_and_marketing_expenses: builtins.str, - selling_general_and_administrative_expenses: builtins.str, - other_expenses: builtins.str, - operating_expenses: builtins.str, - cost_and_expenses: builtins.str, - net_interest_income: builtins.str, - interest_income: builtins.str, - interest_expense: builtins.str, - depreciation_and_amortization: builtins.str, - ebitda: builtins.str, - ebit: builtins.str, - non_operating_income_excluding_interest: builtins.str, - operating_income: builtins.str, - total_other_income_expenses_net: builtins.str, - income_before_tax: builtins.str, - income_tax_expense: builtins.str, - net_income_from_continuing_operations: builtins.str, - net_income_from_discontinued_operations: builtins.str, - other_adjustments_to_net_income: builtins.str, - net_income: builtins.str, - net_income_deductions: builtins.str, - bottom_line_net_income: builtins.str, - eps: builtins.str, - eps_diluted: builtins.str, - weighted_average_shs_out: builtins.str, - weighted_average_shs_out_dil: builtins.str, + revenue: typing.Optional[builtins.str], + cost_of_revenue: typing.Optional[builtins.str], + gross_profit: typing.Optional[builtins.str], + research_and_development_expenses: typing.Optional[builtins.str], + general_and_administrative_expenses: typing.Optional[builtins.str], + selling_and_marketing_expenses: typing.Optional[builtins.str], + selling_general_and_administrative_expenses: typing.Optional[builtins.str], + other_expenses: typing.Optional[builtins.str], + operating_expenses: typing.Optional[builtins.str], + cost_and_expenses: typing.Optional[builtins.str], + net_interest_income: typing.Optional[builtins.str], + interest_income: typing.Optional[builtins.str], + interest_expense: typing.Optional[builtins.str], + depreciation_and_amortization: typing.Optional[builtins.str], + ebitda: typing.Optional[builtins.str], + ebit: typing.Optional[builtins.str], + non_operating_income_excluding_interest: typing.Optional[builtins.str], + operating_income: typing.Optional[builtins.str], + total_other_income_expenses_net: typing.Optional[builtins.str], + income_before_tax: typing.Optional[builtins.str], + income_tax_expense: typing.Optional[builtins.str], + net_income_from_continuing_operations: typing.Optional[builtins.str], + net_income_from_discontinued_operations: typing.Optional[builtins.str], + other_adjustments_to_net_income: typing.Optional[builtins.str], + net_income: typing.Optional[builtins.str], + net_income_deductions: typing.Optional[builtins.str], + bottom_line_net_income: typing.Optional[builtins.str], + eps: typing.Optional[builtins.str], + eps_diluted: typing.Optional[builtins.str], + weighted_average_shs_out: typing.Optional[builtins.str], + weighted_average_shs_out_dil: typing.Optional[builtins.str], ) -> BulkIncomeStatement: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... @@ -235,63 +243,77 @@ class BulkIncomeStatementGrowth: @property def reported_currency(self) -> builtins.str: ... @property - def growth_revenue(self) -> builtins.str: ... + def growth_revenue(self) -> typing.Optional[builtins.str]: ... @property - def growth_cost_of_revenue(self) -> builtins.str: ... + def growth_cost_of_revenue(self) -> typing.Optional[builtins.str]: ... @property - def growth_gross_profit(self) -> builtins.str: ... + def growth_gross_profit(self) -> typing.Optional[builtins.str]: ... @property - def growth_gross_profit_ratio(self) -> builtins.str: ... + def growth_gross_profit_ratio(self) -> typing.Optional[builtins.str]: ... @property - def growth_research_and_development_expenses(self) -> builtins.str: ... + def growth_research_and_development_expenses( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_general_and_administrative_expenses(self) -> builtins.str: ... + def growth_general_and_administrative_expenses( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_selling_and_marketing_expenses(self) -> builtins.str: ... + def growth_selling_and_marketing_expenses( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_other_expenses(self) -> builtins.str: ... + def growth_other_expenses(self) -> typing.Optional[builtins.str]: ... @property - def growth_operating_expenses(self) -> builtins.str: ... + def growth_operating_expenses(self) -> typing.Optional[builtins.str]: ... @property - def growth_cost_and_expenses(self) -> builtins.str: ... + def growth_cost_and_expenses(self) -> typing.Optional[builtins.str]: ... @property - def growth_interest_income(self) -> builtins.str: ... + def growth_interest_income(self) -> typing.Optional[builtins.str]: ... @property - def growth_interest_expense(self) -> builtins.str: ... + def growth_interest_expense(self) -> typing.Optional[builtins.str]: ... @property - def growth_depreciation_and_amortization(self) -> builtins.str: ... + def growth_depreciation_and_amortization(self) -> typing.Optional[builtins.str]: ... @property - def growth_ebitda(self) -> builtins.str: ... + def growth_ebitda(self) -> typing.Optional[builtins.str]: ... @property - def growth_operating_income(self) -> builtins.str: ... + def growth_operating_income(self) -> typing.Optional[builtins.str]: ... @property - def growth_income_before_tax(self) -> builtins.str: ... + def growth_income_before_tax(self) -> typing.Optional[builtins.str]: ... @property - def growth_income_tax_expense(self) -> builtins.str: ... + def growth_income_tax_expense(self) -> typing.Optional[builtins.str]: ... @property - def growth_net_income(self) -> builtins.str: ... + def growth_net_income(self) -> typing.Optional[builtins.str]: ... @property - def growth_eps(self) -> builtins.str: ... + def growth_eps(self) -> typing.Optional[builtins.str]: ... @property - def growth_eps_diluted(self) -> builtins.str: ... + def growth_eps_diluted(self) -> typing.Optional[builtins.str]: ... @property - def growth_weighted_average_shs_out(self) -> builtins.str: ... + def growth_weighted_average_shs_out(self) -> typing.Optional[builtins.str]: ... @property - def growth_weighted_average_shs_out_dil(self) -> builtins.str: ... + def growth_weighted_average_shs_out_dil(self) -> typing.Optional[builtins.str]: ... @property - def growth_ebit(self) -> builtins.str: ... + def growth_ebit(self) -> typing.Optional[builtins.str]: ... @property - def growth_non_operating_income_excluding_interest(self) -> builtins.str: ... + def growth_non_operating_income_excluding_interest( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_net_interest_income(self) -> builtins.str: ... + def growth_net_interest_income(self) -> typing.Optional[builtins.str]: ... @property - def growth_total_other_income_expenses_net(self) -> builtins.str: ... + def growth_total_other_income_expenses_net( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_net_income_from_continuing_operations(self) -> builtins.str: ... + def growth_net_income_from_continuing_operations( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_other_adjustments_to_net_income(self) -> builtins.str: ... + def growth_other_adjustments_to_net_income( + self, + ) -> typing.Optional[builtins.str]: ... @property - def growth_net_income_deductions(self) -> builtins.str: ... + def growth_net_income_deductions(self) -> typing.Optional[builtins.str]: ... @property def period(self) -> typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... @@ -303,35 +325,35 @@ class BulkIncomeStatementGrowth: fiscal_year: builtins.str, period: typing.Literal["Q1", "Q2", "Q3", "Q4", "FY"], reported_currency: builtins.str, - growth_revenue: builtins.str, - growth_cost_of_revenue: builtins.str, - growth_gross_profit: builtins.str, - growth_gross_profit_ratio: builtins.str, - growth_research_and_development_expenses: builtins.str, - growth_general_and_administrative_expenses: builtins.str, - growth_selling_and_marketing_expenses: builtins.str, - growth_other_expenses: builtins.str, - growth_operating_expenses: builtins.str, - growth_cost_and_expenses: builtins.str, - growth_interest_income: builtins.str, - growth_interest_expense: builtins.str, - growth_depreciation_and_amortization: builtins.str, - growth_ebitda: builtins.str, - growth_operating_income: builtins.str, - growth_income_before_tax: builtins.str, - growth_income_tax_expense: builtins.str, - growth_net_income: builtins.str, - growth_eps: builtins.str, - growth_eps_diluted: builtins.str, - growth_weighted_average_shs_out: builtins.str, - growth_weighted_average_shs_out_dil: builtins.str, - growth_ebit: builtins.str, - growth_non_operating_income_excluding_interest: builtins.str, - growth_net_interest_income: builtins.str, - growth_total_other_income_expenses_net: builtins.str, - growth_net_income_from_continuing_operations: builtins.str, - growth_other_adjustments_to_net_income: builtins.str, - growth_net_income_deductions: builtins.str, + growth_revenue: typing.Optional[builtins.str], + growth_cost_of_revenue: typing.Optional[builtins.str], + growth_gross_profit: typing.Optional[builtins.str], + growth_gross_profit_ratio: typing.Optional[builtins.str], + growth_research_and_development_expenses: typing.Optional[builtins.str], + growth_general_and_administrative_expenses: typing.Optional[builtins.str], + growth_selling_and_marketing_expenses: typing.Optional[builtins.str], + growth_other_expenses: typing.Optional[builtins.str], + growth_operating_expenses: typing.Optional[builtins.str], + growth_cost_and_expenses: typing.Optional[builtins.str], + growth_interest_income: typing.Optional[builtins.str], + growth_interest_expense: typing.Optional[builtins.str], + growth_depreciation_and_amortization: typing.Optional[builtins.str], + growth_ebitda: typing.Optional[builtins.str], + growth_operating_income: typing.Optional[builtins.str], + growth_income_before_tax: typing.Optional[builtins.str], + growth_income_tax_expense: typing.Optional[builtins.str], + growth_net_income: typing.Optional[builtins.str], + growth_eps: typing.Optional[builtins.str], + growth_eps_diluted: typing.Optional[builtins.str], + growth_weighted_average_shs_out: typing.Optional[builtins.str], + growth_weighted_average_shs_out_dil: typing.Optional[builtins.str], + growth_ebit: typing.Optional[builtins.str], + growth_non_operating_income_excluding_interest: typing.Optional[builtins.str], + growth_net_interest_income: typing.Optional[builtins.str], + growth_total_other_income_expenses_net: typing.Optional[builtins.str], + growth_net_income_from_continuing_operations: typing.Optional[builtins.str], + growth_other_adjustments_to_net_income: typing.Optional[builtins.str], + growth_net_income_deductions: typing.Optional[builtins.str], ) -> BulkIncomeStatementGrowth: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... diff --git a/crates/fmp-py/python/fmp/_native/bulk/metrics/__init__.pyi b/crates/fmp-py/python/fmp/_native/bulk/metrics/__init__.pyi index e0b43f33..94841fc6 100644 --- a/crates/fmp-py/python/fmp/_native/bulk/metrics/__init__.pyi +++ b/crates/fmp-py/python/fmp/_native/bulk/metrics/__init__.pyi @@ -28,9 +28,9 @@ class BulkEarningsSurprise: @property def date(self) -> datetime.date: ... @property - def eps_actual(self) -> builtins.str: ... + def eps_actual(self) -> typing.Optional[builtins.str]: ... @property - def eps_estimated(self) -> builtins.str: ... + def eps_estimated(self) -> typing.Optional[builtins.str]: ... @property def last_updated(self) -> datetime.date: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... @@ -39,8 +39,8 @@ class BulkEarningsSurprise: *, symbol: builtins.str, date: datetime.date, - eps_actual: builtins.str, - eps_estimated: builtins.str, + eps_actual: typing.Optional[builtins.str], + eps_estimated: typing.Optional[builtins.str], last_updated: datetime.date, ) -> BulkEarningsSurprise: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... @@ -121,187 +121,205 @@ class BulkFinancialRatiosTtm: @property def symbol(self) -> builtins.str: ... @property - def gross_profit_margin_ttm(self) -> builtins.str: ... + def gross_profit_margin_ttm(self) -> typing.Optional[builtins.str]: ... @property - def ebit_margin_ttm(self) -> builtins.str: ... + def ebit_margin_ttm(self) -> typing.Optional[builtins.str]: ... @property - def ebitda_margin_ttm(self) -> builtins.str: ... + def ebitda_margin_ttm(self) -> typing.Optional[builtins.str]: ... @property - def operating_profit_margin_ttm(self) -> builtins.str: ... + def operating_profit_margin_ttm(self) -> typing.Optional[builtins.str]: ... @property - def pretax_profit_margin_ttm(self) -> builtins.str: ... + def pretax_profit_margin_ttm(self) -> typing.Optional[builtins.str]: ... @property - def continuous_operations_profit_margin_ttm(self) -> builtins.str: ... + def continuous_operations_profit_margin_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def net_profit_margin_ttm(self) -> builtins.str: ... + def net_profit_margin_ttm(self) -> typing.Optional[builtins.str]: ... @property - def bottom_line_profit_margin_ttm(self) -> builtins.str: ... + def bottom_line_profit_margin_ttm(self) -> typing.Optional[builtins.str]: ... @property - def receivables_turnover_ttm(self) -> builtins.str: ... + def receivables_turnover_ttm(self) -> typing.Optional[builtins.str]: ... @property - def payables_turnover_ttm(self) -> builtins.str: ... + def payables_turnover_ttm(self) -> typing.Optional[builtins.str]: ... @property - def inventory_turnover_ttm(self) -> builtins.str: ... + def inventory_turnover_ttm(self) -> typing.Optional[builtins.str]: ... @property - def fixed_asset_turnover_ttm(self) -> builtins.str: ... + def fixed_asset_turnover_ttm(self) -> typing.Optional[builtins.str]: ... @property - def asset_turnover_ttm(self) -> builtins.str: ... + def asset_turnover_ttm(self) -> typing.Optional[builtins.str]: ... @property - def current_ratio_ttm(self) -> builtins.str: ... + def current_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def quick_ratio_ttm(self) -> builtins.str: ... + def quick_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def solvency_ratio_ttm(self) -> builtins.str: ... + def solvency_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def cash_ratio_ttm(self) -> builtins.str: ... + def cash_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def price_to_earnings_ratio_ttm(self) -> builtins.str: ... + def price_to_earnings_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def price_to_earnings_growth_ratio_ttm(self) -> builtins.str: ... + def price_to_earnings_growth_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def forward_price_to_earnings_growth_ratio_ttm(self) -> builtins.str: ... + def forward_price_to_earnings_growth_ratio_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def price_to_book_ratio_ttm(self) -> builtins.str: ... + def price_to_book_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def price_to_sales_ratio_ttm(self) -> builtins.str: ... + def price_to_sales_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def price_to_free_cash_flow_ratio_ttm(self) -> builtins.str: ... + def price_to_free_cash_flow_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def price_to_operating_cash_flow_ratio_ttm(self) -> builtins.str: ... + def price_to_operating_cash_flow_ratio_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def debt_to_assets_ratio_ttm(self) -> builtins.str: ... + def debt_to_assets_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def debt_to_equity_ratio_ttm(self) -> builtins.str: ... + def debt_to_equity_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def debt_to_capital_ratio_ttm(self) -> builtins.str: ... + def debt_to_capital_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def long_term_debt_to_capital_ratio_ttm(self) -> builtins.str: ... + def long_term_debt_to_capital_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def financial_leverage_ratio_ttm(self) -> builtins.str: ... + def financial_leverage_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def working_capital_turnover_ratio_ttm(self) -> builtins.str: ... + def working_capital_turnover_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def operating_cash_flow_ratio_ttm(self) -> builtins.str: ... + def operating_cash_flow_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def operating_cash_flow_sales_ratio_ttm(self) -> builtins.str: ... + def operating_cash_flow_sales_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def free_cash_flow_operating_cash_flow_ratio_ttm(self) -> builtins.str: ... + def free_cash_flow_operating_cash_flow_ratio_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def debt_service_coverage_ratio_ttm(self) -> builtins.str: ... + def debt_service_coverage_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def interest_coverage_ratio_ttm(self) -> builtins.str: ... + def interest_coverage_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def short_term_operating_cash_flow_coverage_ratio_ttm(self) -> builtins.str: ... + def short_term_operating_cash_flow_coverage_ratio_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def operating_cash_flow_coverage_ratio_ttm(self) -> builtins.str: ... + def operating_cash_flow_coverage_ratio_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def capital_expenditure_coverage_ratio_ttm(self) -> builtins.str: ... + def capital_expenditure_coverage_ratio_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def dividend_paid_and_capex_coverage_ratio_ttm(self) -> builtins.str: ... + def dividend_paid_and_capex_coverage_ratio_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def dividend_payout_ratio_ttm(self) -> builtins.str: ... + def dividend_payout_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def dividend_yield_ttm(self) -> builtins.str: ... + def dividend_yield_ttm(self) -> typing.Optional[builtins.str]: ... @property - def enterprise_value_ttm(self) -> builtins.str: ... + def enterprise_value_ttm(self) -> typing.Optional[builtins.str]: ... @property - def revenue_per_share_ttm(self) -> builtins.str: ... + def revenue_per_share_ttm(self) -> typing.Optional[builtins.str]: ... @property - def net_income_per_share_ttm(self) -> builtins.str: ... + def net_income_per_share_ttm(self) -> typing.Optional[builtins.str]: ... @property - def interest_debt_per_share_ttm(self) -> builtins.str: ... + def interest_debt_per_share_ttm(self) -> typing.Optional[builtins.str]: ... @property - def cash_per_share_ttm(self) -> builtins.str: ... + def cash_per_share_ttm(self) -> typing.Optional[builtins.str]: ... @property - def book_value_per_share_ttm(self) -> builtins.str: ... + def book_value_per_share_ttm(self) -> typing.Optional[builtins.str]: ... @property - def tangible_book_value_per_share_ttm(self) -> builtins.str: ... + def tangible_book_value_per_share_ttm(self) -> typing.Optional[builtins.str]: ... @property - def shareholders_equity_per_share_ttm(self) -> builtins.str: ... + def shareholders_equity_per_share_ttm(self) -> typing.Optional[builtins.str]: ... @property - def operating_cash_flow_per_share_ttm(self) -> builtins.str: ... + def operating_cash_flow_per_share_ttm(self) -> typing.Optional[builtins.str]: ... @property - def capex_per_share_ttm(self) -> builtins.str: ... + def capex_per_share_ttm(self) -> typing.Optional[builtins.str]: ... @property - def free_cash_flow_per_share_ttm(self) -> builtins.str: ... + def free_cash_flow_per_share_ttm(self) -> typing.Optional[builtins.str]: ... @property - def net_income_per_ebt_ttm(self) -> builtins.str: ... + def net_income_per_ebt_ttm(self) -> typing.Optional[builtins.str]: ... @property - def ebt_per_ebit_ttm(self) -> builtins.str: ... + def ebt_per_ebit_ttm(self) -> typing.Optional[builtins.str]: ... @property - def price_to_fair_value_ttm(self) -> builtins.str: ... + def price_to_fair_value_ttm(self) -> typing.Optional[builtins.str]: ... @property - def debt_to_market_cap_ttm(self) -> builtins.str: ... + def debt_to_market_cap_ttm(self) -> typing.Optional[builtins.str]: ... @property - def effective_tax_rate_ttm(self) -> builtins.str: ... + def effective_tax_rate_ttm(self) -> typing.Optional[builtins.str]: ... @property - def enterprise_value_multiple_ttm(self) -> builtins.str: ... + def enterprise_value_multiple_ttm(self) -> typing.Optional[builtins.str]: ... @property - def dividend_per_share_ttm(self) -> builtins.str: ... + def dividend_per_share_ttm(self) -> typing.Optional[builtins.str]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... def __new__( cls, *, symbol: builtins.str, - gross_profit_margin_ttm: builtins.str, - ebit_margin_ttm: builtins.str, - ebitda_margin_ttm: builtins.str, - operating_profit_margin_ttm: builtins.str, - pretax_profit_margin_ttm: builtins.str, - continuous_operations_profit_margin_ttm: builtins.str, - net_profit_margin_ttm: builtins.str, - bottom_line_profit_margin_ttm: builtins.str, - receivables_turnover_ttm: builtins.str, - payables_turnover_ttm: builtins.str, - inventory_turnover_ttm: builtins.str, - fixed_asset_turnover_ttm: builtins.str, - asset_turnover_ttm: builtins.str, - current_ratio_ttm: builtins.str, - quick_ratio_ttm: builtins.str, - solvency_ratio_ttm: builtins.str, - cash_ratio_ttm: builtins.str, - price_to_earnings_ratio_ttm: builtins.str, - price_to_earnings_growth_ratio_ttm: builtins.str, - forward_price_to_earnings_growth_ratio_ttm: builtins.str, - price_to_book_ratio_ttm: builtins.str, - price_to_sales_ratio_ttm: builtins.str, - price_to_free_cash_flow_ratio_ttm: builtins.str, - price_to_operating_cash_flow_ratio_ttm: builtins.str, - debt_to_assets_ratio_ttm: builtins.str, - debt_to_equity_ratio_ttm: builtins.str, - debt_to_capital_ratio_ttm: builtins.str, - long_term_debt_to_capital_ratio_ttm: builtins.str, - financial_leverage_ratio_ttm: builtins.str, - working_capital_turnover_ratio_ttm: builtins.str, - operating_cash_flow_ratio_ttm: builtins.str, - operating_cash_flow_sales_ratio_ttm: builtins.str, - free_cash_flow_operating_cash_flow_ratio_ttm: builtins.str, - debt_service_coverage_ratio_ttm: builtins.str, - interest_coverage_ratio_ttm: builtins.str, - short_term_operating_cash_flow_coverage_ratio_ttm: builtins.str, - operating_cash_flow_coverage_ratio_ttm: builtins.str, - capital_expenditure_coverage_ratio_ttm: builtins.str, - dividend_paid_and_capex_coverage_ratio_ttm: builtins.str, - dividend_payout_ratio_ttm: builtins.str, - dividend_yield_ttm: builtins.str, - enterprise_value_ttm: builtins.str, - revenue_per_share_ttm: builtins.str, - net_income_per_share_ttm: builtins.str, - interest_debt_per_share_ttm: builtins.str, - cash_per_share_ttm: builtins.str, - book_value_per_share_ttm: builtins.str, - tangible_book_value_per_share_ttm: builtins.str, - shareholders_equity_per_share_ttm: builtins.str, - operating_cash_flow_per_share_ttm: builtins.str, - capex_per_share_ttm: builtins.str, - free_cash_flow_per_share_ttm: builtins.str, - net_income_per_ebt_ttm: builtins.str, - ebt_per_ebit_ttm: builtins.str, - price_to_fair_value_ttm: builtins.str, - debt_to_market_cap_ttm: builtins.str, - effective_tax_rate_ttm: builtins.str, - enterprise_value_multiple_ttm: builtins.str, - dividend_per_share_ttm: builtins.str, + gross_profit_margin_ttm: typing.Optional[builtins.str], + ebit_margin_ttm: typing.Optional[builtins.str], + ebitda_margin_ttm: typing.Optional[builtins.str], + operating_profit_margin_ttm: typing.Optional[builtins.str], + pretax_profit_margin_ttm: typing.Optional[builtins.str], + continuous_operations_profit_margin_ttm: typing.Optional[builtins.str], + net_profit_margin_ttm: typing.Optional[builtins.str], + bottom_line_profit_margin_ttm: typing.Optional[builtins.str], + receivables_turnover_ttm: typing.Optional[builtins.str], + payables_turnover_ttm: typing.Optional[builtins.str], + inventory_turnover_ttm: typing.Optional[builtins.str], + fixed_asset_turnover_ttm: typing.Optional[builtins.str], + asset_turnover_ttm: typing.Optional[builtins.str], + current_ratio_ttm: typing.Optional[builtins.str], + quick_ratio_ttm: typing.Optional[builtins.str], + solvency_ratio_ttm: typing.Optional[builtins.str], + cash_ratio_ttm: typing.Optional[builtins.str], + price_to_earnings_ratio_ttm: typing.Optional[builtins.str], + price_to_earnings_growth_ratio_ttm: typing.Optional[builtins.str], + forward_price_to_earnings_growth_ratio_ttm: typing.Optional[builtins.str], + price_to_book_ratio_ttm: typing.Optional[builtins.str], + price_to_sales_ratio_ttm: typing.Optional[builtins.str], + price_to_free_cash_flow_ratio_ttm: typing.Optional[builtins.str], + price_to_operating_cash_flow_ratio_ttm: typing.Optional[builtins.str], + debt_to_assets_ratio_ttm: typing.Optional[builtins.str], + debt_to_equity_ratio_ttm: typing.Optional[builtins.str], + debt_to_capital_ratio_ttm: typing.Optional[builtins.str], + long_term_debt_to_capital_ratio_ttm: typing.Optional[builtins.str], + financial_leverage_ratio_ttm: typing.Optional[builtins.str], + working_capital_turnover_ratio_ttm: typing.Optional[builtins.str], + operating_cash_flow_ratio_ttm: typing.Optional[builtins.str], + operating_cash_flow_sales_ratio_ttm: typing.Optional[builtins.str], + free_cash_flow_operating_cash_flow_ratio_ttm: typing.Optional[builtins.str], + debt_service_coverage_ratio_ttm: typing.Optional[builtins.str], + interest_coverage_ratio_ttm: typing.Optional[builtins.str], + short_term_operating_cash_flow_coverage_ratio_ttm: typing.Optional[ + builtins.str + ], + operating_cash_flow_coverage_ratio_ttm: typing.Optional[builtins.str], + capital_expenditure_coverage_ratio_ttm: typing.Optional[builtins.str], + dividend_paid_and_capex_coverage_ratio_ttm: typing.Optional[builtins.str], + dividend_payout_ratio_ttm: typing.Optional[builtins.str], + dividend_yield_ttm: typing.Optional[builtins.str], + enterprise_value_ttm: typing.Optional[builtins.str], + revenue_per_share_ttm: typing.Optional[builtins.str], + net_income_per_share_ttm: typing.Optional[builtins.str], + interest_debt_per_share_ttm: typing.Optional[builtins.str], + cash_per_share_ttm: typing.Optional[builtins.str], + book_value_per_share_ttm: typing.Optional[builtins.str], + tangible_book_value_per_share_ttm: typing.Optional[builtins.str], + shareholders_equity_per_share_ttm: typing.Optional[builtins.str], + operating_cash_flow_per_share_ttm: typing.Optional[builtins.str], + capex_per_share_ttm: typing.Optional[builtins.str], + free_cash_flow_per_share_ttm: typing.Optional[builtins.str], + net_income_per_ebt_ttm: typing.Optional[builtins.str], + ebt_per_ebit_ttm: typing.Optional[builtins.str], + price_to_fair_value_ttm: typing.Optional[builtins.str], + debt_to_market_cap_ttm: typing.Optional[builtins.str], + effective_tax_rate_ttm: typing.Optional[builtins.str], + enterprise_value_multiple_ttm: typing.Optional[builtins.str], + dividend_per_share_ttm: typing.Optional[builtins.str], ) -> BulkFinancialRatiosTtm: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... @@ -364,136 +382,142 @@ class BulkKeyMetricsTtm: @property def symbol(self) -> builtins.str: ... @property - def market_cap(self) -> builtins.str: ... + def market_cap(self) -> typing.Optional[builtins.str]: ... @property - def enterprise_value_ttm(self) -> builtins.str: ... + def enterprise_value_ttm(self) -> typing.Optional[builtins.str]: ... @property - def ev_to_sales_ttm(self) -> builtins.str: ... + def ev_to_sales_ttm(self) -> typing.Optional[builtins.str]: ... @property - def ev_to_operating_cash_flow_ttm(self) -> builtins.str: ... + def ev_to_operating_cash_flow_ttm(self) -> typing.Optional[builtins.str]: ... @property - def ev_to_free_cash_flow_ttm(self) -> builtins.str: ... + def ev_to_free_cash_flow_ttm(self) -> typing.Optional[builtins.str]: ... @property - def ev_to_ebitda_ttm(self) -> builtins.str: ... + def ev_to_ebitda_ttm(self) -> typing.Optional[builtins.str]: ... @property - def net_debt_to_ebitda_ttm(self) -> builtins.str: ... + def net_debt_to_ebitda_ttm(self) -> typing.Optional[builtins.str]: ... @property - def current_ratio_ttm(self) -> builtins.str: ... + def current_ratio_ttm(self) -> typing.Optional[builtins.str]: ... @property - def income_quality_ttm(self) -> builtins.str: ... + def income_quality_ttm(self) -> typing.Optional[builtins.str]: ... @property - def graham_number_ttm(self) -> builtins.str: ... + def graham_number_ttm(self) -> typing.Optional[builtins.str]: ... @property - def graham_net_net_ttm(self) -> builtins.str: ... + def graham_net_net_ttm(self) -> typing.Optional[builtins.str]: ... @property - def tax_burden_ttm(self) -> builtins.str: ... + def tax_burden_ttm(self) -> typing.Optional[builtins.str]: ... @property - def interest_burden_ttm(self) -> builtins.str: ... + def interest_burden_ttm(self) -> typing.Optional[builtins.str]: ... @property - def working_capital_ttm(self) -> builtins.str: ... + def working_capital_ttm(self) -> typing.Optional[builtins.str]: ... @property - def invested_capital_ttm(self) -> builtins.str: ... + def invested_capital_ttm(self) -> typing.Optional[builtins.str]: ... @property - def return_on_assets_ttm(self) -> builtins.str: ... + def return_on_assets_ttm(self) -> typing.Optional[builtins.str]: ... @property - def operating_return_on_assets_ttm(self) -> builtins.str: ... + def operating_return_on_assets_ttm(self) -> typing.Optional[builtins.str]: ... @property - def return_on_tangible_assets_ttm(self) -> builtins.str: ... + def return_on_tangible_assets_ttm(self) -> typing.Optional[builtins.str]: ... @property - def return_on_equity_ttm(self) -> builtins.str: ... + def return_on_equity_ttm(self) -> typing.Optional[builtins.str]: ... @property - def return_on_invested_capital_ttm(self) -> builtins.str: ... + def return_on_invested_capital_ttm(self) -> typing.Optional[builtins.str]: ... @property - def return_on_capital_employed_ttm(self) -> builtins.str: ... + def return_on_capital_employed_ttm(self) -> typing.Optional[builtins.str]: ... @property - def earnings_yield_ttm(self) -> builtins.str: ... + def earnings_yield_ttm(self) -> typing.Optional[builtins.str]: ... @property - def free_cash_flow_yield_ttm(self) -> builtins.str: ... + def free_cash_flow_yield_ttm(self) -> typing.Optional[builtins.str]: ... @property - def capex_to_operating_cash_flow_ttm(self) -> builtins.str: ... + def capex_to_operating_cash_flow_ttm(self) -> typing.Optional[builtins.str]: ... @property - def capex_to_depreciation_ttm(self) -> builtins.str: ... + def capex_to_depreciation_ttm(self) -> typing.Optional[builtins.str]: ... @property - def capex_to_revenue_ttm(self) -> builtins.str: ... + def capex_to_revenue_ttm(self) -> typing.Optional[builtins.str]: ... @property - def sales_general_and_administrative_to_revenue_ttm(self) -> builtins.str: ... + def sales_general_and_administrative_to_revenue_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def research_and_development_to_revenue_ttm(self) -> builtins.str: ... + def research_and_development_to_revenue_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def stock_based_compensation_to_revenue_ttm(self) -> builtins.str: ... + def stock_based_compensation_to_revenue_ttm( + self, + ) -> typing.Optional[builtins.str]: ... @property - def intangibles_to_total_assets_ttm(self) -> builtins.str: ... + def intangibles_to_total_assets_ttm(self) -> typing.Optional[builtins.str]: ... @property - def average_receivables_ttm(self) -> builtins.str: ... + def average_receivables_ttm(self) -> typing.Optional[builtins.str]: ... @property - def average_payables_ttm(self) -> builtins.str: ... + def average_payables_ttm(self) -> typing.Optional[builtins.str]: ... @property - def average_inventory_ttm(self) -> builtins.str: ... + def average_inventory_ttm(self) -> typing.Optional[builtins.str]: ... @property - def days_of_sales_outstanding_ttm(self) -> builtins.str: ... + def days_of_sales_outstanding_ttm(self) -> typing.Optional[builtins.str]: ... @property - def days_of_payables_outstanding_ttm(self) -> builtins.str: ... + def days_of_payables_outstanding_ttm(self) -> typing.Optional[builtins.str]: ... @property - def days_of_inventory_outstanding_ttm(self) -> builtins.str: ... + def days_of_inventory_outstanding_ttm(self) -> typing.Optional[builtins.str]: ... @property - def operating_cycle_ttm(self) -> builtins.str: ... + def operating_cycle_ttm(self) -> typing.Optional[builtins.str]: ... @property - def cash_conversion_cycle_ttm(self) -> builtins.str: ... + def cash_conversion_cycle_ttm(self) -> typing.Optional[builtins.str]: ... @property - def free_cash_flow_to_equity_ttm(self) -> builtins.str: ... + def free_cash_flow_to_equity_ttm(self) -> typing.Optional[builtins.str]: ... @property - def free_cash_flow_to_firm_ttm(self) -> builtins.str: ... + def free_cash_flow_to_firm_ttm(self) -> typing.Optional[builtins.str]: ... @property - def tangible_asset_value_ttm(self) -> builtins.str: ... + def tangible_asset_value_ttm(self) -> typing.Optional[builtins.str]: ... @property - def net_current_asset_value_ttm(self) -> builtins.str: ... + def net_current_asset_value_ttm(self) -> typing.Optional[builtins.str]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... def __new__( cls, *, symbol: builtins.str, - market_cap: builtins.str, - enterprise_value_ttm: builtins.str, - ev_to_sales_ttm: builtins.str, - ev_to_operating_cash_flow_ttm: builtins.str, - ev_to_free_cash_flow_ttm: builtins.str, - ev_to_ebitda_ttm: builtins.str, - net_debt_to_ebitda_ttm: builtins.str, - current_ratio_ttm: builtins.str, - income_quality_ttm: builtins.str, - graham_number_ttm: builtins.str, - graham_net_net_ttm: builtins.str, - tax_burden_ttm: builtins.str, - interest_burden_ttm: builtins.str, - working_capital_ttm: builtins.str, - invested_capital_ttm: builtins.str, - return_on_assets_ttm: builtins.str, - operating_return_on_assets_ttm: builtins.str, - return_on_tangible_assets_ttm: builtins.str, - return_on_equity_ttm: builtins.str, - return_on_invested_capital_ttm: builtins.str, - return_on_capital_employed_ttm: builtins.str, - earnings_yield_ttm: builtins.str, - free_cash_flow_yield_ttm: builtins.str, - capex_to_operating_cash_flow_ttm: builtins.str, - capex_to_depreciation_ttm: builtins.str, - capex_to_revenue_ttm: builtins.str, - sales_general_and_administrative_to_revenue_ttm: builtins.str, - research_and_development_to_revenue_ttm: builtins.str, - stock_based_compensation_to_revenue_ttm: builtins.str, - intangibles_to_total_assets_ttm: builtins.str, - average_receivables_ttm: builtins.str, - average_payables_ttm: builtins.str, - average_inventory_ttm: builtins.str, - days_of_sales_outstanding_ttm: builtins.str, - days_of_payables_outstanding_ttm: builtins.str, - days_of_inventory_outstanding_ttm: builtins.str, - operating_cycle_ttm: builtins.str, - cash_conversion_cycle_ttm: builtins.str, - free_cash_flow_to_equity_ttm: builtins.str, - free_cash_flow_to_firm_ttm: builtins.str, - tangible_asset_value_ttm: builtins.str, - net_current_asset_value_ttm: builtins.str, + market_cap: typing.Optional[builtins.str], + enterprise_value_ttm: typing.Optional[builtins.str], + ev_to_sales_ttm: typing.Optional[builtins.str], + ev_to_operating_cash_flow_ttm: typing.Optional[builtins.str], + ev_to_free_cash_flow_ttm: typing.Optional[builtins.str], + ev_to_ebitda_ttm: typing.Optional[builtins.str], + net_debt_to_ebitda_ttm: typing.Optional[builtins.str], + current_ratio_ttm: typing.Optional[builtins.str], + income_quality_ttm: typing.Optional[builtins.str], + graham_number_ttm: typing.Optional[builtins.str], + graham_net_net_ttm: typing.Optional[builtins.str], + tax_burden_ttm: typing.Optional[builtins.str], + interest_burden_ttm: typing.Optional[builtins.str], + working_capital_ttm: typing.Optional[builtins.str], + invested_capital_ttm: typing.Optional[builtins.str], + return_on_assets_ttm: typing.Optional[builtins.str], + operating_return_on_assets_ttm: typing.Optional[builtins.str], + return_on_tangible_assets_ttm: typing.Optional[builtins.str], + return_on_equity_ttm: typing.Optional[builtins.str], + return_on_invested_capital_ttm: typing.Optional[builtins.str], + return_on_capital_employed_ttm: typing.Optional[builtins.str], + earnings_yield_ttm: typing.Optional[builtins.str], + free_cash_flow_yield_ttm: typing.Optional[builtins.str], + capex_to_operating_cash_flow_ttm: typing.Optional[builtins.str], + capex_to_depreciation_ttm: typing.Optional[builtins.str], + capex_to_revenue_ttm: typing.Optional[builtins.str], + sales_general_and_administrative_to_revenue_ttm: typing.Optional[builtins.str], + research_and_development_to_revenue_ttm: typing.Optional[builtins.str], + stock_based_compensation_to_revenue_ttm: typing.Optional[builtins.str], + intangibles_to_total_assets_ttm: typing.Optional[builtins.str], + average_receivables_ttm: typing.Optional[builtins.str], + average_payables_ttm: typing.Optional[builtins.str], + average_inventory_ttm: typing.Optional[builtins.str], + days_of_sales_outstanding_ttm: typing.Optional[builtins.str], + days_of_payables_outstanding_ttm: typing.Optional[builtins.str], + days_of_inventory_outstanding_ttm: typing.Optional[builtins.str], + operating_cycle_ttm: typing.Optional[builtins.str], + cash_conversion_cycle_ttm: typing.Optional[builtins.str], + free_cash_flow_to_equity_ttm: typing.Optional[builtins.str], + free_cash_flow_to_firm_ttm: typing.Optional[builtins.str], + tangible_asset_value_ttm: typing.Optional[builtins.str], + net_current_asset_value_ttm: typing.Optional[builtins.str], ) -> BulkKeyMetricsTtm: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... diff --git a/crates/fmp-py/python/fmp/_native/bulk/snapshots/__init__.pyi b/crates/fmp-py/python/fmp/_native/bulk/snapshots/__init__.pyi index a1a45ebe..95df9813 100644 --- a/crates/fmp-py/python/fmp/_native/bulk/snapshots/__init__.pyi +++ b/crates/fmp-py/python/fmp/_native/bulk/snapshots/__init__.pyi @@ -29,17 +29,17 @@ class BulkDcfValuation: @property def date(self) -> datetime.date: ... @property - def dcf(self) -> builtins.str: ... + def dcf(self) -> typing.Optional[builtins.str]: ... @property - def stock_price(self) -> builtins.str: ... + def stock_price(self) -> typing.Optional[builtins.str]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... def __new__( cls, *, symbol: builtins.str, date: datetime.date, - dcf: builtins.str, - stock_price: builtins.str, + dcf: typing.Optional[builtins.str], + stock_price: typing.Optional[builtins.str], ) -> BulkDcfValuation: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... @@ -62,7 +62,7 @@ class BulkEtfHolding: typing.Literal["cusip"], typing.Literal["isin"], typing.Literal["market_value"], - typing.Literal["last_updated_raw"], + typing.Literal["last_updated"], ] @property @@ -70,32 +70,32 @@ class BulkEtfHolding: @property def name(self) -> builtins.str: ... @property - def shares_number(self) -> builtins.str: ... + def shares_number(self) -> typing.Optional[builtins.str]: ... @property - def asset(self) -> builtins.str: ... + def asset(self) -> typing.Optional[builtins.str]: ... @property - def weight_percentage(self) -> builtins.str: ... + def weight_percentage(self) -> typing.Optional[builtins.str]: ... @property def cusip(self) -> builtins.str: ... @property - def isin(self) -> builtins.str: ... + def isin(self) -> typing.Optional[builtins.str]: ... @property - def market_value(self) -> builtins.str: ... + def market_value(self) -> typing.Optional[builtins.str]: ... @property - def last_updated_raw(self) -> builtins.str: ... + def last_updated(self) -> datetime.date: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... def __new__( cls, *, symbol: builtins.str, name: builtins.str, - shares_number: builtins.str, - asset: builtins.str, - weight_percentage: builtins.str, + shares_number: typing.Optional[builtins.str], + asset: typing.Optional[builtins.str], + weight_percentage: typing.Optional[builtins.str], cusip: builtins.str, - isin: builtins.str, - market_value: builtins.str, - last_updated_raw: builtins.str, + isin: typing.Optional[builtins.str], + market_value: typing.Optional[builtins.str], + last_updated: datetime.date, ) -> BulkEtfHolding: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... @@ -126,40 +126,40 @@ class BulkFinancialScore: @property def symbol(self) -> builtins.str: ... @property - def reported_currency(self) -> builtins.str: ... + def reported_currency(self) -> typing.Optional[builtins.str]: ... @property - def altman_z_score(self) -> builtins.str: ... + def altman_z_score(self) -> typing.Optional[builtins.str]: ... @property - def piotroski_score(self) -> builtins.str: ... + def piotroski_score(self) -> typing.Optional[builtins.str]: ... @property - def working_capital(self) -> builtins.str: ... + def working_capital(self) -> typing.Optional[builtins.str]: ... @property - def total_assets(self) -> builtins.str: ... + def total_assets(self) -> typing.Optional[builtins.str]: ... @property - def retained_earnings(self) -> builtins.str: ... + def retained_earnings(self) -> typing.Optional[builtins.str]: ... @property - def ebit(self) -> builtins.str: ... + def ebit(self) -> typing.Optional[builtins.str]: ... @property - def market_cap(self) -> builtins.str: ... + def market_cap(self) -> typing.Optional[builtins.str]: ... @property - def total_liabilities(self) -> builtins.str: ... + def total_liabilities(self) -> typing.Optional[builtins.str]: ... @property - def revenue(self) -> builtins.str: ... + def revenue(self) -> typing.Optional[builtins.str]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... def __new__( cls, *, symbol: builtins.str, - reported_currency: builtins.str, - altman_z_score: builtins.str, - piotroski_score: builtins.str, - working_capital: builtins.str, - total_assets: builtins.str, - retained_earnings: builtins.str, - ebit: builtins.str, - market_cap: builtins.str, - total_liabilities: builtins.str, - revenue: builtins.str, + reported_currency: typing.Optional[builtins.str], + altman_z_score: typing.Optional[builtins.str], + piotroski_score: typing.Optional[builtins.str], + working_capital: typing.Optional[builtins.str], + total_assets: typing.Optional[builtins.str], + retained_earnings: typing.Optional[builtins.str], + ebit: typing.Optional[builtins.str], + market_cap: typing.Optional[builtins.str], + total_liabilities: typing.Optional[builtins.str], + revenue: typing.Optional[builtins.str], ) -> BulkFinancialScore: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... @@ -189,21 +189,21 @@ class BulkPriceTargetSummary: @property def symbol(self) -> builtins.str: ... @property - def last_month_count(self) -> builtins.str: ... + def last_month_count(self) -> typing.Optional[builtins.str]: ... @property - def last_month_avg_price_target(self) -> builtins.str: ... + def last_month_avg_price_target(self) -> typing.Optional[builtins.str]: ... @property - def last_quarter_count(self) -> builtins.str: ... + def last_quarter_count(self) -> typing.Optional[builtins.str]: ... @property - def last_quarter_avg_price_target(self) -> builtins.str: ... + def last_quarter_avg_price_target(self) -> typing.Optional[builtins.str]: ... @property - def last_year_count(self) -> builtins.str: ... + def last_year_count(self) -> typing.Optional[builtins.str]: ... @property - def last_year_avg_price_target(self) -> builtins.str: ... + def last_year_avg_price_target(self) -> typing.Optional[builtins.str]: ... @property - def all_time_count(self) -> builtins.str: ... + def all_time_count(self) -> typing.Optional[builtins.str]: ... @property - def all_time_avg_price_target(self) -> builtins.str: ... + def all_time_avg_price_target(self) -> typing.Optional[builtins.str]: ... @property def publishers(self) -> builtins.str: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... @@ -211,14 +211,14 @@ class BulkPriceTargetSummary: cls, *, symbol: builtins.str, - last_month_count: builtins.str, - last_month_avg_price_target: builtins.str, - last_quarter_count: builtins.str, - last_quarter_avg_price_target: builtins.str, - last_year_count: builtins.str, - last_year_avg_price_target: builtins.str, - all_time_count: builtins.str, - all_time_avg_price_target: builtins.str, + last_month_count: typing.Optional[builtins.str], + last_month_avg_price_target: typing.Optional[builtins.str], + last_quarter_count: typing.Optional[builtins.str], + last_quarter_avg_price_target: typing.Optional[builtins.str], + last_year_count: typing.Optional[builtins.str], + last_year_avg_price_target: typing.Optional[builtins.str], + all_time_count: typing.Optional[builtins.str], + all_time_avg_price_target: typing.Optional[builtins.str], publishers: builtins.str, ) -> BulkPriceTargetSummary: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... @@ -252,17 +252,17 @@ class BulkStockRating: @property def rating(self) -> builtins.str: ... @property - def discounted_cash_flow_score(self) -> builtins.str: ... + def discounted_cash_flow_score(self) -> typing.Optional[builtins.str]: ... @property - def return_on_equity_score(self) -> builtins.str: ... + def return_on_equity_score(self) -> typing.Optional[builtins.str]: ... @property - def return_on_assets_score(self) -> builtins.str: ... + def return_on_assets_score(self) -> typing.Optional[builtins.str]: ... @property - def debt_to_equity_score(self) -> builtins.str: ... + def debt_to_equity_score(self) -> typing.Optional[builtins.str]: ... @property - def price_to_earnings_score(self) -> builtins.str: ... + def price_to_earnings_score(self) -> typing.Optional[builtins.str]: ... @property - def price_to_book_score(self) -> builtins.str: ... + def price_to_book_score(self) -> typing.Optional[builtins.str]: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... def __new__( cls, @@ -270,12 +270,12 @@ class BulkStockRating: symbol: builtins.str, date: datetime.date, rating: builtins.str, - discounted_cash_flow_score: builtins.str, - return_on_equity_score: builtins.str, - return_on_assets_score: builtins.str, - debt_to_equity_score: builtins.str, - price_to_earnings_score: builtins.str, - price_to_book_score: builtins.str, + discounted_cash_flow_score: typing.Optional[builtins.str], + return_on_equity_score: typing.Optional[builtins.str], + return_on_assets_score: typing.Optional[builtins.str], + debt_to_equity_score: typing.Optional[builtins.str], + price_to_earnings_score: typing.Optional[builtins.str], + price_to_book_score: typing.Optional[builtins.str], ) -> BulkStockRating: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... def __repr__(self) -> builtins.str: ... @@ -302,15 +302,15 @@ class BulkUpgradesDowngradesConsensus: @property def symbol(self) -> builtins.str: ... @property - def strong_buy(self) -> builtins.str: ... + def strong_buy(self) -> typing.Optional[builtins.str]: ... @property - def buy(self) -> builtins.str: ... + def buy(self) -> typing.Optional[builtins.str]: ... @property - def hold(self) -> builtins.str: ... + def hold(self) -> typing.Optional[builtins.str]: ... @property - def sell(self) -> builtins.str: ... + def sell(self) -> typing.Optional[builtins.str]: ... @property - def strong_sell(self) -> builtins.str: ... + def strong_sell(self) -> typing.Optional[builtins.str]: ... @property def consensus(self) -> builtins.str: ... def __eq__(self, other: builtins.object, /) -> builtins.bool: ... @@ -318,11 +318,11 @@ class BulkUpgradesDowngradesConsensus: cls, *, symbol: builtins.str, - strong_buy: builtins.str, - buy: builtins.str, - hold: builtins.str, - sell: builtins.str, - strong_sell: builtins.str, + strong_buy: typing.Optional[builtins.str], + buy: typing.Optional[builtins.str], + hold: typing.Optional[builtins.str], + sell: typing.Optional[builtins.str], + strong_sell: typing.Optional[builtins.str], consensus: builtins.str, ) -> BulkUpgradesDowngradesConsensus: ... def __getnewargs_ex__(self) -> tuple[tuple, dict]: ... diff --git a/crates/fmp-py/python/fmp/_native/company/__init__.pyi b/crates/fmp-py/python/fmp/_native/company/__init__.pyi index 9315aafa..1feed9e7 100644 --- a/crates/fmp-py/python/fmp/_native/company/__init__.pyi +++ b/crates/fmp-py/python/fmp/_native/company/__init__.pyi @@ -322,9 +322,9 @@ class CompanyProfile: @property def range(self) -> builtins.str: ... @property - def change(self) -> builtins.float: ... + def change(self) -> typing.Optional[builtins.float]: ... @property - def change_percentage(self) -> builtins.float: ... + def change_percentage(self) -> typing.Optional[builtins.float]: ... @property def volume(self) -> builtins.float: ... @property @@ -336,7 +336,7 @@ class CompanyProfile: @property def cik(self) -> typing.Optional[builtins.str]: ... @property - def isin(self) -> builtins.str: ... + def isin(self) -> typing.Optional[builtins.str]: ... @property def cusip(self) -> typing.Optional[builtins.str]: ... @property @@ -354,7 +354,7 @@ class CompanyProfile: @property def sector(self) -> builtins.str: ... @property - def country(self) -> builtins.str: ... + def country(self) -> typing.Optional[builtins.str]: ... @property def full_time_employees(self) -> typing.Optional[builtins.str]: ... @property @@ -391,14 +391,14 @@ class CompanyProfile: beta: builtins.float, last_dividend: builtins.float, range: builtins.str, - change: builtins.float, - change_percentage: builtins.float, + change: typing.Optional[builtins.float], + change_percentage: typing.Optional[builtins.float], volume: builtins.float, average_volume: builtins.float, company_name: builtins.str, currency: builtins.str, cik: typing.Optional[builtins.str], - isin: builtins.str, + isin: typing.Optional[builtins.str], cusip: typing.Optional[builtins.str], exchange_full_name: builtins.str, exchange: builtins.str, @@ -407,7 +407,7 @@ class CompanyProfile: description: builtins.str, ceo: builtins.str, sector: builtins.str, - country: builtins.str, + country: typing.Optional[builtins.str], full_time_employees: typing.Optional[builtins.str], phone: typing.Optional[builtins.str], address: builtins.str, diff --git a/crates/fmp-py/src/client.rs b/crates/fmp-py/src/client.rs index b301b435..2e7fce53 100644 --- a/crates/fmp-py/src/client.rs +++ b/crates/fmp-py/src/client.rs @@ -171,7 +171,9 @@ fn positive_duration(value: f64, field: &'static str) -> PyResult { /// the default host the constructor raises `FmpConfigError` naming /// `FMP_API_KEY`. /// `timeout` and `connect_timeout` are positive finite numbers of seconds. -/// `max_response_body_bytes` bounds each buffered response. Authenticated +/// `max_response_body_bytes` bounds each buffered response; when omitted, +/// `client.bulk` methods buffer up to 256 MiB and every other method up to +/// 64 MiB. Authenticated /// non-loopback HTTP requires `danger_allow_insecure_authentication=True`. /// Redirects are either disabled or restricted to the same origin. /// diff --git a/crates/fmp-py/src/models/bulk/balance.rs b/crates/fmp-py/src/models/bulk/balance.rs index a14636b8..1974f6b1 100644 --- a/crates/fmp-py/src/models/bulk/balance.rs +++ b/crates/fmp-py/src/models/bulk/balance.rs @@ -25,111 +25,111 @@ pub(crate) struct BulkBalanceSheetStatement { pub fiscal_year: String, pub period: String, #[pyo3(get)] - pub cash_and_cash_equivalents: String, + pub cash_and_cash_equivalents: Option, #[pyo3(get)] - pub short_term_investments: String, + pub short_term_investments: Option, #[pyo3(get)] - pub cash_and_short_term_investments: String, + pub cash_and_short_term_investments: Option, #[pyo3(get)] - pub net_receivables: String, + pub net_receivables: Option, #[pyo3(get)] - pub accounts_receivables: String, + pub accounts_receivables: Option, #[pyo3(get)] - pub other_receivables: String, + pub other_receivables: Option, #[pyo3(get)] - pub inventory: String, + pub inventory: Option, #[pyo3(get)] - pub prepaids: String, + pub prepaids: Option, #[pyo3(get)] - pub other_current_assets: String, + pub other_current_assets: Option, #[pyo3(get)] - pub total_current_assets: String, + pub total_current_assets: Option, #[pyo3(get)] - pub property_plant_equipment_net: String, + pub property_plant_equipment_net: Option, #[pyo3(get)] - pub goodwill: String, + pub goodwill: Option, #[pyo3(get)] - pub intangible_assets: String, + pub intangible_assets: Option, #[pyo3(get)] - pub goodwill_and_intangible_assets: String, + pub goodwill_and_intangible_assets: Option, #[pyo3(get)] - pub long_term_investments: String, + pub long_term_investments: Option, #[pyo3(get)] - pub tax_assets: String, + pub tax_assets: Option, #[pyo3(get)] - pub other_non_current_assets: String, + pub other_non_current_assets: Option, #[pyo3(get)] - pub total_non_current_assets: String, + pub total_non_current_assets: Option, #[pyo3(get)] - pub other_assets: String, + pub other_assets: Option, #[pyo3(get)] - pub total_assets: String, + pub total_assets: Option, #[pyo3(get)] - pub total_payables: String, + pub total_payables: Option, #[pyo3(get)] - pub account_payables: String, + pub account_payables: Option, #[pyo3(get)] - pub other_payables: String, + pub other_payables: Option, #[pyo3(get)] - pub accrued_expenses: String, + pub accrued_expenses: Option, #[pyo3(get)] - pub short_term_debt: String, + pub short_term_debt: Option, #[pyo3(get)] - pub capital_lease_obligations_current: String, + pub capital_lease_obligations_current: Option, #[pyo3(get)] - pub tax_payables: String, + pub tax_payables: Option, #[pyo3(get)] - pub deferred_revenue: String, + pub deferred_revenue: Option, #[pyo3(get)] - pub other_current_liabilities: String, + pub other_current_liabilities: Option, #[pyo3(get)] - pub total_current_liabilities: String, + pub total_current_liabilities: Option, #[pyo3(get)] - pub long_term_debt: String, + pub long_term_debt: Option, #[pyo3(get)] - pub capital_lease_obligations_non_current: String, + pub capital_lease_obligations_non_current: Option, #[pyo3(get)] - pub deferred_revenue_non_current: String, + pub deferred_revenue_non_current: Option, #[pyo3(get)] - pub deferred_tax_liabilities_non_current: String, + pub deferred_tax_liabilities_non_current: Option, #[pyo3(get)] - pub other_non_current_liabilities: String, + pub other_non_current_liabilities: Option, #[pyo3(get)] - pub total_non_current_liabilities: String, + pub total_non_current_liabilities: Option, #[pyo3(get)] - pub other_liabilities: String, + pub other_liabilities: Option, #[pyo3(get)] - pub capital_lease_obligations: String, + pub capital_lease_obligations: Option, #[pyo3(get)] - pub total_liabilities: String, + pub total_liabilities: Option, #[pyo3(get)] - pub treasury_stock: String, + pub treasury_stock: Option, #[pyo3(get)] - pub preferred_stock: String, + pub preferred_stock: Option, #[pyo3(get)] - pub common_stock: String, + pub common_stock: Option, #[pyo3(get)] - pub retained_earnings: String, + pub retained_earnings: Option, #[pyo3(get)] - pub additional_paid_in_capital: String, + pub additional_paid_in_capital: Option, #[pyo3(get)] - pub accumulated_other_comprehensive_income_loss: String, + pub accumulated_other_comprehensive_income_loss: Option, #[pyo3(get)] - pub other_total_stockholders_equity: String, + pub other_total_stockholders_equity: Option, #[pyo3(get)] - pub total_stockholders_equity: String, + pub total_stockholders_equity: Option, #[pyo3(get)] - pub total_equity: String, + pub total_equity: Option, #[pyo3(get)] - pub minority_interest: String, + pub minority_interest: Option, #[pyo3(get)] - pub total_liabilities_and_total_equity: String, + pub total_liabilities_and_total_equity: Option, #[pyo3(get)] - pub total_investments: String, + pub total_investments: Option, #[pyo3(get)] - pub total_debt: String, + pub total_debt: Option, #[pyo3(get)] - pub net_debt: String, + pub net_debt: Option, } #[gen_stub_pymethods] @@ -149,59 +149,59 @@ impl BulkBalanceSheetStatement { fiscal_year: String, #[gen_stub(override_type(type_repr = "typing.Literal[\"Q1\", \"Q2\", \"Q3\", \"Q4\", \"FY\"]", imports = ("builtins", "typing")))] period: String, - cash_and_cash_equivalents: String, - short_term_investments: String, - cash_and_short_term_investments: String, - net_receivables: String, - accounts_receivables: String, - other_receivables: String, - inventory: String, - prepaids: String, - other_current_assets: String, - total_current_assets: String, - property_plant_equipment_net: String, - goodwill: String, - intangible_assets: String, - goodwill_and_intangible_assets: String, - long_term_investments: String, - tax_assets: String, - other_non_current_assets: String, - total_non_current_assets: String, - other_assets: String, - total_assets: String, - total_payables: String, - account_payables: String, - other_payables: String, - accrued_expenses: String, - short_term_debt: String, - capital_lease_obligations_current: String, - tax_payables: String, - deferred_revenue: String, - other_current_liabilities: String, - total_current_liabilities: String, - long_term_debt: String, - capital_lease_obligations_non_current: String, - deferred_revenue_non_current: String, - deferred_tax_liabilities_non_current: String, - other_non_current_liabilities: String, - total_non_current_liabilities: String, - other_liabilities: String, - capital_lease_obligations: String, - total_liabilities: String, - treasury_stock: String, - preferred_stock: String, - common_stock: String, - retained_earnings: String, - additional_paid_in_capital: String, - accumulated_other_comprehensive_income_loss: String, - other_total_stockholders_equity: String, - total_stockholders_equity: String, - total_equity: String, - minority_interest: String, - total_liabilities_and_total_equity: String, - total_investments: String, - total_debt: String, - net_debt: String, + cash_and_cash_equivalents: Option, + short_term_investments: Option, + cash_and_short_term_investments: Option, + net_receivables: Option, + accounts_receivables: Option, + other_receivables: Option, + inventory: Option, + prepaids: Option, + other_current_assets: Option, + total_current_assets: Option, + property_plant_equipment_net: Option, + goodwill: Option, + intangible_assets: Option, + goodwill_and_intangible_assets: Option, + long_term_investments: Option, + tax_assets: Option, + other_non_current_assets: Option, + total_non_current_assets: Option, + other_assets: Option, + total_assets: Option, + total_payables: Option, + account_payables: Option, + other_payables: Option, + accrued_expenses: Option, + short_term_debt: Option, + capital_lease_obligations_current: Option, + tax_payables: Option, + deferred_revenue: Option, + other_current_liabilities: Option, + total_current_liabilities: Option, + long_term_debt: Option, + capital_lease_obligations_non_current: Option, + deferred_revenue_non_current: Option, + deferred_tax_liabilities_non_current: Option, + other_non_current_liabilities: Option, + total_non_current_liabilities: Option, + other_liabilities: Option, + capital_lease_obligations: Option, + total_liabilities: Option, + treasury_stock: Option, + preferred_stock: Option, + common_stock: Option, + retained_earnings: Option, + additional_paid_in_capital: Option, + accumulated_other_comprehensive_income_loss: Option, + other_total_stockholders_equity: Option, + total_stockholders_equity: Option, + total_equity: Option, + minority_interest: Option, + total_liabilities_and_total_equity: Option, + total_investments: Option, + total_debt: Option, + net_debt: Option, ) -> Self { Self { date, @@ -1161,67 +1161,99 @@ impl From for BulkBalanceShe accepted_date: value.accepted_date.into_inner(), fiscal_year: value.fiscal_year.as_str().to_owned(), period: value.period.to_string(), - cash_and_cash_equivalents: value.cash_and_cash_equivalents.into_inner(), - short_term_investments: value.short_term_investments.into_inner(), - cash_and_short_term_investments: value.cash_and_short_term_investments.into_inner(), - net_receivables: value.net_receivables.into_inner(), - accounts_receivables: value.accounts_receivables.into_inner(), - other_receivables: value.other_receivables.into_inner(), - inventory: value.inventory.into_inner(), - prepaids: value.prepaids.into_inner(), - other_current_assets: value.other_current_assets.into_inner(), - total_current_assets: value.total_current_assets.into_inner(), - property_plant_equipment_net: value.property_plant_equipment_net.into_inner(), - goodwill: value.goodwill.into_inner(), - intangible_assets: value.intangible_assets.into_inner(), - goodwill_and_intangible_assets: value.goodwill_and_intangible_assets.into_inner(), - long_term_investments: value.long_term_investments.into_inner(), - tax_assets: value.tax_assets.into_inner(), - other_non_current_assets: value.other_non_current_assets.into_inner(), - total_non_current_assets: value.total_non_current_assets.into_inner(), - other_assets: value.other_assets.into_inner(), - total_assets: value.total_assets.into_inner(), - total_payables: value.total_payables.into_inner(), - account_payables: value.account_payables.into_inner(), - other_payables: value.other_payables.into_inner(), - accrued_expenses: value.accrued_expenses.into_inner(), - short_term_debt: value.short_term_debt.into_inner(), - capital_lease_obligations_current: value.capital_lease_obligations_current.into_inner(), - tax_payables: value.tax_payables.into_inner(), - deferred_revenue: value.deferred_revenue.into_inner(), - other_current_liabilities: value.other_current_liabilities.into_inner(), - total_current_liabilities: value.total_current_liabilities.into_inner(), - long_term_debt: value.long_term_debt.into_inner(), + cash_and_cash_equivalents: value + .cash_and_cash_equivalents + .map(|value| value.into_inner()), + short_term_investments: value.short_term_investments.map(|value| value.into_inner()), + cash_and_short_term_investments: value + .cash_and_short_term_investments + .map(|value| value.into_inner()), + net_receivables: value.net_receivables.map(|value| value.into_inner()), + accounts_receivables: value.accounts_receivables.map(|value| value.into_inner()), + other_receivables: value.other_receivables.map(|value| value.into_inner()), + inventory: value.inventory.map(|value| value.into_inner()), + prepaids: value.prepaids.map(|value| value.into_inner()), + other_current_assets: value.other_current_assets.map(|value| value.into_inner()), + total_current_assets: value.total_current_assets.map(|value| value.into_inner()), + property_plant_equipment_net: value + .property_plant_equipment_net + .map(|value| value.into_inner()), + goodwill: value.goodwill.map(|value| value.into_inner()), + intangible_assets: value.intangible_assets.map(|value| value.into_inner()), + goodwill_and_intangible_assets: value + .goodwill_and_intangible_assets + .map(|value| value.into_inner()), + long_term_investments: value.long_term_investments.map(|value| value.into_inner()), + tax_assets: value.tax_assets.map(|value| value.into_inner()), + other_non_current_assets: value + .other_non_current_assets + .map(|value| value.into_inner()), + total_non_current_assets: value + .total_non_current_assets + .map(|value| value.into_inner()), + other_assets: value.other_assets.map(|value| value.into_inner()), + total_assets: value.total_assets.map(|value| value.into_inner()), + total_payables: value.total_payables.map(|value| value.into_inner()), + account_payables: value.account_payables.map(|value| value.into_inner()), + other_payables: value.other_payables.map(|value| value.into_inner()), + accrued_expenses: value.accrued_expenses.map(|value| value.into_inner()), + short_term_debt: value.short_term_debt.map(|value| value.into_inner()), + capital_lease_obligations_current: value + .capital_lease_obligations_current + .map(|value| value.into_inner()), + tax_payables: value.tax_payables.map(|value| value.into_inner()), + deferred_revenue: value.deferred_revenue.map(|value| value.into_inner()), + other_current_liabilities: value + .other_current_liabilities + .map(|value| value.into_inner()), + total_current_liabilities: value + .total_current_liabilities + .map(|value| value.into_inner()), + long_term_debt: value.long_term_debt.map(|value| value.into_inner()), capital_lease_obligations_non_current: value .capital_lease_obligations_non_current - .into_inner(), - deferred_revenue_non_current: value.deferred_revenue_non_current.into_inner(), + .map(|value| value.into_inner()), + deferred_revenue_non_current: value + .deferred_revenue_non_current + .map(|value| value.into_inner()), deferred_tax_liabilities_non_current: value .deferred_tax_liabilities_non_current - .into_inner(), - other_non_current_liabilities: value.other_non_current_liabilities.into_inner(), - total_non_current_liabilities: value.total_non_current_liabilities.into_inner(), - other_liabilities: value.other_liabilities.into_inner(), - capital_lease_obligations: value.capital_lease_obligations.into_inner(), - total_liabilities: value.total_liabilities.into_inner(), - treasury_stock: value.treasury_stock.into_inner(), - preferred_stock: value.preferred_stock.into_inner(), - common_stock: value.common_stock.into_inner(), - retained_earnings: value.retained_earnings.into_inner(), - additional_paid_in_capital: value.additional_paid_in_capital.into_inner(), + .map(|value| value.into_inner()), + other_non_current_liabilities: value + .other_non_current_liabilities + .map(|value| value.into_inner()), + total_non_current_liabilities: value + .total_non_current_liabilities + .map(|value| value.into_inner()), + other_liabilities: value.other_liabilities.map(|value| value.into_inner()), + capital_lease_obligations: value + .capital_lease_obligations + .map(|value| value.into_inner()), + total_liabilities: value.total_liabilities.map(|value| value.into_inner()), + treasury_stock: value.treasury_stock.map(|value| value.into_inner()), + preferred_stock: value.preferred_stock.map(|value| value.into_inner()), + common_stock: value.common_stock.map(|value| value.into_inner()), + retained_earnings: value.retained_earnings.map(|value| value.into_inner()), + additional_paid_in_capital: value + .additional_paid_in_capital + .map(|value| value.into_inner()), accumulated_other_comprehensive_income_loss: value .accumulated_other_comprehensive_income_loss - .into_inner(), - other_total_stockholders_equity: value.other_total_stockholders_equity.into_inner(), - total_stockholders_equity: value.total_stockholders_equity.into_inner(), - total_equity: value.total_equity.into_inner(), - minority_interest: value.minority_interest.into_inner(), + .map(|value| value.into_inner()), + other_total_stockholders_equity: value + .other_total_stockholders_equity + .map(|value| value.into_inner()), + total_stockholders_equity: value + .total_stockholders_equity + .map(|value| value.into_inner()), + total_equity: value.total_equity.map(|value| value.into_inner()), + minority_interest: value.minority_interest.map(|value| value.into_inner()), total_liabilities_and_total_equity: value .total_liabilities_and_total_equity - .into_inner(), - total_investments: value.total_investments.into_inner(), - total_debt: value.total_debt.into_inner(), - net_debt: value.net_debt.into_inner(), + .map(|value| value.into_inner()), + total_investments: value.total_investments.map(|value| value.into_inner()), + total_debt: value.total_debt.map(|value| value.into_inner()), + net_debt: value.net_debt.map(|value| value.into_inner()), } } } @@ -1246,107 +1278,107 @@ pub(crate) struct BulkBalanceSheetStatementGrowth { #[pyo3(get)] pub reported_currency: String, #[pyo3(get)] - pub growth_cash_and_cash_equivalents: String, + pub growth_cash_and_cash_equivalents: Option, #[pyo3(get)] - pub growth_short_term_investments: String, + pub growth_short_term_investments: Option, #[pyo3(get)] - pub growth_cash_and_short_term_investments: String, + pub growth_cash_and_short_term_investments: Option, #[pyo3(get)] - pub growth_net_receivables: String, + pub growth_net_receivables: Option, #[pyo3(get)] - pub growth_inventory: String, + pub growth_inventory: Option, #[pyo3(get)] - pub growth_other_current_assets: String, + pub growth_other_current_assets: Option, #[pyo3(get)] - pub growth_total_current_assets: String, + pub growth_total_current_assets: Option, #[pyo3(get)] - pub growth_property_plant_equipment_net: String, + pub growth_property_plant_equipment_net: Option, #[pyo3(get)] - pub growth_goodwill: String, + pub growth_goodwill: Option, #[pyo3(get)] - pub growth_intangible_assets: String, + pub growth_intangible_assets: Option, #[pyo3(get)] - pub growth_goodwill_and_intangible_assets: String, + pub growth_goodwill_and_intangible_assets: Option, #[pyo3(get)] - pub growth_long_term_investments: String, + pub growth_long_term_investments: Option, #[pyo3(get)] - pub growth_tax_assets: String, + pub growth_tax_assets: Option, #[pyo3(get)] - pub growth_other_non_current_assets: String, + pub growth_other_non_current_assets: Option, #[pyo3(get)] - pub growth_total_non_current_assets: String, + pub growth_total_non_current_assets: Option, #[pyo3(get)] - pub growth_other_assets: String, + pub growth_other_assets: Option, #[pyo3(get)] - pub growth_total_assets: String, + pub growth_total_assets: Option, #[pyo3(get)] - pub growth_account_payables: String, + pub growth_account_payables: Option, #[pyo3(get)] - pub growth_short_term_debt: String, + pub growth_short_term_debt: Option, #[pyo3(get)] - pub growth_tax_payables: String, + pub growth_tax_payables: Option, #[pyo3(get)] - pub growth_deferred_revenue: String, + pub growth_deferred_revenue: Option, #[pyo3(get)] - pub growth_other_current_liabilities: String, + pub growth_other_current_liabilities: Option, #[pyo3(get)] - pub growth_total_current_liabilities: String, + pub growth_total_current_liabilities: Option, #[pyo3(get)] - pub growth_long_term_debt: String, + pub growth_long_term_debt: Option, #[pyo3(get)] - pub growth_deferred_revenue_non_current: String, + pub growth_deferred_revenue_non_current: Option, #[pyo3(get)] - pub growth_deferred_tax_liabilities_non_current: String, + pub growth_deferred_tax_liabilities_non_current: Option, #[pyo3(get)] - pub growth_other_non_current_liabilities: String, + pub growth_other_non_current_liabilities: Option, #[pyo3(get)] - pub growth_total_non_current_liabilities: String, + pub growth_total_non_current_liabilities: Option, #[pyo3(get)] - pub growth_other_liabilities: String, + pub growth_other_liabilities: Option, #[pyo3(get)] - pub growth_total_liabilities: String, + pub growth_total_liabilities: Option, #[pyo3(get)] - pub growth_preferred_stock: String, + pub growth_preferred_stock: Option, #[pyo3(get)] - pub growth_common_stock: String, + pub growth_common_stock: Option, #[pyo3(get)] - pub growth_retained_earnings: String, + pub growth_retained_earnings: Option, #[pyo3(get)] - pub growth_accumulated_other_comprehensive_income_loss: String, + pub growth_accumulated_other_comprehensive_income_loss: Option, #[pyo3(get)] - pub growth_other_total_stockholders_equity: String, + pub growth_other_total_stockholders_equity: Option, #[pyo3(get)] - pub growth_total_stockholders_equity: String, + pub growth_total_stockholders_equity: Option, #[pyo3(get)] - pub growth_minority_interest: String, + pub growth_minority_interest: Option, #[pyo3(get)] - pub growth_total_equity: String, + pub growth_total_equity: Option, #[pyo3(get)] - pub growth_total_liabilities_and_stockholders_equity: String, + pub growth_total_liabilities_and_stockholders_equity: Option, #[pyo3(get)] - pub growth_total_investments: String, + pub growth_total_investments: Option, #[pyo3(get)] - pub growth_total_debt: String, + pub growth_total_debt: Option, #[pyo3(get)] - pub growth_net_debt: String, + pub growth_net_debt: Option, #[pyo3(get)] - pub growth_accounts_receivables: String, + pub growth_accounts_receivables: Option, #[pyo3(get)] - pub growth_other_receivables: String, + pub growth_other_receivables: Option, #[pyo3(get)] - pub growth_prepaids: String, + pub growth_prepaids: Option, #[pyo3(get)] - pub growth_total_payables: String, + pub growth_total_payables: Option, #[pyo3(get)] - pub growth_other_payables: String, + pub growth_other_payables: Option, #[pyo3(get)] - pub growth_accrued_expenses: String, + pub growth_accrued_expenses: Option, #[pyo3(get)] - pub growth_capital_lease_obligations_current: String, + pub growth_capital_lease_obligations_current: Option, #[pyo3(get)] - pub growth_additional_paid_in_capital: String, + pub growth_additional_paid_in_capital: Option, #[pyo3(get)] - pub growth_treasury_stock: String, + pub growth_treasury_stock: Option, } #[gen_stub_pymethods] @@ -1363,57 +1395,57 @@ impl BulkBalanceSheetStatementGrowth { #[gen_stub(override_type(type_repr = "typing.Literal[\"Q1\", \"Q2\", \"Q3\", \"Q4\", \"FY\"]", imports = ("builtins", "typing")))] period: String, reported_currency: String, - growth_cash_and_cash_equivalents: String, - growth_short_term_investments: String, - growth_cash_and_short_term_investments: String, - growth_net_receivables: String, - growth_inventory: String, - growth_other_current_assets: String, - growth_total_current_assets: String, - growth_property_plant_equipment_net: String, - growth_goodwill: String, - growth_intangible_assets: String, - growth_goodwill_and_intangible_assets: String, - growth_long_term_investments: String, - growth_tax_assets: String, - growth_other_non_current_assets: String, - growth_total_non_current_assets: String, - growth_other_assets: String, - growth_total_assets: String, - growth_account_payables: String, - growth_short_term_debt: String, - growth_tax_payables: String, - growth_deferred_revenue: String, - growth_other_current_liabilities: String, - growth_total_current_liabilities: String, - growth_long_term_debt: String, - growth_deferred_revenue_non_current: String, - growth_deferred_tax_liabilities_non_current: String, - growth_other_non_current_liabilities: String, - growth_total_non_current_liabilities: String, - growth_other_liabilities: String, - growth_total_liabilities: String, - growth_preferred_stock: String, - growth_common_stock: String, - growth_retained_earnings: String, - growth_accumulated_other_comprehensive_income_loss: String, - growth_other_total_stockholders_equity: String, - growth_total_stockholders_equity: String, - growth_minority_interest: String, - growth_total_equity: String, - growth_total_liabilities_and_stockholders_equity: String, - growth_total_investments: String, - growth_total_debt: String, - growth_net_debt: String, - growth_accounts_receivables: String, - growth_other_receivables: String, - growth_prepaids: String, - growth_total_payables: String, - growth_other_payables: String, - growth_accrued_expenses: String, - growth_capital_lease_obligations_current: String, - growth_additional_paid_in_capital: String, - growth_treasury_stock: String, + growth_cash_and_cash_equivalents: Option, + growth_short_term_investments: Option, + growth_cash_and_short_term_investments: Option, + growth_net_receivables: Option, + growth_inventory: Option, + growth_other_current_assets: Option, + growth_total_current_assets: Option, + growth_property_plant_equipment_net: Option, + growth_goodwill: Option, + growth_intangible_assets: Option, + growth_goodwill_and_intangible_assets: Option, + growth_long_term_investments: Option, + growth_tax_assets: Option, + growth_other_non_current_assets: Option, + growth_total_non_current_assets: Option, + growth_other_assets: Option, + growth_total_assets: Option, + growth_account_payables: Option, + growth_short_term_debt: Option, + growth_tax_payables: Option, + growth_deferred_revenue: Option, + growth_other_current_liabilities: Option, + growth_total_current_liabilities: Option, + growth_long_term_debt: Option, + growth_deferred_revenue_non_current: Option, + growth_deferred_tax_liabilities_non_current: Option, + growth_other_non_current_liabilities: Option, + growth_total_non_current_liabilities: Option, + growth_other_liabilities: Option, + growth_total_liabilities: Option, + growth_preferred_stock: Option, + growth_common_stock: Option, + growth_retained_earnings: Option, + growth_accumulated_other_comprehensive_income_loss: Option, + growth_other_total_stockholders_equity: Option, + growth_total_stockholders_equity: Option, + growth_minority_interest: Option, + growth_total_equity: Option, + growth_total_liabilities_and_stockholders_equity: Option, + growth_total_investments: Option, + growth_total_debt: Option, + growth_net_debt: Option, + growth_accounts_receivables: Option, + growth_other_receivables: Option, + growth_prepaids: Option, + growth_total_payables: Option, + growth_other_payables: Option, + growth_accrued_expenses: Option, + growth_capital_lease_obligations_current: Option, + growth_additional_paid_in_capital: Option, + growth_treasury_stock: Option, ) -> Self { Self { symbol, @@ -2414,79 +2446,123 @@ impl From fiscal_year: value.fiscal_year.as_str().to_owned(), period: value.period.to_string(), reported_currency: value.reported_currency.into_inner(), - growth_cash_and_cash_equivalents: value.growth_cash_and_cash_equivalents.into_inner(), - growth_short_term_investments: value.growth_short_term_investments.into_inner(), + growth_cash_and_cash_equivalents: value + .growth_cash_and_cash_equivalents + .map(|value| value.into_inner()), + growth_short_term_investments: value + .growth_short_term_investments + .map(|value| value.into_inner()), growth_cash_and_short_term_investments: value .growth_cash_and_short_term_investments - .into_inner(), - growth_net_receivables: value.growth_net_receivables.into_inner(), - growth_inventory: value.growth_inventory.into_inner(), - growth_other_current_assets: value.growth_other_current_assets.into_inner(), - growth_total_current_assets: value.growth_total_current_assets.into_inner(), + .map(|value| value.into_inner()), + growth_net_receivables: value.growth_net_receivables.map(|value| value.into_inner()), + growth_inventory: value.growth_inventory.map(|value| value.into_inner()), + growth_other_current_assets: value + .growth_other_current_assets + .map(|value| value.into_inner()), + growth_total_current_assets: value + .growth_total_current_assets + .map(|value| value.into_inner()), growth_property_plant_equipment_net: value .growth_property_plant_equipment_net - .into_inner(), - growth_goodwill: value.growth_goodwill.into_inner(), - growth_intangible_assets: value.growth_intangible_assets.into_inner(), + .map(|value| value.into_inner()), + growth_goodwill: value.growth_goodwill.map(|value| value.into_inner()), + growth_intangible_assets: value + .growth_intangible_assets + .map(|value| value.into_inner()), growth_goodwill_and_intangible_assets: value .growth_goodwill_and_intangible_assets - .into_inner(), - growth_long_term_investments: value.growth_long_term_investments.into_inner(), - growth_tax_assets: value.growth_tax_assets.into_inner(), - growth_other_non_current_assets: value.growth_other_non_current_assets.into_inner(), - growth_total_non_current_assets: value.growth_total_non_current_assets.into_inner(), - growth_other_assets: value.growth_other_assets.into_inner(), - growth_total_assets: value.growth_total_assets.into_inner(), - growth_account_payables: value.growth_account_payables.into_inner(), - growth_short_term_debt: value.growth_short_term_debt.into_inner(), - growth_tax_payables: value.growth_tax_payables.into_inner(), - growth_deferred_revenue: value.growth_deferred_revenue.into_inner(), - growth_other_current_liabilities: value.growth_other_current_liabilities.into_inner(), - growth_total_current_liabilities: value.growth_total_current_liabilities.into_inner(), - growth_long_term_debt: value.growth_long_term_debt.into_inner(), + .map(|value| value.into_inner()), + growth_long_term_investments: value + .growth_long_term_investments + .map(|value| value.into_inner()), + growth_tax_assets: value.growth_tax_assets.map(|value| value.into_inner()), + growth_other_non_current_assets: value + .growth_other_non_current_assets + .map(|value| value.into_inner()), + growth_total_non_current_assets: value + .growth_total_non_current_assets + .map(|value| value.into_inner()), + growth_other_assets: value.growth_other_assets.map(|value| value.into_inner()), + growth_total_assets: value.growth_total_assets.map(|value| value.into_inner()), + growth_account_payables: value + .growth_account_payables + .map(|value| value.into_inner()), + growth_short_term_debt: value.growth_short_term_debt.map(|value| value.into_inner()), + growth_tax_payables: value.growth_tax_payables.map(|value| value.into_inner()), + growth_deferred_revenue: value + .growth_deferred_revenue + .map(|value| value.into_inner()), + growth_other_current_liabilities: value + .growth_other_current_liabilities + .map(|value| value.into_inner()), + growth_total_current_liabilities: value + .growth_total_current_liabilities + .map(|value| value.into_inner()), + growth_long_term_debt: value.growth_long_term_debt.map(|value| value.into_inner()), growth_deferred_revenue_non_current: value .growth_deferred_revenue_non_current - .into_inner(), + .map(|value| value.into_inner()), growth_deferred_tax_liabilities_non_current: value .growth_deferred_tax_liabilities_non_current - .into_inner(), + .map(|value| value.into_inner()), growth_other_non_current_liabilities: value .growth_other_non_current_liabilities - .into_inner(), + .map(|value| value.into_inner()), growth_total_non_current_liabilities: value .growth_total_non_current_liabilities - .into_inner(), - growth_other_liabilities: value.growth_other_liabilities.into_inner(), - growth_total_liabilities: value.growth_total_liabilities.into_inner(), - growth_preferred_stock: value.growth_preferred_stock.into_inner(), - growth_common_stock: value.growth_common_stock.into_inner(), - growth_retained_earnings: value.growth_retained_earnings.into_inner(), + .map(|value| value.into_inner()), + growth_other_liabilities: value + .growth_other_liabilities + .map(|value| value.into_inner()), + growth_total_liabilities: value + .growth_total_liabilities + .map(|value| value.into_inner()), + growth_preferred_stock: value.growth_preferred_stock.map(|value| value.into_inner()), + growth_common_stock: value.growth_common_stock.map(|value| value.into_inner()), + growth_retained_earnings: value + .growth_retained_earnings + .map(|value| value.into_inner()), growth_accumulated_other_comprehensive_income_loss: value .growth_accumulated_other_comprehensive_income_loss - .into_inner(), + .map(|value| value.into_inner()), growth_other_total_stockholders_equity: value .growth_other_total_stockholders_equity - .into_inner(), - growth_total_stockholders_equity: value.growth_total_stockholders_equity.into_inner(), - growth_minority_interest: value.growth_minority_interest.into_inner(), - growth_total_equity: value.growth_total_equity.into_inner(), + .map(|value| value.into_inner()), + growth_total_stockholders_equity: value + .growth_total_stockholders_equity + .map(|value| value.into_inner()), + growth_minority_interest: value + .growth_minority_interest + .map(|value| value.into_inner()), + growth_total_equity: value.growth_total_equity.map(|value| value.into_inner()), growth_total_liabilities_and_stockholders_equity: value .growth_total_liabilities_and_stockholders_equity - .into_inner(), - growth_total_investments: value.growth_total_investments.into_inner(), - growth_total_debt: value.growth_total_debt.into_inner(), - growth_net_debt: value.growth_net_debt.into_inner(), - growth_accounts_receivables: value.growth_accounts_receivables.into_inner(), - growth_other_receivables: value.growth_other_receivables.into_inner(), - growth_prepaids: value.growth_prepaids.into_inner(), - growth_total_payables: value.growth_total_payables.into_inner(), - growth_other_payables: value.growth_other_payables.into_inner(), - growth_accrued_expenses: value.growth_accrued_expenses.into_inner(), + .map(|value| value.into_inner()), + growth_total_investments: value + .growth_total_investments + .map(|value| value.into_inner()), + growth_total_debt: value.growth_total_debt.map(|value| value.into_inner()), + growth_net_debt: value.growth_net_debt.map(|value| value.into_inner()), + growth_accounts_receivables: value + .growth_accounts_receivables + .map(|value| value.into_inner()), + growth_other_receivables: value + .growth_other_receivables + .map(|value| value.into_inner()), + growth_prepaids: value.growth_prepaids.map(|value| value.into_inner()), + growth_total_payables: value.growth_total_payables.map(|value| value.into_inner()), + growth_other_payables: value.growth_other_payables.map(|value| value.into_inner()), + growth_accrued_expenses: value + .growth_accrued_expenses + .map(|value| value.into_inner()), growth_capital_lease_obligations_current: value .growth_capital_lease_obligations_current - .into_inner(), - growth_additional_paid_in_capital: value.growth_additional_paid_in_capital.into_inner(), - growth_treasury_stock: value.growth_treasury_stock.into_inner(), + .map(|value| value.into_inner()), + growth_additional_paid_in_capital: value + .growth_additional_paid_in_capital + .map(|value| value.into_inner()), + growth_treasury_stock: value.growth_treasury_stock.map(|value| value.into_inner()), } } } diff --git a/crates/fmp-py/src/models/bulk/cash_flow.rs b/crates/fmp-py/src/models/bulk/cash_flow.rs index 0735f241..eb644aa5 100644 --- a/crates/fmp-py/src/models/bulk/cash_flow.rs +++ b/crates/fmp-py/src/models/bulk/cash_flow.rs @@ -25,83 +25,83 @@ pub(crate) struct BulkCashFlowStatement { pub fiscal_year: String, pub period: String, #[pyo3(get)] - pub net_income: String, + pub net_income: Option, #[pyo3(get)] - pub depreciation_and_amortization: String, + pub depreciation_and_amortization: Option, #[pyo3(get)] - pub deferred_income_tax: String, + pub deferred_income_tax: Option, #[pyo3(get)] - pub stock_based_compensation: String, + pub stock_based_compensation: Option, #[pyo3(get)] - pub change_in_working_capital: String, + pub change_in_working_capital: Option, #[pyo3(get)] - pub accounts_receivables: String, + pub accounts_receivables: Option, #[pyo3(get)] - pub inventory: String, + pub inventory: Option, #[pyo3(get)] - pub accounts_payables: String, + pub accounts_payables: Option, #[pyo3(get)] - pub other_working_capital: String, + pub other_working_capital: Option, #[pyo3(get)] - pub other_non_cash_items: String, + pub other_non_cash_items: Option, #[pyo3(get)] - pub net_cash_provided_by_operating_activities: String, + pub net_cash_provided_by_operating_activities: Option, #[pyo3(get)] - pub investments_in_property_plant_and_equipment: String, + pub investments_in_property_plant_and_equipment: Option, #[pyo3(get)] - pub acquisitions_net: String, + pub acquisitions_net: Option, #[pyo3(get)] - pub purchases_of_investments: String, + pub purchases_of_investments: Option, #[pyo3(get)] - pub sales_maturities_of_investments: String, + pub sales_maturities_of_investments: Option, #[pyo3(get)] - pub other_investing_activities: String, + pub other_investing_activities: Option, #[pyo3(get)] - pub net_cash_provided_by_investing_activities: String, + pub net_cash_provided_by_investing_activities: Option, #[pyo3(get)] - pub net_debt_issuance: String, + pub net_debt_issuance: Option, #[pyo3(get)] - pub long_term_net_debt_issuance: String, + pub long_term_net_debt_issuance: Option, #[pyo3(get)] - pub short_term_net_debt_issuance: String, + pub short_term_net_debt_issuance: Option, #[pyo3(get)] - pub net_stock_issuance: String, + pub net_stock_issuance: Option, #[pyo3(get)] - pub net_common_stock_issuance: String, + pub net_common_stock_issuance: Option, #[pyo3(get)] - pub common_stock_issuance: String, + pub common_stock_issuance: Option, #[pyo3(get)] - pub common_stock_repurchased: String, + pub common_stock_repurchased: Option, #[pyo3(get)] - pub net_preferred_stock_issuance: String, + pub net_preferred_stock_issuance: Option, #[pyo3(get)] - pub net_dividends_paid: String, + pub net_dividends_paid: Option, #[pyo3(get)] - pub common_dividends_paid: String, + pub common_dividends_paid: Option, #[pyo3(get)] - pub preferred_dividends_paid: String, + pub preferred_dividends_paid: Option, #[pyo3(get)] - pub other_financing_activities: String, + pub other_financing_activities: Option, #[pyo3(get)] - pub net_cash_provided_by_financing_activities: String, + pub net_cash_provided_by_financing_activities: Option, #[pyo3(get)] - pub effect_of_forex_changes_on_cash: String, + pub effect_of_forex_changes_on_cash: Option, #[pyo3(get)] - pub net_change_in_cash: String, + pub net_change_in_cash: Option, #[pyo3(get)] - pub cash_at_end_of_period: String, + pub cash_at_end_of_period: Option, #[pyo3(get)] - pub cash_at_beginning_of_period: String, + pub cash_at_beginning_of_period: Option, #[pyo3(get)] - pub operating_cash_flow: String, + pub operating_cash_flow: Option, #[pyo3(get)] - pub capital_expenditure: String, + pub capital_expenditure: Option, #[pyo3(get)] - pub free_cash_flow: String, + pub free_cash_flow: Option, #[pyo3(get)] - pub income_taxes_paid: String, + pub income_taxes_paid: Option, #[pyo3(get)] - pub interest_paid: String, + pub interest_paid: Option, } #[gen_stub_pymethods] @@ -121,45 +121,45 @@ impl BulkCashFlowStatement { fiscal_year: String, #[gen_stub(override_type(type_repr = "typing.Literal[\"Q1\", \"Q2\", \"Q3\", \"Q4\", \"FY\"]", imports = ("builtins", "typing")))] period: String, - net_income: String, - depreciation_and_amortization: String, - deferred_income_tax: String, - stock_based_compensation: String, - change_in_working_capital: String, - accounts_receivables: String, - inventory: String, - accounts_payables: String, - other_working_capital: String, - other_non_cash_items: String, - net_cash_provided_by_operating_activities: String, - investments_in_property_plant_and_equipment: String, - acquisitions_net: String, - purchases_of_investments: String, - sales_maturities_of_investments: String, - other_investing_activities: String, - net_cash_provided_by_investing_activities: String, - net_debt_issuance: String, - long_term_net_debt_issuance: String, - short_term_net_debt_issuance: String, - net_stock_issuance: String, - net_common_stock_issuance: String, - common_stock_issuance: String, - common_stock_repurchased: String, - net_preferred_stock_issuance: String, - net_dividends_paid: String, - common_dividends_paid: String, - preferred_dividends_paid: String, - other_financing_activities: String, - net_cash_provided_by_financing_activities: String, - effect_of_forex_changes_on_cash: String, - net_change_in_cash: String, - cash_at_end_of_period: String, - cash_at_beginning_of_period: String, - operating_cash_flow: String, - capital_expenditure: String, - free_cash_flow: String, - income_taxes_paid: String, - interest_paid: String, + net_income: Option, + depreciation_and_amortization: Option, + deferred_income_tax: Option, + stock_based_compensation: Option, + change_in_working_capital: Option, + accounts_receivables: Option, + inventory: Option, + accounts_payables: Option, + other_working_capital: Option, + other_non_cash_items: Option, + net_cash_provided_by_operating_activities: Option, + investments_in_property_plant_and_equipment: Option, + acquisitions_net: Option, + purchases_of_investments: Option, + sales_maturities_of_investments: Option, + other_investing_activities: Option, + net_cash_provided_by_investing_activities: Option, + net_debt_issuance: Option, + long_term_net_debt_issuance: Option, + short_term_net_debt_issuance: Option, + net_stock_issuance: Option, + net_common_stock_issuance: Option, + common_stock_issuance: Option, + common_stock_repurchased: Option, + net_preferred_stock_issuance: Option, + net_dividends_paid: Option, + common_dividends_paid: Option, + preferred_dividends_paid: Option, + other_financing_activities: Option, + net_cash_provided_by_financing_activities: Option, + effect_of_forex_changes_on_cash: Option, + net_change_in_cash: Option, + cash_at_end_of_period: Option, + cash_at_beginning_of_period: Option, + operating_cash_flow: Option, + capital_expenditure: Option, + free_cash_flow: Option, + income_taxes_paid: Option, + interest_paid: Option, ) -> Self { Self { date, @@ -937,53 +937,83 @@ impl From for BulkCashFlowStatem accepted_date: value.accepted_date.into_inner(), fiscal_year: value.fiscal_year.as_str().to_owned(), period: value.period.to_string(), - net_income: value.net_income.into_inner(), - depreciation_and_amortization: value.depreciation_and_amortization.into_inner(), - deferred_income_tax: value.deferred_income_tax.into_inner(), - stock_based_compensation: value.stock_based_compensation.into_inner(), - change_in_working_capital: value.change_in_working_capital.into_inner(), - accounts_receivables: value.accounts_receivables.into_inner(), - inventory: value.inventory.into_inner(), - accounts_payables: value.accounts_payables.into_inner(), - other_working_capital: value.other_working_capital.into_inner(), - other_non_cash_items: value.other_non_cash_items.into_inner(), + net_income: value.net_income.map(|value| value.into_inner()), + depreciation_and_amortization: value + .depreciation_and_amortization + .map(|value| value.into_inner()), + deferred_income_tax: value.deferred_income_tax.map(|value| value.into_inner()), + stock_based_compensation: value + .stock_based_compensation + .map(|value| value.into_inner()), + change_in_working_capital: value + .change_in_working_capital + .map(|value| value.into_inner()), + accounts_receivables: value.accounts_receivables.map(|value| value.into_inner()), + inventory: value.inventory.map(|value| value.into_inner()), + accounts_payables: value.accounts_payables.map(|value| value.into_inner()), + other_working_capital: value.other_working_capital.map(|value| value.into_inner()), + other_non_cash_items: value.other_non_cash_items.map(|value| value.into_inner()), net_cash_provided_by_operating_activities: value .net_cash_provided_by_operating_activities - .into_inner(), + .map(|value| value.into_inner()), investments_in_property_plant_and_equipment: value .investments_in_property_plant_and_equipment - .into_inner(), - acquisitions_net: value.acquisitions_net.into_inner(), - purchases_of_investments: value.purchases_of_investments.into_inner(), - sales_maturities_of_investments: value.sales_maturities_of_investments.into_inner(), - other_investing_activities: value.other_investing_activities.into_inner(), + .map(|value| value.into_inner()), + acquisitions_net: value.acquisitions_net.map(|value| value.into_inner()), + purchases_of_investments: value + .purchases_of_investments + .map(|value| value.into_inner()), + sales_maturities_of_investments: value + .sales_maturities_of_investments + .map(|value| value.into_inner()), + other_investing_activities: value + .other_investing_activities + .map(|value| value.into_inner()), net_cash_provided_by_investing_activities: value .net_cash_provided_by_investing_activities - .into_inner(), - net_debt_issuance: value.net_debt_issuance.into_inner(), - long_term_net_debt_issuance: value.long_term_net_debt_issuance.into_inner(), - short_term_net_debt_issuance: value.short_term_net_debt_issuance.into_inner(), - net_stock_issuance: value.net_stock_issuance.into_inner(), - net_common_stock_issuance: value.net_common_stock_issuance.into_inner(), - common_stock_issuance: value.common_stock_issuance.into_inner(), - common_stock_repurchased: value.common_stock_repurchased.into_inner(), - net_preferred_stock_issuance: value.net_preferred_stock_issuance.into_inner(), - net_dividends_paid: value.net_dividends_paid.into_inner(), - common_dividends_paid: value.common_dividends_paid.into_inner(), - preferred_dividends_paid: value.preferred_dividends_paid.into_inner(), - other_financing_activities: value.other_financing_activities.into_inner(), + .map(|value| value.into_inner()), + net_debt_issuance: value.net_debt_issuance.map(|value| value.into_inner()), + long_term_net_debt_issuance: value + .long_term_net_debt_issuance + .map(|value| value.into_inner()), + short_term_net_debt_issuance: value + .short_term_net_debt_issuance + .map(|value| value.into_inner()), + net_stock_issuance: value.net_stock_issuance.map(|value| value.into_inner()), + net_common_stock_issuance: value + .net_common_stock_issuance + .map(|value| value.into_inner()), + common_stock_issuance: value.common_stock_issuance.map(|value| value.into_inner()), + common_stock_repurchased: value + .common_stock_repurchased + .map(|value| value.into_inner()), + net_preferred_stock_issuance: value + .net_preferred_stock_issuance + .map(|value| value.into_inner()), + net_dividends_paid: value.net_dividends_paid.map(|value| value.into_inner()), + common_dividends_paid: value.common_dividends_paid.map(|value| value.into_inner()), + preferred_dividends_paid: value + .preferred_dividends_paid + .map(|value| value.into_inner()), + other_financing_activities: value + .other_financing_activities + .map(|value| value.into_inner()), net_cash_provided_by_financing_activities: value .net_cash_provided_by_financing_activities - .into_inner(), - effect_of_forex_changes_on_cash: value.effect_of_forex_changes_on_cash.into_inner(), - net_change_in_cash: value.net_change_in_cash.into_inner(), - cash_at_end_of_period: value.cash_at_end_of_period.into_inner(), - cash_at_beginning_of_period: value.cash_at_beginning_of_period.into_inner(), - operating_cash_flow: value.operating_cash_flow.into_inner(), - capital_expenditure: value.capital_expenditure.into_inner(), - free_cash_flow: value.free_cash_flow.into_inner(), - income_taxes_paid: value.income_taxes_paid.into_inner(), - interest_paid: value.interest_paid.into_inner(), + .map(|value| value.into_inner()), + effect_of_forex_changes_on_cash: value + .effect_of_forex_changes_on_cash + .map(|value| value.into_inner()), + net_change_in_cash: value.net_change_in_cash.map(|value| value.into_inner()), + cash_at_end_of_period: value.cash_at_end_of_period.map(|value| value.into_inner()), + cash_at_beginning_of_period: value + .cash_at_beginning_of_period + .map(|value| value.into_inner()), + operating_cash_flow: value.operating_cash_flow.map(|value| value.into_inner()), + capital_expenditure: value.capital_expenditure.map(|value| value.into_inner()), + free_cash_flow: value.free_cash_flow.map(|value| value.into_inner()), + income_taxes_paid: value.income_taxes_paid.map(|value| value.into_inner()), + interest_paid: value.interest_paid.map(|value| value.into_inner()), } } } @@ -1008,79 +1038,79 @@ pub(crate) struct BulkCashFlowStatementGrowth { #[pyo3(get)] pub reported_currency: String, #[pyo3(get)] - pub growth_net_income: String, + pub growth_net_income: Option, #[pyo3(get)] - pub growth_depreciation_and_amortization: String, + pub growth_depreciation_and_amortization: Option, #[pyo3(get)] - pub growth_deferred_income_tax: String, + pub growth_deferred_income_tax: Option, #[pyo3(get)] - pub growth_stock_based_compensation: String, + pub growth_stock_based_compensation: Option, #[pyo3(get)] - pub growth_change_in_working_capital: String, + pub growth_change_in_working_capital: Option, #[pyo3(get)] - pub growth_accounts_receivables: String, + pub growth_accounts_receivables: Option, #[pyo3(get)] - pub growth_inventory: String, + pub growth_inventory: Option, #[pyo3(get)] - pub growth_accounts_payables: String, + pub growth_accounts_payables: Option, #[pyo3(get)] - pub growth_other_working_capital: String, + pub growth_other_working_capital: Option, #[pyo3(get)] - pub growth_other_non_cash_items: String, + pub growth_other_non_cash_items: Option, #[pyo3(get)] - pub growth_net_cash_provided_by_operating_activities: String, + pub growth_net_cash_provided_by_operating_activities: Option, #[pyo3(get)] - pub growth_investments_in_property_plant_and_equipment: String, + pub growth_investments_in_property_plant_and_equipment: Option, #[pyo3(get)] - pub growth_acquisitions_net: String, + pub growth_acquisitions_net: Option, #[pyo3(get)] - pub growth_purchases_of_investments: String, + pub growth_purchases_of_investments: Option, #[pyo3(get)] - pub growth_sales_maturities_of_investments: String, + pub growth_sales_maturities_of_investments: Option, #[pyo3(get)] - pub growth_other_investing_activities: String, + pub growth_other_investing_activities: Option, #[pyo3(get)] - pub growth_net_cash_used_for_investing_activities: String, + pub growth_net_cash_used_for_investing_activities: Option, #[pyo3(get)] - pub growth_debt_repayment: String, + pub growth_debt_repayment: Option, #[pyo3(get)] - pub growth_common_stock_issued: String, + pub growth_common_stock_issued: Option, #[pyo3(get)] - pub growth_common_stock_repurchased: String, + pub growth_common_stock_repurchased: Option, #[pyo3(get)] - pub growth_dividends_paid: String, + pub growth_dividends_paid: Option, #[pyo3(get)] - pub growth_other_financing_activities: String, + pub growth_other_financing_activities: Option, #[pyo3(get)] - pub growth_net_cash_used_provided_by_financing_activities: String, + pub growth_net_cash_used_provided_by_financing_activities: Option, #[pyo3(get)] - pub growth_effect_of_forex_changes_on_cash: String, + pub growth_effect_of_forex_changes_on_cash: Option, #[pyo3(get)] - pub growth_net_change_in_cash: String, + pub growth_net_change_in_cash: Option, #[pyo3(get)] - pub growth_cash_at_end_of_period: String, + pub growth_cash_at_end_of_period: Option, #[pyo3(get)] - pub growth_cash_at_beginning_of_period: String, + pub growth_cash_at_beginning_of_period: Option, #[pyo3(get)] - pub growth_operating_cash_flow: String, + pub growth_operating_cash_flow: Option, #[pyo3(get)] - pub growth_capital_expenditure: String, + pub growth_capital_expenditure: Option, #[pyo3(get)] - pub growth_free_cash_flow: String, + pub growth_free_cash_flow: Option, #[pyo3(get)] - pub growth_net_debt_issuance: String, + pub growth_net_debt_issuance: Option, #[pyo3(get)] - pub growth_long_term_net_debt_issuance: String, + pub growth_long_term_net_debt_issuance: Option, #[pyo3(get)] - pub growth_short_term_net_debt_issuance: String, + pub growth_short_term_net_debt_issuance: Option, #[pyo3(get)] - pub growth_net_stock_issuance: String, + pub growth_net_stock_issuance: Option, #[pyo3(get)] - pub growth_preferred_dividends_paid: String, + pub growth_preferred_dividends_paid: Option, #[pyo3(get)] - pub growth_income_taxes_paid: String, + pub growth_income_taxes_paid: Option, #[pyo3(get)] - pub growth_interest_paid: String, + pub growth_interest_paid: Option, } #[gen_stub_pymethods] @@ -1097,43 +1127,43 @@ impl BulkCashFlowStatementGrowth { #[gen_stub(override_type(type_repr = "typing.Literal[\"Q1\", \"Q2\", \"Q3\", \"Q4\", \"FY\"]", imports = ("builtins", "typing")))] period: String, reported_currency: String, - growth_net_income: String, - growth_depreciation_and_amortization: String, - growth_deferred_income_tax: String, - growth_stock_based_compensation: String, - growth_change_in_working_capital: String, - growth_accounts_receivables: String, - growth_inventory: String, - growth_accounts_payables: String, - growth_other_working_capital: String, - growth_other_non_cash_items: String, - growth_net_cash_provided_by_operating_activities: String, - growth_investments_in_property_plant_and_equipment: String, - growth_acquisitions_net: String, - growth_purchases_of_investments: String, - growth_sales_maturities_of_investments: String, - growth_other_investing_activities: String, - growth_net_cash_used_for_investing_activities: String, - growth_debt_repayment: String, - growth_common_stock_issued: String, - growth_common_stock_repurchased: String, - growth_dividends_paid: String, - growth_other_financing_activities: String, - growth_net_cash_used_provided_by_financing_activities: String, - growth_effect_of_forex_changes_on_cash: String, - growth_net_change_in_cash: String, - growth_cash_at_end_of_period: String, - growth_cash_at_beginning_of_period: String, - growth_operating_cash_flow: String, - growth_capital_expenditure: String, - growth_free_cash_flow: String, - growth_net_debt_issuance: String, - growth_long_term_net_debt_issuance: String, - growth_short_term_net_debt_issuance: String, - growth_net_stock_issuance: String, - growth_preferred_dividends_paid: String, - growth_income_taxes_paid: String, - growth_interest_paid: String, + growth_net_income: Option, + growth_depreciation_and_amortization: Option, + growth_deferred_income_tax: Option, + growth_stock_based_compensation: Option, + growth_change_in_working_capital: Option, + growth_accounts_receivables: Option, + growth_inventory: Option, + growth_accounts_payables: Option, + growth_other_working_capital: Option, + growth_other_non_cash_items: Option, + growth_net_cash_provided_by_operating_activities: Option, + growth_investments_in_property_plant_and_equipment: Option, + growth_acquisitions_net: Option, + growth_purchases_of_investments: Option, + growth_sales_maturities_of_investments: Option, + growth_other_investing_activities: Option, + growth_net_cash_used_for_investing_activities: Option, + growth_debt_repayment: Option, + growth_common_stock_issued: Option, + growth_common_stock_repurchased: Option, + growth_dividends_paid: Option, + growth_other_financing_activities: Option, + growth_net_cash_used_provided_by_financing_activities: Option, + growth_effect_of_forex_changes_on_cash: Option, + growth_net_change_in_cash: Option, + growth_cash_at_end_of_period: Option, + growth_cash_at_beginning_of_period: Option, + growth_operating_cash_flow: Option, + growth_capital_expenditure: Option, + growth_free_cash_flow: Option, + growth_net_debt_issuance: Option, + growth_long_term_net_debt_issuance: Option, + growth_short_term_net_debt_issuance: Option, + growth_net_stock_issuance: Option, + growth_preferred_dividends_paid: Option, + growth_income_taxes_paid: Option, + growth_interest_paid: Option, ) -> Self { Self { symbol, @@ -1933,63 +1963,105 @@ impl From for BulkCashFlow fiscal_year: value.fiscal_year.as_str().to_owned(), period: value.period.to_string(), reported_currency: value.reported_currency.into_inner(), - growth_net_income: value.growth_net_income.into_inner(), + growth_net_income: value.growth_net_income.map(|value| value.into_inner()), growth_depreciation_and_amortization: value .growth_depreciation_and_amortization - .into_inner(), - growth_deferred_income_tax: value.growth_deferred_income_tax.into_inner(), - growth_stock_based_compensation: value.growth_stock_based_compensation.into_inner(), - growth_change_in_working_capital: value.growth_change_in_working_capital.into_inner(), - growth_accounts_receivables: value.growth_accounts_receivables.into_inner(), - growth_inventory: value.growth_inventory.into_inner(), - growth_accounts_payables: value.growth_accounts_payables.into_inner(), - growth_other_working_capital: value.growth_other_working_capital.into_inner(), - growth_other_non_cash_items: value.growth_other_non_cash_items.into_inner(), + .map(|value| value.into_inner()), + growth_deferred_income_tax: value + .growth_deferred_income_tax + .map(|value| value.into_inner()), + growth_stock_based_compensation: value + .growth_stock_based_compensation + .map(|value| value.into_inner()), + growth_change_in_working_capital: value + .growth_change_in_working_capital + .map(|value| value.into_inner()), + growth_accounts_receivables: value + .growth_accounts_receivables + .map(|value| value.into_inner()), + growth_inventory: value.growth_inventory.map(|value| value.into_inner()), + growth_accounts_payables: value + .growth_accounts_payables + .map(|value| value.into_inner()), + growth_other_working_capital: value + .growth_other_working_capital + .map(|value| value.into_inner()), + growth_other_non_cash_items: value + .growth_other_non_cash_items + .map(|value| value.into_inner()), growth_net_cash_provided_by_operating_activities: value .growth_net_cash_provided_by_operating_activities - .into_inner(), + .map(|value| value.into_inner()), growth_investments_in_property_plant_and_equipment: value .growth_investments_in_property_plant_and_equipment - .into_inner(), - growth_acquisitions_net: value.growth_acquisitions_net.into_inner(), - growth_purchases_of_investments: value.growth_purchases_of_investments.into_inner(), + .map(|value| value.into_inner()), + growth_acquisitions_net: value + .growth_acquisitions_net + .map(|value| value.into_inner()), + growth_purchases_of_investments: value + .growth_purchases_of_investments + .map(|value| value.into_inner()), growth_sales_maturities_of_investments: value .growth_sales_maturities_of_investments - .into_inner(), - growth_other_investing_activities: value.growth_other_investing_activities.into_inner(), + .map(|value| value.into_inner()), + growth_other_investing_activities: value + .growth_other_investing_activities + .map(|value| value.into_inner()), growth_net_cash_used_for_investing_activities: value .growth_net_cash_used_for_investing_activities - .into_inner(), - growth_debt_repayment: value.growth_debt_repayment.into_inner(), - growth_common_stock_issued: value.growth_common_stock_issued.into_inner(), - growth_common_stock_repurchased: value.growth_common_stock_repurchased.into_inner(), - growth_dividends_paid: value.growth_dividends_paid.into_inner(), - growth_other_financing_activities: value.growth_other_financing_activities.into_inner(), + .map(|value| value.into_inner()), + growth_debt_repayment: value.growth_debt_repayment.map(|value| value.into_inner()), + growth_common_stock_issued: value + .growth_common_stock_issued + .map(|value| value.into_inner()), + growth_common_stock_repurchased: value + .growth_common_stock_repurchased + .map(|value| value.into_inner()), + growth_dividends_paid: value.growth_dividends_paid.map(|value| value.into_inner()), + growth_other_financing_activities: value + .growth_other_financing_activities + .map(|value| value.into_inner()), growth_net_cash_used_provided_by_financing_activities: value .growth_net_cash_used_provided_by_financing_activities - .into_inner(), + .map(|value| value.into_inner()), growth_effect_of_forex_changes_on_cash: value .growth_effect_of_forex_changes_on_cash - .into_inner(), - growth_net_change_in_cash: value.growth_net_change_in_cash.into_inner(), - growth_cash_at_end_of_period: value.growth_cash_at_end_of_period.into_inner(), + .map(|value| value.into_inner()), + growth_net_change_in_cash: value + .growth_net_change_in_cash + .map(|value| value.into_inner()), + growth_cash_at_end_of_period: value + .growth_cash_at_end_of_period + .map(|value| value.into_inner()), growth_cash_at_beginning_of_period: value .growth_cash_at_beginning_of_period - .into_inner(), - growth_operating_cash_flow: value.growth_operating_cash_flow.into_inner(), - growth_capital_expenditure: value.growth_capital_expenditure.into_inner(), - growth_free_cash_flow: value.growth_free_cash_flow.into_inner(), - growth_net_debt_issuance: value.growth_net_debt_issuance.into_inner(), + .map(|value| value.into_inner()), + growth_operating_cash_flow: value + .growth_operating_cash_flow + .map(|value| value.into_inner()), + growth_capital_expenditure: value + .growth_capital_expenditure + .map(|value| value.into_inner()), + growth_free_cash_flow: value.growth_free_cash_flow.map(|value| value.into_inner()), + growth_net_debt_issuance: value + .growth_net_debt_issuance + .map(|value| value.into_inner()), growth_long_term_net_debt_issuance: value .growth_long_term_net_debt_issuance - .into_inner(), + .map(|value| value.into_inner()), growth_short_term_net_debt_issuance: value .growth_short_term_net_debt_issuance - .into_inner(), - growth_net_stock_issuance: value.growth_net_stock_issuance.into_inner(), - growth_preferred_dividends_paid: value.growth_preferred_dividends_paid.into_inner(), - growth_income_taxes_paid: value.growth_income_taxes_paid.into_inner(), - growth_interest_paid: value.growth_interest_paid.into_inner(), + .map(|value| value.into_inner()), + growth_net_stock_issuance: value + .growth_net_stock_issuance + .map(|value| value.into_inner()), + growth_preferred_dividends_paid: value + .growth_preferred_dividends_paid + .map(|value| value.into_inner()), + growth_income_taxes_paid: value + .growth_income_taxes_paid + .map(|value| value.into_inner()), + growth_interest_paid: value.growth_interest_paid.map(|value| value.into_inner()), } } } diff --git a/crates/fmp-py/src/models/bulk/eod.rs b/crates/fmp-py/src/models/bulk/eod.rs index 6e35029f..d06b411a 100644 --- a/crates/fmp-py/src/models/bulk/eod.rs +++ b/crates/fmp-py/src/models/bulk/eod.rs @@ -14,17 +14,17 @@ pub(crate) struct BulkEodBar { #[pyo3(get)] pub date: ::chrono::NaiveDate, #[pyo3(get)] - pub open: String, + pub open: Option, #[pyo3(get)] - pub low: String, + pub low: Option, #[pyo3(get)] - pub high: String, + pub high: Option, #[pyo3(get)] - pub close: String, + pub close: Option, #[pyo3(get)] - pub adj_close: String, + pub adj_close: Option, #[pyo3(get)] - pub volume: String, + pub volume: Option, } #[gen_stub_pymethods] @@ -37,12 +37,12 @@ impl BulkEodBar { fn new( symbol: String, date: ::chrono::NaiveDate, - open: String, - low: String, - high: String, - close: String, - adj_close: String, - volume: String, + open: Option, + low: Option, + high: Option, + close: Option, + adj_close: Option, + volume: Option, ) -> Self { Self { symbol, @@ -156,12 +156,12 @@ impl From for BulkEodBar { Self { symbol: value.symbol.into_inner(), date: value.date.into_inner(), - open: value.open.into_inner(), - low: value.low.into_inner(), - high: value.high.into_inner(), - close: value.close.into_inner(), - adj_close: value.adj_close.into_inner(), - volume: value.volume.into_inner(), + open: value.open.map(|value| value.into_inner()), + low: value.low.map(|value| value.into_inner()), + high: value.high.map(|value| value.into_inner()), + close: value.close.map(|value| value.into_inner()), + adj_close: value.adj_close.map(|value| value.into_inner()), + volume: value.volume.map(|value| value.into_inner()), } } } diff --git a/crates/fmp-py/src/models/bulk/income.rs b/crates/fmp-py/src/models/bulk/income.rs index d65af1ad..7a2d6f5b 100644 --- a/crates/fmp-py/src/models/bulk/income.rs +++ b/crates/fmp-py/src/models/bulk/income.rs @@ -25,67 +25,67 @@ pub(crate) struct BulkIncomeStatement { pub fiscal_year: String, pub period: String, #[pyo3(get)] - pub revenue: String, + pub revenue: Option, #[pyo3(get)] - pub cost_of_revenue: String, + pub cost_of_revenue: Option, #[pyo3(get)] - pub gross_profit: String, + pub gross_profit: Option, #[pyo3(get)] - pub research_and_development_expenses: String, + pub research_and_development_expenses: Option, #[pyo3(get)] - pub general_and_administrative_expenses: String, + pub general_and_administrative_expenses: Option, #[pyo3(get)] - pub selling_and_marketing_expenses: String, + pub selling_and_marketing_expenses: Option, #[pyo3(get)] - pub selling_general_and_administrative_expenses: String, + pub selling_general_and_administrative_expenses: Option, #[pyo3(get)] - pub other_expenses: String, + pub other_expenses: Option, #[pyo3(get)] - pub operating_expenses: String, + pub operating_expenses: Option, #[pyo3(get)] - pub cost_and_expenses: String, + pub cost_and_expenses: Option, #[pyo3(get)] - pub net_interest_income: String, + pub net_interest_income: Option, #[pyo3(get)] - pub interest_income: String, + pub interest_income: Option, #[pyo3(get)] - pub interest_expense: String, + pub interest_expense: Option, #[pyo3(get)] - pub depreciation_and_amortization: String, + pub depreciation_and_amortization: Option, #[pyo3(get)] - pub ebitda: String, + pub ebitda: Option, #[pyo3(get)] - pub ebit: String, + pub ebit: Option, #[pyo3(get)] - pub non_operating_income_excluding_interest: String, + pub non_operating_income_excluding_interest: Option, #[pyo3(get)] - pub operating_income: String, + pub operating_income: Option, #[pyo3(get)] - pub total_other_income_expenses_net: String, + pub total_other_income_expenses_net: Option, #[pyo3(get)] - pub income_before_tax: String, + pub income_before_tax: Option, #[pyo3(get)] - pub income_tax_expense: String, + pub income_tax_expense: Option, #[pyo3(get)] - pub net_income_from_continuing_operations: String, + pub net_income_from_continuing_operations: Option, #[pyo3(get)] - pub net_income_from_discontinued_operations: String, + pub net_income_from_discontinued_operations: Option, #[pyo3(get)] - pub other_adjustments_to_net_income: String, + pub other_adjustments_to_net_income: Option, #[pyo3(get)] - pub net_income: String, + pub net_income: Option, #[pyo3(get)] - pub net_income_deductions: String, + pub net_income_deductions: Option, #[pyo3(get)] - pub bottom_line_net_income: String, + pub bottom_line_net_income: Option, #[pyo3(get)] - pub eps: String, + pub eps: Option, #[pyo3(get)] - pub eps_diluted: String, + pub eps_diluted: Option, #[pyo3(get)] - pub weighted_average_shs_out: String, + pub weighted_average_shs_out: Option, #[pyo3(get)] - pub weighted_average_shs_out_dil: String, + pub weighted_average_shs_out_dil: Option, } #[gen_stub_pymethods] @@ -105,37 +105,37 @@ impl BulkIncomeStatement { fiscal_year: String, #[gen_stub(override_type(type_repr = "typing.Literal[\"Q1\", \"Q2\", \"Q3\", \"Q4\", \"FY\"]", imports = ("builtins", "typing")))] period: String, - revenue: String, - cost_of_revenue: String, - gross_profit: String, - research_and_development_expenses: String, - general_and_administrative_expenses: String, - selling_and_marketing_expenses: String, - selling_general_and_administrative_expenses: String, - other_expenses: String, - operating_expenses: String, - cost_and_expenses: String, - net_interest_income: String, - interest_income: String, - interest_expense: String, - depreciation_and_amortization: String, - ebitda: String, - ebit: String, - non_operating_income_excluding_interest: String, - operating_income: String, - total_other_income_expenses_net: String, - income_before_tax: String, - income_tax_expense: String, - net_income_from_continuing_operations: String, - net_income_from_discontinued_operations: String, - other_adjustments_to_net_income: String, - net_income: String, - net_income_deductions: String, - bottom_line_net_income: String, - eps: String, - eps_diluted: String, - weighted_average_shs_out: String, - weighted_average_shs_out_dil: String, + revenue: Option, + cost_of_revenue: Option, + gross_profit: Option, + research_and_development_expenses: Option, + general_and_administrative_expenses: Option, + selling_and_marketing_expenses: Option, + selling_general_and_administrative_expenses: Option, + other_expenses: Option, + operating_expenses: Option, + cost_and_expenses: Option, + net_interest_income: Option, + interest_income: Option, + interest_expense: Option, + depreciation_and_amortization: Option, + ebitda: Option, + ebit: Option, + non_operating_income_excluding_interest: Option, + operating_income: Option, + total_other_income_expenses_net: Option, + income_before_tax: Option, + income_tax_expense: Option, + net_income_from_continuing_operations: Option, + net_income_from_discontinued_operations: Option, + other_adjustments_to_net_income: Option, + net_income: Option, + net_income_deductions: Option, + bottom_line_net_income: Option, + eps: Option, + eps_diluted: Option, + weighted_average_shs_out: Option, + weighted_average_shs_out_dil: Option, ) -> Self { Self { date, @@ -764,47 +764,61 @@ impl From for BulkIncomeStatement accepted_date: value.accepted_date.into_inner(), fiscal_year: value.fiscal_year.as_str().to_owned(), period: value.period.to_string(), - revenue: value.revenue.into_inner(), - cost_of_revenue: value.cost_of_revenue.into_inner(), - gross_profit: value.gross_profit.into_inner(), - research_and_development_expenses: value.research_and_development_expenses.into_inner(), + revenue: value.revenue.map(|value| value.into_inner()), + cost_of_revenue: value.cost_of_revenue.map(|value| value.into_inner()), + gross_profit: value.gross_profit.map(|value| value.into_inner()), + research_and_development_expenses: value + .research_and_development_expenses + .map(|value| value.into_inner()), general_and_administrative_expenses: value .general_and_administrative_expenses - .into_inner(), - selling_and_marketing_expenses: value.selling_and_marketing_expenses.into_inner(), + .map(|value| value.into_inner()), + selling_and_marketing_expenses: value + .selling_and_marketing_expenses + .map(|value| value.into_inner()), selling_general_and_administrative_expenses: value .selling_general_and_administrative_expenses - .into_inner(), - other_expenses: value.other_expenses.into_inner(), - operating_expenses: value.operating_expenses.into_inner(), - cost_and_expenses: value.cost_and_expenses.into_inner(), - net_interest_income: value.net_interest_income.into_inner(), - interest_income: value.interest_income.into_inner(), - interest_expense: value.interest_expense.into_inner(), - depreciation_and_amortization: value.depreciation_and_amortization.into_inner(), - ebitda: value.ebitda.into_inner(), - ebit: value.ebit.into_inner(), + .map(|value| value.into_inner()), + other_expenses: value.other_expenses.map(|value| value.into_inner()), + operating_expenses: value.operating_expenses.map(|value| value.into_inner()), + cost_and_expenses: value.cost_and_expenses.map(|value| value.into_inner()), + net_interest_income: value.net_interest_income.map(|value| value.into_inner()), + interest_income: value.interest_income.map(|value| value.into_inner()), + interest_expense: value.interest_expense.map(|value| value.into_inner()), + depreciation_and_amortization: value + .depreciation_and_amortization + .map(|value| value.into_inner()), + ebitda: value.ebitda.map(|value| value.into_inner()), + ebit: value.ebit.map(|value| value.into_inner()), non_operating_income_excluding_interest: value .non_operating_income_excluding_interest - .into_inner(), - operating_income: value.operating_income.into_inner(), - total_other_income_expenses_net: value.total_other_income_expenses_net.into_inner(), - income_before_tax: value.income_before_tax.into_inner(), - income_tax_expense: value.income_tax_expense.into_inner(), + .map(|value| value.into_inner()), + operating_income: value.operating_income.map(|value| value.into_inner()), + total_other_income_expenses_net: value + .total_other_income_expenses_net + .map(|value| value.into_inner()), + income_before_tax: value.income_before_tax.map(|value| value.into_inner()), + income_tax_expense: value.income_tax_expense.map(|value| value.into_inner()), net_income_from_continuing_operations: value .net_income_from_continuing_operations - .into_inner(), + .map(|value| value.into_inner()), net_income_from_discontinued_operations: value .net_income_from_discontinued_operations - .into_inner(), - other_adjustments_to_net_income: value.other_adjustments_to_net_income.into_inner(), - net_income: value.net_income.into_inner(), - net_income_deductions: value.net_income_deductions.into_inner(), - bottom_line_net_income: value.bottom_line_net_income.into_inner(), - eps: value.eps.into_inner(), - eps_diluted: value.eps_diluted.into_inner(), - weighted_average_shs_out: value.weighted_average_shs_out.into_inner(), - weighted_average_shs_out_dil: value.weighted_average_shs_out_dil.into_inner(), + .map(|value| value.into_inner()), + other_adjustments_to_net_income: value + .other_adjustments_to_net_income + .map(|value| value.into_inner()), + net_income: value.net_income.map(|value| value.into_inner()), + net_income_deductions: value.net_income_deductions.map(|value| value.into_inner()), + bottom_line_net_income: value.bottom_line_net_income.map(|value| value.into_inner()), + eps: value.eps.map(|value| value.into_inner()), + eps_diluted: value.eps_diluted.map(|value| value.into_inner()), + weighted_average_shs_out: value + .weighted_average_shs_out + .map(|value| value.into_inner()), + weighted_average_shs_out_dil: value + .weighted_average_shs_out_dil + .map(|value| value.into_inner()), } } } @@ -829,63 +843,63 @@ pub(crate) struct BulkIncomeStatementGrowth { #[pyo3(get)] pub reported_currency: String, #[pyo3(get)] - pub growth_revenue: String, + pub growth_revenue: Option, #[pyo3(get)] - pub growth_cost_of_revenue: String, + pub growth_cost_of_revenue: Option, #[pyo3(get)] - pub growth_gross_profit: String, + pub growth_gross_profit: Option, #[pyo3(get)] - pub growth_gross_profit_ratio: String, + pub growth_gross_profit_ratio: Option, #[pyo3(get)] - pub growth_research_and_development_expenses: String, + pub growth_research_and_development_expenses: Option, #[pyo3(get)] - pub growth_general_and_administrative_expenses: String, + pub growth_general_and_administrative_expenses: Option, #[pyo3(get)] - pub growth_selling_and_marketing_expenses: String, + pub growth_selling_and_marketing_expenses: Option, #[pyo3(get)] - pub growth_other_expenses: String, + pub growth_other_expenses: Option, #[pyo3(get)] - pub growth_operating_expenses: String, + pub growth_operating_expenses: Option, #[pyo3(get)] - pub growth_cost_and_expenses: String, + pub growth_cost_and_expenses: Option, #[pyo3(get)] - pub growth_interest_income: String, + pub growth_interest_income: Option, #[pyo3(get)] - pub growth_interest_expense: String, + pub growth_interest_expense: Option, #[pyo3(get)] - pub growth_depreciation_and_amortization: String, + pub growth_depreciation_and_amortization: Option, #[pyo3(get)] - pub growth_ebitda: String, + pub growth_ebitda: Option, #[pyo3(get)] - pub growth_operating_income: String, + pub growth_operating_income: Option, #[pyo3(get)] - pub growth_income_before_tax: String, + pub growth_income_before_tax: Option, #[pyo3(get)] - pub growth_income_tax_expense: String, + pub growth_income_tax_expense: Option, #[pyo3(get)] - pub growth_net_income: String, + pub growth_net_income: Option, #[pyo3(get)] - pub growth_eps: String, + pub growth_eps: Option, #[pyo3(get)] - pub growth_eps_diluted: String, + pub growth_eps_diluted: Option, #[pyo3(get)] - pub growth_weighted_average_shs_out: String, + pub growth_weighted_average_shs_out: Option, #[pyo3(get)] - pub growth_weighted_average_shs_out_dil: String, + pub growth_weighted_average_shs_out_dil: Option, #[pyo3(get)] - pub growth_ebit: String, + pub growth_ebit: Option, #[pyo3(get)] - pub growth_non_operating_income_excluding_interest: String, + pub growth_non_operating_income_excluding_interest: Option, #[pyo3(get)] - pub growth_net_interest_income: String, + pub growth_net_interest_income: Option, #[pyo3(get)] - pub growth_total_other_income_expenses_net: String, + pub growth_total_other_income_expenses_net: Option, #[pyo3(get)] - pub growth_net_income_from_continuing_operations: String, + pub growth_net_income_from_continuing_operations: Option, #[pyo3(get)] - pub growth_other_adjustments_to_net_income: String, + pub growth_other_adjustments_to_net_income: Option, #[pyo3(get)] - pub growth_net_income_deductions: String, + pub growth_net_income_deductions: Option, } #[gen_stub_pymethods] @@ -902,35 +916,35 @@ impl BulkIncomeStatementGrowth { #[gen_stub(override_type(type_repr = "typing.Literal[\"Q1\", \"Q2\", \"Q3\", \"Q4\", \"FY\"]", imports = ("builtins", "typing")))] period: String, reported_currency: String, - growth_revenue: String, - growth_cost_of_revenue: String, - growth_gross_profit: String, - growth_gross_profit_ratio: String, - growth_research_and_development_expenses: String, - growth_general_and_administrative_expenses: String, - growth_selling_and_marketing_expenses: String, - growth_other_expenses: String, - growth_operating_expenses: String, - growth_cost_and_expenses: String, - growth_interest_income: String, - growth_interest_expense: String, - growth_depreciation_and_amortization: String, - growth_ebitda: String, - growth_operating_income: String, - growth_income_before_tax: String, - growth_income_tax_expense: String, - growth_net_income: String, - growth_eps: String, - growth_eps_diluted: String, - growth_weighted_average_shs_out: String, - growth_weighted_average_shs_out_dil: String, - growth_ebit: String, - growth_non_operating_income_excluding_interest: String, - growth_net_interest_income: String, - growth_total_other_income_expenses_net: String, - growth_net_income_from_continuing_operations: String, - growth_other_adjustments_to_net_income: String, - growth_net_income_deductions: String, + growth_revenue: Option, + growth_cost_of_revenue: Option, + growth_gross_profit: Option, + growth_gross_profit_ratio: Option, + growth_research_and_development_expenses: Option, + growth_general_and_administrative_expenses: Option, + growth_selling_and_marketing_expenses: Option, + growth_other_expenses: Option, + growth_operating_expenses: Option, + growth_cost_and_expenses: Option, + growth_interest_income: Option, + growth_interest_expense: Option, + growth_depreciation_and_amortization: Option, + growth_ebitda: Option, + growth_operating_income: Option, + growth_income_before_tax: Option, + growth_income_tax_expense: Option, + growth_net_income: Option, + growth_eps: Option, + growth_eps_diluted: Option, + growth_weighted_average_shs_out: Option, + growth_weighted_average_shs_out_dil: Option, + growth_ebit: Option, + growth_non_operating_income_excluding_interest: Option, + growth_net_interest_income: Option, + growth_total_other_income_expenses_net: Option, + growth_net_income_from_continuing_operations: Option, + growth_other_adjustments_to_net_income: Option, + growth_net_income_deductions: Option, ) -> Self { Self { symbol, @@ -1539,53 +1553,73 @@ impl From for BulkIncomeStat fiscal_year: value.fiscal_year.as_str().to_owned(), period: value.period.to_string(), reported_currency: value.reported_currency.into_inner(), - growth_revenue: value.growth_revenue.into_inner(), - growth_cost_of_revenue: value.growth_cost_of_revenue.into_inner(), - growth_gross_profit: value.growth_gross_profit.into_inner(), - growth_gross_profit_ratio: value.growth_gross_profit_ratio.into_inner(), + growth_revenue: value.growth_revenue.map(|value| value.into_inner()), + growth_cost_of_revenue: value.growth_cost_of_revenue.map(|value| value.into_inner()), + growth_gross_profit: value.growth_gross_profit.map(|value| value.into_inner()), + growth_gross_profit_ratio: value + .growth_gross_profit_ratio + .map(|value| value.into_inner()), growth_research_and_development_expenses: value .growth_research_and_development_expenses - .into_inner(), + .map(|value| value.into_inner()), growth_general_and_administrative_expenses: value .growth_general_and_administrative_expenses - .into_inner(), + .map(|value| value.into_inner()), growth_selling_and_marketing_expenses: value .growth_selling_and_marketing_expenses - .into_inner(), - growth_other_expenses: value.growth_other_expenses.into_inner(), - growth_operating_expenses: value.growth_operating_expenses.into_inner(), - growth_cost_and_expenses: value.growth_cost_and_expenses.into_inner(), - growth_interest_income: value.growth_interest_income.into_inner(), - growth_interest_expense: value.growth_interest_expense.into_inner(), + .map(|value| value.into_inner()), + growth_other_expenses: value.growth_other_expenses.map(|value| value.into_inner()), + growth_operating_expenses: value + .growth_operating_expenses + .map(|value| value.into_inner()), + growth_cost_and_expenses: value + .growth_cost_and_expenses + .map(|value| value.into_inner()), + growth_interest_income: value.growth_interest_income.map(|value| value.into_inner()), + growth_interest_expense: value + .growth_interest_expense + .map(|value| value.into_inner()), growth_depreciation_and_amortization: value .growth_depreciation_and_amortization - .into_inner(), - growth_ebitda: value.growth_ebitda.into_inner(), - growth_operating_income: value.growth_operating_income.into_inner(), - growth_income_before_tax: value.growth_income_before_tax.into_inner(), - growth_income_tax_expense: value.growth_income_tax_expense.into_inner(), - growth_net_income: value.growth_net_income.into_inner(), - growth_eps: value.growth_eps.into_inner(), - growth_eps_diluted: value.growth_eps_diluted.into_inner(), - growth_weighted_average_shs_out: value.growth_weighted_average_shs_out.into_inner(), + .map(|value| value.into_inner()), + growth_ebitda: value.growth_ebitda.map(|value| value.into_inner()), + growth_operating_income: value + .growth_operating_income + .map(|value| value.into_inner()), + growth_income_before_tax: value + .growth_income_before_tax + .map(|value| value.into_inner()), + growth_income_tax_expense: value + .growth_income_tax_expense + .map(|value| value.into_inner()), + growth_net_income: value.growth_net_income.map(|value| value.into_inner()), + growth_eps: value.growth_eps.map(|value| value.into_inner()), + growth_eps_diluted: value.growth_eps_diluted.map(|value| value.into_inner()), + growth_weighted_average_shs_out: value + .growth_weighted_average_shs_out + .map(|value| value.into_inner()), growth_weighted_average_shs_out_dil: value .growth_weighted_average_shs_out_dil - .into_inner(), - growth_ebit: value.growth_ebit.into_inner(), + .map(|value| value.into_inner()), + growth_ebit: value.growth_ebit.map(|value| value.into_inner()), growth_non_operating_income_excluding_interest: value .growth_non_operating_income_excluding_interest - .into_inner(), - growth_net_interest_income: value.growth_net_interest_income.into_inner(), + .map(|value| value.into_inner()), + growth_net_interest_income: value + .growth_net_interest_income + .map(|value| value.into_inner()), growth_total_other_income_expenses_net: value .growth_total_other_income_expenses_net - .into_inner(), + .map(|value| value.into_inner()), growth_net_income_from_continuing_operations: value .growth_net_income_from_continuing_operations - .into_inner(), + .map(|value| value.into_inner()), growth_other_adjustments_to_net_income: value .growth_other_adjustments_to_net_income - .into_inner(), - growth_net_income_deductions: value.growth_net_income_deductions.into_inner(), + .map(|value| value.into_inner()), + growth_net_income_deductions: value + .growth_net_income_deductions + .map(|value| value.into_inner()), } } } diff --git a/crates/fmp-py/src/models/bulk/metrics.rs b/crates/fmp-py/src/models/bulk/metrics.rs index 784a2882..66396529 100644 --- a/crates/fmp-py/src/models/bulk/metrics.rs +++ b/crates/fmp-py/src/models/bulk/metrics.rs @@ -12,89 +12,89 @@ pub(crate) struct BulkKeyMetricsTtm { #[pyo3(get)] pub symbol: String, #[pyo3(get)] - pub market_cap: String, + pub market_cap: Option, #[pyo3(get)] - pub enterprise_value_ttm: String, + pub enterprise_value_ttm: Option, #[pyo3(get)] - pub ev_to_sales_ttm: String, + pub ev_to_sales_ttm: Option, #[pyo3(get)] - pub ev_to_operating_cash_flow_ttm: String, + pub ev_to_operating_cash_flow_ttm: Option, #[pyo3(get)] - pub ev_to_free_cash_flow_ttm: String, + pub ev_to_free_cash_flow_ttm: Option, #[pyo3(get)] - pub ev_to_ebitda_ttm: String, + pub ev_to_ebitda_ttm: Option, #[pyo3(get)] - pub net_debt_to_ebitda_ttm: String, + pub net_debt_to_ebitda_ttm: Option, #[pyo3(get)] - pub current_ratio_ttm: String, + pub current_ratio_ttm: Option, #[pyo3(get)] - pub income_quality_ttm: String, + pub income_quality_ttm: Option, #[pyo3(get)] - pub graham_number_ttm: String, + pub graham_number_ttm: Option, #[pyo3(get)] - pub graham_net_net_ttm: String, + pub graham_net_net_ttm: Option, #[pyo3(get)] - pub tax_burden_ttm: String, + pub tax_burden_ttm: Option, #[pyo3(get)] - pub interest_burden_ttm: String, + pub interest_burden_ttm: Option, #[pyo3(get)] - pub working_capital_ttm: String, + pub working_capital_ttm: Option, #[pyo3(get)] - pub invested_capital_ttm: String, + pub invested_capital_ttm: Option, #[pyo3(get)] - pub return_on_assets_ttm: String, + pub return_on_assets_ttm: Option, #[pyo3(get)] - pub operating_return_on_assets_ttm: String, + pub operating_return_on_assets_ttm: Option, #[pyo3(get)] - pub return_on_tangible_assets_ttm: String, + pub return_on_tangible_assets_ttm: Option, #[pyo3(get)] - pub return_on_equity_ttm: String, + pub return_on_equity_ttm: Option, #[pyo3(get)] - pub return_on_invested_capital_ttm: String, + pub return_on_invested_capital_ttm: Option, #[pyo3(get)] - pub return_on_capital_employed_ttm: String, + pub return_on_capital_employed_ttm: Option, #[pyo3(get)] - pub earnings_yield_ttm: String, + pub earnings_yield_ttm: Option, #[pyo3(get)] - pub free_cash_flow_yield_ttm: String, + pub free_cash_flow_yield_ttm: Option, #[pyo3(get)] - pub capex_to_operating_cash_flow_ttm: String, + pub capex_to_operating_cash_flow_ttm: Option, #[pyo3(get)] - pub capex_to_depreciation_ttm: String, + pub capex_to_depreciation_ttm: Option, #[pyo3(get)] - pub capex_to_revenue_ttm: String, + pub capex_to_revenue_ttm: Option, #[pyo3(get)] - pub sales_general_and_administrative_to_revenue_ttm: String, + pub sales_general_and_administrative_to_revenue_ttm: Option, #[pyo3(get)] - pub research_and_development_to_revenue_ttm: String, + pub research_and_development_to_revenue_ttm: Option, #[pyo3(get)] - pub stock_based_compensation_to_revenue_ttm: String, + pub stock_based_compensation_to_revenue_ttm: Option, #[pyo3(get)] - pub intangibles_to_total_assets_ttm: String, + pub intangibles_to_total_assets_ttm: Option, #[pyo3(get)] - pub average_receivables_ttm: String, + pub average_receivables_ttm: Option, #[pyo3(get)] - pub average_payables_ttm: String, + pub average_payables_ttm: Option, #[pyo3(get)] - pub average_inventory_ttm: String, + pub average_inventory_ttm: Option, #[pyo3(get)] - pub days_of_sales_outstanding_ttm: String, + pub days_of_sales_outstanding_ttm: Option, #[pyo3(get)] - pub days_of_payables_outstanding_ttm: String, + pub days_of_payables_outstanding_ttm: Option, #[pyo3(get)] - pub days_of_inventory_outstanding_ttm: String, + pub days_of_inventory_outstanding_ttm: Option, #[pyo3(get)] - pub operating_cycle_ttm: String, + pub operating_cycle_ttm: Option, #[pyo3(get)] - pub cash_conversion_cycle_ttm: String, + pub cash_conversion_cycle_ttm: Option, #[pyo3(get)] - pub free_cash_flow_to_equity_ttm: String, + pub free_cash_flow_to_equity_ttm: Option, #[pyo3(get)] - pub free_cash_flow_to_firm_ttm: String, + pub free_cash_flow_to_firm_ttm: Option, #[pyo3(get)] - pub tangible_asset_value_ttm: String, + pub tangible_asset_value_ttm: Option, #[pyo3(get)] - pub net_current_asset_value_ttm: String, + pub net_current_asset_value_ttm: Option, } #[gen_stub_pymethods] @@ -106,48 +106,48 @@ impl BulkKeyMetricsTtm { #[pyo3(signature = (*, symbol, market_cap, enterprise_value_ttm, ev_to_sales_ttm, ev_to_operating_cash_flow_ttm, ev_to_free_cash_flow_ttm, ev_to_ebitda_ttm, net_debt_to_ebitda_ttm, current_ratio_ttm, income_quality_ttm, graham_number_ttm, graham_net_net_ttm, tax_burden_ttm, interest_burden_ttm, working_capital_ttm, invested_capital_ttm, return_on_assets_ttm, operating_return_on_assets_ttm, return_on_tangible_assets_ttm, return_on_equity_ttm, return_on_invested_capital_ttm, return_on_capital_employed_ttm, earnings_yield_ttm, free_cash_flow_yield_ttm, capex_to_operating_cash_flow_ttm, capex_to_depreciation_ttm, capex_to_revenue_ttm, sales_general_and_administrative_to_revenue_ttm, research_and_development_to_revenue_ttm, stock_based_compensation_to_revenue_ttm, intangibles_to_total_assets_ttm, average_receivables_ttm, average_payables_ttm, average_inventory_ttm, days_of_sales_outstanding_ttm, days_of_payables_outstanding_ttm, days_of_inventory_outstanding_ttm, operating_cycle_ttm, cash_conversion_cycle_ttm, free_cash_flow_to_equity_ttm, free_cash_flow_to_firm_ttm, tangible_asset_value_ttm, net_current_asset_value_ttm))] fn new( symbol: String, - market_cap: String, - enterprise_value_ttm: String, - ev_to_sales_ttm: String, - ev_to_operating_cash_flow_ttm: String, - ev_to_free_cash_flow_ttm: String, - ev_to_ebitda_ttm: String, - net_debt_to_ebitda_ttm: String, - current_ratio_ttm: String, - income_quality_ttm: String, - graham_number_ttm: String, - graham_net_net_ttm: String, - tax_burden_ttm: String, - interest_burden_ttm: String, - working_capital_ttm: String, - invested_capital_ttm: String, - return_on_assets_ttm: String, - operating_return_on_assets_ttm: String, - return_on_tangible_assets_ttm: String, - return_on_equity_ttm: String, - return_on_invested_capital_ttm: String, - return_on_capital_employed_ttm: String, - earnings_yield_ttm: String, - free_cash_flow_yield_ttm: String, - capex_to_operating_cash_flow_ttm: String, - capex_to_depreciation_ttm: String, - capex_to_revenue_ttm: String, - sales_general_and_administrative_to_revenue_ttm: String, - research_and_development_to_revenue_ttm: String, - stock_based_compensation_to_revenue_ttm: String, - intangibles_to_total_assets_ttm: String, - average_receivables_ttm: String, - average_payables_ttm: String, - average_inventory_ttm: String, - days_of_sales_outstanding_ttm: String, - days_of_payables_outstanding_ttm: String, - days_of_inventory_outstanding_ttm: String, - operating_cycle_ttm: String, - cash_conversion_cycle_ttm: String, - free_cash_flow_to_equity_ttm: String, - free_cash_flow_to_firm_ttm: String, - tangible_asset_value_ttm: String, - net_current_asset_value_ttm: String, + market_cap: Option, + enterprise_value_ttm: Option, + ev_to_sales_ttm: Option, + ev_to_operating_cash_flow_ttm: Option, + ev_to_free_cash_flow_ttm: Option, + ev_to_ebitda_ttm: Option, + net_debt_to_ebitda_ttm: Option, + current_ratio_ttm: Option, + income_quality_ttm: Option, + graham_number_ttm: Option, + graham_net_net_ttm: Option, + tax_burden_ttm: Option, + interest_burden_ttm: Option, + working_capital_ttm: Option, + invested_capital_ttm: Option, + return_on_assets_ttm: Option, + operating_return_on_assets_ttm: Option, + return_on_tangible_assets_ttm: Option, + return_on_equity_ttm: Option, + return_on_invested_capital_ttm: Option, + return_on_capital_employed_ttm: Option, + earnings_yield_ttm: Option, + free_cash_flow_yield_ttm: Option, + capex_to_operating_cash_flow_ttm: Option, + capex_to_depreciation_ttm: Option, + capex_to_revenue_ttm: Option, + sales_general_and_administrative_to_revenue_ttm: Option, + research_and_development_to_revenue_ttm: Option, + stock_based_compensation_to_revenue_ttm: Option, + intangibles_to_total_assets_ttm: Option, + average_receivables_ttm: Option, + average_payables_ttm: Option, + average_inventory_ttm: Option, + days_of_sales_outstanding_ttm: Option, + days_of_payables_outstanding_ttm: Option, + days_of_inventory_outstanding_ttm: Option, + operating_cycle_ttm: Option, + cash_conversion_cycle_ttm: Option, + free_cash_flow_to_equity_ttm: Option, + free_cash_flow_to_firm_ttm: Option, + tangible_asset_value_ttm: Option, + net_current_asset_value_ttm: Option, ) -> Self { Self { symbol, @@ -900,54 +900,92 @@ impl From for BulkKeyMetricsTtm { fn from(value: libfmp::responses::bulk::BulkKeyMetricsTtm) -> Self { Self { symbol: value.symbol.into_inner(), - market_cap: value.market_cap.into_inner(), - enterprise_value_ttm: value.enterprise_value_ttm.into_inner(), - ev_to_sales_ttm: value.ev_to_sales_ttm.into_inner(), - ev_to_operating_cash_flow_ttm: value.ev_to_operating_cash_flow_ttm.into_inner(), - ev_to_free_cash_flow_ttm: value.ev_to_free_cash_flow_ttm.into_inner(), - ev_to_ebitda_ttm: value.ev_to_ebitda_ttm.into_inner(), - net_debt_to_ebitda_ttm: value.net_debt_to_ebitda_ttm.into_inner(), - current_ratio_ttm: value.current_ratio_ttm.into_inner(), - income_quality_ttm: value.income_quality_ttm.into_inner(), - graham_number_ttm: value.graham_number_ttm.into_inner(), - graham_net_net_ttm: value.graham_net_net_ttm.into_inner(), - tax_burden_ttm: value.tax_burden_ttm.into_inner(), - interest_burden_ttm: value.interest_burden_ttm.into_inner(), - working_capital_ttm: value.working_capital_ttm.into_inner(), - invested_capital_ttm: value.invested_capital_ttm.into_inner(), - return_on_assets_ttm: value.return_on_assets_ttm.into_inner(), - operating_return_on_assets_ttm: value.operating_return_on_assets_ttm.into_inner(), - return_on_tangible_assets_ttm: value.return_on_tangible_assets_ttm.into_inner(), - return_on_equity_ttm: value.return_on_equity_ttm.into_inner(), - return_on_invested_capital_ttm: value.return_on_invested_capital_ttm.into_inner(), - return_on_capital_employed_ttm: value.return_on_capital_employed_ttm.into_inner(), - earnings_yield_ttm: value.earnings_yield_ttm.into_inner(), - free_cash_flow_yield_ttm: value.free_cash_flow_yield_ttm.into_inner(), - capex_to_operating_cash_flow_ttm: value.capex_to_operating_cash_flow_ttm.into_inner(), - capex_to_depreciation_ttm: value.capex_to_depreciation_ttm.into_inner(), - capex_to_revenue_ttm: value.capex_to_revenue_ttm.into_inner(), + market_cap: value.market_cap.map(|value| value.into_inner()), + enterprise_value_ttm: value.enterprise_value_ttm.map(|value| value.into_inner()), + ev_to_sales_ttm: value.ev_to_sales_ttm.map(|value| value.into_inner()), + ev_to_operating_cash_flow_ttm: value + .ev_to_operating_cash_flow_ttm + .map(|value| value.into_inner()), + ev_to_free_cash_flow_ttm: value + .ev_to_free_cash_flow_ttm + .map(|value| value.into_inner()), + ev_to_ebitda_ttm: value.ev_to_ebitda_ttm.map(|value| value.into_inner()), + net_debt_to_ebitda_ttm: value.net_debt_to_ebitda_ttm.map(|value| value.into_inner()), + current_ratio_ttm: value.current_ratio_ttm.map(|value| value.into_inner()), + income_quality_ttm: value.income_quality_ttm.map(|value| value.into_inner()), + graham_number_ttm: value.graham_number_ttm.map(|value| value.into_inner()), + graham_net_net_ttm: value.graham_net_net_ttm.map(|value| value.into_inner()), + tax_burden_ttm: value.tax_burden_ttm.map(|value| value.into_inner()), + interest_burden_ttm: value.interest_burden_ttm.map(|value| value.into_inner()), + working_capital_ttm: value.working_capital_ttm.map(|value| value.into_inner()), + invested_capital_ttm: value.invested_capital_ttm.map(|value| value.into_inner()), + return_on_assets_ttm: value.return_on_assets_ttm.map(|value| value.into_inner()), + operating_return_on_assets_ttm: value + .operating_return_on_assets_ttm + .map(|value| value.into_inner()), + return_on_tangible_assets_ttm: value + .return_on_tangible_assets_ttm + .map(|value| value.into_inner()), + return_on_equity_ttm: value.return_on_equity_ttm.map(|value| value.into_inner()), + return_on_invested_capital_ttm: value + .return_on_invested_capital_ttm + .map(|value| value.into_inner()), + return_on_capital_employed_ttm: value + .return_on_capital_employed_ttm + .map(|value| value.into_inner()), + earnings_yield_ttm: value.earnings_yield_ttm.map(|value| value.into_inner()), + free_cash_flow_yield_ttm: value + .free_cash_flow_yield_ttm + .map(|value| value.into_inner()), + capex_to_operating_cash_flow_ttm: value + .capex_to_operating_cash_flow_ttm + .map(|value| value.into_inner()), + capex_to_depreciation_ttm: value + .capex_to_depreciation_ttm + .map(|value| value.into_inner()), + capex_to_revenue_ttm: value.capex_to_revenue_ttm.map(|value| value.into_inner()), sales_general_and_administrative_to_revenue_ttm: value .sales_general_and_administrative_to_revenue_ttm - .into_inner(), + .map(|value| value.into_inner()), research_and_development_to_revenue_ttm: value .research_and_development_to_revenue_ttm - .into_inner(), + .map(|value| value.into_inner()), stock_based_compensation_to_revenue_ttm: value .stock_based_compensation_to_revenue_ttm - .into_inner(), - intangibles_to_total_assets_ttm: value.intangibles_to_total_assets_ttm.into_inner(), - average_receivables_ttm: value.average_receivables_ttm.into_inner(), - average_payables_ttm: value.average_payables_ttm.into_inner(), - average_inventory_ttm: value.average_inventory_ttm.into_inner(), - days_of_sales_outstanding_ttm: value.days_of_sales_outstanding_ttm.into_inner(), - days_of_payables_outstanding_ttm: value.days_of_payables_outstanding_ttm.into_inner(), - days_of_inventory_outstanding_ttm: value.days_of_inventory_outstanding_ttm.into_inner(), - operating_cycle_ttm: value.operating_cycle_ttm.into_inner(), - cash_conversion_cycle_ttm: value.cash_conversion_cycle_ttm.into_inner(), - free_cash_flow_to_equity_ttm: value.free_cash_flow_to_equity_ttm.into_inner(), - free_cash_flow_to_firm_ttm: value.free_cash_flow_to_firm_ttm.into_inner(), - tangible_asset_value_ttm: value.tangible_asset_value_ttm.into_inner(), - net_current_asset_value_ttm: value.net_current_asset_value_ttm.into_inner(), + .map(|value| value.into_inner()), + intangibles_to_total_assets_ttm: value + .intangibles_to_total_assets_ttm + .map(|value| value.into_inner()), + average_receivables_ttm: value + .average_receivables_ttm + .map(|value| value.into_inner()), + average_payables_ttm: value.average_payables_ttm.map(|value| value.into_inner()), + average_inventory_ttm: value.average_inventory_ttm.map(|value| value.into_inner()), + days_of_sales_outstanding_ttm: value + .days_of_sales_outstanding_ttm + .map(|value| value.into_inner()), + days_of_payables_outstanding_ttm: value + .days_of_payables_outstanding_ttm + .map(|value| value.into_inner()), + days_of_inventory_outstanding_ttm: value + .days_of_inventory_outstanding_ttm + .map(|value| value.into_inner()), + operating_cycle_ttm: value.operating_cycle_ttm.map(|value| value.into_inner()), + cash_conversion_cycle_ttm: value + .cash_conversion_cycle_ttm + .map(|value| value.into_inner()), + free_cash_flow_to_equity_ttm: value + .free_cash_flow_to_equity_ttm + .map(|value| value.into_inner()), + free_cash_flow_to_firm_ttm: value + .free_cash_flow_to_firm_ttm + .map(|value| value.into_inner()), + tangible_asset_value_ttm: value + .tangible_asset_value_ttm + .map(|value| value.into_inner()), + net_current_asset_value_ttm: value + .net_current_asset_value_ttm + .map(|value| value.into_inner()), } } } @@ -965,123 +1003,123 @@ pub(crate) struct BulkFinancialRatiosTtm { #[pyo3(get)] pub symbol: String, #[pyo3(get)] - pub gross_profit_margin_ttm: String, + pub gross_profit_margin_ttm: Option, #[pyo3(get)] - pub ebit_margin_ttm: String, + pub ebit_margin_ttm: Option, #[pyo3(get)] - pub ebitda_margin_ttm: String, + pub ebitda_margin_ttm: Option, #[pyo3(get)] - pub operating_profit_margin_ttm: String, + pub operating_profit_margin_ttm: Option, #[pyo3(get)] - pub pretax_profit_margin_ttm: String, + pub pretax_profit_margin_ttm: Option, #[pyo3(get)] - pub continuous_operations_profit_margin_ttm: String, + pub continuous_operations_profit_margin_ttm: Option, #[pyo3(get)] - pub net_profit_margin_ttm: String, + pub net_profit_margin_ttm: Option, #[pyo3(get)] - pub bottom_line_profit_margin_ttm: String, + pub bottom_line_profit_margin_ttm: Option, #[pyo3(get)] - pub receivables_turnover_ttm: String, + pub receivables_turnover_ttm: Option, #[pyo3(get)] - pub payables_turnover_ttm: String, + pub payables_turnover_ttm: Option, #[pyo3(get)] - pub inventory_turnover_ttm: String, + pub inventory_turnover_ttm: Option, #[pyo3(get)] - pub fixed_asset_turnover_ttm: String, + pub fixed_asset_turnover_ttm: Option, #[pyo3(get)] - pub asset_turnover_ttm: String, + pub asset_turnover_ttm: Option, #[pyo3(get)] - pub current_ratio_ttm: String, + pub current_ratio_ttm: Option, #[pyo3(get)] - pub quick_ratio_ttm: String, + pub quick_ratio_ttm: Option, #[pyo3(get)] - pub solvency_ratio_ttm: String, + pub solvency_ratio_ttm: Option, #[pyo3(get)] - pub cash_ratio_ttm: String, + pub cash_ratio_ttm: Option, #[pyo3(get)] - pub price_to_earnings_ratio_ttm: String, + pub price_to_earnings_ratio_ttm: Option, #[pyo3(get)] - pub price_to_earnings_growth_ratio_ttm: String, + pub price_to_earnings_growth_ratio_ttm: Option, #[pyo3(get)] - pub forward_price_to_earnings_growth_ratio_ttm: String, + pub forward_price_to_earnings_growth_ratio_ttm: Option, #[pyo3(get)] - pub price_to_book_ratio_ttm: String, + pub price_to_book_ratio_ttm: Option, #[pyo3(get)] - pub price_to_sales_ratio_ttm: String, + pub price_to_sales_ratio_ttm: Option, #[pyo3(get)] - pub price_to_free_cash_flow_ratio_ttm: String, + pub price_to_free_cash_flow_ratio_ttm: Option, #[pyo3(get)] - pub price_to_operating_cash_flow_ratio_ttm: String, + pub price_to_operating_cash_flow_ratio_ttm: Option, #[pyo3(get)] - pub debt_to_assets_ratio_ttm: String, + pub debt_to_assets_ratio_ttm: Option, #[pyo3(get)] - pub debt_to_equity_ratio_ttm: String, + pub debt_to_equity_ratio_ttm: Option, #[pyo3(get)] - pub debt_to_capital_ratio_ttm: String, + pub debt_to_capital_ratio_ttm: Option, #[pyo3(get)] - pub long_term_debt_to_capital_ratio_ttm: String, + pub long_term_debt_to_capital_ratio_ttm: Option, #[pyo3(get)] - pub financial_leverage_ratio_ttm: String, + pub financial_leverage_ratio_ttm: Option, #[pyo3(get)] - pub working_capital_turnover_ratio_ttm: String, + pub working_capital_turnover_ratio_ttm: Option, #[pyo3(get)] - pub operating_cash_flow_ratio_ttm: String, + pub operating_cash_flow_ratio_ttm: Option, #[pyo3(get)] - pub operating_cash_flow_sales_ratio_ttm: String, + pub operating_cash_flow_sales_ratio_ttm: Option, #[pyo3(get)] - pub free_cash_flow_operating_cash_flow_ratio_ttm: String, + pub free_cash_flow_operating_cash_flow_ratio_ttm: Option, #[pyo3(get)] - pub debt_service_coverage_ratio_ttm: String, + pub debt_service_coverage_ratio_ttm: Option, #[pyo3(get)] - pub interest_coverage_ratio_ttm: String, + pub interest_coverage_ratio_ttm: Option, #[pyo3(get)] - pub short_term_operating_cash_flow_coverage_ratio_ttm: String, + pub short_term_operating_cash_flow_coverage_ratio_ttm: Option, #[pyo3(get)] - pub operating_cash_flow_coverage_ratio_ttm: String, + pub operating_cash_flow_coverage_ratio_ttm: Option, #[pyo3(get)] - pub capital_expenditure_coverage_ratio_ttm: String, + pub capital_expenditure_coverage_ratio_ttm: Option, #[pyo3(get)] - pub dividend_paid_and_capex_coverage_ratio_ttm: String, + pub dividend_paid_and_capex_coverage_ratio_ttm: Option, #[pyo3(get)] - pub dividend_payout_ratio_ttm: String, + pub dividend_payout_ratio_ttm: Option, #[pyo3(get)] - pub dividend_yield_ttm: String, + pub dividend_yield_ttm: Option, #[pyo3(get)] - pub enterprise_value_ttm: String, + pub enterprise_value_ttm: Option, #[pyo3(get)] - pub revenue_per_share_ttm: String, + pub revenue_per_share_ttm: Option, #[pyo3(get)] - pub net_income_per_share_ttm: String, + pub net_income_per_share_ttm: Option, #[pyo3(get)] - pub interest_debt_per_share_ttm: String, + pub interest_debt_per_share_ttm: Option, #[pyo3(get)] - pub cash_per_share_ttm: String, + pub cash_per_share_ttm: Option, #[pyo3(get)] - pub book_value_per_share_ttm: String, + pub book_value_per_share_ttm: Option, #[pyo3(get)] - pub tangible_book_value_per_share_ttm: String, + pub tangible_book_value_per_share_ttm: Option, #[pyo3(get)] - pub shareholders_equity_per_share_ttm: String, + pub shareholders_equity_per_share_ttm: Option, #[pyo3(get)] - pub operating_cash_flow_per_share_ttm: String, + pub operating_cash_flow_per_share_ttm: Option, #[pyo3(get)] - pub capex_per_share_ttm: String, + pub capex_per_share_ttm: Option, #[pyo3(get)] - pub free_cash_flow_per_share_ttm: String, + pub free_cash_flow_per_share_ttm: Option, #[pyo3(get)] - pub net_income_per_ebt_ttm: String, + pub net_income_per_ebt_ttm: Option, #[pyo3(get)] - pub ebt_per_ebit_ttm: String, + pub ebt_per_ebit_ttm: Option, #[pyo3(get)] - pub price_to_fair_value_ttm: String, + pub price_to_fair_value_ttm: Option, #[pyo3(get)] - pub debt_to_market_cap_ttm: String, + pub debt_to_market_cap_ttm: Option, #[pyo3(get)] - pub effective_tax_rate_ttm: String, + pub effective_tax_rate_ttm: Option, #[pyo3(get)] - pub enterprise_value_multiple_ttm: String, + pub enterprise_value_multiple_ttm: Option, #[pyo3(get)] - pub dividend_per_share_ttm: String, + pub dividend_per_share_ttm: Option, } #[gen_stub_pymethods] @@ -1093,65 +1131,65 @@ impl BulkFinancialRatiosTtm { #[pyo3(signature = (*, symbol, gross_profit_margin_ttm, ebit_margin_ttm, ebitda_margin_ttm, operating_profit_margin_ttm, pretax_profit_margin_ttm, continuous_operations_profit_margin_ttm, net_profit_margin_ttm, bottom_line_profit_margin_ttm, receivables_turnover_ttm, payables_turnover_ttm, inventory_turnover_ttm, fixed_asset_turnover_ttm, asset_turnover_ttm, current_ratio_ttm, quick_ratio_ttm, solvency_ratio_ttm, cash_ratio_ttm, price_to_earnings_ratio_ttm, price_to_earnings_growth_ratio_ttm, forward_price_to_earnings_growth_ratio_ttm, price_to_book_ratio_ttm, price_to_sales_ratio_ttm, price_to_free_cash_flow_ratio_ttm, price_to_operating_cash_flow_ratio_ttm, debt_to_assets_ratio_ttm, debt_to_equity_ratio_ttm, debt_to_capital_ratio_ttm, long_term_debt_to_capital_ratio_ttm, financial_leverage_ratio_ttm, working_capital_turnover_ratio_ttm, operating_cash_flow_ratio_ttm, operating_cash_flow_sales_ratio_ttm, free_cash_flow_operating_cash_flow_ratio_ttm, debt_service_coverage_ratio_ttm, interest_coverage_ratio_ttm, short_term_operating_cash_flow_coverage_ratio_ttm, operating_cash_flow_coverage_ratio_ttm, capital_expenditure_coverage_ratio_ttm, dividend_paid_and_capex_coverage_ratio_ttm, dividend_payout_ratio_ttm, dividend_yield_ttm, enterprise_value_ttm, revenue_per_share_ttm, net_income_per_share_ttm, interest_debt_per_share_ttm, cash_per_share_ttm, book_value_per_share_ttm, tangible_book_value_per_share_ttm, shareholders_equity_per_share_ttm, operating_cash_flow_per_share_ttm, capex_per_share_ttm, free_cash_flow_per_share_ttm, net_income_per_ebt_ttm, ebt_per_ebit_ttm, price_to_fair_value_ttm, debt_to_market_cap_ttm, effective_tax_rate_ttm, enterprise_value_multiple_ttm, dividend_per_share_ttm))] fn new( symbol: String, - gross_profit_margin_ttm: String, - ebit_margin_ttm: String, - ebitda_margin_ttm: String, - operating_profit_margin_ttm: String, - pretax_profit_margin_ttm: String, - continuous_operations_profit_margin_ttm: String, - net_profit_margin_ttm: String, - bottom_line_profit_margin_ttm: String, - receivables_turnover_ttm: String, - payables_turnover_ttm: String, - inventory_turnover_ttm: String, - fixed_asset_turnover_ttm: String, - asset_turnover_ttm: String, - current_ratio_ttm: String, - quick_ratio_ttm: String, - solvency_ratio_ttm: String, - cash_ratio_ttm: String, - price_to_earnings_ratio_ttm: String, - price_to_earnings_growth_ratio_ttm: String, - forward_price_to_earnings_growth_ratio_ttm: String, - price_to_book_ratio_ttm: String, - price_to_sales_ratio_ttm: String, - price_to_free_cash_flow_ratio_ttm: String, - price_to_operating_cash_flow_ratio_ttm: String, - debt_to_assets_ratio_ttm: String, - debt_to_equity_ratio_ttm: String, - debt_to_capital_ratio_ttm: String, - long_term_debt_to_capital_ratio_ttm: String, - financial_leverage_ratio_ttm: String, - working_capital_turnover_ratio_ttm: String, - operating_cash_flow_ratio_ttm: String, - operating_cash_flow_sales_ratio_ttm: String, - free_cash_flow_operating_cash_flow_ratio_ttm: String, - debt_service_coverage_ratio_ttm: String, - interest_coverage_ratio_ttm: String, - short_term_operating_cash_flow_coverage_ratio_ttm: String, - operating_cash_flow_coverage_ratio_ttm: String, - capital_expenditure_coverage_ratio_ttm: String, - dividend_paid_and_capex_coverage_ratio_ttm: String, - dividend_payout_ratio_ttm: String, - dividend_yield_ttm: String, - enterprise_value_ttm: String, - revenue_per_share_ttm: String, - net_income_per_share_ttm: String, - interest_debt_per_share_ttm: String, - cash_per_share_ttm: String, - book_value_per_share_ttm: String, - tangible_book_value_per_share_ttm: String, - shareholders_equity_per_share_ttm: String, - operating_cash_flow_per_share_ttm: String, - capex_per_share_ttm: String, - free_cash_flow_per_share_ttm: String, - net_income_per_ebt_ttm: String, - ebt_per_ebit_ttm: String, - price_to_fair_value_ttm: String, - debt_to_market_cap_ttm: String, - effective_tax_rate_ttm: String, - enterprise_value_multiple_ttm: String, - dividend_per_share_ttm: String, + gross_profit_margin_ttm: Option, + ebit_margin_ttm: Option, + ebitda_margin_ttm: Option, + operating_profit_margin_ttm: Option, + pretax_profit_margin_ttm: Option, + continuous_operations_profit_margin_ttm: Option, + net_profit_margin_ttm: Option, + bottom_line_profit_margin_ttm: Option, + receivables_turnover_ttm: Option, + payables_turnover_ttm: Option, + inventory_turnover_ttm: Option, + fixed_asset_turnover_ttm: Option, + asset_turnover_ttm: Option, + current_ratio_ttm: Option, + quick_ratio_ttm: Option, + solvency_ratio_ttm: Option, + cash_ratio_ttm: Option, + price_to_earnings_ratio_ttm: Option, + price_to_earnings_growth_ratio_ttm: Option, + forward_price_to_earnings_growth_ratio_ttm: Option, + price_to_book_ratio_ttm: Option, + price_to_sales_ratio_ttm: Option, + price_to_free_cash_flow_ratio_ttm: Option, + price_to_operating_cash_flow_ratio_ttm: Option, + debt_to_assets_ratio_ttm: Option, + debt_to_equity_ratio_ttm: Option, + debt_to_capital_ratio_ttm: Option, + long_term_debt_to_capital_ratio_ttm: Option, + financial_leverage_ratio_ttm: Option, + working_capital_turnover_ratio_ttm: Option, + operating_cash_flow_ratio_ttm: Option, + operating_cash_flow_sales_ratio_ttm: Option, + free_cash_flow_operating_cash_flow_ratio_ttm: Option, + debt_service_coverage_ratio_ttm: Option, + interest_coverage_ratio_ttm: Option, + short_term_operating_cash_flow_coverage_ratio_ttm: Option, + operating_cash_flow_coverage_ratio_ttm: Option, + capital_expenditure_coverage_ratio_ttm: Option, + dividend_paid_and_capex_coverage_ratio_ttm: Option, + dividend_payout_ratio_ttm: Option, + dividend_yield_ttm: Option, + enterprise_value_ttm: Option, + revenue_per_share_ttm: Option, + net_income_per_share_ttm: Option, + interest_debt_per_share_ttm: Option, + cash_per_share_ttm: Option, + book_value_per_share_ttm: Option, + tangible_book_value_per_share_ttm: Option, + shareholders_equity_per_share_ttm: Option, + operating_cash_flow_per_share_ttm: Option, + capex_per_share_ttm: Option, + free_cash_flow_per_share_ttm: Option, + net_income_per_ebt_ttm: Option, + ebt_per_ebit_ttm: Option, + price_to_fair_value_ttm: Option, + debt_to_market_cap_ttm: Option, + effective_tax_rate_ttm: Option, + enterprise_value_multiple_ttm: Option, + dividend_per_share_ttm: Option, ) -> Self { Self { symbol, @@ -2229,89 +2267,143 @@ impl From for BulkFinancialRati fn from(value: libfmp::responses::bulk::BulkFinancialRatiosTtm) -> Self { Self { symbol: value.symbol.into_inner(), - gross_profit_margin_ttm: value.gross_profit_margin_ttm.into_inner(), - ebit_margin_ttm: value.ebit_margin_ttm.into_inner(), - ebitda_margin_ttm: value.ebitda_margin_ttm.into_inner(), - operating_profit_margin_ttm: value.operating_profit_margin_ttm.into_inner(), - pretax_profit_margin_ttm: value.pretax_profit_margin_ttm.into_inner(), + gross_profit_margin_ttm: value + .gross_profit_margin_ttm + .map(|value| value.into_inner()), + ebit_margin_ttm: value.ebit_margin_ttm.map(|value| value.into_inner()), + ebitda_margin_ttm: value.ebitda_margin_ttm.map(|value| value.into_inner()), + operating_profit_margin_ttm: value + .operating_profit_margin_ttm + .map(|value| value.into_inner()), + pretax_profit_margin_ttm: value + .pretax_profit_margin_ttm + .map(|value| value.into_inner()), continuous_operations_profit_margin_ttm: value .continuous_operations_profit_margin_ttm - .into_inner(), - net_profit_margin_ttm: value.net_profit_margin_ttm.into_inner(), - bottom_line_profit_margin_ttm: value.bottom_line_profit_margin_ttm.into_inner(), - receivables_turnover_ttm: value.receivables_turnover_ttm.into_inner(), - payables_turnover_ttm: value.payables_turnover_ttm.into_inner(), - inventory_turnover_ttm: value.inventory_turnover_ttm.into_inner(), - fixed_asset_turnover_ttm: value.fixed_asset_turnover_ttm.into_inner(), - asset_turnover_ttm: value.asset_turnover_ttm.into_inner(), - current_ratio_ttm: value.current_ratio_ttm.into_inner(), - quick_ratio_ttm: value.quick_ratio_ttm.into_inner(), - solvency_ratio_ttm: value.solvency_ratio_ttm.into_inner(), - cash_ratio_ttm: value.cash_ratio_ttm.into_inner(), - price_to_earnings_ratio_ttm: value.price_to_earnings_ratio_ttm.into_inner(), + .map(|value| value.into_inner()), + net_profit_margin_ttm: value.net_profit_margin_ttm.map(|value| value.into_inner()), + bottom_line_profit_margin_ttm: value + .bottom_line_profit_margin_ttm + .map(|value| value.into_inner()), + receivables_turnover_ttm: value + .receivables_turnover_ttm + .map(|value| value.into_inner()), + payables_turnover_ttm: value.payables_turnover_ttm.map(|value| value.into_inner()), + inventory_turnover_ttm: value.inventory_turnover_ttm.map(|value| value.into_inner()), + fixed_asset_turnover_ttm: value + .fixed_asset_turnover_ttm + .map(|value| value.into_inner()), + asset_turnover_ttm: value.asset_turnover_ttm.map(|value| value.into_inner()), + current_ratio_ttm: value.current_ratio_ttm.map(|value| value.into_inner()), + quick_ratio_ttm: value.quick_ratio_ttm.map(|value| value.into_inner()), + solvency_ratio_ttm: value.solvency_ratio_ttm.map(|value| value.into_inner()), + cash_ratio_ttm: value.cash_ratio_ttm.map(|value| value.into_inner()), + price_to_earnings_ratio_ttm: value + .price_to_earnings_ratio_ttm + .map(|value| value.into_inner()), price_to_earnings_growth_ratio_ttm: value .price_to_earnings_growth_ratio_ttm - .into_inner(), + .map(|value| value.into_inner()), forward_price_to_earnings_growth_ratio_ttm: value .forward_price_to_earnings_growth_ratio_ttm - .into_inner(), - price_to_book_ratio_ttm: value.price_to_book_ratio_ttm.into_inner(), - price_to_sales_ratio_ttm: value.price_to_sales_ratio_ttm.into_inner(), - price_to_free_cash_flow_ratio_ttm: value.price_to_free_cash_flow_ratio_ttm.into_inner(), + .map(|value| value.into_inner()), + price_to_book_ratio_ttm: value + .price_to_book_ratio_ttm + .map(|value| value.into_inner()), + price_to_sales_ratio_ttm: value + .price_to_sales_ratio_ttm + .map(|value| value.into_inner()), + price_to_free_cash_flow_ratio_ttm: value + .price_to_free_cash_flow_ratio_ttm + .map(|value| value.into_inner()), price_to_operating_cash_flow_ratio_ttm: value .price_to_operating_cash_flow_ratio_ttm - .into_inner(), - debt_to_assets_ratio_ttm: value.debt_to_assets_ratio_ttm.into_inner(), - debt_to_equity_ratio_ttm: value.debt_to_equity_ratio_ttm.into_inner(), - debt_to_capital_ratio_ttm: value.debt_to_capital_ratio_ttm.into_inner(), + .map(|value| value.into_inner()), + debt_to_assets_ratio_ttm: value + .debt_to_assets_ratio_ttm + .map(|value| value.into_inner()), + debt_to_equity_ratio_ttm: value + .debt_to_equity_ratio_ttm + .map(|value| value.into_inner()), + debt_to_capital_ratio_ttm: value + .debt_to_capital_ratio_ttm + .map(|value| value.into_inner()), long_term_debt_to_capital_ratio_ttm: value .long_term_debt_to_capital_ratio_ttm - .into_inner(), - financial_leverage_ratio_ttm: value.financial_leverage_ratio_ttm.into_inner(), + .map(|value| value.into_inner()), + financial_leverage_ratio_ttm: value + .financial_leverage_ratio_ttm + .map(|value| value.into_inner()), working_capital_turnover_ratio_ttm: value .working_capital_turnover_ratio_ttm - .into_inner(), - operating_cash_flow_ratio_ttm: value.operating_cash_flow_ratio_ttm.into_inner(), + .map(|value| value.into_inner()), + operating_cash_flow_ratio_ttm: value + .operating_cash_flow_ratio_ttm + .map(|value| value.into_inner()), operating_cash_flow_sales_ratio_ttm: value .operating_cash_flow_sales_ratio_ttm - .into_inner(), + .map(|value| value.into_inner()), free_cash_flow_operating_cash_flow_ratio_ttm: value .free_cash_flow_operating_cash_flow_ratio_ttm - .into_inner(), - debt_service_coverage_ratio_ttm: value.debt_service_coverage_ratio_ttm.into_inner(), - interest_coverage_ratio_ttm: value.interest_coverage_ratio_ttm.into_inner(), + .map(|value| value.into_inner()), + debt_service_coverage_ratio_ttm: value + .debt_service_coverage_ratio_ttm + .map(|value| value.into_inner()), + interest_coverage_ratio_ttm: value + .interest_coverage_ratio_ttm + .map(|value| value.into_inner()), short_term_operating_cash_flow_coverage_ratio_ttm: value .short_term_operating_cash_flow_coverage_ratio_ttm - .into_inner(), + .map(|value| value.into_inner()), operating_cash_flow_coverage_ratio_ttm: value .operating_cash_flow_coverage_ratio_ttm - .into_inner(), + .map(|value| value.into_inner()), capital_expenditure_coverage_ratio_ttm: value .capital_expenditure_coverage_ratio_ttm - .into_inner(), + .map(|value| value.into_inner()), dividend_paid_and_capex_coverage_ratio_ttm: value .dividend_paid_and_capex_coverage_ratio_ttm - .into_inner(), - dividend_payout_ratio_ttm: value.dividend_payout_ratio_ttm.into_inner(), - dividend_yield_ttm: value.dividend_yield_ttm.into_inner(), - enterprise_value_ttm: value.enterprise_value_ttm.into_inner(), - revenue_per_share_ttm: value.revenue_per_share_ttm.into_inner(), - net_income_per_share_ttm: value.net_income_per_share_ttm.into_inner(), - interest_debt_per_share_ttm: value.interest_debt_per_share_ttm.into_inner(), - cash_per_share_ttm: value.cash_per_share_ttm.into_inner(), - book_value_per_share_ttm: value.book_value_per_share_ttm.into_inner(), - tangible_book_value_per_share_ttm: value.tangible_book_value_per_share_ttm.into_inner(), - shareholders_equity_per_share_ttm: value.shareholders_equity_per_share_ttm.into_inner(), - operating_cash_flow_per_share_ttm: value.operating_cash_flow_per_share_ttm.into_inner(), - capex_per_share_ttm: value.capex_per_share_ttm.into_inner(), - free_cash_flow_per_share_ttm: value.free_cash_flow_per_share_ttm.into_inner(), - net_income_per_ebt_ttm: value.net_income_per_ebt_ttm.into_inner(), - ebt_per_ebit_ttm: value.ebt_per_ebit_ttm.into_inner(), - price_to_fair_value_ttm: value.price_to_fair_value_ttm.into_inner(), - debt_to_market_cap_ttm: value.debt_to_market_cap_ttm.into_inner(), - effective_tax_rate_ttm: value.effective_tax_rate_ttm.into_inner(), - enterprise_value_multiple_ttm: value.enterprise_value_multiple_ttm.into_inner(), - dividend_per_share_ttm: value.dividend_per_share_ttm.into_inner(), + .map(|value| value.into_inner()), + dividend_payout_ratio_ttm: value + .dividend_payout_ratio_ttm + .map(|value| value.into_inner()), + dividend_yield_ttm: value.dividend_yield_ttm.map(|value| value.into_inner()), + enterprise_value_ttm: value.enterprise_value_ttm.map(|value| value.into_inner()), + revenue_per_share_ttm: value.revenue_per_share_ttm.map(|value| value.into_inner()), + net_income_per_share_ttm: value + .net_income_per_share_ttm + .map(|value| value.into_inner()), + interest_debt_per_share_ttm: value + .interest_debt_per_share_ttm + .map(|value| value.into_inner()), + cash_per_share_ttm: value.cash_per_share_ttm.map(|value| value.into_inner()), + book_value_per_share_ttm: value + .book_value_per_share_ttm + .map(|value| value.into_inner()), + tangible_book_value_per_share_ttm: value + .tangible_book_value_per_share_ttm + .map(|value| value.into_inner()), + shareholders_equity_per_share_ttm: value + .shareholders_equity_per_share_ttm + .map(|value| value.into_inner()), + operating_cash_flow_per_share_ttm: value + .operating_cash_flow_per_share_ttm + .map(|value| value.into_inner()), + capex_per_share_ttm: value.capex_per_share_ttm.map(|value| value.into_inner()), + free_cash_flow_per_share_ttm: value + .free_cash_flow_per_share_ttm + .map(|value| value.into_inner()), + net_income_per_ebt_ttm: value.net_income_per_ebt_ttm.map(|value| value.into_inner()), + ebt_per_ebit_ttm: value.ebt_per_ebit_ttm.map(|value| value.into_inner()), + price_to_fair_value_ttm: value + .price_to_fair_value_ttm + .map(|value| value.into_inner()), + debt_to_market_cap_ttm: value.debt_to_market_cap_ttm.map(|value| value.into_inner()), + effective_tax_rate_ttm: value.effective_tax_rate_ttm.map(|value| value.into_inner()), + enterprise_value_multiple_ttm: value + .enterprise_value_multiple_ttm + .map(|value| value.into_inner()), + dividend_per_share_ttm: value.dividend_per_share_ttm.map(|value| value.into_inner()), } } } @@ -2414,9 +2506,9 @@ pub(crate) struct BulkEarningsSurprise { #[pyo3(get)] pub date: ::chrono::NaiveDate, #[pyo3(get)] - pub eps_actual: String, + pub eps_actual: Option, #[pyo3(get)] - pub eps_estimated: String, + pub eps_estimated: Option, #[pyo3(get)] pub last_updated: ::chrono::NaiveDate, } @@ -2431,8 +2523,8 @@ impl BulkEarningsSurprise { fn new( symbol: String, date: ::chrono::NaiveDate, - eps_actual: String, - eps_estimated: String, + eps_actual: Option, + eps_estimated: Option, last_updated: ::chrono::NaiveDate, ) -> Self { Self { @@ -2535,8 +2627,8 @@ impl From for BulkEarningsSurpris Self { symbol: value.symbol.into_inner(), date: value.date.into_inner(), - eps_actual: value.eps_actual.into_inner(), - eps_estimated: value.eps_estimated.into_inner(), + eps_actual: value.eps_actual.map(|value| value.into_inner()), + eps_estimated: value.eps_estimated.map(|value| value.into_inner()), last_updated: value.last_updated.into_inner(), } } diff --git a/crates/fmp-py/src/models/bulk/snapshots.rs b/crates/fmp-py/src/models/bulk/snapshots.rs index 16381577..5f4898d0 100644 --- a/crates/fmp-py/src/models/bulk/snapshots.rs +++ b/crates/fmp-py/src/models/bulk/snapshots.rs @@ -16,17 +16,17 @@ pub(crate) struct BulkStockRating { #[pyo3(get)] pub rating: String, #[pyo3(get)] - pub discounted_cash_flow_score: String, + pub discounted_cash_flow_score: Option, #[pyo3(get)] - pub return_on_equity_score: String, + pub return_on_equity_score: Option, #[pyo3(get)] - pub return_on_assets_score: String, + pub return_on_assets_score: Option, #[pyo3(get)] - pub debt_to_equity_score: String, + pub debt_to_equity_score: Option, #[pyo3(get)] - pub price_to_earnings_score: String, + pub price_to_earnings_score: Option, #[pyo3(get)] - pub price_to_book_score: String, + pub price_to_book_score: Option, } #[gen_stub_pymethods] @@ -40,12 +40,12 @@ impl BulkStockRating { symbol: String, date: ::chrono::NaiveDate, rating: String, - discounted_cash_flow_score: String, - return_on_equity_score: String, - return_on_assets_score: String, - debt_to_equity_score: String, - price_to_earnings_score: String, - price_to_book_score: String, + discounted_cash_flow_score: Option, + return_on_equity_score: Option, + return_on_assets_score: Option, + debt_to_equity_score: Option, + price_to_earnings_score: Option, + price_to_book_score: Option, ) -> Self { Self { symbol, @@ -208,12 +208,16 @@ impl From for BulkStockRating { symbol: value.symbol.into_inner(), date: value.date.into_inner(), rating: value.rating, - discounted_cash_flow_score: value.discounted_cash_flow_score.into_inner(), - return_on_equity_score: value.return_on_equity_score.into_inner(), - return_on_assets_score: value.return_on_assets_score.into_inner(), - debt_to_equity_score: value.debt_to_equity_score.into_inner(), - price_to_earnings_score: value.price_to_earnings_score.into_inner(), - price_to_book_score: value.price_to_book_score.into_inner(), + discounted_cash_flow_score: value + .discounted_cash_flow_score + .map(|value| value.into_inner()), + return_on_equity_score: value.return_on_equity_score.map(|value| value.into_inner()), + return_on_assets_score: value.return_on_assets_score.map(|value| value.into_inner()), + debt_to_equity_score: value.debt_to_equity_score.map(|value| value.into_inner()), + price_to_earnings_score: value + .price_to_earnings_score + .map(|value| value.into_inner()), + price_to_book_score: value.price_to_book_score.map(|value| value.into_inner()), } } } @@ -233,9 +237,9 @@ pub(crate) struct BulkDcfValuation { #[pyo3(get)] pub date: ::chrono::NaiveDate, #[pyo3(get)] - pub dcf: String, + pub dcf: Option, #[pyo3(get)] - pub stock_price: String, + pub stock_price: Option, } #[gen_stub_pymethods] @@ -245,7 +249,12 @@ impl BulkDcfValuation { #[allow(clippy::too_many_arguments)] #[allow(clippy::fn_params_excessive_bools)] #[pyo3(signature = (*, symbol, date, dcf, stock_price))] - fn new(symbol: String, date: ::chrono::NaiveDate, dcf: String, stock_price: String) -> Self { + fn new( + symbol: String, + date: ::chrono::NaiveDate, + dcf: Option, + stock_price: Option, + ) -> Self { Self { symbol, date, @@ -324,8 +333,8 @@ impl From for BulkDcfValuation { Self { symbol: value.symbol.into_inner(), date: value.date.into_inner(), - dcf: value.dcf.into_inner(), - stock_price: value.stock_price.into_inner(), + dcf: value.dcf.map(|value| value.into_inner()), + stock_price: value.stock_price.map(|value| value.into_inner()), } } } @@ -343,25 +352,25 @@ pub(crate) struct BulkFinancialScore { #[pyo3(get)] pub symbol: String, #[pyo3(get)] - pub reported_currency: String, + pub reported_currency: Option, #[pyo3(get)] - pub altman_z_score: String, + pub altman_z_score: Option, #[pyo3(get)] - pub piotroski_score: String, + pub piotroski_score: Option, #[pyo3(get)] - pub working_capital: String, + pub working_capital: Option, #[pyo3(get)] - pub total_assets: String, + pub total_assets: Option, #[pyo3(get)] - pub retained_earnings: String, + pub retained_earnings: Option, #[pyo3(get)] - pub ebit: String, + pub ebit: Option, #[pyo3(get)] - pub market_cap: String, + pub market_cap: Option, #[pyo3(get)] - pub total_liabilities: String, + pub total_liabilities: Option, #[pyo3(get)] - pub revenue: String, + pub revenue: Option, } #[gen_stub_pymethods] @@ -373,16 +382,16 @@ impl BulkFinancialScore { #[pyo3(signature = (*, symbol, reported_currency, altman_z_score, piotroski_score, working_capital, total_assets, retained_earnings, ebit, market_cap, total_liabilities, revenue))] fn new( symbol: String, - reported_currency: String, - altman_z_score: String, - piotroski_score: String, - working_capital: String, - total_assets: String, - retained_earnings: String, - ebit: String, - market_cap: String, - total_liabilities: String, - revenue: String, + reported_currency: Option, + altman_z_score: Option, + piotroski_score: Option, + working_capital: Option, + total_assets: Option, + retained_earnings: Option, + ebit: Option, + market_cap: Option, + total_liabilities: Option, + revenue: Option, ) -> Self { Self { symbol, @@ -561,16 +570,16 @@ impl From for BulkFinancialScore { fn from(value: libfmp::responses::bulk::BulkFinancialScore) -> Self { Self { symbol: value.symbol.into_inner(), - reported_currency: value.reported_currency.into_inner(), - altman_z_score: value.altman_z_score.into_inner(), - piotroski_score: value.piotroski_score.into_inner(), - working_capital: value.working_capital.into_inner(), - total_assets: value.total_assets.into_inner(), - retained_earnings: value.retained_earnings.into_inner(), - ebit: value.ebit.into_inner(), - market_cap: value.market_cap.into_inner(), - total_liabilities: value.total_liabilities.into_inner(), - revenue: value.revenue.into_inner(), + reported_currency: value.reported_currency.map(|value| value.into_inner()), + altman_z_score: value.altman_z_score.map(|value| value.into_inner()), + piotroski_score: value.piotroski_score.map(|value| value.into_inner()), + working_capital: value.working_capital.map(|value| value.into_inner()), + total_assets: value.total_assets.map(|value| value.into_inner()), + retained_earnings: value.retained_earnings.map(|value| value.into_inner()), + ebit: value.ebit.map(|value| value.into_inner()), + market_cap: value.market_cap.map(|value| value.into_inner()), + total_liabilities: value.total_liabilities.map(|value| value.into_inner()), + revenue: value.revenue.map(|value| value.into_inner()), } } } @@ -588,21 +597,21 @@ pub(crate) struct BulkPriceTargetSummary { #[pyo3(get)] pub symbol: String, #[pyo3(get)] - pub last_month_count: String, + pub last_month_count: Option, #[pyo3(get)] - pub last_month_avg_price_target: String, + pub last_month_avg_price_target: Option, #[pyo3(get)] - pub last_quarter_count: String, + pub last_quarter_count: Option, #[pyo3(get)] - pub last_quarter_avg_price_target: String, + pub last_quarter_avg_price_target: Option, #[pyo3(get)] - pub last_year_count: String, + pub last_year_count: Option, #[pyo3(get)] - pub last_year_avg_price_target: String, + pub last_year_avg_price_target: Option, #[pyo3(get)] - pub all_time_count: String, + pub all_time_count: Option, #[pyo3(get)] - pub all_time_avg_price_target: String, + pub all_time_avg_price_target: Option, #[pyo3(get)] pub publishers: String, } @@ -616,14 +625,14 @@ impl BulkPriceTargetSummary { #[pyo3(signature = (*, symbol, last_month_count, last_month_avg_price_target, last_quarter_count, last_quarter_avg_price_target, last_year_count, last_year_avg_price_target, all_time_count, all_time_avg_price_target, publishers))] fn new( symbol: String, - last_month_count: String, - last_month_avg_price_target: String, - last_quarter_count: String, - last_quarter_avg_price_target: String, - last_year_count: String, - last_year_avg_price_target: String, - all_time_count: String, - all_time_avg_price_target: String, + last_month_count: Option, + last_month_avg_price_target: Option, + last_quarter_count: Option, + last_quarter_avg_price_target: Option, + last_year_count: Option, + last_year_avg_price_target: Option, + all_time_count: Option, + all_time_avg_price_target: Option, publishers: String, ) -> Self { Self { @@ -817,14 +826,22 @@ impl From for BulkPriceTargetSu fn from(value: libfmp::responses::bulk::BulkPriceTargetSummary) -> Self { Self { symbol: value.symbol.into_inner(), - last_month_count: value.last_month_count.into_inner(), - last_month_avg_price_target: value.last_month_avg_price_target.into_inner(), - last_quarter_count: value.last_quarter_count.into_inner(), - last_quarter_avg_price_target: value.last_quarter_avg_price_target.into_inner(), - last_year_count: value.last_year_count.into_inner(), - last_year_avg_price_target: value.last_year_avg_price_target.into_inner(), - all_time_count: value.all_time_count.into_inner(), - all_time_avg_price_target: value.all_time_avg_price_target.into_inner(), + last_month_count: value.last_month_count.map(|value| value.into_inner()), + last_month_avg_price_target: value + .last_month_avg_price_target + .map(|value| value.into_inner()), + last_quarter_count: value.last_quarter_count.map(|value| value.into_inner()), + last_quarter_avg_price_target: value + .last_quarter_avg_price_target + .map(|value| value.into_inner()), + last_year_count: value.last_year_count.map(|value| value.into_inner()), + last_year_avg_price_target: value + .last_year_avg_price_target + .map(|value| value.into_inner()), + all_time_count: value.all_time_count.map(|value| value.into_inner()), + all_time_avg_price_target: value + .all_time_avg_price_target + .map(|value| value.into_inner()), publishers: value.publishers, } } @@ -845,19 +862,19 @@ pub(crate) struct BulkEtfHolding { #[pyo3(get)] pub name: String, #[pyo3(get)] - pub shares_number: String, + pub shares_number: Option, #[pyo3(get)] - pub asset: String, + pub asset: Option, #[pyo3(get)] - pub weight_percentage: String, + pub weight_percentage: Option, #[pyo3(get)] pub cusip: String, #[pyo3(get)] - pub isin: String, + pub isin: Option, #[pyo3(get)] - pub market_value: String, + pub market_value: Option, #[pyo3(get)] - pub last_updated_raw: String, + pub last_updated: ::chrono::NaiveDate, } #[gen_stub_pymethods] @@ -866,17 +883,17 @@ impl BulkEtfHolding { #[new] #[allow(clippy::too_many_arguments)] #[allow(clippy::fn_params_excessive_bools)] - #[pyo3(signature = (*, symbol, name, shares_number, asset, weight_percentage, cusip, isin, market_value, last_updated_raw))] + #[pyo3(signature = (*, symbol, name, shares_number, asset, weight_percentage, cusip, isin, market_value, last_updated))] fn new( symbol: String, name: String, - shares_number: String, - asset: String, - weight_percentage: String, + shares_number: Option, + asset: Option, + weight_percentage: Option, cusip: String, - isin: String, - market_value: String, - last_updated_raw: String, + isin: Option, + market_value: Option, + last_updated: ::chrono::NaiveDate, ) -> Self { Self { symbol, @@ -887,7 +904,7 @@ impl BulkEtfHolding { cusip, isin, market_value, - last_updated_raw, + last_updated, } } @@ -895,7 +912,7 @@ impl BulkEtfHolding { const __hash__: Option> = None; #[classattr] - #[gen_stub(override_return_type(type_repr = "tuple[typing.Literal['symbol'], typing.Literal['name'], typing.Literal['shares_number'], typing.Literal['asset'], typing.Literal['weight_percentage'], typing.Literal['cusip'], typing.Literal['isin'], typing.Literal['market_value'], typing.Literal['last_updated_raw']]", imports = ("typing",)))] + #[gen_stub(override_return_type(type_repr = "tuple[typing.Literal['symbol'], typing.Literal['name'], typing.Literal['shares_number'], typing.Literal['asset'], typing.Literal['weight_percentage'], typing.Literal['cusip'], typing.Literal['isin'], typing.Literal['market_value'], typing.Literal['last_updated']]", imports = ("typing",)))] fn __match_args__(py: Python<'_>) -> PyResult> { PyTuple::new( py, @@ -908,7 +925,7 @@ impl BulkEtfHolding { "cusip", "isin", "market_value", - "last_updated_raw", + "last_updated", ], ) } @@ -937,8 +954,8 @@ impl BulkEtfHolding { self.market_value.clone().into_bound_py_any(py)?, )?; kwargs.set_item( - "last_updated_raw", - self.last_updated_raw.clone().into_bound_py_any(py)?, + "last_updated", + self.last_updated.clone().into_bound_py_any(py)?, )?; Ok((PyTuple::empty(py), kwargs)) } @@ -965,8 +982,8 @@ impl BulkEtfHolding { self.market_value.clone().into_bound_py_any(py)?, ), ( - "last_updated_raw", - self.last_updated_raw.clone().into_bound_py_any(py)?, + "last_updated", + self.last_updated.clone().into_bound_py_any(py)?, ), ], ) @@ -1010,8 +1027,8 @@ impl BulkEtfHolding { crate::models::convert::DictValue::dict_value(&self.market_value, py)?, )?; dict.set_item( - "last_updated_raw", - crate::models::convert::DictValue::dict_value(&self.last_updated_raw, py)?, + "last_updated", + crate::models::convert::DictValue::dict_value(&self.last_updated, py)?, )?; Ok(dict) } @@ -1022,13 +1039,13 @@ impl From for BulkEtfHolding { Self { symbol: value.symbol.into_inner(), name: value.name, - shares_number: value.shares_number.into_inner(), - asset: value.asset.into_inner(), - weight_percentage: value.weight_percentage.into_inner(), + shares_number: value.shares_number.map(|value| value.into_inner()), + asset: value.asset.map(|value| value.into_inner()), + weight_percentage: value.weight_percentage.map(|value| value.into_inner()), cusip: value.cusip, - isin: value.isin.into_inner(), - market_value: value.market_value.into_inner(), - last_updated_raw: value.last_updated_raw, + isin: value.isin.map(|value| value.into_inner()), + market_value: value.market_value.map(|value| value.into_inner()), + last_updated: value.last_updated.into_inner(), } } } @@ -1046,15 +1063,15 @@ pub(crate) struct BulkUpgradesDowngradesConsensus { #[pyo3(get)] pub symbol: String, #[pyo3(get)] - pub strong_buy: String, + pub strong_buy: Option, #[pyo3(get)] - pub buy: String, + pub buy: Option, #[pyo3(get)] - pub hold: String, + pub hold: Option, #[pyo3(get)] - pub sell: String, + pub sell: Option, #[pyo3(get)] - pub strong_sell: String, + pub strong_sell: Option, #[pyo3(get)] pub consensus: String, } @@ -1068,11 +1085,11 @@ impl BulkUpgradesDowngradesConsensus { #[pyo3(signature = (*, symbol, strong_buy, buy, hold, sell, strong_sell, consensus))] fn new( symbol: String, - strong_buy: String, - buy: String, - hold: String, - sell: String, - strong_sell: String, + strong_buy: Option, + buy: Option, + hold: Option, + sell: Option, + strong_sell: Option, consensus: String, ) -> Self { Self { @@ -1186,11 +1203,11 @@ impl From fn from(value: libfmp::responses::bulk::BulkUpgradesDowngradesConsensus) -> Self { Self { symbol: value.symbol, - strong_buy: value.strong_buy.into_inner(), - buy: value.buy.into_inner(), - hold: value.hold.into_inner(), - sell: value.sell.into_inner(), - strong_sell: value.strong_sell.into_inner(), + strong_buy: value.strong_buy.map(|value| value.into_inner()), + buy: value.buy.map(|value| value.into_inner()), + hold: value.hold.map(|value| value.into_inner()), + sell: value.sell.map(|value| value.into_inner()), + strong_sell: value.strong_sell.map(|value| value.into_inner()), consensus: value.consensus, } } diff --git a/crates/fmp-py/src/models/company.rs b/crates/fmp-py/src/models/company.rs index 00397929..166e447f 100644 --- a/crates/fmp-py/src/models/company.rs +++ b/crates/fmp-py/src/models/company.rs @@ -22,9 +22,9 @@ pub(crate) struct CompanyProfile { #[pyo3(get)] pub range: String, #[pyo3(get)] - pub change: f64, + pub change: Option, #[pyo3(get)] - pub change_percentage: f64, + pub change_percentage: Option, #[pyo3(get)] pub volume: f64, #[pyo3(get)] @@ -36,7 +36,7 @@ pub(crate) struct CompanyProfile { #[pyo3(get)] pub cik: Option, #[pyo3(get)] - pub isin: String, + pub isin: Option, #[pyo3(get)] pub cusip: Option, #[pyo3(get)] @@ -54,7 +54,7 @@ pub(crate) struct CompanyProfile { #[pyo3(get)] pub sector: String, #[pyo3(get)] - pub country: String, + pub country: Option, #[pyo3(get)] pub full_time_employees: Option, #[pyo3(get)] @@ -97,14 +97,14 @@ impl CompanyProfile { beta: f64, last_dividend: f64, range: String, - change: f64, - change_percentage: f64, + change: Option, + change_percentage: Option, volume: f64, average_volume: f64, company_name: String, currency: String, cik: Option, - isin: String, + isin: Option, cusip: Option, exchange_full_name: String, exchange: String, @@ -113,7 +113,7 @@ impl CompanyProfile { description: String, ceo: String, sector: String, - country: String, + country: Option, full_time_employees: Option, phone: Option, address: String, @@ -529,7 +529,7 @@ impl From for CompanyProfile { company_name: value.company_name, currency: value.currency.into_inner(), cik: value.cik.map(|value| value.into_inner()), - isin: value.isin.into_inner(), + isin: value.isin.map(|value| value.into_inner()), cusip: value.cusip.map(|value| value.into_inner()), exchange_full_name: value.exchange_full_name, exchange: value.exchange.into_inner(), @@ -538,7 +538,7 @@ impl From for CompanyProfile { description: value.description, ceo: value.ceo, sector: value.sector.into_inner(), - country: value.country.into_inner(), + country: value.country.map(|value| value.into_inner()), full_time_employees: value.full_time_employees.map(|value| value.into_inner()), phone: value.phone, address: value.address, diff --git a/crates/fmp-py/tests/conftest.py b/crates/fmp-py/tests/conftest.py index 8e5145d2..24bb1616 100644 --- a/crates/fmp-py/tests/conftest.py +++ b/crates/fmp-py/tests/conftest.py @@ -55,12 +55,19 @@ def test_profile_decodes_documented_fixture(client, fixture_server): JSON_CONTENT_TYPE = "application/json" +CSV_CONTENT_TYPE = "text/csv" + def load_fixture(name: str) -> Any: """Return the parsed JSON body of ``crates/libfmp/tests/fixtures/``.""" return json.loads((FIXTURES_DIR / name).read_text(encoding="utf-8")) +def load_csv_fixture(name: str) -> bytes: + """Return the raw CSV body of ``crates/libfmp/tests/fixtures/``, as bulk routes answer.""" + return (FIXTURES_DIR / name).read_bytes() + + def _errors_namespace() -> SimpleNamespace: """Expose the exception hierarchy under its public names. diff --git a/crates/fmp-py/tests/test_bulk_a.py b/crates/fmp-py/tests/test_bulk_a.py index 3568cc0e..7fa918e0 100644 --- a/crates/fmp-py/tests/test_bulk_a.py +++ b/crates/fmp-py/tests/test_bulk_a.py @@ -2,10 +2,9 @@ Covers the eleven bulk methods whose bodies live in ``fmp.bulk.snapshots``, ``fmp.bulk.metrics``, and (for ``company_profiles``) ``fmp.company``: one test -per method routes the documented fixture body, calls the method, and asserts -the exact request target plus a few typed fields. Bulk bodies are CSV-backed, -so most numeric columns arrive as ``str``; only the date columns decode to -``datetime.date``. The expected targets are the ones the Rust +per method routes the live CSV fixture body as ``text/csv``, calls the method, and asserts +the exact request target plus a few typed fields. Numeric columns arrive as +``str`` verbatim; only the date columns decode to ``datetime.date``. The expected targets are the ones the Rust ``bulk_snapshot_endpoints.rs`` and ``bulk_metrics_endpoints.rs`` tests pin. """ @@ -14,7 +13,7 @@ from typing import Any import pytest -from conftest import FixtureServer, load_fixture +from conftest import CSV_CONTENT_TYPE, FixtureServer, load_csv_fixture from fmp.bulk import BulkNamespace from fmp.bulk.metrics import BulkEarningsSurprise, BulkFinancialRatiosTtm, BulkKeyMetricsTtm, BulkStockPeer from fmp.bulk.snapshots import ( @@ -27,7 +26,9 @@ ) from fmp.company import CompanyProfile -DATE_2025_07_09 = datetime.date(2025, 7, 9) +def route_csv(fixture_server: FixtureServer, path: str, fixture: str) -> None: + """Serve the shared CSV fixture on ``path`` as ``text/csv``, as the bulk routes answer.""" + fixture_server.route(path, load_csv_fixture(fixture), content_type=CSV_CONTENT_TYPE) def test_bulk_namespace_is_the_generated_type(client: Any) -> None: @@ -37,24 +38,25 @@ def test_bulk_namespace_is_the_generated_type(client: Any) -> None: def test_company_profiles_form_encodes_the_part(client: Any, fixture_server: FixtureServer) -> None: """``company_profiles`` sends ``part`` verbatim, form-encoding spaces and slashes.""" - fixture_server.route("/profile-bulk", load_fixture("bulk_company_profiles.json")) + route_csv(fixture_server, "/profile-bulk", "bulk_company_profiles.csv") rows = client.bulk.company_profiles("segment 0/alpha") assert fixture_server.requests[0].target == "/profile-bulk?part=segment+0%2Falpha" - assert len(rows) == 1 + assert len(rows) == 4 row = rows[0] assert isinstance(row, CompanyProfile) - assert row.symbol == "AAPL" - assert row.company_name == "Apple Inc." - assert row.price == pytest.approx(271.36) - assert row.market_cap == 4_009_711_150_080 - assert row.ipo_date == datetime.date(1980, 12, 12) + assert row.symbol == "WMB" + assert row.company_name == "The Williams Companies, Inc." + assert row.price == pytest.approx(67.78) + assert row.market_cap == 82_906_291_252 assert row.is_etf is False + assert (rows[2].symbol, rows[2].change, rows[2].change_percentage) == ("ACCV", None, None) + assert (rows[3].symbol, rows[3].isin, rows[3].country) == ("CFHAX", None, None) def test_company_profiles_with_a_numeric_part(client: Any, fixture_server: FixtureServer) -> None: """A plain numeric partition is sent as-is without inferring a range.""" - fixture_server.route("/profile-bulk", load_fixture("bulk_company_profiles.json")) + route_csv(fixture_server, "/profile-bulk", "bulk_company_profiles.csv") client.bulk.company_profiles("0") assert fixture_server.requests[0].target == "/profile-bulk?part=0" @@ -62,167 +64,182 @@ def test_company_profiles_with_a_numeric_part(client: Any, fixture_server: Fixtu def test_stock_ratings_takes_no_arguments(client: Any, fixture_server: FixtureServer) -> None: """``stock_ratings`` maps to ``/rating-bulk`` with no query string.""" - fixture_server.route("/rating-bulk", load_fixture("bulk_stock_ratings.json")) + route_csv(fixture_server, "/rating-bulk", "bulk_stock_ratings.csv") rows = client.bulk.stock_ratings() assert fixture_server.requests[0].target == "/rating-bulk" - assert len(rows) == 1 + assert len(rows) == 2 row = rows[0] assert isinstance(row, BulkStockRating) assert row.symbol == "000001.SZ" - assert row.date == DATE_2025_07_09 - assert row.rating == "B+" - assert row.discounted_cash_flow_score == "5" + assert row.date == datetime.date(2026, 9, 30) + assert row.rating == "B-" + assert row.discounted_cash_flow_score == "1" with pytest.raises(TypeError): client.bulk.stock_ratings("0") def test_dcf_valuations_decodes_the_spaced_stock_price_column(client: Any, fixture_server: FixtureServer) -> None: """``dcf_valuations`` maps the provider's ``Stock Price`` column to ``stock_price``.""" - fixture_server.route("/dcf-bulk", load_fixture("bulk_dcf_valuations.json")) + route_csv(fixture_server, "/dcf-bulk", "bulk_dcf_valuations.csv") rows = client.bulk.dcf_valuations() assert fixture_server.requests[0].target == "/dcf-bulk" - assert len(rows) == 1 + assert len(rows) == 3 row = rows[0] assert isinstance(row, BulkDcfValuation) - assert row.symbol == "000002.SZ" - assert row.date == DATE_2025_07_09 - assert row.dcf == "179.6654688379575" - assert row.stock_price == "6.54" + assert row.symbol == "000006.SZ" + assert row.date == datetime.date(2026, 9, 29) + assert row.dcf == "2.525226853334803" + assert row.stock_price == "7.62" + assert rows[2].dcf is None + assert rows[2].stock_price == "1.72" def test_financial_scores_keeps_string_backed_numbers(client: Any, fixture_server: FixtureServer) -> None: """``financial_scores`` maps to ``/scores-bulk`` and keeps the CSV strings.""" - fixture_server.route("/scores-bulk", load_fixture("bulk_financial_scores.json")) + route_csv(fixture_server, "/scores-bulk", "bulk_financial_scores.csv") rows = client.bulk.financial_scores() assert fixture_server.requests[0].target == "/scores-bulk" - assert len(rows) == 1 + assert len(rows) == 3 row = rows[0] assert isinstance(row, BulkFinancialScore) assert row.symbol == "000001.SZ" assert row.reported_currency == "CNY" - assert row.altman_z_score == "0.29153682196643543" - assert row.piotroski_score == "5" - assert row.market_cap == "236751980000" + assert row.altman_z_score == "-0.06634014283050256" + assert row.piotroski_score == "4" + assert row.market_cap == "219286875637" + assert rows[2].reported_currency is None + assert rows[2].altman_z_score is None def test_price_target_summaries_preserves_the_raw_publishers_cell(client: Any, fixture_server: FixtureServer) -> None: - """``price_target_summaries`` keeps the provider's malformed ``publishers`` cell verbatim.""" - fixture_server.route("/price-target-summary-bulk", load_fixture("bulk_price_target_summaries.json")) + """``price_target_summaries`` keeps the quoted ``publishers`` cell verbatim as one string.""" + route_csv(fixture_server, "/price-target-summary-bulk", "bulk_price_target_summaries.csv") rows = client.bulk.price_target_summaries() assert fixture_server.requests[0].target == "/price-target-summary-bulk" - assert len(rows) == 1 + assert len(rows) == 2 row = rows[0] assert isinstance(row, BulkPriceTargetSummary) assert row.symbol == "A" - assert row.all_time_count == "18" - assert row.all_time_avg_price_target == "146.61" - assert row.publishers == '[""TheFly"' + assert row.all_time_count == "53" + assert row.all_time_avg_price_target == "159.49" + assert row.publishers == '["StreetInsider","Benzinga","Pulse 2.0"]' def test_etf_holdings_form_encodes_the_part(client: Any, fixture_server: FixtureServer) -> None: - """``etf_holdings`` shares the ``part`` shape and keeps the raw ``lastUpdated"`` column.""" - fixture_server.route("/etf-holder-bulk", load_fixture("bulk_etf_holdings.json")) + """``etf_holdings`` shares the ``part`` shape and decodes ``lastUpdated`` as a date.""" + route_csv(fixture_server, "/etf-holder-bulk", "bulk_etf_holdings.csv") rows = client.bulk.etf_holdings("part 1/beta") assert fixture_server.requests[0].target == "/etf-holder-bulk?part=part+1%2Fbeta" - assert len(rows) == 1 + assert len(rows) == 4 row = rows[0] assert isinstance(row, BulkEtfHolding) - assert row.symbol == "EXCH.AS" - assert row.asset == "009150.KS" - assert row.isin == "KR7009150004" - assert row.weight_percentage == "0.09611" - assert row.last_updated_raw == '2024-09-06"' - - -def test_upgrades_downgrades_consensus_accepts_an_empty_symbol(client: Any, fixture_server: FixtureServer) -> None: - """``upgrades_downgrades_consensus`` decodes the documented empty-symbol row.""" - fixture_server.route( - "/upgrades-downgrades-consensus-bulk", load_fixture("bulk_upgrades_downgrades_consensus.json") - ) + assert row.symbol == " -- " + assert row.asset == "TDW" + assert row.isin == "US88642R1095" + assert row.weight_percentage == "2.63" + assert row.last_updated == datetime.date(2026, 9, 27) + assert (rows[2].asset, rows[2].isin, rows[2].cusip) == (None, None, "") + + +def test_upgrades_downgrades_consensus_takes_no_arguments(client: Any, fixture_server: FixtureServer) -> None: + """``upgrades_downgrades_consensus`` maps to its bulk route and keeps the counts as text.""" + route_csv(fixture_server, "/upgrades-downgrades-consensus-bulk", "bulk_upgrades_downgrades_consensus.csv") rows = client.bulk.upgrades_downgrades_consensus() assert fixture_server.requests[0].target == "/upgrades-downgrades-consensus-bulk" - assert len(rows) == 1 + assert len(rows) == 2 row = rows[0] assert isinstance(row, BulkUpgradesDowngradesConsensus) - assert row.symbol == "" + assert row.symbol == "000550.SZ" assert row.consensus == "Buy" - assert row.buy == "1" - assert row.strong_buy == "0" + assert row.buy == "14" + assert row.strong_buy == "1" def test_key_metrics_ttm_takes_no_arguments(client: Any, fixture_server: FixtureServer) -> None: """``key_metrics_ttm`` maps to ``/key-metrics-ttm-bulk`` with no query string.""" - fixture_server.route("/key-metrics-ttm-bulk", load_fixture("bulk_key_metrics_ttm.json")) + route_csv(fixture_server, "/key-metrics-ttm-bulk", "bulk_key_metrics_ttm.csv") rows = client.bulk.key_metrics_ttm() assert fixture_server.requests[0].target == "/key-metrics-ttm-bulk" - assert len(rows) == 1 + assert len(rows) == 3 row = rows[0] assert isinstance(row, BulkKeyMetricsTtm) assert row.symbol == "000001.SZ" - assert row.market_cap == "249171756000" - assert row.ev_to_ebitda_ttm == "-14.656106051669223" - assert row.earnings_yield_ttm == "0.14960077934639543" + assert row.market_cap == "224526473551" + assert row.ev_to_ebitda_ttm == "29.23198788110799" + assert row.earnings_yield_ttm == "0.19355846887948813" + assert rows[2].enterprise_value_ttm is None def test_financial_ratios_ttm_takes_no_arguments(client: Any, fixture_server: FixtureServer) -> None: """``financial_ratios_ttm`` maps to ``/ratios-ttm-bulk`` with no query string.""" - fixture_server.route("/ratios-ttm-bulk", load_fixture("bulk_financial_ratios_ttm.json")) + route_csv(fixture_server, "/ratios-ttm-bulk", "bulk_financial_ratios_ttm.csv") rows = client.bulk.financial_ratios_ttm() assert fixture_server.requests[0].target == "/ratios-ttm-bulk" - assert len(rows) == 1 + assert len(rows) == 3 row = rows[0] assert isinstance(row, BulkFinancialRatiosTtm) assert row.symbol == "000001.SZ" - assert row.asset_turnover_ttm == "0.029075827062555015" - assert row.book_value_per_share_ttm == "22.260885357516656" - assert row.net_income_per_ebt_ttm == "0.8225101702576465" + assert row.asset_turnover_ttm == "0.03463701192046656" + assert row.book_value_per_share_ttm == "24.12738217279904" + assert row.net_income_per_ebt_ttm == "0.83539656299258" def test_stock_peers_takes_no_arguments(client: Any, fixture_server: FixtureServer) -> None: """``stock_peers`` maps to ``/peers-bulk`` and keeps the peers cell as one string.""" - fixture_server.route("/peers-bulk", load_fixture("bulk_stock_peers.json")) + route_csv(fixture_server, "/peers-bulk", "bulk_stock_peers.csv") rows = client.bulk.stock_peers() assert fixture_server.requests[0].target == "/peers-bulk" - assert len(rows) == 1 + assert len(rows) == 3 row = rows[0] assert isinstance(row, BulkStockPeer) assert row.symbol == "000001.SZ" - assert row.peers == "600036.SS" + assert row.peers == "3698.HK,600000.SS,600015.SS,600016.SS,600036.SS,601166.SS,601658.SS" def test_earnings_surprises_encodes_the_required_year(client: Any, fixture_server: FixtureServer) -> None: """``earnings_surprises`` sends ``year`` as the only query parameter.""" - fixture_server.route("/earnings-surprises-bulk", load_fixture("bulk_earnings_surprises.json")) + route_csv(fixture_server, "/earnings-surprises-bulk", "bulk_earnings_surprises.csv") rows = client.bulk.earnings_surprises(2026) assert fixture_server.requests[0].target == "/earnings-surprises-bulk?year=2026" - assert len(rows) == 1 + assert len(rows) == 3 row = rows[0] assert isinstance(row, BulkEarningsSurprise) - assert row.symbol == "AMKYF" - assert row.date == DATE_2025_07_09 - assert row.eps_actual == "0.3631" - assert row.eps_estimated == "0.3615" - assert row.last_updated == DATE_2025_07_09 + assert row.symbol == "AUTO.OL" + assert row.date == datetime.date(2024, 12, 31) + assert row.eps_actual == "0.1332" + assert row.eps_estimated == "0.1581" + assert row.last_updated == datetime.date(2025, 10, 7) + assert rows[2].eps_estimated is None def test_earnings_surprises_accepts_the_year_keyword(client: Any, fixture_server: FixtureServer) -> None: """The constructor argument is also reachable by keyword.""" - fixture_server.route("/earnings-surprises-bulk", load_fixture("bulk_earnings_surprises.json")) + route_csv(fixture_server, "/earnings-surprises-bulk", "bulk_earnings_surprises.csv") client.bulk.earnings_surprises(year=2024) assert fixture_server.requests[0].target == "/earnings-surprises-bulk?year=2024" +@pytest.mark.parametrize("body", [b"", b'"symbol","peers"\n'], ids=["empty", "header-only"]) +def test_empty_and_header_only_csv_bodies_decode_to_no_rows( + client: Any, fixture_server: FixtureServer, body: bytes +) -> None: + """An empty or header-only ``text/csv`` body is an empty list, not a provider error.""" + fixture_server.route("/peers-bulk", body, content_type=CSV_CONTENT_TYPE) + + assert client.bulk.stock_peers() == [] + + def test_status_error_carries_the_bulk_endpoint_id( client: Any, fixture_server: FixtureServer, errors: SimpleNamespace ) -> None: @@ -239,8 +256,8 @@ def test_status_error_carries_the_bulk_endpoint_id( def test_decode_error_names_the_bulk_endpoint( client: Any, fixture_server: FixtureServer, errors: SimpleNamespace ) -> None: - """A non-JSON body on a bulk method maps to ``FmpDecodeError``.""" - fixture_server.route("/peers-bulk", b"not-json", content_type="text/plain") + """A body that is not ``text/csv`` on a bulk method maps to ``FmpDecodeError``.""" + fixture_server.route("/peers-bulk", b"not-csv", content_type="text/plain") with pytest.raises(errors.FmpDecodeError): client.bulk.stock_peers() assert fixture_server.requests[0].target == "/peers-bulk" diff --git a/crates/fmp-py/tests/test_bulk_b.py b/crates/fmp-py/tests/test_bulk_b.py index f863ca74..374cf04a 100644 --- a/crates/fmp-py/tests/test_bulk_b.py +++ b/crates/fmp-py/tests/test_bulk_b.py @@ -12,148 +12,151 @@ from typing import Any import pytest -from conftest import FixtureServer, load_fixture +from conftest import CSV_CONTENT_TYPE, FixtureServer, load_csv_fixture from fmp.bulk.balance import BulkBalanceSheetStatement, BulkBalanceSheetStatementGrowth from fmp.bulk.cash_flow import BulkCashFlowStatement, BulkCashFlowStatementGrowth from fmp.bulk.eod import BulkEodBar from fmp.bulk.income import BulkIncomeStatement, BulkIncomeStatementGrowth -DATE_2025_03_31 = datetime.date(2025, 3, 31) DATE_2024_10_22 = datetime.date(2024, 10, 22) +def route_csv(fixture_server: FixtureServer, path: str, fixture: str) -> None: + """Serve the shared CSV fixture on ``path`` as ``text/csv``, as the bulk routes answer.""" + fixture_server.route(path, load_csv_fixture(fixture), content_type=CSV_CONTENT_TYPE) + + def test_income_statements_encodes_year_then_period(client: Any, fixture_server: FixtureServer) -> None: """``income_statements`` sends ``year`` before ``period`` and decodes the datetime column.""" - fixture_server.route("/income-statement-bulk", load_fixture("bulk_income_statements.json")) + route_csv(fixture_server, "/income-statement-bulk", "bulk_income_statements.csv") rows = client.bulk.income_statements(2026, "Q1") assert fixture_server.requests[0].target == "/income-statement-bulk?year=2026&period=Q1" - assert len(rows) == 1 + assert len(rows) == 3 row = rows[0] assert isinstance(row, BulkIncomeStatement) assert row.symbol == "000001.SZ" - assert row.date == DATE_2025_03_31 - assert row.filing_date == DATE_2025_03_31 - assert row.accepted_date == datetime.datetime(2025, 3, 31, 0, 0, 0) - assert (row.fiscal_year, row.period) == ("2025", "Q1") - assert row.revenue == "33644000000" - assert row.net_income == "14096000000" - assert row.eps == "0.62" + assert row.date == datetime.date(2024, 12, 31) + assert row.filing_date == datetime.date(2024, 12, 31) + assert row.accepted_date == datetime.datetime(2024, 12, 31, 0, 0, 0) + assert (row.fiscal_year, row.period) == ("2024", "FY") + assert row.revenue == "251641000000" + assert row.net_income == "44508000000" + assert row.eps == "2.15" + assert (rows[2].symbol, rows[2].eps, rows[2].weighted_average_shs_out_dil) == ("OASMY", None, None) def test_income_statement_growth_with_keyword_arguments(client: Any, fixture_server: FixtureServer) -> None: """``income_statement_growth`` accepts both constructor arguments by keyword.""" - fixture_server.route("/income-statement-growth-bulk", load_fixture("bulk_income_statement_growth.json")) + route_csv(fixture_server, "/income-statement-growth-bulk", "bulk_income_statement_growth.csv") rows = client.bulk.income_statement_growth(period="Q1", year=2026) assert fixture_server.requests[0].target == "/income-statement-growth-bulk?year=2026&period=Q1" - assert len(rows) == 1 + assert len(rows) == 2 row = rows[0] assert isinstance(row, BulkIncomeStatementGrowth) assert row.symbol == "000001.SZ" - assert row.date == DATE_2025_03_31 - assert (row.fiscal_year, row.period) == ("2025", "Q1") - assert row.growth_revenue == "-0.04159070191431176" - assert row.growth_net_income == "1.9495710399665203" - assert row.growth_eps == "1.6956521739130435" + assert row.date == datetime.date(2024, 12, 31) + assert (row.fiscal_year, row.period) == ("2024", "FY") + assert row.growth_revenue == "-0.08220846812871789" + assert row.growth_net_income == "-0.04191152728446884" + assert row.growth_eps == "-0.04444444444444448" def test_balance_sheet_statements_with_the_full_year_period(client: Any, fixture_server: FixtureServer) -> None: """``balance_sheet_statements`` accepts ``FY`` and decodes the mid-day accepted timestamp.""" - fixture_server.route("/balance-sheet-statement-bulk", load_fixture("bulk_balance_sheet_statements.json")) + route_csv(fixture_server, "/balance-sheet-statement-bulk", "bulk_balance_sheet_statements.csv") rows = client.bulk.balance_sheet_statements(2025, "FY") assert fixture_server.requests[0].target == "/balance-sheet-statement-bulk?year=2025&period=FY" - assert len(rows) == 1 + assert len(rows) == 2 row = rows[0] assert isinstance(row, BulkBalanceSheetStatement) - assert row.symbol == "MTLRP.ME" - assert row.date == DATE_2025_03_31 - assert row.filing_date == datetime.date(2025, 5, 31) - assert row.accepted_date == datetime.datetime(2025, 3, 31, 7, 0, 0) - assert row.total_assets == "247871857000" - assert row.cash_and_cash_equivalents == "1985000" - assert row.total_debt == "183766847000" + assert row.symbol == "000001.SZ" + assert row.date == datetime.date(2024, 12, 31) + assert row.filing_date == datetime.date(2024, 12, 31) + assert row.accepted_date == datetime.datetime(2024, 12, 31, 0, 0, 0) + assert row.total_assets == "5769270000000" + assert row.cash_and_cash_equivalents == "680935000000" + assert row.total_debt == "756251000000" def test_balance_sheet_statement_growth_is_case_insensitive_on_period( client: Any, fixture_server: FixtureServer ) -> None: """A lower-case fiscal period is normalised to the documented upper-case spelling.""" - fixture_server.route( - "/balance-sheet-statement-growth-bulk", load_fixture("bulk_balance_sheet_statement_growth.json") - ) + route_csv(fixture_server, "/balance-sheet-statement-growth-bulk", "bulk_balance_sheet_statement_growth.csv") rows = client.bulk.balance_sheet_statement_growth(2026, "q4") assert fixture_server.requests[0].target == "/balance-sheet-statement-growth-bulk?year=2026&period=Q4" - assert len(rows) == 1 + assert len(rows) == 2 row = rows[0] assert isinstance(row, BulkBalanceSheetStatementGrowth) assert row.symbol == "000001.SZ" - assert row.date == DATE_2025_03_31 - assert row.growth_total_assets == "0.001488576544346165" - assert row.growth_cash_and_cash_equivalents == "0.09574482145872953" - assert row.growth_net_debt == "-0.09574482145872953" + assert row.date == datetime.date(2024, 12, 31) + assert row.growth_total_assets == "0.032602509058340653" + assert row.growth_cash_and_cash_equivalents == "0.13745663137040692" + assert row.growth_net_debt == "-0.5927764260610976" def test_cash_flow_statements_encodes_year_then_period(client: Any, fixture_server: FixtureServer) -> None: """``cash_flow_statements`` maps to ``/cash-flow-statement-bulk`` in documented order.""" - fixture_server.route("/cash-flow-statement-bulk", load_fixture("bulk_cash_flow_statements.json")) + route_csv(fixture_server, "/cash-flow-statement-bulk", "bulk_cash_flow_statements.csv") rows = client.bulk.cash_flow_statements(2026, "Q2") assert fixture_server.requests[0].target == "/cash-flow-statement-bulk?year=2026&period=Q2" - assert len(rows) == 1 + assert len(rows) == 2 row = rows[0] assert isinstance(row, BulkCashFlowStatement) assert row.symbol == "000001.SZ" - assert row.date == DATE_2025_03_31 - assert row.accepted_date == datetime.datetime(2025, 3, 31, 0, 0, 0) - assert row.net_income == "0" - assert row.free_cash_flow == "162608000000" - assert row.capital_expenditure == "-338000000" + assert row.date == datetime.date(2024, 3, 31) + assert row.accepted_date == datetime.datetime(2024, 3, 30, 20, 0, 0) + assert row.net_income == "14932000000" + assert row.free_cash_flow == "-21623000000" + assert row.capital_expenditure == "-241000000" def test_cash_flow_statement_growth_encodes_year_then_period(client: Any, fixture_server: FixtureServer) -> None: """``cash_flow_statement_growth`` maps to ``/cash-flow-statement-growth-bulk``.""" - fixture_server.route("/cash-flow-statement-growth-bulk", load_fixture("bulk_cash_flow_statement_growth.json")) + route_csv(fixture_server, "/cash-flow-statement-growth-bulk", "bulk_cash_flow_statement_growth.csv") rows = client.bulk.cash_flow_statement_growth(2026, "Q3") assert fixture_server.requests[0].target == "/cash-flow-statement-growth-bulk?year=2026&period=Q3" - assert len(rows) == 1 + assert len(rows) == 2 row = rows[0] assert isinstance(row, BulkCashFlowStatementGrowth) assert row.symbol == "000001.SZ" - assert row.date == DATE_2025_03_31 - assert (row.fiscal_year, row.period) == ("2025", "Q1") - assert row.growth_net_income == "0" - assert row.growth_free_cash_flow == "3.16553689621649" - assert row.growth_capital_expenditure == "0.7332280978689818" + assert row.date == datetime.date(2024, 12, 31) + assert (row.fiscal_year, row.period) == ("2024", "FY") + assert row.growth_net_income == "-0.04191152728446884" + assert row.growth_free_cash_flow == "-0.3179880266323505" + assert row.growth_capital_expenditure == "0.22868217054263565" def test_eod_accepts_a_date_object(client: Any, fixture_server: FixtureServer) -> None: """``eod`` encodes a ``datetime.date`` as the ISO ``date`` parameter.""" - fixture_server.route("/eod-bulk", load_fixture("bulk_eod.json")) + route_csv(fixture_server, "/eod-bulk", "bulk_eod.csv") rows = client.bulk.eod(DATE_2024_10_22) assert fixture_server.requests[0].target == "/eod-bulk?date=2024-10-22" - assert len(rows) == 1 + assert len(rows) == 2 row = rows[0] assert isinstance(row, BulkEodBar) - assert row.symbol == "EGS745W1C011.CA" - assert row.date == DATE_2024_10_22 - assert row.open == "2.67" - assert row.close == "2.93" - assert row.adj_close == "2.93" - assert row.volume == "920904" + assert row.symbol == "PCFD.SG" + assert row.date == datetime.date(2025, 6, 2) + assert row.open == "7.35" + assert row.close == "19.71" + assert row.adj_close == "19.71" + assert row.volume == "0" def test_eod_accepts_an_iso_string(client: Any, fixture_server: FixtureServer) -> None: """``eod`` also takes the date as an ISO string, by keyword.""" - fixture_server.route("/eod-bulk", load_fixture("bulk_eod.json")) + route_csv(fixture_server, "/eod-bulk", "bulk_eod.csv") rows = client.bulk.eod(date="2024-10-22") assert fixture_server.requests[0].target == "/eod-bulk?date=2024-10-22" - assert rows[0].date == DATE_2024_10_22 + assert rows[1].date == datetime.date(2025, 6, 2) def test_statement_arguments_are_required(client: Any, fixture_server: FixtureServer) -> None: diff --git a/crates/fmp-py/tests/typecheck_contract.py b/crates/fmp-py/tests/typecheck_contract.py index 1473b119..cae75ef2 100644 --- a/crates/fmp-py/tests/typecheck_contract.py +++ b/crates/fmp-py/tests/typecheck_contract.py @@ -151,12 +151,12 @@ def check_bulk_contract(client: FmpClient) -> None: ipo_date: datetime.date | None = profiles[0].ipo_date statements: list[BulkIncomeStatement] = client.bulk.income_statements(2026, "Q1") accepted: datetime.datetime = statements[0].accepted_date - revenue: str = statements[0].revenue + revenue: str | None = statements[0].revenue surprises: list[BulkEarningsSurprise] = client.bulk.earnings_surprises(year=2026) - eps_actual: str = surprises[0].eps_actual + eps_actual: str | None = surprises[0].eps_actual bars: list[BulkEodBar] = client.bulk.eod(datetime.date(2024, 10, 22)) more_bars: list[BulkEodBar] = client.bulk.eod(date="2024-10-22") - close: str = bars[0].close + close: str | None = bars[0].close _ = (ipo_date, accepted, revenue, eps_actual, close, more_bars) diff --git a/crates/libfmp/Cargo.toml b/crates/libfmp/Cargo.toml index 7fd53c52..d6a79051 100644 --- a/crates/libfmp/Cargo.toml +++ b/crates/libfmp/Cargo.toml @@ -31,6 +31,7 @@ harness = false [dependencies] bytes.workspace = true chrono.workspace = true +csv.workspace = true http.workspace = true reqwest.workspace = true serde.workspace = true diff --git a/crates/libfmp/src/client.rs b/crates/libfmp/src/client.rs index 314c047e..dc209619 100644 --- a/crates/libfmp/src/client.rs +++ b/crates/libfmp/src/client.rs @@ -33,9 +33,17 @@ const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30); const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); /// Default maximum bytes buffered for each HTTP response (64 MiB). /// -/// This finite default also applies to bulk and XLSX convenience methods. -/// Callers expecting a larger response can raise the client limit explicitly. +/// This finite default also applies to XLSX convenience methods. Callers +/// expecting a larger response can raise the client limit explicitly. pub const DEFAULT_MAX_RESPONSE_BODY_BYTES: usize = 64 * 1024 * 1024; + +/// Default maximum bytes buffered for each CSV bulk response (256 MiB). +/// +/// Whole-market bulk bodies exceed the general default: `ratios-ttm-bulk` +/// measured 65.6 MiB on 2026-10-01. It applies only while the client limit +/// is unset; [`ClientBuilder::max_response_body_bytes`] replaces it for +/// every endpoint, bulk included (ADR 0035). +pub const DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES: usize = 256 * 1024 * 1024; const MAX_REDIRECTS: usize = 10; #[derive(Clone)] @@ -66,7 +74,7 @@ pub struct ClientBuilder { user_agent: String, timeout: Duration, connect_timeout: Duration, - max_response_body_bytes: usize, + max_response_body_bytes: Option, danger_allow_insecure_authentication: bool, redirect_policy: RedirectPolicy, executor: Option>, @@ -84,7 +92,7 @@ impl Default for ClientBuilder { user_agent: format!("libfmp/{VERSION}"), timeout: DEFAULT_TIMEOUT, connect_timeout: DEFAULT_CONNECT_TIMEOUT, - max_response_body_bytes: DEFAULT_MAX_RESPONSE_BODY_BYTES, + max_response_body_bytes: None, danger_allow_insecure_authentication: false, redirect_policy: RedirectPolicy::SameOrigin, executor: None, @@ -180,10 +188,12 @@ impl ClientBuilder { /// error body while streaming. Buffers returned by custom executors are /// checked defensively for every status, including redirects. An endpoint /// can replace the limit with - /// [`EndpointSpec::with_max_response_body_bytes`]. Bulk and XLSX - /// convenience methods inherit this client-wide value. + /// [`EndpointSpec::with_max_response_body_bytes`]. Until this is set, + /// each endpoint uses its own default: [`DEFAULT_MAX_RESPONSE_BODY_BYTES`], + /// or [`DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES`] for CSV bulk routes. Once + /// set, it applies to every endpoint, bulk and XLSX included. pub fn max_response_body_bytes(mut self, max_bytes: usize) -> Self { - self.max_response_body_bytes = max_bytes; + self.max_response_body_bytes = Some(max_bytes); self } @@ -300,7 +310,7 @@ struct ClientInner { executor: Arc, deadline_backend: DeadlineBackend, timeout: Duration, - max_response_body_bytes: usize, + max_response_body_bytes: Option, redirect_policy: RedirectPolicy, } @@ -396,7 +406,8 @@ impl Client { apply_query_auth(&mut url, self.inner.auth.query.as_ref()); let max_body_bytes = endpoint .max_response_body_bytes() - .unwrap_or(self.inner.max_response_body_bytes); + .or(self.inner.max_response_body_bytes) + .unwrap_or(endpoint.default_max_response_body_bytes()); let response = self .inner .executor @@ -1115,9 +1126,7 @@ mod tests { #[test] fn default_response_limit_is_the_documented_finite_value() { assert_eq!(DEFAULT_MAX_RESPONSE_BODY_BYTES, 64 * 1024 * 1024); - assert_eq!( - ClientBuilder::default().max_response_body_bytes, - DEFAULT_MAX_RESPONSE_BODY_BYTES - ); + assert_eq!(DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES, 256 * 1024 * 1024); + assert_eq!(ClientBuilder::default().max_response_body_bytes, None); } } diff --git a/crates/libfmp/src/endpoints/bulk.rs b/crates/libfmp/src/endpoints/bulk.rs index f633a5fb..72e2e124 100644 --- a/crates/libfmp/src/endpoints/bulk.rs +++ b/crates/libfmp/src/endpoints/bulk.rs @@ -1,17 +1,21 @@ //! Bulk endpoint contracts. //! -//! Every bulk route answers one `GET` with one bare JSON array, and each -//! convenience method sends exactly one request: the client never paginates -//! a `part`, retries, or fans out on the caller's behalf. Numeric cells arrive -//! as strings on every route except `profile-bulk` and are preserved verbatim -//! as [`crate::codecs::NumericString`]. +//! Every bulk route answers one `GET` with one headed `text/csv` body (ADR +//! 0035), and each convenience method sends exactly one request: the client +//! never paginates a `part`, retries, or fans out on the caller's behalf. Each +//! record decodes into one row by header name; an empty or header-only body is +//! no rows. Numeric cells are preserved verbatim as +//! [`crate::codecs::NumericString`] on every route except `profile-bulk`, and +//! an empty numeric cell is `None`. //! -//! Bulk descriptors and convenience methods inherit the client's finite -//! response-body limit and its single logical timeout. Raise -//! [`crate::client::ClientBuilder::max_response_body_bytes`] and, when needed, -//! [`crate::client::ClientBuilder::timeout`] when a known provider response -//! will exceed the configured limits. The Python binding exposes the same -//! eighteen methods under `client.bulk`. +//! Bulk descriptors buffer up to +//! [`crate::client::DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES`] (256 MiB) while the +//! client limit is unset: whole-market bodies such as `ratios-ttm-bulk` +//! exceed the 64 MiB general default. A limit set with +//! [`crate::client::ClientBuilder::max_response_body_bytes`] applies to bulk +//! routes too, and every bulk call shares the client's single logical +//! timeout, which [`crate::client::ClientBuilder::timeout`] can raise. The +//! Python binding exposes the same eighteen methods under `client.bulk`. use crate::{ Client, Result, @@ -169,39 +173,39 @@ const US_ONLY: EndpointMetadata = pub fn bulk_company_profiles( query: BulkPartQuery, ) -> EndpointSpec> { - EndpointSpec::get("profile-bulk", "profile-bulk", query).with_metadata(WORLDWIDE) + EndpointSpec::get_csv("profile-bulk", "profile-bulk", query).with_metadata(WORLDWIDE) } /// Describes `GET rating-bulk` without binding a transport. pub fn bulk_stock_ratings() -> EndpointSpec<(), Vec> { - EndpointSpec::get("rating-bulk", "rating-bulk", ()).with_metadata(WORLDWIDE) + EndpointSpec::get_csv("rating-bulk", "rating-bulk", ()).with_metadata(WORLDWIDE) } /// Describes `GET dcf-bulk` without binding a transport. pub fn bulk_dcf_valuations() -> EndpointSpec<(), Vec> { - EndpointSpec::get("dcf-bulk", "dcf-bulk", ()).with_metadata(WORLDWIDE) + EndpointSpec::get_csv("dcf-bulk", "dcf-bulk", ()).with_metadata(WORLDWIDE) } /// Describes `GET scores-bulk` without binding a transport. pub fn bulk_financial_scores() -> EndpointSpec<(), Vec> { - EndpointSpec::get("scores-bulk", "scores-bulk", ()).with_metadata(WORLDWIDE) + EndpointSpec::get_csv("scores-bulk", "scores-bulk", ()).with_metadata(WORLDWIDE) } /// Describes `GET price-target-summary-bulk` without binding a transport. pub fn bulk_price_target_summaries() -> EndpointSpec<(), Vec> { - EndpointSpec::get("price-target-summary-bulk", "price-target-summary-bulk", ()) + EndpointSpec::get_csv("price-target-summary-bulk", "price-target-summary-bulk", ()) .with_metadata(US_ONLY) } /// Describes `GET etf-holder-bulk` without binding a transport. pub fn bulk_etf_holdings(query: BulkPartQuery) -> EndpointSpec> { - EndpointSpec::get("etf-holder-bulk", "etf-holder-bulk", query).with_metadata(WORLDWIDE) + EndpointSpec::get_csv("etf-holder-bulk", "etf-holder-bulk", query).with_metadata(WORLDWIDE) } /// Describes `GET upgrades-downgrades-consensus-bulk` without binding a transport. pub fn bulk_upgrades_downgrades_consensus() -> EndpointSpec<(), Vec> { - EndpointSpec::get( + EndpointSpec::get_csv( "upgrades-downgrades-consensus-bulk", "upgrades-downgrades-consensus-bulk", (), @@ -211,24 +215,25 @@ pub fn bulk_upgrades_downgrades_consensus() -> EndpointSpec<(), Vec EndpointSpec<(), Vec> { - EndpointSpec::get("key-metrics-ttm-bulk", "key-metrics-ttm-bulk", ()).with_metadata(WORLDWIDE) + EndpointSpec::get_csv("key-metrics-ttm-bulk", "key-metrics-ttm-bulk", ()) + .with_metadata(WORLDWIDE) } /// Describes `GET ratios-ttm-bulk` without binding a transport. pub fn bulk_financial_ratios_ttm() -> EndpointSpec<(), Vec> { - EndpointSpec::get("ratios-ttm-bulk", "ratios-ttm-bulk", ()).with_metadata(WORLDWIDE) + EndpointSpec::get_csv("ratios-ttm-bulk", "ratios-ttm-bulk", ()).with_metadata(WORLDWIDE) } /// Describes `GET peers-bulk` without binding a transport. pub fn bulk_stock_peers() -> EndpointSpec<(), Vec> { - EndpointSpec::get("peers-bulk", "peers-bulk", ()).with_metadata(WORLDWIDE) + EndpointSpec::get_csv("peers-bulk", "peers-bulk", ()).with_metadata(WORLDWIDE) } /// Describes `GET earnings-surprises-bulk` without binding a transport. pub fn bulk_earnings_surprises( query: BulkYearQuery, ) -> EndpointSpec> { - EndpointSpec::get("earnings-surprises-bulk", "earnings-surprises-bulk", query) + EndpointSpec::get_csv("earnings-surprises-bulk", "earnings-surprises-bulk", query) .with_metadata(WORLDWIDE) } @@ -236,7 +241,7 @@ pub fn bulk_earnings_surprises( pub fn bulk_income_statements( query: BulkStatementQuery, ) -> EndpointSpec> { - EndpointSpec::get("income-statement-bulk", "income-statement-bulk", query) + EndpointSpec::get_csv("income-statement-bulk", "income-statement-bulk", query) .with_metadata(WORLDWIDE) } @@ -244,7 +249,7 @@ pub fn bulk_income_statements( pub fn bulk_income_statement_growth( query: BulkStatementQuery, ) -> EndpointSpec> { - EndpointSpec::get( + EndpointSpec::get_csv( "income-statement-growth-bulk", "income-statement-growth-bulk", query, @@ -256,7 +261,7 @@ pub fn bulk_income_statement_growth( pub fn bulk_balance_sheet_statements( query: BulkStatementQuery, ) -> EndpointSpec> { - EndpointSpec::get( + EndpointSpec::get_csv( "balance-sheet-statement-bulk", "balance-sheet-statement-bulk", query, @@ -268,7 +273,7 @@ pub fn bulk_balance_sheet_statements( pub fn bulk_balance_sheet_statement_growth( query: BulkStatementQuery, ) -> EndpointSpec> { - EndpointSpec::get( + EndpointSpec::get_csv( "balance-sheet-statement-growth-bulk", "balance-sheet-statement-growth-bulk", query, @@ -280,7 +285,7 @@ pub fn bulk_balance_sheet_statement_growth( pub fn bulk_cash_flow_statements( query: BulkStatementQuery, ) -> EndpointSpec> { - EndpointSpec::get( + EndpointSpec::get_csv( "cash-flow-statement-bulk", "cash-flow-statement-bulk", query, @@ -292,7 +297,7 @@ pub fn bulk_cash_flow_statements( pub fn bulk_cash_flow_statement_growth( query: BulkStatementQuery, ) -> EndpointSpec> { - EndpointSpec::get( + EndpointSpec::get_csv( "cash-flow-statement-growth-bulk", "cash-flow-statement-growth-bulk", query, @@ -302,7 +307,7 @@ pub fn bulk_cash_flow_statement_growth( /// Describes `GET eod-bulk` without binding a transport. pub fn bulk_eod(query: BulkEodQuery) -> EndpointSpec> { - EndpointSpec::get("eod-bulk", "eod-bulk", query).with_metadata(WORLDWIDE) + EndpointSpec::get_csv("eod-bulk", "eod-bulk", query).with_metadata(WORLDWIDE) } impl Client { @@ -489,9 +494,21 @@ mod tests { } #[test] - fn bulk_descriptors_inherit_the_finite_client_response_limit() { + fn bulk_descriptors_default_to_the_bulk_limit_without_overriding_the_client() { + let bulk = crate::client::DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES; assert_eq!(bulk_stock_ratings().max_response_body_bytes(), None); - assert_eq!(bulk_key_metrics_ttm().max_response_body_bytes(), None); - assert_eq!(bulk_financial_ratios_ttm().max_response_body_bytes(), None); + assert_eq!(bulk_stock_ratings().default_max_response_body_bytes(), bulk); + assert_eq!( + bulk_key_metrics_ttm().default_max_response_body_bytes(), + bulk + ); + assert_eq!( + bulk_financial_ratios_ttm().default_max_response_body_bytes(), + bulk + ); + assert_eq!( + bulk_eod(Date::parse("2025-06-02").unwrap().into()).default_max_response_body_bytes(), + bulk + ); } } diff --git a/crates/libfmp/src/endpoints/csv_rows.rs b/crates/libfmp/src/endpoints/csv_rows.rs new file mode 100644 index 00000000..d2218634 --- /dev/null +++ b/crates/libfmp/src/endpoints/csv_rows.rs @@ -0,0 +1,373 @@ +//! CSV row decoding for the bulk routes (ADR 0035). +//! +//! The `csv` crate only splits the body into records. Each record is then +//! handed to the row's `Deserialize` impl as a map from header name to cell, +//! so serde names, renames, and field codecs apply unchanged and a failure +//! reports `[row].member` like the JSON decoder. Every member of the row, +//! `Option` or not, must be a header column; the first record checks it. A +//! cell is always text: an empty cell is the only absence marker, so it +//! becomes `None` for an `Option` member, stays `""` for a string member, +//! and is reported as [`DecodeErrorKind::Null`] anywhere a value is +//! required. + +use bytes::Bytes; +use serde::de::{ + self, DeserializeOwned, IntoDeserializer, Unexpected, Visitor, + value::{Error as CellError, MapDeserializer}, +}; + +use super::{DecodeFailure, ResponseMetadata, missing_member_name}; +use crate::error::DecodeErrorKind; + +pub(super) fn decode_csv( + body: Bytes, + _metadata: ResponseMetadata<'_>, +) -> std::result::Result, DecodeFailure> +where + Row: DeserializeOwned, +{ + let mut reader = csv::ReaderBuilder::new() + .has_headers(true) + .flexible(false) + .from_reader(body.as_ref()); + let headers = reader.headers().map_err(|_| syntax(None))?.clone(); + let mut rows = Vec::new(); + let mut record = csv::StringRecord::new(); + loop { + match reader.read_record(&mut record) { + Ok(true) => {} + Ok(false) => return Ok(rows), + Err(_) => return Err(syntax(Some(rows.len()))), + } + let cells = RowCells { + headers: &headers, + record: &record, + require_columns: rows.is_empty(), + }; + let row = serde_path_to_error::deserialize(cells) + .map_err(|error| row_failure(rows.len(), &error, &headers, &record))?; + rows.push(row); + } +} + +fn syntax(row: Option) -> DecodeFailure { + DecodeFailure::Shape { + path: row.map(|row| format!("[{row}]")), + kind: DecodeErrorKind::Syntax, + } +} + +fn row_failure( + row: usize, + error: &serde_path_to_error::Error, + headers: &csv::StringRecord, + record: &csv::StringRecord, +) -> DecodeFailure { + let message = error.inner().to_string(); + let member = if error.path().iter().next().is_some() { + Some(error.path().to_string()) + } else { + missing_member_name(&message).map(str::to_owned) + }; + let kind = if message.starts_with("missing field ") { + DecodeErrorKind::MissingMember + } else if member + .as_deref() + .and_then(|member| headers.iter().position(|header| header == member)) + .and_then(|column| record.get(column)) + .is_some_and(str::is_empty) + { + DecodeErrorKind::Null + } else if message.starts_with("invalid type: ") { + DecodeErrorKind::WrongType + } else { + DecodeErrorKind::InvalidValue + }; + let path = match member { + Some(member) => format!("[{row}].{member}"), + None => format!("[{row}]"), + }; + DecodeFailure::Shape { + path: Some(path), + kind, + } +} + +struct RowCells<'a> { + headers: &'a csv::StringRecord, + record: &'a csv::StringRecord, + require_columns: bool, +} + +impl<'de> de::Deserializer<'de> for RowCells<'_> { + type Error = CellError; + + fn deserialize_any>(self, visitor: V) -> Result { + visitor.visit_map(MapDeserializer::new( + self.headers.iter().zip(self.record.iter().map(Cell)), + )) + } + + fn deserialize_struct>( + self, + _name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result { + if self.require_columns + && let Some(field) = fields + .iter() + .find(|field| !self.headers.iter().any(|header| header == **field)) + { + return Err(de::Error::missing_field(field)); + } + self.deserialize_any(visitor) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes byte_buf + option unit unit_struct newtype_struct seq tuple tuple_struct map enum identifier + ignored_any + } +} + +struct Cell<'a>(&'a str); + +impl<'de> IntoDeserializer<'de, CellError> for Cell<'_> { + type Deserializer = Self; + + fn into_deserializer(self) -> Self { + self + } +} + +macro_rules! parse_cell { + ($($method:ident => $visit:ident),* $(,)?) => { + $( + fn $method>(self, visitor: V) -> Result { + match self.0.parse() { + Ok(value) => visitor.$visit(value), + Err(_) => Err(de::Error::invalid_type(Unexpected::Str(self.0), &visitor)), + } + } + )* + }; +} + +impl<'de> de::Deserializer<'de> for Cell<'_> { + type Error = CellError; + + fn deserialize_any>(self, visitor: V) -> Result { + visitor.visit_str(self.0) + } + + fn deserialize_option>(self, visitor: V) -> Result { + if self.0.is_empty() { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } + + fn deserialize_bool>(self, visitor: V) -> Result { + match self.0 { + "true" => visitor.visit_bool(true), + "false" => visitor.visit_bool(false), + other => Err(de::Error::invalid_type(Unexpected::Str(other), &visitor)), + } + } + + fn deserialize_unit>(self, visitor: V) -> Result { + if self.0.is_empty() { + visitor.visit_unit() + } else { + Err(de::Error::invalid_type(Unexpected::Str(self.0), &visitor)) + } + } + + fn deserialize_newtype_struct>( + self, + _name: &'static str, + visitor: V, + ) -> Result { + visitor.visit_newtype_struct(self) + } + + fn deserialize_enum>( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result { + visitor.visit_enum(self.0.into_deserializer()) + } + + fn deserialize_ignored_any>(self, visitor: V) -> Result { + visitor.visit_unit() + } + + parse_cell! { + deserialize_i8 => visit_i8, + deserialize_i16 => visit_i16, + deserialize_i32 => visit_i32, + deserialize_i64 => visit_i64, + deserialize_i128 => visit_i128, + deserialize_u8 => visit_u8, + deserialize_u16 => visit_u16, + deserialize_u32 => visit_u32, + deserialize_u64 => visit_u64, + deserialize_u128 => visit_u128, + deserialize_f32 => visit_f32, + deserialize_f64 => visit_f64, + } + + serde::forward_to_deserialize_any! { + char str string bytes byte_buf unit_struct seq tuple tuple_struct map struct identifier + } +} + +#[cfg(test)] +mod tests { + use serde::Deserialize; + + use super::*; + use crate::{codecs::NumericString, endpoints::ResponseContract, types::Date}; + + #[derive(Debug, PartialEq, Deserialize)] + #[serde(rename_all = "camelCase")] + struct Row { + symbol: String, + date: Date, + price: f64, + listed: bool, + volume: Option, + #[serde(rename = "Stock Price")] + stock_price: Option, + peers: String, + } + + const HEADER: &str = + "\"symbol\",\"date\",\"price\",\"listed\",\"volume\",\"Stock Price\",\"peers\"\n"; + + fn decode(body: &str) -> std::result::Result, DecodeFailure> { + ResponseContract::>::csv().decode( + Bytes::from(body.to_owned()), + ResponseMetadata::new("text/csv", None), + ) + } + + fn failure(body: &str) -> (Option, DecodeErrorKind) { + match decode(body).unwrap_err() { + DecodeFailure::Shape { path, kind } => (path, kind), + DecodeFailure::ProviderMessage => panic!("CSV never reports a provider message"), + } + } + + #[test] + fn empty_and_header_only_bodies_decode_to_no_rows() { + assert_eq!(decode("").unwrap(), []); + assert_eq!(decode(HEADER).unwrap(), []); + assert_eq!(decode("symbol,price\n").unwrap(), []); + } + + #[test] + fn cells_decode_by_header_name_with_quoted_commas_and_verbatim_numbers() { + let body = format!( + "{HEADER}\"AAPL\",\"2025-06-02\",201.7,true,6.9148336e-9,12.5,\"MSFT,GOOG\"\n\ + \"\",\"2025-06-02\",-0.5,false,,,\"say \"\"hi\"\"\"\n" + ); + let rows = decode(&body).unwrap(); + + assert_eq!(rows[0].symbol, "AAPL"); + assert_eq!(rows[0].date, Date::parse("2025-06-02").unwrap()); + assert_eq!(rows[0].price, 201.7); + assert!(rows[0].listed); + assert_eq!(rows[0].volume.as_ref().unwrap().as_str(), "6.9148336e-9"); + assert_eq!(rows[0].stock_price, Some(12.5)); + assert_eq!(rows[0].peers, "MSFT,GOOG"); + assert_eq!(rows[1].symbol, ""); + assert_eq!(rows[1].volume, None); + assert_eq!(rows[1].stock_price, None); + assert_eq!(rows[1].peers, "say \"hi\""); + } + + #[test] + fn a_leading_byte_order_mark_is_not_part_of_the_first_header() { + let rows = decode(&format!("\u{feff}{HEADER}A,2025-06-02,1,true,1,1,x\r\n")).unwrap(); + + assert_eq!(rows[0].symbol, "A"); + assert_eq!(rows[0].peers, "x"); + } + + #[test] + fn an_empty_cell_on_a_required_member_is_a_null_at_its_row_and_member() { + let body = format!("{HEADER}A,2025-06-02,1,true,1,1,x\nB,2025-06-02,,true,1,1,x\n"); + assert_eq!( + failure(&body), + (Some("[1].price".into()), DecodeErrorKind::Null) + ); + let body = format!("{HEADER}A,,1,true,1,1,x\n"); + assert_eq!( + failure(&body), + (Some("[0].date".into()), DecodeErrorKind::Null) + ); + } + + #[test] + fn malformed_cells_are_classified_without_their_values() { + let body = format!("{HEADER}A,2025-06-02,abc,true,1,1,x\n"); + assert_eq!( + failure(&body), + (Some("[0].price".into()), DecodeErrorKind::WrongType) + ); + let body = format!("{HEADER}A,2025-06-02,1,yes,1,1,x\n"); + assert_eq!( + failure(&body), + (Some("[0].listed".into()), DecodeErrorKind::WrongType) + ); + let body = format!("{HEADER}A,2025-13-40,1,true,1,1,x\n"); + assert_eq!( + failure(&body), + (Some("[0].date".into()), DecodeErrorKind::InvalidValue) + ); + } + + #[test] + fn a_missing_column_is_a_missing_member_even_for_an_option() { + let body = "symbol,date,price,listed,volume,Stock Price\nA,2025-06-02,1,true,1,1\n"; + assert_eq!( + failure(body), + (Some("[0].peers".into()), DecodeErrorKind::MissingMember) + ); + let body = "symbol,date,price,listed,Stock Price,peers\nA,2025-06-02,1,true,1,x\n"; + assert_eq!( + failure(body), + (Some("[0].volume".into()), DecodeErrorKind::MissingMember) + ); + assert_eq!(decode("symbol,date\n").unwrap(), []); + } + + #[test] + fn a_record_with_the_wrong_cell_count_is_a_syntax_error_at_its_row() { + let body = format!("{HEADER}A,2025-06-02,1,true,1,1,x\nB,2025-06-02\n"); + assert_eq!( + failure(&body), + (Some("[1]".into()), DecodeErrorKind::Syntax) + ); + } + + #[test] + fn a_one_line_message_is_a_header_not_a_provider_message() { + assert_eq!(decode("Invalid name\n").unwrap(), []); + } + + #[test] + fn the_csv_contract_accepts_only_text_csv() { + let expected = ResponseContract::>::csv().expected_content_type(); + + assert!(expected.matches("text/csv")); + assert!(expected.matches("Text/CSV; charset=utf-8")); + assert!(!expected.matches("application/json")); + assert!(!expected.matches("text/plain")); + } +} diff --git a/crates/libfmp/src/endpoints/mod.rs b/crates/libfmp/src/endpoints/mod.rs index a0364718..a3a973d3 100644 --- a/crates/libfmp/src/endpoints/mod.rs +++ b/crates/libfmp/src/endpoints/mod.rs @@ -10,6 +10,7 @@ pub mod commodities; pub mod company; pub mod congressional; pub mod crypto; +mod csv_rows; pub mod dcf; pub mod directory; pub mod economics; @@ -44,6 +45,9 @@ use metadata::EndpointMetadata; /// The JSON media type requested by FMP's structured-data endpoints. pub const APPLICATION_JSON: &str = "application/json"; +/// The CSV media type FMP answers on every bulk route (ADR 0035). +pub const TEXT_CSV: &str = "text/csv"; + /// A typed endpoint description that contains no transport configuration. /// /// Domain endpoint modules own values of this type. Base URLs, @@ -56,6 +60,7 @@ pub struct EndpointSpec { response: ResponseContract, metadata: EndpointMetadata, max_response_body_bytes: Option, + default_max_response_body_bytes: usize, } impl EndpointSpec { @@ -75,6 +80,7 @@ impl EndpointSpec { response, metadata: EndpointMetadata::new(), max_response_body_bytes: None, + default_max_response_body_bytes: crate::client::DEFAULT_MAX_RESPONSE_BODY_BYTES, } } @@ -127,6 +133,15 @@ impl EndpointSpec { pub const fn max_response_body_bytes(&self) -> Option { self.max_response_body_bytes } + + /// Returns the limit used when neither this endpoint nor the client sets one. + /// + /// This is [`crate::client::DEFAULT_MAX_RESPONSE_BODY_BYTES`] except for + /// CSV bulk descriptors, which use + /// [`crate::client::DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES`]. + pub const fn default_max_response_body_bytes(&self) -> usize { + self.default_max_response_body_bytes + } } impl EndpointSpec @@ -158,6 +173,28 @@ where } } +impl EndpointSpec> +where + Row: DeserializeOwned, +{ + /// Creates a reusable GET descriptor for a CSV bulk response. + /// + /// Each record decodes into one `Row` by header name. Unless the endpoint + /// or the client sets a limit, the body may grow to + /// [`crate::client::DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES`]. + pub const fn get_csv(id: &'static str, relative_path: &'static str, query: Q) -> Self { + let mut spec = Self::with_response( + HttpMethod::Get, + id, + relative_path, + query, + ResponseContract::csv(), + ); + spec.default_max_response_body_bytes = crate::client::DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES; + spec + } +} + impl EndpointSpec { /// Creates a reusable GET descriptor for a binary response. /// @@ -260,6 +297,8 @@ impl QueryParameters for [(&str, &str); N] { pub enum ExpectedContentType { /// Standard JSON and registered vendor JSON media types (`+json`). Json, + /// `text/csv`, compared case-insensitively. + Csv, /// One of the listed binary media types, compared case-insensitively. Binary(&'static [&'static str]), } @@ -272,6 +311,7 @@ impl ExpectedContentType { .trim(); match self { Self::Json => valid_json_media_type(media_type), + Self::Csv => media_type.eq_ignore_ascii_case(TEXT_CSV), Self::Binary(expected) => expected .iter() .any(|expected| media_type.eq_ignore_ascii_case(expected)), @@ -389,6 +429,24 @@ where } } +impl ResponseContract> +where + Row: DeserializeOwned, +{ + /// Creates a contract that decodes a headed CSV body into rows. + /// + /// An empty or header-only body yields no rows. The provider-message + /// check of the JSON contract does not apply: a short header-only CSV + /// body is data, not an error message. + pub const fn csv() -> Self { + Self { + expected_content_type: ExpectedContentType::Csv, + decoder: csv_rows::decode_csv::, + response: PhantomData, + } + } +} + impl ResponseContract { /// Creates a raw binary contract restricted to documented media types. pub const fn binary(expected_content_types: &'static [&'static str]) -> Self { diff --git a/crates/libfmp/src/error.rs b/crates/libfmp/src/error.rs index b15eca99..dfd0f088 100644 --- a/crates/libfmp/src/error.rs +++ b/crates/libfmp/src/error.rs @@ -89,7 +89,7 @@ pub enum ConfigurationErrorKind { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum DecodeErrorKind { - /// The body is not well-formed JSON. + /// The body is not well-formed JSON, or a CSV record is malformed. Syntax, /// A member that requires a value was JSON `null`. Null, diff --git a/crates/libfmp/src/responses/bulk/balance.rs b/crates/libfmp/src/responses/bulk/balance.rs index e2ad8cd7..b1f73753 100644 --- a/crates/libfmp/src/responses/bulk/balance.rs +++ b/crates/libfmp/src/responses/bulk/balance.rs @@ -21,59 +21,59 @@ pub struct BulkBalanceSheetStatement { pub accepted_date: ApiDateTime, pub fiscal_year: FiscalYearString, pub period: FiscalPeriod, - pub cash_and_cash_equivalents: NumericString, - pub short_term_investments: NumericString, - pub cash_and_short_term_investments: NumericString, - pub net_receivables: NumericString, - pub accounts_receivables: NumericString, - pub other_receivables: NumericString, - pub inventory: NumericString, - pub prepaids: NumericString, - pub other_current_assets: NumericString, - pub total_current_assets: NumericString, - pub property_plant_equipment_net: NumericString, - pub goodwill: NumericString, - pub intangible_assets: NumericString, - pub goodwill_and_intangible_assets: NumericString, - pub long_term_investments: NumericString, - pub tax_assets: NumericString, - pub other_non_current_assets: NumericString, - pub total_non_current_assets: NumericString, - pub other_assets: NumericString, - pub total_assets: NumericString, - pub total_payables: NumericString, - pub account_payables: NumericString, - pub other_payables: NumericString, - pub accrued_expenses: NumericString, - pub short_term_debt: NumericString, - pub capital_lease_obligations_current: NumericString, - pub tax_payables: NumericString, - pub deferred_revenue: NumericString, - pub other_current_liabilities: NumericString, - pub total_current_liabilities: NumericString, - pub long_term_debt: NumericString, - pub capital_lease_obligations_non_current: NumericString, - pub deferred_revenue_non_current: NumericString, - pub deferred_tax_liabilities_non_current: NumericString, - pub other_non_current_liabilities: NumericString, - pub total_non_current_liabilities: NumericString, - pub other_liabilities: NumericString, - pub capital_lease_obligations: NumericString, - pub total_liabilities: NumericString, - pub treasury_stock: NumericString, - pub preferred_stock: NumericString, - pub common_stock: NumericString, - pub retained_earnings: NumericString, - pub additional_paid_in_capital: NumericString, - pub accumulated_other_comprehensive_income_loss: NumericString, - pub other_total_stockholders_equity: NumericString, - pub total_stockholders_equity: NumericString, - pub total_equity: NumericString, - pub minority_interest: NumericString, - pub total_liabilities_and_total_equity: NumericString, - pub total_investments: NumericString, - pub total_debt: NumericString, - pub net_debt: NumericString, + pub cash_and_cash_equivalents: Option, + pub short_term_investments: Option, + pub cash_and_short_term_investments: Option, + pub net_receivables: Option, + pub accounts_receivables: Option, + pub other_receivables: Option, + pub inventory: Option, + pub prepaids: Option, + pub other_current_assets: Option, + pub total_current_assets: Option, + pub property_plant_equipment_net: Option, + pub goodwill: Option, + pub intangible_assets: Option, + pub goodwill_and_intangible_assets: Option, + pub long_term_investments: Option, + pub tax_assets: Option, + pub other_non_current_assets: Option, + pub total_non_current_assets: Option, + pub other_assets: Option, + pub total_assets: Option, + pub total_payables: Option, + pub account_payables: Option, + pub other_payables: Option, + pub accrued_expenses: Option, + pub short_term_debt: Option, + pub capital_lease_obligations_current: Option, + pub tax_payables: Option, + pub deferred_revenue: Option, + pub other_current_liabilities: Option, + pub total_current_liabilities: Option, + pub long_term_debt: Option, + pub capital_lease_obligations_non_current: Option, + pub deferred_revenue_non_current: Option, + pub deferred_tax_liabilities_non_current: Option, + pub other_non_current_liabilities: Option, + pub total_non_current_liabilities: Option, + pub other_liabilities: Option, + pub capital_lease_obligations: Option, + pub total_liabilities: Option, + pub treasury_stock: Option, + pub preferred_stock: Option, + pub common_stock: Option, + pub retained_earnings: Option, + pub additional_paid_in_capital: Option, + pub accumulated_other_comprehensive_income_loss: Option, + pub other_total_stockholders_equity: Option, + pub total_stockholders_equity: Option, + pub total_equity: Option, + pub minority_interest: Option, + pub total_liabilities_and_total_equity: Option, + pub total_investments: Option, + pub total_debt: Option, + pub net_debt: Option, } /// One worldwide bulk balance-sheet-statement-growth row. @@ -86,56 +86,56 @@ pub struct BulkBalanceSheetStatementGrowth { pub fiscal_year: FiscalYearString, pub period: FiscalPeriod, pub reported_currency: CurrencyCode, - pub growth_cash_and_cash_equivalents: NumericString, - pub growth_short_term_investments: NumericString, - pub growth_cash_and_short_term_investments: NumericString, - pub growth_net_receivables: NumericString, - pub growth_inventory: NumericString, - pub growth_other_current_assets: NumericString, - pub growth_total_current_assets: NumericString, - pub growth_property_plant_equipment_net: NumericString, - pub growth_goodwill: NumericString, - pub growth_intangible_assets: NumericString, - pub growth_goodwill_and_intangible_assets: NumericString, - pub growth_long_term_investments: NumericString, - pub growth_tax_assets: NumericString, - pub growth_other_non_current_assets: NumericString, - pub growth_total_non_current_assets: NumericString, - pub growth_other_assets: NumericString, - pub growth_total_assets: NumericString, - pub growth_account_payables: NumericString, - pub growth_short_term_debt: NumericString, - pub growth_tax_payables: NumericString, - pub growth_deferred_revenue: NumericString, - pub growth_other_current_liabilities: NumericString, - pub growth_total_current_liabilities: NumericString, - pub growth_long_term_debt: NumericString, - pub growth_deferred_revenue_non_current: NumericString, - pub growth_deferred_tax_liabilities_non_current: NumericString, - pub growth_other_non_current_liabilities: NumericString, - pub growth_total_non_current_liabilities: NumericString, - pub growth_other_liabilities: NumericString, - pub growth_total_liabilities: NumericString, - pub growth_preferred_stock: NumericString, - pub growth_common_stock: NumericString, - pub growth_retained_earnings: NumericString, - pub growth_accumulated_other_comprehensive_income_loss: NumericString, + pub growth_cash_and_cash_equivalents: Option, + pub growth_short_term_investments: Option, + pub growth_cash_and_short_term_investments: Option, + pub growth_net_receivables: Option, + pub growth_inventory: Option, + pub growth_other_current_assets: Option, + pub growth_total_current_assets: Option, + pub growth_property_plant_equipment_net: Option, + pub growth_goodwill: Option, + pub growth_intangible_assets: Option, + pub growth_goodwill_and_intangible_assets: Option, + pub growth_long_term_investments: Option, + pub growth_tax_assets: Option, + pub growth_other_non_current_assets: Option, + pub growth_total_non_current_assets: Option, + pub growth_other_assets: Option, + pub growth_total_assets: Option, + pub growth_account_payables: Option, + pub growth_short_term_debt: Option, + pub growth_tax_payables: Option, + pub growth_deferred_revenue: Option, + pub growth_other_current_liabilities: Option, + pub growth_total_current_liabilities: Option, + pub growth_long_term_debt: Option, + pub growth_deferred_revenue_non_current: Option, + pub growth_deferred_tax_liabilities_non_current: Option, + pub growth_other_non_current_liabilities: Option, + pub growth_total_non_current_liabilities: Option, + pub growth_other_liabilities: Option, + pub growth_total_liabilities: Option, + pub growth_preferred_stock: Option, + pub growth_common_stock: Option, + pub growth_retained_earnings: Option, + pub growth_accumulated_other_comprehensive_income_loss: Option, #[serde(rename = "growthOthertotalStockholdersEquity")] - pub growth_other_total_stockholders_equity: NumericString, - pub growth_total_stockholders_equity: NumericString, - pub growth_minority_interest: NumericString, - pub growth_total_equity: NumericString, - pub growth_total_liabilities_and_stockholders_equity: NumericString, - pub growth_total_investments: NumericString, - pub growth_total_debt: NumericString, - pub growth_net_debt: NumericString, - pub growth_accounts_receivables: NumericString, - pub growth_other_receivables: NumericString, - pub growth_prepaids: NumericString, - pub growth_total_payables: NumericString, - pub growth_other_payables: NumericString, - pub growth_accrued_expenses: NumericString, - pub growth_capital_lease_obligations_current: NumericString, - pub growth_additional_paid_in_capital: NumericString, - pub growth_treasury_stock: NumericString, + pub growth_other_total_stockholders_equity: Option, + pub growth_total_stockholders_equity: Option, + pub growth_minority_interest: Option, + pub growth_total_equity: Option, + pub growth_total_liabilities_and_stockholders_equity: Option, + pub growth_total_investments: Option, + pub growth_total_debt: Option, + pub growth_net_debt: Option, + pub growth_accounts_receivables: Option, + pub growth_other_receivables: Option, + pub growth_prepaids: Option, + pub growth_total_payables: Option, + pub growth_other_payables: Option, + pub growth_accrued_expenses: Option, + pub growth_capital_lease_obligations_current: Option, + pub growth_additional_paid_in_capital: Option, + pub growth_treasury_stock: Option, } diff --git a/crates/libfmp/src/responses/bulk/cash_flow.rs b/crates/libfmp/src/responses/bulk/cash_flow.rs index d5d9dee1..50bbe7c2 100644 --- a/crates/libfmp/src/responses/bulk/cash_flow.rs +++ b/crates/libfmp/src/responses/bulk/cash_flow.rs @@ -21,45 +21,45 @@ pub struct BulkCashFlowStatement { pub accepted_date: ApiDateTime, pub fiscal_year: FiscalYearString, pub period: FiscalPeriod, - pub net_income: NumericString, - pub depreciation_and_amortization: NumericString, - pub deferred_income_tax: NumericString, - pub stock_based_compensation: NumericString, - pub change_in_working_capital: NumericString, - pub accounts_receivables: NumericString, - pub inventory: NumericString, - pub accounts_payables: NumericString, - pub other_working_capital: NumericString, - pub other_non_cash_items: NumericString, - pub net_cash_provided_by_operating_activities: NumericString, - pub investments_in_property_plant_and_equipment: NumericString, - pub acquisitions_net: NumericString, - pub purchases_of_investments: NumericString, - pub sales_maturities_of_investments: NumericString, - pub other_investing_activities: NumericString, - pub net_cash_provided_by_investing_activities: NumericString, - pub net_debt_issuance: NumericString, - pub long_term_net_debt_issuance: NumericString, - pub short_term_net_debt_issuance: NumericString, - pub net_stock_issuance: NumericString, - pub net_common_stock_issuance: NumericString, - pub common_stock_issuance: NumericString, - pub common_stock_repurchased: NumericString, - pub net_preferred_stock_issuance: NumericString, - pub net_dividends_paid: NumericString, - pub common_dividends_paid: NumericString, - pub preferred_dividends_paid: NumericString, - pub other_financing_activities: NumericString, - pub net_cash_provided_by_financing_activities: NumericString, - pub effect_of_forex_changes_on_cash: NumericString, - pub net_change_in_cash: NumericString, - pub cash_at_end_of_period: NumericString, - pub cash_at_beginning_of_period: NumericString, - pub operating_cash_flow: NumericString, - pub capital_expenditure: NumericString, - pub free_cash_flow: NumericString, - pub income_taxes_paid: NumericString, - pub interest_paid: NumericString, + pub net_income: Option, + pub depreciation_and_amortization: Option, + pub deferred_income_tax: Option, + pub stock_based_compensation: Option, + pub change_in_working_capital: Option, + pub accounts_receivables: Option, + pub inventory: Option, + pub accounts_payables: Option, + pub other_working_capital: Option, + pub other_non_cash_items: Option, + pub net_cash_provided_by_operating_activities: Option, + pub investments_in_property_plant_and_equipment: Option, + pub acquisitions_net: Option, + pub purchases_of_investments: Option, + pub sales_maturities_of_investments: Option, + pub other_investing_activities: Option, + pub net_cash_provided_by_investing_activities: Option, + pub net_debt_issuance: Option, + pub long_term_net_debt_issuance: Option, + pub short_term_net_debt_issuance: Option, + pub net_stock_issuance: Option, + pub net_common_stock_issuance: Option, + pub common_stock_issuance: Option, + pub common_stock_repurchased: Option, + pub net_preferred_stock_issuance: Option, + pub net_dividends_paid: Option, + pub common_dividends_paid: Option, + pub preferred_dividends_paid: Option, + pub other_financing_activities: Option, + pub net_cash_provided_by_financing_activities: Option, + pub effect_of_forex_changes_on_cash: Option, + pub net_change_in_cash: Option, + pub cash_at_end_of_period: Option, + pub cash_at_beginning_of_period: Option, + pub operating_cash_flow: Option, + pub capital_expenditure: Option, + pub free_cash_flow: Option, + pub income_taxes_paid: Option, + pub interest_paid: Option, } /// One worldwide bulk cash-flow-statement-growth row. @@ -72,45 +72,45 @@ pub struct BulkCashFlowStatementGrowth { pub fiscal_year: FiscalYearString, pub period: FiscalPeriod, pub reported_currency: CurrencyCode, - pub growth_net_income: NumericString, - pub growth_depreciation_and_amortization: NumericString, - pub growth_deferred_income_tax: NumericString, - pub growth_stock_based_compensation: NumericString, - pub growth_change_in_working_capital: NumericString, - pub growth_accounts_receivables: NumericString, - pub growth_inventory: NumericString, - pub growth_accounts_payables: NumericString, - pub growth_other_working_capital: NumericString, - pub growth_other_non_cash_items: NumericString, + pub growth_net_income: Option, + pub growth_depreciation_and_amortization: Option, + pub growth_deferred_income_tax: Option, + pub growth_stock_based_compensation: Option, + pub growth_change_in_working_capital: Option, + pub growth_accounts_receivables: Option, + pub growth_inventory: Option, + pub growth_accounts_payables: Option, + pub growth_other_working_capital: Option, + pub growth_other_non_cash_items: Option, #[serde(rename = "growthNetCashProvidedByOperatingActivites")] - pub growth_net_cash_provided_by_operating_activities: NumericString, - pub growth_investments_in_property_plant_and_equipment: NumericString, - pub growth_acquisitions_net: NumericString, - pub growth_purchases_of_investments: NumericString, - pub growth_sales_maturities_of_investments: NumericString, + pub growth_net_cash_provided_by_operating_activities: Option, + pub growth_investments_in_property_plant_and_equipment: Option, + pub growth_acquisitions_net: Option, + pub growth_purchases_of_investments: Option, + pub growth_sales_maturities_of_investments: Option, #[serde(rename = "growthOtherInvestingActivites")] - pub growth_other_investing_activities: NumericString, + pub growth_other_investing_activities: Option, #[serde(rename = "growthNetCashUsedForInvestingActivites")] - pub growth_net_cash_used_for_investing_activities: NumericString, - pub growth_debt_repayment: NumericString, - pub growth_common_stock_issued: NumericString, - pub growth_common_stock_repurchased: NumericString, - pub growth_dividends_paid: NumericString, + pub growth_net_cash_used_for_investing_activities: Option, + pub growth_debt_repayment: Option, + pub growth_common_stock_issued: Option, + pub growth_common_stock_repurchased: Option, + pub growth_dividends_paid: Option, #[serde(rename = "growthOtherFinancingActivites")] - pub growth_other_financing_activities: NumericString, - pub growth_net_cash_used_provided_by_financing_activities: NumericString, - pub growth_effect_of_forex_changes_on_cash: NumericString, - pub growth_net_change_in_cash: NumericString, - pub growth_cash_at_end_of_period: NumericString, - pub growth_cash_at_beginning_of_period: NumericString, - pub growth_operating_cash_flow: NumericString, - pub growth_capital_expenditure: NumericString, - pub growth_free_cash_flow: NumericString, - pub growth_net_debt_issuance: NumericString, - pub growth_long_term_net_debt_issuance: NumericString, - pub growth_short_term_net_debt_issuance: NumericString, - pub growth_net_stock_issuance: NumericString, - pub growth_preferred_dividends_paid: NumericString, - pub growth_income_taxes_paid: NumericString, - pub growth_interest_paid: NumericString, + pub growth_other_financing_activities: Option, + pub growth_net_cash_used_provided_by_financing_activities: Option, + pub growth_effect_of_forex_changes_on_cash: Option, + pub growth_net_change_in_cash: Option, + pub growth_cash_at_end_of_period: Option, + pub growth_cash_at_beginning_of_period: Option, + pub growth_operating_cash_flow: Option, + pub growth_capital_expenditure: Option, + pub growth_free_cash_flow: Option, + pub growth_net_debt_issuance: Option, + pub growth_long_term_net_debt_issuance: Option, + pub growth_short_term_net_debt_issuance: Option, + pub growth_net_stock_issuance: Option, + pub growth_preferred_dividends_paid: Option, + pub growth_income_taxes_paid: Option, + pub growth_interest_paid: Option, } diff --git a/crates/libfmp/src/responses/bulk/eod.rs b/crates/libfmp/src/responses/bulk/eod.rs index 4f592ed2..7deebd15 100644 --- a/crates/libfmp/src/responses/bulk/eod.rs +++ b/crates/libfmp/src/responses/bulk/eod.rs @@ -14,10 +14,10 @@ use crate::{ pub struct BulkEodBar { pub symbol: Ticker, pub date: Date, - pub open: NumericString, - pub low: NumericString, - pub high: NumericString, - pub close: NumericString, - pub adj_close: NumericString, - pub volume: NumericString, + pub open: Option, + pub low: Option, + pub high: Option, + pub close: Option, + pub adj_close: Option, + pub volume: Option, } diff --git a/crates/libfmp/src/responses/bulk/income.rs b/crates/libfmp/src/responses/bulk/income.rs index 35992ec3..53fbbd30 100644 --- a/crates/libfmp/src/responses/bulk/income.rs +++ b/crates/libfmp/src/responses/bulk/income.rs @@ -21,37 +21,37 @@ pub struct BulkIncomeStatement { pub accepted_date: ApiDateTime, pub fiscal_year: FiscalYearString, pub period: FiscalPeriod, - pub revenue: NumericString, - pub cost_of_revenue: NumericString, - pub gross_profit: NumericString, - pub research_and_development_expenses: NumericString, - pub general_and_administrative_expenses: NumericString, - pub selling_and_marketing_expenses: NumericString, - pub selling_general_and_administrative_expenses: NumericString, - pub other_expenses: NumericString, - pub operating_expenses: NumericString, - pub cost_and_expenses: NumericString, - pub net_interest_income: NumericString, - pub interest_income: NumericString, - pub interest_expense: NumericString, - pub depreciation_and_amortization: NumericString, - pub ebitda: NumericString, - pub ebit: NumericString, - pub non_operating_income_excluding_interest: NumericString, - pub operating_income: NumericString, - pub total_other_income_expenses_net: NumericString, - pub income_before_tax: NumericString, - pub income_tax_expense: NumericString, - pub net_income_from_continuing_operations: NumericString, - pub net_income_from_discontinued_operations: NumericString, - pub other_adjustments_to_net_income: NumericString, - pub net_income: NumericString, - pub net_income_deductions: NumericString, - pub bottom_line_net_income: NumericString, - pub eps: NumericString, - pub eps_diluted: NumericString, - pub weighted_average_shs_out: NumericString, - pub weighted_average_shs_out_dil: NumericString, + pub revenue: Option, + pub cost_of_revenue: Option, + pub gross_profit: Option, + pub research_and_development_expenses: Option, + pub general_and_administrative_expenses: Option, + pub selling_and_marketing_expenses: Option, + pub selling_general_and_administrative_expenses: Option, + pub other_expenses: Option, + pub operating_expenses: Option, + pub cost_and_expenses: Option, + pub net_interest_income: Option, + pub interest_income: Option, + pub interest_expense: Option, + pub depreciation_and_amortization: Option, + pub ebitda: Option, + pub ebit: Option, + pub non_operating_income_excluding_interest: Option, + pub operating_income: Option, + pub total_other_income_expenses_net: Option, + pub income_before_tax: Option, + pub income_tax_expense: Option, + pub net_income_from_continuing_operations: Option, + pub net_income_from_discontinued_operations: Option, + pub other_adjustments_to_net_income: Option, + pub net_income: Option, + pub net_income_deductions: Option, + pub bottom_line_net_income: Option, + pub eps: Option, + pub eps_diluted: Option, + pub weighted_average_shs_out: Option, + pub weighted_average_shs_out_dil: Option, } /// One worldwide bulk income-statement-growth row. @@ -64,37 +64,37 @@ pub struct BulkIncomeStatementGrowth { pub fiscal_year: FiscalYearString, pub period: FiscalPeriod, pub reported_currency: CurrencyCode, - pub growth_revenue: NumericString, - pub growth_cost_of_revenue: NumericString, - pub growth_gross_profit: NumericString, - pub growth_gross_profit_ratio: NumericString, - pub growth_research_and_development_expenses: NumericString, - pub growth_general_and_administrative_expenses: NumericString, - pub growth_selling_and_marketing_expenses: NumericString, - pub growth_other_expenses: NumericString, - pub growth_operating_expenses: NumericString, - pub growth_cost_and_expenses: NumericString, - pub growth_interest_income: NumericString, - pub growth_interest_expense: NumericString, - pub growth_depreciation_and_amortization: NumericString, + pub growth_revenue: Option, + pub growth_cost_of_revenue: Option, + pub growth_gross_profit: Option, + pub growth_gross_profit_ratio: Option, + pub growth_research_and_development_expenses: Option, + pub growth_general_and_administrative_expenses: Option, + pub growth_selling_and_marketing_expenses: Option, + pub growth_other_expenses: Option, + pub growth_operating_expenses: Option, + pub growth_cost_and_expenses: Option, + pub growth_interest_income: Option, + pub growth_interest_expense: Option, + pub growth_depreciation_and_amortization: Option, #[serde(rename = "growthEBITDA")] - pub growth_ebitda: NumericString, - pub growth_operating_income: NumericString, - pub growth_income_before_tax: NumericString, - pub growth_income_tax_expense: NumericString, - pub growth_net_income: NumericString, + pub growth_ebitda: Option, + pub growth_operating_income: Option, + pub growth_income_before_tax: Option, + pub growth_income_tax_expense: Option, + pub growth_net_income: Option, #[serde(rename = "growthEPS")] - pub growth_eps: NumericString, + pub growth_eps: Option, #[serde(rename = "growthEPSDiluted")] - pub growth_eps_diluted: NumericString, - pub growth_weighted_average_shs_out: NumericString, - pub growth_weighted_average_shs_out_dil: NumericString, + pub growth_eps_diluted: Option, + pub growth_weighted_average_shs_out: Option, + pub growth_weighted_average_shs_out_dil: Option, #[serde(rename = "growthEBIT")] - pub growth_ebit: NumericString, - pub growth_non_operating_income_excluding_interest: NumericString, - pub growth_net_interest_income: NumericString, - pub growth_total_other_income_expenses_net: NumericString, - pub growth_net_income_from_continuing_operations: NumericString, - pub growth_other_adjustments_to_net_income: NumericString, - pub growth_net_income_deductions: NumericString, + pub growth_ebit: Option, + pub growth_non_operating_income_excluding_interest: Option, + pub growth_net_interest_income: Option, + pub growth_total_other_income_expenses_net: Option, + pub growth_net_income_from_continuing_operations: Option, + pub growth_other_adjustments_to_net_income: Option, + pub growth_net_income_deductions: Option, } diff --git a/crates/libfmp/src/responses/bulk/metrics.rs b/crates/libfmp/src/responses/bulk/metrics.rs index d27f130d..4ed72ee1 100644 --- a/crates/libfmp/src/responses/bulk/metrics.rs +++ b/crates/libfmp/src/responses/bulk/metrics.rs @@ -13,89 +13,89 @@ use crate::{ pub struct BulkKeyMetricsTtm { pub symbol: Ticker, #[serde(rename = "marketCap")] - pub market_cap: NumericString, + pub market_cap: Option, #[serde(rename = "enterpriseValueTTM")] - pub enterprise_value_ttm: NumericString, + pub enterprise_value_ttm: Option, #[serde(rename = "evToSalesTTM")] - pub ev_to_sales_ttm: NumericString, + pub ev_to_sales_ttm: Option, #[serde(rename = "evToOperatingCashFlowTTM")] - pub ev_to_operating_cash_flow_ttm: NumericString, + pub ev_to_operating_cash_flow_ttm: Option, #[serde(rename = "evToFreeCashFlowTTM")] - pub ev_to_free_cash_flow_ttm: NumericString, + pub ev_to_free_cash_flow_ttm: Option, #[serde(rename = "evToEBITDATTM")] - pub ev_to_ebitda_ttm: NumericString, + pub ev_to_ebitda_ttm: Option, #[serde(rename = "netDebtToEBITDATTM")] - pub net_debt_to_ebitda_ttm: NumericString, + pub net_debt_to_ebitda_ttm: Option, #[serde(rename = "currentRatioTTM")] - pub current_ratio_ttm: NumericString, + pub current_ratio_ttm: Option, #[serde(rename = "incomeQualityTTM")] - pub income_quality_ttm: NumericString, + pub income_quality_ttm: Option, #[serde(rename = "grahamNumberTTM")] - pub graham_number_ttm: NumericString, + pub graham_number_ttm: Option, #[serde(rename = "grahamNetNetTTM")] - pub graham_net_net_ttm: NumericString, + pub graham_net_net_ttm: Option, #[serde(rename = "taxBurdenTTM")] - pub tax_burden_ttm: NumericString, + pub tax_burden_ttm: Option, #[serde(rename = "interestBurdenTTM")] - pub interest_burden_ttm: NumericString, + pub interest_burden_ttm: Option, #[serde(rename = "workingCapitalTTM")] - pub working_capital_ttm: NumericString, + pub working_capital_ttm: Option, #[serde(rename = "investedCapitalTTM")] - pub invested_capital_ttm: NumericString, + pub invested_capital_ttm: Option, #[serde(rename = "returnOnAssetsTTM")] - pub return_on_assets_ttm: NumericString, + pub return_on_assets_ttm: Option, #[serde(rename = "operatingReturnOnAssetsTTM")] - pub operating_return_on_assets_ttm: NumericString, + pub operating_return_on_assets_ttm: Option, #[serde(rename = "returnOnTangibleAssetsTTM")] - pub return_on_tangible_assets_ttm: NumericString, + pub return_on_tangible_assets_ttm: Option, #[serde(rename = "returnOnEquityTTM")] - pub return_on_equity_ttm: NumericString, + pub return_on_equity_ttm: Option, #[serde(rename = "returnOnInvestedCapitalTTM")] - pub return_on_invested_capital_ttm: NumericString, + pub return_on_invested_capital_ttm: Option, #[serde(rename = "returnOnCapitalEmployedTTM")] - pub return_on_capital_employed_ttm: NumericString, + pub return_on_capital_employed_ttm: Option, #[serde(rename = "earningsYieldTTM")] - pub earnings_yield_ttm: NumericString, + pub earnings_yield_ttm: Option, #[serde(rename = "freeCashFlowYieldTTM")] - pub free_cash_flow_yield_ttm: NumericString, + pub free_cash_flow_yield_ttm: Option, #[serde(rename = "capexToOperatingCashFlowTTM")] - pub capex_to_operating_cash_flow_ttm: NumericString, + pub capex_to_operating_cash_flow_ttm: Option, #[serde(rename = "capexToDepreciationTTM")] - pub capex_to_depreciation_ttm: NumericString, + pub capex_to_depreciation_ttm: Option, #[serde(rename = "capexToRevenueTTM")] - pub capex_to_revenue_ttm: NumericString, + pub capex_to_revenue_ttm: Option, #[serde(rename = "salesGeneralAndAdministrativeToRevenueTTM")] - pub sales_general_and_administrative_to_revenue_ttm: NumericString, + pub sales_general_and_administrative_to_revenue_ttm: Option, #[serde(rename = "researchAndDevelopementToRevenueTTM")] - pub research_and_development_to_revenue_ttm: NumericString, + pub research_and_development_to_revenue_ttm: Option, #[serde(rename = "stockBasedCompensationToRevenueTTM")] - pub stock_based_compensation_to_revenue_ttm: NumericString, + pub stock_based_compensation_to_revenue_ttm: Option, #[serde(rename = "intangiblesToTotalAssetsTTM")] - pub intangibles_to_total_assets_ttm: NumericString, + pub intangibles_to_total_assets_ttm: Option, #[serde(rename = "averageReceivablesTTM")] - pub average_receivables_ttm: NumericString, + pub average_receivables_ttm: Option, #[serde(rename = "averagePayablesTTM")] - pub average_payables_ttm: NumericString, + pub average_payables_ttm: Option, #[serde(rename = "averageInventoryTTM")] - pub average_inventory_ttm: NumericString, + pub average_inventory_ttm: Option, #[serde(rename = "daysOfSalesOutstandingTTM")] - pub days_of_sales_outstanding_ttm: NumericString, + pub days_of_sales_outstanding_ttm: Option, #[serde(rename = "daysOfPayablesOutstandingTTM")] - pub days_of_payables_outstanding_ttm: NumericString, + pub days_of_payables_outstanding_ttm: Option, #[serde(rename = "daysOfInventoryOutstandingTTM")] - pub days_of_inventory_outstanding_ttm: NumericString, + pub days_of_inventory_outstanding_ttm: Option, #[serde(rename = "operatingCycleTTM")] - pub operating_cycle_ttm: NumericString, + pub operating_cycle_ttm: Option, #[serde(rename = "cashConversionCycleTTM")] - pub cash_conversion_cycle_ttm: NumericString, + pub cash_conversion_cycle_ttm: Option, #[serde(rename = "freeCashFlowToEquityTTM")] - pub free_cash_flow_to_equity_ttm: NumericString, + pub free_cash_flow_to_equity_ttm: Option, #[serde(rename = "freeCashFlowToFirmTTM")] - pub free_cash_flow_to_firm_ttm: NumericString, + pub free_cash_flow_to_firm_ttm: Option, #[serde(rename = "tangibleAssetValueTTM")] - pub tangible_asset_value_ttm: NumericString, + pub tangible_asset_value_ttm: Option, #[serde(rename = "netCurrentAssetValueTTM")] - pub net_current_asset_value_ttm: NumericString, + pub net_current_asset_value_ttm: Option, } /// One worldwide financial-ratios trailing-twelve-month bulk row. @@ -104,123 +104,123 @@ pub struct BulkKeyMetricsTtm { pub struct BulkFinancialRatiosTtm { pub symbol: Ticker, #[serde(rename = "grossProfitMarginTTM")] - pub gross_profit_margin_ttm: NumericString, + pub gross_profit_margin_ttm: Option, #[serde(rename = "ebitMarginTTM")] - pub ebit_margin_ttm: NumericString, + pub ebit_margin_ttm: Option, #[serde(rename = "ebitdaMarginTTM")] - pub ebitda_margin_ttm: NumericString, + pub ebitda_margin_ttm: Option, #[serde(rename = "operatingProfitMarginTTM")] - pub operating_profit_margin_ttm: NumericString, + pub operating_profit_margin_ttm: Option, #[serde(rename = "pretaxProfitMarginTTM")] - pub pretax_profit_margin_ttm: NumericString, + pub pretax_profit_margin_ttm: Option, #[serde(rename = "continuousOperationsProfitMarginTTM")] - pub continuous_operations_profit_margin_ttm: NumericString, + pub continuous_operations_profit_margin_ttm: Option, #[serde(rename = "netProfitMarginTTM")] - pub net_profit_margin_ttm: NumericString, + pub net_profit_margin_ttm: Option, #[serde(rename = "bottomLineProfitMarginTTM")] - pub bottom_line_profit_margin_ttm: NumericString, + pub bottom_line_profit_margin_ttm: Option, #[serde(rename = "receivablesTurnoverTTM")] - pub receivables_turnover_ttm: NumericString, + pub receivables_turnover_ttm: Option, #[serde(rename = "payablesTurnoverTTM")] - pub payables_turnover_ttm: NumericString, + pub payables_turnover_ttm: Option, #[serde(rename = "inventoryTurnoverTTM")] - pub inventory_turnover_ttm: NumericString, + pub inventory_turnover_ttm: Option, #[serde(rename = "fixedAssetTurnoverTTM")] - pub fixed_asset_turnover_ttm: NumericString, + pub fixed_asset_turnover_ttm: Option, #[serde(rename = "assetTurnoverTTM")] - pub asset_turnover_ttm: NumericString, + pub asset_turnover_ttm: Option, #[serde(rename = "currentRatioTTM")] - pub current_ratio_ttm: NumericString, + pub current_ratio_ttm: Option, #[serde(rename = "quickRatioTTM")] - pub quick_ratio_ttm: NumericString, + pub quick_ratio_ttm: Option, #[serde(rename = "solvencyRatioTTM")] - pub solvency_ratio_ttm: NumericString, + pub solvency_ratio_ttm: Option, #[serde(rename = "cashRatioTTM")] - pub cash_ratio_ttm: NumericString, + pub cash_ratio_ttm: Option, #[serde(rename = "priceToEarningsRatioTTM")] - pub price_to_earnings_ratio_ttm: NumericString, + pub price_to_earnings_ratio_ttm: Option, #[serde(rename = "priceToEarningsGrowthRatioTTM")] - pub price_to_earnings_growth_ratio_ttm: NumericString, + pub price_to_earnings_growth_ratio_ttm: Option, #[serde(rename = "forwardPriceToEarningsGrowthRatioTTM")] - pub forward_price_to_earnings_growth_ratio_ttm: NumericString, + pub forward_price_to_earnings_growth_ratio_ttm: Option, #[serde(rename = "priceToBookRatioTTM")] - pub price_to_book_ratio_ttm: NumericString, + pub price_to_book_ratio_ttm: Option, #[serde(rename = "priceToSalesRatioTTM")] - pub price_to_sales_ratio_ttm: NumericString, + pub price_to_sales_ratio_ttm: Option, #[serde(rename = "priceToFreeCashFlowRatioTTM")] - pub price_to_free_cash_flow_ratio_ttm: NumericString, + pub price_to_free_cash_flow_ratio_ttm: Option, #[serde(rename = "priceToOperatingCashFlowRatioTTM")] - pub price_to_operating_cash_flow_ratio_ttm: NumericString, + pub price_to_operating_cash_flow_ratio_ttm: Option, #[serde(rename = "debtToAssetsRatioTTM")] - pub debt_to_assets_ratio_ttm: NumericString, + pub debt_to_assets_ratio_ttm: Option, #[serde(rename = "debtToEquityRatioTTM")] - pub debt_to_equity_ratio_ttm: NumericString, + pub debt_to_equity_ratio_ttm: Option, #[serde(rename = "debtToCapitalRatioTTM")] - pub debt_to_capital_ratio_ttm: NumericString, + pub debt_to_capital_ratio_ttm: Option, #[serde(rename = "longTermDebtToCapitalRatioTTM")] - pub long_term_debt_to_capital_ratio_ttm: NumericString, + pub long_term_debt_to_capital_ratio_ttm: Option, #[serde(rename = "financialLeverageRatioTTM")] - pub financial_leverage_ratio_ttm: NumericString, + pub financial_leverage_ratio_ttm: Option, #[serde(rename = "workingCapitalTurnoverRatioTTM")] - pub working_capital_turnover_ratio_ttm: NumericString, + pub working_capital_turnover_ratio_ttm: Option, #[serde(rename = "operatingCashFlowRatioTTM")] - pub operating_cash_flow_ratio_ttm: NumericString, + pub operating_cash_flow_ratio_ttm: Option, #[serde(rename = "operatingCashFlowSalesRatioTTM")] - pub operating_cash_flow_sales_ratio_ttm: NumericString, + pub operating_cash_flow_sales_ratio_ttm: Option, #[serde(rename = "freeCashFlowOperatingCashFlowRatioTTM")] - pub free_cash_flow_operating_cash_flow_ratio_ttm: NumericString, + pub free_cash_flow_operating_cash_flow_ratio_ttm: Option, #[serde(rename = "debtServiceCoverageRatioTTM")] - pub debt_service_coverage_ratio_ttm: NumericString, + pub debt_service_coverage_ratio_ttm: Option, #[serde(rename = "interestCoverageRatioTTM")] - pub interest_coverage_ratio_ttm: NumericString, + pub interest_coverage_ratio_ttm: Option, #[serde(rename = "shortTermOperatingCashFlowCoverageRatioTTM")] - pub short_term_operating_cash_flow_coverage_ratio_ttm: NumericString, + pub short_term_operating_cash_flow_coverage_ratio_ttm: Option, #[serde(rename = "operatingCashFlowCoverageRatioTTM")] - pub operating_cash_flow_coverage_ratio_ttm: NumericString, + pub operating_cash_flow_coverage_ratio_ttm: Option, #[serde(rename = "capitalExpenditureCoverageRatioTTM")] - pub capital_expenditure_coverage_ratio_ttm: NumericString, + pub capital_expenditure_coverage_ratio_ttm: Option, #[serde(rename = "dividendPaidAndCapexCoverageRatioTTM")] - pub dividend_paid_and_capex_coverage_ratio_ttm: NumericString, + pub dividend_paid_and_capex_coverage_ratio_ttm: Option, #[serde(rename = "dividendPayoutRatioTTM")] - pub dividend_payout_ratio_ttm: NumericString, + pub dividend_payout_ratio_ttm: Option, #[serde(rename = "dividendYieldTTM")] - pub dividend_yield_ttm: NumericString, + pub dividend_yield_ttm: Option, #[serde(rename = "enterpriseValueTTM")] - pub enterprise_value_ttm: NumericString, + pub enterprise_value_ttm: Option, #[serde(rename = "revenuePerShareTTM")] - pub revenue_per_share_ttm: NumericString, + pub revenue_per_share_ttm: Option, #[serde(rename = "netIncomePerShareTTM")] - pub net_income_per_share_ttm: NumericString, + pub net_income_per_share_ttm: Option, #[serde(rename = "interestDebtPerShareTTM")] - pub interest_debt_per_share_ttm: NumericString, + pub interest_debt_per_share_ttm: Option, #[serde(rename = "cashPerShareTTM")] - pub cash_per_share_ttm: NumericString, + pub cash_per_share_ttm: Option, #[serde(rename = "bookValuePerShareTTM")] - pub book_value_per_share_ttm: NumericString, + pub book_value_per_share_ttm: Option, #[serde(rename = "tangibleBookValuePerShareTTM")] - pub tangible_book_value_per_share_ttm: NumericString, + pub tangible_book_value_per_share_ttm: Option, #[serde(rename = "shareholdersEquityPerShareTTM")] - pub shareholders_equity_per_share_ttm: NumericString, + pub shareholders_equity_per_share_ttm: Option, #[serde(rename = "operatingCashFlowPerShareTTM")] - pub operating_cash_flow_per_share_ttm: NumericString, + pub operating_cash_flow_per_share_ttm: Option, #[serde(rename = "capexPerShareTTM")] - pub capex_per_share_ttm: NumericString, + pub capex_per_share_ttm: Option, #[serde(rename = "freeCashFlowPerShareTTM")] - pub free_cash_flow_per_share_ttm: NumericString, + pub free_cash_flow_per_share_ttm: Option, #[serde(rename = "netIncomePerEBTTTM")] - pub net_income_per_ebt_ttm: NumericString, + pub net_income_per_ebt_ttm: Option, #[serde(rename = "ebtPerEbitTTM")] - pub ebt_per_ebit_ttm: NumericString, + pub ebt_per_ebit_ttm: Option, #[serde(rename = "priceToFairValueTTM")] - pub price_to_fair_value_ttm: NumericString, + pub price_to_fair_value_ttm: Option, #[serde(rename = "debtToMarketCapTTM")] - pub debt_to_market_cap_ttm: NumericString, + pub debt_to_market_cap_ttm: Option, #[serde(rename = "effectiveTaxRateTTM")] - pub effective_tax_rate_ttm: NumericString, + pub effective_tax_rate_ttm: Option, #[serde(rename = "enterpriseValueMultipleTTM")] - pub enterprise_value_multiple_ttm: NumericString, + pub enterprise_value_multiple_ttm: Option, #[serde(rename = "dividendPerShareTTM")] - pub dividend_per_share_ttm: NumericString, + pub dividend_per_share_ttm: Option, } /// One worldwide stock-peer bulk row. @@ -238,7 +238,7 @@ pub struct BulkStockPeer { pub struct BulkEarningsSurprise { pub symbol: Ticker, pub date: Date, - pub eps_actual: NumericString, - pub eps_estimated: NumericString, + pub eps_actual: Option, + pub eps_estimated: Option, pub last_updated: Date, } diff --git a/crates/libfmp/src/responses/bulk/snapshots.rs b/crates/libfmp/src/responses/bulk/snapshots.rs index b25d1aa7..0b2933fc 100644 --- a/crates/libfmp/src/responses/bulk/snapshots.rs +++ b/crates/libfmp/src/responses/bulk/snapshots.rs @@ -15,12 +15,12 @@ pub struct BulkStockRating { pub symbol: Ticker, pub date: Date, pub rating: String, - pub discounted_cash_flow_score: NumericString, - pub return_on_equity_score: NumericString, - pub return_on_assets_score: NumericString, - pub debt_to_equity_score: NumericString, - pub price_to_earnings_score: NumericString, - pub price_to_book_score: NumericString, + pub discounted_cash_flow_score: Option, + pub return_on_equity_score: Option, + pub return_on_assets_score: Option, + pub debt_to_equity_score: Option, + pub price_to_earnings_score: Option, + pub price_to_book_score: Option, } /// One worldwide discounted-cash-flow bulk row. @@ -30,9 +30,9 @@ pub struct BulkStockRating { pub struct BulkDcfValuation { pub symbol: Ticker, pub date: Date, - pub dcf: NumericString, + pub dcf: Option, #[serde(rename = "Stock Price")] - pub stock_price: NumericString, + pub stock_price: Option, } /// One worldwide financial-score bulk row. @@ -41,16 +41,17 @@ pub struct BulkDcfValuation { #[serde(rename_all = "camelCase")] pub struct BulkFinancialScore { pub symbol: Ticker, - pub reported_currency: CurrencyCode, - pub altman_z_score: NumericString, - pub piotroski_score: NumericString, - pub working_capital: NumericString, - pub total_assets: NumericString, - pub retained_earnings: NumericString, - pub ebit: NumericString, - pub market_cap: NumericString, - pub total_liabilities: NumericString, - pub revenue: NumericString, + #[serde(deserialize_with = "crate::codecs::empty_or_null::deserialize")] + pub reported_currency: Option, + pub altman_z_score: Option, + pub piotroski_score: Option, + pub working_capital: Option, + pub total_assets: Option, + pub retained_earnings: Option, + pub ebit: Option, + pub market_cap: Option, + pub total_liabilities: Option, + pub revenue: Option, } /// One US price-target-summary bulk row. @@ -59,14 +60,14 @@ pub struct BulkFinancialScore { #[serde(rename_all = "camelCase")] pub struct BulkPriceTargetSummary { pub symbol: Ticker, - pub last_month_count: NumericString, - pub last_month_avg_price_target: NumericString, - pub last_quarter_count: NumericString, - pub last_quarter_avg_price_target: NumericString, - pub last_year_count: NumericString, - pub last_year_avg_price_target: NumericString, - pub all_time_count: NumericString, - pub all_time_avg_price_target: NumericString, + pub last_month_count: Option, + pub last_month_avg_price_target: Option, + pub last_quarter_count: Option, + pub last_quarter_avg_price_target: Option, + pub last_year_count: Option, + pub last_year_avg_price_target: Option, + pub all_time_count: Option, + pub all_time_avg_price_target: Option, pub publishers: String, } @@ -77,14 +78,15 @@ pub struct BulkPriceTargetSummary { pub struct BulkEtfHolding { pub symbol: Ticker, pub name: String, - pub shares_number: NumericString, - pub asset: Ticker, - pub weight_percentage: NumericString, + pub shares_number: Option, + #[serde(deserialize_with = "crate::codecs::empty_or_null::deserialize")] + pub asset: Option, + pub weight_percentage: Option, pub cusip: String, - pub isin: Isin, - pub market_value: NumericString, - #[serde(rename = "lastUpdated\"")] - pub last_updated_raw: String, + #[serde(deserialize_with = "crate::codecs::empty_or_null::deserialize")] + pub isin: Option, + pub market_value: Option, + pub last_updated: Date, } /// One worldwide upgrades/downgrades-consensus bulk row. @@ -93,10 +95,10 @@ pub struct BulkEtfHolding { #[serde(rename_all = "camelCase")] pub struct BulkUpgradesDowngradesConsensus { pub symbol: String, - pub strong_buy: NumericString, - pub buy: NumericString, - pub hold: NumericString, - pub sell: NumericString, - pub strong_sell: NumericString, + pub strong_buy: Option, + pub buy: Option, + pub hold: Option, + pub sell: Option, + pub strong_sell: Option, pub consensus: String, } diff --git a/crates/libfmp/src/responses/company.rs b/crates/libfmp/src/responses/company.rs index fdf4a19d..a7de0755 100644 --- a/crates/libfmp/src/responses/company.rs +++ b/crates/libfmp/src/responses/company.rs @@ -37,8 +37,10 @@ pub struct CompanyProfile { #[serde(serialize_with = "crate::codecs::integral_f64::serialize")] pub last_dividend: PerShareAmount, pub range: String, - pub change: Change, - pub change_percentage: Percentage, + #[serde(deserialize_with = "required_option")] + pub change: Option, + #[serde(deserialize_with = "required_option")] + pub change_percentage: Option, #[serde(serialize_with = "crate::codecs::integral_f64::serialize")] pub volume: Volume, #[serde(serialize_with = "crate::codecs::integral_f64::serialize")] @@ -47,7 +49,8 @@ pub struct CompanyProfile { pub currency: CurrencyCode, #[serde(deserialize_with = "required_option")] pub cik: Option, - pub isin: Isin, + #[serde(deserialize_with = "crate::codecs::empty_or_null::deserialize")] + pub isin: Option, #[serde(deserialize_with = "required_option")] pub cusip: Option, pub exchange_full_name: String, @@ -57,7 +60,8 @@ pub struct CompanyProfile { pub description: String, pub ceo: String, pub sector: Sector, - pub country: CountryCode, + #[serde(deserialize_with = "crate::codecs::empty_or_null::deserialize")] + pub country: Option, #[serde(deserialize_with = "required_option")] pub full_time_employees: Option, #[serde(deserialize_with = "required_option")] diff --git a/crates/libfmp/tests/bulk_balance_endpoints.rs b/crates/libfmp/tests/bulk_balance_endpoints.rs index 9f997fba..c98614e8 100644 --- a/crates/libfmp/tests/bulk_balance_endpoints.rs +++ b/crates/libfmp/tests/bulk_balance_endpoints.rs @@ -18,13 +18,17 @@ use libfmp::{ transport::{HttpMethod, TransportResponse}, }; -use support::{FixtureExecutor, json_fixture}; +use support::{FixtureExecutor, fixture_response, json_fixture}; -const BALANCE: &[u8] = include_bytes!("fixtures/bulk_balance_sheet_statements.json"); -const GROWTH: &[u8] = include_bytes!("fixtures/bulk_balance_sheet_statement_growth.json"); +const BALANCE: &[u8] = include_bytes!("fixtures/bulk_balance_sheet_statements.csv"); +const GROWTH: &[u8] = include_bytes!("fixtures/bulk_balance_sheet_statement_growth.csv"); + +fn csv_fixture(body: &'static [u8]) -> TransportResponse { + fixture_response(Some("text/csv"), body) +} fn fixtures() -> [TransportResponse; 2] { - [json_fixture(BALANCE), json_fixture(GROWTH)] + [csv_fixture(BALANCE), csv_fixture(GROWTH)] } #[test] @@ -67,7 +71,7 @@ fn assert_response_types( #[tokio::test] async fn all_five_periods_encode_required_year_then_period_without_defaults_or_ranges() { let executor = Arc::new(FixtureExecutor::new( - std::iter::repeat_with(|| json_fixture(b"[]")).take(5), + std::iter::repeat_with(|| csv_fixture(b"")).take(5), )); let client = Client::builder() .authentication(Authentication::fmp_header("secret")) @@ -124,11 +128,11 @@ async fn custom_proxy_sends_one_exact_request_per_method_with_auth_headers_and_d .await .unwrap(); - assert_eq!(balance[0].symbol.as_str(), "MTLRP.ME"); + assert_eq!(balance[0].symbol.as_str(), "000001.SZ"); assert_eq!(balance[0].cik.as_str(), "0000000000"); - assert_eq!(balance[0].accepted_date.to_string(), "2025-03-31 07:00:00"); + assert_eq!(balance[0].accepted_date.to_string(), "2024-12-31 00:00:00"); assert_eq!(growth[0].reported_currency.as_str(), "CNY"); - assert_eq!(growth[0].period, FiscalPeriod::Q1); + assert_eq!(growth[0].period, FiscalPeriod::FullYear); let requests = executor.requests(); assert_eq!(requests.len(), 2); @@ -197,12 +201,12 @@ async fn direct_fmp_header_and_query_auth_preserve_paths_and_exact_query_order() } #[tokio::test] -async fn bare_empty_arrays_decode_and_malformed_roots_keep_both_endpoint_identities() { +async fn empty_bodies_decode_and_json_bodies_keep_both_endpoint_identities() { let executor = Arc::new(FixtureExecutor::new([ + csv_fixture(b""), + csv_fixture(b""), json_fixture(b"[]"), json_fixture(b"[]"), - json_fixture(b"{}"), - json_fixture(b"{}"), ])); let client = Client::builder() .authentication(Authentication::fmp_header("secret")) diff --git a/crates/libfmp/tests/bulk_balance_responses.rs b/crates/libfmp/tests/bulk_balance_responses.rs index 2dc788c9..192e3f99 100644 --- a/crates/libfmp/tests/bulk_balance_responses.rs +++ b/crates/libfmp/tests/bulk_balance_responses.rs @@ -1,29 +1,36 @@ -use serde::{Serialize, de::DeserializeOwned}; -use serde_json::{Map, Value, json}; +#[path = "support/bulk_csv.rs"] +mod bulk_csv; +#[allow(dead_code)] // The CSV helpers use only the fixture executor. +mod support; use libfmp::{ - query::FiscalPeriod, - responses::bulk::{BulkBalanceSheetStatement, BulkBalanceSheetStatementGrowth}, + endpoints::bulk::{ + BulkStatementQuery, bulk_balance_sheet_statement_growth, bulk_balance_sheet_statements, + }, + query::{FiscalPeriod, Year}, }; -const BALANCE: &[u8] = include_bytes!("fixtures/bulk_balance_sheet_statements.json"); -const GROWTH: &[u8] = include_bytes!("fixtures/bulk_balance_sheet_statement_growth.json"); +use bulk_csv::{ + assert_empty_bodies, assert_required_members, assert_round_trip, decode, first_row, +}; -#[test] -fn exact_outer_md_fixtures_round_trip_with_exact_unique_key_sets() { - assert_exact::(BALANCE, 61); - assert_exact::(GROWTH, 56); +const BALANCE: &[u8] = include_bytes!("fixtures/bulk_balance_sheet_statements.csv"); +const GROWTH: &[u8] = include_bytes!("fixtures/bulk_balance_sheet_statement_growth.csv"); + +fn query() -> BulkStatementQuery { + BulkStatementQuery::new(Year(2024), FiscalPeriod::FullYear) } #[test] -fn every_documented_field_is_required_non_null_and_unknown_fields_are_tolerated() { - assert_required::(BALANCE); - assert_required::(GROWTH); +fn live_csv_fixtures_round_trip_every_cell_with_exact_field_counts() { + assert_round_trip(&bulk_balance_sheet_statements(query()), BALANCE, 61); + assert_round_trip(&bulk_balance_sheet_statement_growth(query()), GROWTH, 56); } #[test] -fn every_documented_metric_preserves_numeric_text_and_rejects_json_numbers() { - assert_all_numbers_rejected::( +fn every_column_is_required_and_only_identity_members_reject_an_empty_cell() { + assert_required_members( + &bulk_balance_sheet_statements(query()), BALANCE, &[ "date", @@ -36,62 +43,36 @@ fn every_documented_metric_preserves_numeric_text_and_rejects_json_numbers() { "period", ], ); - assert_all_numbers_rejected::( + assert_required_members( + &bulk_balance_sheet_statement_growth(query()), GROWTH, &["symbol", "date", "fiscalYear", "period", "reportedCurrency"], ); - - let balance = rows::(BALANCE).remove(0); - assert_eq!(balance.total_assets.as_str(), "247871857000"); - assert_eq!(balance.retained_earnings.as_str(), "-5066509000"); - assert_eq!(balance.other_assets.as_str(), "0"); - assert_eq!(balance.net_debt.as_str(), "183764862000"); - - let growth = rows::(GROWTH).remove(0); - assert_eq!( - growth.growth_cash_and_cash_equivalents.as_str(), - "0.09574482145872953" - ); - assert_eq!(growth.growth_short_term_investments.as_str(), "0"); - assert_eq!( - growth.growth_total_payables.as_str(), - "-0.12022416350749959" - ); } #[test] -fn identity_fields_are_narrow_and_preserve_documented_representations() { - let balance = rows::(BALANCE).remove(0); - assert_eq!(balance.symbol.as_str(), "MTLRP.ME"); - assert_eq!(balance.reported_currency.as_str(), "RUB"); - assert_eq!(balance.cik.as_str(), "0000000000"); - assert_eq!(balance.date.to_string(), "2025-03-31"); - assert_eq!(balance.filing_date.to_string(), "2025-05-31"); - assert_eq!(balance.accepted_date.to_string(), "2025-03-31 07:00:00"); - assert_eq!(balance.fiscal_year.as_str(), "2025"); - assert_eq!(balance.period, FiscalPeriod::Q1); +fn empty_and_header_only_bodies_decode_to_no_rows() { + assert_empty_bodies(&bulk_balance_sheet_statements(query()), BALANCE); + assert_empty_bodies(&bulk_balance_sheet_statement_growth(query()), GROWTH); +} - let mut invalid_date = source_row(BALANCE); - invalid_date.insert("date".into(), json!("2025-03-31 00:00:00")); - assert!( - serde_json::from_value::(Value::Object(invalid_date)).is_err() - ); - let mut invalid_datetime = source_row(BALANCE); - invalid_datetime.insert("acceptedDate".into(), json!("2025-03-31")); - assert!( - serde_json::from_value::(Value::Object(invalid_datetime)) - .is_err() - ); - let mut numeric_cik = source_row(BALANCE); - numeric_cik.insert("cik".into(), json!(0)); - assert!( - serde_json::from_value::(Value::Object(numeric_cik)).is_err() +#[test] +fn identity_fields_are_narrow_and_preserve_provider_representations() { + let balance = decode(&bulk_balance_sheet_statements(query()), BALANCE).unwrap(); + assert_eq!(balance[1].symbol.as_str(), "0002.KL"); + assert_eq!(balance[1].reported_currency.as_str(), "MYR"); + assert_eq!(balance[1].cik.as_str(), "0000000000"); + assert_eq!(balance[1].accepted_date.to_string(), "2025-06-30 00:00:00"); + assert_eq!(balance[1].fiscal_year.as_str(), "2024"); + assert_eq!( + balance[0].total_assets.as_ref().unwrap().as_str(), + "5769270000000" ); } #[test] fn provider_typo_and_liabilities_equity_wording_keep_exact_wire_names() { - let growth = serialized_row::(GROWTH); + let growth = first_row(&bulk_balance_sheet_statement_growth(query()), GROWTH); for key in [ "growthOthertotalStockholdersEquity", "growthTotalLiabilitiesAndStockholdersEquity", @@ -105,96 +86,3 @@ fn provider_typo_and_liabilities_equity_wording_keep_exact_wire_names() { assert!(!growth.contains_key(incorrect)); } } - -#[test] -fn both_contracts_require_bare_array_roots_and_accept_empty_arrays() { - assert_array_contract::(); - assert_array_contract::(); -} - -fn rows(fixture: &[u8]) -> Vec { - serde_json::from_slice(fixture).unwrap() -} - -fn source_row(fixture: &[u8]) -> Map { - serde_json::from_slice::(fixture).unwrap()[0] - .as_object() - .unwrap() - .clone() -} - -fn serialized_row(fixture: &[u8]) -> Map -where - T: DeserializeOwned + Serialize, -{ - serde_json::to_value(rows::(fixture)).unwrap()[0] - .as_object() - .unwrap() - .clone() -} - -fn assert_exact(fixture: &[u8], fields: usize) -where - T: DeserializeOwned + Serialize, -{ - let source: Value = serde_json::from_slice(fixture).unwrap(); - let row = source[0].as_object().unwrap(); - assert_eq!(row.len(), fields); - assert_eq!( - row.keys().collect::>().len(), - fields - ); - assert_eq!(serde_json::to_value(rows::(fixture)).unwrap(), source); -} - -fn assert_required(fixture: &[u8]) -where - T: DeserializeOwned, -{ - let row = source_row(fixture); - for field in row.keys() { - let mut missing = row.clone(); - missing.remove(field); - assert!( - serde_json::from_value::(Value::Object(missing)).is_err(), - "missing {field} unexpectedly decoded" - ); - - let mut null = row.clone(); - null.insert(field.clone(), Value::Null); - assert!( - serde_json::from_value::(Value::Object(null)).is_err(), - "null {field} unexpectedly decoded" - ); - } - - let mut future = row; - future.insert("futureField".into(), json!({"nested": true})); - assert!(serde_json::from_value::(Value::Object(future)).is_ok()); -} - -fn assert_all_numbers_rejected(fixture: &[u8], non_numeric: &[&str]) -where - T: DeserializeOwned, -{ - let row = source_row(fixture); - for field in row - .keys() - .filter(|field| !non_numeric.contains(&field.as_str())) - { - let mut numeric = row.clone(); - numeric.insert(field.clone(), json!(123.5)); - assert!( - serde_json::from_value::(Value::Object(numeric)).is_err(), - "JSON number unexpectedly decoded for {field}" - ); - } -} - -fn assert_array_contract() -where - T: DeserializeOwned, -{ - assert!(serde_json::from_slice::>(b"[]").unwrap().is_empty()); - assert!(serde_json::from_slice::>(b"{}").is_err()); -} diff --git a/crates/libfmp/tests/bulk_cash_eod_endpoints.rs b/crates/libfmp/tests/bulk_cash_eod_endpoints.rs index f9288701..b8f0de01 100644 --- a/crates/libfmp/tests/bulk_cash_eod_endpoints.rs +++ b/crates/libfmp/tests/bulk_cash_eod_endpoints.rs @@ -20,17 +20,21 @@ use libfmp::{ types::Date, }; -use support::{FixtureExecutor, json_fixture}; +use support::{FixtureExecutor, fixture_response, json_fixture}; -const CASH_FLOW: &[u8] = include_bytes!("fixtures/bulk_cash_flow_statements.json"); -const GROWTH: &[u8] = include_bytes!("fixtures/bulk_cash_flow_statement_growth.json"); -const EOD: &[u8] = include_bytes!("fixtures/bulk_eod.json"); +const CASH_FLOW: &[u8] = include_bytes!("fixtures/bulk_cash_flow_statements.csv"); +const GROWTH: &[u8] = include_bytes!("fixtures/bulk_cash_flow_statement_growth.csv"); +const EOD: &[u8] = include_bytes!("fixtures/bulk_eod.csv"); + +fn csv_fixture(body: &'static [u8]) -> TransportResponse { + fixture_response(Some("text/csv"), body) +} fn fixtures() -> [TransportResponse; 3] { [ - json_fixture(CASH_FLOW), - json_fixture(GROWTH), - json_fixture(EOD), + csv_fixture(CASH_FLOW), + csv_fixture(GROWTH), + csv_fixture(EOD), ] } @@ -78,7 +82,7 @@ fn assert_response_types( #[tokio::test] async fn all_five_periods_encode_required_year_then_period_without_defaults_or_ranges() { let executor = Arc::new(FixtureExecutor::new( - std::iter::repeat_with(|| json_fixture(b"[]")).take(5), + std::iter::repeat_with(|| csv_fixture(b"")).take(5), )); let client = Client::builder() .authentication(Authentication::fmp_header("secret")) @@ -139,9 +143,9 @@ async fn custom_proxy_sends_one_exact_request_per_method_with_auth_headers_and_d assert_eq!(cash_flow[0].symbol.as_str(), "000001.SZ"); assert_eq!(cash_flow[0].cik.as_str(), "0000000000"); assert_eq!(growth[0].reported_currency.as_str(), "CNY"); - assert_eq!(growth[0].period, FiscalPeriod::Q1); - assert_eq!(eod[0].symbol.as_str(), "EGS745W1C011.CA"); - assert_eq!(eod[0].date, date); + assert_eq!(growth[0].period, FiscalPeriod::FullYear); + assert_eq!(eod[0].symbol.as_str(), "PCFD.SG"); + assert_eq!(eod[0].date, Date::parse("2025-06-02").unwrap()); let requests = executor.requests(); assert_eq!(requests.len(), 3); @@ -218,14 +222,14 @@ async fn direct_fmp_header_and_query_auth_preserve_paths_and_exact_query_order() } #[tokio::test] -async fn bare_empty_arrays_decode_and_malformed_roots_keep_all_endpoint_identities() { +async fn empty_bodies_decode_and_json_bodies_keep_all_endpoint_identities() { let executor = Arc::new(FixtureExecutor::new([ + csv_fixture(b""), + csv_fixture(b""), + csv_fixture(b""), json_fixture(b"[]"), json_fixture(b"[]"), json_fixture(b"[]"), - json_fixture(b"{}"), - json_fixture(b"{}"), - json_fixture(b"{}"), ])); let client = Client::builder() .authentication(Authentication::fmp_header("secret")) diff --git a/crates/libfmp/tests/bulk_cash_eod_responses.rs b/crates/libfmp/tests/bulk_cash_eod_responses.rs index 64aa3b7e..52dd8036 100644 --- a/crates/libfmp/tests/bulk_cash_eod_responses.rs +++ b/crates/libfmp/tests/bulk_cash_eod_responses.rs @@ -1,32 +1,43 @@ -use serde::{Serialize, de::DeserializeOwned}; -use serde_json::{Map, Value, json}; +#[path = "support/bulk_csv.rs"] +mod bulk_csv; +#[allow(dead_code)] // The CSV helpers use only the fixture executor. +mod support; use libfmp::{ - query::FiscalPeriod, - responses::bulk::{BulkCashFlowStatement, BulkCashFlowStatementGrowth, BulkEodBar}, + endpoints::bulk::{ + BulkStatementQuery, bulk_cash_flow_statement_growth, bulk_cash_flow_statements, bulk_eod, + }, + query::{FiscalPeriod, Year}, + types::Date, }; -const CASH_FLOW: &[u8] = include_bytes!("fixtures/bulk_cash_flow_statements.json"); -const GROWTH: &[u8] = include_bytes!("fixtures/bulk_cash_flow_statement_growth.json"); -const EOD: &[u8] = include_bytes!("fixtures/bulk_eod.json"); +use bulk_csv::{ + assert_empty_bodies, assert_required_members, assert_round_trip, decode, first_row, +}; -#[test] -fn exact_outer_md_fixtures_round_trip_with_exact_unique_key_sets() { - assert_exact::(CASH_FLOW, 47); - assert_exact::(GROWTH, 42); - assert_exact::(EOD, 8); +const CASH_FLOW: &[u8] = include_bytes!("fixtures/bulk_cash_flow_statements.csv"); +const GROWTH: &[u8] = include_bytes!("fixtures/bulk_cash_flow_statement_growth.csv"); +const EOD: &[u8] = include_bytes!("fixtures/bulk_eod.csv"); + +fn query() -> BulkStatementQuery { + BulkStatementQuery::new(Year(2024), FiscalPeriod::Q1) +} + +fn day() -> Date { + Date::parse("2025-06-02").unwrap() } #[test] -fn every_documented_field_is_required_non_null_and_unknown_fields_are_tolerated() { - assert_required::(CASH_FLOW); - assert_required::(GROWTH); - assert_required::(EOD); +fn live_csv_fixtures_round_trip_every_cell_with_exact_field_counts() { + assert_round_trip(&bulk_cash_flow_statements(query()), CASH_FLOW, 47); + assert_round_trip(&bulk_cash_flow_statement_growth(query()), GROWTH, 42); + assert_round_trip(&bulk_eod(day().into()), EOD, 8); } #[test] -fn every_documented_metric_preserves_numeric_text_and_rejects_json_numbers() { - assert_all_numbers_rejected::( +fn every_column_is_required_and_only_identity_members_reject_an_empty_cell() { + assert_required_members( + &bulk_cash_flow_statements(query()), CASH_FLOW, &[ "date", @@ -39,64 +50,41 @@ fn every_documented_metric_preserves_numeric_text_and_rejects_json_numbers() { "period", ], ); - assert_all_numbers_rejected::( + assert_required_members( + &bulk_cash_flow_statement_growth(query()), GROWTH, &["symbol", "date", "fiscalYear", "period", "reportedCurrency"], ); - assert_all_numbers_rejected::(EOD, &["symbol", "date"]); - - let cash_flow = rows::(CASH_FLOW).remove(0); - assert_eq!(cash_flow.other_non_cash_items.as_str(), "162946000000"); - assert_eq!(cash_flow.purchases_of_investments.as_str(), "-227916000000"); - assert_eq!(cash_flow.net_income.as_str(), "0"); - - let growth = rows::(GROWTH).remove(0); - assert_eq!( - growth - .growth_net_cash_used_provided_by_financing_activities - .as_str(), - "-3.2122934677858628" - ); - assert_eq!(growth.growth_net_debt_issuance.as_str(), "1"); + assert_required_members(&bulk_eod(day().into()), EOD, &["symbol", "date"]); +} - let eod = rows::(EOD).remove(0); - assert_eq!(eod.open.as_str(), "2.67"); - assert_eq!(eod.low.as_str(), "2.7"); - assert_eq!(eod.volume.as_str(), "920904"); +#[test] +fn empty_and_header_only_bodies_decode_to_no_rows() { + assert_empty_bodies(&bulk_cash_flow_statements(query()), CASH_FLOW); + assert_empty_bodies(&bulk_cash_flow_statement_growth(query()), GROWTH); + assert_empty_bodies(&bulk_eod(day().into()), EOD); } #[test] -fn identity_fields_are_narrow_and_preserve_documented_representations() { - let cash_flow = rows::(CASH_FLOW).remove(0); - assert_eq!(cash_flow.symbol.as_str(), "000001.SZ"); - assert_eq!(cash_flow.reported_currency.as_str(), "CNY"); +fn identity_fields_and_prices_preserve_provider_representations() { + let cash_flow = decode(&bulk_cash_flow_statements(query()), CASH_FLOW) + .unwrap() + .remove(0); assert_eq!(cash_flow.cik.as_str(), "0000000000"); - assert_eq!(cash_flow.date.to_string(), "2025-03-31"); - assert_eq!(cash_flow.filing_date.to_string(), "2025-03-31"); - assert_eq!(cash_flow.accepted_date.to_string(), "2025-03-31 00:00:00"); - assert_eq!(cash_flow.fiscal_year.as_str(), "2025"); + assert_eq!(cash_flow.accepted_date.to_string(), "2024-03-30 20:00:00"); assert_eq!(cash_flow.period, FiscalPeriod::Q1); + assert_eq!(cash_flow.net_income.unwrap().as_str(), "14932000000"); - let eod = rows::(EOD).remove(0); - assert_eq!(eod.symbol.as_str(), "EGS745W1C011.CA"); - assert_eq!(eod.date.to_string(), "2024-10-22"); - - let mut numeric_cik = source_row(CASH_FLOW); - numeric_cik.insert("cik".into(), json!(0)); - assert!(serde_json::from_value::(Value::Object(numeric_cik)).is_err()); - let mut invalid_datetime = source_row(CASH_FLOW); - invalid_datetime.insert("acceptedDate".into(), json!("2025-03-31")); - assert!( - serde_json::from_value::(Value::Object(invalid_datetime)).is_err() - ); - let mut invalid_eod_date = source_row(EOD); - invalid_eod_date.insert("date".into(), json!("2024-10-22 00:00:00")); - assert!(serde_json::from_value::(Value::Object(invalid_eod_date)).is_err()); + let eod = decode(&bulk_eod(day().into()), EOD).unwrap(); + assert_eq!(eod[1].symbol.as_str(), "HKDCNH"); + assert_eq!(eod[1].date, day()); + assert_eq!(eod[1].open.as_ref().unwrap().as_str(), "0.91858"); + assert_eq!(eod[1].volume.as_ref().unwrap().as_str(), "0"); } #[test] -fn provider_activity_typos_keep_exact_wire_names_with_corrected_rust_fields() { - let growth = serialized_row::(GROWTH); +fn activities_typos_keep_exact_provider_wire_names() { + let growth = first_row(&bulk_cash_flow_statement_growth(query()), GROWTH); for key in [ "growthNetCashProvidedByOperatingActivites", "growthOtherInvestingActivites", @@ -106,107 +94,5 @@ fn provider_activity_typos_keep_exact_wire_names_with_corrected_rust_fields() { ] { assert!(growth.contains_key(key), "missing exact wire key {key}"); } - for incorrect in [ - "growthNetCashProvidedByOperatingActivities", - "growthOtherInvestingActivities", - "growthNetCashUsedForInvestingActivities", - "growthOtherFinancingActivities", - "growthNetCashUsedProvidedByFinancingActivites", - ] { - assert!(!growth.contains_key(incorrect)); - } -} - -#[test] -fn all_three_contracts_require_bare_array_roots_and_accept_empty_arrays() { - assert_array_contract::(); - assert_array_contract::(); - assert_array_contract::(); -} - -fn rows(fixture: &[u8]) -> Vec { - serde_json::from_slice(fixture).unwrap() -} - -fn source_row(fixture: &[u8]) -> Map { - serde_json::from_slice::(fixture).unwrap()[0] - .as_object() - .unwrap() - .clone() -} - -fn serialized_row(fixture: &[u8]) -> Map -where - T: DeserializeOwned + Serialize, -{ - serde_json::to_value(rows::(fixture)).unwrap()[0] - .as_object() - .unwrap() - .clone() -} - -fn assert_exact(fixture: &[u8], fields: usize) -where - T: DeserializeOwned + Serialize, -{ - let source: Value = serde_json::from_slice(fixture).unwrap(); - let row = source[0].as_object().unwrap(); - assert_eq!(row.len(), fields); - assert_eq!( - row.keys().collect::>().len(), - fields - ); - assert_eq!(serde_json::to_value(rows::(fixture)).unwrap(), source); -} - -fn assert_required(fixture: &[u8]) -where - T: DeserializeOwned, -{ - let row = source_row(fixture); - for field in row.keys() { - let mut missing = row.clone(); - missing.remove(field); - assert!( - serde_json::from_value::(Value::Object(missing)).is_err(), - "missing {field} unexpectedly decoded" - ); - - let mut null = row.clone(); - null.insert(field.clone(), Value::Null); - assert!( - serde_json::from_value::(Value::Object(null)).is_err(), - "null {field} unexpectedly decoded" - ); - } - - let mut future = row; - future.insert("futureField".into(), json!({"nested": true})); - assert!(serde_json::from_value::(Value::Object(future)).is_ok()); -} - -fn assert_all_numbers_rejected(fixture: &[u8], non_numeric: &[&str]) -where - T: DeserializeOwned, -{ - let row = source_row(fixture); - for field in row - .keys() - .filter(|field| !non_numeric.contains(&field.as_str())) - { - let mut numeric = row.clone(); - numeric.insert(field.clone(), json!(123.5)); - assert!( - serde_json::from_value::(Value::Object(numeric)).is_err(), - "JSON number unexpectedly decoded for {field}" - ); - } -} - -fn assert_array_contract() -where - T: DeserializeOwned, -{ - assert!(serde_json::from_slice::>(b"[]").unwrap().is_empty()); - assert!(serde_json::from_slice::>(b"{}").is_err()); + assert!(!growth.contains_key("growthOtherInvestingActivities")); } diff --git a/crates/libfmp/tests/bulk_income_endpoints.rs b/crates/libfmp/tests/bulk_income_endpoints.rs index adfe5867..ef957266 100644 --- a/crates/libfmp/tests/bulk_income_endpoints.rs +++ b/crates/libfmp/tests/bulk_income_endpoints.rs @@ -16,13 +16,17 @@ use libfmp::{ transport::{HttpMethod, TransportResponse}, }; -use support::{FixtureExecutor, json_fixture}; +use support::{FixtureExecutor, fixture_response, json_fixture}; -const INCOME: &[u8] = include_bytes!("fixtures/bulk_income_statements.json"); -const GROWTH: &[u8] = include_bytes!("fixtures/bulk_income_statement_growth.json"); +const INCOME: &[u8] = include_bytes!("fixtures/bulk_income_statements.csv"); +const GROWTH: &[u8] = include_bytes!("fixtures/bulk_income_statement_growth.csv"); + +fn csv_fixture(body: &'static [u8]) -> TransportResponse { + fixture_response(Some("text/csv"), body) +} fn fixtures() -> [TransportResponse; 2] { - [json_fixture(INCOME), json_fixture(GROWTH)] + [csv_fixture(INCOME), csv_fixture(GROWTH)] } #[test] @@ -62,7 +66,7 @@ fn assert_response_types( #[tokio::test] async fn all_five_periods_encode_required_year_then_period_without_defaults_or_ranges() { let executor = Arc::new(FixtureExecutor::new( - std::iter::repeat_with(|| json_fixture(b"[]")).take(5), + std::iter::repeat_with(|| csv_fixture(b"")).take(5), )); let client = Client::builder() .authentication(Authentication::fmp_header("secret")) @@ -118,9 +122,9 @@ async fn custom_proxy_sends_one_exact_request_per_method_with_auth_headers_and_d assert_eq!(income[0].symbol.as_str(), "000001.SZ"); assert_eq!(income[0].cik.as_str(), "0000000000"); - assert_eq!(income[0].accepted_date.to_string(), "2025-03-31 00:00:00"); + assert_eq!(income[0].accepted_date.to_string(), "2024-12-31 00:00:00"); assert_eq!(growth[0].reported_currency.as_str(), "CNY"); - assert_eq!(growth[0].period, FiscalPeriod::Q1); + assert_eq!(growth[0].period, FiscalPeriod::FullYear); let requests = executor.requests(); assert_eq!(requests.len(), 2); @@ -184,12 +188,12 @@ async fn direct_fmp_header_and_query_auth_preserve_paths_and_exact_query_order() } #[tokio::test] -async fn bare_empty_arrays_decode_and_malformed_roots_keep_both_endpoint_identities() { +async fn empty_bodies_decode_and_json_bodies_keep_both_endpoint_identities() { let executor = Arc::new(FixtureExecutor::new([ + csv_fixture(b""), + csv_fixture(b""), json_fixture(b"[]"), json_fixture(b"[]"), - json_fixture(b"{}"), - json_fixture(b"{}"), ])); let client = Client::builder() .authentication(Authentication::fmp_header("secret")) diff --git a/crates/libfmp/tests/bulk_income_responses.rs b/crates/libfmp/tests/bulk_income_responses.rs index 0b2af68e..db015f44 100644 --- a/crates/libfmp/tests/bulk_income_responses.rs +++ b/crates/libfmp/tests/bulk_income_responses.rs @@ -1,29 +1,36 @@ -use serde::{Serialize, de::DeserializeOwned}; -use serde_json::{Map, Value, json}; +#[path = "support/bulk_csv.rs"] +mod bulk_csv; +#[allow(dead_code)] // The CSV helpers use only the fixture executor. +mod support; use libfmp::{ - query::FiscalPeriod, - responses::bulk::{BulkIncomeStatement, BulkIncomeStatementGrowth}, + endpoints::bulk::{BulkStatementQuery, bulk_income_statement_growth, bulk_income_statements}, + error::DecodeErrorKind, + query::{FiscalPeriod, Year}, }; -const INCOME: &[u8] = include_bytes!("fixtures/bulk_income_statements.json"); -const GROWTH: &[u8] = include_bytes!("fixtures/bulk_income_statement_growth.json"); +use bulk_csv::{ + assert_cell_failure, assert_empty_bodies, assert_required_members, assert_round_trip, decode, + first_row, with_cells, +}; -#[test] -fn exact_outer_md_fixtures_round_trip_with_exact_unique_key_sets() { - assert_exact::(INCOME, 39); - assert_exact::(GROWTH, 34); +const INCOME: &[u8] = include_bytes!("fixtures/bulk_income_statements.csv"); +const GROWTH: &[u8] = include_bytes!("fixtures/bulk_income_statement_growth.csv"); + +fn query() -> BulkStatementQuery { + BulkStatementQuery::new(Year(2024), FiscalPeriod::FullYear) } #[test] -fn every_documented_field_is_required_non_null_and_unknown_fields_are_tolerated() { - assert_required::(INCOME); - assert_required::(GROWTH); +fn live_csv_fixtures_round_trip_every_cell_with_exact_field_counts() { + assert_round_trip(&bulk_income_statements(query()), INCOME, 39); + assert_round_trip(&bulk_income_statement_growth(query()), GROWTH, 34); } #[test] -fn every_documented_metric_preserves_numeric_text_and_rejects_json_numbers() { - assert_all_numbers_rejected::( +fn every_column_is_required_and_only_identity_members_reject_an_empty_cell() { + assert_required_members( + &bulk_income_statements(query()), INCOME, &[ "date", @@ -36,59 +43,51 @@ fn every_documented_metric_preserves_numeric_text_and_rejects_json_numbers() { "period", ], ); - assert_all_numbers_rejected::( + assert_required_members( + &bulk_income_statement_growth(query()), GROWTH, &["symbol", "date", "fiscalYear", "period", "reportedCurrency"], ); +} - let income = rows::(INCOME).remove(0); - assert_eq!(income.revenue.as_str(), "33644000000"); - assert_eq!(income.cost_of_revenue.as_str(), "0"); - assert_eq!( - income.total_other_income_expenses_net.as_str(), - "-7392000000" - ); - assert_eq!(income.eps.as_str(), "0.62"); - assert_eq!(income.weighted_average_shs_out.as_str(), "22735483871"); - - let growth = rows::(GROWTH).remove(0); - assert_eq!(growth.growth_ebit.as_str(), "1"); - assert_eq!(growth.growth_cost_of_revenue.as_str(), "0"); - assert_eq!(growth.growth_other_expenses.as_str(), "-0.9860376183912135"); - assert_eq!( - growth.growth_operating_income.as_str(), - "-0.018874787810201278" - ); +#[test] +fn empty_and_header_only_bodies_decode_to_no_rows() { + assert_empty_bodies(&bulk_income_statements(query()), INCOME); + assert_empty_bodies(&bulk_income_statement_growth(query()), GROWTH); } #[test] -fn identity_fields_are_narrow_and_preserve_documented_representations() { - let income = rows::(INCOME).remove(0); +fn identity_fields_are_narrow_and_preserve_provider_representations() { + let income = decode(&bulk_income_statements(query()), INCOME) + .unwrap() + .remove(0); assert_eq!(income.symbol.as_str(), "000001.SZ"); assert_eq!(income.reported_currency.as_str(), "CNY"); assert_eq!(income.cik.as_str(), "0000000000"); - assert_eq!(income.date.to_string(), "2025-03-31"); - assert_eq!(income.filing_date.to_string(), "2025-03-31"); - assert_eq!(income.accepted_date.to_string(), "2025-03-31 00:00:00"); - assert_eq!(income.fiscal_year.as_str(), "2025"); - assert_eq!(income.period, FiscalPeriod::Q1); - - let mut invalid_date = source_row(INCOME); - invalid_date.insert("date".into(), json!("2025-03-31 00:00:00")); - assert!(serde_json::from_value::(Value::Object(invalid_date)).is_err()); - let mut invalid_datetime = source_row(INCOME); - invalid_datetime.insert("acceptedDate".into(), json!("2025-03-31")); - assert!( - serde_json::from_value::(Value::Object(invalid_datetime)).is_err() - ); - let mut numeric_cik = source_row(INCOME); - numeric_cik.insert("cik".into(), json!(0)); - assert!(serde_json::from_value::(Value::Object(numeric_cik)).is_err()); + assert_eq!(income.date.to_string(), "2024-12-31"); + assert_eq!(income.accepted_date.to_string(), "2024-12-31 00:00:00"); + assert_eq!(income.fiscal_year.as_str(), "2024"); + assert_eq!(income.period, FiscalPeriod::FullYear); + assert_eq!(income.revenue.unwrap().as_str(), "251641000000"); + + for (member, cell) in [ + ("date", "2024-12-31 00:00:00"), + ("acceptedDate", "2024-12-31"), + ] { + assert_cell_failure( + decode( + &bulk_income_statements(query()), + with_cells(INCOME, &[(member, cell)]), + ), + member, + DecodeErrorKind::InvalidValue, + ); + } } #[test] -fn acronym_hazards_keep_exact_documented_wire_names() { - let growth = serialized_row::(GROWTH); +fn acronym_hazards_keep_exact_provider_wire_names() { + let growth = first_row(&bulk_income_statement_growth(query()), GROWTH); for key in [ "growthEBITDA", "growthEPS", @@ -108,94 +107,12 @@ fn acronym_hazards_keep_exact_documented_wire_names() { } #[test] -fn both_contracts_require_bare_array_roots_and_accept_empty_arrays() { - assert_array_contract::(); - assert_array_contract::(); -} - -fn rows(fixture: &[u8]) -> Vec { - serde_json::from_slice(fixture).unwrap() -} - -fn source_row(fixture: &[u8]) -> Map { - serde_json::from_slice::(fixture).unwrap()[0] - .as_object() +fn live_empty_cells_decode_as_absent_metrics() { + let income = decode(&bulk_income_statements(query()), INCOME) .unwrap() - .clone() -} - -fn serialized_row(fixture: &[u8]) -> Map -where - T: DeserializeOwned + Serialize, -{ - serde_json::to_value(rows::(fixture)).unwrap()[0] - .as_object() - .unwrap() - .clone() -} - -fn assert_exact(fixture: &[u8], fields: usize) -where - T: DeserializeOwned + Serialize, -{ - let source: Value = serde_json::from_slice(fixture).unwrap(); - let row = source[0].as_object().unwrap(); - assert_eq!(row.len(), fields); - assert_eq!( - row.keys().collect::>().len(), - fields - ); - assert_eq!(serde_json::to_value(rows::(fixture)).unwrap(), source); -} - -fn assert_required(fixture: &[u8]) -where - T: DeserializeOwned, -{ - let row = source_row(fixture); - for field in row.keys() { - let mut missing = row.clone(); - missing.remove(field); - assert!( - serde_json::from_value::(Value::Object(missing)).is_err(), - "missing {field} unexpectedly decoded" - ); - - let mut null = row.clone(); - null.insert(field.clone(), Value::Null); - assert!( - serde_json::from_value::(Value::Object(null)).is_err(), - "null {field} unexpectedly decoded" - ); - } - - let mut future = row; - future.insert("futureField".into(), json!({"nested": true})); - assert!(serde_json::from_value::(Value::Object(future)).is_ok()); -} - -fn assert_all_numbers_rejected(fixture: &[u8], non_numeric: &[&str]) -where - T: DeserializeOwned, -{ - let row = source_row(fixture); - for field in row - .keys() - .filter(|field| !non_numeric.contains(&field.as_str())) - { - let mut numeric = row.clone(); - numeric.insert(field.clone(), json!(123.5)); - assert!( - serde_json::from_value::(Value::Object(numeric)).is_err(), - "JSON number unexpectedly decoded for {field}" - ); - } -} - -fn assert_array_contract() -where - T: DeserializeOwned, -{ - assert!(serde_json::from_slice::>(b"[]").unwrap().is_empty()); - assert!(serde_json::from_slice::>(b"{}").is_err()); + .remove(2); + assert_eq!(income.symbol.as_str(), "OASMY"); + assert_eq!(income.eps, None); + assert_eq!(income.weighted_average_shs_out_dil, None); + assert_eq!(income.net_income.unwrap().as_str(), "-39754000"); } diff --git a/crates/libfmp/tests/bulk_metrics_endpoints.rs b/crates/libfmp/tests/bulk_metrics_endpoints.rs index ec462dd6..4a089206 100644 --- a/crates/libfmp/tests/bulk_metrics_endpoints.rs +++ b/crates/libfmp/tests/bulk_metrics_endpoints.rs @@ -21,19 +21,23 @@ use libfmp::{ transport::{HttpMethod, TransportResponse}, }; -use support::{FixtureExecutor, json_fixture}; +use support::{FixtureExecutor, fixture_response, json_fixture}; -const KEY_METRICS: &[u8] = include_bytes!("fixtures/bulk_key_metrics_ttm.json"); -const RATIOS: &[u8] = include_bytes!("fixtures/bulk_financial_ratios_ttm.json"); -const PEERS: &[u8] = include_bytes!("fixtures/bulk_stock_peers.json"); -const SURPRISES: &[u8] = include_bytes!("fixtures/bulk_earnings_surprises.json"); +const KEY_METRICS: &[u8] = include_bytes!("fixtures/bulk_key_metrics_ttm.csv"); +const RATIOS: &[u8] = include_bytes!("fixtures/bulk_financial_ratios_ttm.csv"); +const PEERS: &[u8] = include_bytes!("fixtures/bulk_stock_peers.csv"); +const SURPRISES: &[u8] = include_bytes!("fixtures/bulk_earnings_surprises.csv"); + +fn csv_fixture(body: &'static [u8]) -> TransportResponse { + fixture_response(Some("text/csv"), body) +} fn fixtures() -> [TransportResponse; 4] { [ - json_fixture(KEY_METRICS), - json_fixture(RATIOS), - json_fixture(PEERS), - json_fixture(SURPRISES), + csv_fixture(KEY_METRICS), + csv_fixture(RATIOS), + csv_fixture(PEERS), + csv_fixture(SURPRISES), ] } @@ -106,13 +110,19 @@ async fn custom_proxy_sends_one_exact_request_per_method_with_auth_headers_and_d let peers = client.bulk_stock_peers().await.unwrap(); let surprises = client.bulk_earnings_surprises(Year(2026)).await.unwrap(); - assert_eq!(metrics[0].ev_to_ebitda_ttm.as_str(), "-14.656106051669223"); assert_eq!( - ratios[0].net_income_per_ebt_ttm.as_str(), - "0.8225101702576465" + metrics[0].ev_to_ebitda_ttm.as_ref().unwrap().as_str(), + "29.23198788110799" + ); + assert_eq!( + ratios[0].net_income_per_ebt_ttm.as_ref().unwrap().as_str(), + "0.83539656299258" + ); + assert_eq!(peers[1].symbol.as_str(), "000002.SZ"); + assert_eq!( + surprises[1].eps_actual.as_ref().unwrap().as_str(), + "-0.11675" ); - assert_eq!(peers[0].peers, "600036.SS"); - assert_eq!(surprises[0].eps_actual.as_str(), "0.3631"); let requests = executor.requests(); assert_eq!(requests.len(), 4); @@ -185,11 +195,11 @@ async fn direct_fmp_header_and_query_auth_preserve_paths_and_exact_query_shape() } #[tokio::test] -async fn empty_arrays_decode_and_malformed_roots_keep_all_four_endpoint_identities() { +async fn empty_bodies_decode_and_json_bodies_keep_all_four_endpoint_identities() { let executor = Arc::new(FixtureExecutor::new( - std::iter::repeat_with(|| json_fixture(b"[]")) + std::iter::repeat_with(|| csv_fixture(b"")) .take(4) - .chain(std::iter::repeat_with(|| json_fixture(b"{}")).take(4)), + .chain(std::iter::repeat_with(|| json_fixture(b"[]")).take(4)), )); let client = Client::builder() .authentication(Authentication::fmp_header("secret")) diff --git a/crates/libfmp/tests/bulk_metrics_responses.rs b/crates/libfmp/tests/bulk_metrics_responses.rs index 58f4927c..da38da0a 100644 --- a/crates/libfmp/tests/bulk_metrics_responses.rs +++ b/crates/libfmp/tests/bulk_metrics_responses.rs @@ -1,73 +1,81 @@ -use serde::{Serialize, de::DeserializeOwned}; -use serde_json::{Map, Value, json}; +#[path = "support/bulk_csv.rs"] +mod bulk_csv; +#[allow(dead_code)] // The CSV helpers use only the fixture executor. +mod support; + +use serde_json::{Map, Value}; + +use libfmp::{ + endpoints::bulk::{ + bulk_earnings_surprises, bulk_financial_ratios_ttm, bulk_key_metrics_ttm, bulk_stock_peers, + }, + error::DecodeErrorKind, + query::Year, +}; -use libfmp::responses::bulk::{ - BulkEarningsSurprise, BulkFinancialRatiosTtm, BulkKeyMetricsTtm, BulkStockPeer, +use bulk_csv::{ + assert_empty_bodies, assert_required_members, assert_round_trip, decode, with_cells, }; -const KEY_METRICS: &[u8] = include_bytes!("fixtures/bulk_key_metrics_ttm.json"); -const RATIOS: &[u8] = include_bytes!("fixtures/bulk_financial_ratios_ttm.json"); -const PEERS: &[u8] = include_bytes!("fixtures/bulk_stock_peers.json"); -const SURPRISES: &[u8] = include_bytes!("fixtures/bulk_earnings_surprises.json"); +const KEY_METRICS: &[u8] = include_bytes!("fixtures/bulk_key_metrics_ttm.csv"); +const RATIOS: &[u8] = include_bytes!("fixtures/bulk_financial_ratios_ttm.csv"); +const PEERS: &[u8] = include_bytes!("fixtures/bulk_stock_peers.csv"); +const SURPRISES: &[u8] = include_bytes!("fixtures/bulk_earnings_surprises.csv"); #[test] -fn exact_outer_md_fixtures_round_trip_with_exact_unique_key_sets() { - assert_exact::(KEY_METRICS, 43); - assert_exact::(RATIOS, 60); - assert_exact::(PEERS, 2); - assert_exact::(SURPRISES, 5); +fn live_csv_fixtures_round_trip_every_cell_with_exact_field_counts() { + assert_round_trip(&bulk_key_metrics_ttm(), KEY_METRICS, 43); + assert_round_trip(&bulk_financial_ratios_ttm(), RATIOS, 60); + assert_round_trip(&bulk_stock_peers(), PEERS, 2); + assert_round_trip(&bulk_earnings_surprises(Year(2024).into()), SURPRISES, 5); } #[test] -fn every_documented_field_is_required_non_null_and_unknown_fields_are_tolerated() { - assert_required::(KEY_METRICS); - assert_required::(RATIOS); - assert_required::(PEERS); - assert_required::(SURPRISES); -} - -#[test] -fn every_documented_numeric_string_is_lexical_and_rejects_json_numbers() { - assert_all_numbers_rejected::(KEY_METRICS, &["symbol"]); - assert_all_numbers_rejected::(RATIOS, &["symbol"]); - assert_all_numbers_rejected::( +fn every_column_is_required_and_only_identity_members_reject_an_empty_cell() { + assert_required_members(&bulk_key_metrics_ttm(), KEY_METRICS, &["symbol"]); + assert_required_members(&bulk_financial_ratios_ttm(), RATIOS, &["symbol"]); + assert_required_members(&bulk_stock_peers(), PEERS, &["symbol"]); + assert_required_members( + &bulk_earnings_surprises(Year(2024).into()), SURPRISES, &["symbol", "date", "lastUpdated"], ); +} - let metrics = rows::(KEY_METRICS).remove(0); - assert_eq!(metrics.market_cap.as_str(), "249171756000"); - assert_eq!(metrics.enterprise_value_ttm.as_str(), "-496959244000"); - assert_eq!(metrics.current_ratio_ttm.as_str(), "0"); - assert_eq!( - metrics.free_cash_flow_to_firm_ttm.as_str(), - "-35237570137.11014" - ); - let ratios = rows::(RATIOS).remove(0); - assert_eq!(ratios.enterprise_value_ttm.as_str(), "-496959244000"); - assert_eq!(ratios.receivables_turnover_ttm.as_str(), "0"); - assert_eq!( - ratios.gross_profit_margin_ttm.as_str(), - "1.1622776732779352" - ); +#[test] +fn empty_and_header_only_bodies_decode_to_no_rows() { + assert_empty_bodies(&bulk_key_metrics_ttm(), KEY_METRICS); + assert_empty_bodies(&bulk_financial_ratios_ttm(), RATIOS); + assert_empty_bodies(&bulk_stock_peers(), PEERS); + assert_empty_bodies(&bulk_earnings_surprises(Year(2024).into()), SURPRISES); +} +#[test] +fn numeric_cells_keep_their_exact_text_beyond_u64_and_in_exponent_form() { let beyond_u64 = "18446744073709551616"; let high_precision = "-12345678901234567890123456789.123456789012345678901234567890"; - let mut huge = source_row(KEY_METRICS); - huge.insert("marketCap".into(), json!(beyond_u64)); - huge.insert("freeCashFlowToFirmTTM".into(), json!(high_precision)); - let decoded: BulkKeyMetricsTtm = serde_json::from_value(Value::Object(huge)).unwrap(); - assert_eq!(decoded.market_cap.as_str(), beyond_u64); - assert_eq!(decoded.free_cash_flow_to_firm_ttm.as_str(), high_precision); - let encoded = serde_json::to_value(&decoded).unwrap(); - assert_eq!(encoded["marketCap"], beyond_u64); - assert_eq!(encoded["freeCashFlowToFirmTTM"], high_precision); + let body = with_cells( + KEY_METRICS, + &[ + ("marketCap", beyond_u64), + ("freeCashFlowToFirmTTM", high_precision), + ("evToSalesTTM", "6.9148336e-9"), + ], + ); + let decoded = decode(&bulk_key_metrics_ttm(), body).unwrap().remove(0); + + assert_eq!(decoded.market_cap.unwrap().as_str(), beyond_u64); + assert_eq!( + decoded.free_cash_flow_to_firm_ttm.unwrap().as_str(), + high_precision + ); + assert_eq!(decoded.ev_to_sales_ttm.unwrap().as_str(), "6.9148336e-9"); } #[test] fn all_ttm_keys_and_provider_hazards_keep_exact_wire_spelling() { - let metrics = serialized_row::(KEY_METRICS); - let ratios = serialized_row::(RATIOS); + let metrics = first_row(&decode(&bulk_key_metrics_ttm(), KEY_METRICS).unwrap()); + let ratios = first_row(&decode(&bulk_financial_ratios_ttm(), RATIOS).unwrap()); assert_eq!( metrics.keys().filter(|key| key.ends_with("TTM")).count(), @@ -76,7 +84,6 @@ fn all_ttm_keys_and_provider_hazards_keep_exact_wire_spelling() { assert_eq!(ratios.keys().filter(|key| key.ends_with("TTM")).count(), 59); assert!(metrics.keys().all(|key| !key.contains("Ttm"))); assert!(ratios.keys().all(|key| !key.contains("Ttm"))); - for key in [ "evToEBITDATTM", "netDebtToEBITDATTM", @@ -86,119 +93,57 @@ fn all_ttm_keys_and_provider_hazards_keep_exact_wire_spelling() { } assert!(ratios.contains_key("netIncomePerEBTTTM")); assert!(!metrics.contains_key("researchAndDevelopmentToRevenueTTM")); - assert!(!ratios.contains_key("priceToEarningsRatioDilutedTTM")); - assert!(!ratios.contains_key("priceToEarningsGrowthRatioDilutedTTM")); + assert!(!ratios.contains_key("priceToEarningsDilutedRatioTTM")); + assert!(!ratios.contains_key("priceToEarningsDilutedGrowthRatioTTM")); } #[test] -fn peers_remain_one_scalar_string_and_earnings_dates_are_typed() { - let peers = rows::(PEERS).remove(0); +fn peers_remain_one_quoted_string_and_earnings_dates_are_typed() { + let peers = decode(&bulk_stock_peers(), PEERS).unwrap().remove(0); assert_eq!(peers.symbol.as_str(), "000001.SZ"); - assert_eq!(peers.peers, "600036.SS"); - let mut peer_row = source_row(PEERS); - peer_row.insert("peers".into(), json!(["600036.SS"])); - assert!(serde_json::from_value::(Value::Object(peer_row)).is_err()); - - let surprise = rows::(SURPRISES).remove(0); - assert_eq!(surprise.symbol.as_str(), "AMKYF"); - assert_eq!(surprise.date.to_string(), "2025-07-09"); - assert_eq!(surprise.last_updated.to_string(), "2025-07-09"); - let mut surprise_row = source_row(SURPRISES); - surprise_row.insert("date".into(), json!("2025-07-09T00:00:00")); - assert!(serde_json::from_value::(Value::Object(surprise_row)).is_err()); -} + assert_eq!( + peers.peers, + "3698.HK,600000.SS,600015.SS,600016.SS,600036.SS,601166.SS,601658.SS" + ); -#[test] -fn all_four_contracts_require_bare_array_roots_and_accept_empty_arrays() { - assert_array_contract::(); - assert_array_contract::(); - assert_array_contract::(); - assert_array_contract::(); + let surprises = decode(&bulk_earnings_surprises(Year(2024).into()), SURPRISES).unwrap(); + assert_eq!(surprises[0].symbol.as_str(), "AUTO.OL"); + assert_eq!(surprises[0].date.to_string(), "2024-12-31"); + assert_eq!(surprises[0].last_updated.to_string(), "2025-10-07"); + let timestamped = with_cells(SURPRISES, &[("date", "2024-12-31T00:00:00")]); + let error = decode(&bulk_earnings_surprises(Year(2024).into()), timestamped).unwrap_err(); + assert_eq!(error.decode_path(), Some("[0].date")); + assert_eq!(error.decode_kind(), Some(DecodeErrorKind::InvalidValue)); } -fn rows(fixture: &[u8]) -> Vec { - serde_json::from_slice(fixture).unwrap() +fn first_row(rows: &[R]) -> Map { + match serde_json::to_value(&rows[0]).unwrap() { + Value::Object(members) => members, + other => panic!("a row re-encodes as an object, not {other}"), + } } -fn source_row(fixture: &[u8]) -> Map { - serde_json::from_slice::(fixture).unwrap()[0] - .as_object() +#[test] +fn live_empty_cells_decode_as_absent_metrics_and_empty_text() { + let metrics = decode(&bulk_key_metrics_ttm(), KEY_METRICS) .unwrap() - .clone() -} + .remove(2); + assert_eq!(metrics.symbol.as_str(), "ADAMO"); + assert_eq!(metrics.enterprise_value_ttm, None); + assert_eq!(metrics.market_cap.unwrap().as_str(), "711942736"); -fn serialized_row(fixture: &[u8]) -> Map -where - T: DeserializeOwned + Serialize, -{ - serde_json::to_value(rows::(fixture)).unwrap()[0] - .as_object() + let ratios = decode(&bulk_financial_ratios_ttm(), RATIOS) .unwrap() - .clone() -} - -fn assert_exact(fixture: &[u8], fields: usize) -where - T: DeserializeOwned + Serialize, -{ - let source: Value = serde_json::from_slice(fixture).unwrap(); - let row = source[0].as_object().unwrap(); - assert_eq!(row.len(), fields); - assert_eq!( - row.keys().collect::>().len(), - fields - ); - assert_eq!(serde_json::to_value(rows::(fixture)).unwrap(), source); -} + .remove(2); + assert_eq!(ratios.price_to_earnings_ratio_ttm, None); + assert_eq!(ratios.gross_profit_margin_ttm.unwrap().as_str(), "0"); -fn assert_required(fixture: &[u8]) -where - T: DeserializeOwned, -{ - let row = source_row(fixture); - for field in row.keys() { - let mut missing = row.clone(); - missing.remove(field); - assert!( - serde_json::from_value::(Value::Object(missing)).is_err(), - "missing {field} unexpectedly decoded" - ); - - let mut null = row.clone(); - null.insert(field.clone(), Value::Null); - assert!( - serde_json::from_value::(Value::Object(null)).is_err(), - "null {field} unexpectedly decoded" - ); - } - - let mut future = row; - future.insert("futureField".into(), json!({"nested": true})); - assert!(serde_json::from_value::(Value::Object(future)).is_ok()); -} - -fn assert_all_numbers_rejected(fixture: &[u8], non_numeric: &[&str]) -where - T: DeserializeOwned, -{ - let row = source_row(fixture); - for field in row - .keys() - .filter(|field| !non_numeric.contains(&field.as_str())) - { - let mut numeric = row.clone(); - numeric.insert(field.clone(), json!(123.5)); - assert!( - serde_json::from_value::(Value::Object(numeric)).is_err(), - "JSON number unexpectedly decoded for {field}" - ); - } -} + let surprise = decode(&bulk_earnings_surprises(Year(2024).into()), SURPRISES) + .unwrap() + .remove(2); + assert_eq!(surprise.eps_estimated, None); + assert_eq!(surprise.eps_actual.unwrap().as_str(), "0.00084"); -fn assert_array_contract() -where - T: DeserializeOwned, -{ - assert!(serde_json::from_slice::>(b"[]").unwrap().is_empty()); - assert!(serde_json::from_slice::>(b"{}").is_err()); + let peers = decode(&bulk_stock_peers(), PEERS).unwrap().remove(2); + assert_eq!(peers.peers, ""); } diff --git a/crates/libfmp/tests/bulk_snapshot_endpoints.rs b/crates/libfmp/tests/bulk_snapshot_endpoints.rs index 0cc61c02..58eaed58 100644 --- a/crates/libfmp/tests/bulk_snapshot_endpoints.rs +++ b/crates/libfmp/tests/bulk_snapshot_endpoints.rs @@ -26,25 +26,29 @@ use libfmp::{ types::{BulkPart, StringValueError}, }; -use support::{FixtureExecutor, json_fixture}; +use support::{FixtureExecutor, fixture_response, json_fixture}; -const PROFILE: &[u8] = include_bytes!("fixtures/bulk_company_profiles.json"); -const RATING: &[u8] = include_bytes!("fixtures/bulk_stock_ratings.json"); -const DCF: &[u8] = include_bytes!("fixtures/bulk_dcf_valuations.json"); -const SCORES: &[u8] = include_bytes!("fixtures/bulk_financial_scores.json"); -const TARGET: &[u8] = include_bytes!("fixtures/bulk_price_target_summaries.json"); -const ETF: &[u8] = include_bytes!("fixtures/bulk_etf_holdings.json"); -const CONSENSUS: &[u8] = include_bytes!("fixtures/bulk_upgrades_downgrades_consensus.json"); +const PROFILE: &[u8] = include_bytes!("fixtures/bulk_company_profiles.csv"); +const RATING: &[u8] = include_bytes!("fixtures/bulk_stock_ratings.csv"); +const DCF: &[u8] = include_bytes!("fixtures/bulk_dcf_valuations.csv"); +const SCORES: &[u8] = include_bytes!("fixtures/bulk_financial_scores.csv"); +const TARGET: &[u8] = include_bytes!("fixtures/bulk_price_target_summaries.csv"); +const ETF: &[u8] = include_bytes!("fixtures/bulk_etf_holdings.csv"); +const CONSENSUS: &[u8] = include_bytes!("fixtures/bulk_upgrades_downgrades_consensus.csv"); + +fn csv_fixture(body: &'static [u8]) -> TransportResponse { + fixture_response(Some("text/csv"), body) +} fn fixtures() -> [TransportResponse; 7] { [ - json_fixture(PROFILE), - json_fixture(RATING), - json_fixture(DCF), - json_fixture(SCORES), - json_fixture(TARGET), - json_fixture(ETF), - json_fixture(CONSENSUS), + csv_fixture(PROFILE), + csv_fixture(RATING), + csv_fixture(DCF), + csv_fixture(SCORES), + csv_fixture(TARGET), + csv_fixture(ETF), + csv_fixture(CONSENSUS), ] } @@ -176,13 +180,16 @@ async fn custom_proxy_sends_one_exact_request_per_method_with_auth_headers_and_f .unwrap(); let consensus = client.bulk_upgrades_downgrades_consensus().await.unwrap(); - assert_eq!(profile[0].symbol.as_str(), "AAPL"); - assert_eq!(rating[0].rating, "B+"); - assert_eq!(dcf[0].stock_price.as_str(), "6.54"); - assert_eq!(scores[0].reported_currency.as_str(), "CNY"); - assert_eq!(target[0].publishers, "[\"\"TheFly\""); - assert_eq!(etf[0].last_updated_raw, "2024-09-06\""); - assert_eq!(consensus[0].symbol, ""); + assert_eq!(profile[1].symbol.as_str(), "AMAT"); + assert_eq!(rating[1].rating, "C+"); + assert_eq!(dcf[1].stock_price.as_ref().unwrap().as_str(), "2.39"); + assert_eq!( + scores[0].reported_currency.as_ref().unwrap().as_str(), + "CNY" + ); + assert_eq!(target[1].symbol.as_str(), "AA"); + assert_eq!(etf[1].asset.as_ref().unwrap().as_str(), "3665.TW"); + assert_eq!(consensus[1].consensus, "Hold"); let requests = executor.requests(); assert_eq!(requests.len(), 7); @@ -281,11 +288,11 @@ async fn direct_fmp_header_and_query_auth_preserve_paths_and_exact_query_shape() } #[tokio::test] -async fn empty_arrays_decode_and_malformed_roots_keep_all_endpoint_identities() { +async fn empty_bodies_decode_and_json_bodies_keep_all_endpoint_identities() { let executor = Arc::new(FixtureExecutor::new( - std::iter::repeat_with(|| json_fixture(b"[]")) + std::iter::repeat_with(|| csv_fixture(b"")) .take(7) - .chain(std::iter::repeat_with(|| json_fixture(b"{}")).take(7)), + .chain(std::iter::repeat_with(|| json_fixture(b"[]")).take(7)), )); let client = Client::builder() .authentication(Authentication::fmp_header("secret")) diff --git a/crates/libfmp/tests/bulk_snapshot_responses.rs b/crates/libfmp/tests/bulk_snapshot_responses.rs index 6ed0be56..bb483028 100644 --- a/crates/libfmp/tests/bulk_snapshot_responses.rs +++ b/crates/libfmp/tests/bulk_snapshot_responses.rs @@ -1,166 +1,152 @@ -use serde::{Serialize, de::DeserializeOwned}; -use serde_json::{Map, Value, json}; +#[path = "support/bulk_csv.rs"] +mod bulk_csv; +#[allow(dead_code)] // The CSV helpers use only the fixture executor. +mod support; use libfmp::{ - responses::{ - bulk::{ - BulkDcfValuation, BulkEtfHolding, BulkFinancialScore, BulkPriceTargetSummary, - BulkStockRating, BulkUpgradesDowngradesConsensus, - }, - company::CompanyProfile, + endpoints::bulk::{ + BulkPartQuery, bulk_company_profiles, bulk_dcf_valuations, bulk_etf_holdings, + bulk_financial_scores, bulk_price_target_summaries, bulk_stock_ratings, + bulk_upgrades_downgrades_consensus, }, - types::Ticker, + types::{BulkPart, Date, Ticker}, }; -const PROFILE: &[u8] = include_bytes!("fixtures/bulk_company_profiles.json"); -const RATING: &[u8] = include_bytes!("fixtures/bulk_stock_ratings.json"); -const DCF: &[u8] = include_bytes!("fixtures/bulk_dcf_valuations.json"); -const SCORES: &[u8] = include_bytes!("fixtures/bulk_financial_scores.json"); -const TARGET: &[u8] = include_bytes!("fixtures/bulk_price_target_summaries.json"); -const ETF: &[u8] = include_bytes!("fixtures/bulk_etf_holdings.json"); -const CONSENSUS: &[u8] = include_bytes!("fixtures/bulk_upgrades_downgrades_consensus.json"); +use bulk_csv::{ + assert_empty_bodies, assert_members, assert_required_members, assert_round_trip, decode, +}; -#[test] -fn exact_outer_md_fixtures_round_trip_with_exact_field_counts() { - assert_exact::(PROFILE, 36); - assert_exact::(RATING, 9); - assert_exact::(DCF, 4); - assert_exact::(SCORES, 11); - assert_exact::(TARGET, 10); - assert_exact::(ETF, 9); - assert_exact::(CONSENSUS, 7); -} +const PROFILE: &[u8] = include_bytes!("fixtures/bulk_company_profiles.csv"); +const RATING: &[u8] = include_bytes!("fixtures/bulk_stock_ratings.csv"); +const DCF: &[u8] = include_bytes!("fixtures/bulk_dcf_valuations.csv"); +const SCORES: &[u8] = include_bytes!("fixtures/bulk_financial_scores.csv"); +const TARGET: &[u8] = include_bytes!("fixtures/bulk_price_target_summaries.csv"); +const ETF: &[u8] = include_bytes!("fixtures/bulk_etf_holdings.csv"); +const CONSENSUS: &[u8] = include_bytes!("fixtures/bulk_upgrades_downgrades_consensus.csv"); -#[test] -fn every_documented_field_is_required_non_null_and_unknown_fields_are_tolerated() { - assert_required_nullable::( - PROFILE, - &["cik", "cusip", "fullTimeEmployees", "phone", "ipoDate"], - ); - assert_required::(RATING); - assert_required::(DCF); - assert_required::(SCORES); - assert_required::(TARGET); - assert_required::(ETF); - assert_required::(CONSENSUS); +fn part() -> BulkPartQuery { + BulkPart::new("0").unwrap().into() } #[test] -fn numeric_string_fields_reject_json_numbers_in_every_new_contract() { - assert_number_rejected::(RATING, "discountedCashFlowScore"); - assert_number_rejected::(DCF, "dcf"); - assert_number_rejected::(DCF, "Stock Price"); - assert_number_rejected::(SCORES, "altmanZScore"); - assert_number_rejected::(TARGET, "allTimeAvgPriceTarget"); - assert_number_rejected::(ETF, "marketValue"); - assert_number_rejected::(CONSENSUS, "strongBuy"); +fn live_csv_fixtures_round_trip_every_cell_with_exact_field_counts() { + assert_round_trip(&bulk_company_profiles(part()), PROFILE, 36); + assert_round_trip(&bulk_stock_ratings(), RATING, 9); + assert_round_trip(&bulk_dcf_valuations(), DCF, 4); + assert_round_trip(&bulk_financial_scores(), SCORES, 11); + assert_round_trip(&bulk_price_target_summaries(), TARGET, 10); + assert_round_trip(&bulk_etf_holdings(part()), ETF, 9); + assert_round_trip(&bulk_upgrades_downgrades_consensus(), CONSENSUS, 7); } #[test] -fn malformed_documented_strings_and_wire_keys_are_preserved_verbatim() { - let target = rows::(TARGET).remove(0); - assert_eq!(target.publishers, "[\"\"TheFly\""); - - let etf = rows::(ETF).remove(0); - assert_eq!(etf.cusip, ""); - assert_eq!(etf.last_updated_raw, "2024-09-06\""); - let etf_json = serde_json::to_value(etf).unwrap(); - assert_eq!(etf_json["lastUpdated\""], "2024-09-06\""); - assert!(etf_json.get("lastUpdated").is_none()); - - let consensus = rows::(CONSENSUS).remove(0); - assert_eq!(consensus.symbol, ""); +fn every_column_is_required_and_only_identity_members_reject_an_empty_cell() { + assert_members( + &bulk_company_profiles(part()), + PROFILE, + &[ + "range", + "change", + "changePercentage", + "companyName", + "cik", + "isin", + "cusip", + "exchangeFullName", + "website", + "description", + "ceo", + "country", + "fullTimeEmployees", + "phone", + "address", + "city", + "state", + "zip", + "image", + "ipoDate", + ], + ); + assert_required_members(&bulk_stock_ratings(), RATING, &["symbol", "date"]); + assert_required_members(&bulk_dcf_valuations(), DCF, &["symbol", "date"]); + assert_required_members(&bulk_financial_scores(), SCORES, &["symbol"]); + assert_required_members(&bulk_price_target_summaries(), TARGET, &["symbol"]); + assert_required_members(&bulk_etf_holdings(part()), ETF, &["symbol", "lastUpdated"]); + assert_required_members(&bulk_upgrades_downgrades_consensus(), CONSENSUS, &[]); } #[test] -fn company_profile_is_the_existing_typed_contract_and_new_symbols_are_typed_where_proven() { - let profile = rows::(PROFILE).remove(0); - assert_eq!(profile.symbol, Ticker::new("AAPL").unwrap()); - - let rating = rows::(RATING).remove(0); - assert_eq!(rating.symbol.as_str(), "000001.SZ"); - let etf = rows::(ETF).remove(0); - assert_eq!(etf.asset.as_str(), "009150.KS"); +fn empty_and_header_only_bodies_decode_to_no_rows() { + assert_empty_bodies(&bulk_company_profiles(part()), PROFILE); + assert_empty_bodies(&bulk_stock_ratings(), RATING); + assert_empty_bodies(&bulk_dcf_valuations(), DCF); + assert_empty_bodies(&bulk_financial_scores(), SCORES); + assert_empty_bodies(&bulk_price_target_summaries(), TARGET); + assert_empty_bodies(&bulk_etf_holdings(part()), ETF); + assert_empty_bodies(&bulk_upgrades_downgrades_consensus(), CONSENSUS); } #[test] -fn all_seven_contracts_require_bare_array_roots_and_accept_empty_arrays() { - assert_array_contract::(); - assert_array_contract::(); - assert_array_contract::(); - assert_array_contract::(); - assert_array_contract::(); - assert_array_contract::(); - assert_array_contract::(); -} +fn provider_spellings_and_quoted_text_are_preserved_verbatim() { + let dcf = decode(&bulk_dcf_valuations(), DCF).unwrap(); + assert_eq!(dcf[0].stock_price.as_ref().unwrap().as_str(), "7.62"); + assert_eq!(dcf[0].dcf.as_ref().unwrap().as_str(), "2.525226853334803"); -fn rows(fixture: &[u8]) -> Vec { - serde_json::from_slice(fixture).unwrap() -} - -fn source_row(fixture: &[u8]) -> Map { - serde_json::from_slice::(fixture).unwrap()[0] - .as_object() + let target = decode(&bulk_price_target_summaries(), TARGET) .unwrap() - .clone() -} + .remove(0); + assert_eq!( + target.publishers, + r#"["StreetInsider","Benzinga","Pulse 2.0"]"# + ); -fn assert_exact(fixture: &[u8], fields: usize) -where - T: DeserializeOwned + Serialize, -{ - let source: Value = serde_json::from_slice(fixture).unwrap(); - assert_eq!(source[0].as_object().unwrap().len(), fields); - assert_eq!(serde_json::to_value(rows::(fixture)).unwrap(), source); -} + let etf = decode(&bulk_etf_holdings(part()), ETF).unwrap().remove(0); + assert_eq!(etf.symbol.as_str(), " -- "); + assert_eq!(etf.asset, Some(Ticker::new("TDW").unwrap())); + assert_eq!(etf.last_updated, Date::parse("2026-09-27").unwrap()); -fn assert_required(fixture: &[u8]) -where - T: DeserializeOwned, -{ - assert_required_nullable::(fixture, &[]); -} - -fn assert_required_nullable(fixture: &[u8], nullable: &[&str]) -where - T: DeserializeOwned, -{ - let row = source_row(fixture); - for field in row.keys() { - let mut missing = row.clone(); - missing.remove(field); - assert!( - serde_json::from_value::(Value::Object(missing)).is_err(), - "missing {field} unexpectedly decoded" - ); - - let mut null = row.clone(); - null.insert(field.clone(), Value::Null); - assert_eq!( - serde_json::from_value::(Value::Object(null)).is_ok(), - nullable.contains(&field.as_str()), - "null {field} decoded contrary to its nullability" - ); - } - - let mut future = row; - future.insert("futureField".into(), json!({"nested": true})); - assert!(serde_json::from_value::(Value::Object(future)).is_ok()); + let profile = decode(&bulk_company_profiles(part()), PROFILE) + .unwrap() + .remove(0); + assert_eq!(profile.symbol, Ticker::new("WMB").unwrap()); + assert_eq!(profile.company_name, "The Williams Companies, Inc."); + assert!(!profile.is_etf); } -fn assert_number_rejected(fixture: &[u8], field: &str) -where - T: DeserializeOwned, -{ - let mut row = source_row(fixture); - row.insert(field.into(), json!(123.5)); - assert!(serde_json::from_value::(Value::Object(row)).is_err()); +#[test] +fn live_empty_cells_decode_as_absent_members_and_empty_text() { + let dcf = decode(&bulk_dcf_valuations(), DCF).unwrap().remove(2); + assert_eq!(dcf.symbol.as_str(), "000023.SZ"); + assert_eq!(dcf.dcf, None); + assert_eq!(dcf.stock_price.unwrap().as_str(), "1.72"); + + let scores = decode(&bulk_financial_scores(), SCORES).unwrap().remove(2); + assert_eq!(scores.symbol.as_str(), "AAAU"); + assert_eq!(scores.reported_currency, None); + assert_eq!(scores.altman_z_score, None); + assert_eq!(scores.piotroski_score.unwrap().as_str(), "2"); + + let etf = decode(&bulk_etf_holdings(part()), ETF).unwrap().remove(2); + assert_eq!(etf.name, "Other/Cash"); + assert_eq!(etf.asset, None); + assert_eq!(etf.cusip, ""); + assert_eq!(etf.isin, None); + assert_eq!(etf.shares_number.unwrap().as_str(), "0"); } -fn assert_array_contract() -where - T: DeserializeOwned, -{ - assert!(serde_json::from_slice::>(b"[]").unwrap().is_empty()); - assert!(serde_json::from_slice::>(b"{}").is_err()); +#[test] +fn live_profile_rows_with_empty_codes_and_changes_decode_as_absent() { + let profiles = decode(&bulk_company_profiles(part()), PROFILE).unwrap(); + let accelevation = &profiles[2]; + assert_eq!(accelevation.symbol, Ticker::new("ACCV").unwrap()); + assert_eq!(accelevation.change, None); + assert_eq!(accelevation.change_percentage, None); + assert_eq!(accelevation.range, ""); + + let catalyst = &profiles[3]; + assert_eq!(catalyst.symbol, Ticker::new("CFHAX").unwrap()); + assert_eq!(catalyst.isin, None); + assert_eq!(catalyst.country, None); + assert_eq!(catalyst.ceo, ""); + assert_eq!(catalyst.website, ""); } diff --git a/crates/libfmp/tests/company_responses.rs b/crates/libfmp/tests/company_responses.rs index 3fc23030..33b09bc4 100644 --- a/crates/libfmp/tests/company_responses.rs +++ b/crates/libfmp/tests/company_responses.rs @@ -13,14 +13,14 @@ fn documented_company_profile_decodes_every_exact_field_and_wire_type() { assert_eq!(row.beta, 1.097); assert_eq!(row.last_dividend, 1.05); assert_eq!(row.range, "201.5-344.57"); - assert_eq!(row.change, -6.33498); - assert_eq!(row.change_percentage, -1.8732); + assert_eq!(row.change, Some(-6.33498)); + assert_eq!(row.change_percentage, Some(-1.8732)); assert_eq!(row.volume, 28_718_014.0); assert_eq!(row.average_volume, 55_309_000.0); assert_eq!(row.company_name, "Apple Inc."); assert_eq!(row.currency.as_str(), "USD"); assert_eq!(row.cik.as_ref().unwrap().as_str(), "0000320193"); - assert_eq!(row.isin.as_str(), "US0378331005"); + assert_eq!(row.isin.as_ref().unwrap().as_str(), "US0378331005"); assert_eq!(row.cusip.as_ref().unwrap().as_str(), "037833100"); assert_eq!(row.exchange_full_name, "NASDAQ Global Select"); assert_eq!(row.exchange.as_str(), "NASDAQ"); @@ -32,7 +32,7 @@ fn documented_company_profile_decodes_every_exact_field_and_wire_type() { ); assert_eq!(row.ceo, "Timothy D. Cook"); assert_eq!(row.sector.as_str(), "Technology"); - assert_eq!(row.country.as_str(), "US"); + assert_eq!(row.country.as_ref().unwrap().as_str(), "US"); assert_eq!(row.full_time_employees.as_ref().unwrap().as_str(), "166000"); assert_eq!(row.phone.as_deref(), Some("(408) 996-1010")); assert_eq!(row.address, "One Apple Park Way"); diff --git a/crates/libfmp/tests/fixtures/bulk_balance_sheet_statement_growth.csv b/crates/libfmp/tests/fixtures/bulk_balance_sheet_statement_growth.csv new file mode 100644 index 00000000..1bebb1ea --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_balance_sheet_statement_growth.csv @@ -0,0 +1,3 @@ +"symbol","date","fiscalYear","period","reportedCurrency","growthCashAndCashEquivalents","growthShortTermInvestments","growthCashAndShortTermInvestments","growthNetReceivables","growthInventory","growthOtherCurrentAssets","growthTotalCurrentAssets","growthPropertyPlantEquipmentNet","growthGoodwill","growthIntangibleAssets","growthGoodwillAndIntangibleAssets","growthLongTermInvestments","growthTaxAssets","growthOtherNonCurrentAssets","growthTotalNonCurrentAssets","growthOtherAssets","growthTotalAssets","growthAccountPayables","growthShortTermDebt","growthTaxPayables","growthDeferredRevenue","growthOtherCurrentLiabilities","growthTotalCurrentLiabilities","growthLongTermDebt","growthDeferredRevenueNonCurrent","growthDeferredTaxLiabilitiesNonCurrent","growthOtherNonCurrentLiabilities","growthTotalNonCurrentLiabilities","growthOtherLiabilities","growthTotalLiabilities","growthPreferredStock","growthCommonStock","growthRetainedEarnings","growthAccumulatedOtherComprehensiveIncomeLoss","growthOthertotalStockholdersEquity","growthTotalStockholdersEquity","growthMinorityInterest","growthTotalEquity","growthTotalLiabilitiesAndStockholdersEquity","growthTotalInvestments","growthTotalDebt","growthNetDebt","growthAccountsReceivables","growthOtherReceivables","growthPrepaids","growthTotalPayables","growthOtherPayables","growthAccruedExpenses","growthCapitalLeaseObligationsCurrent","growthAdditionalPaidInCapital","growthTreasuryStock" +"000001.SZ","2024-12-31","2024","FY","CNY",0.13745663137040692,0.3981363245708905,0.2493622132819799,0,0,0,0.2493622132819799,-0.12238614496472097,0,-0.057988523104802174,-0.027061310782241013,-0.00034695865151514703,0.14362829730970125,-0.07488693367017299,-0.017498660254692634,0,0.032602509058340653,0,0,0.5108742004264393,0,0.055551063599693966,0.05612584115347865,-0.034898040702044546,0,0,-0.10559478857406393,-0.05659307561871587,0,0.031211459790708822,0,0,0.10101466633522406,0,0.0012528839248880808,0.04766602869192595,0,0.04766602869192595,0.032602509058340653,0.04673722173415161,-0.034898040702044546,-0.5927764260610976,0,0,0,0.14174413790507734,0.14174413790507734,0,0,0,0 +"000002.SZ","2024-12-31","2024","FY","CNY",-0.11672629689164585,11.982811811370647,-0.11507622608909761,-0.12132529270967031,-0.2603497824915679,-0.17460445350918893,-0.202343793972393,-0.07410898156342684,0.02434416697326749,-0.24268756803052952,-0.1544327862044751,-0.11435863579178189,0.1190329250633101,0.24337089671762815,0.03992686936474342,0,-0.1452571930861965,-0.27814218557737913,14.018458726430618,-0.02819354338907874,0,-0.11188223593418148,-0.12500034406798766,-0.21206661859377662,-1,3.702333174793378,1.0101077832674181,-0.18487040775435004,0,-0.14022062854026723,0,0,-0.5285236432432463,0,0.011267077683393927,-0.1918703241972824,-0.10490211307319319,-0.1590308675502114,-0.1452571930861965,-0.11311806388110532,-0.15260982701384376,-0.1726718823071758,0.11203340779101978,-0.1276637028305984,-0.040289532846611395,-0.17369740510948883,-0.055450442026140453,0,0,0,0 diff --git a/crates/libfmp/tests/fixtures/bulk_balance_sheet_statement_growth.json b/crates/libfmp/tests/fixtures/bulk_balance_sheet_statement_growth.json deleted file mode 100644 index b9e9cf79..00000000 --- a/crates/libfmp/tests/fixtures/bulk_balance_sheet_statement_growth.json +++ /dev/null @@ -1,60 +0,0 @@ -[ - { - "date": "2025-03-31", - "fiscalYear": "2025", - "growthAccountPayables": "0", - "growthAccountsReceivables": "0", - "growthAccruedExpenses": "0", - "growthAccumulatedOtherComprehensiveIncomeLoss": "0", - "growthAdditionalPaidInCapital": "0", - "growthCapitalLeaseObligationsCurrent": "0", - "growthCashAndCashEquivalents": "0.09574482145872953", - "growthCashAndShortTermInvestments": "0.09574482145872953", - "growthCommonStock": "0", - "growthDeferredRevenue": "0", - "growthDeferredRevenueNonCurrent": "0", - "growthDeferredTaxLiabilitiesNonCurrent": "0", - "growthGoodwill": "0", - "growthGoodwillAndIntangibleAssets": "-0.01477618426770969", - "growthIntangibleAssets": "-0.03270278935556268", - "growthInventory": "0", - "growthLongTermDebt": "0", - "growthLongTermInvestments": "-0.0774117797082201", - "growthMinorityInterest": "0", - "growthNetDebt": "-0.09574482145872953", - "growthNetReceivables": "0", - "growthOtherAssets": "0.001488576544346165", - "growthOtherCurrentAssets": "0", - "growthOtherCurrentLiabilities": "0.12022416350749959", - "growthOtherLiabilities": "-0.0005084911577141635", - "growthOtherNonCurrentAssets": "0.07678934705504345", - "growthOtherNonCurrentLiabilities": "0", - "growthOtherPayables": "-0.12022416350749959", - "growthOtherReceivables": "0", - "growthOthertotalStockholdersEquity": "-0.0035208940994345805", - "growthPreferredStock": "0", - "growthPrepaids": "0", - "growthPropertyPlantEquipmentNet": "-0.06373337231398918", - "growthRetainedEarnings": "0.049325752755485314", - "growthShortTermDebt": "0", - "growthShortTermInvestments": "0", - "growthTaxAssets": "0", - "growthTaxPayables": "-0.0279424216765453", - "growthTotalAssets": "0.001488576544346165", - "growthTotalCurrentAssets": "0.09574482145872953", - "growthTotalCurrentLiabilities": "0", - "growthTotalDebt": "0", - "growthTotalEquity": "0.022774946346510602", - "growthTotalInvestments": "-0.0774117797082201", - "growthTotalLiabilities": "-0.0005084911577141635", - "growthTotalLiabilitiesAndStockholdersEquity": "0.001488576544346165", - "growthTotalNonCurrentAssets": "-0.01112505367669385", - "growthTotalNonCurrentLiabilities": "0", - "growthTotalPayables": "-0.12022416350749959", - "growthTotalStockholdersEquity": "0.022774946346510602", - "growthTreasuryStock": "0", - "period": "Q1", - "reportedCurrency": "CNY", - "symbol": "000001.SZ" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_balance_sheet_statements.csv b/crates/libfmp/tests/fixtures/bulk_balance_sheet_statements.csv new file mode 100644 index 00000000..3326cbce --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_balance_sheet_statements.csv @@ -0,0 +1,3 @@ +"date","symbol","reportedCurrency","cik","filingDate","acceptedDate","fiscalYear","period","cashAndCashEquivalents","shortTermInvestments","cashAndShortTermInvestments","netReceivables","accountsReceivables","otherReceivables","inventory","prepaids","otherCurrentAssets","totalCurrentAssets","propertyPlantEquipmentNet","goodwill","intangibleAssets","goodwillAndIntangibleAssets","longTermInvestments","taxAssets","otherNonCurrentAssets","totalNonCurrentAssets","otherAssets","totalAssets","totalPayables","accountPayables","otherPayables","accruedExpenses","shortTermDebt","capitalLeaseObligationsCurrent","taxPayables","deferredRevenue","otherCurrentLiabilities","totalCurrentLiabilities","longTermDebt","capitalLeaseObligationsNonCurrent","deferredRevenueNonCurrent","deferredTaxLiabilitiesNonCurrent","otherNonCurrentLiabilities","totalNonCurrentLiabilities","otherLiabilities","capitalLeaseObligations","totalLiabilities","treasuryStock","preferredStock","commonStock","retainedEarnings","additionalPaidInCapital","accumulatedOtherComprehensiveIncomeLoss","otherTotalStockholdersEquity","totalStockholdersEquity","totalEquity","minorityInterest","totalLiabilitiesAndTotalEquity","totalInvestments","totalDebt","netDebt" +"2024-12-31","000001.SZ","CNY","0000000000","2024-12-31","2024-12-31 00:00:00","2024","FY",680935000000,629571000000,1310506000000,0,0,0,0,0,0,1310506000000,13682000000,7568000000,6238000000,13806000000,3359465000000,52329000000,1019482000000,4458764000000,0,5769270000000,30335000000,0,30335000000,0,0,0,14172000000,0,4177546000000,4207881000000,756251000000,0,0,0,310296000000,1066547000000,0,0,5274428000000,0,19953000000,19406000000,243605000000,80816000000,0,131062000000,494842000000,494842000000,0,5769270000000,3989036000000,756251000000,75316000000 +"2025-06-30","0002.KL","MYR","0000000000","2025-06-30","2025-06-30 00:00:00","2024","FY",32176000,18511000,50687000,35231000,32651000,2580000,51764000,272000,-230000,137724000,209468000,0,0,0,1261000,0,1261000,210729000,0,348453000,59313000,13277000,46036000,0,0,960000,1911000,0,35854000,60273000,1531000,82000,294000,6179000,0,8086000,0,1042000,68359000,0,0,86276000,193818000,0,0,0,280094000,280094000,0,348453000,18511000,2573000,-29603000 diff --git a/crates/libfmp/tests/fixtures/bulk_balance_sheet_statements.json b/crates/libfmp/tests/fixtures/bulk_balance_sheet_statements.json deleted file mode 100644 index 92df6b8f..00000000 --- a/crates/libfmp/tests/fixtures/bulk_balance_sheet_statements.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "acceptedDate": "2025-03-31 07:00:00", - "accountPayables": "3861497000", - "accountsReceivables": "9666577000", - "accruedExpenses": "0", - "accumulatedOtherComprehensiveIncomeLoss": "0", - "additionalPaidInCapital": "6023340000", - "capitalLeaseObligations": "0", - "capitalLeaseObligationsCurrent": "0", - "capitalLeaseObligationsNonCurrent": "0", - "cashAndCashEquivalents": "1985000", - "cashAndShortTermInvestments": "1985000", - "cik": "0000000000", - "commonStock": "5550277000", - "date": "2025-03-31", - "deferredRevenue": "0", - "deferredRevenueNonCurrent": "0", - "deferredTaxLiabilitiesNonCurrent": "737391000", - "filingDate": "2025-05-31", - "fiscalYear": "2025", - "goodwill": "0", - "goodwillAndIntangibleAssets": "5665000", - "intangibleAssets": "5665000", - "inventory": "4520000", - "longTermDebt": "178923999000", - "longTermInvestments": "237373355000", - "minorityInterest": "0", - "netDebt": "183764862000", - "netReceivables": "9666577000", - "otherAssets": "0", - "otherCurrentAssets": "27293000", - "otherCurrentLiabilities": "146647000", - "otherLiabilities": "0", - "otherNonCurrentAssets": "0", - "otherNonCurrentLiabilities": "52574304000", - "otherPayables": "0", - "otherReceivables": "0", - "otherTotalStockholdersEquity": "0", - "period": "Q1", - "preferredStock": "0", - "prepaids": "0", - "propertyPlantEquipmentNet": "194000", - "reportedCurrency": "RUB", - "retainedEarnings": "-5066509000", - "shortTermDebt": "4842848000", - "shortTermInvestments": "0", - "symbol": "MTLRP.ME", - "taxAssets": "791813000", - "taxPayables": "2484576000", - "totalAssets": "247871857000", - "totalCurrentAssets": "9700830000", - "totalCurrentLiabilities": "8851455000", - "totalDebt": "183766847000", - "totalEquity": "6784622000", - "totalInvestments": "237373355000", - "totalLiabilities": "244087635000", - "totalLiabilitiesAndTotalEquity": "247871857000", - "totalNonCurrentAssets": "238171027000", - "totalNonCurrentLiabilities": "232235780000", - "totalPayables": "3861497000", - "totalStockholdersEquity": "6784622000", - "treasuryStock": "0" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_cash_flow_statement_growth.csv b/crates/libfmp/tests/fixtures/bulk_cash_flow_statement_growth.csv new file mode 100644 index 00000000..cfa10899 --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_cash_flow_statement_growth.csv @@ -0,0 +1,3 @@ +"symbol","date","fiscalYear","period","reportedCurrency","growthNetIncome","growthDepreciationAndAmortization","growthDeferredIncomeTax","growthStockBasedCompensation","growthChangeInWorkingCapital","growthAccountsReceivables","growthInventory","growthAccountsPayables","growthOtherWorkingCapital","growthOtherNonCashItems","growthNetCashProvidedByOperatingActivites","growthInvestmentsInPropertyPlantAndEquipment","growthAcquisitionsNet","growthPurchasesOfInvestments","growthSalesMaturitiesOfInvestments","growthOtherInvestingActivites","growthNetCashUsedForInvestingActivites","growthDebtRepayment","growthCommonStockIssued","growthCommonStockRepurchased","growthDividendsPaid","growthOtherFinancingActivites","growthNetCashUsedProvidedByFinancingActivities","growthEffectOfForexChangesOnCash","growthNetChangeInCash","growthCashAtEndOfPeriod","growthCashAtBeginningOfPeriod","growthOperatingCashFlow","growthCapitalExpenditure","growthFreeCashFlow","growthNetDebtIssuance","growthLongTermNetDebtIssuance","growthShortTermNetDebtIssuance","growthNetStockIssuance","growthPreferredDividendsPaid","growthIncomeTaxesPaid","growthInterestPaid" +"000001.SZ","2024-12-31","2024","FY","CNY",-0.04191152728446884,-0.10923518487253081,0,0,-0.5531031325616376,0,0,0,-0.5531031325616376,-1.0121039890537837,-0.31499767469527695,0.22868217054263565,0,-0.4583663057845359,0.4677030505827389,0.5753424657534246,-0.21869023028077422,0.1434209903752942,0,0,-1.5747016706443915,0.021323938438716855,-10.139943132649277,-0.20336700336700336,-1.5438314469055117,-0.13839829118869018,0.3413590853071616,-0.31499767469527695,0.22868217054263565,-0.3179880266323505,-3.1534626292196504,-3.1534626292196504,0,0,-1.5747016706443915,0,0 +"000002.SZ","2024-12-31","2024","FY","CNY",-3.418825680531423,-0.03954598688771192,0,0,0.9581421763368241,-0.7995560363059401,-0.15061396395902196,1,0,34.307963887705945,-0.028749152677538976,0.4648098236195674,1.7915065697370418,0.6916693011479459,0.3825348800713944,0.16537124469098757,3.3418475696338668,0.9862757836011588,0,0.5291703272930137,0.39958402090830386,-1.0898368285307443,0.24828523296720706,2.804478045943588,0.6549502718538156,-0.13341078193124575,-0.2788334388323215,-0.028749152677538976,0.4648098236195674,0.8412007641619601,-1.3619064495275701,-1.3619064495275701,0,0.5291703272930137,0.39958402090830386,0,0 diff --git a/crates/libfmp/tests/fixtures/bulk_cash_flow_statement_growth.json b/crates/libfmp/tests/fixtures/bulk_cash_flow_statement_growth.json deleted file mode 100644 index c8ffa81a..00000000 --- a/crates/libfmp/tests/fixtures/bulk_cash_flow_statement_growth.json +++ /dev/null @@ -1,46 +0,0 @@ -[ - { - "date": "2025-03-31", - "fiscalYear": "2025", - "growthAccountsPayables": "0", - "growthAccountsReceivables": "0", - "growthAcquisitionsNet": "0", - "growthCapitalExpenditure": "0.7332280978689818", - "growthCashAtBeginningOfPeriod": "-0.07809495106059301", - "growthCashAtEndOfPeriod": "0.11426914604625096", - "growthChangeInWorkingCapital": "0", - "growthCommonStockIssued": "0", - "growthCommonStockRepurchased": "0", - "growthDebtRepayment": "1", - "growthDeferredIncomeTax": "0", - "growthDepreciationAndAmortization": "0", - "growthDividendsPaid": "0.6798284344644885", - "growthEffectOfForexChangesOnCash": "-1.0731570061902083", - "growthFreeCashFlow": "3.16553689621649", - "growthIncomeTaxesPaid": "0", - "growthInterestPaid": "0", - "growthInventory": "0", - "growthInvestmentsInPropertyPlantAndEquipment": "0.7332280978689818", - "growthLongTermNetDebtIssuance": "1", - "growthNetCashProvidedByOperatingActivites": "3.2072823819457614", - "growthNetCashUsedForInvestingActivites": "2.1699343339587243", - "growthNetCashUsedProvidedByFinancingActivities": "-3.2122934677858628", - "growthNetChangeInCash": "2.348938711752274", - "growthNetDebtIssuance": "1", - "growthNetIncome": "0", - "growthNetStockIssuance": "0", - "growthOperatingCashFlow": "3.2072823819457614", - "growthOtherFinancingActivites": "-1.7077146619443309", - "growthOtherInvestingActivites": "-0.8417721518987342", - "growthOtherNonCashItems": "3.2072823819457614", - "growthOtherWorkingCapital": "0", - "growthPreferredDividendsPaid": "0.6798284344644885", - "growthPurchasesOfInvestments": "-0.12254537395030414", - "growthSalesMaturitiesOfInvestments": "0.3847853673478318", - "growthShortTermNetDebtIssuance": "0", - "growthStockBasedCompensation": "0", - "period": "Q1", - "reportedCurrency": "CNY", - "symbol": "000001.SZ" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_cash_flow_statements.csv b/crates/libfmp/tests/fixtures/bulk_cash_flow_statements.csv new file mode 100644 index 00000000..a06c5413 --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_cash_flow_statements.csv @@ -0,0 +1,3 @@ +"date","symbol","reportedCurrency","cik","filingDate","acceptedDate","fiscalYear","period","netIncome","depreciationAndAmortization","deferredIncomeTax","stockBasedCompensation","changeInWorkingCapital","accountsReceivables","inventory","accountsPayables","otherWorkingCapital","otherNonCashItems","netCashProvidedByOperatingActivities","investmentsInPropertyPlantAndEquipment","acquisitionsNet","purchasesOfInvestments","salesMaturitiesOfInvestments","otherInvestingActivities","netCashProvidedByInvestingActivities","netDebtIssuance","longTermNetDebtIssuance","shortTermNetDebtIssuance","netStockIssuance","netCommonStockIssuance","commonStockIssuance","commonStockRepurchased","netPreferredStockIssuance","netDividendsPaid","commonDividendsPaid","preferredDividendsPaid","otherFinancingActivities","netCashProvidedByFinancingActivities","effectOfForexChangesOnCash","netChangeInCash","cashAtEndOfPeriod","cashAtBeginningOfPeriod","operatingCashFlow","capitalExpenditure","freeCashFlow","incomeTaxesPaid","interestPaid" +"2024-03-31","000001.SZ","CNY","0000000000","2024-03-31","2024-03-30 20:00:00","2024","Q1",14932000000,0,0,0,0,0,0,0,0,-36314000000,-21382000000,-241000000,17000000,-148027000000,116781000000,39781000000,8311000000,25543000000,25543000000,0,0,0,0,0,0,-2029000000,-2029000000,0,-713000000,22801000000,923000000,10653000000,308872000000,298219000000,-21382000000,-241000000,-21623000000,0,0 +"2024-09-30","0002.KL","MYR","0000000000","2024-09-30","2024-09-30 00:00:00","2024","Q1",11698000,3512000,0,0,1648000,373000,6004000,-4729000,-4729000,3466000,20324000,-7292000,0,0,0,869000,-6423000,0,0,0,0,0,0,0,0,0,0,0,-246000,-246000,-6028000,7627000,73640000,66013000,20324000,-7292000,13032000,0,0 diff --git a/crates/libfmp/tests/fixtures/bulk_cash_flow_statements.json b/crates/libfmp/tests/fixtures/bulk_cash_flow_statements.json deleted file mode 100644 index d0a97404..00000000 --- a/crates/libfmp/tests/fixtures/bulk_cash_flow_statements.json +++ /dev/null @@ -1,51 +0,0 @@ -[ - { - "acceptedDate": "2025-03-31 00:00:00", - "accountsPayables": "0", - "accountsReceivables": "0", - "acquisitionsNet": "0", - "capitalExpenditure": "-338000000", - "cashAtBeginningOfPeriod": "256946000000", - "cashAtEndOfPeriod": "286307000000", - "changeInWorkingCapital": "0", - "cik": "0000000000", - "commonDividendsPaid": "-2538000000", - "commonStockIssuance": "0", - "commonStockRepurchased": "0", - "date": "2025-03-31", - "deferredIncomeTax": "0", - "depreciationAndAmortization": "0", - "effectOfForexChangesOnCash": "-130000000", - "filingDate": "2025-03-31", - "fiscalYear": "2025", - "freeCashFlow": "162608000000", - "incomeTaxesPaid": "0", - "interestPaid": "0", - "inventory": "0", - "investmentsInPropertyPlantAndEquipment": "-338000000", - "longTermNetDebtIssuance": "0", - "netCashProvidedByFinancingActivities": "-158398000000", - "netCashProvidedByInvestingActivities": "24943000000", - "netCashProvidedByOperatingActivities": "162946000000", - "netChangeInCash": "29361000000", - "netCommonStockIssuance": "0", - "netDebtIssuance": "0", - "netDividendsPaid": "-2538000000", - "netIncome": "0", - "netPreferredStockIssuance": "0", - "netStockIssuance": "0", - "operatingCashFlow": "162946000000", - "otherFinancingActivities": "-155860000000", - "otherInvestingActivities": "25000000", - "otherNonCashItems": "162946000000", - "otherWorkingCapital": "0", - "period": "Q1", - "preferredDividendsPaid": "0", - "purchasesOfInvestments": "-227916000000", - "reportedCurrency": "CNY", - "salesMaturitiesOfInvestments": "253172000000", - "shortTermNetDebtIssuance": "0", - "stockBasedCompensation": "0", - "symbol": "000001.SZ" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_company_profiles.csv b/crates/libfmp/tests/fixtures/bulk_company_profiles.csv new file mode 100644 index 00000000..7c9d11ba --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_company_profiles.csv @@ -0,0 +1,5 @@ +"symbol","price","marketCap","beta","lastDividend","range","change","changePercentage","volume","averageVolume","companyName","currency","cik","isin","cusip","exchangeFullName","exchange","industry","website","description","ceo","sector","country","fullTimeEmployees","phone","address","city","state","zip","image","ipoDate","defaultImage","isEtf","isActivelyTrading","isAdr","isFund" +"WMB",67.78,82906291252,0.618,2.075,"55.56-80.08",-0.85,-1.23853,2020441,7138175,"The Williams Companies, Inc.","USD","0000107263","US9694571004","969457100","New York Stock Exchange","NYSE","Oil & Gas Midstream","https://www.williams.com","The Williams Companies, Inc., alongside its subsidiaries, operates as a prominent energy infrastructure entity, primarily conducting business throughout the United States. The company’s operations are organized into four key segments: Transmission & Gulf of Mexico, Northeast G&P, West, and Gas & NGL Marketing Services. The Transmission & Gulf of Mexico division manages crucial natural gas pipelines such as Transco and Northwest, in addition to natural gas gathering and processing, and crude oil production handling and transportation assets situated in the Gulf Coast. This segment also oversees various petrochemical and feedstock pipelines. Focusing on midstream activities, the Northeast G&P segment handles gathering, processing, and fractionation within the Marcellus Shale region, predominantly in Pennsylvania and New York, and the Utica Shale region of eastern Ohio. The West segment delivers gas gathering, processing, and treating services across the Rocky Mountain areas of Colorado and Wyoming, the Barnett Shale in north-central Texas, the Eagle Ford Shale in South Texas, the Haynesville Shale in northwest Louisiana, and the expansive Mid-Continent region (including the Anadarko, Arkoma, and Permian basins). This segment also operates natural gas liquid (NGL) fractionation and storage facilities located near Conway in central Kansas. The Gas & NGL Marketing Services segment provides comprehensive wholesale marketing, trading, storage, and transportation of natural gas to utilities, municipalities, power generators, and producers, while also offering risk and asset management and NGL marketing services. The company possesses and operates an extensive network, including 30,000 miles of pipelines, 29 processing facilities, 7 fractionation facilities, and an approximate NGL storage capacity of 23 million barrels. The Williams Companies, Inc. was established in 1908 and maintains its headquarters in Tulsa, Oklahoma.","Chad J. Zamarin","Energy","US","5987","800-945-5426","One Williams Center","Tulsa","OK","74172","https://images.financialmodelingprep.com/symbol/WMB.png","1981-12-31",false,false,true,false,false +"AMAT",508.787,403956017733,1.597,1.98,"203.4-739.67",-3.223,-0.62948,1689500,8607662,"Applied Materials, Inc.","USD","0000006951","US0382221051","038222105","NASDAQ","NASDAQ","Semiconductors","https://www.appliedmaterials.com","Applied Materials, Inc. engages in provision of materials engineering solutions used to produce semiconductors. The firm also focuses on design, development, production, and servicing of the critical wafer fabrication tools used for customers to manufacture semiconductors. It operates through the following segments: Semiconductor Systems and Applied Global Services (AGS). The Semiconductor Systems segment includes designing, development, manufacturing and sale of equipment used to fabricate semiconductor chips referred to as integrated circuits. The AGS segment engages in provision of services, spares, and factory automation software to customer fabrication plants globally. The company was founded on November 10, 1967 and is headquartered in Santa Clara, CA.","Gary E. Dickerson","Technology","US","36500","408 727 5555","3050 Bowers Ave","Santa Clara","CA","95052-8039","https://images.financialmodelingprep.com/symbol/AMAT.png","1980-03-17",false,false,true,false,false +"ACCV",18,540000000,0,0,,,,0,0,"Accelevation Holdings Corp.","USD",,"US0049661079","004966107","NASDAQ","NASDAQ","Electrical Equipment & Parts","http://www.accelevation.com","Accelevation Holdings Corp. engages in the design, manufacture and installation of power distribution and white space infrastructure products for mission-critical environments. The company was founded by Michael Rubiera and Shawn Rubiera in 2017 and is headquartered in Miamisburg, OH.",,"Industrials","US",,,"9555 N. Springboro Pike, Suite 400","Miamisburg","OH","45342","https://images.financialmodelingprep.com/symbol/ACCV.png","2026-09-30",true,false,true,false,false +"CFHAX",9.8534,0,0,0,"9.51-11.07",-0.0216,-0.21873,0,0,"Catalyst Hedged Commodity Strategy Fund Class A","USD","0001355064",,,"NASDAQ","NASDAQ","Asset Management",,,"","Financial Services",,,"631 549 1859","4221 North 203rd Street, Suite 100","Elkhorn","NE","68022-3474","https://images.financialmodelingprep.com/symbol/CFHAX.png","2019-01-02",true,false,true,false,true diff --git a/crates/libfmp/tests/fixtures/bulk_company_profiles.json b/crates/libfmp/tests/fixtures/bulk_company_profiles.json deleted file mode 100644 index 03fd292b..00000000 --- a/crates/libfmp/tests/fixtures/bulk_company_profiles.json +++ /dev/null @@ -1,40 +0,0 @@ -[ - { - "address": "One Apple Park Way", - "averageVolume": 48811139, - "beta": 1.107, - "ceo": "Timothy D. Cook", - "change": -0.83, - "changePercentage": -0.30493, - "cik": "0000320193", - "city": "Cupertino", - "companyName": "Apple Inc.", - "country": "US", - "currency": "USD", - "cusip": "037833100", - "defaultImage": false, - "description": "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide. The company offers iPhone, a line of smartphones; Mac, a line of personal computers; iPad, a line of multi-purpose tablets; and wearables, home, and accessories comprising AirPods, Apple TV, Apple Watch, Beats products, and HomePod. It also provides AppleCare support and cloud services; and operates various platforms, including the App Store that allow customers to discover and download applications and digital content, such as books, music, video, games, and podcasts, as well as advertising services include third-party licensing arrangements and its own advertising platforms. In addition, the company offers various subscription-based services, such as Apple Arcade, a game subscription service; Apple Fitness+, a personalized fitness service; Apple Music, which offers users a curated listening experience with on-demand radio stations; Apple News+, a subscription news and magazine service; Apple TV+, which offers exclusive original content; Apple Card, a co-branded credit card; and Apple Pay, a cashless payment service, as well as licenses its intellectual property. The company serves consumers, and small and mid-sized businesses; and the education, enterprise, and government markets. It distributes third-party applications for its products through the App Store. The company also sells its products through its retail and online stores, and direct sales force; and third-party cellular network carriers, wholesalers, retailers, and resellers. Apple Inc. was founded in 1976 and is headquartered in Cupertino, California.", - "exchange": "NASDAQ", - "exchangeFullName": "NASDAQ Global Select", - "fullTimeEmployees": "164000", - "image": "https://images.financialmodelingprep.com/symbol/AAPL.png", - "industry": "Consumer Electronics", - "ipoDate": "1980-12-12", - "isActivelyTrading": true, - "isAdr": false, - "isEtf": false, - "isFund": false, - "isin": "US0378331005", - "lastDividend": 1.03, - "marketCap": 4009711150080, - "phone": "(408) 996-1010", - "price": 271.36, - "range": "169.21-288.62", - "sector": "Technology", - "state": "CA", - "symbol": "AAPL", - "volume": 44494594, - "website": "https://www.apple.com", - "zip": "95014" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_dcf_valuations.csv b/crates/libfmp/tests/fixtures/bulk_dcf_valuations.csv new file mode 100644 index 00000000..a45947be --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_dcf_valuations.csv @@ -0,0 +1,4 @@ +"symbol","date","dcf","Stock Price" +"000006.SZ","2026-09-29",2.525226853334803,7.62 +"000008.SZ","2026-09-29",-5.17533839393595,2.39 +"000023.SZ","2024-07-29",,1.72 diff --git a/crates/libfmp/tests/fixtures/bulk_dcf_valuations.json b/crates/libfmp/tests/fixtures/bulk_dcf_valuations.json deleted file mode 100644 index ca623eb3..00000000 --- a/crates/libfmp/tests/fixtures/bulk_dcf_valuations.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "Stock Price": "6.54", - "date": "2025-07-09", - "dcf": "179.6654688379575", - "symbol": "000002.SZ" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_earnings_surprises.csv b/crates/libfmp/tests/fixtures/bulk_earnings_surprises.csv new file mode 100644 index 00000000..216a95d5 --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_earnings_surprises.csv @@ -0,0 +1,4 @@ +"symbol","date","epsActual","epsEstimated","lastUpdated" +"AUTO.OL","2024-12-31",0.1332,0.1581,"2025-10-07" +"601699.SS","2024-12-31",-0.11675,0.67,"2025-06-17" +"GUD.TO","2024-11-07",0.00084,,"2025-06-17" diff --git a/crates/libfmp/tests/fixtures/bulk_earnings_surprises.json b/crates/libfmp/tests/fixtures/bulk_earnings_surprises.json deleted file mode 100644 index bbf606d8..00000000 --- a/crates/libfmp/tests/fixtures/bulk_earnings_surprises.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "date": "2025-07-09", - "epsActual": "0.3631", - "epsEstimated": "0.3615", - "lastUpdated": "2025-07-09", - "symbol": "AMKYF" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_eod.csv b/crates/libfmp/tests/fixtures/bulk_eod.csv new file mode 100644 index 00000000..56f0c949 --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_eod.csv @@ -0,0 +1,3 @@ +"symbol","date","open","low","high","close","adjClose","volume" +"PCFD.SG","2025-06-02",7.35,7.35,19.71,19.71,19.71,0 +"HKDCNH","2025-06-02",0.91858,0.91692,0.9186,0.91741,0.91741,0 diff --git a/crates/libfmp/tests/fixtures/bulk_eod.json b/crates/libfmp/tests/fixtures/bulk_eod.json deleted file mode 100644 index cde7c75e..00000000 --- a/crates/libfmp/tests/fixtures/bulk_eod.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "adjClose": "2.93", - "close": "2.93", - "date": "2024-10-22", - "high": "2.9", - "low": "2.7", - "open": "2.67", - "symbol": "EGS745W1C011.CA", - "volume": "920904" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_etf_holdings.csv b/crates/libfmp/tests/fixtures/bulk_etf_holdings.csv new file mode 100644 index 00000000..05d9cb6c --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_etf_holdings.csv @@ -0,0 +1,5 @@ +"symbol","name","sharesNumber","asset","weightPercentage","cusip","isin","marketValue","lastUpdated" +" -- ","Tidewater Inc",91962,"TDW",2.63,"88642R109","US88642R1095",6457163.796,"2026-09-27" +"0050.TW","Bizlink Holding Inc.",4103274,"3665.TW",0.39,"G11474106","KYG114741062",9672220672.39,"2026-09-24" +" -- ","Other/Cash",0,"",0.15,"","",360428.571,"2026-09-27" +" -- ","Expro Group Holdings Nv",134790,"",0.74,"","",1823848.8606,"2026-09-27" diff --git a/crates/libfmp/tests/fixtures/bulk_etf_holdings.json b/crates/libfmp/tests/fixtures/bulk_etf_holdings.json deleted file mode 100644 index 6e5e50ca..00000000 --- a/crates/libfmp/tests/fixtures/bulk_etf_holdings.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "asset": "009150.KS", - "cusip": "", - "isin": "KR7009150004", - "lastUpdated\"": "2024-09-06\"", - "marketValue": "1553142.49", - "name": "SAMSUNG ELECTRO MECHANICS LTD", - "sharesNumber": "15514", - "symbol": "EXCH.AS", - "weightPercentage": "0.09611" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_financial_ratios_ttm.csv b/crates/libfmp/tests/fixtures/bulk_financial_ratios_ttm.csv new file mode 100644 index 00000000..7832f041 --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_financial_ratios_ttm.csv @@ -0,0 +1,4 @@ +"symbol","grossProfitMarginTTM","ebitMarginTTM","ebitdaMarginTTM","operatingProfitMarginTTM","pretaxProfitMarginTTM","continuousOperationsProfitMarginTTM","netProfitMarginTTM","bottomLineProfitMarginTTM","receivablesTurnoverTTM","payablesTurnoverTTM","inventoryTurnoverTTM","fixedAssetTurnoverTTM","assetTurnoverTTM","currentRatioTTM","quickRatioTTM","solvencyRatioTTM","cashRatioTTM","priceToEarningsRatioTTM","priceToEarningsGrowthRatioTTM","forwardPriceToEarningsGrowthRatioTTM","priceToEarningsDilutedRatioTTM","priceToEarningsDilutedGrowthRatioTTM","priceToBookRatioTTM","priceToSalesRatioTTM","priceToFreeCashFlowRatioTTM","priceToOperatingCashFlowRatioTTM","debtToAssetsRatioTTM","debtToEquityRatioTTM","debtToCapitalRatioTTM","longTermDebtToCapitalRatioTTM","financialLeverageRatioTTM","workingCapitalTurnoverRatioTTM","operatingCashFlowRatioTTM","operatingCashFlowSalesRatioTTM","freeCashFlowOperatingCashFlowRatioTTM","debtServiceCoverageRatioTTM","interestCoverageRatioTTM","shortTermOperatingCashFlowCoverageRatioTTM","operatingCashFlowCoverageRatioTTM","capitalExpenditureCoverageRatioTTM","dividendPaidAndCapexCoverageRatioTTM","dividendPayoutRatioTTM","dividendYieldTTM","enterpriseValueTTM","revenuePerShareTTM","netIncomePerShareTTM","interestDebtPerShareTTM","cashPerShareTTM","bookValuePerShareTTM","tangibleBookValuePerShareTTM","shareholdersEquityPerShareTTM","operatingCashFlowPerShareTTM","capexPerShareTTM","freeCashFlowPerShareTTM","netIncomePerEBTTTM","ebtPerEbitTTM","priceToFairValueTTM","debtToMarketCapTTM","effectiveTaxRateTTM","enterpriseValueMultipleTTM","dividendPerShareTTM" +"000001.SZ",0.5535250166330814,0.2490032117403229,0.2490032117403229,0.2490032117403229,0.2490032117403229,0.20801642726197941,0.20801642726197941,0.19822803834942396,11.876584617133762,0,0,14.59761039686976,0.03463701192046656,0.2105714236740011,0.2105714236740011,0.007811431374385213,0.19800778772590405,5.406542056074767,5.730934579439322,5.49720208631886,5.406542056074767,,0.4795381412345637,1.0746955717759343,16.049068874267334,13.435848917479506,0.26085746254762526,3.360474056734741,0.7706671368780413,0.26820180271423055,12.882414878666593,-0.4366174223979569,0.011935132757014952,0.07998717218470139,0.8371731195021244,0.02949246146924156,0.7086403944912888,0.011935132757014952,0.010620806510145728,6.141492098493201,1.0232059759980408,0.31319174394256655,0.04191876,1520706473551,10.765839575329545,2.239471484935678,84.86236002852326,14.286415102856768,24.12738217279904,23.458359455089937,24.12738217279904,0.861129063824757,0.14021495916864124,0.7209141046561157,0.83539656299258,1,0.4795381412345637,7.000292549657781,0.16460343700741994,29.23198788110799,0.485 +"000002.SZ",0.06666835318272966,-0.35160447542859574,-0.3436062587377696,-0.37712505941262214,-0.401972317997405,-0.48904104484834554,-0.4617804342288643,-0.4617804342288643,1.0423766535264125,1.5911361263677661,0.5349196265632267,6.6192161687368785,0.20659150998665363,1.1799592688017517,0.5548431689425369,-0.12094180514091163,0.10669697244200031,-0.5539661898569571,0.009588860380900295,0.04232837042623997,-0.5539661898569571,,0.49738934892132797,0.25578299899413237,35.65731601593227,12.980122127426482,0.18749632297965432,1.7611042833711095,0.6378260661784668,0.5772176041642411,9.392740376899077,1.787639576217643,0.007074995034555664,0.019747535999527614,0.36325375372663976,-2.3997780236250783,-11.174002151242703,0.13552230486057032,0.021758684201507818,1.5704836982740558,0.5419358420167008,-0.051679771183778936,0,171618750294,16.619498177734524,-7.674559085180067,15.644275053834804,4.950954525799006,18.08864773996082,17.017218420148932,8.564718985717171,0.32819413855889606,0.20897646942758963,0.11921775278259993,1.148786654089562,1.065885991833804,0.49738934892132797,3.3203982125782336,-0.21660378825265927,-2.518998346875115,0 +"ARS.AX",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,,,,,,,,,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,,,0,0,0,0,0,0,0,0,0,0,0,0,,,0,,0 diff --git a/crates/libfmp/tests/fixtures/bulk_financial_ratios_ttm.json b/crates/libfmp/tests/fixtures/bulk_financial_ratios_ttm.json deleted file mode 100644 index f904ade2..00000000 --- a/crates/libfmp/tests/fixtures/bulk_financial_ratios_ttm.json +++ /dev/null @@ -1,64 +0,0 @@ -[ - { - "assetTurnoverTTM": "0.029075827062555015", - "bookValuePerShareTTM": "22.260885357516656", - "bottomLineProfitMarginTTM": "0.25995857044215337", - "capexPerShareTTM": "0.10930051078304583", - "capitalExpenditureCoverageRatioTTM": "67.03702213279678", - "cashPerShareTTM": "32.81790720767194", - "cashRatioTTM": "0", - "continuousOperationsProfitMarginTTM": "0.25995857044215337", - "currentRatioTTM": "0", - "debtServiceCoverageRatioTTM": "0.24758322210087771", - "debtToAssetsRatioTTM": "0", - "debtToCapitalRatioTTM": "0", - "debtToEquityRatioTTM": "0", - "debtToMarketCapTTM": "0", - "dividendPaidAndCapexCoverageRatioTTM": "6.192364879934577", - "dividendPayoutRatioTTM": "0.5590996519509067", - "dividendPerShareTTM": "1.327", - "dividendYieldTTM": "0.10335", - "ebitMarginTTM": "0.22525536322293388", - "ebitdaMarginTTM": "0.2018381390033096", - "ebtPerEbitTTM": "0.6784217520188082", - "effectiveTaxRateTTM": "0.17748982974235347", - "enterpriseValueMultipleTTM": "-14.656106051669223", - "enterpriseValueTTM": "-496959244000", - "financialLeverageRatioTTM": "11.416164801466868", - "fixedAssetTurnoverTTM": "13.114441842310695", - "forwardPriceToEarningsGrowthRatioTTM": "2.4481492401413427", - "freeCashFlowOperatingCashFlowRatioTTM": "0.9850828696116743", - "freeCashFlowPerShareTTM": "7.21788024970599", - "grossProfitMarginTTM": "1.1622776732779352", - "interestCoverageRatioTTM": "0.7914088096104842", - "interestDebtPerShareTTM": "4.349676503966586", - "inventoryTurnoverTTM": "0", - "longTermDebtToCapitalRatioTTM": "0", - "netIncomePerEBTTTM": "0.8225101702576465", - "netIncomePerShareTTM": "1.9208740068077172", - "netProfitMarginTTM": "0.25995857044215337", - "operatingCashFlowCoverageRatioTTM": "0", - "operatingCashFlowPerShareTTM": "7.327180760489036", - "operatingCashFlowRatioTTM": "0", - "operatingCashFlowSalesRatioTTM": "0.991612895545132", - "operatingProfitMarginTTM": "0.4658682349579752", - "payablesTurnoverTTM": "0", - "pretaxProfitMarginTTM": "0.3160551441700993", - "priceToBookRatioTTM": "0.576796465809228", - "priceToEarningsGrowthRatioTTM": "-3.6096068640768793", - "priceToEarningsRatioTTM": "6.68445715569793", - "priceToFairValueTTM": "0.576796465809228", - "priceToFreeCashFlowRatioTTM": "1.518395607609901", - "priceToOperatingCashFlowRatioTTM": "1.7523793147342828", - "priceToSalesRatioTTM": "1.483200528584014", - "quickRatioTTM": "0", - "receivablesTurnoverTTM": "0", - "revenuePerShareTTM": "7.389154370023568", - "shareholdersEquityPerShareTTM": "22.260885357516656", - "shortTermOperatingCashFlowCoverageRatioTTM": "0", - "solvencyRatioTTM": "0.008534174446189174", - "symbol": "000001.SZ", - "tangibleBookValuePerShareTTM": "21.662613507347245", - "workingCapitalTurnoverRatioTTM": "0.23544250931631752" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_financial_scores.csv b/crates/libfmp/tests/fixtures/bulk_financial_scores.csv new file mode 100644 index 00000000..a60f6848 --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_financial_scores.csv @@ -0,0 +1,4 @@ +"symbol","reportedCurrency","altmanZScore","piotroskiScore","workingCapital","totalAssets","retainedEarnings","ebit","marketCap","totalLiabilities","revenue" +"000001.SZ","CNY",-0.06634014283050256,4,-1105320000000,6031727000000,287857000000,52022000000,219286875637,5563513000000,208921000000 +"000002.SZ","CNY",0.04042819128279265,4,99594827000,959761483000,-59369866000,-69715634000,44168410698,743955251000,198278574000 +"AAAU",,,2,0,524010983,0,-96900994,624598741,0,0 diff --git a/crates/libfmp/tests/fixtures/bulk_financial_scores.json b/crates/libfmp/tests/fixtures/bulk_financial_scores.json deleted file mode 100644 index 21c4184b..00000000 --- a/crates/libfmp/tests/fixtures/bulk_financial_scores.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "altmanZScore": "0.29153682196643543", - "ebit": "32590000000", - "marketCap": "236751980000", - "piotroskiScore": "5", - "reportedCurrency": "CNY", - "retainedEarnings": "255621000000", - "revenue": "167996000000", - "symbol": "000001.SZ", - "totalAssets": "5777858000000", - "totalLiabilities": "5271746000000", - "workingCapital": "746131000000" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_income_statement_growth.csv b/crates/libfmp/tests/fixtures/bulk_income_statement_growth.csv new file mode 100644 index 00000000..e61e8b1f --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_income_statement_growth.csv @@ -0,0 +1,3 @@ +"symbol","date","fiscalYear","period","reportedCurrency","growthRevenue","growthCostOfRevenue","growthGrossProfit","growthGrossProfitRatio","growthResearchAndDevelopmentExpenses","growthGeneralAndAdministrativeExpenses","growthSellingAndMarketingExpenses","growthOtherExpenses","growthOperatingExpenses","growthCostAndExpenses","growthInterestIncome","growthInterestExpense","growthDepreciationAndAmortization","growthEBITDA","growthOperatingIncome","growthIncomeBeforeTax","growthIncomeTaxExpense","growthNetIncome","growthEPS","growthEPSDiluted","growthWeightedAverageShsOut","growthWeightedAverageShsOutDil","growthEBIT","growthNonOperatingIncomeExcludingInterest","growthNetInterestIncome","growthTotalOtherIncomeExpensesNet","growthNetIncomeFromContinuingOperations","growthOtherAdjustmentsToNetIncome","growthNetIncomeDeductions" +"000001.SZ","2024-12-31","2024","FY","CNY",-0.08220846812871789,-0.04261762720522504,-0.1085837561909392,-0.028737776781126968,0,-0.1517311140742103,0,-0.13618841669800677,-0.13935247152203825,-0.09036186322835774,-0.12844383328134543,-0.04261762720522504,-0.09851265211512458,-0.055489307576118925,-0.051630340621643164,-0.051630340621643164,-0.09171623901269645,-0.04191152728446884,-0.04444444444444448,-0.04444444444444448,0.0667522931052252,0.0667522931052252,-0.051630340621643164,0,-0.20818537007059862,0,-0.04191152728446884,0,0 +"000002.SZ","2024-12-31","2024","FY","CNY",-0.2631572956889765,-0.2191553347697649,-0.5079774190529218,-0.3322556116134429,-0.05250863825309522,0.10876775722617925,-0.1538891907475451,1.7355792412327944,0.9316205360627275,-0.1092062238913554,-0.29996369591965133,0,-0.03954598688771192,-2.0231006134221006,-2.5603808626246773,-2.583153209542906,-0.8377116473277169,-5.068051837900253,-5.048543689320389,-5.048543689320389,0.006365235696615214,0.006365235696615214,-2.5429478654093614,2.4484685657454373,-0.29996369591965133,-3.7861487450471896,-3.3809633548006857,0,0 diff --git a/crates/libfmp/tests/fixtures/bulk_income_statement_growth.json b/crates/libfmp/tests/fixtures/bulk_income_statement_growth.json deleted file mode 100644 index 1b96ebb0..00000000 --- a/crates/libfmp/tests/fixtures/bulk_income_statement_growth.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "date": "2025-03-31", - "fiscalYear": "2025", - "growthCostAndExpenses": "-0.095830920671685", - "growthCostOfRevenue": "0", - "growthDepreciationAndAmortization": "0", - "growthEBIT": "1", - "growthEBITDA": "0", - "growthEPS": "1.6956521739130435", - "growthEPSDiluted": "1.6956521739130435", - "growthGeneralAndAdministrativeExpenses": "1.7466809598416757", - "growthGrossProfit": "-0.04159070191431176", - "growthGrossProfitRatio": "0", - "growthIncomeBeforeTax": "1.4139262224764084", - "growthIncomeTaxExpense": "0.2582392776523702", - "growthInterestExpense": "-0.08421879522057303", - "growthInterestIncome": "-0.003105727849505302", - "growthNetIncome": "1.9495710399665203", - "growthNetIncomeDeductions": "0", - "growthNetIncomeFromContinuingOperations": "1.9495710399665203", - "growthNetInterestIncome": "0.09080465272126753", - "growthNonOperatingIncomeExcludingInterest": "-0.5659209985158163", - "growthOperatingExpenses": "-0.095830920671685", - "growthOperatingIncome": "-0.018874787810201278", - "growthOtherAdjustmentsToNetIncome": "0", - "growthOtherExpenses": "-0.9860376183912135", - "growthResearchAndDevelopmentExpenses": "0", - "growthRevenue": "-0.04159070191431176", - "growthSellingAndMarketingExpenses": "0", - "growthTotalOtherIncomeExpensesNet": "0.5835023664638269", - "growthWeightedAverageShsOut": "0.09825852256371011", - "growthWeightedAverageShsOutDil": "0.09825852256371011", - "period": "Q1", - "reportedCurrency": "CNY", - "symbol": "000001.SZ" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_income_statements.csv b/crates/libfmp/tests/fixtures/bulk_income_statements.csv new file mode 100644 index 00000000..792ba89b --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_income_statements.csv @@ -0,0 +1,4 @@ +"date","symbol","reportedCurrency","cik","filingDate","acceptedDate","fiscalYear","period","revenue","costOfRevenue","grossProfit","researchAndDevelopmentExpenses","generalAndAdministrativeExpenses","sellingAndMarketingExpenses","sellingGeneralAndAdministrativeExpenses","otherExpenses","operatingExpenses","costAndExpenses","netInterestIncome","interestIncome","interestExpense","depreciationAndAmortization","ebitda","ebit","nonOperatingIncomeExcludingInterest","operatingIncome","totalOtherIncomeExpensesNet","incomeBeforeTax","incomeTaxExpense","netIncomeFromContinuingOperations","netIncomeFromDiscontinuedOperations","otherAdjustmentsToNetIncome","netIncome","netIncomeDeductions","bottomLineNetIncome","eps","epsDiluted","weightedAverageShsOut","weightedAverageShsOutDil" +"2024-12-31","000001.SZ","CNY","0000000000","2024-12-31","2024-12-31 00:00:00","2024","FY",251641000000,104954000000,146687000000,0,18449000000,0,18449000000,73500000000,91949000000,196903000000,93427000000,198381000000,104954000000,4667000000,59405000000,54738000000,0,54738000000,0,54738000000,10230000000,44508000000,0,0,44508000000,0,44508000000,2.15,2.15,20701395000,20701395000 +"2025-06-30","0002.KL","MYR","0000000000","2025-06-30","2025-06-30 00:00:00","2024","FY",230784000,177777000,53007000,0,0,0,0,1624000,1624000,179401000,2258000,2285000,27000,13605000,64988000,51383000,1649000,53032000,-27000,51356000,8881000,42475000,0,0,42475000,0,42475000,0.29,0.29,148314000,148314000 +"2024-12-31","OASMY","SEK","0001607245","2024-12-31","2024-12-31 00:00:00","2024","FY",0,0,0,0,0,0,37180000,4782000,41962000,41962000,0,3000,3000,3194000,-38768000,-41962000,0,-41962000,2208000,-39754000,0,-39754000,0,0,-39754000,0,-39754000,,,, diff --git a/crates/libfmp/tests/fixtures/bulk_income_statements.json b/crates/libfmp/tests/fixtures/bulk_income_statements.json deleted file mode 100644 index 12dbf28f..00000000 --- a/crates/libfmp/tests/fixtures/bulk_income_statements.json +++ /dev/null @@ -1,43 +0,0 @@ -[ - { - "acceptedDate": "2025-03-31 00:00:00", - "bottomLineNetIncome": "14096000000", - "cik": "0000000000", - "costAndExpenses": "9369000000", - "costOfRevenue": "0", - "date": "2025-03-31", - "depreciationAndAmortization": "0", - "ebit": "0", - "ebitda": "16802000000", - "eps": "0.62", - "epsDiluted": "0.62", - "filingDate": "2025-03-31", - "fiscalYear": "2025", - "generalAndAdministrativeExpenses": "9055000000", - "grossProfit": "33644000000", - "incomeBeforeTax": "16883000000", - "incomeTaxExpense": "2787000000", - "interestExpense": "22150000000", - "interestIncome": "44938000000", - "netIncome": "14096000000", - "netIncomeDeductions": "0", - "netIncomeFromContinuingOperations": "14096000000", - "netIncomeFromDiscontinuedOperations": "0", - "netInterestIncome": "22788000000", - "nonOperatingIncomeExcludingInterest": "24275000000", - "operatingExpenses": "9369000000", - "operatingIncome": "24275000000", - "otherAdjustmentsToNetIncome": "0", - "otherExpenses": "314000000", - "period": "Q1", - "reportedCurrency": "CNY", - "researchAndDevelopmentExpenses": "0", - "revenue": "33644000000", - "sellingAndMarketingExpenses": "0", - "sellingGeneralAndAdministrativeExpenses": "9055000000", - "symbol": "000001.SZ", - "totalOtherIncomeExpensesNet": "-7392000000", - "weightedAverageShsOut": "22735483871", - "weightedAverageShsOutDil": "22735483871" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_key_metrics_ttm.csv b/crates/libfmp/tests/fixtures/bulk_key_metrics_ttm.csv new file mode 100644 index 00000000..0dcda6a9 --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_key_metrics_ttm.csv @@ -0,0 +1,4 @@ +"symbol","marketCap","enterpriseValueTTM","evToSalesTTM","evToOperatingCashFlowTTM","evToFreeCashFlowTTM","evToEBITDATTM","netDebtToEBITDATTM","currentRatioTTM","incomeQualityTTM","grahamNumberTTM","grahamNetNetTTM","taxBurdenTTM","interestBurdenTTM","workingCapitalTTM","investedCapitalTTM","returnOnAssetsTTM","operatingReturnOnAssetsTTM","returnOnTangibleAssetsTTM","returnOnEquityTTM","returnOnInvestedCapitalTTM","returnOnCapitalEmployedTTM","earningsYieldTTM","freeCashFlowYieldTTM","capexToOperatingCashFlowTTM","capexToDepreciationTTM","capexToRevenueTTM","salesGeneralAndAdministrativeToRevenueTTM","researchAndDevelopementToRevenueTTM","stockBasedCompensationToRevenueTTM","intangiblesToTotalAssetsTTM","averageReceivablesTTM","averagePayablesTTM","averageInventoryTTM","daysOfSalesOutstandingTTM","daysOfPayablesOutstandingTTM","daysOfInventoryOutstandingTTM","operatingCycleTTM","cashConversionCycleTTM","freeCashFlowToEquityTTM","freeCashFlowToFirmTTM","tangibleAssetValueTTM","netCurrentAssetValueTTM" +"000001.SZ",224526473551,1520706473551,7.278858867950086,91.00032754179881,108.69953349185133,29.23198788110799,24.91599707815924,0.2105714236740011,0.39517120696178587,34.86736509399441,-271.7252927689378,0.83539656299258,1,-1105320000000,-1078025000000,0.007205067470726046,0.008623129603290786,0.00722060948264289,0.09289033782530935,0.007205067470726046,0.011232032300027528,0.19355846887948813,0.062308910743312615,0.16282688049787566,0,0.013024061726681377,0.02260663121466966,0,0,0.002152451528393112,8795500000,0,0,30.73274108395039,0,0,30.73274108395039,30.73274108395039,-106379000000,75317297085.8483,455231000000,-5268681000000 +"000004.SZ",67513944,-23704249,-0.1354217284200267,-0.841897327678881,-0.8962470352112417,-0.44397584773534005,-1.7084985297809208,1.11454892714022,2.388499808322787,1.1473442645398348,-0.20518332970569364,0.3283065153844871,0.7281077817851126,25605421,62495969,0.03424160485786354,-0.04084017529926973,0.03659920094471008,0.196679553604802,-0.07989108744570404,-0.12787045866623842,0.17475741923272,0.39174637760756503,0.06064143634187946,0.10716974410018917,0.009754358225523015,-0.027096298936874624,0.3538632227145465,0,0.06441659998009591,161005774.5,131472875,5696738.5,281.4264857895316,1445.3568449233546,92.13469677474816,373.56118256427976,-1071.7956623590749,29748351,26643066.309467595,92915054,19968242 +"ADAMO",711942736,,,,,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 diff --git a/crates/libfmp/tests/fixtures/bulk_key_metrics_ttm.json b/crates/libfmp/tests/fixtures/bulk_key_metrics_ttm.json deleted file mode 100644 index 8524f486..00000000 --- a/crates/libfmp/tests/fixtures/bulk_key_metrics_ttm.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "averageInventoryTTM": "0", - "averagePayablesTTM": "0", - "averageReceivablesTTM": "0", - "capexToDepreciationTTM": "1.855862584017924", - "capexToOperatingCashFlowTTM": "0.014917130388325619", - "capexToRevenueTTM": "0.014792018857591847", - "cashConversionCycleTTM": "0", - "currentRatioTTM": "0", - "daysOfInventoryOutstandingTTM": "0", - "daysOfPayablesOutstandingTTM": "0", - "daysOfSalesOutstandingTTM": "0", - "earningsYieldTTM": "0.14960077934639543", - "enterpriseValueTTM": "-496959244000", - "evToEBITDATTM": "-14.656106051669223", - "evToFreeCashFlowTTM": "-3.028355803098073", - "evToOperatingCashFlowTTM": "-2.9831814247210167", - "evToSalesTTM": "-2.95816117050406", - "freeCashFlowToEquityTTM": "910233000000", - "freeCashFlowToFirmTTM": "-35237570137.11014", - "freeCashFlowYieldTTM": "0.6585898925077207", - "grahamNetNetTTM": "-199.05514330278228", - "grahamNumberTTM": "31.017865999534138", - "incomeQualityTTM": "15.217593861331872", - "intangiblesToTotalAssetsTTM": "0.002354159621091415", - "interestBurdenTTM": "1.4030970878917606", - "investedCapitalTTM": "772543000000", - "marketCap": "249171756000", - "netCurrentAssetValueTTM": "-4525615000000", - "netDebtToEBITDATTM": "-22.004571192638906", - "operatingCycleTTM": "0", - "operatingReturnOnAssetsTTM": "0.013555578495362656", - "researchAndDevelopementToRevenueTTM": "0", - "returnOnAssetsTTM": "0.007558510437605078", - "returnOnCapitalEmployedTTM": "0.013545504233575834", - "returnOnEquityTTM": "0.09082717681735725", - "returnOnInvestedCapitalTTM": "0.011141314993384131", - "returnOnTangibleAssetsTTM": "0.007576346366296015", - "salesGeneralAndAdministrativeToRevenueTTM": "0.10163337222314817", - "stockBasedCompensationToRevenueTTM": "0", - "symbol": "000001.SZ", - "tangibleAssetValueTTM": "492510000000", - "taxBurdenTTM": "0.8225101702576465", - "workingCapitalTTM": "746131000000" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_price_target_summaries.csv b/crates/libfmp/tests/fixtures/bulk_price_target_summaries.csv new file mode 100644 index 00000000..6cbdda52 --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_price_target_summaries.csv @@ -0,0 +1,3 @@ +"symbol","lastMonthCount","lastMonthAvgPriceTarget","lastQuarterCount","lastQuarterAvgPriceTarget","lastYearCount","lastYearAvgPriceTarget","allTimeCount","allTimeAvgPriceTarget","publishers" +"A",2,160,11,172.27,33,167.64,53,159.49,"[""StreetInsider"",""Benzinga"",""Pulse 2.0""]" +"AA",1,60,6,63,24,61.33,52,57.76,"[""StreetInsider"",""Benzinga"",""Pulse 2.0""]" diff --git a/crates/libfmp/tests/fixtures/bulk_price_target_summaries.json b/crates/libfmp/tests/fixtures/bulk_price_target_summaries.json deleted file mode 100644 index dcd896d0..00000000 --- a/crates/libfmp/tests/fixtures/bulk_price_target_summaries.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "allTimeAvgPriceTarget": "146.61", - "allTimeCount": "18", - "lastMonthAvgPriceTarget": "0", - "lastMonthCount": "0", - "lastQuarterAvgPriceTarget": "116", - "lastQuarterCount": "1", - "lastYearAvgPriceTarget": "142.17", - "lastYearCount": "6", - "publishers": "[\"\"TheFly\"", - "symbol": "A" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_stock_peers.csv b/crates/libfmp/tests/fixtures/bulk_stock_peers.csv new file mode 100644 index 00000000..868f1888 --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_stock_peers.csv @@ -0,0 +1,4 @@ +"symbol","peers" +"000001.SZ","3698.HK,600000.SS,600015.SS,600016.SS,600036.SS,601166.SS,601658.SS" +"000002.SZ","600895.SS,600048.SS,001979.SZ,600376.SS,601588.SS,002244.SZ,600383.SS,600663.SS,600606.SS,600340.SS" +"0126Z0.KS", diff --git a/crates/libfmp/tests/fixtures/bulk_stock_peers.json b/crates/libfmp/tests/fixtures/bulk_stock_peers.json deleted file mode 100644 index 3c426b9d..00000000 --- a/crates/libfmp/tests/fixtures/bulk_stock_peers.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "peers": "600036.SS", - "symbol": "000001.SZ" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_stock_ratings.csv b/crates/libfmp/tests/fixtures/bulk_stock_ratings.csv new file mode 100644 index 00000000..b1a1ad9c --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_stock_ratings.csv @@ -0,0 +1,3 @@ +"symbol","date","rating","discountedCashFlowScore","returnOnEquityScore","returnOnAssetsScore","debtToEquityScore","priceToEarningsScore","priceToBookScore" +"000001.SZ","2026-09-30","B-",1,3,2,1,4,4 +"000002.SZ","2026-09-30","C+",4,1,1,1,1,4 diff --git a/crates/libfmp/tests/fixtures/bulk_stock_ratings.json b/crates/libfmp/tests/fixtures/bulk_stock_ratings.json deleted file mode 100644 index 2ddc594e..00000000 --- a/crates/libfmp/tests/fixtures/bulk_stock_ratings.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "date": "2025-07-09", - "debtToEquityScore": "1", - "discountedCashFlowScore": "5", - "priceToBookScore": "4", - "priceToEarningsScore": "4", - "rating": "B+", - "returnOnAssetsScore": "2", - "returnOnEquityScore": "3", - "symbol": "000001.SZ" - } -] diff --git a/crates/libfmp/tests/fixtures/bulk_upgrades_downgrades_consensus.csv b/crates/libfmp/tests/fixtures/bulk_upgrades_downgrades_consensus.csv new file mode 100644 index 00000000..f9c4d6ae --- /dev/null +++ b/crates/libfmp/tests/fixtures/bulk_upgrades_downgrades_consensus.csv @@ -0,0 +1,3 @@ +"symbol","strongBuy","buy","hold","sell","strongSell","consensus" +"000550.SZ",1,14,4,0,0,"Buy" +"0005.HK",0,5,10,2,0,"Hold" diff --git a/crates/libfmp/tests/fixtures/bulk_upgrades_downgrades_consensus.json b/crates/libfmp/tests/fixtures/bulk_upgrades_downgrades_consensus.json deleted file mode 100644 index 641ffb6c..00000000 --- a/crates/libfmp/tests/fixtures/bulk_upgrades_downgrades_consensus.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "buy": "1", - "consensus": "Buy", - "hold": "1", - "sell": "0", - "strongBuy": "0", - "strongSell": "0", - "symbol": "" - } -] diff --git a/crates/libfmp/tests/support/bulk_csv.rs b/crates/libfmp/tests/support/bulk_csv.rs new file mode 100644 index 00000000..7d836d5a --- /dev/null +++ b/crates/libfmp/tests/support/bulk_csv.rs @@ -0,0 +1,223 @@ +//! Shared CSV contract checks for the bulk test suites (ADR 0035). +//! +//! Bulk routes answer `text/csv`, so these helpers decode a fixture through +//! the real client and CSV contract instead of `serde_json`. + +use std::sync::Arc; + +use http::header::{CONTENT_TYPE, HeaderMap, HeaderValue}; +use libfmp::{ + Client, + client::{EndpointSpec, QueryParameters}, + error::DecodeErrorKind, + transport::TransportResponse, +}; +use serde::Serialize; +use serde_json::Value; + +use crate::support::FixtureExecutor; + +/// A `200 text/csv` fixture response. +fn csv_fixture(body: impl Into>) -> TransportResponse { + let mut headers = HeaderMap::new(); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("text/csv")); + TransportResponse::new(200, headers, body) +} + +/// Decodes `body` through `endpoint` on a fixture client, as the bulk method does. +pub fn decode( + endpoint: &EndpointSpec>, + body: impl Into>, +) -> libfmp::Result> +where + Q: QueryParameters, +{ + let client = Client::builder() + .base_url("https://fixture.test") + .executor(Arc::new(FixtureExecutor::new([csv_fixture(body)]))) + .build() + .unwrap(); + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(client.execute(endpoint)) +} + +/// Asserts every fixture row decodes and re-encodes its first row with +/// `fields` members, each equal to its cell: verbatim text for strings and +/// numeric strings, the parsed value for numbers and booleans, and null or +/// `""` for an empty cell. +pub fn assert_round_trip(endpoint: &EndpointSpec>, fixture: &[u8], fields: usize) +where + Q: QueryParameters, + R: Serialize, +{ + let (header, records) = read(fixture); + let rows = decode(endpoint, fixture).unwrap(); + assert_eq!(rows.len(), records.len()); + let Value::Object(encoded) = serde_json::to_value(&rows[0]).unwrap() else { + panic!("a row re-encodes as a JSON object"); + }; + assert_eq!(encoded.len(), fields); + for (member, value) in &encoded { + let column = header.iter().position(|name| name == member); + let cell = &records[0][column.unwrap_or_else(|| panic!("{member} is a header column"))]; + let matches = match value { + Value::Null => cell.is_empty(), + Value::String(text) => text == cell, + Value::Bool(flag) => flag.to_string() == *cell, + Value::Number(number) => cell.parse::().ok() == number.as_f64(), + _ => false, + }; + assert!(matches, "{member} does not re-encode its cell"); + } +} + +/// Asserts the CSV member contract on the fixture's first record: a blanked +/// model member's cell decodes only for a member in `blankable`, and is +/// otherwise a null at `[0].member`; dropping a model member's column is a missing member even +/// for an `Option`, while any other column, and an unknown one, is ignored. +#[allow(dead_code)] // Each suite uses the member contract that fits its rows. +pub fn assert_members(endpoint: &EndpointSpec>, fixture: &[u8], blankable: &[&str]) +where + Q: QueryParameters, + R: Serialize, +{ + check_members(endpoint, fixture, |member| blankable.contains(&member)); +} + +/// [`assert_members`] for a bulk CSV row under ADR 0035, where every member +/// accepts an empty cell except the identity members in `required`. +#[allow(dead_code)] // Each suite uses the member contract that fits its rows. +pub fn assert_required_members( + endpoint: &EndpointSpec>, + fixture: &[u8], + required: &[&str], +) where + Q: QueryParameters, + R: Serialize, +{ + check_members(endpoint, fixture, |member| !required.contains(&member)); +} + +fn check_members( + endpoint: &EndpointSpec>, + fixture: &[u8], + blankable: impl Fn(&str) -> bool, +) where + Q: QueryParameters, + R: Serialize, +{ + let (header, records) = read(fixture); + let rows = decode(endpoint, fixture).unwrap(); + let Value::Object(members) = serde_json::to_value(&rows[0]).unwrap() else { + panic!("a row re-encodes as a JSON object"); + }; + for (column, member) in header.iter().enumerate() { + let mut blank = records[0].clone(); + blank[column].clear(); + let result = decode(endpoint, write(&header, &blank)); + if blankable(member) || !members.contains_key(member) { + assert!(result.is_ok(), "blank {member} did not decode"); + } else { + assert_failure(result, member, DecodeErrorKind::Null); + } + + let keep = |cells: &[String]| -> Vec { + cells + .iter() + .enumerate() + .filter(|(index, _)| *index != column) + .map(|(_, cell)| cell.clone()) + .collect() + }; + let dropped = decode(endpoint, write(&keep(&header), &keep(&records[0]))); + if members.contains_key(member) { + assert_failure(dropped, member, DecodeErrorKind::MissingMember); + } else { + assert!(dropped.is_ok(), "dropping {member} failed"); + } + } + let mut future_header = header.clone(); + future_header.push("futureColumn".to_owned()); + let mut future = records[0].clone(); + future.push("future value".to_owned()); + assert!(decode(endpoint, write(&future_header, &future)).is_ok()); +} + +/// Asserts the empty and header-only bodies of an endpoint decode to no rows. +pub fn assert_empty_bodies(endpoint: &EndpointSpec>, fixture: &[u8]) +where + Q: QueryParameters, +{ + let (header, _) = read(fixture); + assert!(decode(endpoint, Vec::new()).unwrap().is_empty()); + assert!(decode(endpoint, write(&header, &[])).unwrap().is_empty()); +} + +/// Returns the fixture's header and first record with the named cells replaced. +#[allow(dead_code)] // Only suites that probe individual cells use it. +pub fn with_cells(fixture: &[u8], cells: &[(&str, &str)]) -> Vec { + let (header, records) = read(fixture); + let mut record = records[0].clone(); + for (name, value) in cells { + let column = header.iter().position(|column| column == name); + record[column.unwrap_or_else(|| panic!("{name} is a header column"))] = (*value).to_owned(); + } + write(&header, &record) +} + +/// Decodes the fixture and returns its first row re-encoded as JSON members. +#[allow(dead_code)] // Only suites that check wire spellings use it. +pub fn first_row( + endpoint: &EndpointSpec>, + fixture: &[u8], +) -> serde_json::Map +where + Q: QueryParameters, + R: Serialize, +{ + match serde_json::to_value(&decode(endpoint, fixture).unwrap()[0]).unwrap() { + Value::Object(members) => members, + other => panic!("a row re-encodes as a JSON object, not {other}"), + } +} + +/// Asserts that a decode failed at `[0].member` with `kind`. +#[allow(dead_code)] // Only suites that probe individual cells use it. +pub fn assert_cell_failure(result: libfmp::Result, member: &str, kind: DecodeErrorKind) { + assert_failure(result, member, kind); +} + +fn assert_failure(result: libfmp::Result, member: &str, kind: DecodeErrorKind) { + let Err(error) = result else { + panic!("{member}: expected a {kind:?} failure"); + }; + assert_eq!(error.decode_path(), Some(format!("[0].{member}").as_str())); + assert_eq!(error.decode_kind(), Some(kind), "{member}"); +} + +fn read(fixture: &[u8]) -> (Vec, Vec>) { + let mut reader = csv::Reader::from_reader(fixture); + let header = reader + .headers() + .unwrap() + .iter() + .map(str::to_owned) + .collect(); + let records = reader + .records() + .map(|record| record.unwrap().iter().map(str::to_owned).collect()) + .collect(); + (header, records) +} + +fn write(header: &[String], record: &[String]) -> Vec { + let mut writer = csv::Writer::from_writer(Vec::new()); + writer.write_record(header).unwrap(); + if !record.is_empty() { + writer.write_record(record).unwrap(); + } + writer.into_inner().unwrap() +} diff --git a/crates/libfmp/tests/transport.rs b/crates/libfmp/tests/transport.rs index 9ba54d66..dd740c4d 100644 --- a/crates/libfmp/tests/transport.rs +++ b/crates/libfmp/tests/transport.rs @@ -12,7 +12,7 @@ use bytes::Bytes; use http::header::{CONTENT_TYPE, HeaderMap, HeaderValue, LOCATION}; use libfmp::{ Client, - client::EndpointSpec, + client::{DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES, DEFAULT_MAX_RESPONSE_BODY_BYTES, EndpointSpec}, config::{Authentication, RedirectPolicy}, endpoints::BinaryBody, error::{ConfigurationErrorKind, ErrorCategory}, @@ -711,6 +711,48 @@ async fn endpoint_override_replaces_the_client_body_limit() { assert_eq!(executor.requests()[0].max_response_body_bytes(), 11); } +#[tokio::test] +async fn csv_bulk_endpoints_default_to_the_bulk_body_limit_until_the_client_sets_one() { + let mut headers = HeaderMap::new(); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("text/csv")); + let csv = || TransportResponse::new(200, headers.clone(), b"ok\ntrue\n".as_slice()); + let bulk = EndpointSpec::<(), Vec>::get_csv("bulk-test", "bulk-test", ()); + let executor = Arc::new(ScriptedExecutor::new(vec![ + Ok(csv()), + Ok(json_response()), + Ok(csv()), + ])); + let unset = Client::builder() + .base_url("https://example.test") + .executor(executor.clone()) + .build() + .unwrap(); + let configured = Client::builder() + .base_url("https://example.test") + .max_response_body_bytes(64) + .executor(executor.clone()) + .build() + .unwrap(); + + assert_eq!(unset.execute(&bulk).await.unwrap(), [Answer { ok: true }]); + unset.execute(&endpoint()).await.unwrap(); + configured.execute(&bulk).await.unwrap(); + + let limits: Vec<_> = executor + .requests() + .iter() + .map(PreparedRequest::max_response_body_bytes) + .collect(); + assert_eq!( + limits, + [ + DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES, + DEFAULT_MAX_RESPONSE_BODY_BYTES, + 64 + ] + ); +} + #[tokio::test] async fn binary_response_retains_the_transport_buffer_without_copying() { let bytes = Bytes::from_static(b"binary-response"); diff --git a/docs/adr/0028-bulk-api-contracts.md b/docs/adr/0028-bulk-api-contracts.md index 6d0d8888..61124366 100644 --- a/docs/adr/0028-bulk-api-contracts.md +++ b/docs/adr/0028-bulk-api-contracts.md @@ -5,6 +5,11 @@ Accepted for issue #37's Rust API inventory. All 18 routes are implemented in Rust; the Python facade remains reserved. +Superseded in part by [ADR 0035](0035-bulk-csv-responses.md): the routes +answer `text/csv`, not a bare JSON array; numeric bulk members are optional; +and the ETF key is a clean `lastUpdated` decoded as a date. The names, queries, +single-request rule, and preserved provider spellings below still hold. + ## Decision All 18 documented bulk entries use the following public Rust names. diff --git a/docs/adr/0030-go-sdk-architecture.md b/docs/adr/0030-go-sdk-architecture.md index fcb65715..a5336441 100644 --- a/docs/adr/0030-go-sdk-architecture.md +++ b/docs/adr/0030-go-sdk-architecture.md @@ -147,7 +147,7 @@ that no wire information is invented or lost: | `CalendarQuarter` on a response model (`u8`, validated 1 to 4 in Rust) | `uint8` | no range check on decode: Rust validates, Go parity tests prove the JSON kind only; accepted difference, same policy as `IsoTimestamp` | | `FiscalPeriod` on a response model (`bulk` statement rows) | `string` | the closed Rust enum is not mirrored on the response side; accepted difference, same policy as `IsoTimestamp`. The query-side `FiscalPeriod` stays a typed string | | `pub type` aliases of a model (`FundDisclosureDate = Form13fFilingDate`) | not emitted | the Go method returns the aliased model's Go type (`[]Form13fFilingDate`), so one Rust model is one Go type | -| a wire name Go 1.27.1 `json/v2` cannot spell in a struct tag (a comma, backslash, quote, double quote, or backtick, such as the bulk ETF holdings key `lastUpdated"`) | public field tagged `json:"-"`; the shadow carries a `RawMembers map[string]jsontext.Value` tagged `json:",embed"` fallback, the decoder reads the member through `rawMember`, and a generated `MarshalJSONTo` re-emits the exact key | only plain codecs are bridged; json/v2 allows one embedded fallback per struct, so a model with a raw key cannot also carry an embedded rest map, and the generator fails naming the struct | +| a wire name Go 1.27.1 `json/v2` cannot spell in a struct tag (a comma, backslash, quote, double quote, or backtick; the bulk ETF holdings key `lastUpdated"` was one until ADR 0035) | public field tagged `json:"-"`; the shadow carries a `RawMembers map[string]jsontext.Value` tagged `json:",embed"` fallback, the decoder reads the member through `rawMember`, and a generated `MarshalJSONTo` re-emits the exact key | only plain codecs are bridged; json/v2 allows one embedded fallback per struct, so a model with a raw key cannot also carry an embedded rest map, and the generator fails naming the struct | ### Endpoint metadata (Rust to Go type table) diff --git a/docs/adr/0035-bulk-csv-responses.md b/docs/adr/0035-bulk-csv-responses.md new file mode 100644 index 00000000..4e17a9d8 --- /dev/null +++ b/docs/adr/0035-bulk-csv-responses.md @@ -0,0 +1,146 @@ +# ADR 0035: Bulk CSV responses + +- Status: Accepted +- Date: 2026-10-01 +- Decision owners: libfmp maintainers +- Scope: the eighteen `*-bulk` routes in Rust, Python, and Go; implemented + by [#376](https://github.com/bitbrew-dev/libfmp/issues/376). Supersedes + ADR 0028's "bare JSON array" wire format, its "every documented key is + required and non-null" rule for bulk rows, and its quoted ETF + `lastUpdated"` key. + +## Context + +ADR 0028 typed the bulk routes from the `outer.md` examples, which show JSON +arrays. The 2026-09-30 live audit (#368 rerun) found that every bulk route +answers `200 text/csv` and ignores `Accept: application/json`, so every bulk +call failed to decode in all three SDKs. Live probes on 2026-09-30 and +2026-10-01 established the wire shape: + +- A headed CSV body with LF line endings and no byte-order mark. Strings are + quoted, numbers are bare (including exponent forms such as `6.9e-9`), + quoted cells can carry commas and doubled quotes, and an absent value is an + empty cell. +- A request with no data (for example `earnings-surprises-bulk?year=1900`) + answers `200 text/csv` with an empty body. A bad parameter answers + `400 application/json`, which the existing status path handles. +- The ETF header is a clean `lastUpdated`, not the documented `lastUpdated"`. +- Bodies are large: one `profile-bulk` part is about 30 MB, and the full + `ratios-ttm-bulk` body measured 68,755,170 bytes (65.6 MiB), above the + 64 MiB general default. +- A census of over a million live rows (full bodies for most routes, income + statements for two periods, samples for the other statement routes) found + empty cells on many numeric columns, sparse and varying by period: for example `netIncomeDeductions` empty on 24 of 55,315 FY2024 + income rows and on none of 18,512 Q1 2025 rows; `grahamNumberTTM` empty on + 11,072 of 30,041 key-metrics rows; `dcf` on 1,653 of 35,029 rows. Typed + codes were also empty: `reportedCurrency` (scores, 313 of 62,655), ETF + `asset` and `isin`, and profile `isin`, `country` (with `change` and + `changePercentage` on two rows). + +## Decision + +### Contract + +- `EndpointSpec::get_csv` and `ResponseContract::>::csv()` describe + a CSV route; `ExpectedContentType::Csv` accepts `text/csv` + case-insensitively, parameters ignored. All eighteen bulk descriptors use + it, and gen_go renders them as `getCSV[Row]`. +- A `200` with any other media type, including `application/json`, is the + existing "unexpected content type" decode error. The provider-message check + of ADR 0031 (#381) stays on the JSON path: a header-only CSV body such as + `symbol,price` is data, not an error line. +- An empty body or a header-only body decodes to no rows. + +### Decoding + +- Rust: the `csv` crate (1.4, RFC 4180 records, quoted cells with commas, + doubled quotes and newlines, no trimming, equal field counts) only splits + records. Each record is handed to the row's `Deserialize` as a map from + header name to cell through a small cell deserializer, so serde renames + and field codecs apply unchanged and `serde_path_to_error` reports + `[row].member`, the JSON decoder's path format. The csv crate's own serde + layer was not used: it lost member paths and infers types for + `deserialize_any`. +- Go: `encoding/csv` splits records; each record is written as a JSON object + whose cell kinds come from the model's fields (numbers and booleans raw, + everything else quoted) and decoded with the model's own JSON rules, so + shadows, codecs, and redaction apply and a failure reports + `//`. +- Every model member, `Option` or not, must be a header column, checked on + the first record, so a dropped or renamed provider column is a + `missing_member` error instead of a silent `None`. Columns the model does + not read are ignored. +- Error messages are classified into a `DecodeErrorKind` and dropped; no cell + value reaches an error (ADR 0031). + +### Cell rules + +| Cell | Option member | Plain `String` | Number or bool | Typed code or date, required | +| --- | --- | --- | --- | --- | +| empty | `None` / `None` / `nil` | `""` | `null` decode error | `null` decode error | +| text | the member's own parse | verbatim | parsed; anything else is `wrong_type` | the type's validation | + +`NumericString` keeps the cell text verbatim (`6.9148336e-9`, values beyond +`u64`). Booleans accept exactly `true` and `false`. Go is more lenient on one +point it already had for JSON: a required plain `string` (Go's +`NumericString` and code types) takes an empty cell as `""`. + +### Nullability (bulk CSV only) + +CSV has no null: an empty cell is its only absence marker, and the census +shows sparse empties that change by period, where one empty cell would fail a +50,000-row response. So, for bulk CSV rows only: + +- Every numeric metric member (`NumericString`) is `Option`, + Python `Optional[str]`, Go `*string`. +- Identity members stay required: `symbol`, `date`, and on statement rows + `reportedCurrency`, `cik`, `filingDate`, `acceptedDate`, `fiscalYear`, and + `period`; `lastUpdated` on ETF holdings and earnings surprises. +- Typed codes follow the evidence through `codecs::empty_or_null`: + `BulkFinancialScore.reported_currency`, `BulkEtfHolding.asset` and + `isin`. +- Text members (`BulkStockPeer.peers`, ETF `cusip` and `name`) stay strings; + an empty cell is `""`. + +JSON endpoints keep ADR 0033's evidence-only rule. `CompanyProfile` is shared +with the JSON `company.profile` route; the evidence rule widened its `isin` +and `country` (`empty_or_null`) and `change` and `change_percentage` +(`required_option`), which affects both routes. + +`BulkEtfHolding.last_updated_raw: String` (key `lastUpdated"`) became +`last_updated: Date` (key `lastUpdated`). + +### Body limit and buffering + +CSV bulk descriptors default to `DEFAULT_BULK_MAX_RESPONSE_BODY_BYTES` +(256 MiB) while the client limit is unset; every other endpoint keeps the +64 MiB default. Once a caller sets the client limit +(`ClientBuilder::max_response_body_bytes`, Go `WithMaxResponseBodyBytes`, +Python `max_response_body_bytes=`), it applies to every endpoint, bulk +included, and an endpoint override still wins. Bodies stay buffered, as in +ADR 0028, and decode record by record from the buffer; there is no streaming +API. + +## Alternatives considered + +- **Evidence-only `Option`s for bulk rows.** Rejected: about forty members + today, and any new year or period can add an empty cell that fails a whole + response. +- **Transcode CSV to JSON in Rust and reuse `decode_json`.** Rejected: CSV + cells carry no types, so the transcoder would need the model's schema, + which serde already provides. +- **A separate `BulkCompanyProfile`.** Rejected: a 36-field copy in three + SDKs for the same provider data, where the JSON profile shows the same + gaps. +- **Streaming decode.** Deferred: the bodies fit the buffered limit and every + method returns a `Vec`. + +## Consequences + +- Breaking in all three SDKs: the bulk methods require `text/csv`, most bulk + row members are optional, the ETF field is renamed, and four + `CompanyProfile` members are optional. +- The shared fixtures under `crates/libfmp/tests/fixtures/bulk_*.csv` are + trimmed live captures; the Go fixture check covers `.csv` files. +- A new empty identity cell is a decode error by design and a small model + change under this ADR. diff --git a/sdk/go/binary.go b/sdk/go/binary.go index 1913f257..296348b3 100644 --- a/sdk/go/binary.go +++ b/sdk/go/binary.go @@ -49,7 +49,7 @@ func (c *Client) getBinary(ctx context.Context, endpointID, relativePath string, ctx, cancel := context.WithTimeout(ctx, c.timeout) defer cancel() - resp, err := c.executeRedirects(ctx, endpointID, target) + resp, err := c.executeRedirects(ctx, endpointID, target, c.maxResponseBodyBytes) if err != nil { return BinaryPayload{}, err } diff --git a/sdk/go/bulk.go b/sdk/go/bulk.go index 22e3b227..8f1032ee 100644 --- a/sdk/go/bulk.go +++ b/sdk/go/bulk.go @@ -139,11 +139,7 @@ func (n *BulkNamespace) CompanyProfiles(ctx context.Context, q BulkPartQuery) ([ if err != nil { return nil, err } - var out []CompanyProfile - if err := n.client.getJSON(ctx, "profile-bulk", "profile-bulk", params, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[CompanyProfile](ctx, n.client, "profile-bulk", "profile-bulk", params) } // StockRatings retrieves worldwide stock ratings in one provider bulk @@ -151,11 +147,7 @@ func (n *BulkNamespace) CompanyProfiles(ctx context.Context, q BulkPartQuery) ([ // // GET rating-bulk func (n *BulkNamespace) StockRatings(ctx context.Context) ([]BulkStockRating, error) { - var out []BulkStockRating - if err := n.client.getJSON(ctx, "rating-bulk", "rating-bulk", nil, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkStockRating](ctx, n.client, "rating-bulk", "rating-bulk", nil) } // DCFValuations retrieves worldwide discounted-cash-flow valuations in one @@ -163,11 +155,7 @@ func (n *BulkNamespace) StockRatings(ctx context.Context) ([]BulkStockRating, er // // GET dcf-bulk func (n *BulkNamespace) DCFValuations(ctx context.Context) ([]BulkDCFValuation, error) { - var out []BulkDCFValuation - if err := n.client.getJSON(ctx, "dcf-bulk", "dcf-bulk", nil, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkDCFValuation](ctx, n.client, "dcf-bulk", "dcf-bulk", nil) } // FinancialScores retrieves worldwide financial scores in one provider bulk @@ -175,11 +163,7 @@ func (n *BulkNamespace) DCFValuations(ctx context.Context) ([]BulkDCFValuation, // // GET scores-bulk func (n *BulkNamespace) FinancialScores(ctx context.Context) ([]BulkFinancialScore, error) { - var out []BulkFinancialScore - if err := n.client.getJSON(ctx, "scores-bulk", "scores-bulk", nil, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkFinancialScore](ctx, n.client, "scores-bulk", "scores-bulk", nil) } // PriceTargetSummaries retrieves US price-target summaries in one provider @@ -187,11 +171,7 @@ func (n *BulkNamespace) FinancialScores(ctx context.Context) ([]BulkFinancialSco // // GET price-target-summary-bulk func (n *BulkNamespace) PriceTargetSummaries(ctx context.Context) ([]BulkPriceTargetSummary, error) { - var out []BulkPriceTargetSummary - if err := n.client.getJSON(ctx, "price-target-summary-bulk", "price-target-summary-bulk", nil, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkPriceTargetSummary](ctx, n.client, "price-target-summary-bulk", "price-target-summary-bulk", nil) } // ETFHoldings retrieves one provider partition of worldwide ETF holdings. @@ -202,11 +182,7 @@ func (n *BulkNamespace) ETFHoldings(ctx context.Context, q BulkPartQuery) ([]Bul if err != nil { return nil, err } - var out []BulkETFHolding - if err := n.client.getJSON(ctx, "etf-holder-bulk", "etf-holder-bulk", params, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkETFHolding](ctx, n.client, "etf-holder-bulk", "etf-holder-bulk", params) } // UpgradesDowngradesConsensus retrieves worldwide upgrades/downgrades @@ -214,11 +190,7 @@ func (n *BulkNamespace) ETFHoldings(ctx context.Context, q BulkPartQuery) ([]Bul // // GET upgrades-downgrades-consensus-bulk func (n *BulkNamespace) UpgradesDowngradesConsensus(ctx context.Context) ([]BulkUpgradesDowngradesConsensus, error) { - var out []BulkUpgradesDowngradesConsensus - if err := n.client.getJSON(ctx, "upgrades-downgrades-consensus-bulk", "upgrades-downgrades-consensus-bulk", nil, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkUpgradesDowngradesConsensus](ctx, n.client, "upgrades-downgrades-consensus-bulk", "upgrades-downgrades-consensus-bulk", nil) } // KeyMetricsTTM retrieves worldwide trailing-twelve-month key metrics in one @@ -226,11 +198,7 @@ func (n *BulkNamespace) UpgradesDowngradesConsensus(ctx context.Context) ([]Bulk // // GET key-metrics-ttm-bulk func (n *BulkNamespace) KeyMetricsTTM(ctx context.Context) ([]BulkKeyMetricsTTM, error) { - var out []BulkKeyMetricsTTM - if err := n.client.getJSON(ctx, "key-metrics-ttm-bulk", "key-metrics-ttm-bulk", nil, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkKeyMetricsTTM](ctx, n.client, "key-metrics-ttm-bulk", "key-metrics-ttm-bulk", nil) } // FinancialRatiosTTM retrieves worldwide trailing-twelve-month financial @@ -238,22 +206,14 @@ func (n *BulkNamespace) KeyMetricsTTM(ctx context.Context) ([]BulkKeyMetricsTTM, // // GET ratios-ttm-bulk func (n *BulkNamespace) FinancialRatiosTTM(ctx context.Context) ([]BulkFinancialRatiosTTM, error) { - var out []BulkFinancialRatiosTTM - if err := n.client.getJSON(ctx, "ratios-ttm-bulk", "ratios-ttm-bulk", nil, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkFinancialRatiosTTM](ctx, n.client, "ratios-ttm-bulk", "ratios-ttm-bulk", nil) } // StockPeers retrieves worldwide stock peers in one provider bulk response. // // GET peers-bulk func (n *BulkNamespace) StockPeers(ctx context.Context) ([]BulkStockPeer, error) { - var out []BulkStockPeer - if err := n.client.getJSON(ctx, "peers-bulk", "peers-bulk", nil, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkStockPeer](ctx, n.client, "peers-bulk", "peers-bulk", nil) } // EarningsSurprises retrieves worldwide annual earnings surprises for one @@ -265,11 +225,7 @@ func (n *BulkNamespace) EarningsSurprises(ctx context.Context, q BulkYearQuery) if err != nil { return nil, err } - var out []BulkEarningsSurprise - if err := n.client.getJSON(ctx, "earnings-surprises-bulk", "earnings-surprises-bulk", params, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkEarningsSurprise](ctx, n.client, "earnings-surprises-bulk", "earnings-surprises-bulk", params) } // IncomeStatements retrieves worldwide bulk income statements for one year and @@ -281,11 +237,7 @@ func (n *BulkNamespace) IncomeStatements(ctx context.Context, q BulkStatementQue if err != nil { return nil, err } - var out []BulkIncomeStatement - if err := n.client.getJSON(ctx, "income-statement-bulk", "income-statement-bulk", params, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkIncomeStatement](ctx, n.client, "income-statement-bulk", "income-statement-bulk", params) } // IncomeStatementGrowth retrieves worldwide bulk income-statement growth for @@ -297,11 +249,7 @@ func (n *BulkNamespace) IncomeStatementGrowth(ctx context.Context, q BulkStateme if err != nil { return nil, err } - var out []BulkIncomeStatementGrowth - if err := n.client.getJSON(ctx, "income-statement-growth-bulk", "income-statement-growth-bulk", params, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkIncomeStatementGrowth](ctx, n.client, "income-statement-growth-bulk", "income-statement-growth-bulk", params) } // BalanceSheetStatements retrieves worldwide bulk balance sheets for one year @@ -313,11 +261,7 @@ func (n *BulkNamespace) BalanceSheetStatements(ctx context.Context, q BulkStatem if err != nil { return nil, err } - var out []BulkBalanceSheetStatement - if err := n.client.getJSON(ctx, "balance-sheet-statement-bulk", "balance-sheet-statement-bulk", params, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkBalanceSheetStatement](ctx, n.client, "balance-sheet-statement-bulk", "balance-sheet-statement-bulk", params) } // BalanceSheetStatementGrowth retrieves worldwide bulk balance-sheet growth @@ -329,11 +273,7 @@ func (n *BulkNamespace) BalanceSheetStatementGrowth(ctx context.Context, q BulkS if err != nil { return nil, err } - var out []BulkBalanceSheetStatementGrowth - if err := n.client.getJSON(ctx, "balance-sheet-statement-growth-bulk", "balance-sheet-statement-growth-bulk", params, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkBalanceSheetStatementGrowth](ctx, n.client, "balance-sheet-statement-growth-bulk", "balance-sheet-statement-growth-bulk", params) } // CashFlowStatements retrieves worldwide bulk cash-flow statements for one @@ -345,11 +285,7 @@ func (n *BulkNamespace) CashFlowStatements(ctx context.Context, q BulkStatementQ if err != nil { return nil, err } - var out []BulkCashFlowStatement - if err := n.client.getJSON(ctx, "cash-flow-statement-bulk", "cash-flow-statement-bulk", params, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkCashFlowStatement](ctx, n.client, "cash-flow-statement-bulk", "cash-flow-statement-bulk", params) } // CashFlowStatementGrowth retrieves worldwide bulk cash-flow growth for one @@ -361,11 +297,7 @@ func (n *BulkNamespace) CashFlowStatementGrowth(ctx context.Context, q BulkState if err != nil { return nil, err } - var out []BulkCashFlowStatementGrowth - if err := n.client.getJSON(ctx, "cash-flow-statement-growth-bulk", "cash-flow-statement-growth-bulk", params, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkCashFlowStatementGrowth](ctx, n.client, "cash-flow-statement-growth-bulk", "cash-flow-statement-growth-bulk", params) } // Eod retrieves worldwide bulk end-of-day prices for one required date. @@ -376,9 +308,5 @@ func (n *BulkNamespace) Eod(ctx context.Context, q BulkEodQuery) ([]BulkEodBar, if err != nil { return nil, err } - var out []BulkEodBar - if err := n.client.getJSON(ctx, "eod-bulk", "eod-bulk", params, &out); err != nil { - return nil, err - } - return out, nil + return getCSV[BulkEodBar](ctx, n.client, "eod-bulk", "eod-bulk", params) } diff --git a/sdk/go/bulk_models.go b/sdk/go/bulk_models.go index 9fdf02d3..faa541f3 100644 --- a/sdk/go/bulk_models.go +++ b/sdk/go/bulk_models.go @@ -20,59 +20,59 @@ type BulkBalanceSheetStatement struct { AcceptedDate DateTime `json:"acceptedDate"` FiscalYear string `json:"fiscalYear"` Period string `json:"period"` - CashAndCashEquivalents string `json:"cashAndCashEquivalents"` - ShortTermInvestments string `json:"shortTermInvestments"` - CashAndShortTermInvestments string `json:"cashAndShortTermInvestments"` - NetReceivables string `json:"netReceivables"` - AccountsReceivables string `json:"accountsReceivables"` - OtherReceivables string `json:"otherReceivables"` - Inventory string `json:"inventory"` - Prepaids string `json:"prepaids"` - OtherCurrentAssets string `json:"otherCurrentAssets"` - TotalCurrentAssets string `json:"totalCurrentAssets"` - PropertyPlantEquipmentNet string `json:"propertyPlantEquipmentNet"` - Goodwill string `json:"goodwill"` - IntangibleAssets string `json:"intangibleAssets"` - GoodwillAndIntangibleAssets string `json:"goodwillAndIntangibleAssets"` - LongTermInvestments string `json:"longTermInvestments"` - TaxAssets string `json:"taxAssets"` - OtherNonCurrentAssets string `json:"otherNonCurrentAssets"` - TotalNonCurrentAssets string `json:"totalNonCurrentAssets"` - OtherAssets string `json:"otherAssets"` - TotalAssets string `json:"totalAssets"` - TotalPayables string `json:"totalPayables"` - AccountPayables string `json:"accountPayables"` - OtherPayables string `json:"otherPayables"` - AccruedExpenses string `json:"accruedExpenses"` - ShortTermDebt string `json:"shortTermDebt"` - CapitalLeaseObligationsCurrent string `json:"capitalLeaseObligationsCurrent"` - TaxPayables string `json:"taxPayables"` - DeferredRevenue string `json:"deferredRevenue"` - OtherCurrentLiabilities string `json:"otherCurrentLiabilities"` - TotalCurrentLiabilities string `json:"totalCurrentLiabilities"` - LongTermDebt string `json:"longTermDebt"` - CapitalLeaseObligationsNonCurrent string `json:"capitalLeaseObligationsNonCurrent"` - DeferredRevenueNonCurrent string `json:"deferredRevenueNonCurrent"` - DeferredTaxLiabilitiesNonCurrent string `json:"deferredTaxLiabilitiesNonCurrent"` - OtherNonCurrentLiabilities string `json:"otherNonCurrentLiabilities"` - TotalNonCurrentLiabilities string `json:"totalNonCurrentLiabilities"` - OtherLiabilities string `json:"otherLiabilities"` - CapitalLeaseObligations string `json:"capitalLeaseObligations"` - TotalLiabilities string `json:"totalLiabilities"` - TreasuryStock string `json:"treasuryStock"` - PreferredStock string `json:"preferredStock"` - CommonStock string `json:"commonStock"` - RetainedEarnings string `json:"retainedEarnings"` - AdditionalPaidInCapital string `json:"additionalPaidInCapital"` - AccumulatedOtherComprehensiveIncomeLoss string `json:"accumulatedOtherComprehensiveIncomeLoss"` - OtherTotalStockholdersEquity string `json:"otherTotalStockholdersEquity"` - TotalStockholdersEquity string `json:"totalStockholdersEquity"` - TotalEquity string `json:"totalEquity"` - MinorityInterest string `json:"minorityInterest"` - TotalLiabilitiesAndTotalEquity string `json:"totalLiabilitiesAndTotalEquity"` - TotalInvestments string `json:"totalInvestments"` - TotalDebt string `json:"totalDebt"` - NetDebt string `json:"netDebt"` + CashAndCashEquivalents *string `json:"cashAndCashEquivalents"` + ShortTermInvestments *string `json:"shortTermInvestments"` + CashAndShortTermInvestments *string `json:"cashAndShortTermInvestments"` + NetReceivables *string `json:"netReceivables"` + AccountsReceivables *string `json:"accountsReceivables"` + OtherReceivables *string `json:"otherReceivables"` + Inventory *string `json:"inventory"` + Prepaids *string `json:"prepaids"` + OtherCurrentAssets *string `json:"otherCurrentAssets"` + TotalCurrentAssets *string `json:"totalCurrentAssets"` + PropertyPlantEquipmentNet *string `json:"propertyPlantEquipmentNet"` + Goodwill *string `json:"goodwill"` + IntangibleAssets *string `json:"intangibleAssets"` + GoodwillAndIntangibleAssets *string `json:"goodwillAndIntangibleAssets"` + LongTermInvestments *string `json:"longTermInvestments"` + TaxAssets *string `json:"taxAssets"` + OtherNonCurrentAssets *string `json:"otherNonCurrentAssets"` + TotalNonCurrentAssets *string `json:"totalNonCurrentAssets"` + OtherAssets *string `json:"otherAssets"` + TotalAssets *string `json:"totalAssets"` + TotalPayables *string `json:"totalPayables"` + AccountPayables *string `json:"accountPayables"` + OtherPayables *string `json:"otherPayables"` + AccruedExpenses *string `json:"accruedExpenses"` + ShortTermDebt *string `json:"shortTermDebt"` + CapitalLeaseObligationsCurrent *string `json:"capitalLeaseObligationsCurrent"` + TaxPayables *string `json:"taxPayables"` + DeferredRevenue *string `json:"deferredRevenue"` + OtherCurrentLiabilities *string `json:"otherCurrentLiabilities"` + TotalCurrentLiabilities *string `json:"totalCurrentLiabilities"` + LongTermDebt *string `json:"longTermDebt"` + CapitalLeaseObligationsNonCurrent *string `json:"capitalLeaseObligationsNonCurrent"` + DeferredRevenueNonCurrent *string `json:"deferredRevenueNonCurrent"` + DeferredTaxLiabilitiesNonCurrent *string `json:"deferredTaxLiabilitiesNonCurrent"` + OtherNonCurrentLiabilities *string `json:"otherNonCurrentLiabilities"` + TotalNonCurrentLiabilities *string `json:"totalNonCurrentLiabilities"` + OtherLiabilities *string `json:"otherLiabilities"` + CapitalLeaseObligations *string `json:"capitalLeaseObligations"` + TotalLiabilities *string `json:"totalLiabilities"` + TreasuryStock *string `json:"treasuryStock"` + PreferredStock *string `json:"preferredStock"` + CommonStock *string `json:"commonStock"` + RetainedEarnings *string `json:"retainedEarnings"` + AdditionalPaidInCapital *string `json:"additionalPaidInCapital"` + AccumulatedOtherComprehensiveIncomeLoss *string `json:"accumulatedOtherComprehensiveIncomeLoss"` + OtherTotalStockholdersEquity *string `json:"otherTotalStockholdersEquity"` + TotalStockholdersEquity *string `json:"totalStockholdersEquity"` + TotalEquity *string `json:"totalEquity"` + MinorityInterest *string `json:"minorityInterest"` + TotalLiabilitiesAndTotalEquity *string `json:"totalLiabilitiesAndTotalEquity"` + TotalInvestments *string `json:"totalInvestments"` + TotalDebt *string `json:"totalDebt"` + NetDebt *string `json:"netDebt"` } // bulkBalanceSheetStatementShadow mirrors BulkBalanceSheetStatement with a @@ -167,112 +167,6 @@ func (m *BulkBalanceSheetStatement) UnmarshalJSONFrom(dec *jsontext.Decoder) err return missingMemberError("BulkBalanceSheetStatement", "fiscalYear") case shadow.Period == nil: return missingMemberError("BulkBalanceSheetStatement", "period") - case shadow.CashAndCashEquivalents == nil: - return missingMemberError("BulkBalanceSheetStatement", "cashAndCashEquivalents") - case shadow.ShortTermInvestments == nil: - return missingMemberError("BulkBalanceSheetStatement", "shortTermInvestments") - case shadow.CashAndShortTermInvestments == nil: - return missingMemberError("BulkBalanceSheetStatement", "cashAndShortTermInvestments") - case shadow.NetReceivables == nil: - return missingMemberError("BulkBalanceSheetStatement", "netReceivables") - case shadow.AccountsReceivables == nil: - return missingMemberError("BulkBalanceSheetStatement", "accountsReceivables") - case shadow.OtherReceivables == nil: - return missingMemberError("BulkBalanceSheetStatement", "otherReceivables") - case shadow.Inventory == nil: - return missingMemberError("BulkBalanceSheetStatement", "inventory") - case shadow.Prepaids == nil: - return missingMemberError("BulkBalanceSheetStatement", "prepaids") - case shadow.OtherCurrentAssets == nil: - return missingMemberError("BulkBalanceSheetStatement", "otherCurrentAssets") - case shadow.TotalCurrentAssets == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalCurrentAssets") - case shadow.PropertyPlantEquipmentNet == nil: - return missingMemberError("BulkBalanceSheetStatement", "propertyPlantEquipmentNet") - case shadow.Goodwill == nil: - return missingMemberError("BulkBalanceSheetStatement", "goodwill") - case shadow.IntangibleAssets == nil: - return missingMemberError("BulkBalanceSheetStatement", "intangibleAssets") - case shadow.GoodwillAndIntangibleAssets == nil: - return missingMemberError("BulkBalanceSheetStatement", "goodwillAndIntangibleAssets") - case shadow.LongTermInvestments == nil: - return missingMemberError("BulkBalanceSheetStatement", "longTermInvestments") - case shadow.TaxAssets == nil: - return missingMemberError("BulkBalanceSheetStatement", "taxAssets") - case shadow.OtherNonCurrentAssets == nil: - return missingMemberError("BulkBalanceSheetStatement", "otherNonCurrentAssets") - case shadow.TotalNonCurrentAssets == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalNonCurrentAssets") - case shadow.OtherAssets == nil: - return missingMemberError("BulkBalanceSheetStatement", "otherAssets") - case shadow.TotalAssets == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalAssets") - case shadow.TotalPayables == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalPayables") - case shadow.AccountPayables == nil: - return missingMemberError("BulkBalanceSheetStatement", "accountPayables") - case shadow.OtherPayables == nil: - return missingMemberError("BulkBalanceSheetStatement", "otherPayables") - case shadow.AccruedExpenses == nil: - return missingMemberError("BulkBalanceSheetStatement", "accruedExpenses") - case shadow.ShortTermDebt == nil: - return missingMemberError("BulkBalanceSheetStatement", "shortTermDebt") - case shadow.CapitalLeaseObligationsCurrent == nil: - return missingMemberError("BulkBalanceSheetStatement", "capitalLeaseObligationsCurrent") - case shadow.TaxPayables == nil: - return missingMemberError("BulkBalanceSheetStatement", "taxPayables") - case shadow.DeferredRevenue == nil: - return missingMemberError("BulkBalanceSheetStatement", "deferredRevenue") - case shadow.OtherCurrentLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatement", "otherCurrentLiabilities") - case shadow.TotalCurrentLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalCurrentLiabilities") - case shadow.LongTermDebt == nil: - return missingMemberError("BulkBalanceSheetStatement", "longTermDebt") - case shadow.CapitalLeaseObligationsNonCurrent == nil: - return missingMemberError("BulkBalanceSheetStatement", "capitalLeaseObligationsNonCurrent") - case shadow.DeferredRevenueNonCurrent == nil: - return missingMemberError("BulkBalanceSheetStatement", "deferredRevenueNonCurrent") - case shadow.DeferredTaxLiabilitiesNonCurrent == nil: - return missingMemberError("BulkBalanceSheetStatement", "deferredTaxLiabilitiesNonCurrent") - case shadow.OtherNonCurrentLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatement", "otherNonCurrentLiabilities") - case shadow.TotalNonCurrentLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalNonCurrentLiabilities") - case shadow.OtherLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatement", "otherLiabilities") - case shadow.CapitalLeaseObligations == nil: - return missingMemberError("BulkBalanceSheetStatement", "capitalLeaseObligations") - case shadow.TotalLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalLiabilities") - case shadow.TreasuryStock == nil: - return missingMemberError("BulkBalanceSheetStatement", "treasuryStock") - case shadow.PreferredStock == nil: - return missingMemberError("BulkBalanceSheetStatement", "preferredStock") - case shadow.CommonStock == nil: - return missingMemberError("BulkBalanceSheetStatement", "commonStock") - case shadow.RetainedEarnings == nil: - return missingMemberError("BulkBalanceSheetStatement", "retainedEarnings") - case shadow.AdditionalPaidInCapital == nil: - return missingMemberError("BulkBalanceSheetStatement", "additionalPaidInCapital") - case shadow.AccumulatedOtherComprehensiveIncomeLoss == nil: - return missingMemberError("BulkBalanceSheetStatement", "accumulatedOtherComprehensiveIncomeLoss") - case shadow.OtherTotalStockholdersEquity == nil: - return missingMemberError("BulkBalanceSheetStatement", "otherTotalStockholdersEquity") - case shadow.TotalStockholdersEquity == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalStockholdersEquity") - case shadow.TotalEquity == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalEquity") - case shadow.MinorityInterest == nil: - return missingMemberError("BulkBalanceSheetStatement", "minorityInterest") - case shadow.TotalLiabilitiesAndTotalEquity == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalLiabilitiesAndTotalEquity") - case shadow.TotalInvestments == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalInvestments") - case shadow.TotalDebt == nil: - return missingMemberError("BulkBalanceSheetStatement", "totalDebt") - case shadow.NetDebt == nil: - return missingMemberError("BulkBalanceSheetStatement", "netDebt") } *m = BulkBalanceSheetStatement{ Date: *shadow.Date, @@ -283,59 +177,59 @@ func (m *BulkBalanceSheetStatement) UnmarshalJSONFrom(dec *jsontext.Decoder) err AcceptedDate: *shadow.AcceptedDate, FiscalYear: *shadow.FiscalYear, Period: *shadow.Period, - CashAndCashEquivalents: *shadow.CashAndCashEquivalents, - ShortTermInvestments: *shadow.ShortTermInvestments, - CashAndShortTermInvestments: *shadow.CashAndShortTermInvestments, - NetReceivables: *shadow.NetReceivables, - AccountsReceivables: *shadow.AccountsReceivables, - OtherReceivables: *shadow.OtherReceivables, - Inventory: *shadow.Inventory, - Prepaids: *shadow.Prepaids, - OtherCurrentAssets: *shadow.OtherCurrentAssets, - TotalCurrentAssets: *shadow.TotalCurrentAssets, - PropertyPlantEquipmentNet: *shadow.PropertyPlantEquipmentNet, - Goodwill: *shadow.Goodwill, - IntangibleAssets: *shadow.IntangibleAssets, - GoodwillAndIntangibleAssets: *shadow.GoodwillAndIntangibleAssets, - LongTermInvestments: *shadow.LongTermInvestments, - TaxAssets: *shadow.TaxAssets, - OtherNonCurrentAssets: *shadow.OtherNonCurrentAssets, - TotalNonCurrentAssets: *shadow.TotalNonCurrentAssets, - OtherAssets: *shadow.OtherAssets, - TotalAssets: *shadow.TotalAssets, - TotalPayables: *shadow.TotalPayables, - AccountPayables: *shadow.AccountPayables, - OtherPayables: *shadow.OtherPayables, - AccruedExpenses: *shadow.AccruedExpenses, - ShortTermDebt: *shadow.ShortTermDebt, - CapitalLeaseObligationsCurrent: *shadow.CapitalLeaseObligationsCurrent, - TaxPayables: *shadow.TaxPayables, - DeferredRevenue: *shadow.DeferredRevenue, - OtherCurrentLiabilities: *shadow.OtherCurrentLiabilities, - TotalCurrentLiabilities: *shadow.TotalCurrentLiabilities, - LongTermDebt: *shadow.LongTermDebt, - CapitalLeaseObligationsNonCurrent: *shadow.CapitalLeaseObligationsNonCurrent, - DeferredRevenueNonCurrent: *shadow.DeferredRevenueNonCurrent, - DeferredTaxLiabilitiesNonCurrent: *shadow.DeferredTaxLiabilitiesNonCurrent, - OtherNonCurrentLiabilities: *shadow.OtherNonCurrentLiabilities, - TotalNonCurrentLiabilities: *shadow.TotalNonCurrentLiabilities, - OtherLiabilities: *shadow.OtherLiabilities, - CapitalLeaseObligations: *shadow.CapitalLeaseObligations, - TotalLiabilities: *shadow.TotalLiabilities, - TreasuryStock: *shadow.TreasuryStock, - PreferredStock: *shadow.PreferredStock, - CommonStock: *shadow.CommonStock, - RetainedEarnings: *shadow.RetainedEarnings, - AdditionalPaidInCapital: *shadow.AdditionalPaidInCapital, - AccumulatedOtherComprehensiveIncomeLoss: *shadow.AccumulatedOtherComprehensiveIncomeLoss, - OtherTotalStockholdersEquity: *shadow.OtherTotalStockholdersEquity, - TotalStockholdersEquity: *shadow.TotalStockholdersEquity, - TotalEquity: *shadow.TotalEquity, - MinorityInterest: *shadow.MinorityInterest, - TotalLiabilitiesAndTotalEquity: *shadow.TotalLiabilitiesAndTotalEquity, - TotalInvestments: *shadow.TotalInvestments, - TotalDebt: *shadow.TotalDebt, - NetDebt: *shadow.NetDebt, + CashAndCashEquivalents: shadow.CashAndCashEquivalents, + ShortTermInvestments: shadow.ShortTermInvestments, + CashAndShortTermInvestments: shadow.CashAndShortTermInvestments, + NetReceivables: shadow.NetReceivables, + AccountsReceivables: shadow.AccountsReceivables, + OtherReceivables: shadow.OtherReceivables, + Inventory: shadow.Inventory, + Prepaids: shadow.Prepaids, + OtherCurrentAssets: shadow.OtherCurrentAssets, + TotalCurrentAssets: shadow.TotalCurrentAssets, + PropertyPlantEquipmentNet: shadow.PropertyPlantEquipmentNet, + Goodwill: shadow.Goodwill, + IntangibleAssets: shadow.IntangibleAssets, + GoodwillAndIntangibleAssets: shadow.GoodwillAndIntangibleAssets, + LongTermInvestments: shadow.LongTermInvestments, + TaxAssets: shadow.TaxAssets, + OtherNonCurrentAssets: shadow.OtherNonCurrentAssets, + TotalNonCurrentAssets: shadow.TotalNonCurrentAssets, + OtherAssets: shadow.OtherAssets, + TotalAssets: shadow.TotalAssets, + TotalPayables: shadow.TotalPayables, + AccountPayables: shadow.AccountPayables, + OtherPayables: shadow.OtherPayables, + AccruedExpenses: shadow.AccruedExpenses, + ShortTermDebt: shadow.ShortTermDebt, + CapitalLeaseObligationsCurrent: shadow.CapitalLeaseObligationsCurrent, + TaxPayables: shadow.TaxPayables, + DeferredRevenue: shadow.DeferredRevenue, + OtherCurrentLiabilities: shadow.OtherCurrentLiabilities, + TotalCurrentLiabilities: shadow.TotalCurrentLiabilities, + LongTermDebt: shadow.LongTermDebt, + CapitalLeaseObligationsNonCurrent: shadow.CapitalLeaseObligationsNonCurrent, + DeferredRevenueNonCurrent: shadow.DeferredRevenueNonCurrent, + DeferredTaxLiabilitiesNonCurrent: shadow.DeferredTaxLiabilitiesNonCurrent, + OtherNonCurrentLiabilities: shadow.OtherNonCurrentLiabilities, + TotalNonCurrentLiabilities: shadow.TotalNonCurrentLiabilities, + OtherLiabilities: shadow.OtherLiabilities, + CapitalLeaseObligations: shadow.CapitalLeaseObligations, + TotalLiabilities: shadow.TotalLiabilities, + TreasuryStock: shadow.TreasuryStock, + PreferredStock: shadow.PreferredStock, + CommonStock: shadow.CommonStock, + RetainedEarnings: shadow.RetainedEarnings, + AdditionalPaidInCapital: shadow.AdditionalPaidInCapital, + AccumulatedOtherComprehensiveIncomeLoss: shadow.AccumulatedOtherComprehensiveIncomeLoss, + OtherTotalStockholdersEquity: shadow.OtherTotalStockholdersEquity, + TotalStockholdersEquity: shadow.TotalStockholdersEquity, + TotalEquity: shadow.TotalEquity, + MinorityInterest: shadow.MinorityInterest, + TotalLiabilitiesAndTotalEquity: shadow.TotalLiabilitiesAndTotalEquity, + TotalInvestments: shadow.TotalInvestments, + TotalDebt: shadow.TotalDebt, + NetDebt: shadow.NetDebt, } return nil } @@ -343,62 +237,62 @@ func (m *BulkBalanceSheetStatement) UnmarshalJSONFrom(dec *jsontext.Decoder) err // BulkBalanceSheetStatementGrowth is one worldwide bulk // balance-sheet-statement-growth row. type BulkBalanceSheetStatementGrowth struct { - Symbol string `json:"symbol"` - Date Date `json:"date"` - FiscalYear string `json:"fiscalYear"` - Period string `json:"period"` - ReportedCurrency string `json:"reportedCurrency"` - GrowthCashAndCashEquivalents string `json:"growthCashAndCashEquivalents"` - GrowthShortTermInvestments string `json:"growthShortTermInvestments"` - GrowthCashAndShortTermInvestments string `json:"growthCashAndShortTermInvestments"` - GrowthNetReceivables string `json:"growthNetReceivables"` - GrowthInventory string `json:"growthInventory"` - GrowthOtherCurrentAssets string `json:"growthOtherCurrentAssets"` - GrowthTotalCurrentAssets string `json:"growthTotalCurrentAssets"` - GrowthPropertyPlantEquipmentNet string `json:"growthPropertyPlantEquipmentNet"` - GrowthGoodwill string `json:"growthGoodwill"` - GrowthIntangibleAssets string `json:"growthIntangibleAssets"` - GrowthGoodwillAndIntangibleAssets string `json:"growthGoodwillAndIntangibleAssets"` - GrowthLongTermInvestments string `json:"growthLongTermInvestments"` - GrowthTaxAssets string `json:"growthTaxAssets"` - GrowthOtherNonCurrentAssets string `json:"growthOtherNonCurrentAssets"` - GrowthTotalNonCurrentAssets string `json:"growthTotalNonCurrentAssets"` - GrowthOtherAssets string `json:"growthOtherAssets"` - GrowthTotalAssets string `json:"growthTotalAssets"` - GrowthAccountPayables string `json:"growthAccountPayables"` - GrowthShortTermDebt string `json:"growthShortTermDebt"` - GrowthTaxPayables string `json:"growthTaxPayables"` - GrowthDeferredRevenue string `json:"growthDeferredRevenue"` - GrowthOtherCurrentLiabilities string `json:"growthOtherCurrentLiabilities"` - GrowthTotalCurrentLiabilities string `json:"growthTotalCurrentLiabilities"` - GrowthLongTermDebt string `json:"growthLongTermDebt"` - GrowthDeferredRevenueNonCurrent string `json:"growthDeferredRevenueNonCurrent"` - GrowthDeferredTaxLiabilitiesNonCurrent string `json:"growthDeferredTaxLiabilitiesNonCurrent"` - GrowthOtherNonCurrentLiabilities string `json:"growthOtherNonCurrentLiabilities"` - GrowthTotalNonCurrentLiabilities string `json:"growthTotalNonCurrentLiabilities"` - GrowthOtherLiabilities string `json:"growthOtherLiabilities"` - GrowthTotalLiabilities string `json:"growthTotalLiabilities"` - GrowthPreferredStock string `json:"growthPreferredStock"` - GrowthCommonStock string `json:"growthCommonStock"` - GrowthRetainedEarnings string `json:"growthRetainedEarnings"` - GrowthAccumulatedOtherComprehensiveIncomeLoss string `json:"growthAccumulatedOtherComprehensiveIncomeLoss"` - GrowthOtherTotalStockholdersEquity string `json:"growthOthertotalStockholdersEquity"` - GrowthTotalStockholdersEquity string `json:"growthTotalStockholdersEquity"` - GrowthMinorityInterest string `json:"growthMinorityInterest"` - GrowthTotalEquity string `json:"growthTotalEquity"` - GrowthTotalLiabilitiesAndStockholdersEquity string `json:"growthTotalLiabilitiesAndStockholdersEquity"` - GrowthTotalInvestments string `json:"growthTotalInvestments"` - GrowthTotalDebt string `json:"growthTotalDebt"` - GrowthNetDebt string `json:"growthNetDebt"` - GrowthAccountsReceivables string `json:"growthAccountsReceivables"` - GrowthOtherReceivables string `json:"growthOtherReceivables"` - GrowthPrepaids string `json:"growthPrepaids"` - GrowthTotalPayables string `json:"growthTotalPayables"` - GrowthOtherPayables string `json:"growthOtherPayables"` - GrowthAccruedExpenses string `json:"growthAccruedExpenses"` - GrowthCapitalLeaseObligationsCurrent string `json:"growthCapitalLeaseObligationsCurrent"` - GrowthAdditionalPaidInCapital string `json:"growthAdditionalPaidInCapital"` - GrowthTreasuryStock string `json:"growthTreasuryStock"` + Symbol string `json:"symbol"` + Date Date `json:"date"` + FiscalYear string `json:"fiscalYear"` + Period string `json:"period"` + ReportedCurrency string `json:"reportedCurrency"` + GrowthCashAndCashEquivalents *string `json:"growthCashAndCashEquivalents"` + GrowthShortTermInvestments *string `json:"growthShortTermInvestments"` + GrowthCashAndShortTermInvestments *string `json:"growthCashAndShortTermInvestments"` + GrowthNetReceivables *string `json:"growthNetReceivables"` + GrowthInventory *string `json:"growthInventory"` + GrowthOtherCurrentAssets *string `json:"growthOtherCurrentAssets"` + GrowthTotalCurrentAssets *string `json:"growthTotalCurrentAssets"` + GrowthPropertyPlantEquipmentNet *string `json:"growthPropertyPlantEquipmentNet"` + GrowthGoodwill *string `json:"growthGoodwill"` + GrowthIntangibleAssets *string `json:"growthIntangibleAssets"` + GrowthGoodwillAndIntangibleAssets *string `json:"growthGoodwillAndIntangibleAssets"` + GrowthLongTermInvestments *string `json:"growthLongTermInvestments"` + GrowthTaxAssets *string `json:"growthTaxAssets"` + GrowthOtherNonCurrentAssets *string `json:"growthOtherNonCurrentAssets"` + GrowthTotalNonCurrentAssets *string `json:"growthTotalNonCurrentAssets"` + GrowthOtherAssets *string `json:"growthOtherAssets"` + GrowthTotalAssets *string `json:"growthTotalAssets"` + GrowthAccountPayables *string `json:"growthAccountPayables"` + GrowthShortTermDebt *string `json:"growthShortTermDebt"` + GrowthTaxPayables *string `json:"growthTaxPayables"` + GrowthDeferredRevenue *string `json:"growthDeferredRevenue"` + GrowthOtherCurrentLiabilities *string `json:"growthOtherCurrentLiabilities"` + GrowthTotalCurrentLiabilities *string `json:"growthTotalCurrentLiabilities"` + GrowthLongTermDebt *string `json:"growthLongTermDebt"` + GrowthDeferredRevenueNonCurrent *string `json:"growthDeferredRevenueNonCurrent"` + GrowthDeferredTaxLiabilitiesNonCurrent *string `json:"growthDeferredTaxLiabilitiesNonCurrent"` + GrowthOtherNonCurrentLiabilities *string `json:"growthOtherNonCurrentLiabilities"` + GrowthTotalNonCurrentLiabilities *string `json:"growthTotalNonCurrentLiabilities"` + GrowthOtherLiabilities *string `json:"growthOtherLiabilities"` + GrowthTotalLiabilities *string `json:"growthTotalLiabilities"` + GrowthPreferredStock *string `json:"growthPreferredStock"` + GrowthCommonStock *string `json:"growthCommonStock"` + GrowthRetainedEarnings *string `json:"growthRetainedEarnings"` + GrowthAccumulatedOtherComprehensiveIncomeLoss *string `json:"growthAccumulatedOtherComprehensiveIncomeLoss"` + GrowthOtherTotalStockholdersEquity *string `json:"growthOthertotalStockholdersEquity"` + GrowthTotalStockholdersEquity *string `json:"growthTotalStockholdersEquity"` + GrowthMinorityInterest *string `json:"growthMinorityInterest"` + GrowthTotalEquity *string `json:"growthTotalEquity"` + GrowthTotalLiabilitiesAndStockholdersEquity *string `json:"growthTotalLiabilitiesAndStockholdersEquity"` + GrowthTotalInvestments *string `json:"growthTotalInvestments"` + GrowthTotalDebt *string `json:"growthTotalDebt"` + GrowthNetDebt *string `json:"growthNetDebt"` + GrowthAccountsReceivables *string `json:"growthAccountsReceivables"` + GrowthOtherReceivables *string `json:"growthOtherReceivables"` + GrowthPrepaids *string `json:"growthPrepaids"` + GrowthTotalPayables *string `json:"growthTotalPayables"` + GrowthOtherPayables *string `json:"growthOtherPayables"` + GrowthAccruedExpenses *string `json:"growthAccruedExpenses"` + GrowthCapitalLeaseObligationsCurrent *string `json:"growthCapitalLeaseObligationsCurrent"` + GrowthAdditionalPaidInCapital *string `json:"growthAdditionalPaidInCapital"` + GrowthTreasuryStock *string `json:"growthTreasuryStock"` } // bulkBalanceSheetStatementGrowthShadow mirrors @@ -482,108 +376,6 @@ func (m *BulkBalanceSheetStatementGrowth) UnmarshalJSONFrom(dec *jsontext.Decode return missingMemberError("BulkBalanceSheetStatementGrowth", "period") case shadow.ReportedCurrency == nil: return missingMemberError("BulkBalanceSheetStatementGrowth", "reportedCurrency") - case shadow.GrowthCashAndCashEquivalents == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthCashAndCashEquivalents") - case shadow.GrowthShortTermInvestments == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthShortTermInvestments") - case shadow.GrowthCashAndShortTermInvestments == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthCashAndShortTermInvestments") - case shadow.GrowthNetReceivables == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthNetReceivables") - case shadow.GrowthInventory == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthInventory") - case shadow.GrowthOtherCurrentAssets == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthOtherCurrentAssets") - case shadow.GrowthTotalCurrentAssets == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalCurrentAssets") - case shadow.GrowthPropertyPlantEquipmentNet == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthPropertyPlantEquipmentNet") - case shadow.GrowthGoodwill == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthGoodwill") - case shadow.GrowthIntangibleAssets == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthIntangibleAssets") - case shadow.GrowthGoodwillAndIntangibleAssets == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthGoodwillAndIntangibleAssets") - case shadow.GrowthLongTermInvestments == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthLongTermInvestments") - case shadow.GrowthTaxAssets == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTaxAssets") - case shadow.GrowthOtherNonCurrentAssets == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthOtherNonCurrentAssets") - case shadow.GrowthTotalNonCurrentAssets == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalNonCurrentAssets") - case shadow.GrowthOtherAssets == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthOtherAssets") - case shadow.GrowthTotalAssets == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalAssets") - case shadow.GrowthAccountPayables == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthAccountPayables") - case shadow.GrowthShortTermDebt == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthShortTermDebt") - case shadow.GrowthTaxPayables == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTaxPayables") - case shadow.GrowthDeferredRevenue == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthDeferredRevenue") - case shadow.GrowthOtherCurrentLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthOtherCurrentLiabilities") - case shadow.GrowthTotalCurrentLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalCurrentLiabilities") - case shadow.GrowthLongTermDebt == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthLongTermDebt") - case shadow.GrowthDeferredRevenueNonCurrent == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthDeferredRevenueNonCurrent") - case shadow.GrowthDeferredTaxLiabilitiesNonCurrent == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthDeferredTaxLiabilitiesNonCurrent") - case shadow.GrowthOtherNonCurrentLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthOtherNonCurrentLiabilities") - case shadow.GrowthTotalNonCurrentLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalNonCurrentLiabilities") - case shadow.GrowthOtherLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthOtherLiabilities") - case shadow.GrowthTotalLiabilities == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalLiabilities") - case shadow.GrowthPreferredStock == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthPreferredStock") - case shadow.GrowthCommonStock == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthCommonStock") - case shadow.GrowthRetainedEarnings == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthRetainedEarnings") - case shadow.GrowthAccumulatedOtherComprehensiveIncomeLoss == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthAccumulatedOtherComprehensiveIncomeLoss") - case shadow.GrowthOtherTotalStockholdersEquity == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthOthertotalStockholdersEquity") - case shadow.GrowthTotalStockholdersEquity == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalStockholdersEquity") - case shadow.GrowthMinorityInterest == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthMinorityInterest") - case shadow.GrowthTotalEquity == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalEquity") - case shadow.GrowthTotalLiabilitiesAndStockholdersEquity == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalLiabilitiesAndStockholdersEquity") - case shadow.GrowthTotalInvestments == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalInvestments") - case shadow.GrowthTotalDebt == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalDebt") - case shadow.GrowthNetDebt == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthNetDebt") - case shadow.GrowthAccountsReceivables == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthAccountsReceivables") - case shadow.GrowthOtherReceivables == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthOtherReceivables") - case shadow.GrowthPrepaids == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthPrepaids") - case shadow.GrowthTotalPayables == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTotalPayables") - case shadow.GrowthOtherPayables == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthOtherPayables") - case shadow.GrowthAccruedExpenses == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthAccruedExpenses") - case shadow.GrowthCapitalLeaseObligationsCurrent == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthCapitalLeaseObligationsCurrent") - case shadow.GrowthAdditionalPaidInCapital == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthAdditionalPaidInCapital") - case shadow.GrowthTreasuryStock == nil: - return missingMemberError("BulkBalanceSheetStatementGrowth", "growthTreasuryStock") } *m = BulkBalanceSheetStatementGrowth{ Symbol: *shadow.Symbol, @@ -591,57 +383,57 @@ func (m *BulkBalanceSheetStatementGrowth) UnmarshalJSONFrom(dec *jsontext.Decode FiscalYear: *shadow.FiscalYear, Period: *shadow.Period, ReportedCurrency: *shadow.ReportedCurrency, - GrowthCashAndCashEquivalents: *shadow.GrowthCashAndCashEquivalents, - GrowthShortTermInvestments: *shadow.GrowthShortTermInvestments, - GrowthCashAndShortTermInvestments: *shadow.GrowthCashAndShortTermInvestments, - GrowthNetReceivables: *shadow.GrowthNetReceivables, - GrowthInventory: *shadow.GrowthInventory, - GrowthOtherCurrentAssets: *shadow.GrowthOtherCurrentAssets, - GrowthTotalCurrentAssets: *shadow.GrowthTotalCurrentAssets, - GrowthPropertyPlantEquipmentNet: *shadow.GrowthPropertyPlantEquipmentNet, - GrowthGoodwill: *shadow.GrowthGoodwill, - GrowthIntangibleAssets: *shadow.GrowthIntangibleAssets, - GrowthGoodwillAndIntangibleAssets: *shadow.GrowthGoodwillAndIntangibleAssets, - GrowthLongTermInvestments: *shadow.GrowthLongTermInvestments, - GrowthTaxAssets: *shadow.GrowthTaxAssets, - GrowthOtherNonCurrentAssets: *shadow.GrowthOtherNonCurrentAssets, - GrowthTotalNonCurrentAssets: *shadow.GrowthTotalNonCurrentAssets, - GrowthOtherAssets: *shadow.GrowthOtherAssets, - GrowthTotalAssets: *shadow.GrowthTotalAssets, - GrowthAccountPayables: *shadow.GrowthAccountPayables, - GrowthShortTermDebt: *shadow.GrowthShortTermDebt, - GrowthTaxPayables: *shadow.GrowthTaxPayables, - GrowthDeferredRevenue: *shadow.GrowthDeferredRevenue, - GrowthOtherCurrentLiabilities: *shadow.GrowthOtherCurrentLiabilities, - GrowthTotalCurrentLiabilities: *shadow.GrowthTotalCurrentLiabilities, - GrowthLongTermDebt: *shadow.GrowthLongTermDebt, - GrowthDeferredRevenueNonCurrent: *shadow.GrowthDeferredRevenueNonCurrent, - GrowthDeferredTaxLiabilitiesNonCurrent: *shadow.GrowthDeferredTaxLiabilitiesNonCurrent, - GrowthOtherNonCurrentLiabilities: *shadow.GrowthOtherNonCurrentLiabilities, - GrowthTotalNonCurrentLiabilities: *shadow.GrowthTotalNonCurrentLiabilities, - GrowthOtherLiabilities: *shadow.GrowthOtherLiabilities, - GrowthTotalLiabilities: *shadow.GrowthTotalLiabilities, - GrowthPreferredStock: *shadow.GrowthPreferredStock, - GrowthCommonStock: *shadow.GrowthCommonStock, - GrowthRetainedEarnings: *shadow.GrowthRetainedEarnings, - GrowthAccumulatedOtherComprehensiveIncomeLoss: *shadow.GrowthAccumulatedOtherComprehensiveIncomeLoss, - GrowthOtherTotalStockholdersEquity: *shadow.GrowthOtherTotalStockholdersEquity, - GrowthTotalStockholdersEquity: *shadow.GrowthTotalStockholdersEquity, - GrowthMinorityInterest: *shadow.GrowthMinorityInterest, - GrowthTotalEquity: *shadow.GrowthTotalEquity, - GrowthTotalLiabilitiesAndStockholdersEquity: *shadow.GrowthTotalLiabilitiesAndStockholdersEquity, - GrowthTotalInvestments: *shadow.GrowthTotalInvestments, - GrowthTotalDebt: *shadow.GrowthTotalDebt, - GrowthNetDebt: *shadow.GrowthNetDebt, - GrowthAccountsReceivables: *shadow.GrowthAccountsReceivables, - GrowthOtherReceivables: *shadow.GrowthOtherReceivables, - GrowthPrepaids: *shadow.GrowthPrepaids, - GrowthTotalPayables: *shadow.GrowthTotalPayables, - GrowthOtherPayables: *shadow.GrowthOtherPayables, - GrowthAccruedExpenses: *shadow.GrowthAccruedExpenses, - GrowthCapitalLeaseObligationsCurrent: *shadow.GrowthCapitalLeaseObligationsCurrent, - GrowthAdditionalPaidInCapital: *shadow.GrowthAdditionalPaidInCapital, - GrowthTreasuryStock: *shadow.GrowthTreasuryStock, + GrowthCashAndCashEquivalents: shadow.GrowthCashAndCashEquivalents, + GrowthShortTermInvestments: shadow.GrowthShortTermInvestments, + GrowthCashAndShortTermInvestments: shadow.GrowthCashAndShortTermInvestments, + GrowthNetReceivables: shadow.GrowthNetReceivables, + GrowthInventory: shadow.GrowthInventory, + GrowthOtherCurrentAssets: shadow.GrowthOtherCurrentAssets, + GrowthTotalCurrentAssets: shadow.GrowthTotalCurrentAssets, + GrowthPropertyPlantEquipmentNet: shadow.GrowthPropertyPlantEquipmentNet, + GrowthGoodwill: shadow.GrowthGoodwill, + GrowthIntangibleAssets: shadow.GrowthIntangibleAssets, + GrowthGoodwillAndIntangibleAssets: shadow.GrowthGoodwillAndIntangibleAssets, + GrowthLongTermInvestments: shadow.GrowthLongTermInvestments, + GrowthTaxAssets: shadow.GrowthTaxAssets, + GrowthOtherNonCurrentAssets: shadow.GrowthOtherNonCurrentAssets, + GrowthTotalNonCurrentAssets: shadow.GrowthTotalNonCurrentAssets, + GrowthOtherAssets: shadow.GrowthOtherAssets, + GrowthTotalAssets: shadow.GrowthTotalAssets, + GrowthAccountPayables: shadow.GrowthAccountPayables, + GrowthShortTermDebt: shadow.GrowthShortTermDebt, + GrowthTaxPayables: shadow.GrowthTaxPayables, + GrowthDeferredRevenue: shadow.GrowthDeferredRevenue, + GrowthOtherCurrentLiabilities: shadow.GrowthOtherCurrentLiabilities, + GrowthTotalCurrentLiabilities: shadow.GrowthTotalCurrentLiabilities, + GrowthLongTermDebt: shadow.GrowthLongTermDebt, + GrowthDeferredRevenueNonCurrent: shadow.GrowthDeferredRevenueNonCurrent, + GrowthDeferredTaxLiabilitiesNonCurrent: shadow.GrowthDeferredTaxLiabilitiesNonCurrent, + GrowthOtherNonCurrentLiabilities: shadow.GrowthOtherNonCurrentLiabilities, + GrowthTotalNonCurrentLiabilities: shadow.GrowthTotalNonCurrentLiabilities, + GrowthOtherLiabilities: shadow.GrowthOtherLiabilities, + GrowthTotalLiabilities: shadow.GrowthTotalLiabilities, + GrowthPreferredStock: shadow.GrowthPreferredStock, + GrowthCommonStock: shadow.GrowthCommonStock, + GrowthRetainedEarnings: shadow.GrowthRetainedEarnings, + GrowthAccumulatedOtherComprehensiveIncomeLoss: shadow.GrowthAccumulatedOtherComprehensiveIncomeLoss, + GrowthOtherTotalStockholdersEquity: shadow.GrowthOtherTotalStockholdersEquity, + GrowthTotalStockholdersEquity: shadow.GrowthTotalStockholdersEquity, + GrowthMinorityInterest: shadow.GrowthMinorityInterest, + GrowthTotalEquity: shadow.GrowthTotalEquity, + GrowthTotalLiabilitiesAndStockholdersEquity: shadow.GrowthTotalLiabilitiesAndStockholdersEquity, + GrowthTotalInvestments: shadow.GrowthTotalInvestments, + GrowthTotalDebt: shadow.GrowthTotalDebt, + GrowthNetDebt: shadow.GrowthNetDebt, + GrowthAccountsReceivables: shadow.GrowthAccountsReceivables, + GrowthOtherReceivables: shadow.GrowthOtherReceivables, + GrowthPrepaids: shadow.GrowthPrepaids, + GrowthTotalPayables: shadow.GrowthTotalPayables, + GrowthOtherPayables: shadow.GrowthOtherPayables, + GrowthAccruedExpenses: shadow.GrowthAccruedExpenses, + GrowthCapitalLeaseObligationsCurrent: shadow.GrowthCapitalLeaseObligationsCurrent, + GrowthAdditionalPaidInCapital: shadow.GrowthAdditionalPaidInCapital, + GrowthTreasuryStock: shadow.GrowthTreasuryStock, } return nil } @@ -656,45 +448,45 @@ type BulkCashFlowStatement struct { AcceptedDate DateTime `json:"acceptedDate"` FiscalYear string `json:"fiscalYear"` Period string `json:"period"` - NetIncome string `json:"netIncome"` - DepreciationAndAmortization string `json:"depreciationAndAmortization"` - DeferredIncomeTax string `json:"deferredIncomeTax"` - StockBasedCompensation string `json:"stockBasedCompensation"` - ChangeInWorkingCapital string `json:"changeInWorkingCapital"` - AccountsReceivables string `json:"accountsReceivables"` - Inventory string `json:"inventory"` - AccountsPayables string `json:"accountsPayables"` - OtherWorkingCapital string `json:"otherWorkingCapital"` - OtherNonCashItems string `json:"otherNonCashItems"` - NetCashProvidedByOperatingActivities string `json:"netCashProvidedByOperatingActivities"` - InvestmentsInPropertyPlantAndEquipment string `json:"investmentsInPropertyPlantAndEquipment"` - AcquisitionsNet string `json:"acquisitionsNet"` - PurchasesOfInvestments string `json:"purchasesOfInvestments"` - SalesMaturitiesOfInvestments string `json:"salesMaturitiesOfInvestments"` - OtherInvestingActivities string `json:"otherInvestingActivities"` - NetCashProvidedByInvestingActivities string `json:"netCashProvidedByInvestingActivities"` - NetDebtIssuance string `json:"netDebtIssuance"` - LongTermNetDebtIssuance string `json:"longTermNetDebtIssuance"` - ShortTermNetDebtIssuance string `json:"shortTermNetDebtIssuance"` - NetStockIssuance string `json:"netStockIssuance"` - NetCommonStockIssuance string `json:"netCommonStockIssuance"` - CommonStockIssuance string `json:"commonStockIssuance"` - CommonStockRepurchased string `json:"commonStockRepurchased"` - NetPreferredStockIssuance string `json:"netPreferredStockIssuance"` - NetDividendsPaid string `json:"netDividendsPaid"` - CommonDividendsPaid string `json:"commonDividendsPaid"` - PreferredDividendsPaid string `json:"preferredDividendsPaid"` - OtherFinancingActivities string `json:"otherFinancingActivities"` - NetCashProvidedByFinancingActivities string `json:"netCashProvidedByFinancingActivities"` - EffectOfForexChangesOnCash string `json:"effectOfForexChangesOnCash"` - NetChangeInCash string `json:"netChangeInCash"` - CashAtEndOfPeriod string `json:"cashAtEndOfPeriod"` - CashAtBeginningOfPeriod string `json:"cashAtBeginningOfPeriod"` - OperatingCashFlow string `json:"operatingCashFlow"` - CapitalExpenditure string `json:"capitalExpenditure"` - FreeCashFlow string `json:"freeCashFlow"` - IncomeTaxesPaid string `json:"incomeTaxesPaid"` - InterestPaid string `json:"interestPaid"` + NetIncome *string `json:"netIncome"` + DepreciationAndAmortization *string `json:"depreciationAndAmortization"` + DeferredIncomeTax *string `json:"deferredIncomeTax"` + StockBasedCompensation *string `json:"stockBasedCompensation"` + ChangeInWorkingCapital *string `json:"changeInWorkingCapital"` + AccountsReceivables *string `json:"accountsReceivables"` + Inventory *string `json:"inventory"` + AccountsPayables *string `json:"accountsPayables"` + OtherWorkingCapital *string `json:"otherWorkingCapital"` + OtherNonCashItems *string `json:"otherNonCashItems"` + NetCashProvidedByOperatingActivities *string `json:"netCashProvidedByOperatingActivities"` + InvestmentsInPropertyPlantAndEquipment *string `json:"investmentsInPropertyPlantAndEquipment"` + AcquisitionsNet *string `json:"acquisitionsNet"` + PurchasesOfInvestments *string `json:"purchasesOfInvestments"` + SalesMaturitiesOfInvestments *string `json:"salesMaturitiesOfInvestments"` + OtherInvestingActivities *string `json:"otherInvestingActivities"` + NetCashProvidedByInvestingActivities *string `json:"netCashProvidedByInvestingActivities"` + NetDebtIssuance *string `json:"netDebtIssuance"` + LongTermNetDebtIssuance *string `json:"longTermNetDebtIssuance"` + ShortTermNetDebtIssuance *string `json:"shortTermNetDebtIssuance"` + NetStockIssuance *string `json:"netStockIssuance"` + NetCommonStockIssuance *string `json:"netCommonStockIssuance"` + CommonStockIssuance *string `json:"commonStockIssuance"` + CommonStockRepurchased *string `json:"commonStockRepurchased"` + NetPreferredStockIssuance *string `json:"netPreferredStockIssuance"` + NetDividendsPaid *string `json:"netDividendsPaid"` + CommonDividendsPaid *string `json:"commonDividendsPaid"` + PreferredDividendsPaid *string `json:"preferredDividendsPaid"` + OtherFinancingActivities *string `json:"otherFinancingActivities"` + NetCashProvidedByFinancingActivities *string `json:"netCashProvidedByFinancingActivities"` + EffectOfForexChangesOnCash *string `json:"effectOfForexChangesOnCash"` + NetChangeInCash *string `json:"netChangeInCash"` + CashAtEndOfPeriod *string `json:"cashAtEndOfPeriod"` + CashAtBeginningOfPeriod *string `json:"cashAtBeginningOfPeriod"` + OperatingCashFlow *string `json:"operatingCashFlow"` + CapitalExpenditure *string `json:"capitalExpenditure"` + FreeCashFlow *string `json:"freeCashFlow"` + IncomeTaxesPaid *string `json:"incomeTaxesPaid"` + InterestPaid *string `json:"interestPaid"` } // bulkCashFlowStatementShadow mirrors BulkCashFlowStatement with a pointer or @@ -775,84 +567,6 @@ func (m *BulkCashFlowStatement) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return missingMemberError("BulkCashFlowStatement", "fiscalYear") case shadow.Period == nil: return missingMemberError("BulkCashFlowStatement", "period") - case shadow.NetIncome == nil: - return missingMemberError("BulkCashFlowStatement", "netIncome") - case shadow.DepreciationAndAmortization == nil: - return missingMemberError("BulkCashFlowStatement", "depreciationAndAmortization") - case shadow.DeferredIncomeTax == nil: - return missingMemberError("BulkCashFlowStatement", "deferredIncomeTax") - case shadow.StockBasedCompensation == nil: - return missingMemberError("BulkCashFlowStatement", "stockBasedCompensation") - case shadow.ChangeInWorkingCapital == nil: - return missingMemberError("BulkCashFlowStatement", "changeInWorkingCapital") - case shadow.AccountsReceivables == nil: - return missingMemberError("BulkCashFlowStatement", "accountsReceivables") - case shadow.Inventory == nil: - return missingMemberError("BulkCashFlowStatement", "inventory") - case shadow.AccountsPayables == nil: - return missingMemberError("BulkCashFlowStatement", "accountsPayables") - case shadow.OtherWorkingCapital == nil: - return missingMemberError("BulkCashFlowStatement", "otherWorkingCapital") - case shadow.OtherNonCashItems == nil: - return missingMemberError("BulkCashFlowStatement", "otherNonCashItems") - case shadow.NetCashProvidedByOperatingActivities == nil: - return missingMemberError("BulkCashFlowStatement", "netCashProvidedByOperatingActivities") - case shadow.InvestmentsInPropertyPlantAndEquipment == nil: - return missingMemberError("BulkCashFlowStatement", "investmentsInPropertyPlantAndEquipment") - case shadow.AcquisitionsNet == nil: - return missingMemberError("BulkCashFlowStatement", "acquisitionsNet") - case shadow.PurchasesOfInvestments == nil: - return missingMemberError("BulkCashFlowStatement", "purchasesOfInvestments") - case shadow.SalesMaturitiesOfInvestments == nil: - return missingMemberError("BulkCashFlowStatement", "salesMaturitiesOfInvestments") - case shadow.OtherInvestingActivities == nil: - return missingMemberError("BulkCashFlowStatement", "otherInvestingActivities") - case shadow.NetCashProvidedByInvestingActivities == nil: - return missingMemberError("BulkCashFlowStatement", "netCashProvidedByInvestingActivities") - case shadow.NetDebtIssuance == nil: - return missingMemberError("BulkCashFlowStatement", "netDebtIssuance") - case shadow.LongTermNetDebtIssuance == nil: - return missingMemberError("BulkCashFlowStatement", "longTermNetDebtIssuance") - case shadow.ShortTermNetDebtIssuance == nil: - return missingMemberError("BulkCashFlowStatement", "shortTermNetDebtIssuance") - case shadow.NetStockIssuance == nil: - return missingMemberError("BulkCashFlowStatement", "netStockIssuance") - case shadow.NetCommonStockIssuance == nil: - return missingMemberError("BulkCashFlowStatement", "netCommonStockIssuance") - case shadow.CommonStockIssuance == nil: - return missingMemberError("BulkCashFlowStatement", "commonStockIssuance") - case shadow.CommonStockRepurchased == nil: - return missingMemberError("BulkCashFlowStatement", "commonStockRepurchased") - case shadow.NetPreferredStockIssuance == nil: - return missingMemberError("BulkCashFlowStatement", "netPreferredStockIssuance") - case shadow.NetDividendsPaid == nil: - return missingMemberError("BulkCashFlowStatement", "netDividendsPaid") - case shadow.CommonDividendsPaid == nil: - return missingMemberError("BulkCashFlowStatement", "commonDividendsPaid") - case shadow.PreferredDividendsPaid == nil: - return missingMemberError("BulkCashFlowStatement", "preferredDividendsPaid") - case shadow.OtherFinancingActivities == nil: - return missingMemberError("BulkCashFlowStatement", "otherFinancingActivities") - case shadow.NetCashProvidedByFinancingActivities == nil: - return missingMemberError("BulkCashFlowStatement", "netCashProvidedByFinancingActivities") - case shadow.EffectOfForexChangesOnCash == nil: - return missingMemberError("BulkCashFlowStatement", "effectOfForexChangesOnCash") - case shadow.NetChangeInCash == nil: - return missingMemberError("BulkCashFlowStatement", "netChangeInCash") - case shadow.CashAtEndOfPeriod == nil: - return missingMemberError("BulkCashFlowStatement", "cashAtEndOfPeriod") - case shadow.CashAtBeginningOfPeriod == nil: - return missingMemberError("BulkCashFlowStatement", "cashAtBeginningOfPeriod") - case shadow.OperatingCashFlow == nil: - return missingMemberError("BulkCashFlowStatement", "operatingCashFlow") - case shadow.CapitalExpenditure == nil: - return missingMemberError("BulkCashFlowStatement", "capitalExpenditure") - case shadow.FreeCashFlow == nil: - return missingMemberError("BulkCashFlowStatement", "freeCashFlow") - case shadow.IncomeTaxesPaid == nil: - return missingMemberError("BulkCashFlowStatement", "incomeTaxesPaid") - case shadow.InterestPaid == nil: - return missingMemberError("BulkCashFlowStatement", "interestPaid") } *m = BulkCashFlowStatement{ Date: *shadow.Date, @@ -863,45 +577,45 @@ func (m *BulkCashFlowStatement) UnmarshalJSONFrom(dec *jsontext.Decoder) error { AcceptedDate: *shadow.AcceptedDate, FiscalYear: *shadow.FiscalYear, Period: *shadow.Period, - NetIncome: *shadow.NetIncome, - DepreciationAndAmortization: *shadow.DepreciationAndAmortization, - DeferredIncomeTax: *shadow.DeferredIncomeTax, - StockBasedCompensation: *shadow.StockBasedCompensation, - ChangeInWorkingCapital: *shadow.ChangeInWorkingCapital, - AccountsReceivables: *shadow.AccountsReceivables, - Inventory: *shadow.Inventory, - AccountsPayables: *shadow.AccountsPayables, - OtherWorkingCapital: *shadow.OtherWorkingCapital, - OtherNonCashItems: *shadow.OtherNonCashItems, - NetCashProvidedByOperatingActivities: *shadow.NetCashProvidedByOperatingActivities, - InvestmentsInPropertyPlantAndEquipment: *shadow.InvestmentsInPropertyPlantAndEquipment, - AcquisitionsNet: *shadow.AcquisitionsNet, - PurchasesOfInvestments: *shadow.PurchasesOfInvestments, - SalesMaturitiesOfInvestments: *shadow.SalesMaturitiesOfInvestments, - OtherInvestingActivities: *shadow.OtherInvestingActivities, - NetCashProvidedByInvestingActivities: *shadow.NetCashProvidedByInvestingActivities, - NetDebtIssuance: *shadow.NetDebtIssuance, - LongTermNetDebtIssuance: *shadow.LongTermNetDebtIssuance, - ShortTermNetDebtIssuance: *shadow.ShortTermNetDebtIssuance, - NetStockIssuance: *shadow.NetStockIssuance, - NetCommonStockIssuance: *shadow.NetCommonStockIssuance, - CommonStockIssuance: *shadow.CommonStockIssuance, - CommonStockRepurchased: *shadow.CommonStockRepurchased, - NetPreferredStockIssuance: *shadow.NetPreferredStockIssuance, - NetDividendsPaid: *shadow.NetDividendsPaid, - CommonDividendsPaid: *shadow.CommonDividendsPaid, - PreferredDividendsPaid: *shadow.PreferredDividendsPaid, - OtherFinancingActivities: *shadow.OtherFinancingActivities, - NetCashProvidedByFinancingActivities: *shadow.NetCashProvidedByFinancingActivities, - EffectOfForexChangesOnCash: *shadow.EffectOfForexChangesOnCash, - NetChangeInCash: *shadow.NetChangeInCash, - CashAtEndOfPeriod: *shadow.CashAtEndOfPeriod, - CashAtBeginningOfPeriod: *shadow.CashAtBeginningOfPeriod, - OperatingCashFlow: *shadow.OperatingCashFlow, - CapitalExpenditure: *shadow.CapitalExpenditure, - FreeCashFlow: *shadow.FreeCashFlow, - IncomeTaxesPaid: *shadow.IncomeTaxesPaid, - InterestPaid: *shadow.InterestPaid, + NetIncome: shadow.NetIncome, + DepreciationAndAmortization: shadow.DepreciationAndAmortization, + DeferredIncomeTax: shadow.DeferredIncomeTax, + StockBasedCompensation: shadow.StockBasedCompensation, + ChangeInWorkingCapital: shadow.ChangeInWorkingCapital, + AccountsReceivables: shadow.AccountsReceivables, + Inventory: shadow.Inventory, + AccountsPayables: shadow.AccountsPayables, + OtherWorkingCapital: shadow.OtherWorkingCapital, + OtherNonCashItems: shadow.OtherNonCashItems, + NetCashProvidedByOperatingActivities: shadow.NetCashProvidedByOperatingActivities, + InvestmentsInPropertyPlantAndEquipment: shadow.InvestmentsInPropertyPlantAndEquipment, + AcquisitionsNet: shadow.AcquisitionsNet, + PurchasesOfInvestments: shadow.PurchasesOfInvestments, + SalesMaturitiesOfInvestments: shadow.SalesMaturitiesOfInvestments, + OtherInvestingActivities: shadow.OtherInvestingActivities, + NetCashProvidedByInvestingActivities: shadow.NetCashProvidedByInvestingActivities, + NetDebtIssuance: shadow.NetDebtIssuance, + LongTermNetDebtIssuance: shadow.LongTermNetDebtIssuance, + ShortTermNetDebtIssuance: shadow.ShortTermNetDebtIssuance, + NetStockIssuance: shadow.NetStockIssuance, + NetCommonStockIssuance: shadow.NetCommonStockIssuance, + CommonStockIssuance: shadow.CommonStockIssuance, + CommonStockRepurchased: shadow.CommonStockRepurchased, + NetPreferredStockIssuance: shadow.NetPreferredStockIssuance, + NetDividendsPaid: shadow.NetDividendsPaid, + CommonDividendsPaid: shadow.CommonDividendsPaid, + PreferredDividendsPaid: shadow.PreferredDividendsPaid, + OtherFinancingActivities: shadow.OtherFinancingActivities, + NetCashProvidedByFinancingActivities: shadow.NetCashProvidedByFinancingActivities, + EffectOfForexChangesOnCash: shadow.EffectOfForexChangesOnCash, + NetChangeInCash: shadow.NetChangeInCash, + CashAtEndOfPeriod: shadow.CashAtEndOfPeriod, + CashAtBeginningOfPeriod: shadow.CashAtBeginningOfPeriod, + OperatingCashFlow: shadow.OperatingCashFlow, + CapitalExpenditure: shadow.CapitalExpenditure, + FreeCashFlow: shadow.FreeCashFlow, + IncomeTaxesPaid: shadow.IncomeTaxesPaid, + InterestPaid: shadow.InterestPaid, } return nil } @@ -909,48 +623,48 @@ func (m *BulkCashFlowStatement) UnmarshalJSONFrom(dec *jsontext.Decoder) error { // BulkCashFlowStatementGrowth is one worldwide bulk cash-flow-statement-growth // row. type BulkCashFlowStatementGrowth struct { - Symbol string `json:"symbol"` - Date Date `json:"date"` - FiscalYear string `json:"fiscalYear"` - Period string `json:"period"` - ReportedCurrency string `json:"reportedCurrency"` - GrowthNetIncome string `json:"growthNetIncome"` - GrowthDepreciationAndAmortization string `json:"growthDepreciationAndAmortization"` - GrowthDeferredIncomeTax string `json:"growthDeferredIncomeTax"` - GrowthStockBasedCompensation string `json:"growthStockBasedCompensation"` - GrowthChangeInWorkingCapital string `json:"growthChangeInWorkingCapital"` - GrowthAccountsReceivables string `json:"growthAccountsReceivables"` - GrowthInventory string `json:"growthInventory"` - GrowthAccountsPayables string `json:"growthAccountsPayables"` - GrowthOtherWorkingCapital string `json:"growthOtherWorkingCapital"` - GrowthOtherNonCashItems string `json:"growthOtherNonCashItems"` - GrowthNetCashProvidedByOperatingActivities string `json:"growthNetCashProvidedByOperatingActivites"` - GrowthInvestmentsInPropertyPlantAndEquipment string `json:"growthInvestmentsInPropertyPlantAndEquipment"` - GrowthAcquisitionsNet string `json:"growthAcquisitionsNet"` - GrowthPurchasesOfInvestments string `json:"growthPurchasesOfInvestments"` - GrowthSalesMaturitiesOfInvestments string `json:"growthSalesMaturitiesOfInvestments"` - GrowthOtherInvestingActivities string `json:"growthOtherInvestingActivites"` - GrowthNetCashUsedForInvestingActivities string `json:"growthNetCashUsedForInvestingActivites"` - GrowthDebtRepayment string `json:"growthDebtRepayment"` - GrowthCommonStockIssued string `json:"growthCommonStockIssued"` - GrowthCommonStockRepurchased string `json:"growthCommonStockRepurchased"` - GrowthDividendsPaid string `json:"growthDividendsPaid"` - GrowthOtherFinancingActivities string `json:"growthOtherFinancingActivites"` - GrowthNetCashUsedProvidedByFinancingActivities string `json:"growthNetCashUsedProvidedByFinancingActivities"` - GrowthEffectOfForexChangesOnCash string `json:"growthEffectOfForexChangesOnCash"` - GrowthNetChangeInCash string `json:"growthNetChangeInCash"` - GrowthCashAtEndOfPeriod string `json:"growthCashAtEndOfPeriod"` - GrowthCashAtBeginningOfPeriod string `json:"growthCashAtBeginningOfPeriod"` - GrowthOperatingCashFlow string `json:"growthOperatingCashFlow"` - GrowthCapitalExpenditure string `json:"growthCapitalExpenditure"` - GrowthFreeCashFlow string `json:"growthFreeCashFlow"` - GrowthNetDebtIssuance string `json:"growthNetDebtIssuance"` - GrowthLongTermNetDebtIssuance string `json:"growthLongTermNetDebtIssuance"` - GrowthShortTermNetDebtIssuance string `json:"growthShortTermNetDebtIssuance"` - GrowthNetStockIssuance string `json:"growthNetStockIssuance"` - GrowthPreferredDividendsPaid string `json:"growthPreferredDividendsPaid"` - GrowthIncomeTaxesPaid string `json:"growthIncomeTaxesPaid"` - GrowthInterestPaid string `json:"growthInterestPaid"` + Symbol string `json:"symbol"` + Date Date `json:"date"` + FiscalYear string `json:"fiscalYear"` + Period string `json:"period"` + ReportedCurrency string `json:"reportedCurrency"` + GrowthNetIncome *string `json:"growthNetIncome"` + GrowthDepreciationAndAmortization *string `json:"growthDepreciationAndAmortization"` + GrowthDeferredIncomeTax *string `json:"growthDeferredIncomeTax"` + GrowthStockBasedCompensation *string `json:"growthStockBasedCompensation"` + GrowthChangeInWorkingCapital *string `json:"growthChangeInWorkingCapital"` + GrowthAccountsReceivables *string `json:"growthAccountsReceivables"` + GrowthInventory *string `json:"growthInventory"` + GrowthAccountsPayables *string `json:"growthAccountsPayables"` + GrowthOtherWorkingCapital *string `json:"growthOtherWorkingCapital"` + GrowthOtherNonCashItems *string `json:"growthOtherNonCashItems"` + GrowthNetCashProvidedByOperatingActivities *string `json:"growthNetCashProvidedByOperatingActivites"` + GrowthInvestmentsInPropertyPlantAndEquipment *string `json:"growthInvestmentsInPropertyPlantAndEquipment"` + GrowthAcquisitionsNet *string `json:"growthAcquisitionsNet"` + GrowthPurchasesOfInvestments *string `json:"growthPurchasesOfInvestments"` + GrowthSalesMaturitiesOfInvestments *string `json:"growthSalesMaturitiesOfInvestments"` + GrowthOtherInvestingActivities *string `json:"growthOtherInvestingActivites"` + GrowthNetCashUsedForInvestingActivities *string `json:"growthNetCashUsedForInvestingActivites"` + GrowthDebtRepayment *string `json:"growthDebtRepayment"` + GrowthCommonStockIssued *string `json:"growthCommonStockIssued"` + GrowthCommonStockRepurchased *string `json:"growthCommonStockRepurchased"` + GrowthDividendsPaid *string `json:"growthDividendsPaid"` + GrowthOtherFinancingActivities *string `json:"growthOtherFinancingActivites"` + GrowthNetCashUsedProvidedByFinancingActivities *string `json:"growthNetCashUsedProvidedByFinancingActivities"` + GrowthEffectOfForexChangesOnCash *string `json:"growthEffectOfForexChangesOnCash"` + GrowthNetChangeInCash *string `json:"growthNetChangeInCash"` + GrowthCashAtEndOfPeriod *string `json:"growthCashAtEndOfPeriod"` + GrowthCashAtBeginningOfPeriod *string `json:"growthCashAtBeginningOfPeriod"` + GrowthOperatingCashFlow *string `json:"growthOperatingCashFlow"` + GrowthCapitalExpenditure *string `json:"growthCapitalExpenditure"` + GrowthFreeCashFlow *string `json:"growthFreeCashFlow"` + GrowthNetDebtIssuance *string `json:"growthNetDebtIssuance"` + GrowthLongTermNetDebtIssuance *string `json:"growthLongTermNetDebtIssuance"` + GrowthShortTermNetDebtIssuance *string `json:"growthShortTermNetDebtIssuance"` + GrowthNetStockIssuance *string `json:"growthNetStockIssuance"` + GrowthPreferredDividendsPaid *string `json:"growthPreferredDividendsPaid"` + GrowthIncomeTaxesPaid *string `json:"growthIncomeTaxesPaid"` + GrowthInterestPaid *string `json:"growthInterestPaid"` } // bulkCashFlowStatementGrowthShadow mirrors BulkCashFlowStatementGrowth with a @@ -1020,80 +734,6 @@ func (m *BulkCashFlowStatementGrowth) UnmarshalJSONFrom(dec *jsontext.Decoder) e return missingMemberError("BulkCashFlowStatementGrowth", "period") case shadow.ReportedCurrency == nil: return missingMemberError("BulkCashFlowStatementGrowth", "reportedCurrency") - case shadow.GrowthNetIncome == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthNetIncome") - case shadow.GrowthDepreciationAndAmortization == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthDepreciationAndAmortization") - case shadow.GrowthDeferredIncomeTax == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthDeferredIncomeTax") - case shadow.GrowthStockBasedCompensation == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthStockBasedCompensation") - case shadow.GrowthChangeInWorkingCapital == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthChangeInWorkingCapital") - case shadow.GrowthAccountsReceivables == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthAccountsReceivables") - case shadow.GrowthInventory == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthInventory") - case shadow.GrowthAccountsPayables == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthAccountsPayables") - case shadow.GrowthOtherWorkingCapital == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthOtherWorkingCapital") - case shadow.GrowthOtherNonCashItems == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthOtherNonCashItems") - case shadow.GrowthNetCashProvidedByOperatingActivities == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthNetCashProvidedByOperatingActivites") - case shadow.GrowthInvestmentsInPropertyPlantAndEquipment == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthInvestmentsInPropertyPlantAndEquipment") - case shadow.GrowthAcquisitionsNet == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthAcquisitionsNet") - case shadow.GrowthPurchasesOfInvestments == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthPurchasesOfInvestments") - case shadow.GrowthSalesMaturitiesOfInvestments == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthSalesMaturitiesOfInvestments") - case shadow.GrowthOtherInvestingActivities == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthOtherInvestingActivites") - case shadow.GrowthNetCashUsedForInvestingActivities == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthNetCashUsedForInvestingActivites") - case shadow.GrowthDebtRepayment == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthDebtRepayment") - case shadow.GrowthCommonStockIssued == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthCommonStockIssued") - case shadow.GrowthCommonStockRepurchased == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthCommonStockRepurchased") - case shadow.GrowthDividendsPaid == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthDividendsPaid") - case shadow.GrowthOtherFinancingActivities == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthOtherFinancingActivites") - case shadow.GrowthNetCashUsedProvidedByFinancingActivities == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthNetCashUsedProvidedByFinancingActivities") - case shadow.GrowthEffectOfForexChangesOnCash == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthEffectOfForexChangesOnCash") - case shadow.GrowthNetChangeInCash == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthNetChangeInCash") - case shadow.GrowthCashAtEndOfPeriod == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthCashAtEndOfPeriod") - case shadow.GrowthCashAtBeginningOfPeriod == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthCashAtBeginningOfPeriod") - case shadow.GrowthOperatingCashFlow == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthOperatingCashFlow") - case shadow.GrowthCapitalExpenditure == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthCapitalExpenditure") - case shadow.GrowthFreeCashFlow == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthFreeCashFlow") - case shadow.GrowthNetDebtIssuance == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthNetDebtIssuance") - case shadow.GrowthLongTermNetDebtIssuance == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthLongTermNetDebtIssuance") - case shadow.GrowthShortTermNetDebtIssuance == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthShortTermNetDebtIssuance") - case shadow.GrowthNetStockIssuance == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthNetStockIssuance") - case shadow.GrowthPreferredDividendsPaid == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthPreferredDividendsPaid") - case shadow.GrowthIncomeTaxesPaid == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthIncomeTaxesPaid") - case shadow.GrowthInterestPaid == nil: - return missingMemberError("BulkCashFlowStatementGrowth", "growthInterestPaid") } *m = BulkCashFlowStatementGrowth{ Symbol: *shadow.Symbol, @@ -1101,57 +741,57 @@ func (m *BulkCashFlowStatementGrowth) UnmarshalJSONFrom(dec *jsontext.Decoder) e FiscalYear: *shadow.FiscalYear, Period: *shadow.Period, ReportedCurrency: *shadow.ReportedCurrency, - GrowthNetIncome: *shadow.GrowthNetIncome, - GrowthDepreciationAndAmortization: *shadow.GrowthDepreciationAndAmortization, - GrowthDeferredIncomeTax: *shadow.GrowthDeferredIncomeTax, - GrowthStockBasedCompensation: *shadow.GrowthStockBasedCompensation, - GrowthChangeInWorkingCapital: *shadow.GrowthChangeInWorkingCapital, - GrowthAccountsReceivables: *shadow.GrowthAccountsReceivables, - GrowthInventory: *shadow.GrowthInventory, - GrowthAccountsPayables: *shadow.GrowthAccountsPayables, - GrowthOtherWorkingCapital: *shadow.GrowthOtherWorkingCapital, - GrowthOtherNonCashItems: *shadow.GrowthOtherNonCashItems, - GrowthNetCashProvidedByOperatingActivities: *shadow.GrowthNetCashProvidedByOperatingActivities, - GrowthInvestmentsInPropertyPlantAndEquipment: *shadow.GrowthInvestmentsInPropertyPlantAndEquipment, - GrowthAcquisitionsNet: *shadow.GrowthAcquisitionsNet, - GrowthPurchasesOfInvestments: *shadow.GrowthPurchasesOfInvestments, - GrowthSalesMaturitiesOfInvestments: *shadow.GrowthSalesMaturitiesOfInvestments, - GrowthOtherInvestingActivities: *shadow.GrowthOtherInvestingActivities, - GrowthNetCashUsedForInvestingActivities: *shadow.GrowthNetCashUsedForInvestingActivities, - GrowthDebtRepayment: *shadow.GrowthDebtRepayment, - GrowthCommonStockIssued: *shadow.GrowthCommonStockIssued, - GrowthCommonStockRepurchased: *shadow.GrowthCommonStockRepurchased, - GrowthDividendsPaid: *shadow.GrowthDividendsPaid, - GrowthOtherFinancingActivities: *shadow.GrowthOtherFinancingActivities, - GrowthNetCashUsedProvidedByFinancingActivities: *shadow.GrowthNetCashUsedProvidedByFinancingActivities, - GrowthEffectOfForexChangesOnCash: *shadow.GrowthEffectOfForexChangesOnCash, - GrowthNetChangeInCash: *shadow.GrowthNetChangeInCash, - GrowthCashAtEndOfPeriod: *shadow.GrowthCashAtEndOfPeriod, - GrowthCashAtBeginningOfPeriod: *shadow.GrowthCashAtBeginningOfPeriod, - GrowthOperatingCashFlow: *shadow.GrowthOperatingCashFlow, - GrowthCapitalExpenditure: *shadow.GrowthCapitalExpenditure, - GrowthFreeCashFlow: *shadow.GrowthFreeCashFlow, - GrowthNetDebtIssuance: *shadow.GrowthNetDebtIssuance, - GrowthLongTermNetDebtIssuance: *shadow.GrowthLongTermNetDebtIssuance, - GrowthShortTermNetDebtIssuance: *shadow.GrowthShortTermNetDebtIssuance, - GrowthNetStockIssuance: *shadow.GrowthNetStockIssuance, - GrowthPreferredDividendsPaid: *shadow.GrowthPreferredDividendsPaid, - GrowthIncomeTaxesPaid: *shadow.GrowthIncomeTaxesPaid, - GrowthInterestPaid: *shadow.GrowthInterestPaid, + GrowthNetIncome: shadow.GrowthNetIncome, + GrowthDepreciationAndAmortization: shadow.GrowthDepreciationAndAmortization, + GrowthDeferredIncomeTax: shadow.GrowthDeferredIncomeTax, + GrowthStockBasedCompensation: shadow.GrowthStockBasedCompensation, + GrowthChangeInWorkingCapital: shadow.GrowthChangeInWorkingCapital, + GrowthAccountsReceivables: shadow.GrowthAccountsReceivables, + GrowthInventory: shadow.GrowthInventory, + GrowthAccountsPayables: shadow.GrowthAccountsPayables, + GrowthOtherWorkingCapital: shadow.GrowthOtherWorkingCapital, + GrowthOtherNonCashItems: shadow.GrowthOtherNonCashItems, + GrowthNetCashProvidedByOperatingActivities: shadow.GrowthNetCashProvidedByOperatingActivities, + GrowthInvestmentsInPropertyPlantAndEquipment: shadow.GrowthInvestmentsInPropertyPlantAndEquipment, + GrowthAcquisitionsNet: shadow.GrowthAcquisitionsNet, + GrowthPurchasesOfInvestments: shadow.GrowthPurchasesOfInvestments, + GrowthSalesMaturitiesOfInvestments: shadow.GrowthSalesMaturitiesOfInvestments, + GrowthOtherInvestingActivities: shadow.GrowthOtherInvestingActivities, + GrowthNetCashUsedForInvestingActivities: shadow.GrowthNetCashUsedForInvestingActivities, + GrowthDebtRepayment: shadow.GrowthDebtRepayment, + GrowthCommonStockIssued: shadow.GrowthCommonStockIssued, + GrowthCommonStockRepurchased: shadow.GrowthCommonStockRepurchased, + GrowthDividendsPaid: shadow.GrowthDividendsPaid, + GrowthOtherFinancingActivities: shadow.GrowthOtherFinancingActivities, + GrowthNetCashUsedProvidedByFinancingActivities: shadow.GrowthNetCashUsedProvidedByFinancingActivities, + GrowthEffectOfForexChangesOnCash: shadow.GrowthEffectOfForexChangesOnCash, + GrowthNetChangeInCash: shadow.GrowthNetChangeInCash, + GrowthCashAtEndOfPeriod: shadow.GrowthCashAtEndOfPeriod, + GrowthCashAtBeginningOfPeriod: shadow.GrowthCashAtBeginningOfPeriod, + GrowthOperatingCashFlow: shadow.GrowthOperatingCashFlow, + GrowthCapitalExpenditure: shadow.GrowthCapitalExpenditure, + GrowthFreeCashFlow: shadow.GrowthFreeCashFlow, + GrowthNetDebtIssuance: shadow.GrowthNetDebtIssuance, + GrowthLongTermNetDebtIssuance: shadow.GrowthLongTermNetDebtIssuance, + GrowthShortTermNetDebtIssuance: shadow.GrowthShortTermNetDebtIssuance, + GrowthNetStockIssuance: shadow.GrowthNetStockIssuance, + GrowthPreferredDividendsPaid: shadow.GrowthPreferredDividendsPaid, + GrowthIncomeTaxesPaid: shadow.GrowthIncomeTaxesPaid, + GrowthInterestPaid: shadow.GrowthInterestPaid, } return nil } // BulkEodBar is one worldwide bulk end-of-day price row. type BulkEodBar struct { - Symbol string `json:"symbol"` - Date Date `json:"date"` - Open string `json:"open"` - Low string `json:"low"` - High string `json:"high"` - Close string `json:"close"` - AdjClose string `json:"adjClose"` - Volume string `json:"volume"` + Symbol string `json:"symbol"` + Date Date `json:"date"` + Open *string `json:"open"` + Low *string `json:"low"` + High *string `json:"high"` + Close *string `json:"close"` + AdjClose *string `json:"adjClose"` + Volume *string `json:"volume"` } // bulkEodBarShadow mirrors BulkEodBar with a pointer or raw value for every @@ -1180,28 +820,16 @@ func (m *BulkEodBar) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return missingMemberError("BulkEodBar", "symbol") case shadow.Date == nil: return missingMemberError("BulkEodBar", "date") - case shadow.Open == nil: - return missingMemberError("BulkEodBar", "open") - case shadow.Low == nil: - return missingMemberError("BulkEodBar", "low") - case shadow.High == nil: - return missingMemberError("BulkEodBar", "high") - case shadow.Close == nil: - return missingMemberError("BulkEodBar", "close") - case shadow.AdjClose == nil: - return missingMemberError("BulkEodBar", "adjClose") - case shadow.Volume == nil: - return missingMemberError("BulkEodBar", "volume") } *m = BulkEodBar{ Symbol: *shadow.Symbol, Date: *shadow.Date, - Open: *shadow.Open, - Low: *shadow.Low, - High: *shadow.High, - Close: *shadow.Close, - AdjClose: *shadow.AdjClose, - Volume: *shadow.Volume, + Open: shadow.Open, + Low: shadow.Low, + High: shadow.High, + Close: shadow.Close, + AdjClose: shadow.AdjClose, + Volume: shadow.Volume, } return nil } @@ -1216,37 +844,37 @@ type BulkIncomeStatement struct { AcceptedDate DateTime `json:"acceptedDate"` FiscalYear string `json:"fiscalYear"` Period string `json:"period"` - Revenue string `json:"revenue"` - CostOfRevenue string `json:"costOfRevenue"` - GrossProfit string `json:"grossProfit"` - ResearchAndDevelopmentExpenses string `json:"researchAndDevelopmentExpenses"` - GeneralAndAdministrativeExpenses string `json:"generalAndAdministrativeExpenses"` - SellingAndMarketingExpenses string `json:"sellingAndMarketingExpenses"` - SellingGeneralAndAdministrativeExpenses string `json:"sellingGeneralAndAdministrativeExpenses"` - OtherExpenses string `json:"otherExpenses"` - OperatingExpenses string `json:"operatingExpenses"` - CostAndExpenses string `json:"costAndExpenses"` - NetInterestIncome string `json:"netInterestIncome"` - InterestIncome string `json:"interestIncome"` - InterestExpense string `json:"interestExpense"` - DepreciationAndAmortization string `json:"depreciationAndAmortization"` - Ebitda string `json:"ebitda"` - Ebit string `json:"ebit"` - NonOperatingIncomeExcludingInterest string `json:"nonOperatingIncomeExcludingInterest"` - OperatingIncome string `json:"operatingIncome"` - TotalOtherIncomeExpensesNet string `json:"totalOtherIncomeExpensesNet"` - IncomeBeforeTax string `json:"incomeBeforeTax"` - IncomeTaxExpense string `json:"incomeTaxExpense"` - NetIncomeFromContinuingOperations string `json:"netIncomeFromContinuingOperations"` - NetIncomeFromDiscontinuedOperations string `json:"netIncomeFromDiscontinuedOperations"` - OtherAdjustmentsToNetIncome string `json:"otherAdjustmentsToNetIncome"` - NetIncome string `json:"netIncome"` - NetIncomeDeductions string `json:"netIncomeDeductions"` - BottomLineNetIncome string `json:"bottomLineNetIncome"` - EPS string `json:"eps"` - EPSDiluted string `json:"epsDiluted"` - WeightedAverageShsOut string `json:"weightedAverageShsOut"` - WeightedAverageShsOutDil string `json:"weightedAverageShsOutDil"` + Revenue *string `json:"revenue"` + CostOfRevenue *string `json:"costOfRevenue"` + GrossProfit *string `json:"grossProfit"` + ResearchAndDevelopmentExpenses *string `json:"researchAndDevelopmentExpenses"` + GeneralAndAdministrativeExpenses *string `json:"generalAndAdministrativeExpenses"` + SellingAndMarketingExpenses *string `json:"sellingAndMarketingExpenses"` + SellingGeneralAndAdministrativeExpenses *string `json:"sellingGeneralAndAdministrativeExpenses"` + OtherExpenses *string `json:"otherExpenses"` + OperatingExpenses *string `json:"operatingExpenses"` + CostAndExpenses *string `json:"costAndExpenses"` + NetInterestIncome *string `json:"netInterestIncome"` + InterestIncome *string `json:"interestIncome"` + InterestExpense *string `json:"interestExpense"` + DepreciationAndAmortization *string `json:"depreciationAndAmortization"` + Ebitda *string `json:"ebitda"` + Ebit *string `json:"ebit"` + NonOperatingIncomeExcludingInterest *string `json:"nonOperatingIncomeExcludingInterest"` + OperatingIncome *string `json:"operatingIncome"` + TotalOtherIncomeExpensesNet *string `json:"totalOtherIncomeExpensesNet"` + IncomeBeforeTax *string `json:"incomeBeforeTax"` + IncomeTaxExpense *string `json:"incomeTaxExpense"` + NetIncomeFromContinuingOperations *string `json:"netIncomeFromContinuingOperations"` + NetIncomeFromDiscontinuedOperations *string `json:"netIncomeFromDiscontinuedOperations"` + OtherAdjustmentsToNetIncome *string `json:"otherAdjustmentsToNetIncome"` + NetIncome *string `json:"netIncome"` + NetIncomeDeductions *string `json:"netIncomeDeductions"` + BottomLineNetIncome *string `json:"bottomLineNetIncome"` + EPS *string `json:"eps"` + EPSDiluted *string `json:"epsDiluted"` + WeightedAverageShsOut *string `json:"weightedAverageShsOut"` + WeightedAverageShsOutDil *string `json:"weightedAverageShsOutDil"` } // bulkIncomeStatementShadow mirrors BulkIncomeStatement with a pointer or raw @@ -1319,68 +947,6 @@ func (m *BulkIncomeStatement) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return missingMemberError("BulkIncomeStatement", "fiscalYear") case shadow.Period == nil: return missingMemberError("BulkIncomeStatement", "period") - case shadow.Revenue == nil: - return missingMemberError("BulkIncomeStatement", "revenue") - case shadow.CostOfRevenue == nil: - return missingMemberError("BulkIncomeStatement", "costOfRevenue") - case shadow.GrossProfit == nil: - return missingMemberError("BulkIncomeStatement", "grossProfit") - case shadow.ResearchAndDevelopmentExpenses == nil: - return missingMemberError("BulkIncomeStatement", "researchAndDevelopmentExpenses") - case shadow.GeneralAndAdministrativeExpenses == nil: - return missingMemberError("BulkIncomeStatement", "generalAndAdministrativeExpenses") - case shadow.SellingAndMarketingExpenses == nil: - return missingMemberError("BulkIncomeStatement", "sellingAndMarketingExpenses") - case shadow.SellingGeneralAndAdministrativeExpenses == nil: - return missingMemberError("BulkIncomeStatement", "sellingGeneralAndAdministrativeExpenses") - case shadow.OtherExpenses == nil: - return missingMemberError("BulkIncomeStatement", "otherExpenses") - case shadow.OperatingExpenses == nil: - return missingMemberError("BulkIncomeStatement", "operatingExpenses") - case shadow.CostAndExpenses == nil: - return missingMemberError("BulkIncomeStatement", "costAndExpenses") - case shadow.NetInterestIncome == nil: - return missingMemberError("BulkIncomeStatement", "netInterestIncome") - case shadow.InterestIncome == nil: - return missingMemberError("BulkIncomeStatement", "interestIncome") - case shadow.InterestExpense == nil: - return missingMemberError("BulkIncomeStatement", "interestExpense") - case shadow.DepreciationAndAmortization == nil: - return missingMemberError("BulkIncomeStatement", "depreciationAndAmortization") - case shadow.Ebitda == nil: - return missingMemberError("BulkIncomeStatement", "ebitda") - case shadow.Ebit == nil: - return missingMemberError("BulkIncomeStatement", "ebit") - case shadow.NonOperatingIncomeExcludingInterest == nil: - return missingMemberError("BulkIncomeStatement", "nonOperatingIncomeExcludingInterest") - case shadow.OperatingIncome == nil: - return missingMemberError("BulkIncomeStatement", "operatingIncome") - case shadow.TotalOtherIncomeExpensesNet == nil: - return missingMemberError("BulkIncomeStatement", "totalOtherIncomeExpensesNet") - case shadow.IncomeBeforeTax == nil: - return missingMemberError("BulkIncomeStatement", "incomeBeforeTax") - case shadow.IncomeTaxExpense == nil: - return missingMemberError("BulkIncomeStatement", "incomeTaxExpense") - case shadow.NetIncomeFromContinuingOperations == nil: - return missingMemberError("BulkIncomeStatement", "netIncomeFromContinuingOperations") - case shadow.NetIncomeFromDiscontinuedOperations == nil: - return missingMemberError("BulkIncomeStatement", "netIncomeFromDiscontinuedOperations") - case shadow.OtherAdjustmentsToNetIncome == nil: - return missingMemberError("BulkIncomeStatement", "otherAdjustmentsToNetIncome") - case shadow.NetIncome == nil: - return missingMemberError("BulkIncomeStatement", "netIncome") - case shadow.NetIncomeDeductions == nil: - return missingMemberError("BulkIncomeStatement", "netIncomeDeductions") - case shadow.BottomLineNetIncome == nil: - return missingMemberError("BulkIncomeStatement", "bottomLineNetIncome") - case shadow.EPS == nil: - return missingMemberError("BulkIncomeStatement", "eps") - case shadow.EPSDiluted == nil: - return missingMemberError("BulkIncomeStatement", "epsDiluted") - case shadow.WeightedAverageShsOut == nil: - return missingMemberError("BulkIncomeStatement", "weightedAverageShsOut") - case shadow.WeightedAverageShsOutDil == nil: - return missingMemberError("BulkIncomeStatement", "weightedAverageShsOutDil") } *m = BulkIncomeStatement{ Date: *shadow.Date, @@ -1391,77 +957,77 @@ func (m *BulkIncomeStatement) UnmarshalJSONFrom(dec *jsontext.Decoder) error { AcceptedDate: *shadow.AcceptedDate, FiscalYear: *shadow.FiscalYear, Period: *shadow.Period, - Revenue: *shadow.Revenue, - CostOfRevenue: *shadow.CostOfRevenue, - GrossProfit: *shadow.GrossProfit, - ResearchAndDevelopmentExpenses: *shadow.ResearchAndDevelopmentExpenses, - GeneralAndAdministrativeExpenses: *shadow.GeneralAndAdministrativeExpenses, - SellingAndMarketingExpenses: *shadow.SellingAndMarketingExpenses, - SellingGeneralAndAdministrativeExpenses: *shadow.SellingGeneralAndAdministrativeExpenses, - OtherExpenses: *shadow.OtherExpenses, - OperatingExpenses: *shadow.OperatingExpenses, - CostAndExpenses: *shadow.CostAndExpenses, - NetInterestIncome: *shadow.NetInterestIncome, - InterestIncome: *shadow.InterestIncome, - InterestExpense: *shadow.InterestExpense, - DepreciationAndAmortization: *shadow.DepreciationAndAmortization, - Ebitda: *shadow.Ebitda, - Ebit: *shadow.Ebit, - NonOperatingIncomeExcludingInterest: *shadow.NonOperatingIncomeExcludingInterest, - OperatingIncome: *shadow.OperatingIncome, - TotalOtherIncomeExpensesNet: *shadow.TotalOtherIncomeExpensesNet, - IncomeBeforeTax: *shadow.IncomeBeforeTax, - IncomeTaxExpense: *shadow.IncomeTaxExpense, - NetIncomeFromContinuingOperations: *shadow.NetIncomeFromContinuingOperations, - NetIncomeFromDiscontinuedOperations: *shadow.NetIncomeFromDiscontinuedOperations, - OtherAdjustmentsToNetIncome: *shadow.OtherAdjustmentsToNetIncome, - NetIncome: *shadow.NetIncome, - NetIncomeDeductions: *shadow.NetIncomeDeductions, - BottomLineNetIncome: *shadow.BottomLineNetIncome, - EPS: *shadow.EPS, - EPSDiluted: *shadow.EPSDiluted, - WeightedAverageShsOut: *shadow.WeightedAverageShsOut, - WeightedAverageShsOutDil: *shadow.WeightedAverageShsOutDil, + Revenue: shadow.Revenue, + CostOfRevenue: shadow.CostOfRevenue, + GrossProfit: shadow.GrossProfit, + ResearchAndDevelopmentExpenses: shadow.ResearchAndDevelopmentExpenses, + GeneralAndAdministrativeExpenses: shadow.GeneralAndAdministrativeExpenses, + SellingAndMarketingExpenses: shadow.SellingAndMarketingExpenses, + SellingGeneralAndAdministrativeExpenses: shadow.SellingGeneralAndAdministrativeExpenses, + OtherExpenses: shadow.OtherExpenses, + OperatingExpenses: shadow.OperatingExpenses, + CostAndExpenses: shadow.CostAndExpenses, + NetInterestIncome: shadow.NetInterestIncome, + InterestIncome: shadow.InterestIncome, + InterestExpense: shadow.InterestExpense, + DepreciationAndAmortization: shadow.DepreciationAndAmortization, + Ebitda: shadow.Ebitda, + Ebit: shadow.Ebit, + NonOperatingIncomeExcludingInterest: shadow.NonOperatingIncomeExcludingInterest, + OperatingIncome: shadow.OperatingIncome, + TotalOtherIncomeExpensesNet: shadow.TotalOtherIncomeExpensesNet, + IncomeBeforeTax: shadow.IncomeBeforeTax, + IncomeTaxExpense: shadow.IncomeTaxExpense, + NetIncomeFromContinuingOperations: shadow.NetIncomeFromContinuingOperations, + NetIncomeFromDiscontinuedOperations: shadow.NetIncomeFromDiscontinuedOperations, + OtherAdjustmentsToNetIncome: shadow.OtherAdjustmentsToNetIncome, + NetIncome: shadow.NetIncome, + NetIncomeDeductions: shadow.NetIncomeDeductions, + BottomLineNetIncome: shadow.BottomLineNetIncome, + EPS: shadow.EPS, + EPSDiluted: shadow.EPSDiluted, + WeightedAverageShsOut: shadow.WeightedAverageShsOut, + WeightedAverageShsOutDil: shadow.WeightedAverageShsOutDil, } return nil } // BulkIncomeStatementGrowth is one worldwide bulk income-statement-growth row. type BulkIncomeStatementGrowth struct { - Symbol string `json:"symbol"` - Date Date `json:"date"` - FiscalYear string `json:"fiscalYear"` - Period string `json:"period"` - ReportedCurrency string `json:"reportedCurrency"` - GrowthRevenue string `json:"growthRevenue"` - GrowthCostOfRevenue string `json:"growthCostOfRevenue"` - GrowthGrossProfit string `json:"growthGrossProfit"` - GrowthGrossProfitRatio string `json:"growthGrossProfitRatio"` - GrowthResearchAndDevelopmentExpenses string `json:"growthResearchAndDevelopmentExpenses"` - GrowthGeneralAndAdministrativeExpenses string `json:"growthGeneralAndAdministrativeExpenses"` - GrowthSellingAndMarketingExpenses string `json:"growthSellingAndMarketingExpenses"` - GrowthOtherExpenses string `json:"growthOtherExpenses"` - GrowthOperatingExpenses string `json:"growthOperatingExpenses"` - GrowthCostAndExpenses string `json:"growthCostAndExpenses"` - GrowthInterestIncome string `json:"growthInterestIncome"` - GrowthInterestExpense string `json:"growthInterestExpense"` - GrowthDepreciationAndAmortization string `json:"growthDepreciationAndAmortization"` - GrowthEbitda string `json:"growthEBITDA"` - GrowthOperatingIncome string `json:"growthOperatingIncome"` - GrowthIncomeBeforeTax string `json:"growthIncomeBeforeTax"` - GrowthIncomeTaxExpense string `json:"growthIncomeTaxExpense"` - GrowthNetIncome string `json:"growthNetIncome"` - GrowthEPS string `json:"growthEPS"` - GrowthEPSDiluted string `json:"growthEPSDiluted"` - GrowthWeightedAverageShsOut string `json:"growthWeightedAverageShsOut"` - GrowthWeightedAverageShsOutDil string `json:"growthWeightedAverageShsOutDil"` - GrowthEbit string `json:"growthEBIT"` - GrowthNonOperatingIncomeExcludingInterest string `json:"growthNonOperatingIncomeExcludingInterest"` - GrowthNetInterestIncome string `json:"growthNetInterestIncome"` - GrowthTotalOtherIncomeExpensesNet string `json:"growthTotalOtherIncomeExpensesNet"` - GrowthNetIncomeFromContinuingOperations string `json:"growthNetIncomeFromContinuingOperations"` - GrowthOtherAdjustmentsToNetIncome string `json:"growthOtherAdjustmentsToNetIncome"` - GrowthNetIncomeDeductions string `json:"growthNetIncomeDeductions"` + Symbol string `json:"symbol"` + Date Date `json:"date"` + FiscalYear string `json:"fiscalYear"` + Period string `json:"period"` + ReportedCurrency string `json:"reportedCurrency"` + GrowthRevenue *string `json:"growthRevenue"` + GrowthCostOfRevenue *string `json:"growthCostOfRevenue"` + GrowthGrossProfit *string `json:"growthGrossProfit"` + GrowthGrossProfitRatio *string `json:"growthGrossProfitRatio"` + GrowthResearchAndDevelopmentExpenses *string `json:"growthResearchAndDevelopmentExpenses"` + GrowthGeneralAndAdministrativeExpenses *string `json:"growthGeneralAndAdministrativeExpenses"` + GrowthSellingAndMarketingExpenses *string `json:"growthSellingAndMarketingExpenses"` + GrowthOtherExpenses *string `json:"growthOtherExpenses"` + GrowthOperatingExpenses *string `json:"growthOperatingExpenses"` + GrowthCostAndExpenses *string `json:"growthCostAndExpenses"` + GrowthInterestIncome *string `json:"growthInterestIncome"` + GrowthInterestExpense *string `json:"growthInterestExpense"` + GrowthDepreciationAndAmortization *string `json:"growthDepreciationAndAmortization"` + GrowthEbitda *string `json:"growthEBITDA"` + GrowthOperatingIncome *string `json:"growthOperatingIncome"` + GrowthIncomeBeforeTax *string `json:"growthIncomeBeforeTax"` + GrowthIncomeTaxExpense *string `json:"growthIncomeTaxExpense"` + GrowthNetIncome *string `json:"growthNetIncome"` + GrowthEPS *string `json:"growthEPS"` + GrowthEPSDiluted *string `json:"growthEPSDiluted"` + GrowthWeightedAverageShsOut *string `json:"growthWeightedAverageShsOut"` + GrowthWeightedAverageShsOutDil *string `json:"growthWeightedAverageShsOutDil"` + GrowthEbit *string `json:"growthEBIT"` + GrowthNonOperatingIncomeExcludingInterest *string `json:"growthNonOperatingIncomeExcludingInterest"` + GrowthNetInterestIncome *string `json:"growthNetInterestIncome"` + GrowthTotalOtherIncomeExpensesNet *string `json:"growthTotalOtherIncomeExpensesNet"` + GrowthNetIncomeFromContinuingOperations *string `json:"growthNetIncomeFromContinuingOperations"` + GrowthOtherAdjustmentsToNetIncome *string `json:"growthOtherAdjustmentsToNetIncome"` + GrowthNetIncomeDeductions *string `json:"growthNetIncomeDeductions"` } // bulkIncomeStatementGrowthShadow mirrors BulkIncomeStatementGrowth with a @@ -1523,64 +1089,6 @@ func (m *BulkIncomeStatementGrowth) UnmarshalJSONFrom(dec *jsontext.Decoder) err return missingMemberError("BulkIncomeStatementGrowth", "period") case shadow.ReportedCurrency == nil: return missingMemberError("BulkIncomeStatementGrowth", "reportedCurrency") - case shadow.GrowthRevenue == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthRevenue") - case shadow.GrowthCostOfRevenue == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthCostOfRevenue") - case shadow.GrowthGrossProfit == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthGrossProfit") - case shadow.GrowthGrossProfitRatio == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthGrossProfitRatio") - case shadow.GrowthResearchAndDevelopmentExpenses == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthResearchAndDevelopmentExpenses") - case shadow.GrowthGeneralAndAdministrativeExpenses == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthGeneralAndAdministrativeExpenses") - case shadow.GrowthSellingAndMarketingExpenses == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthSellingAndMarketingExpenses") - case shadow.GrowthOtherExpenses == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthOtherExpenses") - case shadow.GrowthOperatingExpenses == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthOperatingExpenses") - case shadow.GrowthCostAndExpenses == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthCostAndExpenses") - case shadow.GrowthInterestIncome == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthInterestIncome") - case shadow.GrowthInterestExpense == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthInterestExpense") - case shadow.GrowthDepreciationAndAmortization == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthDepreciationAndAmortization") - case shadow.GrowthEbitda == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthEBITDA") - case shadow.GrowthOperatingIncome == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthOperatingIncome") - case shadow.GrowthIncomeBeforeTax == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthIncomeBeforeTax") - case shadow.GrowthIncomeTaxExpense == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthIncomeTaxExpense") - case shadow.GrowthNetIncome == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthNetIncome") - case shadow.GrowthEPS == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthEPS") - case shadow.GrowthEPSDiluted == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthEPSDiluted") - case shadow.GrowthWeightedAverageShsOut == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthWeightedAverageShsOut") - case shadow.GrowthWeightedAverageShsOutDil == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthWeightedAverageShsOutDil") - case shadow.GrowthEbit == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthEBIT") - case shadow.GrowthNonOperatingIncomeExcludingInterest == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthNonOperatingIncomeExcludingInterest") - case shadow.GrowthNetInterestIncome == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthNetInterestIncome") - case shadow.GrowthTotalOtherIncomeExpensesNet == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthTotalOtherIncomeExpensesNet") - case shadow.GrowthNetIncomeFromContinuingOperations == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthNetIncomeFromContinuingOperations") - case shadow.GrowthOtherAdjustmentsToNetIncome == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthOtherAdjustmentsToNetIncome") - case shadow.GrowthNetIncomeDeductions == nil: - return missingMemberError("BulkIncomeStatementGrowth", "growthNetIncomeDeductions") } *m = BulkIncomeStatementGrowth{ Symbol: *shadow.Symbol, @@ -1588,35 +1096,35 @@ func (m *BulkIncomeStatementGrowth) UnmarshalJSONFrom(dec *jsontext.Decoder) err FiscalYear: *shadow.FiscalYear, Period: *shadow.Period, ReportedCurrency: *shadow.ReportedCurrency, - GrowthRevenue: *shadow.GrowthRevenue, - GrowthCostOfRevenue: *shadow.GrowthCostOfRevenue, - GrowthGrossProfit: *shadow.GrowthGrossProfit, - GrowthGrossProfitRatio: *shadow.GrowthGrossProfitRatio, - GrowthResearchAndDevelopmentExpenses: *shadow.GrowthResearchAndDevelopmentExpenses, - GrowthGeneralAndAdministrativeExpenses: *shadow.GrowthGeneralAndAdministrativeExpenses, - GrowthSellingAndMarketingExpenses: *shadow.GrowthSellingAndMarketingExpenses, - GrowthOtherExpenses: *shadow.GrowthOtherExpenses, - GrowthOperatingExpenses: *shadow.GrowthOperatingExpenses, - GrowthCostAndExpenses: *shadow.GrowthCostAndExpenses, - GrowthInterestIncome: *shadow.GrowthInterestIncome, - GrowthInterestExpense: *shadow.GrowthInterestExpense, - GrowthDepreciationAndAmortization: *shadow.GrowthDepreciationAndAmortization, - GrowthEbitda: *shadow.GrowthEbitda, - GrowthOperatingIncome: *shadow.GrowthOperatingIncome, - GrowthIncomeBeforeTax: *shadow.GrowthIncomeBeforeTax, - GrowthIncomeTaxExpense: *shadow.GrowthIncomeTaxExpense, - GrowthNetIncome: *shadow.GrowthNetIncome, - GrowthEPS: *shadow.GrowthEPS, - GrowthEPSDiluted: *shadow.GrowthEPSDiluted, - GrowthWeightedAverageShsOut: *shadow.GrowthWeightedAverageShsOut, - GrowthWeightedAverageShsOutDil: *shadow.GrowthWeightedAverageShsOutDil, - GrowthEbit: *shadow.GrowthEbit, - GrowthNonOperatingIncomeExcludingInterest: *shadow.GrowthNonOperatingIncomeExcludingInterest, - GrowthNetInterestIncome: *shadow.GrowthNetInterestIncome, - GrowthTotalOtherIncomeExpensesNet: *shadow.GrowthTotalOtherIncomeExpensesNet, - GrowthNetIncomeFromContinuingOperations: *shadow.GrowthNetIncomeFromContinuingOperations, - GrowthOtherAdjustmentsToNetIncome: *shadow.GrowthOtherAdjustmentsToNetIncome, - GrowthNetIncomeDeductions: *shadow.GrowthNetIncomeDeductions, + GrowthRevenue: shadow.GrowthRevenue, + GrowthCostOfRevenue: shadow.GrowthCostOfRevenue, + GrowthGrossProfit: shadow.GrowthGrossProfit, + GrowthGrossProfitRatio: shadow.GrowthGrossProfitRatio, + GrowthResearchAndDevelopmentExpenses: shadow.GrowthResearchAndDevelopmentExpenses, + GrowthGeneralAndAdministrativeExpenses: shadow.GrowthGeneralAndAdministrativeExpenses, + GrowthSellingAndMarketingExpenses: shadow.GrowthSellingAndMarketingExpenses, + GrowthOtherExpenses: shadow.GrowthOtherExpenses, + GrowthOperatingExpenses: shadow.GrowthOperatingExpenses, + GrowthCostAndExpenses: shadow.GrowthCostAndExpenses, + GrowthInterestIncome: shadow.GrowthInterestIncome, + GrowthInterestExpense: shadow.GrowthInterestExpense, + GrowthDepreciationAndAmortization: shadow.GrowthDepreciationAndAmortization, + GrowthEbitda: shadow.GrowthEbitda, + GrowthOperatingIncome: shadow.GrowthOperatingIncome, + GrowthIncomeBeforeTax: shadow.GrowthIncomeBeforeTax, + GrowthIncomeTaxExpense: shadow.GrowthIncomeTaxExpense, + GrowthNetIncome: shadow.GrowthNetIncome, + GrowthEPS: shadow.GrowthEPS, + GrowthEPSDiluted: shadow.GrowthEPSDiluted, + GrowthWeightedAverageShsOut: shadow.GrowthWeightedAverageShsOut, + GrowthWeightedAverageShsOutDil: shadow.GrowthWeightedAverageShsOutDil, + GrowthEbit: shadow.GrowthEbit, + GrowthNonOperatingIncomeExcludingInterest: shadow.GrowthNonOperatingIncomeExcludingInterest, + GrowthNetInterestIncome: shadow.GrowthNetInterestIncome, + GrowthTotalOtherIncomeExpensesNet: shadow.GrowthTotalOtherIncomeExpensesNet, + GrowthNetIncomeFromContinuingOperations: shadow.GrowthNetIncomeFromContinuingOperations, + GrowthOtherAdjustmentsToNetIncome: shadow.GrowthOtherAdjustmentsToNetIncome, + GrowthNetIncomeDeductions: shadow.GrowthNetIncomeDeductions, } return nil } @@ -1624,49 +1132,49 @@ func (m *BulkIncomeStatementGrowth) UnmarshalJSONFrom(dec *jsontext.Decoder) err // BulkKeyMetricsTTM is one worldwide key-metrics trailing-twelve-month bulk // row. type BulkKeyMetricsTTM struct { - Symbol string `json:"symbol"` - MarketCap string `json:"marketCap"` - EnterpriseValueTTM string `json:"enterpriseValueTTM"` - EvToSalesTTM string `json:"evToSalesTTM"` - EvToOperatingCashFlowTTM string `json:"evToOperatingCashFlowTTM"` - EvToFreeCashFlowTTM string `json:"evToFreeCashFlowTTM"` - EvToEbitdaTTM string `json:"evToEBITDATTM"` - NetDebtToEbitdaTTM string `json:"netDebtToEBITDATTM"` - CurrentRatioTTM string `json:"currentRatioTTM"` - IncomeQualityTTM string `json:"incomeQualityTTM"` - GrahamNumberTTM string `json:"grahamNumberTTM"` - GrahamNetNetTTM string `json:"grahamNetNetTTM"` - TaxBurdenTTM string `json:"taxBurdenTTM"` - InterestBurdenTTM string `json:"interestBurdenTTM"` - WorkingCapitalTTM string `json:"workingCapitalTTM"` - InvestedCapitalTTM string `json:"investedCapitalTTM"` - ReturnOnAssetsTTM string `json:"returnOnAssetsTTM"` - OperatingReturnOnAssetsTTM string `json:"operatingReturnOnAssetsTTM"` - ReturnOnTangibleAssetsTTM string `json:"returnOnTangibleAssetsTTM"` - ReturnOnEquityTTM string `json:"returnOnEquityTTM"` - ReturnOnInvestedCapitalTTM string `json:"returnOnInvestedCapitalTTM"` - ReturnOnCapitalEmployedTTM string `json:"returnOnCapitalEmployedTTM"` - EarningsYieldTTM string `json:"earningsYieldTTM"` - FreeCashFlowYieldTTM string `json:"freeCashFlowYieldTTM"` - CapexToOperatingCashFlowTTM string `json:"capexToOperatingCashFlowTTM"` - CapexToDepreciationTTM string `json:"capexToDepreciationTTM"` - CapexToRevenueTTM string `json:"capexToRevenueTTM"` - SalesGeneralAndAdministrativeToRevenueTTM string `json:"salesGeneralAndAdministrativeToRevenueTTM"` - ResearchAndDevelopmentToRevenueTTM string `json:"researchAndDevelopementToRevenueTTM"` - StockBasedCompensationToRevenueTTM string `json:"stockBasedCompensationToRevenueTTM"` - IntangiblesToTotalAssetsTTM string `json:"intangiblesToTotalAssetsTTM"` - AverageReceivablesTTM string `json:"averageReceivablesTTM"` - AveragePayablesTTM string `json:"averagePayablesTTM"` - AverageInventoryTTM string `json:"averageInventoryTTM"` - DaysOfSalesOutstandingTTM string `json:"daysOfSalesOutstandingTTM"` - DaysOfPayablesOutstandingTTM string `json:"daysOfPayablesOutstandingTTM"` - DaysOfInventoryOutstandingTTM string `json:"daysOfInventoryOutstandingTTM"` - OperatingCycleTTM string `json:"operatingCycleTTM"` - CashConversionCycleTTM string `json:"cashConversionCycleTTM"` - FreeCashFlowToEquityTTM string `json:"freeCashFlowToEquityTTM"` - FreeCashFlowToFirmTTM string `json:"freeCashFlowToFirmTTM"` - TangibleAssetValueTTM string `json:"tangibleAssetValueTTM"` - NetCurrentAssetValueTTM string `json:"netCurrentAssetValueTTM"` + Symbol string `json:"symbol"` + MarketCap *string `json:"marketCap"` + EnterpriseValueTTM *string `json:"enterpriseValueTTM"` + EvToSalesTTM *string `json:"evToSalesTTM"` + EvToOperatingCashFlowTTM *string `json:"evToOperatingCashFlowTTM"` + EvToFreeCashFlowTTM *string `json:"evToFreeCashFlowTTM"` + EvToEbitdaTTM *string `json:"evToEBITDATTM"` + NetDebtToEbitdaTTM *string `json:"netDebtToEBITDATTM"` + CurrentRatioTTM *string `json:"currentRatioTTM"` + IncomeQualityTTM *string `json:"incomeQualityTTM"` + GrahamNumberTTM *string `json:"grahamNumberTTM"` + GrahamNetNetTTM *string `json:"grahamNetNetTTM"` + TaxBurdenTTM *string `json:"taxBurdenTTM"` + InterestBurdenTTM *string `json:"interestBurdenTTM"` + WorkingCapitalTTM *string `json:"workingCapitalTTM"` + InvestedCapitalTTM *string `json:"investedCapitalTTM"` + ReturnOnAssetsTTM *string `json:"returnOnAssetsTTM"` + OperatingReturnOnAssetsTTM *string `json:"operatingReturnOnAssetsTTM"` + ReturnOnTangibleAssetsTTM *string `json:"returnOnTangibleAssetsTTM"` + ReturnOnEquityTTM *string `json:"returnOnEquityTTM"` + ReturnOnInvestedCapitalTTM *string `json:"returnOnInvestedCapitalTTM"` + ReturnOnCapitalEmployedTTM *string `json:"returnOnCapitalEmployedTTM"` + EarningsYieldTTM *string `json:"earningsYieldTTM"` + FreeCashFlowYieldTTM *string `json:"freeCashFlowYieldTTM"` + CapexToOperatingCashFlowTTM *string `json:"capexToOperatingCashFlowTTM"` + CapexToDepreciationTTM *string `json:"capexToDepreciationTTM"` + CapexToRevenueTTM *string `json:"capexToRevenueTTM"` + SalesGeneralAndAdministrativeToRevenueTTM *string `json:"salesGeneralAndAdministrativeToRevenueTTM"` + ResearchAndDevelopmentToRevenueTTM *string `json:"researchAndDevelopementToRevenueTTM"` + StockBasedCompensationToRevenueTTM *string `json:"stockBasedCompensationToRevenueTTM"` + IntangiblesToTotalAssetsTTM *string `json:"intangiblesToTotalAssetsTTM"` + AverageReceivablesTTM *string `json:"averageReceivablesTTM"` + AveragePayablesTTM *string `json:"averagePayablesTTM"` + AverageInventoryTTM *string `json:"averageInventoryTTM"` + DaysOfSalesOutstandingTTM *string `json:"daysOfSalesOutstandingTTM"` + DaysOfPayablesOutstandingTTM *string `json:"daysOfPayablesOutstandingTTM"` + DaysOfInventoryOutstandingTTM *string `json:"daysOfInventoryOutstandingTTM"` + OperatingCycleTTM *string `json:"operatingCycleTTM"` + CashConversionCycleTTM *string `json:"cashConversionCycleTTM"` + FreeCashFlowToEquityTTM *string `json:"freeCashFlowToEquityTTM"` + FreeCashFlowToFirmTTM *string `json:"freeCashFlowToFirmTTM"` + TangibleAssetValueTTM *string `json:"tangibleAssetValueTTM"` + NetCurrentAssetValueTTM *string `json:"netCurrentAssetValueTTM"` } // bulkKeyMetricsTTMShadow mirrors BulkKeyMetricsTTM with a pointer or raw @@ -1729,135 +1237,51 @@ func (m *BulkKeyMetricsTTM) UnmarshalJSONFrom(dec *jsontext.Decoder) error { switch { case shadow.Symbol == nil: return missingMemberError("BulkKeyMetricsTTM", "symbol") - case shadow.MarketCap == nil: - return missingMemberError("BulkKeyMetricsTTM", "marketCap") - case shadow.EnterpriseValueTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "enterpriseValueTTM") - case shadow.EvToSalesTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "evToSalesTTM") - case shadow.EvToOperatingCashFlowTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "evToOperatingCashFlowTTM") - case shadow.EvToFreeCashFlowTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "evToFreeCashFlowTTM") - case shadow.EvToEbitdaTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "evToEBITDATTM") - case shadow.NetDebtToEbitdaTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "netDebtToEBITDATTM") - case shadow.CurrentRatioTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "currentRatioTTM") - case shadow.IncomeQualityTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "incomeQualityTTM") - case shadow.GrahamNumberTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "grahamNumberTTM") - case shadow.GrahamNetNetTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "grahamNetNetTTM") - case shadow.TaxBurdenTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "taxBurdenTTM") - case shadow.InterestBurdenTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "interestBurdenTTM") - case shadow.WorkingCapitalTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "workingCapitalTTM") - case shadow.InvestedCapitalTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "investedCapitalTTM") - case shadow.ReturnOnAssetsTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "returnOnAssetsTTM") - case shadow.OperatingReturnOnAssetsTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "operatingReturnOnAssetsTTM") - case shadow.ReturnOnTangibleAssetsTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "returnOnTangibleAssetsTTM") - case shadow.ReturnOnEquityTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "returnOnEquityTTM") - case shadow.ReturnOnInvestedCapitalTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "returnOnInvestedCapitalTTM") - case shadow.ReturnOnCapitalEmployedTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "returnOnCapitalEmployedTTM") - case shadow.EarningsYieldTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "earningsYieldTTM") - case shadow.FreeCashFlowYieldTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "freeCashFlowYieldTTM") - case shadow.CapexToOperatingCashFlowTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "capexToOperatingCashFlowTTM") - case shadow.CapexToDepreciationTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "capexToDepreciationTTM") - case shadow.CapexToRevenueTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "capexToRevenueTTM") - case shadow.SalesGeneralAndAdministrativeToRevenueTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "salesGeneralAndAdministrativeToRevenueTTM") - case shadow.ResearchAndDevelopmentToRevenueTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "researchAndDevelopementToRevenueTTM") - case shadow.StockBasedCompensationToRevenueTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "stockBasedCompensationToRevenueTTM") - case shadow.IntangiblesToTotalAssetsTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "intangiblesToTotalAssetsTTM") - case shadow.AverageReceivablesTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "averageReceivablesTTM") - case shadow.AveragePayablesTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "averagePayablesTTM") - case shadow.AverageInventoryTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "averageInventoryTTM") - case shadow.DaysOfSalesOutstandingTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "daysOfSalesOutstandingTTM") - case shadow.DaysOfPayablesOutstandingTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "daysOfPayablesOutstandingTTM") - case shadow.DaysOfInventoryOutstandingTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "daysOfInventoryOutstandingTTM") - case shadow.OperatingCycleTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "operatingCycleTTM") - case shadow.CashConversionCycleTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "cashConversionCycleTTM") - case shadow.FreeCashFlowToEquityTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "freeCashFlowToEquityTTM") - case shadow.FreeCashFlowToFirmTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "freeCashFlowToFirmTTM") - case shadow.TangibleAssetValueTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "tangibleAssetValueTTM") - case shadow.NetCurrentAssetValueTTM == nil: - return missingMemberError("BulkKeyMetricsTTM", "netCurrentAssetValueTTM") } *m = BulkKeyMetricsTTM{ Symbol: *shadow.Symbol, - MarketCap: *shadow.MarketCap, - EnterpriseValueTTM: *shadow.EnterpriseValueTTM, - EvToSalesTTM: *shadow.EvToSalesTTM, - EvToOperatingCashFlowTTM: *shadow.EvToOperatingCashFlowTTM, - EvToFreeCashFlowTTM: *shadow.EvToFreeCashFlowTTM, - EvToEbitdaTTM: *shadow.EvToEbitdaTTM, - NetDebtToEbitdaTTM: *shadow.NetDebtToEbitdaTTM, - CurrentRatioTTM: *shadow.CurrentRatioTTM, - IncomeQualityTTM: *shadow.IncomeQualityTTM, - GrahamNumberTTM: *shadow.GrahamNumberTTM, - GrahamNetNetTTM: *shadow.GrahamNetNetTTM, - TaxBurdenTTM: *shadow.TaxBurdenTTM, - InterestBurdenTTM: *shadow.InterestBurdenTTM, - WorkingCapitalTTM: *shadow.WorkingCapitalTTM, - InvestedCapitalTTM: *shadow.InvestedCapitalTTM, - ReturnOnAssetsTTM: *shadow.ReturnOnAssetsTTM, - OperatingReturnOnAssetsTTM: *shadow.OperatingReturnOnAssetsTTM, - ReturnOnTangibleAssetsTTM: *shadow.ReturnOnTangibleAssetsTTM, - ReturnOnEquityTTM: *shadow.ReturnOnEquityTTM, - ReturnOnInvestedCapitalTTM: *shadow.ReturnOnInvestedCapitalTTM, - ReturnOnCapitalEmployedTTM: *shadow.ReturnOnCapitalEmployedTTM, - EarningsYieldTTM: *shadow.EarningsYieldTTM, - FreeCashFlowYieldTTM: *shadow.FreeCashFlowYieldTTM, - CapexToOperatingCashFlowTTM: *shadow.CapexToOperatingCashFlowTTM, - CapexToDepreciationTTM: *shadow.CapexToDepreciationTTM, - CapexToRevenueTTM: *shadow.CapexToRevenueTTM, - SalesGeneralAndAdministrativeToRevenueTTM: *shadow.SalesGeneralAndAdministrativeToRevenueTTM, - ResearchAndDevelopmentToRevenueTTM: *shadow.ResearchAndDevelopmentToRevenueTTM, - StockBasedCompensationToRevenueTTM: *shadow.StockBasedCompensationToRevenueTTM, - IntangiblesToTotalAssetsTTM: *shadow.IntangiblesToTotalAssetsTTM, - AverageReceivablesTTM: *shadow.AverageReceivablesTTM, - AveragePayablesTTM: *shadow.AveragePayablesTTM, - AverageInventoryTTM: *shadow.AverageInventoryTTM, - DaysOfSalesOutstandingTTM: *shadow.DaysOfSalesOutstandingTTM, - DaysOfPayablesOutstandingTTM: *shadow.DaysOfPayablesOutstandingTTM, - DaysOfInventoryOutstandingTTM: *shadow.DaysOfInventoryOutstandingTTM, - OperatingCycleTTM: *shadow.OperatingCycleTTM, - CashConversionCycleTTM: *shadow.CashConversionCycleTTM, - FreeCashFlowToEquityTTM: *shadow.FreeCashFlowToEquityTTM, - FreeCashFlowToFirmTTM: *shadow.FreeCashFlowToFirmTTM, - TangibleAssetValueTTM: *shadow.TangibleAssetValueTTM, - NetCurrentAssetValueTTM: *shadow.NetCurrentAssetValueTTM, + MarketCap: shadow.MarketCap, + EnterpriseValueTTM: shadow.EnterpriseValueTTM, + EvToSalesTTM: shadow.EvToSalesTTM, + EvToOperatingCashFlowTTM: shadow.EvToOperatingCashFlowTTM, + EvToFreeCashFlowTTM: shadow.EvToFreeCashFlowTTM, + EvToEbitdaTTM: shadow.EvToEbitdaTTM, + NetDebtToEbitdaTTM: shadow.NetDebtToEbitdaTTM, + CurrentRatioTTM: shadow.CurrentRatioTTM, + IncomeQualityTTM: shadow.IncomeQualityTTM, + GrahamNumberTTM: shadow.GrahamNumberTTM, + GrahamNetNetTTM: shadow.GrahamNetNetTTM, + TaxBurdenTTM: shadow.TaxBurdenTTM, + InterestBurdenTTM: shadow.InterestBurdenTTM, + WorkingCapitalTTM: shadow.WorkingCapitalTTM, + InvestedCapitalTTM: shadow.InvestedCapitalTTM, + ReturnOnAssetsTTM: shadow.ReturnOnAssetsTTM, + OperatingReturnOnAssetsTTM: shadow.OperatingReturnOnAssetsTTM, + ReturnOnTangibleAssetsTTM: shadow.ReturnOnTangibleAssetsTTM, + ReturnOnEquityTTM: shadow.ReturnOnEquityTTM, + ReturnOnInvestedCapitalTTM: shadow.ReturnOnInvestedCapitalTTM, + ReturnOnCapitalEmployedTTM: shadow.ReturnOnCapitalEmployedTTM, + EarningsYieldTTM: shadow.EarningsYieldTTM, + FreeCashFlowYieldTTM: shadow.FreeCashFlowYieldTTM, + CapexToOperatingCashFlowTTM: shadow.CapexToOperatingCashFlowTTM, + CapexToDepreciationTTM: shadow.CapexToDepreciationTTM, + CapexToRevenueTTM: shadow.CapexToRevenueTTM, + SalesGeneralAndAdministrativeToRevenueTTM: shadow.SalesGeneralAndAdministrativeToRevenueTTM, + ResearchAndDevelopmentToRevenueTTM: shadow.ResearchAndDevelopmentToRevenueTTM, + StockBasedCompensationToRevenueTTM: shadow.StockBasedCompensationToRevenueTTM, + IntangiblesToTotalAssetsTTM: shadow.IntangiblesToTotalAssetsTTM, + AverageReceivablesTTM: shadow.AverageReceivablesTTM, + AveragePayablesTTM: shadow.AveragePayablesTTM, + AverageInventoryTTM: shadow.AverageInventoryTTM, + DaysOfSalesOutstandingTTM: shadow.DaysOfSalesOutstandingTTM, + DaysOfPayablesOutstandingTTM: shadow.DaysOfPayablesOutstandingTTM, + DaysOfInventoryOutstandingTTM: shadow.DaysOfInventoryOutstandingTTM, + OperatingCycleTTM: shadow.OperatingCycleTTM, + CashConversionCycleTTM: shadow.CashConversionCycleTTM, + FreeCashFlowToEquityTTM: shadow.FreeCashFlowToEquityTTM, + FreeCashFlowToFirmTTM: shadow.FreeCashFlowToFirmTTM, + TangibleAssetValueTTM: shadow.TangibleAssetValueTTM, + NetCurrentAssetValueTTM: shadow.NetCurrentAssetValueTTM, } return nil } @@ -1865,66 +1289,66 @@ func (m *BulkKeyMetricsTTM) UnmarshalJSONFrom(dec *jsontext.Decoder) error { // BulkFinancialRatiosTTM is one worldwide financial-ratios // trailing-twelve-month bulk row. type BulkFinancialRatiosTTM struct { - Symbol string `json:"symbol"` - GrossProfitMarginTTM string `json:"grossProfitMarginTTM"` - EbitMarginTTM string `json:"ebitMarginTTM"` - EbitdaMarginTTM string `json:"ebitdaMarginTTM"` - OperatingProfitMarginTTM string `json:"operatingProfitMarginTTM"` - PretaxProfitMarginTTM string `json:"pretaxProfitMarginTTM"` - ContinuousOperationsProfitMarginTTM string `json:"continuousOperationsProfitMarginTTM"` - NetProfitMarginTTM string `json:"netProfitMarginTTM"` - BottomLineProfitMarginTTM string `json:"bottomLineProfitMarginTTM"` - ReceivablesTurnoverTTM string `json:"receivablesTurnoverTTM"` - PayablesTurnoverTTM string `json:"payablesTurnoverTTM"` - InventoryTurnoverTTM string `json:"inventoryTurnoverTTM"` - FixedAssetTurnoverTTM string `json:"fixedAssetTurnoverTTM"` - AssetTurnoverTTM string `json:"assetTurnoverTTM"` - CurrentRatioTTM string `json:"currentRatioTTM"` - QuickRatioTTM string `json:"quickRatioTTM"` - SolvencyRatioTTM string `json:"solvencyRatioTTM"` - CashRatioTTM string `json:"cashRatioTTM"` - PriceToEarningsRatioTTM string `json:"priceToEarningsRatioTTM"` - PriceToEarningsGrowthRatioTTM string `json:"priceToEarningsGrowthRatioTTM"` - ForwardPriceToEarningsGrowthRatioTTM string `json:"forwardPriceToEarningsGrowthRatioTTM"` - PriceToBookRatioTTM string `json:"priceToBookRatioTTM"` - PriceToSalesRatioTTM string `json:"priceToSalesRatioTTM"` - PriceToFreeCashFlowRatioTTM string `json:"priceToFreeCashFlowRatioTTM"` - PriceToOperatingCashFlowRatioTTM string `json:"priceToOperatingCashFlowRatioTTM"` - DebtToAssetsRatioTTM string `json:"debtToAssetsRatioTTM"` - DebtToEquityRatioTTM string `json:"debtToEquityRatioTTM"` - DebtToCapitalRatioTTM string `json:"debtToCapitalRatioTTM"` - LongTermDebtToCapitalRatioTTM string `json:"longTermDebtToCapitalRatioTTM"` - FinancialLeverageRatioTTM string `json:"financialLeverageRatioTTM"` - WorkingCapitalTurnoverRatioTTM string `json:"workingCapitalTurnoverRatioTTM"` - OperatingCashFlowRatioTTM string `json:"operatingCashFlowRatioTTM"` - OperatingCashFlowSalesRatioTTM string `json:"operatingCashFlowSalesRatioTTM"` - FreeCashFlowOperatingCashFlowRatioTTM string `json:"freeCashFlowOperatingCashFlowRatioTTM"` - DebtServiceCoverageRatioTTM string `json:"debtServiceCoverageRatioTTM"` - InterestCoverageRatioTTM string `json:"interestCoverageRatioTTM"` - ShortTermOperatingCashFlowCoverageRatioTTM string `json:"shortTermOperatingCashFlowCoverageRatioTTM"` - OperatingCashFlowCoverageRatioTTM string `json:"operatingCashFlowCoverageRatioTTM"` - CapitalExpenditureCoverageRatioTTM string `json:"capitalExpenditureCoverageRatioTTM"` - DividendPaidAndCapexCoverageRatioTTM string `json:"dividendPaidAndCapexCoverageRatioTTM"` - DividendPayoutRatioTTM string `json:"dividendPayoutRatioTTM"` - DividendYieldTTM string `json:"dividendYieldTTM"` - EnterpriseValueTTM string `json:"enterpriseValueTTM"` - RevenuePerShareTTM string `json:"revenuePerShareTTM"` - NetIncomePerShareTTM string `json:"netIncomePerShareTTM"` - InterestDebtPerShareTTM string `json:"interestDebtPerShareTTM"` - CashPerShareTTM string `json:"cashPerShareTTM"` - BookValuePerShareTTM string `json:"bookValuePerShareTTM"` - TangibleBookValuePerShareTTM string `json:"tangibleBookValuePerShareTTM"` - ShareholdersEquityPerShareTTM string `json:"shareholdersEquityPerShareTTM"` - OperatingCashFlowPerShareTTM string `json:"operatingCashFlowPerShareTTM"` - CapexPerShareTTM string `json:"capexPerShareTTM"` - FreeCashFlowPerShareTTM string `json:"freeCashFlowPerShareTTM"` - NetIncomePerEbtTTM string `json:"netIncomePerEBTTTM"` - EbtPerEbitTTM string `json:"ebtPerEbitTTM"` - PriceToFairValueTTM string `json:"priceToFairValueTTM"` - DebtToMarketCapTTM string `json:"debtToMarketCapTTM"` - EffectiveTaxRateTTM string `json:"effectiveTaxRateTTM"` - EnterpriseValueMultipleTTM string `json:"enterpriseValueMultipleTTM"` - DividendPerShareTTM string `json:"dividendPerShareTTM"` + Symbol string `json:"symbol"` + GrossProfitMarginTTM *string `json:"grossProfitMarginTTM"` + EbitMarginTTM *string `json:"ebitMarginTTM"` + EbitdaMarginTTM *string `json:"ebitdaMarginTTM"` + OperatingProfitMarginTTM *string `json:"operatingProfitMarginTTM"` + PretaxProfitMarginTTM *string `json:"pretaxProfitMarginTTM"` + ContinuousOperationsProfitMarginTTM *string `json:"continuousOperationsProfitMarginTTM"` + NetProfitMarginTTM *string `json:"netProfitMarginTTM"` + BottomLineProfitMarginTTM *string `json:"bottomLineProfitMarginTTM"` + ReceivablesTurnoverTTM *string `json:"receivablesTurnoverTTM"` + PayablesTurnoverTTM *string `json:"payablesTurnoverTTM"` + InventoryTurnoverTTM *string `json:"inventoryTurnoverTTM"` + FixedAssetTurnoverTTM *string `json:"fixedAssetTurnoverTTM"` + AssetTurnoverTTM *string `json:"assetTurnoverTTM"` + CurrentRatioTTM *string `json:"currentRatioTTM"` + QuickRatioTTM *string `json:"quickRatioTTM"` + SolvencyRatioTTM *string `json:"solvencyRatioTTM"` + CashRatioTTM *string `json:"cashRatioTTM"` + PriceToEarningsRatioTTM *string `json:"priceToEarningsRatioTTM"` + PriceToEarningsGrowthRatioTTM *string `json:"priceToEarningsGrowthRatioTTM"` + ForwardPriceToEarningsGrowthRatioTTM *string `json:"forwardPriceToEarningsGrowthRatioTTM"` + PriceToBookRatioTTM *string `json:"priceToBookRatioTTM"` + PriceToSalesRatioTTM *string `json:"priceToSalesRatioTTM"` + PriceToFreeCashFlowRatioTTM *string `json:"priceToFreeCashFlowRatioTTM"` + PriceToOperatingCashFlowRatioTTM *string `json:"priceToOperatingCashFlowRatioTTM"` + DebtToAssetsRatioTTM *string `json:"debtToAssetsRatioTTM"` + DebtToEquityRatioTTM *string `json:"debtToEquityRatioTTM"` + DebtToCapitalRatioTTM *string `json:"debtToCapitalRatioTTM"` + LongTermDebtToCapitalRatioTTM *string `json:"longTermDebtToCapitalRatioTTM"` + FinancialLeverageRatioTTM *string `json:"financialLeverageRatioTTM"` + WorkingCapitalTurnoverRatioTTM *string `json:"workingCapitalTurnoverRatioTTM"` + OperatingCashFlowRatioTTM *string `json:"operatingCashFlowRatioTTM"` + OperatingCashFlowSalesRatioTTM *string `json:"operatingCashFlowSalesRatioTTM"` + FreeCashFlowOperatingCashFlowRatioTTM *string `json:"freeCashFlowOperatingCashFlowRatioTTM"` + DebtServiceCoverageRatioTTM *string `json:"debtServiceCoverageRatioTTM"` + InterestCoverageRatioTTM *string `json:"interestCoverageRatioTTM"` + ShortTermOperatingCashFlowCoverageRatioTTM *string `json:"shortTermOperatingCashFlowCoverageRatioTTM"` + OperatingCashFlowCoverageRatioTTM *string `json:"operatingCashFlowCoverageRatioTTM"` + CapitalExpenditureCoverageRatioTTM *string `json:"capitalExpenditureCoverageRatioTTM"` + DividendPaidAndCapexCoverageRatioTTM *string `json:"dividendPaidAndCapexCoverageRatioTTM"` + DividendPayoutRatioTTM *string `json:"dividendPayoutRatioTTM"` + DividendYieldTTM *string `json:"dividendYieldTTM"` + EnterpriseValueTTM *string `json:"enterpriseValueTTM"` + RevenuePerShareTTM *string `json:"revenuePerShareTTM"` + NetIncomePerShareTTM *string `json:"netIncomePerShareTTM"` + InterestDebtPerShareTTM *string `json:"interestDebtPerShareTTM"` + CashPerShareTTM *string `json:"cashPerShareTTM"` + BookValuePerShareTTM *string `json:"bookValuePerShareTTM"` + TangibleBookValuePerShareTTM *string `json:"tangibleBookValuePerShareTTM"` + ShareholdersEquityPerShareTTM *string `json:"shareholdersEquityPerShareTTM"` + OperatingCashFlowPerShareTTM *string `json:"operatingCashFlowPerShareTTM"` + CapexPerShareTTM *string `json:"capexPerShareTTM"` + FreeCashFlowPerShareTTM *string `json:"freeCashFlowPerShareTTM"` + NetIncomePerEbtTTM *string `json:"netIncomePerEBTTTM"` + EbtPerEbitTTM *string `json:"ebtPerEbitTTM"` + PriceToFairValueTTM *string `json:"priceToFairValueTTM"` + DebtToMarketCapTTM *string `json:"debtToMarketCapTTM"` + EffectiveTaxRateTTM *string `json:"effectiveTaxRateTTM"` + EnterpriseValueMultipleTTM *string `json:"enterpriseValueMultipleTTM"` + DividendPerShareTTM *string `json:"dividendPerShareTTM"` } // bulkFinancialRatiosTTMShadow mirrors BulkFinancialRatiosTTM with a pointer @@ -2004,186 +1428,68 @@ func (m *BulkFinancialRatiosTTM) UnmarshalJSONFrom(dec *jsontext.Decoder) error switch { case shadow.Symbol == nil: return missingMemberError("BulkFinancialRatiosTTM", "symbol") - case shadow.GrossProfitMarginTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "grossProfitMarginTTM") - case shadow.EbitMarginTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "ebitMarginTTM") - case shadow.EbitdaMarginTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "ebitdaMarginTTM") - case shadow.OperatingProfitMarginTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "operatingProfitMarginTTM") - case shadow.PretaxProfitMarginTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "pretaxProfitMarginTTM") - case shadow.ContinuousOperationsProfitMarginTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "continuousOperationsProfitMarginTTM") - case shadow.NetProfitMarginTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "netProfitMarginTTM") - case shadow.BottomLineProfitMarginTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "bottomLineProfitMarginTTM") - case shadow.ReceivablesTurnoverTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "receivablesTurnoverTTM") - case shadow.PayablesTurnoverTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "payablesTurnoverTTM") - case shadow.InventoryTurnoverTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "inventoryTurnoverTTM") - case shadow.FixedAssetTurnoverTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "fixedAssetTurnoverTTM") - case shadow.AssetTurnoverTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "assetTurnoverTTM") - case shadow.CurrentRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "currentRatioTTM") - case shadow.QuickRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "quickRatioTTM") - case shadow.SolvencyRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "solvencyRatioTTM") - case shadow.CashRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "cashRatioTTM") - case shadow.PriceToEarningsRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "priceToEarningsRatioTTM") - case shadow.PriceToEarningsGrowthRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "priceToEarningsGrowthRatioTTM") - case shadow.ForwardPriceToEarningsGrowthRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "forwardPriceToEarningsGrowthRatioTTM") - case shadow.PriceToBookRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "priceToBookRatioTTM") - case shadow.PriceToSalesRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "priceToSalesRatioTTM") - case shadow.PriceToFreeCashFlowRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "priceToFreeCashFlowRatioTTM") - case shadow.PriceToOperatingCashFlowRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "priceToOperatingCashFlowRatioTTM") - case shadow.DebtToAssetsRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "debtToAssetsRatioTTM") - case shadow.DebtToEquityRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "debtToEquityRatioTTM") - case shadow.DebtToCapitalRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "debtToCapitalRatioTTM") - case shadow.LongTermDebtToCapitalRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "longTermDebtToCapitalRatioTTM") - case shadow.FinancialLeverageRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "financialLeverageRatioTTM") - case shadow.WorkingCapitalTurnoverRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "workingCapitalTurnoverRatioTTM") - case shadow.OperatingCashFlowRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "operatingCashFlowRatioTTM") - case shadow.OperatingCashFlowSalesRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "operatingCashFlowSalesRatioTTM") - case shadow.FreeCashFlowOperatingCashFlowRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "freeCashFlowOperatingCashFlowRatioTTM") - case shadow.DebtServiceCoverageRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "debtServiceCoverageRatioTTM") - case shadow.InterestCoverageRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "interestCoverageRatioTTM") - case shadow.ShortTermOperatingCashFlowCoverageRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "shortTermOperatingCashFlowCoverageRatioTTM") - case shadow.OperatingCashFlowCoverageRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "operatingCashFlowCoverageRatioTTM") - case shadow.CapitalExpenditureCoverageRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "capitalExpenditureCoverageRatioTTM") - case shadow.DividendPaidAndCapexCoverageRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "dividendPaidAndCapexCoverageRatioTTM") - case shadow.DividendPayoutRatioTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "dividendPayoutRatioTTM") - case shadow.DividendYieldTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "dividendYieldTTM") - case shadow.EnterpriseValueTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "enterpriseValueTTM") - case shadow.RevenuePerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "revenuePerShareTTM") - case shadow.NetIncomePerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "netIncomePerShareTTM") - case shadow.InterestDebtPerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "interestDebtPerShareTTM") - case shadow.CashPerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "cashPerShareTTM") - case shadow.BookValuePerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "bookValuePerShareTTM") - case shadow.TangibleBookValuePerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "tangibleBookValuePerShareTTM") - case shadow.ShareholdersEquityPerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "shareholdersEquityPerShareTTM") - case shadow.OperatingCashFlowPerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "operatingCashFlowPerShareTTM") - case shadow.CapexPerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "capexPerShareTTM") - case shadow.FreeCashFlowPerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "freeCashFlowPerShareTTM") - case shadow.NetIncomePerEbtTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "netIncomePerEBTTTM") - case shadow.EbtPerEbitTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "ebtPerEbitTTM") - case shadow.PriceToFairValueTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "priceToFairValueTTM") - case shadow.DebtToMarketCapTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "debtToMarketCapTTM") - case shadow.EffectiveTaxRateTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "effectiveTaxRateTTM") - case shadow.EnterpriseValueMultipleTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "enterpriseValueMultipleTTM") - case shadow.DividendPerShareTTM == nil: - return missingMemberError("BulkFinancialRatiosTTM", "dividendPerShareTTM") } *m = BulkFinancialRatiosTTM{ Symbol: *shadow.Symbol, - GrossProfitMarginTTM: *shadow.GrossProfitMarginTTM, - EbitMarginTTM: *shadow.EbitMarginTTM, - EbitdaMarginTTM: *shadow.EbitdaMarginTTM, - OperatingProfitMarginTTM: *shadow.OperatingProfitMarginTTM, - PretaxProfitMarginTTM: *shadow.PretaxProfitMarginTTM, - ContinuousOperationsProfitMarginTTM: *shadow.ContinuousOperationsProfitMarginTTM, - NetProfitMarginTTM: *shadow.NetProfitMarginTTM, - BottomLineProfitMarginTTM: *shadow.BottomLineProfitMarginTTM, - ReceivablesTurnoverTTM: *shadow.ReceivablesTurnoverTTM, - PayablesTurnoverTTM: *shadow.PayablesTurnoverTTM, - InventoryTurnoverTTM: *shadow.InventoryTurnoverTTM, - FixedAssetTurnoverTTM: *shadow.FixedAssetTurnoverTTM, - AssetTurnoverTTM: *shadow.AssetTurnoverTTM, - CurrentRatioTTM: *shadow.CurrentRatioTTM, - QuickRatioTTM: *shadow.QuickRatioTTM, - SolvencyRatioTTM: *shadow.SolvencyRatioTTM, - CashRatioTTM: *shadow.CashRatioTTM, - PriceToEarningsRatioTTM: *shadow.PriceToEarningsRatioTTM, - PriceToEarningsGrowthRatioTTM: *shadow.PriceToEarningsGrowthRatioTTM, - ForwardPriceToEarningsGrowthRatioTTM: *shadow.ForwardPriceToEarningsGrowthRatioTTM, - PriceToBookRatioTTM: *shadow.PriceToBookRatioTTM, - PriceToSalesRatioTTM: *shadow.PriceToSalesRatioTTM, - PriceToFreeCashFlowRatioTTM: *shadow.PriceToFreeCashFlowRatioTTM, - PriceToOperatingCashFlowRatioTTM: *shadow.PriceToOperatingCashFlowRatioTTM, - DebtToAssetsRatioTTM: *shadow.DebtToAssetsRatioTTM, - DebtToEquityRatioTTM: *shadow.DebtToEquityRatioTTM, - DebtToCapitalRatioTTM: *shadow.DebtToCapitalRatioTTM, - LongTermDebtToCapitalRatioTTM: *shadow.LongTermDebtToCapitalRatioTTM, - FinancialLeverageRatioTTM: *shadow.FinancialLeverageRatioTTM, - WorkingCapitalTurnoverRatioTTM: *shadow.WorkingCapitalTurnoverRatioTTM, - OperatingCashFlowRatioTTM: *shadow.OperatingCashFlowRatioTTM, - OperatingCashFlowSalesRatioTTM: *shadow.OperatingCashFlowSalesRatioTTM, - FreeCashFlowOperatingCashFlowRatioTTM: *shadow.FreeCashFlowOperatingCashFlowRatioTTM, - DebtServiceCoverageRatioTTM: *shadow.DebtServiceCoverageRatioTTM, - InterestCoverageRatioTTM: *shadow.InterestCoverageRatioTTM, - ShortTermOperatingCashFlowCoverageRatioTTM: *shadow.ShortTermOperatingCashFlowCoverageRatioTTM, - OperatingCashFlowCoverageRatioTTM: *shadow.OperatingCashFlowCoverageRatioTTM, - CapitalExpenditureCoverageRatioTTM: *shadow.CapitalExpenditureCoverageRatioTTM, - DividendPaidAndCapexCoverageRatioTTM: *shadow.DividendPaidAndCapexCoverageRatioTTM, - DividendPayoutRatioTTM: *shadow.DividendPayoutRatioTTM, - DividendYieldTTM: *shadow.DividendYieldTTM, - EnterpriseValueTTM: *shadow.EnterpriseValueTTM, - RevenuePerShareTTM: *shadow.RevenuePerShareTTM, - NetIncomePerShareTTM: *shadow.NetIncomePerShareTTM, - InterestDebtPerShareTTM: *shadow.InterestDebtPerShareTTM, - CashPerShareTTM: *shadow.CashPerShareTTM, - BookValuePerShareTTM: *shadow.BookValuePerShareTTM, - TangibleBookValuePerShareTTM: *shadow.TangibleBookValuePerShareTTM, - ShareholdersEquityPerShareTTM: *shadow.ShareholdersEquityPerShareTTM, - OperatingCashFlowPerShareTTM: *shadow.OperatingCashFlowPerShareTTM, - CapexPerShareTTM: *shadow.CapexPerShareTTM, - FreeCashFlowPerShareTTM: *shadow.FreeCashFlowPerShareTTM, - NetIncomePerEbtTTM: *shadow.NetIncomePerEbtTTM, - EbtPerEbitTTM: *shadow.EbtPerEbitTTM, - PriceToFairValueTTM: *shadow.PriceToFairValueTTM, - DebtToMarketCapTTM: *shadow.DebtToMarketCapTTM, - EffectiveTaxRateTTM: *shadow.EffectiveTaxRateTTM, - EnterpriseValueMultipleTTM: *shadow.EnterpriseValueMultipleTTM, - DividendPerShareTTM: *shadow.DividendPerShareTTM, + GrossProfitMarginTTM: shadow.GrossProfitMarginTTM, + EbitMarginTTM: shadow.EbitMarginTTM, + EbitdaMarginTTM: shadow.EbitdaMarginTTM, + OperatingProfitMarginTTM: shadow.OperatingProfitMarginTTM, + PretaxProfitMarginTTM: shadow.PretaxProfitMarginTTM, + ContinuousOperationsProfitMarginTTM: shadow.ContinuousOperationsProfitMarginTTM, + NetProfitMarginTTM: shadow.NetProfitMarginTTM, + BottomLineProfitMarginTTM: shadow.BottomLineProfitMarginTTM, + ReceivablesTurnoverTTM: shadow.ReceivablesTurnoverTTM, + PayablesTurnoverTTM: shadow.PayablesTurnoverTTM, + InventoryTurnoverTTM: shadow.InventoryTurnoverTTM, + FixedAssetTurnoverTTM: shadow.FixedAssetTurnoverTTM, + AssetTurnoverTTM: shadow.AssetTurnoverTTM, + CurrentRatioTTM: shadow.CurrentRatioTTM, + QuickRatioTTM: shadow.QuickRatioTTM, + SolvencyRatioTTM: shadow.SolvencyRatioTTM, + CashRatioTTM: shadow.CashRatioTTM, + PriceToEarningsRatioTTM: shadow.PriceToEarningsRatioTTM, + PriceToEarningsGrowthRatioTTM: shadow.PriceToEarningsGrowthRatioTTM, + ForwardPriceToEarningsGrowthRatioTTM: shadow.ForwardPriceToEarningsGrowthRatioTTM, + PriceToBookRatioTTM: shadow.PriceToBookRatioTTM, + PriceToSalesRatioTTM: shadow.PriceToSalesRatioTTM, + PriceToFreeCashFlowRatioTTM: shadow.PriceToFreeCashFlowRatioTTM, + PriceToOperatingCashFlowRatioTTM: shadow.PriceToOperatingCashFlowRatioTTM, + DebtToAssetsRatioTTM: shadow.DebtToAssetsRatioTTM, + DebtToEquityRatioTTM: shadow.DebtToEquityRatioTTM, + DebtToCapitalRatioTTM: shadow.DebtToCapitalRatioTTM, + LongTermDebtToCapitalRatioTTM: shadow.LongTermDebtToCapitalRatioTTM, + FinancialLeverageRatioTTM: shadow.FinancialLeverageRatioTTM, + WorkingCapitalTurnoverRatioTTM: shadow.WorkingCapitalTurnoverRatioTTM, + OperatingCashFlowRatioTTM: shadow.OperatingCashFlowRatioTTM, + OperatingCashFlowSalesRatioTTM: shadow.OperatingCashFlowSalesRatioTTM, + FreeCashFlowOperatingCashFlowRatioTTM: shadow.FreeCashFlowOperatingCashFlowRatioTTM, + DebtServiceCoverageRatioTTM: shadow.DebtServiceCoverageRatioTTM, + InterestCoverageRatioTTM: shadow.InterestCoverageRatioTTM, + ShortTermOperatingCashFlowCoverageRatioTTM: shadow.ShortTermOperatingCashFlowCoverageRatioTTM, + OperatingCashFlowCoverageRatioTTM: shadow.OperatingCashFlowCoverageRatioTTM, + CapitalExpenditureCoverageRatioTTM: shadow.CapitalExpenditureCoverageRatioTTM, + DividendPaidAndCapexCoverageRatioTTM: shadow.DividendPaidAndCapexCoverageRatioTTM, + DividendPayoutRatioTTM: shadow.DividendPayoutRatioTTM, + DividendYieldTTM: shadow.DividendYieldTTM, + EnterpriseValueTTM: shadow.EnterpriseValueTTM, + RevenuePerShareTTM: shadow.RevenuePerShareTTM, + NetIncomePerShareTTM: shadow.NetIncomePerShareTTM, + InterestDebtPerShareTTM: shadow.InterestDebtPerShareTTM, + CashPerShareTTM: shadow.CashPerShareTTM, + BookValuePerShareTTM: shadow.BookValuePerShareTTM, + TangibleBookValuePerShareTTM: shadow.TangibleBookValuePerShareTTM, + ShareholdersEquityPerShareTTM: shadow.ShareholdersEquityPerShareTTM, + OperatingCashFlowPerShareTTM: shadow.OperatingCashFlowPerShareTTM, + CapexPerShareTTM: shadow.CapexPerShareTTM, + FreeCashFlowPerShareTTM: shadow.FreeCashFlowPerShareTTM, + NetIncomePerEbtTTM: shadow.NetIncomePerEbtTTM, + EbtPerEbitTTM: shadow.EbtPerEbitTTM, + PriceToFairValueTTM: shadow.PriceToFairValueTTM, + DebtToMarketCapTTM: shadow.DebtToMarketCapTTM, + EffectiveTaxRateTTM: shadow.EffectiveTaxRateTTM, + EnterpriseValueMultipleTTM: shadow.EnterpriseValueMultipleTTM, + DividendPerShareTTM: shadow.DividendPerShareTTM, } return nil } @@ -2225,11 +1531,11 @@ func (m *BulkStockPeer) UnmarshalJSONFrom(dec *jsontext.Decoder) error { // BulkEarningsSurprise is one worldwide annual earnings-surprise bulk row. type BulkEarningsSurprise struct { - Symbol string `json:"symbol"` - Date Date `json:"date"` - EPSActual string `json:"epsActual"` - EPSEstimated string `json:"epsEstimated"` - LastUpdated Date `json:"lastUpdated"` + Symbol string `json:"symbol"` + Date Date `json:"date"` + EPSActual *string `json:"epsActual"` + EPSEstimated *string `json:"epsEstimated"` + LastUpdated Date `json:"lastUpdated"` } // bulkEarningsSurpriseShadow mirrors BulkEarningsSurprise with a pointer or @@ -2256,18 +1562,14 @@ func (m *BulkEarningsSurprise) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return missingMemberError("BulkEarningsSurprise", "symbol") case shadow.Date == nil: return missingMemberError("BulkEarningsSurprise", "date") - case shadow.EPSActual == nil: - return missingMemberError("BulkEarningsSurprise", "epsActual") - case shadow.EPSEstimated == nil: - return missingMemberError("BulkEarningsSurprise", "epsEstimated") case shadow.LastUpdated == nil: return missingMemberError("BulkEarningsSurprise", "lastUpdated") } *m = BulkEarningsSurprise{ Symbol: *shadow.Symbol, Date: *shadow.Date, - EPSActual: *shadow.EPSActual, - EPSEstimated: *shadow.EPSEstimated, + EPSActual: shadow.EPSActual, + EPSEstimated: shadow.EPSEstimated, LastUpdated: *shadow.LastUpdated, } return nil @@ -2275,15 +1577,15 @@ func (m *BulkEarningsSurprise) UnmarshalJSONFrom(dec *jsontext.Decoder) error { // BulkStockRating is one worldwide stock-rating bulk row. type BulkStockRating struct { - Symbol string `json:"symbol"` - Date Date `json:"date"` - Rating string `json:"rating"` - DiscountedCashFlowScore string `json:"discountedCashFlowScore"` - ReturnOnEquityScore string `json:"returnOnEquityScore"` - ReturnOnAssetsScore string `json:"returnOnAssetsScore"` - DebtToEquityScore string `json:"debtToEquityScore"` - PriceToEarningsScore string `json:"priceToEarningsScore"` - PriceToBookScore string `json:"priceToBookScore"` + Symbol string `json:"symbol"` + Date Date `json:"date"` + Rating string `json:"rating"` + DiscountedCashFlowScore *string `json:"discountedCashFlowScore"` + ReturnOnEquityScore *string `json:"returnOnEquityScore"` + ReturnOnAssetsScore *string `json:"returnOnAssetsScore"` + DebtToEquityScore *string `json:"debtToEquityScore"` + PriceToEarningsScore *string `json:"priceToEarningsScore"` + PriceToBookScore *string `json:"priceToBookScore"` } // bulkStockRatingShadow mirrors BulkStockRating with a pointer or raw value @@ -2316,39 +1618,27 @@ func (m *BulkStockRating) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return missingMemberError("BulkStockRating", "date") case shadow.Rating == nil: return missingMemberError("BulkStockRating", "rating") - case shadow.DiscountedCashFlowScore == nil: - return missingMemberError("BulkStockRating", "discountedCashFlowScore") - case shadow.ReturnOnEquityScore == nil: - return missingMemberError("BulkStockRating", "returnOnEquityScore") - case shadow.ReturnOnAssetsScore == nil: - return missingMemberError("BulkStockRating", "returnOnAssetsScore") - case shadow.DebtToEquityScore == nil: - return missingMemberError("BulkStockRating", "debtToEquityScore") - case shadow.PriceToEarningsScore == nil: - return missingMemberError("BulkStockRating", "priceToEarningsScore") - case shadow.PriceToBookScore == nil: - return missingMemberError("BulkStockRating", "priceToBookScore") } *m = BulkStockRating{ Symbol: *shadow.Symbol, Date: *shadow.Date, Rating: *shadow.Rating, - DiscountedCashFlowScore: *shadow.DiscountedCashFlowScore, - ReturnOnEquityScore: *shadow.ReturnOnEquityScore, - ReturnOnAssetsScore: *shadow.ReturnOnAssetsScore, - DebtToEquityScore: *shadow.DebtToEquityScore, - PriceToEarningsScore: *shadow.PriceToEarningsScore, - PriceToBookScore: *shadow.PriceToBookScore, + DiscountedCashFlowScore: shadow.DiscountedCashFlowScore, + ReturnOnEquityScore: shadow.ReturnOnEquityScore, + ReturnOnAssetsScore: shadow.ReturnOnAssetsScore, + DebtToEquityScore: shadow.DebtToEquityScore, + PriceToEarningsScore: shadow.PriceToEarningsScore, + PriceToBookScore: shadow.PriceToBookScore, } return nil } // BulkDCFValuation is one worldwide discounted-cash-flow bulk row. type BulkDCFValuation struct { - Symbol string `json:"symbol"` - Date Date `json:"date"` - DCF string `json:"dcf"` - StockPrice string `json:"Stock Price"` + Symbol string `json:"symbol"` + Date Date `json:"date"` + DCF *string `json:"dcf"` + StockPrice *string `json:"Stock Price"` } // bulkDCFValuationShadow mirrors BulkDCFValuation with a pointer or raw value @@ -2374,40 +1664,19 @@ func (m *BulkDCFValuation) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return missingMemberError("BulkDCFValuation", "symbol") case shadow.Date == nil: return missingMemberError("BulkDCFValuation", "date") - case shadow.DCF == nil: - return missingMemberError("BulkDCFValuation", "dcf") - case shadow.StockPrice == nil: - return missingMemberError("BulkDCFValuation", "Stock Price") } *m = BulkDCFValuation{ Symbol: *shadow.Symbol, Date: *shadow.Date, - DCF: *shadow.DCF, - StockPrice: *shadow.StockPrice, + DCF: shadow.DCF, + StockPrice: shadow.StockPrice, } return nil } // BulkFinancialScore is one worldwide financial-score bulk row. type BulkFinancialScore struct { - Symbol string `json:"symbol"` - ReportedCurrency string `json:"reportedCurrency"` - AltmanZScore string `json:"altmanZScore"` - PiotroskiScore string `json:"piotroskiScore"` - WorkingCapital string `json:"workingCapital"` - TotalAssets string `json:"totalAssets"` - RetainedEarnings string `json:"retainedEarnings"` - Ebit string `json:"ebit"` - MarketCap string `json:"marketCap"` - TotalLiabilities string `json:"totalLiabilities"` - Revenue string `json:"revenue"` -} - -// bulkFinancialScoreShadow mirrors BulkFinancialScore with a pointer or raw -// value for every required member so a missing or null member is observable -// after decoding. -type bulkFinancialScoreShadow struct { - Symbol *string `json:"symbol"` + Symbol string `json:"symbol"` ReportedCurrency *string `json:"reportedCurrency"` AltmanZScore *string `json:"altmanZScore"` PiotroskiScore *string `json:"piotroskiScore"` @@ -2420,6 +1689,23 @@ type bulkFinancialScoreShadow struct { Revenue *string `json:"revenue"` } +// bulkFinancialScoreShadow mirrors BulkFinancialScore with a pointer or raw +// value for every required member so a missing or null member is observable +// after decoding. +type bulkFinancialScoreShadow struct { + Symbol *string `json:"symbol"` + ReportedCurrency jsontext.Value `json:"reportedCurrency"` + AltmanZScore *string `json:"altmanZScore"` + PiotroskiScore *string `json:"piotroskiScore"` + WorkingCapital *string `json:"workingCapital"` + TotalAssets *string `json:"totalAssets"` + RetainedEarnings *string `json:"retainedEarnings"` + Ebit *string `json:"ebit"` + MarketCap *string `json:"marketCap"` + TotalLiabilities *string `json:"totalLiabilities"` + Revenue *string `json:"revenue"` +} + // UnmarshalJSONFrom decodes one JSON object and rejects it with a Decode // error naming the first required member that is missing or null, as the // Rust decoder does. Unknown members are ignored. @@ -2431,55 +1717,47 @@ func (m *BulkFinancialScore) UnmarshalJSONFrom(dec *jsontext.Decoder) error { switch { case shadow.Symbol == nil: return missingMemberError("BulkFinancialScore", "symbol") - case shadow.ReportedCurrency == nil: + case len(shadow.ReportedCurrency) == 0: return missingMemberError("BulkFinancialScore", "reportedCurrency") - case shadow.AltmanZScore == nil: - return missingMemberError("BulkFinancialScore", "altmanZScore") - case shadow.PiotroskiScore == nil: - return missingMemberError("BulkFinancialScore", "piotroskiScore") - case shadow.WorkingCapital == nil: - return missingMemberError("BulkFinancialScore", "workingCapital") - case shadow.TotalAssets == nil: - return missingMemberError("BulkFinancialScore", "totalAssets") - case shadow.RetainedEarnings == nil: - return missingMemberError("BulkFinancialScore", "retainedEarnings") - case shadow.Ebit == nil: - return missingMemberError("BulkFinancialScore", "ebit") - case shadow.MarketCap == nil: - return missingMemberError("BulkFinancialScore", "marketCap") - case shadow.TotalLiabilities == nil: - return missingMemberError("BulkFinancialScore", "totalLiabilities") - case shadow.Revenue == nil: - return missingMemberError("BulkFinancialScore", "revenue") + } + var reportedCurrency *string + if shadow.ReportedCurrency.Kind() != 'n' { + var value string + if err := json.Unmarshal(shadow.ReportedCurrency, &value); err != nil { + return memberDecodeError("BulkFinancialScore", "reportedCurrency", shadow.ReportedCurrency, err) + } + if value != "" { + reportedCurrency = &value + } } *m = BulkFinancialScore{ Symbol: *shadow.Symbol, - ReportedCurrency: *shadow.ReportedCurrency, - AltmanZScore: *shadow.AltmanZScore, - PiotroskiScore: *shadow.PiotroskiScore, - WorkingCapital: *shadow.WorkingCapital, - TotalAssets: *shadow.TotalAssets, - RetainedEarnings: *shadow.RetainedEarnings, - Ebit: *shadow.Ebit, - MarketCap: *shadow.MarketCap, - TotalLiabilities: *shadow.TotalLiabilities, - Revenue: *shadow.Revenue, + ReportedCurrency: reportedCurrency, + AltmanZScore: shadow.AltmanZScore, + PiotroskiScore: shadow.PiotroskiScore, + WorkingCapital: shadow.WorkingCapital, + TotalAssets: shadow.TotalAssets, + RetainedEarnings: shadow.RetainedEarnings, + Ebit: shadow.Ebit, + MarketCap: shadow.MarketCap, + TotalLiabilities: shadow.TotalLiabilities, + Revenue: shadow.Revenue, } return nil } // BulkPriceTargetSummary is one US price-target-summary bulk row. type BulkPriceTargetSummary struct { - Symbol string `json:"symbol"` - LastMonthCount string `json:"lastMonthCount"` - LastMonthAvgPriceTarget string `json:"lastMonthAvgPriceTarget"` - LastQuarterCount string `json:"lastQuarterCount"` - LastQuarterAvgPriceTarget string `json:"lastQuarterAvgPriceTarget"` - LastYearCount string `json:"lastYearCount"` - LastYearAvgPriceTarget string `json:"lastYearAvgPriceTarget"` - AllTimeCount string `json:"allTimeCount"` - AllTimeAvgPriceTarget string `json:"allTimeAvgPriceTarget"` - Publishers string `json:"publishers"` + Symbol string `json:"symbol"` + LastMonthCount *string `json:"lastMonthCount"` + LastMonthAvgPriceTarget *string `json:"lastMonthAvgPriceTarget"` + LastQuarterCount *string `json:"lastQuarterCount"` + LastQuarterAvgPriceTarget *string `json:"lastQuarterAvgPriceTarget"` + LastYearCount *string `json:"lastYearCount"` + LastYearAvgPriceTarget *string `json:"lastYearAvgPriceTarget"` + AllTimeCount *string `json:"allTimeCount"` + AllTimeAvgPriceTarget *string `json:"allTimeAvgPriceTarget"` + Publishers string `json:"publishers"` } // bulkPriceTargetSummaryShadow mirrors BulkPriceTargetSummary with a pointer @@ -2509,35 +1787,19 @@ func (m *BulkPriceTargetSummary) UnmarshalJSONFrom(dec *jsontext.Decoder) error switch { case shadow.Symbol == nil: return missingMemberError("BulkPriceTargetSummary", "symbol") - case shadow.LastMonthCount == nil: - return missingMemberError("BulkPriceTargetSummary", "lastMonthCount") - case shadow.LastMonthAvgPriceTarget == nil: - return missingMemberError("BulkPriceTargetSummary", "lastMonthAvgPriceTarget") - case shadow.LastQuarterCount == nil: - return missingMemberError("BulkPriceTargetSummary", "lastQuarterCount") - case shadow.LastQuarterAvgPriceTarget == nil: - return missingMemberError("BulkPriceTargetSummary", "lastQuarterAvgPriceTarget") - case shadow.LastYearCount == nil: - return missingMemberError("BulkPriceTargetSummary", "lastYearCount") - case shadow.LastYearAvgPriceTarget == nil: - return missingMemberError("BulkPriceTargetSummary", "lastYearAvgPriceTarget") - case shadow.AllTimeCount == nil: - return missingMemberError("BulkPriceTargetSummary", "allTimeCount") - case shadow.AllTimeAvgPriceTarget == nil: - return missingMemberError("BulkPriceTargetSummary", "allTimeAvgPriceTarget") case shadow.Publishers == nil: return missingMemberError("BulkPriceTargetSummary", "publishers") } *m = BulkPriceTargetSummary{ Symbol: *shadow.Symbol, - LastMonthCount: *shadow.LastMonthCount, - LastMonthAvgPriceTarget: *shadow.LastMonthAvgPriceTarget, - LastQuarterCount: *shadow.LastQuarterCount, - LastQuarterAvgPriceTarget: *shadow.LastQuarterAvgPriceTarget, - LastYearCount: *shadow.LastYearCount, - LastYearAvgPriceTarget: *shadow.LastYearAvgPriceTarget, - AllTimeCount: *shadow.AllTimeCount, - AllTimeAvgPriceTarget: *shadow.AllTimeAvgPriceTarget, + LastMonthCount: shadow.LastMonthCount, + LastMonthAvgPriceTarget: shadow.LastMonthAvgPriceTarget, + LastQuarterCount: shadow.LastQuarterCount, + LastQuarterAvgPriceTarget: shadow.LastQuarterAvgPriceTarget, + LastYearCount: shadow.LastYearCount, + LastYearAvgPriceTarget: shadow.LastYearAvgPriceTarget, + AllTimeCount: shadow.AllTimeCount, + AllTimeAvgPriceTarget: shadow.AllTimeAvgPriceTarget, Publishers: *shadow.Publishers, } return nil @@ -2545,31 +1807,30 @@ func (m *BulkPriceTargetSummary) UnmarshalJSONFrom(dec *jsontext.Decoder) error // BulkETFHolding is one worldwide ETF holding bulk row. type BulkETFHolding struct { - Symbol string `json:"symbol"` - Name string `json:"name"` - SharesNumber string `json:"sharesNumber"` - Asset string `json:"asset"` - WeightPercentage string `json:"weightPercentage"` - CUSIP string `json:"cusip"` - ISIN string `json:"isin"` - MarketValue string `json:"marketValue"` - LastUpdatedRaw string `json:"-"` + Symbol string `json:"symbol"` + Name string `json:"name"` + SharesNumber *string `json:"sharesNumber"` + Asset *string `json:"asset"` + WeightPercentage *string `json:"weightPercentage"` + CUSIP string `json:"cusip"` + ISIN *string `json:"isin"` + MarketValue *string `json:"marketValue"` + LastUpdated Date `json:"lastUpdated"` } // bulkETFHoldingShadow mirrors BulkETFHolding with a pointer or raw value for // every required member so a missing or null member is observable after -// decoding. The embedded fallback collects the members json/v2 cannot spell in -// a struct tag. +// decoding. type bulkETFHoldingShadow struct { - Symbol *string `json:"symbol"` - Name *string `json:"name"` - SharesNumber *string `json:"sharesNumber"` - Asset *string `json:"asset"` - WeightPercentage *string `json:"weightPercentage"` - CUSIP *string `json:"cusip"` - ISIN *string `json:"isin"` - MarketValue *string `json:"marketValue"` - RawMembers map[string]jsontext.Value `json:",embed"` + Symbol *string `json:"symbol"` + Name *string `json:"name"` + SharesNumber *string `json:"sharesNumber"` + Asset jsontext.Value `json:"asset"` + WeightPercentage *string `json:"weightPercentage"` + CUSIP *string `json:"cusip"` + ISIN jsontext.Value `json:"isin"` + MarketValue *string `json:"marketValue"` + LastUpdated *Date `json:"lastUpdated"` } // UnmarshalJSONFrom decodes one JSON object and rejects it with a Decode @@ -2580,83 +1841,64 @@ func (m *BulkETFHolding) UnmarshalJSONFrom(dec *jsontext.Decoder) error { if err := json.UnmarshalDecode(dec, &shadow); err != nil { return err } - lastUpdatedRawWire := rawMember(shadow.RawMembers, "lastUpdated\"") switch { case shadow.Symbol == nil: return missingMemberError("BulkETFHolding", "symbol") case shadow.Name == nil: return missingMemberError("BulkETFHolding", "name") - case shadow.SharesNumber == nil: - return missingMemberError("BulkETFHolding", "sharesNumber") - case shadow.Asset == nil: + case len(shadow.Asset) == 0: return missingMemberError("BulkETFHolding", "asset") - case shadow.WeightPercentage == nil: - return missingMemberError("BulkETFHolding", "weightPercentage") case shadow.CUSIP == nil: return missingMemberError("BulkETFHolding", "cusip") - case shadow.ISIN == nil: + case len(shadow.ISIN) == 0: return missingMemberError("BulkETFHolding", "isin") - case shadow.MarketValue == nil: - return missingMemberError("BulkETFHolding", "marketValue") - case lastUpdatedRawWire == nil: - return missingMemberError("BulkETFHolding", "lastUpdated\"") + case shadow.LastUpdated == nil: + return missingMemberError("BulkETFHolding", "lastUpdated") + } + var asset *string + if shadow.Asset.Kind() != 'n' { + var value string + if err := json.Unmarshal(shadow.Asset, &value); err != nil { + return memberDecodeError("BulkETFHolding", "asset", shadow.Asset, err) + } + if value != "" { + asset = &value + } } - var lastUpdatedRaw string - if err := json.Unmarshal(lastUpdatedRawWire, &lastUpdatedRaw); err != nil { - return memberDecodeError("BulkETFHolding", "lastUpdated\"", lastUpdatedRawWire, err) + var isin *string + if shadow.ISIN.Kind() != 'n' { + var value string + if err := json.Unmarshal(shadow.ISIN, &value); err != nil { + return memberDecodeError("BulkETFHolding", "isin", shadow.ISIN, err) + } + if value != "" { + isin = &value + } } *m = BulkETFHolding{ Symbol: *shadow.Symbol, Name: *shadow.Name, - SharesNumber: *shadow.SharesNumber, - Asset: *shadow.Asset, - WeightPercentage: *shadow.WeightPercentage, + SharesNumber: shadow.SharesNumber, + Asset: asset, + WeightPercentage: shadow.WeightPercentage, CUSIP: *shadow.CUSIP, - ISIN: *shadow.ISIN, - MarketValue: *shadow.MarketValue, - LastUpdatedRaw: lastUpdatedRaw, + ISIN: isin, + MarketValue: shadow.MarketValue, + LastUpdated: *shadow.LastUpdated, } return nil } -// bulkETFHoldingPlain is BulkETFHolding without its JSON methods, so its -// tagged members marshal through the ordinary struct rules. -type bulkETFHoldingPlain BulkETFHolding - -// bulkETFHoldingEmit carries the members json/v2 cannot spell in a struct tag -// through an embedded fallback, next to the promoted tagged members of -// bulkETFHoldingPlain. -type bulkETFHoldingEmit struct { - bulkETFHoldingPlain - RawMembers map[string]jsontext.Value `json:",embed"` -} - -// MarshalJSONTo encodes the object with every member under its exact wire -// name, including the names json/v2 cannot spell in a struct tag. The value -// receiver keeps non-addressable values, such as slice elements, on this path. -func (m BulkETFHolding) MarshalJSONTo(enc *jsontext.Encoder) error { - rawMembers := make(map[string]jsontext.Value, 1) - lastUpdatedRawWire, err := json.Marshal(m.LastUpdatedRaw) - if err != nil { - return err - } - rawMembers["lastUpdated\""] = lastUpdatedRawWire - return json.MarshalEncode(enc, bulkETFHoldingEmit{ - bulkETFHoldingPlain: bulkETFHoldingPlain(m), - RawMembers: rawMembers, - }) -} - // BulkUpgradesDowngradesConsensus is one worldwide // upgrades/downgrades-consensus bulk row. type BulkUpgradesDowngradesConsensus struct { - Symbol string `json:"symbol"` - StrongBuy string `json:"strongBuy"` - Buy string `json:"buy"` - Hold string `json:"hold"` - Sell string `json:"sell"` - StrongSell string `json:"strongSell"` - Consensus string `json:"consensus"` + Symbol string `json:"symbol"` + StrongBuy *string `json:"strongBuy"` + Buy *string `json:"buy"` + Hold *string `json:"hold"` + Sell *string `json:"sell"` + StrongSell *string `json:"strongSell"` + Consensus string `json:"consensus"` } // bulkUpgradesDowngradesConsensusShadow mirrors @@ -2683,26 +1925,16 @@ func (m *BulkUpgradesDowngradesConsensus) UnmarshalJSONFrom(dec *jsontext.Decode switch { case shadow.Symbol == nil: return missingMemberError("BulkUpgradesDowngradesConsensus", "symbol") - case shadow.StrongBuy == nil: - return missingMemberError("BulkUpgradesDowngradesConsensus", "strongBuy") - case shadow.Buy == nil: - return missingMemberError("BulkUpgradesDowngradesConsensus", "buy") - case shadow.Hold == nil: - return missingMemberError("BulkUpgradesDowngradesConsensus", "hold") - case shadow.Sell == nil: - return missingMemberError("BulkUpgradesDowngradesConsensus", "sell") - case shadow.StrongSell == nil: - return missingMemberError("BulkUpgradesDowngradesConsensus", "strongSell") case shadow.Consensus == nil: return missingMemberError("BulkUpgradesDowngradesConsensus", "consensus") } *m = BulkUpgradesDowngradesConsensus{ Symbol: *shadow.Symbol, - StrongBuy: *shadow.StrongBuy, - Buy: *shadow.Buy, - Hold: *shadow.Hold, - Sell: *shadow.Sell, - StrongSell: *shadow.StrongSell, + StrongBuy: shadow.StrongBuy, + Buy: shadow.Buy, + Hold: shadow.Hold, + Sell: shadow.Sell, + StrongSell: shadow.StrongSell, Consensus: *shadow.Consensus, } return nil diff --git a/sdk/go/bulk_parity_test.go b/sdk/go/bulk_parity_test.go index 60bb6b16..7b312b81 100644 --- a/sdk/go/bulk_parity_test.go +++ b/sdk/go/bulk_parity_test.go @@ -1,10 +1,9 @@ package fmp import ( - "encoding/json/jsontext" + "encoding/csv" "encoding/json/v2" - "errors" - "strconv" + "slices" "strings" "testing" ) @@ -15,132 +14,81 @@ import ( // relatively from the shared fixture directory, never copied. func TestBulkFixturesDecodeAndReencodeToTheSameMemberSet(t *testing.T) { t.Parallel() - assertFixtureParity[CompanyProfile](t, "bulk_company_profiles.json") - assertFixtureParity[BulkStockRating](t, "bulk_stock_ratings.json") - assertFixtureParity[BulkDCFValuation](t, "bulk_dcf_valuations.json") - assertFixtureParity[BulkFinancialScore](t, "bulk_financial_scores.json") - assertFixtureParity[BulkPriceTargetSummary](t, "bulk_price_target_summaries.json") - assertFixtureParity[BulkETFHolding](t, "bulk_etf_holdings.json") - assertFixtureParity[BulkUpgradesDowngradesConsensus](t, "bulk_upgrades_downgrades_consensus.json") - assertFixtureParity[BulkKeyMetricsTTM](t, "bulk_key_metrics_ttm.json") - assertFixtureParity[BulkFinancialRatiosTTM](t, "bulk_financial_ratios_ttm.json") - assertFixtureParity[BulkStockPeer](t, "bulk_stock_peers.json") - assertFixtureParity[BulkEarningsSurprise](t, "bulk_earnings_surprises.json") - assertFixtureParity[BulkIncomeStatement](t, "bulk_income_statements.json") - assertFixtureParity[BulkIncomeStatementGrowth](t, "bulk_income_statement_growth.json") - assertFixtureParity[BulkBalanceSheetStatement](t, "bulk_balance_sheet_statements.json") - assertFixtureParity[BulkBalanceSheetStatementGrowth](t, "bulk_balance_sheet_statement_growth.json") - assertFixtureParity[BulkCashFlowStatement](t, "bulk_cash_flow_statements.json") - assertFixtureParity[BulkCashFlowStatementGrowth](t, "bulk_cash_flow_statement_growth.json") - assertFixtureParity[BulkEodBar](t, "bulk_eod.json") + assertCSVFixtureParity[CompanyProfile](t, "bulk_company_profiles.csv", 36) + assertCSVFixtureParity[BulkStockRating](t, "bulk_stock_ratings.csv", 9) + assertCSVFixtureParity[BulkDCFValuation](t, "bulk_dcf_valuations.csv", 4) + assertCSVFixtureParity[BulkFinancialScore](t, "bulk_financial_scores.csv", 11) + assertCSVFixtureParity[BulkPriceTargetSummary](t, "bulk_price_target_summaries.csv", 10) + assertCSVFixtureParity[BulkETFHolding](t, "bulk_etf_holdings.csv", 9) + assertCSVFixtureParity[BulkUpgradesDowngradesConsensus](t, "bulk_upgrades_downgrades_consensus.csv", 7) + assertCSVFixtureParity[BulkKeyMetricsTTM](t, "bulk_key_metrics_ttm.csv", 43) + assertCSVFixtureParity[BulkFinancialRatiosTTM](t, "bulk_financial_ratios_ttm.csv", 60) + assertCSVFixtureParity[BulkStockPeer](t, "bulk_stock_peers.csv", 2) + assertCSVFixtureParity[BulkEarningsSurprise](t, "bulk_earnings_surprises.csv", 5) + assertCSVFixtureParity[BulkIncomeStatement](t, "bulk_income_statements.csv", 39) + assertCSVFixtureParity[BulkIncomeStatementGrowth](t, "bulk_income_statement_growth.csv", 34) + assertCSVFixtureParity[BulkBalanceSheetStatement](t, "bulk_balance_sheet_statements.csv", 61) + assertCSVFixtureParity[BulkBalanceSheetStatementGrowth](t, "bulk_balance_sheet_statement_growth.csv", 56) + assertCSVFixtureParity[BulkCashFlowStatement](t, "bulk_cash_flow_statements.csv", 47) + assertCSVFixtureParity[BulkCashFlowStatementGrowth](t, "bulk_cash_flow_statement_growth.csv", 42) + assertCSVFixtureParity[BulkEodBar](t, "bulk_eod.csv", 8) } // Exact values copied from crates/libfmp/tests/bulk_snapshot_responses.rs: -// every numeric cell stays the provider's string, and the documented source -// defects (the "Stock Price" key, the malformed publishers text, the empty -// CUSIP and consensus symbol) are preserved verbatim. -func TestDocumentedBulkSnapshotsDecodeExactValues(t *testing.T) { +// every numeric cell stays the provider's text, and the provider spellings +// (the "Stock Price" column, the quoted publishers list, the " -- " ETF +// symbol) are preserved verbatim. +func TestBulkSnapshotsDecodeExactValues(t *testing.T) { t.Parallel() - profiles := assertFixtureParity[CompanyProfile](t, "bulk_company_profiles.json") - if len(profiles) != 1 || profiles[0].Symbol != "AAPL" || profiles[0].CIK == nil || *profiles[0].CIK != "0000320193" { - t.Fatalf("bulk_company_profiles = %+v", profiles) + profiles := assertCSVFixtureParity[CompanyProfile](t, "bulk_company_profiles.csv", 36) + if profiles[0].Symbol != "WMB" || profiles[0].CompanyName != "The Williams Companies, Inc." || + profiles[0].CIK == nil || *profiles[0].CIK != "0000107263" || profiles[0].IsETF { + t.Fatalf("bulk_company_profiles = %+v", profiles[0]) } - ratings := assertFixtureParity[BulkStockRating](t, "bulk_stock_ratings.json") - if want := (BulkStockRating{Symbol: "000001.SZ", Date: mustParseDate(t, "2025-07-09"), Rating: "B+", - DiscountedCashFlowScore: "5", ReturnOnEquityScore: "3", ReturnOnAssetsScore: "2", DebtToEquityScore: "1", - PriceToEarningsScore: "4", PriceToBookScore: "4"}); len(ratings) != 1 || ratings[0] != want { - t.Fatalf("bulk_stock_ratings = %+v", ratings) + if profiles[2].Symbol != "ACCV" || profiles[2].Change != nil || profiles[2].ChangePercentage != nil || + profiles[3].Symbol != "CFHAX" || profiles[3].ISIN != nil || profiles[3].Country != nil || profiles[3].Ceo != "" { + t.Fatalf("empty profile cells = %+v, %+v", profiles[2], profiles[3]) } - dcf := assertFixtureParity[BulkDCFValuation](t, "bulk_dcf_valuations.json") - if want := (BulkDCFValuation{Symbol: "000002.SZ", Date: mustParseDate(t, "2025-07-09"), - DCF: "179.6654688379575", StockPrice: "6.54"}); len(dcf) != 1 || dcf[0] != want { - t.Fatalf("bulk_dcf_valuations = %+v", dcf) + ratings := assertCSVFixtureParity[BulkStockRating](t, "bulk_stock_ratings.csv", 9) + if ratings[0].Rating != "B-" || cellText(ratings[0].PriceToBookScore) != "4" { + t.Fatalf("bulk_stock_ratings = %+v", ratings[0]) } - if encoded, err := json.Marshal(dcf[0]); err != nil || !strings.Contains(string(encoded), `"Stock Price":"6.54"`) { + dcf := assertCSVFixtureParity[BulkDCFValuation](t, "bulk_dcf_valuations.csv", 4) + if encoded, err := json.Marshal(dcf[0]); err != nil || !strings.Contains(string(encoded), `"Stock Price":"7.62"`) { t.Fatalf("re-encoded dcf = %s, %v", encoded, err) } - scores := assertFixtureParity[BulkFinancialScore](t, "bulk_financial_scores.json") - if len(scores) != 1 || scores[0].Symbol != "000001.SZ" || scores[0].ReportedCurrency != "CNY" || - scores[0].AltmanZScore != "0.29153682196643543" || scores[0].PiotroskiScore != "5" || - scores[0].TotalAssets != "5777858000000" { - t.Fatalf("bulk_financial_scores = %+v", scores) + if dcf[2].Symbol != "000023.SZ" || dcf[2].DCF != nil || cellText(dcf[2].StockPrice) != "1.72" { + t.Fatalf("empty dcf cell = %+v", dcf[2]) } - targets := assertFixtureParity[BulkPriceTargetSummary](t, "bulk_price_target_summaries.json") - if len(targets) != 1 || targets[0].Symbol != "A" || targets[0].Publishers != `[""TheFly"` || - targets[0].LastMonthCount != "0" || targets[0].AllTimeAvgPriceTarget != "146.61" { - t.Fatalf("bulk_price_target_summaries = %+v", targets) + scores := assertCSVFixtureParity[BulkFinancialScore](t, "bulk_financial_scores.csv", 11) + if scores[2].Symbol != "AAAU" || scores[2].ReportedCurrency != nil || scores[2].AltmanZScore != nil || + cellText(scores[2].PiotroskiScore) != "2" { + t.Fatalf("empty score cells = %+v", scores[2]) } - consensus := assertFixtureParity[BulkUpgradesDowngradesConsensus](t, "bulk_upgrades_downgrades_consensus.json") - if want := (BulkUpgradesDowngradesConsensus{Symbol: "", StrongBuy: "0", Buy: "1", Hold: "1", Sell: "0", - StrongSell: "0", Consensus: "Buy"}); len(consensus) != 1 || consensus[0] != want { - t.Fatalf("bulk_upgrades_downgrades_consensus = %+v", consensus) + targets := assertCSVFixtureParity[BulkPriceTargetSummary](t, "bulk_price_target_summaries.csv", 10) + if targets[0].Publishers != `["StreetInsider","Benzinga","Pulse 2.0"]` || + cellText(targets[0].AllTimeAvgPriceTarget) != "159.49" { + t.Fatalf("bulk_price_target_summaries = %+v", targets[0]) } -} - -// The ETF holding row carries the documented wire key `lastUpdated"`, which -// encoding/json/v2 cannot spell in a struct tag: the member travels through -// the embedded fallback and MarshalJSONTo (see rawMember). Its value keeps -// the trailing quote, re-encodes under the exact key, and is required. -func TestBulkEtfHoldingKeepsTheQuotedLastUpdatedKeyVerbatim(t *testing.T) { - t.Parallel() - holdings := assertFixtureParity[BulkETFHolding](t, "bulk_etf_holdings.json") - if want := (BulkETFHolding{Symbol: "EXCH.AS", Name: "SAMSUNG ELECTRO MECHANICS LTD", SharesNumber: "15514", - Asset: "009150.KS", WeightPercentage: "0.09611", CUSIP: "", ISIN: "KR7009150004", MarketValue: "1553142.49", - LastUpdatedRaw: `2024-09-06"`}); len(holdings) != 1 || holdings[0] != want { - t.Fatalf("bulk_etf_holdings = %+v", holdings) + holdings := assertCSVFixtureParity[BulkETFHolding](t, "bulk_etf_holdings.csv", 9) + if holdings[0].Symbol != " -- " || cellText(holdings[0].Asset) != "TDW" || + holdings[0].LastUpdated != mustParseDate(t, "2026-09-27") { + t.Fatalf("bulk_etf_holdings = %+v", holdings[0]) } - encoded, err := json.Marshal(holdings) - if err != nil || !strings.Contains(string(encoded), `"lastUpdated\"":"2024-09-06\""`) || - strings.Contains(string(encoded), `"lastUpdated":`) || strings.Contains(string(encoded), "RawMembers") { - t.Fatalf("re-encoded holdings = %s, %v", encoded, err) - } - var decoded []BulkETFHolding - if err := json.Unmarshal(encoded, &decoded); err != nil || len(decoded) != 1 || decoded[0] != holdings[0] { - t.Fatalf("round trip = %+v, %v", decoded, err) - } - - var wire []map[string]jsontext.Value - if err := json.Unmarshal(readFixture(t, "bulk_etf_holdings.json"), &wire); err != nil { - t.Fatal(err) - } - for _, tc := range []struct{ name, member, value string }{ - {"missing raw key", `lastUpdated"`, ""}, - {"null raw key", `lastUpdated"`, "null"}, - {"missing tagged member", "symbol", ""}, - {"null tagged member", "symbol", "null"}, - } { - row := map[string]jsontext.Value{} - for member, value := range wire[0] { - row[member] = value - } - delete(row, tc.member) - if tc.value != "" { - row[tc.member] = jsontext.Value(tc.value) - } - body, err := json.Marshal([]map[string]jsontext.Value{row}) - if err != nil { - t.Fatal(err) - } - var rows []BulkETFHolding - err = json.Unmarshal(body, &rows) - var typed *Error - if !errors.As(err, &typed) || typed.Category != CategoryDecode || - !strings.Contains(typed.Message, "required member "+strconv.Quote(tc.member)+" of BulkETFHolding") { - t.Fatalf("%s: error = %v, want a Decode *Error naming %q", tc.name, err, tc.member) - } + if holdings[2].Name != "Other/Cash" || holdings[2].Asset != nil || holdings[2].ISIN != nil || holdings[2].CUSIP != "" { + t.Fatalf("empty holding cells = %+v", holdings[2]) } } // Exact values copied from crates/libfmp/tests/bulk_metrics_responses.rs, -// including the beyond-u64 and high-precision strings that must survive -// untouched (ADR 0028: numeric strings are never parsed). +// including the beyond-u64, high-precision, and exponent cells that must +// survive untouched (numeric strings are never parsed). func TestBulkMetricsPreserveNumericTextIncludingBeyondU64(t *testing.T) { t.Parallel() - metrics := assertFixtureParity[BulkKeyMetricsTTM](t, "bulk_key_metrics_ttm.json") - if len(metrics) != 1 || metrics[0].Symbol != "000001.SZ" || metrics[0].MarketCap != "249171756000" || - metrics[0].EnterpriseValueTTM != "-496959244000" || metrics[0].CurrentRatioTTM != "0" || - metrics[0].FreeCashFlowToFirmTTM != "-35237570137.11014" { - t.Fatalf("bulk_key_metrics_ttm = %+v", metrics) + metrics := assertCSVFixtureParity[BulkKeyMetricsTTM](t, "bulk_key_metrics_ttm.csv", 43) + if metrics[0].Symbol != "000001.SZ" || cellText(metrics[0].MarketCap) != "224526473551" || + cellText(metrics[0].EvToEbitdaTTM) != "29.23198788110799" { + t.Fatalf("bulk_key_metrics_ttm = %+v", metrics[0]) } encoded, err := json.Marshal(metrics[0]) if err != nil || !strings.Contains(string(encoded), `"evToEBITDATTM":`) || @@ -149,114 +97,88 @@ func TestBulkMetricsPreserveNumericTextIncludingBeyondU64(t *testing.T) { strings.Contains(string(encoded), "Ttm") { t.Fatalf("re-encoded key metrics = %s, %v", encoded, err) } - ratios := assertFixtureParity[BulkFinancialRatiosTTM](t, "bulk_financial_ratios_ttm.json") - if len(ratios) != 1 || ratios[0].EnterpriseValueTTM != "-496959244000" || ratios[0].ReceivablesTurnoverTTM != "0" || - ratios[0].GrossProfitMarginTTM != "1.1622776732779352" { - t.Fatalf("bulk_financial_ratios_ttm = %+v", ratios) - } - if encoded, err := json.Marshal(ratios[0]); err != nil || !strings.Contains(string(encoded), `"netIncomePerEBTTTM":`) { - t.Fatalf("re-encoded ratios = %s, %v", encoded, err) + ratios := assertCSVFixtureParity[BulkFinancialRatiosTTM](t, "bulk_financial_ratios_ttm.csv", 60) + if cellText(ratios[0].GrossProfitMarginTTM) != "0.5535250166330814" || cellText(ratios[0].NetIncomePerEbtTTM) != "0.83539656299258" { + t.Fatalf("bulk_financial_ratios_ttm = %+v", ratios[0]) } const beyondU64 = "18446744073709551616" const highPrecision = "-12345678901234567890123456789.123456789012345678901234567890" - var wire []map[string]jsontext.Value - if err := json.Unmarshal(readFixture(t, "bulk_key_metrics_ttm.json"), &wire); err != nil { - t.Fatal(err) - } - wire[0]["marketCap"] = jsontext.Value(`"` + beyondU64 + `"`) - wire[0]["freeCashFlowToFirmTTM"] = jsontext.Value(`"` + highPrecision + `"`) - huge, err := json.Marshal(wire) + records, err := csv.NewReader(strings.NewReader(string(readFixture(t, "bulk_key_metrics_ttm.csv")))).ReadAll() if err != nil { t.Fatal(err) } - var decoded []BulkKeyMetricsTTM - if err := json.Unmarshal(huge, &decoded); err != nil || len(decoded) != 1 || - decoded[0].MarketCap != beyondU64 || decoded[0].FreeCashFlowToFirmTTM != highPrecision { + row := slices.Clone(records[1]) + for name, value := range map[string]string{"marketCap": beyondU64, "freeCashFlowToFirmTTM": highPrecision, + "evToSalesTTM": "6.9148336e-9"} { + row[slices.Index(records[0], name)] = value + } + var body strings.Builder + writer := csv.NewWriter(&body) + _ = writer.WriteAll([][]string{records[0], row}) + decoded, _, kind, err := decodeCSVRows[BulkKeyMetricsTTM]([]byte(body.String())) + if kind != DecodeKindNone || cellText(decoded[0].MarketCap) != beyondU64 || + cellText(decoded[0].FreeCashFlowToFirmTTM) != highPrecision || cellText(decoded[0].EvToSalesTTM) != "6.9148336e-9" { t.Fatalf("beyond-u64 decode = %+v, %v", decoded, err) } - if encoded, err := json.Marshal(decoded[0]); err != nil || - !strings.Contains(string(encoded), `"marketCap":"`+beyondU64+`"`) || - !strings.Contains(string(encoded), `"freeCashFlowToFirmTTM":"`+highPrecision+`"`) { - t.Fatalf("beyond-u64 re-encode = %s, %v", encoded, err) - } - wire[0]["marketCap"] = jsontext.Value(`249171756000`) - numeric, err := json.Marshal(wire) - if err != nil { - t.Fatal(err) + + peers := assertCSVFixtureParity[BulkStockPeer](t, "bulk_stock_peers.csv", 2) + if want := (BulkStockPeer{Symbol: "000001.SZ", + Peers: "3698.HK,600000.SS,600015.SS,600016.SS,600036.SS,601166.SS,601658.SS"}); peers[0] != want { + t.Fatalf("bulk_stock_peers = %+v", peers[0]) } - if err := json.Unmarshal(numeric, &decoded); err == nil { - t.Fatalf("a JSON number decoded into the numeric-string member marketCap: %+v", decoded) + surprises := assertCSVFixtureParity[BulkEarningsSurprise](t, "bulk_earnings_surprises.csv", 5) + if surprises[0].Symbol != "AUTO.OL" || cellText(surprises[0].EPSActual) != "0.1332" || + surprises[0].LastUpdated != mustParseDate(t, "2025-10-07") { + t.Fatalf("bulk_earnings_surprises = %+v", surprises[0]) } - - peers := assertFixtureParity[BulkStockPeer](t, "bulk_stock_peers.json") - if want := (BulkStockPeer{Symbol: "000001.SZ", Peers: "600036.SS"}); len(peers) != 1 || peers[0] != want { - t.Fatalf("bulk_stock_peers = %+v", peers) + if surprises[2].Symbol != "GUD.TO" || surprises[2].EPSEstimated != nil || cellText(surprises[2].EPSActual) != "0.00084" { + t.Fatalf("empty earnings cell = %+v", surprises[2]) } - surprises := assertFixtureParity[BulkEarningsSurprise](t, "bulk_earnings_surprises.json") - if want := (BulkEarningsSurprise{Symbol: "AMKYF", Date: mustParseDate(t, "2025-07-09"), EPSActual: "0.3631", - EPSEstimated: "0.3615", LastUpdated: mustParseDate(t, "2025-07-09")}); len(surprises) != 1 || surprises[0] != want { - t.Fatalf("bulk_earnings_surprises = %+v", surprises) + if metrics[2].Symbol != "ADAMO" || metrics[2].EnterpriseValueTTM != nil || peers[2].Peers != "" { + t.Fatalf("empty metric cells = %+v, %+v", metrics[2], peers[2]) } } // Exact values copied from crates/libfmp/tests/bulk_{income,balance,cash_eod}_responses.rs: -// identity fields keep their documented representations (ten-zero CIK, naive -// accepted date, Q1 period) and the provider typos keep their wire spelling. +// identity fields keep their provider representations (ten-zero CIK, naive +// accepted date, period) and the provider typos keep their wire spelling. func TestBulkStatementsDecodeExactValuesAndKeepProviderTypos(t *testing.T) { t.Parallel() - income := assertFixtureParity[BulkIncomeStatement](t, "bulk_income_statements.json") - if len(income) != 1 || income[0].Symbol != "000001.SZ" || income[0].ReportedCurrency != "CNY" || - income[0].CIK != "0000000000" || income[0].Date != mustParseDate(t, "2025-03-31") || - income[0].FilingDate != mustParseDate(t, "2025-03-31") || - income[0].AcceptedDate != mustParseDateTime(t, "2025-03-31 00:00:00") || income[0].FiscalYear != "2025" || - income[0].Period != "Q1" || income[0].Revenue != "33644000000" || income[0].CostOfRevenue != "0" || - income[0].TotalOtherIncomeExpensesNet != "-7392000000" || income[0].EPS != "0.62" || - income[0].WeightedAverageShsOut != "22735483871" { - t.Fatalf("bulk_income_statements = %+v", income) + income := assertCSVFixtureParity[BulkIncomeStatement](t, "bulk_income_statements.csv", 39) + if income[0].Symbol != "000001.SZ" || income[0].ReportedCurrency != "CNY" || income[0].CIK != "0000000000" || + income[0].Date != mustParseDate(t, "2024-12-31") || + income[0].AcceptedDate != mustParseDateTime(t, "2024-12-31 00:00:00") || income[0].FiscalYear != "2024" || + income[0].Period != "FY" || cellText(income[0].Revenue) != "251641000000" { + t.Fatalf("bulk_income_statements = %+v", income[0]) } - incomeGrowth := assertFixtureParity[BulkIncomeStatementGrowth](t, "bulk_income_statement_growth.json") - if len(incomeGrowth) != 1 || incomeGrowth[0].GrowthEbit != "1" || incomeGrowth[0].GrowthCostOfRevenue != "0" || - incomeGrowth[0].GrowthOtherExpenses != "-0.9860376183912135" || - incomeGrowth[0].GrowthOperatingIncome != "-0.018874787810201278" { - t.Fatalf("bulk_income_statement_growth = %+v", incomeGrowth) + if income[2].Symbol != "OASMY" || income[2].EPS != nil || cellText(income[2].NetIncome) != "-39754000" { + t.Fatalf("empty income cells = %+v", income[2]) } + incomeGrowth := assertCSVFixtureParity[BulkIncomeStatementGrowth](t, "bulk_income_statement_growth.csv", 34) if encoded, err := json.Marshal(incomeGrowth[0]); err != nil || !strings.Contains(string(encoded), `"growthEBITDA":`) || !strings.Contains(string(encoded), `"growthEPSDiluted":`) || !strings.Contains(string(encoded), `"growthEBIT":`) { t.Fatalf("re-encoded income growth = %s, %v", encoded, err) } - balance := assertFixtureParity[BulkBalanceSheetStatement](t, "bulk_balance_sheet_statements.json") - if len(balance) != 1 || balance[0].Symbol != "MTLRP.ME" || balance[0].ReportedCurrency != "RUB" || - balance[0].CIK != "0000000000" || balance[0].FilingDate != mustParseDate(t, "2025-05-31") || - balance[0].AcceptedDate != mustParseDateTime(t, "2025-03-31 07:00:00") || balance[0].Period != "Q1" || - balance[0].TotalAssets != "247871857000" || balance[0].RetainedEarnings != "-5066509000" || - balance[0].OtherAssets != "0" || balance[0].NetDebt != "183764862000" { + balance := assertCSVFixtureParity[BulkBalanceSheetStatement](t, "bulk_balance_sheet_statements.csv", 61) + if balance[1].Symbol != "0002.KL" || balance[1].ReportedCurrency != "MYR" || + balance[1].AcceptedDate != mustParseDateTime(t, "2025-06-30 00:00:00") || cellText(balance[0].TotalAssets) != "5769270000000" { t.Fatalf("bulk_balance_sheet_statements = %+v", balance) } - balanceGrowth := assertFixtureParity[BulkBalanceSheetStatementGrowth](t, "bulk_balance_sheet_statement_growth.json") - if len(balanceGrowth) != 1 || balanceGrowth[0].GrowthCashAndCashEquivalents != "0.09574482145872953" || - balanceGrowth[0].GrowthShortTermInvestments != "0" || balanceGrowth[0].GrowthTotalPayables != "-0.12022416350749959" { - t.Fatalf("bulk_balance_sheet_statement_growth = %+v", balanceGrowth) - } + balanceGrowth := assertCSVFixtureParity[BulkBalanceSheetStatementGrowth](t, "bulk_balance_sheet_statement_growth.csv", 56) if encoded, err := json.Marshal(balanceGrowth[0]); err != nil || !strings.Contains(string(encoded), `"growthOthertotalStockholdersEquity":`) || !strings.Contains(string(encoded), `"growthTotalLiabilitiesAndStockholdersEquity":`) { t.Fatalf("re-encoded balance growth = %s, %v", encoded, err) } - cashFlow := assertFixtureParity[BulkCashFlowStatement](t, "bulk_cash_flow_statements.json") - if len(cashFlow) != 1 || cashFlow[0].Symbol != "000001.SZ" || cashFlow[0].CIK != "0000000000" || - cashFlow[0].AcceptedDate != mustParseDateTime(t, "2025-03-31 00:00:00") || cashFlow[0].Period != "Q1" || - cashFlow[0].OtherNonCashItems != "162946000000" || cashFlow[0].PurchasesOfInvestments != "-227916000000" || - cashFlow[0].NetIncome != "0" { - t.Fatalf("bulk_cash_flow_statements = %+v", cashFlow) - } - cashGrowth := assertFixtureParity[BulkCashFlowStatementGrowth](t, "bulk_cash_flow_statement_growth.json") - if len(cashGrowth) != 1 || cashGrowth[0].GrowthNetCashUsedProvidedByFinancingActivities != "-3.2122934677858628" || - cashGrowth[0].GrowthNetDebtIssuance != "1" { - t.Fatalf("bulk_cash_flow_statement_growth = %+v", cashGrowth) + cashFlow := assertCSVFixtureParity[BulkCashFlowStatement](t, "bulk_cash_flow_statements.csv", 47) + if cashFlow[0].CIK != "0000000000" || cashFlow[0].AcceptedDate != mustParseDateTime(t, "2024-03-30 20:00:00") || + cashFlow[0].Period != "Q1" || cellText(cashFlow[0].NetIncome) != "14932000000" { + t.Fatalf("bulk_cash_flow_statements = %+v", cashFlow[0]) } + cashGrowth := assertCSVFixtureParity[BulkCashFlowStatementGrowth](t, "bulk_cash_flow_statement_growth.csv", 42) if encoded, err := json.Marshal(cashGrowth[0]); err != nil || !strings.Contains(string(encoded), `"growthNetCashProvidedByOperatingActivites":`) || !strings.Contains(string(encoded), `"growthOtherInvestingActivites":`) || @@ -265,9 +187,9 @@ func TestBulkStatementsDecodeExactValuesAndKeepProviderTypos(t *testing.T) { t.Fatalf("re-encoded cash-flow growth = %s, %v", encoded, err) } - eod := assertFixtureParity[BulkEodBar](t, "bulk_eod.json") - if want := (BulkEodBar{Symbol: "EGS745W1C011.CA", Date: mustParseDate(t, "2024-10-22"), Open: "2.67", Low: "2.7", - High: "2.9", Close: "2.93", AdjClose: "2.93", Volume: "920904"}); len(eod) != 1 || eod[0] != want { - t.Fatalf("bulk_eod = %+v", eod) + eod := assertCSVFixtureParity[BulkEodBar](t, "bulk_eod.csv", 8) + if eod[1].Symbol != "HKDCNH" || eod[1].Date != mustParseDate(t, "2025-06-02") || + cellText(eod[1].Open) != "0.91858" || cellText(eod[1].Volume) != "0" { + t.Fatalf("bulk_eod = %+v", eod[1]) } } diff --git a/sdk/go/bulk_test.go b/sdk/go/bulk_test.go index e906b240..3fde6eaa 100644 --- a/sdk/go/bulk_test.go +++ b/sdk/go/bulk_test.go @@ -14,24 +14,24 @@ import ( // crates/libfmp/tests/bulk_*_endpoints.rs (year before period, one GET, no // fan-out over parts). var bulkRoutes = map[string]string{ - "/router/stable/profile-bulk?part=0": "bulk_company_profiles.json", - "/router/stable/rating-bulk": "bulk_stock_ratings.json", - "/router/stable/dcf-bulk": "bulk_dcf_valuations.json", - "/router/stable/scores-bulk": "bulk_financial_scores.json", - "/router/stable/price-target-summary-bulk": "bulk_price_target_summaries.json", - "/router/stable/etf-holder-bulk?part=segment+A%2F7": "bulk_etf_holdings.json", - "/router/stable/upgrades-downgrades-consensus-bulk": "bulk_upgrades_downgrades_consensus.json", - "/router/stable/key-metrics-ttm-bulk": "bulk_key_metrics_ttm.json", - "/router/stable/ratios-ttm-bulk": "bulk_financial_ratios_ttm.json", - "/router/stable/peers-bulk": "bulk_stock_peers.json", - "/router/stable/earnings-surprises-bulk?year=4294967295": "bulk_earnings_surprises.json", - "/router/stable/income-statement-bulk?year=2026&period=Q1": "bulk_income_statements.json", - "/router/stable/income-statement-growth-bulk?year=2026&period=Q2": "bulk_income_statement_growth.json", - "/router/stable/balance-sheet-statement-bulk?year=2026&period=Q3": "bulk_balance_sheet_statements.json", - "/router/stable/balance-sheet-statement-growth-bulk?year=2026&period=Q4": "bulk_balance_sheet_statement_growth.json", - "/router/stable/cash-flow-statement-bulk?year=4294967295&period=FY": "bulk_cash_flow_statements.json", - "/router/stable/cash-flow-statement-growth-bulk?year=2025&period=FY": "bulk_cash_flow_statement_growth.json", - "/router/stable/eod-bulk?date=2024-10-22": "bulk_eod.json", + "/router/stable/profile-bulk?part=0": "bulk_company_profiles.csv", + "/router/stable/rating-bulk": "bulk_stock_ratings.csv", + "/router/stable/dcf-bulk": "bulk_dcf_valuations.csv", + "/router/stable/scores-bulk": "bulk_financial_scores.csv", + "/router/stable/price-target-summary-bulk": "bulk_price_target_summaries.csv", + "/router/stable/etf-holder-bulk?part=segment+A%2F7": "bulk_etf_holdings.csv", + "/router/stable/upgrades-downgrades-consensus-bulk": "bulk_upgrades_downgrades_consensus.csv", + "/router/stable/key-metrics-ttm-bulk": "bulk_key_metrics_ttm.csv", + "/router/stable/ratios-ttm-bulk": "bulk_financial_ratios_ttm.csv", + "/router/stable/peers-bulk": "bulk_stock_peers.csv", + "/router/stable/earnings-surprises-bulk?year=4294967295": "bulk_earnings_surprises.csv", + "/router/stable/income-statement-bulk?year=2026&period=Q1": "bulk_income_statements.csv", + "/router/stable/income-statement-growth-bulk?year=2026&period=Q2": "bulk_income_statement_growth.csv", + "/router/stable/balance-sheet-statement-bulk?year=2026&period=Q3": "bulk_balance_sheet_statements.csv", + "/router/stable/balance-sheet-statement-growth-bulk?year=2026&period=Q4": "bulk_balance_sheet_statement_growth.csv", + "/router/stable/cash-flow-statement-bulk?year=4294967295&period=FY": "bulk_cash_flow_statements.csv", + "/router/stable/cash-flow-statement-growth-bulk?year=2025&period=FY": "bulk_cash_flow_statement_growth.csv", + "/router/stable/eod-bulk?date=2024-10-22": "bulk_eod.csv", } func bulkRouter(t *testing.T) http.HandlerFunc { @@ -44,6 +44,9 @@ func bulkRouter(t *testing.T) http.HandlerFunc { return } w.Header().Set("Content-Type", "application/json") + if strings.HasSuffix(fixture, ".csv") { + w.Header().Set("Content-Type", "text/csv") + } _, _ = w.Write(readFixture(t, fixture)) } } @@ -55,75 +58,75 @@ func TestBulkMethodsUseExactPathsAndWireParameterOrder(t *testing.T) { ctx := context.Background() profiles, err := client.Bulk.CompanyProfiles(ctx, NewBulkPartQuery("0")) - if err != nil || len(profiles) != 1 || profiles[0].Symbol != "AAPL" || profiles[0].MarketCap != 4_009_711_150_080 { + if err != nil || len(profiles) != 4 || profiles[0].Symbol != "WMB" || profiles[0].MarketCap != 82_906_291_252 { t.Fatalf("CompanyProfiles = %+v, %v", profiles, err) } ratings, err := client.Bulk.StockRatings(ctx) - if err != nil || len(ratings) != 1 || ratings[0].Rating != "B+" { + if err != nil || len(ratings) != 2 || ratings[1].Rating != "C+" { t.Fatalf("StockRatings = %+v, %v", ratings, err) } dcf, err := client.Bulk.DCFValuations(ctx) - if err != nil || len(dcf) != 1 || dcf[0].StockPrice != "6.54" { + if err != nil || len(dcf) != 3 || cellText(dcf[1].StockPrice) != "2.39" { t.Fatalf("DCFValuations = %+v, %v", dcf, err) } scores, err := client.Bulk.FinancialScores(ctx) - if err != nil || len(scores) != 1 || scores[0].PiotroskiScore != "5" { + if err != nil || len(scores) != 3 || cellText(scores[0].PiotroskiScore) != "4" { t.Fatalf("FinancialScores = %+v, %v", scores, err) } targets, err := client.Bulk.PriceTargetSummaries(ctx) - if err != nil || len(targets) != 1 || targets[0].Symbol != "A" { + if err != nil || len(targets) != 2 || targets[1].Symbol != "AA" { t.Fatalf("PriceTargetSummaries = %+v, %v", targets, err) } holdings, err := client.Bulk.ETFHoldings(ctx, NewBulkPartQuery("segment A/7")) - if err != nil || len(holdings) != 1 || holdings[0].LastUpdatedRaw != `2024-09-06"` { + if err != nil || len(holdings) != 4 || cellText(holdings[1].Asset) != "3665.TW" { t.Fatalf("ETFHoldings = %+v, %v", holdings, err) } consensus, err := client.Bulk.UpgradesDowngradesConsensus(ctx) - if err != nil || len(consensus) != 1 || consensus[0].Consensus != "Buy" { + if err != nil || len(consensus) != 2 || consensus[1].Consensus != "Hold" { t.Fatalf("UpgradesDowngradesConsensus = %+v, %v", consensus, err) } metrics, err := client.Bulk.KeyMetricsTTM(ctx) - if err != nil || len(metrics) != 1 || metrics[0].MarketCap != "249171756000" { + if err != nil || len(metrics) != 3 || cellText(metrics[0].MarketCap) != "224526473551" { t.Fatalf("KeyMetricsTTM = %+v, %v", metrics, err) } ratios, err := client.Bulk.FinancialRatiosTTM(ctx) - if err != nil || len(ratios) != 1 || ratios[0].GrossProfitMarginTTM != "1.1622776732779352" { + if err != nil || len(ratios) != 3 || cellText(ratios[0].GrossProfitMarginTTM) != "0.5535250166330814" { t.Fatalf("FinancialRatiosTTM = %+v, %v", ratios, err) } peers, err := client.Bulk.StockPeers(ctx) - if err != nil || len(peers) != 1 || peers[0].Peers != "600036.SS" { + if err != nil || len(peers) != 3 || peers[0].Peers != "3698.HK,600000.SS,600015.SS,600016.SS,600036.SS,601166.SS,601658.SS" { t.Fatalf("StockPeers = %+v, %v", peers, err) } surprises, err := client.Bulk.EarningsSurprises(ctx, NewBulkYearQuery(4_294_967_295)) - if err != nil || len(surprises) != 1 || surprises[0].Symbol != "AMKYF" { + if err != nil || len(surprises) != 3 || surprises[0].Symbol != "AUTO.OL" { t.Fatalf("EarningsSurprises = %+v, %v", surprises, err) } income, err := client.Bulk.IncomeStatements(ctx, NewBulkStatementQuery(2026, FiscalPeriodQ1)) - if err != nil || len(income) != 1 || income[0].Revenue != "33644000000" { + if err != nil || len(income) != 3 || cellText(income[0].Revenue) != "251641000000" { t.Fatalf("IncomeStatements = %+v, %v", income, err) } incomeGrowth, err := client.Bulk.IncomeStatementGrowth(ctx, NewBulkStatementQuery(2026, FiscalPeriodQ2)) - if err != nil || len(incomeGrowth) != 1 || incomeGrowth[0].GrowthEbit != "1" { + if err != nil || len(incomeGrowth) != 2 || cellText(incomeGrowth[0].GrowthRevenue) != "-0.08220846812871789" { t.Fatalf("IncomeStatementGrowth = %+v, %v", incomeGrowth, err) } balance, err := client.Bulk.BalanceSheetStatements(ctx, NewBulkStatementQuery(2026, FiscalPeriodQ3)) - if err != nil || len(balance) != 1 || balance[0].Symbol != "MTLRP.ME" { + if err != nil || len(balance) != 2 || balance[1].Symbol != "0002.KL" { t.Fatalf("BalanceSheetStatements = %+v, %v", balance, err) } balanceGrowth, err := client.Bulk.BalanceSheetStatementGrowth(ctx, NewBulkStatementQuery(2026, FiscalPeriodQ4)) - if err != nil || len(balanceGrowth) != 1 || balanceGrowth[0].GrowthShortTermInvestments != "0" { + if err != nil || len(balanceGrowth) != 2 || cellText(balanceGrowth[0].GrowthShortTermInvestments) != "0.3981363245708905" { t.Fatalf("BalanceSheetStatementGrowth = %+v, %v", balanceGrowth, err) } cashFlow, err := client.Bulk.CashFlowStatements(ctx, NewBulkStatementQuery(4_294_967_295, FiscalPeriodFullYear)) - if err != nil || len(cashFlow) != 1 || cashFlow[0].NetIncome != "0" { + if err != nil || len(cashFlow) != 2 || cellText(cashFlow[0].NetIncome) != "14932000000" { t.Fatalf("CashFlowStatements = %+v, %v", cashFlow, err) } cashGrowth, err := client.Bulk.CashFlowStatementGrowth(ctx, NewBulkStatementQuery(2025, FiscalPeriodFullYear)) - if err != nil || len(cashGrowth) != 1 || cashGrowth[0].GrowthNetDebtIssuance != "1" { + if err != nil || len(cashGrowth) != 2 || cellText(cashGrowth[0].GrowthNetIncome) != "-0.04191152728446884" { t.Fatalf("CashFlowStatementGrowth = %+v, %v", cashGrowth, err) } eod, err := client.Bulk.Eod(ctx, NewBulkEodQuery(mustParseDate(t, "2024-10-22"))) - if err != nil || len(eod) != 1 || eod[0].Volume != "920904" { + if err != nil || len(eod) != 2 || cellText(eod[1].Close) != "0.91741" { t.Fatalf("Eod = %+v, %v", eod, err) } @@ -203,20 +206,27 @@ func TestBulkQueriesAreValidatedBeforeAnyRequest(t *testing.T) { } } -// A bulk row missing its raw-keyed member surfaces as a Decode error on the -// endpoint, exactly like a missing tagged member does. +// A bulk body missing a model member's column surfaces as a Decode error on +// the endpoint at the first row and that member. func TestBulkMethodsReportMissingMembersAsDecodeErrors(t *testing.T) { t.Parallel() - body := strings.Replace(string(readFixture(t, "bulk_etf_holdings.json")), `"lastUpdated\"": "2024-09-06\"",`, "", 1) - if body == string(readFixture(t, "bulk_etf_holdings.json")) { - t.Fatal("fixture no longer carries the quoted lastUpdated member on one line") - } - server, _ := newServer(t, jsonHandler(body)) + lines := strings.Split(strings.TrimSuffix(string(readFixture(t, "bulk_etf_holdings.csv")), "\n"), "\n") + if !strings.HasSuffix(lines[0], `,"lastUpdated"`) { + t.Fatal("fixture no longer ends its header with the lastUpdated column") + } + for index, line := range lines { + lines[index] = line[:strings.LastIndex(line, ",")] + } + body := strings.Join(lines, "\n") + "\n" + server, _ := newServer(t, func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/csv") + _, _ = w.Write([]byte(body)) + }) client := newClient(t, server, WithAuthentication(FMPHeader("route-secret"))) _, err := client.Bulk.ETFHoldings(context.Background(), NewBulkPartQuery("0")) typed := assertQuoteError(t, err, CategoryDecode, http.StatusOK, "etf-holder-bulk") - if cause := typed.Unwrap(); cause == nil || !strings.Contains(cause.Error(), `"lastUpdated\""`) { - t.Fatalf("cause = %v, want it to name the missing member lastUpdated\"", cause) + if typed.Path != "/0/lastUpdated" || typed.DecodeKind != DecodeKindMissingMember { + t.Fatalf("error = %+v, want a missing member at /0/lastUpdated", typed) } } diff --git a/sdk/go/client.go b/sdk/go/client.go index 16f1fe6d..18e0a862 100644 --- a/sdk/go/client.go +++ b/sdk/go/client.go @@ -24,6 +24,10 @@ const ( DefaultConnectTimeout = 10 * time.Second // DefaultMaxResponseBodyBytes is the largest body buffered per response (64 MiB). DefaultMaxResponseBodyBytes int64 = 64 * 1024 * 1024 + // DefaultBulkMaxResponseBodyBytes is the largest CSV bulk body buffered + // while WithMaxResponseBodyBytes is unset (256 MiB): ratios-ttm-bulk + // measured 65.6 MiB on 2026-10-01 (ADR 0035). + DefaultBulkMaxResponseBodyBytes int64 = 256 * 1024 * 1024 maxRedirects = 10 ) @@ -64,6 +68,7 @@ type clientConfig struct { timeout time.Duration connectTimeout time.Duration maxResponseBodyBytes int64 + maxResponseBodyBytesSet bool dangerAllowInsecureAuth bool redirectPolicy RedirectPolicy httpClient *http.Client @@ -123,8 +128,14 @@ func WithConnectTimeout(timeout time.Duration) Option { } // WithMaxResponseBodyBytes sets the largest body buffered for one response. +// It applies to every endpoint, bulk included; until it is set, bulk +// endpoints use DefaultBulkMaxResponseBodyBytes and the others use +// DefaultMaxResponseBodyBytes. func WithMaxResponseBodyBytes(maxBytes int64) Option { - return func(c *clientConfig) { c.maxResponseBodyBytes = maxBytes } + return func(c *clientConfig) { + c.maxResponseBodyBytes = maxBytes + c.maxResponseBodyBytesSet = true + } } // WithDangerAllowInsecureAuthentication allows credentials over plaintext @@ -165,7 +176,10 @@ type Client struct { ownsTransport bool timeout time.Duration maxResponseBodyBytes int64 - redirectPolicy RedirectPolicy + // bulkMaxResponseBodyBytes caps CSV bulk bodies: the configured limit, + // or DefaultBulkMaxResponseBodyBytes when none was set. + bulkMaxResponseBodyBytes int64 + redirectPolicy RedirectPolicy } // NewClient validates the options and builds a client with the direct FMP @@ -256,6 +270,10 @@ func NewClient(opts ...Option) (*Client, error) { maxResponseBodyBytes: cfg.maxResponseBodyBytes, redirectPolicy: cfg.redirectPolicy, } + client.bulkMaxResponseBodyBytes = DefaultBulkMaxResponseBodyBytes + if cfg.maxResponseBodyBytesSet { + client.bulkMaxResponseBodyBytes = cfg.maxResponseBodyBytes + } client.bindNamespaces() return client, nil } @@ -306,7 +324,7 @@ func (c *Client) getJSON(ctx context.Context, endpointID, relativePath string, q ctx, cancel := context.WithTimeout(ctx, c.timeout) defer cancel() - resp, err := c.executeRedirects(ctx, endpointID, target) + resp, err := c.executeRedirects(ctx, endpointID, target, c.maxResponseBodyBytes) if err != nil { return err } diff --git a/sdk/go/company_models.go b/sdk/go/company_models.go index 0cee64aa..02c63d40 100644 --- a/sdk/go/company_models.go +++ b/sdk/go/company_models.go @@ -13,42 +13,42 @@ import ( // CompanyProfile is a detailed worldwide company profile returned by symbol or // US CIK lookup. type CompanyProfile struct { - Symbol string `json:"symbol"` - Price float64 `json:"price"` - MarketCap float64 `json:"marketCap"` - Beta float64 `json:"beta"` - LastDividend float64 `json:"lastDividend"` - Range string `json:"range"` - Change float64 `json:"change"` - ChangePercentage float64 `json:"changePercentage"` - Volume float64 `json:"volume"` - AverageVolume float64 `json:"averageVolume"` - CompanyName string `json:"companyName"` - Currency string `json:"currency"` - CIK *string `json:"cik"` - ISIN string `json:"isin"` - CUSIP *string `json:"cusip"` - ExchangeFullName string `json:"exchangeFullName"` - Exchange string `json:"exchange"` - Industry string `json:"industry"` - Website string `json:"website"` - Description string `json:"description"` - Ceo string `json:"ceo"` - Sector string `json:"sector"` - Country string `json:"country"` - FullTimeEmployees *string `json:"fullTimeEmployees"` - Phone *string `json:"phone"` - Address string `json:"address"` - City string `json:"city"` - State string `json:"state"` - Zip string `json:"zip"` - Image string `json:"image"` - IPODate *Date `json:"ipoDate"` - DefaultImage bool `json:"defaultImage"` - IsETF bool `json:"isEtf"` - IsActivelyTrading bool `json:"isActivelyTrading"` - IsAdr bool `json:"isAdr"` - IsFund bool `json:"isFund"` + Symbol string `json:"symbol"` + Price float64 `json:"price"` + MarketCap float64 `json:"marketCap"` + Beta float64 `json:"beta"` + LastDividend float64 `json:"lastDividend"` + Range string `json:"range"` + Change *float64 `json:"change"` + ChangePercentage *float64 `json:"changePercentage"` + Volume float64 `json:"volume"` + AverageVolume float64 `json:"averageVolume"` + CompanyName string `json:"companyName"` + Currency string `json:"currency"` + CIK *string `json:"cik"` + ISIN *string `json:"isin"` + CUSIP *string `json:"cusip"` + ExchangeFullName string `json:"exchangeFullName"` + Exchange string `json:"exchange"` + Industry string `json:"industry"` + Website string `json:"website"` + Description string `json:"description"` + Ceo string `json:"ceo"` + Sector string `json:"sector"` + Country *string `json:"country"` + FullTimeEmployees *string `json:"fullTimeEmployees"` + Phone *string `json:"phone"` + Address string `json:"address"` + City string `json:"city"` + State string `json:"state"` + Zip string `json:"zip"` + Image string `json:"image"` + IPODate *Date `json:"ipoDate"` + DefaultImage bool `json:"defaultImage"` + IsETF bool `json:"isEtf"` + IsActivelyTrading bool `json:"isActivelyTrading"` + IsAdr bool `json:"isAdr"` + IsFund bool `json:"isFund"` } // companyProfileShadow mirrors CompanyProfile with a pointer or raw value for @@ -61,14 +61,14 @@ type companyProfileShadow struct { Beta *float64 `json:"beta"` LastDividend *float64 `json:"lastDividend"` Range *string `json:"range"` - Change *float64 `json:"change"` - ChangePercentage *float64 `json:"changePercentage"` + Change jsontext.Value `json:"change"` + ChangePercentage jsontext.Value `json:"changePercentage"` Volume *float64 `json:"volume"` AverageVolume *float64 `json:"averageVolume"` CompanyName *string `json:"companyName"` Currency *string `json:"currency"` CIK jsontext.Value `json:"cik"` - ISIN *string `json:"isin"` + ISIN jsontext.Value `json:"isin"` CUSIP jsontext.Value `json:"cusip"` ExchangeFullName *string `json:"exchangeFullName"` Exchange *string `json:"exchange"` @@ -77,7 +77,7 @@ type companyProfileShadow struct { Description *string `json:"description"` Ceo *string `json:"ceo"` Sector *string `json:"sector"` - Country *string `json:"country"` + Country jsontext.Value `json:"country"` FullTimeEmployees jsontext.Value `json:"fullTimeEmployees"` Phone jsontext.Value `json:"phone"` Address *string `json:"address"` @@ -114,9 +114,9 @@ func (m *CompanyProfile) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return missingMemberError("CompanyProfile", "lastDividend") case shadow.Range == nil: return missingMemberError("CompanyProfile", "range") - case shadow.Change == nil: + case len(shadow.Change) == 0: return missingMemberError("CompanyProfile", "change") - case shadow.ChangePercentage == nil: + case len(shadow.ChangePercentage) == 0: return missingMemberError("CompanyProfile", "changePercentage") case shadow.Volume == nil: return missingMemberError("CompanyProfile", "volume") @@ -128,7 +128,7 @@ func (m *CompanyProfile) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return missingMemberError("CompanyProfile", "currency") case len(shadow.CIK) == 0: return missingMemberError("CompanyProfile", "cik") - case shadow.ISIN == nil: + case len(shadow.ISIN) == 0: return missingMemberError("CompanyProfile", "isin") case len(shadow.CUSIP) == 0: return missingMemberError("CompanyProfile", "cusip") @@ -146,7 +146,7 @@ func (m *CompanyProfile) UnmarshalJSONFrom(dec *jsontext.Decoder) error { return missingMemberError("CompanyProfile", "ceo") case shadow.Sector == nil: return missingMemberError("CompanyProfile", "sector") - case shadow.Country == nil: + case len(shadow.Country) == 0: return missingMemberError("CompanyProfile", "country") case len(shadow.FullTimeEmployees) == 0: return missingMemberError("CompanyProfile", "fullTimeEmployees") @@ -175,6 +175,22 @@ func (m *CompanyProfile) UnmarshalJSONFrom(dec *jsontext.Decoder) error { case shadow.IsFund == nil: return missingMemberError("CompanyProfile", "isFund") } + var change *float64 + if shadow.Change.Kind() != 'n' { + var value float64 + if err := json.Unmarshal(shadow.Change, &value); err != nil { + return memberDecodeError("CompanyProfile", "change", shadow.Change, err) + } + change = &value + } + var changePercentage *float64 + if shadow.ChangePercentage.Kind() != 'n' { + var value float64 + if err := json.Unmarshal(shadow.ChangePercentage, &value); err != nil { + return memberDecodeError("CompanyProfile", "changePercentage", shadow.ChangePercentage, err) + } + changePercentage = &value + } var cik *string if shadow.CIK.Kind() != 'n' { var value string @@ -183,6 +199,16 @@ func (m *CompanyProfile) UnmarshalJSONFrom(dec *jsontext.Decoder) error { } cik = &value } + var isin *string + if shadow.ISIN.Kind() != 'n' { + var value string + if err := json.Unmarshal(shadow.ISIN, &value); err != nil { + return memberDecodeError("CompanyProfile", "isin", shadow.ISIN, err) + } + if value != "" { + isin = &value + } + } var cusip *string if shadow.CUSIP.Kind() != 'n' { var value string @@ -191,6 +217,16 @@ func (m *CompanyProfile) UnmarshalJSONFrom(dec *jsontext.Decoder) error { } cusip = &value } + var country *string + if shadow.Country.Kind() != 'n' { + var value string + if err := json.Unmarshal(shadow.Country, &value); err != nil { + return memberDecodeError("CompanyProfile", "country", shadow.Country, err) + } + if value != "" { + country = &value + } + } var fullTimeEmployees *string if shadow.FullTimeEmployees.Kind() != 'n' { var value string @@ -218,14 +254,14 @@ func (m *CompanyProfile) UnmarshalJSONFrom(dec *jsontext.Decoder) error { Beta: *shadow.Beta, LastDividend: *shadow.LastDividend, Range: *shadow.Range, - Change: *shadow.Change, - ChangePercentage: *shadow.ChangePercentage, + Change: change, + ChangePercentage: changePercentage, Volume: *shadow.Volume, AverageVolume: *shadow.AverageVolume, CompanyName: *shadow.CompanyName, Currency: *shadow.Currency, CIK: cik, - ISIN: *shadow.ISIN, + ISIN: isin, CUSIP: cusip, ExchangeFullName: *shadow.ExchangeFullName, Exchange: *shadow.Exchange, @@ -234,7 +270,7 @@ func (m *CompanyProfile) UnmarshalJSONFrom(dec *jsontext.Decoder) error { Description: *shadow.Description, Ceo: *shadow.Ceo, Sector: *shadow.Sector, - Country: *shadow.Country, + Country: country, FullTimeEmployees: fullTimeEmployees, Phone: phone, Address: *shadow.Address, diff --git a/sdk/go/company_parity_test.go b/sdk/go/company_parity_test.go index 8c50bbf0..c4c77c47 100644 --- a/sdk/go/company_parity_test.go +++ b/sdk/go/company_parity_test.go @@ -52,11 +52,11 @@ func TestDocumentedCompanyProfileNoteAndPeerDecodeExactValues(t *testing.T) { profile := profiles[0] want := CompanyProfile{ Symbol: "AAPL", Price: 331.85501, MarketCap: 4_874_072_686_740, Beta: 1.097, LastDividend: 1.05, - Range: "201.5-344.57", Change: -6.33498, ChangePercentage: -1.8732, Volume: 28_718_014, + Range: "201.5-344.57", Change: new(-6.33498), ChangePercentage: new(-1.8732), Volume: 28_718_014, AverageVolume: 55_309_000, CompanyName: "Apple Inc.", Currency: "USD", CIK: new("0000320193"), - ISIN: "US0378331005", CUSIP: new("037833100"), ExchangeFullName: "NASDAQ Global Select", Exchange: "NASDAQ", + ISIN: new("US0378331005"), CUSIP: new("037833100"), ExchangeFullName: "NASDAQ Global Select", Exchange: "NASDAQ", Industry: "Consumer Electronics", Website: "https://www.apple.com", Description: profile.Description, - Ceo: "Timothy D. Cook", Sector: "Technology", Country: "US", FullTimeEmployees: new("166000"), + Ceo: "Timothy D. Cook", Sector: "Technology", Country: new("US"), FullTimeEmployees: new("166000"), Phone: new("(408) 996-1010"), Address: "One Apple Park Way", City: "Cupertino", State: "CA", Zip: "95014", Image: "https://images.financialmodelingprep.com/symbol/AAPL.png", IPODate: new(mustParseDate(t, "1980-12-12")), DefaultImage: false, IsETF: false, IsActivelyTrading: true, IsAdr: false, IsFund: false, diff --git a/sdk/go/csv.go b/sdk/go/csv.go new file mode 100644 index 00000000..07c0170c --- /dev/null +++ b/sdk/go/csv.go @@ -0,0 +1,207 @@ +package fmp + +import ( + "bytes" + "context" + "encoding/csv" + "encoding/json/jsontext" + "encoding/json/v2" + "errors" + "io" + "reflect" + "slices" + "strconv" + "strings" +) + +// textCSV is the media type FMP answers on every bulk route (ADR 0035). +const textCSV = "text/csv" + +// cellKind says how one CSV cell is written into the JSON member its model +// field decodes from. +type cellKind uint8 + +const ( + cellString cellKind = iota + cellPlainString + cellNumber + cellBool +) + +// csvColumn is the decode plan of one model member: its cell kind and +// whether the field is a pointer, which makes an empty cell absent. +type csvColumn struct { + kind cellKind + optional bool +} + +// getCSV issues one GET for a bulk endpoint and decodes the headed CSV body +// into rows of T, mirroring the Rust crate's CSV contract. Each record is +// written as a JSON object keyed by the header names and decoded with the +// model's own JSON rules, so a failure reports "//". Every +// json-tagged member, pointer or not, must be a header column; the first +// record checks it. An empty +// or header-only body yields no rows, and the provider-message check of +// getJSON does not apply. The redirect, timeout, and no-retry rules of +// getJSON are unchanged; the body cap is the bulk limit. +func getCSV[T any](ctx context.Context, c *Client, endpointID, relativePath string, query []queryParam) ([]T, error) { + if err := validateRelativePath(relativePath); err != nil { + return nil, err + } + target := c.buildEndpointURL(relativePath) + rawQuery, err := encodeQuery(query, c.auth.queryName) + if err != nil { + return nil, err + } + target.RawQuery = rawQuery + + ctx, cancel := context.WithTimeout(ctx, c.timeout) + defer cancel() + resp, err := c.executeRedirects(ctx, endpointID, target, c.bulkMaxResponseBodyBytes) + if err != nil { + return nil, err + } + + contentType := resp.header.Get("Content-Type") + switch { + case contentType == "": + return nil, decodeError(endpointID, resp.status, c.safeBody(resp.body), + "successful response omitted its content type", nil) + case !strings.EqualFold(mediaTypeOf(contentType), textCSV): + return nil, decodeError(endpointID, resp.status, c.safeBody(resp.body), + "successful response used an unexpected content type", nil) + } + rows, path, kind, cause := decodeCSVRows[T](resp.body) + if kind != DecodeKindNone { + decoded := decodeError(endpointID, resp.status, c.safeBody(resp.body), + "successful response could not be decoded", c.redactCause(cause)) + decoded.Path, decoded.DecodeKind = c.redactor.Redact(path), kind + return nil, decoded + } + return rows, nil +} + +// utf8BOM is the byte order mark the Rust csv reader strips before the +// header; encoding/csv keeps it, so decodeCSVRows drops it first. +var utf8BOM = []byte("\xef\xbb\xbf") + +// decodeCSVRows decodes body into rows of T. On failure it returns the JSON +// pointer of the row (and member), a DecodeKind, and the cause. +func decodeCSVRows[T any](body []byte) ([]T, string, DecodeKind, error) { + reader := csv.NewReader(bytes.NewReader(bytes.TrimPrefix(body, utf8BOM))) + reader.ReuseRecord = true + header, err := reader.Read() + if errors.Is(err, io.EOF) { + return []T{}, "", DecodeKindNone, nil + } + if err != nil { + return nil, "", DecodeKindSyntax, err + } + header = slices.Clone(header) + plan := csvPlan(reflect.TypeFor[T]()) + rows := []T{} + var object []byte + for { + record, err := reader.Read() + if errors.Is(err, io.EOF) { + return rows, "", DecodeKindNone, nil + } + row := "/" + strconv.Itoa(len(rows)) + if err != nil { + return nil, row, DecodeKindSyntax, err + } + if len(rows) == 0 { + if missing, ok := missingColumn(header, reflect.TypeFor[T]()); ok { + return nil, string(jsontext.Pointer(row).AppendToken(missing)), DecodeKindMissingMember, + errors.New("a model member is not a header column") + } + } + object = appendCSVObject(object[:0], header, record, plan) + var value T + if err := json.Unmarshal(object, &value); err != nil { + path, kind := decodeLocation(object, err) + return nil, row + path, kind, err + } + rows = append(rows, value) + } +} + +// appendCSVObject writes one record as a JSON object. An empty cell is null +// unless the member is a required plain string, where it stays "". +func appendCSVObject(dst []byte, header, record []string, plan map[string]csvColumn) []byte { + dst = append(dst, '{') + for index, name := range header { + if index > 0 { + dst = append(dst, ',') + } + dst, _ = jsontext.AppendQuote(dst, name) + dst = append(dst, ':') + cell, column := record[index], plan[name] + switch { + case cell == "" && (column.optional || column.kind != cellPlainString): + dst = append(dst, "null"...) + case column.kind == cellNumber && isJSONNumber(cell), + column.kind == cellBool && (cell == "true" || cell == "false"): + dst = append(dst, cell...) + default: + dst, _ = jsontext.AppendQuote(dst, cell) + } + } + return append(dst, '}') +} + +// missingColumn reports the first member of model, in declaration order as +// serde reports it, that the header does not carry. +func missingColumn(header []string, model reflect.Type) (string, bool) { + if model.Kind() != reflect.Struct { + return "", false + } + for field := range model.Fields() { + name, _, _ := strings.Cut(field.Tag.Get("json"), ",") + if name != "" && name != "-" && !slices.Contains(header, name) { + return name, true + } + } + return "", false +} + +func isJSONNumber(cell string) bool { + value := jsontext.Value(cell) + return value.IsValid() && value.Kind() == '0' +} + +// csvPlan maps every json-tagged field of the struct type to its column plan. +func csvPlan(model reflect.Type) map[string]csvColumn { + plan := map[string]csvColumn{} + if model.Kind() != reflect.Struct { + return plan + } + for field := range model.Fields() { + name, _, _ := strings.Cut(field.Tag.Get("json"), ",") + if name == "" || name == "-" { + continue + } + fieldType, optional := field.Type, false + if fieldType.Kind() == reflect.Pointer { + fieldType, optional = fieldType.Elem(), true + } + plan[name] = csvColumn{kind: cellKindOf(fieldType), optional: optional} + } + return plan +} + +func cellKindOf(fieldType reflect.Type) cellKind { + switch fieldType.Kind() { + case reflect.Bool: + return cellBool + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + return cellNumber + case reflect.String: + if fieldType == reflect.TypeFor[string]() { + return cellPlainString + } + } + return cellString +} diff --git a/sdk/go/csv_test.go b/sdk/go/csv_test.go new file mode 100644 index 00000000..3ebbcf33 --- /dev/null +++ b/sdk/go/csv_test.go @@ -0,0 +1,222 @@ +package fmp + +import ( + "bytes" + "context" + "encoding/csv" + "encoding/json/v2" + "errors" + "net/http" + "slices" + "strconv" + "testing" +) + +const csvEndpoint = "csv-contract-test" + +type csvProbeRow struct { + Symbol string `json:"symbol"` + Date Date `json:"date"` + Price float64 `json:"price"` + Listed bool `json:"listed"` + Volume *string `json:"volume"` + StockPrice *float64 `json:"Stock Price"` + Peers string `json:"peers"` +} + +const csvProbeHeader = `"symbol","date","price","listed","volume","Stock Price","peers"` + "\n" + +func csvServer(t *testing.T, contentType, body string) *Client { + t.Helper() + server, _ := newServer(t, func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", contentType) + _, _ = w.Write([]byte(body)) + }) + return newClient(t, server) +} + +func getCSVProbe[T any](t *testing.T, body string) ([]T, error) { + t.Helper() + return getCSV[T](context.Background(), csvServer(t, "text/csv", body), csvEndpoint, "bulk-probe", nil) +} + +func assertCSVDecodeError(t *testing.T, err error, path string, kind DecodeKind) { + t.Helper() + var typed *Error + if !errors.As(err, &typed) { + t.Fatalf("error %v (%T) is not *Error", err, err) + } + if typed.Category != CategoryDecode || typed.Endpoint != csvEndpoint || typed.Path != path || typed.DecodeKind != kind { + t.Fatalf("error = %+v, want decode at %q kind %v", typed, path, kind) + } +} + +// TestGetCSVDecodesEmptyAndHeaderOnlyBodiesToNoRows mirrors +// empty_and_header_only_bodies_decode_to_no_rows. +func TestGetCSVDecodesEmptyAndHeaderOnlyBodiesToNoRows(t *testing.T) { + t.Parallel() + for _, body := range []string{"", csvProbeHeader, "symbol,price\n", "Invalid name\n"} { + rows, err := getCSVProbe[csvProbeRow](t, body) + if err != nil || rows == nil || len(rows) != 0 { + t.Fatalf("body %q: rows = %v, err = %v", body, rows, err) + } + } +} + +// TestGetCSVDecodesCellsByHeaderName mirrors +// cells_decode_by_header_name_with_quoted_commas_and_verbatim_numbers. +func TestGetCSVDecodesCellsByHeaderName(t *testing.T) { + t.Parallel() + rows, err := getCSVProbe[csvProbeRow](t, csvProbeHeader+ + `"AAPL","2025-06-02",201.7,true,6.9148336e-9,12.5,"MSFT,GOOG"`+"\n"+ + `"","2025-06-02",-0.5,false,,,"say ""hi"""`+"\n") + if err != nil { + t.Fatal(err) + } + first, second := rows[0], rows[1] + if first.Symbol != "AAPL" || first.Date != mustParseDate(t, "2025-06-02") || first.Price != 201.7 || !first.Listed || + first.Volume == nil || *first.Volume != "6.9148336e-9" || first.StockPrice == nil || *first.StockPrice != 12.5 || + first.Peers != "MSFT,GOOG" { + t.Fatalf("first row = %+v", first) + } + if second.Symbol != "" || second.Volume != nil || second.StockPrice != nil || second.Peers != `say "hi"` { + t.Fatalf("second row = %+v", second) + } +} + +// TestGetCSVSkipsALeadingByteOrderMark mirrors +// a_leading_byte_order_mark_is_not_part_of_the_first_header. +func TestGetCSVSkipsALeadingByteOrderMark(t *testing.T) { + t.Parallel() + rows, err := getCSVProbe[csvProbeRow](t, "\ufeff"+csvProbeHeader+"A,2025-06-02,1,true,1,1,x\r\n") + if err != nil || len(rows) != 1 || rows[0].Symbol != "A" || rows[0].Peers != "x" { + t.Fatalf("rows = %+v, err = %v", rows, err) + } +} + +// TestGetCSVReportsRowAndMemberOfAFailure mirrors the Rust decoder's +// null, missing-member, and syntax tests on a generated model. +func TestGetCSVReportsRowAndMemberOfAFailure(t *testing.T) { + t.Parallel() + const header = `"symbol","date","dcf","Stock Price"` + "\n" + cases := []struct { + body string + path string + kind DecodeKind + }{ + {header + "A,2025-06-02,1,2\nB,,1,2\n", "/1/date", DecodeKindNull}, + {"symbol,date,Stock Price\nA,2025-06-02,2\n", "/0/dcf", DecodeKindMissingMember}, + {header + "A,2025-06-02,1,2\nB,2025-06-02\n", "/1", DecodeKindSyntax}, + } + for _, tc := range cases { + _, err := getCSVProbe[BulkDCFValuation](t, tc.body) + assertCSVDecodeError(t, err, tc.path, tc.kind) + } + _, err := getCSVProbe[csvProbeRow](t, `"symbol","date","price","listed","Stock Price","peers"`+"\nA,2025-06-02,1,true,1,x\n") + assertCSVDecodeError(t, err, "/0/volume", DecodeKindMissingMember) + rows, err := getCSVProbe[csvProbeRow](t, "symbol,date\n") + if err != nil || len(rows) != 0 { + t.Fatalf("header-only body with missing columns: rows = %v, err = %v", rows, err) + } +} + +func TestGetCSVAcceptsOnlyTextCSV(t *testing.T) { + t.Parallel() + rows, err := getCSV[csvProbeRow](context.Background(), csvServer(t, "Text/CSV; charset=utf-8", csvProbeHeader), + csvEndpoint, "bulk-probe", nil) + if err != nil || len(rows) != 0 { + t.Fatalf("rows = %v, err = %v", rows, err) + } + _, err = getCSV[csvProbeRow](context.Background(), csvServer(t, "application/json", `{"Error Message":"x"}`), + csvEndpoint, "bulk-probe", nil) + var typed *Error + if !errors.As(err, &typed) || typed.Category != CategoryDecode || + typed.Message != "successful response used an unexpected content type" { + t.Fatalf("err = %v", err) + } +} + +// TestBulkBodyLimitDefaultsUntilTheClientSetsOne mirrors +// csv_bulk_endpoints_default_to_the_bulk_body_limit_until_the_client_sets_one. +func TestBulkBodyLimitDefaultsUntilTheClientSetsOne(t *testing.T) { + t.Parallel() + server, _ := newServer(t, func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/csv") + _, _ = w.Write([]byte(csvProbeHeader)) + }) + unset := newClient(t, server) + if unset.bulkMaxResponseBodyBytes != DefaultBulkMaxResponseBodyBytes || + unset.maxResponseBodyBytes != DefaultMaxResponseBodyBytes { + t.Fatalf("limits = %d, %d", unset.bulkMaxResponseBodyBytes, unset.maxResponseBodyBytes) + } + configured := newClient(t, server, WithMaxResponseBodyBytes(8)) + if configured.bulkMaxResponseBodyBytes != 8 { + t.Fatalf("bulk limit = %d, want the configured 8", configured.bulkMaxResponseBodyBytes) + } + _, err := getCSV[csvProbeRow](context.Background(), configured, csvEndpoint, "bulk-probe", nil) + var typed *Error + if !errors.As(err, &typed) || typed.Category != CategoryTransport || + typed.Message != "response body exceeded configured limit" { + t.Fatalf("err = %v", err) + } +} + +// assertCSVFixtureParity mirrors assert_round_trip in +// crates/libfmp/tests/support/bulk_csv.rs: every row of the shared CSV +// fixture decodes, and the first row re-encodes fields members, each a header +// column equal to its cell (verbatim text, the parsed number or boolean, and +// null or "" for an empty cell). +func assertCSVFixtureParity[T any](t *testing.T, name string, fields int) []T { + t.Helper() + raw := readFixture(t, name) + rows, path, kind, err := decodeCSVRows[T](raw) + if kind != DecodeKindNone { + t.Fatalf("%s: decode at %q (%v): %v", name, path, kind, err) + } + records, err := csv.NewReader(bytes.NewReader(raw)).ReadAll() + if err != nil || len(records) != len(rows)+1 { + t.Fatalf("%s: %d records for %d rows: %v", name, len(records), len(rows), err) + } + members := memberValues(t, rows[0]) + if len(members) != fields { + t.Fatalf("%s: %d members re-encoded, want %d", name, len(members), fields) + } + for member, value := range members { + column := slices.Index(records[0], member) + if column < 0 { + t.Fatalf("%s: member %q is not a header column", name, member) + } + cell := records[1][column] + var text string + switch value.Kind() { + case 'n': + text = "" + case '"': + if err := json.Unmarshal(value, &text); err != nil { + t.Fatal(err) + } + case '0': + want, _ := strconv.ParseFloat(cell, 64) + got, _ := strconv.ParseFloat(string(value), 64) + if got == want { + continue + } + text = string(value) + default: + text = string(value) + } + if text != cell { + t.Fatalf("%s: member %q re-encoded %s for cell %q", name, member, value, cell) + } + } + return rows +} + +// cellText returns the text of an optional numeric-string member, or "" when +// its CSV cell was empty. +func cellText(value *string) string { + if value == nil { + return "" + } + return *value +} diff --git a/sdk/go/fixtures_test.go b/sdk/go/fixtures_test.go index 7c313bc9..5700efed 100644 --- a/sdk/go/fixtures_test.go +++ b/sdk/go/fixtures_test.go @@ -222,7 +222,7 @@ func mutateFixtureMember(t *testing.T, fixture, member string, value jsontext.Va // TestEveryFixtureIsReferencedByAGoTest enforces the ADR 0030 acceptance // check that every shared fixture decodes through a Go model: each .json -// file under the fixture directory must be named, as a quoted literal, in at +// or .csv file under the fixture directory must be named, as a quoted literal, in at // least one *_test.go file of this package. The fixture README is not a // fixture and is the only file the scan skips. Every unreferenced fixture is // listed, so a newly recorded fixture fails here until a domain test uses it. @@ -246,7 +246,7 @@ func TestEveryFixtureIsReferencedByAGoTest(t *testing.T) { } var fixtures, unreferenced []string for _, entry := range entries { - if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { + if entry.IsDir() || !slices.Contains([]string{".json", ".csv"}, filepath.Ext(entry.Name())) { continue } fixtures = append(fixtures, entry.Name()) diff --git a/sdk/go/transport.go b/sdk/go/transport.go index 7950a377..0fe598cd 100644 --- a/sdk/go/transport.go +++ b/sdk/go/transport.go @@ -50,10 +50,11 @@ type bufferedResponse struct { body []byte } -func (c *Client) executeRedirects(ctx context.Context, endpointID string, target *url.URL) (*bufferedResponse, error) { +func (c *Client) executeRedirects(ctx context.Context, endpointID string, target *url.URL, + maxBodyBytes int64) (*bufferedResponse, error) { for hop := 0; hop <= maxRedirects; hop++ { c.applyQueryAuth(target) - resp, err := c.roundTrip(ctx, endpointID, target) + resp, err := c.roundTrip(ctx, endpointID, target, maxBodyBytes) if err != nil { return nil, err } @@ -97,7 +98,8 @@ func (c *Client) redirectDestination(current *url.URL, location string) (*url.UR return destination, true } -func (c *Client) roundTrip(ctx context.Context, endpointID string, target *url.URL) (*bufferedResponse, error) { +func (c *Client) roundTrip(ctx context.Context, endpointID string, target *url.URL, + maxBodyBytes int64) (*bufferedResponse, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, target.String(), nil) if err != nil { return nil, transportError(endpointID, "request execution failed", c.redactCause(err)) @@ -121,10 +123,10 @@ func (c *Client) roundTrip(ctx context.Context, endpointID string, target *url.U if isRedirect(resp.StatusCode) { return buffered, nil } - if resp.ContentLength > c.maxResponseBodyBytes { + if resp.ContentLength > maxBodyBytes { return nil, transportError(endpointID, "response body exceeded configured limit", nil) } - body, err := io.ReadAll(io.LimitReader(resp.Body, c.maxResponseBodyBytes+1)) + body, err := io.ReadAll(io.LimitReader(resp.Body, maxBodyBytes+1)) if err != nil { message := "request execution failed" if errors.Is(err, context.DeadlineExceeded) { @@ -132,7 +134,7 @@ func (c *Client) roundTrip(ctx context.Context, endpointID string, target *url.U } return nil, transportError(endpointID, message, c.redactCause(err)) } - if int64(len(body)) > c.maxResponseBodyBytes { + if int64(len(body)) > maxBodyBytes { return nil, transportError(endpointID, "response body exceeded configured limit", nil) } buffered.body = body