oapi-profile-builder 3.0.1__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {oapi_profile_builder-3.0.1/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.2.0}/PKG-INFO +250 -10
  2. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/README.md +247 -9
  3. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/pyproject.toml +2 -1
  4. oapi_profile_builder-3.2.0/src/oapi_profile_builder/cover.py +176 -0
  5. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder/generate.py +351 -36
  6. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder/models.py +377 -10
  7. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0/src/oapi_profile_builder.egg-info}/PKG-INFO +250 -10
  8. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder.egg-info/SOURCES.txt +1 -0
  9. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder.egg-info/requires.txt +3 -0
  10. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/LICENSE +0 -0
  11. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/setup.cfg +0 -0
  12. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder/__init__.py +0 -0
  13. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder/cite.py +0 -0
  14. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder/cite_features.py +0 -0
  15. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder/cli.py +0 -0
  16. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder/compile.py +0 -0
  17. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder/server_validation.py +0 -0
  18. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
  19. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
  20. {oapi_profile_builder-3.0.1 → oapi_profile_builder-3.2.0}/src/oapi_profile_builder.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oapi-profile-builder
3
- Version: 3.0.1
3
+ Version: 3.2.0
4
4
  Summary: Authoritative tooling for creating OGC API Service Profiles (EDR, Features)
5
5
  Author-email: Shane Mill <shane.mill@noaa.gov>
6
6
  License: Apache License
@@ -41,11 +41,13 @@ Requires-Dist: pyyaml>=6.0
41
41
  Requires-Dist: requests>=2.31
42
42
  Provides-Extra: validate
43
43
  Requires-Dist: schemathesis>=3.0; extra == "validate"
44
+ Provides-Extra: pdf
45
+ Requires-Dist: pillow>=10.0; extra == "pdf"
44
46
  Dynamic: license-file
45
47
 
46
48
  # OGC API Service Profile Builder
47
49
 
48
- Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — OpenAPI 3.1.0, AsyncAPI, AsciiDoc requirements, and conformance tests.
50
+ Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — OpenAPI 3.1.0, AsyncAPI, AsciiDoc requirements and conformance tests, and a publication-ready Metanorma PDF with optional custom (e.g. DGIWG) branding.
49
51
 
50
52
  [![PyPI](https://img.shields.io/pypi/v/oapi-profile-builder)](https://pypi.org/project/oapi-profile-builder/)
51
53
  [![License](https://img.shields.io/badge/license-Apache-blue)](LICENSE)
@@ -80,13 +82,16 @@ output/
80
82
 
81
83
  ## Example Profiles
82
84
 
83
- Three working examples are included:
85
+ Six working examples are included:
84
86
 
85
87
  | File | What it shows |
86
88
  |---|---|
87
89
  | [`examples/minimal_profile.yaml`](examples/minimal_profile.yaml) | Smallest valid profile — one collection, one requirement |
88
90
  | [`examples/insitu_observations_profile.yaml`](examples/insitu_observations_profile.yaml) | Full meteorological profile — 8 parameters with QUDT units, CF standard names, metocean extensions, CRS listing, temporal extent, custom dimensions, `parameter_schema` |
91
+ | [`examples/nws_connect_profile.yaml`](examples/nws_connect_profile.yaml) | PubSub profile — generates `asyncapi.yaml` alongside the OpenAPI |
89
92
  | [`examples/nwsviz_profile.yaml`](examples/nwsviz_profile.yaml) | Production profile — 13 collections, 3 OGC API Processes, PDF metadata |
93
+ | [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) | **DGIWG-branded** profile — custom cover logo, DGIWG colours/footer/legal page, `default_output_format`, per-query CRS, vertical `positive`, DGIWG namespace |
94
+ | [`examples/nwp_earth_observations_lightning_profile.yaml`](examples/nwp_earth_observations_lightning_profile.yaml) | DGIWG lightning profile — same branding, CF units |
90
95
 
91
96
  ---
92
97
 
@@ -115,6 +120,8 @@ Add `--pdf` to also compile an OGC-compliant PDF via the `metanorma/metanorma` D
115
120
  oapi-profile-builder generate --config my_profile.yaml --output ./output --pdf
116
121
  ```
117
122
 
123
+ The PDF can be rebranded for another SDO (e.g. DGIWG) — custom cover logo, colours, legal page, footer, and identifier namespace — all from the profile config. See [Document generation and branding](#document-generation-and-branding).
124
+
118
125
  ### `validate`
119
126
 
120
127
  Validates the config without writing any files. Useful in CI before generating.
@@ -219,6 +226,29 @@ jobs:
219
226
 
220
227
  ---
221
228
 
229
+ ## Document generation and branding
230
+
231
+ With `--pdf`, the builder compiles a Metanorma document (`document.adoc` + `sections/`, `requirements/`, `abstract_tests/`) into `document.pdf` using the official `metanorma/metanorma` Docker image. By default this is an OGC `draft-standard` document.
232
+
233
+ Everything about the document — cover, title-page metadata, legal page, footer, colours, and even the requirement/conformance identifier namespace — is driven from the profile config, so the same tooling can produce a document branded for another SDO such as **DGIWG** with no OGC branding remaining.
234
+
235
+ | What you want to change | Field | Mechanism |
236
+ |---|---|---|
237
+ | Cover logo + tagline (replaces the OGC cover) | `document_metadata.cover` | Generated cover image via `:coverpage-image:` full replacement |
238
+ | Title, doc number, edition, dates, sub-title/status | `title`, `version`, `document_metadata.{doc_number, doc_type, doc_subtype, status, *_date}` | Metanorma document attributes |
239
+ | PDF colours (titles, tables, cover, backgrounds) | `document_metadata.colors` | `:presentation-metadata-color-*:` |
240
+ | Page-ii legal text (copyright / license / notice) | `document_metadata.boilerplate` | `:boilerplate-authority:` |
241
+ | Footer organisation name | `document_metadata.copyright_holder` | `:copyright-holder:` |
242
+ | Remove the OGC logo from the legal page | `document_metadata.suppress_flavor_logo` | targeted `:pdf-stylesheet-override:` |
243
+ | Requirement / conformance identifier namespace | `spec_uri_base` | derives `req_uri` / `conf_uri` |
244
+ | Submitter table, notice paragraph, normative refs | `document_metadata.{submitters, notice, normative_references}` | Metanorma attributes / sections |
245
+
246
+ Custom branding requires Pillow for the cover image: `pip install 'oapi-profile-builder[pdf]'`. Field-by-field detail is in the [`document_metadata`](#document_metadata) reference below; [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml) is a complete DGIWG-branded example.
247
+
248
+ > The `suppress_flavor_logo` override reaches into the OGC Metanorma flavor's XSL (template `insertLogoPreface`) and is coupled to that flavor. Everything else uses documented Metanorma attributes. For long-term SDO adoption, a dedicated Metanorma "taste"/flavor is the most durable path.
249
+
250
+ ---
251
+
222
252
  ## Profile Config Reference
223
253
 
224
254
  A profile config is a YAML file. The full JSON Schema is at [`profile.schema.json`](profile.schema.json).
@@ -290,12 +320,18 @@ abstract_tests: []
290
320
  | `description` | string | no | Human-readable description of the service profile. Surfaces in the OpenAPI `info.description` and the landing page response schema |
291
321
  | `keywords` | list | no | Service-level keywords (e.g. query types, parameter names, domain terms). Surfaces in `info.x-keywords` and the landing page response schema. Distinct from `document_metadata.keywords`, which are for the OGC PDF header |
292
322
  | `server_url` | string | no | Documentation only — not written to the profile OpenAPI |
323
+ | `provider` | object | no | Service provider / responsible party. Surfaces in OpenAPI `info.contact` and the document Point of Contact (see below) |
324
+ | `classification` | object | no | Security classification (`level` + `system`). Surfaces as a document banner and `info.x-classification` |
325
+ | `metadata_date` | string | no | Metadata update date (ISO 8601). Surfaces as `info.x-metadata-date` |
326
+ | `resource_service_publish_date` | string | no | Resource/service publication date (ISO 8601). Surfaces as `info.x-resource-publish-date` |
327
+ | `resource_default_locale` | string | no | Default resource locale (e.g. `eng`). Surfaces as `info.x-default-locale` |
293
328
  | `allow_post_queries` | bool | no | When `true`, generates `POST` alongside `GET` for all EDR data query endpoints. Defaults to `false` |
294
329
  | `collections` | list | yes | One or more EDR collections (see below) |
295
330
  | `required_conformance_classes` | list | no | Conformance classes implementations must declare. Defaults to EDR Core |
296
331
  | `extent_requirements` | object | no | Profile-level CRS/TRS/VRS constraints (see below) |
297
332
  | `output_formats` | list | no | Format name → media type + schema ref mappings |
298
333
  | `collection_id_pattern` | string | no | Regex all collection IDs must match |
334
+ | `spec_uri_base` | string | no | Base URI for the requirements/conformance class identifiers. Defaults to the OGC EDR Part 3 namespace; override to publish under another SDO namespace (e.g. DGIWG) |
299
335
  | `parameter_name_pattern` | string | no | Regex all `parameter_names` keys must match |
300
336
  | `parameter_schema` | object | no | JSON Schema for parameter objects — replaces the default schema in the generated OpenAPI (see below) |
301
337
  | `processes` | list | no | OGC API Processes to expose in the OpenAPI |
@@ -303,7 +339,7 @@ abstract_tests: []
303
339
  | `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
304
340
  | `pubsub` | object | no | OGC API - EDR Part 2 PubSub config — generates `asyncapi.yaml` |
305
341
  | `collection_examples` | object | no | `{collectionId: {instanceId: "..."}}` — used by `validate-server` |
306
- | `document_metadata` | object | no | Metanorma PDF header (doc number, editors, orgs, keywords) |
342
+ | `document_metadata` | object | no | Metanorma PDF header — doc number/type/subtype, status, editors, submitters, dates, notice, normative references (see below) |
307
343
 
308
344
  ---
309
345
 
@@ -360,6 +396,22 @@ data_queries:
360
396
  crs_details:
361
397
  - crs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
362
398
  - crs: "http://www.opengis.net/def/crs/EPSG/0/4326"
399
+ cube:
400
+ link:
401
+ href: https://example.com/collections/obs/cube
402
+ rel: data
403
+ variables:
404
+ query_type: cube
405
+ output_formats: [CoverageJSON, GeoTIFF]
406
+ # default_output_format: response format used when the `f` query
407
+ # parameter is omitted. Must be one of output_formats. Sets the
408
+ # `default` on the generated OpenAPI `f` parameter for this query.
409
+ default_output_format: GeoTIFF
410
+ # crs: shorthand for crs_details — a plain list of CRS URIs.
411
+ # Validated against extent_requirements.supported_crs.
412
+ crs:
413
+ - "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
414
+ - "http://www.opengis.net/def/crs/EPSG/0/4326"
363
415
  ```
364
416
 
365
417
  #### `parameter_names` example
@@ -423,6 +475,61 @@ extent_requirements:
423
475
 
424
476
  At least one of `extent_crs` or `supported_crs` is required. The same applies to TRS and VRS — all are optional individually but at least one CRS constraint must be present.
425
477
 
478
+ #### Vertical direction (`positive`)
479
+
480
+ OGC API - EDR's vertical extent has no field for which way values increase. This is ambiguous for VRS like pressure levels, which different providers order top-to-bottom or bottom-to-top inconsistently (pressure increases as altitude *decreases*). Set `extent.vertical.positive` to `up` or `down` on a collection (following the CF Conventions `positive` attribute) to make the ordering explicit:
481
+
482
+ ```yaml
483
+ collections:
484
+ - id: my_collection
485
+ extent:
486
+ spatial: { ... }
487
+ vertical:
488
+ interval:
489
+ - ["1000", "100"] # hPa, surface to top of atmosphere
490
+ values: ["1000", "850", "700", "500", "300", "100"]
491
+ vrs: "http://www.opengis.net/def/crs/EPSG/0/5798" # or similar pressure VRS
492
+ positive: down # pressure increases downward, toward the surface
493
+ ```
494
+
495
+ Set `extent_requirements.require_vertical_direction: true` to make `positive` mandatory on every collection with a vertical extent, so a profile can enforce one consistent convention rather than leaving it up to each collection author:
496
+
497
+ ```yaml
498
+ extent_requirements:
499
+ minimum_bbox: [-180, -90, 180, 90]
500
+ extent_crs:
501
+ allowed: ["http://www.opengis.net/def/crs/OGC/1.3/CRS84"]
502
+ require_vertical_direction: true
503
+ ```
504
+
505
+ ---
506
+
507
+ ### `output_formats`
508
+
509
+ Maps format names (referenced by `collections[].output_formats` and `data_queries.*.variables.output_formats`) to a media type and, optionally, a schema reference. The `schema_ref` flows into the generated OpenAPI response `content` for that format — use it to point a format at a specific schema (e.g. a DGIWG-published schema for GeoTIFF or CoverageJSON) instead of the tool's built-in default.
510
+
511
+ ```yaml
512
+ output_formats:
513
+ - name: CoverageJSON
514
+ media_type: application/prs.coverage+json
515
+ schema_ref: https://schemas.opengis.net/covjson/1.0/coveragejson.json
516
+ - name: GeoJSON
517
+ media_type: application/geo+json
518
+ schema_ref: https://geojson.org/schema/FeatureCollection.json
519
+ - name: GeoTIFF
520
+ media_type: image/tiff; application=geotiff
521
+ # Omit schema_ref until a published schema exists for this format —
522
+ # the requirement/abstract test can still state the standard to conform to.
523
+ ```
524
+
525
+ | Field | Required | Description |
526
+ |---|---|---|
527
+ | `name` | yes | Format name, referenced elsewhere as a string (e.g. `CoverageJSON`) |
528
+ | `media_type` | yes | MIME type used as the OpenAPI response `content` key and the `f` parameter value |
529
+ | `schema_ref` | no | URL to a JSON Schema (or other schema) for this format. When set, the generated response uses `{"$ref": schema_ref}`; when omitted, a generic/default schema is used |
530
+
531
+ `schema_ref` is optional — if no schema exists yet for a format (as is currently the case for some binary formats like GeoTIFF), leave it unset and capture the required standard as a normative `requirements[]` entry instead. Add `schema_ref` later once a schema URL exists.
532
+
426
533
  ---
427
534
 
428
535
  ### `parameter_schema`
@@ -536,16 +643,39 @@ pubsub:
536
643
 
537
644
  ### `document_metadata`
538
645
 
539
- Required only when compiling a PDF with `--pdf`.
646
+ Required only when compiling a PDF with `--pdf`. Fields map to Metanorma OGC document attributes.
540
647
 
541
648
  ```yaml
542
649
  document_metadata:
543
650
  doc_number: "25-myprofile"
544
- doc_subtype: implementation # implementation | best-practice | engineering-report
651
+ doc_type: draft-standard # Metanorma :doctype: (draft-standard | standard | best-practice | engineering-report | ...)
652
+ doc_subtype: implementation # implementation | best-practice | engineering-report | profile
653
+ status: approved # Metanorma :status:/:docstage: (swg-draft | public-rfc | approved | ...)
545
654
  editors:
546
655
  - Jane Smith
656
+ # submitters: structured table for page iv — each row carries its own affiliation.
657
+ # When provided, takes precedence over the editors/submitting_orgs fallback.
658
+ submitters:
659
+ - name: Jane Smith
660
+ affiliation: My Organization
661
+ role: editor
662
+ - name: Standards Working Group
663
+ affiliation: My SDO
547
664
  submitting_orgs:
548
665
  - My Organization
666
+ # Title-page dates (YYYY-MM-DD). Omitted dates fall back to {copyright_year}-01-01.
667
+ submission_date: "2026-05-01" # → :received-date:
668
+ approval_date: "2026-06-15" # → :issued-date:
669
+ publication_date: "2026-07-01" # → :published-date:
670
+ # Custom notice paragraph rendered in the front matter. Note: the OGC cover-page
671
+ # legal notice itself is generated by Metanorma from doc_type/status.
672
+ notice: >
673
+ This is a DRAFT standard and is subject to change.
674
+ # Additional normative references appended to the References section.
675
+ normative_references:
676
+ - anchor: DGIWG-250
677
+ citation: DGIWG 250
678
+ title: DGIWG GeoTIFF Profile
549
679
  keywords:
550
680
  - ogcdoc
551
681
  - OGC API
@@ -554,6 +684,113 @@ document_metadata:
554
684
  external_id: http://www.opengis.net/doc/dp/my-profile/1.0
555
685
  ```
556
686
 
687
+ The cover-page sub-title (e.g. "Draft Standard — Implementation — Approved") is produced by Metanorma from the `doc_type`, `doc_subtype`, and `status` combination.
688
+
689
+ #### Custom branding (logo + colors)
690
+
691
+ Two `document_metadata` blocks let you rebrand the PDF (e.g. for DGIWG) without a custom Metanorma flavor:
692
+
693
+ - **`colors`** — recolours the PDF via Metanorma's `:presentation-metadata-color-*:` attributes. Values are hex colours.
694
+ - **`cover`** — replaces the built-in OGC cover page with a generated one: your logo plus the document's title, number, edition and date. Requires Pillow (`pip install 'oapi-profile-builder[pdf]'`). The builder renders `cover.png` into the output directory and points Metanorma at it via `:coverpage-image:` + `:presentation-metadata-full-coverpage-replacement:`.
695
+
696
+ ```yaml
697
+ document_metadata:
698
+ doc_number: "DGIWG 134"
699
+ cover:
700
+ logo: assets/dgiwg_logo.png # path relative to the working directory (or absolute)
701
+ tagline: Delivering Military Advantage through multi-national geospatial interoperability
702
+ background: "#FFFFFF" # optional cover background (default white)
703
+ text_color: "#1F3864" # optional cover text colour (default black)
704
+ colors:
705
+ text: "#000000" # body text
706
+ cover_text: "#1F3864" # cover text / section numbers / ToC
707
+ cover_lines: "#1F3864" # preface 'crossing lines' element
708
+ title: "#1F3864" # clause/table/figure titles
709
+ page_background: "#FFFFFF" # cover + section page background
710
+ table_header: "#1F3864" # table header background
711
+ table_row_even: "#EEF1F7" # even table rows
712
+ table_row_odd: "#FFFFFF" # odd table rows
713
+ ```
714
+
715
+ Note: colours work against the normal OGC cover too. The `cover` block does a *full* cover replacement, so the dynamic title/number/edition/date are drawn onto the generated image (which is why the builder composes it rather than relying on Metanorma attributes — logo attributes like `:coverpage-image:` alone are not rendered by the OGC flavor).
716
+
717
+ #### De-branding the rest of the document (legal page + footer + logo)
718
+
719
+ Cover + colors handle the front page, but the OGC flavor also stamps its identity on the page-ii legal boilerplate, the page footer, and an OGC logo on the legal page. Three more `document_metadata` fields remove those:
720
+
721
+ - **`copyright_holder`** — sets `:copyright-holder:`, which also drives the **PDF footer** organisation name (replacing "OPEN GEOSPATIAL CONSORTIUM").
722
+ - **`boilerplate`** — replaces the page-ii legal text (copyright / license / legal notice / feedback) via `:boilerplate-authority:`. Any field left unset is synthesised from `copyright_holder`, so no OGC text leaks through.
723
+ - **`suppress_flavor_logo`** — removes the OGC logo from the preface/legal page via a targeted PDF-stylesheet override. (Coupled to the OGC Metanorma flavor's XSL.)
724
+
725
+ ```yaml
726
+ document_metadata:
727
+ copyright_holder: DGIWG
728
+ suppress_flavor_logo: true
729
+ boilerplate:
730
+ copyright: "Copyright © 2026 Defence Geospatial Information Working Group (DGIWG). All rights reserved."
731
+ license: "Use of this document is subject to the DGIWG terms and conditions."
732
+ legal: "Attention is drawn to the possibility that some elements may be subject to patent rights; DGIWG shall not be held responsible for identifying any such rights."
733
+ feedback: "Comments on this document should be directed to DGIWG."
734
+ ```
735
+
736
+ With `cover` + `colors` + `copyright_holder` + `boilerplate` + `suppress_flavor_logo` (and `spec_uri_base` for the requirement/conformance identifiers), the generated PDF carries no OGC branding — only legitimate citations to the OGC standards the profile conforms to. See [`examples/nwp_radar.yaml`](examples/nwp_radar.yaml).
737
+
738
+ ---
739
+
740
+ ### `provider`, `classification`, and provenance metadata
741
+
742
+ Optional service-level metadata. Each may also be set per-collection (the collection value overrides the profile root for that collection and is round-tripped into `profile_config.json`).
743
+
744
+ ```yaml
745
+ provider:
746
+ name: Austrian Military
747
+ url: "https://www.bmlv.gv.at/english/forces/organisation.shtml"
748
+ contact:
749
+ email: presse@bmlvs.gv.at
750
+ phone: "+43 ..."
751
+ hours: Monday to Friday 08:00 - 17:00
752
+ instructions: During hours of Service
753
+ address: "Ministry of Defence, Rossauer Lände 1"
754
+ postalcode: A-1090
755
+ city: Vienna
756
+ country: Austria
757
+
758
+ classification:
759
+ level: "NATO RESTRICTED (NR)"
760
+ system: NATO
761
+
762
+ metadata_date: "2026-06-25T07:31Z"
763
+ resource_service_publish_date: "2026-06-24T14:10Z"
764
+ resource_default_locale: eng
765
+ ```
766
+
767
+ Where these surface:
768
+
769
+ - `provider` → OpenAPI `info.contact` (`name`/`url`/`email`; the remaining contact fields become `x-` extensions) and a **Point of Contact** section in the generated document.
770
+ - `classification` → a **security classification banner** in the document Abstract and `info.x-classification` in the OpenAPI.
771
+ - `metadata_date` / `resource_service_publish_date` / `resource_default_locale` → `info.x-metadata-date` / `info.x-resource-publish-date` / `info.x-default-locale`.
772
+
773
+ > The profile OpenAPI is implementation-independent (per EDR Part 3 REQ_publishing), so these describe the profile/data provenance rather than a specific deployment endpoint.
774
+
775
+ ---
776
+
777
+ ### `spec_uri_base`
778
+
779
+ By default, the requirements class and conformance class identifiers are published under the OGC API - EDR Part 3 namespace:
780
+
781
+ ```
782
+ http://www.opengis.net/spec/ogcapi-edr-3/1.0/req/<name>
783
+ http://www.opengis.net/spec/ogcapi-edr-3/1.0/conf/<name>
784
+ ```
785
+
786
+ Set `spec_uri_base` to publish under a different SDO namespace. This updates the OpenAPI `x-ogc-profile` link, the requirements/conformance AsciiDoc identifiers, and the conformance section:
787
+
788
+ ```yaml
789
+ spec_uri_base: "https://schemas.dgiwg.org/edr/1.0"
790
+ # → https://schemas.dgiwg.org/edr/1.0/req/<name>
791
+ # → https://schemas.dgiwg.org/edr/1.0/conf/<name>
792
+ ```
793
+
557
794
  ---
558
795
 
559
796
  ## Validation Rules
@@ -564,10 +801,13 @@ The tool enforces these rules at build time. Violations produce clear error mess
564
801
  |---|---|
565
802
  | `name` format | Must match `^[a-z0-9_]+$` |
566
803
  | No duplicate collection IDs | Across the whole profile |
567
- | `extent_requirements` requires CRS spec | Either `allowed_crs` or `crs_pattern` must be set |
568
- | Collection CRS validated | Each `extent.spatial.crs` checked against `allowed_crs`/`crs_pattern` |
569
- | Collection TRS validated | Each `extent.temporal.trs` checked against `allowed_trs`/`trs_pattern` |
570
- | `crs_details` validated | Each `data_queries.*.variables.crs_details[].crs` checked against CRS constraints |
804
+ | `extent_requirements` requires CRS spec | At least one of `extent_crs` or `supported_crs` must be set (each uses `allowed` or `pattern`) |
805
+ | Collection CRS validated | Each `extent.spatial.crs` checked against `extent_crs`; `crs[]` and `crs_details` checked against `supported_crs` |
806
+ | Collection TRS validated | Each `extent.temporal.trs` checked against `extent_trs` |
807
+ | `crs_details` / `crs` validated | Each `data_queries.*.variables.crs_details[].crs` and `crs[]` checked against `supported_crs` |
808
+ | `default_output_format` validated | Each `data_queries.*.variables.default_output_format` must be one of that query's `output_formats` |
809
+ | `require_vertical_direction` enforced | If set, every collection with a vertical extent must declare `extent.vertical.positive` as `up` or `down` |
810
+ | Document dates format | `submission_date` / `approval_date` / `publication_date` must be `YYYY-MM-DD` |
571
811
  | Parameters need `unit` + `observedProperty` | Required by OGC API - EDR Part 3 |
572
812
  | `parameter_name_pattern` enforced | All `parameter_names` keys must match if set |
573
813
  | `collection_id_pattern` enforced | All collection IDs must match if set |