Back to Dashboard
ORVICS

Integration Guide

Everything your clinic's IT team needs to connect your practice management system to the ORVICS kinetic engine — ready-to-deploy bridge code included.

How It Works

Your PMS

Dentrix, Open Dental, Eaglesoft, etc.

ORVICS Bridge

Lightweight script (below) runs on your server

ORVICS Engine

Cloud — kinetic analysis & directives

Dashboard

Real-time alerts & flow timeline

Supported Practice Management Systems

Open Dental

MySQL · REST API available

Easy

Dentrix

SQL Server · Dentrix API (limited)

Medium

Eaglesoft

SQL Server · Database access

Medium

Curve Dental

Cloud · REST API

Easy

Denticon

Cloud · REST API

Easy

Practice Web

SQL Server · Database access

Medium

Don't see your system? ORVICS works with any PMS that has database or API access. Contact your IT team.

Setup Steps

Step 1

Get Your API Key

Your ORVICS ingest API key is on your dashboard under "Scheduling Bridge Configuration." Copy it — you'll paste it into the bridge script below.
Step 2

Deploy the Bridge Script

Copy the Python bridge script below to a machine on your clinic's network that has access to your PMS database. This can be your front-desk computer, a server, or any always-on device. Install Python 3.8+ and run: pip install requests pymysql pyodbc schedule
Step 3

Configure Your PMS Connection

Edit the CONFIG section at the top of the script. Enter your database credentials, your ORVICS API key, and the endpoint URL. The script includes an Open Dental example — adjust the SQL query for your specific PMS schema.
Step 4

Map Your Procedure Baselines

Edit the get_baseline_for_procedure() function to match your practice's standard procedure durations. These baselines are what the engine measures deviation against — they power the kinetic analysis.
Step 5

Run the Bridge

Start the bridge: python orvics_bridge.py. It will poll your PMS every 60 seconds and push flow readings to the ORVICS engine. Your dashboard updates in real time.

Ready-to-Deploy Bridge Script

orvics_bridge.py
#!/usr/bin/env python3
"""
ORVICS Bridge Service — Connects your dental PMS to the ORVICS kinetic engine.

This lightweight service polls your practice management database for appointment
status changes, computes elapsed flow times, and pushes readings to the ORVICS
ingest endpoint for real-time kinetic analysis.

Supports: Open Dental (MySQL), Dentrix/Eaglessoft (SQL Server), and any
PMS with database or API access.

Setup:
  1. pip install requests pymysql pyodbc schedule
  2. Edit CONFIG below with your PMS credentials and ORVICS API key
  3. python orvics_bridge.py
"""

import requests
import schedule
import time
import json
from datetime import datetime, timedelta

# ============================================================
# CONFIGURATION — Edit these values for your clinic
# ============================================================

ORVICS_API_KEY = "YOUR_ORVICS_API_KEY_HERE"  # From your ORVICS dashboard
ORVICS_ENDPOINT = "https://looqoe.com/api/functions/orvicsIngest"

# PMS Database (Open Dental example — adjust for your system)
DB_HOST = "localhost"
DB_PORT = 3306
DB_NAME = "opendental"
DB_USER = "root"
DB_PASS = "your_password"

# Polling interval (seconds). 60 = check every minute.
POLL_INTERVAL = 60

# ============================================================
# PMS DATA EXTRACTION — Adapt for your specific system
# ============================================================

def get_active_appointments():
    """
    Query your PMS for appointments that are currently in progress
    or recently completed. Returns a list of dicts with:
      - chair_label: operatory/chair name
      - patient_id: anonymized ID (NO patient names)
      - flow_type: which metric this reading represents
      - flow_value: elapsed minutes
      - baseline: expected duration for this procedure type
    """
    import pymysql
    conn = pymysql.connect(
        host=DB_HOST, port=DB_PORT, user=DB_USER,
        password=DB_PASS, database=DB_NAME
    )
    cursor = conn.cursor(pymysql.cursors.DictCursor)

    # Open Dental example: query appointment table for in-progress appts
    cursor.execute("""
        SELECT
            OperatoryName AS chair_label,
            PatNum AS patient_id,
            AptDateTime AS start_time,
            Pattern AS duration_pattern,
            ProcDescript AS procedure_desc
        FROM appointment
        WHERE AptStatus = 2  -- Scheduled
          AND AptDateTime <= NOW()
          AND AptDateTime >= NOW() - INTERVAL 4 HOUR
    """)

    appointments = cursor.fetchall()
    conn.close()

    readings = []
    now = datetime.now()

    for appt in appointments:
        start = appt["start_time"]
        if isinstance(start, str):
            start = datetime.fromisoformat(start)

        elapsed_min = (now - start).total_seconds() / 60

        # Map procedure to a baseline (customize for your codes)
        baseline = get_baseline_for_procedure(appt["procedure_desc"])

        readings.append({
            "flow_type": "procedure_duration",
            "flow_value": round(elapsed_min, 1),
            "baseline": baseline,
            "patient_id": f"Chair-{appt['chair_label']}-P",
            "chair_label": appt["chair_label"],
        })

    return readings


def get_baseline_for_procedure(proc_desc):
    """Map your procedure codes to expected baseline durations (minutes)."""
    baselines = {
        "cleaning": 45,
        "exam": 30,
        "filling": 60,
        "crown": 90,
        "extraction": 60,
        "root canal": 120,
        "whitening": 75,
    }
    desc_lower = (proc_desc or "").lower()
    for key, val in baselines.items():
        if key in desc_lower:
            return val
    return 45  # Default baseline


# ============================================================
# ORVICS INGEST — Push readings to the kinetic engine
# ============================================================

def push_to_orvics(readings):
    """Send flow readings to the ORVICS ingest endpoint."""
    if not readings:
        return

    headers = {
        "Authorization": f"Bearer {ORVICS_API_KEY}",
        "Content-Type": "application/json",
    }

    for reading in readings:
        try:
            resp = requests.post(
                ORVICS_ENDPOINT,
                headers=headers,
                json=reading,
                timeout=10
            )
            result = resp.json()

            directive = result.get("directive_key", "unknown")
            deviation = result.get("deviation_rate", 0)

            if directive != "stable":
                print(f"  ⚠ {reading['chair_label']}: {directive} "
                      f"(+{deviation}% deviation)")

        except Exception as e:
            print(f"  ✗ Failed to push reading: {e}")


# ============================================================
# MAIN LOOP
# ============================================================

def tick():
    """Poll PMS and push readings to ORVICS."""
    print(f"[{datetime.now().strftime('%H:%M:%S')}] Polling PMS...")
    try:
        readings = get_active_appointments()
        print(f"  Found {len(readings)} active appointments")
        push_to_orvics(readings)
    except Exception as e:
        print(f"  Error: {e}")


if __name__ == "__main__":
    print("=" * 60)
    print("  ORVICS Bridge Service — Kinetic Clinic Flow Engine")
    print("=" * 60)
    print(f"  Endpoint:  {ORVICS_ENDPOINT}")
    print(f"  Interval:   every {POLL_INTERVAL}s")
    print(f"  PMS:        {DB_NAME} @ {DB_HOST}")
    print("=" * 60)
    print()

    # Run immediately, then on schedule
    tick()
    schedule.every(POLL_INTERVAL).seconds.do(tick)

    while True:
        schedule.run_pending()
        time.sleep(1)

Quick Test (cURL)

Test your API key with a single reading before deploying the bridge:

test_connection.sh
# Test the connection with a single reading:

curl -X POST https://looqoe.com/api/functions/orvicsIngest \
  -H "Authorization: Bearer YOUR_ORVICS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "flow_type": "procedure_duration",
    "flow_value": 52,
    "baseline": 45,
    "patient_id": "Chair-3-P",
    "chair_label": "Chair-3"
  }'

# Response:
# {
#   "directive_key": "observe",
#   "directive": "BOTTLENECK FORMING — OBSERVE CLOSELY",
#   "deviation_rate": 15.56,
#   "optimization_index": 17.06,
#   "flow_mass": 7,
#   ...
# }

Node.js Alternative

For cloud-based PMS systems (Curve Dental, Denticon) or if your team prefers JavaScript:

orvics_bridge.js
// Node.js bridge example (for cloud-based PMS like Curve/Denticon)

const axios = require('axios');

const ORVICS_API_KEY = 'YOUR_ORVICS_API_KEY';
const ORVICS_ENDPOINT = 'https://looqoe.com/api/functions/orvicsIngest';

async function pushReading(reading) {
  const res = await axios.post(ORVICS_ENDPOINT, reading, {
    headers: {
      'Authorization': `Bearer ${ORVICS_API_KEY}`,
      'Content-Type': 'application/json',
    },
  });
  return res.data;
}

// Example: push a procedure duration reading
pushReading({
  flow_type: 'procedure_duration',
  flow_value: 52,
  baseline: 45,
  patient_id: 'Chair-3-P',
  chair_label: 'Chair-3',
}).then(result => {
  console.log('Directive:', result.directive_key);
  console.log('Deviation:', result.deviation_rate + '%');
});

Data Mapping Reference

ORVICS FieldPMS SourcePurpose
flow_typeProcedure code / appointment statusDefines which metric (procedure_duration, chair_idle_time, patient_wait_time, turnover_time)
flow_valueElapsed minutes (computed by bridge)The raw time reading — how long the flow has been running
baselineStandard procedure duration (your config)The expected "normal" time — the engine measures deviation from this
patient_idAppointment ID / chair-patient tokenAnonymized tracking — NO patient names or PHI
chair_labelOperatory / chair nameIdentifies which physical chair the reading is from

Privacy & PHI

ORVICS does not store patient names, dates of birth, social security numbers, or any Protected Health Information (PHI). The bridge script sends only anonymized chair/patient tokens and elapsed time readings. All patient data stays in your PMS — only operational flow telemetry reaches the engine.