All notable changes to pyFIA will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Restores the NSVB-recomputation tree-carbon path (#121) — deleted when the
broader carbon subsystem was deferred — so first-party downstream consumers
(forest-carbon) can resume their NSVB-vs-FIADB reconciliation. Only the two
tree estimators and their NSVB machinery are restored; the condition-level
pools remain deferred pending NULL-handling verification (#90).
pyfia.carbonsubpackage — NSVB equation library, coefficient loaders, and carbon fractions (Models 1/2/4/5; S1a–S8b coefficient tables; Bailey DIVISION → species-level → Jenkins lookup precedence; S10a/S10b carbon fractions; vendored coefficient CSVs from GTR-WO-104 Supp1).live_tree()— NSVB live-tree carbon, recomputed tree-by-tree (AG from the NSVB biomass pipeline × species-specific S10a carbon fractions; BG bridged to FIADBTREE.CARBON_BG). ReturnsCARBON_ACRE,CARBON_TOTAL,N_PLOTS,N_TREES, andCARBON_ACRE_SE/CARBON_TOTAL_SE. Validated vs FIADBCARBON_AGon Georgia EVALID 132401 (130,806 trees): median per-tree error 0.085%.standing_dead()— NSVB standing-dead carbon (STANDING_DEAD_CD=1) withREF_TREE_DECAY_PROPdecay reductions, Appendix K broken-top corrections (Table S11 crown ratios), and S10b dead carbon fractions.- Woodland-species coverage — woodland species (
REF_SPECIES.WOODLAND='Y'), which NSVB does not model, route to FIADB-storedCARBON_AGinstead of collapsing to zero; a non-woodland SPCD matching neither an NSVB species-level row nor a Jenkins 1–9 group raises rather than silently zeroing. - NSVB validation gates — per-tree parity tests against FIADB
CARBON_AGon real Georgia inventory data.
- Condition-level carbon pools (
understory,downed_dead,litter,soil_organic),total_ecosystem, andstock_changeremain held pending domain verification of their NULL handling (#90) — tracked separately. - Native NSVB belowground coarse-root model (currently bridged to FIADB
TREE.CARBON_BG).
Bug-fix release for most_recent evaluation selection (#130).
clip_by_state(most_recent=True)/find_evalid(most_recent=True)could select an old periodic evaluation instead of the current annual one (#130) — the most-recent sort ordered byEND_INVYRdescending, but polars' defaultnulls_last=FalsesortsNULLfirst underdescending=True. Periodic evaluations (which predateEND_INVYRand store it asNULL) therefore outranked dated annual evaluations. For example,FIA("CA.duckdb").clip_by_state(6, most_recent=True, eval_type="VOL")selected the 1994 periodic EVALID69401instead of the 2021 annual EVALID62101, silently returning 1994-vintage plots. The sort now passesnulls_last=Trueso periodic evaluations sink to the bottom whenever a dated annual evaluation is available, in both the general and Texas East/West branches.
Bug-fix release closing four estimator issues found in the v1.4.1 public-docs audit and the follow-up review (#109, #110, #111, #116). Point estimates and standard errors are unchanged for every previously-working query; the changes concern input validation, spatial grouping, and the opt-in variance columns.
biomass(component="ROOT")crashed with aBinderException(#110) —"root"passed validation but then selected a non-existentDRYBIO_ROOTcolumn."root"has been removed from the valid component set; use"bg"for belowground/coarse-root biomass (DRYBIO_BG). Valid components areAG,BG,TOTAL,BOLE,BRANCH,FOLIAGE(case-insensitive).area(grp_by=<intersect_polygons attribute>)crashed with aBinderException(#116) — a spatial attribute (e.g.REGION) added byintersect_polygons()was routed into theCONDquery, where it does not exist.area()no longer requests polygon attributes fromCOND(they are supplied by the plot/stratification join), so spatial grouping works.- GRM estimators rejected the
tree_typevalues they actually support (#111) —growth()/mortality()/removals()advertise and internally handlegs/al/sl/live/sawtimber, but the shared validator only allowed{all, dead, gs, live}, so following the documented API raisedValueError. These estimators now validate against{gs, al, sl, live, sawtimber}(al/live→ all-live,sl/sawtimber→ sawtimber-size).area()/volume()/biomass()/tpa()keep{all, dead, gs, live}. variance=Truedid not add variance columns (and broketpa()) (#109) — the flag was a no-op for most estimators, and fortpa()it dropped the standard-error columns entirely. Standard errors (*_SE) are now always returned, andvariance=Trueadds matching*_VARIANCEcolumns (equal to the standard error squared) uniformly across every estimator.
- The standard-error / variance output contract is now uniform and opt-in.
area()andvolume()no longer emit variance columns by default — passvariance=True. Variance columns use the standard-error-mirrored name (e.g.VOLCFNET_TOTAL_SE→VOLCFNET_TOTAL_VARIANCE,AREA_SE_PERCENT→AREA_VARIANCE_PERCENT);tpa()'s previous*_VARcolumns are renamed to*_VARIANCE.area_change()now follows the same contract: it always returnsAREA_CHANGE_SEand addsAREA_CHANGE_VARIANCEonly withvariance=True(replacing the previousse_total/variance_totalcolumns). - GRM estimators no longer silently accept
tree_type="all"/"dead"(part of #111) — these previously fell through to growing stock; they now raise a clearValueError. mortality(tree_type="sawtimber")now matchestree_type="sl"exactly — a redundant diameter/VOLCSNETfilter was removed; the GRMSLpopulation columns already encode sawtimber, so the two aliases are guaranteed identical.
1.4.1 - 2026-06-30
Bug-fix release for the Growth-Removal-Mortality (GRM) / condition-column paths, reported while building disturbance × treatment-stratified carbon stock-change against per-state FIADB DuckDBs. All public APIs from 1.4.0 remain backward compatible; no estimation math, variance formulas, or EVALIDator-validated numbers change for previously-working queries.
clip_most_recent(eval_type="GRM")always raisedNoEVALIDError(#102) —find_evalid()built a non-existentEXPGRMevaluation type.eval_typeis now resolved through an explicit token →EVAL_TYPmap:"GRM"is a working alias for the shared growth/removal/mortality family EVALID, rawEXP*codes pass through, and unknown tokens raise a clear error listing the valid options.area_domaincould not filter on COND columns thatgrp_byaccepts (#103) — e.g.area_domain="DSTRBCD1 > 0"raisedColumnNotFoundErrorforgrowth()/mortality()/removals(). Columns referenced ingrp_by,area_domain, andtree_domainare now resolved against the real table schemas and threaded into the COND/TREE/PLOT loads uniformly across all four estimators.mortality()/removals()crashed ongrp_bycolumns outside a fixed allowlist (#104) — e.g.TRTCD1was dropped before the variance step (aColumnNotFoundError) wheregrowth()/biomass()succeeded.aggregate_cond_to_plot()now carries every loaded condition column to plot level, so any grouping column survives.biomass(grp_by=...)crashed on states where a grouping key is null for every group (#105) — e.g.DSTRBCD1on Oregon raised a polarsSchemaError(i64vsnull) when the per-group variance frame inferred aNulldtype. Variance join keys are now dtype-aligned before joining; no joined value changes.- GRM estimators crashed on state databases that store numeric columns as
VARCHAR(#106) — e.g.growth()on AZ/NM/WY (division with 'String') andremovals()on MT/NV (cannot compare string with numeric). GRM arithmetic columns (SUBP_TPA*_UNADJ_*,REMPER,DIA*,DRYBIO_*,MACRO_BREAKPOINT_DIA, …) are cast to their declared numeric types at load.
1.4.0 - 2026-06-26
Adds the tree_metrics() estimator and a public query() method, plus EVALID,
variance, and downloader fixes and release hardening. All public APIs from
1.3.0 remain backward compatible.
tree_metrics()estimator — TPA-weighted descriptive statistics for derived per-condition tree metrics (#73).- Public
query()method onFIAfor raw SQL execution (#72). - PLT_CN / CONDID grouping — support plot- and condition-level grouping columns for plot-condition estimates (#71).
__version__is now read from package metadata viaimportlib.metadata(#92) —pyproject.tomlis the single source of truth, so the version string can no longer drift from the published package.
- EVALID year parsing for single-digit state FIPS codes (#78, #79, #80) —
_extract_evaluation_year()now usesEND_INVYRfromPOP_EVALwith an EVALID tiebreaker. - EVALID filtering not applied to all PLT_CN tables (#76, #77) — tables such as
TREE_GRM_COMPONENTwere not filtered by EVALID; column-detection filtering now applies to direct and spatial filter paths. - Area variance underestimation for rare categories when using
grp_by(#68). sanitize_sql_path()failure on Windows when callingdownload()(#74).- Non-atomic file writes in the downloader (#88) — downloads now write to a temporary file and atomically replace the destination (with cleanup on interrupt), and cache metadata is written the same way; an interrupted download/write no longer leaves a truncated file.
get_cached()now verifies file size on every hit and supports opt-in MD5 verification (the stored checksum was previously never checked). - Silently cached broken databases on reference-table download failure (#86) —
download()now verifies the built database contains the required reference tables (REF_SPECIES,REF_FOREST_TYPE,REF_STATE) before caching. If any are missing/empty the incomplete database is discarded and a clearDownloadErroris raised telling the user to retry, instead of caching a database that breaksby_species/ name-join operations.
clip_by_state()/clip_by_evalid()now coerce arguments toint(#87) — enforces the documentedint | list[int]contract and rejects non-numeric input (e.g."37 OR 1=1") before it reaches the SQLIN (...)clause, closing a string-interpolation seam.
1.3.0 - 2026-02-07
- Ratio-of-means variance for per-acre estimates (#70).
Makefilewith test and validation convenience commands.- Unit tests for previously untested estimators.
- Modernized type annotations; explicit GRM error handling; stricter domain validation.
- All mypy type errors across 22 source files.
- Panel validation now sets the EVALID filter before calling
removals(). - Out-of-memory crash from conftest marker hooks; slow tests filtered by default.
- Miscellaneous technical-debt cleanup (#69).
1.2.1 – [1.2.3] - 2026-01-28 – 2026-01-29
Rapid maintenance releases. (Entries reconstructed from git history — these versions predate git tagging, so per-patch attribution is approximate.)
site_index()estimator — area-weighted mean site index.- Jupyter tutorial notebooks for learning pyFIA.
- Refactored variance calculation to eliminate duplication across estimators.
- Standardized EVALID handling across estimators.
- Consolidated input-validation logic into shared utilities.
- Removed fiatools branding/references.
growth()now handles a missingBEGINENDtable in DuckDB.- Reference-table helper functions accept
Pathobjects. - Misleading ERROR log for missing tables (e.g.
BEGINEND). - Numerous notebook column-name, API, and Colab-compatibility fixes.
1.2.0 - 2026-01-18
panel()function - Create t1/t2 remeasurement panels from FIA data:- Condition-level panels for harvest and area change analysis
- Tree-level panels with GRM-based tree fate classification (survivor, mortality, cut, diversion, ingrowth)
- Expansion factor support for per-acre estimates via
expand=True - Configurable filters:
min_remper,max_remper,min_invyr,harvest_only
by_size_classparameter for GRM estimators - Group mortality, growth, and removals by diameter class:size_class_type="market": Pre-merchantable, Pulpwood, Chip-n-Saw, Sawtimber (species-aware)size_class_type="standard": FIA numeric ranges (1.0-4.9, 5.0-9.9, etc.)size_class_type="descriptive": Saplings, Small, Medium, Large
- Pre-merchantable tree support - Trees <5" DBH now supported in mortality estimation (fixes #67):
- Use
tree_type="live"to include all live trees - Properly categorized as "Pre-merchantable" in market size classes
- TPA recommended for small trees (FIA doesn't calculate volume for <5" DBH)
- Use
- AGENTCD grouping in mortality() - Group mortality estimates by cause of death (tree-level)
- DSTRBCD grouping in mortality() - Group mortality estimates by disturbance code (condition-level)
- Example script
examples/mortality_by_cause.pyfor timber casualty loss analysis
- GRM totals ~60x too high when EVALID not set - Trees were counted multiple times across all annual evaluations. Now auto-filters to most recent GRM evaluation with warning.
area_domainfilter not applied inarea()function - Domain filters were being ignored- SQL injection vulnerabilities - Fixed high-severity security issues in query construction
- UNITCD not included when using AGENTCD grouping - Missing grouping column in mortality output
- Table caching bug - Tables now reload when new columns are needed
- Null rows in area estimation grouped output - Fixed null rows appearing in results
- COND table caching after other estimators - Fixed stale cache issues
- Refactored estimators to use centralized column resolution and data loading modules
- Improved error handling with custom exception hierarchy
- Enhanced type hints and validation across estimation module
1.1.0b1 - 2025-12-23
- MotherDuck backend - Cloud-based FIA data access via MotherDuck serverless warehouse
- Spatial filtering - New methods for geographic subsetting:
clip_by_polygon()- Filter plots to polygon boundaryintersect_polygons()- Join polygon attributes to plots for grouping
area_change()estimator - Forest land transition analysis between inventory cyclesget_table_schema()method for MotherDuckReaderWrapperinclude_treesparameter toprepare_estimation_data()- Area change estimates to EVALIDator client
- Comprehensive spatial filtering guide
- Optimized memory usage in biomass estimator with SQL-level filtering
- Optimized
area.pyapply_filters for cloud backends - Standardized error handling with custom exception hierarchy
- Replaced magic numbers with EVALIDYearParsing constants
- MACRO_BREAKPOINT_DIA type comparison by casting to Float64
- Estimation import paths to use re-exported functions
- Spatial filters now properly flow polygon attributes through estimators
- Added area_change validation tests against EVALIDator
- Added comprehensive unit tests for area_change estimator
- Added comprehensive unit tests for exceptions, parser, and constants
1.0.0b1 - 2025-12-16
- Complete estimation API with 8 core estimators:
area()- Forest land area estimationvolume()- Standing timber volume (net/gross/sound/sawlog)biomass()- Above/below ground biomasstpa()- Trees per acre and basal areamortality()- Annual mortality volumegrowth()- Annual net growth volumeremovals()- Annual removals volumecarbon_pools()- Carbon stock by pool (AG/BG/total)
- EVALIDator validation framework with automated API testing
- Comprehensive variance estimation following Bechtold & Patterson (2005)
- FIA DataMart integration for direct data downloads
- Reference table utilities for species, forest types, and state names
- Filtering system for land types, tree types, and domain expressions
- Grouping capabilities for stratified analysis
- DuckDB backend with Polars LazyFrame for high performance
- All core estimators validated against USFS EVALIDator API
- Point estimates match exactly (within floating point tolerance)
- Standard errors within acceptable tolerance:
- Area estimates: 3%
- Tree-based estimates (volume, biomass, tpa): 15%
- GRM estimates (growth, mortality, removals): 30%
- Implements stratified ratio-of-means variance formula (Eq. 4.1, 4.2, 4.8)
- Two-stage aggregation for proper stratum weighting
- Supports EVALID-based plot filtering for consistent estimation units
0.3.0 - 2025-12-15
- GRMBaseEstimator for growth/mortality/removals (Phase 2 estimates)
- Type hints across estimation module
- Property-based tests with Hypothesis
- Plot count tracking in validation tests
- SE validation with configurable tolerances
- Replaced placeholder variance calculations with proper error handling
- Improved documentation with specific Bechtold & Patterson (2005) citations
- Enhanced validation test framework with plot count comparisons
- Variance calculations now properly handle ratio-of-means estimation
- GRM tests now correctly unpack 3 values from extract_grm_estimate
0.2.0 - 2025-XX-XX
- Initial estimation functionality
- Core database connectivity with DuckDB
- Basic filtering capabilities
- EVALIDator client for validation
0.1.0 - 2025-XX-XX
- Initial project structure
- FIA database abstraction layer
- Basic data reading capabilities