Case — Clinical platform · 2025—26
Vera
AudGraph — audiology management and decision support
One system, two shapes of data. Patient identity is relational and rigid. Clinical reasoning is not relational at all — it is a network. Rather than compress both into one database, each was given the store that fits it.
Vera is an end-to-end clinical management platform for hearing-aid centres, built freelance under VeraHQ. Its own promise is a single line —"Kliniğinizin dijital kalbi", the clinic's digital heart — and the architecture behind it is the part worth writing down.
- PostgreSQL
- Neo4j · Cypher
- Weighted graph traversal
- Hash-chained audit log
- AES-256-GCM
- Server-sent events
- Role
- Freelance developer under VeraHQ, 10/2025 — present
- Product
- verahq.com.tr — repository Vera-Odyoloji-Yonetim-Sistemi, v0.3.0
- Scope
- Five modules, two databases, full Turkish/English i18n
- Constraints
- KVKK and ISO 27001 compliant, 24/7 Turkish support
- Right ear, air
- Left ear, air
- Right ear, bone
- Left ear, bone
Two shapes of data, one habit
A hearing-aid centre's system has to hold two things at once, and they are not the same kind of thing. The first is patient identity: who this person is, when they came in, which device they were fitted with, what was invoiced, what is in stock. That has a fixed shape. Every row has the same columns, the columns mean the same thing every time, and one wrong row is a real-world error somebody has to answer for.
The second is clinical reasoning, and it has no fixed shape at all. Which other patients presented like this one? Which device was fitted after which finding, and what happened at the next visit? What connects this diagnosis to that one, three hops away, through a device nobody thought to compare? Those are questions about a network: variable depth, variable degree, and an answer that is a weighted path rather than a set of rows.
The habit is to pick one database and make the other shape fit. Model the graph as join tables and walk it with recursive CTEs; it works, right up to the point where the traversal depth stops being known in advance and what you actually want back is a ranking. Or go the other way — put identity in a graph and give up the constraints and transactional guarantees that make identity safe to trust in the first place.
The question that settled it
Not "which database is better", which has no answer, but "what shape is this data, and what is this store good at holding".
Two answers, so two stores.
That is a cost, not a free win — two stores mean two things to run, two things to back up, and a boundary between them that has to be decided rather than discovered. It was worth paying because the boundary here is obvious: identity on one side, relationships on the other.
ARelational — one shape, repeated
Fixed columns. Interchangeable rows. Joins written down in advance, and constraints that refuse anything else.
BNetwork — every node a different shape
Variable degree, variable depth. The interesting answer is a path and a weight, and nobody knows how long it is until it is walked.
Give each shape the store it deserves
PostgreSQL holds relational data and patient identity. Neo4j holds clinical relationships, diagnosis history and the similar-patient algorithms. One system, two stores, and a boundary drawn by ownership rather than by convenience.
On the relational side nothing exotic happens, and that is the intent. Identity wants a schema that refuses malformed data, transactions that either happen or do not, and constraints that are the same on Tuesday as they were on Monday. A clinical record is a legal document as much as a technical one; the store under it should be boring.
On the graph side the shape of the question changes. The Clinical Network Map module renders the relationships between patient, visit, diagnosis, disease and device as an interactive graph, and differential diagnosis is answered by weighted traversal across it: start at the visit, follow the edges that carry weight, and accumulate. Patient, Disease and Device nodes are indexed automatically, so the entry points into a traversal do not have to be scanned for.
What the traversal returns is a ranking for a clinician to read, not a verdict. The system is decision support; it puts candidates in an order and shows the weights that produced the order. Anything stronger than that would be a claim this software has no business making.
Who owns what
by shapePostgreSQL
- Relational data
- Patient identity
Neo4j — Cypher
- Clinical relationships
- Diagnosis history
- Similar-patient algorithms
- Automatic indexing on Patient, Disease and Device nodes
Modules
five- Dashboard
- The clinic at a glance
- Patients
- Identity, records, history
- Network Map
- Relationships as an interactive graph
- Diagnosis
- Weighted traversal for differential diagnosis
- Audiogram
- Thresholds per frequency, per ear
Ranking — each candidate's score as a share of the highest
Synthetic weights, and the arc is a real ratio rather than a decorative fill: each score divided by 1.42, the highest of the four.
Trust cannot live in the application layer
A clinical system is defensible only as far as its audit trail is. If the trail is written by the application, then every code path that forgets to write is a hole — and every code path that can write can also rewrite.
That is not a hypothetical failure mode, it is the ordinary one. Audit logging added as a helper function is audit logging that a refactor can quietly drop, that a migration script can bypass, and that anyone with write access to the table can edit afterwards without leaving a mark. The log looks complete precisely because the thing that would have shown the gap is the log.
So the guarantee was moved below the application. The audit log is append-only and hash-chained, and it is a database trigger that enforces it: the application layer cannot bypass the trigger, because the trigger is not in the application layer. Two independent properties come out of that. The trigger refuses the write — an update or a delete against the log does not succeed. And the chain testifies — because each entry's digest is computed over the previous entry's digest, altering a row in the middle leaves every row after it unable to reproduce its own link.
Patient data itself is encrypted with AES-256-GCM, and the platform is run under KVKK and ISO 27001 compliance. Those are constraints the product operates within rather than achievements of the architecture — but the append-only chain is what makes them checkable instead of merely asserted.
Turning point
A rule that lives in the application is a rule that survives until somebody forgets it. Put it in the database and forgetting stops being possible.
Enforced below
not by convention- Append-only — updates and deletes are refused
- Hash-chained — each entry's digest covers the previous digest
- Enforced by a database trigger, not by application code
- AES-256-GCM for patient data
#0141visit opened
- prev
- 00000000
- hash
- 7c8f7009
verified
#0142thresholds recorded
- prev
- 7c8f7009
- hash
- 478319de
payload edited — recomputes to 66996976
altered
#0143device assigned
- prev
- 478319de
- hash
- 79e02c34
unverifiable
#0144report exported
- prev
- 79e02c34
- hash
- f74cb832
unverifiable
What shipped, and what was not measured
Vera runs in production for hearing-aid centres across Turkey. The figures below are counts and properties of the delivered system — none of them is a performance claim, because no performance comparison was run.
40+
Hearing-aid centres running the platform across Turkey.
2stores
PostgreSQL and Neo4j, divided by the shape of the data rather than by module.
5modules
Dashboard, Patients, Network Map, Diagnosis, Audiogram.
3labels
Node labels indexed automatically in Neo4j — Patient, Disease and Device; the count is those three named types.
Operational detail
shipped- Stock is entered from a phone by scanning a QR code, and the desktop view updates over server-sent events — the person at the counter does not refresh anything, and the browser does not poll.
- Deploys run a smoke test afterwards and roll back automatically when it fails, so a bad release is a short outage rather than a phone call.
- Turkish and English throughout, via react-i18next, switchable in one click — both languages are complete rather than one being a fallback.
- Process management on PM2; the client is React 19 on Vite, with Recharts for the chart surfaces and react-force-graph-2d for the network map.
What was not measured
- No benchmark comparing this two-store design against a single-store one. The claim is fit, not speed.
- Traversal latency was not measured for this write-up. The ranking is described, not timed.
- The deployment count comes from the product site, not from an instrumented count I ran myself.
- Every figure on this page is drawn from synthetic values. No patient record, screenshot or client name appears anywhere.
| Property | Value | Detail |
|---|---|---|
| Centres | 40+ | Hearing-aid centres running it across Turkey |
| Stores | 2 | PostgreSQL and Neo4j, split by the shape of the data |
| Modules | 5 | Dashboard, Patients, Network Map, Diagnosis, Audiogram |
| Languages | 2 | Turkish and English — react-i18next, one-click switch |
| Indexed node labels | 3 | Patient, Disease and Device, indexed automatically in Neo4j |
| Encryption | AES-256-GCM | Patient data |
| Audit log | Hash-chained, append-only | Enforced by a database trigger |
| Compliance | KVKK · ISO 27001 | Stated by the product, with 24/7 Turkish support |
| Version | 0.3.0 | Repository Vera-Odyoloji-Yonetim-Sistemi |
Client
React 19 · Vite · Recharts · react-force-graph-2d
Server
Node.js · Express
Stores
PostgreSQL · Neo4j (Cypher)
Process
PM2 · post-deploy smoke test with automatic rollback
What this case is evidence of
A system holds more than one shape of data. Choose storage by shape, not by habit.Ali Batuhan Özsürmeli
The technical content here is polyglot persistence and a hash-chained audit log. The transferable part is narrower and more useful: noticing that two questions in the same product have different shapes, being willing to pay the operational cost of answering them separately, and then putting the guarantee that matters underneath the code rather than inside it.
The same move shows up elsewhere in this work. Şeffaf Şehir puts a privacy rule into the type system so the compiler enforces it; Limon Akademi pushes tenant isolation into PostgreSQL row-level security so no endpoint has to remember. Vera is the same instinct applied to an audit trail — the rule belongs one layer below the place where it can be forgotten.
Stack
applied- React 19
- Vite
- Recharts
- react-force-graph-2d
- Node.js
- Express
- PostgreSQL
- Neo4j · Cypher
- PM2
- Engagement
- Freelance, VeraHQ · 10/2025 — present
- Product
- verahq.com.tr