← INDEXCH 03 / 1004 HI ENTRIES →

CHAPTER 03Data Model

Req. prefix: DMv0.4 draft

Entities are specified logically (attributes and constraints), not physically. Any storage technology satisfying the constraints and the performance obligations of Ch. 06 is acceptable. Read-only master data (§3.1–3.6 — masters, calendar, forecast, split tables, phase profiles) is synthetically generated by the seed-profile generator (Ch. 10) and changes wholesale only via seed selection/reset or forecast regeneration (DE-08); there is no ingestion and no editing (D-23). Application state (§3.7–3.8) is owned by HiLo.FM and managed through its interfaces.

3.1Product master: hierarchy & attributes

The product dimension has exactly two hierarchy levels: UPC → Product ID. The UPC (Universal Product Code) is the externally defined article; a UPC contains one or more Product IDs — the finished products actually produced. Everything else that used to look like a hierarchy (brand, category, subcategory, sector, …) is modelled as attributes of the product, because such classifications do not nest cleanly (one brand spans categories) and therefore cannot form a tree.

AttributeTypeNotes
product_idstring, keyBase-grain identifier: the finished product
upc_idstringparent UPC; mandatory, exactly one
namestringdisplay only
attributesmap attribute → valuee.g. brand=B1, category=C-A, sector=S2; open set of attribute names
DM-01The product hierarchy MUST be a strict two-level tree: every product belongs to exactly one UPC; a UPC contains one or more products. An entry's product scope is a single attribute=value pair per DM-02; scoping to a UPC or a single product uses the same mechanism via the built-in attributes.
DM-02Product scope = exactly one attribute=value pair. Attributes are flat name→value classifications; the built-in fields upc and product_id are addressable as attributes through the identical mechanism. The pair selects a product set: product_id=P2 → one product; upc=U2 → that UPC's products; brand=B1 → all products carrying the value, spanning any number of UPCs. No AND-combinations, no multi-value (OR) conditions, no product lists: a volume across two brands is simply two entries (HE-05 permits free overlap). Deliberate demonstrator minimalism — richer scope UX can be layered on top of the fast core later. Attribute-scoped entries are disaggregated via the attribute→UPC stage (Ch. 05/06); scopes on upc / product_id skip the stages they are already at or below (DF-01). [Q-12 resolved → D-25]

3.2Location & customer masters

DM-03Locations are a flat list of location_id (+ name). No location hierarchy. An entry scope names one location or all.
DM-04Customers are a flat list of customer_id (+ name). Customer appears only in entry scopes, split tables and phase profiles — never in forecast, never in LO.

3.3Planning calendar

AttributeTypeNotes
week_idstring, keye.g. 2026-W14; totally ordered
month_idstringe.g. 2026-M04
quarter_idstringe.g. 2026-Q2
DM-05The calendar is the sole authority on which weeks constitute a month or quarter. The system MUST NOT derive period membership from civil-calendar arithmetic.
DM-06The calendar MUST cover the full planning horizon. Entries referencing periods outside it are rejected at validation (HE-04); the seed generator MUST NOT produce forecast or rule rows outside it.

3.4Forecast

AttributeTypeNotes
product_id, location_id, week_idcomposite keybase grain; defined at Product ID level and aggregable to UPC and to any attribute value
quantity_sudecimal ≥ 0generated values may be fractional; HiLo.FM outputs are integer
DM-07The forecast is sparse: absent rows mean zero. It is read-only and synthetically generated (Ch. 10); it changes wholesale only via seed selection/reset or forecast regeneration (DE-08), each followed by a batch run (Ch. 06). A brand-new product (NPI) typically has no forecast rows at all; its volume arises from the phase profile of its UPC (§3.6, DR-05).

3.5Location split tables (week-range form)

AttributeTypeNotes
product_idstringbase-product level
customer_idstring, nullablenull = generic (non-customer) rule
week_from, week_toweek ids, inclusiveranges are the native form; expanded to weeks internally
location_idstring
fractiondecimal (0…1]
DM-08After range expansion, for every populated key (product, customer, week) the fractions MUST sum to 1.0 exactly (tolerance 1e-9), and ranges for the same key MUST NOT overlap. These are invariants the seed generator MUST guarantee; the engine MAY assert them and fail loudly (naming the offending key) if violated.
DM-09Split-table resolution for a given product/week within an entry: (1) if the entry is customer-scoped and a matching customer row set exists → use it; (2) else if a null-customer row set exists → use it; (3) else → no split table applies; the default basis of Ch. 05 is used.

3.6Phase profiles (NPI / EOL)

A phase profile declares, per UPC, how volume is divided among that UPC's products over time — the mechanism of new-product introduction (a product ramping up from zero) and end-of-life (a product ramping down to zero). It is the product-dimension counterpart of the location split table.

AttributeTypeNotes
upc_idstringprofile owner
customer_idstring, nullablenull = generic; customers may have different introduction schedules
week_from, week_toweek ids, inclusivenative week-range form
product_idstringMUST belong to the UPC
fractiondecimal [0…1]share of the UPC volume in those weeks
DM-14After range expansion, for every populated key (upc, customer, week) the fractions MUST sum to 1.0 exactly (tolerance 1e-9), without overlapping ranges for the same key — generator invariants as in DM-08. Products of the UPC not listed for a covered week have share 0 there.
DM-15Phase-profile resolution for a given UPC/week within an entry mirrors DM-09: (1) customer-scoped entry with matching customer rows → use them; (2) null-customer rows → use them; (3) no rows for this UPC/week → the UPC→product split falls back to forecast proportions among the UPC's in-scope products (DR-05). Profiles therefore exist only where phasing genuinely happens; stable products need no profile.
DM-16A profile MAY cover only part of the horizon. Weeks outside all its ranges use the fallback of DM-15(3). No requirement to maintain profiles for steady state.

3.7HI store — entries

Full semantics in Ch. 04; attributes here for completeness.

AttributeTypeNotes
entry_idstring, keysystem-assigned, stable
titlestringe.g. "B1 spring campaign"
product_scopesingle attribute=value pairper DM-02; upc and product_id are built-in attributes
location_scopelocation_id or ALL
customer_scopecustomer_id, nullable
periodweek / month / quarter idquarter is the coarsest supported
value_suinteger ≥ 0the modification volume
stateenumDisaggregated | InError | RequiresDisaggregation
error_messagestring, nullablepopulated iff InError
created_by, created_at, updated_at—attribution only; no history (no audit trail)

3.8LO store — disaggregated rows

AttributeTypeNotes
entry_idstringparent entry (GL-02)
product_id, location_id, week_idcompositebase grain
quantity_suinteger ≥ 0rounded per Ch. 06
DM-10Key of the LO store: (entry_id, product_id, location_id, week_id). Rows of different entries on the same base cell coexist (OV-07).
DM-11LO rows with quantity 0 SHOULD NOT be stored. Absence means zero.
DM-12Replacing an entry's disaggregation MUST be atomic per entry: readers see either the previous complete row set or the new one, never a mixture.
DM-13Reporting aggregates the LO store only (any grouping of UPC/product, attribute values, location, week/month/quarter, entry). HI values are never summed for reporting.

3.9Entity relationships

UPC ─< Product ──< Forecast >── Location, Week
 │        └─ attributes (brand, category, …; built-in: upc, product_id)   — flat, non-nesting
 └──< PhaseProfile >── Product, WeekRange, [Customer]
Product ──< SplitTable >── Location, WeekRange, [Customer]
Entry ── scope: attribute=value · Location|ALL · [Customer] · Period(Week|Month|Quarter)
Entry ─< LO row >── Product, Location, Week          Calendar: Week >── Month >── Quarter