fhops.salvage Package
Published salvage degradation curves and a read-only shelf-life report consumer. The package separates the retention curve, the advisory product windows, and the source/boundary record. It does not schedule salvage or change solver behaviour.
Published salvage degradation and product shelf-life curves.
Why this module exists
FHOPS models the salvage machine chain and records a
SalvageProcessingMode, but it does not
model the biological clock that changes what can be recovered from dead wood.
This module is the shared, read-only contract for that clock. It separates three
things that are often mixed together:
a retention curve (how much recoverable volume remains at a given age);
a product window (which products are still plausible at that age); and
a source record (where the numbers came from and what they do not cover).
The first curve is the published mixed-conifer fire-killed net-volume retention series in Prestemon and Holmes (2008), section 4.2.6. The product windows are the Canadian boreal shelf-life statements in Barrette et al. (2015). No coefficient in this module is fitted, extrapolated, or invented by FHOPS. A future BC or species-specific curve can be added as a new data entry without changing code.
The module deliberately returns fractions and product names, not prices or objective terms. Callers that need money must supply their own price and label it as an assumption.
- fhops.salvage.degradation.DEFAULT_CURVE_ID = 'pnw_fire_killed_mixed_conifer'
Default curve used by the salvage report consumer.
- class fhops.salvage.degradation.DecayCurve(*, curve_id, name, age_unit, retention_definition, points, source)[source]
Bases:
BaseModelA published retention curve.
- Parameters:
curve_id (str)
name (str)
age_unit (str)
retention_definition (str)
points (list[DecayCurvePoint])
source (DecayCurveSource)
- retention_definition
What the retention fraction measures (volume, grade, value, and so on).
- Type:
- points
Published age/retention pairs in strictly increasing age order.
- source
Citation, URL, and applicability boundary.
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- retention_at(age_years)[source]
Return the retained fraction at
age_years.- Parameters:
age_years (float) – Age since fire or tree death, in years. Must be non-negative.
- Returns:
Linear interpolation between the two surrounding published points. Ages before the first point use the first fraction; ages after the last point use the last fraction. The result is clipped to
[0, 1]because the source values are fractions.- Return type:
- Raises:
ValueError – If
age_yearsis negative or not finite.
Notes
Interpolation is a presentation choice for ages between published years; it does not add new coefficients. The published whole-year values are returned exactly.
- class fhops.salvage.degradation.DecayCurveLibrary(*, schema_version, curves, product_windows)[source]
Bases:
BaseModelThe validated contents of the packaged
salvage.decay.v1data file.- Parameters:
schema_version (str)
curves (list[DecayCurve])
product_windows (list[ProductWindow])
- curves
Published retention curves, keyed by
DecayCurve.curve_id.
- product_windows
Advisory product windows from the shelf-life source.
- curve(curve_id)[source]
Return one curve by id.
- eligible_products(age_years)[source]
Return advisory product names for
age_years.- Parameters:
age_years (float) – Age since fire or tree death, in years. Must be non-negative.
- Returns:
Product names whose published windows contain the age, in the order declared by the data file. The result may contain more than one product because the source windows overlap.
- Return type:
- Raises:
ValueError – If
age_yearsis negative or not finite.
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.salvage.degradation.DecayCurvePoint(*, age_years, retention_fraction)[source]
Bases:
BaseModelOne published age/retention pair.
- retention_fraction
Recoverable volume divided by pre-disturbance green volume. Must lie in
[0, 1].- Type:
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.salvage.degradation.DecayCurveSource(*, citation, url, boundary, retrieved=None)[source]
Bases:
BaseModelProvenance and applicability boundary for a published curve or window.
- boundary
Plain-language statement of what the source does not cover. Consumers must surface this text rather than treating the curve as universal.
- Type:
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.salvage.degradation.ProductWindow(*, product, min_years=0.0, max_years=None, note, source)[source]
Bases:
BaseModelAn advisory product-eligibility window from a published shelf-life review.
- Parameters:
product (str)
min_years (float)
max_years (float | None)
note (str)
source (DecayCurveSource)
- max_years
Latest age at which the product is considered plausible.
Nonemeans the source gives no upper bound (open-ended).- Type:
float | None
- source
Citation, URL, and applicability boundary.
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- fhops.salvage.degradation.get_decay_curve(curve_id='pnw_fire_killed_mixed_conifer', library=None)[source]
Return a curve by id from the packaged library or a supplied library.
- Parameters:
curve_id (str) – Curve identifier. Defaults to
DEFAULT_CURVE_ID.library (DecayCurveLibrary | None) – Optional pre-loaded library.
Noneloads the packaged data.
- Returns:
The requested curve.
- Return type:
- Raises:
KeyError – If the curve id is not present.
- fhops.salvage.degradation.load_decay_library(path=None)[source]
Load and validate the packaged salvage decay library.
- Parameters:
path (str | Path | None) – Optional explicit JSON path.
None(default) resolves the packageddata/reference/degradation/salvage_decay.v1.jsonfile throughfhops.resources.data_path().- Returns:
Validated curve and product-window library.
- Return type:
- Raises:
FileNotFoundError – If the JSON file does not exist.
pydantic.ValidationError – If the payload is malformed, out of range, or has duplicate ids.
json.JSONDecodeError – If the file is not valid JSON.
Read-only salvage shelf-life report consumer.
This module joins an explicit storage/age table to a validated FHOPS scenario and
applies the published curves in fhops.salvage.degradation. It does not
schedule, price, or optimise anything. The scenario remains the authority for
block volume; the side table supplies only the age since fire or tree death.
The report is intentionally conservative:
every block id must resolve to a scenario block;
ages must be finite and non-negative;
the output names the curve, source, and applicability boundary;
an optional value is computed only when the caller supplies a price per cubic metre, and that price is labelled as a caller assumption.
- fhops.salvage.report.REQUIRED_STORAGE_COLUMNS = ('block_id', 'years_since_disturbance')
Columns the storage/age table must provide.
- class fhops.salvage.report.SalvageReport(frame, curve, library, blocks_without_salvage_mode)[source]
Bases:
objectResult of joining a storage/age table to a scenario.
- Parameters:
frame (DataFrame)
curve (DecayCurve)
library (DecayCurveLibrary)
- frame
Deterministic per-block table with the columns listed in
_REPORT_COLUMNS.retained_valueisNonewhen the caller did not supply a price.- Type:
pandas.DataFrame
- curve
The applied retention curve, including provenance and boundary text.
- library
The full curve library, used to expose product windows and sources.
- blocks_without_salvage_mode
Scenario block ids in the table that do not declare a
salvage_processing_mode. They are reported, not rejected, because some callers use salvage systems without setting the optional field.
- fhops.salvage.report.build_salvage_report(scenario_path, storage_table, *, curve_id='pnw_fire_killed_mixed_conifer', value_per_m3=None, library=None)[source]
Build a per-block salvage retention report.
- Parameters:
scenario_path (str | Path) – Path to a standard FHOPS
scenario.yaml. Blocks supplywork_required(original volume, m³) and the optionalsalvage_processing_mode.storage_table (str | Path | DataFrame) – CSV path or DataFrame with
block_idandyears_since_disturbancecolumns. Ages are years since fire or tree death and must be non-negative. Duplicate block rows are rejected so the report has one deterministic row per block.curve_id (str) – Curve identifier from the packaged library. Defaults to the published Pacific Northwest fire-killed mixed-conifer curve.
value_per_m3 (float | None) – Optional caller-supplied value per cubic metre of retained volume. When omitted,
retained_valueis blank. This is not a price forecast.library (DecayCurveLibrary | None) – Optional pre-loaded curve library.
Noneloads the packaged data.
- Returns:
Deterministic per-block table plus the applied curve and library.
- Return type:
- Raises:
ValueError – If required columns are missing, ages are invalid, block ids are duplicated, a block id is not in the scenario, or
value_per_m3is negative.KeyError – If
curve_idis not present in the library.FileNotFoundError – If the scenario or storage table does not exist.
- fhops.salvage.report.write_salvage_report(report, out_dir)[source]
Write deterministic CSV and Markdown report artifacts.
- Parameters:
report (SalvageReport) – Result from
build_salvage_report().out_dir (str | Path) – Directory for
salvage_shelf_life.csvandsalvage_shelf_life.md. Created when absent.
- Returns:
Mapping with
csvandmarkdownkeys.- Return type:
dict of str to pathlib.Path