feat: Add NPV comparison and distribution charts to reporting
Some checks failed
CI / lint (push) Successful in 15s
CI / build (push) Has been skipped
CI / test (push) Failing after 17s
CI / deploy (push) Has been skipped

- Implemented NPV comparison chart generation using Plotly in ReportingService.
- Added distribution histogram for Monte Carlo results.
- Updated reporting templates to include new charts and improved layout.
- Created new settings and currencies management pages.
- Enhanced sidebar navigation with dynamic URL handling.
- Improved CSS styles for chart containers and overall layout.
- Added new simulation and theme settings pages with placeholders for future features.
This commit is contained in:
2025-11-12 19:39:27 +01:00
parent ad306bd0aa
commit acf6f50bbd
15 changed files with 819 additions and 435 deletions

View File

@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}CalMiner{% endblock %}</title>
<link rel="stylesheet" href="/static/css/main.css" />
<link rel="stylesheet" href="/static/css/imports.css" />
<link rel="stylesheet" href="/static/css/imports.css" />
{% block head_extra %}{% endblock %}
</head>
<body>
@@ -21,11 +21,27 @@
</div>
</div>
{% block scripts %}{% endblock %}
<script src="/static/js/projects.js" defer></script>
<script src="/static/js/exports.js" defer></script>
<script src="/static/js/imports.js" defer></script>
<script src="/static/js/notifications.js" defer></script>
<script src="/static/js/navigation.js" defer></script>
<script>
window.NAVIGATION_URLS = {
dashboard:
'{{ request.url_for("dashboard.home") if request else "/" }}',
projects:
'{{ request.url_for("projects.project_list_page") if request else "/projects/ui" }}',
imports:
'{{ request.url_for("imports.ui") if request else "/imports/ui" }}',
simulations:
'{{ request.url_for("ui.simulations") if request else "/ui/simulations" }}',
reporting:
'{{ request.url_for("ui.reporting") if request else "/ui/reporting" }}',
settings:
'{{ request.url_for("ui.settings") if request else "/ui/settings" }}',
};
</script>
<script src="/static/js/projects.js" defer></script>
<script src="/static/js/exports.js" defer></script>
<script src="/static/js/imports.js" defer></script>
<script src="/static/js/notifications.js" defer></script>
<script src="/static/js/navigation.js" defer></script>
<script src="/static/js/theme.js"></script>
</body>
</html>

31
templates/currencies.html Normal file
View File

@@ -0,0 +1,31 @@
{% extends "base.html" %}
{% block title %}{{ title }} | CalMiner{% endblock %}
{% block content %}
<div class="page-header">
<div>
<h1>{{ title }}</h1>
<p class="page-subtitle">Manage currency settings and exchange rates for financial calculations.</p>
</div>
</div>
<div class="settings-grid">
<div class="settings-card">
<h2>Currency Configuration</h2>
<p>Define available currencies and their properties.</p>
<p class="settings-card-note">Currency management coming soon</p>
</div>
<div class="settings-card">
<h2>Exchange Rates</h2>
<p>Configure and update currency exchange rates.</p>
<p class="settings-card-note">Exchange rate management coming soon</p>
</div>
<div class="settings-card">
<h2>Default Settings</h2>
<p>Set default currencies for new projects and scenarios.</p>
<p class="settings-card-note">Default currency settings coming soon</p>
</div>
</div>
{% endblock %}

View File

@@ -1,98 +1,67 @@
{% set dashboard_href = request.url_for('dashboard.home') if request else '/' %}
{% set projects_href = request.url_for('projects.project_list_page') if request else '/projects/ui' %}
{% set project_create_href = request.url_for('projects.create_project_form') if request else '/projects/create' %}
{% set auth_session = request.state.auth_session if request else None %}
{% set is_authenticated = auth_session and auth_session.is_authenticated %}
{% if is_authenticated %}
{% set logout_href = request.url_for('auth.logout') if request else '/logout' %}
{% set account_links = [
{"href": logout_href, "label": "Logout", "match_prefix": "/logout"}
] %}
{% else %}
{% set login_href = request.url_for('auth.login_form') if request else '/login' %}
{% set register_href = request.url_for('auth.register_form') if request else '/register' %}
{% set forgot_href = request.url_for('auth.password_reset_request_form') if request else '/forgot-password' %}
{% set account_links = [
{"href": login_href, "label": "Login", "match_prefix": "/login"},
{"href": register_href, "label": "Register", "match_prefix": "/register"},
{"href": forgot_href, "label": "Forgot Password", "match_prefix": "/forgot-password"}
] %}
{% endif %}
{% set nav_groups = [
{
"label": "Workspace",
"links": [
{"href": dashboard_href, "label": "Dashboard", "match_prefix": "/"},
{"href": projects_href, "label": "Projects", "match_prefix": "/projects"},
{"href": project_create_href, "label": "New Project", "match_prefix": "/projects/create"},
{"href": "/imports/ui", "label": "Imports", "match_prefix": "/imports"}
]
},
{
"label": "Insights",
"links": [
{"href": "/ui/simulations", "label": "Simulations"},
{"href": "/ui/reporting", "label": "Reporting"}
]
},
{
"label": "Configuration",
"links": [
{
"href": "/ui/settings",
"label": "Settings",
"children": [
{"href": "/theme-settings", "label": "Themes"},
{"href": "/ui/currencies", "label": "Currency Management"}
]
}
]
},
{
"label": "Account",
"links": account_links
}
] %}
{% set projects_href = request.url_for('projects.project_list_page') if request
else '/projects/ui' %} {% set project_create_href =
request.url_for('projects.create_project_form') if request else
'/projects/create' %} {% set auth_session = request.state.auth_session if
request else None %} {% set is_authenticated = auth_session and
auth_session.is_authenticated %} {% if is_authenticated %} {% set logout_href =
request.url_for('auth.logout') if request else '/logout' %} {% set account_links
= [ {"href": logout_href, "label": "Logout", "match_prefix": "/logout"} ] %} {%
else %} {% set login_href = request.url_for('auth.login_form') if request else
'/login' %} {% set register_href = request.url_for('auth.register_form') if
request else '/register' %} {% set forgot_href =
request.url_for('auth.password_reset_request_form') if request else
'/forgot-password' %} {% set account_links = [ {"href": login_href, "label":
"Login", "match_prefix": "/login"}, {"href": register_href, "label": "Register",
"match_prefix": "/register"}, {"href": forgot_href, "label": "Forgot Password",
"match_prefix": "/forgot-password"} ] %} {% endif %} {% set nav_groups = [ {
"label": "Workspace", "links": [ {"href": dashboard_href, "label": "Dashboard",
"match_prefix": "/"}, {"href": projects_href, "label": "Projects",
"match_prefix": "/projects"}, {"href": project_create_href, "label": "New
Project", "match_prefix": "/projects/create"}, {"href": "/imports/ui", "label":
"Imports", "match_prefix": "/imports"} ] }, { "label": "Insights", "links": [
{"href": "/ui/simulations", "label": "Simulations"}, {"href": "/ui/reporting",
"label": "Reporting"} ] }, { "label": "Configuration", "links": [ { "href":
"/ui/settings", "label": "Settings", "children": [ {"href": "/theme-settings",
"label": "Themes"}, {"href": "/ui/currencies", "label": "Currency Management"} ]
} ] }, { "label": "Account", "links": account_links } ] %}
<nav class="sidebar-nav" aria-label="Primary navigation">
{% set current_path = request.url.path if request else '' %}
{% for group in nav_groups %}
{% if group.links %}
<div class="sidebar-section">
<div class="sidebar-section-label">{{ group.label }}</div>
<div class="sidebar-section-links">
{% for link in group.links %}
{% set href = link.href %}
{% set match_prefix = link.get('match_prefix', href) %}
{% if match_prefix == '/' %}
{% set is_active = current_path == '/' %}
{% else %}
{% set is_active = current_path.startswith(match_prefix) %}
{% endif %}
<div class="sidebar-link-block">
<a href="{{ href }}" class="sidebar-link{% if is_active %} is-active{% endif %}">
{{ link.label }}
</a>
{% if link.children %}
<div class="sidebar-sublinks">
{% for child in link.children %}
{% set child_prefix = child.get('match_prefix', child.href) %}
{% if child_prefix == '/' %}
{% set child_active = current_path == '/' %}
{% else %}
{% set child_active = current_path.startswith(child_prefix) %}
{% endif %}
<a href="{{ child.href }}" class="sidebar-sublink{% if child_active %} is-active{% endif %}">
{{ child.label }}
</a>
{% endfor %}
</div>
{% endif %}
</div>
{% set current_path = request.url.path if request else '' %} {% for group in
nav_groups %} {% if group.links %}
<div class="sidebar-section">
<div class="sidebar-section-label">{{ group.label }}</div>
<div class="sidebar-section-links">
{% for link in group.links %} {% set href = link.href | string %} {% set
match_prefix = link.get('match_prefix', href) | string %} {% if
match_prefix == '/' %} {% set is_active = current_path == '/' %} {% else
%} {% set is_active = current_path.startswith(match_prefix) %} {% endif %}
<div class="sidebar-link-block">
<a
href="{{ href }}"
class="sidebar-link{% if is_active %} is-active{% endif %}"
>
{{ link.label }}
</a>
{% if link.children %}
<div class="sidebar-sublinks">
{% for child in link.children %} {% set child_prefix =
child.get('match_prefix', child.href) | string %} {% if child_prefix
== '/' %} {% set child_active = current_path == '/' %} {% else %} {%
set child_active = current_path.startswith(child_prefix) %} {% endif
%}
<a
href="{{ child.href | string }}"
class="sidebar-sublink{% if child_active %} is-active{% endif %}"
>
{{ child.label }}
</a>
{% endfor %}
</div>
{% endif %}
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
</div>
{% endif %} {% endfor %}
</nav>

23
templates/reporting.html Normal file
View File

@@ -0,0 +1,23 @@
{% extends "base.html" %} {% block title %}{{ title }} | CalMiner{% endblock %}
{% block content %} {% include "partials/reports_header.html" %}
<section class="report-overview">
<div class="report-grid">
<article class="report-card">
<h2>Reporting Dashboard</h2>
<p class="muted">Generate and view comprehensive financial reports.</p>
<p class="muted">
Access project summaries, scenario comparisons, and distribution
analysis.
</p>
<div class="page-actions">
<a
href="{{ request.url_for('projects.project_list_page') }}"
class="button"
>View Reports</a
>
</div>
</article>
</div>
</section>
{% endblock %}

View File

@@ -1,205 +1,248 @@
{% extends "base.html" %}
{% block title %}Project Summary | CalMiner{% endblock %}
{% extends "base.html" %} {% block title %}Project Summary | CalMiner{% endblock
%} {% block content %} {% include "partials/reports_header.html" %} {% include
"partials/reports/options_card.html" %} {% include
"partials/reports/filters_card.html" %}
{% block content %}
{% include "partials/reports_header.html" %}
<section class="report-overview">
<div class="report-grid">
<article class="report-card">
<h2>Project Details</h2>
<dl class="definition-list">
<div>
<dt>Name</dt>
<dd>{{ project.name }}</dd>
</div>
<div>
<dt>Location</dt>
<dd>{{ project.location or "—" }}</dd>
</div>
<div>
<dt>Operation Type</dt>
<dd>{{ project.operation_type | replace("_", " ") | title }}</dd>
</div>
<div>
<dt>Scenarios</dt>
<dd>{{ scenario_count }}</dd>
</div>
<div>
<dt>Created</dt>
<dd>{{ project.created_at | format_datetime }}</dd>
</div>
<div>
<dt>Updated</dt>
<dd>{{ project.updated_at | format_datetime }}</dd>
</div>
</dl>
</article>
{% include "partials/reports/options_card.html" %}
{% include "partials/reports/filters_card.html" %}
<article class="report-card">
<h2>Financial Summary</h2>
<ul class="metric-list">
<li>
<span>Total Inflows</span>
<strong
>{{ aggregates.financials.total_inflows |
currency_display(project.currency) }}</strong
>
</li>
<li>
<span>Total Outflows</span>
<strong
>{{ aggregates.financials.total_outflows |
currency_display(project.currency) }}</strong
>
</li>
<li>
<span>Net Cash Flow</span>
<strong
>{{ aggregates.financials.total_net |
currency_display(project.currency) }}</strong
>
</li>
</ul>
</article>
<section class="report-overview">
<div class="report-grid">
<article class="report-card">
<h2>Project Details</h2>
<dl class="definition-list">
<div>
<dt>Name</dt>
<dd>{{ project.name }}</dd>
</div>
<div>
<dt>Location</dt>
<dd>{{ project.location or "—" }}</dd>
</div>
<div>
<dt>Operation Type</dt>
<dd>{{ project.operation_type | replace("_", " ") | title }}</dd>
</div>
<div>
<dt>Scenarios</dt>
<dd>{{ scenario_count }}</dd>
</div>
<div>
<dt>Created</dt>
<dd>{{ project.created_at | format_datetime }}</dd>
</div>
<div>
<dt>Updated</dt>
<dd>{{ project.updated_at | format_datetime }}</dd>
</div>
</dl>
</article>
<article class="report-card">
<h2>Deterministic Metrics</h2>
{% if aggregates.deterministic_metrics %}
<table class="metrics-table">
<thead>
<tr>
<th scope="col">Metric</th>
<th scope="col">Average</th>
<th scope="col">Best</th>
<th scope="col">Worst</th>
</tr>
</thead>
<tbody>
{% for key, metric in aggregates.deterministic_metrics.items() %}
<tr>
<th scope="row">{{ key | replace("_", " ") | title }}</th>
<td>{{ metric.average | format_metric(key, project.currency) }}</td>
<td>{{ metric.maximum | format_metric(key, project.currency) }}</td>
<td>{{ metric.minimum | format_metric(key, project.currency) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="muted">
Deterministic metrics are unavailable for the current filters.
</p>
{% endif %}
</article>
</div>
</section>
<article class="report-card">
<h2>Financial Summary</h2>
<ul class="metric-list">
<section class="report-section">
<header class="section-header">
<h2>NPV Comparison</h2>
<p class="section-subtitle">
Visual comparison of Net Present Value across scenarios.
</p>
</header>
<div id="npv-chart" class="chart-container"></div>
</section>
<section class="report-section">
<header class="section-header">
<h2>Scenario Breakdown</h2>
<p class="section-subtitle">
Deterministic metrics and Monte Carlo summaries for each scenario.
</p>
</header>
{% if scenarios %} {% for item in scenarios %}
<article class="scenario-card">
<div class="scenario-card-header">
<div>
<h3>{{ item.scenario.name }}</h3>
<p class="muted">
{{ item.scenario.status | title }} · {{ item.scenario.primary_resource
or "No primary resource" }}
</p>
</div>
<div class="scenario-meta">
<span class="meta-label">Currency</span>
<span class="meta-value"
>{{ item.scenario.currency or project.currency or "—" }}</span
>
</div>
{% include "partials/reports/scenario_actions.html" %}
</div>
<div class="scenario-grid">
<section class="scenario-panel">
<h4>Financial Totals</h4>
<ul class="metric-list compact">
<li>
<span>Total Inflows</span>
<strong>{{ aggregates.financials.total_inflows | currency_display(project.currency) }}</strong>
<span>Inflows</span>
<strong
>{{ item.financials.inflows |
currency_display(item.scenario.currency or project.currency)
}}</strong
>
</li>
<li>
<span>Total Outflows</span>
<strong>{{ aggregates.financials.total_outflows | currency_display(project.currency) }}</strong>
<span>Outflows</span>
<strong
>{{ item.financials.outflows |
currency_display(item.scenario.currency or project.currency)
}}</strong
>
</li>
<li>
<span>Net Cash Flow</span>
<strong>{{ aggregates.financials.total_net | currency_display(project.currency) }}</strong>
<span>Net</span>
<strong
>{{ item.financials.net | currency_display(item.scenario.currency
or project.currency) }}</strong
>
</li>
</ul>
</article>
<article class="report-card">
<h2>Deterministic Metrics</h2>
{% if aggregates.deterministic_metrics %}
<table class="metrics-table">
<thead>
<tr>
<th scope="col">Metric</th>
<th scope="col">Average</th>
<th scope="col">Best</th>
<th scope="col">Worst</th>
</tr>
</thead>
<tbody>
{% for key, metric in aggregates.deterministic_metrics.items() %}
<tr>
<th scope="row">{{ key | replace("_", " ") | title }}</th>
<td>{{ metric.average | format_metric(key, project.currency) }}</td>
<td>{{ metric.maximum | format_metric(key, project.currency) }}</td>
<td>{{ metric.minimum | format_metric(key, project.currency) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h5>By Category</h5>
{% if item.financials.by_category %}
<ul class="metric-list compact">
{% for label, value in item.financials.by_category.items() %}
<li>
<span>{{ label | replace("_", " ") | title }}</span>
<strong
>{{ value | currency_display(item.scenario.currency or
project.currency) }}</strong
>
</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">Deterministic metrics are unavailable for the current filters.</p>
<p class="muted">No financial inputs recorded.</p>
{% endif %}
</article>
</section>
<section class="scenario-panel">
<h4>Deterministic Metrics</h4>
<table class="metrics-table">
<tbody>
<tr>
<th scope="row">Discount Rate</th>
<td>{{ item.metrics.discount_rate | percentage_display }}</td>
</tr>
<tr>
<th scope="row">NPV</th>
<td>
{{ item.metrics.npv | currency_display(item.scenario.currency or
project.currency) }}
</td>
</tr>
<tr>
<th scope="row">IRR</th>
<td>{{ item.metrics.irr | percentage_display }}</td>
</tr>
<tr>
<th scope="row">Payback Period</th>
<td>{{ item.metrics.payback_period | period_display }}</td>
</tr>
</tbody>
</table>
{% if item.metrics.notes %}
<ul class="note-list">
{% for note in item.metrics.notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
{% endif %}
</section>
<section class="scenario-panel">
<h4>Monte Carlo Summary</h4>
{% if item.monte_carlo and item.monte_carlo.available %}
<p class="muted">
Iterations: {{ item.monte_carlo.iterations }} {% if percentiles %} ·
Percentiles: {% for percentile in percentiles %} {{ '%g' % percentile
}}{% if not loop.last %}, {% endif %} {% endfor %} {% endif %}
</p>
{% include "partials/reports/monte_carlo_table.html" %} {% else %}
<p class="muted">
Monte Carlo metrics are unavailable for this scenario.
</p>
{% if item.monte_carlo and item.monte_carlo.notes %}
<ul class="note-list">
{% for note in item.monte_carlo.notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
{% endif %} {% endif %}
</section>
</div>
</section>
<section class="report-section">
<header class="section-header">
<h2>Scenario Breakdown</h2>
<p class="section-subtitle">Deterministic metrics and Monte Carlo summaries for each scenario.</p>
</header>
{% if scenarios %}
{% for item in scenarios %}
<article class="scenario-card">
<div class="scenario-card-header">
<div>
<h3>{{ item.scenario.name }}</h3>
<p class="muted">{{ item.scenario.status | title }} · {{ item.scenario.primary_resource or "No primary resource" }}</p>
</div>
<div class="scenario-meta">
<span class="meta-label">Currency</span>
<span class="meta-value">{{ item.scenario.currency or project.currency or "—" }}</span>
</div>
{% include "partials/reports/scenario_actions.html" %}
</div>
<div class="scenario-grid">
<section class="scenario-panel">
<h4>Financial Totals</h4>
<ul class="metric-list compact">
<li>
<span>Inflows</span>
<strong>{{ item.financials.inflows | currency_display(item.scenario.currency or project.currency) }}</strong>
</li>
<li>
<span>Outflows</span>
<strong>{{ item.financials.outflows | currency_display(item.scenario.currency or project.currency) }}</strong>
</li>
<li>
<span>Net</span>
<strong>{{ item.financials.net | currency_display(item.scenario.currency or project.currency) }}</strong>
</li>
</ul>
<h5>By Category</h5>
{% if item.financials.by_category %}
<ul class="metric-list compact">
{% for label, value in item.financials.by_category.items() %}
<li>
<span>{{ label | replace("_", " ") | title }}</span>
<strong>{{ value | currency_display(item.scenario.currency or project.currency) }}</strong>
</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">No financial inputs recorded.</p>
{% endif %}
</section>
<section class="scenario-panel">
<h4>Deterministic Metrics</h4>
<table class="metrics-table">
<tbody>
<tr>
<th scope="row">Discount Rate</th>
<td>{{ item.metrics.discount_rate | percentage_display }}</td>
</tr>
<tr>
<th scope="row">NPV</th>
<td>{{ item.metrics.npv | currency_display(item.scenario.currency or project.currency) }}</td>
</tr>
<tr>
<th scope="row">IRR</th>
<td>{{ item.metrics.irr | percentage_display }}</td>
</tr>
<tr>
<th scope="row">Payback Period</th>
<td>{{ item.metrics.payback_period | period_display }}</td>
</tr>
</tbody>
</table>
{% if item.metrics.notes %}
<ul class="note-list">
{% for note in item.metrics.notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
{% endif %}
</section>
<section class="scenario-panel">
<h4>Monte Carlo Summary</h4>
{% if item.monte_carlo and item.monte_carlo.available %}
<p class="muted">
Iterations: {{ item.monte_carlo.iterations }}
{% if percentiles %}
· Percentiles:
{% for percentile in percentiles %}
{{ '%g' % percentile }}{% if not loop.last %}, {% endif %}
{% endfor %}
{% endif %}
</p>
{% include "partials/reports/monte_carlo_table.html" %}
{% else %}
<p class="muted">Monte Carlo metrics are unavailable for this scenario.</p>
{% if item.monte_carlo and item.monte_carlo.notes %}
<ul class="note-list">
{% for note in item.monte_carlo.notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
</section>
</div>
</article>
{% endfor %}
{% else %}
<p class="muted">No scenarios match the current filters.</p>
{% endif %}
</section>
</article>
{% endfor %} {% else %}
<p class="muted">No scenarios match the current filters.</p>
{% endif %}
</section>
{% endblock %} {% block scripts %}
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script>
const chartData = {{ chart_data | safe }};
if (chartData && chartData.data) {
Plotly.newPlot('npv-chart', chartData.data, chartData.layout);
}
</script>
{% endblock %}

View File

@@ -1,149 +1,177 @@
{% extends "base.html" %}
{% block title %}Scenario Distribution | CalMiner{% endblock %}
{% extends "base.html" %} {% block title %}Scenario Distribution | CalMiner{%
endblock %} {% block content %} {% include "partials/reports_header.html" %}
{% block content %}
{% include "partials/reports_header.html" %}
<section class="report-overview">
<div class="report-grid">
<article class="report-card">
<h2>Scenario Details</h2>
<dl class="definition-list">
<div>
<dt>Name</dt>
<dd>{{ scenario.name }}</dd>
</div>
<div>
<dt>Project ID</dt>
<dd>{{ scenario.project_id }}</dd>
</div>
<div>
<dt>Status</dt>
<dd>{{ scenario.status | title }}</dd>
</div>
<div>
<dt>Currency</dt>
<dd>{{ scenario.currency or "—" }}</dd>
</div>
<div>
<dt>Discount Rate</dt>
<dd>{{ metrics.discount_rate | percentage_display }}</dd>
</div>
<div>
<dt>Updated</dt>
<dd>{{ scenario.updated_at | format_datetime }}</dd>
</div>
</dl>
</article>
<section class="report-overview">
<div class="report-grid">
<article class="report-card">
<h2>Scenario Details</h2>
<dl class="definition-list">
<div>
<dt>Name</dt>
<dd>{{ scenario.name }}</dd>
</div>
<div>
<dt>Project ID</dt>
<dd>{{ scenario.project_id }}</dd>
</div>
<div>
<dt>Status</dt>
<dd>{{ scenario.status | title }}</dd>
</div>
<div>
<dt>Currency</dt>
<dd>{{ scenario.currency or "—" }}</dd>
</div>
<div>
<dt>Discount Rate</dt>
<dd>{{ metrics.discount_rate | percentage_display }}</dd>
</div>
<div>
<dt>Updated</dt>
<dd>{{ scenario.updated_at | format_datetime }}</dd>
</div>
</dl>
</article>
<article class="report-card">
<h2>Financial Totals</h2>
<ul class="metric-list">
<li>
<span>Inflows</span>
<strong>{{ summary.inflows | currency_display(scenario.currency) }}</strong>
</li>
<li>
<span>Outflows</span>
<strong>{{ summary.outflows | currency_display(scenario.currency) }}</strong>
</li>
<li>
<span>Net Cash Flow</span>
<strong>{{ summary.net | currency_display(scenario.currency) }}</strong>
</li>
</ul>
{% if summary.by_category %}
<h3>By Category</h3>
<ul class="metric-list compact">
{% for label, value in summary.by_category.items() %}
<li>
<span>{{ label | replace("_", " ") | title }}</span>
<strong>{{ value | currency_display(scenario.currency) }}</strong>
</li>
{% endfor %}
</ul>
{% endif %}
</article>
</div>
</section>
<section class="report-section">
<header class="section-header">
<h2>Deterministic Metrics</h2>
<p class="section-subtitle">Key financial indicators calculated from deterministic cash flows.</p>
</header>
<table class="metrics-table">
<tbody>
<tr>
<th scope="row">NPV</th>
<td>{{ metrics.npv | currency_display(scenario.currency) }}</td>
</tr>
<tr>
<th scope="row">IRR</th>
<td>{{ metrics.irr | percentage_display }}</td>
</tr>
<tr>
<th scope="row">Payback Period</th>
<td>{{ metrics.payback_period | period_display }}</td>
</tr>
</tbody>
</table>
{% if metrics.notes %}
<ul class="note-list">
{% for note in metrics.notes %}
<li>{{ note }}</li>
<article class="report-card">
<h2>Financial Totals</h2>
<ul class="metric-list">
<li>
<span>Inflows</span>
<strong
>{{ summary.inflows | currency_display(scenario.currency) }}</strong
>
</li>
<li>
<span>Outflows</span>
<strong
>{{ summary.outflows | currency_display(scenario.currency)
}}</strong
>
</li>
<li>
<span>Net Cash Flow</span>
<strong
>{{ summary.net | currency_display(scenario.currency) }}</strong
>
</li>
</ul>
{% if summary.by_category %}
<h3>By Category</h3>
<ul class="metric-list compact">
{% for label, value in summary.by_category.items() %}
<li>
<span>{{ label | replace("_", " ") | title }}</span>
<strong>{{ value | currency_display(scenario.currency) }}</strong>
</li>
{% endfor %}
</ul>
{% endif %}
</section>
<section class="report-section">
<header class="section-header">
<h2>Monte Carlo Distribution</h2>
<p class="section-subtitle">Simulation-driven distributions contextualize stochastic variability.</p>
</header>
{% if monte_carlo and monte_carlo.available %}
<div class="simulation-summary">
<p>Iterations: {{ monte_carlo.iterations }} · Percentiles: {{ percentiles | join(", ") }}</p>
<table class="metrics-table">
<thead>
<tr>
<th scope="col">Metric</th>
<th scope="col">Mean</th>
<th scope="col">P5</th>
<th scope="col">Median</th>
<th scope="col">P95</th>
</tr>
</thead>
<tbody>
{% for metric, summary in monte_carlo.metrics.items() %}
<tr>
<th scope="row">{{ metric | replace("_", " ") | title }}</th>
<td>{{ summary.mean | format_metric(metric, scenario.currency) }}</td>
<td>{{ summary.percentiles['5'] | format_metric(metric, scenario.currency) }}</td>
<td>{{ summary.percentiles['50'] | format_metric(metric, scenario.currency) }}</td>
<td>{{ summary.percentiles['95'] | format_metric(metric, scenario.currency) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if monte_carlo.notes %}
<ul class="note-list">
{% for note in monte_carlo.notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% else %}
<p class="muted">Monte Carlo output is unavailable for this scenario.</p>
{% if monte_carlo and monte_carlo.notes %}
<ul class="note-list">
{% for note in monte_carlo.notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
{% endif %}
</article>
</div>
</section>
<section class="report-section">
<header class="section-header">
<h2>Deterministic Metrics</h2>
<p class="section-subtitle">
Key financial indicators calculated from deterministic cash flows.
</p>
</header>
<table class="metrics-table">
<tbody>
<tr>
<th scope="row">NPV</th>
<td>{{ metrics.npv | currency_display(scenario.currency) }}</td>
</tr>
<tr>
<th scope="row">IRR</th>
<td>{{ metrics.irr | percentage_display }}</td>
</tr>
<tr>
<th scope="row">Payback Period</th>
<td>{{ metrics.payback_period | period_display }}</td>
</tr>
</tbody>
</table>
{% if metrics.notes %}
<ul class="note-list">
{% for note in metrics.notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
{% endif %}
</section>
<section class="report-section">
<header class="section-header">
<h2>Monte Carlo Distribution</h2>
<p class="section-subtitle">
Simulation-driven distributions contextualize stochastic variability.
</p>
</header>
{% if monte_carlo and monte_carlo.available %}
<div id="distribution-chart" class="chart-container"></div>
<div class="simulation-summary">
<p>
Iterations: {{ monte_carlo.iterations }} · Percentiles: {{ percentiles |
join(", ") }}
</p>
<table class="metrics-table">
<thead>
<tr>
<th scope="col">Metric</th>
<th scope="col">Mean</th>
<th scope="col">P5</th>
<th scope="col">Median</th>
<th scope="col">P95</th>
</tr>
</thead>
<tbody>
{% for metric, summary in monte_carlo.metrics.items() %}
<tr>
<th scope="row">{{ metric | replace("_", " ") | title }}</th>
<td>{{ summary.mean | format_metric(metric, scenario.currency) }}</td>
<td>
{{ summary.percentiles['5'] | format_metric(metric,
scenario.currency) }}
</td>
<td>
{{ summary.percentiles['50'] | format_metric(metric,
scenario.currency) }}
</td>
<td>
{{ summary.percentiles['95'] | format_metric(metric,
scenario.currency) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if monte_carlo.notes %}
<ul class="note-list">
{% for note in monte_carlo.notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
{% endif %}
</section>
</div>
{% else %}
<p class="muted">Monte Carlo output is unavailable for this scenario.</p>
{% if monte_carlo and monte_carlo.notes %}
<ul class="note-list">
{% for note in monte_carlo.notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
{% endif %} {% endif %}
</section>
{% endblock %} {% block scripts %}
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script>
const chartData = {{ chart_data | safe }};
if (chartData && chartData.data) {
Plotly.newPlot('distribution-chart', chartData.data, chartData.layout);
}
</script>
{% endblock %}

41
templates/settings.html Normal file
View File

@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %}{{ title }} | CalMiner{% endblock %}
{% block content %}
<div class="page-header">
<div>
<h1>{{ title }}</h1>
<p class="page-subtitle">Configure application settings and preferences.</p>
</div>
</div>
<div class="settings-grid">
<div class="settings-card">
<h2>Theme Settings</h2>
<p>Customize the appearance and color scheme of the application.</p>
<div class="page-actions">
<a href="{{ request.url_for('ui.theme_settings') }}" class="button">Configure Themes</a>
</div>
</div>
<div class="settings-card">
<h2>Currency Management</h2>
<p>Manage currency settings and exchange rates.</p>
<div class="page-actions">
<a href="{{ request.url_for('ui.currencies') }}" class="button">Manage Currencies</a>
</div>
</div>
<div class="settings-card">
<h2>User Preferences</h2>
<p>Configure personal preferences and defaults.</p>
<p class="settings-card-note">Coming soon</p>
</div>
<div class="settings-card">
<h2>System Configuration</h2>
<p>Advanced system settings and maintenance options.</p>
<p class="settings-card-note">Coming soon</p>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block title %}{{ title }} | CalMiner{% endblock %}
{% block content %}
{% include "partials/reports_header.html" %}
<section class="report-overview">
<div class="report-grid">
<article class="report-card">
<h2>Simulation Dashboard</h2>
<p class="muted">Run and monitor Monte Carlo simulations across scenarios.</p>
<p class="muted">This feature is coming soon.</p>
</article>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,31 @@
{% extends "base.html" %}
{% block title %}{{ title }} | CalMiner{% endblock %}
{% block content %}
<div class="page-header">
<div>
<h1>{{ title }}</h1>
<p class="page-subtitle">Customize the visual appearance of the application.</p>
</div>
</div>
<div class="settings-grid">
<div class="settings-card">
<h2>Color Theme</h2>
<p>Select your preferred color scheme.</p>
<p class="settings-card-note">Theme customization coming soon</p>
</div>
<div class="settings-card">
<h2>Layout Options</h2>
<p>Configure sidebar and navigation preferences.</p>
<p class="settings-card-note">Layout options coming soon</p>
</div>
<div class="settings-card">
<h2>Accessibility</h2>
<p>Adjust settings for better accessibility.</p>
<p class="settings-card-note">Accessibility settings coming soon</p>
</div>
</div>
{% endblock %}