inou/docs/INTAKE_SYSTEM_SPEC.md

35 KiB

Inou Intake System — Design Specification

Vision

Transform how non-technical users capture health data. No forms. No configuration. Just conversation that crystallizes into structure.

The user talks, the system learns. Yesterday's input becomes today's smart prompt with previous values pre-filled.


Core Principles

Principle Meaning
Intake AI, not advice AI We capture, structure, and ask smart follow-ups. We never diagnose or recommend treatment.
Complexity on server, simplicity on client All intelligence lives server-side. Apps are dumb renderers.
Templates emerge, not configured User describes leg trainer once → system learns the shape → next time shows fields with previous values.
Conversation, not forms Onboarding isn't a moment, it's a relationship. Learn one thing at a time, naturally.
Show before save Always confirm what was understood. Build trust.

Phase 1: Server + Ugly UI

Goal

Fully functional intake system with web-based interface. Prove the model works before investing in pretty UI or mobile.

Deliverables

1. Data Model Extensions

Prompt table:

prompts (
    prompt_id       INTEGER PRIMARY KEY,
    dossier_id      INTEGER NOT NULL,
    category        TEXT,           -- "medication", "vital", "exercise", "symptom"
    type            TEXT,           -- "Lisinopril", "BP", "leg_trainer"
    question        TEXT,           -- "Did you take your morning meds?"
    
    -- Scheduling
    frequency       TEXT,           -- "daily", "weekly:mon,wed,fri", "once", "until_resolved"
    time_of_day     TEXT,           -- "08:00", "12:00", "21:00", NULL for anytime
    next_ask        INTEGER,        -- epoch: next time to show
    expires_at      INTEGER,        -- epoch: stop asking after this (0 = never)
    
    -- Input definition
    input_type      TEXT,           -- "checkbox", "checkbox_group", "number", "text", "scale", "custom"
    input_config    TEXT,           -- JSON: field definitions, scale labels
    
    -- Grouping
    group_name      TEXT,           -- "morning_meds", "gym_session" — for collapsing
    
    -- Source/linking
    trigger_entry   INTEGER,        -- entry that spawned this (for follow-ups)
    created_by      INTEGER,        -- dossier_id of who created (self, caregiver)
    
    -- Last response (for pre-filling)
    last_response       TEXT,       -- JSON: parsed/structured answer
    last_response_raw   TEXT,       -- what they actually said/typed
    last_response_at    INTEGER,    -- epoch
    
    -- State
    dismissed       INTEGER DEFAULT 0,  -- 1 = "don't ask again"
    active          INTEGER DEFAULT 1,  -- 1 = active, 0 = paused
    
    created_at      INTEGER,
    updated_at      INTEGER
)

CREATE INDEX idx_prompts_dossier ON prompts(dossier_id);
CREATE INDEX idx_prompts_dossier_active ON prompts(dossier_id, active, next_ask);

input_config Field Schema

The input_config column stores a JSON object defining form fields. The frontend renders these directly — no AI interpretation needed.

Structure:

{
  "fields": [
    {
      "key": "duration_min",
      "label": "Duration",
      "type": "number",
      "datatype": "int",
      "min": 1,
      "max": 120,
      "unit": "min",
      "required": true
    },
    {
      "key": "intensity",
      "label": "How hard was it?",
      "type": "select",
      "datatype": "string",
      "options": ["easy", "moderate", "hard"],
      "required": true
    },
    {
      "key": "exercises",
      "label": "Exercises",
      "type": "multiselect",
      "datatype": "string[]",
      "options": ["bench", "squat", "deadlift", "treadmill"],
      "required": false
    },
    {
      "key": "notes",
      "label": "Notes",
      "type": "text",
      "datatype": "string",
      "maxlength": 500,
      "required": false
    }
  ]
}

Field types (what to render):

type datatype renders as
text string text input
textarea string multiline
number int / float number input with +/-
checkbox bool single checkbox
select string dropdown
multiselect string[] checkboxes or multi-picker
scale int 1-5 or 1-10 buttons
time string time picker (HH:MM)
date string date picker

Field properties:

property applies to meaning
key all JSON key in response
label all display label
type all widget type
datatype all validation type
required all must fill
default all pre-fill value (overridden by last_response)
min number minimum value
max number maximum value
unit number suffix to display ("°", "min", "lbs")
step number increment (0.1 for float)
maxlength text character limit
options select, multiselect choices
scale_labels scale e.g. ["terrible", "poor", "ok", "good", "great"]

Response handling:

When rendering a prompt, the frontend:

  1. Reads input_config.fields
  2. For each field, checks last_response[key]
  3. Pre-fills if exists
  4. User submits → stored as new last_response

Entry table additions:

-- Existing entry table, ensure these fields:
entries (
    ...
    parent_id       INTEGER,        -- for nested entries (gym session → exercises)
    data            TEXT,           -- JSON blob for structured details + raw input
    ...
)

Dossier table additions (now live):

-- Added to dossiers table:
    weight_unit     TEXT,           -- "kg" or "lbs"
    height_unit     TEXT,           -- "cm" or "ft"
    last_pull_at    INTEGER,        -- epoch: last time app fetched prompts
    is_provider     INTEGER DEFAULT 0,  -- 1 if healthcare provider
    provider_name   TEXT,           -- practice/clinic name
    away_message    TEXT,           -- auto-responder text
    away_enabled    INTEGER DEFAULT 0   -- 1 if auto-responder active
-- (language already exists on dossier)

2. API Endpoints

GET /api/prompts?dossier=X

Returns prompts due now for this dossier.

Response:

{
  "prompts": [
    {
      "id": "abc123",
      "category": "medication",
      "type": "morning_meds",
      "question": "Did you take your morning medications?",
      "input_type": "checkbox_group",
      "input_config": {
        "items": [
          { "name": "Lisinopril", "amount": "10mg" },
          { "name": "Metoprolol", "amount": "25mg" }
        ]
      },
      "group_name": "morning_meds",
      "group_count": 2,
      "actions": ["took_all", "missed_some", "skip"]
    },
    {
      "id": "def456",
      "category": "exercise",
      "type": "leg_trainer",
      "question": "Leg trainer (morning)",
      "input_type": "custom",
      "input_config": {
        "fields": [
          { "name": "left_min", "label": "Left min", "type": "number", "previous": 20 },
          { "name": "left_angle", "label": "Left °", "type": "number", "previous": 30 },
          { "name": "left_speed", "label": "Left speed", "type": "number", "previous": 4 },
          { "name": "right_min", "label": "Right min", "type": "number", "previous": 20 },
          { "name": "right_angle", "label": "Right °", "type": "number", "previous": 42 },
          { "name": "right_speed", "label": "Right speed", "type": "number", "previous": 5 }
        ],
        "notes_field": true
      },
      "actions": ["save", "skip"]
    },
    {
      "id": "ghi789",
      "category": "followup",
      "type": "symptom_check",
      "question": "Yesterday you had a headache. How are you today?",
      "input_type": "scale",
      "input_config": {
        "options": ["Much better", "Same", "Worse"]
      },
      "actions": ["select", "skip"]
    },
    {
      "id": "open",
      "category": "general",
      "type": "anything_else",
      "question": "Anything else you'd like to mention?",
      "input_type": "freeform",
      "input_config": {
        "placeholder": "Voice, text, or photo...",
        "allow_photo": true,
        "allow_voice": true
      },
      "actions": ["save", "nothing"]
    }
  ],
  "for_dossiers": [
    { "id": "xxx", "name": "Johan", "relation": "self" },
    { "id": "yyy", "name": "Sophia", "relation": "daughter" },
    { "id": "zzz", "name": "Mama", "relation": "mother-in-law" }
  ]
}

POST /api/prompts/respond

Submit response to a prompt.

Request:

{
  "prompt_id": "abc123",
  "dossier_id": "xxx",
  "action": "took_all",
  "response": null
}

Or for custom input:

{
  "prompt_id": "def456",
  "dossier_id": "yyy",
  "action": "save",
  "response": {
    "left_min": 15,
    "left_angle": 25,
    "left_speed": 3,
    "right_min": 15,
    "right_angle": 35,
    "right_speed": 4,
    "notes": "She was tired today"
  }
}

Returns:

{
  "success": true,
  "entries_created": ["entry_id_1", "entry_id_2"],
  "prompt_updated": true
}

POST /api/intake/parse

Parse freeform input (text, voice transcript, photo).

Request:

{
  "dossier_id": "xxx",
  "input_type": "text",
  "content": "Sophia did leg trainer 20 min, left 30 degrees speed 4, right 40 degrees speed 5",
  "attachments": []
}

Or with photo:

{
  "dossier_id": "xxx",
  "input_type": "photo",
  "content": null,
  "attachments": [
    { "type": "image", "data": "base64..." }
  ],
  "context": "supplement_bottle"
}

Returns parsed interpretation for confirmation:

{
  "interpretation": {
    "target_dossier": { "id": "yyy", "name": "Sophia", "confidence": 0.95 },
    "entries": [
      {
        "category": "exercise",
        "type": "leg_trainer",
        "display": "Leg trainer — 20 min",
        "data": {
          "duration_min": 20,
          "left": { "angle": 30, "speed": 4 },
          "right": { "angle": 40, "speed": 5 }
        }
      }
    ],
    "follow_up_question": null,
    "suggested_prompt": {
      "question": "You log leg trainer regularly. Want me to remind you?",
      "options": ["Yes, daily", "Yes, twice daily", "No thanks"]
    }
  },
  "raw": "Sophia did leg trainer 20 min, left 30 degrees speed 4, right 40 degrees speed 5"
}

POST /api/intake/confirm

Confirm parsed interpretation, create entries.

Request:

{
  "interpretation_id": "temp_xxx",
  "confirmed_dossier": "yyy",
  "entries": [...],
  "create_prompt": {
    "answer": "Yes, twice daily"
  }
}

POST /api/intake/ask

Handle intake AI asking follow-up questions.

Request:

{
  "dossier_id": "xxx",
  "context": "unit_clarification",
  "question_type": "weight_unit",
  "user_input": "80"
}

Response:

{
  "question": "Is that kg or lbs?",
  "options": ["kg", "lbs"],
  "store_preference": true
}

GET /api/summary?dossier=X&period=30d

Generate summary for doctor visit.

Response:

{
  "dossier": { "name": "Sophia", "dob": "...", "sex": "female" },
  "period": "last 30 days",
  "sections": {
    "medications": [],
    "therapy": [
      {
        "name": "Leg trainer",
        "frequency": "2x daily",
        "compliance": "12/14 sessions",
        "typical": "L 30° speed 4, R 40° speed 5",
        "trend": "Right side improving (was 35° in December)"
      }
    ],
    "symptoms": [
      { "date": "Dec 28", "type": "headache", "resolution": "resolved after 2 days" }
    ],
    "vitals": {
      "weight": { "start": "32kg", "end": "32.5kg" }
    },
    "imaging": [...],
    "labs": [...],
    "notes": [
      "Tired after school most days",
      "Slept poorly Tuesday, better Wednesday"
    ]
  },
  "markdown": "...",
  "shareable_link": "https://inou.com/s/xxxxx"
}

3. Intake AI Logic (Server-side)

Parsing engine:

  • Receives text/voice transcript/photo
  • Identifies target dossier from context ("Sophia", "my son", "I")
  • Extracts structured data
  • Detects patterns that could become prompts
  • Generates follow-up questions when needed

Prompt generation:

  • From explicit statements: "I take vitamin D every morning" → daily prompt
  • From repeated behavior: 5 similar entries → suggest prompt
  • From concerning entries: "running stool" → follow-up tomorrow

Template learning:

  • First entry with structure → store shape in prompt's input_config
  • Next time → pre-fill with previous values
  • Evolves as user input evolves

Multi-dossier routing:

  • Parse input for person references
  • Match against accessible dossiers
  • Require confirmation if ambiguous
  • Default to last-used dossier if truly unclear

4. Onboarding Flows

Not a wizard. A series of prompts that appear naturally.

Initial (account creation):

  • Name, DOB, sex — required
  • That's it. Done.

Day 1-7 (prompted when they return):

  • "What brings you to inou?" — capture chief concern
  • "Do you take any daily medications?" — yes → capture list
  • "Any supplements?" — yes → capture list
  • "Anything you're tracking? Weight, blood pressure, exercise?"

Week 2+ (contextual):

  • After first lab upload: "Any conditions we should know about?"
  • After logging same thing 5x: "Want me to remind you?"
  • After 2 weeks: "It helps to know family health history. Mind sharing?"

Family history flow:

  • "Is your mother still living?" → [Yes] [No]
  • If no: "How old? What happened?" → captures cause
  • If yes: "Any health conditions?" → open field
  • Same for father, siblings
  • "Anything that runs in the family?" → open field

5. Ugly Web UI

Minimal but functional. Prove the model.

Dashboard:

  • List of prompts due today
  • Grouped where applicable ("Morning meds (8)")
  • Each renders based on input_type
  • "Anything else" always at bottom

Prompt rendering:

  • checkbox_group: checkboxes + "Took all" button
  • number: input field with unit
  • scale: buttons with labels
  • custom: dynamic fields from input_config
  • freeform: text area + photo upload + mic button (browser speech-to-text)

Confirmation modal:

  • Shows parsed interpretation
  • Allows dossier change if wrong
  • Edit button for corrections
  • Save / Cancel

History view:

  • Today's entries
  • Past 7 days
  • Searchable
  • Editable (tap to modify)

Summary view:

  • Generate summary for selected period
  • Copy as text
  • Print
  • Share link

Phase 2: Pretty UI + Mobile

Goal

Native mobile experience with Flutter. Beautiful UI. Local capabilities (speech-to-text, barcode, camera).

Deliverables

1. Flutter App

Architecture:

  • Calls same API endpoints
  • Local speech-to-text (send transcript to server)
  • Local barcode scanning (send code to server for lookup)
  • Local OCR first pass (send text + image if low confidence)
  • Local notifications scheduled from prompt data

Pull model:

  • On app open: GET /api/prompts
  • Background refresh: once daily
  • Store prompts locally for offline
  • Queue responses if offline, sync when connected

Push fallback:

  • Server tracks last_pull_at per dossier
  • If no pull in 3+ days AND prompts due → push notification
  • Push just says "Don't forget to log" — opens app → pull → render

Screens:

Home (Prompts):

  • Cards for each due prompt
  • Swipe to skip
  • Tap to expand/respond
  • Grouped cards for multi-item (morning meds)
  • Always: "Anything else?" at bottom

Input:

  • Big mic button for voice
  • Camera for photos (bottle labels, wounds, readings)
  • Text field
  • Confirmation before save

Log (History):

  • Timeline view
  • Filter by category, dossier
  • Tap to edit

Summary:

  • Pick dossier, pick period
  • Generate and display
  • Share / Export

Dossiers:

  • Switch between accessible dossiers
  • Default for logging (remembers last used)

2. Notification System

Local notifications:

  • App schedules based on prompt times
  • "Morning meds" at 8:00
  • "Leg trainer (evening)" at 18:00
  • Tapping opens app directly to that prompt

Server push:

  • Firebase Cloud Messaging
  • Only when app hasn't pulled in days
  • Generic "Check in with inou"

3. Offline Support

Cached data:

  • Prompts (with input_config, previous values)
  • Recent entries
  • Dossier list

Queued responses:

  • If offline, store locally
  • Sync on reconnect
  • Show "pending" indicator

4. Voice Flow

User taps mic:

  1. Device speech-to-text (on-device)
  2. Transcript sent to server: POST /api/intake/parse
  3. Server parses, returns interpretation
  4. App shows confirmation
  5. User confirms → entry created

Natural language examples:

  • "Sophia did leg trainer, 20 minutes, left 30 degrees speed 4, right 40 degrees speed 5"
  • "I took 2 Tylenol for a headache"
  • "Mom took her pills"
  • "Blood pressure 120 over 80"
  • "Walked for an hour this morning"

5. Photo Flow

Supplement bottle:

  1. User taps camera on "Any supplements?" prompt
  2. Takes photo of bottle
  3. Device OCR extracts text (or barcode scan)
  4. Server: looks up product, extracts dose
  5. Returns: "Vitamin D3 5000 IU — when do you take this?"
  6. User selects [Morning] [Evening] etc.
  7. Prompt created

Vitals (BP monitor, scale):

  1. Photo of display
  2. OCR extracts numbers
  3. Confirm values
  4. Entry created

Injury/symptom:

  1. Photo of knee scrape
  2. User adds context: "My son fell"
  3. Entry created with photo attached

6. Pretty UI Design

Follow existing inou aesthetic:

  • Warm, not clinical
  • IBM Plex Sans
  • Desert palette: warm beige (#F5EDE4), dark brown (#4A3728), terracotta (#C4704B)

Cards with generous padding. Large tap targets. Clear typography.

Accessible: works for someone with shaky hands or poor vision.


Appendix A: Example Prompt Types

Type Input Previous values? Follow-up?
Medication (single) checkbox N/A If missed 3+ days
Medications (group) checkbox_group + "took all" N/A If missed
Vital (number) number field + unit Optional If concerning
Supplement checkbox N/A No
Exercise (simple) number (duration) Yes If goal missed
Exercise (complex) custom fields Yes, all fields No
Symptom follow-up scale N/A If "worse"
Freeform text + voice + photo N/A Based on content

Appendix B: Entry Structure Examples

Simple medication:

{
  "category": "medication",
  "type": "Lisinopril",
  "value": "taken",
  "data": { "dose": "10mg" },
  "timestamp": 1704369600
}

Leg trainer session:

{
  "category": "exercise",
  "type": "leg_trainer",
  "value": "20 min",
  "data": {
    "duration_min": 20,
    "left": { "angle": 30, "speed": 4 },
    "right": { "angle": 40, "speed": 5 },
    "notes": "She was tired today",
    "raw": "Sophia did leg trainer, 20 min..."
  },
  "timestamp": 1704369600
}

Gym session with nested entries:

// Parent
{
  "id": "gym_001",
  "category": "exercise",
  "type": "gym_session",
  "value": null,
  "timestamp": 1704369600
}

// Children
{
  "parent_id": "gym_001",
  "category": "exercise",
  "type": "bench_press",
  "value": "200",
  "data": { "unit": "lbs" }
}
{
  "parent_id": "gym_001",
  "category": "exercise",
  "type": "squats",
  "value": "150",
  "data": { "unit": "lbs" }
}

Supplement from photo:

{
  "category": "supplement",
  "type": "Vitamin D3",
  "value": "1",
  "data": {
    "dose": "5000 IU",
    "brand": "Nature's Way",
    "source": "photo_ocr",
    "photo_id": "attach_xxx"
  },
  "timestamp": 1704369600
}

Family history:

{
  "category": "family_history",
  "type": "mother",
  "value": "deceased",
  "data": {
    "age_at_death": 78,
    "cause": "heart attack",
    "conditions": ["diabetes", "hypertension"],
    "raw": "She had diabetes and high blood pressure, died of a heart attack at 78"
  }
}

Appendix C: Intake AI Prompt Guidelines

Role: You are an intake assistant for a health tracking system. Your job is to:

  1. Parse natural language into structured health data
  2. Ask clarifying questions when needed
  3. Identify which person (dossier) an entry is about
  4. Never give medical advice or interpret results

Parsing rules:

  • Extract: category, type, value, relevant details
  • Preserve raw input in data.raw
  • Resolve aliases (D3 → Vitamin D3, BP → blood pressure)
  • Detect time references ("yesterday", "this morning")
  • Detect person references ("my son", "Sophia", "mom", "I")

Multi-dossier:

  • If person mentioned matches an accessible dossier → route there
  • If ambiguous ("blood pressure 120/80") → ask who
  • If clearly about self ("I walked") → current user's dossier

Follow-up questions:

  • Unknown unit: "Is that kg or lbs?"
  • New supplement: "When do you take this?"
  • Symptom logged: create follow-up prompt for tomorrow
  • Repeated activity: "Want me to remind you about this?"

What NOT to do:

  • Don't interpret if values are good/bad
  • Don't recommend treatments
  • Don't diagnose
  • Don't say "you should see a doctor" (unless asked about resources)

Appendix D: Implementation Order

Phase 1 — Server + Ugly UI

  1. Database: Add prompts table, extend entries
  2. API: /api/prompts, /api/prompts/respond
  3. Web UI: Render prompts, handle responses
  4. Intake parsing: POST /api/intake/parse (start simple, text only)
  5. Confirmation flow: Show interpretation, confirm, save
  6. Prompt generation: From explicit statements ("I take X daily")
  7. Follow-up logic: Symptoms trigger next-day prompts
  8. Template learning: Remember structure from first entry
  9. Previous values: Pre-fill from last entry of same type
  10. Grouping: Collapse multiple meds into "took all"
  11. Multi-dossier: Parse who, confirm, route
  12. Onboarding prompts: Medications, supplements, goals
  13. Summary generation: /api/summary
  14. Family history flow: Guided questions

Phase 2 — Pretty UI + Mobile

  1. Flutter scaffold: Navigation, screens
  2. API integration: Same endpoints
  3. Prompt rendering: All input types
  4. Voice input: On-device STT → server parse
  5. Photo input: Camera, OCR, send to server
  6. Barcode: On-device scan → server lookup
  7. Local notifications: Schedule from prompt times
  8. Offline support: Cache prompts, queue responses
  9. Push fallback: Firebase integration
  10. Polish: Design system, animations, accessibility

Document version: 1.0 Last updated: January 5, 2025


Phase 1b: Provider Chat

Goal

Secure messaging between patients/caregivers and healthcare providers. Replace Spruce for basic communication. FIPS 140-3 encrypted.

Why Providers Switch

Spruce Inou
$24-49/user/month Free
Just messages Messages + patient's complete dossier
HIPAA HIPAA + FIPS 140-3

Data Model (now live)

Messages table:

messages (
    message_id      INTEGER PRIMARY KEY,
    conversation_id INTEGER NOT NULL,
    dossier_id      INTEGER NOT NULL,     -- which patient dossier this relates to
    sender_id       INTEGER NOT NULL,
    recipient_id    INTEGER NOT NULL,
    
    body            TEXT,                 -- encrypted
    attachments     TEXT,                 -- encrypted JSON
    
    sent_at         INTEGER NOT NULL,
    read_at         INTEGER,
    
    auto_reply      INTEGER DEFAULT 0
)

CREATE INDEX idx_messages_conversation ON messages(conversation_id, sent_at);
CREATE INDEX idx_messages_recipient_unread ON messages(recipient_id, read_at);

Conversations table:

conversations (
    conversation_id INTEGER PRIMARY KEY,
    dossier_id      INTEGER NOT NULL,     -- the patient dossier
    provider_id     INTEGER NOT NULL,     -- the provider's dossier
    
    last_message_at INTEGER,
    unread_count    INTEGER DEFAULT 0,
    
    created_at      INTEGER
)

CREATE INDEX idx_conversations_dossier ON conversations(dossier_id);
CREATE INDEX idx_conversations_provider ON conversations(provider_id);

Provider fields (added to dossiers table):

-- See Phase 1 dossier additions for:
--   is_provider, provider_name, away_message, away_enabled

API Endpoints

GET /api/conversations?dossier=X

List all conversations for a dossier (as patient or provider).

{
  "conversations": [
    {
      "id": "conv_123",
      "patient": { "id": "xxx", "name": "Sophia" },
      "provider": { "id": "yyy", "name": "Dr. Patel", "practice": "Kids First Pediatrics" },
      "last_message": {
        "body": "See you at the appointment tomorrow",
        "sent_at": 1704369600,
        "sender": "provider"
      },
      "unread_count": 0
    }
  ]
}

GET /api/messages?conversation=X&limit=50&before=timestamp

Get messages in a conversation (paginated).

{
  "messages": [
    {
      "id": "msg_456",
      "sender": { "id": "xxx", "name": "Johan", "role": "caregiver" },
      "body": "Sophia's leg trainer went well today. See attached notes.",
      "attachments": [
        { "type": "entry", "id": "entry_789", "name": "Leg trainer session" }
      ],
      "sent_at": 1704369600,
      "read_at": 1704369700
    }
  ]
}

POST /api/messages

Send a message.

{
  "conversation_id": "conv_123",
  "body": "Quick question about her medication dosage",
  "attachments": []
}

Response:

{
  "id": "msg_789",
  "sent_at": 1704369600,
  "auto_reply": {
    "id": "msg_790",
    "body": "Thank you for your message. I'm currently unavailable but will respond within 24 hours. For urgent matters, please call 555-1234.",
    "sent_at": 1704369601
  }
}

POST /api/messages/:id/read

Mark message as read.

PUT /api/provider/settings

Update provider settings (away message, etc).

{
  "away_enabled": true,
  "away_message": "Thank you for your message. I typically respond within 24 hours. For urgent matters, call 555-1234."
}

Features

Core:

  • Text messages (encrypted at rest, FIPS)
  • Attach entries from dossier (labs, imaging, notes)
  • Attach photos
  • Read receipts
  • Auto-responder when provider unavailable

Notifications:

  • Push notification on new message
  • Email notification if app not opened in 24h

Provider View:

  • List of patient conversations
  • Unread count badge
  • Click patient → see conversation + dossier summary
  • Quick access to recent entries, labs, imaging

Provider Onboarding

Invited by patient:

  1. Patient sends invite to provider email
  2. Provider receives: "Johan invited you to view Sophia's health dossier"
  3. Provider signs up (free): name, email, practice name
  4. Provider linked to patient's dossier with role = medical
  5. Provider can message, view dossier

Provider invites patients:

  1. Provider creates account (free)
  2. Provider uploads patient list (email CSV) or invites one by one
  3. Patient receives: "Dr. Patel invited you to connect on inou"
  4. Patient signs up → linked to provider
  5. Patient can message, optionally upgrade to full tracking

Patient Tiers

Tier Cost Features
Passive Free Receive messages, basic profile, view shared docs
Active Paid Full dossier: intake AI, tracking, prompts, summaries, imaging, labs, genome

Provider brings patients in free. Some upgrade. That's the revenue.

Auto-Responder Logic

func sendMessage(msg Message) {
    save(msg)
    notify(recipient)
    
    if recipient.is_provider && recipient.away_enabled {
        autoReply := Message{
            sender: recipient,
            recipient: msg.sender,
            body: recipient.away_message,
            auto_reply: true,
        }
        save(autoReply)
        notify(msg.sender)
    }
}

Security

  • All messages encrypted with FIPS 140-3 (AES-256-GCM)
  • TLS 1.3 in transit
  • Messages only visible to conversation participants
  • Providers can only see dossiers they're invited to
  • Audit log for all access

Implementation Order

  1. Database: messages, conversations tables; provider fields on dossier
  2. API: conversations, messages, send, read
  3. Web UI: conversation list, message thread, compose
  4. Auto-responder: provider settings, auto-reply logic
  5. Notifications: push (Firebase), email fallback
  6. Provider invite flow: patient invites provider
  7. Bulk invite: provider imports patient list
  8. Provider dashboard: patient list, unread counts, dossier access

Document version: 1.1 Last updated: January 5, 2025


Appendix E: Entry Categories

Complete list of entry categories supported by the system.

Core (Daily Logging)

Category Description Example Types
medication Medications taken or prescribed Lisinopril, Metoprolol, anti-seizure
vital Vital signs and measurements BP, weight, temperature, SpO2
exercise Physical activity and therapy sessions leg_trainer, walking, gym_session
symptom Symptoms and complaints headache, seizure, fatigue
supplement Supplements and vitamins Vitamin D3, magnesium, iron
note Free-form notes daily observations

Events

Category Description Example Types Nesting
surgery Surgical procedures shunt_placement, shunt_revision, ETV Parent with child procedure entries
hospitalization Hospital stays NICU, inpatient, ER Parent with child entries for daily events
consultation Doctor visits and consults video_call, office_visit, second_opinion Parent with child finding or recommendation entries

Conditions

Category Description Example Types Notes
diagnosis Medical diagnoses IVH, hydrocephalus, epilepsy, ROP Can have status: active, resolved, suspected
device Implanted or external devices VP_shunt, Ommaya_reservoir, cochlear_implant Track settings changes over time as new entries

Development

Category Description Example Types Notes
therapy Therapeutic interventions Vojta, Bobath, Feldenkrais, ABM, speech_therapy Include outcome: effective, ineffective, ongoing
assessment Developmental evaluations motor, speech, cognitive, vision Qualitative findings, milestones

Foundational

Category Description Example Types Notes
birth Birth and prenatal history delivery, Apgar, gestational_age, complications One-time entries, foundational context
history Past medical events without full documentation surgery, illness, injury, allergy Brief recollections: "appendectomy at age 12"
family_history Family medical history mother, father, sibling, maternal_grandmother Include condition, age at diagnosis, outcome

Findings (Interpretations)

Category Description Example Types Notes
imaging_finding Radiologist or AI interpretation MRI_finding, CT_finding, XR_finding Links to DICOM study via data.study_guid
eeg_finding EEG report findings epileptiform_activity, sleep_pattern From EEG monitoring reports

Meta

Category Description Example Types Notes
provider Healthcare providers neurosurgeon, neurologist, therapist Institution, contact, relationship period
question Open medical questions surgical, diagnostic, treatment Answer via child entry when resolved
upload Uploaded files imaging, genetics, document, lab_report Existing category

Nesting Examples

Complex surgery with sub-procedures:

Parent:
  category: surgery
  type: shunt_revision
  value: "Complex Shunt Revision"
  timestamp: 2021-12-10
  data: {
    location: "University Clinic Ulm",
    surgeon: "Dr. Aurelia Peraud",
    indication: "Isolated 4th ventricle with brainstem compression"
  }

Children (parent_id → parent):
  category: procedure, type: shunt_replacement, data: {old: "Codman", new: "Miethke Blu"}
  category: procedure, type: catheter_placement, data: {target: "4th ventricle"}
  category: procedure, type: ETV, data: {location: "3rd ventricle floor"}
  category: procedure, type: catheter_placement, data: {from: "3rd ventricle", to: "anterior brain stem"}

Device with settings history:

-- Initial installation --
category: device
type: miethke_blu_valve
value: "installed"
timestamp: 2021-12-10
data: {setting: "20 cmH2O", component: "M. blue plus", surgeon: "Dr. Peraud"}

-- Setting adjustment --
category: device
type: miethke_blu_valve
value: "adjusted"
timestamp: 2022-08-15
data: {setting: "30 cmH2O", adjusted_by: "Ulm team"}

-- Suspected malfunction --
category: device
type: miethke_blu_valve
value: "malfunction"
timestamp: 2024-10-21
data: {expected_setting: "30 cmH2O", actual_setting: "24 cmH2O", status: "suspected dysfunction"}

Open question with answer:

Parent:
  category: question
  type: surgical
  value: "Is there opportunity for endoscopy to rearrange natural CSF flow?"
  timestamp: 2025-01-01
  data: {status: "unanswered", raised_by: "parent"}

Child (when answered):
  parent_id: [question entry]
  category: answer
  type: specialist_opinion
  value: "Dr. Peraud recommends shunt revision over endoscopy due to..."
  timestamp: 2025-02-15
  data: {source: "video consultation", provider: "Dr. Peraud"}

Consultation with findings:

Parent:
  category: consultation
  type: video_call
  value: "Ulm Neurosurgery Follow-up"
  timestamp: 2024-10-21
  data: {
    institution: "University Clinic Ulm",
    provider: "Prof. Dr. Aurelia Peraud",
    interpreter: "Frau Natalya Kazmina"
  }

Children:
  category: finding, type: imaging, value: "Ventricular enlargement progression"
  category: finding, type: device, value: "Suspected valve dysfunction"
  category: recommendation, type: surgical, value: "Shunt and valve revision recommended"
  category: recommendation, type: screening, value: "MRSA/MRGN testing required pre-op"

Personal medical history (undocumented past events):

category: history
type: surgery
value: "Appendectomy"
data: {age: 12, approximate_year: 2005, notes: "no complications"}

category: history
type: illness
value: "Chickenpox"
data: {age: 6, severity: "normal"}

category: history
type: injury
value: "Broke left arm"
data: {year: 2015, treatment: "cast 6 weeks"}

category: history
type: allergy
value: "Penicillin"
data: {reaction: "rash", severity: "moderate", discovered: "childhood"}

Family history:

category: family_history
type: mother
value: "Breast cancer"
data: {age_at_diagnosis: 58, outcome: "survived", treatment: "mastectomy + chemo"}

category: family_history
type: father
value: "Type 2 diabetes"
data: {age_at_diagnosis: 52, status: "ongoing", management: "metformin"}

category: family_history
type: maternal_grandmother
value: "Alzheimer's disease"
data: {age_at_onset: 72, age_at_death: 81}

category: family_history
type: brother
value: "Healthy"
data: {age: 35, notes: "no known conditions"}

category: family_history
type: family_pattern
value: "Heart disease"
data: {notes: "Multiple paternal relatives with early heart attacks"}