oapi-profile-builder 3.4.1__tar.gz → 3.6.1__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.4.1/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.6.1}/PKG-INFO +101 -5
  2. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/README.md +100 -4
  3. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/pyproject.toml +1 -1
  4. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder/cover.py +8 -5
  5. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder/generate.py +297 -43
  6. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder/models.py +333 -6
  7. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1/src/oapi_profile_builder.egg-info}/PKG-INFO +101 -5
  8. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/LICENSE +0 -0
  9. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/setup.cfg +0 -0
  10. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder/__init__.py +0 -0
  11. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder/cite.py +0 -0
  12. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder/cite_features.py +0 -0
  13. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder/cli.py +0 -0
  14. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder/compile.py +0 -0
  15. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder/server_validation.py +0 -0
  16. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder.egg-info/SOURCES.txt +0 -0
  17. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
  18. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
  19. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/src/oapi_profile_builder.egg-info/requires.txt +0 -0
  20. {oapi_profile_builder-3.4.1 → oapi_profile_builder-3.6.1}/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.4.1
3
+ Version: 3.6.1
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
@@ -181,6 +181,20 @@ OGC API - EDR CITE Results
181
181
 
182
182
  ## GitHub Actions
183
183
 
184
+ ### Self-service: generate artifacts from an issue (no fork, no install)
185
+
186
+ Anyone with a GitHub account can generate artifacts from their own config without cloning or installing anything:
187
+
188
+ 1. Open a new issue using the **"Generate profile artifacts"** template ([New issue](../../issues/new/choose)).
189
+ 2. Paste your profile YAML into the config field and submit.
190
+ 3. A workflow validates the config, generates the artifacts, and comments on the issue with a link to download them as a zip (retained for 14 days). Optionally tick "Compile a PDF?" to also get a Metanorma PDF.
191
+
192
+ The workflow only runs the generator over your declarative config — it never executes user-supplied code — and comments back on both success and failure. Don't paste secrets: the artifacts are attached to a public workflow run.
193
+
194
+ > Maintainers: create a `profile-request` label so requests are easy to filter (the workflow gates on a marker in the issue body, so it works even before the label exists).
195
+
196
+ ### Use the action in your own workflow
197
+
184
198
  No local install needed. Add this to any workflow to generate profile artifacts from a config file:
185
199
 
186
200
  ```yaml
@@ -239,7 +253,7 @@ Everything about the document — cover, title-page metadata, legal page, footer
239
253
  | PDF colours (titles, tables, cover, backgrounds) | `document_metadata.colors` | `:presentation-metadata-color-*:` |
240
254
  | Page-ii legal text (copyright / license / notice) | `document_metadata.boilerplate` | `:boilerplate-authority:` |
241
255
  | Footer organisation name | `document_metadata.copyright_holder` | `:copyright-holder:` |
242
- | Cover layout (logo size/position, tagline/title size, bold edition) | `document_metadata.cover.{logo_width, logo_y, tagline_font_size, title_font_size, bold_edition}` | Generated cover image |
256
+ | Cover layout (logo size/position, tagline/title/edition-date size, bold edition+date) | `document_metadata.cover.{logo_width, logo_y, tagline_font_size, title_font_size, edition_date_font_size, bold_edition}` | Generated cover image |
243
257
  | Cover date | `document_metadata.doc_pub_date` | Long-format date on the generated cover + `:published-date:` |
244
258
  | Remove the OGC logo from the legal page | `document_metadata.suppress_flavor_logo` | targeted `:pdf-stylesheet-override:` |
245
259
  | Remove OGC design (crossing lines, circled numbers, rules, navy divider pages) | `document_metadata.suppress_design_elements` (or the individual `suppress_crossing_lines` / `plain_section_numbers` / `suppress_title_underlines`) | `:pdf-stylesheet-override:` + colour attributes |
@@ -342,6 +356,13 @@ abstract_tests: []
342
356
  | `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) |
343
357
  | `parameter_name_pattern` | string | no | Regex all `parameter_names` keys must match |
344
358
  | `parameter_schema` | object | no | JSON Schema for parameter objects — replaces the default schema in the generated OpenAPI (see below) |
359
+ | `collection_title_max_length` | int | no | When set, every collection title must be present and within this many characters (MetOcean `/req/core/collection_title`) |
360
+ | `require_license_link` | bool | no | When `true`, every collection must declare a `rel='license'` link (MetOcean `/req/core/collection_license`). Defaults to `false` |
361
+ | `license_link_type` | string | no | Expected media type of the `rel='license'` link when `require_license_link` is set. Defaults to `text/html`; set to null to accept any type |
362
+ | `required_data_queries` | list | no | Data-query types every collection must define (e.g. `[locations, area, radius]`) — enforces the mandatory data-query sets |
363
+ | `radius_within_units_required` | list | no | Unit tokens each radius query's `within_units` must contain (e.g. `[m]`) (MetOcean `/req/core/collection_radius_data_query`) |
364
+ | `locations_feature_required_properties` | list | no | Feature properties (e.g. `[name]`) marked required in the generated `/locations` GeoJSON response schema, in addition to the always-required string `id` |
365
+ | `conformance_class_requirements` | object | no | Map of conformance-class short name → constraints (`required_data_queries`, `radius_within_units_required`) applied only to collections that declare the class. Lets one profile document model several EDR Part 3 requirements classes (see below) |
345
366
  | `processes` | list | no | OGC API Processes to expose in the OpenAPI |
346
367
  | `requirements` | list | no | Normative requirements for the AsciiDoc/PDF |
347
368
  | `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
@@ -381,6 +402,8 @@ Uses the [edr-pydantic](https://github.com/KNMI/edr-pydantic) `Collection` model
381
402
  | `output_formats` | no | Format names this collection supports (e.g. `[CoverageJSON, GeoJSON]`) |
382
403
  | `data_queries` | no | EDR query types: `position`, `area`, `radius`, `cube`, `trajectory`, `corridor`, `locations`, `items`, `instances` |
383
404
  | `parameter_names` | no | Map of parameter id → Parameter object. All parameters must have `unit` and `observedProperty` |
405
+ | `conformance_classes` | no | Profile conformance-class short names this collection implements (e.g. `[core, insitu-observations]`). Selects which `conformance_class_requirements` are enforced; surfaced as `x-conformance-classes` in the OpenAPI |
406
+ | `parameter_schema` | no | Per-collection JSON Schema for parameter objects, overriding the profile-level `parameter_schema` for this collection only (precedence: collection → profile → default) |
384
407
 
385
408
  #### `data_queries` example
386
409
 
@@ -513,6 +536,38 @@ extent_requirements:
513
536
 
514
537
  ---
515
538
 
539
+ ### Multiple conformance classes in one profile
540
+
541
+ OGC API - EDR Part 3 profiles are modular: a single profile document can define several requirements/conformance classes, and different collections in the same service may implement different classes. The MetOcean EDR Profile is a good example — Core applies everywhere, but only in-situ collections must offer `locations`/`area`/`radius` while NWP collections must offer `position`/`cube`.
542
+
543
+ Model this with `conformance_class_requirements` (profile level) plus `conformance_classes` (per collection):
544
+
545
+ ```yaml
546
+ # Core-level rules apply to every collection:
547
+ collection_title_max_length: 50
548
+ require_license_link: true
549
+
550
+ # Class-scoped rules apply only where a collection opts in:
551
+ conformance_class_requirements:
552
+ insitu-observations:
553
+ required_data_queries: [locations, area, radius]
554
+ radius_within_units_required: [m]
555
+ nwp:
556
+ required_data_queries: [position, cube]
557
+
558
+ collections:
559
+ - id: insitu-observations
560
+ conformance_classes: [core, insitu-observations] # must have locations/area/radius
561
+ # ...
562
+ - id: nwp-forecast
563
+ conformance_classes: [core, nwp] # must have position/cube
564
+ # ...
565
+ ```
566
+
567
+ The flat `required_data_queries` / `radius_within_units_required` fields still apply to *every* collection (the Core-applies-to-all shorthand); class-scoped rules are additive. See `examples/metocean_profile.yaml` for a full worked profile spanning all four MetOcean classes.
568
+
569
+ ---
570
+
516
571
  ### `output_formats`
517
572
 
518
573
  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.
@@ -654,7 +709,7 @@ When present, generates `asyncapi.yaml` alongside `openapi.yaml`.
654
709
  pubsub:
655
710
  broker_host: my-broker.example.com
656
711
  broker_port: 5672
657
- protocol: amqp # amqp | mqtt | kafka
712
+ protocol: amqp # amqp | amqp1 | mqtt | kafka
658
713
  collections:
659
714
  - my_collection
660
715
  filters:
@@ -663,6 +718,40 @@ pubsub:
663
718
  type: string
664
719
  ```
665
720
 
721
+ `broker_host`/`broker_port`/`protocol` synthesise a single implicit
722
+ `production` AsyncAPI server. To describe an exact transport topology instead —
723
+ e.g. a native WebSocket `/ws` endpoint plus AMQP 1.0 and MQTT brokers, as the
724
+ `nws_connect_profile.yaml` example does — list them explicitly under `servers`.
725
+ When `servers` is set, the implicit `production` server is **not** emitted:
726
+
727
+ ```yaml
728
+ pubsub:
729
+ broker_host: edr-broker
730
+ broker_port: 5672
731
+ protocol: amqp1 # AMQP 1.0 (e.g. RabbitMQ 4.x native)
732
+ collections: [water_gauge, wwa]
733
+ servers:
734
+ - name: websocket_secure
735
+ description: Native WebSocket live map (production)
736
+ host: ''
737
+ protocol: wss # amqp | amqp1 | mqtt | kafka | ws | wss
738
+ pathname: /ws
739
+ - name: amqp
740
+ description: AMQP 1.0 broker (RabbitMQ 4.x native AMQP 1.0)
741
+ host: 'edr-broker:5672'
742
+ protocol: amqp1
743
+ - name: mqtt
744
+ description: MQTT broker (RabbitMQ MQTT plugin)
745
+ host: 'edr-broker:1883'
746
+ protocol: mqtt
747
+ collection_filters: # per-collection filter overrides
748
+ water_gauge:
749
+ filters:
750
+ - name: flood_stages
751
+ description: Alert for selected flood stages
752
+ type: array
753
+ ```
754
+
666
755
  ---
667
756
 
668
757
  ### `document_metadata`
@@ -732,11 +821,12 @@ document_metadata:
732
821
  font_italic: "Source Sans Pro" # optional italic face for the tagline
733
822
  watermark: "DRAFT" # optional diagonal watermark stamped on the cover
734
823
  # --- cover layout (all optional) ---
735
- logo_width: 200 # target logo width in px (default 420)
824
+ logo_width: 300 # target logo width in px (default 420)
736
825
  logo_y: 100 # logo vertical offset from top in px (default 180)
737
826
  tagline_font_size: 20 # tagline point size (default 30)
738
827
  title_font_size: 40 # title point size (default 54)
739
- bold_edition: true # render the "Edition X" line in bold (default false)
828
+ edition_date_font_size: 22 # edition/date point size (default 24)
829
+ bold_edition: true # render the "Edition X" and "Dated ..." lines in bold (default false)
740
830
  colors:
741
831
  text: "#000000" # body text
742
832
  cover_text: "#1F3864" # cover text / section numbers / ToC
@@ -898,6 +988,12 @@ The tool enforces these rules at build time. Violations produce clear error mess
898
988
  | Parameters need `unit` + `observedProperty` | Required by OGC API - EDR Part 3 |
899
989
  | `parameter_name_pattern` enforced | All `parameter_names` keys must match if set |
900
990
  | `collection_id_pattern` enforced | All collection IDs must match if set |
991
+ | `collection_title_max_length` enforced | If set, every collection title must be present and within the limit |
992
+ | `require_license_link` enforced | If set, every collection must have a `rel='license'` link (of `license_link_type` when specified) |
993
+ | `required_data_queries` enforced | If set, every collection with `data_queries` must define each listed query type |
994
+ | `radius_within_units_required` enforced | If set, each radius query's `within_units` must contain every listed unit token |
995
+ | `conformance_class_requirements` enforced | Class-scoped `required_data_queries` / `radius_within_units_required` applied to collections whose `conformance_classes` include the class key |
996
+ | `conformance_classes` references known class | When `conformance_class_requirements` is set, a collection's `conformance_classes` may only use `core` or a defined class key (catches typos) |
901
997
  | Abstract test IDs match requirements | `requirement_id` must reference an existing requirement |
902
998
  | Requirement IDs | Must match `^[a-z0-9][a-z0-9\-]*$`, no trailing hyphen |
903
999
 
@@ -134,6 +134,20 @@ OGC API - EDR CITE Results
134
134
 
135
135
  ## GitHub Actions
136
136
 
137
+ ### Self-service: generate artifacts from an issue (no fork, no install)
138
+
139
+ Anyone with a GitHub account can generate artifacts from their own config without cloning or installing anything:
140
+
141
+ 1. Open a new issue using the **"Generate profile artifacts"** template ([New issue](../../issues/new/choose)).
142
+ 2. Paste your profile YAML into the config field and submit.
143
+ 3. A workflow validates the config, generates the artifacts, and comments on the issue with a link to download them as a zip (retained for 14 days). Optionally tick "Compile a PDF?" to also get a Metanorma PDF.
144
+
145
+ The workflow only runs the generator over your declarative config — it never executes user-supplied code — and comments back on both success and failure. Don't paste secrets: the artifacts are attached to a public workflow run.
146
+
147
+ > Maintainers: create a `profile-request` label so requests are easy to filter (the workflow gates on a marker in the issue body, so it works even before the label exists).
148
+
149
+ ### Use the action in your own workflow
150
+
137
151
  No local install needed. Add this to any workflow to generate profile artifacts from a config file:
138
152
 
139
153
  ```yaml
@@ -192,7 +206,7 @@ Everything about the document — cover, title-page metadata, legal page, footer
192
206
  | PDF colours (titles, tables, cover, backgrounds) | `document_metadata.colors` | `:presentation-metadata-color-*:` |
193
207
  | Page-ii legal text (copyright / license / notice) | `document_metadata.boilerplate` | `:boilerplate-authority:` |
194
208
  | Footer organisation name | `document_metadata.copyright_holder` | `:copyright-holder:` |
195
- | Cover layout (logo size/position, tagline/title size, bold edition) | `document_metadata.cover.{logo_width, logo_y, tagline_font_size, title_font_size, bold_edition}` | Generated cover image |
209
+ | Cover layout (logo size/position, tagline/title/edition-date size, bold edition+date) | `document_metadata.cover.{logo_width, logo_y, tagline_font_size, title_font_size, edition_date_font_size, bold_edition}` | Generated cover image |
196
210
  | Cover date | `document_metadata.doc_pub_date` | Long-format date on the generated cover + `:published-date:` |
197
211
  | Remove the OGC logo from the legal page | `document_metadata.suppress_flavor_logo` | targeted `:pdf-stylesheet-override:` |
198
212
  | Remove OGC design (crossing lines, circled numbers, rules, navy divider pages) | `document_metadata.suppress_design_elements` (or the individual `suppress_crossing_lines` / `plain_section_numbers` / `suppress_title_underlines`) | `:pdf-stylesheet-override:` + colour attributes |
@@ -295,6 +309,13 @@ abstract_tests: []
295
309
  | `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) |
296
310
  | `parameter_name_pattern` | string | no | Regex all `parameter_names` keys must match |
297
311
  | `parameter_schema` | object | no | JSON Schema for parameter objects — replaces the default schema in the generated OpenAPI (see below) |
312
+ | `collection_title_max_length` | int | no | When set, every collection title must be present and within this many characters (MetOcean `/req/core/collection_title`) |
313
+ | `require_license_link` | bool | no | When `true`, every collection must declare a `rel='license'` link (MetOcean `/req/core/collection_license`). Defaults to `false` |
314
+ | `license_link_type` | string | no | Expected media type of the `rel='license'` link when `require_license_link` is set. Defaults to `text/html`; set to null to accept any type |
315
+ | `required_data_queries` | list | no | Data-query types every collection must define (e.g. `[locations, area, radius]`) — enforces the mandatory data-query sets |
316
+ | `radius_within_units_required` | list | no | Unit tokens each radius query's `within_units` must contain (e.g. `[m]`) (MetOcean `/req/core/collection_radius_data_query`) |
317
+ | `locations_feature_required_properties` | list | no | Feature properties (e.g. `[name]`) marked required in the generated `/locations` GeoJSON response schema, in addition to the always-required string `id` |
318
+ | `conformance_class_requirements` | object | no | Map of conformance-class short name → constraints (`required_data_queries`, `radius_within_units_required`) applied only to collections that declare the class. Lets one profile document model several EDR Part 3 requirements classes (see below) |
298
319
  | `processes` | list | no | OGC API Processes to expose in the OpenAPI |
299
320
  | `requirements` | list | no | Normative requirements for the AsciiDoc/PDF |
300
321
  | `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
@@ -334,6 +355,8 @@ Uses the [edr-pydantic](https://github.com/KNMI/edr-pydantic) `Collection` model
334
355
  | `output_formats` | no | Format names this collection supports (e.g. `[CoverageJSON, GeoJSON]`) |
335
356
  | `data_queries` | no | EDR query types: `position`, `area`, `radius`, `cube`, `trajectory`, `corridor`, `locations`, `items`, `instances` |
336
357
  | `parameter_names` | no | Map of parameter id → Parameter object. All parameters must have `unit` and `observedProperty` |
358
+ | `conformance_classes` | no | Profile conformance-class short names this collection implements (e.g. `[core, insitu-observations]`). Selects which `conformance_class_requirements` are enforced; surfaced as `x-conformance-classes` in the OpenAPI |
359
+ | `parameter_schema` | no | Per-collection JSON Schema for parameter objects, overriding the profile-level `parameter_schema` for this collection only (precedence: collection → profile → default) |
337
360
 
338
361
  #### `data_queries` example
339
362
 
@@ -466,6 +489,38 @@ extent_requirements:
466
489
 
467
490
  ---
468
491
 
492
+ ### Multiple conformance classes in one profile
493
+
494
+ OGC API - EDR Part 3 profiles are modular: a single profile document can define several requirements/conformance classes, and different collections in the same service may implement different classes. The MetOcean EDR Profile is a good example — Core applies everywhere, but only in-situ collections must offer `locations`/`area`/`radius` while NWP collections must offer `position`/`cube`.
495
+
496
+ Model this with `conformance_class_requirements` (profile level) plus `conformance_classes` (per collection):
497
+
498
+ ```yaml
499
+ # Core-level rules apply to every collection:
500
+ collection_title_max_length: 50
501
+ require_license_link: true
502
+
503
+ # Class-scoped rules apply only where a collection opts in:
504
+ conformance_class_requirements:
505
+ insitu-observations:
506
+ required_data_queries: [locations, area, radius]
507
+ radius_within_units_required: [m]
508
+ nwp:
509
+ required_data_queries: [position, cube]
510
+
511
+ collections:
512
+ - id: insitu-observations
513
+ conformance_classes: [core, insitu-observations] # must have locations/area/radius
514
+ # ...
515
+ - id: nwp-forecast
516
+ conformance_classes: [core, nwp] # must have position/cube
517
+ # ...
518
+ ```
519
+
520
+ The flat `required_data_queries` / `radius_within_units_required` fields still apply to *every* collection (the Core-applies-to-all shorthand); class-scoped rules are additive. See `examples/metocean_profile.yaml` for a full worked profile spanning all four MetOcean classes.
521
+
522
+ ---
523
+
469
524
  ### `output_formats`
470
525
 
471
526
  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.
@@ -607,7 +662,7 @@ When present, generates `asyncapi.yaml` alongside `openapi.yaml`.
607
662
  pubsub:
608
663
  broker_host: my-broker.example.com
609
664
  broker_port: 5672
610
- protocol: amqp # amqp | mqtt | kafka
665
+ protocol: amqp # amqp | amqp1 | mqtt | kafka
611
666
  collections:
612
667
  - my_collection
613
668
  filters:
@@ -616,6 +671,40 @@ pubsub:
616
671
  type: string
617
672
  ```
618
673
 
674
+ `broker_host`/`broker_port`/`protocol` synthesise a single implicit
675
+ `production` AsyncAPI server. To describe an exact transport topology instead —
676
+ e.g. a native WebSocket `/ws` endpoint plus AMQP 1.0 and MQTT brokers, as the
677
+ `nws_connect_profile.yaml` example does — list them explicitly under `servers`.
678
+ When `servers` is set, the implicit `production` server is **not** emitted:
679
+
680
+ ```yaml
681
+ pubsub:
682
+ broker_host: edr-broker
683
+ broker_port: 5672
684
+ protocol: amqp1 # AMQP 1.0 (e.g. RabbitMQ 4.x native)
685
+ collections: [water_gauge, wwa]
686
+ servers:
687
+ - name: websocket_secure
688
+ description: Native WebSocket live map (production)
689
+ host: ''
690
+ protocol: wss # amqp | amqp1 | mqtt | kafka | ws | wss
691
+ pathname: /ws
692
+ - name: amqp
693
+ description: AMQP 1.0 broker (RabbitMQ 4.x native AMQP 1.0)
694
+ host: 'edr-broker:5672'
695
+ protocol: amqp1
696
+ - name: mqtt
697
+ description: MQTT broker (RabbitMQ MQTT plugin)
698
+ host: 'edr-broker:1883'
699
+ protocol: mqtt
700
+ collection_filters: # per-collection filter overrides
701
+ water_gauge:
702
+ filters:
703
+ - name: flood_stages
704
+ description: Alert for selected flood stages
705
+ type: array
706
+ ```
707
+
619
708
  ---
620
709
 
621
710
  ### `document_metadata`
@@ -685,11 +774,12 @@ document_metadata:
685
774
  font_italic: "Source Sans Pro" # optional italic face for the tagline
686
775
  watermark: "DRAFT" # optional diagonal watermark stamped on the cover
687
776
  # --- cover layout (all optional) ---
688
- logo_width: 200 # target logo width in px (default 420)
777
+ logo_width: 300 # target logo width in px (default 420)
689
778
  logo_y: 100 # logo vertical offset from top in px (default 180)
690
779
  tagline_font_size: 20 # tagline point size (default 30)
691
780
  title_font_size: 40 # title point size (default 54)
692
- bold_edition: true # render the "Edition X" line in bold (default false)
781
+ edition_date_font_size: 22 # edition/date point size (default 24)
782
+ bold_edition: true # render the "Edition X" and "Dated ..." lines in bold (default false)
693
783
  colors:
694
784
  text: "#000000" # body text
695
785
  cover_text: "#1F3864" # cover text / section numbers / ToC
@@ -851,6 +941,12 @@ The tool enforces these rules at build time. Violations produce clear error mess
851
941
  | Parameters need `unit` + `observedProperty` | Required by OGC API - EDR Part 3 |
852
942
  | `parameter_name_pattern` enforced | All `parameter_names` keys must match if set |
853
943
  | `collection_id_pattern` enforced | All collection IDs must match if set |
944
+ | `collection_title_max_length` enforced | If set, every collection title must be present and within the limit |
945
+ | `require_license_link` enforced | If set, every collection must have a `rel='license'` link (of `license_link_type` when specified) |
946
+ | `required_data_queries` enforced | If set, every collection with `data_queries` must define each listed query type |
947
+ | `radius_within_units_required` enforced | If set, each radius query's `within_units` must contain every listed unit token |
948
+ | `conformance_class_requirements` enforced | Class-scoped `required_data_queries` / `radius_within_units_required` applied to collections whose `conformance_classes` include the class key |
949
+ | `conformance_classes` references known class | When `conformance_class_requirements` is set, a collection's `conformance_classes` may only use `core` or a defined class key (catches typos) |
854
950
  | Abstract test IDs match requirements | `requirement_id` must reference an existing requirement |
855
951
  | Requirement IDs | Must match `^[a-z0-9][a-z0-9\-]*$`, no trailing hyphen |
856
952
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "oapi-profile-builder"
7
- version = "3.4.1"
7
+ version = "3.6.1"
8
8
  description = "Authoritative tooling for creating OGC API Service Profiles (EDR, Features)"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -197,17 +197,20 @@ def build_cover_image(profile, output_dir: Path) -> str | None:
197
197
  if m and m.doc_number:
198
198
  y = centered(m.doc_number, fonts.regular_font(46, bold=True), y, fg) + 40
199
199
 
200
- # --- Edition ---
200
+ # --- Edition & date ---
201
+ # Per DGIWG feedback these two lines are both bold and set a little smaller
202
+ # than the document number so they read as a compact sub-heading.
203
+ ed_date_fs = getattr(cover, "edition_date_font_size", 24)
204
+ bold_ed = bool(getattr(cover, "bold_edition", False))
205
+
201
206
  edition = getattr(profile, "version", None)
202
207
  if edition:
203
- bold_ed = bool(getattr(cover, "bold_edition", False))
204
- y = centered(f"Edition {edition}", fonts.regular_font(28, bold=bold_ed), y, fg) + 24
208
+ y = centered(f"Edition {edition}", fonts.regular_font(ed_date_fs, bold=bold_ed), y, fg) + 20
205
209
 
206
- # --- Date ---
207
210
  pub_date = (m.doc_pub_date or m.publication_date) if m else None
208
211
  date_str = _format_date(pub_date)
209
212
  if date_str:
210
- y = centered(f"Dated {date_str}", fonts.regular_font(28), y, fg) + 80
213
+ y = centered(f"Dated {date_str}", fonts.regular_font(ed_date_fs, bold=bold_ed), y, fg) + 80
211
214
 
212
215
  # --- Title (large, bold, wrapped) ---
213
216
  title = getattr(profile, "title", "") or ""