mnt/data/ucls-spectrum-unified-all.zip


# Build a fresh, self-contained repository that includes:
# - Core UCLS DNS/IP/Domain/RF ontology & shapes
# - Policy packs (IDN/Locale, Domain, Address, RF, Spectrum Suite)
# - Gates & bindings
# - Per-country band plans (US/EU) + acronym crosswalk
# - Satellite beam overlays (GeoJSON) and Grafana geomap panels
# - Telemetry (ClickHouse schemas) + Prometheus stubs
# - Adapters stubs + Docker Compose (incl. static-geo server)
# - Samples
# Then zip it for download.
#
# Output zip: /mnt/data/ucls-spectrum-unified-all.zip

import os, json, yaml, zipfile, datetime, textwrap

root = "/mnt/data/ucls-dns-packs"
if os.path.isdir(root):
    pass
else:
    os.makedirs(root, exist_ok=True)

def write(path, content):
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        f.write(content.strip() + "\n")

def dump_yaml(path, obj):
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        yaml.safe_dump(obj, f, sort_keys=False)

now_iso = datetime.datetime.utcnow().replace(microsecond=0).isoformat() + "Z"

# README ---------------------------------------------------------------------
write(f"{root}/README.md", f"""
# UCLS DNS/IP/RF Spectrum Suite — Unified Packs

Everything-as-a-Service for names, addresses, RF sites/links, and spectrum policy —
grapheme-rooted, legacy-compatible, and future-proof. Includes per-country band plans,
automatic site→spectrum validation, and satellite beam overlays.

- **Schemas:** contexts + SHACL shapes
- **Packs:** Domain/IP/IDN/Locale + RF + Spectrum Suite
- **Gates & Bindings:** ready to attach to DomainName, Address, BroadcastSite, RFLink
- **Telemetry:** ClickHouse tables + Prometheus stubs
- **Dashboards:** Grafana JSON with tables, stats, and geomaps
- **Adapters:** RDAP/EPP/DoH, CT, DMARC/MTA-STS/TLS-RPT, SDR/cell/Wi‑Fi/BLE/GNSS/Satcom
- **Geo:** `dashboards/geo/sat_beams.geojson` served by `static-geo` (nginx) in docker compose

> Seed band tables (US/EU) provided for development. Replace with regulator exports for production.
""")

# Schemas: Contexts ----------------------------------------------------------
ctx = {
  "@context": {
    "ucls": "https://ucls.org/terms#",
    "id": "@id", "type": "@type",
    "Token":"ucls:Token",
    "DomainName":"ucls:DomainName",
    "TopLevelDomain":"ucls:TopLevelDomain",
    "Bundle":"ucls:Bundle",
    "Address":"ucls:Address",
    "IPv4":"ucls:IPv4",
    "IPv6":"ucls:IPv6",
    "ASN":"ucls:ASN",
    "Endpoint":"ucls:Endpoint",
    "MobileEndpoint":"ucls:MobileEndpoint",
    "BroadcastSite":"ucls:BroadcastSite",
    "BaseStation":"ucls:BaseStation",
    "Antenna":"ucls:Antenna",
    "RFLink":"ucls:RFLink",
    "SpectrumBlock":"ucls:SpectrumBlock",
    "EmissionMask":"ucls:EmissionMask",
    # common properties
    "value":"ucls:value",
    "tld":"ucls:tld",
    "bundleId":"ucls:bundleId",
    "controller":"ucls:controller",
    "script":"ucls:script",
    "scripts":"ucls:scripts",
    "locale":"ucls:locale",
    "region":"ucls:region",
    "policy":"ucls:policy",
    "asn":"ucls:asn",
    "ip":"ucls:ip",
    "ipv4":"ucls:ipv4",
    "ipv6":"ucls:ipv6",
    "mac":"ucls:mac",
    "lat":"ucls:lat",
    "lon":"ucls:lon",
    "alt":"ucls:alt",
    "band":"ucls:band",
    "tech":"ucls:tech",
    "freqFromMHz":"ucls:freqFromMHz",
    "freqToMHz":"ucls:freqToMHz",
    "ssid":"ucls:ssid",
    "bssid":"ucls:bssid",
    "enbId":"ucls:enbId",
    "gnbId":"ucls:gnbId",
    "nrArfcn":"ucls:nrArfcn",
    "azimuth":"ucls:azimuth",
    "tilt":"ucls:tilt",
    "gainDbi":"ucls:gainDbi",
    "licenseId":"ucls:licenseId",
    "regBody":"ucls:regBody",
    "start":"ucls:start",
    "end":"ucls:end"
  }
}
write(f"{root}/schemas/contexts/ucls-endpoint-context.jsonld", json.dumps(ctx, indent=2))

# Schemas: SHACL shapes ------------------------------------------------------
write(f"{root}/schemas/shacl/DomainNameShape.ttl", """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ucls: <https://ucls.org/terms#> .

ucls:DomainNameShape a sh:NodeShape ;
  sh:targetClass ucls:DomainName ;
  sh:property [ sh:path ucls:value ; sh:datatype xsd:string ; sh:minCount 1 ] ;
  sh:property [ sh:path ucls:tld ; sh:datatype xsd:string ; sh:minCount 1 ] ;
  sh:property [ sh:path ucls:script ; sh:datatype xsd:string ; sh:minCount 0 ] .
""")

write(f"{root}/schemas/shacl/AddressShape.ttl", """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ucls: <https://ucls.org/terms#> .

ucls:AddressShape a sh:NodeShape ;
  sh:targetClass ucls:Address ;
  sh:property [ sh:path ucls:ip ; sh:datatype xsd:string ; sh:minCount 1 ] ;
  sh:property [ sh:path ucls:asn ; sh:datatype xsd:string ; sh:minCount 0 ] .
""")

write(f"{root}/schemas/shacl/BroadcastSiteShape.ttl", """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ucls: <https://ucls.org/terms#> .

ucls:BroadcastSiteShape a sh:NodeShape ;
  sh:targetClass ucls:BroadcastSite ;
  sh:property [ sh:path ucls:lat ; sh:datatype xsd:decimal ; sh:minCount 1 ] ;
  sh:property [ sh:path ucls:lon ; sh:datatype xsd:decimal ; sh:minCount 1 ] ;
  sh:property [ sh:path ucls:licenseId ; sh:datatype xsd:string ; sh:minCount 0 ] ;
  sh:property [ sh:path ucls:regBody ; sh:datatype xsd:string ; sh:minCount 0 ] .
""")

write(f"{root}/schemas/shacl/RFLinkShape.ttl", """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ucls: <https://ucls.org/terms#> .

ucls:RFLinkShape a sh:NodeShape ;
  sh:targetClass ucls:RFLink ;
  sh:property [ sh:path ucls:tech ; sh:datatype xsd:string ; sh:minCount 1 ] ;
  sh:property [ sh:path ucls:freqFromMHz ; sh:datatype xsd:decimal ; sh:minCount 1 ] ;
  sh:property [ sh:path ucls:freqToMHz ; sh:datatype xsd:decimal ; sh:minCount 1 ] ;
  sh:property [ sh:path ucls:start ; sh:datatype xsd:dateTime ; sh:minCount 1 ] .
""")

# Packs ----------------------------------------------------------------------
packs = {
  "IDN-Locale-Policy-1.0.0.yaml": {
    "packId":"IDN-Locale-Policy",
    "policyVersion":"1.0.0",
    "appliesTo":[{"targetKind":"DomainName"}],
    "config":{"cldrScriptAllowlists":{"finance":["Latn"],"health":["Latn","Grek"],"global":["Latn","Cyrl","Arab","Hans","Hant"]}},
    "enforces":{
      "scriptAllowlist":["If sector provided, domain.script MUST be in allowlist"],
      "bundleController":["All IDN variants and brand TLDs MUST share the same controller attestation"]
    },
    "metrics":[{"key":"script_violation_rate","target":"==0"},{"key":"bundle_mismatch_rate","target":"==0"}]
  },
  "Domain-Name-Policy-1.0.0.yaml": {
    "packId":"Domain-Name-Policy",
    "policyVersion":"1.0.0",
    "appliesTo":[{"targetKind":"DomainName"}],
    "enforces":{
      "dnssec":["If tld supports DNSSEC, require DS/DO records verified via DoH probes"],
      "ctMonitor":["WebPKI leafs MUST appear in CT within 24h of issuance"]
    }
  },
  "Address-Policy-1.0.0.yaml": {
    "packId":"Address-Policy",
    "policyVersion":"1.0.0",
    "appliesTo":[{"targetKind":"Address"}],
    "enforces":{
      "rpki":["Route MUST be RPKI valid or at least not invalid"],
      "bogon":["Reject bogon ranges for public resources"]
    }
  },
  "RF-Emissions-Policy-1.0.0.yaml": {
    "packId":"RF-Emissions-Policy",
    "policyVersion":"1.0.0",
    "appliesTo":[{"targetKind":"RFLink"},{"targetKind":"BroadcastSite"}],
    "enforces":{
      "license":["If regBody present at site, licenseId must validate"],
      "eirp":["EIRP/ERP thresholds per locale"],
      "mask":["Emission masks by tech"]
    }
  },
  "Broadcast-Site-Policy-1.0.0.yaml": {
    "packId":"Broadcast-Site-Policy",
    "policyVersion":"1.0.0",
    "appliesTo":[{"targetKind":"BroadcastSite"}],
    "enforces":{
      "geoSanity":["lat/lon plausible for regBody"],
      "antenna":["If macro site, azimuth/tilt/gain should be present"]
    }
  }
}
for fn, obj in packs.items():
    dump_yaml(f"{root}/packs/{fn}", obj)

# Spectrum packs (new) -------------------------------------------------------
US = {
  "region":"US","regulator":"FCC",
  "mobile":{"NR":[
      {"band":"n2","duplex":"FDD","downlink_mhz":[1850,1910],"uplink_mhz":[1930,1990]},
      {"band":"n5","duplex":"FDD","downlink_mhz":[824,849],"uplink_mhz":[869,894]},
      {"band":"n12","duplex":"FDD","downlink_mhz":[699,716],"uplink_mhz":[729,746]},
      {"band":"n41","duplex":"TDD","tdd_mhz":[2496,2690]},
      {"band":"n66","duplex":"FDD","downlink_mhz":[2110,2200],"uplink_mhz":[1710,1780]},
      {"band":"n77","duplex":"TDD","tdd_mhz":[3450,3980]},
      {"band":"n258","duplex":"TDD","tdd_mhz":[24250,27500]}
    ],
    "LTE":[
      {"band":"B2","duplex":"FDD","downlink_mhz":[1850,1910],"uplink_mhz":[1930,1990]},
      {"band":"B4","duplex":"FDD","downlink_mhz":[1710,1755],"uplink_mhz":[2110,2155]},
      {"band":"B5","duplex":"FDD","downlink_mhz":[824,849],"uplink_mhz":[869,894]},
      {"band":"B12","duplex":"FDD","downlink_mhz":[699,716],"uplink_mhz":[729,746]},
      {"band":"B13","duplex":"FDD","downlink_mhz":[746,756],"uplink_mhz":[777,787]},
      {"band":"B66","duplex":"FDD","downlink_mhz":[2110,2200],"uplink_mhz":[1710,1780]}
    ]},
  "wifi":{"2g4":{"channels":[{"ch":i,"center_mhz":2412+(i-1)*5} for i in range(1,12)],"dfs":False},
          "5g":{"channels":[
             {"ch":36,"center_mhz":5180,"dfs":False},{"ch":40,"center_mhz":5200,"dfs":False},
             {"ch":44,"center_mhz":5220,"dfs":False},{"ch":48,"center_mhz":5240,"dfs":False},
             {"ch":52,"center_mhz":5260,"dfs":True},{"ch":56,"center_mhz":5280,"dfs":True},
             {"ch":60,"center_mhz":5300,"dfs":True},{"ch":64,"center_mhz":5320,"dfs":True},
             {"ch":100,"center_mhz":5500,"dfs":True},{"ch":104,"center_mhz":5520,"dfs":True},
             {"ch":108,"center_mhz":5540,"dfs":True},{"ch":112,"center_mhz":5560,"dfs":True},
             {"ch":116,"center_mhz":5580,"dfs":True},{"ch":120,"center_mhz":5600,"dfs":True},
             {"ch":124,"center_mhz":5620,"dfs":True},{"ch":128,"center_mhz":5640,"dfs":True},
             {"ch":132,"center_mhz":5660,"dfs":True},{"ch":136,"center_mhz":5680,"dfs":True},
             {"ch":140,"center_mhz":5700,"dfs":True},{"ch":144,"center_mhz":5720,"dfs":True},
             {"ch":149,"center_mhz":5745,"dfs":False},{"ch":153,"center_mhz":5765,"dfs":False},
             {"ch":157,"center_mhz":5785,"dfs":False},{"ch":161,"center_mhz":5805,"dfs":False},
             {"ch":165,"center_mhz":5825,"dfs":False}
          ]},
          "6g":{"desc":"U-NII-5..8, AFC may apply","subbands":[
            {"name":"U-NII-5","mhz":[5925,6425]},{"name":"U-NII-6","mhz":[6425,6875]},
            {"name":"U-NII-7","mhz":[6525,6875]},{"name":"U-NII-8","mhz":[6875,7125]}
          ]}},
  "ism":[{"name":"902-928 MHz ISM","mhz":[902,928]},{"name":"2.4 GHz ISM","mhz":[2400,2483.5]},{"name":"5.8 GHz ISM","mhz":[5725,5850]}]
}
EU = {
  "region":"EU","regulator":"ETSI/CEPT",
  "mobile":{"NR":[
      {"band":"n1","duplex":"FDD","downlink_mhz":[2110,2170],"uplink_mhz":[1920,1980]},
      {"band":"n3","duplex":"FDD","downlink_mhz":[1805,1880],"uplink_mhz":[1710,1785]},
      {"band":"n7","duplex":"FDD","downlink_mhz":[2620,2690],"uplink_mhz":[2500,2570]},
      {"band":"n20","duplex":"FDD","downlink_mhz":[791,821],"uplink_mhz":[832,862]},
      {"band":"n28","duplex":"FDD","downlink_mhz":[758,803],"uplink_mhz":[703,748]},
      {"band":"n78","duplex":"TDD","tdd_mhz":[3300,3800]}
    ],
    "LTE":[
      {"band":"B1","duplex":"FDD","downlink_mhz":[2110,2170],"uplink_mhz":[1920,1980]},
      {"band":"B3","duplex":"FDD","downlink_mhz":[1805,1880],"uplink_mhz":[1710,1785]},
      {"band":"B7","duplex":"FDD","downlink_mhz":[2620,2690],"uplink_mhz":[2500,2570]},
      {"band":"B20","duplex":"FDD","downlink_mhz":[791,821],"uplink_mhz":[832,862]},
      {"band":"B28","duplex":"FDD","downlink_mhz":[758,803],"uplink_mhz":[703,748]}
    ]},
  "wifi":{"2g4":{"channels":[{"ch":i,"center_mhz":2412+(i-1)*5} for i in range(1,14)],"dfs":False},
          "5g":{"channels":[
            {"ch":36,"center_mhz":5180,"dfs":False},{"ch":40,"center_mhz":5200,"dfs":False},
            {"ch":44,"center_mhz":5220,"dfs":False},{"ch":48,"center_mhz":5240,"dfs":False},
            {"ch":52,"center_mhz":5260,"dfs":True},{"ch":56,"center_mhz":5280,"dfs":True},
            {"ch":60,"center_mhz":5300,"dfs":True},{"ch":64,"center_mhz":5320,"dfs":True},
            {"ch":100,"center_mhz":5500,"dfs":True},{"ch":104,"center_mhz":5520,"dfs":True},
            {"ch":108,"center_mhz":5540,"dfs":True},{"ch":112,"center_mhz":5560,"dfs":True},
            {"ch":116,"center_mhz":5580,"dfs":True},{"ch":120,"center_mhz":5600,"dfs":True},
            {"ch":124,"center_mhz":5620,"dfs":True},{"ch":128,"center_mhz":5640,"dfs":True},
            {"ch":132,"center_mhz":5660,"dfs":True},{"ch":136,"center_mhz":5680,"dfs":True},
            {"ch":140,"center_mhz":5700,"dfs":True}
          ]},
          "6g":{"desc":"5925–6425 MHz (country-dependent)","subbands":[{"name":"5925-6425","mhz":[5925,6425]}]}},
  "ism":[{"name":"433 MHz SRD","mhz":[433.05,434.79]},{"name":"863-870 MHz SRD","mhz":[863,870]},{"name":"2.4 GHz ISM","mhz":[2400,2483.5]}]
}
dump_yaml(f"{root}/spectrum/bands/US.yaml", US)
dump_yaml(f"{root}/spectrum/bands/EU.yaml", EU)

dump_yaml(f"{root}/spectrum/crosswalk/acronyms.yaml", {
  "3GPP":{"NR":{"n41":"2496–2690 MHz TDD","n77":"(regional) 3300–4200 slice","n78":"3300–3800 MHz TDD","n258":"24.25–27.5 GHz TDD"},
           "LTE":{"B12":"700 MHz Lower A/B","B13":"700 MHz Upper C","B66":"AWS-3 (B4 ext)"}},
  "WIFI":{"UNII-1":[5150,5350],"UNII-2A":[5250,5350],"UNII-2C":[5470,5725],"UNII-3":[5725,5850],"DFS":"Radar detection on UNII-2A/2C in many regions"},
  "ISM":{"US915":[902,928],"EU868":[863,870],"EU433":[433.05,434.79],"2G4":[2400,2483.5]}
})

# Spectrum-Policy 1.1.0 + Suite --------------------------------------------
dump_yaml(f"{root}/packs/Spectrum-Policy-1.1.0.yaml", {
  "packId":"Spectrum-Policy","policyVersion":"1.1.0",
  "requires":["RF-Emissions-Policy@1.0.0"],
  "appliesTo":[{"targetKind":"RFLink"}],
  "config":{"bandDataPath":"spectrum/bands","acronymsPath":"spectrum/crosswalk/acronyms.yaml","regionSelect":"CLDR"},
  "enforces":{"bandMembership":["RFLink freq range must be contained by any allowed band for region+tech"],
              "dfsEnforcement":["Wi‑Fi channels flagged dfs=true require CAC/controller attestation"]},
  "metrics":[{"key":"band_membership_violation_rate","target":"==0"},{"key":"dfs_missing_attest_rate","target":"==0"}]
})
dump_yaml(f"{root}/packs/Spectrum-Policy-Suite-1.0.0.yaml", {
  "packId":"Spectrum-Policy-Suite","policyVersion":"1.0.0",
  "requires":["Broadcast-Site-Policy@1.0.0","RF-Emissions-Policy@1.0.0","Spectrum-Policy@1.1.0"],
  "appliesTo":[{"targetKind":"RFLink"},{"targetKind":"BroadcastSite"}]
})

# Gates & bindings -----------------------------------------------------------
write(f"{root}/gates/gate-domain-name.json", json.dumps({
  "gateId":"gate:domain-name","targetKind":"DomainName",
  "shapes":["ucls:DomainNameShape"],"packs":["IDN-Locale-Policy@1.0.0","Domain-Name-Policy@1.0.0"],
  "failureMode":"fail-open","onFail":[{"action":"open-praas-case","severity":"medium"}]
}, indent=2))

write(f"{root}/gates/gate-address.json", json.dumps({
  "gateId":"gate:address","targetKind":"Address",
  "shapes":["ucls:AddressShape"],"packs":["Address-Policy@1.0.0"],
  "failureMode":"fail-open","onFail":[{"action":"open-praas-case","severity":"low"}]
}, indent=2))

write(f"{root}/gates/gate-broadcast-site.json", json.dumps({
  "gateId":"gate:broadcast-site","targetKind":"BroadcastSite",
  "shapes":["ucls:BroadcastSiteShape"],"packs":["Broadcast-Site-Policy@1.0.0","RF-Emissions-Policy@1.0.0"],
  "failureMode":"fail-open","onFail":[{"action":"open-praas-case","severity":"medium"}]
}, indent=2))

write(f"{root}/gates/gate-rf-link.json", json.dumps({
  "gateId":"gate:rf-link","targetKind":"RFLink",
  "shapes":["ucls:RFLinkShape"],"packs":["RF-Emissions-Policy@1.0.0","Spectrum-Policy@1.1.0"],
  "failureMode":"fail-open","onFail":[{"action":"open-praas-case","severity":"high"}]
}, indent=2))

write(f"{root}/bindings/bind-spectrum-suite.json", json.dumps({
  "@type":"PolicyBinding","bindingId":"bind:spectrum-suite",
  "selector":{"kind":["RFLink","BroadcastSite"]},"packs":["Spectrum-Policy-Suite@1.0.0"],"precedence":8
}, indent=2))

# Adapters (stubs) -----------------------------------------------------------
adapters = {
  "rdap-epp/README.md":"# RDAP/EPP Adapter\nResolves domain/TLD facts via RDAP; manages registrar/registry EPP flows.\n",
  "doh-prober/README.md":"# DoH Prober\nResolves A/AAAA/NS/DS/CAA via DNS-over-HTTPS; validates DNSSEC (AD bit).\n",
  "ct-monitor/README.md":"# CT Monitor\nWatches Certificate Transparency logs; correlates leaf certs to DomainName.\n",
  "email-parsers/README.md":"# DMARC / MTA-STS / TLS-RPT\nParses reports, extracts policy posture, links to DomainName.\n",
  "sdr-collector/README.md":"# SDR Collector\nIQ/PSD decode for NR/LTE/Wi‑Fi/BLE; emits RFLink.\n",
  "cellmapper-adapter/README.md":"# Cell/Radio Adapter\nFrom NMS/exports to BroadcastSite/BaseStation (azimuth/tilt/gain, bands, ARFCN/NR-ARFCN).\n",
  "wifi-collector/README.md":"# Wi‑Fi Collector\nController telemetry→RFLink; DFS CAC attestation.\n",
  "bt-collector/README.md":"# Bluetooth Collector\nScan logs→RFLink advertisement facts.\n",
  "satcom-collector/README.md":"# Satcom Collector\nGateway/beam summaries→RFLink; overlays beam footprints.\n",
  "gnss-collector/README.md":"# GNSS Collector\nReceiver locks/ephemeris→RFLink (GPS/Galileo/BeiDou/GLONASS).\n"
}
for path, content in adapters.items():
    write(f"{root}/adapters/{path}", content)

# Docker Compose with static-geo ---------------------------------------------
compose = """
version: "3.8"
services:
  static-geo:
    image: nginx:alpine
    volumes:
      - ../dashboards/geo:/usr/share/nginx/html/geo:ro
    ports:
      - "8089:80"
networks:
  default: {}
"""
write(f"{root}/adapters/docker-compose.yml", compose)

# Telemetry ------------------------------------------------------------------
click_sql = """
-- ClickHouse tables (DNS/Domain/Address + RF)
CREATE TABLE IF NOT EXISTS domains_ch (
  ts DateTime,
  domain String,
  tld String,
  script LowCardinality(String),
  sector LowCardinality(String),
  dnssec UInt8,
  ct_seen UInt8,
  posture_score Float32
) ENGINE = MergeTree ORDER BY (domain, ts);

CREATE TABLE IF NOT EXISTS addresses_ch (
  ts DateTime,
  ip String,
  asn String,
  rpki String,
  bogon UInt8
) ENGINE = MergeTree ORDER BY (ip, ts);

CREATE TABLE IF NOT EXISTS rf_links_ch (
  ts DateTime,
  link_id String,
  endpoint_id String,
  site_id String,
  tech LowCardinality(String),
  band LowCardinality(String),
  freqFromMHz Float32,
  freqToMHz Float32,
  out_of_band UInt8,
  dfs_missing UInt8,
  license_valid UInt8
) ENGINE = MergeTree ORDER BY (link_id, ts);

CREATE TABLE IF NOT EXISTS sites_ch (
  ts DateTime,
  site_id String,
  siteCode String,
  regBody LowCardinality(String),
  licenseId String,
  lat Float64,
  lon Float64,
  alt Float32,
  azimuth Float32,
  tilt Float32,
  gainDbi Float32
) ENGINE = MergeTree ORDER BY (site_id, ts);
"""
write(f"{root}/telemetry/clickhouse_schema.sql", click_sql)

prom = """
groups:
- name: spectrum_compliance
  rules:
  - alert: OutOfBandDetected
    expr: sum(rate(ucls_rf_out_of_band_total[5m])) > 0
    for: 1m
    labels: {severity: high}
    annotations:
      summary: "Out-of-band RF transmission detected"
  - alert: DFSMissingCAC
    expr: sum(rate(ucls_wifi_dfs_missing_total[5m])) > 0
    for: 5m
    labels: {severity: medium}
    annotations:
      summary: "DFS channel in use without CAC attestation"
"""
write(f"{root}/telemetry/prometheus_rules.yaml", prom)

# Dashboards -----------------------------------------------------------------
dash = {
  "title":"UCLS Observability",
  "timezone":"browser",
  "panels":[
    {"type":"stat","title":"Domain Posture Score (p95)","id":1,
     "datasource":{"type":"vertamedia-clickhouse-datasource","uid":"CLICKHOUSE_DS"},
     "targets":[{"refId":"A","query":"SELECT quantile(0.95)(posture_score) AS p95 FROM domains_ch"}],
     "gridPos":{"h":4,"w":8,"x":0,"y":0}},
    {"type":"table","title":"Recent RF Links","id":2,
     "datasource":{"type":"vertamedia-clickhouse-datasource","uid":"CLICKHOUSE_DS"},
     "targets":[{"refId":"A","query":"SELECT ts, link_id, endpoint_id, site_id, tech, band, freqFromMHz, freqToMHz, out_of_band, dfs_missing, license_valid FROM rf_links_ch ORDER BY ts DESC LIMIT 300"}],
     "gridPos":{"h":10,"w":24,"x":0,"y":4}},
  ]
}
write(f"{root}/dashboards/ucls-observability.json", json.dumps(dash, indent=2))

# Geomap layers --------------------------------------------------------------
beams = {
  "type":"FeatureCollection",
  "features":[
    {"type":"Feature","properties":{"satellite":"Starlink-EX","beam":"BEAM-101","color":"#33a02c"},
     "geometry":{"type":"Polygon","coordinates":[[[-124.5,48.5],[-114,48.5],[-114,42],[-124.5,42],[-124.5,48.5]]]} },
    {"type":"Feature","properties":{"satellite":"Intelsat-EX","beam":"IS-29E-Spot-7","color":"#1f78b4"},
     "geometry":{"type":"Polygon","coordinates":[[[-10,53],[2,53],[2,45],[-10,45],[-10,53]]]} }
  ]
}
write(f"{root}/dashboards/geo/sat_beams.geojson", json.dumps(beams))

# Append geomap panels
dash = json.load(open(f"{root}/dashboards/ucls-observability.json","r",encoding="utf-8"))
next_id = max(p["id"] for p in dash["panels"]) + 1
dash["panels"].append({
  "type":"geomap","title":"Broadcast Sites (Geo)","id":next_id,
  "datasource":{"type":"vertamedia-clickhouse-datasource","uid":"CLICKHOUSE_DS"},
  "targets":[{"refId":"A","query":"SELECT site_id, siteCode, lat AS latitude, lon AS longitude FROM sites_ch ORDER BY ts DESC LIMIT 1000"}],
  "gridPos":{"h":12,"w":24,"x":0,"y":14},
  "options":{"view":{"id":"world"}}
}); next_id += 1
dash["panels"].append({
  "type":"geomap","title":"Satellite Beam Footprints","id":next_id,
  "datasource":{"type":"grafana","uid":"grafana"},
  "gridPos":{"h":12,"w":24,"x":0,"y":26},
  "options":{"view":{"id":"world"},
             "layers":[{"type":"geojson","name":"Sat Beams","config":{"style":{"color":"#ff8800","fillOpacity":0.15}},
                        "url":"http://static-geo:8089/geo/sat_beams.geojson"}]}
})
write(f"{root}/dashboards/ucls-observability.json", json.dumps(dash, indent=2))

# Samples --------------------------------------------------------------------
mobile = {
  "@type":"MobileEndpoint","vendor":"Samsung","model":"S23 Ultra (SM-S918U)",
  "android":{"oneUi":"7.0","android":"15","kernel":"5.15.153","patchLevel":"2025-07-01"},
  "radios":["NR","LTE","Wi‑Fi","BLE","GNSS"],"locale":"en_US","region":"US","start":now_iso
}
write(f"{root}/samples/mobile-endpoint-s918u-2025-07.json", json.dumps(mobile, indent=2))

rf_samples = {
  "links":[
    {"@type":"RFLink","tech":"NR","band":"n41","freqFromMHz":2496.0,"freqToMHz":2690.0,"start":now_iso},
    {"@type":"RFLink","tech":"LTE","band":"B12","freqFromMHz":699.0,"freqToMHz":716.0,"start":now_iso},
    {"@type":"RFLink","tech":"Wi‑Fi","band":"5GHz","freqFromMHz":5500.0,"freqToMHz":5520.0,"start":now_iso},
    {"@type":"RFLink","tech":"BLE","band":"2.4GHz","freqFromMHz":2402.0,"freqToMHz":2480.0,"start":now_iso}
  ],
  "sites":[
    {"@type":"BroadcastSite","siteCode":"SITE-ATT-CA-XYZ","regBody":"FCC","licenseId":"WQAB123",
     "lat":34.05,"lon":-118.25,"azimuth":120.0,"tilt":-3.0,"gainDbi":17.0,"start":now_iso}
  ]
}
write(f"{root}/samples/rf-and-sites.json", json.dumps(rf_samples, indent=2))

domains = [
  {"@type":"DomainName","value":"example.com","tld":"com","script":"Latn","sector":"finance"},
  {"@type":"DomainName","value":"παράδειγμα.gr","tld":"gr","script":"Grek","sector":"health"}
]
write(f"{root}/samples/domains.json", json.dumps(domains, indent=2))

# Final ZIP ------------------------------------------------------------------
zip_path = "/mnt/data/ucls-spectrum-unified-all.zip"
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
    for folder, _, files in os.walk(root):
        for f in files:
            ap = os.path.join(folder, f)
            zf.write(ap, os.path.relpath(ap, os.path.dirname(root)))
print("ZIP", zip_path)