diff --git a/scripts/fix-oceanrunner-irdis.ts b/scripts/fix-oceanrunner-irdis.ts index cc48f857..348d27fb 100644 --- a/scripts/fix-oceanrunner-irdis.ts +++ b/scripts/fix-oceanrunner-irdis.ts @@ -4,7 +4,7 @@ * * Background: the OceanRunner demonstration CDDAL * (data-private/reference-docs/examples/oceanrunner.cddal) uses bare codes - * like "AAA001" and "UNIVERSE" everywhere, with no top-level + * like "ORA001" and "UNIVERSE" everywhere, with no top-level * dictionary / supplier declaration. The Ruby exporter faithfully * emits those bare codes into the JSON, so the browser ends up with * 40+ reference fields that are not valid IRDIs. diff --git a/src/components/islands/DictionaryTable.vue b/src/components/islands/DictionaryTable.vue index b9aeec47..bae790c4 100644 --- a/src/components/islands/DictionaryTable.vue +++ b/src/components/islands/DictionaryTable.vue @@ -18,6 +18,7 @@ export interface DictRow { totalVersions: number; multiVersionEntities: number; demonstration: boolean; + profile: string; } const props = defineProps<{ @@ -239,6 +240,12 @@ function fmt(n: number): string { > Demo + + {{ row.profile }} +

diff --git a/src/lib/build/stages.ts b/src/lib/build/stages.ts index cfbe993d..04bb881e 100644 --- a/src/lib/build/stages.ts +++ b/src/lib/build/stages.ts @@ -147,7 +147,7 @@ export function acquireFromRelease(repo = "opencdd/cdd-data"): Stage { /** * Fix: upgrade OceanRunner bare-code references to synthetic IRDIs. * - * The OceanRunner CDDAL fixture emits bare codes (e.g. "AAA001", + * The OceanRunner CDDAL fixture emits bare codes (e.g. "ORA001", * "UNIVERSE") because it has no top-level dictionary prefix. This * stage rewrites the JSON in place so every reference is a valid IRDI. * Idempotent — safe to run repeatedly. See diff --git a/src/lib/dictMetadata.ts b/src/lib/dictMetadata.ts index b0413c3f..59eeb031 100644 --- a/src/lib/dictMetadata.ts +++ b/src/lib/dictMetadata.ts @@ -36,6 +36,12 @@ export interface DictMetadata { * badge so users do not confuse it with real published data. */ demonstration?: boolean; + /** + * What the dictionary is for, shown as a chip on the dictionaries + * index: learning resources teach the meta-model; IEC dictionaries + * are reference data served under the EULA. + */ + profile?: string; } export const DICT_METADATA: Record = { @@ -50,6 +56,7 @@ export const DICT_METADATA: Record = { technicalCommittee: "IEC TC 3", webstoreUrl: "https://webstore.iec.ch/en/publication/28560", publishedYear: 2017, + profile: "Reference data (IEC CDD)", }, "iec-61360-7": { publicationId: "IEC 61360-7:2024", @@ -62,6 +69,7 @@ export const DICT_METADATA: Record = { technicalCommittee: "IEC TC 3", webstoreUrl: "https://webstore.iec.ch/en/publication/72956", publishedYear: 2024, + profile: "Reference data (IEC CDD)", }, "iec-61987": { publicationId: "IEC 61987 (series)", @@ -73,6 +81,7 @@ export const DICT_METADATA: Record = { 'IEC 61987-1:2024 "defines a generic structure in which product features of industrial process measurement devices shall be arranged, in order to facilitate the categorization of those product features, their representation by defined properties, and their exchange by properties." The series includes generic structures (Part 1), Lists of Properties for operating and device parameters (OLOP/DLOP, Parts 13–15), and domain-specific type blocks for valves, positioners, actuators, and analysers (Parts 20+).', technicalCommittee: "IEC TC 65", webstoreUrl: "https://webstore.iec.ch/en/publication/62181", + profile: "Reference data (IEC CDD)", }, "iec-62683": { publicationId: "IEC 62683-1:2026", @@ -85,6 +94,7 @@ export const DICT_METADATA: Record = { technicalCommittee: "IEC SC 121A", webstoreUrl: "https://webstore.iec.ch/en/publication/80053", publishedYear: 2026, + profile: "Reference data (IEC CDD)", }, "iec-63213": { publicationId: "IEC TR 63213:2019", @@ -97,6 +107,7 @@ export const DICT_METADATA: Record = { technicalCommittee: "IEC TC 85", webstoreUrl: "https://webstore.iec.ch/en/publication/63704", publishedYear: 2019, + profile: "Reference data (IEC CDD)", }, "iec-62720": { publicationId: "IEC 62720:2023", @@ -109,6 +120,7 @@ export const DICT_METADATA: Record = { technicalCommittee: "IEC TC 1", webstoreUrl: "https://webstore.iec.ch/en/publication/60017", publishedYear: 2023, + profile: "Reference data (IEC CDD)", }, "iec-63508": { publicationId: "IEC 63508:2026", @@ -121,6 +133,7 @@ export const DICT_METADATA: Record = { technicalCommittee: "IEC SC 23E", webstoreUrl: "https://webstore.iec.ch/en/publication/82396", publishedYear: 2026, + profile: "Reference data (IEC CDD)", }, oceanrunner: { publicationId: "Demonstration fixture", @@ -128,9 +141,36 @@ export const DICT_METADATA: Record = { title: "OceanRunner — demonstration dictionary", shortTitle: "OceanRunner (demonstration)", abstract: - "A synthetic CDD dictionary used to exercise the browser end-to-end and to illustrate power-type modelling in the docs. Not a published IEC standard. Models a fictional 'OceanRunner' transmedium vehicle manufacturer (boat / car / submarine product lines) with categorical classes, conditional properties, CLASS_REFERENCE data types, and configured-product subclasses. All entities share the IRDI scheme 0112/2///OCEANRUNNER#.", + "Learning resource: a fictional 'OceanRunner' transmedium-vehicle manufacturer (boat / car / submarine product lines) modelled the way a product catalogue works — option powertypes (EngineType, InteriorPackage, HullFinish), configured-product subclasses, multi-domain inheritance via is_case_of, and conditional properties gated by operating mode. 38 properties across the hierarchy, with every measured property bound to the real IEC 62720 units dictionary and cross-linked to it. Not a published IEC standard; all entities share the IRDI scheme 0112/2///OCEANRUNNER#.", + technicalCommittee: "n/a (demonstration)", + webstoreUrl: "", + demonstration: true, + profile: "Learning — configuration catalogue", + }, + scenicspots: { + publicationId: "Demonstration dictionary", + edition: "n/a", + title: + "ScenicSpots 旅遊景點 — demonstration dictionary (extended instances)", + shortTitle: "ScenicSpots 旅遊景點 (demonstration)", + abstract: + "Learning resource: an ontology of scenic spots across Taiwan, Japan, Korea, and Italy plus a registry of twelve real places. Demonstrates CDD power-type modelling that UML cannot express — three classification dimensions (spot type, religious tradition, official grade) whose members are extended instances owning class-level properties (Hot Spring ⇒ water temperature, flow rate, source depth), configured subclasses (Buddhist Temple, Taoist Temple, Folk Temple), a conditional property that fires only on temples, and registered individuals with native multilingual names in zh-Hant, ja, ko, and it. Individual facts are sourced from each spot's Wikipedia article.", + technicalCommittee: "n/a (demonstration)", + webstoreUrl: "", + demonstration: true, + profile: "Learning — ontology + spot registry", + }, + antiques: { + publicationId: "Demonstration dictionary", + edition: "n/a", + title: + "East Asian Antiques 東方古董 — demonstration dictionary (extended instances)", + shortTitle: "East Asian Antiques 東方古董 (demonstration)", + abstract: + "Learning resource: an auction-style cataloguing taxonomy for East Asian works of art — ceramics, bronzes, jade, painting and calligraphy, furniture, lacquer, cloisonné enamel, Buddhist sculpture, snuff bottles, and ukiyo-e prints — stacked over category × dynasty × culture powertypes, with a 'Mark and Period' conditional (reign marks apply only to ceramics), configured subclasses (Ming blue-and-white, Goryeo celadon), and twelve well-known museum objects registered as individuals with native names. Measurements cross-link to the IEC 62720 units dictionary.", technicalCommittee: "n/a (demonstration)", webstoreUrl: "", demonstration: true, + profile: "Learning — taxonomy + collection", }, }; diff --git a/src/lib/dictionary.ts b/src/lib/dictionary.ts index 35bccd98..344d6d16 100644 --- a/src/lib/dictionary.ts +++ b/src/lib/dictionary.ts @@ -60,6 +60,8 @@ export interface Dictionary { publishedYear?: number; /** True for demonstration fixtures (e.g. OceanRunner). */ demonstration: boolean; + /** What the dictionary is for, e.g. "Learning — configuration catalogue". */ + profile: string; /** Source language code, e.g. "en". */ sourceLanguage: string; @@ -93,6 +95,7 @@ function metadataFor(slug: string, fallbackTitle: string): DictMetadata { abstract: "", technicalCommittee: "", webstoreUrl: "", + profile: "", } ); } @@ -113,6 +116,7 @@ function toDictionary( webstoreUrl: meta.webstoreUrl, publishedYear: meta.publishedYear, demonstration: meta.demonstration === true, + profile: meta.profile ?? "", sourceLanguage: entry.sourceLanguage, translationLanguages: entry.translationLanguages, counts: entry.counts, diff --git a/src/lib/entityRef.ts b/src/lib/entityRef.ts index 75c94adb..6225ced5 100644 --- a/src/lib/entityRef.ts +++ b/src/lib/entityRef.ts @@ -12,6 +12,7 @@ import type { DictionaryBundle } from "./bundle"; import type { EntityNode } from "./types"; import { codeFromIrdi } from "./irdi"; import { entityRoute, detailableTypeOf } from "./entityTypeMeta"; +import { isPubliclyServed } from "./licensing"; export interface EntityRef { /** Short code, e.g. "AAA001". Falls back to codeFromIrdi. */ @@ -24,6 +25,34 @@ export interface EntityRef { resolved: boolean; /** First ~200 chars of the entity definition, for hover previews. */ definition: string | null; + /** True when the link crosses into another dictionary (unit bindings). */ + crossDictionary?: boolean; +} + +/** + * Cross-dictionary schemes. References whose IRDI data-identifier names + * one of these dictionaries resolve into that dictionary's pages — our + * own dictionaries bind their unit references to the IEC 62720 units + * dictionary (free in its entirety under EULA §6) at build time. + */ +const CROSS_DICTIONARY_SCHEMES: Record = { + "62720": "iec-62720", +}; + +function crossDictionaryRef(irdi: string): EntityRef | null { + const m = irdi.match(/\/\/\/([^/#]+)#([^#]+)/); + if (!m?.[1] || !m[2]) return null; + const slug = CROSS_DICTIONARY_SCHEMES[m[1]]; + if (!slug || !isPubliclyServed(slug)) return null; + const code = m[2]; + return { + code, + name: code, + href: entityRoute(slug, "unit", code), + resolved: false, + definition: null, + crossDictionary: true, + }; } export function resolveEntityRef( @@ -39,6 +68,10 @@ export function resolveEntityRef( const code = node?.code ?? codeFromIrdi(irdi); const name = node?.preferred_name ?? code; const href = detailable ? entityRoute(slug, detailable, code) : null; + if (href === null) { + const cross = crossDictionaryRef(irdi); + if (cross) return cross; + } const rawDef = node?.definition; const definition = rawDef ? rawDef.length > 200 diff --git a/src/lib/licensing.ts b/src/lib/licensing.ts index 212560ea..2bdf619c 100644 --- a/src/lib/licensing.ts +++ b/src/lib/licensing.ts @@ -33,6 +33,13 @@ function normalizeDict(dict: string): string { return dict.toLowerCase().replace(/[-_]/g, ""); } +/** + * Dictionaries authored by OpenCDD itself (demonstration fixtures, not + * IEC content): OceanRunner and the power-type demonstration + * dictionaries. Serve in full. + */ +const OPENCDD_OWN_KEYS = ["oceanrunner", "scenicspots", "antiques"] as const; + /** * Which license regime applies to a dictionary slug. Unknown * dictionaries default to the most restrictive regime — adding a new @@ -41,7 +48,9 @@ function normalizeDict(dict: string): string { */ export function dictLicenseRegime(dict: string): DictLicenseRegime { const key = normalizeDict(dict); - if (key === "oceanrunner") return "opencdd-own"; + if ((OPENCDD_OWN_KEYS as readonly string[]).includes(key)) { + return "opencdd-own"; + } if (key === "iec62720") return "eula-full"; return "eula-free-attributes"; } diff --git a/src/pages/d/[dict]/p/[code].astro b/src/pages/d/[dict]/p/[code].astro index f297fb87..0a42502c 100644 --- a/src/pages/d/[dict]/p/[code].astro +++ b/src/pages/d/[dict]/p/[code].astro @@ -10,13 +10,16 @@ import EntityLink from "~/components/ui/EntityLink.astro"; import { entityStaticPaths, loadEntityForPage } from "~/lib/entityPage"; import type { PropertyNode } from "~/lib/types"; +/** Fields shipped in newer @opencdd/opencdd wire releases. */ +type PropertyNodeWire = PropertyNode & { unit_text?: string }; + export function getStaticPaths() { return entityStaticPaths("property"); } const { dict } = Astro.params; const { irdi } = Astro.props; -const { node, bundle } = loadEntityForPage(dict!, irdi as string); +const { node, bundle } = loadEntityForPage(dict!, irdi as string); if (!node || node.type !== "property") { return Astro.redirect("/404/"); @@ -56,7 +59,12 @@ const stats = usedBy.length > 0 {unit && ( - + )} {valueList && ( diff --git a/src/pages/dictionaries.astro b/src/pages/dictionaries.astro index 27bd2041..ca204aa3 100644 --- a/src/pages/dictionaries.astro +++ b/src/pages/dictionaries.astro @@ -36,12 +36,13 @@ const rows: DictRow[] = allDicts.map((d) => { totalVersions, multiVersionEntities: totalVersions, demonstration: d.demonstration === true, + profile: d.profile, }; }); -// Categorised by license regime: our own demonstration data first, -// then the fully-free units dictionary, then the IEC CDD dictionaries -// served as §5 free attributes. +// Categorised by role: our learning resources first, then the IEC +// reference data — freely licensed in full for the units dictionary, +// §5 free attributes for the rest. const categories: Array<{ id: string; heading: string; @@ -50,23 +51,23 @@ const categories: Array<{ }> = [ { id: "opencdd-own", - heading: "OpenCDD demonstration dictionaries", + heading: "OpenCDD learning resources — demonstration dictionaries", caption: - "Invented by OpenCDD — not IEC content. Full data, BSD-3-Clause, like the rest of our code.", + "Authored by OpenCDD to teach the CDD meta-model — not IEC content. OceanRunner demonstrates product-line configuration modelling (option powertypes, configured subclasses, conditional properties); ScenicSpots and Antiques demonstrate power-typed ontologies with registered real-world individuals and native multilingual names. Their measured properties reference the IEC 62720 units dictionary directly and cross-link to it. Full data, BSD-3-Clause, like the rest of our code.", rows: rows.filter((r) => dictLicenseRegime(r.slug) === "opencdd-own"), }, { id: "eula-full", - heading: "Freely licensed — IEC 62720 units dictionary", + heading: "Reference data — IEC 62720 units dictionary (fully free)", caption: - "Free in its entirety under IEC CDD License Agreement § 6: every attribute, with IEC referenced as the source.", + "Reference data, free in its entirety under IEC CDD License Agreement § 6: every attribute of the quantities-and-units dictionary, with IEC referenced as the source. This is the dictionary our learning resources link their units to.", rows: rows.filter((r) => dictLicenseRegime(r.slug) === "eula-full"), }, { id: "eula-free-attributes", - heading: "IEC CDD dictionaries — free attributes (§ 5)", + heading: "Reference data — IEC CDD dictionaries (§ 5 free attributes)", caption: - "Served strictly as the FREE ATTRIBUTES of the IEC CDD License Agreement § 5 — identities, names, value formats, structure. Definitions and other reserved attributes are not served; get them from cdd.iec.ch or run OpenCDD yourself.", + "Real IEC CDD data for reference only, served strictly as the FREE ATTRIBUTES of the IEC CDD License Agreement § 5 — identities, names, value formats, structure. Definitions and other reserved attributes are not served; get them from cdd.iec.ch or run OpenCDD yourself. To learn the meta-model, start with the demonstration dictionaries above.", rows: rows.filter((r) => dictLicenseRegime(r.slug) === "eula-free-attributes"), }, ].filter((c) => c.rows.length > 0); diff --git a/tests/lib/licensing.test.ts b/tests/lib/licensing.test.ts index 6c3f8800..16c7b55b 100644 --- a/tests/lib/licensing.test.ts +++ b/tests/lib/licensing.test.ts @@ -20,6 +20,11 @@ describe("dictLicenseRegime", () => { expect(dictLicenseRegime("oceanrunner")).toBe("opencdd-own"); }); + it("treats the demonstration dictionaries as OpenCDD's own data", () => { + expect(dictLicenseRegime("scenicspots")).toBe("opencdd-own"); + expect(dictLicenseRegime("antiques")).toBe("opencdd-own"); + }); + it("treats the IEC 62720 units dictionary as EULA-full", () => { expect(dictLicenseRegime("iec62720")).toBe("eula-full"); expect(dictLicenseRegime("iec-62720")).toBe("eula-full");