@@ -93,67 +93,96 @@ def _deploy_uid(icao_id: str) -> str:
9393
9494# ═══════════════════════════════════════════════════════════════════════════
9595# Resource definitions
96+ #
97+ # Strict-parsing servers (csapi-go-v2 and later) reject any field in
98+ # GeoJSON `properties` outside the closed set
99+ # {featureType, uid, name, description, validTime, platform@link}.
100+ # All SensorML metadata (keywords, identifiers, classifiers, contacts,
101+ # documents, characteristics, capabilities, lineage, usageConstraints,
102+ # typeOf, ...) lives in a SEPARATE `application/sml+json` body that is
103+ # PUT against /systems/{id} or /procedures/{id} after creation.
104+ # See: docs/research/Strict_Parsing_Migration_Spec_Grounded_Reanalysis_2026-05-09.md §9.
96105# ═══════════════════════════════════════════════════════════════════════════
97106
98- PROCEDURE_BODY = {
107+ PROCEDURE_BODY_STUB = {
99108 "type" : "Feature" ,
100109 "geometry" : None ,
101110 "properties" : {
102- "uid" : PROC_UID ,
103111 "featureType" : "sosa:ObservingProcedure" ,
112+ "uid" : PROC_UID ,
104113 "name" : "METAR Decoder v1" ,
105114 "description" : (
106115 "Publishes real-time METAR observations from the AviationWeather.gov REST API. "
107116 "Data includes temperature, dew point, wind speed/direction, visibility, "
108117 "barometric pressure, cloud layers, flight category, and the raw METAR string. "
109118 "Observations are decoded from standard METAR format and published to CSAPI."
110119 ),
111- "keywords" : [
112- "METAR" , "aviation" , "weather" , "AviationWeather.gov" , "FAA" , "AWC" ,
113- "ASOS" , "surface observation" , "flight category" ,
114- ],
115- "documentation" : [
116- {"title" : "AviationWeather.gov" , "href" : AWX_HOME , "rel" : "about" },
117- {"title" : "AviationWeather Data API" , "href" : AWX_API_DOC , "rel" : "documentation" },
118- {"title" : "METAR Format Guide" , "href" : "https://www.weather.gov/media/wrh/mesowest/metar_decode_key.pdf" , "rel" : "describedby" },
119- ],
120- "contacts" : [
121- {
122- "role" : "operator" ,
123- "organizationName" : FAA_CONTACT_ORG ,
124- "website" : AWX_HOME ,
125- "email" : FAA_CONTACT_EMAIL ,
126- },
127- {
128- "role" : "publisher" ,
129- "organizationName" : "OS4CSAPI" ,
130- "website" : "https://github.com/OS4CSAPI/OSHConnect-Python" ,
120+ "validTime" : [VALID_TIME_START , ".." ],
121+ },
122+ }
123+
124+ PROCEDURE_SML = {
125+ "type" : "SimpleProcess" ,
126+ "id" : PROC_UID ,
127+ "uniqueId" : PROC_UID ,
128+ "definition" : "sosa:ObservingProcedure" ,
129+ "label" : "METAR Decoder v1" ,
130+ "description" : (
131+ "Publishes real-time METAR observations from the AviationWeather.gov REST API. "
132+ "Data includes temperature, dew point, wind speed/direction, visibility, "
133+ "barometric pressure, cloud layers, flight category, and the raw METAR string. "
134+ "Observations are decoded from standard METAR format and published to CSAPI."
135+ ),
136+ "keywords" : [
137+ "METAR" , "aviation" , "weather" , "AviationWeather.gov" , "FAA" , "AWC" ,
138+ "ASOS" , "surface observation" , "flight category" ,
139+ ],
140+ # NOTE: csapi-go-v2 ProcedureSensorMLFeature struct still has the
141+ # `documentation` typo (the c2ab201 fix landed only on
142+ # SystemSensorMLFeature). Until that lands, procedure SML PUT requires
143+ # the typo'd field name. /systems uses `documents` (correct).
144+ "documentation" : [
145+ {"role" : "http://dbpedia.org/resource/Web_page" ,
146+ "name" : "AviationWeather.gov" ,
147+ "link" : {"href" : AWX_HOME , "type" : "text/html" }},
148+ {"role" : "http://dbpedia.org/resource/Web_page" ,
149+ "name" : "AviationWeather Data API" ,
150+ "link" : {"href" : AWX_API_DOC , "type" : "text/html" }},
151+ {"role" : "http://dbpedia.org/resource/Web_page" ,
152+ "name" : "METAR Format Guide" ,
153+ "link" : {"href" : "https://www.weather.gov/media/wrh/mesowest/metar_decode_key.pdf" ,
154+ "type" : "application/pdf" }},
155+ ],
156+ "contacts" : [
157+ {
158+ "role" : "operator" ,
159+ "organisationName" : FAA_CONTACT_ORG ,
160+ "contactInfo" : {
161+ "address" : {
162+ "deliveryPoint" : FAA_CONTACT_ADDRESS ,
163+ "electronicMailAddress" : FAA_CONTACT_EMAIL ,
164+ },
165+ "onlineResource" : {"linkage" : AWX_HOME },
131166 },
132- ],
133- "lineage" : {
134- "source" : "FAA / Aviation Weather Center (AWC)" ,
135- "upstream" : f"AviationWeather.gov REST API at { AWX_API_DOC } " ,
136- "normalization" : (
137- "Publisher fetches decoded METAR JSON from the AviationWeather.gov API "
138- "and emits a flat JSON result with metric/aviation-standard units."
139- ),
140167 },
141- "usageConstraints" : {
142- "sourceProtocol" : "HTTPS" ,
143- "sourceFormat" : "JSON via AviationWeather.gov REST API" ,
144- "rateLimitNote" : "No explicit rate limit documented; publisher uses 5-minute cadence." ,
145- "qualityControlNote" : (
146- "METAR reports undergo NWS/FAA quality control. SPECI (special) reports "
147- "are issued between standard hourly observations when conditions change significantly."
148- ),
168+ {
169+ "role" : "publisher" ,
170+ "organisationName" : "OS4CSAPI" ,
171+ "contactInfo" : {
172+ "onlineResource" : {"linkage" : "https://github.com/OS4CSAPI/OSHConnect-Python" },
173+ },
149174 },
150- "validTime" : [VALID_TIME_START , ".." ],
151- },
175+ ],
152176}
153177
154178
155179def _system_stub (station : dict , proc_id : str ) -> dict :
156- """GeoJSON Feature stub for an aviation weather station system."""
180+ """GeoJSON Feature stub for an aviation weather station system.
181+
182+ Properties closed to {featureType, uid, name, description} per OGC 23-001
183+ strict parsing. typeOf, validTime, and links live in the companion SML body
184+ (see ``_system_sml``).
185+ """
157186 icao_id = station ["icao_id" ]
158187 return {
159188 "type" : "Feature" ,
@@ -162,21 +191,15 @@ def _system_stub(station: dict, proc_id: str) -> dict:
162191 "coordinates" : [station ["lon" ], station ["lat" ]],
163192 },
164193 "properties" : {
165- "uid" : _system_uid (icao_id ),
166194 "featureType" : "sosa:Sensor" ,
195+ "uid" : _system_uid (icao_id ),
167196 "name" : f"AWX { icao_id } — { station ['name' ]} " ,
168197 "description" : (
169198 f"AviationWeather.gov METAR station { icao_id } at { station ['name' ]} , "
170199 f"{ station ['city' ]} , { station ['state' ]} . "
171200 f"Station type: { station .get ('station_type' , 'ASOS' )} . "
172201 f"Field elevation: { station .get ('elev_m' , '?' )} m MSL."
173202 ),
174- "typeOf@link" : {"href" : proc_id , "title" : "METAR Decoder v1" },
175- "links" : [
176- {"rel" : "about" , "title" : "METAR Data" , "href" : _station_page_url (icao_id )},
177- {"rel" : "alternate" , "title" : "API Endpoint" , "href" : _station_api_url (icao_id )},
178- ],
179- "validTime" : [VALID_TIME_START , ".." ],
180203 },
181204 }
182205
@@ -289,29 +312,13 @@ def _system_sml(station: dict) -> dict:
289312 },
290313 ],
291314 "documents" : docs ,
292- "characteristics" : [
293- {
294- "name" : "station_characteristics" ,
295- "type" : "DataRecord" ,
296- "label" : "Station Characteristics" ,
297- "fields" : char_items ,
298- },
299- ],
300- "capabilities" : [
301- {
302- "name" : "publisher_capabilities" ,
303- "type" : "DataRecord" ,
304- "label" : "Publisher Capabilities" ,
305- "capabilities" : [
306- {"type" : "Quantity" , "name" : "update_interval" ,
307- "definition" : "http://qudt.org/vocab/quantitykind/Period" ,
308- "label" : "Publish Interval" , "uom" : {"code" : "s" }, "value" : 300.0 },
309- {"type" : "Text" , "name" : "data_source" ,
310- "definition" : "http://sensorml.com/ont/swe/property/DataSource" ,
311- "label" : "Data Source" , "value" : "AviationWeather.gov REST API (METAR JSON)" },
312- ],
313- },
314- ],
315+ # NOTE: characteristics/capabilities are part of OGC SensorML JSON encoding
316+ # but the strict csapi-go-v2 server does not accept them on the
317+ # SystemSensorMLFeature struct (see empirical probe 2026-05-09).
318+ # Field-elevation, station_type, operator, and update_interval information
319+ # is preserved in identifiers/classifiers/position above. char_items
320+ # (operator, station_type, faa_id, field_elevation) are intentionally not
321+ # serialised here; restore once upstream adds these fields back.
315322 "position" : {
316323 "type" : "Point" ,
317324 "coordinates" : [station ["lon" ], station ["lat" ]],
@@ -336,33 +343,24 @@ def _datastream_schema(icao_id: str = "") -> dict:
336343 cloud_base_ft - Lowest cloud base (feet AGL)
337344 raw_metar - Raw METAR text
338345 """
339- uid_suffix = f":{ icao_id .lower ()} " if icao_id else ""
346+ # NOTE: Strict csapi-go-v2 rejects 'documentation', 'characteristics', and
347+ # SWE Time field 'referenceTime'. Keeping body to fields the server accepts.
340348 return {
341- "uid" : f"urn:os4csapi:datastream:awx{ uid_suffix } :metarObs:v1" ,
342349 "outputName" : DS_OUTPUT_NAME ,
343350 "name" : "METAR Observation" ,
344351 "description" : (
345352 "Decoded METAR aviation weather observation from an AviationWeather.gov station. "
346353 "Includes temperature, dew point, wind, visibility, altimeter setting, cloud layers, "
347354 "flight category, and the raw METAR string. Some fields may be NaN if not reported."
348355 ),
349- "documentation" : [
350- {"title" : "AviationWeather Data API" , "href" : AWX_API_DOC , "rel" : "documentation" },
351- {"title" : "METAR Decode Key" , "href" : "https://www.weather.gov/media/wrh/mesowest/metar_decode_key.pdf" , "rel" : "describedby" },
352- ],
353- "characteristics" : [
354- {"label" : "Source Format" , "value" : "JSON via AviationWeather.gov REST API" },
355- {"label" : "Nominal Availability" , "value" : "Hourly METAR; SPECI on significant changes" },
356- {"label" : "Quality Control" , "value" : "NWS/FAA automated and manual QC applied" },
357- ],
358356 "schema" : {
359357 "obsFormat" : "application/om+json" ,
360358 "resultSchema" : {
361359 "type" : "DataRecord" ,
362360 "label" : "METAR Observation" ,
363361 "description" : "Decoded METAR aviation weather observation" ,
364362 "fields" : [
365- {"type" : "Time" , "name" : "timestamp" , "label" : "Observation Time" , "definition" : "http://www.opengis.net/def/property/OGC/0/SamplingTime" , "referenceTime" : "1970-01-01T00:00:00Z" , " uom" : {"code" : "s" }},
363+ {"type" : "Time" , "name" : "timestamp" , "label" : "Observation Time" , "definition" : "http://www.opengis.net/def/property/OGC/0/SamplingTime" , "uom" : {"code" : "s" }},
366364 {"type" : "Text" , "name" : "stationId" , "label" : "ICAO Station ID" , "definition" : "http://sensorml.com/ont/swe/property/StationID" },
367365 {"type" : "Quantity" , "name" : "lat_deg" , "label" : "Latitude" , "definition" : "http://sensorml.com/ont/swe/property/GeodeticLatitude" , "uom" : {"code" : "deg" }},
368366 {"type" : "Quantity" , "name" : "lon_deg" , "label" : "Longitude" , "definition" : "http://sensorml.com/ont/swe/property/GeodeticLongitude" , "uom" : {"code" : "deg" }},
@@ -391,17 +389,14 @@ def _deploy_root() -> dict:
391389 "coordinates" : [- 111.5 , 33.0 ],
392390 },
393391 "properties" : {
394- "uid" : DEPLOY_ROOT_UID ,
395392 "featureType" : "sosa:Deployment" ,
393+ "uid" : DEPLOY_ROOT_UID ,
396394 "name" : "AviationWeather METAR Demo Deployment" ,
397395 "description" : (
398396 "Top-level CSAPI deployment grouping for AviationWeather.gov METAR stations "
399397 "published by OSHConnect-Python. This grouping represents the demo / integration "
400398 "scope, not a single physical field deployment."
401399 ),
402- "documentation" : [
403- {"title" : "AviationWeather.gov" , "href" : AWX_HOME , "rel" : "about" },
404- ],
405400 "validTime" : [VALID_TIME_START , ".." ],
406401 },
407402 }
@@ -415,17 +410,13 @@ def _deploy_group() -> dict:
415410 "coordinates" : [- 111.5 , 33.0 ],
416411 },
417412 "properties" : {
418- "uid" : DEPLOY_GROUP_UID ,
419413 "featureType" : "sosa:Deployment" ,
414+ "uid" : DEPLOY_GROUP_UID ,
420415 "name" : "AviationWeather METAR Stations" ,
421416 "description" : (
422417 "Grouping deployment for curated AviationWeather.gov METAR stations. Each child "
423418 "deployment links a station/system resource to the demo deployment tree."
424419 ),
425- "documentation" : [
426- {"title" : "AviationWeather.gov" , "href" : AWX_HOME , "rel" : "about" },
427- {"title" : "AviationWeather Data API" , "href" : AWX_API_DOC , "rel" : "documentation" },
428- ],
429420 "validTime" : [VALID_TIME_START , ".." ],
430421 },
431422 }
@@ -440,8 +431,8 @@ def _deploy_station(station: dict, system_server_id: str) -> dict:
440431 "coordinates" : [station ["lon" ], station ["lat" ]],
441432 },
442433 "properties" : {
443- "uid" : _deploy_uid (icao_id ),
444434 "featureType" : "sosa:Deployment" ,
435+ "uid" : _deploy_uid (icao_id ),
445436 "name" : f"METAR { icao_id } Feed" ,
446437 "description" : f"AviationWeather METAR station { icao_id } ({ station ['name' ]} ) observation feed." ,
447438 "validTime" : [VALID_TIME_START , ".." ],
@@ -450,10 +441,6 @@ def _deploy_station(station: dict, system_server_id: str) -> dict:
450441 "uid" : _system_uid (icao_id ),
451442 "title" : f"AWX { icao_id } " ,
452443 },
453- "links" : [
454- {"rel" : "about" , "title" : "METAR Data" , "href" : _station_page_url (icao_id )},
455- {"rel" : "alternate" , "title" : "API Endpoint" , "href" : _station_api_url (icao_id )},
456- ],
457444 },
458445 }
459446
@@ -513,8 +500,10 @@ def bootstrap(*, clean: bool = False, clean_only: bool = False,
513500
514501 # ── Procedure ─────────────────────────────────────────────────────
515502 print (" ── Procedures ──" )
516- proc_id = ensure_procedure (base_url , auth , PROC_UID , PROCEDURE_BODY ,
517- dry_run = dry_run , stats = stats )
503+ proc_id = ensure_procedure (base_url , auth , PROC_UID , PROCEDURE_BODY_STUB ,
504+ sml_body = PROCEDURE_SML ,
505+ dry_run = dry_run , stats = stats ,
506+ force_sml = force_sml )
518507
519508 # ── Systems + Datastreams ─────────────────────────────────────────
520509 print (" ── Systems + Datastreams ──" )
0 commit comments