Clinical Dental Engine
Clinical Dental Engine
Section titled “Clinical Dental Engine”The Clinical Dental Engine is the core feature of the ClinicFlow Pro tier. Unlike traditional CRM software, ClinicFlow provides a highly interactive, clinically accurate charting engine. The system is split into three primary modules: the Odontogram, the Periogram, and the Treatment Planner.
This engine relies on complex SVG geometry for visual accuracy, specialized UI interfaces for rapid clinical input, and secure backend RPCs for automated plan generation.
1. Tooth Geometry Architecture (toothGeometry.ts)
Section titled “1. Tooth Geometry Architecture (toothGeometry.ts)”To provide accurate visual representations of clinical findings, the Odontogram does not use static images. Instead, it relies on a dynamic, math-driven SVG geometry engine that distinctively maps Anterior (Incisors, Canines) and Posterior (Molars, Premolars) teeth.
Posterior vs. Anterior Geometries
Section titled “Posterior vs. Anterior Geometries”The geometry maps exact viewBox coordinates and hit-targets for tooth surfaces (B, L, M, D, O, I).
- Posterior Geometry (Molars/Premolars): Uses a
40x56viewBox. The surfaces are mapped geometrically: the Mesial (M) and Distal (D) zones are calculated as Left and Right Triangles, while the Occlusal (O) zone is represented by a central diamond polygon (M 20 16 L 30 28 L 20 40 L 10 28 Z). - Anterior Geometry (Incisors/Canines): Uses a
30x60viewBox. The Incisal (I) edge is represented as a top band, and the body is split into left and right panels with distinct bottom corner trapezoids.
Interaction & Hit Targets
Section titled “Interaction & Hit Targets”Because standard SVG path onClick events can be imprecise for rapid clinical clicking, the system utilizes explicit hitTargets (min 44x44 pixels) centered around the visual centroid of each zone, ensuring an accessible and fast charting experience on both desktop and tablet devices.
2. Periogram Interface (PeriogramDial.tsx)
Section titled “2. Periogram Interface (PeriogramDial.tsx)”Periodontal charting requires massive data entry (6 measurements per tooth, times two for Recession and Pocket Depth). To solve this UX challenge, ClinicFlow abandons traditional spreadsheet views in favor of the Periogram Dial.
Rapid Input Workflow
Section titled “Rapid Input Workflow”The PeriogramDial component mounts an overlay focusing on a single tooth.
- Site Clusters: The interface maps 6 sites (
mesial-vestibular,central-vestibular,distal-vestibular,mesial-lingual,central-lingual,distal-lingual) around a central graphic. - Abstract State Machine: The user selects either MG (Recesión) or PS (Bolsa) mode.
- Periogram Numpad: Clicking the custom
PeriogramNumpadimmediately updates the state and auto-advances the cursor to the next site. Once all 6 sites are completed for Recession, it automatically cycles to Pocket Depth input.
This dramatically reduces the cognitive load and mouse movement required by the dentist.
3. Automated Treatment Planner (generateTreatmentPlan.ts)
Section titled “3. Automated Treatment Planner (generateTreatmentPlan.ts)”Once clinical findings are charted on the Odontogram or Periogram, they must be converted into actionable, billable treatment plans.
Security & JWT Extraction
Section titled “Security & JWT Extraction”The API endpoint (POST /api/dental/patients/:patientId/treatment-plans/generate) strictly follows ClinicFlow’s tenant isolation principles. The clinicId is never accepted from the req.body or req.params. It is securely extracted from the authenticated user’s JWT (req.user.clinicId). Zod validates that the array of findingIds are valid UUIDs before processing.
Background Quotation Generation
Section titled “Background Quotation Generation”The backend executes a secure Supabase RPC via the chartService, transforming the clinical findings into a logical plan structure. Because generating complex financial quotes can be CPU intensive, the route offloads the final pricing calculations to a BullMQ worker:
flowchart LR A["Clinician Charts Finding (Odontogram / Periogram)"] -->|POST /api/dental/charts/entries| B["Database RPC (add_dental_finding)"] B -->|Materializes Chart State| C["dental_chart_current Table"] C -->|POST /generate-plan| D["RPC (generate_treatment_plan_from_findings)"] D -->|Creates Plan Record| E["treatment_plans Table"] E -->|GET /treatment-plans/:id/quote-draft| F["services/dental/quoteDraft.ts"] F -->|Reads live availability| G["Message the clinic edits and sends"]
style A fill:#1e293b,stroke:#0284c7,color:#ffffff style B fill:#0f172a,stroke:#0284c7,color:#ffffff style C fill:#1e293b,stroke:#0284c7,color:#ffffff style D fill:#0f172a,stroke:#0284c7,color:#ffffff style E fill:#1e293b,stroke:#0284c7,color:#ffffff style F fill:#0369a1,stroke:#38bdf8,color:#ffffff style G fill:#0369a1,stroke:#38bdf8,color:#ffffff// Built when somebody asks for it, never stored.const draft = await buildQuoteDraftForPlan(req.db, clinicId, planId);res.json(draft); // { body, slots, totalDurationMinutes, patientPhone, ... }QuoteDraftModal shows body in an editable textarea and sends it through the
ordinary reply route. Two things about that are deliberate, and both replaced a
generate_quote_draft BullMQ job removed on 2026-09-16:
- It is not stored. The job INSERTED the text into
conversation_messagesasdirection: 'outbound', sender: 'staff'— the row the inbox renders as a message the clinic already sent. Nothing ever called that route, which is the only reason no patient thread ever showed a message nobody wrote. - It is not stale. The message offers three real openings. Written when the
plan was created and read three days later, it offered appointments that were
gone.
getAvailableSlotsis called with no options, so the offer also keeps the strict clinic-hours bound — reception may BOOK outside clinic hours, but the product must not OFFER a time the clinic is closed.