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:
33
templates/partials/reports/filters_card.html
Normal file
33
templates/partials/reports/filters_card.html
Normal 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 %}
|
||||
46
templates/partials/reports/monte_carlo_table.html
Normal file
46
templates/partials/reports/monte_carlo_table.html
Normal 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 %}
|
||||
56
templates/partials/reports/options_card.html
Normal file
56
templates/partials/reports/options_card.html
Normal 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>
|
||||
14
templates/partials/reports/scenario_actions.html
Normal file
14
templates/partials/reports/scenario_actions.html
Normal 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>
|
||||
24
templates/partials/reports_header.html
Normal file
24
templates/partials/reports_header.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">{{ title }}</h1>
|
||||
{% if subtitle %}
|
||||
<p class="page-subtitle">{{ subtitle }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if actions %}
|
||||
<div class="page-actions">
|
||||
{% for action in actions %}
|
||||
{% set classes = action.classes or 'button button-secondary' %}
|
||||
<a
|
||||
href="{{ action.href }}"
|
||||
class="{{ classes }}"
|
||||
{% if action.target %}target="{{ action.target }}"{% endif %}
|
||||
{% if action.rel %}rel="{{ action.rel }}"{% endif %}
|
||||
{% if action.download %}download="{{ action.download }}"{% endif %}
|
||||
>
|
||||
{{ action.label }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</header>
|
||||
Reference in New Issue
Block a user