holosphere 1.1.20 → 2.0.0-alpha0
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/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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Verify what data is actually on the relay
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { HoloSphere } from '../src/index.js';
|
|
8
|
+
import dotenv from 'dotenv';
|
|
9
|
+
|
|
10
|
+
dotenv.config();
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
console.log('Verifying Relay Data');
|
|
14
|
+
console.log('====================\n');
|
|
15
|
+
|
|
16
|
+
const holonId = process.argv[2] || '235114395';
|
|
17
|
+
const relayUrl = process.env.HOLOSPHERE_RELAY || 'wss://relay.holons.io';
|
|
18
|
+
|
|
19
|
+
// First, check what we have locally
|
|
20
|
+
console.log('Step 1: Checking local storage...\n');
|
|
21
|
+
const hsLocal = new HoloSphere({
|
|
22
|
+
appName: 'Holons',
|
|
23
|
+
relays: [],
|
|
24
|
+
persistence: true,
|
|
25
|
+
logLevel: 'ERROR',
|
|
26
|
+
privateKey: process.env.HOLOSPHERE_PRIVATE_KEY
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
30
|
+
|
|
31
|
+
const localQuests = await hsLocal.read(holonId, 'quests');
|
|
32
|
+
const localQuestIds = localQuests ? Object.keys(localQuests).sort() : [];
|
|
33
|
+
console.log(`Local storage: ${localQuestIds.length} quests`);
|
|
34
|
+
|
|
35
|
+
// Now check what's on the relay
|
|
36
|
+
console.log(`\nStep 2: Checking relay (${relayUrl})...\n`);
|
|
37
|
+
const hsRelay = new HoloSphere({
|
|
38
|
+
appName: 'Holons',
|
|
39
|
+
relays: [relayUrl],
|
|
40
|
+
persistence: false, // Don't load from local storage
|
|
41
|
+
logLevel: 'ERROR',
|
|
42
|
+
privateKey: process.env.HOLOSPHERE_PRIVATE_KEY
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Wait for relay connection
|
|
46
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
47
|
+
|
|
48
|
+
const relayQuests = await hsRelay.read(holonId, 'quests');
|
|
49
|
+
const relayQuestIds = relayQuests ? Object.keys(relayQuests).sort() : [];
|
|
50
|
+
console.log(`Relay storage: ${relayQuestIds.length} quests`);
|
|
51
|
+
|
|
52
|
+
// Compare
|
|
53
|
+
console.log('\n' + '='.repeat(60));
|
|
54
|
+
console.log('Comparison');
|
|
55
|
+
console.log('='.repeat(60) + '\n');
|
|
56
|
+
|
|
57
|
+
console.log(`Local: ${localQuestIds.length} quests`);
|
|
58
|
+
console.log(`Relay: ${relayQuestIds.length} quests`);
|
|
59
|
+
console.log(`Missing from relay: ${localQuestIds.length - relayQuestIds.length} quests\n`);
|
|
60
|
+
|
|
61
|
+
// Find which quests are missing from relay
|
|
62
|
+
const missingFromRelay = localQuestIds.filter(id => !relayQuestIds.includes(id));
|
|
63
|
+
|
|
64
|
+
if (missingFromRelay.length > 0) {
|
|
65
|
+
console.log(`Missing quest IDs (first 20):`);
|
|
66
|
+
missingFromRelay.slice(0, 20).forEach(id => {
|
|
67
|
+
const quest = localQuests[id];
|
|
68
|
+
console.log(` - ${id}: ${quest.title || 'N/A'}`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (missingFromRelay.length > 20) {
|
|
72
|
+
console.log(` ... and ${missingFromRelay.length - 20} more`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check if there are any on relay that aren't local
|
|
77
|
+
const onlyOnRelay = relayQuestIds.filter(id => !localQuestIds.includes(id));
|
|
78
|
+
if (onlyOnRelay.length > 0) {
|
|
79
|
+
console.log(`\nQuests on relay but not local: ${onlyOnRelay.length}`);
|
|
80
|
+
onlyOnRelay.forEach(id => {
|
|
81
|
+
console.log(` - ${id}`);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check other lenses
|
|
86
|
+
console.log('\n' + '='.repeat(60));
|
|
87
|
+
console.log('All Lenses Comparison');
|
|
88
|
+
console.log('='.repeat(60) + '\n');
|
|
89
|
+
|
|
90
|
+
const lenses = ['3891', 'announcements', 'checklists', 'expenses', 'linked_quests',
|
|
91
|
+
'profile', 'quests', 'roles', 'settings', 'users'];
|
|
92
|
+
|
|
93
|
+
console.log('Lens'.padEnd(20) + 'Local'.padEnd(10) + 'Relay'.padEnd(10) + 'Missing');
|
|
94
|
+
console.log('-'.repeat(60));
|
|
95
|
+
|
|
96
|
+
for (const lens of lenses) {
|
|
97
|
+
const localData = await hsLocal.read(holonId, lens);
|
|
98
|
+
const relayData = await hsRelay.read(holonId, lens);
|
|
99
|
+
|
|
100
|
+
const localCount = localData ? Object.keys(localData).length : 0;
|
|
101
|
+
const relayCount = relayData ? Object.keys(relayData).length : 0;
|
|
102
|
+
const missing = localCount - relayCount;
|
|
103
|
+
|
|
104
|
+
console.log(
|
|
105
|
+
lens.padEnd(20) +
|
|
106
|
+
localCount.toString().padEnd(10) +
|
|
107
|
+
relayCount.toString().padEnd(10) +
|
|
108
|
+
missing.toString()
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Test a few specific quests that we know should exist
|
|
113
|
+
console.log('\n' + '='.repeat(60));
|
|
114
|
+
console.log('Testing Specific Quest Retrieval from Relay');
|
|
115
|
+
console.log('='.repeat(60) + '\n');
|
|
116
|
+
|
|
117
|
+
const testQuestIds = ['1002', '10026', '1073', '1005', '1011'];
|
|
118
|
+
|
|
119
|
+
for (const questId of testQuestIds) {
|
|
120
|
+
const inLocal = localQuests && localQuests[questId];
|
|
121
|
+
const inRelay = relayQuests && relayQuests[questId];
|
|
122
|
+
|
|
123
|
+
console.log(`Quest ${questId}:`);
|
|
124
|
+
console.log(` Local: ${inLocal ? '✓ Found' : '✗ Not found'}`);
|
|
125
|
+
console.log(` Relay: ${inRelay ? '✓ Found' : '✗ Not found'}`);
|
|
126
|
+
if (inLocal) {
|
|
127
|
+
console.log(` Title: ${inLocal.title || 'N/A'}`);
|
|
128
|
+
}
|
|
129
|
+
console.log('');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log('✓ Verification complete\n');
|
|
133
|
+
process.exit(0);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
main().catch(error => {
|
|
137
|
+
console.error('Error:', error);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
});
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Aggregation - AI-assisted hierarchical data aggregation up the holon tree
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as h3 from 'h3-js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Smart Aggregation class
|
|
9
|
+
*/
|
|
10
|
+
export class SmartAggregation {
|
|
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
|
+
* Smart upcast - AI-summarized hierarchical aggregation
|
|
30
|
+
* @param {string} holon - Starting holon
|
|
31
|
+
* @param {string} lens - Lens to aggregate
|
|
32
|
+
* @param {Object} options - Options
|
|
33
|
+
* @returns {Promise<Object>} Aggregation result
|
|
34
|
+
*/
|
|
35
|
+
async smartUpcast(holon, lens, options = {}) {
|
|
36
|
+
if (!this.holosphere) {
|
|
37
|
+
throw new Error('HoloSphere instance required');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { maxLevels = 5, storeResults = true } = options;
|
|
41
|
+
|
|
42
|
+
// Get current data
|
|
43
|
+
const data = await this.holosphere.getAll(holon, lens);
|
|
44
|
+
|
|
45
|
+
// Generate summary for current level
|
|
46
|
+
const summary = await this._generateSummary(data, holon, lens);
|
|
47
|
+
|
|
48
|
+
// Get parent holon
|
|
49
|
+
const resolution = h3.getResolution(holon);
|
|
50
|
+
if (resolution <= 0 || maxLevels <= 0) {
|
|
51
|
+
return { holon, lens, summary, level: resolution };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const parent = h3.cellToParent(holon, resolution - 1);
|
|
55
|
+
|
|
56
|
+
// Store summary at parent level
|
|
57
|
+
if (storeResults) {
|
|
58
|
+
await this.holosphere.put(parent, `${lens}_summaries`, {
|
|
59
|
+
id: holon,
|
|
60
|
+
childHolon: holon,
|
|
61
|
+
summary,
|
|
62
|
+
dataCount: data.length,
|
|
63
|
+
timestamp: Date.now(),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Recursively upcast to parent
|
|
68
|
+
const parentResult = await this.smartUpcast(parent, lens, {
|
|
69
|
+
...options,
|
|
70
|
+
maxLevels: maxLevels - 1,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
holon,
|
|
75
|
+
lens,
|
|
76
|
+
summary,
|
|
77
|
+
dataCount: data.length,
|
|
78
|
+
parent: parentResult,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Generate AI summary for data
|
|
84
|
+
* @private
|
|
85
|
+
*/
|
|
86
|
+
async _generateSummary(data, holon, lens) {
|
|
87
|
+
if (data.length === 0) {
|
|
88
|
+
return { text: 'No data in this region', stats: { count: 0 } };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const systemPrompt = `You are a data summarization expert. Create a concise summary of this geographic data.
|
|
92
|
+
|
|
93
|
+
Region: ${holon}
|
|
94
|
+
Category: ${lens}
|
|
95
|
+
Items: ${data.length}
|
|
96
|
+
|
|
97
|
+
Generate:
|
|
98
|
+
1. A brief text summary (2-3 sentences)
|
|
99
|
+
2. Key statistics
|
|
100
|
+
3. Notable items
|
|
101
|
+
4. Themes/patterns
|
|
102
|
+
|
|
103
|
+
Return JSON:
|
|
104
|
+
{
|
|
105
|
+
"text": "summary text",
|
|
106
|
+
"stats": {"count": n, "key_metric": value},
|
|
107
|
+
"notable": ["item1", "item2"],
|
|
108
|
+
"themes": ["theme1", "theme2"]
|
|
109
|
+
}`;
|
|
110
|
+
|
|
111
|
+
return this.llm.getJSON(
|
|
112
|
+
systemPrompt,
|
|
113
|
+
JSON.stringify(data.slice(0, 30), null, 2),
|
|
114
|
+
{ temperature: 0.3 }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Generate comprehensive holon summary
|
|
120
|
+
* @param {string} holon - Holon identifier
|
|
121
|
+
* @returns {Promise<Object>} Comprehensive summary
|
|
122
|
+
*/
|
|
123
|
+
async generateHolonSummary(holon) {
|
|
124
|
+
if (!this.holosphere) {
|
|
125
|
+
throw new Error('HoloSphere instance required');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Get all available lenses (would need to discover)
|
|
129
|
+
const lenses = ['projects', 'quests', 'events', 'resources', 'default'];
|
|
130
|
+
const allData = {};
|
|
131
|
+
|
|
132
|
+
for (const lens of lenses) {
|
|
133
|
+
try {
|
|
134
|
+
const data = await this.holosphere.getAll(holon, lens);
|
|
135
|
+
if (data.length > 0) {
|
|
136
|
+
allData[lens] = data;
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// Skip unavailable lenses
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const systemPrompt = `You are a regional analyst. Generate a comprehensive summary of this geographic area.
|
|
144
|
+
|
|
145
|
+
Region: ${holon}
|
|
146
|
+
Data available: ${Object.entries(allData).map(([l, d]) => `${l}: ${d.length}`).join(', ')}
|
|
147
|
+
|
|
148
|
+
Generate a complete overview including:
|
|
149
|
+
1. Executive summary
|
|
150
|
+
2. Key activities and projects
|
|
151
|
+
3. Community engagement
|
|
152
|
+
4. Resources and assets
|
|
153
|
+
5. Challenges and opportunities
|
|
154
|
+
6. Overall health assessment
|
|
155
|
+
|
|
156
|
+
Return JSON:
|
|
157
|
+
{
|
|
158
|
+
"title": "Region Title",
|
|
159
|
+
"executive_summary": "brief overview",
|
|
160
|
+
"highlights": ["highlight1", "highlight2"],
|
|
161
|
+
"activities": {"count": n, "summary": "text"},
|
|
162
|
+
"community": {"engagement_level": "high|medium|low", "notes": "text"},
|
|
163
|
+
"resources": ["resource1"],
|
|
164
|
+
"challenges": ["challenge1"],
|
|
165
|
+
"opportunities": ["opp1"],
|
|
166
|
+
"health_score": 0.0-1.0,
|
|
167
|
+
"recommendations": ["rec1"]
|
|
168
|
+
}`;
|
|
169
|
+
|
|
170
|
+
const dataSample = {};
|
|
171
|
+
for (const [lens, data] of Object.entries(allData)) {
|
|
172
|
+
dataSample[lens] = data.slice(0, 15);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const summary = await this.llm.getJSON(
|
|
176
|
+
systemPrompt,
|
|
177
|
+
JSON.stringify(dataSample, null, 2),
|
|
178
|
+
{ temperature: 0.4, maxTokens: 2000 }
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
holon,
|
|
183
|
+
summary,
|
|
184
|
+
lensesAnalyzed: Object.keys(allData),
|
|
185
|
+
totalItems: Object.values(allData).reduce((sum, d) => sum + d.length, 0),
|
|
186
|
+
timestamp: Date.now(),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Aggregate child summaries into parent
|
|
192
|
+
* @param {string} parentHolon - Parent holon
|
|
193
|
+
* @param {string} lens - Lens
|
|
194
|
+
* @returns {Promise<Object>} Aggregated summary
|
|
195
|
+
*/
|
|
196
|
+
async aggregateChildren(parentHolon, lens) {
|
|
197
|
+
if (!this.holosphere) {
|
|
198
|
+
throw new Error('HoloSphere instance required');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Get children holons
|
|
202
|
+
const resolution = h3.getResolution(parentHolon);
|
|
203
|
+
const children = h3.cellToChildren(parentHolon, resolution + 1);
|
|
204
|
+
|
|
205
|
+
// Collect summaries from children
|
|
206
|
+
const childSummaries = [];
|
|
207
|
+
for (const child of children.slice(0, 7)) { // Limit to 7 children for token limits
|
|
208
|
+
try {
|
|
209
|
+
const data = await this.holosphere.getAll(child, lens);
|
|
210
|
+
if (data.length > 0) {
|
|
211
|
+
const summary = await this._generateSummary(data, child, lens);
|
|
212
|
+
childSummaries.push({ holon: child, summary });
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
// Skip
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (childSummaries.length === 0) {
|
|
220
|
+
return { holon: parentHolon, summary: null, message: 'No child data found' };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Aggregate child summaries
|
|
224
|
+
const systemPrompt = `You are aggregating summaries from child regions into a parent region summary.
|
|
225
|
+
|
|
226
|
+
Parent region: ${parentHolon}
|
|
227
|
+
Child summaries: ${childSummaries.length}
|
|
228
|
+
|
|
229
|
+
Create an aggregated summary that:
|
|
230
|
+
1. Synthesizes common themes
|
|
231
|
+
2. Highlights regional diversity
|
|
232
|
+
3. Identifies cross-cutting patterns
|
|
233
|
+
4. Notes outliers
|
|
234
|
+
|
|
235
|
+
Return JSON:
|
|
236
|
+
{
|
|
237
|
+
"aggregated_summary": "text",
|
|
238
|
+
"common_themes": ["theme1"],
|
|
239
|
+
"regional_diversity": ["diff1"],
|
|
240
|
+
"patterns": ["pattern1"],
|
|
241
|
+
"total_activity": {"count": n, "trend": "growing|stable|declining"}
|
|
242
|
+
}`;
|
|
243
|
+
|
|
244
|
+
const summary = await this.llm.getJSON(
|
|
245
|
+
systemPrompt,
|
|
246
|
+
JSON.stringify(childSummaries, null, 2),
|
|
247
|
+
{ temperature: 0.3 }
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
holon: parentHolon,
|
|
252
|
+
lens,
|
|
253
|
+
childCount: childSummaries.length,
|
|
254
|
+
summary,
|
|
255
|
+
timestamp: Date.now(),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Compare aggregations across different time periods
|
|
261
|
+
* @param {string} holon - Holon
|
|
262
|
+
* @param {string} lens - Lens
|
|
263
|
+
* @param {Object} period1 - First period {start, end}
|
|
264
|
+
* @param {Object} period2 - Second period {start, end}
|
|
265
|
+
* @returns {Promise<Object>} Comparison
|
|
266
|
+
*/
|
|
267
|
+
async comparePeriods(holon, lens, period1, period2) {
|
|
268
|
+
if (!this.holosphere) {
|
|
269
|
+
throw new Error('HoloSphere instance required');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const allData = await this.holosphere.getAll(holon, lens);
|
|
273
|
+
|
|
274
|
+
const filterByPeriod = (data, period) => {
|
|
275
|
+
return data.filter(item => {
|
|
276
|
+
const ts = item.timestamp || item.created_at;
|
|
277
|
+
if (!ts) return false;
|
|
278
|
+
const itemTime = new Date(ts).getTime();
|
|
279
|
+
return itemTime >= new Date(period.start).getTime() &&
|
|
280
|
+
itemTime <= new Date(period.end).getTime();
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const data1 = filterByPeriod(allData, period1);
|
|
285
|
+
const data2 = filterByPeriod(allData, period2);
|
|
286
|
+
|
|
287
|
+
const systemPrompt = `Compare data between two time periods for a geographic region.
|
|
288
|
+
|
|
289
|
+
Region: ${holon}
|
|
290
|
+
Period 1: ${JSON.stringify(period1)} - ${data1.length} items
|
|
291
|
+
Period 2: ${JSON.stringify(period2)} - ${data2.length} items
|
|
292
|
+
|
|
293
|
+
Analyze:
|
|
294
|
+
1. Growth/decline
|
|
295
|
+
2. New themes in period 2
|
|
296
|
+
3. Themes that disappeared
|
|
297
|
+
4. Key changes
|
|
298
|
+
|
|
299
|
+
Return JSON:
|
|
300
|
+
{
|
|
301
|
+
"growth_rate": percentage,
|
|
302
|
+
"direction": "growth|decline|stable",
|
|
303
|
+
"new_themes": ["theme1"],
|
|
304
|
+
"disappeared": ["theme1"],
|
|
305
|
+
"key_changes": ["change1"],
|
|
306
|
+
"summary": "text"
|
|
307
|
+
}`;
|
|
308
|
+
|
|
309
|
+
const userMessage = `Period 1 data:
|
|
310
|
+
${JSON.stringify(data1.slice(0, 20), null, 2)}
|
|
311
|
+
|
|
312
|
+
Period 2 data:
|
|
313
|
+
${JSON.stringify(data2.slice(0, 20), null, 2)}`;
|
|
314
|
+
|
|
315
|
+
return this.llm.getJSON(systemPrompt, userMessage, { temperature: 0.3 });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export default SmartAggregation;
|