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

3.2 KiB

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

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