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 11: A Matter of Weight

The overweight-dog alert had been firing for a week, and Dr. Portbridge was starting to distrust it. It flagged Rex, a 45-kilo Labrador, fair enough. But it had also flagged nothing at all for a visiting Great Dane that clearly needed a diet, and it had thrown an alarm for a Chihuahua whose owner recorded its weight in grams. She looked at the rule again: weight [ > 40.0 ]. Forty. Forty what? And why would one number fit a parrot and a mastiff? The rule wasn’t wrong so much as it didn’t know what it was talking about.


The problem with a bare number

In Chapter 10 we wrote our first numeric comparison:


rule flag_overweight_dog:
  match:
    ?dog a Dog
    ?dog weight [ > 40.0 ]
  then:
    ?dog a OverweightAnimal

That 40.0 is a bare float, and a bare float carries no meaning. Is it forty kilograms? Forty pounds? Whoever writes a fact has to remember the convention and stick to it, and the moment one owner records weight 45.0 meaning 45 kg while another records weight 20.0 meaning 20 lb, the numbers lie to us. It is the same trouble string dates gave us in Chapter 9: a value the machine can store but cannot understand.

What we actually mean is a physical quantity: a number and a unit, together, as one indivisible thing.

Quantities

Dolfin writes a quantity as a smart literal, the same shape as the temporal values from Chapter 9, a keyword and human-friendly notation in parentheses:


quantity(45 kg)      # forty-five kilograms
quantity(4500 g)     # four and a half kilograms, written in grams
quantity(90 lb)      # ninety pounds
quantity(55 g)       # a small parakeet

The unit lives inside the value. Nothing is left to convention. And because Dolfin understands the units, it can do the one thing a bare number never could: compare measurements written in different units. Ninety pounds and forty kilograms are no longer two unrelated numbers, Dolfin knows that 90 lb is about 40.8 kg, and can tell you which is heavier.

Quantities are not just for weight. quantity(42 km/h), quantity(9.81 m/s^2), quantity(10 N/m^2) all work, Dolfin has a whole SI unit system built in. The Units & Quantities reference covers compound units, prefixes, arithmetic, and as conversions. Here we only need weight.

Weighing the patients properly

The Animal concept already has a weight attribute. We leave its declared type as float, a quantity’s real type is its unit, which the value carries, so the attribute only has to say “a number”:


concept Animal:
  has name: one string
  has species: one Species
  has age: optional int
  has weight: optional float   # values are quantities: quantity(45 kg)
  has owner: optional Owner
  has vaccinations: Vaccination
  has allergies: string

float accepts any quantity, quantity(45 kg) or quantity(3 s) alike, nothing here checks that a weight is actually a mass. If you want that checked, the Units & Quantities reference covers dimension-typed ranges (has weight: unit.Mass). This tutorial keeps float to focus on quantities themselves first.

Now the facts record real measurements, and each animal can use whatever unit its chart was written in. Here are this week’s patients:


fact rex a Dog
  name "Rex"
  species Dog
  weight quantity(45 kg)
  neutered true

fact bella a Dog
  name "Bella"
  species Dog
  weight quantity(90 lb)     # ≈ 40.8 kg
  neutered true

fact buddy a Dog
  name "Buddy"
  species Dog
  weight quantity(22 kg)
  neutered true

fact mittens a Cat
  name "Mittens"
  species Cat
  weight quantity(7 kg)
  indoor true

A threshold that knows its units

Now we rewrite the alert so the threshold is a quantity too:


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

When the reasoner runs, it does not compare the written numbers, it compares the underlying physical magnitudes:

rex    a OverweightAnimal    # 45 kg      > 40 kg   → flagged
bella  a OverweightAnimal    # 90 lb ≈ 40.8 kg > 40 kg → flagged
# buddy is left untouched    # 22 kg      > 40 kg   → does not match

Bella is the point of the whole chapter. Her weight was recorded in pounds, the threshold is in kilograms, and the rule still fires correctly, because Dolfin converts both to a common footing before comparing. The bare-number version could never have caught her.

Both sides must be quantities. The unit-aware comparison only runs when the stored value and the threshold are quantity(...). Comparing a quantity(...) weight against a bare 40.0, or vice versa, falls back to a plain numeric test with no unit reasoning, exactly the ambiguity we set out to remove. So once a value is a quantity, keep the threshold a quantity as well.

One threshold per species

Forty kilograms is a sensible line for a dog and absurd for a cat. Because the threshold is written right into each rule, every species gets its own:


concept OverweightCat:

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

Mittens, the cat in our patient list above at 7 kg, is a chunky cat and gets flagged. A parakeet recorded as quantity(55 g) would sit far below any of these lines, and a threshold written in grams (quantity(60 g)) would compare against it perfectly, grams, kilograms, and pounds are all the same dimension (mass), so they are all comparable.

Comparing unlike things

What if a threshold and a value are not the same kind of measurement? Suppose someone fat-fingers a length where a weight belongs:


?dog weight [ > quantity(40 m) ]     # metres — a length, not a weight

Dolfin does not crash and does not silently coerce. Mass and length are different dimensions, so the comparison simply fails and the animal is never flagged. A comparison you cannot make is treated as one that does not hold, the same rule Dolfin uses everywhere: a condition it cannot satisfy does not match.

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

# Weight thresholds, now unit-aware (this chapter)
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

Try it

Birds are recorded in grams. Write a rule that flags a Bird heavier than 500 grams as a HeavyBird, then add a fact for a parrot that weighs quantity(1.2 kg) and check that it trips the threshold even though its weight is written in kilograms and the threshold in grams:


concept HeavyBird:

rule flag_heavy_bird:
  match:
    ?bird a Bird
    # your quantity comparison here
  then:
    # your assertion here

Dr. Portbridge re-ran the alerts. Rex and Bella both surfaced, the Great Dane among them this time, and the Chihuahua-in-grams was quietly left alone. Every weight now meant exactly what it said. But catching an overweight dog after the fact was one thing. She wanted the system to refuse bad data outright, a surgery booked with an intern, a completed appointment with no diagnosis, before it was ever written down. She needed guard rails.