feat: documentation update

- Completed export workflow implementation (query builders, CSV/XLSX serializers, streaming API endpoints, UI modals, automated tests).
- Added export modal UI and client script to trigger downloads directly from dashboard.
- Documented import/export field mapping and usage guidelines in FR-008.
- Updated installation guide with export environment variables, dependencies, and CLI/CI usage instructions.
This commit is contained in:
2025-11-11 18:34:02 +01:00
parent 02906bc960
commit 29f16139a3
11 changed files with 751 additions and 15 deletions

View File

@@ -0,0 +1,77 @@
# Pricing Settings Data Model
## Objective
Persist pricing configuration values that currently live in environment variables so projects can own default payable percentages, currency, and penalty factors without redeploying the application.
## Core Entity: `pricing_settings`
Holds the defaults that are injected into `PricingMetadata` when evaluating scenarios.
| Column | Type | Nullable | Default | Notes |
| -------------------------- | ----------------------- | -------- | -------- | -------------------------------------------------------------------------- |
| `id` | Integer | No | Identity | Primary key |
| `name` | String(128) | No | — | Human readable label displayed in the UI |
| `slug` | String(64) | No | — | Unique code used for programmatic lookup (e.g. `default`, `contract-a`) |
| `description` | Text | Yes | `NULL` | Optional descriptive text |
| `default_currency` | String(3) | Yes | `NULL` | Normalised ISO-4217 code; fallback when scenario currency is absent |
| `default_payable_pct` | Numeric(5,2) | No | `100.00` | Default payable percentage applied when not supplied with an input |
| `moisture_threshold_pct` | Numeric(5,2) | No | `8.00` | Percentage moisture threshold before penalties apply |
| `moisture_penalty_per_pct` | Numeric(14,4) | No | `0.0000` | Currency amount deducted per percentage point above the moisture threshold |
| `metadata` | JSON | Yes | `NULL` | Future extension bucket (e.g. FX assumptions) |
| `created_at` | DateTime(timezone=True) | No | `now()` | Creation timestamp |
| `updated_at` | DateTime(timezone=True) | No | `now()` | Auto updated timestamp |
## Child Entity: `pricing_metal_settings`
Stores overrides that apply to specific commodities (payable percentage or alternate thresholds).
| Column | Type | Nullable | Default | Notes |
| -------------------------- | ----------------------- | -------- | -------- | ------------------------------------------------------------------------------ |
| `id` | Integer | No | Identity | Primary key |
| `pricing_settings_id` | Integer (FK) | No | — | References `pricing_settings.id` with cascade delete |
| `metal_code` | String(32) | No | — | Normalised commodity identifier (e.g. `copper`, `gold`) |
| `payable_pct` | Numeric(5,2) | Yes | `NULL` | Contractual payable percentage for this metal; overrides parent value when set |
| `moisture_threshold_pct` | Numeric(5,2) | Yes | `NULL` | Optional metal specific moisture threshold |
| `moisture_penalty_per_pct` | Numeric(14,4) | Yes | `NULL` | Optional metal specific penalty factor |
| `data` | JSON | Yes | `NULL` | Additional metal settings (credits, payable deductions) |
| `created_at` | DateTime(timezone=True) | No | `now()` | Creation timestamp |
| `updated_at` | DateTime(timezone=True) | No | `now()` | Auto updated timestamp |
`metal_code` should have a unique constraint together with `pricing_settings_id` to prevent duplication.
## Child Entity: `pricing_impurity_settings`
Represents impurity penalty factors and thresholds that are injected into `PricingMetadata.impurity_thresholds` and `PricingMetadata.impurity_penalty_per_ppm`.
| Column | Type | Nullable | Default | Notes |
| --------------------- | ----------------------- | -------- | -------- | ---------------------------------------------------- |
| `id` | Integer | No | Identity | Primary key |
| `pricing_settings_id` | Integer (FK) | No | — | References `pricing_settings.id` with cascade delete |
| `impurity_code` | String(32) | No | — | Identifier such as `As`, `Pb`, `Zn` |
| `threshold_ppm` | Numeric(14,4) | No | `0.0000` | Contractual impurity allowance |
| `penalty_per_ppm` | Numeric(14,4) | No | `0.0000` | Currency penalty applied per ppm above the threshold |
| `notes` | Text | Yes | `NULL` | Optional narrative about the contract rule |
| `created_at` | DateTime(timezone=True) | No | `now()` | Creation timestamp |
| `updated_at` | DateTime(timezone=True) | No | `now()` | Auto updated timestamp |
Add a unique constraint on `(pricing_settings_id, impurity_code)`.
## Mapping to `PricingMetadata`
- `default_currency`, `default_payable_pct`, `moisture_threshold_pct`, and `moisture_penalty_per_pct` map directly to the dataclass fields.
- `pricing_metal_settings` rows provide per-metal overrides. During load, prefer metal-specific values when present, falling back to the parent record. These values hydrate a composed `PricingMetadata` or supplementary structure passed to the evaluator.
- `pricing_impurity_settings` rows populate `PricingMetadata.impurity_thresholds` and `PricingMetadata.impurity_penalty_per_ppm` dictionaries.
## Usage Notes
- Global defaults can be represented by a `pricing_settings` row referenced by new projects. Future migrations will add `projects.pricing_settings_id` to point at the desired configuration.
- The `metadata` JSON column gives room to store additional contract attributes (e.g. minimum lot penalties, premium formulas) without immediate schema churn.
- Numeric precision follows existing financial models (two decimal places for percentages, four for monetary/ppm penalties) to align with current tests.
## Bootstrap & Runtime Loading
- `services.bootstrap.bootstrap_pricing_settings` ensures a baseline record (slug `default`) exists during FastAPI startup and when running `scripts/initial_data.py`. Initial values come from `config.settings.Settings.pricing_metadata()`, allowing operators to shape the first record via environment variables.
- When projects lack an explicit configuration, the bootstrap associates them with the default record through `UnitOfWork.set_project_pricing_settings`, guaranteeing every project has pricing metadata.
- At request time, `dependencies.get_pricing_metadata` loads the persisted defaults using `UnitOfWork.get_pricing_metadata(include_children=True)`. If the slug is missing, it reseeds the default record from the bootstrap metadata before returning a `PricingMetadata` instance.
- Callers therefore observe a consistent fallback chain: project-specific settings → default database record → freshly seeded defaults derived from environment values.