Files
calminer/templates/scenarios/profitability.html
zwitschi fb6816de00 Add form styles and update button classes for consistency
- Introduced a new CSS file for form styles (forms.css) to enhance form layout and design.
- Removed deprecated button styles from imports.css and updated button classes across templates to use the new utility classes.
- Updated various templates to reflect the new button styles, ensuring a consistent look and feel throughout the application.
- Refactored form-related styles in main.css and removed redundant styles from projects.css and scenarios.css.
- Ensured responsive design adjustments for form actions in smaller viewports.
2025-11-13 21:18:32 +01:00

346 lines
14 KiB
HTML

{% extends "base.html" %}
{% block title %}Profitability Calculator · CalMiner{% endblock %}
{% block content %}
<nav class="breadcrumb">
<a href="{{ url_for('projects.project_list_page') }}">Projects</a>
{% if project %}
<a href="{{ url_for('projects.view_project', project_id=project.id) }}">{{ project.name }}</a>
{% endif %}
{% if scenario %}
<a href="{{ url_for('scenarios.view_scenario', scenario_id=scenario.id) }}">{{ scenario.name }}</a>
{% endif %}
<span aria-current="page">Profitability Calculator</span>
</nav>
<header class="page-header">
<div>
<h1>Profitability Calculator</h1>
<p class="text-muted">Evaluate revenue, costs, and key financial metrics for a scenario.</p>
</div>
<div class="header-actions">
{% if scenario_url %}
<a class="btn btn--secondary" href="{{ scenario_url }}">Scenario Overview</a>
{% elif project_url %}
<a class="btn btn--secondary" href="{{ project_url }}">Project Overview</a>
{% elif cancel_url %}
<a class="btn btn--secondary" href="{{ cancel_url }}">Back</a>
{% endif %}
{% if scenario_portfolio_url %}
<a class="btn btn--secondary" href="{{ scenario_portfolio_url }}">Scenario Portfolio</a>
{% endif %}
<button class="btn btn--primary" type="submit" form="profitability-form">Run Calculation</button>
</div>
</header>
{% if errors %}
<div class="alert alert-error">
<h2 class="sr-only">Submission errors</h2>
<ul>
{% for message in errors %}
<li>{{ message }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if notices %}
<div class="alert alert-info">
<ul>
{% for message in notices %}
<li>{{ message }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<div class="layout-two-column">
<section class="panel">
<h2>Input Parameters</h2>
<form id="profitability-form" class="form scenario-form" method="post" action="{{ form_action }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token or '' }}" />
<div class="form-grid">
<div class="form-group">
<label for="metal">Commodity</label>
<select id="metal" name="metal" required>
{% for metal in supported_metals %}
<option value="{{ metal.value }}" {% if data.metal == metal.value %}selected{% endif %}>{{ metal.label }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="ore_tonnage">Ore Tonnage (t)</label>
<input id="ore_tonnage" name="ore_tonnage" type="number" min="0" step="0.01" value="{{ data.ore_tonnage }}" required />
</div>
<div class="form-group">
<label for="head_grade_pct">Head Grade (%)</label>
<input id="head_grade_pct" name="head_grade_pct" type="number" min="0" max="100" step="0.01" value="{{ data.head_grade_pct }}" required />
</div>
<div class="form-group">
<label for="recovery_pct">Recovery (%)</label>
<input id="recovery_pct" name="recovery_pct" type="number" min="0" max="100" step="0.01" value="{{ data.recovery_pct }}" required />
</div>
<div class="form-group">
<label for="payable_pct">Payable (%)</label>
<input id="payable_pct" name="payable_pct" type="number" min="0" max="100" step="0.01" value="{{ data.payable_pct or metadata.default_payable_pct }}" />
<p class="field-help">Default {{ metadata.default_payable_pct or 100 }}% if blank.</p>
</div>
<div class="form-group">
<label for="reference_price">Reference Price</label>
<input id="reference_price" name="reference_price" type="number" min="0" step="0.01" value="{{ data.reference_price }}" required />
</div>
<div class="form-group">
<label for="fx_rate">FX Rate</label>
<input id="fx_rate" name="fx_rate" type="number" min="0" step="0.0001" value="{{ data.fx_rate or 1 }}" required />
</div>
<div class="form-group">
<label for="currency_code">Scenario Currency</label>
<input id="currency_code" name="currency_code" type="text" maxlength="3" value="{{ data.currency_code or scenario.currency or project.currency }}" />
</div>
</div>
<fieldset class="form-fieldset">
<legend>Processing Charges</legend>
<div class="form-grid">
<div class="form-group">
<label for="treatment_charge">Treatment Charge</label>
<input id="treatment_charge" name="treatment_charge" type="number" min="0" step="0.01" value="{{ data.treatment_charge }}" />
</div>
<div class="form-group">
<label for="smelting_charge">Smelting Charge</label>
<input id="smelting_charge" name="smelting_charge" type="number" min="0" step="0.01" value="{{ data.smelting_charge }}" />
</div>
<div class="form-group">
<label for="opex">Opex (per period)</label>
<input id="opex" name="opex" type="number" min="0" step="0.01" value="{{ data.opex }}" />
</div>
</div>
</fieldset>
<fieldset class="form-fieldset">
<legend>Penalties &amp; Premiums</legend>
<div class="form-grid">
<div class="form-group">
<label for="moisture_pct">Moisture (%)</label>
<input id="moisture_pct" name="moisture_pct" type="number" min="0" max="100" step="0.01" value="{{ data.moisture_pct }}" />
</div>
<div class="form-group">
<label for="moisture_threshold_pct">Moisture Threshold (%)</label>
<input id="moisture_threshold_pct" name="moisture_threshold_pct" type="number" min="0" max="100" step="0.01" value="{{ data.moisture_threshold_pct or metadata.moisture_threshold_pct }}" />
</div>
<div class="form-group">
<label for="moisture_penalty_per_pct">Moisture Penalty / %</label>
<input id="moisture_penalty_per_pct" name="moisture_penalty_per_pct" type="number" step="0.01" value="{{ data.moisture_penalty_per_pct or metadata.moisture_penalty_per_pct }}" />
</div>
<div class="form-group">
<label for="premiums">Premiums / Credits</label>
<input id="premiums" name="premiums" type="number" step="0.01" value="{{ data.premiums }}" />
</div>
</div>
<div class="impurity-table">
<label>Impurities</label>
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Content (ppm)</th>
<th scope="col">Threshold (ppm)</th>
<th scope="col">Penalty / ppm</th>
</tr>
</thead>
<tbody>
{% set impurity_entries = data.impurities or metadata_impurities %}
{% for impurity in impurity_entries %}
<tr>
<td>
<input type="text" name="impurities[{{ loop.index0 }}][name]" value="{{ impurity.name }}" />
</td>
<td>
<input type="number" step="0.01" min="0" name="impurities[{{ loop.index0 }}][value]" value="{{ impurity.value }}" />
</td>
<td>
<input type="number" step="0.01" min="0" name="impurities[{{ loop.index0 }}][threshold]" value="{{ impurity.threshold }}" />
</td>
<td>
<input type="number" step="0.01" min="0" name="impurities[{{ loop.index0 }}][penalty]" value="{{ impurity.penalty }}" />
</td>
</tr>
{% else %}
<tr>
<td colspan="4" class="muted">No impurity penalties configured.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</fieldset>
<fieldset class="form-fieldset">
<legend>Capital &amp; Discounting</legend>
<div class="form-grid">
<div class="form-group">
<label for="capex">Capex</label>
<input id="capex" name="capex" type="number" min="0" step="0.01" value="{{ data.capex }}" />
</div>
<div class="form-group">
<label for="sustaining_capex">Sustaining Capex (per period)</label>
<input id="sustaining_capex" name="sustaining_capex" type="number" min="0" step="0.01" value="{{ data.sustaining_capex }}" />
</div>
<div class="form-group">
<label for="discount_rate">Discount Rate (%)</label>
<input id="discount_rate" name="discount_rate" type="number" min="0" max="100" step="0.01" value="{{ data.discount_rate or scenario.discount_rate }}" />
</div>
<div class="form-group">
<label for="periods">Evaluation Periods</label>
<input id="periods" name="periods" type="number" min="1" step="1" value="{{ data.periods or default_periods }}" />
</div>
</div>
</fieldset>
</form>
</section>
<aside class="panel">
<h2>Assumption Summary</h2>
<dl class="definition-list">
<div>
<dt>Default Payable</dt>
<dd>{{ metadata.default_payable_pct or 100 }}%</dd>
</div>
<div>
<dt>Moisture Threshold</dt>
<dd>{{ metadata.moisture_threshold_pct or 0 }}%</dd>
</div>
<div>
<dt>Moisture Penalty</dt>
<dd>{{ metadata.moisture_penalty_per_pct or 0 }}</dd>
</div>
<div>
<dt>Base Currency</dt>
<dd>{{ metadata.default_currency or "—" }}</dd>
</div>
</dl>
{% if metadata_impurities %}
<h3>Configured Impurities</h3>
<ul class="metric-list compact">
{% for impurity in metadata_impurities %}
<li>
<span>{{ impurity.name }}</span>
<strong>Threshold {{ impurity.threshold }} ppm · Penalty {{ impurity.penalty }}</strong>
</li>
{% endfor %}
</ul>
{% endif %}
<p class="muted">
Adjust values to reflect contract terms. Leave fields blank to use defaults sourced from pricing metadata.
</p>
</aside>
</div>
<section class="report-section">
<header class="section-header">
<h2>Calculation Results</h2>
<p class="section-subtitle">Outputs reflect the latest submission.</p>
</header>
{% if result %}
<div class="report-grid">
<article class="report-card">
<h3>Revenue Summary</h3>
<ul class="metric-list">
<li>
<span>Payable Metal</span>
<strong>{{ result.pricing.payable_metal_tonnes | default('—') }}</strong>
</li>
<li>
<span>Gross Revenue</span>
<strong>{{ result.pricing.gross_revenue | currency_display(result.pricing.currency) }}</strong>
</li>
<li>
<span>Net Revenue</span>
<strong>{{ result.pricing.net_revenue | currency_display(result.pricing.currency) }}</strong>
</li>
</ul>
</article>
<article class="report-card">
<h3>Cost Breakdown</h3>
<ul class="metric-list">
<li>
<span>Opex</span>
<strong>{{ result.costs.opex_total | currency_display(result.currency) }}</strong>
</li>
<li>
<span>Sustaining Capex</span>
<strong>{{ result.costs.sustaining_capex_total | currency_display(result.currency) }}</strong>
</li>
<li>
<span>Capex</span>
<strong>{{ result.costs.capex | currency_display(result.currency) }}</strong>
</li>
</ul>
</article>
<article class="report-card">
<h3>Key Metrics</h3>
<ul class="metric-list">
<li>
<span>NPV</span>
<strong>{{ result.metrics.npv | currency_display(result.currency) }}</strong>
</li>
<li>
<span>IRR</span>
<strong>{{ result.metrics.irr | percentage_display }}</strong>
</li>
<li>
<span>Payback</span>
<strong>{{ result.metrics.payback_period | period_display }}</strong>
</li>
<li>
<span>Margin</span>
<strong>{{ result.metrics.margin | percentage_display }}</strong>
</li>
</ul>
</article>
</div>
{% if result.cash_flows %}
<table class="metrics-table">
<thead>
<tr>
<th scope="col">Period</th>
<th scope="col">Revenue</th>
<th scope="col">Opex</th>
<th scope="col">Sustaining Capex</th>
<th scope="col">Net Cash Flow</th>
</tr>
</thead>
<tbody>
{% for entry in result.cash_flows %}
<tr>
<th scope="row">{{ entry.period }}</th>
<td>{{ entry.revenue | currency_display(result.currency) }}</td>
<td>{{ entry.opex | currency_display(result.currency) }}</td>
<td>{{ entry.sustaining_capex | currency_display(result.currency) }}</td>
<td>{{ entry.net | currency_display(result.currency) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% else %}
<p class="muted">Provide inputs and run the profitability calculator to see scenario metrics.</p>
{% endif %}
</section>
<section class="report-section">
<header class="section-header">
<h2>Visualisations</h2>
<p class="section-subtitle">Charts render after calculations complete.</p>
</header>
<div id="profitability-chart" class="chart-container"></div>
<div id="cashflow-chart" class="chart-container"></div>
</section>
{% endblock %}