# Opex Planner The Opex Planner captures recurring operational costs, applies escalation/discount assumptions, and persists calculation snapshots for projects and scenarios. It satisfies the Operational Expenditure tooling defined in [FR-014](../requirements/FR-014.md). ## Access Paths - **Workspace sidebar**: Navigate to _Workspace → Opex Planner_. - **Scenario detail header**: Use the _Processing Opex Planner_ button to open the planner pre-loaded with the active project/scenario context. ## Prerequisites - Authenticated CalMiner account with viewer or higher access to the target project or scenario. - Baseline scenario/project metadata (currency, discount rate, primary resource) to seed form defaults when IDs are provided. ## Planner Workflow 1. **Open the planner** via the sidebar or scenario action. Query parameters (`project_id`, `scenario_id`) load project and scenario metadata plus the latest persisted snapshot, when available. 2. **Review defaults** in the global parameters panel. Currency, discount rate, evaluation horizon, and persist toggle derive from the scenario, project, or system defaults surfaced by `_prepare_opex_context` in `routes/calculations.py`. 3. **Capture opex components** in the components table. Each row records the details listed in the _Component Fields_ table below. Rows support dynamic add/remove interactions and inline validation messages for missing data. 4. **Adjust global parameters** such as escalation percentage, discount rate, evaluation horizon, and whether escalation applies to the timeline. Parameter defaults derive from the active scenario, project, or environment configuration. 5. **Add optional snapshot notes** that persist alongside stored results and appear in the snapshot history UI (planned) and API responses. 6. **Run the calculation** with _Save & Calculate_. The POST handler validates input via `OpexCalculationRequest`, calls `services.calculations.calculate_opex`, and repopulates the form with normalized data. When validation fails, the page returns a 422 status with component-level alerts. 7. **Review results** in the summary cards and detailed breakdowns: total annual opex, per-ton cost, category totals, escalated timeline table, and the normalized component listing. Alerts surface validation issues or component-level notices above the table. ### Component Fields | Field | Description | Notes | | -------------- | --------------------------------------------------------------------------- | ------------------------------------------------------ | | `category` | Cost grouping (`labor`, `materials`, `energy`, `maintenance`, `other`). | Drives category totals and stacked charts. | | `name` | Descriptive label for the component. | Displayed in breakdown tables and persisted snapshots. | | `unit_cost` | Cost per unit in the selected currency. | Decimal, >= 0. | | `quantity` | Units consumed per frequency period. | Decimal, >= 0. | | `frequency` | Recurrence cadence (`daily`, `weekly`, `monthly`, `quarterly`, `annually`). | Controls timeline expansion scaling. | | `currency` | ISO-4217 currency code. | Must match parameters currency when persisting. | | `period_start` | First evaluation period (1-indexed) to include the component. | Optional; defaults to 1. | | `period_end` | Final evaluation period to include the component. | Optional; defaults to evaluation horizon. | | `notes` | Free-form text stored with the component. | Optional; hidden by default in HTML form. | ### Global Parameters | Parameter | Purpose | | -------------------------- | --------------------------------------------------------------------------------- | | `currency_code` | Normalized currency for totals and timeline. | | `escalation_pct` | Annual escalation applied to eligible components when `apply_escalation` is true. | | `discount_rate_pct` | Discount rate surfaced for downstream profitability workflows. | | `evaluation_horizon_years` | Number of years to expand the timeline. | | `apply_escalation` | Boolean flag enabling escalation across the timeline. | | `persist` (options) | Persists the calculation when project/scenario context is present. | | `snapshot_notes` (options) | Optional metadata attached to stored snapshots. | ## Persistence Behaviour - When `project_id` or `scenario_id` is supplied and `options[persist]` evaluates true (default for HTML form), snapshots are stored via `ProjectOpexSnapshot` and `ScenarioOpexSnapshot` repositories before rendering the response. - Snapshot payloads capture normalized component entries, parameters, escalation settings, calculated totals, and optional notes, enabling historical comparison and downstream profitability inputs. - JSON clients can disable persistence by sending `"options": {"persist": false}` or omit identifiers for ad hoc calculations. ## API Reference | Route | Method | Description | | ------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | `/calculations/opex` (`calculations.opex_form`) | GET | Renders the planner template with defaults and any latest snapshot context for the supplied project/scenario IDs. | | `/calculations/opex` (`calculations.opex_submit`) | POST | Accepts form or JSON payload matching `OpexCalculationRequest`, returns HTML or JSON results, and persists snapshots when context is present. | Key schemas and services: - `schemas.calculations.OpexComponentInput`, `OpexParameters`, `OpexCalculationRequest`, `OpexCalculationResult` - `services.calculations.calculate_opex` - `_prepare_opex_context`, `_persist_opex_snapshots`, and related helpers in `routes/calculations.py` ### Example JSON Request ```json { "components": [ { "category": "energy", "name": "Processing Plant Power", "unit_cost": "480.00", "quantity": "1.0", "frequency": "monthly", "currency": "USD", "period_start": 1, "period_end": 12 } ], "parameters": { "currency_code": "USD", "escalation_pct": "3.0", "discount_rate_pct": "8.0", "evaluation_horizon_years": 10, "apply_escalation": true }, "options": { "persist": true, "snapshot_notes": "Baseline processing OPEX" } } ``` The response payload mirrors `OpexCalculationResult`, returning normalized components, aggregated totals, timeline series, and snapshot metadata when persistence is enabled. ## Troubleshooting - **Validation errors**: The planner surfaces field-level issues above the component table. JSON responses include `errors` and `message` keys mirroring Pydantic validation output. - **Currency mismatch**: All component rows must share the same currency. Adjust row currencies or the default currency in the parameters panel to resolve mismatches enforced by the service layer. - **Timeline coverage**: Ensure `period_start` and `period_end` fall within the evaluation horizon. Rows outside the horizon are ignored in the timeline though they still influence totals. ## Related Resources - [Opex planner template](../../calminer/templates/scenarios/opex.html) - [Calculations route handlers](../../calminer/routes/calculations.py) - [Opex schemas and results](../../calminer/schemas/calculations.py) - [Service implementation and tests](../../calminer/services/calculations.py), [tests/services/test_calculations_opex.py](../../calminer/tests/services/test_calculations_opex.py) - [Integration coverage](../../calminer/tests/integration/test_opex_calculations.py)