Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions docs/src/content/docs/oceanrunner-tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: "OceanRunner modelling tutorial"
section: "Tutorials"
order: 6
---

The OceanRunner dictionary is OpenCDD's **modelling tutorial**: a
small, complete, fictional marine-propulsion catalogue whose only job
is to demonstrate — step by step — what the CDD meta-model can
express that ordinary class diagrams cannot. Every pattern below is
real modelling; the brand is fiction so the data is unencumbered.

The fixture lives in
[opencdd/data-oceanrunner](https://github.com/opencdd/data-oceanrunner)
(`reference-docs/examples/oceanrunner.cddal`, 65 entities). Load it
and follow along:

```ruby
require "opencdd"
db = Opencdd::Cddal.parse_file("oceanrunner.cddal")
```

## Step 1 — a product line is a class hierarchy

`ScenicSpot`-style roots exist here too: `Vehicle` (ORA001) is the
abstract product, with boat / car / submarine product lines beneath
it via ordinary `superclass` references. Nothing exotic yet — that is
the point: ordinary ISO 61360 modelling composes with everything
below.

```ruby
vehicle = db.find_by_code("ORA001")
vehicle.children.map(&:code) # => ["ORA010", "ORA020", "ORA030", ...]
```

## Step 2 — options are a power type

An `EngineType` is declared `CATEGORICAL_CLASS`: its instances
(Single Diesel, Twin Diesel, Electric Hybrid) are values a customer
chooses — and, because CDD power-types work this way, **those values
are themselves classes** that can own properties:

```ruby
engine_type = db.find_by_code("ORA200")
engine_type.powertype? # => true
db.instances_of(engine_type).map(&:preferred_name)
electric = db.find_by_code("ORA203")
electric.effective_properties.map(&:preferred_name)
# battery capacity, charging power, electric range — owned by the
# option value itself, with units bound into IEC 62720
```

UML cannot express this: an "instance" there is terminal. In CDD the
option value is a first-class class — the exact shape a
configuration catalogue needs.

## Step 3 — configured products are subclasses of the option

A configured product (`ORCA 30 Twin Diesel Premium Carbon Black`,
the ORA4xx block) is a subclass of the product **with a
`sub_class_selection`** naming one value per option:

```ruby
configured = db.find_by_code("ORA400")
configured.sub_class_selection.map(&:preferred_name)
# => ["Twin Diesel Engine", "Premium", "Carbon Black"]
```

Step 2 + Step 3 together are the whole configuration pattern: option
powertypes for the choice dimensions, configured subclasses for the
catalogue entries. Every property of the selected options applies to
the configured product automatically.

## Step 4 — serialized individuals close the loop

`ORI0001` is an M0 individual: a real hull with a serial number,
classified under a configured product. The M2 → M1 → M0 stack is
complete in one dictionary:

```ruby
hull = db.find_by_code("ORI0001")
hull.preferred_name # => "ORCA 30 Hull #0001"
hull.hull_identification_number
```

## What to copy for your own dictionary

1. Register a two-letter stem (OceanRunner's is `OR`) and allocate
blocks by structure: `ORA` classes, `ORB` properties, `ORI`
individuals; 3xx brand, 4xx configured.
2. Declare every choice dimension `CATEGORICAL_CLASS` and give its
values their own `applicable_properties`.
3. Model catalogue entries as subclasses with `sub_class_selection`.
4. Bind every measured property to a real unit — the build rewrites
`unit: kilowatt_hour` into the IEC 62720 IRDI
`0112/2///62720#UAA584` and cross-links it on the site.

For the data-counterpoint — a registry of real, verified entities
rather than a modelled catalogue — see the ScenicSpots registry
dictionary, generated by the pipeline in
[opencdd/data-scenicspots](https://github.com/opencdd/data-scenicspots).
6 changes: 3 additions & 3 deletions spec/antiques_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
let(:db) { Opencdd::Cddal.parse_file(ANTIQUES) }

it "loads every entity" do
expect(db.entities.size).to eq(148)
expect(db.entities.size).to eq(146)
end

describe "powertypes" do
Expand Down Expand Up @@ -118,8 +118,8 @@
it "carries the Met Museum Open Access bulk registry" do
codes = db.entities.map(&:code).grep(/\AANI/)
# 12 curated + 78 Met highlights, contiguous three-digit numbering
expect(codes.size).to eq(90)
expect(codes).to include("ANI013", "ANI050", "ANI090")
expect(codes.size).to eq(88)
expect(codes).to include("ANI013", "ANI050", "ANI088")
met = db.find_by_code("ANI013")
expect(met.preferred_name(:en)).to be_a(String)
expect(met.definition).to include("The Metropolitan Museum of Art")
Expand Down
41 changes: 0 additions & 41 deletions spec/poi_spec.rb

This file was deleted.

13 changes: 10 additions & 3 deletions spec/scenicspots_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
let(:db) { Opencdd::Cddal.parse_file(SCENICSPOTS) }

it "loads every entity" do
expect(db.entities.size).to eq(164)
expect(db.entities.size).to eq(804)
end

describe "powertypes" do
Expand Down Expand Up @@ -125,10 +125,17 @@
expect(inari.preferred_name(:ja)).to eq("伏見稲荷大社")
end

it "is the curated learning dictionary — the bulk registry lives in data-poi" do
it "is one real dataset: the full verified registry, no curated subset" do
indiv = db.entities.map(&:code).grep(/\ASPI/)
expect(indiv.size).to eq(26)
expect(indiv.size).to be > 600
expect(db.find_by_code("SPA019").preferred_name("zh-CN")).to eq("中國")
end

it "grades UNESCO World Heritage sites from their P757 claims" do
graded = db.entities.count do |e|
e.respond_to?(:properties) && e.properties["official_grade"] == "WorldHeritage"
end
expect(graded).to eq(32)
end
end
end
Loading