SportRx Customizer — Data Model
Visual anatomy of the net-new data entities for the product customizer. Companion to the flow wireframes — these frames answer "what does the data actually look like?" rather than "what does the user see?" Stub values use Huckson goggle (5-step template) and Oakley Sport frame as reference products. Schema reconciled against wireframes v2 + the Magento dependency export CSVs.
Aurora (AWS)
Shopify Metaobject
Shopify Metafield
Shopify Order Attribute
Net-new (no Magento equivalent)
Magento → Shopify (Logan's sheet)
D0
Entity map — all customizer entities + relationships
OverviewAurora (AWS)
Build
Aurora
ULID PK
| build_id | ULID | Only ref allowed outside Aurora |
| product_id | ID | |
| product_handle | string | |
| frame_variant_id | ID | Runtime ref (can churn) |
| frame_variant_sku | string | Stable join key → Logan's sheet |
| selections[] | SelectionItem[] | step + option + sku + price_delta |
| prescription_id | ULID? | Null if plano order |
| goggle_insert_variant_id | ID? | Goggle orders only |
| price_total | decimal | |
| market | MarketContext | Country + currency + lang snapshot |
| ruleset_version | string | Locked at Build creation |
| customer_id | ID? | Null for guest checkout |
| status | enum | draft → carted → ordered → cancelled |
| created_at | timestamp | |
| ordered_at | timestamp? | Set on status flip to ordered |
SelectionItem (array element)
| step | string | Step key, e.g. lens_feature |
| key | string | Option key, e.g. build-your-own |
| sku | string | Stable → Logan's sheet |
| price_delta | decimal | |
| dvi_code | string |
Shopify Metaobjects (net-new)
rule_template
Metaobject
| name | string | e.g. "Oakley Sport Template" |
| applies_to_product_type | string | e.g. "goggle", "frame" |
| steps[] | StepObject[] | Embedded — see D1 |
| rules_by_step | {stepKey: rule refs} | Per-step rule map; chunked fields (rules_vision_type, …) to dodge 256-ref cap |
Step object (embedded in metaobject field)
| key | string | e.g. lens_feature |
| title | string | Display label |
| oos_behavior | enum | hide / disable — step-level default |
| options[] | configurator_option refs | Max 256 per step |
configurator_option
Metaobject
| step_key | string | Which step this option belongs to |
| title | string | Tile label |
| image | image ref | Tile image |
| product_ref | handle | Shopify product |
| variant_id | ID | Runtime (can churn) |
| dvi_code | string? | Nullable |
| out_of_stock_behavior | enum | show/hide/disable — also on step (see OQ-7) |
| price_delta | decimal | Confirmed in v2 schema |
rule
Metaobject
| key | string | e.g. byo-shows-coating |
| type | enum | dependency / independence |
| trigger | OptionRef[] | Metaobject refs, not strings |
| effect | enum | show / hide / require |
| targets | OptionRef[] | Metaobject refs, not strings |
| priority | integer | Eval order when rules conflict |
goggle_insert_compatibility
Metaobject
| goggle_frame_upc | string | Goggle frames only |
| compatible_insert_upcs[] | string[] |
Shopify (metafields + order)
Product metafields
Metafield
| ruleset.template | MO ref | Points to rule_template |
| ruleset.overrides_by_step | JSON | Per-product step overrides |
| ruleset.removed_rule_keys | string[] | Rules suppressed for this product |
Order attributes
Order
| build_id | ULID | Immutable post-order |
| prescription_id | ULID? | Nullable |
| _pd | string | FittingBox — binocular PD |
| _pd_left | string | FittingBox — monocular |
| _pd_right | string | FittingBox — monocular |
| spec_role | string ⚠️ | In wireframe C10, not in SA — confirm |
D1
rule_template anatomy — Huckson goggle (5-step)
Schema anatomyMetaobject structure
rule_template: huckson-goggle-v1
applies_to_product_type: "goggle"
steps: [StepObject × 5]
▸ Step 1 vision_type
title: "Vision Type"
oos_behavior: hide
options: [plano, rx]
▸ Step 2 lens_material
title: "Lens Material"
oos_behavior: hide
options: [polycarbonate, trivex, …]
▸ Step 3 lens_feature
title: "Lens Feature"
oos_behavior: disable
options: [sport-optimized, build-your-own, polarized]
▸ Step 4 coating
title: "Coating"
oos_behavior: disable
options: [ar-scratch, no-coating]
▸ Step 5 add_ons
title: "Add-ons"
oos_behavior: hide
options: [goggle-insert-rx, …]
Rules — referenced via rule_template.rules_by_step (sample)
rule: byo-shows-coating
type: dependency
trigger: [→ build-your-own OptionRef]
effect: show
targets: [→ ar-scratch OptionRef]
priority: 1
rule: sport-opt-hides-coating
type: independence
trigger: [→ sport-optimized OptionRef]
effect: hide
targets: [→ ar-scratch OptionRef]
priority: 2
rule: rx-requires-insert
type: dependency
trigger: [→ rx OptionRef]
effect: require
targets: [→ goggle-insert-rx OptionRef]
configurator_option metaobjects (Step 3 — lens_feature)
sport-optimized
Metaobject
| handle | sport-optimized (= option_type_sku) | |
| product_ref | sport-optimized-lens | |
| variant_id | → resolves to SO-LNS-001 SKU | |
| price_delta | +$0 | |
| dvi_code | (TBD — Logan) | |
build-your-own
Metaobject
| handle | build-your-own | |
| product_ref | build-your-own-lens | |
| variant_id | → resolves to BYO-LNS-001 SKU | |
| price_delta | +$10 | |
polarized
Metaobject
| handle | polarized (= feat-polarized) | |
| product_ref | polarized-lens | |
| variant_id | → resolves to POL-LNS-001 SKU | |
| price_delta | +$20 | |
Per-product override (product metafield)
ruleset.template → huckson-goggle-v1ruleset.overrides_by_step →
{} (none for base product)ruleset.removed_rule_keys →
[]
D2
Build record lifecycle — field values at each status
Schema anatomy
Rows highlighted in green = newly set at this status.
amber = changed from prior status.
blue = immutable from this point.
draft Created at first step call
| build_id | 01HXN3P4Q5R6S7T8… |
| status | "draft" |
| product_id | gid://shopify/Product/… |
| product_handle | "huckson-goggle" |
| frame_variant_id | gid://shopify/… |
| frame_variant_sku | "HUCK-BASE-STD" |
| ruleset_version | "huckson-goggle-v1" |
| market | {country:"US", currency:"USD"} |
| selections | [] → grows with each step |
| prescription_id | null |
| price_total | 149.00 (frame base) |
| customer_id | null (guest) |
| ordered_at | null |
→
carted After cart add (C10)
| build_id | 01HXN3P4Q5R6S7T8… |
| status | "carted" |
| product_id | gid://shopify/Product/… |
| frame_variant_sku | "HUCK-BASE-STD" |
| ruleset_version | "huckson-goggle-v1" |
| selections | [{step_key:"lens_feature", variant_id:"gid://…", sku:"BYO-LNS-001", price_delta:10}, {step_key:"coating", variant_id:"gid://…", sku:"AR-SCR-001", price_delta:29}, …] |
| prescription_id | 01RXN3P4Q5R6S7T8… |
| goggle_insert_variant_id | gid://shopify/… |
| price_total | 237.00 |
| ordered_at | null |
→
ordered After shopify-sync Lambda
| build_id | 01HXN3P4Q5R6S7T8… |
| status | "ordered" |
| frame_variant_sku | "HUCK-BASE-STD" |
| ruleset_version | "huckson-goggle-v1" |
| selections | […immutable…] |
| prescription_id | 01RXN3P4Q5R6S7T8… |
| price_total | 237.00 |
| ordered_at | 2026-05-28T18:34:00Z |
What shopify-sync Lambda does at orders/create: reads
build_id from order line-item properties → flips Build.status = "ordered" → sets ordered_at → stamps build_id + prescription_id onto Shopify order metafield → starts Step Functions lifecycle workflow. Build is now immutable.
D3
Import sheet tab map — stub rows + join keys
Import sheet
Tab origin rule: Products & Variants is Logan's tab (Magento → Shopify). Customizer Config, Options, Rules, and Compatibility are entirely net-new — no Magento counterpart. The only place the two workstreams touch is
variant_sku.
Products & Variants
Logan's sheet (Magento → Shopify)| handle | title | product_type | variant_title | variant_sku ↔ join key | price | dvi_code |
|---|---|---|---|---|---|---|
| huckson-goggle | Huckson Goggle | goggle | Standard | HUCK-BASE-STD | 149.00 | (Logan) |
| sport-optimized-lens | Sport Optimized Lens | lens | Default | SO-LNS-001 | 89.00 | (Logan) |
| build-your-own-lens | Build Your Own Lens | lens | Default | BYO-LNS-001 | 79.00 | (Logan) |
| polarized-lens | Polarized Lens | lens | Default | POL-LNS-001 | 109.00 | (Logan) |
| ar-scratch-coating | AR + Scratch Coating | coating | Default | AR-SCR-001 | 29.00 | (Logan) |
| goggle-insert-rx | Goggle Rx Insert | insert | Default | INS-RX-001 | 49.00 | (Logan) |
Customizer Config
Net-new — no Magento equivalent| template_key | applies_to_product_type | step_key | step_title | step_order | oos_behavior |
|---|---|---|---|---|---|
| huckson-goggle-v1 | goggle | vision_type | Vision Type | 1 | hide |
| huckson-goggle-v1 | goggle | lens_material | Lens Material | 2 | hide |
| huckson-goggle-v1 | goggle | lens_feature | Lens Feature | 3 | disable |
| huckson-goggle-v1 | goggle | coating | Coating | 4 | disable |
| huckson-goggle-v1 | goggle | add_ons | Add-ons | 5 | hide |
| oakley-sport-v1 | frame | vision_type | Vision Type | 1 | hide |
| oakley-sport-v1 | frame | lens_type | Lens Type | 2 | hide |
| oakley-sport-v1 | frame | coating | Coating | 3 | disable |
Options
Net-new — no Magento equivalentvariant_sku here must match a row in Products & Variants ↑
| handle (option_type_sku) | step_key | template_key | product_ref | variant_sku ↔ | price_delta |
|---|---|---|---|---|---|
| sport-optimized | lens_feature | huckson-goggle-v1 | sport-optimized-lens | SO-LNS-001 | +$0 |
| build-your-own | lens_feature | huckson-goggle-v1 | build-your-own-lens | BYO-LNS-001 | +$10 |
| polarized | lens_feature | huckson-goggle-v1 | polarized-lens | POL-LNS-001 | +$20 |
| ar-scratch | coating | huckson-goggle-v1 | ar-scratch-coating | AR-SCR-001 | +$29 |
| no-coating | coating | huckson-goggle-v1 | — | — | +$0 |
| goggle-insert-rx | add_ons | huckson-goggle-v1 | goggle-insert-rx | INS-RX-001 | +$49 |
Rules
Net-new — no Magento equivalenttrigger and targets here are option_key slugs; Lambda resolves to OptionRef metaobject refs at import time
| rule_key | template_key | type | trigger | effect | targets | priority |
|---|---|---|---|---|---|---|
| byo-shows-coating | huckson-goggle-v1 | dependency | build-your-own | show | ar-scratch | 1 |
| sport-opt-hides-coating | huckson-goggle-v1 | independence | sport-optimized | hide | ar-scratch | 2 |
| rx-requires-insert | huckson-goggle-v1 | dependency | vision_type:rx | require | goggle-insert-rx | 1 |
Compatibility
Net-new — goggle frames only| goggle_frame_upc | compatible_insert_upcs |
|---|---|
| HUCK-UPC-001 | INS-UPC-001, INS-UPC-002, INS-UPC-003 |
D4
Cart line-item schema — what C10 produces (Huckson Rx build)
Cart + order
One cart add per spec component. All lines share
build_id. Cart Transform Function collapses these into one nested line at checkout. Stub values from a completed Huckson goggle + Rx build.
frame Huckson Goggle — Matte Black / L SKU: HUCK-BASE-STD · $149.00
| build_id | 01HXN3P4Q5R6S7T8U9V0W1X2Y3 |
| spec_role ⚠️ | "frame" |
| prescription_id | null (frame line carries null) |
| _pd | null (set on Rx slot line) |
lens Build Your Own Lens SKU: BYO-LNS-001 · +$10
| build_id | 01HXN3P4Q5R6S7T8U9V0W1X2Y3 |
| spec_role ⚠️ | "lens" |
| prescription_id | null |
| _pd | null |
coating AR + Scratch Coating SKU: AR-SCR-001 · +$29
| build_id | 01HXN3P4Q5R6S7T8U9V0W1X2Y3 |
| spec_role ⚠️ | "coating" |
| prescription_id | null |
insert Goggle Rx Insert SKU: INS-RX-001 · +$49
| build_id | 01HXN3P4Q5R6S7T8U9V0W1X2Y3 |
| spec_role ⚠️ | "insert" |
| prescription_id | null (Rx slot line carries it) |
rx slot Prescription Rx (slot) SKU: RX-SLOT-001 · +$0
| build_id | 01HXN3P4Q5R6S7T8U9V0W1X2Y3 |
| spec_role ⚠️ | "rx" |
| prescription_id | 01RXN3P4Q5R6S7T8U9V0W1X2Y3 |
| _pd | "63" |
| _pd_left | "31.5" |
| _pd_right | "31.5" |
After Cart Transform Function: Customer sees one nested line — "Huckson Goggle — custom build · $237.00" with child items collapsible. Underlying cart retains all 5 lines.
shopify-sync reads build_id from any line and looks up the Build record in Aurora.
What stamps on the Shopify Order (after checkout):
Order attribute:
Order attribute:
(Attribution attrs — _channel, _initial_rep, _current_rep — moved to Order Management scope in v2; not stamped by the customizer.)
Order attribute:
build_id = 01HXN3P4Q5R6S7T8…Order attribute:
prescription_id = 01RXN3P4Q5R6S7T8…(Attribution attrs — _channel, _initial_rep, _current_rep — moved to Order Management scope in v2; not stamped by the customizer.)
OQ
Open questions — items blocking stories or import sheet finalization
Reference| ID | Question | Blocks | Owner | Appears in |
|---|---|---|---|---|
| OQ-1 | spec_role line-item property — in wireframe C10 (frame / lens / coating / add-on / insert / rx), absent from FigJam + Karan's SA. Canonical model leaves role implicit. Keep or drop? | Cart Transform stories; D4 | Justin + Karan | D4 |
| OQ-2 | Product-level vs variant-level for filterable attrs (reflective, polarized, acetate type). Must be Metafield (not Metaobject) for Algolia. Level determines metafield namespace. | Product metafield schema; Logan's migration sheet | Karan + Logan | D0 |
| OQ-3 | Option handle convention — no explicit option_key field; rules reference options via OptionRef. The string key (Magento option_type_sku, e.g. feat-polarized) becomes the metaobject handle. Confirm 1:1 mapping + uniqueness across templates. |
CSV import handle resolution; rule authoring | Karan + Logan | D1, D3 |
| OQ-7 | Step vs option OOS behavior — v2 carries an out-of-stock setting on both the step (oos_behavior) and the option (out_of_stock_behavior). Confirm precedence (likely option overrides step default) and whether both are authored. |
Rule authoring UI; rules engine OOS handling | Karan | D0, D1 |
| OQ-5 | Options-per-step count vs 256-ref cap — 256-ref limit is per step's options list. Do any product types exceed this? Unlikely given Huckson's 87 rules total, but confirm for goggle frames. | rule_template implementation | Justin + Logan | D1 |
| OQ-6 | template_key naming convention — working assumption: human-readable slug + version suffix (e.g. huckson-goggle-v1). Confirm versioning scheme before populating the import sheet. | Customizer Config tab; ruleset_version in Build | Karan + SportRx | D2, D3 |