feat: Enhance currency handling and validation across scenarios

- Updated form template to prefill currency input with default value and added help text for clarity.
- Modified integration tests to assert more descriptive error messages for invalid currency codes.
- Introduced new tests for currency normalization and validation in various scenarios, including imports and exports.
- Added comprehensive tests for pricing calculations, ensuring defaults are respected and overrides function correctly.
- Implemented unit tests for pricing settings repository, ensuring CRUD operations and default settings are handled properly.
- Enhanced scenario pricing evaluation tests to validate currency handling and metadata defaults.
- Added simulation tests to ensure Monte Carlo runs are accurate and handle various distribution scenarios.
This commit is contained in:
2025-11-11 18:29:59 +01:00
parent 032e6d2681
commit 795a9f99f4
50 changed files with 5110 additions and 81 deletions

View File

@@ -0,0 +1,33 @@
{% if filters %}
<section class="report-filters">
<div class="report-card">
<h2>Active Filters</h2>
<dl class="definition-list">
{% if filters.scenario_ids %}
<div>
<dt>Scenario IDs</dt>
<dd>{{ filters.scenario_ids | join(', ') }}</dd>
</div>
{% endif %}
{% if filters.start_date %}
<div>
<dt>Start Date</dt>
<dd>{{ filters.start_date }}</dd>
</div>
{% endif %}
{% if filters.end_date %}
<div>
<dt>End Date</dt>
<dd>{{ filters.end_date }}</dd>
</div>
{% endif %}
{% if not (filters.scenario_ids or filters.start_date or filters.end_date) %}
<div>
<dt>Status</dt>
<dd>No filters applied</dd>
</div>
{% endif %}
</dl>
</div>
</section>
{% endif %}

View File

@@ -0,0 +1,46 @@
{% set sorted_metrics = metrics | dictsort %}
{% set ns = namespace(percentile_keys=[]) %}
{% if percentiles %}
{% set ns.percentile_keys = percentiles %}
{% elif sorted_metrics %}
{% set reference_percentiles = sorted_metrics[0][1].percentiles.keys() | list %}
{% set ns.percentile_keys = reference_percentiles %}
{% endif %}
{% if sorted_metrics %}
<table class="metrics-table">
<thead>
<tr>
<th scope="col">Metric</th>
<th scope="col">Mean</th>
{% for percentile in ns.percentile_keys %}
{% set percentile_label = '%g' % percentile %}
<th scope="col">P{{ percentile_label }}</th>
{% endfor %}
<th scope="col">Std Dev</th>
</tr>
</thead>
<tbody>
{% for metric_name, summary in sorted_metrics %}
<tr>
<th scope="row">{{ metric_name | replace('_', ' ') | title }}</th>
<td>{{ summary.mean | format_metric(metric_name, currency) }}</td>
{% for percentile in ns.percentile_keys %}
{% set percentile_key = '%g' % percentile %}
{% set percentile_value = summary.percentiles.get(percentile_key) %}
<td>
{% if percentile_value is not none %}
{{ percentile_value | format_metric(metric_name, currency) }}
{% else %}
{% endif %}
</td>
{% endfor %}
<td>{{ summary.std_dev | format_metric(metric_name, currency) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="muted">Monte Carlo metrics are unavailable.</p>
{% endif %}

View File

@@ -0,0 +1,56 @@
{% if options %}
{% set distribution_enabled = options.distribution %}
{% set samples_enabled = options.samples and options.distribution %}
{% endif %}
<section class="report-options">
<div class="report-card">
<h2>Data Options</h2>
<ul class="metric-list compact">
<li>
<span>Monte Carlo Distribution</span>
<strong>
{% if options %}
{{ distribution_enabled and "Enabled" or "Disabled" }}
{% else %}
Not requested
{% endif %}
</strong>
</li>
<li>
<span>Sample Storage</span>
<strong>
{% if options %}
{% if options.samples %}
{% if samples_enabled %}
Enabled
{% else %}
Requires distribution
{% endif %}
{% else %}
Disabled
{% endif %}
{% else %}
Not requested
{% endif %}
</strong>
</li>
<li>
<span>Iterations</span>
<strong>{{ iterations }}</strong>
</li>
<li>
<span>Percentiles</span>
<strong>
{% if percentiles %}
{% for percentile in percentiles %}
{{ '%g' % percentile }}{% if not loop.last %}, {% endif %}
{% endfor %}
{% else %}
Defaults
{% endif %}
</strong>
</li>
</ul>
</div>
</section>

View File

@@ -0,0 +1,14 @@
<div class="scenario-actions">
<a
href="{{ request.url_for('reports.scenario_distribution_page', scenario_id=scenario.id) }}"
class="button button-secondary"
>
View Distribution
</a>
<a
href="{{ request.url_for('reports.scenario_distribution', scenario_id=scenario.id) }}"
class="button button-secondary"
>
Download JSON
</a>
</div>