Chapter 2: Object Types, Properties, Links, Actions
Chapter Introduction
Chapter 1 argued for the idea of ontology-first analytics. This chapter teaches the actual craft: how to take a domain — healthcare, lending, retail, operations — and build the four primitives (object types, properties, links, actions) into a working model that downstream code can use.
The challenge is that “modelling the domain” is not a unique exercise. There are bad ontologies, good ontologies, and many merely adequate ones. The same source data can be modelled in materially different ways depending on which questions you intend to ask, which actions you intend to take, and which downstream consumers you are serving. The chapter walks three full worked examples — healthcare claims, consumer lending, fleet operations — that demonstrate the trade-offs, and ends with an ontology canvas: a one-page template a practitioner can fill out at the start of any project to make the modelling decisions explicit before any code is written.
By the end of the chapter you should be able to look at a fresh problem domain, sketch its object types and links in under an hour, defend each modelling choice against alternatives, and produce a runnable Python representation that downstream analysis can use without rewrites.
Table of Contents
- Object Types — the Nouns of the Business
- Properties — Primitive, Derived, and Computed
- Links — Typed Relationships
- Actions — Verbs That Change the World
- Worked Example: Healthcare Claims
- Worked Example: Consumer Lending
- Worked Example: Fleet Operations
- The Ontology Canvas
Object Types — the Nouns of the Business
An object type is the schema-level definition of a class of business entities. Patient, Provider, Loan, Vessel, SKU, Branch, Shift, WorkOrder. Two pieces of advice that save the most time:
1. Object types should map to the words the business actually uses. If a hospital’s ops team talks about “encounters,” then Encounter is the right object type — not Visit (clinical word) or Event (engineer word). Adopting the business vocabulary forces alignment and surfaces ambiguity early. When the team genuinely uses two words for what is really one thing, the modelling exercise is the moment to resolve that.
2. Object types are stable; properties change. A Patient is a Patient whether their address moved or their insurance lapsed. If the candidate object type is something that fluctuates — “active customer,” “high-risk loan” — that’s not an object type, it’s a derived property or a state. The test: ask “can this entity exist without that adjective?” If yes, the adjective is a property; if no, you may have two distinct object types.
Sub-types and inheritance
A Patient can specialise into Inpatient and Outpatient. A Loan can specialise into Mortgage, AutoLoan, PersonalLoan. Sub-typing is useful when sub-types have genuinely different properties and actions — a Mortgage has a propertyAddress; a PersonalLoan does not. Avoid sub-typing for the sake of taxonomic elegance: every sub-type that does not differ in properties or actions is over-engineering.
Naming conventions
- Use PascalCase singular nouns:
Customer, notcustomersorCustomer_record. - Avoid suffixes that describe the implementation: not
CustomerTable, notCustomerDTO. - Avoid acronyms unless the business uses them:
Account, notAcct. - One canonical name per concept, recorded in the ontology glossary.
A simple metadata dictionary like the one above is the most lightweight working ontology you can have. It is enough to drive the rest of the book; production systems use Pydantic models, Pulumi resources, Foundry’s ontology editor, or raw SQL DDL — but the conceptual content is the same.
Properties — Primitive, Derived, and Computed
Properties are the columns of an object type. They split into three categories that should be modelled differently:
- Primitive properties — facts stored directly.
name,dob,open_date,amount. Mutable or immutable, but their value comes from source data. - Derived properties — deterministic computations over the object and its links.
age = today − dob;account_balance = sum(transactions.amount);tenure_months = months_between(open_date, now). These should be defined once in the ontology and computed on-demand, not stored as denormalised columns that drift out of sync. - Computed / modelled properties — outputs of statistical or ML functions over the object and its links.
churn_probability,lifetime_value_estimate,risk_score,cluster_label. These attach to the object type but require model code, training data, and lineage tracking.
Three rules that prevent the most common production headaches:
- Never store a derived property as if it were primitive. If
ageistoday − dob, do not putagein the source schema — it will be wrong as soon as the row is read after a birthday. - Every computed property has a model version. The
risk_score(Customer)returned today is frommodel_v3.4.2. If the model changes, every downstream consumer must know. - Property names are part of the public API. Renaming
lifetime_value_estimatetoltvbreaks every dashboard and downstream model that referenced the old name. Use a versioned alias table if rename is genuinely necessary.
Note the column model_version. In a real ontology every computed property carries a version pointer to the function/model that produced it. Without this, the line “the customer’s risk score is 0.34” is ambiguous — which version of which model said so, when? Lineage at this granularity is what makes the ontology auditable.
Links — Typed Relationships
A link connects two object types. Two pieces of metadata define a link:
- Direction and cardinality.
Customerone-to-manyAccount.Accountmany-to-manyTransaction(a transfer affects two accounts). - Link type / role.
Account—owned_by→Customeris different fromAccount—authorised_signatory_of→Customer. The role matters; if you only model “they are connected” you’ve lost the operational meaning.
Links can themselves carry properties: a Customer —holds→ Account link can carry an acquisition_channel, a signed_date, a signature_method. When the link’s properties matter, model the link itself as a first-class object — sometimes called an associative object type. A Loan is genuinely an object (it has terms, payments, status); it is not “a link between Customer and Bank.”
Identity of links
A link should be uniquely identified by (source_id, target_id, link_type) at minimum, and (source_id, target_id, link_type, valid_from) if the link is temporal. “Customer C1 owned Account A1 from 2020-03 to 2024-08, and then no longer.” Modelling this requires temporal link records, not a single edge.
Reading the table tells the operational story: account A1 was originally owned by C1 with C2 as a signatory; on 2024-08-15 the signatory was changed from C2 to C3. The naive flat-table “Customer has Account” loses both the role and the time evolution.
Multi-hop reasoning
Links unlock multi-hop queries that single-table joins cannot express naturally:
- “Which customers are within two hops of a customer flagged for fraud?” — a graph query, two-hop neighbourhood of the flagged customer.
- “What is the path through corporate ownership from this private company to its ultimate beneficial owner?” — recursive traversal of ownership links.
- “Which physicians refer to which physicians most often?” — a graph projection of referral links.
These are everyday queries in compliance, fraud detection, healthcare-network analysis, and supply-chain risk. They are mechanical when the ontology has typed links and painful when the schema treats relationships as ad-hoc joins.
Actions — Verbs That Change the World
An action is an operation that mutates the ontology. OpenAccount, CloseAccount, ApproveLoan, DeclineApplication, ScheduleSurgery, DispatchVessel, MarkOrderShipped. Actions are the bridge from analysis to decision.
Every action has four pieces of metadata:
- Pre-conditions — the state of the ontology required for the action to be valid. (“
Account.status == 'open'andCustomer.kyc_status == 'verified'”.) - Parameters — inputs the actor provides. (Amount, target account, reason.)
- Effects — the post-action state. (New
Loanobject created;Customer.outstanding_loansupdated; an event record appended.) - Idempotency — does running the action twice with the same parameters produce the same result, or a different one? Order-creation actions must usually be idempotent or they double-charge.
In production, actions are typically implemented as functions or workflow tasks. In Palantir Foundry they are first-class objects with permission models, parameter validation, and dry-run support. In open-source stacks like Dagster or Prefect they appear as decorated functions with input/output schemas. The pattern is the same.
Three things to notice in the cell above:
- The action signature names its parameters — the loan officer cannot run
approve_loan(C1, 25000)ambiguously. - The pre-conditions are checked inside the action — invalid state cannot be entered by skipping the check.
- The audit event is appended inside the action, atomically with the state change. There is no path through the code that mutates state without an audit record.
These are the discipline points that distinguish “code that happens to work” from “code that produces an auditable system.” A regulator reading the audit log can reconstruct exactly who did what, when, with what parameters, and what the system state was before and after.
Multiple callers (UI, batch job, API, internal tool, a manual SQL query in an emergency) will exercise the action. Pre-conditions enforced only in the UI are bypassable by every other caller, which is how unreviewed loans, double-charges, and ghost accounts get created. Putting the pre-condition inside the action means there is exactly one place to look when a regulator asks “could this state have occurred?” The answer is: no, the action rejected it.
Worked Example: Healthcare Claims
Let us build a real ontology end-to-end for a healthcare claims workflow. The business: a payer (insurance company) receives claims from providers and must adjudicate them — pay, deny, or send back for more information.
Step 1 — list the object types
A 30-minute interview with claims operations would produce roughly this list:
Patient— the insured person.Provider— the clinician or hospital submitting the claim.Claim— the submitted bill, a single event.Diagnosis— an ICD-10 code (an entity in the medical ontology).Procedure— a CPT code (another medical-ontology entity).PriorAuth— an authorisation request that may precede certain claims.Policy— the patient’s insurance plan defining coverage.
Each object type would be defined with its identifier and primitive properties:
Step 2 — define the links
A claims-business interview would also produce the links:
Step 3 — declare the actions
The verbs the business actually performs on claims:
SubmitClaim(claim_id, patient_id, provider_id, diagnoses, procedures, billed_amount)— provider submits a claim.RequestPriorAuth(patient_id, procedure_cpt, reason)— requested before high-cost procedures.ApproveClaim(claim_id)— payer pays.DenyClaim(claim_id, reason_code)— payer declines.RequestMoreInfo(claim_id, requested_fields)— payer asks for additional documentation.Appeal(claim_id, appellant, supporting_documents)— provider or patient contests a denial.
Each action has its pre-conditions, parameters, and effects. The full implementation would be a few hundred lines of Python in the open-source teaching stack — a small, well-defined surface area.
Step 4 — derive analytical properties
With the ontology in place, analytical properties are functions over it:
Patient.annual_out_of_pocket(year)= sum of patient-responsibility amounts on claims in that year.Provider.denial_rate(period)= ratio of denied to total claims by that provider in the period.Provider.specialty_mix= histogram of diagnoses across the provider’s claims.Claim.is_likely_fraudulent= output of a trained classifier on claim-level features (using the ontology to assemble the features).
Each of these is a function attached to an object type. Each is computed on demand, versioned, and lineage-tracked. The analytical workflow is now compositional: build the ontology once, then write each new analysis as a one-page function.
Worked Example: Consumer Lending
The same procedure for a consumer-lending business.
The trick — and this is where good practitioners distinguish themselves — is the explicit Decision object type. Many naive ontologies do not have one. They store the outcome on the Application as a status field. That works until the regulator asks “show me every decision your model made on this applicant, including the rejected internal recommendations that were overridden by a human.” Without a first-class Decision object, that record doesn’t exist. With it, decisioning is auditable by construction.
This is one of those moments where the ontology design anticipates a regulatory or audit requirement that hasn’t been asked yet but will be. Experienced modellers learn to over-model in places where the future audit cost is high.
Worked Example: Fleet Operations
A logistics company managing a fleet of vessels (or trucks, or aircraft — the pattern is identical).
Three useful observations:
FuelEventis its own object type, not a column onVesselorVoyage. A vessel refuels many times per voyage; refuelling has its own properties (location, price, type) and is a first-class event in the operational story.Incidentis its own object type for the same reason asDecisionin the lending case — when the auditor (or insurance investigator) asks “give me everything that happened on this vessel,” you need a single object type to query rather than scattered columns.- The link structure mirrors how the operations team narrates the business: “this voyage departs from this port, operated by this vessel, carrying these cargo units.”
The Ontology Canvas
Across all three examples, the workflow is the same. To make it reusable, the ontology canvas is a one-page template a practitioner fills out at the start of every project, before any code is written. It produces alignment between modellers, domain experts, and downstream consumers, and surfaces ambiguities while they are still cheap to fix.
┌─────────────────────────────────────────────────────────────────────────┐
│ ONTOLOGY CANVAS — Project: _________________ Date: _______ │
├──────────────────────────┬──────────────────────────────────────────────┤
│ ❶ Business Questions │ │
│ (top 3-5 questions │ │
│ the ontology must │ │
│ make easy to ask) │ │
├──────────────────────────┼──────────────────────────────────────────────┤
│ ❷ Decisions │ │
│ (top 3-5 decisions the │ │
│ ontology must support) │ │
├──────────────────────────┼──────────────────────────────────────────────┤
│ ❸ Object Types │ Name Identifier Primitive props │
│ │ _______ __________ __________________ │
│ │ _______ __________ __________________ │
│ │ _______ __________ __________________ │
├──────────────────────────┼──────────────────────────────────────────────┤
│ ❹ Links │ Source → Target Role Cardinality│
│ │ _________________ ______ ___________ │
│ │ _________________ ______ ___________ │
├──────────────────────────┼──────────────────────────────────────────────┤
│ ❺ Actions │ Action Pre-conditions Params Effects │
│ │ ______ _______________ ______ __________│
│ │ ______ _______________ ______ __________│
├──────────────────────────┼──────────────────────────────────────────────┤
│ ❻ Derived Properties │ Object.property = f(other properties) │
│ │ ________________________________________ │
├──────────────────────────┼──────────────────────────────────────────────┤
│ ❼ Out-of-Scope │ Things we explicitly do NOT model now │
│ │ ________________________________________ │
├──────────────────────────┼──────────────────────────────────────────────┤
│ ❽ Audit / Regulatory │ Who asks what about this ontology? │
│ Requirements │ ________________________________________ │
└──────────────────────────┴──────────────────────────────────────────────┘
A few rules for filling it out:
- Box ❶ and ❷ are non-negotiable. If you can’t articulate the top business questions and decisions, you are not ready to model. Push back on the project until they are clear.
- Box ❼ is the most-skipped box and the highest-leverage one. Naming what you are not modelling now prevents scope creep and gives later teams a roadmap for extension.
- Box ❽ shapes box ❸-❺. Audit and regulatory requirements often mandate first-class object types (
Decision,Incident,Consent) that the business team would not have asked for unprompted.
In a one-hour kickoff session with a domain team, filling this canvas usually produces a draft ontology good enough to start building. The remaining chapters of the book are about hardening each box on the canvas: how to handle change-over-time (Chapter 3), how to resolve messy identities (Chapter 4), how to standardise against published ontologies (Chapter 5), how to build it in code at scale (Chapter 6), how to query it (Chapter 7), how to execute actions (Chapter 8), how to layer AI on top (Chapter 9), and how to govern it all (Chapter 10).
It prevents scope creep and gives every later team — six months from now, two years from now — an explicit map of what was deliberately not modelled and what was simply forgotten. The difference matters: deliberate omissions are a feature, forgotten ones are a bug. Recording them on day one is cheaper than litigating it later.
Chapter Wrap-up
The four primitives — Object Type, Property, Link, Action — are the entire grammar of operational ontologies. Plus two: Function (statistical / ML computation attached to the ontology) and Derived Property (deterministic computation over the ontology). With those six concepts you can model any business domain.
The three worked examples above were each followed by the same recipe: name the objects, define their identifiers and primitive properties, declare the links with roles and cardinalities, enumerate the actions with their pre-conditions and effects, and derive the analytical properties on top. The ontology canvas is the reusable template; ninety per cent of practitioner work for the rest of your career will be variations of that one-page exercise.
Chapter 3 takes up the question this chapter has carefully avoided: what about time? An object type is stable, but its properties change. Its links change. Its very existence has a beginning and (sometimes) an end. The discipline of modelling change-over-time without breaking auditability is the next, and largest, chapter.
← Chapter 1 · Contents · Chapter 3: Grain, Time, and Point-in-Time Correctness →