/opt/mawid
NameSizeModeActions
.git/-0755rm
apps/-0755rm
backups/-0755rm
packages/-0755rm
scripts/-0755rm
.env.droplet7840644editdlrm
.gitignore990644editdlrm
.nvmrc80644editdlrm
.prettierignore520644editdlrm
.prettierrc730644editdlrm
Caddyfile2420644editdlrm
DEMO.md53980644editdlrm
deploy.sh7890755editdlrm
docker-compose.droplet.yml10570644editdlrm
docker-compose.prod.yml10330644editdlrm
docker-compose.yml21870644editdlrm
eslint.config.mjs6470644editdlrm
GOLIVE.md83250644editdlrm
package.json6190644editdlrm
pnpm-lock.yaml2477460644editdlrm
pnpm-workspace.yaml1850644editdlrm
PROGRESS.md225590644editdlrm
PROJECT_PLAN.md129970644editdlrm
README.md9820644editdlrm
RUNBOOK.md51250644editdlrm
tsconfig.base.json3160644editdlrm
Edit: /opt/mawid/PROJECT_PLAN.md (12997B)
# Project: WhatsApp-First Booking & No-Show Reduction SaaS for Clinics # (Working name: "Mawid" — rename freely) > **How to use this file:** This is the master build plan. Work through phases **in order**. > Do not start a phase until the previous phase's acceptance criteria all pass. > At the end of each phase, write a short summary of what was built into `PROGRESS.md`. --- ## 1. Product Summary A SaaS for small service businesses (pilot vertical: **dental/aesthetic clinics in Turkey**) where: - Patients book, confirm, reschedule, and cancel appointments **entirely over WhatsApp**, in **Arabic and Turkish** (English fallback). - An AI agent (Claude API) handles the conversation: understands intent, checks availability, books slots. - The system sends **smart reminders** (24h + 2h before) with one-tap Confirm / Cancel buttons. - When a slot is cancelled, the system **auto-refills it** from a waitlist. - The clinic owner gets a **minimal web dashboard** (calendar, patients, settings) but 90% of their interaction is also via WhatsApp (weekly summary messages). **Primary success metric for pilot:** reduce clinic no-show rate by ≥30% within 2 months. --- ## 2. Tech Stack (fixed — do not substitute without asking) | Layer | Choice | Notes | |---|---|---| | Backend | **NestJS 11 + Fastify** | TypeScript, modular | | DB | **PostgreSQL 16 + Prisma** | single DB, multi-tenant by `clinicId` | | Queue/Jobs | **BullMQ + Redis** | reminders, waitlist refill, scheduled sends | | AI | **OpenAI API** (`gpt-5.6-terra` for conversations, `gpt-5.6-luna` for classification/cheap tasks) | tool-calling agent. Changed from Claude API by owner request, 2026-07-18 | | Messaging | **WhatsApp Business Cloud API (Meta)** | webhooks + template messages | | Dashboard | **Next.js 15 (App Router)** + Tailwind + shadcn/ui | minimal, mobile-first | | Auth | Simple email+password with JWT (owner accounts only for MVP) | no OAuth needed yet | | Deployment | Docker Compose → single VPS (DigitalOcean droplet) for pilot | keep it boring | | i18n | All patient-facing strings in **ar / tr / en** | agent replies in the patient's language | **Monorepo layout:** ``` /apps /api → NestJS backend /dashboard → Next.js owner dashboard /packages /db → Prisma schema + client /shared → shared types, zod schemas, i18n message templates docker-compose.yml PROJECT_PLAN.md (this file) PROGRESS.md ``` --- ## 3. Data Model (Prisma — Phase 1 deliverable) Core entities (add fields as needed, keep names): - **Clinic** — id, name, phone (WhatsApp number), timezone (default `Europe/Istanbul`), defaultLanguage, workingHours (JSON per weekday), settings (JSON: reminder offsets, deposit on/off) - **Staff** — id, clinicId, name, role, workingHours (JSON), services (relation) - **Service** — id, clinicId, name (i18n JSON), durationMinutes, price, active - **Patient** — id, clinicId, waPhone (unique per clinic), name, language, notes, tags, lastVisitAt - **Appointment** — id, clinicId, patientId, staffId, serviceId, startsAt, endsAt, status (`pending | confirmed | cancelled | no_show | completed`), source (`whatsapp | dashboard`), cancelReason - **WaitlistEntry** — id, clinicId, patientId, serviceId, preferredWindow (JSON: date ranges/times), status, createdAt - **Conversation** — id, clinicId, patientId, state (JSON — agent memory/slots), lastMessageAt - **Message** — id, conversationId, direction (`in | out`), waMessageId, type, body, payload (JSON), createdAt - **ReminderJob** — id, appointmentId, kind (`24h | 2h`), scheduledFor, status - **AuditLog** — id, clinicId, actor, action, meta (JSON) Multi-tenancy rule: **every query must be scoped by `clinicId`.** Enforce via a Prisma middleware or repository pattern. --- ## 4. Phases ### Phase 0 — Scaffold & Infrastructure **Goal:** running skeleton, nothing functional. Tasks: 1. Init monorepo (pnpm workspaces), NestJS app, Next.js app, Prisma package, shared package. 2. Docker Compose: postgres, redis, api, dashboard. 3. ENV management: `.env.example` with all vars (see §6). Config module with zod validation — app must refuse to boot with missing env. 4. Health endpoints: `GET /health` (api), basic page (dashboard). 5. Set up ESLint + Prettier + a minimal CI script (`pnpm lint && pnpm build && pnpm test`). **Acceptance:** `docker compose up` boots everything; health checks green; CI script passes. --- ### Phase 1 — Database, Auth, Clinic Setup **Goal:** a clinic owner can exist and configure their clinic. Tasks: 1. Implement full Prisma schema (§3) + migrations + seed script (1 demo clinic, 2 staff, 4 services, 10 fake patients). 2. Auth module: register/login (email+password, argon2), JWT access token, guard on all clinic routes. 3. CRUD APIs: clinic settings, staff, services, working hours. 4. Unit tests for auth + tenancy scoping (a user from clinic A must never read clinic B data — write an explicit test for this). **Acceptance:** seed runs; API CRUD works via REST client; tenancy test passes. --- ### Phase 2 — WhatsApp Integration (plumbing, no AI yet) **Goal:** reliable two-way messaging. Tasks: 1. Webhook endpoint for Meta WhatsApp Cloud API (`GET` verify + `POST` receive). Validate signature (`X-Hub-Signature-256`). 2. Outbound message service: text, interactive buttons, list messages, template messages. Retry with backoff via BullMQ. 3. Persist all inbound/outbound messages to `Message` + `Conversation`. 4. Echo mode behind a feature flag: reply "received: {text}" — used only to verify the pipe. 5. **24-hour window handling:** free-form messages only inside the 24h customer service window; outside it, use approved template messages. Build a `canSendFreeform(conversation)` helper. **Acceptance:** send a WhatsApp message to the test number → it's stored, echoed back, visible in DB. Signature validation rejects tampered payloads (test). --- ### Phase 3 — AI Booking Agent **Goal:** the core magic — natural-language booking in ar/tr/en. Architecture: - One agent loop per inbound message: load `Conversation.state` → call Claude with **tool use** → execute tools → persist state → reply. - **Tools to expose to Claude:** - `get_services()` — list clinic services - `get_availability(serviceId, staffId?, dateRange)` — computed from working hours minus existing appointments - `create_appointment(patientId, serviceId, staffId, startsAt)` — creates as `pending` - `reschedule_appointment(appointmentId, newStartsAt)` - `cancel_appointment(appointmentId, reason?)` - `add_to_waitlist(serviceId, preferredWindow)` - `get_patient_appointments(patientId)` - `handoff_to_human(reason)` — flags conversation for owner, agent stops - System prompt requirements: - Detect and reply in the patient's language (Levantine Arabic / Turkish / English). - Never invent availability — always call tools. - Confirm details (service, staff, date, time) before booking. - Concise, warm, WhatsApp-appropriate tone. No markdown. - If asked anything medical → politely deflect + offer to book a consultation. - Escalate to `handoff_to_human` on anger, complaints, or 2 consecutive failed understandings. - Guardrails: max 15 agent turns per conversation per hour (rate limit), strip PII from logs, log every tool call to `AuditLog`. - Use `claude-haiku-4-5` first for a cheap intent gate (booking-related vs FAQ vs other), route to `claude-sonnet-4-6` only when needed. Tasks: 1. Availability engine (pure functions + tests): working hours × staff × service duration × existing appointments → free slots. **This must be exhaustively unit-tested** (edge cases: overlaps, breaks, timezone, DST). 2. Agent service with tool loop as above. 3. Conversation state persistence (compact — store facts/slots, not full transcripts; transcripts already live in `Message`). 4. E2E test with mocked Claude responses covering: happy booking path, reschedule, cancel, waitlist, handoff. **Acceptance:** from a real WhatsApp number, in Arabic and in Turkish: book, reschedule, and cancel an appointment successfully. Availability never conflicts (test proves it). --- ### Phase 4 — Reminders & Confirmations **Goal:** the no-show killer. Tasks: 1. On appointment creation/update, schedule BullMQ jobs: reminder at T-24h and T-2h (offsets configurable per clinic). 2. Reminder message = WhatsApp **template** with interactive buttons: ✅ Confirm / ❌ Cancel / 🔁 Reschedule. 3. Button replies route back through the agent tools (confirm → status `confirmed`; cancel → Phase 5 refill flow; reschedule → agent conversation). 4. If no response to 24h reminder by T-4h → send the 2h reminder as a stronger nudge; mark appointment `unconfirmed_risk` flag for the dashboard. 5. Cancel/reschedule of the appointment must cancel/rebuild its pending jobs (idempotent). **Acceptance:** create an appointment 25h out (use fake timers/short offsets in test mode) → both reminders fire, buttons work end-to-end, job cleanup verified. --- ### Phase 5 — Waitlist & Auto-Refill **Goal:** cancelled slots turn back into revenue. Tasks: 1. When an appointment is cancelled ≥3h before start: find matching `WaitlistEntry`s (service + time window), rank by createdAt. 2. Message the top candidate: "A slot opened {date} {time} — want it?" with Accept/Decline buttons. Hold the slot for 20 minutes (configurable), then cascade to next candidate. 3. Concurrency safety: slot hold must be atomic (DB transaction / row lock) — two candidates must never book the same slot. Write a test for this. 4. Notify the owner via WhatsApp when a slot is refilled ("Slot recovered: {patient}, {service}, {time}"). **Acceptance:** cancel a booked slot with 2 waitlisted patients → first gets offer; on decline/timeout the second gets it; double-booking test passes. ---### Phase 6 — Owner Dashboard (minimal) **Goal:** enough UI to run a pilot, nothing more. Pages (Next.js, mobile-first, Arabic/Turkish UI toggle): 1. **Calendar** — day/week view of appointments, color by status, click → details, manual create/edit/cancel. 2. **Patients** — list, search, profile (history, notes, tags). 3. **Conversations** — read-only transcript viewer + "take over" button (pauses agent for that conversation). 4. **Settings** — working hours, services, staff, reminder offsets, language. 5. **Weekly summary** — also sent as a WhatsApp message to the owner every Monday 09:00 clinic time: appointments count, cancellations, no-shows prevented (confirmed-after-reminder count), refilled slots. **Acceptance:** owner can fully operate a week of the clinic from the dashboard alone; weekly summary job renders correct numbers from seeded data. --- ### Phase 7 — Pilot Hardening **Goal:** safe to put a real clinic on it. Tasks: 1. Structured logging (pino) + request IDs; error tracking (Sentry or self-hosted GlitchTip). 2. Backups: nightly `pg_dump` to object storage; document restore procedure. 3. Rate limiting on webhook + API; input validation everywhere (zod). 4. KVKK/GDPR basics: data export per patient, delete-on-request endpoint, PII minimization in logs. 5. Deployment: Docker Compose on VPS + Caddy (auto-HTTPS); `deploy.sh`; staging vs production env split. 6. Load sanity test: 50 concurrent conversations without message loss. 7. Write `RUNBOOK.md`: how to onboard a clinic, rotate WhatsApp tokens, common failures. **Acceptance:** staging deploy from clean clone using only README + RUNBOOK; restore-from-backup drill succeeds. --- ## 5. Explicitly OUT of scope for MVP - Payments/deposits (Phase 2 of the product, post-pilot) - Multi-clinic chains / franchise features - iOS/Android apps - Marketing campaigns / re-activation blasts - Advanced analytics dashboards - Instagram/Telegram channels Do not build these even if tempting. Note ideas in `IDEAS.md` instead. --- ## 6. Environment Variables (`.env.example`) ``` DATABASE_URL= REDIS_URL= JWT_SECRET= ANTHROPIC_API_KEY= WA_PHONE_NUMBER_ID= WA_BUSINESS_ACCOUNT_ID= WA_ACCESS_TOKEN= WA_WEBHOOK_VERIFY_TOKEN= WA_APP_SECRET= APP_BASE_URL= DASHBOARD_BASE_URL= NODE_ENV= TZ=Europe/Istanbul FEATURE_ECHO_MODE=false ``` --- ## 7. Working Rules for Claude Code 1. **One phase at a time.** Finish, test, summarize in `PROGRESS.md`, then ask before moving on. 2. **Tests are not optional** for: availability engine, tenancy isolation, reminder job lifecycle, waitlist concurrency. 3. Small commits with conventional messages (`feat:`, `fix:`, `test:`...). 4. All patient-facing text lives in `/packages/shared/i18n` — never hardcode strings in agent prompts or services. 5. Timezone discipline: store UTC in DB, convert at the edges using clinic timezone. Never use server local time. 6. If a Meta/WhatsApp API detail is uncertain, stub it behind an interface and flag it in `PROGRESS.md` rather than guessing. 7. Keep dependencies minimal; prefer stdlib/Nest built-ins.