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:
- Reads
input_config.fields - For each field, checks
last_response[key] - Pre-fills if exists
- 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
- 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:
- Device speech-to-text (on-device)
- Transcript sent to server: POST /api/intake/parse
- Server parses, returns interpretation
- App shows confirmation
- 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:
- User taps camera on "Any supplements?" prompt
- Takes photo of bottle
- Device OCR extracts text (or barcode scan)
- Server: looks up product, extracts dose
- Returns: "Vitamin D3 5000 IU — when do you take this?"
- User selects [Morning] [Evening] etc.
- Prompt created
Vitals (BP monitor, scale):
- Photo of display
- OCR extracts numbers
- Confirm values
- Entry created
Injury/symptom:
- Photo of knee scrape
- User adds context: "My son fell"
- 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:
- Parse natural language into structured health data
- Ask clarifying questions when needed
- Identify which person (dossier) an entry is about
- 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
- Database: Add prompts table, extend entries
- API: /api/prompts, /api/prompts/respond
- Web UI: Render prompts, handle responses
- Intake parsing: POST /api/intake/parse (start simple, text only)
- Confirmation flow: Show interpretation, confirm, save
- Prompt generation: From explicit statements ("I take X daily")
- Follow-up logic: Symptoms trigger next-day prompts
- Template learning: Remember structure from first entry
- Previous values: Pre-fill from last entry of same type
- Grouping: Collapse multiple meds into "took all"
- Multi-dossier: Parse who, confirm, route
- Onboarding prompts: Medications, supplements, goals
- Summary generation: /api/summary
- Family history flow: Guided questions
Phase 2 — Pretty UI + Mobile
- Flutter scaffold: Navigation, screens
- API integration: Same endpoints
- Prompt rendering: All input types
- Voice input: On-device STT → server parse
- Photo input: Camera, OCR, send to server
- Barcode: On-device scan → server lookup
- Local notifications: Schedule from prompt times
- Offline support: Cache prompts, queue responses
- Push fallback: Firebase integration
- 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:
- Patient sends invite to provider email
- Provider receives: "Johan invited you to view Sophia's health dossier"
- Provider signs up (free): name, email, practice name
- Provider linked to patient's dossier with role = medical
- Provider can message, view dossier
Provider invites patients:
- Provider creates account (free)
- Provider uploads patient list (email CSV) or invites one by one
- Patient receives: "Dr. Patel invited you to connect on inou"
- Patient signs up → linked to provider
- 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
- Database: messages, conversations tables; provider fields on dossier
- API: conversations, messages, send, read
- Web UI: conversation list, message thread, compose
- Auto-responder: provider settings, auto-reply logic
- Notifications: push (Firebase), email fallback
- Provider invite flow: patient invites provider
- Bulk invite: provider imports patient list
- 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"}