Chapter 3: The Appointment Book
By noon, Dr. Portbridge had seen three patients. She’d scribbled notes on separate napkins, but already she couldn’t remember whether the cat with the limp came before or after the parrot with the cough. She needed a concept for the visit itself, something that ties an animal, a date, and a reason together.
Modeling the appointment
concept Appointment:
has animal: Animal
has date: string
has reason: string
has diagnosis: string
has treatment: string
Notice that animal is of type Animal, a reference to the concept we defined earlier. This is how relationships emerge naturally in Dolfin. You don’t need a separate “relationship” syntax for simple ownership; has does the job.
Why is
datea string? Good catch. Dolfin 1.0 has four primitive types and doesn’t yet include a built-in date type. For now, we use a string like"2025-01-15". In a later chapter, we’ll see how prefixes and IRIs let you link to external type systems likexsd:date.
Standalone properties
So far, we’ve defined relationships inside concepts using has. But some relationships are important enough to deserve their own definition, especially when they could apply to multiple concepts or when you want to give them metadata.
A property is a standalone, reusable relationship:
property treatedBy:
Animal -> string
This reads: “treatedBy is a relationship from Animal to a string.” The arrow (->) separates the domain (what has the property) from the range (what values it takes).
That string is a placeholder. In reality, we’d want a Veterinarian concept. Let’s make one:
concept Veterinarian:
has name: string
has license_number: string
property treatedBy:
Animal -> Veterinarian
Now treatedBy is a first-class relationship between Animal and Veterinarian, defined separately from either concept.
When to use has vs property
| Use case | Use has | Use property |
|---|---|---|
| Simple attribute (name, age) | ✅ | ❌ |
| Core part of a concept’s identity | ✅ | ❌ |
| Relationship shared across concepts | ❌ | ✅ |
| Relationship you want to annotate or reason about | ❌ | ✅ |
Both compile to OWL properties. The difference is readability and intent.
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 Animal:
has name: string
has species: string
has age: int
has owner: Owner
concept Owner:
has first_name: string
has last_name: string
has phone: string
concept Appointment:
has animal: Animal
has date: string
has reason: string
has diagnosis: string
has treatment: string
concept Veterinarian:
has name: string
has license_number: string
property treatedBy:
Animal -> Veterinarian
Try it
Add a standalone property owns that goes from Owner to Animal. Then add a property scheduledWith from Appointment to Veterinarian:
concept Animal:
has name: string
has species: string
has age: int
has owner: Owner
concept Owner:
has first_name: string
has last_name: string
has phone: string
concept Appointment:
has animal: Animal
has date: string
has reason: string
has diagnosis: string
has treatment: string
concept Veterinarian:
has name: string
has license_number: string
property treatedBy:
Animal -> Veterinarian
# Add your properties here
The appointment book was taking shape. But as Dr. Portbridge typed
species: stringfor the tenth time, she winced. “Dog” could be typed as “dog”, “Dog”, “DOG”, “canine”, or “golden retriever”. A free-text field was an invitation for chaos. She needed a fixed list.