{
"cells": [
{
"cell_type": "markdown",
"id": "ddd4c056",
"metadata": {},
"source": [
"# Landing Congestion Analysis\n",
"\n",
"Explore the impact of landing shock events on stochastic playback outputs for the synthetic medium scenario."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "16d5fc8e",
"metadata": {
"execution": {
"iopub.execute_input": "2026-10-05T12:57:04.800215Z",
"iopub.status.busy": "2026-10-05T12:57:04.800096Z",
"iopub.status.idle": "2026-10-05T12:57:07.907173Z",
"shell.execute_reply": "2026-10-05T12:57:07.906310Z"
}
},
"outputs": [],
"source": [
"import sys\n",
"from pathlib import Path\n",
"\n",
"PROJECT_ROOT = Path.cwd().resolve()\n",
"while PROJECT_ROOT != PROJECT_ROOT.parent and not (PROJECT_ROOT / \"pyproject.toml\").exists():\n",
" PROJECT_ROOT = PROJECT_ROOT.parent\n",
"if not (PROJECT_ROOT / \"pyproject.toml\").exists():\n",
" raise RuntimeError(\n",
" \"Notebook must be executed within a FHOPS checkout (pyproject.toml not found).\"\n",
" )\n",
"if str(PROJECT_ROOT) not in sys.path:\n",
" sys.path.insert(0, str(PROJECT_ROOT))\n",
"\n",
"from docs.examples.analytics import utils\n",
"from fhops.scenario.synthetic import SyntheticDatasetConfig, sampling_config_for\n",
"\n",
"SCENARIO = PROJECT_ROOT / \"examples/synthetic/medium/scenario.yaml\"\n",
"ASSIGNMENTS = PROJECT_ROOT / \"docs/examples/analytics/data/synthetic_medium_sa_assignments.csv\"\n",
"\n",
"config = SyntheticDatasetConfig(\n",
" name=\"synthetic-medium\",\n",
" tier=\"medium\",\n",
" num_blocks=8,\n",
" num_days=12,\n",
" num_machines=4,\n",
")\n",
"\n",
"baseline_sampling = sampling_config_for(config).model_copy()\n",
"baseline_sampling.landing.enabled = False\n",
"baseline_tables, baseline_sampling = utils.run_stochastic_summary(\n",
" SCENARIO, ASSIGNMENTS, sampling_config=baseline_sampling\n",
")\n",
"\n",
"shock_sampling = baseline_sampling.model_copy()\n",
"shock_sampling.landing.enabled = True\n",
"shock_sampling.landing.probability = 0.3\n",
"shock_sampling.landing.capacity_multiplier_range = (0.35, 0.7)\n",
"shock_sampling.landing.duration_days = 3\n",
"shock_tables, shock_sampling = utils.run_stochastic_summary(\n",
" SCENARIO, ASSIGNMENTS, sampling_config=shock_sampling\n",
")"
]
},
{
"cell_type": "markdown",
"id": "45420d70",
"metadata": {},
"source": [
"## Baseline Ensemble Snapshot"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "9dcd3bd4",
"metadata": {
"execution": {
"iopub.execute_input": "2026-10-05T12:57:07.909164Z",
"iopub.status.busy": "2026-10-05T12:57:07.908997Z",
"iopub.status.idle": "2026-10-05T12:57:07.918025Z",
"shell.execute_reply": "2026-10-05T12:57:07.917449Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" day | \n",
" shift_id | \n",
" machine_id | \n",
" machine_role | \n",
" sample_id | \n",
" production_units | \n",
" total_hours | \n",
" idle_hours | \n",
" mobilisation_cost | \n",
" sequencing_violations | \n",
" blackout_conflicts | \n",
" available_hours | \n",
" utilisation_ratio | \n",
" downtime_hours | \n",
" downtime_events | \n",
" weather_severity_total | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 2 | \n",
" S1 | \n",
" M4 | \n",
" forwarder | \n",
" 0 | \n",
" 5.1558 | \n",
" 8.0 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0 | \n",
" 8.0 | \n",
" 1.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0.4 | \n",
"
\n",
" \n",
" | 1 | \n",
" 8 | \n",
" S1 | \n",
" M1 | \n",
" harvester | \n",
" 0 | \n",
" 15.0340 | \n",
" 8.0 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0 | \n",
" 8.0 | \n",
" 1.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0.0 | \n",
"
\n",
" \n",
" | 2 | \n",
" 2 | \n",
" S1 | \n",
" M1 | \n",
" harvester | \n",
" 1 | \n",
" 12.6600 | \n",
" 8.0 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0 | \n",
" 8.0 | \n",
" 1.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0.0 | \n",
"
\n",
" \n",
" | 3 | \n",
" 8 | \n",
" S1 | \n",
" M1 | \n",
" harvester | \n",
" 1 | \n",
" 9.0204 | \n",
" 8.0 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0 | \n",
" 8.0 | \n",
" 1.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0.4 | \n",
"
\n",
" \n",
" | 4 | \n",
" 2 | \n",
" S1 | \n",
" M4 | \n",
" forwarder | \n",
" 2 | \n",
" 8.5930 | \n",
" 8.0 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0 | \n",
" 8.0 | \n",
" 1.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0.0 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" day shift_id machine_id machine_role sample_id production_units \\\n",
"0 2 S1 M4 forwarder 0 5.1558 \n",
"1 8 S1 M1 harvester 0 15.0340 \n",
"2 2 S1 M1 harvester 1 12.6600 \n",
"3 8 S1 M1 harvester 1 9.0204 \n",
"4 2 S1 M4 forwarder 2 8.5930 \n",
"\n",
" total_hours idle_hours mobilisation_cost sequencing_violations \\\n",
"0 8.0 0.0 0.0 0 \n",
"1 8.0 0.0 0.0 0 \n",
"2 8.0 0.0 0.0 0 \n",
"3 8.0 0.0 0.0 0 \n",
"4 8.0 0.0 0.0 0 \n",
"\n",
" blackout_conflicts available_hours utilisation_ratio downtime_hours \\\n",
"0 0 8.0 1.0 0.0 \n",
"1 0 8.0 1.0 0.0 \n",
"2 0 8.0 1.0 0.0 \n",
"3 0 8.0 1.0 0.0 \n",
"4 0 8.0 1.0 0.0 \n",
"\n",
" downtime_events weather_severity_total \n",
"0 0 0.4 \n",
"1 0 0.0 \n",
"2 0 0.0 \n",
"3 0 0.4 \n",
"4 0 0.0 "
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"baseline_tables.shift.head()"
]
},
{
"cell_type": "markdown",
"id": "12a88345",
"metadata": {},
"source": [
"## Landing Shock Ensemble Snapshot"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "97c7830c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-10-05T12:57:07.919473Z",
"iopub.status.busy": "2026-10-05T12:57:07.919359Z",
"iopub.status.idle": "2026-10-05T12:57:07.925913Z",
"shell.execute_reply": "2026-10-05T12:57:07.925369Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" day | \n",
" shift_id | \n",
" machine_id | \n",
" machine_role | \n",
" sample_id | \n",
" production_units | \n",
" total_hours | \n",
" idle_hours | \n",
" mobilisation_cost | \n",
" sequencing_violations | \n",
" blackout_conflicts | \n",
" available_hours | \n",
" utilisation_ratio | \n",
" downtime_hours | \n",
" downtime_events | \n",
" weather_severity_total | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 2 | \n",
" S1 | \n",
" M4 | \n",
" forwarder | \n",
" 0 | \n",
" 5.15580 | \n",
" 8.0 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0 | \n",
" 8.0 | \n",
" 1.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0.4 | \n",
"
\n",
" \n",
" | 1 | \n",
" 8 | \n",
" S1 | \n",
" M1 | \n",
" harvester | \n",
" 0 | \n",
" 15.03400 | \n",
" 8.0 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0 | \n",
" 8.0 | \n",
" 1.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0.0 | \n",
"
\n",
" \n",
" | 2 | \n",
" 2 | \n",
" S1 | \n",
" M1 | \n",
" harvester | \n",
" 1 | \n",
" 12.66000 | \n",
" 8.0 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0 | \n",
" 8.0 | \n",
" 1.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0.0 | \n",
"
\n",
" \n",
" | 3 | \n",
" 8 | \n",
" S1 | \n",
" M1 | \n",
" harvester | \n",
" 1 | \n",
" 9.02040 | \n",
" 8.0 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0 | \n",
" 8.0 | \n",
" 1.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0.4 | \n",
"
\n",
" \n",
" | 4 | \n",
" 2 | \n",
" S1 | \n",
" M4 | \n",
" forwarder | \n",
" 2 | \n",
" 4.84338 | \n",
" 8.0 | \n",
" 0.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0 | \n",
" 8.0 | \n",
" 1.0 | \n",
" 0.0 | \n",
" 0 | \n",
" 0.0 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" day shift_id machine_id machine_role sample_id production_units \\\n",
"0 2 S1 M4 forwarder 0 5.15580 \n",
"1 8 S1 M1 harvester 0 15.03400 \n",
"2 2 S1 M1 harvester 1 12.66000 \n",
"3 8 S1 M1 harvester 1 9.02040 \n",
"4 2 S1 M4 forwarder 2 4.84338 \n",
"\n",
" total_hours idle_hours mobilisation_cost sequencing_violations \\\n",
"0 8.0 0.0 0.0 0 \n",
"1 8.0 0.0 0.0 0 \n",
"2 8.0 0.0 0.0 0 \n",
"3 8.0 0.0 0.0 0 \n",
"4 8.0 0.0 0.0 0 \n",
"\n",
" blackout_conflicts available_hours utilisation_ratio downtime_hours \\\n",
"0 0 8.0 1.0 0.0 \n",
"1 0 8.0 1.0 0.0 \n",
"2 0 8.0 1.0 0.0 \n",
"3 0 8.0 1.0 0.0 \n",
"4 0 8.0 1.0 0.0 \n",
"\n",
" downtime_events weather_severity_total \n",
"0 0 0.4 \n",
"1 0 0.0 \n",
"2 0 0.0 \n",
"3 0 0.4 \n",
"4 0 0.0 "
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"shock_tables.shift.head()"
]
},
{
"cell_type": "markdown",
"id": "6854bc62",
"metadata": {},
"source": [
"## KPI Comparison"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "fb6f580b",
"metadata": {
"execution": {
"iopub.execute_input": "2026-10-05T12:57:07.927189Z",
"iopub.status.busy": "2026-10-05T12:57:07.927067Z",
"iopub.status.idle": "2026-10-05T12:57:07.933625Z",
"shell.execute_reply": "2026-10-05T12:57:07.933024Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" scenario | \n",
" mean_production | \n",
" mean_downtime_hours | \n",
" mean_utilisation | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" baseline | \n",
" 20.266083 | \n",
" 0.0 | \n",
" 1.0 | \n",
"
\n",
" \n",
" | 1 | \n",
" landing_shock | \n",
" 19.929846 | \n",
" 0.0 | \n",
" 1.0 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" scenario mean_production mean_downtime_hours mean_utilisation\n",
"0 baseline 20.266083 0.0 1.0\n",
"1 landing_shock 19.929846 0.0 1.0"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import pandas as pd\n",
"\n",
"comparison = pd.DataFrame(\n",
" {\n",
" \"scenario\": [\"baseline\", \"landing_shock\"],\n",
" \"mean_production\": [\n",
" baseline_tables.shift.groupby(\"sample_id\")[\"production_units\"].sum().mean(),\n",
" shock_tables.shift.groupby(\"sample_id\")[\"production_units\"].sum().mean(),\n",
" ],\n",
" \"mean_downtime_hours\": [\n",
" baseline_tables.shift[\"downtime_hours\"].mean(),\n",
" shock_tables.shift[\"downtime_hours\"].mean(),\n",
" ],\n",
" \"mean_utilisation\": [\n",
" baseline_tables.shift[\"utilisation_ratio\"].mean(),\n",
" shock_tables.shift[\"utilisation_ratio\"].mean(),\n",
" ],\n",
" }\n",
")\n",
"comparison"
]
},
{
"cell_type": "markdown",
"id": "4ab289eb",
"metadata": {},
"source": [
"## Production Distributions"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e87408e5",
"metadata": {
"execution": {
"iopub.execute_input": "2026-10-05T12:57:07.934944Z",
"iopub.status.busy": "2026-10-05T12:57:07.934832Z",
"iopub.status.idle": "2026-10-05T12:57:07.949748Z",
"shell.execute_reply": "2026-10-05T12:57:07.949282Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"baseline_prod = baseline_tables.shift.groupby(\"sample_id\")[\"production_units\"].sum()\n",
"baseline_chart = utils.plot_distribution(\n",
" baseline_prod, title=\"Baseline Production Distribution\", xlabel=\"Production Units\"\n",
")\n",
"baseline_chart"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "bafa8bd1",
"metadata": {
"execution": {
"iopub.execute_input": "2026-10-05T12:57:07.951323Z",
"iopub.status.busy": "2026-10-05T12:57:07.951181Z",
"iopub.status.idle": "2026-10-05T12:57:07.957959Z",
"shell.execute_reply": "2026-10-05T12:57:07.957278Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"\n",
""
],
"text/plain": [
"alt.Chart(...)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"shock_prod = shock_tables.shift.groupby(\"sample_id\")[\"production_units\"].sum()\n",
"shock_chart = utils.plot_distribution(\n",
" shock_prod, title=\"Landing Shock Production Distribution\", xlabel=\"Production Units\"\n",
")\n",
"shock_chart"
]
}
],
"metadata": {
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.16"
}
},
"nbformat": 4,
"nbformat_minor": 5
}