Aesthetix
Two photos become a scored physique report — muscle groups, symmetry, V-taper, body fat — then a coach and a history that tracks the next scan against the last.



The product
People who train seriously still judge progress from the mirror or a gym selfie. Aesthetix is a physique-analysis app: a front and back photo become a structured report in about a minute — scores for eleven muscle groups, composite metrics (symmetry, V-taper, posture, proportions, athleticism, aesthetics), a body-fat estimate, and written priorities for what to train first.
The home screen is one 0–100 physique score, with a rank, streak and XP, and the three weakest focus areas. History and Progress chart score, body fat, and V-taper across scans, so the next photo is compared to the last instead of sitting in a camera roll. Coach is a chat that has already read the latest report — cut versus bulk, calories, what to train — behind Starter, Pro, and Max. The public site sends people to the App Store and Google Play.
My role
Solo build end to end: the React Native / Expo app, the Supabase backend (auth, Postgres, Edge Functions), the GPT-4o Vision scoring pipeline, the RevenueCat paywall, and the marketing site that has to get someone from an ad into a store.

How it's built
React Native on Expo 54, TypeScript in strict mode, Zustand for state, NativeWind for styling, Reanimated for the radar chart and circular-progress rings. Supabase is the backend. GPT-4o Vision does the visual read; it never emits the final 0–100 number. RevenueCat owns the subscription tiers.

Decisions
1. GPT-4o never returns the final score
The model is prompted for coarse 0–5 ordinal ratings per muscle group and category — never a 0–100 number. A hand-tuned non-linear curve maps those ordinals onto the scale the UI shows. Categories are blended with a harmonic mean, so one weak area actually pulls the composite down the way a human judge would, and a body-fat modifier adjusts the result. Keeping the LLM out of the final-number business makes the score reproducible and tunable without re-prompting.
2. The OpenAI call moved server-side after a real exposure
The API key was originally called from the client via an EXPO_PUBLIC_OPENAI_API_KEY env var — which meant it shipped inside the app bundle. It now lives only as a Supabase Edge Function secret. The client never sees it.
3. Gamification state is enforced server-side
XP, streaks, and the free-scan limit are written through Postgres RPCs gated by row-level security, not incremented in local state. A user can't spoof scan counts by tampering with the app. Free-scan abuse is checked separately with a salted hash of device ID plus IP bucket.
