Skip to main content

qualia_client_core/chora/
flagship_worlds.rs

1//! P8 flagship world configurations for the Chora canvas.
2//!
3//! Each world is a curated layer-stack + norms bundle — substrate validation, not new engine code.
4
5use chrono::{TimeZone, Utc};
6
7use crate::canvas_store::{CanvasWorldStore, UpsertError};
8use crate::canvas_world::{
9    CanvasAssetRef, CanvasLayer, CanvasNorm, CanvasWorldConfig, WorldStratum,
10};
11
12/// Sydney CBD reference origin (degrees).
13const SYDNEY_LAT: f64 = -33.8688;
14const SYDNEY_LON: f64 = 151.2093;
15
16/// Mid-year temporal anchor for calendar-year valid-time intervals.
17///
18/// Pre-1970 dates have **negative** Unix timestamps; casting those to `u64`
19/// wraps to huge values and inverts `valid_from <= valid_until` (kent-brewery
20/// failed validation). We store post-epoch as real Unix seconds; pre-epoch as
21/// ordered synthetic stamps in `0..FIRST_UNIX_YEAR_MID` so relative order of
22/// historical years is preserved under `u64`.
23fn year_mid(y: u32) -> u64 {
24    const FIRST_UNIX: u64 = 0; // 1970-01-01
25                               // ~ mid 1970
26    const YEAR_1970_MID: u64 = 15_778_800;
27    match Utc.with_ymd_and_hms(y as i32, 7, 1, 0, 0, 0).single() {
28        Some(dt) => {
29            let ts = dt.timestamp();
30            if ts >= 0 {
31                ts as u64
32            } else {
33                // Map years [0, 1970) into [0, YEAR_1970_MID) linearly by year.
34                let y = y.min(1969) as u64;
35                FIRST_UNIX + (y * YEAR_1970_MID) / 1970
36            }
37        }
38        None => y as u64,
39    }
40}
41
42/// OpenHistoricalMap-style historical world: temporal scrub over Sydney built fabric.
43pub fn history_world() -> CanvasWorldConfig {
44    CanvasWorldConfig {
45        id: "q42:world:history-sydney".to_string(),
46        title: "Sydney History (OpenHistoricalMap)".to_string(),
47        temporal_range: Some((1800, 2026)),
48        layer_stack: vec![
49            CanvasLayer::Historical {
50                endpoint: "adapter://openhistoricalmap/v1".to_string(),
51            },
52            CanvasLayer::GeoSpatial {
53                endpoint: "adapter://osm/terrain/sydney".to_string(),
54                stratum: WorldStratum::WorldOfGod,
55            },
56        ],
57        assets: vec![
58            CanvasAssetRef {
59                asset_id: "urn:qualia:asset:history:customs-house".to_string(),
60                lat: Some(-33.8672),
61                lon: Some(151.2113),
62                alt_m: Some(0.0),
63                valid_from: Some(year_mid(1845)),
64                valid_until: None,
65                licence: "CC0".to_string(),
66            },
67            CanvasAssetRef {
68                asset_id: "urn:qualia:asset:history:general-post-office".to_string(),
69                lat: Some(-33.8720),
70                lon: Some(151.2075),
71                alt_m: Some(0.0),
72                valid_from: Some(year_mid(1874)),
73                valid_until: None,
74                licence: "CC0".to_string(),
75            },
76            CanvasAssetRef {
77                asset_id: "urn:qualia:asset:history:town-hall".to_string(),
78                lat: Some(-33.8732),
79                lon: Some(151.2060),
80                alt_m: Some(0.0),
81                valid_from: Some(year_mid(1889)),
82                valid_until: None,
83                licence: "CC0".to_string(),
84            },
85            CanvasAssetRef {
86                asset_id: "urn:qualia:asset:history:harbour-bridge".to_string(),
87                lat: Some(-33.8523),
88                lon: Some(151.2108),
89                alt_m: Some(0.0),
90                valid_from: Some(year_mid(1932)),
91                valid_until: None,
92                licence: "CC0".to_string(),
93            },
94            CanvasAssetRef {
95                asset_id: "urn:qualia:asset:history:opera-house".to_string(),
96                lat: Some(-33.8568),
97                lon: Some(151.2153),
98                alt_m: Some(0.0),
99                valid_from: Some(year_mid(1973)),
100                valid_until: None,
101                licence: "CC0".to_string(),
102            },
103            CanvasAssetRef {
104                asset_id: "urn:qualia:asset:history:kent-brewery".to_string(),
105                lat: Some(-33.8795),
106                lon: Some(151.1948),
107                alt_m: Some(0.0),
108                valid_from: Some(year_mid(1835)),
109                valid_until: Some(year_mid(2005)),
110                licence: "CC0".to_string(),
111            },
112        ],
113        norms: vec![
114            CanvasNorm {
115                rule_uri: "urn:qualia:canvas:public-commons".to_string(),
116                description: "Historical commons read; HGIS attribution required on export"
117                    .to_string(),
118            },
119            CanvasNorm {
120                rule_uri: "urn:qualia:canvas:temporal-scrub".to_string(),
121                description: "Spawn/decay governed by valid-time intervals".to_string(),
122            },
123        ],
124        origin_lat: SYDNEY_LAT,
125        origin_lon: SYDNEY_LON,
126        origin_alt_m: 0.0,
127        ..Default::default()
128    }
129}
130
131/// Biosphere + geospatial world: GBIF occurrence layer over terrain (world-of-god stratum).
132pub fn biosphere_world() -> CanvasWorldConfig {
133    CanvasWorldConfig {
134        id: "q42:world:biosphere".to_string(),
135        title: "Biosphere (GBIF)".to_string(),
136        temporal_range: None,
137        layer_stack: vec![
138            CanvasLayer::Biosphere {
139                endpoint: "adapter://gbif/v1/occurrence".to_string(),
140            },
141            CanvasLayer::GeoSpatial {
142                endpoint: "adapter://terrain/global-dem".to_string(),
143                stratum: WorldStratum::WorldOfGod,
144            },
145        ],
146        assets: vec![],
147        norms: vec![CanvasNorm {
148            rule_uri: "urn:qualia:canvas:world-of-god".to_string(),
149            description: "Biosphere outputs are Hypothesis under F/A — never ground truth"
150                .to_string(),
151        }],
152        origin_lat: 0.0,
153        origin_lon: 0.0,
154        origin_alt_m: 0.0,
155        ..Default::default()
156    }
157}
158
159/// Council municipal open-data world (world-of-man stratum).
160pub fn council_world() -> CanvasWorldConfig {
161    CanvasWorldConfig {
162        id: "q42:world:council-sydney".to_string(),
163        title: "Sydney Council Open Data".to_string(),
164        temporal_range: None,
165        layer_stack: vec![
166            CanvasLayer::Council {
167                endpoint: "adapter://council/sydney-open-data/v1".to_string(),
168            },
169            CanvasLayer::GeoSpatial {
170                endpoint: "adapter://osm/buildings/sydney".to_string(),
171                stratum: WorldStratum::WorldOfMan,
172            },
173        ],
174        assets: vec![],
175        norms: vec![CanvasNorm {
176            rule_uri: "urn:qualia:canvas:council-commons".to_string(),
177            description: "Municipal open data; placement requires council placement right"
178                .to_string(),
179        }],
180        origin_lat: SYDNEY_LAT,
181        origin_lon: SYDNEY_LON,
182        origin_alt_m: 0.0,
183        ..Default::default()
184    }
185}
186
187/// SDG dashboard walkthrough: infosphere metrics composited with council context.
188pub fn sdg_world() -> CanvasWorldConfig {
189    CanvasWorldConfig {
190        id: "q42:world:sdg-dashboard".to_string(),
191        title: "SDG Dashboard Walkthrough".to_string(),
192        temporal_range: Some((2015, 2030)),
193        layer_stack: vec![
194            CanvasLayer::Infosphere {
195                endpoint: "adapter://infosphere/sdg-indicators/v1".to_string(),
196            },
197            CanvasLayer::Council {
198                endpoint: "adapter://council/sdg-local-metrics/v1".to_string(),
199            },
200        ],
201        assets: vec![CanvasAssetRef {
202            asset_id: "urn:qualia:asset:sdg:walkthrough-anchor".to_string(),
203            lat: Some(SYDNEY_LAT),
204            lon: Some(SYDNEY_LON),
205            alt_m: Some(0.0),
206            valid_from: Some(year_mid(2015)),
207            valid_until: Some(year_mid(2030)),
208            licence: "CC-BY-4.0".to_string(),
209        }],
210        norms: vec![
211            CanvasNorm {
212                rule_uri: "urn:qualia:canvas:sdg-alignment".to_string(),
213                description: "Constructed-vs-natural interaction metrics for SDG walkthrough"
214                    .to_string(),
215            },
216            CanvasNorm {
217                rule_uri: "urn:qualia:canvas:public-commons".to_string(),
218                description: "Indicator dashboards readable under permissive commons".to_string(),
219            },
220        ],
221        origin_lat: SYDNEY_LAT,
222        origin_lon: SYDNEY_LON,
223        origin_alt_m: 0.0,
224        ..Default::default()
225    }
226}
227
228/// Library / GLAM publishing flagship (P8 curation-led).
229pub fn glam_world() -> CanvasWorldConfig {
230    CanvasWorldConfig {
231        id: "q42:world:glam-commons".to_string(),
232        title: "GLAM Commons Publishing".to_string(),
233        temporal_range: None,
234        layer_stack: vec![
235            CanvasLayer::Infosphere {
236                endpoint: "adapter://glam/iiif-manifests/v1".to_string(),
237            },
238            CanvasLayer::Historical {
239                endpoint: "adapter://hgis/world-historical-gazetteer/v1".to_string(),
240            },
241        ],
242        assets: vec![CanvasAssetRef {
243            asset_id: "urn:qualia:asset:glam:sample-map-tile".to_string(),
244            lat: Some(SYDNEY_LAT),
245            lon: Some(SYDNEY_LON),
246            alt_m: Some(0.0),
247            valid_from: None,
248            valid_until: None,
249            licence: "CC0".to_string(),
250        }],
251        norms: vec![CanvasNorm {
252            rule_uri: "urn:qualia:canvas:glam-stewardship".to_string(),
253            description: "GLAM holdings explorable; export requires provenance sidecar".to_string(),
254        }],
255        origin_lat: SYDNEY_LAT,
256        origin_lon: SYDNEY_LON,
257        origin_alt_m: 0.0,
258        ..Default::default()
259    }
260}
261
262/// All P8 flagship world configurations (distinct ids).
263pub fn all_flagship_worlds() -> Vec<CanvasWorldConfig> {
264    vec![
265        history_world(),
266        biosphere_world(),
267        council_world(),
268        sdg_world(),
269        glam_world(),
270    ]
271}
272
273/// Upsert each flagship world when its id is not already present in the store.
274pub fn seed_all_flagships(store: &CanvasWorldStore, now_unix: u64) -> std::io::Result<usize> {
275    let existing: std::collections::HashSet<String> =
276        store.list()?.into_iter().map(|w| w.id).collect();
277    let mut seeded = 0usize;
278    for config in all_flagship_worlds() {
279        if existing.contains(&config.id) {
280            continue;
281        }
282        store.upsert(config, now_unix).map_err(|e| match e {
283            UpsertError::Io(e) => e,
284            UpsertError::Invalid(err) => std::io::Error::other(err.to_string()),
285        })?;
286        seeded += 1;
287    }
288    Ok(seeded)
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294    use std::collections::HashSet;
295    use std::fs;
296
297    #[test]
298    fn year_mid_preserves_historical_order() {
299        let a = year_mid(1835);
300        let b = year_mid(2005);
301        assert!(a <= b, "1835 mid ({a}) must be <= 2005 mid ({b})");
302        let c = year_mid(1973);
303        let d = year_mid(2015);
304        assert!(c <= d);
305    }
306
307    #[test]
308    fn flagship_worlds_validate_five_distinct_ids() {
309        let worlds = all_flagship_worlds();
310        assert_eq!(worlds.len(), 5, "expected five flagship worlds");
311        let ids: HashSet<_> = worlds.iter().map(|w| w.id.as_str()).collect();
312        assert_eq!(ids.len(), 5, "flagship world ids must be distinct");
313        for world in &worlds {
314            world.validate().expect("flagship world should validate");
315        }
316    }
317
318    #[test]
319    fn seed_all_flagships_upserts_only_missing() {
320        let dir = std::env::temp_dir().join(format!("chora-flagship-{}", std::process::id()));
321        let _ = fs::remove_dir_all(&dir);
322        let store = CanvasWorldStore::open(&dir).unwrap();
323
324        let first = seed_all_flagships(&store, 1_700_000_000).unwrap();
325        assert_eq!(first, 5);
326        assert_eq!(store.list().unwrap().len(), 5);
327
328        let second = seed_all_flagships(&store, 1_700_000_100).unwrap();
329        assert_eq!(second, 0);
330        assert_eq!(store.list().unwrap().len(), 5);
331
332        let _ = fs::remove_dir_all(&dir);
333    }
334}