Files
PriceForge/bible-local/data-rules.md
Mikhail Chusavitin 5f8aec456b Unified Quote Journal (parts_log) v3
- New unified append-only quote log table parts_log replaces three
  separate log tables (stock_log, partnumber_log_competitors, lot_log)
- Migrations 042-049: extend supplier, create parts_log/import_formats/
  ignore_rules, rework qt_lot_metadata composite PK, add lead_time_weeks
  to pricelist_items, backfill data, migrate ignore rules
- New services: PartsLogBackfillService, ImportFormatService,
  UnifiedImportService; new world pricelist type (all supplier types)
- qt_lot_metadata PK changed to (lot_name, pricelist_type); all queries
  now filter WHERE pricelist_type='estimate'
- Fix pre-existing bug: qt_component_usage_stats column names
  quotes_last30d/quotes_last7d (no underscore) — added explicit gorm tags
- Bible: full table inventory, baseline schema snapshot, updated pricelist/
  data-rules/api/history/architecture docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:25:54 +03:00

97 lines
3.2 KiB
Markdown

# Data Rules
## Component Categories (lot_category)
### Source
Categories are **always** taken from `lot.lot_category` (table `lot`).
**NOT** from `qt_lot_metadata`.
**NOT** derived from the LOT name or article name.
### When creating pricelists
1. **Estimate**: include only components with `current_price > 0` and `is_hidden = 0` (`pricelist_type='estimate'`), then load `lot.lot_category`.
2. **Warehouse**: load `lot.lot_category` for all positions (`pricelist_type='warehouse'`).
3. **Competitor**: load `lot.lot_category` for all positions (`pricelist_type='competitor'`).
4. **World**: load `lot.lot_category` for all positions (`pricelist_type='world'`).
5. Persist into `lot_category` column of `qt_pricelist_items`.
### Model
```go
type PricelistItem struct {
LotCategory *string `gorm:"column:lot_category;size:50" json:"category,omitempty"`
// JSON field is "category" for the frontend
}
```
### Rules
- Category is **not** a virtual field — it is persisted to DB when the pricelist is created.
- JOIN with `lot` is only needed for `lot_description`; category is already in `qt_pricelist_items`.
- Default value when category is absent in source or LOT row is missing: `PART_`.
---
## Price Method
- **Estimate**: method determined by per-component settings in `qt_lot_metadata` (`pricelist_type='estimate'`). Default: `median`.
- **Warehouse**: `weighted_avg` (quantity-weighted average). Default `period_days=7`.
- **Competitor**: `weighted_median`. `qty NULL` → weight = 1.
- **World**: `weighted_median` across all supplier types. `qty NULL` → weight = 1.
---
## supplier_type
The `supplier` table `supplier_type` column determines which pricelist(s) a supplier's quotes feed:
| supplier_type | Feeds into |
|---------------|-----------|
| `trader` | estimate, world |
| `self` | warehouse, world |
| `competitor` | competitor, world |
**price_uplift**: divisor applied at import. `effective_price = raw_price / price_uplift`. All prices in `parts_log` are always in USD.
**Defaults by type**:
| supplier_type | price_uplift | default_lead_time_weeks |
|---------------|-------------|------------------------|
| trader | 1.0 | NULL |
| self | 1.0 | 0 |
| competitor | 1.3 | 2 |
---
## offer_type
`parts_log.offer_type` ENUM: `'private'` or `'public'` (default `'public'`).
Selected at import time. Both types are currently included in all pricelist calculations.
---
## qt_ignore_rules
Replaces the `is_ignored` mechanism in `qt_vendor_partnumber_seen`. Checked at import before mapping.
| field | match_type | behavior |
|-------|-----------|---------|
| partnumber / vendor / description | exact / glob / contains | DROP row if matched |
Order of checks at import: **ignore rules → partnumber book → write to parts_log**.
---
## Filtering by qt_partnumber_book_items
**Rule (CRITICAL)**: only `parts_log` rows with a non-NULL `lot_name` are included in any pricelist. `lot_name` is populated by the daily backfill job (`PartsLogBackfillService`) which resolves partnumbers via `qt_partnumber_book_items`. Rows that cannot be resolved remain with `lot_name=NULL` and are excluded from all pricelists.
---
## Component Data Source
- Source: MariaDB (not SQLite).
- `handlers/component.go` uses `componentService.List()` (MariaDB).