You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat!: nullable members for values FMP can omit (1.2.0) #368
Source: docs/requests/nillable.md (item 1). Target: 1.2.0, breaking. Commit type feat(...) / fix(...) with a BREAKING CHANGE: footer (maps to a minor bump in .releaserc.json). Never incompat (maintainer policy: breaking changes ship as minors).
Problem
Members that FMP leaves empty are required non-null types. One null fails a whole call, and consumers cannot tell "no value" from zero (a missing treasury tenor must not read as 0%).
Part 1: audit first
Sample live data once with a script (screener, profile, economics, analyst, statements) and list every member seen as null, missing, "" or malformed. Output a table in this issue. Goal: one breaking release, not another one after the next surprise. Never commit the API key or raw responses with secrets.
Part 2: make them optional
Known so far:
Model
Members
Seen
TreasuryRate
all 12 tenors
month2 null before 2018
EconomicIndicatorObservation
value
risk
CompanyScreenerResult
beta
null
CompanyProfile
phone, full_time_employees, ipo_date
null / "" / malformed date
Rust Option<T>; Go pointers via existing gen_go mapping; Python T | None. Regen all.
ipo_date: empty, null and malformed -> None (ADR 0002 already maps empty/null dates to None).
Record the policy in an ADR: which members are optional and why.
Acceptance
treasury-rates row with "month2": null decodes with month_2 == None / Month2 == nil, other tenors set.
Screener page with one "beta": null row decodes.
aquor can drop internal/screener/sanitize.go and move internal/macro onto the SDK.
Out of scope
A lenient/zero-default decoding option (item 2 of the request): declined, because it recreates the zero vs no-value ambiguity.
changed the title [-]incompat: nullable members for values FMP can omit (2.0)[/-][+]feat!: nullable members for values FMP can omit (1.2.0)[/+]on Sep 29, 2026
BalanceSheetStatement (new): 38 StatementAmount members null in one RIVN quarterly row -> Option on StatementAmount members, or skip/tolerate all-null pre-IPO stub rows (BS 2021-03-31 Q1)
CashFlowStatement (new): 19 StatementAmount members null in RIVN stub quarters 2020-12-31 Q4, 2021-09-30 Q3 (same choice as above)
CongressionalMemberProfile.image (new); CongressionalMemberNetWorth: category, name, value (new)
Policies: "" on plain String stays ""; "" on typed codes and dates -> None; malformed ipoDate stays an error (0 seen on profile).
Rerun diff (new rows and still-failing paths)
#368 audit rerun vs first run (a368-v2, 2026-09-30)
Plan: ULTIMATE. Code: main 78da933. First run: 392 req, 277x200, 96x402, 230/243 paths tried, 148 with data.
Coverage: 10 non-US listings (7203.T, SAP.DE, NESN.SW, VOD.L, SHOP.TO, 0700.HK, RELIANCE.NS, ASML.AS, 005930.KS, BHP.AX), ETFs SPY/QQQ/VTI, funds VFIAX/FXAIX, ADRs TSM/BABA/NVO/SONY/TM, small caps + loss-makers (GPRO, ZOM, RIVN, LCID, PLUG, SNDL, MVIS, CLOV, PTON, SNAP), crypto/forex/commodities/indexes, treasury walked 1990-2026 in 84-day windows (9560 rows), 21 economic indicators in decade windows plus GDP/CPI/unemploymentRate/federalFunds walked 1990-2026 in 91-day windows (1500 observations, value never null), statements annual (limit 60) + quarterly (limit 120), 6 screener pages + 25 filter combos, calendars in 2008/2020/2026.
Resume note: the previous worker's in-flight bulk calls reused request numbers 415/416 on resume, overwriting their raw bodies with treasury rows. Those two index records now point to stubs holding the logged CSV header (field note), so they register as not_json like every other bulk route.
402 Restricted Endpoint even on ULTIMATE (separate add-on)
all 18 *-bulk routes
200 text/csv (Accept: application/json ignored); SDK decodes a JSON array and test fixtures are JSON -> every client.bulk call fails. NEW (no bulk route returned a 200 body in the first run)
financial-reports-json
still one JSON object vs Vec<FinancialReportJson> (5/5 calls fail)
economic-indicators row cap
a 1-year window returns 2 rows, a quarter 3, GDP over 6 years returns []: callers must walk short windows (doc note, like treasury)
economic-indicators?name=
200 with plain-text body Invalid name (not JSON) -> decode error instead of a typed error. NEW
New rows (model: members not in the first-run table) [sdk result today]
Note: the 38 BalanceSheetStatement and 19 CashFlowStatement null rows all come from RIVN quarterly stubs (BS 2021-03-31 Q1, CF 2020-12-31 Q4 and 2021-09-30 Q3, around the Nov 2021 IPO): a "skip or tolerate all-null stub rows" policy is an alternative to widening 57 members.
47/47 first-run failing keys fail again; none disappeared (the old table's rows are all present in the new one).
Key scan
grep -rF $FMP_API_KEY over the work dir: 0 hits.
apikey= not followed by ***: 887 hits, NOT zero. 886 are the literal provider placeholder apikey=YOUR_API_KEY inside financial-reports-dates bodies (17 raw files), 1 is the scrub regex in fetch.py. No real key.
treasury-rates row with "month2": null decodes to None / nil, other tenors set (Rust, Go, Python).
A screener page with one "beta": null row decodes (Rust, Go, Python).
Policy recorded in ADR 0033: members seen null become Option / pointer / Optional; "" on typed codes and dates becomes None; "" on plain strings stays ""; no zero or NaN defaults, no dropped rows.
Related type fixes filed separately: #376 (bulk CSV), #377, #378, #379, #380, #381, and #387 (Go decode path on nullable members).
Activity
[-]incompat: nullable members for values FMP can omit (2.0)[/-][+]feat!: nullable members for values FMP can omit (1.2.0)[/+]benbenbang commentedon Sep 30, 2026
Part 1: live audit results (2026-09-30)
Coverage: 392 requests (277x 200, 96x 402 plan tier, 0x 401/429). 230 of 243 registry paths tried, 148 returned data. Raw responses were not kept.
Recommended
Optionlist for 1.2.0Policies: "" on plain String stays ""; "" on typed codes -> None; malformed ipoDate stays an error (0 seen; ask aquor for the exact string).
Separate bug: financial-reports-json returns one object, SDK decodes Vec (endpoints/statements/reports.rs:121) -> always fails.
Doc note: treasury-rates returns max ~60 rows per call.
Open decisions
""on typed codes (countryCode,cusip,transactionType,reportingCurrency): map toNone, or keep as a decode error?financial-reports-jsonbug as a separatefixissue?Evidence table (77 rows)
benbenbang commentedon Sep 30, 2026
Audit rerun on the ULTIMATE plan (2026-09-30)
Only the 7
tipranks-*paths still return 402 (add-on). All 47 first-run failures still fail.Decisions (maintainer, 2026-09-30)
""on typed codes and dates ->None.""on plainStringmembers stays"".IndexConstituent.founded, fix!: IpoProspectus.ipoDate can be year-only #379IpoProspectus.ipoDate, fix!: FundDisclosureSearchResult.entityOrgType receives the string NULL #380FundDisclosureSearchResult.entityOrgType, fix: economic-indicators plain-text Invalid name surfaces as a decode error #381 economic-indicatorsInvalid name. fix!: financial-reports-json returns one object, SDK decodes a list #374 (financial-reports-json) is already fixed in PR 375.Updated Option list ("(new)" = found by the rerun)
Policies: "" on plain String stays ""; "" on typed codes and dates -> None; malformed ipoDate stays an error (0 seen on profile).
Rerun diff (new rows and still-failing paths)
#368 audit rerun vs first run (a368-v2, 2026-09-30)
Plan: ULTIMATE. Code: main 78da933. First run: 392 req, 277x200, 96x402, 230/243 paths tried, 148 with data.
Request stats
Coverage: 10 non-US listings (7203.T, SAP.DE, NESN.SW, VOD.L, SHOP.TO, 0700.HK, RELIANCE.NS, ASML.AS, 005930.KS, BHP.AX), ETFs SPY/QQQ/VTI, funds VFIAX/FXAIX, ADRs TSM/BABA/NVO/SONY/TM, small caps + loss-makers (GPRO, ZOM, RIVN, LCID, PLUG, SNDL, MVIS, CLOV, PTON, SNAP), crypto/forex/commodities/indexes, treasury walked 1990-2026 in 84-day windows (9560 rows), 21 economic indicators in decade windows plus GDP/CPI/unemploymentRate/federalFunds walked 1990-2026 in 91-day windows (1500 observations, value never null), statements annual (limit 60) + quarterly (limit 120), 6 screener pages + 25 filter combos, calendars in 2008/2020/2026.
Resume note: the previous worker's in-flight bulk calls reused request numbers 415/416 on resume, overwriting their raw bodies with treasury rows. Those two index records now point to stubs holding the logged CSV header (field
note), so they register as not_json like every other bulk route.Still failing / never decodable
*-bulkroutestext/csv(Accept: application/json ignored); SDK decodes a JSON array and test fixtures are JSON -> everyclient.bulkcall fails. NEW (no bulk route returned a 200 body in the first run)Vec<FinancialReportJson>(5/5 calls fail)[]: callers must walk short windows (doc note, like treasury)Invalid name(not JSON) -> decode error instead of a typed error. NEW[]with the params triedNew rows (model: members not in the first-run table) [sdk result today]
Note: the 38 BalanceSheetStatement and 19 CashFlowStatement null rows all come from RIVN quarterly stubs (BS 2021-03-31 Q1, CF 2020-12-31 Q4 and 2021-09-30 Q3, around the Nov 2021 IPO): a "skip or tolerate all-null stub rows" policy is an alternative to widening 57 members.
First-run SDK failures that stayed
47/47 first-run failing keys fail again; none disappeared (the old table's rows are all present in the new one).
Key scan
grep -rF $FMP_API_KEYover the work dir: 0 hits.apikey=not followed by***: 887 hits, NOT zero. 886 are the literal provider placeholderapikey=YOUR_API_KEYinside financial-reports-dates bodies (17 raw files), 1 is the scrub regex in fetch.py. No real key.feat(codecs): add empty_or_null deserializer for nullable typed codes
feat(economics): make treasury tenors and calendar figures optional
docs(crates): update fixture README for issue #368 optional beta
benbenbang commentedon Sep 30, 2026
All 12 units merged (1.2.0, breaking; ships on the next release).
empty_or_nullcodec, gen_go support, ADR 0033Acceptance:
treasury-ratesrow with"month2": nulldecodes toNone/nil, other tenors set (Rust, Go, Python)."beta": nullrow decodes (Rust, Go, Python).Policy recorded in ADR 0033: members seen
nullbecomeOption/ pointer /Optional;""on typed codes and dates becomesNone;""on plain strings stays""; no zero or NaN defaults, no dropped rows.Related type fixes filed separately: #376 (bulk CSV), #377, #378, #379, #380, #381, and #387 (Go decode path on nullable members).
chore(release): update version to 1.2.0 [skip ci]