diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5231fe4..fd11e84 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -27,8 +27,9 @@ jobs:
- name: Drift gate (generated models + schema docs match the schema)
# Regenerate on the same Python 3.9 target and fail if the committed artifacts differ.
- # xsdata is pinned (==25.7, the last 3.9-supporting line) so generation is byte-stable.
- # See docs/decisions/0008.
+ # Models: xsdata is pinned (==25.7, the last 3.9-supporting line) so generation is
+ # byte-stable. Schema reference: HTML produced from the XSD by the vendored xs3p
+ # stylesheet (deterministic). See docs/decisions/0008 and 0010.
run: |
make generate
make gen-schema-docs
diff --git a/.gitignore b/.gitignore
index 73cc612..6104002 100644
--- a/.gitignore
+++ b/.gitignore
@@ -222,3 +222,6 @@ site/
# Local verification venvs
.venv-check/
+
+# Private real material — real XSD/corpus/models. NEVER committed (proprietary).
+private/
diff --git a/Makefile b/Makefile
index 7674bd9..221c5aa 100644
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,7 @@ docs-serve: ## Live-preview the HTML docs at http://localhost:8000
generate: ## Regenerate typed models from schema/*.xsd via xsdata.
python -m acoustic_dataset.cli generate
-gen-schema-docs: ## Generate schema reference pages + Mermaid ERD from the enriched XSD.
+gen-schema-docs: ## Generate the HTML schema reference from the XSD (via vendored xs3p).
python -m acoustic_dataset.cli gen-schema-docs
pipeline: ## End-to-end: map example input -> objects -> XML -> validate -> round-trip.
diff --git a/docs/concepts/pipeline-data-flow.md b/docs/concepts/pipeline-data-flow.md
index 59e4ac3..07b7ca0 100644
--- a/docs/concepts/pipeline-data-flow.md
+++ b/docs/concepts/pipeline-data-flow.md
@@ -27,11 +27,11 @@ no pickle) is needed to get testability; that whole chain was removed (ADR 0002)
## The entities as an ER diagram
-This is the kind of **Mermaid ERD** the docs render — here drawn by hand for the data-flow story
-(it deliberately includes pipeline entities like the golden and reference files). The
-**[schema reference](../reference/schema/index.md)** ERD, by contrast, is produced
-**automatically from the schema** by `make gen-schema-docs`
-(ADR 0009).
+This **Mermaid ERD** is drawn by hand for the data-flow story (it deliberately includes pipeline
+entities like the golden and reference files). The
+**[schema reference](../reference/schema/index.html)**, by contrast, is produced
+**automatically from the schema** (as HTML) by `make gen-schema-docs`
+(ADR 0011).
```mermaid
erDiagram
diff --git a/docs/concepts/schema-as-contract.md b/docs/concepts/schema-as-contract.md
index 0f51503..7e506b1 100644
--- a/docs/concepts/schema-as-contract.md
+++ b/docs/concepts/schema-as-contract.md
@@ -34,8 +34,7 @@ flowchart TD
XSD["Enriched XSD (schema/acoustic_dataset.xsd)"]
XSD --> Models["Typed data classes (xsdata)"]
XSD --> Validate["Validation gate (xmlschema)"]
- XSD --> Docs["HTML schema reference (MkDocs Material)"]
- XSD --> ERD["Mermaid ERD"]
+ XSD --> Docs["HTML schema reference (xs3p)"]
XSD --> Bindings["Other-language bindings (Java, JSON Schema — later)"]
Models --> XML["Emitted XML Acoustic Dataset"]
Validate --> XML
diff --git a/docs/decisions/0009-mkdocs-material-mermaid-html-docs.md b/docs/decisions/0009-mkdocs-material-mermaid-html-docs.md
index 1c77375..6177093 100644
--- a/docs/decisions/0009-mkdocs-material-mermaid-html-docs.md
+++ b/docs/decisions/0009-mkdocs-material-mermaid-html-docs.md
@@ -1,9 +1,14 @@
# ADR 0009: MkDocs Material + Mermaid for HTML docs and ERDs generated from the XSD
-- **Status**: Accepted
+- **Status**: Superseded (in part) by [ADR 0011](0011-xs3p-html-schema-reference.md)
- **Date**: 2026-06-13
- **Deciders**: Project team
+> **Note (superseded in part):** the decision to *generate the schema reference + Mermaid ERD as
+> Markdown by walking the XSD ourselves* is superseded by [ADR 0011](0011-xs3p-html-schema-reference.md),
+> which generates the reference as HTML with the off-the-shelf xs3p stylesheet (no generated ERD).
+> The choice of MkDocs Material + Mermaid for the rest of the site still stands.
+
## Context
Two documentation needs exist:
diff --git a/docs/decisions/0011-xs3p-html-schema-reference.md b/docs/decisions/0011-xs3p-html-schema-reference.md
new file mode 100644
index 0000000..a72d3f3
--- /dev/null
+++ b/docs/decisions/0011-xs3p-html-schema-reference.md
@@ -0,0 +1,44 @@
+# ADR 0011: Generate the schema reference as HTML with xs3p
+
+**Status:** Accepted (supersedes the schema-reference/ERD portion of
+[ADR 0009](0009-mkdocs-material-mermaid-html-docs.md))
+
+## Context
+
+[ADR 0009](0009-mkdocs-material-mermaid-html-docs.md) chose to generate the schema reference as
+Markdown — entity tables plus a hand-shaped Mermaid ERD — by walking the XSD in our own code
+(`schema_docs.py`). When that code was first pointed at a **real** XSD (no-namespace, XSD 1.1,
+"salami-slice": every element global, complex types built from `xs:all` and `xs:element ref="..."`,
+documentation on the global elements / ref sites), it produced almost nothing: the hand-rolled
+walker assumed `xs:sequence` + inline-named elements + `xs:extension` inheritance and silently
+skipped everything else.
+
+That is a direct violation of the project's **"configure, don't create"** principle: we were
+hand-implementing XSD semantics (ref resolution, model groups, type resolution, XSD 1.1) — the
+exact thing a real schema library or tool does correctly. Re-implementing it is both effort and a
+standing source of bugs.
+
+## Decision
+
+Generate the schema reference as **HTML** using the off-the-shelf **xs3p** XSLT stylesheet,
+driven through `lxml`'s XSLT engine (`make gen-schema-docs` → `schema_html.py`):
+
+- xs3p (`tools/xs3p/xs3p.xsl`, vendored unmodified) renders any XSD — including the real idiom —
+ into a single self-contained HTML reference. We own no XSD-parsing code.
+- Output is **byte-deterministic** (no embedded timestamp/path), so the committed
+ `docs/reference/schema/index.html` is **drift-checked** in CI exactly as the generated models are.
+- The hand-rolled `schema_docs.py` and its Mermaid ERD are removed. The remaining Mermaid diagrams
+ in the *concept* pages are hand-drawn illustrations and stay.
+
+xs3p is licensed under the **DSTC Public License (DPL) 1.1** (an MPL-1.1-derived weak copyleft);
+its `LICENSE.html` is vendored alongside the stylesheet and the file is kept unmodified
+(`tools/xs3p/README.md`).
+
+## Consequences
+
+- The reference is robust to real-world XSD idioms by construction — no parser to maintain.
+- We trade the MkDocs-integrated Markdown + Mermaid ERD for a standalone HTML page linked from the
+ site. No entity-relationship *diagram* of the contract is generated anymore.
+- A new, file-level-copyleft licence (DPL 1.1) enters the tree, scoped to `tools/xs3p/`.
+- ADR 0009's choice of MkDocs Material + Mermaid for the rest of the site still stands; only its
+ "schema reference + ERD generated as Markdown" portion is superseded here.
diff --git a/docs/decisions/index.md b/docs/decisions/index.md
index a2a66c8..de82f71 100644
--- a/docs/decisions/index.md
+++ b/docs/decisions/index.md
@@ -31,7 +31,9 @@ To add one, follow [Add a decision record](../how-to/add-a-decision-record.md) u
| [0006](0006-codespaces-with-local-fallback.md) | GitHub Codespaces as primary env, with a portable local fallback | Accepted |
| [0007](0007-pin-python-3-9-4.md) | Pin to exactly Python 3.9.4 to match the target system | Accepted |
| [0008](0008-generated-models-no-drift.md) | Treat bindings as generated artifacts; fail CI on drift | Accepted |
-| [0009](0009-mkdocs-material-mermaid-html-docs.md) | MkDocs Material + Mermaid for HTML docs and ERDs from the XSD | Accepted |
+| [0009](0009-mkdocs-material-mermaid-html-docs.md) | MkDocs Material + Mermaid for HTML docs and ERDs from the XSD | Superseded (in part) |
+| [0010](0010-build-schema-object-directly.md) | Build the schema's data object directly (no intermediate hierarchy) | Accepted |
+| [0011](0011-xs3p-html-schema-reference.md) | Generate the schema reference as HTML with xs3p | Accepted |
!!! note
Most decisions are **provisional in spirit** — the delivery plan records that the design
diff --git a/docs/glossary.md b/docs/glossary.md
index 9ab600a..b787d06 100644
--- a/docs/glossary.md
+++ b/docs/glossary.md
@@ -44,9 +44,9 @@ catch *schema-valid-but-different* output during migration.
validation yet differs from what a consumer depended on. Caught by the migration-safety
comparison, not by validation alone.
-**ERD (Entity-Relationship Diagram)** — a diagram of entities and how they relate. We
-render these with **Mermaid** and generate them from the enriched XSD so they cannot drift
-from the contract. See the [generated schema reference](reference/schema/index.md).
+**ERD (Entity-Relationship Diagram)** — a diagram of entities and how they relate. We draw
+these by hand with **Mermaid** in the concept pages. The entity/type structure of the contract
+itself is in the [generated schema reference](reference/schema/index.html) (HTML, from the XSD).
## Tooling
diff --git a/docs/how-to/build-the-docs-site.md b/docs/how-to/build-the-docs-site.md
index 65ecd86..700ab9b 100644
--- a/docs/how-to/build-the-docs-site.md
+++ b/docs/how-to/build-the-docs-site.md
@@ -44,15 +44,15 @@ erDiagram
```
````
-See the [pipeline ERD](../concepts/pipeline-data-flow.md) and the
-[generated schema ERD](../reference/schema/index.md) for working examples.
+See the [pipeline ERD](../concepts/pipeline-data-flow.md) for a hand-drawn example; the
+[generated schema reference](../reference/schema/index.html) is HTML produced from the XSD.
-## Schema reference & ERD are *generated*
+## The schema reference is *generated*
-The [schema reference](../reference/schema/index.md) — the entity tables and the Mermaid ERD —
-is produced from the **enriched XSD** by `make gen-schema-docs`, not hand-written, so it can't
-drift from the contract. Regenerate it after any schema change; the CI drift gate fails if the
-committed page is stale.
+The [schema reference](../reference/schema/index.html) — every element, type, facet and
+`xs:documentation` note — is produced from the XSD by the vendored xs3p stylesheet
+(`make gen-schema-docs`), not hand-written, so it can't drift from the contract. Regenerate it
+after any schema change; the CI drift gate fails if the committed page is stale.
## Linking rules (to keep `--strict` happy)
diff --git a/docs/how-to/change-the-schema.md b/docs/how-to/change-the-schema.md
index 7c0cb5b..2bc47a5 100644
--- a/docs/how-to/change-the-schema.md
+++ b/docs/how-to/change-the-schema.md
@@ -21,12 +21,12 @@ redesign*: models, validation, bindings and the schema docs all regenerate from
(ADR 0008). Generation is pinned to the 3.9
toolchain so the output is byte-reproducible for the drift gate.
-3. **Regenerate the schema docs + ERD.**
+3. **Regenerate the schema reference.**
```bash
make gen-schema-docs
```
- Produces the reference pages and the Mermaid ERD from the schema
- (ADR 0009).
+ Produces the HTML reference from the schema via xs3p
+ (ADR 0011).
4. **Update the builder.**
`src/acoustic_dataset/build.py` is the **one place** that knows element names — update it
@@ -64,5 +64,5 @@ output that is schema-valid but differs from what a consumer depends on
## What you should *not* touch
- Generated models (`src/acoustic_dataset/models/`) — regenerate instead.
-- Generated schema reference/ERD under `reference/schema/` — regenerate instead.
+- Generated HTML schema reference under `reference/schema/` — regenerate instead.
- The generation code — it's schema-agnostic by design.
diff --git a/docs/how-to/run-the-migration-safety-comparison.md b/docs/how-to/run-the-migration-safety-comparison.md
index 8d63301..e72efd4 100644
--- a/docs/how-to/run-the-migration-safety-comparison.md
+++ b/docs/how-to/run-the-migration-safety-comparison.md
@@ -47,11 +47,11 @@ The comparison canonicalises both sides first, so these **never** cause a failur
|---|---|
| Attribute order | `a="1" b="2"` vs `b="2" a="1"` |
| Whitespace / indentation | pretty-printed vs single-line |
-| Namespace **prefix** | default `xmlns=…` vs `ds:` prefix (same URI) |
+| Namespace **prefix** | the same URI under a prefix vs the default namespace (the contract itself is no-namespace) |
| Comments | a `` banner |
Anything that changes the **data** — an element value, a missing or extra element, a different
-structure — is reported as a difference. For instance, a radiated-noise `Level` of `134.000`
+structure — is reported as a difference. For instance, a radiated-noise `SectorLevel` of `134.000`
that becomes `144.000` is still inside the schema's decibel band (so it stays schema-valid) but
is surfaced as a one-line diff.
diff --git a/docs/index.md b/docs/index.md
index 3efc10a..904076d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -28,8 +28,8 @@ different question — plus a set of **Architecture Decision Records (ADRs)** th
## The one-paragraph mental model
The **schema (XSD) is the contract.** Everything derives from it: typed data classes
-(generated by `xsdata`), the validation gate (`xmlschema`), the HTML reference docs and
-Mermaid ERD, and the language bindings shipped to consumers. We never hand-write the
+(generated by `xsdata`), the validation gate (`xmlschema`), the HTML reference docs
+(generated by xs3p), and the language bindings shipped to consumers. We never hand-write the
things the schema can generate — *configure, don't create*. When you write Python here you
start from a structured set of parameters and the data stays in **typed objects end to end**,
from that structure through to the XML — see
@@ -44,8 +44,8 @@ human semantic gate (is the science right?). See
- Every significant choice is recorded as a numbered **ADR**, kept in the
repository under `docs/decisions/` (not published to this site).
- New understanding lands as a **concept** page; new recipes as **how-to** guides.
-- The **schema reference and ERD** are *generated from the enriched XSD* — see the
- [generated schema reference](reference/schema/index.md) — so the docs can never drift from the contract.
+- The **schema reference** is *generated from the XSD* (by xs3p) — see the
+ [generated schema reference](reference/schema/index.html) — so the docs can never drift from the contract.
!!! note "Status"
The environment (Codespace, tests, docs site) and the full schema-driven pipeline are real
diff --git a/docs/onboarding.md b/docs/onboarding.md
index db19b27..b79fbba 100644
--- a/docs/onboarding.md
+++ b/docs/onboarding.md
@@ -31,7 +31,7 @@ before the same `make verify` / `make pipeline`. Both paths reach the *same* gre
| Example calculation input | `examples/calculation_input.json` |
| Tests (unit / integration / golden) | `tests/` |
| The plan & design artifacts | `specs/001-codespace-xml-scaffold/` (`spec.md`, `plan.md`, `tasks.md`) |
-| The generated schema reference + ERD | [reference/schema](reference/schema/index.md) (run `make gen-schema-docs`) |
+| The generated HTML schema reference | [reference/schema](reference/schema/index.html) (run `make gen-schema-docs`) |
| Why each choice was made | `docs/decisions/` (ADRs, kept in the repo) |
## Build the mental model
@@ -45,5 +45,5 @@ Read these, in order — they're short:
!!! tip "What's done, what's next"
The full Phase 1 pipeline is in place — environment, end-to-end pipeline, migration-safety
- `compare`, the generated schema reference/ERD, and the distribution `bundle` with a CI drift
+ `compare`, the generated HTML schema reference, and the distribution `bundle` with a CI drift
gate. Remaining polish is tracked in `specs/001-codespace-xml-scaffold/tasks.md`.
diff --git a/docs/reference/commands.md b/docs/reference/commands.md
index 1fcc49f..044450f 100644
--- a/docs/reference/commands.md
+++ b/docs/reference/commands.md
@@ -13,7 +13,7 @@
| `make docs` | Build static HTML site (`mkdocs build --strict`) → `site/` | ✅ |
| `make docs-serve` | Live-preview docs at | ✅ |
| `make generate` | Regenerate models from `schema/*.xsd` via `xsdata` | ✅ |
-| `make gen-schema-docs` | Generate schema reference + Mermaid ERD from the enriched XSD | ✅ |
+| `make gen-schema-docs` | Generate the HTML schema reference from the XSD (via xs3p) | ✅ |
| `make pipeline` | End-to-end: map → serialise → validate → round-trip | ✅ |
| `make compare` | Migration-safety diff vs a known-good reference | ✅ |
| `make bundle` | Distribution bundle (data + schema + models) | ✅ |
diff --git a/docs/reference/index.md b/docs/reference/index.md
index 3051dd7..00fa2a7 100644
--- a/docs/reference/index.md
+++ b/docs/reference/index.md
@@ -5,7 +5,7 @@
| Page | What it gives you |
|---|---|
| [Commands](commands.md) | Every `make` target and CLI command, with exit-code meaning |
-| [Schema reference](schema/index.md) | Generated entity tables + Mermaid ERD, produced from the enriched XSD |
+| [Schema reference](schema/index.html) | Generated HTML reference (entities, types, facets, `xs:documentation`), produced from the XSD by xs3p |
## Authoritative specifications
@@ -19,7 +19,7 @@ the docs site, under version control):
## Generated reference
-`make gen-schema-docs` (re)generates the [schema reference](schema/index.md) from the enriched
-XSD — the entity tables, every field's `xs:documentation` prose, and the Mermaid ERD — produced
-*from the schema* so they cannot drift (ADR 0009).
+`make gen-schema-docs` (re)generates the [schema reference](schema/index.html) from the XSD via
+the vendored xs3p stylesheet — every element, type, facet and `xs:documentation` note — produced
+*from the schema* so it cannot drift (ADR 0011).
The committed page is regenerated by CI; a stale copy fails the drift gate.
diff --git a/docs/reference/schema/index.html b/docs/reference/schema/index.html
new file mode 100644
index 0000000..893d171
--- /dev/null
+++ b/docs/reference/schema/index.html
@@ -0,0 +1,6370 @@
+
+
+
+ XML Schema Documentation
+
+
+
+
+
+
+
A level expressed in decibels (dB), bounded to a sane sonar range.
+
+
+
+
+
+
+
+
+
+
+
+
+
Simple Type Metres
+
+
+
+
A non-negative distance in metres.
+
+
+
+
+
+
+
+
+
+
+
+
+
Simple Type Hertz
+
+
+
+
A non-negative frequency in hertz (Hz).
+
+
+
+
+
+
+
+
+
+
+
+
+
Simple Type Tonnes
+
+
+
+
A non-negative mass/displacement in tonnes (1000 kg).
+
+
+
+
+
+
+
+
+
+
+
+
+
Simple Type Seconds
+
+
+
+
A non-negative duration in seconds.
+
+
+
+
+
+
+
+
+
+
+
+
+
Simple Type Degrees
+
+
+
+
An angle in degrees, in the closed interval [0, 360] (e.g. a beamwidth).
+
+
+
+
+
+
+
+
+
+
+
+
+
Simple Type Bearing
+
+
+
+
A compass bearing in degrees, in the half-open interval [0, 360): 0 is dead
+ ahead, increasing clockwise.
+
+
+
+
+
+
+
+
+
+
+
+
+
Simple Type Year
+
+
+
+
A Gregorian calendar year, constrained to a sane modern range [1900, 2100].
+
+
+
+
+
+
+
+
+
+
+
+
+
Simple Type SignatureQuality
+
+
+
+
How a radiated-noise signature was obtained: measured at sea, modelled, or
+ estimated.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element SchemaVersion
+
+
+
+
Version of the schema this document targets.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element PlatformName
+
+
+
+
Human-readable name of the platform.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element GeneratedUtc
+
+
+
+
UTC timestamp identifying when the document was produced.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element Draft
+
+
+
+
Draft (depth of the lowest point below the waterline), in metres.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element Length
+
+
+
+
Overall length of the platform, in metres.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element Weight
+
+
+
+
Displacement (weight) of the platform, in tonnes.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element YearIntroduced
+
+
+
+
Calendar year the platform class entered service.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element BandIndex
+
+
+
+
1-based ordinal of the band within the signature.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element CentreFrequency
+
+
+
+
Centre frequency of the band, in hertz.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element Quality
+
+
+
+
How this signature was obtained.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element SectorBearing
+
+
+
+
Centre bearing of the sector, in degrees [0, 360).
+
+
+
+
+
+
+
+
+
+
+
+
+
Element SectorLevel
+
+
+
+
Radiated noise level in this sector, in decibels.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element ActiveName
+
+
+
+
Model name of the active sonar.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element ActiveManufacturer
+
+
+
+
Manufacturer of the active sonar.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element ActiveOperatingFrequency
+
+
+
+
Nominal operating (centre) frequency of the active sonar, in hertz.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element SourceLevel
+
+
+
+
Transmit source level, in decibels.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element Beamwidth
+
+
+
+
Transmit/receive beamwidth, in degrees.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element PulseLength
+
+
+
+
Transmit pulse length, in seconds.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element MaxRange
+
+
+
+
Maximum echo detection range, in metres.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element PassiveName
+
+
+
+
Model name of the passive sonar.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element PassiveManufacturer
+
+
+
+
Manufacturer of the passive sonar.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element PassiveOperatingFrequency
+
+
+
+
Nominal operating (centre) frequency of the passive sonar, in hertz.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element ArrayGain
+
+
+
+
Array gain of the receiving array, in decibels.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element DetectionThreshold
+
+
+
+
Signal-to-noise ratio required for detection, in decibels.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element BearingAccuracy
+
+
+
+
1-sigma bearing accuracy of the sonar, in degrees.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element Characteristics
+
+
+
+
The physical characteristics of the platform.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element Directional
+
+
+
+
The all-round radiated noise for one band: the directional sectors in ascending bearing order.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element Band
+
+
+
+
The directional radiated noise for one frequency band.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element RadiatedNoise
+
+
+
+
The platform's radiated-noise signature: one or more frequency bands in ascending index order.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element ActiveSonar
+
+
+
+
An active sonar: it transmits, so it carries a source level, beam/pulse figures, and a derived maximum (echo) detection range.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element PassiveSonar
+
+
+
+
A passive sonar: it only listens, so it carries array gain, a detection threshold and a bearing accuracy rather than a transmit source level.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element Sensors
+
+
+
+
The sonar fit carried by the platform: one active sonar and one or more passive sonars.
+
+
+
+
+
+
+
+
+
+
+
+
+
Element Platform
+
+
+
+
Root element: titled, timestamped reference data for one platform in three parts — physical characteristics, directional radiated noise, and the sonar fit.
+ Platform schema. A document describes one platform: its physical characteristics, its
+ directional radiated-noise signature across frequency bands, and the sonar sensors it
+ carries. The named value types below carry XSD facets (ranges) and one enumeration, and the
+ radiated-noise structure carries enforced cardinalities, so the validation gate has something
+ meaningful to enforce.
+
Root element: titled, timestamped reference data for one platform in three parts — physical characteristics, directional radiated noise, and the sonar fit.
Abstract(Applies to complex type definitions and element declarations). An abstract element or complex type cannot used to validate an element instance. If there is a reference to an abstract element, only element declarations that can substitute the abstract element can be used to validate the instance. For references to abstract type definitions, only derived types can be used.
Collapse Whitespace PolicyReplace tab, line feed, and carriage return characters with space character (Unicode character 32). Then, collapse contiguous sequences of space characters into single space character, and remove leading and trailing space characters.
+
Disallowed Substitutions(Applies to element declarations). If substitution is specified, then substitution group members cannot be used in place of the given element declaration to validate element instances. If derivation methods, e.g. extension, restriction, are specified, then the given element declaration will not validate element instances that have types derived from the element declaration's type using the specified derivation methods. Normally, element instances can override their declaration's type by specifying an xsi:type attribute.
Nillable(Applies to element declarations). If an element declaration is nillable, instances can use the xsi:nil attribute. The xsi:nil attribute is the boolean attribute, nil, from the http://www.w3.org/2001/XMLSchema-instance namespace. If an element instance has an xsi:nil attribute set to true, it can be left empty, even though its element declaration may have required content.
Prohibited Derivations(Applies to type definitions). Derivation methods that cannot be used to create sub-types from a given type definition.
+
Prohibited Substitutions(Applies to complex type definitions). Prevents sub-types that have been derived using the specified derivation methods from validating element instances in place of the given type definition.
+
Replace Whitespace PolicyReplace tab, line feed, and carriage return characters with space character (Unicode character 32).
Substitution GroupElements that are members of a substitution group can be used wherever the head element of the substitution group is referenced.
+
Substitution Group Exclusions(Applies to element declarations). Prohibits element declarations from nominating themselves as being able to substitute a given element declaration, if they have types that are derived from the original element's type using the specified derivation methods.
+
Target NamespaceThe target namespace identifies the namespace that components in this schema belongs to. If no target namespace is provided, then the schema components do not belong to any namespace.
Generated by xs3p-ch (fork of xs3p)
+ . Last Modified:
+
+
+
+
+
+
+
+
diff --git a/docs/reference/schema/index.md b/docs/reference/schema/index.md
deleted file mode 100644
index 450487e..0000000
--- a/docs/reference/schema/index.md
+++ /dev/null
@@ -1,286 +0,0 @@
-
-
-# Schema reference
-
-> **Reference (generated)** — produced from `schema/acoustic_dataset.xsd` (version `0.2.0`) by `make gen-schema-docs`. Every entity, field, range and definition below is read from the XSD's `xs:annotation/xs:documentation`, so this page cannot drift from the contract.
-
-Platform schema. A document describes one platform: its physical characteristics, its directional radiated-noise signature across ten frequency bands, and the sonar sensors it carries. The banded numeric types below carry XSD facets (ranges) and the radiated-noise structure carries enforced cardinalities (exactly ten bands, exactly twelve 30-degree sectors per band) so the validation gate has something meaningful to enforce.
-
-## Entity-relationship diagram
-
-```mermaid
-erDiagram
- DIRECTIONAL ||--|{ SECTOR : "Sector (12)"
- RADIATED_BAND ||--|| DIRECTIONAL : "Directional"
- RADIATED_NOISE ||--|{ RADIATED_BAND : "Band (10)"
- SENSOR_SUITE ||--|| ACTIVE_SONAR : "Active"
- SENSOR_SUITE ||--|{ PASSIVE_SONAR : "Passive (2)"
- PLATFORM_TYPE ||--|| PLATFORM_CHARACTERISTICS : "Characteristics"
- PLATFORM_TYPE ||--|| RADIATED_NOISE : "RadiatedNoise"
- PLATFORM_TYPE ||--|| SENSOR_SUITE : "Sensors"
- PLATFORM_CHARACTERISTICS {
- Metres Draft "Draft (depth of the lowest point below the waterline), in metres."
- Metres Length "Overall length of the platform, in metres."
- Tonnes Weight "Displacement (weight) of the platform, in tonnes."
- Year YearIntroduced "Calendar year the platform class entered service."
- }
- SECTOR {
- Bearing Bearing "Centre bearing of the sector, in degrees [0, 360)."
- Decibels Level "Radiated noise level in this sector, in decibels."
- }
- RADIATED_BAND {
- Hertz CentreFrequency "Centre frequency of the band, in hertz."
- positiveInteger index "1-based ordinal of the band within the signature."
- }
- ACTIVE_SONAR {
- string Name "Model name of the sonar."
- string Manufacturer "Manufacturer of the sonar."
- Hertz OperatingFrequency "Nominal operating (centre) frequency of the sonar, in hertz."
- Decibels SourceLevel "Transmit source level, in decibels."
- Degrees Beamwidth "Transmit/receive beamwidth, in degrees."
- Seconds PulseLength "Transmit pulse length, in seconds."
- Metres MaxRange "Maximum echo detection range, in metres."
- }
- PASSIVE_SONAR {
- string Name "Model name of the sonar."
- string Manufacturer "Manufacturer of the sonar."
- Hertz OperatingFrequency "Nominal operating (centre) frequency of the sonar, in hertz."
- Decibels ArrayGain "Array gain of the receiving array, in decibels."
- Decibels DetectionThreshold "Signal-to-noise ratio required for detection, in decibels."
- Degrees BearingAccuracy "1-sigma bearing accuracy of the sonar, in degrees."
- }
- PLATFORM_TYPE {
- string SchemaVersion "Version of the schema this document targets."
- string Name "Human-readable name of the platform."
- dateTime GeneratedUtc "UTC timestamp identifying when the document was produced."
- }
-```
-
-Legend: `||--||` one-to-one, `||--|{` one-to-(one-or-many), `||--o{` one-to-(zero-or-many). The number in each label is the exact cardinality the schema enforces. Sonar sub-types show their inherited fields inline.
-
-## Entities
-
-### PlatformCharacteristics
-
-The physical characteristics of the platform.
-
-| Field | Type | Cardinality | Definition |
-|---|---|---|---|
-| `Draft` | [`Metres`](#banded-numeric-types) | 1 | Draft (depth of the lowest point below the waterline), in metres. |
-| `Length` | [`Metres`](#banded-numeric-types) | 1 | Overall length of the platform, in metres. |
-| `Weight` | [`Tonnes`](#banded-numeric-types) | 1 | Displacement (weight) of the platform, in tonnes. |
-| `YearIntroduced` | [`Year`](#banded-numeric-types) | 1 | Calendar year the platform class entered service. |
-
-### Sector
-
-The radiated noise level in one 30-degree bearing sector of a band.
-
-| Field | Type | Cardinality | Definition |
-|---|---|---|---|
-| `Bearing` | [`Bearing`](#banded-numeric-types) | 1 | Centre bearing of the sector, in degrees [0, 360). |
-| `Level` | [`Decibels`](#banded-numeric-types) | 1 | Radiated noise level in this sector, in decibels. |
-
-### Directional
-
-The all-round radiated noise for one band: exactly twelve sectors at 30-degree intervals, in ascending bearing order (0, 30, ..., 330).
-
-| Field | Type | Cardinality | Definition |
-|---|---|---|---|
-| `Sector` | [`Sector`](#sector) | 12 | One 30-degree bearing sector. |
-
-### RadiatedBand
-
-The directional radiated noise for one frequency band.
-
-| Field | Type | Cardinality | Definition |
-|---|---|---|---|
-| `CentreFrequency` | [`Hertz`](#banded-numeric-types) | 1 | Centre frequency of the band, in hertz. |
-| `Directional` | [`Directional`](#directional) | 1 | The twelve 30-degree directional noise sectors for this band. |
-| `index` | `xs:positiveInteger` | 1 (attribute) | 1-based ordinal of the band within the signature. |
-
-### RadiatedNoise
-
-The platform's radiated-noise signature: exactly ten frequency bands, in ascending index order.
-
-| Field | Type | Cardinality | Definition |
-|---|---|---|---|
-| `Band` | [`RadiatedBand`](#radiatedband) | 10 | One frequency band of the radiated-noise signature. |
-
-### Sonar
-
-Fields common to every sonar: identity plus a nominal operating frequency.
-
-| Field | Type | Cardinality | Definition |
-|---|---|---|---|
-| `Name` | `xs:string` | 1 | Model name of the sonar. |
-| `Manufacturer` | `xs:string` | 1 | Manufacturer of the sonar. |
-| `OperatingFrequency` | [`Hertz`](#banded-numeric-types) | 1 | Nominal operating (centre) frequency of the sonar, in hertz. |
-
-### ActiveSonar
-
-An active sonar: it transmits, so it carries a source level and beam/pulse figures, and a derived maximum (echo) detection range.
-
-Extends [`Sonar`](#sonar); inherited fields are marked below.
-
-| Field | Type | Cardinality | Definition |
-|---|---|---|---|
-| `Name` *(from Sonar)* | `xs:string` | 1 | Model name of the sonar. |
-| `Manufacturer` *(from Sonar)* | `xs:string` | 1 | Manufacturer of the sonar. |
-| `OperatingFrequency` *(from Sonar)* | [`Hertz`](#banded-numeric-types) | 1 | Nominal operating (centre) frequency of the sonar, in hertz. |
-| `SourceLevel` | [`Decibels`](#banded-numeric-types) | 1 | Transmit source level, in decibels. |
-| `Beamwidth` | [`Degrees`](#banded-numeric-types) | 1 | Transmit/receive beamwidth, in degrees. |
-| `PulseLength` | [`Seconds`](#banded-numeric-types) | 1 | Transmit pulse length, in seconds. |
-| `MaxRange` | [`Metres`](#banded-numeric-types) | 1 | Maximum echo detection range, in metres. |
-
-### PassiveSonar
-
-A passive sonar: it only listens, so it carries array gain, a detection threshold and a bearing accuracy rather than a transmit source level.
-
-Extends [`Sonar`](#sonar); inherited fields are marked below.
-
-| Field | Type | Cardinality | Definition |
-|---|---|---|---|
-| `Name` *(from Sonar)* | `xs:string` | 1 | Model name of the sonar. |
-| `Manufacturer` *(from Sonar)* | `xs:string` | 1 | Manufacturer of the sonar. |
-| `OperatingFrequency` *(from Sonar)* | [`Hertz`](#banded-numeric-types) | 1 | Nominal operating (centre) frequency of the sonar, in hertz. |
-| `ArrayGain` | [`Decibels`](#banded-numeric-types) | 1 | Array gain of the receiving array, in decibels. |
-| `DetectionThreshold` | [`Decibels`](#banded-numeric-types) | 1 | Signal-to-noise ratio required for detection, in decibels. |
-| `BearingAccuracy` | [`Degrees`](#banded-numeric-types) | 1 | 1-sigma bearing accuracy of the sonar, in degrees. |
-
-### SensorSuite
-
-The sonar fit carried by the platform: one active sonar and two passive sonars.
-
-| Field | Type | Cardinality | Definition |
-|---|---|---|---|
-| `Active` | [`ActiveSonar`](#activesonar) | 1 | The platform's single active sonar. |
-| `Passive` | [`PassiveSonar`](#passivesonar) | 2 | The platform's two passive sonars. |
-
-### PlatformType (root element `Platform`)
-
-A single platform: titled, timestamped reference data in three parts — physical characteristics, directional radiated noise, and the sonar fit.
-
-| Field | Type | Cardinality | Definition |
-|---|---|---|---|
-| `SchemaVersion` | `xs:string` | 1 | Version of the schema this document targets. |
-| `Name` | `xs:string` | 1 | Human-readable name of the platform. |
-| `GeneratedUtc` | `xs:dateTime` | 1 | UTC timestamp identifying when the document was produced. |
-| `Characteristics` | [`PlatformCharacteristics`](#platformcharacteristics) | 1 | The platform's physical characteristics. |
-| `RadiatedNoise` | [`RadiatedNoise`](#radiatednoise) | 1 | The platform's directional radiated-noise signature. |
-| `Sensors` | [`SensorSuite`](#sensorsuite) | 1 | The platform's sonar fit. |
-
-## Banded numeric types
-
-The numeric primitives below carry real XSD range facets, so an out-of-band value fails the validation gate.
-
-| Type | Base | Range | Definition |
-|---|---|---|---|
-| `Decibels` | `xs:decimal` | ≥ -200, ≤ 300 | A level expressed in decibels (dB), bounded to a sane sonar range. |
-| `Metres` | `xs:decimal` | ≥ 0 | A non-negative distance in metres. |
-| `Hertz` | `xs:decimal` | ≥ 0 | A non-negative frequency in hertz (Hz). |
-| `Tonnes` | `xs:decimal` | ≥ 0 | A non-negative mass/displacement in tonnes (1000 kg). |
-| `Seconds` | `xs:decimal` | ≥ 0 | A non-negative duration in seconds. |
-| `Degrees` | `xs:decimal` | ≥ 0, ≤ 360 | An angle in degrees, in the closed interval [0, 360] (e.g. a beamwidth). |
-| `Bearing` | `xs:decimal` | ≥ 0, < 360 | A compass bearing in degrees, in the half-open interval [0, 360): 0 is dead ahead, increasing clockwise. Radiated-noise sectors are spaced 30 degrees apart. |
-| `Year` | `xs:integer` | ≥ 1900, ≤ 2100 | A Gregorian calendar year, constrained to a sane modern range [1900, 2100]. |
-
-## Example document
-
-A validated document produced by `make pipeline` from `examples/calculation_input.json` (most radiated-noise detail elided):
-
-```xml
-
- 0.2.0
- Reference Platform A
- 2026-06-14T00:00:00Z
-
- 7.500
- 95.000
- 2400.000
- 1998
-
-
-
- 50.000
-
-
- 0.000
- 134.000
-
-
- 30.000
- 134.804
-
-
-
-
-
-
-
-
- AS-900 Echo
- DeepBlue Sonics
- 6000.000
- 215.000
- 15.000
- 0.500
- 118850.223
-
-
- PA-110 Flank Array
- DeepBlue Sonics
- 1500.000
- 18.000
- 10.000
- 1.500
-
-
- PA-220 Towed Array
- Marine Acoustics Ltd
- 300.000
- 22.000
- 8.000
- 2.000
-
-
-
-```
-
-## Working with the typed objects
-
-The pipeline builds the calculation **directly** into the generated dataclasses; tests assert on those typed objects (the testable boundary) and they serialise straight to XML:
-
-```python
-from acoustic_dataset import build, serialize
-
-platform = build.build_platform_from_file( # a generated Platform object
- "examples/calculation_input.json"
-)
-
-platform.name # 'Reference Platform A'
-platform.characteristics.draft # Decimal('7.500')
-len(platform.radiated_noise.band) # 10
-platform.sensors.active.max_range # Decimal('118850.223')
-
-xml = serialize.to_xml(platform) # -> the validated document shown above
-```
-
-## Worked example: deriving a value from elementary physics
-
-Not every element is copied from the input — some are **computed** from typed inputs. The active sonar's maximum echo range is one: it falls out of the sonar equation under two-way spherical spreading.
-
-An echo travels out *and back*, so transmission loss is `TL = 40 * log10(r)` dB at range `r` metres. The platform can just detect the returning echo when its source level, less that loss, reaches the detection threshold — solve for `r`:
-
-```text
-SL - 40*log10(r) = DT => r = 10 ** ((SL - DT) / 40)
-```
-
-This platform's active sonar transmits at `SL = 215` dB with a detection threshold `DT = 12` dB, so:
-
-```python
-from acoustic_dataset.acoustics import active_max_range_m
-active_max_range_m(215, 12) # => 118850.223 (metres)
-```
-
-That value serialises into the document as `118850.223`.
-
diff --git a/examples/reference/trial_known_good.xml b/examples/reference/trial_known_good.xml
index 036b704..8414e91 100644
--- a/examples/reference/trial_known_good.xml
+++ b/examples/reference/trial_known_good.xml
@@ -1,572 +1,582 @@
-
-
- 0.2.0
- Reference Platform A
- 2026-06-14T00:00:00Z
-
- 7.500
- 95.000
- 2400.000
- 1998
-
-
-
- 50.000
-
-
- 0.000
- 134.000
-
-
- 30.000
- 134.804
-
-
- 60.000
- 137.000
-
-
- 90.000
- 140.000
-
-
- 120.000
- 143.000
-
-
- 150.000
- 145.196
-
-
- 180.000
- 146.000
-
-
- 210.000
- 145.196
-
-
- 240.000
- 143.000
-
-
- 270.000
- 140.000
-
-
- 300.000
- 137.000
-
-
- 330.000
- 134.804
-
-
-
-
- 100.000
-
-
- 0.000
- 129.000
-
-
- 30.000
- 129.804
-
-
- 60.000
- 132.000
-
-
- 90.000
- 135.000
-
-
- 120.000
- 138.000
-
-
- 150.000
- 140.196
-
-
- 180.000
- 141.000
-
-
- 210.000
- 140.196
-
-
- 240.000
- 138.000
-
-
- 270.000
- 135.000
-
-
- 300.000
- 132.000
-
-
- 330.000
- 129.804
-
-
-
-
- 200.000
-
-
- 0.000
- 124.000
-
-
- 30.000
- 124.804
-
-
- 60.000
- 127.000
-
-
- 90.000
- 130.000
-
-
- 120.000
- 133.000
-
-
- 150.000
- 135.196
-
-
- 180.000
- 136.000
-
-
- 210.000
- 135.196
-
-
- 240.000
- 133.000
-
-
- 270.000
- 130.000
-
-
- 300.000
- 127.000
-
-
- 330.000
- 124.804
-
-
-
-
- 400.000
-
-
- 0.000
- 119.000
-
-
- 30.000
- 119.804
-
-
- 60.000
- 122.000
-
-
- 90.000
- 125.000
-
-
- 120.000
- 128.000
-
-
- 150.000
- 130.196
-
-
- 180.000
- 131.000
-
-
- 210.000
- 130.196
-
-
- 240.000
- 128.000
-
-
- 270.000
- 125.000
-
-
- 300.000
- 122.000
-
-
- 330.000
- 119.804
-
-
-
-
- 800.000
-
-
- 0.000
- 114.000
-
-
- 30.000
- 114.804
-
-
- 60.000
- 117.000
-
-
- 90.000
- 120.000
-
-
- 120.000
- 123.000
-
-
- 150.000
- 125.196
-
-
- 180.000
- 126.000
-
-
- 210.000
- 125.196
-
-
- 240.000
- 123.000
-
-
- 270.000
- 120.000
-
-
- 300.000
- 117.000
-
-
- 330.000
- 114.804
-
-
-
-
- 1600.000
-
-
- 0.000
- 109.000
-
-
- 30.000
- 109.804
-
-
- 60.000
- 112.000
-
-
- 90.000
- 115.000
-
-
- 120.000
- 118.000
-
-
- 150.000
- 120.196
-
-
- 180.000
- 121.000
-
-
- 210.000
- 120.196
-
-
- 240.000
- 118.000
-
-
- 270.000
- 115.000
-
-
- 300.000
- 112.000
-
-
- 330.000
- 109.804
-
-
-
-
- 3200.000
-
-
- 0.000
- 104.000
-
-
- 30.000
- 104.804
-
-
- 60.000
- 107.000
-
-
- 90.000
- 110.000
-
-
- 120.000
- 113.000
-
-
- 150.000
- 115.196
-
-
- 180.000
- 116.000
-
-
- 210.000
- 115.196
-
-
- 240.000
- 113.000
-
-
- 270.000
- 110.000
-
-
- 300.000
- 107.000
-
-
- 330.000
- 104.804
-
-
-
-
- 6400.000
-
-
- 0.000
- 99.000
-
-
- 30.000
- 99.804
-
-
- 60.000
- 102.000
-
-
- 90.000
- 105.000
-
-
- 120.000
- 108.000
-
-
- 150.000
- 110.196
-
-
- 180.000
- 111.000
-
-
- 210.000
- 110.196
-
-
- 240.000
- 108.000
-
-
- 270.000
- 105.000
-
-
- 300.000
- 102.000
-
-
- 330.000
- 99.804
-
-
-
-
- 12800.000
-
-
- 0.000
- 94.000
-
-
- 30.000
- 94.804
-
-
- 60.000
- 97.000
-
-
- 90.000
- 100.000
-
-
- 120.000
- 103.000
-
-
- 150.000
- 105.196
-
-
- 180.000
- 106.000
-
-
- 210.000
- 105.196
-
-
- 240.000
- 103.000
-
-
- 270.000
- 100.000
-
-
- 300.000
- 97.000
-
-
- 330.000
- 94.804
-
-
-
-
- 25600.000
-
-
- 0.000
- 89.000
-
-
- 30.000
- 89.804
-
-
- 60.000
- 92.000
-
-
- 90.000
- 95.000
-
-
- 120.000
- 98.000
-
-
- 150.000
- 100.196
-
-
- 180.000
- 101.000
-
-
- 210.000
- 100.196
-
-
- 240.000
- 98.000
-
-
- 270.000
- 95.000
-
-
- 300.000
- 92.000
-
-
- 330.000
- 89.804
-
-
-
-
-
-
- AS-900 Echo
- DeepBlue Sonics
- 6000.000
- 215.000
- 15.000
- 0.500
- 118850.223
-
-
- PA-110 Flank Array
- DeepBlue Sonics
- 1500.000
- 18.000
- 10.000
- 1.500
-
-
- PA-220 Towed Array
- Marine Acoustics Ltd
- 300.000
- 22.000
- 8.000
- 2.000
-
-
-
+
+
+ 0.2.0
+ Reference Platform A
+ 2026-06-14T00:00:00Z
+
+ 7.500
+ 95.000
+ 2400.000
+ 1998
+
+
+
+ 1
+ 50.000
+
+
+ 0.000
+ 134.000
+
+
+ 30.000
+ 134.804
+
+
+ 60.000
+ 137.000
+
+
+ 90.000
+ 140.000
+
+
+ 120.000
+ 143.000
+
+
+ 150.000
+ 145.196
+
+
+ 180.000
+ 146.000
+
+
+ 210.000
+ 145.196
+
+
+ 240.000
+ 143.000
+
+
+ 270.000
+ 140.000
+
+
+ 300.000
+ 137.000
+
+
+ 330.000
+ 134.804
+
+
+
+
+ 2
+ 100.000
+
+
+ 0.000
+ 129.000
+
+
+ 30.000
+ 129.804
+
+
+ 60.000
+ 132.000
+
+
+ 90.000
+ 135.000
+
+
+ 120.000
+ 138.000
+
+
+ 150.000
+ 140.196
+
+
+ 180.000
+ 141.000
+
+
+ 210.000
+ 140.196
+
+
+ 240.000
+ 138.000
+
+
+ 270.000
+ 135.000
+
+
+ 300.000
+ 132.000
+
+
+ 330.000
+ 129.804
+
+
+
+
+ 3
+ 200.000
+
+
+ 0.000
+ 124.000
+
+
+ 30.000
+ 124.804
+
+
+ 60.000
+ 127.000
+
+
+ 90.000
+ 130.000
+
+
+ 120.000
+ 133.000
+
+
+ 150.000
+ 135.196
+
+
+ 180.000
+ 136.000
+
+
+ 210.000
+ 135.196
+
+
+ 240.000
+ 133.000
+
+
+ 270.000
+ 130.000
+
+
+ 300.000
+ 127.000
+
+
+ 330.000
+ 124.804
+
+
+
+
+ 4
+ 400.000
+
+
+ 0.000
+ 119.000
+
+
+ 30.000
+ 119.804
+
+
+ 60.000
+ 122.000
+
+
+ 90.000
+ 125.000
+
+
+ 120.000
+ 128.000
+
+
+ 150.000
+ 130.196
+
+
+ 180.000
+ 131.000
+
+
+ 210.000
+ 130.196
+
+
+ 240.000
+ 128.000
+
+
+ 270.000
+ 125.000
+
+
+ 300.000
+ 122.000
+
+
+ 330.000
+ 119.804
+
+
+
+
+ 5
+ 800.000
+
+
+ 0.000
+ 114.000
+
+
+ 30.000
+ 114.804
+
+
+ 60.000
+ 117.000
+
+
+ 90.000
+ 120.000
+
+
+ 120.000
+ 123.000
+
+
+ 150.000
+ 125.196
+
+
+ 180.000
+ 126.000
+
+
+ 210.000
+ 125.196
+
+
+ 240.000
+ 123.000
+
+
+ 270.000
+ 120.000
+
+
+ 300.000
+ 117.000
+
+
+ 330.000
+ 114.804
+
+
+
+
+ 6
+ 1600.000
+
+
+ 0.000
+ 109.000
+
+
+ 30.000
+ 109.804
+
+
+ 60.000
+ 112.000
+
+
+ 90.000
+ 115.000
+
+
+ 120.000
+ 118.000
+
+
+ 150.000
+ 120.196
+
+
+ 180.000
+ 121.000
+
+
+ 210.000
+ 120.196
+
+
+ 240.000
+ 118.000
+
+
+ 270.000
+ 115.000
+
+
+ 300.000
+ 112.000
+
+
+ 330.000
+ 109.804
+
+
+
+
+ 7
+ 3200.000
+
+
+ 0.000
+ 104.000
+
+
+ 30.000
+ 104.804
+
+
+ 60.000
+ 107.000
+
+
+ 90.000
+ 110.000
+
+
+ 120.000
+ 113.000
+
+
+ 150.000
+ 115.196
+
+
+ 180.000
+ 116.000
+
+
+ 210.000
+ 115.196
+
+
+ 240.000
+ 113.000
+
+
+ 270.000
+ 110.000
+
+
+ 300.000
+ 107.000
+
+
+ 330.000
+ 104.804
+
+
+
+
+ 8
+ 6400.000
+
+
+ 0.000
+ 99.000
+
+
+ 30.000
+ 99.804
+
+
+ 60.000
+ 102.000
+
+
+ 90.000
+ 105.000
+
+
+ 120.000
+ 108.000
+
+
+ 150.000
+ 110.196
+
+
+ 180.000
+ 111.000
+
+
+ 210.000
+ 110.196
+
+
+ 240.000
+ 108.000
+
+
+ 270.000
+ 105.000
+
+
+ 300.000
+ 102.000
+
+
+ 330.000
+ 99.804
+
+
+
+
+ 9
+ 12800.000
+
+
+ 0.000
+ 94.000
+
+
+ 30.000
+ 94.804
+
+
+ 60.000
+ 97.000
+
+
+ 90.000
+ 100.000
+
+
+ 120.000
+ 103.000
+
+
+ 150.000
+ 105.196
+
+
+ 180.000
+ 106.000
+
+
+ 210.000
+ 105.196
+
+
+ 240.000
+ 103.000
+
+
+ 270.000
+ 100.000
+
+
+ 300.000
+ 97.000
+
+
+ 330.000
+ 94.804
+
+
+
+
+ 10
+ 25600.000
+
+
+ 0.000
+ 89.000
+
+
+ 30.000
+ 89.804
+
+
+ 60.000
+ 92.000
+
+
+ 90.000
+ 95.000
+
+
+ 120.000
+ 98.000
+
+
+ 150.000
+ 100.196
+
+
+ 180.000
+ 101.000
+
+
+ 210.000
+ 100.196
+
+
+ 240.000
+ 98.000
+
+
+ 270.000
+ 95.000
+
+
+ 300.000
+ 92.000
+
+
+ 330.000
+ 89.804
+
+
+
+
+
+
+ AS-900 Echo
+ DeepBlue Sonics
+ 6000.000
+ 215.000
+ 15.000
+ 0.500
+ 118850.223
+
+
+ PA-110 Flank Array
+ DeepBlue Sonics
+ 1500.000
+ 18.000
+ 10.000
+ 1.500
+
+
+ PA-220 Towed Array
+ Marine Acoustics Ltd
+ 300.000
+ 22.000
+ 8.000
+ 2.000
+
+
+
diff --git a/mkdocs.yml b/mkdocs.yml
index e4ebe11..33aae40 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -65,5 +65,5 @@ nav:
- Reference:
- Overview: reference/index.md
- Commands: reference/commands.md
- - Schema reference (generated): reference/schema/index.md
+ - Schema reference (generated): reference/schema/index.html
- Glossary: glossary.md
diff --git a/schema/acoustic_dataset.xsd b/schema/acoustic_dataset.xsd
index 0383fff..227864f 100644
--- a/schema/acoustic_dataset.xsd
+++ b/schema/acoustic_dataset.xsd
@@ -2,40 +2,38 @@
+ vc:minVersion="1.1">
Platform schema. A document describes one platform: its physical characteristics, its
- directional radiated-noise signature across ten frequency bands, and the sonar sensors it
- carries. The banded numeric types below carry XSD facets (ranges) and the radiated-noise
- structure carries enforced cardinalities (exactly ten bands, exactly twelve 30-degree
- sectors per band) so the validation gate has something meaningful to enforce.
+ directional radiated-noise signature across frequency bands, and the sonar sensors it
+ carries. The named value types below carry XSD facets (ranges) and one enumeration, and the
+ radiated-noise structure carries enforced cardinalities, so the validation gate has something
+ meaningful to enforce.
-
+
@@ -95,8 +93,8 @@
- A compass bearing in degrees, in the half-open interval [0, 360):
- 0 is dead ahead, increasing clockwise. Radiated-noise sectors are spaced 30 degrees apart.
+ A compass bearing in degrees, in the half-open interval [0, 360): 0 is dead
+ ahead, increasing clockwise.
@@ -114,256 +112,225 @@
-
-
-
+
- The physical characteristics of the platform.
+ How a radiated-noise signature was obtained: measured at sea, modelled, or
+ estimated.
-
-
-
- Draft (depth of the lowest point below the waterline), in metres.
-
-
-
-
- Overall length of the platform, in metres.
-
-
-
-
- Displacement (weight) of the platform, in tonnes.
-
-
-
-
- Calendar year the platform class entered service.
-
-
-
-
+
+
+
+
+
+
-
+
-
-
- The radiated noise level in one 30-degree bearing sector of a band.
-
-
-
-
- Centre bearing of the sector, in degrees [0, 360).
-
-
-
-
- Radiated noise level in this sector, in decibels.
-
-
-
-
+
+ Version of the schema this document targets.
+
+
+ Human-readable name of the platform.
+
+
+ UTC timestamp identifying when the document was produced.
+
-
-
- The all-round radiated noise for one band: exactly twelve sectors at
- 30-degree intervals, in ascending bearing order (0, 30, ..., 330).
-
-
-
-
- One 30-degree bearing sector.
-
-
-
-
+
+ Draft (depth of the lowest point below the waterline), in metres.
+
+
+ Overall length of the platform, in metres.
+
+
+ Displacement (weight) of the platform, in tonnes.
+
+
+ Calendar year the platform class entered service.
+
-
-
- The directional radiated noise for one frequency band.
-
-
-
-
- Centre frequency of the band, in hertz.
-
-
-
-
- The twelve 30-degree directional noise sectors for this band.
-
-
-
-
-
- 1-based ordinal of the band within the signature.
-
-
-
+
+ 1-based ordinal of the band within the signature.
+
+
+ Centre frequency of the band, in hertz.
+
+
+ How this signature was obtained.
+
+
+ Centre bearing of the sector, in degrees [0, 360).
+
+
+ Radiated noise level in this sector, in decibels.
+
-
-
- The platform's radiated-noise signature: exactly ten frequency bands,
- in ascending index order.
-
-
-
-
- One frequency band of the radiated-noise signature.
-
-
-
-
+
+ Model name of the active sonar.
+
+
+ Manufacturer of the active sonar.
+
+
+ Nominal operating (centre) frequency of the active sonar, in hertz.
+
+
+ Transmit source level, in decibels.
+
+
+ Transmit/receive beamwidth, in degrees.
+
+
+ Transmit pulse length, in seconds.
+
+
+ Maximum echo detection range, in metres.
+
-
+
+ Model name of the passive sonar.
+
+
+ Manufacturer of the passive sonar.
+
+
+ Nominal operating (centre) frequency of the passive sonar, in hertz.
+
+
+ Array gain of the receiving array, in decibels.
+
+
+ Signal-to-noise ratio required for detection, in decibels.
+
+
+ 1-sigma bearing accuracy of the sonar, in degrees.
+
-
-
- Fields common to every sonar: identity plus a nominal operating frequency.
-
-
-
-
- Model name of the sonar.
-
-
-
-
- Manufacturer of the sonar.
-
-
-
-
- Nominal operating (centre) frequency of the sonar, in hertz.
-
-
-
-
+
-
-
- An active sonar: it transmits, so it carries a source level and beam/pulse
- figures, and a derived maximum (echo) detection range.
-
-
-
-
-
-
- Transmit source level, in decibels.
-
-
-
-
- Transmit/receive beamwidth, in degrees.
-
-
-
-
- Transmit pulse length, in seconds.
-
-
-
-
- Maximum echo detection range, in metres.
-
-
-
-
-
-
+
+ The physical characteristics of the platform.
+
+
+
+
+
+
+
+
+
-
-
- A passive sonar: it only listens, so it carries array gain, a detection
- threshold and a bearing accuracy rather than a transmit source level.
-
-
-
-
-
-
- Array gain of the receiving array, in decibels.
-
-
-
-
- Signal-to-noise ratio required for detection, in decibels.
-
-
-
-
- 1-sigma bearing accuracy of the sonar, in degrees.
-
-
-
-
-
+
+
+ The radiated noise level in one directional bearing sector of a band.
+
+
+
+
+
-
-
- The sonar fit carried by the platform: one active sonar and two passive sonars.
-
-
-
-
- The platform's single active sonar.
-
-
-
-
- The platform's two passive sonars.
-
-
-
-
+
+ The all-round radiated noise for one band: the directional sectors in ascending bearing order.
+
+
+
+ One directional bearing sector.
+
+
+
+
-
+
+ The directional radiated noise for one frequency band.
+
+
+
+
+
+ The directional noise sectors for this band.
+
+
+
+
-
-
- A single platform: titled, timestamped reference data in three parts —
- physical characteristics, directional radiated noise, and the sonar fit.
-
-
-
-
- Version of the schema this document targets.
-
-
-
-
- Human-readable name of the platform.
-
-
-
-
- UTC timestamp identifying when the document was produced.
-
-
-
-
- The platform's physical characteristics.
-
-
-
-
- The platform's directional radiated-noise signature.
-
-
-
-
- The platform's sonar fit.
-
-
-
-
+
+ The platform's radiated-noise signature: one or more frequency bands in ascending index order.
+
+
+
+ Optional provenance of the signature.
+
+
+ One frequency band of the radiated-noise signature.
+
+
+
+
-
-
- Root element: the reference data for one platform.
-
+
+ An active sonar: it transmits, so it carries a source level, beam/pulse figures, and a derived maximum (echo) detection range.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A passive sonar: it only listens, so it carries array gain, a detection threshold and a bearing accuracy rather than a transmit source level.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The sonar fit carried by the platform: one active sonar and one or more passive sonars.
+
+
+
+ The platform's single active sonar.
+
+
+ The platform's passive sonars.
+
+
+
+
+
+
+
+
+ Root element: titled, timestamped reference data for one platform in three parts — physical characteristics, directional radiated noise, and the sonar fit.
+
+
+
+
+
+
+ The platform's physical characteristics.
+
+
+ The platform's directional radiated-noise signature.
+
+
+ The platform's sonar fit.
+
+
+
diff --git a/src/acoustic_dataset/build.py b/src/acoustic_dataset/build.py
index 3719aa0..f73ed1f 100644
--- a/src/acoustic_dataset/build.py
+++ b/src/acoustic_dataset/build.py
@@ -2,8 +2,12 @@
This is the **one builder** that produces schema-typed objects. It computes the dataset's
values with the pure seam functions in :mod:`acoustic_dataset.acoustics` and populates the
-generated ``Platform`` / ``RadiatedBand`` / ``Sector`` / ... classes directly — there is no
-intermediate domain hierarchy that is built only to be converted.
+generated ``Platform`` / ``Band`` / ``Sector`` / ... classes directly — there is no intermediate
+domain hierarchy that is built only to be converted.
+
+The schema is in the "salami-slice" idiom (every element global; complex types hold
+``xs:element ref=...``), so xsdata emits a wrapper dataclass per element. Each scalar is therefore
+constructed as ``ElementClass(value=...)`` rather than assigned bare.
It enforces, at the point of construction, that every value **meets the schema**:
@@ -24,15 +28,39 @@
from acoustic_dataset import acoustics
from acoustic_dataset.models.acoustic_dataset import (
+ ActiveManufacturer,
+ ActiveName,
+ ActiveOperatingFrequency,
ActiveSonar,
+ ArrayGain,
+ Band,
+ BandIndex,
+ Beamwidth,
+ BearingAccuracy,
+ CentreFrequency,
+ Characteristics,
+ DetectionThreshold,
Directional,
+ Draft,
+ GeneratedUtc,
+ Length,
+ MaxRange,
+ PassiveManufacturer,
+ PassiveName,
+ PassiveOperatingFrequency,
PassiveSonar,
Platform,
- PlatformCharacteristics,
- RadiatedBand,
+ PlatformName,
+ PulseLength,
RadiatedNoise,
+ SchemaVersion,
Sector,
- SensorSuite,
+ SectorBearing,
+ SectorLevel,
+ Sensors,
+ SourceLevel,
+ Weight,
+ YearIntroduced,
)
# Declared schema bands (kept in step with schema/acoustic_dataset.xsd). These mirror the
@@ -84,20 +112,28 @@ def _require_int_in_range(value: int, low: int, high: int, *, where: str, field:
return value
-def _build_characteristics(spec: dict) -> PlatformCharacteristics:
+def _build_characteristics(spec: dict) -> Characteristics:
where = "characteristics"
- return PlatformCharacteristics(
- draft=_require_min(
- _dec(float(spec["draftMetres"])), _NON_NEGATIVE, where=where, field="Draft"
+ return Characteristics(
+ draft=Draft(
+ _require_min(
+ _dec(float(spec["draftMetres"])), _NON_NEGATIVE, where=where, field="Draft"
+ )
),
- length=_require_min(
- _dec(float(spec["lengthMetres"])), _NON_NEGATIVE, where=where, field="Length"
+ length=Length(
+ _require_min(
+ _dec(float(spec["lengthMetres"])), _NON_NEGATIVE, where=where, field="Length"
+ )
),
- weight=_require_min(
- _dec(float(spec["weightTonnes"])), _NON_NEGATIVE, where=where, field="Weight"
+ weight=Weight(
+ _require_min(
+ _dec(float(spec["weightTonnes"])), _NON_NEGATIVE, where=where, field="Weight"
+ )
),
- year_introduced=_require_int_in_range(
- int(spec["yearIntroduced"]), *_YEAR_RANGE, where=where, field="YearIntroduced"
+ year_introduced=YearIntroduced(
+ _require_int_in_range(
+ int(spec["yearIntroduced"]), *_YEAR_RANGE, where=where, field="YearIntroduced"
+ )
),
)
@@ -105,12 +141,16 @@ def _build_characteristics(spec: dict) -> PlatformCharacteristics:
def _build_sector(band_index: int, bearing_deg: float, level_db: float) -> Sector:
where = f"band {band_index} bearing {bearing_deg:g}"
return Sector(
- bearing=_require_below(_dec(bearing_deg), *_BEARING_RANGE, where=where, field="Bearing"),
- level=_require_in_range(_dec(level_db), *_DECIBELS_RANGE, where=where, field="Level"),
+ sector_bearing=SectorBearing(
+ _require_below(_dec(bearing_deg), *_BEARING_RANGE, where=where, field="Bearing")
+ ),
+ sector_level=SectorLevel(
+ _require_in_range(_dec(level_db), *_DECIBELS_RANGE, where=where, field="Level")
+ ),
)
-def _build_bands(spec: dict) -> list[RadiatedBand]:
+def _build_bands(spec: dict) -> list[Band]:
"""Synthesise the directional radiated-noise bands and build them into schema objects."""
base_hz = float(spec["baseFrequencyHz"])
ratio = float(spec["bandRatio"])
@@ -121,7 +161,7 @@ def _build_bands(spec: dict) -> list[RadiatedBand]:
amplitude = float(spec["directivity"]["amplitudeDb"])
sampled_bearings = acoustics.bearings(float(spec["bearingStepDeg"]))
- bands: list[RadiatedBand] = []
+ bands: list[Band] = []
for index in range(1, band_count + 1):
centre = acoustics.band_centre_hz(base_hz, ratio, index)
rolloff_db = acoustics.spectral_rolloff_db(centre, base_hz, rolloff)
@@ -138,12 +178,14 @@ def _build_bands(spec: dict) -> list[RadiatedBand]:
for bearing in sampled_bearings
]
bands.append(
- RadiatedBand(
- centre_frequency=_require_min(
- _dec(centre), _NON_NEGATIVE, where=f"band {index}", field="CentreFrequency"
+ Band(
+ band_index=BandIndex(index),
+ centre_frequency=CentreFrequency(
+ _require_min(
+ _dec(centre), _NON_NEGATIVE, where=f"band {index}", field="CentreFrequency"
+ )
),
directional=Directional(sector=sectors),
- index=index,
)
)
return bands
@@ -154,55 +196,73 @@ def _build_active(spec: dict) -> ActiveSonar:
source_level = float(spec["sourceLevelDb"])
max_range = acoustics.active_max_range_m(source_level, float(spec["detectionThresholdDb"]))
return ActiveSonar(
- name=str(spec["name"]),
- manufacturer=str(spec["manufacturer"]),
- operating_frequency=_require_min(
- _dec(float(spec["operatingFrequencyHz"])),
- _NON_NEGATIVE,
- where=where,
- field="OperatingFrequency",
- ),
- source_level=_require_in_range(
- _dec(source_level), *_DECIBELS_RANGE, where=where, field="SourceLevel"
- ),
- beamwidth=_require_in_range(
- _dec(float(spec["beamwidthDeg"])), *_DEGREES_RANGE, where=where, field="Beamwidth"
- ),
- pulse_length=_require_min(
- _dec(float(spec["pulseLengthSeconds"])),
- _NON_NEGATIVE,
- where=where,
- field="PulseLength",
- ),
- max_range=_require_min(_dec(max_range), _NON_NEGATIVE, where=where, field="MaxRange"),
+ active_name=ActiveName(str(spec["name"])),
+ active_manufacturer=ActiveManufacturer(str(spec["manufacturer"])),
+ active_operating_frequency=ActiveOperatingFrequency(
+ _require_min(
+ _dec(float(spec["operatingFrequencyHz"])),
+ _NON_NEGATIVE,
+ where=where,
+ field="OperatingFrequency",
+ )
+ ),
+ source_level=SourceLevel(
+ _require_in_range(
+ _dec(source_level), *_DECIBELS_RANGE, where=where, field="SourceLevel"
+ )
+ ),
+ beamwidth=Beamwidth(
+ _require_in_range(
+ _dec(float(spec["beamwidthDeg"])), *_DEGREES_RANGE, where=where, field="Beamwidth"
+ )
+ ),
+ pulse_length=PulseLength(
+ _require_min(
+ _dec(float(spec["pulseLengthSeconds"])),
+ _NON_NEGATIVE,
+ where=where,
+ field="PulseLength",
+ )
+ ),
+ max_range=MaxRange(
+ _require_min(_dec(max_range), _NON_NEGATIVE, where=where, field="MaxRange")
+ ),
)
def _build_passive(ordinal: int, spec: dict) -> PassiveSonar:
where = f"passive sonar {ordinal}"
return PassiveSonar(
- name=str(spec["name"]),
- manufacturer=str(spec["manufacturer"]),
- operating_frequency=_require_min(
- _dec(float(spec["operatingFrequencyHz"])),
- _NON_NEGATIVE,
- where=where,
- field="OperatingFrequency",
- ),
- array_gain=_require_in_range(
- _dec(float(spec["arrayGainDb"])), *_DECIBELS_RANGE, where=where, field="ArrayGain"
- ),
- detection_threshold=_require_in_range(
- _dec(float(spec["detectionThresholdDb"])),
- *_DECIBELS_RANGE,
- where=where,
- field="DetectionThreshold",
- ),
- bearing_accuracy=_require_in_range(
- _dec(float(spec["bearingAccuracyDeg"])),
- *_DEGREES_RANGE,
- where=where,
- field="BearingAccuracy",
+ passive_name=PassiveName(str(spec["name"])),
+ passive_manufacturer=PassiveManufacturer(str(spec["manufacturer"])),
+ passive_operating_frequency=PassiveOperatingFrequency(
+ _require_min(
+ _dec(float(spec["operatingFrequencyHz"])),
+ _NON_NEGATIVE,
+ where=where,
+ field="OperatingFrequency",
+ )
+ ),
+ array_gain=ArrayGain(
+ _require_in_range(
+ _dec(float(spec["arrayGainDb"])), *_DECIBELS_RANGE, where=where, field="ArrayGain"
+ )
+ ),
+ detection_threshold=DetectionThreshold(
+ _require_in_range(
+ _dec(float(spec["detectionThresholdDb"])),
+ *_DECIBELS_RANGE,
+ where=where,
+ field="DetectionThreshold",
+ )
+ ),
+ bearing_accuracy=BearingAccuracy(
+ _require_in_range(
+ _dec(float(spec["bearingAccuracyDeg"])),
+ *_DEGREES_RANGE,
+ where=where,
+ field="BearingAccuracy",
+ )
),
)
@@ -218,14 +278,14 @@ def build_platform(data: dict) -> Platform:
if not bands:
raise MappingError("calculation produced no bands; nothing to build")
return Platform(
- schema_version=str(data.get("schemaVersion", "0.2.0")),
- name=str(data["name"]),
- generated_utc=XmlDateTime.from_string(str(data["generatedUtc"])),
+ schema_version=SchemaVersion(str(data.get("schemaVersion", "0.2.0"))),
+ platform_name=PlatformName(str(data["name"])),
+ generated_utc=GeneratedUtc(XmlDateTime.from_string(str(data["generatedUtc"]))),
characteristics=_build_characteristics(data["characteristics"]),
radiated_noise=RadiatedNoise(band=bands),
- sensors=SensorSuite(
- active=_build_active(data["sensors"]["active"]),
- passive=[
+ sensors=Sensors(
+ active_sonar=_build_active(data["sensors"]["active"]),
+ passive_sonar=[
_build_passive(i, p) for i, p in enumerate(data["sensors"]["passive"], start=1)
],
),
diff --git a/src/acoustic_dataset/cli.py b/src/acoustic_dataset/cli.py
index 703c432..9b5e339 100644
--- a/src/acoustic_dataset/cli.py
+++ b/src/acoustic_dataset/cli.py
@@ -75,10 +75,10 @@ def cmd_validate(args: argparse.Namespace) -> int:
def cmd_gen_schema_docs(args: argparse.Namespace) -> int:
- """Generate the schema reference + Mermaid ERD (and worked examples) from the schema."""
- from acoustic_dataset import schema_docs
+ """Generate the schema reference as HTML from the XSD (via the vendored xs3p stylesheet)."""
+ from acoustic_dataset import schema_html
- out_file = schema_docs.generate(args.schema, args.out, example_input=args.input)
+ out_file = schema_html.generate(args.schema, args.out)
print(f"schema docs ok: generated {out_file} from {args.schema}")
return 0
@@ -153,11 +153,12 @@ def build_parser() -> argparse.ArgumentParser:
p_val.set_defaults(func=cmd_validate)
p_doc = sub.add_parser(
- "gen-schema-docs", help="Generate schema reference + Mermaid ERD from the XSD (US5)."
+ "gen-schema-docs", help="Generate the schema reference as HTML from the XSD (xs3p)."
)
p_doc.add_argument("--schema", type=Path, default=DEFAULT_SCHEMA)
- p_doc.add_argument("--input", type=Path, default=DEFAULT_INPUT)
- p_doc.add_argument("--out", type=Path, default=_REPO_ROOT / "docs" / "reference" / "schema")
+ p_doc.add_argument(
+ "--out", type=Path, default=_REPO_ROOT / "docs" / "reference" / "schema" / "index.html"
+ )
p_doc.set_defaults(func=cmd_gen_schema_docs)
p_cmp = sub.add_parser("compare", help="Migration-safety diff vs a reference (US3).")
diff --git a/src/acoustic_dataset/models/__init__.py b/src/acoustic_dataset/models/__init__.py
index 5db6c16..ee404e4 100644
--- a/src/acoustic_dataset/models/__init__.py
+++ b/src/acoustic_dataset/models/__init__.py
@@ -2,29 +2,79 @@
# Generated from schema/acoustic_dataset.xsd by `make generate` (xsdata).
# Regenerate after any schema change; CI fails on drift. See docs/decisions/0008.
from acoustic_dataset.models.acoustic_dataset import (
+ ActiveManufacturer,
+ ActiveName,
+ ActiveOperatingFrequency,
ActiveSonar,
+ ArrayGain,
+ Band,
+ BandIndex,
+ Beamwidth,
+ BearingAccuracy,
+ CentreFrequency,
+ Characteristics,
+ DetectionThreshold,
Directional,
+ Draft,
+ GeneratedUtc,
+ Length,
+ MaxRange,
+ PassiveManufacturer,
+ PassiveName,
+ PassiveOperatingFrequency,
PassiveSonar,
Platform,
- PlatformCharacteristics,
- PlatformType,
- RadiatedBand,
+ PlatformName,
+ PulseLength,
+ Quality,
RadiatedNoise,
+ SchemaVersion,
Sector,
- SensorSuite,
- Sonar,
+ SectorBearing,
+ SectorLevel,
+ SectorType,
+ Sensors,
+ SignatureQuality,
+ SourceLevel,
+ Weight,
+ YearIntroduced,
)
__all__ = [
+ "ActiveManufacturer",
+ "ActiveName",
+ "ActiveOperatingFrequency",
"ActiveSonar",
+ "ArrayGain",
+ "Band",
+ "BandIndex",
+ "Beamwidth",
+ "BearingAccuracy",
+ "CentreFrequency",
+ "Characteristics",
+ "DetectionThreshold",
"Directional",
+ "Draft",
+ "GeneratedUtc",
+ "Length",
+ "MaxRange",
+ "PassiveManufacturer",
+ "PassiveName",
+ "PassiveOperatingFrequency",
"PassiveSonar",
"Platform",
- "PlatformCharacteristics",
- "PlatformType",
- "RadiatedBand",
+ "PlatformName",
+ "PulseLength",
+ "Quality",
"RadiatedNoise",
+ "SchemaVersion",
"Sector",
- "SensorSuite",
- "Sonar",
+ "SectorBearing",
+ "SectorLevel",
+ "SectorType",
+ "Sensors",
+ "SignatureQuality",
+ "SourceLevel",
+ "Weight",
+ "YearIntroduced",
]
diff --git a/src/acoustic_dataset/models/acoustic_dataset.py b/src/acoustic_dataset/models/acoustic_dataset.py
index cdda384..b0f7726 100644
--- a/src/acoustic_dataset/models/acoustic_dataset.py
+++ b/src/acoustic_dataset/models/acoustic_dataset.py
@@ -3,94 +3,140 @@
# Regenerate after any schema change; CI fails on drift. See docs/decisions/0008.
from dataclasses import dataclass, field
from decimal import Decimal
+from enum import Enum
from typing import Optional
from xsdata.models.datatype import XmlDateTime
-__NAMESPACE__ = "https://deepblue.example/acoustic-dataset/v0"
+
+@dataclass
+class ActiveManufacturer:
+ """
+ Manufacturer of the active sonar.
+ """
+
+ value: str = field(
+ default="",
+ metadata={
+ "required": True,
+ },
+ )
@dataclass
-class PlatformCharacteristics:
+class ActiveName:
"""
- The physical characteristics of the platform.
+ Model name of the active sonar.
+ """
+
+ value: str = field(
+ default="",
+ metadata={
+ "required": True,
+ },
+ )
+
- :ivar draft: Draft (depth of the lowest point below the waterline),
- in metres.
- :ivar length: Overall length of the platform, in metres.
- :ivar weight: Displacement (weight) of the platform, in tonnes.
- :ivar year_introduced: Calendar year the platform class entered
- service.
+@dataclass
+class ActiveOperatingFrequency:
+ """
+ Nominal operating (centre) frequency of the active sonar, in hertz.
"""
- draft: Optional[Decimal] = field(
+ value: Optional[Decimal] = field(
default=None,
metadata={
- "name": "Draft",
- "type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
"min_inclusive": Decimal("0"),
},
)
- length: Optional[Decimal] = field(
+
+
+@dataclass
+class ArrayGain:
+ """
+ Array gain of the receiving array, in decibels.
+ """
+
+ value: Optional[Decimal] = field(
default=None,
metadata={
- "name": "Length",
- "type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
- "min_inclusive": Decimal("0"),
+ "min_inclusive": Decimal("-200"),
+ "max_inclusive": Decimal("300"),
},
)
- weight: Optional[Decimal] = field(
+
+
+@dataclass
+class BandIndex:
+ """
+ 1-based ordinal of the band within the signature.
+ """
+
+ value: Optional[int] = field(
default=None,
metadata={
- "name": "Weight",
- "type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
- "min_inclusive": Decimal("0"),
},
)
- year_introduced: Optional[int] = field(
+
+
+@dataclass
+class Beamwidth:
+ """
+ Transmit/receive beamwidth, in degrees.
+ """
+
+ value: Optional[Decimal] = field(
default=None,
metadata={
- "name": "YearIntroduced",
- "type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
- "min_inclusive": 1900,
- "max_inclusive": 2100,
+ "min_inclusive": Decimal("0"),
+ "max_inclusive": Decimal("360"),
},
)
@dataclass
-class Sector:
+class BearingAccuracy:
+ """
+ 1-sigma bearing accuracy of the sonar, in degrees.
"""
- The radiated noise level in one 30-degree bearing sector of a band.
- :ivar bearing: Centre bearing of the sector, in degrees [0, 360).
- :ivar level: Radiated noise level in this sector, in decibels.
+ value: Optional[Decimal] = field(
+ default=None,
+ metadata={
+ "required": True,
+ "min_inclusive": Decimal("0"),
+ "max_inclusive": Decimal("360"),
+ },
+ )
+
+
+@dataclass
+class CentreFrequency:
+ """
+ Centre frequency of the band, in hertz.
"""
- bearing: Optional[Decimal] = field(
+ value: Optional[Decimal] = field(
default=None,
metadata={
- "name": "Bearing",
- "type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
"min_inclusive": Decimal("0"),
- "max_exclusive": Decimal("360"),
},
)
- level: Optional[Decimal] = field(
+
+
+@dataclass
+class DetectionThreshold:
+ """
+ Signal-to-noise ratio required for detection, in decibels.
+ """
+
+ value: Optional[Decimal] = field(
default=None,
metadata={
- "name": "Level",
- "type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
"min_inclusive": Decimal("-200"),
"max_inclusive": Decimal("300"),
@@ -99,39 +145,101 @@ class Sector:
@dataclass
-class Sonar:
- """Fields common to every sonar: identity plus a nominal operating frequency.
+class Draft:
+ """
+ Draft (depth of the lowest point below the waterline), in metres.
+ """
+
+ value: Optional[Decimal] = field(
+ default=None,
+ metadata={
+ "required": True,
+ "min_inclusive": Decimal("0"),
+ },
+ )
+
- :ivar name: Model name of the sonar.
- :ivar manufacturer: Manufacturer of the sonar.
- :ivar operating_frequency: Nominal operating (centre) frequency of
- the sonar, in hertz.
+@dataclass
+class GeneratedUtc:
+ """
+ UTC timestamp identifying when the document was produced.
"""
- name: Optional[str] = field(
+ value: Optional[XmlDateTime] = field(
default=None,
metadata={
- "name": "Name",
- "type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
},
)
- manufacturer: Optional[str] = field(
+
+
+@dataclass
+class Length:
+ """
+ Overall length of the platform, in metres.
+ """
+
+ value: Optional[Decimal] = field(
default=None,
metadata={
- "name": "Manufacturer",
- "type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
+ "required": True,
+ "min_inclusive": Decimal("0"),
+ },
+ )
+
+
+@dataclass
+class MaxRange:
+ """
+ Maximum echo detection range, in metres.
+ """
+
+ value: Optional[Decimal] = field(
+ default=None,
+ metadata={
+ "required": True,
+ "min_inclusive": Decimal("0"),
+ },
+ )
+
+
+@dataclass
+class PassiveManufacturer:
+ """
+ Manufacturer of the passive sonar.
+ """
+
+ value: str = field(
+ default="",
+ metadata={
+ "required": True,
+ },
+ )
+
+
+@dataclass
+class PassiveName:
+ """
+ Model name of the passive sonar.
+ """
+
+ value: str = field(
+ default="",
+ metadata={
"required": True,
},
)
- operating_frequency: Optional[Decimal] = field(
+
+
+@dataclass
+class PassiveOperatingFrequency:
+ """
+ Nominal operating (centre) frequency of the passive sonar, in hertz.
+ """
+
+ value: Optional[Decimal] = field(
default=None,
metadata={
- "name": "OperatingFrequency",
- "type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
"min_inclusive": Decimal("0"),
},
@@ -139,191 +247,410 @@ class Sonar:
@dataclass
-class ActiveSonar(Sonar):
- """An active sonar: it transmits, so it carries a source level and beam/pulse
- figures, and a derived maximum (echo) detection range.
+class PlatformName:
+ """
+ Human-readable name of the platform.
+ """
+
+ value: str = field(
+ default="",
+ metadata={
+ "required": True,
+ },
+ )
+
- :ivar source_level: Transmit source level, in decibels.
- :ivar beamwidth: Transmit/receive beamwidth, in degrees.
- :ivar pulse_length: Transmit pulse length, in seconds.
- :ivar max_range: Maximum echo detection range, in metres.
+@dataclass
+class PulseLength:
+ """
+ Transmit pulse length, in seconds.
"""
- source_level: Optional[Decimal] = field(
+ value: Optional[Decimal] = field(
+ default=None,
+ metadata={
+ "required": True,
+ "min_inclusive": Decimal("0"),
+ },
+ )
+
+
+@dataclass
+class SchemaVersion:
+ """
+ Version of the schema this document targets.
+ """
+
+ value: str = field(
+ default="",
+ metadata={
+ "required": True,
+ },
+ )
+
+
+@dataclass
+class SectorBearing:
+ """
+ Centre bearing of the sector, in degrees [0, 360).
+ """
+
+ value: Optional[Decimal] = field(
+ default=None,
+ metadata={
+ "required": True,
+ "min_inclusive": Decimal("0"),
+ "max_exclusive": Decimal("360"),
+ },
+ )
+
+
+@dataclass
+class SectorLevel:
+ """
+ Radiated noise level in this sector, in decibels.
+ """
+
+ value: Optional[Decimal] = field(
default=None,
metadata={
- "name": "SourceLevel",
- "type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
"min_inclusive": Decimal("-200"),
"max_inclusive": Decimal("300"),
},
)
- beamwidth: Optional[Decimal] = field(
+
+
+class SignatureQuality(Enum):
+ """How a radiated-noise signature was obtained: measured at sea, modelled, or
+ estimated."""
+
+ MEASURED = "measured"
+ MODELLED = "modelled"
+ ESTIMATED = "estimated"
+
+
+@dataclass
+class SourceLevel:
+ """
+ Transmit source level, in decibels.
+ """
+
+ value: Optional[Decimal] = field(
+ default=None,
+ metadata={
+ "required": True,
+ "min_inclusive": Decimal("-200"),
+ "max_inclusive": Decimal("300"),
+ },
+ )
+
+
+@dataclass
+class Weight:
+ """
+ Displacement (weight) of the platform, in tonnes.
+ """
+
+ value: Optional[Decimal] = field(
+ default=None,
+ metadata={
+ "required": True,
+ "min_inclusive": Decimal("0"),
+ },
+ )
+
+
+@dataclass
+class YearIntroduced:
+ """
+ Calendar year the platform class entered service.
+ """
+
+ value: Optional[int] = field(
+ default=None,
+ metadata={
+ "required": True,
+ "min_inclusive": 1900,
+ "max_inclusive": 2100,
+ },
+ )
+
+
+@dataclass
+class ActiveSonar:
+ """An active sonar: it transmits, so it carries a source level, beam/pulse figures, and a derived maximum (echo) detection range."""
+
+ active_name: Optional[ActiveName] = field(
+ default=None,
+ metadata={
+ "name": "ActiveName",
+ "type": "Element",
+ "required": True,
+ },
+ )
+ active_manufacturer: Optional[ActiveManufacturer] = field(
+ default=None,
+ metadata={
+ "name": "ActiveManufacturer",
+ "type": "Element",
+ "required": True,
+ },
+ )
+ active_operating_frequency: Optional[ActiveOperatingFrequency] = field(
+ default=None,
+ metadata={
+ "name": "ActiveOperatingFrequency",
+ "type": "Element",
+ "required": True,
+ },
+ )
+ source_level: Optional[SourceLevel] = field(
+ default=None,
+ metadata={
+ "name": "SourceLevel",
+ "type": "Element",
+ "required": True,
+ },
+ )
+ beamwidth: Optional[Beamwidth] = field(
default=None,
metadata={
"name": "Beamwidth",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
- "min_inclusive": Decimal("0"),
- "max_inclusive": Decimal("360"),
},
)
- pulse_length: Optional[Decimal] = field(
+ pulse_length: Optional[PulseLength] = field(
default=None,
metadata={
"name": "PulseLength",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
- "min_inclusive": Decimal("0"),
},
)
- max_range: Optional[Decimal] = field(
+ max_range: Optional[MaxRange] = field(
default=None,
metadata={
"name": "MaxRange",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
- "min_inclusive": Decimal("0"),
},
)
@dataclass
-class Directional:
- """The all-round radiated noise for one band: exactly twelve sectors at
- 30-degree intervals, in ascending bearing order (0, 30, ..., 330).
-
- :ivar sector: One 30-degree bearing sector.
+class Characteristics:
+ """
+ The physical characteristics of the platform.
"""
- sector: list[Sector] = field(
- default_factory=list,
+ draft: Optional[Draft] = field(
+ default=None,
metadata={
- "name": "Sector",
+ "name": "Draft",
+ "type": "Element",
+ "required": True,
+ },
+ )
+ length: Optional[Length] = field(
+ default=None,
+ metadata={
+ "name": "Length",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
- "min_occurs": 12,
- "max_occurs": 12,
+ "required": True,
+ },
+ )
+ weight: Optional[Weight] = field(
+ default=None,
+ metadata={
+ "name": "Weight",
+ "type": "Element",
+ "required": True,
+ },
+ )
+ year_introduced: Optional[YearIntroduced] = field(
+ default=None,
+ metadata={
+ "name": "YearIntroduced",
+ "type": "Element",
+ "required": True,
},
)
@dataclass
-class PassiveSonar(Sonar):
- """A passive sonar: it only listens, so it carries array gain, a detection
- threshold and a bearing accuracy rather than a transmit source level.
-
- :ivar array_gain: Array gain of the receiving array, in decibels.
- :ivar detection_threshold: Signal-to-noise ratio required for
- detection, in decibels.
- :ivar bearing_accuracy: 1-sigma bearing accuracy of the sonar, in
- degrees.
- """
+class PassiveSonar:
+ """A passive sonar: it only listens, so it carries array gain, a detection threshold and a bearing accuracy rather than a transmit source level."""
- array_gain: Optional[Decimal] = field(
+ passive_name: Optional[PassiveName] = field(
+ default=None,
+ metadata={
+ "name": "PassiveName",
+ "type": "Element",
+ "required": True,
+ },
+ )
+ passive_manufacturer: Optional[PassiveManufacturer] = field(
+ default=None,
+ metadata={
+ "name": "PassiveManufacturer",
+ "type": "Element",
+ "required": True,
+ },
+ )
+ passive_operating_frequency: Optional[PassiveOperatingFrequency] = field(
+ default=None,
+ metadata={
+ "name": "PassiveOperatingFrequency",
+ "type": "Element",
+ "required": True,
+ },
+ )
+ array_gain: Optional[ArrayGain] = field(
default=None,
metadata={
"name": "ArrayGain",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
- "min_inclusive": Decimal("-200"),
- "max_inclusive": Decimal("300"),
},
)
- detection_threshold: Optional[Decimal] = field(
+ detection_threshold: Optional[DetectionThreshold] = field(
default=None,
metadata={
"name": "DetectionThreshold",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
- "min_inclusive": Decimal("-200"),
- "max_inclusive": Decimal("300"),
},
)
- bearing_accuracy: Optional[Decimal] = field(
+ bearing_accuracy: Optional[BearingAccuracy] = field(
default=None,
metadata={
"name": "BearingAccuracy",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
- "min_inclusive": Decimal("0"),
- "max_inclusive": Decimal("360"),
},
)
@dataclass
-class RadiatedBand:
+class Quality:
"""
- The directional radiated noise for one frequency band.
+ How this signature was obtained.
+ """
+
+ value: Optional[SignatureQuality] = field(
+ default=None,
+ metadata={
+ "required": True,
+ },
+ )
+
- :ivar centre_frequency: Centre frequency of the band, in hertz.
- :ivar directional: The twelve 30-degree directional noise sectors
- for this band.
- :ivar index: 1-based ordinal of the band within the signature.
+@dataclass
+class SectorType:
+ """
+ The radiated noise level in one directional bearing sector of a band.
"""
- centre_frequency: Optional[Decimal] = field(
+ sector_bearing: Optional[SectorBearing] = field(
default=None,
metadata={
- "name": "CentreFrequency",
+ "name": "SectorBearing",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
- "min_inclusive": Decimal("0"),
},
)
- directional: Optional[Directional] = field(
+ sector_level: Optional[SectorLevel] = field(
default=None,
metadata={
- "name": "Directional",
+ "name": "SectorLevel",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
},
)
- index: Optional[int] = field(
+
+
+@dataclass
+class Sector(SectorType):
+ pass
+
+
+@dataclass
+class Sensors:
+ """The sonar fit carried by the platform: one active sonar and one or more passive sonars.
+
+ :ivar active_sonar: The platform's single active sonar.
+ :ivar passive_sonar: The platform's passive sonars.
+ """
+
+ active_sonar: Optional[ActiveSonar] = field(
default=None,
metadata={
- "type": "Attribute",
+ "name": "ActiveSonar",
+ "type": "Element",
"required": True,
},
)
+ passive_sonar: list[PassiveSonar] = field(
+ default_factory=list,
+ metadata={
+ "name": "PassiveSonar",
+ "type": "Element",
+ "min_occurs": 1,
+ },
+ )
@dataclass
-class SensorSuite:
- """The sonar fit carried by the platform: one active sonar and two passive sonars.
+class Directional:
+ """The all-round radiated noise for one band: the directional sectors in ascending bearing order.
+
+ :ivar sector: One directional bearing sector.
+ """
+
+ sector: list[Sector] = field(
+ default_factory=list,
+ metadata={
+ "name": "Sector",
+ "type": "Element",
+ "min_occurs": 1,
+ },
+ )
- :ivar active: The platform's single active sonar.
- :ivar passive: The platform's two passive sonars.
+
+@dataclass
+class Band:
"""
+ The directional radiated noise for one frequency band.
- active: Optional[ActiveSonar] = field(
+ :ivar band_index:
+ :ivar centre_frequency:
+ :ivar directional: The directional noise sectors for this band.
+ """
+
+ band_index: Optional[BandIndex] = field(
default=None,
metadata={
- "name": "Active",
+ "name": "BandIndex",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
},
)
- passive: list[PassiveSonar] = field(
- default_factory=list,
+ centre_frequency: Optional[CentreFrequency] = field(
+ default=None,
metadata={
- "name": "Passive",
+ "name": "CentreFrequency",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
- "min_occurs": 2,
- "max_occurs": 2,
+ "required": True,
+ },
+ )
+ directional: Optional[Directional] = field(
+ default=None,
+ metadata={
+ "name": "Directional",
+ "type": "Element",
+ "required": True,
},
)
@@ -331,72 +658,72 @@ class SensorSuite:
@dataclass
class RadiatedNoise:
"""
- The platform's radiated-noise signature: exactly ten frequency bands, in
+ The platform's radiated-noise signature: one or more frequency bands in
ascending index order.
+ :ivar quality: Optional provenance of the signature.
:ivar band: One frequency band of the radiated-noise signature.
"""
- band: list[RadiatedBand] = field(
+ quality: Optional[Quality] = field(
+ default=None,
+ metadata={
+ "name": "Quality",
+ "type": "Element",
+ },
+ )
+ band: list[Band] = field(
default_factory=list,
metadata={
"name": "Band",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
- "min_occurs": 10,
- "max_occurs": 10,
+ "min_occurs": 1,
},
)
@dataclass
-class PlatformType:
- """A single platform: titled, timestamped reference data in three parts —
- physical characteristics, directional radiated noise, and the sonar fit.
+class Platform:
+ """Root element: titled, timestamped reference data for one platform in three parts — physical characteristics, directional radiated noise, and the sonar fit.
- :ivar schema_version: Version of the schema this document targets.
- :ivar name: Human-readable name of the platform.
- :ivar generated_utc: UTC timestamp identifying when the document was
- produced.
+ :ivar schema_version:
+ :ivar platform_name:
+ :ivar generated_utc:
:ivar characteristics: The platform's physical characteristics.
:ivar radiated_noise: The platform's directional radiated-noise
signature.
:ivar sensors: The platform's sonar fit.
"""
- schema_version: Optional[str] = field(
+ schema_version: Optional[SchemaVersion] = field(
default=None,
metadata={
"name": "SchemaVersion",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
},
)
- name: Optional[str] = field(
+ platform_name: Optional[PlatformName] = field(
default=None,
metadata={
- "name": "Name",
+ "name": "PlatformName",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
},
)
- generated_utc: Optional[XmlDateTime] = field(
+ generated_utc: Optional[GeneratedUtc] = field(
default=None,
metadata={
"name": "GeneratedUtc",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
},
)
- characteristics: Optional[PlatformCharacteristics] = field(
+ characteristics: Optional[Characteristics] = field(
default=None,
metadata={
"name": "Characteristics",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
},
)
@@ -405,24 +732,14 @@ class PlatformType:
metadata={
"name": "RadiatedNoise",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
},
)
- sensors: Optional[SensorSuite] = field(
+ sensors: Optional[Sensors] = field(
default=None,
metadata={
"name": "Sensors",
"type": "Element",
- "namespace": "https://deepblue.example/acoustic-dataset/v0",
"required": True,
},
)
-
-
-@dataclass
-class Platform(PlatformType):
- """Root element: the reference data for one platform."""
-
- class Meta:
- namespace = "https://deepblue.example/acoustic-dataset/v0"
diff --git a/src/acoustic_dataset/schema_docs.py b/src/acoustic_dataset/schema_docs.py
deleted file mode 100644
index e1a2ebd..0000000
--- a/src/acoustic_dataset/schema_docs.py
+++ /dev/null
@@ -1,431 +0,0 @@
-"""Generate the schema reference + Mermaid ERD from the enriched XSD (US5, FR-020/021/022).
-
-The enriched XSD is the single source of truth. This walks it and emits a Markdown reference —
-a Mermaid ``erDiagram`` of the entities and their relationships, a per-complex-type field
-reference carrying the ``xs:documentation`` prose, and a catalogue of the banded numeric types —
-into ``docs/reference/schema/``. The page is a *generated artifact*, never hand-edited, so the
-reference and the diagram cannot drift from the contract (ADR 0008/0009); the CI drift gate
-regenerates it and fails on any diff.
-"""
-
-from __future__ import annotations
-
-import dataclasses
-import re
-from dataclasses import dataclass
-from pathlib import Path
-
-from lxml import etree
-
-_XS = "http://www.w3.org/2001/XMLSchema"
-_NS = {"xs": _XS}
-
-_FACET_ORDER = ("minInclusive", "minExclusive", "maxInclusive", "maxExclusive")
-_FACET_OP = {
- "minInclusive": "≥",
- "minExclusive": ">",
- "maxInclusive": "≤",
- "maxExclusive": "<",
-}
-
-
-@dataclass
-class SchemaField:
- """One element or attribute of a complex type."""
-
- name: str
- type_ref: str # raw @type, e.g. "Metres" or "xs:string"
- is_attribute: bool
- min_occurs: int
- max_occurs: str # an int as a string, or "unbounded"
- required: bool
- doc: str
- inherited_from: str = ""
-
- @property
- def type_local(self) -> str:
- return self.type_ref.split(":")[-1] if self.type_ref else ""
-
-
-@dataclass
-class ComplexType:
- name: str
- doc: str
- base: str # base complex-type name, or ""
- fields: list[SchemaField]
-
-
-@dataclass
-class SimpleType:
- name: str
- base: str
- facets: dict[str, str]
- doc: str
-
-
-@dataclass
-class SchemaModel:
- version: str
- doc: str
- simple_types: list[SimpleType]
- complex_types: list[ComplexType]
- root_element: str
- root_type: str
-
-
-# --------------------------------------------------------------------------- parsing
-
-
-def _collapse(text: str) -> str:
- return re.sub(r"\s+", " ", text).strip()
-
-
-def _doc(node: etree._Element) -> str:
- doc = node.find("xs:annotation/xs:documentation", _NS)
- return _collapse("".join(doc.itertext())) if doc is not None else ""
-
-
-def _parse_fields(ct: etree._Element) -> tuple[str, list[SchemaField]]:
- """Return (base_type_name, fields) for a complex type, flattening xs:extension."""
- extension = ct.find("xs:complexContent/xs:extension", _NS)
- base = extension.get("base", "") if extension is not None else ""
- container = extension if extension is not None else ct
-
- fields: list[SchemaField] = []
- sequence = container.find("xs:sequence", _NS)
- if sequence is not None:
- for el in sequence.findall("xs:element", _NS):
- fields.append(
- SchemaField(
- name=el.get("name", ""),
- type_ref=el.get("type", ""),
- is_attribute=False,
- min_occurs=int(el.get("minOccurs", "1")),
- max_occurs=el.get("maxOccurs", "1"),
- required=True,
- doc=_doc(el),
- )
- )
- for at in container.findall("xs:attribute", _NS):
- required = at.get("use", "optional") == "required"
- fields.append(
- SchemaField(
- name=at.get("name", ""),
- type_ref=at.get("type", ""),
- is_attribute=True,
- min_occurs=1 if required else 0,
- max_occurs="1",
- required=required,
- doc=_doc(at),
- )
- )
- return base, fields
-
-
-def parse_schema(xsd_path: Path) -> SchemaModel:
- """Parse the enriched XSD into a small, render-ready model (document order preserved)."""
- root = etree.parse(str(xsd_path)).getroot()
-
- simple_types = [
- SimpleType(
- name=st.get("name", ""),
- base=(r := st.find("xs:restriction", _NS)) is not None and r.get("base", "") or "",
- facets={
- etree.QName(f).localname: f.get("value", "")
- for f in (r if r is not None else [])
- if etree.QName(f).localname in _FACET_OP
- },
- doc=_doc(st),
- )
- for st in root.findall("xs:simpleType", _NS)
- ]
-
- complex_types = []
- for ct in root.findall("xs:complexType", _NS):
- base, fields = _parse_fields(ct)
- complex_types.append(
- ComplexType(name=ct.get("name", ""), doc=_doc(ct), base=base, fields=fields)
- )
-
- root_el = root.find("xs:element", _NS)
- return SchemaModel(
- version=root.get("version", ""),
- doc=_doc(root),
- simple_types=simple_types,
- complex_types=complex_types,
- root_element=root_el.get("name", "") if root_el is not None else "",
- root_type=root_el.get("type", "") if root_el is not None else "",
- )
-
-
-# --------------------------------------------------------------------------- rendering
-
-
-def _entity_id(name: str) -> str:
- return re.sub(r"(? str:
- return text.replace('"', "'")
-
-
-def _cell(text: str) -> str:
- return text.replace("|", "\\|")
-
-
-def _all_fields(ct: ComplexType, by_name: dict[str, ComplexType]) -> list[SchemaField]:
- """Fields of a type including those flattened in from its base, marked as inherited."""
- out: list[SchemaField] = []
- if ct.base in by_name:
- base = by_name[ct.base]
- for bf in _all_fields(base, by_name):
- out.append(dataclasses.replace(bf, inherited_from=bf.inherited_from or base.name))
- out.extend(ct.fields)
- return out
-
-
-def _relationship(parent_id: str, child_id: str, f: SchemaField) -> str:
- many = f.max_occurs == "unbounded" or int(f.max_occurs) > 1
- right = ("|{" if f.min_occurs >= 1 else "o{") if many else ("||" if f.min_occurs >= 1 else "o|")
- if f.max_occurs == "unbounded":
- label = f"{f.name} ({'1+' if f.min_occurs >= 1 else 'many'})"
- elif int(f.max_occurs) > 1:
- label = f"{f.name} ({f.max_occurs})"
- else:
- label = f.name
- return f' {parent_id} ||--{right} {child_id} : "{label}"'
-
-
-def _cardinality(f: SchemaField) -> str:
- if f.is_attribute:
- return "1 (attribute)" if f.required else "0..1 (attribute)"
- if f.max_occurs == "unbounded":
- return f"{f.min_occurs}..*"
- if str(f.min_occurs) == str(f.max_occurs):
- return str(f.min_occurs)
- return f"{f.min_occurs}..{f.max_occurs}"
-
-
-def _render_erd(model: SchemaModel) -> list[str]:
- by_name = {ct.name: ct for ct in model.complex_types}
- referenced = {
- f.type_local
- for ct in model.complex_types
- for f in ct.fields
- if not f.is_attribute and f.type_local in by_name
- }
- entities = [
- ct for ct in model.complex_types if ct.name == model.root_type or ct.name in referenced
- ]
-
- lines = ["```mermaid", "erDiagram"]
- for ct in entities:
- pid = _entity_id(ct.name)
- for f in _all_fields(ct, by_name):
- if not f.is_attribute and f.type_local in by_name:
- lines.append(_relationship(pid, _entity_id(f.type_local), f))
- for ct in entities:
- scalars = [f for f in _all_fields(ct, by_name) if f.type_local not in by_name]
- if not scalars:
- continue
- lines.append(f" {_entity_id(ct.name)} {{")
- for f in scalars:
- lines.append(f' {f.type_local} {f.name} "{_mermaid_comment(f.doc)}"')
- lines.append(" }")
- lines.append("```")
- return lines
-
-
-def _type_md(f: SchemaField, complex_names: set[str], simple_names: set[str]) -> str:
- local = f.type_local
- if local in complex_names:
- return f"[`{local}`](#{local.lower()})"
- if local in simple_names:
- return f"[`{local}`](#banded-numeric-types)"
- return f"`{f.type_ref}`"
-
-
-def render_markdown(model: SchemaModel) -> str:
- by_name = {ct.name: ct for ct in model.complex_types}
- simple_names = {st.name for st in model.simple_types}
-
- lines = [
- "",
- "",
- "# Schema reference",
- "",
- "> **Reference (generated)** — produced from `schema/acoustic_dataset.xsd` "
- f"(version `{model.version}`) by `make gen-schema-docs`. Every entity, field, range and "
- "definition below is read from the XSD's `xs:annotation/xs:documentation`, so this page "
- "cannot drift from the contract.",
- "",
- ]
- if model.doc:
- lines += [model.doc, ""]
-
- lines += ["## Entity-relationship diagram", ""]
- lines += _render_erd(model)
- lines += [
- "",
- "Legend: `||--||` one-to-one, `||--|{` one-to-(one-or-many), `||--o{` "
- "one-to-(zero-or-many). The number in each label is the exact cardinality the schema "
- "enforces. Sonar sub-types show their inherited fields inline.",
- "",
- "## Entities",
- "",
- ]
- for ct in model.complex_types:
- suffix = f" (root element `{model.root_element}`)" if ct.name == model.root_type else ""
- section = [f"### {ct.name}{suffix}", ""]
- if ct.doc:
- section += [ct.doc, ""]
- if ct.base:
- section += [
- f"Extends [`{ct.base}`](#{ct.base.lower()}); inherited fields are marked below.",
- "",
- ]
- section += ["| Field | Type | Cardinality | Definition |", "|---|---|---|---|"]
- for f in _all_fields(ct, by_name):
- inherited = f" *(from {f.inherited_from})*" if f.inherited_from else ""
- name_cell = f"`{f.name}`{inherited}"
- section.append(
- f"| {name_cell} | {_type_md(f, set(by_name), simple_names)} "
- f"| {_cardinality(f)} | {_cell(f.doc)} |"
- )
- section.append("")
- lines += section
-
- lines += [
- "## Banded numeric types",
- "",
- "The numeric primitives below carry real XSD range facets, so an out-of-band value fails "
- "the validation gate.",
- "",
- "| Type | Base | Range | Definition |",
- "|---|---|---|---|",
- ]
- for st in model.simple_types:
- range_text = ", ".join(
- f"{_FACET_OP[k]} {st.facets[k]}" for k in _FACET_ORDER if k in st.facets
- ) or "—"
- lines.append(f"| `{st.name}` | `{st.base}` | {range_text} | {_cell(st.doc)} |")
- lines.append("")
-
- return "\n".join(lines) + "\n"
-
-
-# --------------------------------------------------------------------------- entry point
-
-
-def _render_examples(example_input: Path) -> list[str]:
- """Worked examples generated from the canonical pipeline run, so they cannot drift:
-
- a trimmed sample document, typed-object usage, and a field *derived* from elementary physics.
- """
- from acoustic_dataset import acoustics, build, serialize
-
- raw = acoustics.load_input(example_input)
- platform = build.build_platform(raw)
- xml = serialize.to_xml(platform)
-
- characteristics = platform.characteristics
- radiated = platform.radiated_noise
- sensors = platform.sensors
- assert characteristics is not None and radiated is not None and sensors is not None
- active = sensors.active
- assert active is not None and active.source_level is not None and active.max_range is not None
-
- # Trim the document to a compact but representative excerpt (the bulk is radiated noise).
- ns = serialize.NAMESPACE
- root = etree.fromstring(xml.encode("utf-8"), etree.XMLParser(remove_blank_text=True))
- rn = root.find(f"{{{ns}}}RadiatedNoise")
- if rn is not None:
- bands = rn.findall(f"{{{ns}}}Band")
- for extra in bands[1:]:
- rn.remove(extra)
- rn.append(etree.Comment(" bands 2-10 omitted for brevity "))
- directional = bands[0].find(f"{{{ns}}}Directional") if bands else None
- if directional is not None:
- for extra in directional.findall(f"{{{ns}}}Sector")[2:]:
- directional.remove(extra)
- directional.append(etree.Comment(" sectors 3-12 omitted for brevity "))
- excerpt = etree.tostring(root, pretty_print=True, encoding="unicode").rstrip()
-
- sl = float(active.source_level)
- dt = float(raw["sensors"]["active"]["detectionThresholdDb"])
- derived = acoustics.active_max_range_m(sl, dt)
-
- return [
- "## Example document",
- "",
- "A validated document produced by `make pipeline` from "
- "`examples/calculation_input.json` (most radiated-noise detail elided):",
- "",
- "```xml",
- excerpt,
- "```",
- "",
- "## Working with the typed objects",
- "",
- "The pipeline builds the calculation **directly** into the generated dataclasses; tests "
- "assert on those typed objects (the testable boundary) and they serialise straight to XML:",
- "",
- "```python",
- "from acoustic_dataset import build, serialize",
- "",
- 'platform = build.build_platform_from_file( # a generated Platform object',
- ' "examples/calculation_input.json"',
- ")",
- "",
- f"platform.name # {platform.name!r}",
- f"platform.characteristics.draft # Decimal('{characteristics.draft}')",
- f"len(platform.radiated_noise.band) # {len(radiated.band)}",
- f"platform.sensors.active.max_range # Decimal('{active.max_range}')",
- "",
- "xml = serialize.to_xml(platform) # -> the validated document shown above",
- "```",
- "",
- "## Worked example: deriving a value from elementary physics",
- "",
- "Not every element is copied from the input — some are **computed** from typed inputs. "
- "The active sonar's maximum echo range is one: it falls out of the sonar equation under "
- "two-way spherical spreading.",
- "",
- "An echo travels out *and back*, so transmission loss is `TL = 40 * log10(r)` dB at "
- "range `r` metres. The platform can just detect the returning echo when its source level, "
- "less that loss, reaches the detection threshold — solve for `r`:",
- "",
- "```text",
- "SL - 40*log10(r) = DT => r = 10 ** ((SL - DT) / 40)",
- "```",
- "",
- f"This platform's active sonar transmits at `SL = {sl:g}` dB with a detection threshold "
- f"`DT = {dt:g}` dB, so:",
- "",
- "```python",
- "from acoustic_dataset.acoustics import active_max_range_m",
- f"active_max_range_m({sl:g}, {dt:g}) # => {derived:.3f} (metres)",
- "```",
- "",
- f"That value serialises into the document as "
- f"`{active.max_range}`.",
- "",
- ]
-
-
-def generate(schema_path: Path, out_dir: Path, example_input: Path | None = None) -> Path:
- """Write the generated schema reference to ``/index.md`` and return its path.
-
- When ``example_input`` is given, append worked examples (a sample document, typed-object
- usage, and a physics-derived field) computed from that input via the pipeline.
- """
- model = parse_schema(schema_path)
- md = render_markdown(model)
- if example_input is not None:
- md = md.rstrip("\n") + "\n\n" + "\n".join(_render_examples(example_input)) + "\n"
- out_dir.mkdir(parents=True, exist_ok=True)
- out_file = out_dir / "index.md"
- out_file.write_text(md, encoding="utf-8")
- return out_file
diff --git a/src/acoustic_dataset/schema_html.py b/src/acoustic_dataset/schema_html.py
new file mode 100644
index 0000000..548fb6c
--- /dev/null
+++ b/src/acoustic_dataset/schema_html.py
@@ -0,0 +1,34 @@
+"""Generate the schema reference as HTML via the vendored xs3p XSLT stylesheet.
+
+We do not parse the XSD ourselves — ``tools/xs3p/xs3p.xsl`` renders any XSD into a single
+self-contained HTML reference, and we drive it through ``lxml``'s XSLT engine. This honours the
+"configure, don't create" principle: an off-the-shelf tool owns the schema semantics (refs,
+groups, ``xs:all``, XSD 1.1, types), we only wire it up. xs3p output is byte-deterministic, so the
+committed page is drift-checked in CI. See ``tools/xs3p/README.md`` (provenance + DPL 1.1 licence).
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+from lxml import etree
+
+_PKG_DIR = Path(__file__).resolve().parent
+_REPO_ROOT = _PKG_DIR.parent.parent
+
+#: The vendored xs3p stylesheet — the single source of the reference's HTML rendering.
+XS3P_XSL = _REPO_ROOT / "tools" / "xs3p" / "xs3p.xsl"
+
+
+def render(schema_path: Path, stylesheet: Path | None = None) -> bytes:
+ """Transform ``schema_path`` to HTML bytes with xs3p (no filesystem writes)."""
+ transform = etree.XSLT(etree.parse(str(stylesheet or XS3P_XSL)))
+ return bytes(transform(etree.parse(str(schema_path))))
+
+
+def generate(schema_path: Path, out_file: Path, stylesheet: Path | None = None) -> Path:
+ """Render ``schema_path`` to a single HTML file at ``out_file``; return ``out_file``."""
+ html = render(schema_path, stylesheet)
+ out_file.parent.mkdir(parents=True, exist_ok=True)
+ out_file.write_bytes(html)
+ return out_file
diff --git a/src/acoustic_dataset/serialize.py b/src/acoustic_dataset/serialize.py
index 567fd16..5a2d78f 100644
--- a/src/acoustic_dataset/serialize.py
+++ b/src/acoustic_dataset/serialize.py
@@ -1,8 +1,8 @@
"""Serialise populated domain objects to XML text (xsdata ``XmlSerializer``).
Binding-driven: there is no hand-built XML string anywhere, so the emitted document can only
-contain what the generated models (and therefore the schema) allow. The default namespace is
-bound to the schema target namespace for clean, prefix-free output.
+contain what the generated models (and therefore the schema) allow. The schema is a no-namespace
+contract (as the real one is), so the emitted document is unqualified — no default namespace.
"""
from __future__ import annotations
@@ -12,7 +12,8 @@
from acoustic_dataset.models.acoustic_dataset import Platform
-NAMESPACE = "https://deepblue.example/acoustic-dataset/v0"
+#: The contract has no target namespace; emitted documents are unqualified.
+NAMESPACE = ""
def _serializer() -> XmlSerializer:
@@ -27,5 +28,5 @@ def _serializer() -> XmlSerializer:
def to_xml(model: Platform) -> str:
- """Serialise a ``Platform`` to an XML string (default-namespace bound)."""
- return _serializer().render(model, ns_map={None: NAMESPACE})
+ """Serialise a ``Platform`` to an XML string (unqualified — no namespace)."""
+ return _serializer().render(model)
diff --git a/tests/golden/acoustic_dataset.xml b/tests/golden/acoustic_dataset.xml
index 6b1204d..16177c0 100644
--- a/tests/golden/acoustic_dataset.xml
+++ b/tests/golden/acoustic_dataset.xml
@@ -1,7 +1,7 @@
-
+0.2.0
- Reference Platform A
+ Reference Platform A2026-06-14T00:00:00Z7.500
@@ -10,562 +10,572 @@
1998
-
+
+ 150.000
- 0.000
- 134.000
+ 0.000
+ 134.000
- 30.000
- 134.804
+ 30.000
+ 134.804
- 60.000
- 137.000
+ 60.000
+ 137.000
- 90.000
- 140.000
+ 90.000
+ 140.000
- 120.000
- 143.000
+ 120.000
+ 143.000
- 150.000
- 145.196
+ 150.000
+ 145.196
- 180.000
- 146.000
+ 180.000
+ 146.000
- 210.000
- 145.196
+ 210.000
+ 145.196
- 240.000
- 143.000
+ 240.000
+ 143.000
- 270.000
- 140.000
+ 270.000
+ 140.000
- 300.000
- 137.000
+ 300.000
+ 137.000
- 330.000
- 134.804
+ 330.000
+ 134.804
-
+
+ 2100.000
- 0.000
- 129.000
+ 0.000
+ 129.000
- 30.000
- 129.804
+ 30.000
+ 129.804
- 60.000
- 132.000
+ 60.000
+ 132.000
- 90.000
- 135.000
+ 90.000
+ 135.000
- 120.000
- 138.000
+ 120.000
+ 138.000
- 150.000
- 140.196
+ 150.000
+ 140.196
- 180.000
- 141.000
+ 180.000
+ 141.000
- 210.000
- 140.196
+ 210.000
+ 140.196
- 240.000
- 138.000
+ 240.000
+ 138.000
- 270.000
- 135.000
+ 270.000
+ 135.000
- 300.000
- 132.000
+ 300.000
+ 132.000
- 330.000
- 129.804
+ 330.000
+ 129.804
-
+
+ 3200.000
- 0.000
- 124.000
+ 0.000
+ 124.000
- 30.000
- 124.804
+ 30.000
+ 124.804
- 60.000
- 127.000
+ 60.000
+ 127.000
- 90.000
- 130.000
+ 90.000
+ 130.000
- 120.000
- 133.000
+ 120.000
+ 133.000
- 150.000
- 135.196
+ 150.000
+ 135.196
- 180.000
- 136.000
+ 180.000
+ 136.000
- 210.000
- 135.196
+ 210.000
+ 135.196
- 240.000
- 133.000
+ 240.000
+ 133.000
- 270.000
- 130.000
+ 270.000
+ 130.000
- 300.000
- 127.000
+ 300.000
+ 127.000
- 330.000
- 124.804
+ 330.000
+ 124.804
-
+
+ 4400.000
- 0.000
- 119.000
+ 0.000
+ 119.000
- 30.000
- 119.804
+ 30.000
+ 119.804
- 60.000
- 122.000
+ 60.000
+ 122.000
- 90.000
- 125.000
+ 90.000
+ 125.000
- 120.000
- 128.000
+ 120.000
+ 128.000
- 150.000
- 130.196
+ 150.000
+ 130.196
- 180.000
- 131.000
+ 180.000
+ 131.000
- 210.000
- 130.196
+ 210.000
+ 130.196
- 240.000
- 128.000
+ 240.000
+ 128.000
- 270.000
- 125.000
+ 270.000
+ 125.000
- 300.000
- 122.000
+ 300.000
+ 122.000
- 330.000
- 119.804
+ 330.000
+ 119.804
-
+
+ 5800.000
- 0.000
- 114.000
+ 0.000
+ 114.000
- 30.000
- 114.804
+ 30.000
+ 114.804
- 60.000
- 117.000
+ 60.000
+ 117.000
- 90.000
- 120.000
+ 90.000
+ 120.000
- 120.000
- 123.000
+ 120.000
+ 123.000
- 150.000
- 125.196
+ 150.000
+ 125.196
- 180.000
- 126.000
+ 180.000
+ 126.000
- 210.000
- 125.196
+ 210.000
+ 125.196
- 240.000
- 123.000
+ 240.000
+ 123.000
- 270.000
- 120.000
+ 270.000
+ 120.000
- 300.000
- 117.000
+ 300.000
+ 117.000
- 330.000
- 114.804
+ 330.000
+ 114.804
-
+
+ 61600.000
- 0.000
- 109.000
+ 0.000
+ 109.000
- 30.000
- 109.804
+ 30.000
+ 109.804
- 60.000
- 112.000
+ 60.000
+ 112.000
- 90.000
- 115.000
+ 90.000
+ 115.000
- 120.000
- 118.000
+ 120.000
+ 118.000
- 150.000
- 120.196
+ 150.000
+ 120.196
- 180.000
- 121.000
+ 180.000
+ 121.000
- 210.000
- 120.196
+ 210.000
+ 120.196
- 240.000
- 118.000
+ 240.000
+ 118.000
- 270.000
- 115.000
+ 270.000
+ 115.000
- 300.000
- 112.000
+ 300.000
+ 112.000
- 330.000
- 109.804
+ 330.000
+ 109.804
-
+
+ 73200.000
- 0.000
- 104.000
+ 0.000
+ 104.000
- 30.000
- 104.804
+ 30.000
+ 104.804
- 60.000
- 107.000
+ 60.000
+ 107.000
- 90.000
- 110.000
+ 90.000
+ 110.000
- 120.000
- 113.000
+ 120.000
+ 113.000
- 150.000
- 115.196
+ 150.000
+ 115.196
- 180.000
- 116.000
+ 180.000
+ 116.000
- 210.000
- 115.196
+ 210.000
+ 115.196
- 240.000
- 113.000
+ 240.000
+ 113.000
- 270.000
- 110.000
+ 270.000
+ 110.000
- 300.000
- 107.000
+ 300.000
+ 107.000
- 330.000
- 104.804
+ 330.000
+ 104.804
-
+
+ 86400.000
- 0.000
- 99.000
+ 0.000
+ 99.000
- 30.000
- 99.804
+ 30.000
+ 99.804
- 60.000
- 102.000
+ 60.000
+ 102.000
- 90.000
- 105.000
+ 90.000
+ 105.000
- 120.000
- 108.000
+ 120.000
+ 108.000
- 150.000
- 110.196
+ 150.000
+ 110.196
- 180.000
- 111.000
+ 180.000
+ 111.000
- 210.000
- 110.196
+ 210.000
+ 110.196
- 240.000
- 108.000
+ 240.000
+ 108.000
- 270.000
- 105.000
+ 270.000
+ 105.000
- 300.000
- 102.000
+ 300.000
+ 102.000
- 330.000
- 99.804
+ 330.000
+ 99.804
-
+
+ 912800.000
- 0.000
- 94.000
+ 0.000
+ 94.000
- 30.000
- 94.804
+ 30.000
+ 94.804
- 60.000
- 97.000
+ 60.000
+ 97.000
- 90.000
- 100.000
+ 90.000
+ 100.000
- 120.000
- 103.000
+ 120.000
+ 103.000
- 150.000
- 105.196
+ 150.000
+ 105.196
- 180.000
- 106.000
+ 180.000
+ 106.000
- 210.000
- 105.196
+ 210.000
+ 105.196
- 240.000
- 103.000
+ 240.000
+ 103.000
- 270.000
- 100.000
+ 270.000
+ 100.000
- 300.000
- 97.000
+ 300.000
+ 97.000
- 330.000
- 94.804
+ 330.000
+ 94.804
-
+
+ 1025600.000
- 0.000
- 89.000
+ 0.000
+ 89.000
- 30.000
- 89.804
+ 30.000
+ 89.804
- 60.000
- 92.000
+ 60.000
+ 92.000
- 90.000
- 95.000
+ 90.000
+ 95.000
- 120.000
- 98.000
+ 120.000
+ 98.000
- 150.000
- 100.196
+ 150.000
+ 100.196
- 180.000
- 101.000
+ 180.000
+ 101.000
- 210.000
- 100.196
+ 210.000
+ 100.196
- 240.000
- 98.000
+ 240.000
+ 98.000
- 270.000
- 95.000
+ 270.000
+ 95.000
- 300.000
- 92.000
+ 300.000
+ 92.000
- 330.000
- 89.804
+ 330.000
+ 89.804
-
- AS-900 Echo
- DeepBlue Sonics
- 6000.000
+
+ AS-900 Echo
+ DeepBlue Sonics
+ 6000.000215.00015.0000.500118850.223
-
-
- PA-110 Flank Array
- DeepBlue Sonics
- 1500.000
+
+
+ PA-110 Flank Array
+ DeepBlue Sonics
+ 1500.00018.00010.0001.500
-
-
- PA-220 Towed Array
- Marine Acoustics Ltd
- 300.000
+
+
+ PA-220 Towed Array
+ Marine Acoustics Ltd
+ 300.00022.0008.0002.000
-
+
diff --git a/tests/integration/test_compare.py b/tests/integration/test_compare.py
index 4ad9d37..60df2b4 100644
--- a/tests/integration/test_compare.py
+++ b/tests/integration/test_compare.py
@@ -26,10 +26,12 @@ def test_whitespace_and_indentation_are_cosmetic(golden_path):
assert compare.compare(squashed, text).equal
-def test_namespace_prefix_is_cosmetic(golden_path, reference_path):
- # The shipped reference uses a ``ds:`` prefix; the golden uses the default namespace.
- assert "xmlns:ds" in reference_path.read_text(encoding="utf-8")
- assert compare.compare(golden_path, reference_path).equal
+def test_namespace_prefix_is_cosmetic():
+ # The contract is no-namespace, but the equality key still rewrites prefixes, so the same
+ # document under a prefix vs the default namespace compares equal.
+ default_ns = '1'
+ prefixed = '1'
+ assert compare.compare(default_ns, prefixed).equal
def test_attribute_order_is_cosmetic():
@@ -57,7 +59,9 @@ def test_shipped_reference_matches_the_pipeline_output(golden_path, reference_pa
def test_schema_valid_but_different_is_surfaced(golden_path, schema_path):
text = golden_path.read_text(encoding="utf-8")
# 144.000 dB is still inside the schema's Decibels band [-200, 300] -> schema-valid...
- different = text.replace("134.000", "144.000", 1)
+ different = text.replace(
+ "134.000", "144.000", 1
+ )
assert different != text
assert validate.schema_errors(different, schema_path) == [] # ...yet schema-valid
@@ -69,11 +73,13 @@ def test_schema_valid_but_different_is_surfaced(golden_path, schema_path):
def test_diff_is_oriented_reference_to_generated(golden_path):
text = golden_path.read_text(encoding="utf-8")
- generated = text.replace("134.000", "144.000", 1)
+ generated = text.replace(
+ "134.000", "144.000", 1
+ )
diff = compare.compare(generated, text).diff
# reference (134.000) removed, generated (144.000) added
- assert "- 134.000" in diff
- assert "+ 144.000" in diff
+ assert "- 134.000" in diff
+ assert "+ 144.000" in diff
# --- CLI exit-code contract (contracts/cli-commands.md §compare) --------------------------
@@ -89,7 +95,7 @@ def test_cli_meaningful_difference_exits_nonzero(tmp_path, golden_path, capsys):
different = tmp_path / "generated.xml"
different.write_text(
golden_path.read_text(encoding="utf-8").replace(
- "134.000", "144.000", 1
+ "134.000", "144.000", 1
),
encoding="utf-8",
)
diff --git a/tests/integration/test_gates.py b/tests/integration/test_gates.py
index 0472079..a1b17f8 100644
--- a/tests/integration/test_gates.py
+++ b/tests/integration/test_gates.py
@@ -28,7 +28,9 @@ def test_pipeline_output_passes_both_gates(input_path, schema_path):
def test_schema_invalid_document_is_caught(input_path, schema_path):
# Push a bearing past the schema's [0, 360) band: must fail the structural gate.
xml = serialize.to_xml(build.build_platform_from_file(input_path))
- bad = xml.replace("0.000", "400.000", 1)
+ bad = xml.replace(
+ "0.000", "400.000", 1
+ )
assert bad != xml, "expected to find a bearing to tamper with"
report = validate.validate(bad, schema_path)
assert not report.schema_valid
diff --git a/tests/integration/test_pipeline.py b/tests/integration/test_pipeline.py
index aa31e0a..4da7765 100644
--- a/tests/integration/test_pipeline.py
+++ b/tests/integration/test_pipeline.py
@@ -22,28 +22,29 @@ def _build_xml(input_path):
def test_populated_objects_carry_the_expected_typed_values(input_path):
model = build.build_platform_from_file(input_path)
- assert model.schema_version == "0.2.0"
+ # Every scalar is a wrapper element (salami-slice idiom); the datum is on ``.value``.
+ assert model.schema_version.value == "0.2.0"
# Platform characteristics: Decimals (the typed boundary the schema declares), not floats.
- assert model.characteristics.draft == Decimal("7.500")
- assert model.characteristics.weight == Decimal("2400.000")
- assert model.characteristics.year_introduced == 1998
+ assert model.characteristics.draft.value == Decimal("7.500")
+ assert model.characteristics.weight.value == Decimal("2400.000")
+ assert model.characteristics.year_introduced.value == 1998
# Radiated noise: exactly ten bands, each with twelve directional sectors.
- assert [b.index for b in model.radiated_noise.band] == list(range(1, 11))
+ assert [b.band_index.value for b in model.radiated_noise.band] == list(range(1, 11))
band1 = model.radiated_noise.band[0]
- assert band1.centre_frequency == Decimal("50.000")
+ assert band1.centre_frequency.value == Decimal("50.000")
assert len(band1.directional.sector) == 12
# Directivity peaks astern (180 deg) and troughs ahead (0 deg).
- assert band1.directional.sector[0].bearing == Decimal("0.000")
- assert band1.directional.sector[0].level == Decimal("134.000")
- assert band1.directional.sector[6].bearing == Decimal("180.000")
- assert band1.directional.sector[6].level == Decimal("146.000")
+ assert band1.directional.sector[0].sector_bearing.value == Decimal("0.000")
+ assert band1.directional.sector[0].sector_level.value == Decimal("134.000")
+ assert band1.directional.sector[6].sector_bearing.value == Decimal("180.000")
+ assert band1.directional.sector[6].sector_level.value == Decimal("146.000")
# Sensor fit: one active sonar (with a derived max range) and two passive sonars.
- assert model.sensors.active.name == "AS-900 Echo"
- assert model.sensors.active.max_range == Decimal("118850.223")
- assert [p.name for p in model.sensors.passive] == [
+ assert model.sensors.active_sonar.active_name.value == "AS-900 Echo"
+ assert model.sensors.active_sonar.max_range.value == Decimal("118850.223")
+ assert [p.passive_name.value for p in model.sensors.passive_sonar] == [
"PA-110 Flank Array",
"PA-220 Towed Array",
]
diff --git a/tests/integration/test_schema_docs.py b/tests/integration/test_schema_docs.py
deleted file mode 100644
index f81d56a..0000000
--- a/tests/integration/test_schema_docs.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""Schema-docs generator tests (T027, US5 / FR-020/021/022, SC-008/SC-009).
-
-The schema reference + ERD are generated from the enriched XSD so they cannot drift from the
-contract. These tests assert the generator emits the entities, the ``xs:documentation`` prose,
-a Mermaid ``erDiagram``, and the worked examples (sample document, typed-object usage, and a
-physics-derived field) — and that the committed page is byte-identical to a fresh regeneration
-(the property the CI drift gate depends on).
-"""
-
-from __future__ import annotations
-
-from acoustic_dataset import schema_docs
-
-
-def _generate(base, schema_path, input_path) -> str:
- out = schema_docs.generate(schema_path, base / "schema", example_input=input_path)
- return out.read_text(encoding="utf-8")
-
-
-def test_output_contains_a_mermaid_erdiagram(tmp_path, schema_path, input_path):
- md = _generate(tmp_path, schema_path, input_path)
- assert "```mermaid" in md
- assert "erDiagram" in md
-
-
-def test_output_contains_every_complex_type_entity(tmp_path, schema_path, input_path):
- md = _generate(tmp_path, schema_path, input_path)
- model = schema_docs.parse_schema(schema_path)
- assert model.complex_types, "expected the schema to declare complex types"
- for ct in model.complex_types:
- assert f"### {ct.name}" in md, f"missing entity section for {ct.name}"
-
-
-def test_output_carries_xs_documentation_prose(tmp_path, schema_path, input_path):
- # Prose that lives only in the XSD's xs:annotation/xs:documentation (FR-022).
- md = _generate(tmp_path, schema_path, input_path)
- assert "Centre bearing of the sector, in degrees [0, 360)." in md
- assert "Maximum echo detection range, in metres." in md
-
-
-def test_output_lists_banded_types_with_ranges(tmp_path, schema_path, input_path):
- md = _generate(tmp_path, schema_path, input_path)
- assert "## Banded numeric types" in md
- assert "≥ -200" in md and "≤ 300" in md # Decibels facet range
- assert "< 360" in md # Bearing's maxExclusive
-
-
-def test_erd_shows_relationships_and_cardinalities(tmp_path, schema_path, input_path):
- md = _generate(tmp_path, schema_path, input_path)
- assert "RADIATED_NOISE ||--|{ RADIATED_BAND" in md # one-to-many (10 bands)
- assert "PLATFORM_TYPE ||--|| SENSOR_SUITE" in md # one-to-one
-
-
-def test_inheritance_is_flattened_in_derived_entities(tmp_path, schema_path, input_path):
- md = _generate(tmp_path, schema_path, input_path)
- assert "Extends [`Sonar`]" in md
- assert "*(from Sonar)*" in md # inherited fields marked in ActiveSonar/PassiveSonar
-
-
-# --- worked examples (sample document, typed-object usage, physics) ----------------------
-
-
-def test_example_document_section_has_a_sample_xml(tmp_path, schema_path, input_path):
- md = _generate(tmp_path, schema_path, input_path)
- assert "## Example document" in md
- assert "```xml" in md
- assert "118850.223" in md
- assert "omitted for brevity" in md # the excerpt is trimmed
-
-
-def test_typed_objects_section_shows_real_values(tmp_path, schema_path, input_path):
- md = _generate(tmp_path, schema_path, input_path)
- assert "## Working with the typed objects" in md
- assert "build.build_platform_from_file(" in md
- assert "'Reference Platform A'" in md # the real platform name
- assert "Decimal('118850.223')" in md
-
-
-def test_physics_section_derives_max_range(tmp_path, schema_path, input_path):
- md = _generate(tmp_path, schema_path, input_path)
- assert "deriving a value from elementary physics" in md
- assert "r = 10 ** ((SL - DT) / 40)" in md
- assert "active_max_range_m(215, 12)" in md # the real SL / DT
- assert "118850.223" in md # the computed range == the element
-
-
-# --- determinism / drift gate ------------------------------------------------------------
-
-
-def test_generation_is_idempotent(tmp_path, schema_path, input_path):
- assert _generate(tmp_path / "a", schema_path, input_path) == _generate(
- tmp_path / "b", schema_path, input_path
- )
-
-
-def test_committed_page_matches_a_fresh_regeneration(repo_root, schema_path, input_path, tmp_path):
- # The property the CI drift gate relies on: regeneration is byte-identical to the commit.
- committed = (repo_root / "docs" / "reference" / "schema" / "index.md").read_text(
- encoding="utf-8"
- )
- assert _generate(tmp_path, schema_path, input_path) == committed, (
- "run `make gen-schema-docs` — the committed schema reference is stale"
- )
diff --git a/tests/integration/test_schema_html.py b/tests/integration/test_schema_html.py
new file mode 100644
index 0000000..6006bff
--- /dev/null
+++ b/tests/integration/test_schema_html.py
@@ -0,0 +1,40 @@
+"""Schema-reference (HTML) generator tests.
+
+The reference is produced from the XSD by the vendored xs3p stylesheet (``schema_html``), not by
+hand-rolled code. These tests assert it renders the schema's entities and ``xs:documentation``
+prose, is deterministic, and that the committed page is byte-identical to a fresh regeneration —
+the property the CI drift gate depends on.
+"""
+
+from __future__ import annotations
+
+from acoustic_dataset import schema_html
+
+
+def test_html_is_generated_and_nonempty(tmp_path, schema_path):
+ out = schema_html.generate(schema_path, tmp_path / "index.html")
+ html = out.read_text(encoding="utf-8")
+ assert out.is_file()
+ assert " 1000
+
+
+def test_html_carries_entities_and_documentation(schema_path):
+ html = schema_html.render(schema_path).decode("utf-8")
+ # Entities/types declared in the schema appear in the reference...
+ for name in ("Platform", "ActiveSonar", "RadiatedNoise", "SectorType"):
+ assert name in html, f"missing schema component {name} in the reference"
+ # ...and the xs:documentation prose is carried through.
+ assert "Maximum echo detection range" in html
+
+
+def test_generation_is_deterministic(schema_path):
+ assert schema_html.render(schema_path) == schema_html.render(schema_path)
+
+
+def test_committed_page_matches_a_fresh_regeneration(repo_root, schema_path):
+ # The property the CI drift gate relies on: regeneration is byte-identical to the commit.
+ committed = (repo_root / "docs" / "reference" / "schema" / "index.html").read_bytes()
+ assert schema_html.render(schema_path) == committed, (
+ "run `make gen-schema-docs` — the committed HTML schema reference is stale"
+ )
diff --git a/tests/unit/test_build.py b/tests/unit/test_build.py
index db9fcc1..44a9eba 100644
--- a/tests/unit/test_build.py
+++ b/tests/unit/test_build.py
@@ -14,19 +14,19 @@
def test_build_produces_ten_bands_of_twelve_sectors(input_path):
platform = build.build_platform_from_file(input_path)
bands = platform.radiated_noise.band
- assert [b.index for b in bands] == list(range(1, 11))
+ assert [b.band_index.value for b in bands] == list(range(1, 11))
assert all(len(b.directional.sector) == 12 for b in bands)
- assert [s.bearing for s in bands[0].directional.sector][:3] == [
+ assert [s.sector_bearing.value for s in bands[0].directional.sector][:3] == [
Decimal("0.000"), Decimal("30.000"), Decimal("60.000")
]
def test_build_synthesises_characteristics_and_sensors(input_path):
platform = build.build_platform_from_file(input_path)
- assert platform.schema_version == "0.2.0"
- assert platform.characteristics.year_introduced == 1998
- assert platform.sensors.active.name == "AS-900 Echo"
+ assert platform.schema_version.value == "0.2.0"
+ assert platform.characteristics.year_introduced.value == 1998
+ assert platform.sensors.active_sonar.active_name.value == "AS-900 Echo"
# The derived max range falls straight out of the sonar equation, quantised to mm.
- assert platform.sensors.active.max_range == Decimal("118850.223")
- assert len(platform.sensors.passive) == 2
- assert platform.sensors.passive[1].manufacturer == "Marine Acoustics Ltd"
+ assert platform.sensors.active_sonar.max_range.value == Decimal("118850.223")
+ assert len(platform.sensors.passive_sonar) == 2
+ assert platform.sensors.passive_sonar[1].passive_manufacturer.value == "Marine Acoustics Ltd"
diff --git a/tests/unit/test_typed_vs_dict.py b/tests/unit/test_typed_vs_dict.py
index 2bf89cf..25f5c4f 100644
--- a/tests/unit/test_typed_vs_dict.py
+++ b/tests/unit/test_typed_vs_dict.py
@@ -23,16 +23,24 @@ def test_a_dict_stores_anything_silently():
def test_an_unknown_field_is_rejected_when_constructing():
+ from acoustic_dataset.models.acoustic_dataset import SectorBearing, SectorLevel
+
# The declared shape is accepted...
- Sector(bearing=Decimal("30.000"), level=Decimal("134.000"))
+ Sector(
+ sector_bearing=SectorBearing(Decimal("30.000")),
+ sector_level=SectorLevel(Decimal("134.000")),
+ )
# ...but a name that is not a field fails at construction (a dict would just store it).
with pytest.raises(TypeError):
- Sector(bering=Decimal("30.000"), level=Decimal("134.000"))
+ Sector(
+ bering=SectorBearing(Decimal("30.000")),
+ sector_level=SectorLevel(Decimal("134.000")),
+ )
def test_a_stored_field_has_the_schema_declared_type(input_path):
platform = build.build_platform_from_file(input_path)
- level = platform.radiated_noise.band[0].directional.sector[0].level
+ level = platform.radiated_noise.band[0].directional.sector[0].sector_level.value
assert isinstance(level, Decimal) # stored as the schema's Decimal, not a bare float
diff --git a/tools/xs3p/LICENSE.html b/tools/xs3p/LICENSE.html
new file mode 100644
index 0000000..04559c9
--- /dev/null
+++ b/tools/xs3p/LICENSE.html
@@ -0,0 +1,519 @@
+
+
+
+
+ DSTC Public License
+
+
+
+
DSTC Public License (DPL)
+
Version 1.1
+
+
1. Definitions.
+
+
1.0.1. "Commercial Use" means distribution or otherwise making
+the Covered Code available to a third party.
+
+
1.1. "Contributor" means each entity that creates or
+contributes to the creation of Modifications.
+
+
1.2. "Contributor Version" means the combination of the
+Original Code, prior Modifications used by a Contributor, and the
+Modifications made by that particular Contributor.
+
+
1.3. "Covered Code" means the Original Code or
+Modifications or the combination of the Original Code and Modifications,
+in each case including portions thereof.
+
+
1.4. "Electronic Distribution Mechanism" means a mechanism
+generally accepted in the software development community for the
+electronic transfer of data.
+
+
1.5. "Executable" means Covered Code in any form other than
+Source Code.
+
+
1.6. "Initial Developer" means the individual or entity
+identified as the Initial Developer in the Source Code notice required
+by Exhibit A.
+
+
1.7. "Larger Work" means a work which combines Covered Code
+or portions thereof with code not governed by the terms of this
+License.
+
+
1.8. "License" means this document.
+
+
1.8.1. "Licensable" means having the right to grant, to the
+maximum extent possible, whether at the time of the initial grant or
+subsequently acquired, any and all of the rights conveyed herein.
+
+
1.9. "Modifications" means any addition to or deletion from
+the substance or structure of either the Original Code or any previous
+Modifications. When Covered Code is released as a series of files, a
+Modification is:
+
+
+
A. Any addition to or
+deletion from the contents of a file containing Original Code or previous
+Modifications.
+
+
B. Any new file that contains any part of the Original Code
+or previous Modifications.
+
+
+
+
1.10. "Original Code"
+means Source Code of computer software code which is described in the Source
+Code notice required by Exhibit A as Original Code, and which, at the
+time of its release under this License is not already Covered Code governed by
+this License.
+
+
1.10.1. "Patent Claims" means any patent claim(s), now owned
+or hereafter acquired, including without limitation, method, process,
+and apparatus claims, in any patent Licensable by grantor.
+
+
1.11. "Source Code" means the preferred form of the Covered
+Code for making modifications to it, including all modules it contains,
+plus any associated interface definition files, scripts used to control
+compilation and installation of an Executable, or source code
+differential comparisons against either the Original Code or another
+well known, available Covered Code of the Contributor's choice. The
+Source Code can be in a compressed or archival form, provided the
+appropriate decompression or de-archiving software is widely available
+for no charge.
+
+
1.12. "You" (or "Your") means an individual or a legal
+entity exercising rights under, and complying with all of the terms of,
+this License or a future version of this License issued under Section
+6.1. For legal entities, "You" includes any entity which controls, is
+controlled by, or is under common control with You. For purposes of this
+definition, "control" means (a) the power, direct or indirect, to cause
+the direction or management of such entity, whether by contract or
+otherwise, or (b) ownership of more than fifty percent (50%) of the
+outstanding shares or beneficial ownership of such
+entity.
+
+
+
2. Source Code License.
+
+
2.1. The Initial Developer Grant.
+
+
The Initial Developer hereby grants You a world-wide, royalty-free,
+non-exclusive license, subject to third party intellectual property
+claims:
+
+
(a) under intellectual property rights (other than patent or
+trademark) Licensable by Initial Developer to use, reproduce, modify,
+display, perform, sublicense and distribute the Original Code (or
+portions thereof) with or without Modifications, and/or as part of a
+Larger Work; and
+
+
(b) under Patents Claims infringed by the making, using or
+selling of Original Code, to make, have made, use, practice, sell, and
+offer for sale, and/or otherwise dispose of the Original Code (or
+portions thereof).
+
+
(c) the licenses granted in this Section 2.1(a) and (b) are
+effective on the date Initial Developer first distributes Original Code
+under the terms of this License.
+
+
(d) Notwithstanding Section 2.1(b) above, no patent license is
+granted: 1) for code that You delete from the Original Code; 2) separate
+from the Original Code; or 3) for infringements caused by: i) the
+modification of the Original Code or ii) the combination of the Original
+Code with other software or devices.
+
+
2.2. Contributor Grant.
+
+
Subject to third party intellectual property claims, each Contributor
+hereby grants You a world-wide, royalty-free, non-exclusive license
+
+
(a) under intellectual property rights (other than patent or
+trademark) Licensable by Contributor, to use, reproduce, modify,
+display, perform, sublicense and distribute the Modifications created by
+such Contributor (or portions thereof) either on an unmodified basis,
+with other Modifications, as Covered Code and/or as part of a Larger
+Work; and
+
+
(b) under Patent Claims infringed by the making, using, or
+selling of Modifications made by that Contributor either alone and/or in
+combination with its Contributor Version (or portions of such
+combination), to make, use, sell, offer for sale, have made, and/or
+otherwise dispose of: 1) Modifications made by that Contributor (or
+portions thereof); and 2) the combination of Modifications made by that
+Contributor with its Contributor Version (or portions of such
+combination).
+
+
(c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+effective on the date Contributor first makes Commercial Use of the
+Covered Code.
+
+
(d) Notwithstanding Section 2.2(b) above, no patent
+license is granted: 1) for any code that Contributor has deleted from
+the Contributor Version; 2) separate from the Contributor Version; 3)
+for infringements caused by: i) third party modifications of Contributor
+Version or ii) the combination of Modifications made by that Contributor
+with other software (except as part of the Contributor Version) or other
+devices; or 4) under Patent Claims infringed by Covered Code in the
+absence of Modifications made by that Contributor.
+
+
+
3. Distribution Obligations
+
+
3.1. Application of License.
+
+
The Modifications which You create or to which You contribute are
+governed by the terms of this License, including without limitation
+Section 2.2. The Source Code version of Covered Code may be
+distributed only under the terms of this License or a future version of
+this License released under Section 6.1, and You must include a
+copy of this License with every copy of the Source Code You
+distribute. You may not offer or impose any terms on any Source Code
+version that alters or restricts the applicable version of this License
+or the recipients' rights hereunder. However, You may include an
+additional document offering the additional rights described in Section
+3.5.
+
+
3.2. Availability of Source Code.
+
+
Any Modification which You create or to which You contribute must be
+made available in Source Code form under the terms of this License
+either on the same media as an Executable version or via an accepted
+Electronic Distribution Mechanism to anyone to whom you made an
+Executable version available; and if made available via Electronic
+Distribution Mechanism, must remain available for at least twelve (12)
+months after the date it initially became available, or at least six (6)
+months after a subsequent version of that particular Modification has
+been made available to such recipients. You are responsible for ensuring
+that the Source Code version remains available even if the Electronic
+Distribution Mechanism is maintained by a third party.
+
+
3.3. Description of Modifications.
+
+
You must cause all Covered Code to which You contribute to contain a
+file documenting the changes You made to create that Covered Code and
+the date of any change. You must include a prominent statement that the
+Modification is derived, directly or indirectly, from Original Code
+provided by the Initial Developer and including the name of the Initial
+Developer in (a) the Source Code, and (b) in any notice in an Executable
+version or related documentation in which You describe the origin or
+ownership of the Covered Code.
+
+
3.4. Intellectual Property Matters
+
+
(a) Third Party Claims.
+
+
If Contributor has knowledge that a license under a third party's
+intellectual property rights is required to exercise the rights granted
+by such Contributor under Sections 2.1 or 2.2, Contributor must include
+a text file with the Source Code distribution titled "LEGAL" which
+describes the claim and the party making the claim in sufficient detail
+that a recipient will know whom to contact. If Contributor obtains such
+knowledge after the Modification is made available as described in
+Section 3.2, Contributor shall promptly modify the LEGAL file in all
+copies Contributor makes available thereafter and shall take other steps
+(such as notifying appropriate mailing lists or newsgroups) reasonably
+calculated to inform those who received the Covered Code that new
+knowledge has been obtained.
+
+
(b) Contributor APIs.
+
+
If Contributor's Modifications include an application programming
+interface and Contributor has knowledge of patent licenses which are
+reasonably necessary to implement that API, Contributor must also
+include this information in the LEGAL file.
+
+
(c) Representations.
+
+
Contributor represents that, except
+as disclosed pursuant to Section 3.4(a) above, Contributor believes that
+Contributor's Modifications are Contributor's original creation(s)
+and/or Contributor has sufficient rights to grant the rights conveyed by
+this License.
+
+
3.5. Required Notices.
+
+
You must duplicate the notice in Exhibit A in each file of the
+Source Code. If it is not possible to put such notice in a
+particular Source Code file due to its structure, then You must include
+such notice in a location (such as a relevant directory) where a user
+would be likely to look for such a notice. If You created one or
+more Modification(s) You may add your name as a Contributor to the
+notice described in Exhibit A. You must also duplicate this
+License in any documentation for the Source Code where You describe
+recipients' rights or ownership rights relating to Covered Code.
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Code. However, You may do so only on Your own behalf, and not on behalf
+of the Initial Developer or any Contributor. You must make it absolutely
+clear than any such warranty, support, indemnity or liability obligation
+is offered by You alone, and You hereby agree to indemnify the Initial
+Developer and every Contributor for any liability incurred by the
+Initial Developer or such Contributor as a result of warranty, support,
+indemnity or liability terms You offer.
+
+
3.6. Distribution of Executable Versions.
+
+
You may distribute Covered Code in Executable form only if the
+requirements of Section 3.1-3.5 have been met for that Covered
+Code, and if You include a notice stating that the Source Code version
+of the Covered Code is available under the terms of this License,
+including a description of how and where You have fulfilled the
+obligations of Section 3.2. The notice must be conspicuously
+included in any notice in an Executable version, related documentation
+or collateral in which You describe recipients' rights relating to the
+Covered Code. You may distribute the Executable version of Covered Code
+or ownership rights under a license of Your choice, which may contain
+terms different from this License, provided that You are in compliance
+with the terms of this License and that the license for the Executable
+version does not attempt to limit or alter the recipient's rights in the
+Source Code version from the rights set forth in this License. If You
+distribute the Executable version under a different license You must
+make it absolutely clear that any terms which differ from this License
+are offered by You alone, not by the Initial Developer or any
+Contributor. You hereby agree to indemnify the Initial Developer and
+every Contributor for any liability incurred by the Initial Developer or
+such Contributor as a result of any such terms You offer.
+
+
3.7. Larger Works.
+
+
You may create a Larger Work by combining Covered Code with other code not
+governed by the terms of this License and distribute the Larger Work as a
+single product. In such a case, You must make sure the requirements of this
+License are fulfilled for the Covered Code.
+
+
4. Inability to Comply Due to Statute or Regulation.
+
+
If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Code due to statute,
+judicial order, or regulation then You must: (a) comply with the terms
+of this License to the maximum extent possible; and (b) describe the
+limitations and the code they affect. Such description must be included
+in the LEGAL file described in Section 3.4 and must be included
+with all distributions of the Source Code. Except to the extent
+prohibited by statute or regulation, such description must be
+sufficiently detailed for a recipient of ordinary skill to be able to
+understand it.
+
+
+
5. Application of this License.
+
+
This License applies to code to which the Initial Developer has
+attached the notice in Exhibit A and to related Covered Code.
+
+
+
6. Versions of the License.
+
+
6.1. New Versions
+
+
The Distributed Systems Technology Centre ("DSTC") may publish
+revised and/or new versions of the License from time to time. Each
+version will be given a distinguishing version number.
+
+
6.2. Effect of New Versions
+
+
Once Covered Code has been published under a particular version of
+the License, You may always continue to use it under the terms of that
+version. You may also choose to use such Covered Code under the terms of
+any subsequent version of the License published by DSTC. No one other
+than DSTC has the right to modify the terms applicable to Covered Code
+created under this License.
+
+
6.3. Derivative Works
+
+
If You create or use a modified version of this License (which you
+may only do in order to apply it to code which is not already Covered
+Code governed by this License), You must (a) rename Your license so that
+the phrases "DSTC", "DPL" or any confusingly similar phrase do not
+appear in your license (except to note that your license differs from
+this License) and (b) otherwise make it clear that Your version of the
+license contains terms which differ from the DSTC Public
+License. (Filling in the name of the Initial Developer, Original Code or
+Contributor in the notice described in Exhibit A shall not of
+themselves be deemed to be modifications of this License.)
+
+
7. Disclaimer of Warranty.
+
+
COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS,
+MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE
+RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE IS WITH
+YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
+THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY
+NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY
+CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED
+CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+
+
8. Termination.
+
+
8.1. This License and the rights granted hereunder will
+terminate automatically if You fail to comply with terms herein and fail
+to cure such breach within 30 days of becoming aware of the breach. All
+sublicenses to the Covered Code which are properly granted shall survive
+any termination of this License. Provisions which, by their nature, must
+remain in effect beyond the termination of this License shall
+survive.
+
+
8.2.
+If You initiate
+litigation by asserting a patent infringement claim (excluding declatory
+judgment actions) against Initial Developer or a Contributor (the Initial
+Developer or Contributor against whom You file such action is referred to as
+'Participant') alleging that:
+
+
(a) such Participant's Contributor Version directly or
+indirectly infringes any patent, then any and all rights granted by such
+Participant to You under Sections 2.1 and/or 2.2 of this License shall,
+upon 60 days notice from Participant terminate prospectively, unless if
+within 60 days after receipt of notice You either: (i) agree in writing
+to pay Participant a mutually agreeable reasonable royalty for Your past
+and future use of Modifications made by such Participant, or (ii)
+withdraw Your litigation claim with respect to the Contributor Version
+against such Participant. If within 60 days of notice, a reasonable
+royalty and payment arrangement are not mutually agreed upon in writing
+by the parties or the litigation claim is not withdrawn, the rights
+granted by Participant to You under Sections 2.1 and/or 2.2
+automatically terminate at the expiration of the 60 day notice period
+specified above.
+
+
(b) any software, hardware, or device, other than such
+Participant's Contributor Version, directly or indirectly infringes any
+patent, then any rights granted to You by such Participant under
+Sections 2.1(b) and 2.2(b) are revoked effective as of the date You
+first made, used, sold, distributed, or had made, Modifications made by
+that Participant.
+
+
8.3.
+If You assert a patent
+infringement claim against Participant alleging that such Participant's
+Contributor Version directly or indirectly infringes any patent where such
+claim is resolved (such as by license or settlement) prior to the initiation of
+patent infringement litigation, then the reasonable value of the licenses
+granted by such Participant under Sections 2.1 or 2.2 shall be taken into
+account in determining the amount or value of any payment or license.
+
+
8.4. In the event of termination under
+Sections 8.1 or 8.2 above, all end user license agreements (excluding
+distributors and resellers) which have been validly granted by You or any
+distributor hereunder prior to termination shall survive termination.
+
+
9. Limitation of Liability.
+
+
UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, OR
+ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY
+INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER
+INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK
+STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
+OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION
+AND LIMITATION MAY NOT APPLY TO YOU.
+
+
10. U.S. Government End Users.
+
+
The Covered Code is a "commercial item," as that term is defined in
+48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+software" and "commercial computer software documentation," as such
+terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+all U.S. Government End Users acquire Covered Code with only those
+rights set forth herein.
+
+
+
11. Miscellaneous.
+
+
This License represents the complete agreement concerning subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. This License shall be governed by
+Queensland, Australia law provisions (except to the extent applicable
+law, if any, provides otherwise), excluding its conflict-of-law
+provisions. With respect to disputes in which at least one party is a
+citizen of, or an entity chartered or registered to do business in
+Australia, any litigation relating to this License shall be subject to
+the jurisdiction of Australian Courts, with the losing party responsible
+for costs, including without limitation, court costs and reasonable
+attorneys' fees and expenses. The application of the United Nations
+Convention on Contracts for the International Sale of Goods is expressly
+excluded. Any law or regulation which provides that the language of a
+contract shall be construed against the drafter shall not apply to this
+License.
+
+
+
12. Responsibility for Claims.
+
+
As between Initial Developer and the Contributors, each party is
+responsible for claims and damages arising, directly or indirectly, out
+of its utilization of rights under this License and You agree to work
+with Initial Developer and Contributors to distribute such
+responsibility on an equitable basis. Nothing herein is intended or
+shall be deemed to constitute any admission of liability.
+
+
+
13. Multiple-licensed Code.
+
+
Initial Developer may designate portions of the Covered Code as
+"Multiple-Licensed". "Multiple-Licensed" means that the Initial
+Developer permits you to utilize portions of the Covered Code under Your
+choice of the DPL or the alternative licenses, if any, specified by the
+Initial Developer in the file described in Exhibit A.
+
+
+
14. High Risk Activities.
+
+
The Software is not fault-tolerant and is not designed, manufactured
+or intended for use or resale as on-line control equipment in hazardous
+environments requiring fail-safe performance, such as in the operation
+of nuclear facilities, aircraft navigation or communication systems, air
+traffic control, direct life support machines, or weapons systems, in
+which the failure of the Software could lead directly to death, personal
+injury, or severe physical or environmental damage ("High Risk
+Activities").
+
+
+
EXHIBIT A - DSTC Public License.
+
+
The contents of this file are subject to the DSTC Public License
+Version 1.1 (the 'License'); you may not use this file except in
+compliance with the License.
+
+
Software distributed under the License is distributed on an 'AS IS'
+basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+License for the specific language governing rights and limitations under
+the License.
+
+
The Original Code is ______________________________________.
Alternatively, the contents of this file may be used under the terms
+of the _____ license (the "[___] License"), in which case the provisions
+of [______] License are applicable instead of those above. If you wish
+to allow use of your version of this file only under the terms of the
+[____] License and not to allow others to use your version of this file
+under the DPL, indicate your decision by deleting the provisions above
+and replace them with the notice and other provisions required by the
+[___] License. If you do not delete the provisions above, a recipient
+may use your version of this file under either the DPL or the [___]
+License.'
+
+
[NOTE: The text of this Exhibit A may differ slightly from the text
+of the notices in the Source Code files of the Original Code. You should
+use the text of this Exhibit A rather than the text found in the
+Original Code Source Code for Your Modifications.]
+
+
+
+
+
diff --git a/tools/xs3p/README.md b/tools/xs3p/README.md
new file mode 100644
index 0000000..455bef6
--- /dev/null
+++ b/tools/xs3p/README.md
@@ -0,0 +1,20 @@
+# xs3p — vendored XSD → HTML documentation stylesheet
+
+`xs3p.xsl` renders an XML Schema (XSD) into a single self-contained HTML reference. We run it
+through `lxml`'s XSLT engine (`make gen-schema-docs`) so the schema reference is produced by an
+off-the-shelf tool rather than hand-rolled XSD-walking code ("configure, don't create").
+
+## Provenance
+- Source: (maintained fork of the original DSTC xs3p).
+- Vendored file: `xs3p.xsl` (unmodified).
+
+## License — note
+`xs3p.xsl` is licensed under the **DSTC Public License (DPL) v1.1** (an MPL-1.1-derived,
+file-level weak-copyleft license) — see `LICENSE.html`. This is a *different* license from this
+repository's own. It permits use, modification and distribution provided the license travels with
+the file; if `xs3p.xsl` is ever modified, the DPL requires those modifications be made available.
+We keep the file unmodified to avoid that obligation.
+
+## Determinism
+xs3p output is byte-deterministic for a given input schema (no embedded timestamp), so the
+generated HTML is committed and the CI drift gate can assert it matches a fresh regeneration.
diff --git a/tools/xs3p/xs3p.xsl b/tools/xs3p/xs3p.xsl
new file mode 100644
index 0000000..d596d11
--- /dev/null
+++ b/tools/xs3p/xs3p.xsl
@@ -0,0 +1,8354 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+ https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js
+
+
+ https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6
+
+
+
+
+ http://www.w3.org/2001/XMLSchema
+
+
+ http://www.w3.org/XML/1998/namespace
+
+
+ 3
+
+
+ 1
+
+
+ XML Schema Documentation
+
+
+
+ type_
+
+ attribute_
+
+ attributeGroup_
+
+
+
+ element_
+
+ key_
+
+ group_
+
+ notation_
+
+ ns_
+
+
+
+ term_
+
+
+
+
+
+
+ This table shows the schema components type hierarchy.
+
+ This table displays the properties of the schema component.
+
+ This panel contains the schema components documentation.
+
+
+ The XML Instance Representation table shows the schema component's content as an XML instance.
+ <ul>
+ <li>The minimum and maximum occurrence of elements and attributes are provided in square brackets, e.g. [0..1].</li>
+ <li>Model group information are shown in gray, e.g. Start Choice ... End Choice.</li>
+ <li>For type derivations, the elements and attributes that have been added to or changed from the base type's content are shown in <strong>bold</strong></li>
+ <li>If an element/attribute has a fixed value, the fixed value is shown in green.</li>
+ <li>More stuff</li>
+ <li>If a local element/attribute has documentation, it will be displayed in a window that pops up when the question mark inside the attribute or next to the element is clicked.</li>
+ </ul>
+
+
+
+ The Schema Component Representation table above displays the underlying XML representation of the schema component. (Annotations are not shown.)
+
+
+
+
+
+
+
+
+ true
+
+'linksFile' variable must be provided if either
+'searchIncludedSchemas' or 'searchImportedSchemas' is true.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/* XS3P specific CSS */
+body {
+ background-color: #FFF;
+ padding-top: 50px;
+}
+
+.nav > li.active {
+ background-color: #FFF;
+}
+.nav > li > a:hover {
+ background-color: #CCC;
+}
+
+code {
+ color: #333;
+}
+
+.container-fluid {
+ padding: 15px 15px;
+}
+
+.nav-sub-item > a {
+ padding-left: 30px !important;
+}
+
+a.name {
+ padding-top: 65px;
+}
+
+h3.xs3p-subsection-heading {
+ margin-bottom: 30px;
+}
+
+section, #top {
+ margin-top: -65px;
+ padding-top: 65px;
+}
+
+pre {
+ padding: 5px;
+}
+
+.xs3p-sidenav {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ background-color: #EEE;
+ border-radius: 10px;
+}
+.xs3p-navbar-title {
+ color: #FFF !important;
+ font-weight: bold;
+}
+.xs3p-in-panel-table {
+ margin-bottom: 0px;
+}
+.xs3p-sidebar {
+ position: static;
+}
+.xs3p-collapse-button {
+ font-size: 8pt;
+}
+.panel-heading .xs3p-panel-title:after {
+ font-family: 'Glyphicons Halflings';
+ content: "\e114";
+ float: left;
+ color: grey;
+ margin-right: 10px;
+}
+.panel-heading .xs3p-panel-title.collapsed:after {
+ content: "\e080";
+}
+.panel-info > .panel-heading .xs3p-panel-title:after {
+ color: white;
+}
+.xs3p-panel-help {
+ color: #CCCCCC;
+ cursor: pointer;
+}
+
+.panel-group {
+ margin-bottom: 20px;
+}
+
+.btn-doc {
+ padding: 0px;
+ border: 0px none;
+ background: none repeat scroll 0% 0% transparent;
+ line-height: 1;
+ font-size: 12px;
+}
+
+.unpre {
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 14px;
+ white-space: normal;
+ word-break: normal;
+ word-wrap: normal;
+}
+
+.popover {
+ max-width: 400px;
+}
+
+// Syntax highlighting
+.codehilite .err {color: #FFF; background-color: #D2322D; font-weight: bold;} /* Error */
+.codehilite .c {color: #999;}
+.codehilite .cs {color: #999; font-style: italic;}
+.codehilite .nt {color: #2F6F9F;}
+.codehilite .nn {color: #39B3D7;}
+.codehilite .na {color: #47A447;}
+.codehilite .s {color: #D2322D;}
+.codehilite a {color: inherit !important; text-decoration: underline !important;}
+.codehilite a:hover {opacity: 0.7 !important;}
+
+@media (min-width: 992px) {
+ .xs3p-sidebar {
+ position: fixed;
+ top: 65px;
+ width: 22%;
+ }
+}
+
+
+
+
+
+
+ Abstract
+ Abstract
+
+ (Applies to complex type definitions and element declarations).
+ An abstract element or complex type cannot used to validate an element instance.
+ If there is a reference to an abstract element, only element declarations that can substitute the abstract element can be used to validate the instance.
+ For references to abstract type definitions, only derived types can be used.
+
+
+
+
+ All
+ All Model Group
+
+ Child elements can be provided
+
+ in any order
+
+ in instances.
+
+ http://www.w3.org/TR/xmlschema-1/#element-all
+
+
+
+ Choice
+ Choice Model Group
+
+
+ Only one
+
+ from the list of child elements and model groups can be provided in instances.
+
+ http://www.w3.org/TR/xmlschema-1/#element-choice
+
+
+
+ CollapseWS
+ Collapse Whitespace Policy
+ Replace tab, line feed, and carriage return characters with space character (Unicode character 32). Then, collapse contiguous sequences of space characters into single space character, and remove leading and trailing space characters.
+
+
+
+ ElemBlock
+ Disallowed Substitutions
+
+ (Applies to element declarations).
+ If
+ substitution
+ is specified, then
+
+ SubGroup
+ substitution group
+
+ members cannot be used in place of the given element declaration to validate element instances.
+
+ If
+ derivation methods
+ , e.g. extension, restriction, are specified, then the given element declaration will not validate element instances that have types derived from the element declaration's type using the specified derivation methods.
+ Normally, element instances can override their declaration's type by specifying an
+ xsi:type
+ attribute.
+
+
+
+
+ Key
+ Key Constraint
+
+ Like
+
+ Unique
+ Uniqueness Constraint
+
+ , but additionally requires that the specified value(s) must be provided.
+
+ http://www.w3.org/TR/xmlschema-1/#cIdentity-constraint_Definitions
+
+
+
+ KeyRef
+ Key Reference Constraint
+
+ Ensures that the specified value(s) must match value(s) from a
+
+ Key
+ Key Constraint
+
+ or
+
+ Unique
+ Uniqueness Constraint
+
+ .
+
+ http://www.w3.org/TR/xmlschema-1/#cIdentity-constraint_Definitions
+
+
+
+ ModelGroup
+ Model Group
+
+ Groups together element content, specifying the order in which the element content can occur and the number of times the group of element content may be repeated.
+
+ http://www.w3.org/TR/xmlschema-1/#Model_Groups
+
+
+
+ Nillable
+ Nillable
+
+ (Applies to element declarations).
+ If an element declaration is nillable, instances can use the
+ xsi:nil
+ attribute.
+ The
+ xsi:nil
+ attribute is the boolean attribute,
+ nil
+ , from the
+ http://www.w3.org/2001/XMLSchema-instance
+ namespace.
+ If an element instance has an
+ xsi:nil
+ attribute set to true, it can be left empty, even though its element declaration may have required content.
+
+
+
+
+ Notation
+ Notation
+ A notation is used to identify the format of a piece of data. Values of elements and attributes that are of type, NOTATION, must come from the names of declared notations.
+ http://www.w3.org/TR/xmlschema-1/#cNotation_Declarations
+
+
+
+ PreserveWS
+ Preserve Whitespace Policy
+ Preserve whitespaces exactly as they appear in instances.
+
+
+
+ TypeFinal
+ Prohibited Derivations
+
+ (Applies to type definitions).
+ Derivation methods that cannot be used to create sub-types from a given type definition.
+
+
+
+
+ TypeBlock
+ Prohibited Substitutions
+
+ (Applies to complex type definitions).
+ Prevents sub-types that have been derived using the specified derivation methods from validating element instances in place of the given type definition.
+
+
+
+
+ ReplaceWS
+ Replace Whitespace Policy
+ Replace tab, line feed, and carriage return characters with space character (Unicode character 32).
+
+
+
+ Sequence
+ Sequence Model Group
+
+ Child elements and model groups must be provided
+
+ in the specified order
+
+ in instances.
+
+ http://www.w3.org/TR/xmlschema-1/#element-sequence
+
+
+
+ SubGroup
+ Substitution Group
+
+ Elements that are
+
+ members
+
+ of a substitution group can be used wherever the
+
+ head
+
+ element of the substitution group is referenced.
+
+
+
+
+ ElemFinal
+ Substitution Group Exclusions
+
+ (Applies to element declarations).
+ Prohibits element declarations from nominating themselves as being able to substitute a given element declaration, if they have types that are derived from the original element's type using the specified derivation methods.
+
+
+
+
+ TargetNS
+ Target Namespace
+ The target namespace identifies the namespace that components in this schema belongs to. If no target namespace is provided, then the schema components do not belong to any namespace.
+
+
+
+ Unique
+ Uniqueness Constraint
+ Ensures uniqueness of an element/attribute value, or a combination of values, within a specified scope.
+ http://www.w3.org/TR/xmlschema-1/#cIdentity-constraint_Definitions
+
+
+
+
+
+
+
+
+
+
+
Global element and attribute declarations belong to this schema's target namespace.
+
+
+
+ By default, local element declarations belong to this schema's target namespace.
+
+
+ By default, local element declarations have no namespace.
+
+
+
+
+
+
+ By default, local attribute declarations belong to this schema's target namespace.
+
+
+ By default, local attribute declarations have no namespace.
+
+
+
+
+
+
+
+
+
+
Schema Composition
+
+
+
+
+
+ This schema imports schema(s) from the following namespace(s):
+
+
+
+
+
+ (at
+
+
+
+ )
+
+
+
+
+
+
+
+
+
+ This schema includes components from the following schema document(s):
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This schema includes components from the following schema document(s), where some of the components have been redefined:
+