Lume
SaaS for psychologists — patients, agenda, AI-assisted clinical notes, and finances in one workspace.
MVP in active development. Solo build, end-to-end. The case study describes decisions that are locked in or in flight — items below the "in flight" heading are scoped but not yet shipped.
The problem
Solo psychologists in Brazil run their practice with a stack of mismatched tools: WhatsApp for scheduling, a notebook or Word doc for session notes, a spreadsheet for charges, Google Calendar for the agenda, and a separate process to issue invoices. Nothing talks to anything else. The cost isn't tooling; it's the cognitive load of stitching them together between sessions.
Existing Brazilian practice-management tools either:
- Hide the clinical record behind heavy bureaucracy — they were built for clinics with 20 professionals, not for one psychologist with 30 active patients.
- Bolt AI on as a gimmick — a "summarise the session" button that hallucinates without context, or worse, replaces the psychologist's note with the AI output and loses the original.
I wanted to build something a solo psychologist could start using in five minutes, where AI structures their notes without ever being the source of truth.
The approach
Four product principles drove every architectural decision:
- AI suggests, never finalises silently. The flow is fixed: psychologist writes → clicks "Finalise" → AI proposes structured fields → psychologist reviews and edits → psychologist confirms. The structured output is a draft, not a decision.
- The raw record is sacred. Every finalised session stores the
original free-text note, the structured fields the psychologist
approved, a snapshot of the template that was active at that
moment, the prompt version, and the model used. If the structured
fields are edited later, the change goes in
audit_logs. The underlying note never gets overwritten. - Multi-tenant from day one. Even though the MVP is one
psychologist per account, the schema has
workspaces,workspace_members, androleswith Postgres RLS already wired. Adding a secretary role later is a permissions change, not a migration. - Sessions and charges are separate domains. Each session creates a charge, but they live in different tables with independent status machines. No-show, partial payment, courtesy waiver, and refund all become local rules instead of leaking across the clinical/financial boundary.
Architecture
Every operational query filters by workspace_id; row-level security
in Postgres enforces it at the database layer so a bug in the app
can't leak across tenants. Long-running work — AI extraction, case
summary updates, Calendar sync — runs in a job queue, not in the
request handler. The session-finalise endpoint returns immediately
and the UI polls for the AI result.
Tech decisions & trade-offs
Why Supabase + Postgres instead of a NoSQL store? The domain is relational at every layer (workspace → patients → appointments → session records → charges → invoices). RLS at the database is non-negotiable for sensitive health data. Supabase bundles auth + Postgres + storage from one account, which compresses the solo build.
Why split Google login from Google Calendar consent? OAuth
scopes. Asking for calendar.events at signup tanks conversion —
the user hasn't seen the product yet. Login uses only
openid/profile/email; Calendar is a separate consent offered
in onboarding step 3 and again in settings. The system works fully
without Calendar connected.
Why incremental case-summary updates? A patient's "consolidated summary" sits at the top of their profile and reflects the whole treatment arc. Naïve approach: regenerate from all sessions every time. That blows up tokens and latency as the relationship grows. Lume sends the previous summary + the new session's brief to Claude and asks for the next version. Constant cost per session, linear quality.
Why never send identifiable patient data in the prompt? Defence in depth. The clinical content goes; the patient's name, CPF, and e-mail don't. If the model provider is ever breached or mis-configured, the leaked content can't be tied back to an identifiable person without a separate join through our database.
What's shipped vs. in flight
Shipped (MVP scope locked):
- Workspace, professional profile, and onboarding wizard
- Patient CRUD with soft-delete (inactive, never deleted)
- Agenda with day and month views, conflict detection, no-show flow
- Session editor with auto-save and AI-assisted finalisation
- 3 default analysis fields: complaint, brief summary, evolution
- Charges generated automatically on session completion
In flight:
- Public homepage and pricing page (design from the brand system)
- Stripe subscription with 14-day trial
- Google Calendar bi-directional sync (V1 is system → Google only)
- Audit log surface in the UI (table is populated; admin viewer not yet built)
What I'd do differently — open questions
- Validate the AI loop with a real psychologist before tuning the prompt. I have a schema, a prompt template, and three default fields. I haven't yet sat with a working psychologist and watched them finalise a session. Doing that next is more valuable than any model upgrade.
- Don't ship the multi-template UI in v1. The data model supports versioned templates with custom fields per workspace. The v1 product ships the three default fields hard-coded. The template editor lives in the schema for future flexibility, not in the UI yet.
- Question the recurring-sessions feature. Almost every patient has a weekly slot, so recurrence looks essential. But every competing product I looked at has it and a confused state machine around exceptions ("this week we moved to Thursday"). I'd rather ship without it and add it once the failure modes are observed in real use.
Status & links
- Live demo: lume-psico.vercel.app
- GitHub: MarcosNespolo/lume
- Brand & design system: cool teal + mint, calm and clinical — documented internally; public homepage drops with the v1 launch.