# 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).