Reef-A-Matic Architecture
This document maps the current Reef-A-Matic application from the UI to API Gateway, Lambda functions, SQS queues, S3 buckets, EventBridge, DynamoDB tables, Stripe, Cognito, and the GitHub Actions deployment path.
It is based on the current implementation in:
apps/ui/src/components/*apps/ui/src/api/*terraform/api-gateway.tfterraform/lambda.tfterraform/sqs.tfterraform/ws-gateway.tfterraform/event-bridge.tfterraform/dyanmodb.tf.github/workflows/deploy.ymlstripe/
System Overview
flowchart LR
subgraph Client["Browser Client"]
LP["Landing / Login"]
APP["CustomAppLayout"]
HOME["Home dashboard"]
DASHEDIT["Dashboard editor"]
TEMPLATEEDIT["Template editor"]
UP["ICPUpload modal"]
ICP["ICP test detail"]
TREND["Element trend modal"]
TEST["Manual test detail"]
DOSE["Dose history views"]
PROGRAM["Program history views"]
PROFILE["User profile / billing modal"]
TANKSETTINGS["Tank settings"]
ADMIN["Admin page"]
NAV["Side navigation + websocket status"]
end
subgraph Auth["Auth"]
COG["Amazon Cognito"]
SES["Amazon SES: reefamatic.com sender identity"]
R53MAIL["Route 53: SES verification, DKIM, MAIL FROM"]
PRESIGNUP["Lambda: user-cognito-pre-register"]
POSTSIGNUP["Lambda: user-cognito-post-register"]
end
subgraph Edge["Frontend Hosting"]
CF["CloudFront"]
UIB["UI S3 bucket"]
DOCSCF["Docs CloudFront"]
DOCSB["Docs S3 bucket"]
end
subgraph Deploy["GitHub Actions / Terraform"]
DEVBR["dev branch"]
PRODBR["prod branch"]
WF["Build and Deploy Platform"]
TF["Terraform env state"]
CFG["Resolve env API, WS, Cognito, CloudFront"]
DOCSGEN["Render architecture docs"]
end
subgraph Observability["Operations Observability"]
CWDASH["CloudWatch dashboard: env-reef-a-matic-operations"]
GRAFANA["Amazon Managed Grafana: env-reefamatic-ops"]
OPSALERT["SNS: env-reefamatic-ops-alerts"]
CWALARMS["CloudWatch alarms"]
CWLOGS["CloudWatch Logs Insights"]
CE["AWS Cost Explorer: service + tag costs"]
end
subgraph Stripe["Stripe"]
SCAT["Stripe catalog Terraform"]
CHECKOUT["Stripe Checkout"]
PORTAL["Stripe Billing Portal"]
SWH["Stripe webhook events"]
end
subgraph Api["REST API Gateway"]
U1["POST /user"]
U2["POST /user/save"]
U3["POST /user/billing/checkout"]
U4["POST /user/billing/portal"]
U5["POST /user/billing/friend-access"]
U6["POST /user/billing/webhook"]
U7["POST /user/admin/config"]
U8["POST /user/admin/config/save"]
U9["POST /user/admin/errors"]
U10["POST /user/admin/overview"]
U11["POST /user/dashboard"]
U12["POST /user/dashboard/save"]
U13["POST /user/dashboard/delete"]
U14["POST /user/template"]
U15["POST /user/template/save"]
U16["POST /user/template/delete"]
U17["POST /user/admin/costs"]
U18["POST /user/target-ranges"]
U19["POST /user/reef-reviews"]
T1["POST /test"]
T2["POST /test/elements/list"]
T3["POST /test/history"]
T4["POST /test/history/icp"]
T5["POST /test/history/list"]
T6["POST /test/history/save"]
T7["POST /test/history/icp/trend"]
C1["POST /corrections"]
D1["POST /dose/elements/list"]
D2["POST /dose/history"]
D3["POST /dose/history/list"]
D4["POST /dose/history/save"]
D5["POST /dose/history/delete"]
P1["POST /program/history"]
P2["POST /program/history/list"]
P3["POST /program/history/save"]
P4["POST /program/history/delete"]
end
subgraph UploadPath["ICP Upload / Processing"]
PRESIGN["Lambda: upload-presigned"]
S3U["S3: env-reef-a-matic-upload"]
CONVERT["Lambda: upload-convert"]
XVAL["Extraction validation gate"]
Q2["SQS FIFO: env-upload-results.fifo"]
EVAL["Lambda: corrections-eval-element-test-result"]
end
subgraph AppLambdas["App Lambdas"]
LUSER1["Lambda: user-get-user-profile"]
LUSER2["Lambda: user-save-user-profile"]
LUSER3["Lambda: user-billing-checkout"]
LUSER4["Lambda: user-billing-portal"]
LUSER5["Lambda: user-billing-friend-access"]
LUSER6["Lambda: user-billing-webhook"]
LUSER7["Lambda: user-admin-config-get"]
LUSER8["Lambda: user-admin-config-save"]
LUSER9["Lambda: user-admin-errors-list"]
LUSER10["Lambda: user-admin-overview-get"]
LUSER11["Lambda: user-dashboard-list"]
LUSER12["Lambda: user-dashboard-save"]
LUSER13["Lambda: user-dashboard-delete"]
LUSER14["Lambda: user-entry-template-list"]
LUSER15["Lambda: user-entry-template-save"]
LUSER16["Lambda: user-entry-template-delete"]
LUSER17["Lambda: user-admin-costs-get"]
LUSER18["Lambda: user-target-ranges"]
LUSER19["Lambda: user-reef-reviews"]
LTEST1["Lambda: test-list-test-elements"]
LTEST2["Lambda: test-get-test-history"]
LTEST3["Lambda: test-get-icp-test-history"]
LTEST4["Lambda: test-list-test-history"]
LTEST5["Lambda: test-save-test-history"]
LTEST6["Lambda: test-get-icp-element-trend"]
LCORR["Lambda: corrections-apply-correction"]
LDOSE1["Lambda: dose-list-dose-elements"]
LDOSE2["Lambda: get-dose-history"]
LDOSE3["Lambda: dose-list-dose-history"]
LDOSE4["Lambda: dose-save-dose-history-apigw"]
LDOSE5["Lambda: dose-delete-dose-history"]
LDOSE6["Lambda: save-dose-history-sqs"]
LPROG1["Lambda: dose-get-program-history"]
LPROG2["Lambda: dose-list-program-history"]
LPROG3["Lambda: dose-save-program-history"]
LPROG4["Lambda: dose-delete-program-history"]
LPROG5["Lambda: generate-past-program-history"]
LPROG6["Lambda: dose-save-daily-dose"]
LPROG7["Lambda: dose-apply-daily-dose"]
end
subgraph AsyncDose["Dose / Program Async"]
Q3["SQS FIFO: env-icp-correction.fifo"]
Q4["SQS: env-dose-save-daily-dose"]
Q5["SQS: env-dose-generate-past-dose"]
EB["EventBridge rule: apply-daily-dose (01:00 UTC)"]
end
subgraph Realtime["WebSocket API"]
WS["API Gateway WebSocket"]
WSC["Lambda: ws-test-history-connect"]
WSD["Lambda: ws-test-history-disconnect"]
WST["Lambda: ws-test-history-notify"]
WSDOSE["Lambda: ws-dose-history-notify"]
WSPROG["Lambda: ws-program-history-notify"]
WSTBL["DynamoDB: env-websocket-connections"]
end
subgraph Data["DynamoDB"]
USER["env-user"]
TANK["env-user-tank"]
TESTH["env-test-history"]
TESTE["env-test-element"]
DOSEH["env-dose-history"]
DOSEE["env-dose-element"]
PROGH["env-program-history"]
DAILY["env-daily-dose"]
DRAFTFB["env-program-draft-feedback"]
CORR["env-element-correction"]
TARGETOVR["env-element-target-override"]
REEFREV["env-reef-review"]
BATCH["env-batch-history"]
TDH["env-tank-dose-history"]
TDE["env-tank-dose-elements"]
APPCFG["env-app-config"]
APPERR["env-app-error"]
USAGE["env-usage-event"]
FRIEND["env-friend-access-grant"]
USERDASH["env-user-dashboard"]
TEMPLATE["env-user-entry-template"]
end
DEVBR --> WF
PRODBR --> WF
WF --> CFG
WF --> TF
WF --> UIB
WF --> CF
WF --> DOCSGEN
DOCSGEN --> DOCSB
DOCSB --> DOCSCF
TF --> CWDASH
TF --> GRAFANA
TF --> OPSALERT
TF --> CWALARMS
LP --> COG
COG --> PRESIGNUP
COG --> POSTSIGNUP
APP --> CF --> UIB
APP --> COG
APP --> U1
APP --> T5
APP --> D3
APP --> P2
NAV --> WS
PROFILE --> U1
PROFILE --> U2
PROFILE --> U3
PROFILE --> U4
PROFILE --> U5
TANKSETTINGS --> U18
ICP --> U19
ADMIN --> U7
ADMIN --> U8
ADMIN --> U9
ADMIN --> U10
ADMIN --> U17
UP --> T1
UP --> S3U
ICP --> T4
ICP --> C1
TREND --> T7
TREND --> T8
TEST --> T2
TEST --> T3
TEST --> T6
HOME --> T5
HOME --> D1
HOME --> D2
HOME --> U11
DASHEDIT --> D1
DASHEDIT --> U11
DASHEDIT --> U12
DASHEDIT --> U13
TEMPLATEEDIT --> D1
TEMPLATEEDIT --> T2
TEMPLATEEDIT --> U14
TEMPLATEEDIT --> U15
TEMPLATEEDIT --> U16
DOSE --> D1
DOSE --> D2
DOSE --> D3
DOSE --> D4
DOSE --> D5
DOSE --> U14
TEST --> U14
PROGRAM --> P1
PROGRAM --> P2
PROGRAM --> P3
PROGRAM --> P4
U1 --> LUSER1
U2 --> LUSER2
U3 --> LUSER3
U4 --> LUSER4
U5 --> LUSER5
U6 --> LUSER6
U7 --> LUSER7
U8 --> LUSER8
U9 --> LUSER9
U10 --> LUSER10
U11 --> LUSER11
U12 --> LUSER12
U13 --> LUSER13
U14 --> LUSER14
U15 --> LUSER15
U16 --> LUSER16
U17 --> LUSER17
U18 --> LUSER18
U19 --> LUSER19
LUSER18 --> TANK
LUSER18 --> TESTE
LUSER18 --> CORR
LUSER18 --> TARGETOVR
LUSER19 --> REEFREV
LUSER19 --> TESTH
LUSER19 --> PROGH
LUSER17 --> CE
LUSER17 --> USAGE
LUSER17 --> TESTH
LUSER17 --> USER
T1 --> PRESIGN
T2 --> LTEST1
T3 --> LTEST2
T4 --> LTEST3
T5 --> LTEST4
T6 --> LTEST5
T7 --> LTEST6
C1 --> LCORR
D1 --> LDOSE1
D2 --> LDOSE2
D3 --> LDOSE3
D4 --> LDOSE4
D5 --> LDOSE5
P1 --> LPROG1
P2 --> LPROG2
P3 --> LPROG3
P4 --> LPROG4
PRESIGN --> S3U
S3U --> CONVERT
CONVERT --> XVAL
XVAL -->|"load validation_required source=tank elements"| TESTE
XVAL -->|"valid required panel"| Q2
XVAL -->|"invalid metadata or duplicates"| TESTH
Q2 --> EVAL
LCORR --> Q3
Q3 --> LDOSE6
LPROG3 --> Q4
LPROG3 --> Q5
Q4 --> LPROG6
Q5 --> LPROG5
EB --> LPROG7
LPROG7 --> Q4
LUSER1 --> USER
LUSER1 --> TANK
LUSER2 --> USER
LUSER2 --> TANK
LUSER3 --> USER
LUSER3 --> CHECKOUT
LUSER4 --> USER
LUSER4 --> PORTAL
LUSER5 --> USER
LUSER5 --> FRIEND
LUSER6 --> SWH
LUSER6 --> USER
LUSER7 --> APPCFG
LUSER8 --> APPCFG
LUSER9 --> APPERR
LUSER10 --> USER
LUSER10 --> TANK
LUSER10 --> TESTH
LUSER10 --> DOSEH
LUSER10 --> PROGH
LUSER10 --> APPERR
LUSER10 --> USAGE
LUSER11 --> USERDASH
LUSER12 --> USER
LUSER12 --> USERDASH
LUSER13 --> USERDASH
LUSER14 --> TEMPLATE
LUSER15 --> TEMPLATE
LUSER16 --> TEMPLATE
POSTSIGNUP --> USER
POSTSIGNUP --> FRIEND
LTEST1 --> TESTE
LTEST2 --> TESTH
LTEST3 --> TESTH
LTEST3 --> PROGH
LTEST3 --> DOSEH
LTEST6 --> TESTH
LTEST6 --> DOSEH
LTEST6 --> CORR
LTEST6 --> TARGETOVR
LTEST6 --> TANK
LTEST4 --> TESTH
LTEST5 --> TESTH
CONVERT --> TESTH
EVAL --> TESTH
EVAL --> CORR
EVAL --> TANK
EVAL --> REEFREV
LCORR --> TESTH
LCORR --> PROGH
LCORR --> DOSEH
LCORR --> REEFREV
LDOSE1 --> DOSEE
LDOSE2 --> DOSEH
LDOSE3 --> DOSEH
LDOSE4 --> DOSEH
LDOSE5 --> DOSEH
LDOSE6 --> DOSEH
LDOSE6 --> TDH
LDOSE6 --> TDE
LPROG1 --> PROGH
LPROG2 --> PROGH
LPROG3 --> PROGH
LPROG4 --> PROGH
LPROG5 --> PROGH
LPROG5 --> DOSEH
LPROG6 --> DAILY
LPROG7 --> DAILY
LPROG7 --> DOSEH
LPROG3 --> DRAFTFB
LPROG3 --> REEFREV
TESTH --> WST
DOSEH --> WSDOSE
PROGH --> WSPROG
LUSER9 --> APPERR
AppLambdas --> CWLOGS
CWDASH --> CWLOGS
CWDASH --> AppLambdas
CWDASH --> Api
CWDASH --> AsyncDose
CWDASH --> Data
GRAFANA --> CWLOGS
GRAFANA --> AppLambdas
CWALARMS --> OPSALERT
WS --> WSC
WS --> WSD
WSC --> WSTBL
WSD --> WSTBL
WST --> WSTBL
WSDOSE --> WSTBL
WSPROG --> WSTBLEnvironment Isolation And Deployment Flow
flowchart TB
DEV["dev branch"] --> GHA["Build and Deploy Platform workflow"]
PROD["prod branch"] --> GHA
GHA --> DETECT["Detect changed components"]
DETECT --> BUILDUI["Build UI when apps/ui changed"]
DETECT --> BUILDDOCS["Render architecture docs when docs changed"]
DETECT --> BUILDBE["Build changed Lambda modules"]
DETECT --> APPLYTF["Apply Terraform when backend or terraform changed"]
BUILDUI --> RESOLVE["Resolve config from AWS for target env"]
RESOLVE --> REST["REST API id: env-rest-api"]
RESOLVE --> WSAPI["WebSocket API id: env-websocket-api with env stage + $connect"]
RESOLVE --> COGPOOL["Cognito pool/client: env-user-pool/env-client"]
RESOLVE --> DOMAIN["App URL: dev.reefamatic.com or www.reefamatic.com"]
RESOLVE --> GUARD["Prod guard rejects dev URLs, dev Cognito pool, or dev domain"]
GUARD --> UIS3["Sync UI dist to env-reef-a-matic-ui"]
UIS3 --> CFDIST["Resolve CloudFront distribution by domain"]
CFDIST --> INVALIDATE["Invalidate /*"]
BUILDDOCS --> DOCSHTML["Generate static HTML from markdown + Mermaid"]
DOCSHTML --> DOCSS3["Sync to env-reef-a-matic-docs"]
DOCSS3 --> DOCSCF["Invalidate docs CloudFront distribution"]
BUILDBE --> ARTIFACTS["Upload changed jars to env-reef-a-matic-api"]
ARTIFACTS --> APPLYTF
APPLYTF --> TFSTATE["S3 state key: terraform/env/reef-a-matic-api.tfstate"]
APPLYTF --> AWS["Environment-scoped AWS resources"]
BUILDBE --> SEED["Refresh reference seed JSON"]
SEED --> LOADER["Invoke seed loaders"]
LOADER --> TESTELEMENT["env-test-element reference table"]
LOADER --> DOSEELEMENT["env-dose-element method catalogs"]
LOADER --> CORRECTIONREF["env-element-correction method metadata"]- Pull requests build and validate but do not deploy.
- Merging to
devdeploys the dev environment. - Merging to
proddeploys the prod environment. - The UI no longer trusts repo-level
VITE_*variables for deployed builds. It resolves API, WebSocket, Cognito, and redirect config from AWS for the target environment. - WebSocket config resolution ignores empty duplicate API shells by requiring the API to have the target environment stage and a
$connectroute. - When the test module changes, deployment refreshes the Oceamo element seed JSON and invokes the test seed loader so
env-test-elementhas currentvalidation_requiredflags before new uploads rely on them. - When dose or correction modules change, deployment refreshes method-specific dose catalogs and correction metadata. Triton, Fauna Marin, and Custom / No Method are seeded as separate dose brands/correction versions so UI drawers and backend categorization can follow the tank's selected dosing method.
- Prod UI builds fail if the compiled artifact contains known dev API URLs, the dev WebSocket URL pattern, the dev Cognito user pool id, or
dev.reefamatic.com. - CloudFront invalidation is resolved by the target domain, avoiding cross-environment distribution variables.
Operations Observability
Terraform provisions an AWS-native operations baseline without Prometheus:
- Amazon Managed Grafana workspace
${environment}-reefamatic-opswith CloudWatch and X-Ray data sources - CloudWatch dashboard
${environment}-reef-a-matic-operations - SNS alarm topic
${environment}-reefamatic-ops-alerts - CloudWatch alarms for REST API 5XX errors, selected Lambda errors, SQS oldest message age, and DynamoDB throttled requests
The CloudWatch operations dashboard has an Environment filter with dev and prod, so operators can switch between environments instead of reading combined charts. Alarms are deployed per environment to avoid dev and prod Terraform states managing the same alarm names.
The recent Lambda errors widget queries selected Lambda log groups with separate Logs Insights SOURCE clauses and a shared piped query body, preventing CloudWatch from receiving a comma-joined log group name when the dashboard starts a query.
ICP Upload And Evaluation Flow
sequenceDiagram
participant User
participant UI as ICPUpload modal
participant API as REST API Gateway
participant Pre as upload-presigned
participant S3 as S3 upload bucket
participant Convert as upload-convert
participant Validate as extraction validation gate
participant TestElement as DynamoDB test-element
participant QEval as SQS upload-results.fifo
participant Eval as corrections-eval-element-test-result
participant DailyDraft as Shared DailyDoseDraftProgramService
participant ProgramHistory as DynamoDB program-history
participant ReefReview as DynamoDB reef-review
participant TestHistory as DynamoDB test-history
participant WS as WebSocket notifier
User->>UI: Select 1..n PDFs and click Upload
UI->>API: POST /test validateOnly with requested file count
API->>Pre: enforce upload allowance
Pre-->>UI: allowed or entitlement error
loop per file
UI->>API: POST /test
API->>Pre: invoke
Pre->>TestHistory: Create upload placeholder / PROCESSING state with selected ICP lab brand
Pre-->>UI: signed S3 PUT URL + test id
UI->>UI: Show immediate "uploaded, processing" indicator
UI->>S3: PUT PDF bytes
S3->>Convert: S3 notification
Convert->>TestHistory: Read upload placeholder to resolve ICP lab brand
Convert->>Convert: Extract with Bedrock using the selected lab panel and override canonical metadata and RO/Osmose rows from PDF text
Convert->>TestHistory: Save/refresh ICP row as IN_PROGRESS
Convert->>Validate: Canonicalize and validate extracted metadata + reference tank panel
Validate->>TestElement: Load validation_required source=tank panel for the ICP brand
alt extraction is structurally valid
Validate->>TestHistory: Add MISSING placeholders for absent expected elements in their normal category
Validate->>QEval: Send one message per non-RO extracted element
QEval->>Eval: invoke
Eval->>TestHistory: Enrich categories, messages, targets, corrections, trend signals
Eval->>DailyDraft: Generate Pro daily-dose draft suggestions from active program + tank history
DailyDraft->>ProgramHistory: Create/update draft program
Eval->>ReefReview: Create/update guided Reef Review linked to draft
Eval->>TestHistory: Mark test DONE only after expected elements finish
else extraction has missing metadata or duplicate element names
Validate->>TestHistory: Mark test ERROR and skip corrections/draft generation
end
TestHistory->>WS: DynamoDB stream
WS-->>UI: TEST status update for left nav / detail
endICP Trend And Predictive Detail Flow
sequenceDiagram
participant User
participant Card as ICP element card
participant API as REST API Gateway
participant Trend as test-get-icp-element-trend
participant TankSettings as user-target-ranges
participant TestHistory as DynamoDB test-history
participant DoseHistory as DynamoDB dose-history
participant Corrections as DynamoDB element-correction
participant Overrides as DynamoDB element-target-override
User->>Card: Click chart icon
Card->>API: POST /test/history/icp/trend
API->>Trend: invoke
Trend->>TestHistory: Load historical measured values for tank/element
Trend->>DoseHistory: Load related dosing history
Trend->>Corrections: Load Moonshiners target guidance
Trend->>Overrides: Load tank-level custom target if present
alt Pro or friend/pro-equivalent access
Trend-->>Card: Measured history, dose history, target range, explanation, future projection
else Basic access
Trend-->>Card: Measured history and target range, no future projection
end
opt User manages tank target ranges
User->>API: POST /user/target-ranges
API->>TankSettings: invoke
TankSettings->>Overrides: Close active range and create new active version, or reset to default
TankSettings-->>User: Current ranges and version history
end- Element cards carry compact trend severity styling and a short signal so dangerous drift is visible without crowding the card.
- Detailed trend data is loaded only when the chart icon is clicked.
- Trend modals show the active tank target range as a chart band or line. Editing is handled from the Tank Settings target range page so ranges are managed at the tank level.
- Future projection and advanced dose-response analytics are pro-level features.
- Test detail responses can include lightweight
trendAssessmentmetadata so cards can stand out immediately. - The default target range comes from the selected dosing method's correction reference data, currently Moonshiners formula targets. Tank custom ranges are stored as versioned active/inactive rows per tank/element in
env-element-target-override.
Subscription And Free Access Flow
sequenceDiagram
participant UI as User profile / Admin page
participant API as REST API Gateway
participant Checkout as user-billing-checkout
participant Portal as user-billing-portal
participant Friend as user-billing-friend-access
participant Stripe as Stripe
participant Webhook as user-billing-webhook
participant Cognito as Cognito post-confirmation
participant UserTable as DynamoDB env-user
participant GrantTable as DynamoDB env-friend-access-grant
UI->>API: POST /user/billing/checkout
API->>Checkout: invoke
Checkout->>UserTable: Read Stripe customer, pending checkout, and subscription state
alt Open pending checkout session exists
Checkout->>Stripe: Retrieve checkout session
Checkout-->>UI: Reuse Checkout URL
else Active Stripe subscription exists
Checkout->>Stripe: Create billing portal session
Checkout-->>UI: Portal URL
else New paid subscription
Checkout->>Stripe: Create checkout session with idempotency key
Checkout->>UserTable: Mark checkout_pending / Stripe customer/session
Checkout-->>UI: Checkout URL
end
Stripe->>API: POST /user/billing/webhook
API->>Webhook: invoke
Webhook->>UserTable: Store subscription id, status, tier, price id, clear pending checkout
UI->>API: POST /user/billing/portal
API->>Portal: invoke
Portal->>Stripe: Create billing portal session
Portal-->>UI: Portal URL
UI->>API: POST /user/billing/friend-access
API->>Friend: invoke as admin
Friend->>GrantTable: Save email grant for this environment
alt User already exists
Friend->>UserTable: Set billing_exempt=true, access_tier=PRO
else User signs up later
Cognito->>GrantTable: Check email grant
Cognito->>UserTable: Create user as PRO / billing exempt
end- Free/friend access is account data, not a GitHub secret or build variable, and grants Pro-level access without Stripe billing.
- Friend grants are environment-scoped because the table name includes the environment.
- Granting friend access does not grant admin permissions.
- Admin-only API access is reserved for the built-in bootstrap admin account.
- Production Cognito verification email uses the SES-backed
Reef.A.Matic <no-reply@reefamatic.com>sender. The SES domain identity, DKIM records, and custom MAIL FROM records are managed by the prod Terraform state becausereefamatic.comis shared across environments.
UI Components To Endpoint Map
| UI surface | Primary components | Endpoints called |
|---|---|---|
| App shell | CustomAppLayout, DashboardSideNavigation | POST /user, POST /test/history/list, POST /dose/history/list, POST /program/history/list, websocket connect; the authenticated shell is Tailwind-first with a custom Reef.A.Matic side rail, top page header, profile action, and right-side element drawer while legacy route surfaces continue using existing local components during migration |
| Home dashboard | Home | POST /test/history/list, POST /dose/history/list, POST /dose/elements/list, POST /test/elements/list, POST /test/history/icp/trend, POST /user/dashboard; loads the Reef Review summary first, renders 90-day default/custom trend cards only after the user requests Element Trends, uses the tank-method dose catalog for the default dashboard, uses selected test references and up to two selected dosing elements per custom chart, overlays the tank's Moonshiners/custom target range on each measured chart, opens raw measured/dosed values in a modal, and links those values to the underlying history records |
| Dashboard editor | DashboardEditor, DraggableElementDrawer | POST /test/elements/list, POST /dose/elements/list, POST /user/dashboard, POST /user/dashboard/save, POST /user/dashboard/delete; starts with a blank canvas, lets users drag tests from the right drawer, lets each chart card select up to two tank-method dosing elements to compare against the measured test, saves named dashboards scoped to user and tank, preserves legacy saved dose-element selections, and enforces one saved dashboard for Basic users through the backend |
| Template editor | TemplateEditor, DraggableElementDrawer | POST /dose/elements/list, POST /test/elements/list, POST /user/template, POST /user/template/save, POST /user/template/delete; starts with a blank canvas, remounts from the current router path when switching between test/dose/program templates, opens a type-specific right drawer, lets users create named dose/test/program entry templates from the tank-method dose catalog, and stores templates scoped to user and tank |
| Upload modal | ICPUpload | POST /test, then direct PUT to signed S3 URL |
| Tank settings | TankTargetRanges | POST /user/target-ranges; lists active Moonshiners/default or custom tank target ranges, saves custom ranges as new active versions, closes prior active rows, resets elements to method defaults, and shows per-element version history |
| ICP detail | ICPTest, ParameterWidget | POST /test/history/icp, POST /test/history/icp/trend, POST /corrections; element trend modals show active tank target ranges and link to Tank Settings for edits |
| Reef Review | Home, ReefReviewList, ReefReviewPage, DashboardSideNavigation, ICPTest, Testing | POST /user/reef-reviews; lazy-loads review summaries on the dashboard and review pages instead of blocking the global shell, uses /reef-review as the command-center entry route that opens the highest-priority review when one exists or shows a structured empty command-center state when none exist, links ICP and manual test results to their guided review, and displays a focused Tailwind command-center review with source context, corrections, daily adjustments, decisions, element attention, follow-up outcomes, and links back to the source test and generated draft program when applicable |
| Manual test detail | Testing | POST /test/elements/list, POST /test/history, POST /test/history/save, POST /user/template; applies tank-scoped entry templates so common element sets can be reused without dragging each element individually |
| Dose history | Dosing | POST /dose/elements/list, POST /dose/history, POST /dose/history/list, POST /dose/history/save, POST /dose/history/delete, POST /user/template; loads the selected tank method's dose catalog, applies tank-scoped entry templates, and supports multi-day dose counts that fan out into one dose-history save per date |
| Program history | Program | POST /dose/elements/list, POST /program/history, POST /program/history/list, POST /program/history/save, POST /program/history/delete, POST /user/template; loads the selected tank method's dose catalog and applies tank-scoped program templates so common dosing schedules can be reused without dragging each element individually |
| User profile / billing | UserProfile | POST /user, POST /user/save, POST /user/billing/checkout, POST /user/billing/portal, POST /user/billing/friend-access |
| Admin operations console | AdminConfig, AdminOverview, AppErrorSummary | POST /user/admin/overview, POST /user/admin/costs, POST /user/admin/config, POST /user/admin/config/save, POST /user/admin/errors, POST /user/admin/errors/clear, POST /user/billing/friend-access; shows deployed version/build metadata/release notes, all users with nested tank details, live login status from active WebSocket connections, usage patterns, AWS Cost Explorer service spend, Module tag spend, per-PDF cost attribution, RaM draft suggestion feedback quality, friend Pro access grants, config, recent support errors, and error acknowledgement actions |
| Realtime nav badges | WebSocketManager, DashboardSideNavigation | WebSocket $connect / $disconnect, plus notifier pushes from DynamoDB streams |
REST Endpoint To Lambda Map
| Endpoint | Lambda |
|---|---|
POST /user | user-get-user-profile |
POST /user/save | user-save-user-profile |
POST /user/billing/checkout | user-billing-checkout |
POST /user/billing/portal | user-billing-portal |
POST /user/billing/friend-access | user-billing-friend-access |
POST /user/billing/webhook | user-billing-webhook |
POST /user/admin/config | user-admin-config-get |
POST /user/admin/config/save | user-admin-config-save |
POST /user/admin/errors | user-admin-errors-list |
POST /user/admin/errors/clear | user-admin-errors-clear |
POST /user/admin/overview | user-admin-overview-get |
POST /user/admin/costs | user-admin-costs-get |
POST /user/dashboard | user-dashboard-list |
POST /user/dashboard/save | user-dashboard-save |
POST /user/dashboard/delete | user-dashboard-delete |
POST /user/template | user-entry-template-list |
POST /user/template/save | user-entry-template-save |
POST /user/template/delete | user-entry-template-delete |
POST /user/target-ranges | user-target-ranges |
POST /user/reef-reviews | user-reef-reviews |
POST /test | upload-presigned |
POST /test/elements/list | test-list-test-elements |
POST /test/history | test-get-test-history |
POST /test/history/icp | test-get-icp-test-history |
POST /test/history/icp/trend | test-get-icp-element-trend |
POST /test/history/list | test-list-test-history |
POST /test/history/save | test-save-test-history |
POST /corrections | corrections-apply-correction |
POST /dose/elements/list | dose-list-dose-elements |
POST /dose/history | get-dose-history |
POST /dose/history/list | dose-list-dose-history |
POST /dose/history/save | dose-save-dose-history-apigw |
POST /dose/history/delete | dose-delete-dose-history |
POST /program/history | dose-get-program-history |
POST /program/history/list | dose-list-program-history |
POST /program/history/save | dose-save-program-history |
POST /program/history/delete | dose-delete-program-history |
POST /test/elements/list returns the selectable tank test catalog for manual tests, templates, and custom dashboards. The seed table also stores RODI panel rows for ICP rendering, so the list Lambda filters to source=tank and defensively deduplicates by normalized element name before returning picker options.
Element trend charts match related tested and dosed names through canonical element groups. For example, Manganese charts include Manganese and Manganese-X doses, Iron charts include Iron and Iron-X doses, and Phosphorus charts include Phosphate dosing. Chart lines render each series from its own measured or dosed points so unrelated dates do not break the other line, and the X axis uses numeric timestamps so sparse measured points and dense dosing points align by actual date rather than array position.
Async Components
S3
| Bucket | Purpose |
|---|---|
env-reef-a-matic-ui | Static UI hosting origin behind CloudFront |
env-reef-a-matic-api | Lambda artifact/bootstrap bucket |
env-reef-a-matic-upload | Uploaded ICP PDFs; S3 notification target for upload-convert |
dev-reef-a-matic-api | Shared Terraform backend bucket; environment state is separated by key |
Stripe
| Component | Purpose |
|---|---|
stripe/ Terraform | Defines Basic and Pro products/prices in the selected Stripe workspace |
| Checkout sessions | New paid subscription start flow from user profile; backend reuses open pending sessions, redirects existing subscribers to the portal, and uses Stripe idempotency keys to prevent duplicate subscription starts |
| Billing portal sessions | Customer self-service management flow from user profile |
| Webhook endpoint | Updates env-user with subscription status, tier, price id, and Stripe ids |
SQS
| Queue | Producer | Consumer | Purpose |
|---|---|---|---|
env-upload-results.fifo | upload-convert after extraction validation passes | corrections-eval-element-test-result | Per-element ICP evaluation and categorization |
env-icp-correction.fifo | corrections-apply-correction, generate-past-program-history | save-dose-history-sqs | Turn recommended corrections into dose-history writes |
env-dose-save-daily-dose | dose-apply-daily-dose after reading active env-program-history records | dose-save-daily-dose | Schedule/apply recurring daily dose records |
env-dose-generate-past-dose | dose-save-program-history | generate-past-program-history | Backfill retrospective dose history from programs |
EventBridge
| Rule | Target | Purpose |
|---|---|---|
apply-daily-dose | dose-apply-daily-dose | Runs once per day at 01:00 UTC, reads active program-history records, filters elements by selected day of week, and pushes due entries into the daily-dose flow |
WebSocket
| Component | Purpose |
|---|---|
| API Gateway WebSocket API | Browser connects with Cognito token query param |
ws-test-history-connect / ws-test-history-disconnect | Manage connection lifecycle |
ws-test-history-notify | Push test-history stream updates to clients |
ws-dose-history-notify | Push dose-history stream updates to clients |
ws-program-history-notify | Push program-history stream updates to clients |
env-websocket-connections | Stores active websocket connection ids |
DynamoDB Tables
| Table | Role |
|---|---|
env-user | User profile, access tier, Stripe customer ids, billing state, friend/free access state |
env-user-tank | Tank profile per user |
env-app-config | Admin-managed runtime settings, including Basic upload window minutes and Bedrock prompt templates such as bedrock_prompt_reef_review_interpretation |
env-app-error | Persisted system error records keyed by support reference id, including operation, user id, tank id, request ids, exception summary, and stack trace |
env-usage-event | Admin cost attribution and metering events by user, tank, resource type, quantity, estimated USD cost, correlation id, and timestamp |
env-friend-access-grant | Admin-created free-access email grants, applied immediately or at signup/profile load |
env-user-dashboard | User-defined dashboard names, legacy selected element ids, and custom chart definitions keyed by dashboard id with a user_id_tank_id-index for tank-scoped dashboard lookup; each custom chart stores one test element id and up to two dosing element ids; legacy dose-element ids can still render for dashboards saved before the test-selection flow; Basic users can save one per tank, Pro users can save unlimited dashboards |
env-user-entry-template | User-defined manual entry templates for Test, Dose, and Program pages, keyed by template id with a user_id_tank_id_template_type-index for tank/type-scoped lookup |
env-test-element | Test element metadata/catalog |
env-test-history | Manual test history and ICP history, plus ICP element lists and recommendations |
env-reef-review | Reef Review records keyed by review id with user/tank, test-history, and draft-program indexes. ICP completion generates one review per test with status, severity, comparison context, triage findings, recommended actions, generated draft-program links, applied correction progress, and user decisions |
env-batch-history | Batch-oriented test history support data |
env-element-correction | Correction formulas, categories, comments, targets |
env-element-target-override | Versioned tank-level custom target ranges for ICP trend charts. Active rows override the method default, inactive rows preserve range history, and reset closes the active row so Moonshiners/default guidance is used again |
env-dose-element | Dose element metadata/catalog |
env-dose-history | Saved dose-history records |
env-program-history | Saved dosing programs, including active/draft/inactive |
env-daily-dose | Legacy daily-dose projection table; scheduled dosing now derives due entries from active env-program-history records |
env-program-draft-feedback | Human-in-the-loop feedback captured when a user changes RaM draft program suggestions before activation |
env-tank-dose-history | Tank/date dose aggregation |
env-tank-dose-elements | Tank/date/element dose aggregation |
env-websocket-connections | Active websocket connections, including user id and connection timestamp for admin live login status |
Notes
- The UI reads almost everything through
CustomAppLayoutfirst, then individual route components call deeper detail endpoints. - The authenticated shell and Reef Review 2.0 workflow use Tailwind utility classes for the custom application frame and command-center review UI. Cloudscape theme tokens remain available for older route components until those surfaces are migrated.
- System error handling:
- REST Lambdas use a shared support-reference response for unexpected 500s
- each generated reference id is logged to CloudWatch and persisted to
env-app-error - persisted error records include operation, user id, tank id, API/Lambda request ids, exception summary, and stack trace when available
- the Admin page displays recent error summaries from
POST /user/admin/errors - admins can acknowledge errors through
POST /user/admin/errors/clear, which deletes selected support-reference rows fromenv-app-error - async ICP conversion errors also write the reference id and friendly message to the affected
env-test-historyrecord - Admin usage and cost visibility:
POST /user/admin/overviewaggregatesenv-user,env-user-tank, history tables,env-app-error, andenv-usage-eventPOST /user/admin/costscalls AWS Cost Explorer for actual spend grouped by AWS service and by the activeModulecost allocation tag while filtering by the activeEnvironmenttag- the Admin Usage & Costs tab combines Cost Explorer actuals with RaM per-PDF attribution so a single ICP upload can be reviewed by service cost split
- existing history records provide immediate user/tank activity counts
env-usage-eventprovides durable per-user/per-tank/per-PDF cost attribution as workflows emit metering events with a correlation id, while older PDFs fall back to calculated estimates from their stored records- Admin HITL suggestion feedback:
POST /user/admin/overviewalso readsenv-program-draft-feedback- the Admin page's RaM Feedback tab summarizes suggestion-vs-activated drift by amount, selected days, weighted weekly exposure, element, user, and recency
- the same tab compares activated draft feedback to the next follow-on ICP result when available, including elapsed days, target-distance before/after, and average outcome improvement
- large draft edits are highlighted so repeated misses can be reviewed before they become a support pattern
- ICP uploads are the most asynchronous part of the system:
- REST creates the signed upload URL
- S3 triggers conversion
- conversion uses Bedrock for extraction, then deterministically overrides canonical fields such as
Analysis No, sample date, tank measurements, and Osmose/RO measurements from PDF text before writing the history row - conversion writes the initial history row directly and validates required metadata, duplicate element names, and the
validation_required=truesource=tankpanel fromenv-test-elementbefore fan-out - Osmose/RO measurements are stored as separate ICP result cards, for example
Copper (RO), in theOsmosecategory and are not sent to correction/draft-program evaluation - missing expected elements are added as
status=MISSINGplaceholders in their normal ICP category so the user can investigate - structurally invalid extraction, such as missing metadata or duplicate element names, is saved as
ERRORand never reaches corrections or draft-program generation - correction and websocket updates converge back into
env-test-history - upload placeholders and websocket updates keep the UI from relying on polling for final state
- Test reference data deploy:
- when the test module changes, the deploy workflow refreshes the Oceamo element seed JSON in
reef-a-matic-seed-data - the workflow also refreshes the Triton ICP element seed JSON so Triton uploads can use a Triton-branded reference panel without enabling Triton as a user profile dosing method
- the workflow invokes
env-test-load-test-datafor each test seed soenv-test-elementcarries currentvalidation_requiredflags before new uploads depend on them - Dose and program history also have async behavior:
- saving a program can enqueue backfill work
- inactive-program backfill caps generated dose-history records at the current date and honors each element's selected weekdays
- active programs with a start date on or before today keep their open-ended active record, schedule future daily dosing, and enqueue a bounded backfill through today
- editing the current active program versions the record instead of updating it in place: the existing active program is closed as inactive, the edited values are saved as a new active program, and historical generated dose records are not rewritten
- applying ICP corrections can enqueue dose-history writes
- EventBridge creates daily dose-history records automatically by reading active program records and applying the selected weekdays for each element
- Manual dose entry can create several dated dose-history records from one screen when an element has a multi-day dose count. New saves merge with existing dose-history records on those dates instead of replacing unrelated elements.
- Manual test, dose, and program templates are stored separately from history records and scoped by user, tank, and template type.
- Draft dosing programs can be created from ICP processing and can be activated later, moving the prior active program to inactive status. Pro draft generation is centralized in the shared
DailyDoseDraftProgramService, which is called by bothcorrections-eval-element-test-resultduring upload processing andtest-get-icp-test-historyduring ICP detail reads. It uses AI daily-dose suggestions with deterministic tank-response guardrails based on the user's own active program snapshot, selected dosing days, ICP history, dose history, prior draft outcomes, measured values, target ranges, and lab guidance. Drafts preserve nonzero active-program elements and actionable suggested changes, but first-ICP drafts without an active baseline do not include zero-dose placeholder elements. Drafts can adjust either the dose amount or the checked days of the week, such as keeping the same amount while reducing frequency. When a user edits a generated draft before activation, RaM stores the original suggestion, activated value, and day-of-week changes as human-in-the-loop feedback. Future draft suggestions can include prior outcome context that compares the source ICP result to the next ICP after activation so RaM can favor adjustments that moved the tank closer to target over an appropriate time window. - Reef Review generation runs after ICP evaluation completes and after manual test saves that produce review-worthy results.
corrections-eval-element-test-resultcreates or updates oneenv-reef-reviewrow for the completed ICP, compares it against the default prior-test scope, stores findings/actions, preserves existing user decision state on retries, links the generated Pro draft program when one exists, and creates per-element daily adjustment actions from draft-program suggestion metadata. When older/restored or first-draft program elements do not carry explicit suggestion metadata, Reef Review derives the current daily value from the latest non-ICP-correction dose history on or before the source test date and uses the draft program value as the suggestion so the daily adjustment rail still has element-level rows. Finding severity always checks absolute measured values against target ranges before trend stability; hazardous fallback thresholds, such as Copper, also provide target bounds when the report/reference row lacks one.test-save-test-historycreates lightweight manual-test reviews when tank target ranges, statuses, or trends need attention; these guide users back to the source test and recent dosing/program context without creating correction checklists or draft programs. Bedrock interpretation only rewrites human-readable ICP review, finding, and action text from deterministic facts; the prompt template is read fromenv-app-configand auto-seeded with a default if missing. Applying ICP corrections updates one-time action progress, including partial completion for multi-day corrections, and review reads/recalculations reconcile correction progress only from ICP-correction dose rows dated on or after the source test date so older ICP corrections cannot make a newer review appear partially applied. Daily adjustment counts include only draft-program rows where amount, unit, or selected dosing days actually changed. Activating a generated draft program marks the draft action applied while recording accepted or overridden values, and review-level completed/dismissed timestamps separate active queues from history. Initial ICP review generation and Reef ReviewRECALCULATEboth support prior-test trend history forLast 3,Last 5,Last 10, andAllcompare scopes by selecting from the chosen lookback and regenerating comparison-derived findings/actions while preserving action state. Reef Review detail reads also compute follow-up outcomes from the next completed test for the tank so users can see whether findings moved closer to target without persisting derived rows. The UI exposes a dashboard review summary, a single Reef Reviews nav entry, a/reef-reviewcommand-center entry point, and detail routes through/reef-review/:id; review summaries are loaded by the dashboard/inbox surfaces instead of the global layout, and dashboard Element Trends are loaded on demand, so initial page load does not fetch review metadata or trend details for unrelated work. - Subscription behavior gates uploads and predictive features:
- new account creation starts with an explicit Free Trial, Basic, or Pro selection
- Free Trial accounts use a 7-day app trial that does not require Basic or Pro checkout
- app trial users receive Pro functionality and 3 total ICP PDF uploads until the trial expires
- upload requests run an allowance preflight before any S3 PUT begins so trial/basic entitlement failures do not partially upload a selected batch
- paid subscriptions use Stripe recurring monthly or annual prices and auto-renew until canceled
- paid subscriptions receive a 30-day free Pro trial with payment collected up front and billing starting after the trial
- launch promotion code
RAM_2026applies 50% off the selected monthly or annual subscription price for the first year - basic: one upload per configured window
- basic: one saved custom dashboard per tank
- pro: unlimited uploads, unlimited saved custom dashboards, future projections, trend warnings, dose-response analytics, and draft program automation
- friend/free access: grants Pro access, bypasses Stripe billing, and is stored on the user record
- canceled, cancel-at-period-end, or inactive Stripe subscriptions retain user, tank, and history records but resolve to no effective app access until billing is restored
- Realtime updates shown in the left navigation are driven by DynamoDB streams into websocket notifier Lambdas, not by polling. Test, dose, and program history streams use new-and-old images so delete events can include the record identity needed for websocket removal messages.
- The app shell applies dose/program websocket menu payloads directly and keeps a short refresh fallback for eventual consistency during async queue fan-out.
- The Admin Users view reads active WebSocket connection rows to show which users are currently logged in, with the UI refreshing the overview periodically.
- Dev and prod are isolated by branch, Terraform state key, resource names, Cognito user pools, API Gateway stages, S3 buckets, CloudFront aliases, and Stripe workspace/key usage. Shared domain email infrastructure is owned by prod Terraform, while each Cognito pool uses the verified SES identity so verification messages come from
Reef.A.Matic <no-reply@reefamatic.com>.