fhops.scenario Package
This package hosts the scenario contract, IO utilities, and synthetic dataset generator. Use it when
authoring datasets, validating inputs, or programmatically instantiating fhops.scenario.contract.Problem
objects before passing them to solvers. The typical workflow is:
Define CSV tables + YAML metadata (see Data Contract Guide).
Load scenarios with
fhops.scenario.io.load_scenario().Create a
fhops.scenario.contract.ProblemviaProblem.from_scenariofor use with MIP/heuristics.Optionally call
fhops.scenario.synthetichelpers to generate benchmark datasets.
Scenario package exposing contracts, IO helpers, and synthetic generators.
- class fhops.scenario.Block(*, id, landing_id, work_required, earliest_start=1, latest_finish=None, harvest_system_id=None, avg_stem_size_m3=None, volume_per_ha_m3=None, volume_per_ha_m3_sigma=None, stem_density_per_ha=None, stem_density_per_ha_sigma=None, ground_slope_percent=None, salvage_processing_mode=None)[source]
Bases:
BaseModelHarvest block metadata and scheduling window.
- Parameters:
id (str)
landing_id (str)
work_required (float)
earliest_start (int | None)
latest_finish (int | None)
harvest_system_id (str | None)
avg_stem_size_m3 (float | None)
volume_per_ha_m3 (float | None)
volume_per_ha_m3_sigma (float | None)
stem_density_per_ha (float | None)
stem_density_per_ha_sigma (float | None)
ground_slope_percent (float | None)
salvage_processing_mode (SalvageProcessingMode | None)
- id
Unique block identifier (referenced by production rates and assignments).
- Type:
- landing_id
Landing where wood is forwarded; constrains landing daily capacity.
- Type:
- work_required
Volume (m³) that must be delivered from the block to complete it — the terminal (e.g., loader) output measured in the same units as
ProductionRate.rate. Every role of an explicit harvest system processes up to this volume; loader truckload batching, playback production, KPIs (total_production,remaining_work_total) and all reference datasets treat it as m³. Must be non-negative.- Type:
- earliest_start
Optional earliest day (inclusive, 1-indexed) when the block can begin.
- Type:
Day | None
- latest_finish
Optional latest day (inclusive) when the block must finish.
- Type:
Day | None
- harvest_system_id
Optional harvest system definition that restricts machine roles per block. Scenario validation requires the id to be a key of
Scenario.harvest_systemsor offhops.scheduling.systems.default_system_registry().- Type:
str | None
- avg_stem_size_m3 / volume_per_ha_m3 / volume_per_ha_m3_sigma
Stand descriptors (cubic metres) surfaced in analytics and productivity lookups.
- stem_density_per_ha / stem_density_per_ha_sigma
Stems per hectare statistics used by some productivity models.
- ground_slope_percent
Mean slope (%) for the block — used by productivity heuristics and diagnostics.
- Type:
float | None
- salvage_processing_mode
Enum describing downstream salvage processing (affects evaluation notes).
- Type:
SalvageProcessingMode | None
- earliest_start: Day | None
- id: str
- landing_id: str
- latest_finish: Day | None
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- salvage_processing_mode: SalvageProcessingMode | None
- work_required: float
- class fhops.scenario.BlockOperability(block_id, landing_id, terrain_class, current_system_id, current_system_class, current_system_compatible, recommended_system_id, evidence_source, fallbacks, slope_percent, roughness_m, canopy_cover_percent, aspect_degrees, landing_slope_percent, landing_roughness_m, move_in_multiplier, landing_multiplier, road_multiplier, reasons=<factory>)[source]
Bases:
objectAdvisory operability result for one block.
- Parameters:
block_id (str)
landing_id (str)
terrain_class (Literal['ground', 'cable', 'helicopter'])
current_system_id (str | None)
current_system_class (Literal['ground', 'cable', 'helicopter'] | None)
current_system_compatible (bool | None)
recommended_system_id (str)
evidence_source (str)
slope_percent (float)
roughness_m (float)
canopy_cover_percent (float | None)
aspect_degrees (float | None)
landing_slope_percent (float | None)
landing_roughness_m (float | None)
move_in_multiplier (float)
landing_multiplier (float)
road_multiplier (float)
- block_id: str
- evidence_source: str
- landing_id: str
- landing_multiplier: float
- move_in_multiplier: float
- recommended_system_id: str
- road_multiplier: float
- roughness_m: float
- slope_percent: float
- terrain_class: Literal['ground', 'cable', 'helicopter']
- class fhops.scenario.CalendarEntry(*, machine_id, day, available=1)[source]
Bases:
BaseModelDay-level availability for a machine.
- machine_id
Identifier of the machine whose availability is being set.
- Type:
- day
One-indexed day number relative to the scenario horizon.
- Type:
Day
- available
Binary flag (1 available, 0 unavailable) controlling day-level assignment eligibility.
- Type:
- available: int
- day: Day
- machine_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- fhops.scenario.Day
alias of
int
- class fhops.scenario.FemicHydrologyArtifact(role, relative_path, schema_version, media_type, size_bytes, sha256, description)[source]
Bases:
objectOne pinned artifact from a FEMIC hydrology manifest.
- Parameters:
- role
Producer artifact role.
- Type:
- relative_path
Path relative to the bundle root.
- Type:
- schema_version
Artifact schema recorded by the producer.
- Type:
- media_type
Artifact media type.
- Type:
- size_bytes
Declared artifact size.
- Type:
- sha256
Declared artifact SHA-256 digest.
- Type:
- description
Producer description.
- Type:
- description: str
- media_type: str
- relative_path: str
- role: str
- schema_version: str
- sha256: str
- size_bytes: int
- exception fhops.scenario.FemicHydrologyAssetError[source]
Bases:
RuntimeErrorRaised when a road/culvert asset table cannot be consumed or mapped.
- class fhops.scenario.FemicHydrologyBundle(bundle_dir, manifest_path, bundle_id, bundle_version, generated_utc, domain_id, ledger_id, response_surface_id, manifest_sha256, artifacts, response_surface, flood_risk, applicability_domain, consumer_contract, caveats, total_bytes)[source]
Bases:
objectValidated and pinned FEMIC hydrology bundle metadata.
- Parameters:
- bundle_dir
Resolved bundle root.
- Type:
Path
- manifest_path
Resolved manifest path.
- Type:
Path
- bundle_id
Producer bundle identifier.
- Type:
- bundle_version
Producer bundle version.
- Type:
- generated_utc
Producer generation timestamp.
- Type:
- domain_id
FEMIC domain identifier.
- Type:
- ledger_id
FEMIC disturbance-ledger identifier.
- Type:
- response_surface_id
FEMIC response-surface identifier.
- Type:
- manifest_sha256
SHA-256 digest of the manifest bytes.
- Type:
- total_bytes
Sum of declared artifact sizes.
- Type:
- bundle_dir: Path
- bundle_id: str
- bundle_version: str
- domain_id: str
- generated_utc: str
- ledger_id: str
- manifest_path: Path
- manifest_sha256: str
- response_surface_id: str
- total_bytes: int
- exception fhops.scenario.FemicHydrologyConstraintError[source]
Bases:
RuntimeErrorA schedule cannot be checked against hazard evidence.
- exception fhops.scenario.FemicHydrologyError[source]
Bases:
RuntimeErrorRaised when a FEMIC hydrology bundle cannot be consumed.
- exception fhops.scenario.FemicHydrologyExposureError[source]
Bases:
RuntimeErrorRaised when a road/culvert exposure table cannot be consumed.
- exception fhops.scenario.FemicHydrologyHazardError[source]
Bases:
RuntimeErrorRaised when a hazard-window table cannot be consumed.
- exception fhops.scenario.FemicHydrologyMappingError[source]
Bases:
RuntimeErrorA hydrology crosswalk cannot be loaded or applied to a scenario.
- class fhops.scenario.FemicHydrologyReadiness(status, validation_report_id, model_card_id, review_status, full_process_pilot_ready, hydrologist_review_recorded, tail_check_failures, usable_fits, benchmark_cases, benchmark_passed, benchmark_failed, benchmark_not_evaluated, warnings, caveats, evidence_sha256)[source]
Bases:
objectValidation/model-card evidence for a pinned hydrology bundle.
- Parameters:
status (str)
validation_report_id (str | None)
model_card_id (str | None)
review_status (str | None)
full_process_pilot_ready (bool | None)
hydrologist_review_recorded (bool | None)
benchmark_cases (int)
benchmark_passed (int)
benchmark_failed (int)
benchmark_not_evaluated (int)
evidence_sha256 (str)
- status
not_available,provisional,conditionally_ready,ready, orblocked.- Type:
- validation_report_id
Validation-report identifier, when packaged.
- Type:
str | None
- model_card_id
Model-card identifier, when packaged.
- Type:
str | None
- review_status
Producer model-card review status.
- Type:
str | None
- full_process_pilot_ready
Producer full-pilot readiness flag.
- Type:
bool | None
- hydrologist_review_recorded
Producer review-record flag.
- Type:
bool | None
- benchmark_cases
Published benchmark count.
- Type:
- benchmark_passed
Passed benchmark count.
- Type:
- benchmark_failed
Failed benchmark count.
- Type:
- benchmark_not_evaluated
Not-evaluated benchmark count.
- Type:
- evidence_sha256
Deterministic digest of the evidence record.
- Type:
- benchmark_cases: int
- benchmark_failed: int
- benchmark_not_evaluated: int
- benchmark_passed: int
- evidence_sha256: str
- status: str
- to_dict()[source]
Return the readiness report as a JSON-serializable mapping.
- exception fhops.scenario.FemicHydrologyRegistrationError[source]
Bases:
RuntimeErrorA hydrology bundle cannot be bound to a scenario or written safely.
- exception fhops.scenario.FemicHydrologyScreenError[source]
Bases:
RuntimeErrorA schedule cannot be screened against the hydrology evidence.
- class fhops.scenario.FemicHydrologyValidation(bundle_dir, is_valid, checked_utc, errors, warnings, artifact_count, total_bytes)[source]
Bases:
objectValidation report for a FEMIC hydrology bundle.
- Parameters:
- bundle_dir
Resolved bundle root.
- Type:
Path
- is_valid
Whether every required check passed.
- Type:
- checked_utc
Report timestamp.
- Type:
- artifact_count
Number of artifacts inspected.
- Type:
- total_bytes
Sum of declared artifact sizes.
- Type:
- artifact_count: int
- bundle_dir: Path
- checked_utc: str
- is_valid: bool
- to_dict()[source]
Return the validation report as a JSON-serializable mapping.
- total_bytes: int
- class fhops.scenario.FemicLidarBundle(bundle_dir, manifest_path, bundle_id, bundle_version, source, selection, processing, products, manifest_sha256, total_bytes)[source]
Bases:
objectPinned FEMIC LidarBC bundle metadata.
- Parameters:
- bundle_dir: Path
- bundle_id: str
- bundle_version: str
- manifest_path: Path
- manifest_sha256: str
- total_bytes: int
- exception fhops.scenario.FemicLidarError[source]
Bases:
RuntimeErrorRaised when a FEMIC LidarBC bundle cannot be consumed.
- class fhops.scenario.FemicLidarProduct(product, relative_path, sha256, size_bytes, crs, resolution_m, vertical_datum, licence_id, source_tile_ids, derived, lineage)[source]
Bases:
objectOne pinned product record from a FEMIC bundle manifest.
- Parameters:
- crs: str
- derived: bool
- licence_id: str
- product: str
- relative_path: str
- sha256: str
- size_bytes: int
- vertical_datum: str
- class fhops.scenario.FemicLidarValidation(bundle_dir, is_valid, checked_utc, errors, warnings, product_count, total_bytes)[source]
Bases:
objectValidation report for a FEMIC LidarBC bundle.
- Parameters:
- bundle_dir: Path
- checked_utc: str
- is_valid: bool
- product_count: int
- to_dict()[source]
Return the validation report as a JSON-serializable mapping.
- total_bytes: int
- class fhops.scenario.HydrologyAsset(asset_id, asset_type, watershed_id, road_job_id, vulnerability_class, notes)[source]
Bases:
objectOne explicit road or culvert asset.
- Parameters:
- asset_id
Stable asset identifier.
- Type:
- asset_type
roadorculvert.- Type:
- watershed_id
FEMIC watershed identifier.
- Type:
- road_job_id
Scenario road-construction id for a road asset.
- Type:
str | None
- vulnerability_class
low,moderate,high, orunknown.- Type:
- notes
Human notes.
- Type:
- asset_id: str
- asset_type: str
- notes: str
- to_dict()[source]
Return the asset as a JSON-serializable mapping.
- vulnerability_class: str
- watershed_id: str
- class fhops.scenario.HydrologyAssetMapping(inventory_id, generated_utc, source_id, review_status, reviewer_id, assets, road_job_ids, watershed_ids, warnings, caveats, assets_sha256)[source]
Bases:
objectAssets resolved against a scenario and hydrology mapping.
- Parameters:
- inventory_id
Asset inventory identifier.
- Type:
- generated_utc
Inventory timestamp.
- Type:
- source_id
Asset source.
- Type:
- review_status
Review state.
- Type:
- reviewer_id
Reviewer when reviewed.
- Type:
str | None
- assets
Validated assets.
- Type:
tuple[HydrologyAsset, …]
- assets_sha256
Asset file digest.
- Type:
- assets: tuple[HydrologyAsset, ...]
- assets_sha256: str
- generated_utc: str
- inventory_id: str
- review_status: str
- source_id: str
- to_dict()[source]
Return the mapped inventory as a JSON-serializable mapping.
- class fhops.scenario.HydrologyAssetValidation(path, is_valid, checked_utc, errors, warnings, asset_count, assets_sha256)[source]
Bases:
objectValidation report for an asset table.
- Parameters:
- path
Asset JSON path.
- Type:
Path
- is_valid
Whether every required check passed.
- Type:
- checked_utc
Report timestamp.
- Type:
- asset_count
Parsed asset count.
- Type:
- assets_sha256
File digest.
- Type:
- asset_count: int
- assets_sha256: str
- checked_utc: str
- is_valid: bool
- path: Path
- to_dict()[source]
Return the validation report as a JSON-serializable mapping.
- class fhops.scenario.HydrologyConstraintCheck(check_id, generated_utc, scenario_path, hazard_id, mapping_id, schedule_sha256, events, failed, failure_reasons, coverage, warnings, caveats)[source]
Bases:
objectResult of checking one schedule against hazard windows.
- Parameters:
- check_id
Deterministic check identifier.
- Type:
- generated_utc
Check timestamp.
- Type:
- scenario_path
Scenario YAML.
- Type:
Path
- hazard_id
Hazard table identifier.
- Type:
- mapping_id
Hydrology mapping identifier.
- Type:
- schedule_sha256
Assignments CSV digest.
- Type:
- events
All watch/warning/closure events.
- Type:
tuple[HydrologyConstraintEvent, …]
- failed
Whether the selected policy treats an event as a failure.
- Type:
- check_id: str
- events: tuple[HydrologyConstraintEvent, ...]
- failed: bool
- generated_utc: str
- hazard_id: str
- mapping_id: str
- scenario_path: Path
- schedule_sha256: str
- to_dict()[source]
Return the check result as a JSON-serializable mapping.
- class fhops.scenario.HydrologyConstraintEvent(day, shift_id, machine_id, block_id, watershed_id, severity, asset_ids, reason)[source]
Bases:
objectOne operational activity inside a hazard window.
- Parameters:
- day
One-indexed schedule day.
- Type:
- shift_id
Optional shift label.
- Type:
str | None
- machine_id
Optional machine identifier.
- Type:
str | None
- block_id
Scheduled block identifier.
- Type:
- watershed_id
FEMIC watershed identifier.
- Type:
- severity
watch,warning, orclosure.- Type:
- reason
Human-readable exposure reason.
- Type:
- block_id: str
- day: int
- reason: str
- severity: str
- to_dict()[source]
Return the event as a JSON-serializable mapping.
- watershed_id: str
- class fhops.scenario.HydrologyCrosswalk(path, sha256, rows, metadata)[source]
Bases:
objectLoaded and checksummed explicit crosswalk.
- Parameters:
- path
Crosswalk CSV path.
- Type:
Path
- sha256
SHA-256 of the CSV bytes.
- Type:
- rows
Mapping rows.
- Type:
tuple[HydrologyCrosswalkRow, …]
- metadata
Review metadata.
- Type:
HydrologyCrosswalkMetadata
- metadata: HydrologyCrosswalkMetadata
- path: Path
- rows: tuple[HydrologyCrosswalkRow, ...]
- sha256: str
- class fhops.scenario.HydrologyCrosswalkMetadata(crosswalk_id, created_utc, review_status, reviewed_by, method, source, caveats)[source]
Bases:
objectReview metadata for an explicit hydrology crosswalk.
- Parameters:
- crosswalk_id
Stable crosswalk identifier.
- Type:
- created_utc
Creation timestamp.
- Type:
- review_status
not_reviewed,review_pending, orreviewed.- Type:
- reviewed_by
Reviewer identifier when reviewed.
- Type:
str | None
- method
Mapping method or process.
- Type:
- source
Source document or process.
- Type:
- created_utc: str
- crosswalk_id: str
- method: str
- review_status: str
- source: str
- class fhops.scenario.HydrologyCrosswalkRow(block_id, landing_id, road_id, unit_id, watershed_id, receptor_id, notes)[source]
Bases:
objectOne explicit FHOPS support to FEMIC unit/watershed mapping.
- Parameters:
- block_id
FHOPS block identifier, when the row is block-level.
- Type:
str | None
- landing_id
FHOPS landing identifier, when the row is landing-level.
- Type:
str | None
- road_id
FHOPS road-construction identifier, when the row is road-level.
- Type:
str | None
- unit_id
FEMIC disturbance-ledger unit identifier.
- Type:
- watershed_id
FEMIC watershed identifier.
- Type:
- receptor_id
Optional FEMIC flood-risk receptor identifier.
- Type:
str | None
- notes
Human review notes.
- Type:
- notes: str
- support_key()[source]
Return the support unit and identifier.
- unit_id: str
- watershed_id: str
- class fhops.scenario.HydrologyExposure(path, sha256, exposure_id, generated_utc, source_id, review_status, reviewer_id, schedule_sha256, watershed_ids, periods, rows, caveats)[source]
Bases:
objectLoaded and checksummed schedule-bound exposure table.
- Parameters:
- path
Exposure JSON path.
- Type:
Path
- sha256
SHA-256 digest of the file bytes.
- Type:
- exposure_id
Stable exposure identifier.
- Type:
- generated_utc
Producer generation timestamp.
- Type:
- source_id
Source or process that produced the table.
- Type:
- review_status
not_reviewed,review_pending, orreviewed.- Type:
- reviewer_id
Reviewer identifier when reviewed.
- Type:
str | None
- schedule_sha256
SHA-256 of the bound FHOPS schedule file.
- Type:
- rows
Complete watershed/period grid.
- Type:
tuple[HydrologyExposureRow, …]
- exposure_id: str
- features_for(watershed_id, period, *, watershed_area_ha=None)[source]
Return raw and optional density features for one watershed/period.
- Parameters:
- Returns:
Raw exposure features and, when an area is supplied, density features.
- Return type:
- Raises:
FemicHydrologyExposureError – If the row is missing or the area is not positive when supplied.
- generated_utc: str
- path: Path
- review_status: str
- row(watershed_id, period)[source]
Return one watershed/period row.
- rows: tuple[HydrologyExposureRow, ...]
- schedule_sha256: str
- sha256: str
- source_id: str
- to_dict()[source]
Return the exposure table as a JSON-serializable mapping.
- class fhops.scenario.HydrologyExposureRow(watershed_id, period, road_built_km, road_used_km, road_decommissioned_km, culvert_count, culvert_exposed_count)[source]
Bases:
objectOne watershed/period exposure record.
- Parameters:
- watershed_id
FEMIC watershed identifier.
- Type:
- period
Non-negative screening period.
- Type:
- road_built_km
Road length built in the period.
- Type:
- road_used_km
Road length used in the period.
- Type:
- road_decommissioned_km
Road length decommissioned in the period.
- Type:
- culvert_count
Culvert count in the watershed.
- Type:
- culvert_exposed_count
Culverts exposed to the flood path.
- Type:
- culvert_count: int
- culvert_exposed_count: int
- period: int
- road_built_km: float
- road_decommissioned_km: float
- road_used_km: float
- to_dict()[source]
Return the row in the shared exposure-table shape.
- watershed_id: str
- class fhops.scenario.HydrologyExposureValidation(path, is_valid, checked_utc, errors, warnings, row_count, watershed_count, period_count, exposure_sha256, schedule_sha256)[source]
Bases:
objectValidation report for a road/culvert exposure table.
- Parameters:
- path
Exposure path.
- Type:
Path
- is_valid
Whether every required check passed.
- Type:
- checked_utc
Report timestamp.
- Type:
- row_count
Number of parsed rows.
- Type:
- watershed_count
Number of declared watersheds.
- Type:
- period_count
Number of declared periods.
- Type:
- exposure_sha256
File digest.
- Type:
- schedule_sha256
Declared schedule digest.
- Type:
str | None
- checked_utc: str
- exposure_sha256: str
- is_valid: bool
- path: Path
- period_count: int
- row_count: int
- to_dict()[source]
Return the validation report as a JSON-serializable mapping.
- watershed_count: int
- class fhops.scenario.HydrologyHazard(path, sha256, hazard_id, generated_utc, source_id, review_status, reviewer_id, windows, caveats)[source]
Bases:
objectLoaded and checksummed hazard-window table.
- Parameters:
- path
Hazard JSON path.
- Type:
Path
- sha256
SHA-256 digest of the file bytes.
- Type:
- hazard_id
Stable hazard identifier.
- Type:
- generated_utc
Producer generation timestamp.
- Type:
- source_id
Source or process that produced the windows.
- Type:
- review_status
not_reviewed,review_pending, orreviewed.- Type:
- reviewer_id
Reviewer identifier when reviewed.
- Type:
str | None
- windows
Validated windows.
- Type:
tuple[HydrologyHazardWindow, …]
- generated_utc: str
- hazard_id: str
- path: Path
- review_status: str
- severity_for(watershed_id, day)[source]
Return the highest severity covering a watershed/day.
- sha256: str
- source_id: str
- to_dict()[source]
Return the hazard table as a JSON-serializable mapping.
- windows: tuple[HydrologyHazardWindow, ...]
- class fhops.scenario.HydrologyHazardValidation(path, is_valid, checked_utc, errors, warnings, window_count, watershed_count, hazard_sha256)[source]
Bases:
objectValidation report for a hazard-window table.
- Parameters:
- path
Hazard path.
- Type:
Path
- is_valid
Whether every required check passed.
- Type:
- checked_utc
Report timestamp.
- Type:
- window_count
Number of parsed windows.
- Type:
- watershed_count
Number of distinct watersheds.
- Type:
- hazard_sha256
File digest.
- Type:
- checked_utc: str
- hazard_sha256: str
- is_valid: bool
- path: Path
- to_dict()[source]
Return the validation report as a JSON-serializable mapping.
- watershed_count: int
- window_count: int
- class fhops.scenario.HydrologyHazardWindow(watershed_id, start_day, end_day, severity, return_period_years, source_id, notes)[source]
Bases:
objectOne watershed/day hazard window.
- Parameters:
- watershed_id
FEMIC watershed identifier.
- Type:
- start_day
Inclusive first day (1-indexed).
- Type:
- end_day
Inclusive last day.
- Type:
- severity
watch,warning, orclosure.- Type:
- return_period_years
Optional return period associated with the hazard.
- Type:
float | None
- source_id
Optional source identifier for this window.
- Type:
str | None
- notes
Human notes.
- Type:
- covers(day)[source]
Return whether the window covers a day.
- end_day: int
- notes: str
- severity: str
- start_day: int
- to_dict()[source]
Return the window as a JSON-serializable mapping.
- watershed_id: str
- class fhops.scenario.HydrologyMapping(mapping_id, generated_utc, scenario_path, bundle_id, bundle_version, manifest_sha256, crosswalk_id, crosswalk_sha256, review_status, rows, coverage, warnings, caveats)[source]
Bases:
objectResolved scenario-to-FEMIC mapping and coverage.
- Parameters:
- mapping_id
Deterministic mapping identifier.
- Type:
- generated_utc
Mapping timestamp.
- Type:
- scenario_path
Scenario YAML.
- Type:
Path
- bundle_id
FEMIC bundle identifier.
- Type:
- bundle_version
FEMIC bundle version.
- Type:
- manifest_sha256
FEMIC manifest digest.
- Type:
- crosswalk_id
Crosswalk identifier.
- Type:
- crosswalk_sha256
Crosswalk CSV digest.
- Type:
- review_status
Crosswalk review state.
- Type:
- rows
Resolved rows.
- Type:
tuple[HydrologyMappingRow, …]
- bundle_id: str
- bundle_version: str
- crosswalk_id: str
- crosswalk_sha256: str
- generated_utc: str
- manifest_sha256: str
- mapping_id: str
- review_status: str
- rows: tuple[HydrologyMappingRow, ...]
- scenario_path: Path
- to_dict()[source]
Return the mapping manifest as a JSON-serializable mapping.
- class fhops.scenario.HydrologyMappingRow(support_unit, support_id, unit_id, watershed_id, receptor_id, notes)[source]
Bases:
objectOne resolved support mapping.
- Parameters:
- support_unit
block,landing, orroad.- Type:
- support_id
FHOPS support identifier.
- Type:
- unit_id
FEMIC unit identifier.
- Type:
- watershed_id
FEMIC watershed identifier.
- Type:
- receptor_id
Optional FEMIC receptor identifier.
- Type:
str | None
- notes
Human review notes.
- Type:
- notes: str
- support_id: str
- support_unit: str
- to_dict()[source]
Return the mapping row as a JSON-serializable mapping.
- unit_id: str
- watershed_id: str
- fhops.scenario.HydrologyRegistrationResult
alias of
RegistrationResult
- class fhops.scenario.HydrologyScreen(screen_id, generated_utc, scenario_path, bundle_id, bundle_version, manifest_sha256, mapping_id, exposure_id, exposure_sha256, schedule_sha256, readiness_status, policy, rows, refusals, coverage, warnings, caveats)[source]
Bases:
objectOne-schedule hydrology screening result.
- Parameters:
- screen_id
Deterministic screening identifier.
- Type:
- generated_utc
Screening timestamp.
- Type:
- scenario_path
Scenario YAML.
- Type:
Path
- bundle_id
FEMIC bundle identifier.
- Type:
- bundle_version
FEMIC bundle version.
- Type:
- manifest_sha256
FEMIC manifest digest.
- Type:
- mapping_id
Crosswalk mapping identifier.
- Type:
- exposure_id
Exposure table identifier.
- Type:
- schedule_sha256
Assignments CSV digest.
- Type:
- readiness_status
Bundle readiness state at screening time.
- Type:
- policy
Readiness policy applied.
- Type:
- bundle_id: str
- bundle_version: str
- exposure_id: str
- exposure_sha256: str
- generated_utc: str
- manifest_sha256: str
- mapping_id: str
- policy: str
- readiness_status: str
- scenario_path: Path
- schedule_sha256: str
- screen_id: str
- to_dict()[source]
Return the screening manifest as a JSON-serializable mapping.
- class fhops.scenario.Landing(*, id, daily_capacity=2)[source]
Bases:
BaseModelLanding metadata including its machine capacity.
- id
Landing identifier referenced by blocks and mobilisation logic.
- Type:
- daily_capacity
Maximum number of machines that can work the landing’s blocks concurrently, counted per shift slot
(day, shift_id)(on single-shift scenarios: per day). The name is kept for compatibility. The heuristics and the operational MILP apply the same rule: whenObjectiveWeights.landing_surplusis 0 (default) the capacity is hard (heuristics: the repair keeps landings within capacity and any remaining extra machine costs the hard violation penalty, at least 1000, #140; MILP: constraint), otherwise each slot’sk-th machine beyond capacity costsk × landing_surplus. Before FHOPS 1.0.1 the operational MILP counted machine-shifts per day with a slack that was free at weight 0 (#125).- Type:
- daily_capacity: int
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.scenario.LidarPolygonSummary(polygon_id, product, valid_pixels, statistics)[source]
Bases:
objectOne polygon-level support summary from a FEMIC product.
- polygon_id: str
- product: str
- valid_pixels: int
- class fhops.scenario.LidarProductPin(*, product, relative_path, sha256, crs, resolution_m=None, vertical_datum, source_tile_ids=<factory>)[source]
Bases:
BaseModelOne product file recorded in a scenario bundle pin.
- Parameters:
- crs: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- product: str
- relative_path: str
- sha256: str
- vertical_datum: str
- class fhops.scenario.LidarProductRef(*, bundle_id, bundle_version, manifest_sha256=None, licence_id=None, manifest_path=None, crs=None, aoi_bbox=None, selection=None, products=None)[source]
Bases:
BaseModelA scenario-level pin to one FEMIC bundle version.
The fields mirror
fhops.scenario.lidar.bundle_pin();manifest_sha256is the digest of the bundle manifest, not of an individual raster.- Parameters:
- bundle_id: str
- bundle_version: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.scenario.LidarSupport(*, support_unit, support_id, bundle_id, bundle_version, product, aggregation='area_weighted_mean', native_resolution_m, support_area_m2=None, valid_pixels=None, slope_percent=None, aspect_degrees=None, roughness_m=None, canopy_cover_percent=None, canopy_height_m=None, vertical_structure_index=None, vertical_structure_class=None, provenance=None)[source]
Bases:
BaseModelAggregated LidarBC evidence for one block, landing, or road.
The values are the output of a FEMIC raster summary (for example
fhops.scenario.lidar.summarize_femic_lidar_for_polygons()). Native resolution, product name, aggregation, valid-pixel count, and support footprint preserve how the summary was made.- Parameters:
support_unit (Literal['block', 'landing', 'road'])
aggregation (Literal['mean', 'area_weighted_mean', 'median', 'minimum', 'maximum', 'p95'])
aspect_degrees (Annotated[float | None, Ge(ge=0), Le(le=360)])
canopy_cover_percent (Annotated[float | None, Ge(ge=0), Le(le=100)])
vertical_structure_index (Annotated[float | None, Ge(ge=0)])
vertical_structure_class (str | None)
provenance (str | None)
- aggregation: AggregationMethod
- bundle_id: str
- bundle_version: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- native_resolution_m: float
- product: str
- support_id: str
- support_unit: LidarSupportUnit
- class fhops.scenario.Machine(*, id, crew=None, daily_hours=24.0, operating_cost=0.0, role=None, repair_usage_hours=None)[source]
Bases:
BaseModelMachine definition (identifier, crew, availability, and costing metadata).
- Parameters:
- id
Unique machine identifier referenced throughout calendars/assignments.
- Type:
- crew
Optional crew label for reporting/telemetry grouping.
- Type:
str | None
- daily_hours
Maximum hours the machine can operate per day (defaults to 24).
- Type:
- operating_cost
Cost per scheduled machine hour (SMH) expressed in scenario currency units.
- Type:
- role
Optional machine role string (normalised via
normalize_machine_role) used by harvest systems and rental-rate lookups.- Type:
str | None
- repair_usage_hours
Optional cumulative repair hours that influences the rental-rate defaults.
- Type:
int | None
- daily_hours: float
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- operating_cost: float
- class fhops.scenario.MappingResult(scenario_path, bundle_id, bundle_version, rows, warnings, written)[source]
Bases:
objectOutcome of mapping FEMIC products into a scenario.
- Parameters:
- bundle_id: str
- bundle_version: str
- rows: tuple[LidarSupport, ...]
- scenario_path: Path
- written: bool
- class fhops.scenario.OperabilityRules(ground_max_slope_percent=35.0, ground_max_roughness_m=0.35, cable_max_slope_percent=60.0, cable_max_roughness_m=0.55, fallback_slope_percent=20.0, fallback_roughness_m=0.2)[source]
Bases:
objectAssumption thresholds used by the advisory classifier.
These are planning assumptions, not field-validated rules. They are intentionally conservative and visible in every report.
- Parameters:
- cable_max_roughness_m: float = 0.55
- cable_max_slope_percent: float = 60.0
- fallback_roughness_m: float = 0.2
- fallback_slope_percent: float = 20.0
- ground_max_roughness_m: float = 0.35
- ground_max_slope_percent: float = 35.0
- class fhops.scenario.Problem(**data)[source]
Bases:
BaseModelRuntime representation of a scenario used by solvers.
Problemwraps a validatedScenarioand expands it into concretedaysandshiftsso optimisation code can iterate over deterministic index sets without repeatedly querying the Scenario.Problem.from_scenariois the canonical constructor; it injects the default harvest-system registry (when necessary) and synthesises single-shift calendars for legacy day-indexed inputs.- Parameters:
data (Any)
- scenario
Back-reference to the source
Scenario.- Type:
Scenario
- days
List of integer day indices derived from
scenario.num_days.- Type:
list[Day]
- shifts
List of
ShiftInstanceentries representing every (day, shift_id) slot the solver should consider.- Type:
list[ShiftInstance]
Notes
Any code that builds Pyomo models or heuristic plans should accept a
Problemrather than the rawScenarioto avoid recomputing shift/day metadata.- days: list[Day]
- classmethod from_scenario(scenario)[source]
- Parameters:
scenario (Scenario)
- Return type:
Problem
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- scenario: Scenario
- shifts: list[ShiftInstance]
- class fhops.scenario.ProductionRate(*, machine_id, block_id, rate)[source]
Bases:
BaseModelProduction rate (m³ per assignment) for a machine/block pair.
- machine_id
Machine identifier (must exist in
Scenario.machines).- Type:
- block_id
Block identifier (must exist in
Scenario.blocks).- Type:
- rate
Volume (m³) produced per full shift assignment (per day in single-shift scenarios), in the same units as
Block.work_required. Must be non-negative.- Type:
- block_id: str
- machine_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- rate: float
- class fhops.scenario.RegistrationResult(scenario_path, bundle_id, bundle_version, manifest_sha256, manifest_path, aoi_bbox, warnings, written, pin)[source]
Bases:
objectOutcome of a bundle registration.
- Parameters:
- bundle_id: str
- bundle_version: str
- manifest_path: str
- manifest_sha256: str
- pin: LidarProductRef
- scenario_path: Path
- written: bool
- class fhops.scenario.Scenario(**data)[source]
Bases:
BaseModelTop-level container for the FHOPS data contract.
The model mirrors the CSV/YAML inputs documented in
docs/howto/data_contract.rstand is the object returned byfhops.scenario.io.load_scenario(). Only validated, horizon-bounded data reaches this point, which means downstream solvers (MIP + heuristics) can rely on:every block referencing a known landing/harvest system,
machine calendars/shift calendars never exceeding
num_days,mobilisation tables referencing existing blocks/machines, and
optional extras (crew assignments, road construction, GeoJSON metadata) being present only when fully specified.
- Parameters:
data (Any)
- name
Human-readable scenario label surfaced in CLI/Evaluation outputs.
- Type:
- num_days
Planning horizon length (integer number of days).
- Type:
- schema_version
Version of the input schema; used to guard loader compatibility.
- Type:
- start_date
Optional ISO date string used for timestamped exports.
- Type:
date | None
- blocks / machines / landings
Validated lists of the corresponding Pydantic models.
- calendar / shift_calendar
Availability tables.
shift_calendarmay beNonefor day-level scenarios.
- production_rates
Machine/block productivity table measured in m³ per shift assignment.
- Type:
list[ProductionRate]
- timeline
Optional
TimelineConfigdescribing shifts, blackout windows, etc.- Type:
TimelineConfig | None
- mobilisation
Optional
MobilisationConfigdescribing distances and per-machine parameters.- Type:
MobilisationConfig | None
- harvest_systems
Optional registry mapping harvest-system IDs to
HarvestSystemdefinitions. Entries are overlaid onfhops.scheduling.systems.default_system_registry(); everyBlock.harvest_system_idmust name a system of that combined registry (unknown ids are rejected since 1.0.1; 1.0.0 accepted them).- Type:
dict[str, HarvestSystem] | None
- geo
Optional
GeoMetadatawith GeoJSON lookups.- Type:
GeoMetadata | None
- crew_assignments
Optional list mapping crew IDs to machines for reporting/telemetry.
- Type:
list[CrewAssignment] | None
- locked_assignments
Optional list of
ScheduleLockentries that pin machines to blocks on specific days (or specific day/shift slots whenScheduleLock.shift_idis set). Enforced by the operational MILP, the legacy MIP builder, and the heuristics. Validation rejects unknown machine/block ids, days outside the horizon or the block’s[earliest_start, latest_finish]window, blackout days, unknown shift labels (seeshift_labels()), duplicate or mixed day/shift locks, and machines whose role is not a role of the block’s explicit harvest system. Files usingshift_idrequirefhops>=1.0.1(1.0.0 reads such entries as whole-day locks).- Type:
list[ScheduleLock] | None
- objective_weights
Optional
ObjectiveWeightsoverriding default solver weights.- Type:
ObjectiveWeights | None
- road_construction
Optional list of
RoadConstructionentries used by telemetry/costing exports.- Type:
list[RoadConstruction] | None
- lidar_products
Optional pins to FEMIC-compiled LidarBC bundle versions. A pin records the bundle id, version, manifest checksum, licence, and optional manifest path. FHOPS consumes the FEMIC bundle; it never discovers or materializes source tiles itself.
- Type:
list[LidarProductRef] | None
- hydrology_products
Optional pins to FEMIC hydrology response bundles. A pin records the bundle id, version, manifest checksum, domain/ledger/response-surface ids, artifact checksums, readiness state, and caveats. FHOPS consumes the bundle; it does not copy or re-run FEMIC.
- Type:
list[HydrologyBundleRef] | None
- tree_inventory
Optional reference to a validated individual-tree inventory. The reference records the inventory and metadata checksums, CRS, review state, producer, method, tree count, block memberships, and caveats. The inventory is referenced, not copied.
- Type:
TreeInventoryRef | None
- lidar_support
Optional aggregated LidarBC evidence for blocks, landings, and roads. Each row states the FEMIC product, native resolution, aggregation, support footprint, and provenance; existing scenarios without LiDAR fields remain valid.
- Type:
list[LidarSupport] | None
- hauling
Optional
HaulingConfigwith log sorts, contractors, truck types and trucks, depots, sort yards, haul arcs, loader bindings, and the driver policy.None(default) means the scenario has no hauling layer. Files using this field require schema1.1.0(the loader still accepts1.0.0).- Type:
HaulingConfig | None
- initial_state
Optional
ScenarioInitialStatecarrying staged inter-role inventory, remaining per-role output, head-start shift counts, and each machine’s last block at the start of the horizon.None(default) starts from the 1.0.0 initial conditions (empty staged inventory, fullwork_requiredper role, no prior machine position); it does not undo other 1.0.1 behaviour changes. Validation requires known block/machine ids, non-negative values, role keys drawn from the block’s harvest-system roles,role_remainingvalues not above the block’swork_required, andlast_block_idvalues that are scenario blocks. Files using this field requirefhops>=1.0.1(1.0.0 ignores it).- Type:
ScenarioInitialState | None
Notes
The helper methods (
machine_ids(),window_for(), etc.) are convenience routines for the solver/evaluation layers and are intentionally lightweight so they can be used in tight loops.- block_ids()[source]
Return the list of block identifiers defined in the scenario.
- blocks: list[Block]
- calendar: list[CalendarEntry]
- geo: GeoMetadata | None
- harvest_systems: dict[str, HarvestSystem] | None
- hauling: HaulingConfig | None
- initial_state: ScenarioInitialState | None
- landing_ids()[source]
Return the list of landing identifiers defined in the scenario.
- landings: list[Landing]
- machine_ids()[source]
Return the list of machine identifiers defined in the scenario.
- machines: list[Machine]
- mobilisation: MobilisationConfig | None
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- name: str
- num_days: int
- objective_weights: ObjectiveWeights | None
- production_rates: list[ProductionRate]
- schema_version: str
- shift_labels()[source]
Return the shift labels of the scenario’s shift grid.
These are the labels accepted for
ScheduleLock.shift_idand used for theshift_idcolumn of solver and playback outputs. The source mirrorsProblem.from_scenario().- Returns:
shift_calendarlabels when a shift calendar is present (whether or not each entry is available), otherwise the timeline shift names, otherwise{"S1"}(the synthetic single shift used for day-indexed scenarios).- Return type:
Examples
>>> from fhops.scenario.io import load_scenario >>> load_scenario("examples/tiny7/scenario.yaml").shift_labels() {'S1'}
- start_date: date | None
- timeline: TimelineConfig | None
- tree_inventory: TreeInventoryRef | None
- fhops.scenario.assess_block_operability(scenario, block_id, *, rules=None)[source]
Classify one block from LiDAR support, block fields, and explicit fallbacks.
- Parameters:
scenario (Scenario)
block_id (str)
rules (OperabilityRules | None)
- Return type:
BlockOperability
- fhops.scenario.assess_femic_hydrology_readiness(bundle)[source]
Read optional validation-report and model-card evidence.
- Parameters:
bundle (FemicHydrologyBundle) – Validated bundle.
- Returns:
Review, tail-check, fit, and benchmark evidence. Missing evidence is reported explicitly and never treated as approval.
- Return type:
FemicHydrologyReadiness
- Raises:
FemicHydrologyError – If a packaged evidence artifact is malformed.
- fhops.scenario.assess_scenario_operability(scenario, *, rules=None)[source]
Assess every block in a scenario.
- Parameters:
scenario (Scenario)
rules (OperabilityRules | None)
- Return type:
tuple[BlockOperability, …]
- fhops.scenario.bundle_pin(bundle)[source]
Return the stable pin fields FHOPS records in its scenario provenance.
- fhops.scenario.check_fhops_hydrology_constraints(scenario_path, mapping_path, schedule_path, hazard_path, *, assets_path=None, fail_on_closure=True, fail_on_warning=False, generated_utc=None)[source]
Check one schedule for operational hazard-window exposure.
- Parameters:
scenario_path (str | Path) – FHOPS scenario YAML.
mapping_path (str | Path) – Hydrology mapping directory or manifest.
schedule_path (str | Path) – FHOPS assignments CSV.
hazard_path (str | Path) – Operational hazard JSON.
assets_path (str | Path | None, optional) – Explicit road/culvert asset JSON.
fail_on_closure (bool, optional) – Treat closure events as failures. Defaults to True.
fail_on_warning (bool, optional) – Treat warning events as failures. Defaults to False.
generated_utc (str | None, optional) – Check timestamp.
- Returns:
Events, failure state, coverage, warnings, and caveats.
- Return type:
HydrologyConstraintCheck
- Raises:
FemicHydrologyConstraintError – If evidence is missing or a scheduled block has no mapping.
- fhops.scenario.enforce_hydrology_readiness(readiness, *, policy='require_reviewed')[source]
Enforce a readiness policy for one-schedule screening.
- Parameters:
readiness (FemicHydrologyReadiness) – Bundle readiness report.
policy (str, optional) –
allow_provisional,require_reviewed, orrequire_ready.
- Raises:
FemicHydrologyScreenError – If the policy is unknown or the evidence is insufficient.
- Return type:
None
- fhops.scenario.hydrology_bundle_pin(bundle)
Return a stable downstream pin for a validated bundle.
- fhops.scenario.load_femic_hydrology_bundle(bundle_dir, *, expected_bundle_id=None, expected_bundle_version=None, verify_checksums=True)[source]
Load and validate a FEMIC hydrology bundle.
- Parameters:
bundle_dir (Path) – Bundle directory or manifest path.
expected_bundle_id (str | None, optional) – Require this bundle identifier.
expected_bundle_version (str | None, optional) – Require this bundle version.
verify_checksums (bool, optional) – Verify every declared size and SHA-256. Defaults to True.
- Returns:
Validated bundle with artifact pins and summaries.
- Return type:
FemicHydrologyBundle
- Raises:
FemicHydrologyError – If the bundle is invalid.
- fhops.scenario.load_femic_lidar_bundle(bundle_dir, *, expected_bundle_id=None, expected_bundle_version=None, verify_checksums=True)[source]
Load and validate a FEMIC LidarBC bundle for consumption.
- fhops.scenario.load_hydrology_assets(path)[source]
Load and validate an asset table.
- Parameters:
path (Path) – Asset JSON path.
- Returns:
Loaded assets and their validation report.
- Return type:
tuple[HydrologyAssetMapping, HydrologyAssetValidation]
- Raises:
FemicHydrologyAssetError – If the table is invalid.
- fhops.scenario.load_hydrology_crosswalk(path, *, metadata_path=None)[source]
Load and validate an explicit hydrology crosswalk.
- Parameters:
path (Path) – Crosswalk CSV with one support key per row.
metadata_path (Path | None, optional) – Crosswalk review metadata JSON.
- Returns:
Checksummed rows and review metadata.
- Return type:
HydrologyCrosswalk
- Raises:
FemicHydrologyMappingError – If the CSV or metadata is malformed.
- fhops.scenario.load_hydrology_exposure(path, *, schedule_path=None)[source]
Load and validate a shared exposure table.
- Parameters:
path (Path) – Exposure JSON path.
schedule_path (Path | None, optional) – FHOPS assignments CSV to compare with
schedule_sha256.
- Returns:
Validated exposure table.
- Return type:
HydrologyExposure
- Raises:
FemicHydrologyExposureError – If the table is invalid or bound to a different schedule.
- fhops.scenario.load_hydrology_hazard(path)[source]
Load and validate a hazard-window table.
- Parameters:
path (Path) – Hazard JSON path.
- Returns:
Validated hazard windows.
- Return type:
HydrologyHazard
- Raises:
FemicHydrologyHazardError – If the table is invalid.
- fhops.scenario.load_hydrology_mapping(path)[source]
Load a mapping manifest written by
write_hydrology_mapping().- Parameters:
path (Path) – Mapping directory or
hydrology_mapping.json.- Returns:
Loaded mapping with rows, coverage, and review state.
- Return type:
HydrologyMapping
- Raises:
FemicHydrologyMappingError – If the manifest is missing, malformed, or inconsistent.
- fhops.scenario.load_scenario(yaml_path)[source]
Load a Scenario from the YAML metadata + CSV bundle.
- Parameters:
yaml_path (str | Path) – Path to the
scenario.yamlfile that references the component CSVs.- Returns:
Fully validated Pydantic model ready to be converted into a
fhops.scenario.contract.Problem.- Return type:
Scenario
Notes
The loader performs several quality-of-life tasks that callers usually forget:
normalises optional string columns (e.g.,
harvest_system_idblanks →None),back-fills mobilisation distance matrices from
*_block_distances.csvwhenever present,accepts inline YAML overrides for optional tables (road construction, shift calendar, crew map),
re-roots GeoJSON paths relative to the scenario directory,
ensures every optional extra (timeline, mobilisation config, objective weights) is copied into the resulting Scenario instance,
parses an optional inline
initial_statemapping (seefhops.scenario.contract.ScenarioInitialState) and validates it against the loaded blocks, machines, and harvest systems, andre-validates the assembled scenario once all optional sections are attached, so YAML
locked_assignments(machine/block/day/shift_idchecks, duplicates, day+shift mixes, blackout clashes), mobilisation, crew assignments, andinitial_stateget the samefhops.scenario.contract.Scenariocross-validation as Python-constructed scenarios. Core tables (and theharvest_systemssection, which blockharvest_system_idvalues are checked against together with the default registry) are validated first, so their errors are reported before optional-section errors.
Examples
An inline
initial_statesection inscenario.yaml:initial_state: blocks: - block_id: B01 role_remaining: {feller_buncher: 0.0} staged_inventory: {feller_buncher: 150.0} machines: - machine_id: H3 last_block_id: B02
- Raises:
ValueError – (Pydantic
ValidationError) when any table, theharvest_systems,locked_assignmentsorinitial_statesection, or the scenario cross-validation fails (including a blockharvest_system_idthat is neither inharvest_systemsnor infhops.scheduling.systems.default_system_registry()).- Parameters:
- Return type:
Scenario
- fhops.scenario.map_femic_hydrology_to_scenario(scenario_path, bundle_dir, crosswalk_path, *, metadata_path=None, allow_partial=False, generated_utc=None)[source]
Map explicit support rows to FEMIC watersheds and receptors.
- Parameters:
scenario_path (str | Path) – FHOPS scenario YAML with a registered hydrology pin.
bundle_dir (str | Path) – FEMIC hydrology bundle directory or manifest.
crosswalk_path (str | Path) – Explicit crosswalk CSV.
metadata_path (str | Path | None, optional) – Crosswalk review metadata JSON.
allow_partial (bool, optional) – Record unmapped supports instead of failing. Defaults to False.
generated_utc (str | None, optional) – Mapping timestamp.
- Returns:
Resolved rows, coverage, review state, warnings, and caveats.
- Return type:
HydrologyMapping
- Raises:
FemicHydrologyMappingError – If the bundle is not pinned, a support or FEMIC ID is unknown, a mapping conflicts, or coverage is incomplete without
allow_partial.
- fhops.scenario.map_femic_lidar_to_scenario(scenario_path, bundle_dir, *, products=None, statistic='mean', include_blocks=True, include_landings=True, roads_geojson=None, road_id_field='road_id', dry_run=False)[source]
Summarize FEMIC products over scenario support and write
lidar_supportrows.
- fhops.scenario.map_hydrology_assets(scenario_path, mapping_path, assets_path)[source]
Map explicit assets to scenario road jobs and FEMIC watersheds.
- Parameters:
- Returns:
Resolved assets with coverage and caveats.
- Return type:
HydrologyAssetMapping
- Raises:
FemicHydrologyAssetError – If a road job or watershed is unknown or the mapping cannot load.
- fhops.scenario.read_csv(path)[source]
Load a CSV file using pandas with UTF-8 defaults.
- Parameters:
path (Path)
- Return type:
DataFrame
- fhops.scenario.read_femic_hydrology_domain(bundle)[source]
Return the pinned FEMIC domain artifact.
- fhops.scenario.read_femic_hydrology_flood_risk(bundle)[source]
Return the pinned FEMIC flood-risk artifact.
- fhops.scenario.read_femic_hydrology_ledger(bundle)[source]
Return the pinned FEMIC disturbance-ledger artifact.
- fhops.scenario.read_femic_hydrology_response_surface(bundle)[source]
Return the pinned FEMIC response-surface artifact.
- fhops.scenario.register_femic_hydrology_bundle(scenario_path, bundle_dir, *, expected_bundle_id=None, expected_bundle_version=None, verify_checksums=True, dry_run=False)[source]
Validate a hydrology bundle and pin it in a scenario.
- Parameters:
scenario_path (str | Path) – Scenario YAML to update.
bundle_dir (str | Path) – FEMIC hydrology bundle directory or manifest path.
expected_bundle_id (str | None, optional) – Require this bundle identifier.
expected_bundle_version (str | None, optional) – Require this bundle version.
verify_checksums (bool, optional) – Verify artifact sizes and SHA-256 digests. Defaults to True.
dry_run (bool, optional) – Validate and return the pin without writing the scenario.
- Returns:
Stable pin, readiness state, warnings, and whether the scenario changed.
- Return type:
RegistrationResult
- Raises:
FemicHydrologyRegistrationError – If the bundle or scenario cannot be loaded, the existing pin conflicts, or the updated scenario does not reload.
- fhops.scenario.register_femic_lidar_bundle(scenario_path, bundle_dir, *, expected_bundle_id=None, expected_bundle_version=None, verify_checksums=True, dry_run=False)[source]
Validate a FEMIC bundle and pin it in a scenario’s
lidar_productssection.
- fhops.scenario.schedule_sha256(path)[source]
Return the SHA-256 digest of an assignments CSV.
- Parameters:
path (Path) – FHOPS assignments CSV.
- Returns:
Lowercase SHA-256 hex digest.
- Return type:
- Raises:
FemicHydrologyExposureError – If the file cannot be read.
- fhops.scenario.screen_fhops_hydrology(scenario_path, bundle_dir, mapping_path, schedule_path, exposure_path, *, policy='require_reviewed', scenario_tolerance=0.05, allow_refused=False, generated_utc=None)[source]
Screen one FHOPS assignments schedule against a hydrology bundle.
- Parameters:
scenario_path (str | Path) – FHOPS scenario YAML with a hydrology pin and block geometry.
bundle_dir (str | Path) – FEMIC hydrology bundle directory or manifest.
mapping_path (str | Path) – Mapping directory or manifest from
scenario hydrology map.schedule_path (str | Path) – FHOPS assignments CSV with
block_idandday.exposure_path (str | Path) – Schedule-bound exposure JSON.
policy (str, optional) – Readiness policy. Defaults to
require_reviewed.scenario_tolerance (float, optional) – Maximum feature difference for response-scenario selection.
allow_refused (bool, optional) – Return inside-domain rows plus refusals instead of failing.
generated_utc (str | None, optional) – Screening timestamp.
- Returns:
Screening rows, refusals, coverage, warnings, and caveats.
- Return type:
HydrologyScreen
- Raises:
FemicHydrologyScreenError – If evidence is missing, the policy fails, the schedule is invalid, or a refusal occurs without
allow_refused.
- fhops.scenario.summarize_femic_lidar_for_polygons(bundle, polygons_path, *, product='slope', id_field=None, statistics=('mean', 'p95'))[source]
Summarize one bundle raster over polygon support.
Geometry and raster CRS must already match. No reprojection or resampling is performed.
- fhops.scenario.validate_femic_hydrology_bundle(bundle_dir, *, expected_bundle_id=None, expected_bundle_version=None, verify_checksums=True)[source]
Validate a FEMIC hydrology response bundle without raising.
- Parameters:
bundle_dir (Path) – Bundle directory or manifest path.
expected_bundle_id (str | None, optional) – Require this bundle identifier.
expected_bundle_version (str | None, optional) – Require this bundle version.
verify_checksums (bool, optional) – Verify every declared size and SHA-256. Defaults to True.
- Returns:
Validation report with specific errors and warnings.
- Return type:
FemicHydrologyValidation
- fhops.scenario.validate_femic_lidar_bundle(bundle_dir, *, verify_checksums=True)[source]
Validate a FEMIC LidarBC bundle without raising on contract errors.
- fhops.scenario.validate_hydrology_assets(path)[source]
Validate an asset table without raising.
- Parameters:
path (Path) – Asset JSON path.
- Returns:
Validation report with specific errors and warnings.
- Return type:
HydrologyAssetValidation
- fhops.scenario.validate_hydrology_exposure(path, *, schedule_path=None)[source]
Validate a shared exposure table without raising.
- Parameters:
path (Path) – Exposure JSON path.
schedule_path (Path | None, optional) – FHOPS assignments CSV to compare with the declared schedule digest.
- Returns:
Validation report with specific errors and warnings.
- Return type:
HydrologyExposureValidation
- fhops.scenario.validate_hydrology_hazard(path)[source]
Validate a hazard-window table without raising.
- Parameters:
path (Path) – Hazard JSON path.
- Returns:
Validation report with specific errors and warnings.
- Return type:
HydrologyHazardValidation
- fhops.scenario.write_hydrology_constraint_check(result, out_dir)[source]
Write constraint-check events and manifest.
- fhops.scenario.write_hydrology_mapping(mapping, out_dir)[source]
Write the mapping CSV and manifest.
- fhops.scenario.write_hydrology_screen(screen, out_dir)[source]
Write screening rows and manifest.
Scenario contract models (Pydantic schemas, validators).
- class fhops.scenario.contract.Block(*, id, landing_id, work_required, earliest_start=1, latest_finish=None, harvest_system_id=None, avg_stem_size_m3=None, volume_per_ha_m3=None, volume_per_ha_m3_sigma=None, stem_density_per_ha=None, stem_density_per_ha_sigma=None, ground_slope_percent=None, salvage_processing_mode=None)[source]
Bases:
BaseModelHarvest block metadata and scheduling window.
- Parameters:
id (str)
landing_id (str)
work_required (float)
earliest_start (int | None)
latest_finish (int | None)
harvest_system_id (str | None)
avg_stem_size_m3 (float | None)
volume_per_ha_m3 (float | None)
volume_per_ha_m3_sigma (float | None)
stem_density_per_ha (float | None)
stem_density_per_ha_sigma (float | None)
ground_slope_percent (float | None)
salvage_processing_mode (SalvageProcessingMode | None)
- id
Unique block identifier (referenced by production rates and assignments).
- Type:
- landing_id
Landing where wood is forwarded; constrains landing daily capacity.
- Type:
- work_required
Volume (m³) that must be delivered from the block to complete it — the terminal (e.g., loader) output measured in the same units as
ProductionRate.rate. Every role of an explicit harvest system processes up to this volume; loader truckload batching, playback production, KPIs (total_production,remaining_work_total) and all reference datasets treat it as m³. Must be non-negative.- Type:
- earliest_start
Optional earliest day (inclusive, 1-indexed) when the block can begin.
- Type:
Day | None
- latest_finish
Optional latest day (inclusive) when the block must finish.
- Type:
Day | None
- harvest_system_id
Optional harvest system definition that restricts machine roles per block. Scenario validation requires the id to be a key of
Scenario.harvest_systemsor offhops.scheduling.systems.default_system_registry().- Type:
str | None
- avg_stem_size_m3 / volume_per_ha_m3 / volume_per_ha_m3_sigma
Stand descriptors (cubic metres) surfaced in analytics and productivity lookups.
- stem_density_per_ha / stem_density_per_ha_sigma
Stems per hectare statistics used by some productivity models.
- ground_slope_percent
Mean slope (%) for the block — used by productivity heuristics and diagnostics.
- Type:
float | None
- salvage_processing_mode
Enum describing downstream salvage processing (affects evaluation notes).
- Type:
SalvageProcessingMode | None
- earliest_start: Day | None
- id: str
- landing_id: str
- latest_finish: Day | None
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- salvage_processing_mode: SalvageProcessingMode | None
- work_required: float
- class fhops.scenario.contract.BlockInitialState(*, block_id, role_remaining=<factory>, staged_inventory=<factory>, role_shift_counts=<factory>)[source]
Bases:
BaseModelCarried-over sequencing state for one block at the start of the horizon.
Used to resume planning mid-operation (e.g. a rolling-horizon window that starts after some volume has already been felled, extracted, or processed). The terminal volume still to be delivered is expressed through
Block.work_requireditself; this model captures the intermediate (non-terminal) state of the harvest-system roles.- Parameters:
- block_id
Identifier of a block in
Scenario.blocks.- Type:
- role_remaining
Mapping
role -> volume(m³) the role may still output on this block, i.e.work_requiredat the original start minus the role’s cumulative output. Roles omitted here default to the block’swork_required(the v1.0.0 initialisation). Values must be non-negative.
- staged_inventory
Mapping
role -> volume(m³) output by that role on this block but not yet consumed by its downstream role(s) (for example, felled-but-unskidded wood is keyed by the felling role). Staged volume is kept per upstream role (since 1.0.1): a downstream role consumes its output from the staged volume of every one of its upstream roles, so it can process at most the smallest of them, and the downstream roles of an upstream role that feeds several roles share (split) its staged volume. Roles omitted here start with zero staged volume. Values must be non-negative.
- role_shift_counts
Mapping
role -> shiftsalready worked on this block. Informational: it seedsfhops.evaluation.sequencing.SequencingTracker.role_counts_total(reported by the rolling-horizon carry-forward) but no longer affects sequencing checks, because head-start buffers (role_headstart_shifts) are enforced as staged upstream volume (staged_inventory) in the MILP, the heuristics, and playback alike (#109). Roles omitted here start at zero. Values must be non-negative integers.
Notes
Role keys are normalised with
fhops.costing.machine_rates.normalize_machine_role()(case-insensitive, punctuation →_), so"Feller-Buncher"becomesfeller_buncher. Scenario-level validation requires every role key to be a role of the block’s harvest system, which means the block must declareharvest_system_idwhen any role-keyed state is given.- block_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.scenario.contract.CalendarEntry(*, machine_id, day, available=1)[source]
Bases:
BaseModelDay-level availability for a machine.
- machine_id
Identifier of the machine whose availability is being set.
- Type:
- day
One-indexed day number relative to the scenario horizon.
- Type:
Day
- available
Binary flag (1 available, 0 unavailable) controlling day-level assignment eligibility.
- Type:
- available: int
- day: Day
- machine_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.scenario.contract.CrewAssignment(*, crew_id, machine_id, primary_role=None, notes=None)[source]
Bases:
BaseModelOptional mapping of crews to machines/roles.
- crew_id
Unique crew identifier.
- Type:
- machine_id
Machine assigned to the crew.
- Type:
- primary_role
Optional role label associated with the crew (e.g., fallers, processors).
- Type:
str | None
- notes
Additional metadata surfaced in telemetry exports.
- Type:
str | None
- crew_id: str
- machine_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- fhops.scenario.contract.Day
alias of
int
- class fhops.scenario.contract.HydrologyArtifactPin(*, role, relative_path, schema_version, media_type, size_bytes, sha256)[source]
Bases:
BaseModelOne artifact recorded in a hydrology bundle pin.
- Parameters:
- media_type: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- relative_path: str
- role: str
- schema_version: str
- sha256: str
- size_bytes: int
- class fhops.scenario.contract.HydrologyBundleRef(*, bundle_id, bundle_version, manifest_sha256, domain_id, ledger_id, response_surface_id, manifest_path=None, readiness_status='not_available', readiness_evidence_sha256=None, artifacts=<factory>, caveats=<factory>, consumer_contract=<factory>)[source]
Bases:
BaseModelA scenario-level pin to one FEMIC hydrology bundle version.
The fields mirror
fhops.scenario.hydrology.bundle_pin(). The pin is a reference, not a copy of the bundle.- Parameters:
- artifacts: list[HydrologyArtifactPin]
- bundle_id: str
- bundle_version: str
- domain_id: str
- ledger_id: str
- manifest_sha256: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- readiness_status: HydrologyReadinessStatus
- response_surface_id: str
- class fhops.scenario.contract.Landing(*, id, daily_capacity=2)[source]
Bases:
BaseModelLanding metadata including its machine capacity.
- id
Landing identifier referenced by blocks and mobilisation logic.
- Type:
- daily_capacity
Maximum number of machines that can work the landing’s blocks concurrently, counted per shift slot
(day, shift_id)(on single-shift scenarios: per day). The name is kept for compatibility. The heuristics and the operational MILP apply the same rule: whenObjectiveWeights.landing_surplusis 0 (default) the capacity is hard (heuristics: the repair keeps landings within capacity and any remaining extra machine costs the hard violation penalty, at least 1000, #140; MILP: constraint), otherwise each slot’sk-th machine beyond capacity costsk × landing_surplus. Before FHOPS 1.0.1 the operational MILP counted machine-shifts per day with a slack that was free at weight 0 (#125).- Type:
- daily_capacity: int
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.scenario.contract.LidarProductPin(*, product, relative_path, sha256, crs, resolution_m=None, vertical_datum, source_tile_ids=<factory>)[source]
Bases:
BaseModelOne product file recorded in a scenario bundle pin.
- Parameters:
- crs: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- product: str
- relative_path: str
- sha256: str
- vertical_datum: str
- class fhops.scenario.contract.LidarProductRef(*, bundle_id, bundle_version, manifest_sha256=None, licence_id=None, manifest_path=None, crs=None, aoi_bbox=None, selection=None, products=None)[source]
Bases:
BaseModelA scenario-level pin to one FEMIC bundle version.
The fields mirror
fhops.scenario.lidar.bundle_pin();manifest_sha256is the digest of the bundle manifest, not of an individual raster.- Parameters:
- bundle_id: str
- bundle_version: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.scenario.contract.LidarSupport(*, support_unit, support_id, bundle_id, bundle_version, product, aggregation='area_weighted_mean', native_resolution_m, support_area_m2=None, valid_pixels=None, slope_percent=None, aspect_degrees=None, roughness_m=None, canopy_cover_percent=None, canopy_height_m=None, vertical_structure_index=None, vertical_structure_class=None, provenance=None)[source]
Bases:
BaseModelAggregated LidarBC evidence for one block, landing, or road.
The values are the output of a FEMIC raster summary (for example
fhops.scenario.lidar.summarize_femic_lidar_for_polygons()). Native resolution, product name, aggregation, valid-pixel count, and support footprint preserve how the summary was made.- Parameters:
support_unit (Literal['block', 'landing', 'road'])
aggregation (Literal['mean', 'area_weighted_mean', 'median', 'minimum', 'maximum', 'p95'])
aspect_degrees (Annotated[float | None, Ge(ge=0), Le(le=360)])
canopy_cover_percent (Annotated[float | None, Ge(ge=0), Le(le=100)])
vertical_structure_index (Annotated[float | None, Ge(ge=0)])
vertical_structure_class (str | None)
provenance (str | None)
- aggregation: AggregationMethod
- bundle_id: str
- bundle_version: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- native_resolution_m: float
- product: str
- support_id: str
- support_unit: LidarSupportUnit
- class fhops.scenario.contract.Machine(*, id, crew=None, daily_hours=24.0, operating_cost=0.0, role=None, repair_usage_hours=None)[source]
Bases:
BaseModelMachine definition (identifier, crew, availability, and costing metadata).
- Parameters:
- id
Unique machine identifier referenced throughout calendars/assignments.
- Type:
- crew
Optional crew label for reporting/telemetry grouping.
- Type:
str | None
- daily_hours
Maximum hours the machine can operate per day (defaults to 24).
- Type:
- operating_cost
Cost per scheduled machine hour (SMH) expressed in scenario currency units.
- Type:
- role
Optional machine role string (normalised via
normalize_machine_role) used by harvest systems and rental-rate lookups.- Type:
str | None
- repair_usage_hours
Optional cumulative repair hours that influences the rental-rate defaults.
- Type:
int | None
- daily_hours: float
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- operating_cost: float
- class fhops.scenario.contract.MachineInitialState(*, machine_id, last_block_id=None)[source]
Bases:
BaseModelCarried-over position of one machine at the start of the horizon.
- machine_id
Identifier of a machine in
Scenario.machines.- Type:
- last_block_id
Block the machine occupied in its last worked slot before the horizon, or
Nonewhen unknown (the v1.0.0 behaviour: the first move is free). When set, the machine’s first assignment to a different block is charged mobilisation (MobilisationConfig) and a transition by the operational MILP, the heuristics, and playback.- Type:
str | None
- machine_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.scenario.contract.Problem(*, scenario, days, shifts)[source]
Bases:
BaseModelRuntime representation of a scenario used by solvers.
Problemwraps a validatedScenarioand expands it into concretedaysandshiftsso optimisation code can iterate over deterministic index sets without repeatedly querying the Scenario.Problem.from_scenariois the canonical constructor; it injects the default harvest-system registry (when necessary) and synthesises single-shift calendars for legacy day-indexed inputs.- scenario
Back-reference to the source
Scenario.- Type:
Scenario
- days
List of integer day indices derived from
scenario.num_days.- Type:
list[Day]
- shifts
List of
ShiftInstanceentries representing every (day, shift_id) slot the solver should consider.- Type:
list[ShiftInstance]
Notes
Any code that builds Pyomo models or heuristic plans should accept a
Problemrather than the rawScenarioto avoid recomputing shift/day metadata.- days: list[Day]
- classmethod from_scenario(scenario)[source]
- Parameters:
scenario (Scenario)
- Return type:
Problem
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- scenario: Scenario
- shifts: list[ShiftInstance]
- class fhops.scenario.contract.ProductionRate(*, machine_id, block_id, rate)[source]
Bases:
BaseModelProduction rate (m³ per assignment) for a machine/block pair.
- machine_id
Machine identifier (must exist in
Scenario.machines).- Type:
- block_id
Block identifier (must exist in
Scenario.blocks).- Type:
- rate
Volume (m³) produced per full shift assignment (per day in single-shift scenarios), in the same units as
Block.work_required. Must be non-negative.- Type:
- block_id: str
- machine_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- rate: float
- class fhops.scenario.contract.RoadConstruction(*, id, machine_slug, road_length_m, include_mobilisation=True, soil_profile_ids=None, notes=None)[source]
Bases:
BaseModelRoad/subgrade construction job describing TR-28 soil profiles and costing metadata.
- Parameters:
- id
Unique job identifier referenced in telemetry and costing exports.
- Type:
- machine_slug
Machine rate slug (
tr28index) used to determine construction costs.- Type:
- road_length_m
Length of the road section (metres) to construct.
- Type:
- include_mobilisation
When
True, mobilisation costs are included in the estimate.- Type:
- soil_profile_ids
Optional list of TR-28 soil profile identifiers associated with the job.
- notes
Free-form comments surfaced in CLI summaries.
- Type:
str | None
- id: str
- include_mobilisation: bool
- machine_slug: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- road_length_m: float
- class fhops.scenario.contract.SalvageProcessingMode(*values)[source]
Bases:
StrEnum- IN_WOODS_CHIPPING = 'in_woods_chipping'
- PORTABLE_MILL = 'portable_mill'
- STANDARD_MILL = 'standard_mill'
- class fhops.scenario.contract.Scenario(*, name, num_days, schema_version='1.0.0', start_date=None, blocks, machines, landings, calendar, shift_calendar=None, production_rates, timeline=None, mobilisation=None, harvest_systems=None, geo=None, crew_assignments=None, locked_assignments=None, objective_weights=None, road_construction=None, initial_state=None, hauling=None, lidar_products=None, lidar_support=None, hydrology_products=None, tree_inventory=None)[source]
Bases:
BaseModelTop-level container for the FHOPS data contract.
The model mirrors the CSV/YAML inputs documented in
docs/howto/data_contract.rstand is the object returned byfhops.scenario.io.load_scenario(). Only validated, horizon-bounded data reaches this point, which means downstream solvers (MIP + heuristics) can rely on:every block referencing a known landing/harvest system,
machine calendars/shift calendars never exceeding
num_days,mobilisation tables referencing existing blocks/machines, and
optional extras (crew assignments, road construction, GeoJSON metadata) being present only when fully specified.
- Parameters:
name (str)
num_days (int)
schema_version (str)
start_date (date | None)
blocks (list[Block])
machines (list[Machine])
landings (list[Landing])
calendar (list[CalendarEntry])
shift_calendar (list[ShiftCalendarEntry] | None)
production_rates (list[ProductionRate])
timeline (TimelineConfig | None)
mobilisation (MobilisationConfig | None)
harvest_systems (dict[str, HarvestSystem] | None)
geo (GeoMetadata | None)
crew_assignments (list[CrewAssignment] | None)
locked_assignments (list[ScheduleLock] | None)
objective_weights (ObjectiveWeights | None)
road_construction (list[RoadConstruction] | None)
initial_state (ScenarioInitialState | None)
hauling (HaulingConfig | None)
lidar_products (list[LidarProductRef] | None)
lidar_support (list[LidarSupport] | None)
hydrology_products (list[HydrologyBundleRef] | None)
tree_inventory (TreeInventoryRef | None)
- name
Human-readable scenario label surfaced in CLI/Evaluation outputs.
- Type:
- num_days
Planning horizon length (integer number of days).
- Type:
- schema_version
Version of the input schema; used to guard loader compatibility.
- Type:
- start_date
Optional ISO date string used for timestamped exports.
- Type:
date | None
- blocks / machines / landings
Validated lists of the corresponding Pydantic models.
- calendar / shift_calendar
Availability tables.
shift_calendarmay beNonefor day-level scenarios.
- production_rates
Machine/block productivity table measured in m³ per shift assignment.
- Type:
list[ProductionRate]
- timeline
Optional
TimelineConfigdescribing shifts, blackout windows, etc.- Type:
TimelineConfig | None
- mobilisation
Optional
MobilisationConfigdescribing distances and per-machine parameters.- Type:
MobilisationConfig | None
- harvest_systems
Optional registry mapping harvest-system IDs to
HarvestSystemdefinitions. Entries are overlaid onfhops.scheduling.systems.default_system_registry(); everyBlock.harvest_system_idmust name a system of that combined registry (unknown ids are rejected since 1.0.1; 1.0.0 accepted them).- Type:
dict[str, HarvestSystem] | None
- geo
Optional
GeoMetadatawith GeoJSON lookups.- Type:
GeoMetadata | None
- crew_assignments
Optional list mapping crew IDs to machines for reporting/telemetry.
- Type:
list[CrewAssignment] | None
- locked_assignments
Optional list of
ScheduleLockentries that pin machines to blocks on specific days (or specific day/shift slots whenScheduleLock.shift_idis set). Enforced by the operational MILP, the legacy MIP builder, and the heuristics. Validation rejects unknown machine/block ids, days outside the horizon or the block’s[earliest_start, latest_finish]window, blackout days, unknown shift labels (seeshift_labels()), duplicate or mixed day/shift locks, and machines whose role is not a role of the block’s explicit harvest system. Files usingshift_idrequirefhops>=1.0.1(1.0.0 reads such entries as whole-day locks).- Type:
list[ScheduleLock] | None
- objective_weights
Optional
ObjectiveWeightsoverriding default solver weights.- Type:
ObjectiveWeights | None
- road_construction
Optional list of
RoadConstructionentries used by telemetry/costing exports.- Type:
list[RoadConstruction] | None
- lidar_products
Optional pins to FEMIC-compiled LidarBC bundle versions. A pin records the bundle id, version, manifest checksum, licence, and optional manifest path. FHOPS consumes the FEMIC bundle; it never discovers or materializes source tiles itself.
- Type:
list[LidarProductRef] | None
- hydrology_products
Optional pins to FEMIC hydrology response bundles. A pin records the bundle id, version, manifest checksum, domain/ledger/response-surface ids, artifact checksums, readiness state, and caveats. FHOPS consumes the bundle; it does not copy or re-run FEMIC.
- Type:
list[HydrologyBundleRef] | None
- tree_inventory
Optional reference to a validated individual-tree inventory. The reference records the inventory and metadata checksums, CRS, review state, producer, method, tree count, block memberships, and caveats. The inventory is referenced, not copied.
- Type:
TreeInventoryRef | None
- lidar_support
Optional aggregated LidarBC evidence for blocks, landings, and roads. Each row states the FEMIC product, native resolution, aggregation, support footprint, and provenance; existing scenarios without LiDAR fields remain valid.
- Type:
list[LidarSupport] | None
- hauling
Optional
HaulingConfigwith log sorts, contractors, truck types and trucks, depots, sort yards, haul arcs, loader bindings, and the driver policy.None(default) means the scenario has no hauling layer. Files using this field require schema1.1.0(the loader still accepts1.0.0).- Type:
HaulingConfig | None
- initial_state
Optional
ScenarioInitialStatecarrying staged inter-role inventory, remaining per-role output, head-start shift counts, and each machine’s last block at the start of the horizon.None(default) starts from the 1.0.0 initial conditions (empty staged inventory, fullwork_requiredper role, no prior machine position); it does not undo other 1.0.1 behaviour changes. Validation requires known block/machine ids, non-negative values, role keys drawn from the block’s harvest-system roles,role_remainingvalues not above the block’swork_required, andlast_block_idvalues that are scenario blocks. Files using this field requirefhops>=1.0.1(1.0.0 ignores it).- Type:
ScenarioInitialState | None
Notes
The helper methods (
machine_ids(),window_for(), etc.) are convenience routines for the solver/evaluation layers and are intentionally lightweight so they can be used in tight loops.- block_ids()[source]
Return the list of block identifiers defined in the scenario.
- blocks: list[Block]
- calendar: list[CalendarEntry]
- geo: GeoMetadata | None
- harvest_systems: dict[str, HarvestSystem] | None
- hauling: HaulingConfig | None
- initial_state: ScenarioInitialState | None
- landing_ids()[source]
Return the list of landing identifiers defined in the scenario.
- landings: list[Landing]
- machine_ids()[source]
Return the list of machine identifiers defined in the scenario.
- machines: list[Machine]
- mobilisation: MobilisationConfig | None
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- name: str
- num_days: int
- objective_weights: ObjectiveWeights | None
- production_rates: list[ProductionRate]
- schema_version: str
- shift_labels()[source]
Return the shift labels of the scenario’s shift grid.
These are the labels accepted for
ScheduleLock.shift_idand used for theshift_idcolumn of solver and playback outputs. The source mirrorsProblem.from_scenario().- Returns:
shift_calendarlabels when a shift calendar is present (whether or not each entry is available), otherwise the timeline shift names, otherwise{"S1"}(the synthetic single shift used for day-indexed scenarios).- Return type:
Examples
>>> from fhops.scenario.io import load_scenario >>> load_scenario("examples/tiny7/scenario.yaml").shift_labels() {'S1'}
- start_date: date | None
- timeline: TimelineConfig | None
- tree_inventory: TreeInventoryRef | None
- class fhops.scenario.contract.ScenarioInitialState(*, blocks=<factory>, machines=<factory>)[source]
Bases:
BaseModelOptional initial state for a scenario (resume planning mid-operation).
When
Scenario.initial_stateisNone(default) the horizon starts from the FHOPS 1.0.0 initial conditions: staged inventories and role shift counts start at zero, each role may output the fullwork_required, and machines have no prior position (their first move is free). Solver and playback results can still differ from 1.0.0 because of other 1.0.1 fixes (MILP/playback sequencing alignment, MILP blackouts, stochastic playback events; seedocs/releases/v1.0.1.md).Scenario files that contain
initial_staterequirefhops>=1.0.1: FHOPS 1.0.0 silently ignores the section (schema_versionis unchanged at1.0.0).- blocks
Per-block
BlockInitialStateentries (at most one perblock_id). Blocks not listed start from the default (empty) state.- Type:
list[BlockInitialState]
- machines
Per-machine
MachineInitialStateentries (at most one permachine_id).- Type:
list[MachineInitialState]
Examples
>>> state = ScenarioInitialState( ... blocks=[ ... BlockInitialState( ... block_id="B01", ... role_remaining={"feller_buncher": 0.0}, ... staged_inventory={"feller_buncher": 120.0}, ... ) ... ], ... machines=[MachineInitialState(machine_id="H3", last_block_id="B02")], ... ) >>> state.block_state("B01").staged_inventory {'feller_buncher': 120.0}
- block_state(block_id)[source]
Return the
BlockInitialStateforblock_id(Nonewhen absent).- Parameters:
block_id (str)
- Return type:
BlockInitialState | None
- blocks: list[BlockInitialState]
- last_block_by_machine()[source]
Return
machine_id -> last_block_idfor machines with a known prior position.
- machines: list[MachineInitialState]
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.scenario.contract.ScheduleLock(**data)[source]
Bases:
BaseModelImmutable assignment of a machine to a block on a specific day (or day/shift slot).
- Parameters:
data (Any)
- machine_id
Identifier of the machine being locked (must exist in
Scenario.machines).- Type:
- block_id
Identifier of the block that must be worked during the lock.
- Type:
- day
One-indexed day within the planning horizon where the lock applies.
- Type:
Day
- shift_id
Optional shift label.
None(default) locks every available shift ofdaytoblock_id(the v1.0.0 behaviour). When set, only the(day, shift_id)slot is locked; the label must belong to the scenario’s shift grid (shift_calendarlabels, timeline shift names, orS1for day-indexed scenarios).- Type:
str | None
- production
Optional planned production (m³,
>= 0) of the locked slot. It is used when a lock table is replayed (rolling-horizon carry-forward andfhops.planning.compute_rolling_kpis()), so a stitched operational-MILP plan is evaluated with the production the MILP planned rather than the full production rate.None(default) replays withmin(rate, remaining). The heuristics (SA, ILS, Tabu) and playback of plans without aproductioncolumn use it as an upper bound on the locked slot’s production (0= idle; since #158); a locked slot never produces more than its staged input allows and idles without a violation when it has none. The operational MILP ignores it.- Type:
float | None
Notes
A day-level lock and a shift-level lock for the same machine/day are rejected by
Scenariovalidation, as are duplicate(machine_id, day, shift_id)entries.- block_id: str
- day: Day
- machine_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.scenario.contract.TreeInventoryRef(*, inventory_id, inventory_sha256, metadata_sha256, inventory_path, metadata_path, crs, review_status='not_reviewed', reviewed_by=None, source_id, producer, method, tree_count, block_ids=<factory>, caveats=<factory>)[source]
Bases:
BaseModelA scenario-level reference to one validated tree inventory.
The inventory is referenced, not copied.
inventory_sha256andmetadata_sha256pin the CSV and metadata bytes; the remaining fields preserve identity, CRS, review, and producer provenance.- Parameters:
- inventory_id
Stable inventory identifier from the metadata.
- Type:
- inventory_sha256
SHA-256 digest of the inventory CSV.
- Type:
- metadata_sha256
SHA-256 digest of the metadata JSON.
- Type:
- inventory_path
Inventory CSV path, relative to the scenario when possible.
- Type:
- metadata_path
Metadata JSON path, relative to the scenario when possible.
- Type:
- crs
Inventory coordinate reference system.
- Type:
- review_status
not_reviewed,review_pending,reviewed, orreviewed_with_conditions.- Type:
- reviewed_by
Reviewer identifier when reviewed.
- Type:
str | None
- source_id
Producer source identifier.
- Type:
- producer
Producer name.
- Type:
- method
Detection/segmentation method.
- Type:
- tree_count
Number of tree records.
- Type:
- crs: str
- inventory_id: str
- inventory_path: str
- inventory_sha256: str
- metadata_path: str
- metadata_sha256: str
- method: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- producer: str
- review_status: TreeInventoryReviewStatus
- source_id: str
- tree_count: int
- fhops.scenario.contract.validate_initial_state(scenario)[source]
Validate
scenario.initial_stateagainst the scenario’s blocks, machines, and systems.Called automatically by
Scenariovalidation (including the final re-validation infhops.scenario.io.load_scenario()). Call it directly after attaching aninitial_statewithmodel_copy(update=...), which bypasses Pydantic validators.- Parameters:
scenario (Scenario) – Scenario whose
initial_stateshould be checked.Noneinitial state is accepted.- Raises:
ValueError – If a block/machine id is unknown, a
last_block_idis not a scenario block, a block with role-keyed state has noharvest_system_id, a role key is not a role of the block’s harvest system, or a terminalrole_remainingvalue exceeds the block’swork_requiredby more thanROLE_REMAINING_TOLERANCE(1e-6 m³). Non-terminal roles may exceed the terminal remainder in multi-terminal systems or when staged inventory is carried (#157/N3). Harvest systems are looked up inscenario.harvest_systems(falling back tofhops.scheduling.systems.default_system_registry(), mirroringProblem.from_scenario()).- Return type:
None
Quickstart
from fhops.scenario.io import load_scenario
from fhops.scenario.contract import Problem
scenario = load_scenario("examples/tiny7/scenario.yaml")
problem = Problem.from_scenario(scenario)
print(problem.days, len(problem.shifts))
Key models:
fhops.scenario.contract.Scenario– Pydantic model for inputs (blocks, machines, timelines).fhops.scenario.contract.Problem– Derived object used by solvers (days, shifts, scenario).fhops.scenario.contract.MobilisationConfig/TimelineConfig– optional extras for mobilisation/shift data.fhops.scenario.contract.ScenarioInitialState– optional carried-in state (staged inventory, remaining role output, head-start shift counts, last block per machine); see Initial State (Resuming Mid-Operation).fhops.scenario.contract.ScheduleLock– machine/block/day locks with an optionalshift_id.fhops.hauling.contract.HaulingConfig– optional log-hauling extension (schema1.1.0); see Log Hauling.
Scenario IO helpers (YAML/CSV loaders, schema checks).
- fhops.scenario.io.load_scenario(yaml_path)[source]
Load a Scenario from the YAML metadata + CSV bundle.
- Parameters:
yaml_path (str | Path) – Path to the
scenario.yamlfile that references the component CSVs.- Returns:
Fully validated Pydantic model ready to be converted into a
fhops.scenario.contract.Problem.- Return type:
Scenario
Notes
The loader performs several quality-of-life tasks that callers usually forget:
normalises optional string columns (e.g.,
harvest_system_idblanks →None),back-fills mobilisation distance matrices from
*_block_distances.csvwhenever present,accepts inline YAML overrides for optional tables (road construction, shift calendar, crew map),
re-roots GeoJSON paths relative to the scenario directory,
ensures every optional extra (timeline, mobilisation config, objective weights) is copied into the resulting Scenario instance,
parses an optional inline
initial_statemapping (seefhops.scenario.contract.ScenarioInitialState) and validates it against the loaded blocks, machines, and harvest systems, andre-validates the assembled scenario once all optional sections are attached, so YAML
locked_assignments(machine/block/day/shift_idchecks, duplicates, day+shift mixes, blackout clashes), mobilisation, crew assignments, andinitial_stateget the samefhops.scenario.contract.Scenariocross-validation as Python-constructed scenarios. Core tables (and theharvest_systemssection, which blockharvest_system_idvalues are checked against together with the default registry) are validated first, so their errors are reported before optional-section errors.
Examples
An inline
initial_statesection inscenario.yaml:initial_state: blocks: - block_id: B01 role_remaining: {feller_buncher: 0.0} staged_inventory: {feller_buncher: 150.0} machines: - machine_id: H3 last_block_id: B02
- Raises:
ValueError – (Pydantic
ValidationError) when any table, theharvest_systems,locked_assignmentsorinitial_statesection, or the scenario cross-validation fails (including a blockharvest_system_idthat is neither inharvest_systemsnor infhops.scheduling.systems.default_system_registry()).- Parameters:
- Return type:
Scenario
- fhops.scenario.io.read_csv(path)[source]
Load a CSV file using pandas with UTF-8 defaults.
- Parameters:
path (Path)
- Return type:
DataFrame
Synthetic scenario generators.
- class fhops.scenario.synthetic.BlackoutBias(start_day, end_day, probability, duration=None)[source]
Bases:
objectBias blackout probabilities for specific windows.
- Parameters:
- class fhops.scenario.synthetic.SyntheticDatasetBundle(scenario, blocks, machines, landings, calendar, production_rates, metadata=None)[source]
Bases:
objectContainer for generated scenario tables and helpers to persist them.
- Parameters:
- blocks: DataFrame
- calendar: DataFrame
- landings: DataFrame
- machines: DataFrame
- production_rates: DataFrame
- scenario: Scenario
- class fhops.scenario.synthetic.SyntheticDatasetConfig(name, num_blocks, num_days, num_machines, num_landings=1, shift_hours=(8.0, 12.0), shifts_per_day=1, machine_daily_hours=24.0, landing_capacity=(1, 3), work_required=(6.0, 18.0), production_rate=(6.0, 18.0), availability_probability=0.9, blackout_probability=0.1, blackout_duration=(1, 2), role_pool=<factory>, tier=None, terrain_pool=<factory>, terrain_weights=None, prescription_pool=<factory>, prescription_weights=None, crew_pool=<factory>, capability_pool=<factory>, crew_capability_span=(1, 2), system_mix=None, blackout_biases=<factory>, sampling_overrides=None, block_metric_section='daily')[source]
Bases:
objectConfiguration for generating random synthetic datasets.
Notes
work_requiredandproduction_rateare sampling ranges in m³ (block volume to deliver and volume per shift assignment), matchingBlock.work_requiredandProductionRate.rate;shift_hoursandmachine_daily_hoursare hours.- Parameters:
- blackout_biases: list[BlackoutBias]
- class fhops.scenario.synthetic.SyntheticScenarioSpec(num_blocks, num_days, num_machines, landing_capacity=1, blackout_days=None)[source]
Bases:
objectConfiguration for generating synthetic scenarios.
- Parameters:
- fhops.scenario.synthetic.generate_basic(spec)[source]
Generate a minimal scenario matching the supplied specification.
- Parameters:
spec (SyntheticScenarioSpec)
- Return type:
Scenario
- fhops.scenario.synthetic.generate_random_dataset(config, *, seed=123, systems=None)[source]
Generate a random synthetic dataset bundle (scenario + CSV tables).
- Parameters:
config (SyntheticDatasetConfig) – Sizes, sampling ranges and tier presets (
tierfills unset pools andsystem_mix).seed (int, default 123) – RNG seed; the same config, seed and systems give the same bundle.
systems (dict[str, HarvestSystem] | None) – Harvest systems the blocks may use (
fhops synthpasses the default registry). When given, the fleet is built from them (since 1.0.1, #158): systems with a positivesystem_mixweight are visited in a weighted random order and kept while the distinct roles of the kept systems fit in the sampled machine count; machines take those roles round-robin (upstream roles first;role_poolis ignored) and every block draws its system from the kept systems bysystem_mixweight (round-robin without weights). Every block can therefore be delivered unless no system fits the fleet (then the system with the fewest roles is used and some of its roles have no machine). The chosen systems and roles are recorded inmetadata(fleet_systems,fleet_roles). Without systems, machines takerole_poolroles and blocks have no harvest system.
- Returns:
Scenario, CSV tables and metadata.
- Return type:
- fhops.scenario.synthetic.generate_with_systems(spec, systems=None)[source]
Generate a scenario and assign blocks round-robin to harvest systems.
- Parameters:
spec (SyntheticScenarioSpec)
systems (dict[str, HarvestSystem] | None)
- Return type:
Scenario
- fhops.scenario.synthetic.sampling_config_for(config)[source]
Return the stochastic playback configuration for a synthetic dataset tier.
- Parameters:
config (SyntheticDatasetConfig) – Dataset configuration;
tierselects aSAMPLING_PRESETSentry (small,medium,large; unknown tiers keep the defaults) andsampling_overridesis deep-merged on top.- Returns:
SamplingConfig(samples=10)updated with the tier preset and overrides. Landing-shock presets are calibrated for per-landing-day shocks: the expected fraction of shocked landing-days is1 - (1 - probability) ** duration_days(medium ≈ 4.9 %, large ≈ 10.1 %; the default config gives0.1). Deprecated fields (correlated_days,seed_offset) are dropped before re-validation, so no deprecation warning is emitted.- Return type: