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:
| Quantifier | Meaning |
|---|---|
all | Every element must satisfy the condition |
none | No element may satisfy the condition |
at_least n | At least n elements must satisfy it |
at_most n | At most n elements may satisfy it |
exactly n | Exactly 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:
| Operator | Meaning | Example |
|---|---|---|
= | 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:
- Define an empty concept that represents a condition
- Write a rule that classifies instances into that concept
- 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.