Skip to content

Commit 09b2b3e

Browse files
committed
Expose legacy demo refresh metadata
1 parent b0a1ab6 commit 09b2b3e

1 file changed

Lines changed: 145 additions & 0 deletions

File tree

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#!/usr/bin/env python3
2+
"""Add refresh metadata to legacy scenario/demo systems.
3+
4+
These systems are not normal scheduled public-data publishers. They are legacy
5+
scenario resources whose observations are produced by simulator/demo runs or by
6+
operator action. This script preserves the current SensorML and adds a
7+
card-readable refresh metadata capability so Explorer does not imply a false
8+
polling cadence.
9+
"""
10+
11+
import argparse
12+
import json
13+
import os
14+
import sys
15+
from urllib.error import HTTPError
16+
from urllib.request import Request, urlopen
17+
18+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
19+
from publishers.bootstrap_helpers import get_config, _auth_header
20+
21+
22+
SYSTEMS = [
23+
{
24+
"id": "040g",
25+
"label": "SET-A",
26+
"refresh_rate": "Scenario-driven / not scheduled",
27+
"query_mode": "Operator-authored SENREP reports are created by scenario workflow, not a fixed poller",
28+
},
29+
{
30+
"id": "0410",
31+
"label": "Monitoring Site 001",
32+
"refresh_rate": "Scenario-driven / not scheduled",
33+
"query_mode": "Monitoring-site support metadata changes only during scenario updates",
34+
},
35+
{
36+
"id": "041g",
37+
"label": "Relay",
38+
"refresh_rate": "Scenario-driven / not scheduled",
39+
"query_mode": "Relay support metadata changes only during scenario updates",
40+
},
41+
{
42+
"id": "0420",
43+
"label": "ODAS Mic Array Node AZ-MA-1",
44+
"refresh_rate": "Scenario-driven / simulator active",
45+
"query_mode": "ODAS observations are emitted by scenario simulator runs when active",
46+
},
47+
{
48+
"id": "0490",
49+
"label": "ODAS Mic Array Node AZ-MA-2",
50+
"refresh_rate": "Scenario-driven / simulator active",
51+
"query_mode": "ODAS observations are emitted by scenario simulator runs when active",
52+
},
53+
{
54+
"id": "049g",
55+
"label": "ODAS Mic Array Node AZ-MA-3",
56+
"refresh_rate": "Scenario-driven / simulator active",
57+
"query_mode": "ODAS observations are emitted by scenario simulator runs when active",
58+
},
59+
{
60+
"id": "04o0",
61+
"label": "AZ String Alpha Localizer",
62+
"refresh_rate": "Scenario-driven / simulator active",
63+
"query_mode": "Localizer estimates are emitted when recent scenario LOB observations are available",
64+
},
65+
]
66+
67+
68+
def _request_json(url: str, auth: str, *, method: str = "GET", body: dict | None = None) -> dict:
69+
data = json.dumps(body).encode() if body is not None else None
70+
req = Request(url, data=data, method=method, headers={
71+
"Authorization": auth,
72+
"Accept": "application/sml+json",
73+
"Content-Type": "application/sml+json",
74+
})
75+
try:
76+
with urlopen(req, timeout=30) as resp:
77+
raw = resp.read().decode()
78+
return json.loads(raw) if raw.strip() else {}
79+
except HTTPError as exc:
80+
raw = exc.read().decode(errors="replace")
81+
raise RuntimeError(f"HTTP {exc.code} {method} {url}: {raw[:400]}") from exc
82+
83+
84+
def _refresh_capability(entry: dict) -> dict:
85+
return {
86+
"definition": "http://www.w3.org/ns/ssn/systems/SystemCapability",
87+
"label": "Refresh Metadata",
88+
"capabilities": [
89+
{
90+
"type": "Text",
91+
"name": "refresh_rate",
92+
"definition": "http://sensorml.com/ont/swe/property/ReportingFrequency",
93+
"label": "Refresh Rate",
94+
"value": entry["refresh_rate"],
95+
},
96+
{
97+
"type": "Text",
98+
"name": "source_query_mode",
99+
"definition": "http://sensorml.com/ont/swe/property/ReportingFrequency",
100+
"label": "Source Query Mode",
101+
"value": entry["query_mode"],
102+
},
103+
],
104+
}
105+
106+
107+
def _merge_refresh_metadata(sml: dict, entry: dict) -> dict:
108+
capabilities = [
109+
item for item in sml.get("capabilities", [])
110+
if item.get("label") != "Refresh Metadata" and item.get("name") != "refresh_metadata"
111+
]
112+
capabilities.append(_refresh_capability(entry))
113+
updated = dict(sml)
114+
updated["capabilities"] = capabilities
115+
return updated
116+
117+
118+
def main() -> int:
119+
parser = argparse.ArgumentParser(description="Enrich legacy demo systems with truthful refresh metadata.")
120+
parser.add_argument("--dry-run", action="store_true", help="Fetch and show intended updates without PUT")
121+
args = parser.parse_args()
122+
123+
config = get_config()
124+
base_url = config["base_url"].rstrip("/")
125+
auth = _auth_header(config["user"], config["password"])
126+
127+
for entry in SYSTEMS:
128+
url = f"{base_url}/systems/{entry['id']}?f=sml3"
129+
sml = _request_json(url, auth)
130+
label = sml.get("label") or sml.get("name") or entry["label"]
131+
updated = _merge_refresh_metadata(sml, entry)
132+
if args.dry_run:
133+
print(f"[DRY] {entry['id']} {label}: Refresh Rate={entry['refresh_rate']}")
134+
continue
135+
try:
136+
_request_json(f"{base_url}/systems/{entry['id']}", auth, method="PUT", body=updated)
137+
print(f"[SML] {entry['id']} {label}: Refresh Rate={entry['refresh_rate']}")
138+
except RuntimeError as exc:
139+
print(f"[WARN] PUT returned warning for {entry['id']} {label}: {exc}")
140+
141+
return 0
142+
143+
144+
if __name__ == "__main__":
145+
raise SystemExit(main())

0 commit comments

Comments
 (0)