fhops.evaluation Package
The evaluation layer turns solver assignments into KPI-rich reports. It houses deterministic playback, stochastic extensions, and KPI calculators. Use it to:
Convert solver outputs into shift/day summaries (
fhops.evaluation.playback.run_playback()).Compute KPI bundles for CLI reports or notebooks (
fhops.evaluation.metrics.kpis.compute_kpis()).Export CSV/Parquet/Markdown summaries for documentation or telemetry.
Example:
from fhops.scenario.io import load_scenario
from fhops.scenario.contract import Problem
from fhops.optimization.mip import solve_mip
from fhops.evaluation import compute_kpis
scenario = load_scenario("examples/tiny7/scenario.yaml")
problem = Problem.from_scenario(scenario)
mip_res = solve_mip(problem, time_limit=60)
kpis = compute_kpis(problem, mip_res["assignments"])
print(kpis["total_production"], kpis["mobilisation_cost"])
Evaluation layer (playback, metrics, reporting).
- class fhops.evaluation.DaySummary(day, available_hours=0.0, total_hours=0.0, production_units=0.0, mobilisation_cost=0.0, completed_blocks=0, idle_hours=None, blackout_conflicts=0, sequencing_violations=0, utilisation_ratio=None, sample_id=0, downtime_hours=0.0, downtime_events=0, weather_severity_total=0.0)[source]
Bases:
objectAggregated metrics per day across machines.
- Parameters:
day (int)
available_hours (float)
total_hours (float)
production_units (float)
mobilisation_cost (float)
completed_blocks (int)
idle_hours (float | None)
blackout_conflicts (int)
sequencing_violations (int)
utilisation_ratio (float | None)
sample_id (int)
downtime_hours (float)
downtime_events (int)
weather_severity_total (float)
- class fhops.evaluation.DowntimeEvent(config)[source]
Bases:
objectRandomly remove assignments to simulate downtime.
- class fhops.evaluation.DowntimeEventConfig(*, enabled=True, seed_offset=0, probability=0.15, mean_duration_hours=4.0, std_duration_hours=1.5, max_concurrent=None, target_machine_roles=None)[source]
Bases:
SamplingEventConfigDescribes downtime sampling parameters for machines.
- Parameters:
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.evaluation.EnsembleResult(base_result, samples)[source]
Bases:
objectAggregate of the baseline deterministic playback plus stochastic samples.
- Parameters:
base_result (PlaybackResult)
samples (list[PlaybackSample])
- base_result: PlaybackResult
- samples: list[PlaybackSample]
- class fhops.evaluation.KPIResult(totals=<factory>, shift_calendar=None, day_calendar=None, sequencing_debug=None)[source]
Bases:
Mapping[str,float|int|str]Structured KPI bundle with optional shift/day calendar attachments.
- Parameters:
- DAY_COLUMNS: ClassVar[tuple[str, ...]] = ('day', 'sample_id', 'production_units', 'total_hours', 'idle_hours', 'mobilisation_cost', 'completed_blocks', 'blackout_conflicts', 'sequencing_violations', 'available_hours', 'utilisation_ratio', 'downtime_hours', 'downtime_events', 'weather_severity_total')
- SHIFT_COLUMNS: ClassVar[tuple[str, ...]] = ('day', 'shift_id', 'machine_id', 'machine_role', 'sample_id', 'production_units', 'total_hours', 'idle_hours', 'mobilisation_cost', 'sequencing_violations', 'blackout_conflicts', 'available_hours', 'utilisation_ratio', 'downtime_hours', 'downtime_events', 'weather_severity_total')
- class fhops.evaluation.LandingShockConfig(*, enabled=True, seed_offset=0, probability=0.1, capacity_multiplier_range=(0.4, 0.8), duration_days=1, target_landing_ids=None)[source]
Bases:
SamplingEventConfigParameterises landing congestion shocks reducing capacity.
- Parameters:
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.evaluation.LandingShockEvent(config)[source]
Bases:
objectReduce landing throughput via random shocks.
- class fhops.evaluation.PlaybackConfig(respect_blackouts=True, infer_missing_shifts=True, include_idle_records=False)[source]
Bases:
objectTuning options for deterministic playback execution.
- class fhops.evaluation.PlaybackEvent(*args, **kwargs)[source]
Bases:
ProtocolStochastic event modifying assignments before playback summarisation.
- class fhops.evaluation.PlaybackRecord(day, shift_id, machine_id, block_id, hours_worked=None, production_units=None, mobilisation_cost=None, blackout_hit=False, landing_id=None, machine_role=None, downtime=False, weather_severity=None, metadata=<factory>, sample_id=0)[source]
Bases:
objectAtomic shift-level record produced by deterministic playback.
- Parameters:
- class fhops.evaluation.PlaybackResult(records, shift_summaries, day_summaries, config, sequencing_debug=None, sample_id=0, delivered_total=0.0, remaining_work_total=0.0)[source]
Bases:
objectContainer grouping playback outputs.
- Parameters:
records (Sequence[PlaybackRecord])
shift_summaries (Sequence[ShiftSummary])
day_summaries (Sequence[DaySummary])
config (PlaybackConfig)
sample_id (int)
delivered_total (float)
remaining_work_total (float)
- config: PlaybackConfig
- day_summaries: Sequence[DaySummary]
- records: Sequence[PlaybackRecord]
- shift_summaries: Sequence[ShiftSummary]
- class fhops.evaluation.PlaybackSample(sample_id, result)[source]
Bases:
objectContainer pairing a stochastic sample ID with its
PlaybackResult.- Parameters:
sample_id (int)
result (PlaybackResult)
- result: PlaybackResult
- class fhops.evaluation.SamplingConfig(*, samples=10, base_seed=123, downtime=<factory>, weather=<factory>, landing=<factory>)[source]
Bases:
BaseModelTop-level configuration for stochastic playback ensembles.
- Parameters:
samples (int)
base_seed (int)
downtime (DowntimeEventConfig)
weather (WeatherEventConfig)
landing (LandingShockConfig)
- downtime: DowntimeEventConfig
- landing: LandingShockConfig
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- weather: WeatherEventConfig
- class fhops.evaluation.SamplingContext(problem, sample_id, rng, config)[source]
Bases:
objectPer-sample execution context.
- Parameters:
problem (Problem)
sample_id (int)
rng (Generator)
config (SamplingConfig)
- config: SamplingConfig
- problem: Problem
- rng: Generator
- class fhops.evaluation.SamplingEventConfig(*, enabled=True, seed_offset=0)[source]
Bases:
BaseModelBase configuration shared by all stochastic events.
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.evaluation.ShiftSummary(day, shift_id, machine_id, machine_role=None, available_hours=0.0, total_hours=0.0, production_units=0.0, mobilisation_cost=0.0, idle_hours=None, blackout_conflicts=0, sequencing_violations=0, utilisation_ratio=None, sample_id=0, downtime_hours=0.0, downtime_events=0, weather_severity_total=0.0)[source]
Bases:
objectAggregated metrics per machine/shift.
- Parameters:
day (int)
shift_id (str)
machine_id (str)
machine_role (str | None)
available_hours (float)
total_hours (float)
production_units (float)
mobilisation_cost (float)
idle_hours (float | None)
blackout_conflicts (int)
sequencing_violations (int)
utilisation_ratio (float | None)
sample_id (int)
downtime_hours (float)
downtime_events (int)
weather_severity_total (float)
- class fhops.evaluation.WeatherEvent(config)[source]
Bases:
objectAdjust production based on weather severity.
- class fhops.evaluation.WeatherEventConfig(*, enabled=True, seed_offset=0, day_probability=0.2, severity_levels=<factory>, correlated_days=True, impact_window_days=1, affected_shifts=None)[source]
Bases:
SamplingEventConfigCaptures stochastic weather impacts affecting production rates.
- Parameters:
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- fhops.evaluation.assignments_to_records(problem, assignments)[source]
Convert solver assignments dataframe into playback records.
- Parameters:
problem (Problem)
assignments (DataFrame)
- Return type:
- fhops.evaluation.compute_kpis(pb, assignments)[source]
Compute production, mobilisation, utilisation, and sequencing KPIs from assignments.
- Parameters:
pb (Problem)
assignments (DataFrame)
- Return type:
- fhops.evaluation.compute_makespan_metrics(problem, shift_df, *, fallback_days=None, fallback_shift_keys=None)[source]
Compute makespan metrics (latest productive day/shift) given playback summaries.
- fhops.evaluation.compute_utilisation_metrics(shift_df, day_df=None)[source]
Compute utilisation KPI metrics from shift/day playback DataFrames.
- fhops.evaluation.day_dataframe(result)[source]
Return the day-level playback summaries as a DataFrame.
- Parameters:
result (PlaybackResult)
- Return type:
DataFrame
- fhops.evaluation.day_dataframe_from_ensemble(ensemble, *, include_base=False)[source]
Concatenate day-level summaries from an ensemble.
- Parameters:
ensemble (EnsembleResult)
include_base (bool)
- Return type:
DataFrame
- fhops.evaluation.export_playback(shift_df, day_df, *, shift_csv=None, day_csv=None, shift_parquet=None, day_parquet=None, summary_md=None)[source]
Write playback summaries to the requested outputs.
- Parameters:
shift_df (DataFrame) – DataFrames produced by
fhops.evaluation.playback.aggregates.shift_dataframe()andday_dataframe().day_df (DataFrame) – DataFrames produced by
fhops.evaluation.playback.aggregates.shift_dataframe()andday_dataframe().shift_csv (Path | None) – Optional paths for CSV exports.
day_csv (Path | None) – Optional paths for CSV exports.
shift_parquet (Path | None) – Optional paths for Parquet exports (requires
pyarroworfastparquet).day_parquet (Path | None) – Optional paths for Parquet exports (requires
pyarroworfastparquet).summary_md (Path | None) – Optional Markdown summary export (produced via
render_markdown_summary()).
- Returns:
Scalar metrics from
playback_summary_metrics()(samples, total production, etc.).- Return type:
- fhops.evaluation.machine_utilisation_summary(shift_df)[source]
Aggregate shift summaries into per-machine utilisation metrics.
- Parameters:
shift_df (DataFrame) – DataFrame produced by
shift_dataframe()or*_from_ensemble; must includetotal_hoursandavailable_hourscolumns.- Returns:
One row per
(sample_id, machine_id)with total/available hours, utilisation ratio, production, mobilisation cost, and constraint violation counts.- Return type:
pandas.DataFrame
- fhops.evaluation.playback_summary_metrics(shift_df, day_df)[source]
Compute scalar summary metrics (production, hours, mobilisation, utilisation).
- fhops.evaluation.render_markdown_summary(shift_df, day_df, metrics=None)[source]
Return a Markdown-formatted playback summary.
- fhops.evaluation.run_playback(problem, assignments, *, config=None, sample_id=0)[source]
Convert solver assignments into playback records and aggregated summaries.
- Parameters:
problem (Problem)
assignments (pd.DataFrame)
config (PlaybackConfig | None)
sample_id (int)
- Return type:
- fhops.evaluation.run_stochastic_playback(problem, assignments, *, sampling_config, events=None)[source]
Run stochastic playback over multiple samples.
- Parameters:
problem (Problem)
assignments (DataFrame)
sampling_config (SamplingConfig)
events (Iterable[PlaybackEvent] | None)
- Return type:
- fhops.evaluation.schedule_to_records(problem, schedule)[source]
Convert a heuristic Schedule plan into playback records.
- Parameters:
problem (Problem)
schedule (object)
- Return type:
- fhops.evaluation.shift_dataframe(result)[source]
Return the shift-level playback summaries as a DataFrame.
- Parameters:
result (PlaybackResult) –
PlaybackResultreturned byfhops.evaluation.playback.core.run_playback().- Return type:
DataFrame
- fhops.evaluation.shift_dataframe_from_ensemble(ensemble, *, include_base=False)[source]
Concatenate shift summaries from a stochastic ensemble.
- Parameters:
ensemble (EnsembleResult) – Result of
fhops.evaluation.playback.stochastic.run_stochastic_playback().include_base (bool) – When
True, include the base deterministic result in addition to samples.
- Return type:
DataFrame
- fhops.evaluation.summarise_days(shift_summaries, availability_map, completed_by_day, sample_id=0)[source]
Aggregate playback results to day-level summaries.
- Parameters:
shift_summaries (Iterable[ShiftSummary]) – Iterable produced by
summarise_shifts().availability_map (dict[tuple[int, str, str], float]) – Same map passed to
summarise_shifts; used to recover day-level availability totals.completed_by_day (dict[int, set[str]]) – Mapping
day -> set(block_id)indicating which blocks completed on each day (consumed for KPI reporting).sample_id (int) – Propagated through stochastic playback.
- Return type:
- fhops.evaluation.summarise_shifts(records, availability_map, *, include_idle=False, sample_id=0)[source]
Aggregate playback records to machine/shift summaries.
- Parameters:
records (Iterable[PlaybackRecord]) – Iterator of
PlaybackRecordinstances produced byassignments_to_records.availability_map (dict[tuple[int, str, str], float]) – Mapping
(day, shift_id, machine_id) -> available_hourscomputed via_compute_shift_availability; used to seed summaries and compute utilisation ratios.include_idle (bool) – When
True, emit summaries for every availability entry even if no work occurred (resulting intotal_hours = 0but preserving the availability baseline).sample_id (int) – Identifier propagated through stochastic playback so downstream aggregations can group rows.
- Return type:
KPI helpers for FHOPS schedules.
- class fhops.evaluation.metrics.kpis.KPIResult(totals=<factory>, shift_calendar=None, day_calendar=None, sequencing_debug=None)[source]
Bases:
Mapping[str,float|int|str]Structured KPI bundle with optional shift/day calendar attachments.
- Parameters:
- DAY_COLUMNS: ClassVar[tuple[str, ...]] = ('day', 'sample_id', 'production_units', 'total_hours', 'idle_hours', 'mobilisation_cost', 'completed_blocks', 'blackout_conflicts', 'sequencing_violations', 'available_hours', 'utilisation_ratio', 'downtime_hours', 'downtime_events', 'weather_severity_total')
- SHIFT_COLUMNS: ClassVar[tuple[str, ...]] = ('day', 'shift_id', 'machine_id', 'machine_role', 'sample_id', 'production_units', 'total_hours', 'idle_hours', 'mobilisation_cost', 'sequencing_violations', 'blackout_conflicts', 'available_hours', 'utilisation_ratio', 'downtime_hours', 'downtime_events', 'weather_severity_total')
- day_calendar: DataFrame | None
- get(k[, d]) D[k] if k in D, else d. d defaults to None.[source]
- shift_calendar: DataFrame | None
- to_dict()[source]
Return the scalar KPI totals as a plain dictionary.