Metadata-Version: 2.4
Name: ogc-edr-profile
Version: 1.0.4
Summary: Authoritative tooling for creating OGC API - EDR Part 3 Service Profiles
Author-email: Shane Mill <shane.mill@noaa.gov>
License: Apache License
        Version 2.0, January 2004
        http://www.apache.org/licenses/
        
        Copyright 2025 NOAA/NWS/Meteorological Development Laboratory
        
        Licensed under the Apache License, Version 2.0 (the "License");
        you may not use this file except in compliance with the License.
        You may obtain a copy of the License at
        
            http://www.apache.org/licenses/LICENSE-2.0
        
        Unless required by applicable law or agreed to in writing, software
        distributed under the License is distributed on an "AS IS" BASIS,
        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        See the License for the specific language governing permissions and
        limitations under the License.
        
Project-URL: Homepage, https://github.com/ShaneMill1/OGC-Service-Profile-Creation
Project-URL: Issues, https://github.com/ShaneMill1/OGC-Service-Profile-Creation/issues
Keywords: ogc,edr,environmental-data-retrieval,profile,openapi
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: GIS
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: edr-pydantic>=0.3
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Dynamic: license-file

# OGC API - EDR Part 3 Service Profile Generator

Authoritative tooling for creating OGC API - Environmental Data Retrieval (EDR) Part 3 Service Profiles, built on Pydantic and [edr-pydantic](https://github.com/KNMI/edr-pydantic).

## Overview

Profile structure is defined as Pydantic models (`src/models.py`). Instantiating a `ServiceProfile` validates the entire profile — enums enforce normative OGC values, cross-model validators catch referential errors — before any files are written.

Collections use `edr-pydantic`'s authoritative `Collection` model directly, meaning a profile config is simultaneously a valid EDR collection descriptor and a Part 3 profile definition.

## Installation

```bash
pip install ogc-edr-profile
```

## Usage

### Generate profile artifacts

```bash
ogc-edr-profile generate --config examples/water_gauge.yaml --output ./my_profile
```

### Validate a config without generating output

```bash
ogc-edr-profile validate --config examples/water_gauge.yaml
```

### Export the JSON Schema

```bash
ogc-edr-profile schema --output profile.schema.json
```

The schema can be used for editor autocompletion — point your YAML extension at `profile.schema.json` to get inline validation and suggestions while authoring configs.

### Output

```
my_profile/
├── openapi.yaml
├── asyncapi.yaml                        # if pubsub is configured
├── profile_config.json                  # round-trip model export
├── requirements/
│   ├── requirements_class_core.adoc
│   └── core/REQ_<id>.adoc
└── abstract_tests/
    ├── ATS_class_core.adoc
    └── core/ATS_<id>.adoc
```

## Getting Started

The fastest way to create a profile is to copy the example and modify it:

```bash
cp examples/water_gauge.yaml my_profile.yaml
```

Then edit `my_profile.yaml` — at minimum change `name`, `title`, and the collection `id`. Validate as you go:

```bash
ogc-edr-profile validate --config my_profile.yaml
```

Once valid, generate:

```bash
ogc-edr-profile generate --config my_profile.yaml --output ./my_profile
```

If you prefer to build from scratch, the minimal valid config looks like this:

```yaml
name: my_profile
title: My EDR Profile

collections:
  - id: my_collection
    links:
      - href: https://example.com/collections/my_collection
        rel: self
        type: application/json
    extent:
      spatial:
        bbox:
          - [-180, -90, 180, 90]
        crs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
    parameter_names: {}
```

From there, add `data_queries` to declare which query types your collection supports, `output_formats` to declare response formats, `requirements` to define normative statements, and `abstract_tests` to define conformance tests. Each section is described in detail below.

---

## Authoring a Profile Config

A profile config is a YAML or JSON file that defines a `ServiceProfile`. The full machine-readable schema is in [`profile.schema.json`](profile.schema.json). Below is a field-by-field reference.

### Top-level fields

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | yes | Lowercase identifier using only `a-z`, `0-9`, `_`. Used in OGC URIs, filenames, and OpenAPI `operationId`s. e.g. `water_gauge` |
| `title` | `string` | yes | Human-readable profile title. e.g. `Water Gauge Observations Profile` |
| `version` | `string` | no | Profile version. Defaults to `1.0` |
| `collections` | `list` | yes | One or more EDR collections (see below) |
| `requirements` | `list` | no | Normative requirements (see below) |
| `abstract_tests` | `list` | no | Conformance tests — each must reference a valid requirement `id` (see below) |
| `pubsub` | `object` | no | OGC API - EDR Part 2 PubSub configuration (see below) |

---

### `collections[]`

Each collection uses the [edr-pydantic](https://github.com/KNMI/edr-pydantic) `Collection` schema — the same model an EDR server returns at `/collections/{id}`. Key fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Collection identifier. e.g. `water_gauge` |
| `title` | `string` | no | Human-readable collection name |
| `description` | `string` | no | Collection description |
| `links` | `list` | yes | At minimum a `self` link |
| `extent` | `object` | yes | Spatial (and optionally temporal/vertical) extent |
| `extent.spatial.bbox` | `list` | yes | Bounding box as `[[minLon, minLat, maxLon, maxLat]]` |
| `extent.spatial.crs` | `string` | yes | CRS URI, typically `http://www.opengis.net/def/crs/OGC/1.3/CRS84` |
| `data_queries` | `object` | no | Which EDR query types this collection supports (see below) |
| `output_formats` | `list` | no | Supported output format labels e.g. `GeoJSON`, `CoverageJSON`, `CSV` |
| `parameter_names` | `object` | no | Map of parameter id → `Parameter` object describing observed properties |

#### `data_queries`

Each key is an EDR query type. Set it to enable that query type for the collection. Supported keys:

`items` · `position` · `area` · `radius` · `cube` · `trajectory` · `corridor` · `locations` · `instances`

Each value is an `EDRQuery` object with a `link` field:

```yaml
data_queries:
  position:
    link:
      href: https://example.com/collections/water_gauge/position
      rel: data
      variables:
        query_type: position
        output_formats:
          - CoverageJSON
  items:
    link:
      href: https://example.com/collections/water_gauge/items
      rel: data
      variables:
        query_type: items
        output_formats:
          - GeoJSON
          - CSV
```

#### `parameter_names`

Describes the observed properties the collection exposes. Each entry is a `Parameter` object:

```yaml
parameter_names:
  gauge_height:
    type: Parameter
    observedProperty:
      label: Gauge Height
    unit:
      label: feet
      symbol: ft
  streamflow:
    type: Parameter
    observedProperty:
      label: Streamflow
    unit:
      label: cubic feet per second
      symbol: cfs
```

---

### `requirements[]`

Normative requirements that implementations of this profile must satisfy. Each requirement becomes a `REQ_<id>.adoc` file and an entry in the requirements class.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Lowercase, hyphen-separated identifier. e.g. `data-query-items-water-gauge`. Must match pattern `^[a-z0-9][a-z0-9\-]*$` |
| `statement` | `string` | yes | One-sentence normative statement of the requirement |
| `parts` | `list[string]` | yes | One or more SHALL/MUST clauses that make up the requirement body |

```yaml
requirements:
  - id: data-query-items-water-gauge
    statement: The water_gauge collection SHALL support the Items query type.
    parts:
      - The service SHALL provide a /collections/water_gauge/items endpoint.
      - The Items query SHALL return a GeoJSON FeatureCollection.
```

---

### `abstract_tests[]`

Conformance tests corresponding to requirements. Each test becomes an `ATS_<id>.adoc` file and an entry in the conformance class. **Every `requirement_id` must match an existing requirement `id`** — the model validator will reject the profile if not.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Must equal `requirement_id` |
| `requirement_id` | `string` | yes | The `id` of the requirement this test validates |
| `steps` | `list[string]` | yes | Ordered test steps |

```yaml
abstract_tests:
  - id: data-query-items-water-gauge
    requirement_id: data-query-items-water-gauge
    steps:
      - Send GET request to /collections/water_gauge/items.
      - Verify the response is a valid GeoJSON FeatureCollection.
```

---

### `pubsub`

Optional. Configures OGC API - EDR Part 2 (PubSub) output. When present, an `asyncapi.yaml` is generated.

| Field | Type | Default | Description |
|---|---|---|---|
| `broker_host` | `string` | `localhost` | Message broker hostname |
| `broker_port` | `integer` | `5672` | Broker port (1–65535) |
| `protocol` | `string` | `amqp` | One of `amqp`, `mqtt`, `kafka` |
| `filters` | `list` | `[]` | Subscription filters (see below) |

#### `pubsub.filters[]`

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | yes | Filter parameter name |
| `description` | `string` | yes | What the filter does |
| `type` | `string` | no | One of `string`, `number`, `array`, `boolean`. Defaults to `string` |

```yaml
pubsub:
  broker_host: localhost
  broker_port: 5672
  protocol: amqp
  filters:
    - name: state
      description: Filter by US state abbreviation.
      type: string
    - name: threshold_ft
      description: Only notify when gauge height exceeds this value.
      type: number
```

---

See [`examples/water_gauge.yaml`](examples/water_gauge.yaml) for a complete working config.

## Programmatic Use

```python
from ogc_edr_profile.models import ServiceProfile
from ogc_edr_profile.generate import generate
from pathlib import Path

profile = ServiceProfile.model_validate(config_dict)
generate(profile, Path("./output"))
```

## Repository Structure

```
├── src/
│   ├── models.py         # Authoritative Pydantic schema
│   ├── generate.py       # Serialization: validated model → OpenAPI, AsyncAPI, AsciiDoc
│   └── cli.py            # CLI entry point
├── examples/
│   └── water_gauge.yaml  # Complete example profile config
├── profile.schema.json   # Machine-readable JSON Schema for profile configs
└── pyproject.toml
```

## Standards

- OGC API - EDR Part 1: Core
- OGC API - EDR Part 2: PubSub
- OGC API - EDR Part 3: Service Profiles (draft)
- OpenAPI 3.0 / AsyncAPI 3.0
- Metanorma/AsciiDoc documentation format

## License

Apache License 2.0 — See [LICENSE](LICENSE) for details.

## Contact

- **Author**: Shane Mill (NOAA/NWS/MDL)
- **Email**: shane.mill@noaa.gov
- **Issues**: https://github.com/ShaneMill1/OGC-Service-Profile-Creation/issues
