# ACCESS Intake Micro App
> **Sandbox Testing Tool** — This app is intended for local Healthie sandbox testing, not production use. Healthie Enterprise handles form rendering natively in production via the patient portal. This app exists to validate form structure, conditional visibility, and submission flows against the Healthie API during development.
## Overview
Lightweight, database-free form renderer that fetches a Healthie form definition, renders it as HTML, collects answers, then creates a patient and submits form answers back to the Healthie API — without persisting PHI locally.
**Intended use:**
- Local sandbox testing of form structure and field types
- Validating conditional visibility logic before production rollout
- Testing the full patient creation → form answer submission flow
- Debugging Healthie API integrations without touching the patient portal
**Constraints:**
- No database, no file storage — pass-through only
- Sole external dependency is the Healthie GraphQL API
- No authentication required (public intake form)
- Basic abuse protection (rate limiting, honeypot, timeout)
- Isolated from main Django app
## Production Use (Future)
The app could be adapted for production if needed (e.g., pre-Enterprise or for external-facing intake). Key changes would be:
| Area | Current (Sandbox) | Production Needed |
|------|-------------------|-------------------|
| Sessions | In-memory dict | Redis / session store |
| File uploads | Temp disk storage | Direct S3 via presigned URL or Enterprise file handling |
| Authentication | None | OAuth2 / SSO |
| reCAPTCHA | Pending keys | Configured and enforced |
| Logging | Console only | Structured logging, audit trail |
| Rate limiting | In-memory per-process | Distributed via Redis |
| Monitoring | None | Health checks, metrics, alerts |
## Architecture
```
Patient browser
│
│ GET /sections/{n}
│ POST /sections/{n}
│ POST /upload/file
▼
┌─────────────────┐
│ intake_app │ ← renders HTML, validates, enforces rate limits
│ (FastAPI) │ ← zero PHI persistence
│ │ ← form definition fetched from Healthie at startup
└──────┬──────────┘
│
│ GraphQL mutations
│ - createClient
│ - createFormAnswerGroup
│ - createFormAnswerFileAttachment (images only)
│ - createDocument (PDFs, sandbox workaround)
▼
┌─────────────────┐
│ Healthie API │ ← form template, patient record, form answers, files
└─────────────────┘
```
## Form Source of Truth
**Healthie is the single source of truth for form structure.** The micro app fetches the live form definition from the Healthie API at startup. Any edits made to the form in the Healthie admin UI are automatically reflected.
### Form Definition Fetch
On startup, the micro app queries Healthie for the form template:
```graphql
query {
customModuleForm(id: "2360739") {
custom_modules {
id
label
mod_type
options
metadata
sublabel
position
required
}
}
}
```
This returns all modules with labels, types, options, sublabels, and metadata. The micro app uses this to render sections dynamically — no hardcoded field definitions.
### Metadata Convention
Healthie module `metadata` field stores JSON configuration for the intake app. This encodes conditional visibility rules and file upload markers directly on the Healthie form.
**Metadata schema:**
```json
{
"access_intake": {
"conditional": {
"depends_on": "module_id",
"rule": "equals|contains|not_contains",
"value": "expected value"
},
"file_upload": {
"max_size_mb": 10,
"allowed_types": ["pdf", "jpg", "png"]
},
"section_label": true
}
}
```
**Setting metadata:**
```graphql
mutation($input: updateCustomModuleInput!) {
updateCustomModule(input: $input) {
clientMutationId
}
}
# Variables:
{
"input": {
"id": "20245147",
"metadata": "{\"access_intake\":{\"conditional\":{\"depends_on\":\"20244729\",\"rule\":\"equals\",\"value\":\"Female\"}}}"
}
}
```
### Metadata Convention by Module Type
| Module Type | Metadata Key | Purpose |
|------------|--------------|---------|
| **Section label** | `access_intake.section_label = true` | Marks module as section header; following modules belong until next label |
| **Conditional section** | `access_intake.conditional.depends_on` | Module ID controlling visibility (e.g., the sex field) |
| **Conditional rule** | `access_intake.conditional.rule` | `equals`, `contains`, `not_contains` |
| **Conditional value** | `access_intake.conditional.value` | Value(s) to match against |
| **File upload** | `access_intake.file_upload` | Text field renders as file upload widget |
### Conditional Metadata
Set on the **first section label** of each conditional section:
| Section | Label Module ID | Metadata |
|---------|----------------|----------|
| Female Health | 20245147 | `{"access_intake":{"conditional":{"depends_on":"20244729","rule":"equals","value":"Female"}}}` |
| Male Health | 20245148 | `{"access_intake":{"conditional":{"depends_on":"20244729","rule":"equals","value":"Male"}}}` |
| Vascular Reports | 20245155 | `{"access_intake":{"conditional":{"depends_on":"20244792","rule":"contains","value":"Vascular Plaque / Atherosclerosis / Vascular Narrowing\|Coronary artery disease / history of heart attack"}}}` |
| Fatty Liver | 20245156 | `{"access_intake":{"conditional":{"depends_on":"20244792","rule":"contains","value":"Fatty liver"}}}` |
| Kidney Reports | 20245157 | `{"access_intake":{"conditional":{"depends_on":"20244792","rule":"contains","value":"Chronic kidney disease / CKD\|Abnormal urine protein or abnormal uACR"}}}` |
### File Upload Metadata
Set on text fields that render as file uploads:
| Module ID | Label | Metadata |
|-----------|-------|----------|
| 20244752 | Upload Female Health Reports | `{"access_intake":{"file_upload":{"max_size_mb":10,"allowed_types":["pdf","jpg","png"]}}}` |
| 20244760 | Upload Male Reproductive Reports | `{"access_intake":{"file_upload":{"max_size_mb":10,"allowed_types":["pdf","jpg","png"]}}}` |
| 20244796 | File Upload (Vascular) | `{"access_intake":{"file_upload":{"max_size_mb":10,"allowed_types":["pdf"]}}}` |
| 20244797 | File Upload (Fatty Liver) | `{"access_intake":{"file_upload":{"max_size_mb":10,"allowed_types":["pdf"]}}}` |
| 20244798 | File Upload (Kidney) | `{"access_intake":{"file_upload":{"max_size_mb":10,"allowed_types":["pdf"]}}}` |
| 20244803 | Upload Baseline Reports | `{"access_intake":{"file_upload":{"max_size_mb":10,"allowed_types":["pdf","jpg","png"]}}}` |
> **Note:** File upload capabilities differ across environments:
> - **Healthie sandbox (native portal):** File uploads are not available at all.
> - **Healthie sandbox (API, our micro app):** The `createCustomModuleFileAttachmentUploadUrl` mutation returns presigned URLs that only accept image content types. PDF uploads fail at the presigned URL step.
> - **Workaround (sandbox + Enterprise):** PDFs can be uploaded as patient documents via `createDocument` mutation (accepts base64 or multipart, supports `file_content_type: "application/pdf"`, links to patient via `rel_user_id`). The document ID can then be referenced in form answers.
> - **Healthie Enterprise (expected):** Full file upload support via form attachment API, including PDFs.
> Non-image files in our micro app are saved temporarily to disk and recorded as text answers during submission.
### Setting All Metadata
A setup script (`intake_app/scripts/setup_metadata.py`) bulk-updates all modules with their metadata. Run once after initial form creation.
## Directory Structure
```
intake_app/
├── app/
│ ├── main.py # FastAPI app, routing, sessions, submission
│ ├── config.py # Settings from env vars
│ ├── healthie.py # Healthie GraphQL client (all mutations/queries)
│ ├── form_engine.py # Parses Healthie modules into sections
│ ├── schemas.py # Pydantic models
│ ├── templates/ # Jinja2 HTML templates
│ │ ├── base.html # Layout shell
│ │ ├── index.html # Landing page
│ │ ├── section.html # Generic section renderer (type-aware)
│ │ ├── complete.html # Submission confirmation
│ │ └── error.html # Styled error page
│ └── static/
│ ├── css/style.css # Form styling
│ └── js/
│ ├── conditional.js # Client-side conditional visibility
│ └── fileupload.js # Client-side file upload widget
├── scripts/
│ └── setup_metadata.py # Bulk-set metadata on Healthie form modules
├── requirements.txt
└── run.sh # Quick start script
```
## Module Type Rendering
The form engine normalizes Healthie module types and renders each type with an appropriate HTML widget:
| Healthie Type | Normalized | Widget | Options Source |
|--------------|------------|--------|---------------|
| `text` | `text` | `` | — |
| `textarea` | `textarea` | `