Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 12: Guard Rails

An owner brought in a hamster for dental surgery. The system accepted the appointment without complaint, it didn’t know that dental procedures require a Dentist, or that hamsters aren’t in the Species enum (they’d have to be Other). The rules we’ve written so far could flag problems after the fact, but Dr. Portbridge wanted to prevent mistakes from being recorded in the first place.


The problem

Rules infer new facts, they add information. But they don’t prevent bad data. We need a way to express expectations: “Every X must satisfy Y”, “No X should ever have Z.”

Quantifiers in rules

Dolfin provides quantifiers that express conditions over collections:

QuantifierMeaning
allEvery element must satisfy the condition
noneNo element may satisfy the condition
at_least nAt least n elements must satisfy it
at_most nAt most n elements may satisfy it
exactly nExactly n elements must satisfy it

Ensuring surgical appointments have surgeons


concept InvalidSurgery

rule validate_surgery_staff:
  match:
    ?appt a Appointment
    ?appt reason "surgery"
    ?appt animal ?animal
    none ?vet:
      ?animal treatedBy ?vet
      ?vet a Surgeon
  then:
    ?appt a InvalidSurgery

This reads: “If an appointment is for surgery but no Surgeon is treating the animal, flag it as invalid.”

The none ?vet: quantifier introduces a fresh variable and a nested block; it succeeds when no binding of ?vet satisfies the sub-patterns, here, no vet who both treats the animal and is a Surgeon.

Minimum vaccination rules

Every dog should have at least one vaccination (in the real world, rabies is required by law in most places):


concept UnderVaccinatedDog

rule check_dog_vaccines:
  match:
    ?dog a Dog
    ?dog vaccinations 0
  then:
    ?dog a UnderVaccinatedDog

Comparison operators

You’ve already seen > and = in rules. Here’s the complete set:

OperatorMeaningExample
=Equal?x = 5
!=Not equal?x != "none"
<Less than?age < 1
<=Less than or equal?age <= 12
>Greater than?weight > quantity(40 kg)
>=Greater than or equal?age >= 10

Combining conditions

Rule patterns are combined with implicit AND. All conditions must hold for the rule to fire:


concept AtRiskAnimal

rule flag_at_risk:
  match:
    ?animal a Animal
    ?animal age [ > 15 ]
    ?animal weight [ < quantity(2 kg) ]
    ?animal vaccinations 0
  then:
    ?animal a AtRiskAnimal

“An animal older than 15, weighing less than 2 kg, with no vaccinations, is at risk.”

Validation concepts as a pattern

Notice the design pattern emerging: we create empty concepts (InvalidSurgery, UnderVaccinatedDog, AtRiskAnimal) that exist only to be assigned by rules. They act as tags or flags. Downstream systems can query for all instances of InvalidSurgery and take action.

This is a powerful idiom:

  1. Define an empty concept that represents a condition
  2. Write a rule that classifies instances into that concept
  3. Query or display instances of the condition

Worked example: from data to flag to query

Take a single rule built only from conditions that run today:


concept SeniorCat:

rule flag_senior_cat:
  match:
    ?cat a Cat
    ?cat age [ >= 10 ]
  then:
    ?cat a SeniorCat

Given this patient:


fact mittens a Cat
  name "Mittens"
  species Cat
  age 12
  indoor true

the reasoner derives mittens a SeniorCat. Because the flag is a normal concept, a downstream system finds every senior cat with an ordinary query:

SELECT ?cat WHERE { ?cat a :SeniorCat }

The result includes mittens even though no one ever wrote mittens a SeniorCat by hand. The rule produced it, and it sits in the graph next to the asserted data.

Reminder: no aggregators yet. Guard rails phrased as counts (e.g. “a dog with zero vaccinations”, “an appointment with more than 3 cancellations”) are aggregations and don’t run as rules yet. Express conditions over a single value (comparisons, exact matches, nested patterns) until aggregator support lands.

The story so far


package <http://happypaws.com/clinic>:
  dolfin_version "1"
  version "0.1.0"
  author "Dr. Helen Portbridge"
  description "The Happy Paws veterinary clinic data model"

concept Species:
  one of:
    Dog
    Cat
    Bird
    Rabbit
    Reptile
    Other

concept Urgency:
  one of:
    Routine
    Urgent
    Emergency

concept AppointmentStatus:
  one of:
    Scheduled
    InProgress
    Completed
    Cancelled

concept Owner:
  has first_name: one string
  has last_name: one string
  has phone_numbers: at least 1 string
  has email: optional string
  has address: optional string
  has preferred_vet: optional Veterinarian

concept Veterinarian:
  has name: one string
  has license_number: one string
  has specialization: optional string

concept Surgeon:
  sub Veterinarian
  has surgery_count: one int
  has certified_procedures: at least 1 string

concept Dentist:
  sub Veterinarian
  has dental_certification: one string

concept Intern:
  sub Veterinarian
  has university: one string
  has year: one int

concept Vaccination:
  has vaccine_name: one string
  has date_administered: one string
  has batch_number: optional string

concept Animal:
  has name: one string
  has species: one Species
  has age: optional int
  has weight: optional float
  has owner: optional Owner
  has vaccinations: Vaccination
  has allergies: string

concept Dog:
  sub Animal
  has breed: optional string
  has neutered: one boolean

concept Cat:
  sub Animal
  has indoor: one boolean

concept Bird:
  sub Animal
  has wingspan: optional float
  has can_fly: one boolean

concept Appointment:
  has animal: one Animal
  has scheduled_for: one date_time
  has reason: one string
  has urgency: one Urgency
  has status: one AppointmentStatus
  has diagnosis: optional string
  has treatments: string
  has notes: optional string

property treatedBy: Animal -> Veterinarian

# Flag concepts and inference rules (Chapter 10)
concept UnvaccinatedAnimal
concept UnsafeAssignment
concept OverweightAnimal

rule flag_unvaccinated:
  match:
    ?animal a Animal
    ?animal vaccinations 0
  then:
    ?animal a UnvaccinatedAnimal

rule flag_intern_emergency:
  match:
    ?appt a Appointment
    ?appt urgency Emergency
    ?appt animal [ treatedBy [ a Intern ] ]
  then:
    ?appt a UnsafeAssignment

rule assign_primary_vet:
  match:
    ?animal a Animal
    ?animal owner [ preferred_vet ?vet ]
  then:
    ?animal treatedBy ?vet

# Unit-aware weight thresholds (Chapter 11)
concept OverweightCat

rule flag_overweight_dog:
  match:
    ?dog a Dog
    ?dog weight [ > quantity(40 kg) ]
  then:
    ?dog a OverweightAnimal

rule flag_overweight_cat:
  match:
    ?cat a Cat
    ?cat weight [ > quantity(6 kg) ]
  then:
    ?cat a OverweightCat

# Guard rails: validation concepts and rules (this chapter)
concept SeniorCat
concept InvalidSurgery
concept UnderVaccinatedDog
concept AtRiskAnimal

rule flag_senior_cat:
  match:
    ?cat a Cat
    ?cat age [ >= 10 ]
  then:
    ?cat a SeniorCat

rule validate_surgery_staff:
  match:
    ?appt a Appointment
    ?appt reason "surgery"
    ?appt animal ?animal
    none ?vet:
      ?animal treatedBy ?vet
      ?vet a Surgeon
  then:
    ?appt a InvalidSurgery

rule check_dog_vaccines:
  match:
    ?dog a Dog
    ?dog vaccinations 0
  then:
    ?dog a UnderVaccinatedDog

rule flag_at_risk:
  match:
    ?animal a Animal
    ?animal age [ > 15 ]
    ?animal weight [ < quantity(2 kg) ]
    ?animal vaccinations 0
  then:
    ?animal a AtRiskAnimal

Try it

Write a validation rule that flags an Appointment as MissingDiagnosis when its status is Completed but diagnosis is absent (equals empty string ""):


concept MissingDiagnosis:

rule check_completed_diagnosis:
  match:
    # your patterns here
  then:
    # your assertion here

The guard rails caught two problems on day one: a surgery booked with an intern and a completed appointment with no recorded diagnosis. Dr. Portbridge felt like the system was finally earning its keep.

Then the regional veterinary board called. They needed the clinic to export its data in a format compatible with the national animal health registry, which used standard OWL/RDF vocabularies. Dr. Portbridge’s concept names were fine for her clinic, but the outside world expected specific IRIs. She needed a bridge.