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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Normalise line endings to LF in the working tree on every platform.
#
# Several drift gates byte-compare a committed, generated artifact against a fresh render
# (e.g. tests/integration/test_schema_html.py reads docs/reference/schema/index.html and compares
# it to schema_html.render(), which emits LF). With core.autocrlf=true on Windows, a checkout
# would rewrite those files with CRLF and break the compare — even though CI (LF) stays green.
# Pinning eol=lf keeps the working copy byte-identical to what the generators produce.
* text=auto eol=lf

# Belt-and-braces for the byte-reproducible generated artifacts the drift gates check.
*.html text eol=lf
*.xsd text eol=lf
*.xml text eol=lf
*.py text eol=lf
11 changes: 9 additions & 2 deletions docs/concepts/pipeline-data-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,26 @@

```mermaid
flowchart TD
Input["Calculation input<br/>(examples/calculation_input.json)"]
Build["build.py<br/>(acoustic seams compute &<br/>populate the schema object)"]
Input["Calculation input XML<br/>(examples/calculation_input.xml)"]
Build["build.py<br/>(parse + validate input, acoustic seams<br/>compute & populate the schema object)"]
Objects["Schema data object<br/>(Platform, generated from XSD)"]
XML["Emitted Platform XML"]
Golden["Golden file"]
Reference["Known-good reference"]

Input -. "input gate" .-> Input
Input --> Build --> Objects --> XML
Objects -. "tests assert here" .-> Golden
XML -. "structural gate" .-> XML
XML -. "migration safety" .-> Reference
```

The input is held to its own contract too: `examples/calculation_input.xml` is a compact set of
**parameters** validated against `schema/calculation_input.xsd` (and parsed into a typed
`CalculationInput` model, generated from that schema exactly as `Platform` is generated from the
output schema). The acoustic seams *expand* those parameters into the many bands and sectors of
the output — the input and output schemas are deliberately different shapes.

The acoustic seams compute the values and the builder populates **one schema data object**
directly — there is no intermediate domain hierarchy built only to be converted (ADR 0010).
That object, before serialisation, is the **typed testable boundary**: tests assert on it
Expand Down
10 changes: 6 additions & 4 deletions docs/concepts/typed-vs-dicts.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ typed, and documented by the contract, so values never have to live in a loosely
```python
from acoustic_dataset import build

input_path = "examples/calculation_input.json"
input_path = "examples/calculation_input.xml"

# Build the schema's data object directly from the input:
platform = build.build_platform_from_file(input_path)
Expand Down Expand Up @@ -91,13 +91,15 @@ schema object, and it also enforces the schema's numeric **ranges** at that poin
does not meet the schema is rejected before serialisation:

```python
from acoustic_dataset import acoustics, build
from decimal import Decimal

from acoustic_dataset import build

data = acoustics.load_input("examples/calculation_input.json")
data = build.load_input("examples/calculation_input.xml")

# Decibels are bounded to [-200, 300]. Force an impossible
# source level into the input, then build the schema object:
data["sensors"]["active"]["sourceLevelDb"] = 9999.0
data.sensors.active_sonar.active_source_level_db.value = Decimal("9999")
build.build_platform(data)
# -> MappingError: rejected as it is built,
# not left for a later stage
Expand Down
30 changes: 19 additions & 11 deletions docs/how-to/change-the-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,28 @@
The schema is the single source of truth, so changing it is a *configuration change, not a
redesign*: models, validation, bindings and the schema docs all regenerate from it.

There are **two** schemas, regenerated together by `make generate`: `schema/acoustic_dataset.xsd`
(the output dataset) and `schema/calculation_input.xsd` (the calculation parameters). They are
different shapes — the builder *expands* the input parameters into the output dataset — so an
output change usually means touching the output schema, the builder, and possibly the input
schema if a new parameter is needed.

## Steps

1. **Edit the schema.**
Change `schema/acoustic_dataset.xsd` (the contract). When you add or rename a type/element,
put its definition prose in `xs:annotation/xs:documentation` so it rides through to the
generated model docstrings and the schema reference.
1. **Edit the schema(s).**
Change `schema/acoustic_dataset.xsd` and/or `schema/calculation_input.xsd` (the contracts).
When you add or rename a type/element, put its definition prose in
`xs:annotation/xs:documentation` so it rides through to the generated model docstrings and the
schema reference.

2. **Regenerate the models.**
```bash
make generate
```
Runs `xsdata` over the schema and rewrites `src/acoustic_dataset/models/`. **Do not
hand-edit** the result — it's a generated artifact
(ADR 0008). Generation is pinned to the 3.9
toolchain so the output is byte-reproducible for the drift gate.
Runs `xsdata` over both schemas and rewrites `src/acoustic_dataset/models/` (output) and
`src/acoustic_dataset/input_models/` (input). **Do not hand-edit** the result — it's a
generated artifact (ADR 0008). Generation is pinned to the 3.9 toolchain so the output is
byte-reproducible for the drift gate.

3. **Regenerate the schema reference.**
```bash
Expand All @@ -33,8 +40,8 @@ redesign*: models, validation, bindings and the schema docs all regenerate from
for any added/renamed/retyped fields. Generation code does *not* change.

5. **Update the example and golden file.**
Adjust `examples/calculation_input.json`, then refresh the golden file if the new output is
intended:
Adjust `examples/calculation_input.xml` (it must stay valid against
`schema/calculation_input.xsd`), then refresh the golden file if the new output is intended:
```bash
make pipeline # writes build/acoustic_dataset.xml
cp build/acoustic_dataset.xml tests/golden/acoustic_dataset.xml
Expand Down Expand Up @@ -63,6 +70,7 @@ 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 models (`src/acoustic_dataset/models/`, `src/acoustic_dataset/input_models/`) —
regenerate instead.
- Generated HTML schema reference under `reference/schema/` — regenerate instead.
- The generation code — it's schema-agnostic by design.
5 changes: 3 additions & 2 deletions docs/onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ before the same `make verify` / `make pipeline`. Both paths reach the *same* gre
| You're looking for… | It's here |
|---|---|
| The contract (the XSD) | `schema/acoustic_dataset.xsd` ([how to change it](how-to/change-the-schema.md)) |
| The input contract (calculation parameters) | `schema/calculation_input.xsd` |
| The scientific seams (named, testable calc functions) | `src/acoustic_dataset/acoustics/` |
| The **one** place the schema object is built | `src/acoustic_dataset/build.py` |
| Generated models (never hand-edited — regenerate) | `src/acoustic_dataset/models/` |
| Example calculation input | `examples/calculation_input.json` |
| Generated models (never hand-edited — regenerate) | `src/acoustic_dataset/models/` (output), `src/acoustic_dataset/input_models/` (input) |
| Example calculation input | `examples/calculation_input.xml` |
| Tests (unit / integration / golden) | `tests/` |
| The plan & design artifacts | `specs/001-codespace-xml-scaffold/` (`spec.md`, `plan.md`, `tasks.md`) |
| The generated HTML schema reference | [reference/schema](reference/schema/index.html) (run `make gen-schema-docs`) |
Expand Down
52 changes: 0 additions & 52 deletions examples/calculation_input.json

This file was deleted.

56 changes: 56 additions & 0 deletions examples/calculation_input.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
PLACEHOLDER example calculation input (illustrative values, not real platform data).
Conforms to schema/calculation_input.xsd. The pipeline expands these parameters into the
validated Platform dataset at build/acoustic_dataset.xml.
-->
<CalculationInput>
<SchemaVersion>0.2.0</SchemaVersion>
<Name>Reference Platform A</Name>
<GeneratedUtc>2026-06-14T00:00:00Z</GeneratedUtc>
<Characteristics>
<DraftMetres>7.5</DraftMetres>
<LengthMetres>95.0</LengthMetres>
<WeightTonnes>2400.0</WeightTonnes>
<YearIntroduced>1998</YearIntroduced>
</Characteristics>
<RadiatedNoise>
<BaseFrequencyHz>50.0</BaseFrequencyHz>
<BandRatio>2.0</BandRatio>
<BandCount>10</BandCount>
<BearingStepDeg>30.0</BearingStepDeg>
<BaseLevelDb>140.0</BaseLevelDb>
<RolloffDbPerOctave>5.0</RolloffDbPerOctave>
<Directivity>
<PeakBearingDeg>180.0</PeakBearingDeg>
<AmplitudeDb>6.0</AmplitudeDb>
</Directivity>
</RadiatedNoise>
<Sensors>
<ActiveSonar>
<ActiveName>AS-900 Echo</ActiveName>
<ActiveManufacturer>DeepBlue Sonics</ActiveManufacturer>
<ActiveOperatingFrequencyHz>6000.0</ActiveOperatingFrequencyHz>
<ActiveSourceLevelDb>215.0</ActiveSourceLevelDb>
<ActiveBeamwidthDeg>15.0</ActiveBeamwidthDeg>
<ActivePulseLengthSeconds>0.5</ActivePulseLengthSeconds>
<ActiveDetectionThresholdDb>12.0</ActiveDetectionThresholdDb>
</ActiveSonar>
<PassiveSonar>
<PassiveName>PA-110 Flank Array</PassiveName>
<PassiveManufacturer>DeepBlue Sonics</PassiveManufacturer>
<PassiveOperatingFrequencyHz>1500.0</PassiveOperatingFrequencyHz>
<PassiveArrayGainDb>18.0</PassiveArrayGainDb>
<PassiveDetectionThresholdDb>10.0</PassiveDetectionThresholdDb>
<PassiveBearingAccuracyDeg>1.5</PassiveBearingAccuracyDeg>
</PassiveSonar>
<PassiveSonar>
<PassiveName>PA-220 Towed Array</PassiveName>
<PassiveManufacturer>Marine Acoustics Ltd</PassiveManufacturer>
<PassiveOperatingFrequencyHz>300.0</PassiveOperatingFrequencyHz>
<PassiveArrayGainDb>22.0</PassiveArrayGainDb>
<PassiveDetectionThresholdDb>8.0</PassiveDetectionThresholdDb>
<PassiveBearingAccuracyDeg>2.0</PassiveBearingAccuracyDeg>
</PassiveSonar>
</Sensors>
</CalculationInput>
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,16 @@ select = ["E", "F", "I", "UP", "B"]
[tool.ruff.lint.per-file-ignores]
# Generated bindings are artifacts, never hand-edited — do not lint them.
"src/acoustic_dataset/models/*" = ["ALL"]
"src/acoustic_dataset/input_models/*" = ["ALL"]

[tool.mypy]
python_version = "3.9"
# Check the hand-written source by path (not as an installed package — no py.typed needed),
# so a bare `mypy` in `make verify` works.
files = ["src/acoustic_dataset"]
ignore_missing_imports = true
# Generated models are excluded from type-checking (they are an artifact).
exclude = "src/acoustic_dataset/models/"
# Generated bindings (output + input) are excluded from type-checking (they are artifacts).
exclude = "src/acoustic_dataset/(models|input_models)/"

[tool.pytest.ini_options]
testpaths = ["tests"]
Expand Down
Loading
Loading