Closed-beta AI fashion stylist. Deterministic styling authority today,
behavior-trained personalization when the data matures.
Get Your Fit (GYF) is a closed-beta AI stylist product. A user describes what they want — body type, skin tone, occasion, weather, budget, style preference — and GYF returns ranked outfits and items with explicit reasoning.
Canonical runtime split:
| Layer | Path | Status |
|---|---|---|
| Backend API | backend/app (FastAPI) |
live |
| Frontend | GYF_APP/web-app (React/Vite/Zustand) |
live |
| Styling engine | src/gyf (deterministic ranker) |
live |
| CLIP BPR personalization | src/gyf/clip_bpr_ranker.py |
live_assistive |
| NCF reranker | src/gyf/ncf_ranker.py |
shadow_only |
| Shadow reranker | src/gyf/shadow_reranker.py |
shadow_only |
| Compatibility ML | src/gyf/compatibility_ml.py |
shadow_only |
| VTON preview | backend/app/routers/vton.py |
preview_composite |
| Legacy reference | api/server.py |
internal only |
| Deprecated | beta_finale/ |
not used |
Recommendation engine
- Deterministic stylist engine: retrieval → candidate generation → BM25 ranking → score breakdown → explanation copy
- 6-path CLIP boost map: text→text (0.33), text→image (0.21), item-similarity-text (0.17), item-similarity-vision (0.12), occasion-vibe-anchor (0.10), BPR-preference-vector (0.07)
- Loom occasion anchors (arXiv:2605.09830): rich semantic vibe/anti-vibe anchor texts per occasion, lambda-weighted penalty to suppress style clashes
- Outfit color clash penalty:
P_color = 0.1 × max(0, n_non_neutral − 2)(Loom §4.3) - Feature-aware BPR (arXiv:1205.2618): user preference vector in 512-dim CLIP space updated via gradient ascent on positive–negative impression pairs
- NCF reranker (arXiv:1708.05031): NeuMF — GMF branch (element-wise product) + MLP branch (1024→512→256→128→32) fused to a sigmoid compatibility score; runs shadow-only
- Impression-based LTR negatives: items shown in
RecommendationSnapshotbut not acted on positively become weak negative labels (relevance=0.05) - Counterfactual explanation engine: aligns outfit explanations to non-zero ranking components only (arXiv:2203.01310)
- Rich explanations via
rich_explanationfield in recommendation responses - Wardrobe gap analysis: missing outfit roles, underrepresented occasions, thin color coverage
Platform
- Full FastAPI product backend: health, liveness, readiness, operator status, feed/catalog, auth/profile, favorites, cart intent, wardrobe state, recommendation snapshots, social interaction, VTON preview, recommendation endpoints
- React/Vite/Zustand frontend: onboarding, studio, recommendations, try-on preview, profile, saved-look surfaces
- BM25 posting-list retrieval eliminates O(N) scan; precomputed IDF across full catalog
- PostgreSQL-backed state: saved items, cart intent, owned wardrobe, interaction audit events, recommendation snapshots
- Auth-scoped recommendation context: saved and wardrobe state optionally wired into request context (deterministic, not trained personalization)
- Operator surfaces:
/ops/status,/ops/catalog-quality,/ops/data-quality-scorecard,/ml/status - Data-quality scorecard: catalog role coverage, image truth, label truth, runtime truth — offline-only promotion blockers
- Frontend API contract validator derived by static analysis of React normalizer chains
- Confidence calibration harness: ECE/MCE/Brier scoring
- Shadow pairwise logistic reranker with
_FORBIDDEN_FEATURESleakage guard and leave-one-case-out CV - 63,873-item merged catalog (14k Myntra + 50k Zappos UT-Zappos50k footwear), 49k with local-verified images
GYF follows the principle of promotion by evidence: each ML component starts in shadow_only and advances only after measurable offline gains over the deterministic baseline.
| Component | Algorithm | Promotion State |
|---|---|---|
| CLIP retrieval | CLIP ViT-B/32 text+image dual-tower via fastembed | live_assistive |
| BPR personalization | Bayesian Personalized Ranking preference vector in CLIP space | live_assistive |
| Occasion vibe anchors | Rich semantic anchor/anti-anchor retrieval with λ-weighted penalty | live_assistive |
| MMR diversity reranker | Maximal Marginal Relevance on CLIP embeddings | live_assistive |
| NCF reranker | NeuMF (GMF element-wise + MLP 1024→512→256→128→32) fused to sigmoid | shadow_only |
| Shadow logistic reranker | Pairwise logistic LTR with forbidden-feature leakage guard | shadow_only |
| Compatibility ML | Color-graph harmony + occasion alignment + price coherence | shadow_only |
| Counterfactual explanation engine | Non-zero component alignment prevents phantom styling claims | live |
| VTON | Preview composite | preview_composite |
Training data for NCF/BPR is derived from real RecommendationSnapshot payloads — impression-based negatives (shown but not acted on) combined with explicit like/dislike/save interaction events.
| Endpoint | Description |
|---|---|
POST /recommendations |
Combined items + outfits response |
POST /recommendations/items |
Ranked items with full reasoning |
POST /recommendations/outfits |
Ranked outfits: slots, total price, breakdown |
GET /recommendations |
Contract/default response (not a stub) |
POST /recommendations/actions |
Record item/outfit feedback signals |
GET /recommendations/history |
Authenticated snapshot history |
GET /state/summary |
Auth-scoped state transparency |
| Endpoint | Description |
|---|---|
GET /health |
Process health check |
GET /live |
Fast liveness probe (no DB/ML dependency) |
GET /ready |
Traffic readiness: DB, catalog, stylist engine |
GET /ops/status |
Operator status with AI/VTON truth |
GET /ops/catalog-quality |
Catalog role counts, image status, repair hints |
GET /ops/data-quality-scorecard |
Offline promotion gates for ML/DL work |
GET /ml/status |
Honest AI/model/VTON runtime status |
GET /catalog (/feed/catalog) |
Beta catalog/feed listing |
POST /GET/DELETE /cart/items |
Auth-scoped cart intent (not checkout) |
POST /GET/DELETE /wardrobe/items |
Auth-scoped owned wardrobe state |
POST /wardrobe/gap-analysis |
Missing roles, thin occasion/color coverage |
POST /catalog/ingest-json |
Ingest catalog rows for beta/testing |
| Endpoint | Description |
|---|---|
POST /vton/try-on |
Create try-on preview job (labeled composite unless real provider active) |
GET /vton/jobs/{job_id} |
Read VTON job state and preview truth |
These surfaces exist for internal tooling and older evaluation flows. They are not the canonical product runtime.
POST /recommend— legacy deterministic recommendation referencePOST /preferences/recommend— legacy preference recommendation with optional saved/wardrobe reusePOST /sessions/recommend— legacy session recommendation used by older tests and evaluation flowsPOST /recommendation/compare— legacy comparison endpoint for recommendation snapshotsGET /history/preferences— legacy preference history inspectionGET /state/conflicts— legacy state conflict inspectionPOST /recommendation/action— legacy explicit action mutationGET /capabilities— legacy machine-readable product truthGET /catalog/item/{item_id}/image— legacy verified local catalog-image serving
Example canonical request:
curl -X POST http://127.0.0.1:8000/recommendations/outfits \
-H 'Content-Type: application/json' \
-H 'X-GYF-User-Id: beta-user' \
-d '{
"preferred_occasions": ["date"],
"preferred_styles": ["premium casual"],
"budget_max": 180,
"body_type": "rectangle",
"skin_tone": "warm",
"weather": {
"temperature_c": 29,
"condition": "humid",
"humidity": 74,
"time_of_day": "night"
},
"limit": 4
}'The X-GYF-User-Id header scopes saved items, wardrobe, sessions, and recommendation actions to an explicit user identity.
- Catalog: 63,873 merged items (Myntra 14k + Zappos UT-Zappos50k 50k footwear); 49k with local-verified images
- Qdrant:
fashion_embeddings(63,901 CLIP text vectors),fashion_image_embeddings(50,025 CLIP image vectors) - Interaction labels: captured via
RecommendationSnapshotand explicit action events; behavior-driven ranking ML remains blocked until enough verified data exists - Image truth: demo placeholder assets are local/dev/test fallback; production-grade images require affiliate/licensed/user-owned sources; remote URLs stored as references only
- Trained artifacts: no trained model artifacts are committed; all ML components load from runtime-configured paths or fall back gracefully
Known limitations:
- Local integration tests require reachable PostgreSQL;
make devbrings up local Postgres on host port55432 - Visual embeddings are inactive for live recommendation authority unless a verified Qdrant index is present
- behavior-driven ranking ML is capture-only until enough verified end-user action data exists
- VTON is
preview_compositeby default; no photorealistic provider is active unless runtime status saysactive_provider - Photo analysis is heuristic unless a trained body/skin model is explicitly wired in
- Cart and wardrobe represent intent and state only — not checkout, payment, or retailer integration
- shadow-mode catalog-side AI sidecars and offline CLIP-style foundations exist in
src/gyf; the canonical FastAPI recommendation endpoints exposedeterministic_stylist_engineas the primary live recommendation authority backend/appis the canonical backend;GYF_APP/web-appis the canonical frontendapi/server.pyis legacy/internal reference; it is not the main closed-beta product runtime- Recommendation outputs are deterministic and heuristic by default; the BPR personalization path is
live_assistive(small boost signal, not primary authority) - Body type and skin tone are accepted as assistive styling inputs; skin/tone reasoning does not claim undertone-accurate ML
- Weather-aware ranking is deterministic product logic based on explicit/manual weather context, not an external API
- VTON preview output is labeled
preview/prototypeunless a real model/provider is active and reported by runtime status - Feedback signals are captured; behavior-driven ranking ML remains blocked until sufficient verified data and promotion evaluation exist
- The product avoids fake visual AI, fake aesthetic scores, fake checkout, and fake production try-on claims
backend/app/ ← FastAPI product backend (canonical)
routers/ ← recommendation, vton, catalog, auth, cart, wardrobe, ops
services/ ← recommendation_service.py (6-path CLIP boost map, BPR, anchors)
models/ ← SQLAlchemy ORM (CatalogItem, Interaction, Favorite, …)
src/gyf/ ← Packaged styling intelligence
stylist.py ← Deterministic ranker + BM25 retrieval
clip_bpr_ranker.py ← BPR preference vector in CLIP space (live_assistive)
ncf_ranker.py ← NeuMF NCF reranker (shadow_only)
shadow_reranker.py ← Pairwise logistic reranker (shadow_only)
compatibility_ml.py ← Color-graph + occasion compatibility (shadow_only)
interaction_labels.py ← LTR label extraction + impression negatives
GYF_APP/web-app/ ← React/Vite/Zustand frontend (canonical)
api/server.py ← Legacy/internal reference (not canonical)
tools/ ← Dataset, training, evaluation, and operator CLI scripts
ml/ ← ML experiment code (training, evaluation, serving)
make setup
make prepare-datasets
make devmake dev starts local PostgreSQL on port 55432, applies Alembic migrations, seeds the beta catalog, and launches the API at http://127.0.0.1:8000.
make frontend-install
make frontend-devReact app at http://127.0.0.1:5173.
make help # All targets with descriptions
make bootstrap-live # Full environment bootstrap
make db-migrate # Alembic upgrade head
make seed-beta-catalog # Idempotent catalog seed
make build-real-image-manifest # Build JSONL image metadata from local archives
make import-real-image-manifest # Import verified image references into catalog
make data-quality-scorecard # Offline promotion-gate scorecard
make shadow-reranker-eval # NCF/shadow reranker offline eval
make validate-api-contract # Frontend field-coverage check
make evaluate-confidence-calibration # ECE/MCE/Brier calibration report
make test # Full test suite
make test-gyf # gyf library tests only (no DB)
make test-ml-slow # Slow ML training tests
make lint # Ruff check + format check
make smoke # FastAPI runtime smoke
make verify-release # Release manifest + golden verification
make docker-build # Build Docker image
make docker-up # Start Docker stack
make docker-down # Stop Docker stackmake testCurrent automated suite: 1131 tests.
Golden recommendation evaluation: 22 cases.
make test-gyf # gyf library unit tests (no DB)
make test-ml-slow # long-running ML training convergence tests
make lint # ruff check + format check
make frontend-lint # ESLint on the React app
make frontend-build # production frontend build
make verify-release # release-readiness gate
make smoke # FastAPI runtime smoke (requires local DB)- NCF training pipeline (
tools/train_ncf_ranker.py): end-to-end orchestrator — loads LTR labels → batch-fetches Qdrant embeddings → computes per-user BPR vectors → builds pairwise triples → trains NCF with early stopping → pairwise accuracy evaluation → saves model + JSON report. Promotion criteria logged (accuracy ≥ 0.60, pairs ≥ 500).make train-ncf/make train-ncf-dry - MMR diversity reranker (
src/gyf/diversity_reranker.py): Maximal Marginal Relevance on CLIP embeddings balances relevance vs. embedding-space diversity (λ=0.7default); falls back to role-based round-robin when embeddings unavailable; wired aslive_assistivepost-ranking pass; 18-test coverage - NCF shadow scoring in recommendation service: trained model loaded at warmup via
NCF_MODEL_PATHenv; scores annotated asncf_shadow_scorein every item'sscore_breakdownfor offline NDCG evaluation; zero impact on live ranking order - NCF reranker (NeuMF GMF + MLP) on pre-trained CLIP embeddings; pairwise BCE training;
save_ncf/load_ncfpersistence; 15-test coverage; promotion stateshadow_only - CLIP BPR personalization (arXiv:1205.2618): feature-aware BPR in 512-dim CLIP space; warm-start from positive embedding centroid; gradient ascent with L2 regularization; wired as 6th path (weight 0.07) in CLIP boost map
- Impression-based LTR negatives:
extract_impression_negatives()derives weak negative labels fromRecommendationSnapshotitems shown but not acted on;--no-impressionsflag intools/export_ltr_labels.py - Loom occasion anchors (arXiv:2605.09830): 8 occasions with vibe/anti-vibe anchor texts; lambda-weighted penalty
S_occ = cos(e_i, v_o) − λ × max(0, cos(e_i, a_o) − cos(e_i, v_o)); wired as 5th CLIP path (weight 0.10) - Outfit color clash penalty:
P_color = 0.1 × max(0, n_non_neutral − 2)applied in outfit scoring; neutral color set (black/white/gray/navy/beige/cream/tan/khaki/brown/silver/gold) - Real image ingest pipeline: Myntra images downloaded, background removal via
rembg, CLIP image embeddings generated and upserted to Qdrantfashion_image_embeddings(50k vectors) - 63k merged catalog: Myntra 14k + Zappos UT-Zappos50k 50k; MD5-dedup with brand-aware fingerprinting; 49k local-verified images
- BM25 retrieval: posting-list indexing with precomputed IDF eliminates O(N) scan across all recommendation paths
- Counterfactual explanation engine (arXiv:2203.01310): aligns outfit explanations to non-zero ranking breakdown components;
rich_explanationfield in recommendation responses - Compatibility ML sidecar: color-graph harmony/tension edges, occasion alignment, price coherence, explicit clash rules;
shadow_only, zero weight on live ranking - Shadow logistic reranker: pairwise logistic with
_FORBIDDEN_FEATURESleakage guard, leave-one-case-out CV,make shadow-reranker-eval - Data-quality scorecard: catalog role coverage, image truth, label truth, runtime truth → offline-only ML promotion blockers; exposed at
/ops/data-quality-scorecardandmake data-quality-scorecard - Production data pipeline:
scripts/_catalog_mappings.pySSOT (150+ role mappings, 6 canonical occasions, INR/USD price buckets);normalize_myntra_csv.py,normalize_zappos.py,merge_catalogs.py - Render/Vercel deployment:
render.yaml+vercel.jsonone-click free-tier blueprint;requirements.backend.txtstrips torch/transformers/diffusers (~1.5GB) for free-tier RAM budget - Wardrobe gap analysis: POST
/wardrobe/gap-analysis— missing roles, underrepresented occasions, thin color coverage, actionable suggestions - Frontend API contract validator: static analysis of React
normalizeRecommendationItem/normalizeRecommendationOutfit;make validate-api-contract - Confidence calibration harness: ECE/MCE/Brier scoring;
make evaluate-confidence-calibration
- Collect interaction data → train NCF → promote:
make export-ltr-labels→make train-ncf→ verifypairwise_accuracy ≥ 0.60in report → setNCF_MODEL_PATH=models/ncf_ranker.pt→make shadow-reranker-eval→ promote tolive_assistiveafter measurable NDCG gain - Wire MMR diversity into item ranking:
diversity_reranker.pyis ready; activate by fetching item embeddings from Qdrant in_build_clip_boost_map()and passing them through_rank_items()→diversify()post-pass - Deploy closed beta: set
JWT_SECRET/DATABASE_URL/REDIS_URLin Render dashboard, userender.yaml+vercel.jsonblueprint, thenmake ingest-real-catalogto load 63k catalog - Wire real image manifest:
make build-real-image-manifest→make import-real-image-manifestto replace demo placeholders with Myntra/Zappos-verified image references - Verify catalog role balance:
make data-quality-scorecard+GET /ops/catalog-quality; footwear gap is common in Myntra datasets - TLS termination: nginx.conf HSTS header is commented out; uncomment after Certbot/Let's Encrypt volume or Cloudflare/AWS ALB routing
- Promote behavior-driven ranking ML: keep capture-only until enough verified end-user action data exists for honest model training and baseline comparison
- Promote compatibility ML and shadow reranker: keep
shadow_onlyuntil leave-one-case-out CV shows measurable NDCG gain - Promote visual embeddings / VTON: only after real artifacts/providers, tests, evaluation, and rollback gates exist