Marcos Nespolo
In progress

Lume

SaaS for psychologists — patients, agenda, AI-assisted clinical notes, and finances in one workspace.

TypeScriptNext.jsTailwindSupabasePostgreSQLClaude API

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:

  1. Hide the clinical record behind heavy bureaucracy — they were built for clinics with 20 professionals, not for one psychologist with 30 active patients.
  2. 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:

  1. 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.
  2. 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.
  3. Multi-tenant from day one. Even though the MVP is one psychologist per account, the schema has workspaces, workspace_members, and roles with Postgres RLS already wired. Adding a secretary role later is a permissions change, not a migration.
  4. 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

Diagram (mermaid · placeholder)

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