holosphere 1.1.20 → 2.0.0-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/.eslintrc.json +16 -0
- package/.prettierrc.json +7 -0
- package/LICENSE +162 -38
- package/README.md +483 -367
- package/bin/holosphere-activitypub.js +158 -0
- package/cleanup-test-data.js +204 -0
- package/examples/demo.html +1333 -0
- package/examples/example-bot.js +197 -0
- package/package.json +47 -87
- package/scripts/check-bundle-size.js +54 -0
- package/scripts/check-quest-ids.js +77 -0
- package/scripts/import-holons.js +578 -0
- package/scripts/publish-to-relay.js +101 -0
- package/scripts/read-example.js +186 -0
- package/scripts/relay-diagnostic.js +59 -0
- package/scripts/relay-example.js +179 -0
- package/scripts/resync-to-relay.js +245 -0
- package/scripts/revert-import.js +196 -0
- package/scripts/test-hybrid-mode.js +108 -0
- package/scripts/test-local-storage.js +63 -0
- package/scripts/test-nostr-direct.js +55 -0
- package/scripts/test-read-data.js +45 -0
- package/scripts/test-write-read.js +63 -0
- package/scripts/verify-import.js +95 -0
- package/scripts/verify-relay-data.js +139 -0
- package/src/ai/aggregation.js +319 -0
- package/src/ai/breakdown.js +511 -0
- package/src/ai/classifier.js +217 -0
- package/src/ai/council.js +228 -0
- package/src/ai/embeddings.js +279 -0
- package/src/ai/federation-ai.js +324 -0
- package/src/ai/h3-ai.js +955 -0
- package/src/ai/index.js +112 -0
- package/src/ai/json-ops.js +225 -0
- package/src/ai/llm-service.js +205 -0
- package/src/ai/nl-query.js +223 -0
- package/src/ai/relationships.js +353 -0
- package/src/ai/schema-extractor.js +218 -0
- package/src/ai/spatial.js +293 -0
- package/src/ai/tts.js +194 -0
- package/src/content/social-protocols.js +168 -0
- package/src/core/holosphere.js +273 -0
- package/src/crypto/secp256k1.js +259 -0
- package/src/federation/discovery.js +334 -0
- package/src/federation/hologram.js +1042 -0
- package/src/federation/registry.js +386 -0
- package/src/hierarchical/upcast.js +110 -0
- package/src/index.js +2669 -0
- package/src/schema/validator.js +91 -0
- package/src/spatial/h3-operations.js +110 -0
- package/src/storage/backend-factory.js +125 -0
- package/src/storage/backend-interface.js +142 -0
- package/src/storage/backends/activitypub/server.js +653 -0
- package/src/storage/backends/activitypub-backend.js +272 -0
- package/src/storage/backends/gundb-backend.js +233 -0
- package/src/storage/backends/nostr-backend.js +136 -0
- package/src/storage/filesystem-storage-browser.js +41 -0
- package/src/storage/filesystem-storage.js +138 -0
- package/src/storage/global-tables.js +81 -0
- package/src/storage/gun-async.js +281 -0
- package/src/storage/gun-wrapper.js +221 -0
- package/src/storage/indexeddb-storage.js +122 -0
- package/src/storage/key-storage-simple.js +76 -0
- package/src/storage/key-storage.js +136 -0
- package/src/storage/memory-storage.js +59 -0
- package/src/storage/migration.js +338 -0
- package/src/storage/nostr-async.js +811 -0
- package/src/storage/nostr-client.js +939 -0
- package/src/storage/nostr-wrapper.js +211 -0
- package/src/storage/outbox-queue.js +208 -0
- package/src/storage/persistent-storage.js +109 -0
- package/src/storage/sync-service.js +164 -0
- package/src/subscriptions/manager.js +142 -0
- package/test-ai-real-api.js +202 -0
- package/tests/unit/ai/aggregation.test.js +295 -0
- package/tests/unit/ai/breakdown.test.js +446 -0
- package/tests/unit/ai/classifier.test.js +294 -0
- package/tests/unit/ai/council.test.js +262 -0
- package/tests/unit/ai/embeddings.test.js +384 -0
- package/tests/unit/ai/federation-ai.test.js +344 -0
- package/tests/unit/ai/h3-ai.test.js +458 -0
- package/tests/unit/ai/index.test.js +304 -0
- package/tests/unit/ai/json-ops.test.js +307 -0
- package/tests/unit/ai/llm-service.test.js +390 -0
- package/tests/unit/ai/nl-query.test.js +383 -0
- package/tests/unit/ai/relationships.test.js +311 -0
- package/tests/unit/ai/schema-extractor.test.js +384 -0
- package/tests/unit/ai/spatial.test.js +279 -0
- package/tests/unit/ai/tts.test.js +279 -0
- package/tests/unit/content.test.js +332 -0
- package/tests/unit/contract/core.test.js +88 -0
- package/tests/unit/contract/crypto.test.js +198 -0
- package/tests/unit/contract/data.test.js +223 -0
- package/tests/unit/contract/federation.test.js +181 -0
- package/tests/unit/contract/hierarchical.test.js +113 -0
- package/tests/unit/contract/schema.test.js +114 -0
- package/tests/unit/contract/social.test.js +217 -0
- package/tests/unit/contract/spatial.test.js +110 -0
- package/tests/unit/contract/subscriptions.test.js +128 -0
- package/tests/unit/contract/utils.test.js +159 -0
- package/tests/unit/core.test.js +152 -0
- package/tests/unit/crypto.test.js +328 -0
- package/tests/unit/federation.test.js +234 -0
- package/tests/unit/gun-async.test.js +252 -0
- package/tests/unit/hierarchical.test.js +399 -0
- package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
- package/tests/unit/integration/scenario-02-federation.test.js +76 -0
- package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
- package/tests/unit/integration/scenario-04-validation.test.js +129 -0
- package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
- package/tests/unit/integration/scenario-06-social.test.js +135 -0
- package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
- package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
- package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
- package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
- package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
- package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
- package/tests/unit/performance/benchmark.test.js +85 -0
- package/tests/unit/schema.test.js +213 -0
- package/tests/unit/spatial.test.js +158 -0
- package/tests/unit/storage.test.js +195 -0
- package/tests/unit/subscriptions.test.js +328 -0
- package/tests/unit/test-data-permanence-debug.js +197 -0
- package/tests/unit/test-data-permanence.js +340 -0
- package/tests/unit/test-key-persistence-fixed.js +148 -0
- package/tests/unit/test-key-persistence.js +172 -0
- package/tests/unit/test-relay-permanence.js +376 -0
- package/tests/unit/test-second-node.js +95 -0
- package/tests/unit/test-simple-write.js +89 -0
- package/vite.config.js +49 -0
- package/vitest.config.js +20 -0
- package/FEDERATION.md +0 -213
- package/compute.js +0 -298
- package/content.js +0 -980
- package/federation.js +0 -1234
- package/global.js +0 -736
- package/hexlib.js +0 -335
- package/hologram.js +0 -183
- package/holosphere-bundle.esm.js +0 -33256
- package/holosphere-bundle.js +0 -33287
- package/holosphere-bundle.min.js +0 -39
- package/holosphere.d.ts +0 -601
- package/holosphere.js +0 -719
- package/node.js +0 -246
- package/schema.js +0 -139
- package/utils.js +0 -302
package/src/ai/h3-ai.js
ADDED
|
@@ -0,0 +1,955 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* H3 AI - Geospatial intelligence for H3 hexagonal hierarchies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as h3 from 'h3-js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* H3 AI class for geospatial intelligence
|
|
9
|
+
*/
|
|
10
|
+
export class H3AI {
|
|
11
|
+
/**
|
|
12
|
+
* @param {LLMService} llmService - LLM service instance
|
|
13
|
+
* @param {Object} holosphere - HoloSphere instance
|
|
14
|
+
*/
|
|
15
|
+
constructor(llmService, holosphere = null) {
|
|
16
|
+
this.llm = llmService;
|
|
17
|
+
this.holosphere = holosphere;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Set HoloSphere instance
|
|
22
|
+
* @param {Object} holosphere - HoloSphere instance
|
|
23
|
+
*/
|
|
24
|
+
setHoloSphere(holosphere) {
|
|
25
|
+
this.holosphere = holosphere;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Suggest optimal H3 resolution for a task/project based on its scope
|
|
30
|
+
* @param {Object} item - Item to analyze (task, project, quest)
|
|
31
|
+
* @param {Object} options - Options
|
|
32
|
+
* @returns {Promise<Object>} Resolution recommendation
|
|
33
|
+
*/
|
|
34
|
+
async suggestResolution(item, options = {}) {
|
|
35
|
+
const { currentResolution = null, context = null } = options;
|
|
36
|
+
|
|
37
|
+
const systemPrompt = `You are a geospatial planning expert. Analyze this item and suggest the optimal H3 hexagonal resolution (0-15) for organizing it.
|
|
38
|
+
|
|
39
|
+
H3 Resolution Guide:
|
|
40
|
+
- Resolution 0-2: Continental/country scale (thousands of km)
|
|
41
|
+
- Resolution 3-4: Regional/state scale (hundreds of km)
|
|
42
|
+
- Resolution 5-6: Metropolitan/city scale (tens of km)
|
|
43
|
+
- Resolution 7-8: District/neighborhood scale (km)
|
|
44
|
+
- Resolution 9-10: Block/street scale (hundreds of meters)
|
|
45
|
+
- Resolution 11-12: Building/lot scale (tens of meters)
|
|
46
|
+
- Resolution 13-15: Room/precise scale (meters)
|
|
47
|
+
|
|
48
|
+
Consider:
|
|
49
|
+
1. Geographic scope mentioned in the item
|
|
50
|
+
2. Number of potential participants/stakeholders
|
|
51
|
+
3. Type of activity (local vs regional)
|
|
52
|
+
4. Resource requirements and logistics
|
|
53
|
+
5. Similar projects' typical scale
|
|
54
|
+
|
|
55
|
+
${currentResolution !== null ? `Current resolution: ${currentResolution}` : ''}
|
|
56
|
+
${context ? `Context:\n${JSON.stringify(context, null, 2)}` : ''}
|
|
57
|
+
|
|
58
|
+
Return JSON:
|
|
59
|
+
{
|
|
60
|
+
"recommendedResolution": n,
|
|
61
|
+
"reasoning": "why this resolution",
|
|
62
|
+
"alternativeResolutions": [{"resolution": n, "useCase": "when to use"}],
|
|
63
|
+
"geographicScope": "description of area covered",
|
|
64
|
+
"scaleSuggestions": {
|
|
65
|
+
"expansion": {"resolution": n, "reason": "when to expand"},
|
|
66
|
+
"contraction": {"resolution": n, "reason": "when to focus"}
|
|
67
|
+
}
|
|
68
|
+
}`;
|
|
69
|
+
|
|
70
|
+
return this.llm.getJSON(
|
|
71
|
+
systemPrompt,
|
|
72
|
+
JSON.stringify(item, null, 2),
|
|
73
|
+
{ temperature: 0.3 }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Analyze geographic distribution of data across H3 cells
|
|
79
|
+
* @param {string} holonId - Parent holon to analyze
|
|
80
|
+
* @param {string} lensName - Lens to analyze
|
|
81
|
+
* @param {Object} options - Options
|
|
82
|
+
* @returns {Promise<Object>} Distribution analysis
|
|
83
|
+
*/
|
|
84
|
+
async analyzeDistribution(holonId, lensName, options = {}) {
|
|
85
|
+
if (!this.holosphere) {
|
|
86
|
+
throw new Error('HoloSphere instance required');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const { includeChildren = true, maxChildren = 20 } = options;
|
|
90
|
+
|
|
91
|
+
// Get data from the holon
|
|
92
|
+
const parentData = await this.holosphere.getAll(holonId, lensName);
|
|
93
|
+
|
|
94
|
+
// Get data from children if requested
|
|
95
|
+
const childrenData = {};
|
|
96
|
+
if (includeChildren && h3.isValidCell(holonId)) {
|
|
97
|
+
const resolution = h3.getResolution(holonId);
|
|
98
|
+
if (resolution < 15) {
|
|
99
|
+
const children = h3.cellToChildren(holonId, resolution + 1);
|
|
100
|
+
for (const child of children.slice(0, maxChildren)) {
|
|
101
|
+
try {
|
|
102
|
+
const data = await this.holosphere.getAll(child, lensName);
|
|
103
|
+
if (data.length > 0) {
|
|
104
|
+
childrenData[child] = {
|
|
105
|
+
count: data.length,
|
|
106
|
+
sample: data.slice(0, 3),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// Skip unavailable children
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const systemPrompt = `You are a geospatial analyst. Analyze the distribution of data across this H3 hexagonal region.
|
|
117
|
+
|
|
118
|
+
Parent holon: ${holonId}
|
|
119
|
+
Resolution: ${h3.isValidCell(holonId) ? h3.getResolution(holonId) : 'N/A'}
|
|
120
|
+
Parent data count: ${parentData.length}
|
|
121
|
+
Children with data: ${Object.keys(childrenData).length}
|
|
122
|
+
|
|
123
|
+
Analyze:
|
|
124
|
+
1. Data density and distribution patterns
|
|
125
|
+
2. Geographic hotspots and cold spots
|
|
126
|
+
3. Coverage gaps
|
|
127
|
+
4. Clustering patterns
|
|
128
|
+
5. Recommendations for better coverage
|
|
129
|
+
|
|
130
|
+
Return JSON:
|
|
131
|
+
{
|
|
132
|
+
"distribution": {
|
|
133
|
+
"pattern": "clustered|uniform|sparse|concentrated",
|
|
134
|
+
"density": "high|medium|low",
|
|
135
|
+
"coverage": 0.0-1.0
|
|
136
|
+
},
|
|
137
|
+
"hotspots": [{"holonId": "id", "reason": "why"}],
|
|
138
|
+
"gaps": [{"description": "gap description", "suggestedAction": "what to do"}],
|
|
139
|
+
"clusters": [{"theme": "cluster theme", "holons": ["id1"]}],
|
|
140
|
+
"recommendations": ["rec1"],
|
|
141
|
+
"summary": "overview"
|
|
142
|
+
}`;
|
|
143
|
+
|
|
144
|
+
const userMessage = `Parent data sample:
|
|
145
|
+
${JSON.stringify(parentData.slice(0, 10), null, 2)}
|
|
146
|
+
|
|
147
|
+
Children distribution:
|
|
148
|
+
${JSON.stringify(childrenData, null, 2)}`;
|
|
149
|
+
|
|
150
|
+
const analysis = await this.llm.getJSON(systemPrompt, userMessage, { temperature: 0.3 });
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
holonId,
|
|
154
|
+
lensName,
|
|
155
|
+
resolution: h3.isValidCell(holonId) ? h3.getResolution(holonId) : null,
|
|
156
|
+
parentDataCount: parentData.length,
|
|
157
|
+
childrenAnalyzed: Object.keys(childrenData).length,
|
|
158
|
+
analysis,
|
|
159
|
+
timestamp: Date.now(),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Find relevant data from neighboring H3 cells
|
|
165
|
+
* @param {string} holonId - Center holon
|
|
166
|
+
* @param {string} lensName - Lens to search
|
|
167
|
+
* @param {Object} options - Options
|
|
168
|
+
* @returns {Promise<Object>} Neighbor recommendations
|
|
169
|
+
*/
|
|
170
|
+
async findNeighborRelevance(holonId, lensName, options = {}) {
|
|
171
|
+
if (!this.holosphere) {
|
|
172
|
+
throw new Error('HoloSphere instance required');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!h3.isValidCell(holonId)) {
|
|
176
|
+
throw new Error('Invalid H3 cell');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const { ringSize = 1, relevanceThreshold = 0.5 } = options;
|
|
180
|
+
|
|
181
|
+
// Get center data
|
|
182
|
+
const centerData = await this.holosphere.getAll(holonId, lensName);
|
|
183
|
+
|
|
184
|
+
// Get neighbor data
|
|
185
|
+
const neighbors = h3.gridDisk(holonId, ringSize).filter(n => n !== holonId);
|
|
186
|
+
const neighborData = {};
|
|
187
|
+
|
|
188
|
+
for (const neighbor of neighbors) {
|
|
189
|
+
try {
|
|
190
|
+
const data = await this.holosphere.getAll(neighbor, lensName);
|
|
191
|
+
if (data.length > 0) {
|
|
192
|
+
neighborData[neighbor] = data.slice(0, 5);
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
// Skip
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (Object.keys(neighborData).length === 0) {
|
|
200
|
+
return {
|
|
201
|
+
holonId,
|
|
202
|
+
neighbors: [],
|
|
203
|
+
message: 'No data found in neighboring cells',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const systemPrompt = `You are a geospatial relevance analyst. Find items in neighboring H3 cells that are relevant to the center cell's data.
|
|
208
|
+
|
|
209
|
+
Center cell: ${holonId}
|
|
210
|
+
Ring size: ${ringSize} (immediate neighbors)
|
|
211
|
+
|
|
212
|
+
Consider:
|
|
213
|
+
1. Thematic overlap
|
|
214
|
+
2. Potential collaborations
|
|
215
|
+
3. Shared resources
|
|
216
|
+
4. Cross-boundary projects
|
|
217
|
+
5. Geographic continuity of activities
|
|
218
|
+
|
|
219
|
+
Return JSON:
|
|
220
|
+
{
|
|
221
|
+
"relevantNeighbors": [
|
|
222
|
+
{
|
|
223
|
+
"holonId": "neighbor_id",
|
|
224
|
+
"relevanceScore": 0.0-1.0,
|
|
225
|
+
"relevantItems": [{"id": "item_id", "reason": "why relevant"}],
|
|
226
|
+
"collaborationPotential": "description"
|
|
227
|
+
}
|
|
228
|
+
],
|
|
229
|
+
"crossBoundaryOpportunities": [
|
|
230
|
+
{
|
|
231
|
+
"description": "opportunity",
|
|
232
|
+
"involvedHolons": ["id1", "id2"],
|
|
233
|
+
"suggestedAction": "what to do"
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
"geographicPatterns": ["pattern1"],
|
|
237
|
+
"summary": "overview"
|
|
238
|
+
}`;
|
|
239
|
+
|
|
240
|
+
const userMessage = `Center cell data:
|
|
241
|
+
${JSON.stringify(centerData.slice(0, 10), null, 2)}
|
|
242
|
+
|
|
243
|
+
Neighbor data:
|
|
244
|
+
${JSON.stringify(neighborData, null, 2)}`;
|
|
245
|
+
|
|
246
|
+
const analysis = await this.llm.getJSON(systemPrompt, userMessage, { temperature: 0.4 });
|
|
247
|
+
|
|
248
|
+
// Filter by threshold
|
|
249
|
+
if (analysis.relevantNeighbors) {
|
|
250
|
+
analysis.relevantNeighbors = analysis.relevantNeighbors
|
|
251
|
+
.filter(n => n.relevanceScore >= relevanceThreshold);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
holonId,
|
|
256
|
+
lensName,
|
|
257
|
+
ringSize,
|
|
258
|
+
neighborsAnalyzed: Object.keys(neighborData).length,
|
|
259
|
+
analysis,
|
|
260
|
+
timestamp: Date.now(),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Suggest geographic expansion or contraction for an item
|
|
266
|
+
* @param {Object} item - Item to analyze
|
|
267
|
+
* @param {string} currentHolonId - Current holon
|
|
268
|
+
* @param {string} lensName - Lens name
|
|
269
|
+
* @param {Object} options - Options
|
|
270
|
+
* @returns {Promise<Object>} Expansion/contraction suggestions
|
|
271
|
+
*/
|
|
272
|
+
async suggestGeographicScope(item, currentHolonId, lensName, options = {}) {
|
|
273
|
+
if (!this.holosphere) {
|
|
274
|
+
throw new Error('HoloSphere instance required');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!h3.isValidCell(currentHolonId)) {
|
|
278
|
+
throw new Error('Invalid H3 cell');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const currentResolution = h3.getResolution(currentHolonId);
|
|
282
|
+
|
|
283
|
+
// Get parent data
|
|
284
|
+
let parentData = [];
|
|
285
|
+
if (currentResolution > 0) {
|
|
286
|
+
const parent = h3.cellToParent(currentHolonId, currentResolution - 1);
|
|
287
|
+
try {
|
|
288
|
+
parentData = await this.holosphere.getAll(parent, lensName);
|
|
289
|
+
} catch {
|
|
290
|
+
// No parent data
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Get sample children data
|
|
295
|
+
let childrenData = [];
|
|
296
|
+
if (currentResolution < 15) {
|
|
297
|
+
const children = h3.cellToChildren(currentHolonId, currentResolution + 1);
|
|
298
|
+
for (const child of children.slice(0, 7)) {
|
|
299
|
+
try {
|
|
300
|
+
const data = await this.holosphere.getAll(child, lensName);
|
|
301
|
+
childrenData.push(...data.slice(0, 2));
|
|
302
|
+
} catch {
|
|
303
|
+
// Skip
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const systemPrompt = `You are a geographic scope advisor. Analyze whether this item should expand to a larger region (parent holon) or focus on smaller sub-regions (children holons).
|
|
309
|
+
|
|
310
|
+
Current holon: ${currentHolonId}
|
|
311
|
+
Current resolution: ${currentResolution}
|
|
312
|
+
|
|
313
|
+
Factors to consider:
|
|
314
|
+
1. Item's stated scope and goals
|
|
315
|
+
2. Current participation/activity level
|
|
316
|
+
3. Resource requirements
|
|
317
|
+
4. Similar activities in parent/children
|
|
318
|
+
5. Natural geographic boundaries
|
|
319
|
+
|
|
320
|
+
Return JSON:
|
|
321
|
+
{
|
|
322
|
+
"currentScopeAssessment": {
|
|
323
|
+
"appropriate": true/false,
|
|
324
|
+
"reasoning": "why"
|
|
325
|
+
},
|
|
326
|
+
"expansionRecommendation": {
|
|
327
|
+
"recommended": true/false,
|
|
328
|
+
"targetResolution": n,
|
|
329
|
+
"reasoning": "why expand",
|
|
330
|
+
"benefits": ["benefit1"],
|
|
331
|
+
"risks": ["risk1"]
|
|
332
|
+
},
|
|
333
|
+
"contractionRecommendation": {
|
|
334
|
+
"recommended": true/false,
|
|
335
|
+
"targetResolution": n,
|
|
336
|
+
"reasoning": "why contract",
|
|
337
|
+
"suggestedFocusAreas": ["description"],
|
|
338
|
+
"benefits": ["benefit1"],
|
|
339
|
+
"risks": ["risk1"]
|
|
340
|
+
},
|
|
341
|
+
"optimalAction": "expand|contract|maintain",
|
|
342
|
+
"summary": "recommendation summary"
|
|
343
|
+
}`;
|
|
344
|
+
|
|
345
|
+
const userMessage = `Item to analyze:
|
|
346
|
+
${JSON.stringify(item, null, 2)}
|
|
347
|
+
|
|
348
|
+
Parent region data (${parentData.length} items):
|
|
349
|
+
${JSON.stringify(parentData.slice(0, 5), null, 2)}
|
|
350
|
+
|
|
351
|
+
Children regions data (${childrenData.length} items):
|
|
352
|
+
${JSON.stringify(childrenData.slice(0, 5), null, 2)}`;
|
|
353
|
+
|
|
354
|
+
return this.llm.getJSON(systemPrompt, userMessage, { temperature: 0.3 });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Analyze coverage gaps in a region
|
|
359
|
+
* @param {string} holonId - Region to analyze
|
|
360
|
+
* @param {string} lensName - Lens
|
|
361
|
+
* @param {Object} options - Options
|
|
362
|
+
* @returns {Promise<Object>} Coverage analysis
|
|
363
|
+
*/
|
|
364
|
+
async analyzeCoverage(holonId, lensName, options = {}) {
|
|
365
|
+
if (!this.holosphere) {
|
|
366
|
+
throw new Error('HoloSphere instance required');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (!h3.isValidCell(holonId)) {
|
|
370
|
+
throw new Error('Invalid H3 cell');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const { targetResolution = null } = options;
|
|
374
|
+
const currentResolution = h3.getResolution(holonId);
|
|
375
|
+
const childResolution = targetResolution || Math.min(currentResolution + 1, 15);
|
|
376
|
+
|
|
377
|
+
// Get all children at target resolution
|
|
378
|
+
const children = h3.cellToChildren(holonId, childResolution);
|
|
379
|
+
|
|
380
|
+
// Check which children have data
|
|
381
|
+
const coverage = {
|
|
382
|
+
total: children.length,
|
|
383
|
+
withData: 0,
|
|
384
|
+
withoutData: 0,
|
|
385
|
+
dataDistribution: {},
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const childrenWithData = [];
|
|
389
|
+
const childrenWithoutData = [];
|
|
390
|
+
|
|
391
|
+
for (const child of children) {
|
|
392
|
+
try {
|
|
393
|
+
const data = await this.holosphere.getAll(child, lensName);
|
|
394
|
+
if (data.length > 0) {
|
|
395
|
+
coverage.withData++;
|
|
396
|
+
coverage.dataDistribution[child] = data.length;
|
|
397
|
+
childrenWithData.push({ holon: child, count: data.length });
|
|
398
|
+
} else {
|
|
399
|
+
coverage.withoutData++;
|
|
400
|
+
childrenWithoutData.push(child);
|
|
401
|
+
}
|
|
402
|
+
} catch {
|
|
403
|
+
coverage.withoutData++;
|
|
404
|
+
childrenWithoutData.push(child);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const systemPrompt = `You are a geographic coverage analyst. Analyze the data coverage in this H3 region.
|
|
409
|
+
|
|
410
|
+
Region: ${holonId}
|
|
411
|
+
Resolution: ${currentResolution}
|
|
412
|
+
Child resolution analyzed: ${childResolution}
|
|
413
|
+
Total children: ${coverage.total}
|
|
414
|
+
Children with data: ${coverage.withData}
|
|
415
|
+
Children without data: ${coverage.withoutData}
|
|
416
|
+
Coverage ratio: ${(coverage.withData / coverage.total * 100).toFixed(1)}%
|
|
417
|
+
|
|
418
|
+
Analyze:
|
|
419
|
+
1. Coverage patterns
|
|
420
|
+
2. Potential reasons for gaps
|
|
421
|
+
3. Priority areas for expansion
|
|
422
|
+
4. Whether gaps are concerning or expected
|
|
423
|
+
|
|
424
|
+
Return JSON:
|
|
425
|
+
{
|
|
426
|
+
"coverageScore": 0.0-1.0,
|
|
427
|
+
"coverageQuality": "excellent|good|moderate|poor|minimal",
|
|
428
|
+
"patterns": {
|
|
429
|
+
"type": "clustered|scattered|peripheral|central|uniform",
|
|
430
|
+
"description": "pattern description"
|
|
431
|
+
},
|
|
432
|
+
"gaps": {
|
|
433
|
+
"count": n,
|
|
434
|
+
"significance": "high|medium|low",
|
|
435
|
+
"likelyReasons": ["reason1"],
|
|
436
|
+
"priorityAreas": ["description of areas to focus"]
|
|
437
|
+
},
|
|
438
|
+
"recommendations": [
|
|
439
|
+
{
|
|
440
|
+
"action": "what to do",
|
|
441
|
+
"priority": "high|medium|low",
|
|
442
|
+
"targetArea": "description"
|
|
443
|
+
}
|
|
444
|
+
],
|
|
445
|
+
"summary": "overview"
|
|
446
|
+
}`;
|
|
447
|
+
|
|
448
|
+
const analysis = await this.llm.getJSON(systemPrompt, '', { temperature: 0.3 });
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
holonId,
|
|
452
|
+
lensName,
|
|
453
|
+
resolution: currentResolution,
|
|
454
|
+
childResolution,
|
|
455
|
+
coverage: {
|
|
456
|
+
...coverage,
|
|
457
|
+
ratio: coverage.withData / coverage.total,
|
|
458
|
+
},
|
|
459
|
+
childrenWithData: childrenWithData.slice(0, 10),
|
|
460
|
+
gapCount: childrenWithoutData.length,
|
|
461
|
+
analysis,
|
|
462
|
+
timestamp: Date.now(),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Find patterns across multiple H3 resolutions
|
|
468
|
+
* @param {string} holonId - Starting holon
|
|
469
|
+
* @param {string} lensName - Lens
|
|
470
|
+
* @param {Object} options - Options
|
|
471
|
+
* @returns {Promise<Object>} Cross-resolution insights
|
|
472
|
+
*/
|
|
473
|
+
async crossResolutionInsights(holonId, lensName, options = {}) {
|
|
474
|
+
if (!this.holosphere) {
|
|
475
|
+
throw new Error('HoloSphere instance required');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (!h3.isValidCell(holonId)) {
|
|
479
|
+
throw new Error('Invalid H3 cell');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const { levels = 3 } = options;
|
|
483
|
+
const currentResolution = h3.getResolution(holonId);
|
|
484
|
+
|
|
485
|
+
// Collect data at multiple resolutions
|
|
486
|
+
const resolutionData = {};
|
|
487
|
+
|
|
488
|
+
// Current level
|
|
489
|
+
const currentData = await this.holosphere.getAll(holonId, lensName);
|
|
490
|
+
resolutionData[currentResolution] = {
|
|
491
|
+
holon: holonId,
|
|
492
|
+
count: currentData.length,
|
|
493
|
+
sample: currentData.slice(0, 5),
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// Parent levels
|
|
497
|
+
let parentHolon = holonId;
|
|
498
|
+
for (let i = 0; i < levels && h3.getResolution(parentHolon) > 0; i++) {
|
|
499
|
+
const res = h3.getResolution(parentHolon) - 1;
|
|
500
|
+
parentHolon = h3.cellToParent(parentHolon, res);
|
|
501
|
+
try {
|
|
502
|
+
const data = await this.holosphere.getAll(parentHolon, lensName);
|
|
503
|
+
resolutionData[res] = {
|
|
504
|
+
holon: parentHolon,
|
|
505
|
+
count: data.length,
|
|
506
|
+
sample: data.slice(0, 5),
|
|
507
|
+
};
|
|
508
|
+
} catch {
|
|
509
|
+
// Skip
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Child level (one level down)
|
|
514
|
+
if (currentResolution < 15) {
|
|
515
|
+
const childRes = currentResolution + 1;
|
|
516
|
+
const children = h3.cellToChildren(holonId, childRes);
|
|
517
|
+
let childTotal = 0;
|
|
518
|
+
const childSamples = [];
|
|
519
|
+
|
|
520
|
+
for (const child of children.slice(0, 7)) {
|
|
521
|
+
try {
|
|
522
|
+
const data = await this.holosphere.getAll(child, lensName);
|
|
523
|
+
childTotal += data.length;
|
|
524
|
+
if (data.length > 0) {
|
|
525
|
+
childSamples.push(...data.slice(0, 2));
|
|
526
|
+
}
|
|
527
|
+
} catch {
|
|
528
|
+
// Skip
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
resolutionData[childRes] = {
|
|
533
|
+
holon: `${children.length} children`,
|
|
534
|
+
count: childTotal,
|
|
535
|
+
sample: childSamples.slice(0, 5),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const systemPrompt = `You are a multi-scale geographic analyst. Find patterns that emerge across different H3 resolutions.
|
|
540
|
+
|
|
541
|
+
Starting holon: ${holonId}
|
|
542
|
+
Resolutions analyzed: ${Object.keys(resolutionData).sort().join(', ')}
|
|
543
|
+
|
|
544
|
+
Data at each resolution:
|
|
545
|
+
${JSON.stringify(resolutionData, null, 2)}
|
|
546
|
+
|
|
547
|
+
Analyze:
|
|
548
|
+
1. How themes evolve across scales
|
|
549
|
+
2. What appears only at certain resolutions
|
|
550
|
+
3. Aggregation patterns (local vs regional)
|
|
551
|
+
4. Scale-dependent opportunities
|
|
552
|
+
5. Optimal resolution for different activities
|
|
553
|
+
|
|
554
|
+
Return JSON:
|
|
555
|
+
{
|
|
556
|
+
"scalePatterns": [
|
|
557
|
+
{
|
|
558
|
+
"pattern": "description",
|
|
559
|
+
"visibleAt": [resolution_numbers],
|
|
560
|
+
"significance": "why this matters"
|
|
561
|
+
}
|
|
562
|
+
],
|
|
563
|
+
"themeEvolution": {
|
|
564
|
+
"localThemes": ["themes at fine resolution"],
|
|
565
|
+
"regionalThemes": ["themes at coarse resolution"],
|
|
566
|
+
"consistentThemes": ["themes across all scales"]
|
|
567
|
+
},
|
|
568
|
+
"optimalResolutions": {
|
|
569
|
+
"forCollaboration": n,
|
|
570
|
+
"forResources": n,
|
|
571
|
+
"forCommunity": n,
|
|
572
|
+
"reasoning": "why"
|
|
573
|
+
},
|
|
574
|
+
"insights": ["insight1"],
|
|
575
|
+
"recommendations": ["rec1"],
|
|
576
|
+
"summary": "overview"
|
|
577
|
+
}`;
|
|
578
|
+
|
|
579
|
+
return this.llm.getJSON(systemPrompt, '', { temperature: 0.4 });
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Suggest item migration between holons based on geographic fit
|
|
584
|
+
* @param {Object} item - Item to analyze
|
|
585
|
+
* @param {string} currentHolonId - Current location
|
|
586
|
+
* @param {string} lensName - Lens
|
|
587
|
+
* @param {Object} options - Options
|
|
588
|
+
* @returns {Promise<Object>} Migration suggestions
|
|
589
|
+
*/
|
|
590
|
+
async suggestMigration(item, currentHolonId, lensName, options = {}) {
|
|
591
|
+
if (!this.holosphere) {
|
|
592
|
+
throw new Error('HoloSphere instance required');
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (!h3.isValidCell(currentHolonId)) {
|
|
596
|
+
throw new Error('Invalid H3 cell');
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const { searchRadius = 2 } = options;
|
|
600
|
+
|
|
601
|
+
// Get data from nearby cells
|
|
602
|
+
const nearby = h3.gridDisk(currentHolonId, searchRadius);
|
|
603
|
+
const nearbyData = {};
|
|
604
|
+
|
|
605
|
+
for (const cell of nearby) {
|
|
606
|
+
if (cell === currentHolonId) continue;
|
|
607
|
+
try {
|
|
608
|
+
const data = await this.holosphere.getAll(cell, lensName);
|
|
609
|
+
if (data.length > 0) {
|
|
610
|
+
nearbyData[cell] = {
|
|
611
|
+
count: data.length,
|
|
612
|
+
sample: data.slice(0, 3),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
} catch {
|
|
616
|
+
// Skip
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Also check parent
|
|
621
|
+
const currentRes = h3.getResolution(currentHolonId);
|
|
622
|
+
let parentData = null;
|
|
623
|
+
if (currentRes > 0) {
|
|
624
|
+
const parent = h3.cellToParent(currentHolonId, currentRes - 1);
|
|
625
|
+
try {
|
|
626
|
+
const data = await this.holosphere.getAll(parent, lensName);
|
|
627
|
+
parentData = { holon: parent, count: data.length, sample: data.slice(0, 3) };
|
|
628
|
+
} catch {
|
|
629
|
+
// Skip
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const systemPrompt = `You are a geographic placement advisor. Determine if this item would be better suited in a different H3 cell.
|
|
634
|
+
|
|
635
|
+
Current location: ${currentHolonId}
|
|
636
|
+
Current resolution: ${currentRes}
|
|
637
|
+
|
|
638
|
+
Consider:
|
|
639
|
+
1. Thematic fit with existing data in each cell
|
|
640
|
+
2. Geographic scope of the item
|
|
641
|
+
3. Collaboration opportunities
|
|
642
|
+
4. Resource proximity
|
|
643
|
+
5. Community alignment
|
|
644
|
+
|
|
645
|
+
Return JSON:
|
|
646
|
+
{
|
|
647
|
+
"currentFit": {
|
|
648
|
+
"score": 0.0-1.0,
|
|
649
|
+
"reasoning": "why current location is/isn't appropriate"
|
|
650
|
+
},
|
|
651
|
+
"migrationRecommended": true/false,
|
|
652
|
+
"suggestedDestinations": [
|
|
653
|
+
{
|
|
654
|
+
"holonId": "cell_id",
|
|
655
|
+
"fitScore": 0.0-1.0,
|
|
656
|
+
"reasoning": "why this location",
|
|
657
|
+
"benefits": ["benefit1"],
|
|
658
|
+
"drawbacks": ["drawback1"]
|
|
659
|
+
}
|
|
660
|
+
],
|
|
661
|
+
"stayReasons": ["reason to stay if applicable"],
|
|
662
|
+
"moveReasons": ["reason to move if applicable"],
|
|
663
|
+
"recommendation": "stay|move|duplicate",
|
|
664
|
+
"summary": "final recommendation"
|
|
665
|
+
}`;
|
|
666
|
+
|
|
667
|
+
const userMessage = `Item to place:
|
|
668
|
+
${JSON.stringify(item, null, 2)}
|
|
669
|
+
|
|
670
|
+
Nearby cells with data:
|
|
671
|
+
${JSON.stringify(nearbyData, null, 2)}
|
|
672
|
+
|
|
673
|
+
${parentData ? `Parent cell data:\n${JSON.stringify(parentData, null, 2)}` : ''}`;
|
|
674
|
+
|
|
675
|
+
return this.llm.getJSON(systemPrompt, userMessage, { temperature: 0.3 });
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Generate a geographic activity report for a region
|
|
680
|
+
* @param {string} holonId - Region to report on
|
|
681
|
+
* @param {Object} options - Options
|
|
682
|
+
* @returns {Promise<Object>} Geographic report
|
|
683
|
+
*/
|
|
684
|
+
async generateGeographicReport(holonId, options = {}) {
|
|
685
|
+
if (!this.holosphere) {
|
|
686
|
+
throw new Error('HoloSphere instance required');
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const { lenses = ['projects', 'quests', 'events', 'resources', 'default'] } = options;
|
|
690
|
+
|
|
691
|
+
// Collect data from all lenses
|
|
692
|
+
const lensData = {};
|
|
693
|
+
for (const lens of lenses) {
|
|
694
|
+
try {
|
|
695
|
+
const data = await this.holosphere.getAll(holonId, lens);
|
|
696
|
+
if (data.length > 0) {
|
|
697
|
+
lensData[lens] = {
|
|
698
|
+
count: data.length,
|
|
699
|
+
sample: data.slice(0, 5),
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
} catch {
|
|
703
|
+
// Skip
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Get geographic context
|
|
708
|
+
let geoContext = {};
|
|
709
|
+
if (h3.isValidCell(holonId)) {
|
|
710
|
+
const res = h3.getResolution(holonId);
|
|
711
|
+
const [lat, lng] = h3.cellToLatLng(holonId);
|
|
712
|
+
const boundary = h3.cellToBoundary(holonId);
|
|
713
|
+
const area = h3.cellArea(holonId, 'km2');
|
|
714
|
+
|
|
715
|
+
geoContext = {
|
|
716
|
+
resolution: res,
|
|
717
|
+
center: { lat, lng },
|
|
718
|
+
areaKm2: area,
|
|
719
|
+
vertexCount: boundary.length,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const systemPrompt = `You are a regional activity reporter. Generate a comprehensive report on geographic activity in this H3 region.
|
|
724
|
+
|
|
725
|
+
Region: ${holonId}
|
|
726
|
+
Geographic context: ${JSON.stringify(geoContext, null, 2)}
|
|
727
|
+
Data available in ${Object.keys(lensData).length} categories
|
|
728
|
+
|
|
729
|
+
Generate a report covering:
|
|
730
|
+
1. Executive summary
|
|
731
|
+
2. Activity overview by category
|
|
732
|
+
3. Key highlights and achievements
|
|
733
|
+
4. Geographic patterns
|
|
734
|
+
5. Opportunities and challenges
|
|
735
|
+
6. Recommendations
|
|
736
|
+
|
|
737
|
+
Return JSON:
|
|
738
|
+
{
|
|
739
|
+
"title": "Region Report Title",
|
|
740
|
+
"executiveSummary": "2-3 sentence overview",
|
|
741
|
+
"activityOverview": {
|
|
742
|
+
"totalItems": n,
|
|
743
|
+
"categorySummaries": {"lens": "summary"}
|
|
744
|
+
},
|
|
745
|
+
"highlights": [
|
|
746
|
+
{"title": "highlight", "description": "details", "category": "lens"}
|
|
747
|
+
],
|
|
748
|
+
"geographicPatterns": ["pattern1"],
|
|
749
|
+
"strengths": ["strength1"],
|
|
750
|
+
"challenges": ["challenge1"],
|
|
751
|
+
"opportunities": ["opportunity1"],
|
|
752
|
+
"recommendations": [
|
|
753
|
+
{"priority": "high|medium|low", "action": "what to do", "rationale": "why"}
|
|
754
|
+
],
|
|
755
|
+
"metrics": {
|
|
756
|
+
"activityLevel": "high|medium|low",
|
|
757
|
+
"diversity": "high|medium|low",
|
|
758
|
+
"growth": "growing|stable|declining"
|
|
759
|
+
}
|
|
760
|
+
}`;
|
|
761
|
+
|
|
762
|
+
const report = await this.llm.getJSON(
|
|
763
|
+
systemPrompt,
|
|
764
|
+
JSON.stringify(lensData, null, 2),
|
|
765
|
+
{ temperature: 0.4, maxTokens: 2000 }
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
return {
|
|
769
|
+
holonId,
|
|
770
|
+
geoContext,
|
|
771
|
+
lensesAnalyzed: Object.keys(lensData),
|
|
772
|
+
totalItems: Object.values(lensData).reduce((sum, l) => sum + l.count, 0),
|
|
773
|
+
report,
|
|
774
|
+
timestamp: Date.now(),
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Find geographic clusters of related items
|
|
780
|
+
* @param {string} holonId - Region to analyze
|
|
781
|
+
* @param {string} lensName - Lens
|
|
782
|
+
* @param {Object} options - Options
|
|
783
|
+
* @returns {Promise<Object>} Geographic clusters
|
|
784
|
+
*/
|
|
785
|
+
async findGeographicClusters(holonId, lensName, options = {}) {
|
|
786
|
+
if (!this.holosphere) {
|
|
787
|
+
throw new Error('HoloSphere instance required');
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (!h3.isValidCell(holonId)) {
|
|
791
|
+
throw new Error('Invalid H3 cell');
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const { clusterResolution = null } = options;
|
|
795
|
+
const currentRes = h3.getResolution(holonId);
|
|
796
|
+
const targetRes = clusterResolution || Math.min(currentRes + 1, 15);
|
|
797
|
+
|
|
798
|
+
// Get data from children at cluster resolution
|
|
799
|
+
const children = h3.cellToChildren(holonId, targetRes);
|
|
800
|
+
const cellData = {};
|
|
801
|
+
|
|
802
|
+
for (const child of children) {
|
|
803
|
+
try {
|
|
804
|
+
const data = await this.holosphere.getAll(child, lensName);
|
|
805
|
+
if (data.length > 0) {
|
|
806
|
+
cellData[child] = data;
|
|
807
|
+
}
|
|
808
|
+
} catch {
|
|
809
|
+
// Skip
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (Object.keys(cellData).length < 2) {
|
|
814
|
+
return {
|
|
815
|
+
holonId,
|
|
816
|
+
clusters: [],
|
|
817
|
+
message: 'Not enough data for clustering',
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const systemPrompt = `You are a geographic clustering expert. Find thematic clusters in this spatial data.
|
|
822
|
+
|
|
823
|
+
Region: ${holonId}
|
|
824
|
+
Resolution analyzed: ${targetRes}
|
|
825
|
+
Cells with data: ${Object.keys(cellData).length}
|
|
826
|
+
|
|
827
|
+
Identify:
|
|
828
|
+
1. Thematic clusters (cells with similar content)
|
|
829
|
+
2. Activity clusters (cells with related activities)
|
|
830
|
+
3. Isolated cells (unique content)
|
|
831
|
+
4. Potential connections between clusters
|
|
832
|
+
|
|
833
|
+
Return JSON:
|
|
834
|
+
{
|
|
835
|
+
"clusters": [
|
|
836
|
+
{
|
|
837
|
+
"name": "cluster name",
|
|
838
|
+
"theme": "what unifies this cluster",
|
|
839
|
+
"cells": ["cell_id1", "cell_id2"],
|
|
840
|
+
"strength": 0.0-1.0,
|
|
841
|
+
"characteristics": ["char1"]
|
|
842
|
+
}
|
|
843
|
+
],
|
|
844
|
+
"isolatedCells": [
|
|
845
|
+
{"cellId": "id", "uniqueAspect": "what makes it unique"}
|
|
846
|
+
],
|
|
847
|
+
"interClusterConnections": [
|
|
848
|
+
{"cluster1": "name", "cluster2": "name", "connection": "how related"}
|
|
849
|
+
],
|
|
850
|
+
"spatialPatterns": ["pattern1"],
|
|
851
|
+
"recommendations": ["rec1"]
|
|
852
|
+
}`;
|
|
853
|
+
|
|
854
|
+
const analysis = await this.llm.getJSON(
|
|
855
|
+
systemPrompt,
|
|
856
|
+
JSON.stringify(cellData, null, 2),
|
|
857
|
+
{ temperature: 0.4 }
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
return {
|
|
861
|
+
holonId,
|
|
862
|
+
lensName,
|
|
863
|
+
clusterResolution: targetRes,
|
|
864
|
+
cellsAnalyzed: Object.keys(cellData).length,
|
|
865
|
+
analysis,
|
|
866
|
+
timestamp: Date.now(),
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Calculate and explain geographic impact of an item
|
|
872
|
+
* @param {Object} item - Item to analyze
|
|
873
|
+
* @param {string} holonId - Item's holon
|
|
874
|
+
* @param {string} lensName - Lens
|
|
875
|
+
* @param {Object} options - Options
|
|
876
|
+
* @returns {Promise<Object>} Impact analysis
|
|
877
|
+
*/
|
|
878
|
+
async analyzeGeographicImpact(item, holonId, lensName, options = {}) {
|
|
879
|
+
if (!h3.isValidCell(holonId)) {
|
|
880
|
+
throw new Error('Invalid H3 cell');
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const res = h3.getResolution(holonId);
|
|
884
|
+
const [lat, lng] = h3.cellToLatLng(holonId);
|
|
885
|
+
const area = h3.cellArea(holonId, 'km2');
|
|
886
|
+
|
|
887
|
+
// Get parent info for context
|
|
888
|
+
let parentContext = null;
|
|
889
|
+
if (res > 0) {
|
|
890
|
+
const parent = h3.cellToParent(holonId, Math.max(0, res - 2));
|
|
891
|
+
const parentArea = h3.cellArea(parent, 'km2');
|
|
892
|
+
parentContext = {
|
|
893
|
+
holon: parent,
|
|
894
|
+
resolution: res - 2,
|
|
895
|
+
areaKm2: parentArea,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const systemPrompt = `You are a geographic impact analyst. Analyze the geographic reach and impact of this item.
|
|
900
|
+
|
|
901
|
+
Item location: ${holonId}
|
|
902
|
+
Resolution: ${res}
|
|
903
|
+
Center: ${lat.toFixed(4)}, ${lng.toFixed(4)}
|
|
904
|
+
Area: ${area.toFixed(2)} km²
|
|
905
|
+
|
|
906
|
+
${parentContext ? `Broader region: ${parentContext.areaKm2.toFixed(2)} km²` : ''}
|
|
907
|
+
|
|
908
|
+
Analyze:
|
|
909
|
+
1. Direct impact area (immediate cell)
|
|
910
|
+
2. Indirect impact area (spillover effects)
|
|
911
|
+
3. Potential reach (if expanded)
|
|
912
|
+
4. Geographic barriers/enablers
|
|
913
|
+
5. Network effects
|
|
914
|
+
|
|
915
|
+
Return JSON:
|
|
916
|
+
{
|
|
917
|
+
"directImpact": {
|
|
918
|
+
"areaKm2": n,
|
|
919
|
+
"description": "direct impact area",
|
|
920
|
+
"affectedPopulation": "estimate or N/A"
|
|
921
|
+
},
|
|
922
|
+
"indirectImpact": {
|
|
923
|
+
"estimatedReach": n,
|
|
924
|
+
"mechanisms": ["how impact spreads"],
|
|
925
|
+
"neighboringAreas": ["affected areas"]
|
|
926
|
+
},
|
|
927
|
+
"potentialReach": {
|
|
928
|
+
"ifExpanded": {
|
|
929
|
+
"maxAreaKm2": n,
|
|
930
|
+
"optimalResolution": n,
|
|
931
|
+
"limitingFactors": ["factor1"]
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
"geographicFactors": {
|
|
935
|
+
"enablers": ["what helps geographic spread"],
|
|
936
|
+
"barriers": ["what limits spread"]
|
|
937
|
+
},
|
|
938
|
+
"impactScore": {
|
|
939
|
+
"local": 0.0-1.0,
|
|
940
|
+
"regional": 0.0-1.0,
|
|
941
|
+
"network": 0.0-1.0
|
|
942
|
+
},
|
|
943
|
+
"recommendations": ["how to increase impact"],
|
|
944
|
+
"summary": "impact overview"
|
|
945
|
+
}`;
|
|
946
|
+
|
|
947
|
+
return this.llm.getJSON(
|
|
948
|
+
systemPrompt,
|
|
949
|
+
JSON.stringify(item, null, 2),
|
|
950
|
+
{ temperature: 0.3 }
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
export default H3AI;
|