code wiki / (root) / nx_garment_fit_match.nx

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}