fhops.planning.tactical_operational Package

Tactical–operational planning contracts, period templates, scenario overlays, reporting, scale benchmarks, and tactical→operational handoff helpers introduced in Phase 6.

Typical usage:

from fhops.planning.tactical_operational import load_tactical_operational_scenario
from fhops.model.milp.tactical_operational import (
    build_tactical_operational_bundle,
    solve_tactical_operational_milp,
)

scenario = load_tactical_operational_scenario(
    "tests/fixtures/tactical_operational/topm-mini/specification.yaml"
)
bundle = build_tactical_operational_bundle(scenario)
result = solve_tactical_operational_milp(bundle)
print(result["objective"], result["objective_components"])

Tactical–operational planning contract, loaders, and period utilities.

class fhops.planning.tactical_operational.BlockRoadAccess(*, block_id, road_id)[source]

Bases: BaseModel

Mapping from a planning unit to a road that can enable harvest access.

Parameters:
  • block_id (str)

  • road_id (str)

block_id: str
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

road_id: str
class fhops.planning.tactical_operational.Economics(*, currency='CAD', base_year=2026, discount_rate_per_year=0.0, objective_profile='min_discounted_delivered_cost')[source]

Bases: BaseModel

Currency, base-year, discounting, and objective metadata for tactical planning.

Parameters:
base_year: int
currency: str
discount_rate_per_year: float
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

objective_profile: str
class fhops.planning.tactical_operational.ExternalSupply(*, source_id, destination_id, product_id, period_id, minimum_m3=0.0, maximum_m3=None, delivered_cost_per_m3=0.0)[source]

Bases: BaseModel

Optional outside wood purchase source feeding a facility.

Parameters:
delivered_cost_per_m3: float
destination_id: str
maximum_m3: float | None
minimum_m3: float
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

period_id: str
product_id: str
source_id: str
class fhops.planning.tactical_operational.Facility(*, facility_id, facility_type=None, accepted_products=<factory>, terminal_inventory_value_per_m3=None)[source]

Bases: BaseModel

Mill, terminal, or customer that accepts one or more products.

Parameters:
  • facility_id (str)

  • facility_type (str | None)

  • accepted_products (list[str])

  • terminal_inventory_value_per_m3 (dict[str, float] | None)

accepted_products: list[str]
facility_id: str
facility_type: str | None
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

terminal_inventory_value_per_m3: dict[str, float] | None
class fhops.planning.tactical_operational.FacilityDemand(*, facility_id, product_id, period_id, minimum_m3=0.0, target_m3=None, maximum_m3=None, value_per_m3=None)[source]

Bases: BaseModel

Facility/product/period demand envelope and optional delivered product value.

Parameters:
  • facility_id (str)

  • product_id (str)

  • period_id (str)

  • minimum_m3 (Annotated[float, Ge(ge=0)])

  • target_m3 (Annotated[float | None, Ge(ge=0)])

  • maximum_m3 (Annotated[float | None, Ge(ge=0)])

  • value_per_m3 (Annotated[float | None, Ge(ge=0)])

facility_id: str
maximum_m3: float | None
minimum_m3: float
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

period_id: str
product_id: str
target_m3: float | None
value_per_m3: float | None
class fhops.planning.tactical_operational.FleetCapacity(*, system_id, period_id, capacity_m3=None, capacity_hours=None)[source]

Bases: BaseModel

Aggregate production capacity for a harvest system in one period.

Parameters:
  • system_id (str)

  • period_id (str)

  • capacity_m3 (Annotated[float | None, Ge(ge=0)])

  • capacity_hours (Annotated[float | None, Ge(ge=0)])

capacity_hours: float | None
capacity_m3: float | None
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

period_id: str
system_id: str
class fhops.planning.tactical_operational.FleetOption(*, option_id, system_id, purchase_period_id, purchase_cost=0.0, capacity_m3_per_period, max_units=0, economic_life_periods=1)[source]

Bases: BaseModel

Optional fleet acquisition that adds system capacity over an economic life.

Parameters:
capacity_m3_per_period: float
economic_life_periods: int
max_units: int
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

option_id: str
purchase_cost: float
purchase_period_id: str
system_id: str
class fhops.planning.tactical_operational.HarvestSystemOption(*, option_id, block_id, system_id, period_id, eligible=True, min_area_ha=0.0, max_area_ha=None, variable_cost_per_m3=0.0, fixed_cost=0.0, productivity_m3_per_period=None)[source]

Bases: BaseModel

Eligible block × system × period harvest option with quantity and cost bounds.

Parameters:
block_id: str
eligible: bool
fixed_cost: float
max_area_ha: float | None
min_area_ha: float
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

option_id: str
period_id: str
productivity_m3_per_period: float | None
system_id: str
variable_cost_per_m3: float
class fhops.planning.tactical_operational.InitialInventory(*, facility_id, product_id, opening_m3)[source]

Bases: BaseModel

Opening product inventory at a facility.

Parameters:
facility_id: str
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

opening_m3: float
product_id: str
class fhops.planning.tactical_operational.PeriodLevel(value)[source]

Bases: StrEnum

Supported tactical–operational period resolutions.

DAY = 'day'
FOUR_WEEK = 'four_week'
MONTH = 'month'
SEASON = 'season'
SHIFT = 'shift'
WEEK = 'week'
YEAR = 'year'
class fhops.planning.tactical_operational.PlanningPeriod(*, period_id, level, sequence, start_date, end_date, duration_days=None, available_hours=None, parent_period_id=None, discount_factor=1.0, season_tags=<factory>)[source]

Bases: BaseModel

Aggregate planning period with optional hierarchy and discounting metadata.

Parameters:
available_hours: float | None
discount_factor: float
duration_days: int | None
end_date: date
level: PeriodLevel
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

parent_period_id: str | None
period_id: str
season_tags: list[str]
sequence: int
start_date: date
class fhops.planning.tactical_operational.PlanningUnit(*, block_id, gross_area_ha, operable_area_ha, forest_class=None, initial_state=None, product_yields_m3_per_ha=<factory>)[source]

Bases: BaseModel

Aggregate harvestable unit with area and product yields.

Parameters:
block_id: str
forest_class: str | None
gross_area_ha: float
initial_state: str | None
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

operable_area_ha: float
product_yields_m3_per_ha: dict[str, float]
class fhops.planning.tactical_operational.Product(*, product_id, species_group=None, grade=None)[source]

Bases: BaseModel

A product/species-grade tracked through harvest, transport, and facility inventory.

Parameters:
  • product_id (str)

  • species_group (str | None)

  • grade (str | None)

grade: str | None
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

product_id: str
species_group: str | None
class fhops.planning.tactical_operational.RoadDependency(*, road_id, depends_on_road_id)[source]

Bases: BaseModel

Directed prerequisite relationship between two road projects.

Parameters:
  • road_id (str)

  • depends_on_road_id (str)

depends_on_road_id: str
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

road_id: str
class fhops.planning.tactical_operational.RoadProject(*, road_id, build_cost=0.0, maintenance_cost_per_period=0.0, earliest_period_id=None, capacity_m3_per_period=None)[source]

Bases: BaseModel

Candidate road project with activation timing, cost, and throughput capacity.

Parameters:
  • road_id (str)

  • build_cost (Annotated[float, Ge(ge=0)])

  • maintenance_cost_per_period (Annotated[float, Ge(ge=0)])

  • earliest_period_id (str | None)

  • capacity_m3_per_period (Annotated[float | None, Ge(ge=0)])

build_cost: float
capacity_m3_per_period: float | None
earliest_period_id: str | None
maintenance_cost_per_period: float
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

road_id: str
class fhops.planning.tactical_operational.SilvicultureTransition(*, transition_id, block_id, system_id, activity_id, earliest_period_id, cost_per_ha=0.0, required=True, next_state=None)[source]

Bases: BaseModel

Required follow-up activity generated by a block/system harvest decision.

Parameters:
activity_id: str
block_id: str
cost_per_ha: float
earliest_period_id: str
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

next_state: str | None
required: bool
system_id: str
transition_id: str
class fhops.planning.tactical_operational.TacticalOperationalScenario(*, name, planning_level='tactical_operational', schema_version='0.1.0', parent=None, overlay_id=None, source_hash=None, economics=<factory>, periods, products, planning_units, harvest_system_options, fleet_capacity=<factory>, facilities, facility_demand=<factory>, initial_inventory=<factory>, transport_arcs=<factory>, external_supply=<factory>, roads=<factory>, road_dependencies=<factory>, block_road_access=<factory>, silviculture_transitions=<factory>, fleet_options=<factory>)[source]

Bases: BaseModel

Validated aggregate planning contract for TOPM-inspired FHOPS scenarios.

The schema uses long-form, dimension-flexible tables. Cross-validation checks all period, product, block, system-option, facility, transport, inventory, and external-supply references.

Parameters:
block_road_access: list[BlockRoadAccess]
dimension_summary()[source]

Return model-dimension counts used by CLI validation and telemetry.

Return type:

dict[str, int]

economics: Economics
external_supply: list[ExternalSupply]
facilities: list[Facility]
facility_demand: list[FacilityDemand]
fleet_capacity: list[FleetCapacity]
fleet_options: list[FleetOption]
harvest_system_options: list[HarvestSystemOption]
initial_inventory: list[InitialInventory]
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str
overlay_id: str | None
parent: str | None
periods: list[PlanningPeriod]
planning_level: str
planning_units: list[PlanningUnit]
products: list[Product]
road_dependencies: list[RoadDependency]
roads: list[RoadProject]
schema_version: str
silviculture_transitions: list[SilvicultureTransition]
source_hash: str | None
to_dict()[source]

Return a JSON-compatible round-trip representation of the scenario.

Return type:

dict[str, Any]

transport_arcs: list[TransportArc]
class fhops.planning.tactical_operational.TransportArc(*, arc_id, origin_id, destination_id, product_id, mode='truck', period_id, cost_per_m3=0.0, capacity_m3=None, distance_km=None)[source]

Bases: BaseModel

Eligible product flow arc from a block/origin to a facility for one period.

Parameters:
  • arc_id (str)

  • origin_id (str)

  • destination_id (str)

  • product_id (str)

  • mode (str)

  • period_id (str)

  • cost_per_m3 (Annotated[float, Ge(ge=0)])

  • capacity_m3 (Annotated[float | None, Ge(ge=0)])

  • distance_km (Annotated[float | None, Ge(ge=0)])

arc_id: str
capacity_m3: float | None
cost_per_m3: float
destination_id: str
distance_km: float | None
mode: str
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

origin_id: str
period_id: str
product_id: str
fhops.planning.tactical_operational.children_by_parent(periods)[source]

Group child periods by parent period ID for roll-up reporting.

Parameters:

periods (list[PlanningPeriod])

Return type:

dict[str, list[PlanningPeriod]]

fhops.planning.tactical_operational.four_week_periods(year, *, start_month=1, start_day=1, periods=13, discount_rate_per_year=0.0)[source]

Build thirteen (or a custom count of) four-week planning periods for a calendar year.

The final period is truncated at December 31 when the accounting calendar overflows the year. Discount factors are assigned at each period start using an effective annual rate.

Parameters:
  • year (int)

  • start_month (int)

  • start_day (int)

  • periods (int)

  • discount_rate_per_year (float)

Return type:

list[PlanningPeriod]

fhops.planning.tactical_operational.load_tactical_operational_scenario(yaml_path)[source]

Load and validate a TOPM-inspired tactical–operational scenario.

The loader accepts either inline YAML sections or a data: mapping from section names to CSV files. topm-mini-style specifications may use fixture_id/specification_version; those aliases are normalized to name and schema_version before Pydantic validation.

Parameters:

yaml_path (str | Path)

Return type:

TacticalOperationalScenario

fhops.planning.tactical_operational.seasonal_periods(year, *, discount_rate_per_year=0.0)[source]

Build a conventional four-season calendar-year period template.

Parameters:
  • year (int)

  • discount_rate_per_year (float)

Return type:

list[PlanningPeriod]

fhops.planning.tactical_operational.tactical_scenario_dimensions(scenario)[source]

Return model dimension counts for CLI output and telemetry.

Parameters:

scenario (TacticalOperationalScenario)

Return type:

dict[str, int]

fhops.planning.tactical_operational.tactical_scenario_to_dict(scenario)[source]

Serialize a tactical–operational scenario to a JSON-compatible dictionary.

Parameters:

scenario (TacticalOperationalScenario)

Return type:

dict[str, Any]

fhops.planning.tactical_operational.validate_period_hierarchy(periods)[source]

Validate uniqueness, parent references, and chronological ordering of a period set.

Parameters:

periods (list[PlanningPeriod])

Return type:

None

Pydantic contract for TOPM-inspired tactical–operational planning scenarios.

The models in this module describe aggregate, multi-period planning inputs. They are intentionally separate from fhops.scenario.contract.Scenario, which remains the stable schema 1.0.0 contract for detailed day/shift machine scheduling.

class fhops.planning.tactical_operational.models.BlockRoadAccess(*, block_id, road_id)[source]

Bases: BaseModel

Mapping from a planning unit to a road that can enable harvest access.

Parameters:
  • block_id (str)

  • road_id (str)

block_id: str
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

road_id: str
class fhops.planning.tactical_operational.models.Economics(*, currency='CAD', base_year=2026, discount_rate_per_year=0.0, objective_profile='min_discounted_delivered_cost')[source]

Bases: BaseModel

Currency, base-year, discounting, and objective metadata for tactical planning.

Parameters:
base_year: int
currency: str
discount_rate_per_year: float
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

objective_profile: str
class fhops.planning.tactical_operational.models.ExternalSupply(*, source_id, destination_id, product_id, period_id, minimum_m3=0.0, maximum_m3=None, delivered_cost_per_m3=0.0)[source]

Bases: BaseModel

Optional outside wood purchase source feeding a facility.

Parameters:
delivered_cost_per_m3: float
destination_id: str
maximum_m3: float | None
minimum_m3: float
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

period_id: str
product_id: str
source_id: str
class fhops.planning.tactical_operational.models.Facility(*, facility_id, facility_type=None, accepted_products=<factory>, terminal_inventory_value_per_m3=None)[source]

Bases: BaseModel

Mill, terminal, or customer that accepts one or more products.

Parameters:
  • facility_id (str)

  • facility_type (str | None)

  • accepted_products (list[str])

  • terminal_inventory_value_per_m3 (dict[str, float] | None)

accepted_products: list[str]
facility_id: str
facility_type: str | None
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

terminal_inventory_value_per_m3: dict[str, float] | None
class fhops.planning.tactical_operational.models.FacilityDemand(*, facility_id, product_id, period_id, minimum_m3=0.0, target_m3=None, maximum_m3=None, value_per_m3=None)[source]

Bases: BaseModel

Facility/product/period demand envelope and optional delivered product value.

Parameters:
  • facility_id (str)

  • product_id (str)

  • period_id (str)

  • minimum_m3 (Annotated[float, Ge(ge=0)])

  • target_m3 (Annotated[float | None, Ge(ge=0)])

  • maximum_m3 (Annotated[float | None, Ge(ge=0)])

  • value_per_m3 (Annotated[float | None, Ge(ge=0)])

facility_id: str
maximum_m3: float | None
minimum_m3: float
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

period_id: str
product_id: str
target_m3: float | None
value_per_m3: float | None
class fhops.planning.tactical_operational.models.FleetCapacity(*, system_id, period_id, capacity_m3=None, capacity_hours=None)[source]

Bases: BaseModel

Aggregate production capacity for a harvest system in one period.

Parameters:
  • system_id (str)

  • period_id (str)

  • capacity_m3 (Annotated[float | None, Ge(ge=0)])

  • capacity_hours (Annotated[float | None, Ge(ge=0)])

capacity_hours: float | None
capacity_m3: float | None
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

period_id: str
system_id: str
class fhops.planning.tactical_operational.models.FleetOption(*, option_id, system_id, purchase_period_id, purchase_cost=0.0, capacity_m3_per_period, max_units=0, economic_life_periods=1)[source]

Bases: BaseModel

Optional fleet acquisition that adds system capacity over an economic life.

Parameters:
capacity_m3_per_period: float
economic_life_periods: int
max_units: int
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

option_id: str
purchase_cost: float
purchase_period_id: str
system_id: str
class fhops.planning.tactical_operational.models.HarvestSystemOption(*, option_id, block_id, system_id, period_id, eligible=True, min_area_ha=0.0, max_area_ha=None, variable_cost_per_m3=0.0, fixed_cost=0.0, productivity_m3_per_period=None)[source]

Bases: BaseModel

Eligible block × system × period harvest option with quantity and cost bounds.

Parameters:
block_id: str
eligible: bool
fixed_cost: float
max_area_ha: float | None
min_area_ha: float
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

option_id: str
period_id: str
productivity_m3_per_period: float | None
system_id: str
variable_cost_per_m3: float
class fhops.planning.tactical_operational.models.InitialInventory(*, facility_id, product_id, opening_m3)[source]

Bases: BaseModel

Opening product inventory at a facility.

Parameters:
facility_id: str
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

opening_m3: float
product_id: str
class fhops.planning.tactical_operational.models.PeriodLevel(value)[source]

Bases: StrEnum

Supported tactical–operational period resolutions.

DAY = 'day'
FOUR_WEEK = 'four_week'
MONTH = 'month'
SEASON = 'season'
SHIFT = 'shift'
WEEK = 'week'
YEAR = 'year'
class fhops.planning.tactical_operational.models.PlanningPeriod(*, period_id, level, sequence, start_date, end_date, duration_days=None, available_hours=None, parent_period_id=None, discount_factor=1.0, season_tags=<factory>)[source]

Bases: BaseModel

Aggregate planning period with optional hierarchy and discounting metadata.

Parameters:
available_hours: float | None
discount_factor: float
duration_days: int | None
end_date: date
level: PeriodLevel
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

parent_period_id: str | None
period_id: str
season_tags: list[str]
sequence: int
start_date: date
class fhops.planning.tactical_operational.models.PlanningUnit(*, block_id, gross_area_ha, operable_area_ha, forest_class=None, initial_state=None, product_yields_m3_per_ha=<factory>)[source]

Bases: BaseModel

Aggregate harvestable unit with area and product yields.

Parameters:
block_id: str
forest_class: str | None
gross_area_ha: float
initial_state: str | None
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

operable_area_ha: float
product_yields_m3_per_ha: dict[str, float]
class fhops.planning.tactical_operational.models.Product(*, product_id, species_group=None, grade=None)[source]

Bases: BaseModel

A product/species-grade tracked through harvest, transport, and facility inventory.

Parameters:
  • product_id (str)

  • species_group (str | None)

  • grade (str | None)

grade: str | None
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

product_id: str
species_group: str | None
class fhops.planning.tactical_operational.models.RoadDependency(*, road_id, depends_on_road_id)[source]

Bases: BaseModel

Directed prerequisite relationship between two road projects.

Parameters:
  • road_id (str)

  • depends_on_road_id (str)

depends_on_road_id: str
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

road_id: str
class fhops.planning.tactical_operational.models.RoadProject(*, road_id, build_cost=0.0, maintenance_cost_per_period=0.0, earliest_period_id=None, capacity_m3_per_period=None)[source]

Bases: BaseModel

Candidate road project with activation timing, cost, and throughput capacity.

Parameters:
  • road_id (str)

  • build_cost (Annotated[float, Ge(ge=0)])

  • maintenance_cost_per_period (Annotated[float, Ge(ge=0)])

  • earliest_period_id (str | None)

  • capacity_m3_per_period (Annotated[float | None, Ge(ge=0)])

build_cost: float
capacity_m3_per_period: float | None
earliest_period_id: str | None
maintenance_cost_per_period: float
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

road_id: str
class fhops.planning.tactical_operational.models.SilvicultureTransition(*, transition_id, block_id, system_id, activity_id, earliest_period_id, cost_per_ha=0.0, required=True, next_state=None)[source]

Bases: BaseModel

Required follow-up activity generated by a block/system harvest decision.

Parameters:
activity_id: str
block_id: str
cost_per_ha: float
earliest_period_id: str
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

next_state: str | None
required: bool
system_id: str
transition_id: str
class fhops.planning.tactical_operational.models.TacticalOperationalScenario(*, name, planning_level='tactical_operational', schema_version='0.1.0', parent=None, overlay_id=None, source_hash=None, economics=<factory>, periods, products, planning_units, harvest_system_options, fleet_capacity=<factory>, facilities, facility_demand=<factory>, initial_inventory=<factory>, transport_arcs=<factory>, external_supply=<factory>, roads=<factory>, road_dependencies=<factory>, block_road_access=<factory>, silviculture_transitions=<factory>, fleet_options=<factory>)[source]

Bases: BaseModel

Validated aggregate planning contract for TOPM-inspired FHOPS scenarios.

The schema uses long-form, dimension-flexible tables. Cross-validation checks all period, product, block, system-option, facility, transport, inventory, and external-supply references.

Parameters:
block_road_access: list[BlockRoadAccess]
dimension_summary()[source]

Return model-dimension counts used by CLI validation and telemetry.

Return type:

dict[str, int]

economics: Economics
external_supply: list[ExternalSupply]
facilities: list[Facility]
facility_demand: list[FacilityDemand]
fleet_capacity: list[FleetCapacity]
fleet_options: list[FleetOption]
harvest_system_options: list[HarvestSystemOption]
initial_inventory: list[InitialInventory]
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str
overlay_id: str | None
parent: str | None
periods: list[PlanningPeriod]
planning_level: str
planning_units: list[PlanningUnit]
products: list[Product]
road_dependencies: list[RoadDependency]
roads: list[RoadProject]
schema_version: str
silviculture_transitions: list[SilvicultureTransition]
source_hash: str | None
to_dict()[source]

Return a JSON-compatible round-trip representation of the scenario.

Return type:

dict[str, Any]

transport_arcs: list[TransportArc]
class fhops.planning.tactical_operational.models.TransportArc(*, arc_id, origin_id, destination_id, product_id, mode='truck', period_id, cost_per_m3=0.0, capacity_m3=None, distance_km=None)[source]

Bases: BaseModel

Eligible product flow arc from a block/origin to a facility for one period.

Parameters:
  • arc_id (str)

  • origin_id (str)

  • destination_id (str)

  • product_id (str)

  • mode (str)

  • period_id (str)

  • cost_per_m3 (Annotated[float, Ge(ge=0)])

  • capacity_m3 (Annotated[float | None, Ge(ge=0)])

  • distance_km (Annotated[float | None, Ge(ge=0)])

arc_id: str
capacity_m3: float | None
cost_per_m3: float
destination_id: str
distance_km: float | None
mode: str
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

origin_id: str
period_id: str
product_id: str

YAML/CSV loaders for tactical–operational planning scenarios.

fhops.planning.tactical_operational.io.load_tactical_operational_scenario(yaml_path)[source]

Load and validate a TOPM-inspired tactical–operational scenario.

The loader accepts either inline YAML sections or a data: mapping from section names to CSV files. topm-mini-style specifications may use fixture_id/specification_version; those aliases are normalized to name and schema_version before Pydantic validation.

Parameters:

yaml_path (str | Path)

Return type:

TacticalOperationalScenario

fhops.planning.tactical_operational.io.tactical_scenario_dimensions(scenario)[source]

Return model dimension counts for CLI output and telemetry.

Parameters:

scenario (TacticalOperationalScenario)

Return type:

dict[str, int]

fhops.planning.tactical_operational.io.tactical_scenario_to_dict(scenario)[source]

Serialize a tactical–operational scenario to a JSON-compatible dictionary.

Parameters:

scenario (TacticalOperationalScenario)

Return type:

dict[str, Any]

Period templates and hierarchy helpers for tactical–operational planning.

fhops.planning.tactical_operational.time.children_by_parent(periods)[source]

Group child periods by parent period ID for roll-up reporting.

Parameters:

periods (list[PlanningPeriod])

Return type:

dict[str, list[PlanningPeriod]]

fhops.planning.tactical_operational.time.four_week_periods(year, *, start_month=1, start_day=1, periods=13, discount_rate_per_year=0.0)[source]

Build thirteen (or a custom count of) four-week planning periods for a calendar year.

The final period is truncated at December 31 when the accounting calendar overflows the year. Discount factors are assigned at each period start using an effective annual rate.

Parameters:
  • year (int)

  • start_month (int)

  • start_day (int)

  • periods (int)

  • discount_rate_per_year (float)

Return type:

list[PlanningPeriod]

fhops.planning.tactical_operational.time.seasonal_periods(year, *, discount_rate_per_year=0.0)[source]

Build a conventional four-season calendar-year period template.

Parameters:
  • year (int)

  • discount_rate_per_year (float)

Return type:

list[PlanningPeriod]

fhops.planning.tactical_operational.time.validate_period_hierarchy(periods)[source]

Validate uniqueness, parent references, and chronological ordering of a period set.

Parameters:

periods (list[PlanningPeriod])

Return type:

None

Scenario overlays, diffs, batch runs, and reports for tactical–operational planning.

fhops.planning.tactical_operational.scenario.apply_tactical_overlay_payload(base, overlay)[source]

Merge a sparse tactical overlay into a base scenario payload.

List sections are merged by their declared identifier field(s): matching rows are deep-updated, new rows are appended, and _remove may contain identifiers to delete. Scalar sections such as name and economics are deep-updated recursively.

Parameters:
Return type:

dict[str, Any]

fhops.planning.tactical_operational.scenario.diff_tactical_scenarios(base, candidate)[source]

Return a row-per-field diff between two validated tactical scenarios.

Parameters:
Return type:

DataFrame

fhops.planning.tactical_operational.scenario.load_tactical_overlay_scenario(base_path, overlay_path)[source]

Load a base tactical scenario and apply a sparse overlay YAML.

Parameters:
Return type:

TacticalOperationalScenario

fhops.planning.tactical_operational.scenario.markdown_tactical_summary(result)[source]

Render solver status and objective decomposition as a compact Markdown report.

Parameters:

result (dict[str, Any])

Return type:

str

fhops.planning.tactical_operational.scenario.read_yaml_mapping(path)[source]

Read a YAML mapping from disk.

Parameters:

path (str | Path)

Return type:

dict[str, Any]

fhops.planning.tactical_operational.scenario.scenario_content_hash(payload)[source]

Return a deterministic SHA-256 hash for a scenario payload.

Parameters:

payload (dict[str, Any])

Return type:

str

fhops.planning.tactical_operational.scenario.solve_tactical_batch_manifest(manifest_path, *, out_dir)[source]

Solve a manifest of tactical scenario cases and write per-case plus comparison outputs.

Manifest YAML format:

cases:
  - case_id: base
    scenario: path/to/base.yaml
  - case_id: low-demand
    scenario: path/to/base.yaml
    overlay: path/to/overlay.yaml
    enable_roads: true
Parameters:
Return type:

DataFrame

fhops.planning.tactical_operational.scenario.write_tactical_report(result, out_dir, *, formats='csv,markdown')[source]

Write normalized tactical result tables and a Markdown objective summary.

Parameters:
Return type:

dict[str, Path]

fhops.planning.tactical_operational.scenario.write_tactical_scenario_yaml(scenario, path)[source]

Write a validated tactical scenario as a human-readable YAML mapping.

Parameters:
Return type:

None

Tactical→operational handoff and rolling-state helpers for Phase 6.

class fhops.planning.tactical_operational.integration.TacticalCommitment(block_id, system_id, period_id, area_ha, total_volume_m3, product_volumes_m3=<factory>)[source]

Bases: object

A selected aggregate block/system/period commitment.

Parameters:
area_ha: float
block_id: str
period_id: str
product_volumes_m3: dict[str, float]
system_id: str
total_volume_m3: float
class fhops.planning.tactical_operational.integration.TacticalRollingState(remaining_area_ha, remaining_product_volume_m3, facility_inventory_m3, active_roads, fleet_units, cumulative_costs, commitments)[source]

Bases: object

Aggregate state carried between tactical–operational planning iterations.

Parameters:
active_roads: set[str]
commitments: list[TacticalCommitment]
cumulative_costs: dict[str, float]
facility_inventory_m3: dict[tuple[str, str], float]
fleet_units: dict[str, int]
remaining_area_ha: dict[str, float]
remaining_product_volume_m3: dict[tuple[str, str], float]
fhops.planning.tactical_operational.integration.apply_operational_realization(state, assignments, *, yield_per_ha, block_map=None)[source]

Update block/product state from realized operational assignment production.

yield_per_ha maps operational block IDs to total m³/ha. Production is treated as aggregate delivered volume unless callers pre-aggregate product-specific flows and update facility_inventory_m3 separately.

Parameters:
  • state (TacticalRollingState)

  • assignments (DataFrame)

  • yield_per_ha (dict[str, float])

  • block_map (dict[str, str] | None)

Return type:

TacticalRollingState

fhops.planning.tactical_operational.integration.build_tactical_rolling_state(scenario, result)[source]

Build a rolling state snapshot from a tactical scenario and solve result.

Parameters:
Return type:

TacticalRollingState

fhops.planning.tactical_operational.integration.commitments_from_result(result)[source]

Extract selected harvest commitments from a tactical solve result payload.

Parameters:

result (dict[str, Any])

Return type:

list[TacticalCommitment]

fhops.planning.tactical_operational.integration.compile_business_window_scenario(base, commitments, *, block_map=None, start_day=1, horizon_days=None)[source]

Compile tactical commitments into a filtered operational scenario.

The base operational scenario provides machines, calendars, production rates, landings, and mobilisation tables. Tactical commitments select eligible blocks and override their harvest system IDs. Block windows are clamped to the requested business window without rebasing days.

Parameters:
  • base (Scenario)

  • commitments (list[TacticalCommitment])

  • block_map (dict[str, str] | None)

  • start_day (int)

  • horizon_days (int | None)

Return type:

Scenario

fhops.planning.tactical_operational.integration.write_operational_scenario_bundle(scenario, out_dir)[source]

Write an operational scenario object as a YAML/CSV bundle compatible with load_scenario.

Parameters:
  • scenario (Scenario)

  • out_dir (str | Path)

Return type:

Path

Synthetic scale fixtures and benchmark helpers for tactical–operational planning.

class fhops.planning.tactical_operational.scale.TacticalScaleConfig(num_blocks=100, years=1, periods_per_year=4, num_products=2, num_facilities=2, num_systems=2, seed=44, demand_fraction=0.25)[source]

Bases: object

Configuration for a deterministic tactical–operational scale scenario.

Parameters:
  • num_blocks (int)

  • years (int)

  • periods_per_year (int)

  • num_products (int)

  • num_facilities (int)

  • num_systems (int)

  • seed (int)

  • demand_fraction (float)

demand_fraction: float = 0.25
num_blocks: int = 100
num_facilities: int = 2
num_products: int = 2
num_systems: int = 2
periods_per_year: int = 4
seed: int = 44
years: int = 1
fhops.planning.tactical_operational.scale.generate_tactical_scale_scenario(config)[source]

Generate a deterministic TOPM-shaped scale scenario.

The generator is intentionally simple and synthetic: it creates a rectangular block × system × period eligibility grid with product yields, fleet capacity, facility demand, transport arcs, and optional outside supply. It is meant for model-size/runtime envelopes, not ecological realism.

Parameters:

config (TacticalScaleConfig)

Return type:

TacticalOperationalScenario

fhops.planning.tactical_operational.scale.run_tactical_scale_benchmark(configs, *, solver='highs', time_limit=None, gap=None)[source]

Solve generated tactical scenarios and return scale/timing/model-size telemetry.

Parameters:
  • configs (list[TacticalScaleConfig])

  • solver (str)

  • time_limit (int | None)

  • gap (float | None)

Return type:

DataFrame

fhops.planning.tactical_operational.scale.write_tactical_scale_benchmark(frame, out_dir)[source]

Write benchmark summary CSV/JSON/Markdown files.

Parameters:
  • frame (DataFrame)

  • out_dir (str | Path)

Return type:

dict[str, Path]