nx_garment_fit_match.nx source
↩ module page · 339 lines · 14285 B
1// nx_garment_fit_match.nx -- real-measurement garment-vs-body fit verdict.
2//
3// BUSINESS PROBLEM (user 2026-05-15 background): the apparel industry
4// has ZERO size-label uniformity. "Small" at Brand A is different
5// from "Small" at Brand B. A consumer buys "size Small" thinking
6// she knows what she's getting and ends up with a dress that doesn't
7// fit AND/OR doesn't flatter her body type even when it physically
8// fits. Returns + dissatisfaction are the cost.
9//
10// SUBSTRATE-NATIVE FIX: measure the actual garment (from photo via
11// landmark + reference-scale; that primitive is queued). Match to
12// the customer's body measurements. Emit a substrate-honest verdict:
13//
14// PERFECT_TAILORED within +-5mm on every axis (couture-fit band)
15// GOOD_FIT within +-25mm; typical tailoring tolerance
16// LOOSE_OK larger than ideal but wearable
17// TIGHT_OK smaller than ideal but wearable
18// SIZED_WRONG >50mm off on a critical axis (bust/waist/hip)
19// UNFLATTERING fits physically but the garment's silhouette
20// accentuates wrong axes for this body archetype
21// (e.g., a sheath dress on a pear-archetype
22// body emphasizes hip-bust ratio in a way the
23// customer typically doesn't want)
24//
25// The substrate refuses to silently rate "fits" as "good"; UNFLATTERING
26// is its own verdict so the retailer can offer the customer the option
27// to ship anyway OR see an alternative cut.
28//
29// Recommended-size action sealed enum:
30// SHIP / SIZE_DOWN / SIZE_UP / TRY_DIFFERENT_CUT / TAILOR_TO_FIT
31//
32// LOSSLESS VS LOSSY: this primitive replaces "Small" (a 3-letter label
33// that maps to ~10cm of bust width variance across brands) with
34// "32-26-34 / 165cm / your bust delta = +3mm / verdict GOOD_FIT".
35// Every claim is measurable + queryable. The retail competitive edge
36// the user named.
37//
38// USE CASES:
39// - retail product page: customer enters her measurements; substrate
40// scores every SKU in inventory + ranks by fit_score
41// - virtual try-on (Google Shopping pattern): verdict feeds the
42// gen-img conditioning -- garment is rendered TIGHT-OK or LOOSE-OK
43// instead of generic mid-size
44// - tailoring marketplace: SIZED_WRONG + TAILOR_TO_FIT recommendation
45// routes to a tailor partner
46// - inventory audit: scan supplier catalog photos; flag mislabeled
47// SKUs before they ship
48//
49// All math in mm + Q10. No f64. No body-archetype profiling beyond
50// the existing identity_registry sealed enum.
51//
52// genealogy_id: pisut_2007_apparel_fit + ashdown_2007_sizing_systems +
53// winks_1997_anthropometry + iso_8559_1989_body_measure
54// lineage_id: garment_fit_match_q10_mm
55
56// nx_safety_envelope:
57// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
58// sil_target: SIL1
59// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
60// verdict: NOT_YET_EVALUATED
61
62import "nx_syscalls.nx"
63import "nx_tier.nx"
64
65const NX_FIT_Q: nx_int = 1024
66
67// Sealed-enum verdicts (HIGH = better fit, except UNFLATTERING which
68// is a separate concern from physical fit)
69const NX_FIT_SIZED_WRONG: nx_int = 0
70const NX_FIT_UNFLATTERING: nx_int = 1
71const NX_FIT_TIGHT_OK: nx_int = 2
72const NX_FIT_LOOSE_OK: nx_int = 3
73const NX_FIT_GOOD_FIT: nx_int = 4
74const NX_FIT_PERFECT_TAILORED: nx_int = 5
75const NX_FIT_N_VERDICTS: nx_int = 6
76
77// Recommended actions
78const NX_FIT_ACTION_SHIP: nx_int = 0
79const NX_FIT_ACTION_SIZE_DOWN: nx_int = 1
80const NX_FIT_ACTION_SIZE_UP: nx_int = 2
81const NX_FIT_ACTION_TRY_DIFFERENT_CUT: nx_int = 3
82const NX_FIT_ACTION_TAILOR_TO_FIT: nx_int = 4
83const NX_FIT_N_ACTIONS: nx_int = 5
84
85// Per-axis tolerance bands (millimeters). These are CONSERVATIVE
86// defaults derived from apparel-industry tailoring practice; future
87// extension stores per-customer / per-garment-type tolerances in
88// svc-config or per the user.
89const NX_FIT_PERFECT_TOL_MM: nx_int = 5 // tailored / couture
90const NX_FIT_GOOD_TOL_MM: nx_int = 25 // typical tailoring
91const NX_FIT_LOOSE_TOL_MM: nx_int = 50 // outer wearable
92const NX_FIT_SIZED_WRONG_MM: nx_int = 75 // beyond, not wearable
93
94// Body archetypes (mirrors identity_registry.py BodyArchetype enum).
95// Used for flattery scoring -- some garment cuts emphasize one
96// archetype's strengths and another's weaknesses.
97const NX_FIT_ARCHETYPE_UNKNOWN: nx_int = 0
98const NX_FIT_ARCHETYPE_SLIM: nx_int = 1
99const NX_FIT_ARCHETYPE_ATHLETIC: nx_int = 2
100const NX_FIT_ARCHETYPE_AVERAGE: nx_int = 3
101const NX_FIT_ARCHETYPE_CURVY: nx_int = 4
102const NX_FIT_ARCHETYPE_FULL: nx_int = 5
103const NX_FIT_ARCHETYPE_VOLUPTUOUS: nx_int = 6
104const NX_FIT_ARCHETYPE_MUSCULAR: nx_int = 7
105const NX_FIT_ARCHETYPE_PETITE: nx_int = 8
106
107// Garment cut hints (also sealed; downstream conditioning + flattery)
108const NX_FIT_CUT_UNKNOWN: nx_int = 0
109const NX_FIT_CUT_SHEATH: nx_int = 1 // hugs all axes; unforgiving
110const NX_FIT_CUT_A_LINE: nx_int = 2 // flares at hip; hides hip
111const NX_FIT_CUT_EMPIRE_WAIST: nx_int = 3 // hides waist + accentuates bust
112const NX_FIT_CUT_WRAP: nx_int = 4 // emphasizes waist; flatters curvy
113const NX_FIT_CUT_BODYCON: nx_int = 5 // hugs everything; demands smoothness
114const NX_FIT_CUT_SHIFT: nx_int = 6 // boxy; hides waist; flatters athletic
115const NX_FIT_CUT_FIT_AND_FLARE: nx_int = 7 // emphasizes waist + softens hip
116
117struct GarmentDimensions {
118 bust_mm: nx_int,
119 waist_mm: nx_int,
120 hip_mm: nx_int,
121 length_mm: nx_int,
122 shoulder_width_mm: nx_int,
123 sleeve_length_mm: nx_int, // 0 = sleeveless
124 inseam_mm: nx_int, // 0 = not pants
125 cut: nx_int, // NX_FIT_CUT_*
126 extracted_fidelity_q10: nx_int, // confidence in measurements
127}
128
129struct BodyDimensions {
130 bust_mm: nx_int,
131 waist_mm: nx_int,
132 hip_mm: nx_int,
133 height_mm: nx_int,
134 shoulder_width_mm: nx_int,
135 arm_length_mm: nx_int,
136 inseam_mm: nx_int,
137 archetype: nx_int, // NX_FIT_ARCHETYPE_*
138}
139
140struct FitMatchReport {
141 bust_delta_mm: nx_int, // signed: garment - body
142 waist_delta_mm: nx_int,
143 hip_delta_mm: nx_int,
144 length_proportion_q10: nx_int, // garment_length / body_height
145 shoulder_delta_mm: nx_int,
146
147 max_critical_delta_mm: nx_int, // max of |bust|, |waist|, |hip|
148 worst_axis_index: nx_int, // 0=bust, 1=waist, 2=hip, 3=length, 4=shoulder
149
150 fit_score_q10: nx_int, // composite: high = tight tolerances
151 flattery_score_q10: nx_int, // composite: high = cut suits archetype
152 verdict: nx_int, // NX_FIT_*
153 recommended_action: nx_int, // NX_FIT_ACTION_*
154 fidelity_q10: nx_int, // confidence in the verdict
155}
156
157// ===== Helpers =======================================================
158
159func _fit_abs(x: nx_int) -> nx_int {
160 if x < 0 { return -x }
161 return x
162}
163
164// ===== Flattery score: cut-vs-archetype affinity ====================
165//
166// Returns Q10 of how WELL this cut typically flatters this archetype.
167// Derived from public apparel-fit research; numeric weights are
168// SEED VALUES, callers replace with measured-from-user-feedback
169// priors as the substrate's posterior loop populates them.
170
171func _fit_flattery_seed(cut: nx_int, archetype: nx_int) -> nx_int {
172 // High-affinity pairs (cut + archetype rated highly by typical
173 // apparel-fit guidance). HIGH Q10 = good flattery.
174 if cut == NX_FIT_CUT_WRAP {
175 if archetype == NX_FIT_ARCHETYPE_CURVY { return 900 }
176 if archetype == NX_FIT_ARCHETYPE_VOLUPTUOUS { return 900 }
177 if archetype == NX_FIT_ARCHETYPE_AVERAGE { return 800 }
178 return 700
179 }
180 if cut == NX_FIT_CUT_A_LINE {
181 if archetype == NX_FIT_ARCHETYPE_FULL { return 850 }
182 if archetype == NX_FIT_ARCHETYPE_AVERAGE { return 800 }
183 if archetype == NX_FIT_ARCHETYPE_PETITE { return 750 }
184 return 700
185 }
186 if cut == NX_FIT_CUT_EMPIRE_WAIST {
187 if archetype == NX_FIT_ARCHETYPE_PETITE { return 850 }
188 if archetype == NX_FIT_ARCHETYPE_FULL { return 800 }
189 return 600
190 }
191 if cut == NX_FIT_CUT_FIT_AND_FLARE {
192 if archetype == NX_FIT_ARCHETYPE_CURVY { return 900 }
193 if archetype == NX_FIT_ARCHETYPE_AVERAGE { return 850 }
194 return 700
195 }
196 if cut == NX_FIT_CUT_BODYCON {
197 if archetype == NX_FIT_ARCHETYPE_ATHLETIC { return 800 }
198 if archetype == NX_FIT_ARCHETYPE_SLIM { return 850 }
199 if archetype == NX_FIT_ARCHETYPE_MUSCULAR { return 800 }
200 return 400
201 }
202 if cut == NX_FIT_CUT_SHEATH {
203 if archetype == NX_FIT_ARCHETYPE_SLIM { return 850 }
204 if archetype == NX_FIT_ARCHETYPE_AVERAGE { return 700 }
205 return 500
206 }
207 if cut == NX_FIT_CUT_SHIFT {
208 if archetype == NX_FIT_ARCHETYPE_ATHLETIC { return 850 }
209 if archetype == NX_FIT_ARCHETYPE_SLIM { return 750 }
210 if archetype == NX_FIT_ARCHETYPE_PETITE { return 700 }
211 return 500
212 }
213 // Unknown cut or archetype: neutral 0.5.
214 return 512
215}
216
217// ===== Public compute ================================================
218
219func nx_garment_fit_match(
220 garment: *GarmentDimensions,
221 body: *BodyDimensions,
222 report: *FitMatchReport
223) -> nx_int {
224 // Per-axis signed deltas (mm).
225 report.bust_delta_mm = garment.bust_mm - body.bust_mm
226 report.waist_delta_mm = garment.waist_mm - body.waist_mm
227 report.hip_delta_mm = garment.hip_mm - body.hip_mm
228 report.shoulder_delta_mm = garment.shoulder_width_mm - body.shoulder_width_mm
229
230 // Length proportion: garment length / body height in Q10.
231 // Useful for verdict on dress length appropriateness (e.g., midi
232 // vs maxi proportion to the wearer's height).
233 if body.height_mm > 0 {
234 report.length_proportion_q10 = (garment.length_mm * NX_FIT_Q) / body.height_mm
235 } else {
236 report.length_proportion_q10 = 0
237 }
238
239 // Max critical delta across bust/waist/hip + worst-axis pinpoint.
240 let abs_bust: nx_int = _fit_abs(report.bust_delta_mm)
241 let abs_waist: nx_int = _fit_abs(report.waist_delta_mm)
242 let abs_hip: nx_int = _fit_abs(report.hip_delta_mm)
243
244 var worst: nx_int = abs_bust
245 var worst_idx: nx_int = 0
246 if abs_waist > worst {
247 worst = abs_waist
248 worst_idx = 1
249 }
250 if abs_hip > worst {
251 worst = abs_hip
252 worst_idx = 2
253 }
254 report.max_critical_delta_mm = worst
255 report.worst_axis_index = worst_idx
256
257 // Fit score: max critical delta -> Q10. delta <= PERFECT -> Q10 max.
258 // delta = SIZED_WRONG -> 0. Linear in between.
259 var fit: nx_int = NX_FIT_Q
260 if worst > NX_FIT_PERFECT_TOL_MM {
261 let span: nx_int = NX_FIT_SIZED_WRONG_MM - NX_FIT_PERFECT_TOL_MM
262 let over: nx_int = worst - NX_FIT_PERFECT_TOL_MM
263 var drop: nx_int = (over * NX_FIT_Q) / span
264 if drop > NX_FIT_Q { drop = NX_FIT_Q }
265 fit = NX_FIT_Q - drop
266 }
267 if fit < 0 { fit = 0 }
268 report.fit_score_q10 = fit
269
270 // Flattery score from cut x archetype affinity.
271 report.flattery_score_q10 = _fit_flattery_seed(garment.cut, body.archetype)
272
273 // Fidelity: gated by extraction confidence on the garment side.
274 report.fidelity_q10 = garment.extracted_fidelity_q10
275
276 // ----- Sealed verdict routing -----
277 //
278 // Priority: sized-wrong (physical) > unflattering (style) > fit
279 // bands routed by delta magnitude.
280 if worst > NX_FIT_SIZED_WRONG_MM {
281 report.verdict = NX_FIT_SIZED_WRONG
282 // SIZE_UP if garment is smaller than body; SIZE_DOWN if larger.
283 var pred: nx_int = report.bust_delta_mm
284 if _fit_abs(report.waist_delta_mm) > _fit_abs(pred) { pred = report.waist_delta_mm }
285 if _fit_abs(report.hip_delta_mm) > _fit_abs(pred) { pred = report.hip_delta_mm }
286 if pred < 0 { report.recommended_action = NX_FIT_ACTION_SIZE_UP }
287 if pred > 0 { report.recommended_action = NX_FIT_ACTION_SIZE_DOWN }
288 return 0
289 }
290 // Physical fit OK but flattery low -> UNFLATTERING verdict.
291 if report.flattery_score_q10 < 512 {
292 report.verdict = NX_FIT_UNFLATTERING
293 report.recommended_action = NX_FIT_ACTION_TRY_DIFFERENT_CUT
294 return 0
295 }
296 // Within tailoring tolerance: route on delta magnitude + sign.
297 if worst <= NX_FIT_PERFECT_TOL_MM {
298 report.verdict = NX_FIT_PERFECT_TAILORED
299 report.recommended_action = NX_FIT_ACTION_SHIP
300 return 0
301 }
302 if worst <= NX_FIT_GOOD_TOL_MM {
303 report.verdict = NX_FIT_GOOD_FIT
304 report.recommended_action = NX_FIT_ACTION_SHIP
305 return 0
306 }
307 if worst <= NX_FIT_LOOSE_TOL_MM {
308 // Sign tells us tight vs loose.
309 var pred2: nx_int = report.bust_delta_mm
310 if _fit_abs(report.waist_delta_mm) > _fit_abs(pred2) { pred2 = report.waist_delta_mm }
311 if _fit_abs(report.hip_delta_mm) > _fit_abs(pred2) { pred2 = report.hip_delta_mm }
312 if pred2 < 0 {
313 report.verdict = NX_FIT_TIGHT_OK
314 report.recommended_action = NX_FIT_ACTION_TAILOR_TO_FIT
315 } else {
316 report.verdict = NX_FIT_LOOSE_OK
317 report.recommended_action = NX_FIT_ACTION_TAILOR_TO_FIT
318 }
319 return 0
320 }
321 // Beyond LOOSE_TOL but below SIZED_WRONG: edge band; route loose.
322 report.verdict = NX_FIT_LOOSE_OK
323 report.recommended_action = NX_FIT_ACTION_TAILOR_TO_FIT
324 return 0
325}
326
327// ===== Sealed-enum validity =========================================
328
329func nx_garment_fit_verdict_is_valid(v: nx_int) -> nx_int {
330 if v < 0 { return 0 }
331 if v >= NX_FIT_N_VERDICTS { return 0 }
332 return 1
333}
334
335func nx_garment_fit_action_is_valid(a: nx_int) -> nx_int {
336 if a < 0 { return 0 }
337 if a >= NX_FIT_N_ACTIONS { return 0 }
338 return 1
339}