·
Volledige Technische Architectuur — v0.2
Overzicht
Systemen & verantwoordelijkheden AI context-injectie stack
Data flows
Signaal-pipeline Platform blackbox Spraakmemo's (Ditto) Push-notificaties
Infrastructuur
Multi-tenancy & isolatie Salesforce sync Gegevensverwijdering Salesforce setup Samenvatting

Gravety — Volledige architectuur

Elk systeem, elke dataflow, elke beslissing — uitgewerkt tot op tabelniveau. Dit document is de technische basis voor de bouw. Alle keuzes zijn onderbouwd.

Supabase/Postgres — zorgdata
Salesforce — CRM & business
OpenAI — AI verwerking
Supabase Storage — bestanden
Platform blackbox — anonieme kennislaag
🟢 Supabase (Frankfurt EU) — primaire database
Postgres

Alles wat met zorg te maken heeft. Cliënten, begeleiders, gesprekken, check-ins, signalen, profielen. De app praat uitsluitend met Supabase. Row-level security zorgt voor isolatie tussen organisaties.

Tabel / gebiedInhoudVersleuteld
organizationsNaam, type, zorgpad, instellingen, SF Account ID
org_ai_profilesMethodieken, waarden, communicatiestandaard, grenzen, AI-prompt preview
usersNaam, e-mail, rol, taal, beroepsgeheimbevestiging✓ naam + e-mail
counselor_ai_profilesWerkstijlsamenvatting (AI), persoonlijkheidsassessment, voorkeursaanpakken
client_profilesStressbronnen, strategieën, dynamisch profiel, AI-suggesties
consent_recordsWelke toestemmingen zijn gegeven, wanneer, versie
conversationsMetadata, zichtbaarheid voor begeleider, AI-samenvatting✓ ai_summary
messagesGespreksberichten — cliënt en AI✓ content (per-cliënt sleutel)
check_insDagelijkse en wekelijkse check-in antwoorden
signalsWaarschuwingen + vooruitgang, redenering, betrouwbaarheidsscore, AI-zin✓ ai_sentence
direct_messagesBerichten tussen begeleider en cliënt, crisisberichten✓ content
goalsDoelen van cliënt, voortgang
interventionsUitgevoerde interventies, uitkomst, notities✓ notes
zrm_assessmentsZRM-domeinscores, periode✓ notes
org_knowledge_patternsOrganisatie-eigen kennislaag — anoniem, opt-in
knowledge_patternsPlatform blackbox — anoniem, alle orgs die bijdragen
pattern_nominationsWachtrij voor platform-review door Gravety
audit_logElke actie, door wie, wanneer — append-only
🟠 Supabase Storage — bestanden
Object storage
BucketInhoudBewaartermijnToegang
voice-memosSpraakmemo's van cliënten (audio)30 dagen na transcriptie → auto-deleteCliënt alleen
voice-transcriptionsWhisper-transcripties (tekst)2 jaar (= berichten)Cliënt + begeleider indien gedeeld
exportsAVG-gegevensexports op cliëntverzoek48u na download → auto-deleteCliënt alleen, tijdelijke URL
💡 Audio wordt versleuteld opgeslagen en nooit naar Salesforce gestuurd. Transcripties behandelen we als berichten — zelfde encryptie en retentiebeleid.
🔵 Salesforce — CRM & business
CRM
ObjectInhoudBron
AccountOrganisatienaam, type, status, Postgres UUID als External IDGesynchroniseerd vanuit Postgres
ContactOrg-admin naam + e-mail, gekoppeld aan AccountGesynchroniseerd vanuit Postgres
Subscription__cSeats, prijs, startdatum, verlengingsdatumHandmatig of via Stripe webhook
Task / ActivityFollow-ups, belafspraken, onboarding checkpointsHandmatig door Gravety-team
CampaignOnboarding e-mailflows, renewal-campagnes, upsellSF-native
Health_Score__cAnonieme score: % actieve cliënten, check-in score (geen persoonsdata)Nachtelijk batch vanuit Postgres
❌ CliëntnamenNooit in Salesforce. Technisch geblokkeerd in sync-service.
❌ Gesprekken / check-insNooit in Salesforce.
🟣 OpenAI — AI verwerking
Extern · EU routing
GebruikModelWanneerData die OpenAI ziet
Companion chatGPT-5.4 mini (stream)Per bericht, real-timeContext stack (lagen 0–4) + huidig bericht. Nooit rauwe namen.
Moderatie/v1/moderationsElk bericht in + uitBerichtinhoud (pseudoniem)
Sessie-samenvattingGPT-5.4 miniNa gesprek (async)Gespreksberichten van die sessie
Signaal-analyseGPT-5.4 miniAsync, na check-in of gesprekRecente check-ins + gesprekssamenvattingen
Spraakmemo transcriptieWhisperAsync na uploadAudiobestand (pseudoniem bestandsnaam)
Nominatie-analyseGPT-5.4 nanoBatch, nachtelijkGeanonimiseerde patroondata — geen persoonsdata
⚠️
OpenAI DPA vereist, EU data residency aanvragen, zero data retention instellen — zie eerdere checklist. Cliënt-IDs worden gepseudonomiseerd vóór elke API-call.

Elke keer dat een cliënt een bericht stuurt, worden vijf lagen context samengesteld en als systeem-prompt naar OpenAI gestuurd. De org-laag staat bovenaan — die vormt alles daaronder.

Layer 0 — Org AI profiel
Organisatielaag — leidend voor alles
Tabel: org_ai_profiles (gelezen via org_id van de cliënt) Velden: methodologies[], values[], communication_standard{}, approved_intervention_types[], hard_constraints[] Wanneer bijgewerkt: door org admin in dashboard → geldt direct voor alle sessies Grootte: ~300–500 tokens
Bron: Supabase · gelezen bij sessiestart · gecached 1u
Layer 1 — Begeleider AI profiel
Begeleiderlaag — persoonlijkheid en werkstijl
Tabel: counselor_ai_profiles (gelezen via counselor_id van de cliënt) Velden: working_style_summary (tekst), personality_assessment{}, preferred_approaches[] Wanneer bijgewerkt: na AI-profielgesprek of bevestigde AI-suggestie Grootte: ~150–250 tokens
Bron: Supabase · gelezen bij sessiestart · gecached per sessie
Layer 2 — Dynamisch cliëntprofiel
Cliëntlaag — levend profiel, continu bijgewerkt
Tabel: client_profiles Velden: communication_preferences{tone, language_level, length, no_confrontation} variables{stress_triggers[], regulation_strategies[], warning_signals[]} — met brontag dynamic_profile{current_phase, mood_trend, engagement_trend, recent_pattern_summary} profile_update_suggestions[] — NIET meegestuurd naar AI (nog niet bevestigd) Grootte: ~200–400 tokens
Bron: Supabase · gelezen bij sessiestart · bijgewerkt door nachtelijke batch
Layer 3 — Gesprekssamenvattingen
Geheugen — laatste 3–5 sessies
Tabel: conversations.ai_summary (laatste 3–5 records, gesorteerd op datum) Inhoud: stemming, onderwerpen, wat hielp, signalen — nooit letterlijke citaten Grootte: ~300–600 tokens (afhankelijk van aantal sessies)
Bron: Supabase · gelezen bij sessiestart · versleuteld opgeslagen
Layer 4 — Huidige sessie
Live gesprek
Tabel: messages (WHERE conversation_id = huidig, gesorteerd op created_at) Inhoud: alle berichten van deze sessie — cliënt + AI Grootte: groeit tijdens het gesprek · max context window GPT-5.4 mini: 128k tokens
Bron: Supabase · gelezen bij elk bericht
Totale context per sessie: ~1.000–1.750 tokens systeem-prompt + groeiende gesprekshistorie. Goed binnen het budget van GPT-5.4 mini. Als de context te groot wordt (gesprekken langer dan 30 berichten), worden oudere berichten uit layer 4 gefaseerd eruit gehaald maar NOOIT layer 0–3.

Drie verwerkingsbanen: synchroon voor crisis (veiligheid vóór alles), async voor taalanalyse, nachtelijk batch voor patronen. Elk bericht doorloopt ze in deze volgorde.

Per bericht (elke keer dat de cliënt iets stuurt)

Synchroon · blokkerend
① Moderatie
OpenAI /v1/moderations. Bevat het bericht schadelijke inhoud? Blokkeert reactie als nodig.
Synchroon · blokkerend
② Crisisdetectie
Keyword-check (NL/EN/UK). Resultaat: protocol A (zelfschade) of B (agressie) of geen.
Synchroon
③ AI-reactie
GPT-5.4 mini genereert reactie op basis van volledige context stack. Stream naar cliënt.
Async · wachtrij
④ Taalanalyse
Sentimentanalyse, taalpatroon, terugtrekking — verwerkt binnen 30 sec na bericht.
Async · wachtrij
⑤ Signaal check
Voldoet het patroon aan signaaldrempels? Zo ja → signaal aanmaken + begeleider notificeren.

Na gesprek (triggered door 30 min inactiviteit of cliënt sluit af)

Async · job
① Sessiesamenvatting
AI genereert samenvatting. Slaat op in conversations.ai_summary (versleuteld).
Async · job
② Profielsuggesties
AI vergelijkt gesprek met bestaand profiel. Nieuwe patronen? → profile_update_suggestions[].
Async · job
③ Nominatiecheck
Heeft een interventie een positief resultaat + toestemming? → pattern_nomination aanmaken.

Nachtelijk batch (02:00 elke nacht)

Batch · nachtelijk
① Check-in patronen
AI analyseert trends over alle check-ins van de afgelopen 7 dagen per cliënt.
Batch · nachtelijk
② Dynamisch profiel
dynamic_profile{} bijwerken op basis van trends. Fase-inschatting herevalueren.
Batch · nachtelijk
③ SF health score
Anonieme org-score (% actieve cliënten) sturen naar Salesforce Health_Score__c.
Batch · nachtelijk
④ Reconciliatie
Vergelijk org-aantallen Postgres ↔ Salesforce. Alert bij mismatch.

De platform blackbox is Gravety's eigen kennislaag. Anonieme patronen van alle bijdragende organisaties. Nooit persoonlijk, nooit herleidbaar. De nominatie-flow heeft een menselijke reviewstap — initieel om de AI te trainen, later geautomatiseerd.

Nominatie → Review → Blackbox

1
AI detecteert positieve uitkomst
Interventie geregistreerd met outcome = positive + cliënt heeft anonymised_contribution = true
2
AI maakt pattern_nomination aan
Strips alle persoonlijke data: geen client_id, geen org_id, geen namen, geen vrije tekst. Behoudt: categorie, signaalpatroon, interventie-type, uitkomst, leeftijdsgroep (5-jaar range), zorgpad
3
Naar begeleider voor eerste check
"Heeft deze interventie echt gewerkt?" Begeleider ziet al geanonimiseerd patroon. Goedkeuren of afwijzen + reden.
4
Naar Gravety super admin voor finale review
Menselijke beoordeling bij Gravety. Wachtrij zichtbaar in admin dashboard. Goedkeuren of afwijzen met gestructureerde reden.
✓ Bij goedkeuring
→ Pattern gaat naar knowledge_patterns
→ source_intervention_id uit nomination gewist (definitief anoniem)
→ Beschikbaar voor AI-suggesties (min. 5 cases vereist)
→ Audit trail bewaart wie goedkeurde
✗ Bij afwijzing
→ Reden verplicht (gestructureerd + vrije tekst)
→ Reden gaat in rejection_reasons tabel
→ AI traint op afwijzingen: patronen die X keer zijn afgewezen met zelfde reden worden niet meer genomineerd
→ Self-learning loop: hoe meer reviews, hoe beter de nominaties
Org-eigen kennislaag werkt hetzelfde maar zonder Gravety-stap. Nominatie → begeleider → org admin → org_knowledge_patterns. Blijft volledig binnen de org. Geen kruisbestuiving met andere orgs.
💡
Gravety super admins kunnen nooit de inhoud van de originele gesprekken of cliëntdata lezen. Ze zien uitsluitend het al-geanonimiseerde patroon. Dit is een technische beperking, niet alleen beleid — de source_intervention_id is alleen aanwezig vóór goedkeuring, daarna gewist.

Cliënten kunnen in plaats van typen een spraakmemo inspreken. De AI verwerkt dit als tekst. Het audio-bestand wordt na 30 dagen automatisch verwijderd — alleen de transcriptie blijft.

1
Cliënt neemt op
Audio wordt direct versleuteld in de browser/app — nooit onversleuteld over het netwerk. Max 5 minuten per memo.
2
Upload naar Storage
Versleuteld bestand in Supabase Storage bucket voice-memos. Bestandsnaam = UUID (geen herleidbare naam).
3
Whisper transcriptie
Async job stuurt audio naar OpenAI Whisper. Ondersteunt NL, EN, UK. Resultaat: teksttranscriptie.
4
Opslaan als bericht
Transcriptie wordt opgeslagen in messages tabel met type = 'voice_transcription'. Zelfde versleuteling als tekstberichten.
5
Audio auto-delete
Na 30 dagen verwijdert een scheduled job het originele audiobestand uit Storage. Transcriptie blijft 2 jaar (= bereidtermijn berichten).
6
AI verwerkt
Transcriptie stroomt door dezelfde signaal-pipeline als tekstberichten. Crisisdetectie, taalanalyse, profielupdates — alles werkt op de tekst.

Begeleiders kunnen instellen bij welk signaalniveau ze een pushmelding ontvangen. Kritieke signalen gaan altijd door. De app wordt als PWA (Progressive Web App) gebouwd — geen aparte native app nodig voor notificaties.

🔔
Signaal aangemaakt
In Supabase signals tabel. Severity bepaald.
⚙️
Voorkeur check
Heeft begeleider push aan voor dit niveau? (instelbaar per begeleider)
📤
Push verzend
Via Web Push API (VAPID). Firebase Cloud Messaging als fallback voor iOS.
📱
Begeleider ontvangt
Melding op telefoon/desktop. Tap → opent signaal in dashboard.
SignaalniveauDefaultInstelbaarNacht (00:00–07:00)
Kritiek (zelfschade / crisis)✓ AanNiet uitschakelbaar✓ Altijd doorsturen
Hoog (terugval risico, agressie)✓ Aan✓ Uit te zettenAlleen als begeleider 24/7 aan heeft
Laag / Monitor✗ Uit✓ Aan te zettenNooit
Vooruitgang✗ Uit✓ Aan te zettenNooit
💡
Als een kritiek signaal 1u lang niet bevestigd is, escaleert het naar alle andere begeleiders in de org met de notitie "Primaire begeleider heeft nog niet gereageerd." Na 2u buiten kantooruren: signaal blijft open, wordt opgepakt bij volgende login.

Alle organisaties zitten in dezelfde Supabase-instantie. Isolatie via Row Level Security (RLS) — goedkoper dan aparte instanties, veilig genoeg voor AVG op pilot-schaal. Encryptie per cliënt voegt een tweede isolatielaag toe.

Org A — Arends
RLS org_id = 'uuid-arends'
Begeleider: Arien Arends
8 cliënten · eigen kennislaag
Org B — GGZ Utrecht
RLS org_id = 'uuid-ggzu'
12 begeleiders
340 cliënten · eigen kennislaag
Org C — Radar
RLS org_id = 'uuid-radar'
5 begeleiders
89 cliënten · kennislaag uit
RLS-regels per rol
RolRLS-conditieZiet
clientclient_id = auth.uid()Alleen eigen rijen in alle tabellen
counselororg_id = eigen org AND counselor_id = auth.uid()Eigen cliënten (niet die van collega's)
org_adminorg_id = eigen orgAlle begeleiders + cliënten in eigen org
gravety_admintarget_table IN (organizations, knowledge_patterns)Alleen org-metadata + platform kennislaag. Nooit cliëntinhoud.
⚠️
Wanneer aparte instanties overwegen: Als een grote enterprise-org (100+ begeleiders, AVG-auditverplichtingen) fysieke isolatie eist. Supabase ondersteunt self-hosting — zelfde codebase, andere connection string. Geen rewrite nodig als de interfaces clean zijn.

Eén-richtingssync. Postgres is altijd de bron. Salesforce volgt. Als de sync faalt, blijft de app gewoon werken.

Wat wordt gesynchroniseerd — en wat niet
DataRichtingTriggerVelden
Nieuwe organisatie Postgres → SF Webhook bij INSERT op organizations name, type, contact_email, postgres_uuid (als External ID)
SF Account ID terug SF → Postgres Sync-service schrijft terug na upsert organizations.sf_account_id
Org-update (naam, type) Postgres → SF Webhook bij UPDATE op organizations name, type (upsert op External ID)
Org health score Postgres → SF Nachtelijk batch active_client_pct, checkin_score_7d (anoniem, geen persoonsdata)
Cliëntdata Nooit Technisch geblokkeerd in sync-service
Gesprekken / check-ins Nooit Technisch geblokkeerd
Signalen Nooit Technisch geblokkeerd
💡
Idempotentie: De sync-service gebruikt altijd upsert op de External ID (Postgres UUID). Dubbele webhook = geen dubbele Account. Nachtelijke reconciliatie vergelijkt tellingen en stuurt een alert bij mismatch.

Elke gebruiker heeft het recht zijn data te laten verwijderen. Dit is de exacte volgorde en wat er gebeurt bij elk niveau. Anonieme data in de kennislaag valt buiten het verwijderingsrecht (AVG Overweging 26).

Cliënt verwijdert account
messages — alle berichten van deze cliënt verwijderd
check_ins — alle check-ins verwijderd
conversations — metadata + ai_summary verwijderd
signals — alle signalen verwijderd
client_profiles — profiel, variabelen, dynamisch profiel verwijderd
direct_messages — alle berichten met begeleider verwijderd
goals, interventions, zrm_assessments — verwijderd
voice-memos in Storage — verwijderd
consent_records — 5 jaar bewaard (bewijs rechtmatige verwerking), dan verwijderd
audit_log — 5 jaar bewaard (AVG accountability), dan verwijderd
knowledge_patterns — NIET verwijderd. Data is anoniem, niet meer persoonsdata (AVG Overweging 26). Recht op verwijdering geldt niet.
Begeleider verlaat organisatie
counselor_ai_profiles — werkstijlprofiel verwijderd
!
Cliënten herindelen — org admin moet cliënten van vertrekkende begeleider herindelen. Tot dan: cliënten krijgen melding, geen AI-sessies mogelijk.
direct_messages — bewaard tot retentietermijn van de cliënt verloopt
Organisatie beëindigt contract
Alle cliënten cascade — elke cliënt doorloopt de cliëntverwijderingsprocedure
Alle begeleiders cascade — elke begeleider doorloopt de begeleider-exitprocedure
org_ai_profiles — methodieken, waarden, grenzen verwijderd
org_knowledge_patterns — org-eigen kennislaag volledig verwijderd
organizations — soft-delete (deleted_at ingesteld)
Platform knowledge_patterns — NIET verwijderd. Volledig anoniem, geen eigenaar meer.
SF
Salesforce Account — apart gedeactiveerd door Gravety-team. Geen automatische cascade.

Pascal begint fresh. Hier is de stapsgewijze opbouw — van gratis development-omgeving tot productie-CRM.

FaseSalesforce editieKostenWanneer
DevelopmentDeveloper EditionGratisNu — Pascal bouwt datamodel en automaties
PilotDeveloper Edition of Starter ($25/user)$0–75/maandBij eerste betalende klant — mogelijk Starter voor productie-features
Early growthStarter Suite ($25/user) of Professional ($80/user)$75–240/maandBij 5–20 org-klanten
ScaleEnterprise ($165/user)OnderhandeldBij 50+ orgs en complexe automaties
Salesforce objecten om aan te maken
ObjectTypeDoelKey velden
AccountStandaardGravety-organisatie (zorginst.)Name, Type, Postgres_UUID__c (External ID), Status__c
ContactStandaardOrg-admin contactpersoonFirstName, LastName, Email, Role__c, AccountId
Subscription__cCustom objectAbonnement + seatsAccount__c, Seats__c, Price__c, Start_Date__c, Renewal_Date__c, Status__c
Health_Score__cCustom objectAnonieme platform health metricsAccount__c, Active_Client_Pct__c, Checkin_Score__c, Date__c
CampaignStandaardOnboarding flows, renewalsName, Type, Status, StartDate

De architectuur in één zin

Supabase/Postgres bewaart alles wat met zorg te maken heeft — versleuteld, in de EU, met row-level security per organisatie. Salesforce beheert de zakelijke kant. Ze communiceren via een één-richtingssync waarbij Postgres altijd de baas is. De platform blackbox groeit zichzelf slim via een human-in-the-loop nominatieproces dat afwijzingen terugkoppelt naar de AI.

🟢 Nu (pilot)
Supabase Pro $25/maand. SF Developer Edition gratis. Geen productie-sync nodig. Bouw en test in isolatie.
🔵 Eerste klanten
SF Starter activeren. Sync bouwen: webhook + upsert + reconciliatie. Pascal bouwt automaties voor onboarding en renewals.
🚀 Op schaal
Supabase → Azure Postgres migreren. "We draaien op Microsoft Azure + Salesforce." SF Enterprise voor complexe automaties. KMS-abstractie voor vault.