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 9: 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 from the previous chapter 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"
    among:
      ?appt animal [ treatedBy ?vet ]
    none:
      ?vet a Surgeon
  then:
    ?appt a InvalidSurgery

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

The none ?vet a Surgeon pattern succeeds when the bound ?vet does not belong to the Surgeon concept.

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 > 40.0
>=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 [ < 2.0 ]
    ?animal vaccinations 0
  then:
    ?animal a AtRiskAnimal

“An animal older than 15, weighing less than 2kg, 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

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:
  only values:
    Dog
    Cat
    Bird
    Rabbit
    Reptile
    Other

concept Urgency:
  only values:
    Routine
    Urgent
    Emergency

concept AppointmentStatus:
  only values:
    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 date: one string
  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

# Derived / flag concepts
concept UnvaccinatedAnimal
concept UnsafeAssignment
concept OverweightAnimal
concept SeniorCat
concept InvalidSurgery
concept UnderVaccinatedDog
concept AtRiskAnimal

# Inference rules
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 flag_overweight_dog:
  match:
    ?dog a Dog
    ?dog weight [ > 40.0 ]
  then:
    ?dog a OverweightAnimal

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

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

rule validate_surgery_staff:
  match:
    ?appt a Appointment
    ?appt reason "surgery"
    among:
      ?appt animal [ treatedBy ?vet ]
    none:
      ?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 [ < 2.0 ]
    ?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.