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: BaseModel

A published retention curve.

Parameters:
curve_id

Stable identifier used by the CLI and downstream consumers.

Type:

str

name

Human-readable curve label.

Type:

str

age_unit

Meaning of the age axis, for example years_since_fire_or_tree_death.

Type:

str

retention_definition

What the retention fraction measures (volume, grade, value, and so on).

Type:

str

points

Published age/retention pairs in strictly increasing age order.

Type:

list[fhops.salvage.degradation.DecayCurvePoint]

source

Citation, URL, and applicability boundary.

Type:

fhops.salvage.degradation.DecayCurveSource

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:

float

Raises:

ValueError – If age_years is 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: BaseModel

The validated contents of the packaged salvage.decay.v1 data file.

Parameters:
schema_version

Contract identifier. Must equal SCHEMA_VERSION.

Type:

str

curves

Published retention curves, keyed by DecayCurve.curve_id.

Type:

list[fhops.salvage.degradation.DecayCurve]

product_windows

Advisory product windows from the shelf-life source.

Type:

list[fhops.salvage.degradation.ProductWindow]

curve(curve_id)[source]

Return one curve by id.

Parameters:

curve_id (str) – Identifier of the requested curve.

Returns:

The matching curve.

Return type:

DecayCurve

Raises:

KeyError – If curve_id is not present. The message lists available ids.

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:

tuple of str

Raises:

ValueError – If age_years is 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: BaseModel

One published age/retention pair.

Parameters:
age_years

Age since fire or tree death, in years. Must be non-negative.

Type:

float

retention_fraction

Recoverable volume divided by pre-disturbance green volume. Must lie in [0, 1].

Type:

float

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: BaseModel

Provenance and applicability boundary for a published curve or window.

Parameters:
  • citation (str)

  • url (str)

  • boundary (str)

  • retrieved (str | None)

citation

Human-readable citation, including the section or table when available.

Type:

str

url

Public http(s) URL for the source document or DOI.

Type:

str

boundary

Plain-language statement of what the source does not cover. Consumers must surface this text rather than treating the curve as universal.

Type:

str

retrieved

Optional ISO date when the public source was checked.

Type:

str | None

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: BaseModel

An advisory product-eligibility window from a published shelf-life review.

Parameters:
product

Product name, for example lumber, pulp, or bioenergy.

Type:

str

min_years

Earliest age at which the product is considered plausible. Defaults to zero.

Type:

float

max_years

Latest age at which the product is considered plausible. None means the source gives no upper bound (open-ended).

Type:

float | None

note

The source statement, paraphrased only enough to fit a table cell.

Type:

str

source

Citation, URL, and applicability boundary.

Type:

fhops.salvage.degradation.DecayCurveSource

contains(age_years)[source]

Return whether age_years falls inside this advisory window.

Parameters:

age_years (float)

Return type:

bool

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:
Returns:

The requested curve.

Return type:

DecayCurve

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 packaged data/reference/degradation/salvage_decay.v1.json file through fhops.resources.data_path().

Returns:

Validated curve and product-window library.

Return type:

DecayCurveLibrary

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: object

Result of joining a storage/age table to a scenario.

Parameters:
frame

Deterministic per-block table with the columns listed in _REPORT_COLUMNS. retained_value is None when the caller did not supply a price.

Type:

pandas.DataFrame

curve

The applied retention curve, including provenance and boundary text.

Type:

fhops.salvage.degradation.DecayCurve

library

The full curve library, used to expose product windows and sources.

Type:

fhops.salvage.degradation.DecayCurveLibrary

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.

Type:

tuple[str, …]

property total_original_volume_m3: float

Original volume in the report, in cubic metres.

property total_retained_value: float | None

Retained value when a price was supplied, otherwise None.

property total_retained_volume_m3: float

Retained volume in the report, in cubic metres.

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 supply work_required (original volume, m³) and the optional salvage_processing_mode.

  • storage_table (str | Path | DataFrame) – CSV path or DataFrame with block_id and years_since_disturbance columns. 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_value is blank. This is not a price forecast.

  • library (DecayCurveLibrary | None) – Optional pre-loaded curve library. None loads the packaged data.

Returns:

Deterministic per-block table plus the applied curve and library.

Return type:

SalvageReport

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_m3 is negative.

  • KeyError – If curve_id is 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:
Returns:

Mapping with csv and markdown keys.

Return type:

dict of str to pathlib.Path