Chapter 8: Automatic Alerts
It was 8 PM. Dr. Portbridge was still at the clinic, manually cross-referencing vaccination records with appointment dates to find overdue animals. “There has to be a way to automate this,” she muttered. She needed the system to reason, to look at data and draw conclusions.
The problem
So far, our ontology describes things but doesn’t infer anything. If an animal hasn’t been vaccinated in over a year, a human has to notice. If an appointment is marked as an emergency but assigned to an intern, nobody catches it. We want the system to derive new facts from existing ones.
Rules
A rule defines an if-then inference: if certain patterns hold in the data, then new facts follow.
rule flag_unvaccinated:
match:
?animal a Animal
?animal vaccinations 0
then:
?animal a UnvaccinatedAnimal
Note: This is a simplified example. Real vaccine-overdue logic would involve date arithmetic. The important thing is the pattern: match conditions, then assert conclusions.
Let’s unpack the syntax.
Variables
Variables start with ?. They bind to values during pattern matching:
?animal: binds to any Animal instance
Patterns
Each line in the match: block is a pattern:
| Pattern | Meaning |
|---|---|
?animal a Animal | ?animal is an instance of Animal |
?animal vaccinations 0 | ?animal has an attribute vaccinations whose value is 0 |
Patterns are combined with implicit AND. All must hold simultaneously.
Assertions
Each line in the then: block is an assertion, a new fact to create:
| Assertion | Meaning |
|---|---|
?animal a UnvaccinatedAnimal | Classify ?animal as an UnvaccinatedAnimal |
A more practical example
Let’s flag emergency appointments that are assigned to interns:
concept UnsafeAssignment:
rule flag_intern_emergency:
match:
?appt a Appointment
?appt urgency Emergency
?appt animal [ treatedBy [ a Intern ] ]
then:
?appt a UnsafeAssignment
This says: “If an appointment is an Emergency, and the treating vet is an Intern, flag it as an UnsafeAssignment.”
Inferring new relationships
Rules don’t just classify, they can create relationships:
rule assign_primary_vet:
match:
?animal a Animal
?animal owner [ preferred_vet ?vet ]
then:
?animal treatedBy ?vet
“If an animal’s owner has a preferred vet, assign that vet to the animal.”
(This requires adding preferred_vet to Owner, we’ll do that below.)
Weight-based alerts
Here’s a rule using numeric comparison:
concept OverweightAnimal:
rule flag_overweight_dog:
match:
?dog a Dog
?dog weight [ > 40.0 ]
then:
?dog a OverweightAnimal
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 concepts (created by rules)
concept UnvaccinatedAnimal:
concept UnsafeAssignment:
concept OverweightAnimal:
# 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 assign_primary_vet:
match:
?animal a Animal
?animal owner [ preferred_vet ?vet ]
then:
?animal treatedBy ?vet
Try it
Write a rule that classifies a Cat as a SeniorCat if its age is greater than or equal to 10:
concept SeniorCat:
rule flag_senior_cat:
match:
# your patterns here
then:
# your assertion here
The alerts were a revelation. The system caught an intern assigned to an emergency before it became a problem. It flagged three overweight dogs whose owners hadn’t noticed the gradual change. But Dr. Portbridge wanted to go further, she wanted to express constraints like “a surgery appointment MUST be handled by a Surgeon” and “every animal must have at least one vaccination before age 1.” She needed guard rails.