Healthcare Clinic Attribution Data Model

Overview

A single healthcare practice — one clinic, one patient list — that advertises for patients, screens the enquiries that come back, treats the people a clinician accepts, and is paid in two parts: the patient's copay before treatment, and the insurer's share once the claim has been processed. The model follows that one chain end to end, from an anonymous ad click to money received.

What makes it more than a funnel report is the person identity at its centre. It is minted on someone's very first page view — before a name, an email or a phone number — and it is never reissued, so a patient paying today still resolves to the advertising that first brought them, whether that was weeks or years earlier.

That stitching — one identity held across devices, browsers and years — is the pattern used by APAS® Cloud, whose co-founder described it for this model.

Two distinctions inside it are expensive to lose. An enquiry is not a qualified enquiry: qualification is a clinician's decision, taken at a consultation, and the field is empty on the enquiry record until then — so a report that counts empty as "not qualified" misreads its own funnel. And an invoice is not revenue: a copay is money, a claim is a promise, and a practice that reads its bills as income believes it has been paid when it has not.

Scope: there is no advertising cost anywhere in this model. Clicks, visits, enquiries, consultations, treatments, bills and payments are all here; what the practice paid the ad platforms for them is not, so cost per acquisition, return on ad spend and budget allocation have no answer in it. What it does answer is which advertising brings enquiries a clinician will accept, and what those enquiries are worth once they have been treated and paid for. Also absent, and worth naming so that nobody looks for them: insurance claims as objects — only their trace on an invoice and a payment — multiple clinic locations, clinician capacity and scheduling, and a coded procedure catalogue.

Three healthcare bundles, and which is which. healthcare models a hospital system: appointments, clinical encounters, bed census and the insurance claims that pay for them. healthcare-clinic-network models a multi-location outpatient operation, with advertising spend, clinics, providers and no-shows. This one models a single practice, and its subject is the identity and attribution chain: one person, followed from the click that found them to the payment that settled their treatment.

Example Questions

  • Which pages and which creatives bring the enquiries a clinician goes on to qualify, rather than the ones that bring clicks and stop there?
  • Which advertising brings enquiries that look right all the way to the consultation and are then refused by the clinician, and what reason is recorded for the refusal?
  • How long does money take to arrive once a treatment is booked, split between the copay the patient pays before treatment and the insurer's reimbursement afterwards?

Explore on canvas →

Call Data Mart

Call

Every telephone conversation between the practice and someone enquiring. A phone call and a web form are the same step in the funnel, not two funnels: both are the first engagement, both create the same CRM record, and both carry the same advertising identifiers when the caller was on the website. A report built on web forms alone leaves telephone enquiries out and credits the wrong channels.

Those identifiers are empty for a caller with no web history, and that is the record being accurate rather than a tracking defect. Someone rings a number from a leaflet, a sign or a recommendation, having never opened the site; there is no visit to match them to and no click to credit. Dropping unattributed calls makes the measurable channels look better than they are, so they are kept and reported as demand the practice knows it has and cannot yet trace.

direction separates calls received from calls placed: a first screen can begin with the practice ringing back, and treating a returned call as a fresh enquiry would double-count demand.

Fields

ColumnTypeAliasDescription
call_idSTRINGCall IDPK. Unique identifier for this one call, separate from the enquiry it belongs to — one enquiry can run to several calls.
lead_idSTRINGLead IDThe enquiry record this call opened or added to; every later call about the same enquiry carries the same value. FK to Lead
person_idSTRINGPerson IDThe human on the other end, where they can be recognised from the website — reachable through the enquiry this call opened or through the visit it was placed from. Empty for a caller never seen online, which is a fact about the call rather than missing data.
session_idSTRINGSession IDThe visit the call was placed from, where there was one. Empty for a caller who dialled a number they saw somewhere other than the site. FK to Session
click_idSTRINGClick IDThe paid click behind the call. Empty for organic callers and for anyone who never visited the site at all. FK to Click
called_atTIMESTAMPCalled AtWhen the call took place. Compared with the enquiry's creation time, it separates the first contact from the follow-ups.
directionSTRINGDirectionWho placed it: inbound when the person rang the practice, outbound when reception rang them. A returned call is not a second enquiry.
duration_secondsINTEGERCall Length (Seconds)How long the conversation lasted, in seconds — the difference between a call that was merely picked up and one in which the practice actually spoke to someone.
answeredBOOLEANAnsweredWhether anyone picked up. A call nobody answered is an enquiry that never reached a person, and it appears in no report built on web forms.
tracking_numberSTRINGTracking NumberThe number the caller dialled. When a practice publishes a different number per place — site, ad, print — this is what attributes a call that has no web history at all.
utm_sourceSTRINGCall SourceSource declared by the link that brought this caller to the site, where there was one, such as google or facebook. Empty for a caller with no web visit behind them.
utm_mediumSTRINGCall MediumMedium declared by that link, such as cpc or paid_social — the tag that marks this call as bought rather than earned.
utm_campaignSTRINGCall CampaignCampaign the call is credited to, as named in the ad account. Empty both for unattributed callers and for arrivals that carry no campaign.

Relationships

Related data martOnCardinalityMeaning
Clickclick_id = click_idN:1The paid click behind the call; absent for organic and offline callers.
Leadlead_id = lead_idN:1The CRM record this call produced or added to.
Sessionsession_id = session_idN:1The visit the call was placed from; absent for a caller with no web history.

Click Data Mart

Click

Every click on one of the practice's ads, held as its own record rather than as columns on whatever happened next, and keyed the same way on every platform so clicks across Google, Meta, Microsoft and TikTok count in one place. session_id is empty when the click produced no visit — the person tapped and went straight back, the page failed, tracking was blocked — and those clicks were still paid for, so dropping them is how advertising quietly looks better than it was.

platform_click_id holds the raw value the ad platform itself attached: the gclid, fbclid, msclkid or ttclid. It is the harder evidence of a paid arrival, and needs one guard: a value shorter than ten characters should be treated as absent — below that what turns up is undefined, null, empty strings and truncated fragments, which inflate whichever channel they land in. Lesley van de Mortel, who described this model, uses that minimum in her own SQL and pairs it with a check on utm_source and utm_medium, so a claimed channel agrees with itself from two directions.

Fields

ColumnTypeAliasDescription
click_idSTRINGClick IDPK. The practice's own identifier for the click, minted the same way on every platform so clicks can be counted in one place.
person_idSTRINGPerson IDThe human who clicked, which is what lets several clicks over months be recognised as one person's journey rather than several prospects. FK to Person
session_idSTRINGSession IDThe visit the click produced. Empty when it produced none — the click was still paid for, and excluding those flatters the channel. FK to Session
clicked_atTIMESTAMPClicked AtWhen the ad was clicked. The gap between here and any money that follows can run from weeks to years in this business, so a same-month comparison will miss it.
ad_platformSTRINGAd PlatformWhich advertising platform the click came from: google_ads, meta, microsoft or tiktok.
platform_click_idSTRINGPlatform Click IDThe raw identifier the platform attached — gclid, fbclid, msclkid or ttclid. Treat anything shorter than ten characters as absent: below that it is placeholder text, not a click.
utm_sourceSTRINGUTM SourceSource the ad link declared, such as google or tiktok. Worth checking against ad_platform rather than trusting alone, because it is set by hand in the ad.
utm_mediumSTRINGUTM MediumMedium the ad link declared, such as cpc or paid_social — the tag that marks this traffic as bought.
utm_campaignSTRINGUTM CampaignCampaign the click was bought under, as named in the ad account.
utm_termSTRINGUTM TermKeyword the click was bought under, where the platform works that way. Empty on platforms that sell audiences rather than search terms.
utm_contentSTRINGAd CreativeWhich creative was clicked — the image, video or headline. This is the field that makes "which ad works" answerable at all, rather than only "which campaign works".
landing_page_urlSTRINGAd Landing PageAddress the ad sent the person to, which is what the click's promise has to be judged against. This is the ad's side of a pair: the visit records its own landing page separately.

Relationships

Related data martOnCardinalityMeaning
Personperson_id = person_idN:1The human who clicked the ad.
Sessionsession_id = session_idN:1The visit the click landed in; absent when the click never produced one.

Consultation Data Mart

Consultation

Where an enquiry is judged. A practice screens in two steps, both held here, told apart by type and by who conducted them: they differ in who screens, not in what is recorded. A Pre-Consultation is a telephone conversation held by a receptionist or a practice manager — not a clinician — who decides whether the practice is a plausible fit. A Consultation is the appointment that follows: a clinician examines the person and decides whether treatment can go ahead. The first step exists because the second is expensive: without it every enquiry would go straight to a clinician, and no practice has the hours for that.

Qualification is decided here, and only here. The enquiry record carries a field of the same name, but it is empty: at the moment of a form or a call nobody has yet formed a view. One enquiry can be screened more than once, so it may have several rows here, to be read in order. disqualification_reason carries the practice's own account of a refusal, which turns a low acceptance rate from a count into a diagnosis and shows which campaigns bring people the practice can actually treat.

Fields

ColumnTypeAliasDescription
consultation_idSTRINGConsultation IDPK. Unique identifier for this one screening, not for the enquiry — an enquiry can be screened more than once.
lead_idSTRINGLead IDThe enquiry being screened. Several rows here share one value, which is what makes this a sequence of steps rather than a single verdict. FK to Lead
employee_idSTRINGEmployee IDWho conducted it. This is what distinguishes a receptionist's phone screen from a clinician's assessment in practice, and it lets acceptance rates be compared between staff. FK to Employee
consultation_dateDATEConsultation DateThe day it took place. Measured against the enquiry's creation time, it is how long the practice took to respond; measured against the previous screen, how long the person waited between steps.
typeSTRINGConsultation TypeWhich of the two screens this is: Pre-Consultation for the phone conversation with reception or a manager, Consultation for the appointment with a clinician.
is_qualifiedBOOLEANIs QualifiedThe verdict of this screen: TRUE when the person moves forward, FALSE when they do not. This is where qualification is actually decided; the same field on the enquiry record is empty.
disqualification_reasonSTRINGDisqualification ReasonWhy the person was not taken forward. Empty when they were. This is what turns a low acceptance rate from a count into a diagnosis, because the fix is different for each reason.

Relationships

Related data martOnCardinalityMeaning
Employeeemployee_id = employee_idN:1Who held it, which is what separates a phone screen from a clinician's assessment.
Leadlead_id = lead_idN:1The enquiry being screened; one lead can have several screenings.

Customer (Patient) Data Mart

Customer (Patient)

The patient once money is involved. A person exists from the first anonymous visit; a customer exists once there is an invoice or revenue against them — not when someone enquires, and not when a clinician agrees to treat them — and that is the line between an audience and a patient list. In healthcare the two are the same human, so this is one record rather than two: customer_id is this model's key and patient_id the clinical system's, so a commercial report and a clinical record reconcile without renaming anything. That is not universal — elsewhere an employer or a school pays for the people being served, and payer and patient are separate records.

One customer covers several treatments and several payments, so counting rows here counts patients while counting treatments, invoices or payments counts events, and with returning patients the two are not the same. person_id resolves a paying patient back to the anonymous first visit, so the advertising that started a journey can be credited with the money that ended it, however long the gap.

Fields

ColumnTypeAliasDescription
customer_idSTRINGCustomer IDPK. Unique identifier for the patient as a paying party, issued when the first invoice or payment appears rather than at the enquiry.
person_idSTRINGPerson IDThe same human at their anonymous stage, which is what ties money received back to the visit and the advertising that started the journey. FK to Person
patient_idSTRINGPatient IDThe identifier the clinical system issues for the same human. Held alongside the commercial key so a clinical record and a billing record can be reconciled without renaming either.
first_nameSTRINGPatient First NameGiven name as the practice bills it. The enquiry record holds its own copy of the contact details, on Lead.
last_nameSTRINGPatient Last NameFamily name as the practice bills it.
became_customer_atTIMESTAMPBecame a Customer AtWhen this human stopped being an enquiry and became a paying patient: the moment of the first invoice or payment. Compared with the person's first visit, it is the full length of the journey from first touch to first money.

Relationships

Related data martOnCardinalityMeaning
Personperson_id = person_idN:1The same human, tracked from their first anonymous visit onward.

Employee Data Mart

Employee

Everyone at the practice who takes part in turning an enquiry into a booked treatment — clinicians, receptionists and practice managers alike — kept as one list and told apart by type: Doctor, Receptionist, Manager. They are one mart rather than three because they carry the same properties and differ only in what they do; splitting them would duplicate the same five fields three times and make every question about staff a question asked three times.

Both screening steps are staffed from here: the phone call that decides whether an enquiry is worth an appointment, and the appointment itself, where a clinician decides whether treatment can go ahead. Because both consultations point at this one mart, the same question — who held it, and how did it end — answers for either step, which is what lets a practice see where its enquiries are actually lost, and to whom. Without it every enquiry would appear to go straight to the doctor, and the screen that turns away a poor fit would be invisible.

Fields

ColumnTypeAliasDescription
employee_idSTRINGEmployee IDPK. Unique identifier for this member of staff.
typeSTRINGEmployee TypeWhat the person does in the funnel: Doctor sees patients in the room and decides whether treatment goes ahead, while Receptionist and Manager take the first screening call.
first_nameSTRINGFirst NameGiven name, as the practice records it.
last_nameSTRINGLast NameFamily name, as the practice records it.
positionSTRINGPositionJob title at the practice — finer than type, and the wording a rota or a staff list would use.
specialtySTRINGSpecialtyClinical field the person practises in. Empty for reception and management staff, for whom it has no meaning.

Form Submission Data Mart

Form Submission

Every web form the practice receives, with the advertising that produced it attached. A form submission is one of the two ways an enquiry begins; ringing the practice is the other, and the two are the same step in the funnel. A submission opens a CRM record if the person has not enquired before and attaches to the existing one if they have, so one enquiry can produce several rows: counting rows counts contact attempts, not prospective patients.

It carries two families of identifiers worth keeping apart. The five utm_* fields are what the link itself declared, set by hand when the ad was built; the four click identifiers — gclid, fbclid, msclkid, ttclid — are what the advertising platform attached, and they are the harder evidence of the two. As on the ad click, a platform click identifier shorter than ten characters should be treated as absent.

An empty attribution field is normal rather than broken: someone who arrived through unpaid search or typed the address in carries no campaign and no click identifier, and the identifiers of platforms a person did not come through are empty too.

Fields

ColumnTypeAliasDescription
form_submission_idSTRINGForm Submission IDPK. Unique identifier for this one form, separate from the enquiry it belongs to — one enquiry can produce several.
lead_idSTRINGLead IDThe enquiry record this submission opened or added to; repeat submissions from the same person all carry the same value. FK to Lead
person_idSTRINGPerson IDThe human who submitted it. Reach them through the enquiry this submission opened or through the visit it was sent during, both of which carry the same identifier back to the anonymous browsing and advertising that came before.
session_idSTRINGSession IDThe visit the form was sent during, which is how the pages read beforehand can be counted as part of the same decision. FK to Session
click_idSTRINGClick IDThe paid click that brought this person to the site. Empty for organic and direct arrivals, which is the normal state rather than missing data. FK to Click
submitted_atTIMESTAMPSubmitted AtWhen the form was sent. Measured back to the person's first page view, it is how long the journey to an enquiry took.
form_nameSTRINGForm NameWhich form on the site was used — a consultation request asks for more commitment than a callback request, and they should not be counted as one.
page_urlSTRINGForm PageAddress of the page the form was sent from, which is the page whose argument actually persuaded the person to get in touch.
utm_sourceSTRINGSubmission SourceSource declared by the link that brought this person, such as google or tiktok. Set by hand in the ad, so worth checking against the platform's own click identifier.
utm_mediumSTRINGSubmission MediumMedium declared by the link, such as cpc or paid_social — the tag that marks this enquiry as bought rather than earned.
utm_campaignSTRINGSubmission CampaignCampaign the enquiry is credited to, as named in the ad account. Empty for arrivals that carry no campaign at all.
utm_termSTRINGSubmission Search TermKeyword behind the enquiry, where the channel sells keywords. Empty on platforms that sell audiences instead.
utm_contentSTRINGSubmission CreativeWhich creative the person came through — the image, video or headline. This is the level at which advertising is actually changed, so it is the level worth judging enquiries at.
gclidSTRINGGoogle Click IDGoogle's own identifier for the click behind this submission. Treat a value shorter than ten characters as absent.
fbclidSTRINGMeta Click IDMeta's own identifier for the click behind this submission, covering Facebook and Instagram. Treat a value shorter than ten characters as absent.
msclkidSTRINGMicrosoft Click IDMicrosoft's own identifier for the click behind this submission. Treat a value shorter than ten characters as absent.
ttclidSTRINGTikTok Click IDTikTok's own identifier for the click behind this submission. Treat a value shorter than ten characters as absent.

Relationships

Related data martOnCardinalityMeaning
Clickclick_id = click_idN:1The paid click that brought them; absent for organic and direct visits.
Leadlead_id = lead_idN:1The CRM record this submission produced or added to; one lead may submit several forms.
Sessionsession_id = session_idN:1The visit it was submitted during.

Invoice Data Mart

Invoice

A bill the practice has issued, raised against one treatment and one patient. An invoice is a claim on money, not money: it records what is owed and to whom, and nothing here says any of it has arrived. Money actually received is recorded on Revenue, joined by invoice_id: a report on invoices answers "what have we billed", one on revenue "what have we been paid". Read as income they overstate, carrying the full value of work billed, including the part an insurer has not settled and the part that may never be paid at all.

A treatment is billed in two parts. In the United States, where this pattern was described, the patient's copay can be billed as soon as the treatment is booked, is collected before it starts, and is money straight away, though a small share of the total. The rest is claimed from the insurer after the treatment and becomes money only when the insurer pays, with a lag. payer_type tells the two apart. A written_off invoice is delivered work that earned nothing, so it belongs in any honest reading of what a channel produced.

Fields

ColumnTypeAliasDescription
invoice_idSTRINGInvoice IDPK. Unique identifier for one bill. The patient's part and the insurer's part are billed separately, so one treatment can produce more than one row here.
customer_idSTRINGCustomer IDThe patient being billed. In healthcare this is the same human being treated, so no separate payer record is needed. FK to Customer (Patient)
treatment_idSTRINGTreatment IDThe treatment this bill is for, which is how a charge connects back to the clinician's decision and, through it, to the advertising that produced the enquiry. FK to Treatment
issued_atTIMESTAMPIssued AtWhen the bill was sent. For a copay this can be as early as the booking; for an insurance claim it follows the treatment. It is the start of the clock that Days to Payment measures.
amountNUMERICInvoiced AmountWhat this bill asks for. It is not income — the amount actually received against it is recorded on Revenue, and it can be smaller, arrive in pieces, or never arrive.
currencySTRINGInvoice CurrencyCurrency the bill was raised in, such as USD. Needed before amounts from different bills can be added together.
payer_typeSTRINGPayer TypeWho is being asked to pay: patient_copay for the part the patient settles before treatment, insurance_claim for the part claimed from the insurer afterwards. One is settled before the treatment, the other after the claim is processed.
statusSTRINGInvoice StatusWhere the claim stands: issued, partially_paid, paid, or written_off when the practice has given up on it. Written-off bills are delivered work that earned nothing and should not be dropped from a channel's result.

Relationships

Related data martOnCardinalityMeaning
Customer (Patient)customer_id = customer_idN:1Who is being billed.
Treatmenttreatment_id = treatment_idN:1What is being billed for.

Lead Data Mart

Lead

The enquiry as the practice's CRM holds it: a named person with contact details, created the moment someone first gets in touch, by web form or by phone. It does not exist beforehand, which is why the form submission and the call point at the lead rather than the other way round. person_id traces the enquiry back to the browsing that preceded it by months or years, and the advertising identifiers copied onto the record keep a campaign attached to that name long afterwards. One lead is one person's enquiry, not one contact attempt: the same lead may submit several forms, make several calls, and be screened more than once.

is_qualified is empty on this mart, and that is by design, not by omission. The CRM carries the field from the start, but at the enquiry stage nobody has yet spoken to the person; the decision is taken later, at a consultation, and stored there. A report that counts unqualified enquiries as is_qualified = false will therefore undercount them: the enquiries that were never accepted are not false, they are empty. To judge an enquiry's outcome, look at the consultations attached to it, not at this field.

Fields

ColumnTypeAliasDescription
lead_idSTRINGLead IDPK. The CRM's own reference for the enquiry, issued when the first form or call arrives. It is a number in the CRM, carried here as a fixed-width string of digits so that sorting by it always follows the order the records were issued.
person_idSTRINGPerson IDThe human this enquiry belongs to, which is what ties a named enquiry back to the anonymous browsing and advertising that preceded it. FK to Person
first_nameSTRINGLead First NameGiven name as the enquirer supplied it, which is what the practice has to work with — it need not match any later clinical record.
last_nameSTRINGLead Last NameFamily name as the enquirer supplied it.
emailSTRINGEmailEmail address given at the enquiry. Empty when the person only ever telephoned and left no address.
phoneSTRINGPhoneTelephone number given at the enquiry, and the one reception will call back on for the first screen.
crm_contact_idSTRINGCRM Contact IDThe contact's identifier in the practice's CRM, which is what a marketing team uses to reconcile this record against the system staff work in day to day.
created_atTIMESTAMPCreated AtWhen the CRM record was opened, which is the moment of the first form submission or call rather than of any earlier visit.
first_engagement_typeSTRINGFirst Engagement TypeWhich contact created the record: form or call. The two are the same funnel step, so this is what separates a channel's telephone demand from its web demand.
is_qualifiedBOOLEANQualified on the Lead RecordEmpty at this stage. The CRM carries the field from the start, but nobody has decided at this stage — the verdict is taken at a consultation and stored there. Counting empty as false will undercount the enquiries that were not accepted.

Relationships

Related data martOnCardinalityMeaning
Personperson_id = person_idN:1The human behind the enquiry, which is how a lead connects to anything that happened before it.

Page Data Mart

Page

Every page of the practice's website that a visitor can land on, held once and reused by every view of it. Keeping pages as their own object rather than as a URL repeated on each view is what lets a clinic ask which kinds of page do the work: whether the enquiries that begin on a treatment page and the ones that begin on a pricing page fare the same once a clinician has looked at them. page_type is what lets that question be asked of a kind of page rather than of one URL — whether the visitor was reading about a treatment, checking prices, or browsing the blog.

Fields

ColumnTypeAliasDescription
page_idSTRINGPage IDPK. Unique identifier for this page.
page_urlSTRINGPage URLFull address of the page, including its domain.
page_pathSTRINGPage PathPath portion of the address, without domain or query string — the form that identifies a page across domains and tracking parameters.
page_titleSTRINGPage TitleTitle shown in the browser tab and in search results.
page_typeSTRINGPage TypeWhat the page is for: home, treatment, pricing, contact, blog or booking_form. This is what makes a question about page effectiveness answerable at all.

Page View Data Mart

Page View

Every page a visitor actually opened, in the order they opened it. Where a page is described once and for all, a page view is one reading of it by one person at one moment — which is what turns a site map into a record of behaviour.

This mart reaches further back than anything else in the model: the very first page view is where a person's identity is minted — before a name, before an email, before anyone at the practice knows a prospective patient is there. It is also what makes "what did they read before they got in touch" answerable. sequence_in_session makes a journey readable as an order rather than a pile of URLs; is_entrance and is_exit mark its two ends: entrances say which pages bring people in, exits say where attention is lost, and those are two different lists.

time_on_page_seconds is measured from when the next page was opened, so it cannot be known for the last page of a visit. That value is empty rather than zero, and treating it as zero understates how long the site was read for.

Fields

ColumnTypeAliasDescription
page_view_idSTRINGPage View IDPK. Unique identifier for this one reading of a page by one person at one moment.
session_idSTRINGSession IDThe visit this reading happened in, which is what puts it in order next to everything else seen during the same visit. FK to Session
person_idSTRINGPerson IDThe human who read the page. Carried here as well as on the visit, so a person's whole reading history can be assembled without walking through sessions. FK to Person
page_idSTRINGPage IDWhich page was read. The view holds this, because one page is read many times. FK to Page
viewed_atTIMESTAMPViewed AtWhen the page was opened. On a person's earliest page view, this is also the moment their identity was minted.
sequence_in_sessionINTEGERPosition In VisitWhere this page sat in the visit, counting from 1 — what makes a journey readable as an order rather than a set of URLs.
time_on_page_secondsINTEGERTime On Page (sec)Seconds until the next page was opened. Empty for the last page of a visit, where there is no next page to measure against — not zero.
is_entranceBOOLEANIs EntranceTRUE when this was the first page of the visit: the page that had to earn the visitor's attention.
is_exitBOOLEANIs ExitTRUE when this was the last page of the visit — where the visitor stopped, whether satisfied or not.

Relationships

Related data martOnCardinalityMeaning
Sessionsession_id = session_idN:1The visit this page was seen during.
Personperson_id = person_idN:1The human who viewed it.
Pagepage_id = page_idN:1Which page was viewed.

Person Data Mart

Person

The human behind everything else in this model. The identity is minted at the very first page view — before anyone has given a name, an email or a phone number — and is never reissued, so a phone at lunchtime, a laptop weeks later and a call months after that all belong to one person. It keeps the source, medium and campaign of that first arrival frozen, so the ad that started a journey is still attached when the money arrives, whether that takes two weeks or twenty years.

Two levels of identity sit here, and keeping them apart is what makes attribution honest. visitor_id, carried on the session, is browser-level — the Google Analytics client_id — and changes with a new device, a cleared cookie or a different browser. person_id sits above it: several of those resolved to one human. Grouping by visitor_id counts devices; grouping by person_id counts people, and a practice's patient numbers only mean anything at the second level. This way of stitching a person together across devices and years is the one APAS® Cloud uses, described for this model by its co-founder.

Fields

ColumnTypeAliasDescription
person_idSTRINGPerson IDPK. Unique identifier for the human, minted at their first page view and never reissued — one person across every device and browser they use.
first_seen_atTIMESTAMPFirst Seen AtWhen the practice first saw this person: the moment of the first page view, which is also when the identity was minted.
first_sourceSTRINGFirst SourceWhere the person came from on that very first visit, such as google, facebook or direct. Frozen on the person, so it still describes a patient treated years later.
first_mediumSTRINGFirst MediumHow that first visit arrived, such as cpc, organic or referral — the split between paid and earned traffic at first touch.
first_campaignSTRINGFirst CampaignCampaign that brought the person in the first time. NULL when the first visit came from somewhere that carries no campaign, such as direct or organic search.
first_landing_page_urlSTRINGFirst Landing PageAddress of the page the person first landed on. Plain text rather than a link to a page record, because a site gets rewritten and that page may no longer exist.
identified_atTIMESTAMPIdentified AtWhen the person first gave contact details and stopped being anonymous. NULL for someone who has only ever browsed.

Revenue Data Mart

Revenue

Money the practice has actually received. Issuing a bill creates a claim; only a payment creates revenue, and the two are kept apart because they are not the same event and do not happen at the same time. A single invoice can be settled in more than one payment, or in none, so this mart is joined to the invoice rather than folded into it.

A treatment is paid for in two pieces. The patient's copay is collected before the treatment starts and is revenue straight away, though a small share of the total. The rest comes from the insurer, after the claim is submitted and with a lag; days_to_payment records that wait, and read with payment_source it says who the practice is waiting on. That lag is why a total taken over the month a treatment happened is not the same as one taken over the month the money landed.

Revenue reaches advertising through the patient, whose person_id goes back to the first visit and the campaign frozen on it. One patient's payments can spread over years, so a campaign total sums a whole history rather than a single purchase.

Fields

ColumnTypeAliasDescription
revenue_idSTRINGRevenue IDPK. Unique identifier for one payment received. It is an event, not a balance: one bill can be settled by several rows here.
customer_idSTRINGCustomer IDThe patient the money came in for. Through them the payment reaches the person record, which is how revenue is credited back to the visit and the advertising that started it. FK to Customer (Patient)
invoice_idSTRINGInvoice IDThe bill this payment settles, in whole or in part. Comparing the two is how a practice sees what is still outstanding. FK to Invoice
received_atTIMESTAMPReceived AtWhen the money arrived. This is the date revenue belongs to, and for an insurer's part it can fall in a later period than the treatment it pays for.
amountNUMERICAmount ReceivedHow much actually arrived in this payment. It can be less than the bill asked for, and a bill can be settled by several payments.
currencySTRINGPayment CurrencyCurrency the money arrived in, such as USD. Needed before payments can be summed together.
payment_sourceSTRINGPayment SourceWhere the money came from: patient_copay, collected before the treatment starts and a small share of the total, or insurance_reimbursement, paid after the claim is processed. One arrives before the treatment and the other after the claim is processed, so cash-flow questions need this split.
days_to_paymentINTEGERDays to PaymentHow long this money took to arrive, counted from the bill. It is the reimbursement lag made measurable: read by payment_source it separates the copay collected before treatment from the insurer's part, and tracked over time it shows whether insurers are paying more slowly.

Relationships

Related data martOnCardinalityMeaning
Customer (Patient)customer_id = customer_idN:1Who the money came in for.
Invoiceinvoice_id = invoice_idN:1The bill this payment settles, in whole or in part.

Session Data Mart

Session

One visit to the practice's website, from the moment someone arrives to the moment they go quiet; everything seen or clicked in between belongs to it. A session carries the channel that brought this particular visit, the device it happened on and where in the world it came from, so it answers "what was this trip for", while the person record answers "who was this and what first brought them here".

visitor_id is browser-level — the Google Analytics client_id — lost when someone switches device, clears cookies or changes browser; person_id sits above it. Counting distinct visitors counts browsers, counting distinct people counts prospective patients, and only the second means anything for a practice's own numbers.

The UTM fields describe this visit and are deliberately not the first-touch source frozen on the person: someone who arrived through an ad and returned through a branded search has one record of each, which separates the channel that creates demand from the channel that merely collects it. campaign, term and content are empty for visits that carry no campaign at all, the normal state for direct and organic arrivals.

Fields

ColumnTypeAliasDescription
session_idSTRINGSession IDPK. Unique identifier for this visit. Every page view and every ad click that landed in the visit carries it.
person_idSTRINGPerson IDThe human this visit belongs to, which is how visits made months apart and on different devices add up to one journey. FK to Person
visitor_idSTRINGVisitor IDBrowser-level identifier the visit was recorded under — one browser on one device. Several of these belong to one person, so counting them counts devices, not patients.
started_atTIMESTAMPStarted AtWhen the visit began, which is the moment of its first page view.
ended_atTIMESTAMPEnded AtWhen the visit was closed off after the visitor went quiet. The gap from started_at is the time spent on site.
sourceSTRINGSourceWhere this particular visit came from, such as google, facebook or direct — the source of this trip, not of the person's first ever arrival.
mediumSTRINGMediumHow the visit arrived, such as cpc, organic or referral — the line between traffic that was paid for and traffic that was not.
campaignSTRINGCampaignCampaign behind this visit. Empty for arrivals that carry no campaign, such as direct traffic or unpaid search.
termSTRINGSearch TermKeyword the visit was bought or found under, where the channel supplies one.
contentSTRINGCreativeWhich creative or link variant brought this visit, matching the creative identifier carried on the ad click.
landing_page_urlSTRINGLanding PageAddress of the first page seen in this visit — the page that had to do the persuading.
device_typeSTRINGDevice TypeWhat the visit happened on: mobile, desktop or tablet. One person's visits can be spread across several devices, which is why this sits on the visit rather than on the person.
browserSTRINGBrowserBrowser the visit was made in. Useful for spotting tracking that has broken in one browser and not the others.
countrySTRINGCountryCountry the visit came from, which is what separates local demand from traffic a single-site practice can never treat.
is_first_sessionBOOLEANIs First SessionTRUE for the one visit during which this person's identity was minted, and FALSE for every return. Splits new prospects from returning ones without recomputing the journey.

Relationships

Related data martOnCardinalityMeaning
Personperson_id = person_idN:1The human this visit belongs to, across every device they use.

Treatment Data Mart

Treatment

The step between a clinician saying yes and the practice being paid. Once a consultation qualifies someone a date is agreed and the treatment is booked, the patient's copay is collected before it starts, and the claim goes to the insurer after it. Every bill and every payment in this model is raised against one of these rows: before this record the model describes demand, after it money.

Being qualified and being treated are two different things. Someone can be cleared and still decide not to go ahead, a drop-off that stays invisible unless bookings are counted separately from verdicts. booked_at and scheduled_date are deliberately separate too: one is when the appointment was agreed, the other the day it was set for, and the distance between them is the practice's waiting time.

A treatment points back at the consultation that cleared it, forward at the patient receiving it and at the clinician delivering it — the same staff list the screenings point at — so a clinician's verdict and the money that followed sit on one chain, and advertising can be read against treatments delivered, not only enquiries taken.

Fields

ColumnTypeAliasDescription
treatment_idSTRINGTreatment IDPK. Unique identifier for one booked treatment, which is what every invoice and every payment in this model is ultimately raised against.
consultation_idSTRINGConsultation IDThe assessment that cleared this treatment to go ahead. It is the step that carries a clinician's verdict forward into the money, and the enquiry and its advertising can be reached through it. FK to Consultation
customer_idSTRINGCustomer IDThe patient being treated, as the billing side of the practice knows them. FK to Customer (Patient)
employee_idSTRINGEmployee IDThe clinician delivering the treatment, drawn from the same staff list that holds the screening. It allows one person's assessments and their delivered work to be compared. FK to Employee
booked_atTIMESTAMPBooked AtWhen the appointment was agreed, not when it happens. Measured against the consultation, it is how long the practice took to convert a yes into a booking.
scheduled_dateDATEScheduled DateThe day the treatment is set for. The distance from booked_at is the waiting time a patient is asked to accept.
completed_dateDATECompleted DateThe day the treatment actually happened. Empty while it is still in the diary or was cancelled, which is what separates delivered work from booked work.
treatment_nameSTRINGTreatment NameWhat is being done. It is what makes a channel's demand readable as a case mix rather than as a single count.
statusSTRINGTreatment StatusWhere the booking stands: booked while it is ahead, completed once delivered, cancelled when it will not happen. Cancellations are qualified patients who did not get treated, so they belong in a funnel count rather than being dropped.

Relationships

Related data martOnCardinalityMeaning
Consultationconsultation_id = consultation_idN:1The assessment that cleared this treatment to go ahead.
Customer (Patient)customer_id = customer_idN:1Who is being treated.
Employeeemployee_id = employee_idN:1The clinician delivering it.

Apply to your project

  1. 1

    Install the Import Model plugin

    One plugin, installed once, in your own OWOX workspace.

    Get the plugin →

  2. 2

    Import this model

    Point it at this bundle and it creates every data mart above, joins and all.

    Open the model →

  3. 3

    Plug in your data and destinations

    Connect your own sources and send the results where your team already works.

    Browse connectors →

4. Optional — customize as you wish. Rename a column, drop a mart, add your own: once it is imported it is yours, and nothing here syncs back.