Your skin deserves better than a 10-step routine you found at 2am. 💅
AI-powered skin analysis that actually listens — to your photo, your questionnaire answers, your lifestyle — and gives back something real.
You know that feeling when you buy a serum because a very confident stranger on the internet said it changed their life — and then your skin absolutely hates it? Yeah. Skinwise exists so that doesn't happen to you anymore.
Skinwise is a full-stack skincare analysis MVP that combines a CNN acne detection model with an adaptive questionnaire and a rule-based recommendation engine to give you a personalized, science-backed skin report in under five minutes. No AI hallucinations, no fake promises, no "San Francisco, CA" placeholder text. Just your skin, analyzed properly.
Here's what it actually does:
- 📸 Analyses your photo using a custom-trained EfficientNet CNN to detect acne severity
- 📋 Walks you through a smart questionnaire that adapts based on your concerns (if you don't have acne, it doesn't ask you about acne — revolutionary, we know)
- 🧠 Weighs questionnaire answers at 70%, CNN output at 30% so your self-knowledge matters
- 🧪 Generates ingredient guidance — what to use, what to avoid, and what to approach with caution
- 🌅 Builds personalized AM/PM routines based on your actual skin type and concerns
- 📊 Tracks your progress across assessments so you can see if anything is actually working
- 🔐 Supports magic-link authentication so your history follows you across devices
Educational tool only. Skinwise is not a substitute for professional dermatological advice. When in doubt, see a dermatologist.
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite 5, Tailwind CSS 3, Framer Motion, React Router v6 |
| Backend | FastAPI 0.111, Python 3.11+, Uvicorn |
| Database | MongoDB Atlas (Motor async driver) |
| ML Model | PyTorch 2.3, EfficientNet CNN (3-class acne severity) |
| Auth | JWT via python-jose, magic-link email flow |
| Image Processing | Pillow 10 |
| Deployment | Vercel (frontend) + Render (backend) |
Skinwise/
├── frontend/ # React + Vite application
│ ├── src/
│ │ ├── components/
│ │ │ ├── assessment/ # Adaptive questionnaire UI
│ │ │ ├── dashboard/ # Progress, history, insights cards
│ │ │ ├── results/ # Analysis result sections
│ │ │ ├── layout/ # Navbar, Footer, MainLayout
│ │ │ └── ui/ # Shared design system components
│ │ ├── context/
│ │ │ ├── AssessmentContext.jsx # Questionnaire state
│ │ │ ├── DashboardContext.jsx # Real-time dashboard state
│ │ │ └── UserContext.jsx # Auth / session state
│ │ ├── hooks/
│ │ │ └── useAssessmentFlow.js # API submission logic
│ │ ├── lib/
│ │ │ ├── buildQuestionnairePayload.js # Maps answers → API schema
│ │ │ └── mapAnalysisResult.js # Maps API response → UI props
│ │ ├── pages/
│ │ │ ├── Home.jsx
│ │ │ ├── Assessment.jsx
│ │ │ ├── Results.jsx
│ │ │ ├── Progress.jsx
│ │ │ └── FAQ.jsx
│ │ └── services/
│ │ └── api.js # Centralised API client
│ └── .env.example
│
└── backend/ # FastAPI application
└── app/
├── core/
│ ├── config.py # Pydantic settings (env vars)
│ ├── constants.py # Domain constants
│ ├── dependencies.py # FastAPI DI providers
│ └── security.py # JWT + magic-link token logic
├── database/
│ └── mongodb.py # Motor client lifecycle + indexes
├── ml/
│ ├── inference/
│ │ └── predictor.py # CNN forward pass
│ └── models/
│ ├── Skinwise_acne_model_final.pth
│ └── final_model_config.json
├── models/ # MongoDB document shapes
├── repositories/ # MongoDB CRUD (no business logic)
├── routes/
│ ├── analysis.py # POST /analysis/start, GET /analysis/results/{id}
│ ├── auth.py # POST /auth/request-login, POST /auth/verify
│ ├── dashboard.py # GET /dashboard/overview, GET /dashboard/history
│ └── health.py # GET /health
├── schemas/ # Pydantic v2 API contracts
├── services/
│ ├── analysis_service.py # Questionnaire + CNN → skin profile
│ ├── recommendation_engine.py # Rule-based routine generator
│ ├── dashboard_service.py # History aggregation
│ └── auth_service.py # Magic-link + JWT issuance
├── utils/
│ └── response_formatter.py # Domain → API schema mapping
└── main.py
The core principle: questionnaire first, CNN second.
User Photo + Questionnaire Answers
│
▼
┌─────────────────────────────────────────┐
│ Validation Service │
│ Image format, size, MIME type check │
└─────────────────┬───────────────────────┘
│
┌──────────┴──────────┐
▼ ▼
┌────────────┐ ┌──────────────────────┐
│ CNN Model │ │ Questionnaire Data │
│ (PyTorch) │ │ skin_type │
│ 3 classes: │ │ primary_concern │
│ minimal │ │ secondary_concerns │
│ mild │ │ sensitivity_level │
│ mod/sev │ │ lifestyle factors │
└─────┬──────┘ └──────────┬───────────┘
│ │
│ CNN weight = 30% │ Questionnaire weight = 70%
│ (only for acne, │ (drives skin type, all
│ only if conf ≥ 65%) │ concerns, routines)
└──────────┬────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Analysis Service │
│ Builds SkinProfile + ConcernSnapshots │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Recommendation Engine │
│ Rule-based, concern-driven routines │
│ Different paths for: acne / pigment / │
│ sensitivity / dryness / oiliness │
└─────────────────┬───────────────────────┘
│
▼
AnalysisResultSchema
(stored in MongoDB + returned to frontend)
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/analysis/start |
Optional | Submit questionnaire + image. Returns assessment_id. |
GET |
/analysis/results/{id} |
Optional | Fetch full analysis result. |
POST /analysis/start — multipart/form-data
questionnaire_json string (JSON) QuestionnaireSubmitSchema
image file JPEG or PNG, max 10 MB
Response
{
"assessment_id": "664a...",
"session_id": "session_1234_abcd",
"status": "complete"
}| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/dashboard/overview |
Optional | Full dashboard payload for current session/user. |
GET |
/dashboard/history |
Optional | Paginated history cards. Requires ?session_id=. |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/auth/request-login |
None | Request magic link for email. Returns token in dev mode. |
POST |
/auth/verify |
None | Verify magic link token. Returns JWT. |
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Liveness check. Returns app version + status. |
- Node.js 18+
- Python 3.11+
- MongoDB running locally or a MongoDB Atlas connection string
cd backend
# Create and activate a virtual environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp .env.example .env
# Edit .env — set MONGODB_URI at minimum
# Start the server
uvicorn app.main:app --reloadThe API will be available at http://127.0.0.1:8000.
Interactive docs (dev only): http://127.0.0.1:8000/docs
cd frontend
# Install dependencies
npm install
# Configure environment
cp .env.example .env
# VITE_API_URL defaults to http://127.0.0.1:8000 — no change needed locally
# Start the dev server
npm run devThe app will be available at http://localhost:5173.
Backend .env
APP_ENV=development
DEBUG=true
MONGODB_URI=mongodb://localhost:27017
MONGODB_DB_NAME=Skinwise
SECRET_KEY=your-long-random-secret-here
# Comma-separated — add your frontend URL in production
ALLOWED_ORIGINS=http://localhost:5173
MODEL_PATH=app/ml/models/Skinwise_acne_model_final.pth
MODEL_CONFIG_PATH=app/ml/models/final_model_config.json
MODEL_DEVICE=cpuFrontend .env
VITE_API_URL=http://127.0.0.1:8000-
Push the
backend/folder to a GitHub repository. -
Create a new Web Service on Render.
-
Configure:
- Build command:
pip install -r requirements.txt - Start command:
uvicorn app.main:app --host 0.0.0.0 --port $PORT
- Build command:
-
Set environment variables in the Render dashboard:
Key Value APP_ENVproductionDEBUGfalseMONGODB_URIYour Atlas connection string SECRET_KEYA long, random secret ALLOWED_ORIGINShttps://your-app.vercel.app -
Add a Persistent Disk (1 GB) mounted at
/opt/render/project/src/uploadsto store uploaded images across deploys.
Note on the CNN model weights:
Skinwise_acne_model_final.pth(~15 MB) is included in the repo. If you need to exclude it from git for size reasons, store it in cloud object storage (S3/R2) and download it at startup.
-
Push the
frontend/folder to a GitHub repository. -
Import the project to Vercel. Framework preset: Vite.
-
Set the environment variable:
Key Value VITE_API_URLhttps://your-render-backend.onrender.com -
Deploy. Vercel handles the rest.
Why no LLM? An earlier version used Gemma for generating insights. It was removed because inference was slow (5–15 seconds per request), free-tier API availability was unreliable, and the responses were inconsistent across runs. The current rule-based engine is deterministic, fast (<1 second), and produces results that are just as actionable.
Why questionnaire-first weighting? CNN models see one photo taken in one moment. A person knows whether their skin is dry year-round, how stressed they are, and whether they sleep five hours a night. The questionnaire captures longitudinal self-knowledge that a single image cannot. The CNN is used as a second opinion on acne severity only, and only when its confidence is above 65%.
Why magic-link auth? Password auth for an MVP adds complexity (reset flows, hashing edge cases, brute-force protection) without meaningful benefit. Magic links are simpler to implement, require no password management UX, and work well for a skincare app where sessions are infrequent and low-stakes.
Guest sessions
Every assessment works without an account. A session_id is generated client-side on each new assessment and stored in sessionStorage. The backend groups assessments by session, so guests get full dashboard functionality. Authentication just claims those sessions to a persistent account.
- Email delivery for magic links (SMTP integration)
- Progress charts rendered from multi-assessment history
- Photo comparison view (before / after across assessments)
- Ingredient cross-reference lookup (flag conflicts between current products)
- Push notifications for routine reminders
- Accessibility audit (WCAG 2.1 AA)
Pull requests are welcome. For major changes, please open an issue first to discuss what you'd like to change.
- Fork the repository
- Create a feature branch:
git checkout -b feat/your-feature - Commit your changes:
git commit -m 'feat: add your feature' - Push and open a pull request
MIT — do whatever you want with it, just don't sell it as a medical device.
Made with way too much skincare research and a healthy distrust of marketing claims. 🧴