- 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.
78 lines
7.9 KiB
Markdown
78 lines
7.9 KiB
Markdown
# 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.
|