@@ -105,7 +105,7 @@ def _points_url(lat: float, lon: float) -> str:
105105# Resource definitions
106106# ═══════════════════════════════════════════════════════════════════════════
107107
108- PROCEDURE_BODY = {
108+ PROCEDURE_STUB = {
109109 "type" : "Feature" ,
110110 "geometry" : None ,
111111 "properties" : {
@@ -119,48 +119,128 @@ def _points_url(lat: float, lon: float) -> str:
119119 "ASOS/AWOS; nominal station reporting is hourly with special observations as conditions "
120120 "warrant, and API publication may lag upstream MADIS availability by up to about 20 minutes."
121121 ),
122+ "validTime" : [VALID_TIME_START , ".." ],
123+ },
124+ }
125+
126+
127+ def _procedure_sml () -> dict :
128+ """SensorML body for the NWS Surface Observation procedure.
129+
130+ Encoding: ``application/sml+json``. PUT against ``/procedures/{id}``
131+ after the geo+json stub POST. Field shapes follow the SensorML JSON
132+ encoding (``documents``, ``contacts.organisationName``,
133+ ``contactInfo``) so the CSAPI server persists into the corresponding
134+ procedure-table columns.
135+ """
136+ return {
137+ "type" : "SimpleProcess" ,
138+ "id" : PROC_UID ,
139+ "uniqueId" : PROC_UID ,
140+ "definition" : "http://www.opengis.net/def/procedure/observation" ,
141+ "label" : "NWS Surface Observation v1" ,
142+ "description" : (
143+ "Observing procedure for NWS ASOS/AWOS surface weather stations exposed through "
144+ "api.weather.gov. Latest station observations are normalized to SI-oriented fields "
145+ "(degC, Pa, km/h, m) and published as flat JSON result objects. Nominal station "
146+ "reporting is hourly with special observations as conditions warrant; API publication "
147+ "may lag upstream MADIS availability by up to about 20 minutes."
148+ ),
122149 "keywords" : [
123- "NWS" ,
124- "NOAA" ,
125- "ASOS" ,
126- "AWOS" ,
127- "surface weather" ,
128- "METAR" ,
129- "api.weather.gov" ,
130- "aviation weather" ,
150+ "NWS" , "NOAA" , "ASOS" , "AWOS" ,
151+ "surface weather" , "METAR" ,
152+ "api.weather.gov" , "aviation weather" ,
131153 ],
132- "documentation" : [
133- {"title" : "NWS API Web Service" , "href" : NWS_API_DOCS , "rel" : "documentation" },
134- {"title" : "NWS OpenAPI Specification" , "href" : NWS_OPENAPI , "rel" : "describedby" },
135- {"title" : "NWS ASOS Program" , "href" : NWS_ASOS_PAGE , "rel" : "about" },
136- {"title" : "NWS ASOS Equipment FAQ" , "href" : NWS_ASOS_EQUIP , "rel" : "related" },
137- {"title" : "NWS ASOS Contact" , "href" : NWS_ASOS_CONTACT , "rel" : "contact" },
154+ "identifiers" : [
155+ {"definition" : "http://sensorml.com/ont/swe/property/ShortName" ,
156+ "label" : "Short Name" , "value" : "NWS Surface Observation" },
157+ {"definition" : "http://sensorml.com/ont/swe/property/LongName" ,
158+ "label" : "Long Name" ,
159+ "value" : "National Weather Service ASOS/AWOS Surface Observation Procedure v1" },
160+ {"definition" : "http://sensorml.com/ont/swe/property/UniqueID" ,
161+ "label" : "OS4CSAPI UID" , "value" : PROC_UID },
162+ ],
163+ "classifiers" : [
164+ {"definition" : "http://sensorml.com/ont/swe/property/IntendedApplication" ,
165+ "label" : "Network" , "value" : "ASOS / AWOS Surface Weather Observation Network" },
166+ {"definition" : "http://sensorml.com/ont/swe/property/SystemRole" ,
167+ "label" : "Operator" , "value" : "National Weather Service" },
138168 ],
139169 "contacts" : [
140170 {
141- "role" : "operator" ,
142- "organizationName" : "National Weather Service" ,
143- "website" : NWS_API_BASE ,
171+ "role" : "http://sensorml.com/ont/swe/property/Operator" ,
172+ "organisationName" : "National Weather Service" ,
173+ "contactInfo" : {
174+ "website" : NWS_API_BASE ,
175+ },
144176 },
145177 {
146- "role" : "support" ,
147- "organizationName" : "ASOS Operations and Monitoring Center" ,
148- "email" : NWS_AOMC_EMAIL ,
149- "phone" : NWS_AOMC_PHONE_1 ,
178+ "role" : "http://sensorml.com/ont/swe/property/Maintainer" ,
179+ "organisationName" : "ASOS Operations and Monitoring Center" ,
180+ "contactInfo" : {
181+ "website" : NWS_ASOS_CONTACT ,
182+ "phone" : {"voice" : NWS_AOMC_PHONE_1 },
183+ "email" : NWS_AOMC_EMAIL ,
184+ "address" : {"country" : "United States" },
185+ },
186+ },
187+ ],
188+ "documents" : [
189+ {
190+ "role" : "http://dbpedia.org/resource/Documentation" ,
191+ "name" : "NWS API Web Service" ,
192+ "link" : {"href" : NWS_API_DOCS , "type" : "text/html" },
193+ },
194+ {
195+ "role" : "http://dbpedia.org/resource/OpenAPI_Specification" ,
196+ "name" : "NWS OpenAPI Specification" ,
197+ "link" : {"href" : NWS_OPENAPI , "type" : "application/openapi+json" },
198+ },
199+ {
200+ "role" : "http://dbpedia.org/resource/Web_page" ,
201+ "name" : "NWS ASOS Program" ,
202+ "link" : {"href" : NWS_ASOS_PAGE , "type" : "text/html" },
203+ },
204+ {
205+ "role" : "http://dbpedia.org/resource/FAQ" ,
206+ "name" : "NWS ASOS Equipment FAQ" ,
207+ "link" : {"href" : NWS_ASOS_EQUIP , "type" : "text/html" },
208+ },
209+ {
210+ "role" : "http://dbpedia.org/resource/Contact" ,
211+ "name" : "NWS ASOS Contact" ,
212+ "link" : {"href" : NWS_ASOS_CONTACT , "type" : "text/html" },
213+ },
214+ ],
215+ "characteristics" : [
216+ {
217+ "label" : "Lineage" ,
218+ "definition" : "http://sensorml.com/ont/swe/property/Lineage" ,
219+ "characteristic" : [
220+ {"label" : "Source" , "value" : "NOAA / National Weather Service" },
221+ {"label" : "Upstream" ,
222+ "value" : "MADIS-mediated station observations exposed by api.weather.gov" },
223+ {"label" : "Normalization" ,
224+ "value" : "Publisher normalizes selected values to SI-oriented fields (degC, Pa, km/h, m)." },
225+ ],
226+ },
227+ {
228+ "label" : "Usage Constraints" ,
229+ "definition" : "http://sensorml.com/ont/swe/property/UsageConstraints" ,
230+ "characteristic" : [
231+ {"label" : "User-Agent Required" , "value" : "true" },
232+ {"label" : "Rate Limit" ,
233+ "value" : "NWS API is open data with unpublished but reasonable rate limits." },
234+ ],
150235 },
151236 ],
152- "lineage" : {
153- "source" : "NOAA / National Weather Service" ,
154- "upstream" : "MADIS-mediated station observations exposed by api.weather.gov" ,
155- "normalization" : "Publisher normalizes selected values to SI-oriented fields (degC, Pa, km/h, m)." ,
156- },
157- "usageConstraints" : {
158- "userAgentRequired" : True ,
159- "rateLimitNote" : "NWS API is open data with unpublished but reasonable rate limits." ,
160- },
161237 "validTime" : [VALID_TIME_START , ".." ],
162- },
163- }
238+ }
239+
240+
241+ def _procedure_stub () -> dict :
242+ """Geo+json stub for the procedure (stable copy of PROCEDURE_STUB)."""
243+ return PROCEDURE_STUB
164244
165245
166246def _system_stub (station : dict , proc_id : str ) -> dict :
@@ -382,6 +462,20 @@ def _datastream_schema(station_id: str = "") -> dict:
382462 }
383463
384464
465+ _DEPLOY_DOCUMENTS = [
466+ {
467+ "role" : "http://dbpedia.org/resource/Documentation" ,
468+ "name" : "NWS API Web Service" ,
469+ "link" : {"href" : NWS_API_DOCS , "type" : "text/html" },
470+ },
471+ {
472+ "role" : "http://dbpedia.org/resource/Web_page" ,
473+ "name" : "NWS ASOS Program" ,
474+ "link" : {"href" : NWS_ASOS_PAGE , "type" : "text/html" },
475+ },
476+ ]
477+
478+
385479def _deploy_root () -> dict :
386480 return {
387481 "type" : "Feature" ,
@@ -394,15 +488,24 @@ def _deploy_root() -> dict:
394488 "featureType" : "sosa:Deployment" ,
395489 "name" : "NWS Weather Demo" ,
396490 "description" : "Demonstration deployment for NWS surface weather observation systems published into CSAPI." ,
397- "documentation" : [
398- {"title" : "NWS API Web Service" , "href" : NWS_API_DOCS , "rel" : "documentation" },
399- {"title" : "NWS ASOS Program" , "href" : NWS_ASOS_PAGE , "rel" : "about" },
400- ],
401491 "validTime" : [VALID_TIME_START , ".." ],
402492 },
403493 }
404494
405495
496+ def _deploy_root_sml () -> dict :
497+ """SensorML body for the root demo deployment node."""
498+ return {
499+ "type" : "Deployment" ,
500+ "id" : DEPLOY_ROOT_UID ,
501+ "uniqueId" : DEPLOY_ROOT_UID ,
502+ "label" : "NWS Weather Demo" ,
503+ "description" : "Demonstration deployment for NWS surface weather observation systems published into CSAPI." ,
504+ "documents" : list (_DEPLOY_DOCUMENTS ),
505+ "validTime" : [VALID_TIME_START , ".." ],
506+ }
507+
508+
406509def _deploy_group () -> dict :
407510 return {
408511 "type" : "Feature" ,
@@ -415,15 +518,25 @@ def _deploy_group() -> dict:
415518 "featureType" : "sosa:Deployment" ,
416519 "name" : "NWS Weather Stations" ,
417520 "description" : "NWS ASOS/AWOS surface observation stations across the United States used for the OS4CSAPI demo." ,
418- "documentation" : [
419- {"title" : "NWS API Web Service" , "href" : NWS_API_DOCS , "rel" : "documentation" },
420- {"title" : "NWS ASOS Program" , "href" : NWS_ASOS_PAGE , "rel" : "about" },
421- ],
422521 "validTime" : [VALID_TIME_START , ".." ],
423522 },
424523 }
425524
426525
526+ def _deploy_group_sml () -> dict :
527+ """SensorML body for the NWS Weather Stations deployment grouping."""
528+ return {
529+ "type" : "Deployment" ,
530+ "id" : DEPLOY_GROUP_UID ,
531+ "uniqueId" : DEPLOY_GROUP_UID ,
532+ "label" : "NWS Weather Stations" ,
533+ "description" : "NWS ASOS/AWOS surface observation stations across the United States used for the OS4CSAPI demo." ,
534+ "keywords" : ["NWS" , "NOAA" , "ASOS" , "AWOS" , "surface weather" ],
535+ "documents" : list (_DEPLOY_DOCUMENTS ),
536+ "validTime" : [VALID_TIME_START , ".." ],
537+ }
538+
539+
427540def _deploy_station (station : dict , system_server_id : str ) -> dict :
428541 return {
429542 "type" : "Feature" ,
@@ -508,8 +621,10 @@ def bootstrap(*, clean: bool = False, clean_only: bool = False,
508621
509622 # ── Procedure ─────────────────────────────────────────────────────
510623 print (" ── Procedures ──" )
511- proc_id = ensure_procedure (base_url , auth , PROC_UID , PROCEDURE_BODY ,
512- dry_run = dry_run , stats = stats )
624+ proc_id = ensure_procedure (base_url , auth , PROC_UID ,
625+ _procedure_stub (), _procedure_sml (),
626+ dry_run = dry_run , stats = stats ,
627+ force_sml = force_sml )
513628
514629 # ── Systems + Datastreams ─────────────────────────────────────────
515630 print (" ── Systems + Datastreams ──" )
@@ -534,15 +649,21 @@ def bootstrap(*, clean: bool = False, clean_only: bool = False,
534649
535650 # ── Deployment tree ───────────────────────────────────────────────
536651 print (" ── Deployments ──" )
537- root_id = ensure_deployment (base_url , auth , DEPLOY_ROOT_UID , _deploy_root (),
538- dry_run = dry_run , stats = stats )
539- group_id = ensure_deployment (base_url , auth , DEPLOY_GROUP_UID , _deploy_group (),
652+ root_id = ensure_deployment (base_url , auth , DEPLOY_ROOT_UID ,
653+ _deploy_root (), _deploy_root_sml (),
654+ dry_run = dry_run , stats = stats ,
655+ force_sml = force_sml )
656+ group_id = ensure_deployment (base_url , auth , DEPLOY_GROUP_UID ,
657+ _deploy_group (), _deploy_group_sml (),
540658 parent_id = root_id ,
541- dry_run = dry_run , stats = stats )
659+ dry_run = dry_run , stats = stats ,
660+ force_sml = force_sml )
542661
543662 for st in stations :
544663 sys_id = system_ids .get (st ["id" ])
545664 if sys_id or dry_run :
665+ # _deploy_station carries no SensorML-only fields under properties;
666+ # geo+json stub is sufficient.
546667 ensure_deployment (base_url , auth , _deploy_uid (st ["id" ]),
547668 _deploy_station (st , sys_id or "pending" ),
548669 parent_id = group_id ,
0 commit comments