holosphere 1.1.4 → 1.1.5
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/holosphere.js +69 -106
- package/package.json +8 -3
- package/services/environmentalApi.js +253 -0
- package/services/environmentalApi.test.js +164 -0
- package/test/holosphere.test.js +27 -61
- package/test/jest.setup.js +5 -0
package/holosphere.js
CHANGED
|
@@ -14,6 +14,7 @@ class HoloSphere {
|
|
|
14
14
|
* @param {Gun|null} gunInstance - The Gun instance to use.
|
|
15
15
|
*/
|
|
16
16
|
constructor(appname, strict = false, openaikey = null, gunInstance = null) {
|
|
17
|
+
console.log('HoloSphere v1.1.8');
|
|
17
18
|
this.appname = appname
|
|
18
19
|
this.strict = strict;
|
|
19
20
|
this.validator = new Ajv2019({
|
|
@@ -24,7 +25,7 @@ class HoloSphere {
|
|
|
24
25
|
|
|
25
26
|
// Use provided Gun instance or create new one
|
|
26
27
|
this.gun = gunInstance || Gun({
|
|
27
|
-
peers: ['https://gun.holons.io/gun'
|
|
28
|
+
peers: ['https://gun.holons.io/gun'],
|
|
28
29
|
axe: false,
|
|
29
30
|
// uuid: (content) => { // generate a unique id for each node
|
|
30
31
|
// console.log('uuid', content);
|
|
@@ -65,68 +66,50 @@ class HoloSphere {
|
|
|
65
66
|
throw new Error('setSchema: Schema must have a type field');
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const metaSchema = {
|
|
70
|
+
type: 'object',
|
|
71
|
+
required: ['type', 'properties'],
|
|
72
|
+
properties: {
|
|
73
|
+
type: { type: 'string' },
|
|
72
74
|
properties: {
|
|
73
|
-
type:
|
|
74
|
-
|
|
75
|
+
type: 'object',
|
|
76
|
+
additionalProperties: {
|
|
75
77
|
type: 'object',
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
properties: {
|
|
80
|
-
type: { type: 'string' }
|
|
81
|
-
}
|
|
78
|
+
required: ['type'],
|
|
79
|
+
properties: {
|
|
80
|
+
type: { type: 'string' }
|
|
82
81
|
}
|
|
83
|
-
},
|
|
84
|
-
required: {
|
|
85
|
-
type: 'array',
|
|
86
|
-
items: { type: 'string' }
|
|
87
82
|
}
|
|
83
|
+
},
|
|
84
|
+
required: {
|
|
85
|
+
type: 'array',
|
|
86
|
+
items: { type: 'string' }
|
|
88
87
|
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const valid = this.validator.validate(metaSchema, schema);
|
|
92
|
-
if (!valid) {
|
|
93
|
-
throw new Error(`Invalid schema structure: ${JSON.stringify(this.validator.errors)}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!schema.properties || typeof schema.properties !== 'object') {
|
|
97
|
-
throw new Error('Schema must have properties in strict mode');
|
|
98
88
|
}
|
|
89
|
+
};
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
91
|
+
const valid = this.validator.validate(metaSchema, schema);
|
|
92
|
+
if (!valid) {
|
|
93
|
+
throw new Error(`Invalid schema structure: ${JSON.stringify(this.validator.errors)}`);
|
|
103
94
|
}
|
|
104
95
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const schemaData = {
|
|
109
|
-
schema: schemaString,
|
|
110
|
-
timestamp: Date.now(),
|
|
111
|
-
// Only set owner if there's an authenticated space
|
|
112
|
-
...(this.currentSpace && { owner: this.currentSpace.alias })
|
|
113
|
-
};
|
|
96
|
+
if (!schema.properties || typeof schema.properties !== 'object') {
|
|
97
|
+
throw new Error('Schema must have properties in strict mode');
|
|
98
|
+
}
|
|
114
99
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
});
|
|
126
|
-
} catch (error) {
|
|
127
|
-
reject(error);
|
|
128
|
-
}
|
|
100
|
+
if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
|
|
101
|
+
throw new Error('Schema must have required fields in strict mode');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Store schema in global table with lens as key
|
|
105
|
+
await this.putGlobal('schemas', {
|
|
106
|
+
id: lens,
|
|
107
|
+
schema: schema,
|
|
108
|
+
timestamp: Date.now(),
|
|
109
|
+
owner: this.currentSpace?.alias
|
|
129
110
|
});
|
|
111
|
+
|
|
112
|
+
return true;
|
|
130
113
|
}
|
|
131
114
|
|
|
132
115
|
/**
|
|
@@ -139,39 +122,12 @@ class HoloSphere {
|
|
|
139
122
|
throw new Error('getSchema: Missing lens parameter');
|
|
140
123
|
}
|
|
141
124
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}, 5000);
|
|
147
|
-
|
|
148
|
-
this.gun.get(this.appname)
|
|
149
|
-
.get(lens)
|
|
150
|
-
.get('schema')
|
|
151
|
-
.once(data => {
|
|
152
|
-
clearTimeout(timeout);
|
|
153
|
-
if (!data) {
|
|
154
|
-
resolve(null);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
125
|
+
const schemaData = await this.getGlobal('schemas', lens);
|
|
126
|
+
if (!schemaData || !schemaData.schema) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
157
129
|
|
|
158
|
-
|
|
159
|
-
// Handle both new format and legacy format
|
|
160
|
-
if (data.schema) {
|
|
161
|
-
// New format with timestamp
|
|
162
|
-
resolve(JSON.parse(data.schema));
|
|
163
|
-
} else {
|
|
164
|
-
// Legacy format or direct string
|
|
165
|
-
const schemaStr = typeof data === 'string' ? data :
|
|
166
|
-
Object.values(data).find(v => typeof v === 'string' && v.includes('"type":'));
|
|
167
|
-
resolve(schemaStr ? JSON.parse(schemaStr) : null);
|
|
168
|
-
}
|
|
169
|
-
} catch (error) {
|
|
170
|
-
console.error('getSchema: Error parsing schema:', error);
|
|
171
|
-
resolve(null);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
});
|
|
130
|
+
return schemaData.schema;
|
|
175
131
|
}
|
|
176
132
|
|
|
177
133
|
// ================================ CONTENT FUNCTIONS ================================
|
|
@@ -567,11 +523,19 @@ class HoloSphere {
|
|
|
567
523
|
* @returns {Promise<object>} - The parsed data.
|
|
568
524
|
*/
|
|
569
525
|
async parse(rawData) {
|
|
526
|
+
let parsedData = {};
|
|
527
|
+
|
|
570
528
|
if (!rawData) {
|
|
571
529
|
throw new Error('parse: No data provided');
|
|
572
530
|
}
|
|
573
531
|
|
|
574
532
|
try {
|
|
533
|
+
|
|
534
|
+
if (typeof rawData === 'string') {
|
|
535
|
+
parsedData = await JSON.parse(rawData);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
|
|
575
539
|
if (rawData.soul) {
|
|
576
540
|
const data = await this.getNodeRef(rawData.soul).once();
|
|
577
541
|
if (!data) {
|
|
@@ -580,7 +544,7 @@ class HoloSphere {
|
|
|
580
544
|
return JSON.parse(data);
|
|
581
545
|
}
|
|
582
546
|
|
|
583
|
-
|
|
547
|
+
|
|
584
548
|
if (typeof rawData === 'object' && rawData !== null) {
|
|
585
549
|
if (rawData._ && rawData._["#"]) {
|
|
586
550
|
const pathParts = rawData._['#'].split('/');
|
|
@@ -600,11 +564,10 @@ class HoloSphere {
|
|
|
600
564
|
} else {
|
|
601
565
|
parsedData = rawData;
|
|
602
566
|
}
|
|
603
|
-
} else {
|
|
604
|
-
parsedData = JSON.parse(rawData);
|
|
605
567
|
}
|
|
606
568
|
|
|
607
569
|
return parsedData;
|
|
570
|
+
|
|
608
571
|
} catch (error) {
|
|
609
572
|
console.log("Parsing not a JSON, returning raw data", rawData);
|
|
610
573
|
return rawData;
|
|
@@ -1423,23 +1386,27 @@ class HoloSphere {
|
|
|
1423
1386
|
* @param {function} callback - The callback to execute on changes.
|
|
1424
1387
|
*/
|
|
1425
1388
|
async subscribe(holon, lens, callback) {
|
|
1426
|
-
const subscriptionId = this.
|
|
1427
|
-
this.subscriptions[subscriptionId] =
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1389
|
+
const subscriptionId = this.generateId();
|
|
1390
|
+
this.subscriptions[subscriptionId] =
|
|
1391
|
+
this.gun.get(this.appname).get(holon).get(lens).map().on( async (data, key) => {
|
|
1392
|
+
if (data) {
|
|
1393
|
+
try {
|
|
1394
|
+
let parsed = await this.parse(data)
|
|
1395
|
+
callback(parsed, key)
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
console.error('Error in subscribe:', error);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
})
|
|
1434
1401
|
return {
|
|
1435
1402
|
unsubscribe: () => {
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
}
|
|
1403
|
+
this.gun.get(this.appname).get(holon).get(lens).map().off()
|
|
1404
|
+
delete this.subscriptions[subscriptionId];
|
|
1439
1405
|
}
|
|
1440
|
-
}
|
|
1406
|
+
}
|
|
1441
1407
|
}
|
|
1442
1408
|
|
|
1409
|
+
|
|
1443
1410
|
notifySubscribers(data) {
|
|
1444
1411
|
Object.values(this.subscriptions).forEach(subscription => {
|
|
1445
1412
|
if (subscription.active && this.matchesQuery(data, subscription.query)) {
|
|
@@ -1453,14 +1420,10 @@ class HoloSphere {
|
|
|
1453
1420
|
return Date.now().toString(10) + Math.random().toString(2);
|
|
1454
1421
|
}
|
|
1455
1422
|
|
|
1456
|
-
generateSubscriptionId() {
|
|
1457
|
-
return Date.now().toString(10) + Math.random().toString(2);
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
1423
|
matchesQuery(data, query) {
|
|
1461
|
-
return data && query &&
|
|
1462
|
-
|
|
1463
|
-
|
|
1424
|
+
return data && query &&
|
|
1425
|
+
data.holon === query.holon &&
|
|
1426
|
+
data.lens === query.lens;
|
|
1464
1427
|
}
|
|
1465
1428
|
|
|
1466
1429
|
/**
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "holosphere",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Holonic Geospatial Communication Infrastructure",
|
|
5
5
|
"main": "holosphere.js",
|
|
6
6
|
"types": "holosphere.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
9
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js ./test",
|
|
10
10
|
"build": "",
|
|
11
11
|
"prepare": "npm run build"
|
|
12
12
|
},
|
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
"license": "GPL-3.0-or-later",
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"ajv": "^8.12.0",
|
|
17
|
+
"axios": "^1.6.7",
|
|
17
18
|
"dotenv": "^16.4.7",
|
|
18
19
|
"gun": "^0.2020.1240",
|
|
19
20
|
"h3-js": "^4.1.0",
|
|
20
21
|
"openai": "^4.85.1"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
24
|
+
"@babel/preset-env": "^7.24.0",
|
|
23
25
|
"jest": "^29.7.0",
|
|
24
26
|
"jest-environment-node": "^29.7.0"
|
|
25
27
|
},
|
|
@@ -28,6 +30,9 @@
|
|
|
28
30
|
"transform": {},
|
|
29
31
|
"moduleNameMapper": {
|
|
30
32
|
"^(\\.{1,2}/.*)\\.js$": "$1"
|
|
31
|
-
}
|
|
33
|
+
},
|
|
34
|
+
"setupFilesAfterEnv": [
|
|
35
|
+
"<rootDir>/test/jest.setup.js"
|
|
36
|
+
]
|
|
32
37
|
}
|
|
33
38
|
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
|
|
6
|
+
const logError = (service, error) => {
|
|
7
|
+
const status = error.response?.status;
|
|
8
|
+
const message = error.response?.data?.message || error.message;
|
|
9
|
+
console.error(`${service} API Error:`, { status, message });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Fetch Carbon Sequestration Data (Using NASA POWER API)
|
|
14
|
+
*/
|
|
15
|
+
export async function getCarbonSequestration(lat = 41.9, lon = 12.5) {
|
|
16
|
+
try {
|
|
17
|
+
const response = await axios.default.get(
|
|
18
|
+
`https://power.larc.nasa.gov/api/temporal/daily/point?parameters=T2M,PRECTOT,PS,WS2M&community=RE&longitude=${lon}&latitude=${lat}&start=20230101&end=20231231&format=JSON`
|
|
19
|
+
);
|
|
20
|
+
return response.data;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
logError('Carbon Sequestration', error);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Fetch Soil Carbon Data (Using ISRIC World Soil Information)
|
|
29
|
+
*/
|
|
30
|
+
export async function getSoilCarbon(lat = 41.9, lon = 12.5) {
|
|
31
|
+
try {
|
|
32
|
+
const response = await axios.default.get(
|
|
33
|
+
`https://rest.isric.org/soilgrids/v2.0/properties/query?lat=${lat}&lon=${lon}&property=soc&depth=0-30cm&value=mean`
|
|
34
|
+
);
|
|
35
|
+
return response.data;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
logError('Soil Carbon', error);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Fetch Biodiversity Data (GBIF API)
|
|
44
|
+
*/
|
|
45
|
+
export async function getBiodiversityData(countryCode = "ITA") {
|
|
46
|
+
try {
|
|
47
|
+
const response = await axios.default.get(`https://api.gbif.org/v1/occurrence/search?country=${countryCode}&limit=100`);
|
|
48
|
+
return response.data;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
logError('Biodiversity', error);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Fetch Vegetation Cover Data (Using NASA MODIS Vegetation Index)
|
|
57
|
+
*/
|
|
58
|
+
export async function getVegetationCover(lat = 41.9, lon = 12.5) {
|
|
59
|
+
try {
|
|
60
|
+
const response = await axios.default.get(
|
|
61
|
+
`https://modis.ornl.gov/rst/api/v1/MOD13Q1/subset?latitude=${lat}&longitude=${lon}&band=NDVI&startDate=2023-01-01&endDate=2023-12-31`,
|
|
62
|
+
{
|
|
63
|
+
headers: {
|
|
64
|
+
'Accept': 'application/json'
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
return response.data;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logError('Vegetation Cover', error);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fetch Air Quality Data (OpenWeather API)
|
|
77
|
+
*/
|
|
78
|
+
export async function getAirQuality(lat = 41.9, lon = 12.5) {
|
|
79
|
+
try {
|
|
80
|
+
const response = await axios.default.get(
|
|
81
|
+
`https://api.openweathermap.org/data/2.5/air_pollution?lat=${lat}&lon=${lon}&appid=${process.env.OPENWEATHER_API_KEY}`
|
|
82
|
+
);
|
|
83
|
+
return response.data;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
logError('Air Quality', error);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Fetch Water Retention Data (Using USGS Water Services)
|
|
92
|
+
*/
|
|
93
|
+
export async function getWaterRetention(lat = 41.9, lon = 12.5) {
|
|
94
|
+
try {
|
|
95
|
+
const response = await axios.default.get(
|
|
96
|
+
`https://waterservices.usgs.gov/nwis/iv/?format=json&sites=11447650&siteStatus=active`,
|
|
97
|
+
{
|
|
98
|
+
headers: {
|
|
99
|
+
'Accept': 'application/json'
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
return response.data;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logError('Water Retention', error);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Fetch Deforestation Data (Using World Bank Forest Data)
|
|
112
|
+
*/
|
|
113
|
+
export async function getDeforestationData(countryCode = "ITA") {
|
|
114
|
+
try {
|
|
115
|
+
const response = await axios.default.get(
|
|
116
|
+
`https://api.worldbank.org/v2/country/${countryCode}/indicator/AG.LND.FRST.ZS?format=json`
|
|
117
|
+
);
|
|
118
|
+
return response.data;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
logError('Deforestation', error);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Fetch Flood Risk Data (Using USGS Water Services)
|
|
127
|
+
*/
|
|
128
|
+
export async function getFloodRisk(lat = 41.9, lon = 12.5) {
|
|
129
|
+
try {
|
|
130
|
+
const response = await axios.default.get(
|
|
131
|
+
`https://waterservices.usgs.gov/nwis/iv/?format=json&stateCd=CA¶meterCd=00065&siteStatus=active`,
|
|
132
|
+
{
|
|
133
|
+
headers: {
|
|
134
|
+
'Accept': 'application/json'
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
return response.data;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
logError('Flood Risk', error);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Fetch Food Security Data (World Bank API)
|
|
147
|
+
*/
|
|
148
|
+
export async function getFoodSecurity(countryCode = "ITA") {
|
|
149
|
+
try {
|
|
150
|
+
const response = await axios.default.get(
|
|
151
|
+
`https://api.worldbank.org/v2/country/${countryCode}/indicator/SN.ITK.DEFC.ZS?format=json`
|
|
152
|
+
);
|
|
153
|
+
return response.data;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
logError('Food Security', error);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Fetch Local Employment Rate (World Bank API)
|
|
162
|
+
*/
|
|
163
|
+
export async function getEmploymentRate(countryCode = "ITA") {
|
|
164
|
+
try {
|
|
165
|
+
const response = await axios.default.get(
|
|
166
|
+
`https://api.worldbank.org/v2/country/${countryCode}/indicator/SL.EMP.TOTL.SP.ZS?format=json`
|
|
167
|
+
);
|
|
168
|
+
return response.data;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logError('Employment Rate', error);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Fetch Governance Score (World Bank API)
|
|
177
|
+
*/
|
|
178
|
+
export async function getTransparencyScore(countryCode = "ITA") {
|
|
179
|
+
try {
|
|
180
|
+
const response = await axios.default.get(
|
|
181
|
+
`https://api.worldbank.org/v2/country/${countryCode}/indicator/GE.EST?format=json`
|
|
182
|
+
);
|
|
183
|
+
return response.data;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
logError('Transparency Score', error);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Fetch Blockchain Transactions (Etherscan API)
|
|
192
|
+
*/
|
|
193
|
+
export async function getBlockchainTransactions(address = "0x0000000000000000000000000000000000000000") {
|
|
194
|
+
try {
|
|
195
|
+
const response = await axios.default.get(
|
|
196
|
+
`https://api.etherscan.io/api?module=account&action=txlist&address=${address}&startblock=0&endblock=99999999&sort=desc&apikey=${process.env.ETHERSCAN_API_KEY}`
|
|
197
|
+
);
|
|
198
|
+
return response.data;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logError('Blockchain Transactions', error);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Fetch Circular Economy Data (Using World Bank Development Indicators)
|
|
207
|
+
*/
|
|
208
|
+
export async function getCircularEconomyData(countryCode = "ITA") {
|
|
209
|
+
try {
|
|
210
|
+
const response = await axios.default.get(
|
|
211
|
+
`https://api.worldbank.org/v2/country/${countryCode}/indicator/EN.ATM.GHGT.KT.CE?format=json`
|
|
212
|
+
);
|
|
213
|
+
return response.data;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
logError('Circular Economy', error);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Fetch Renewable Energy Data (World Bank API)
|
|
222
|
+
*/
|
|
223
|
+
export async function getRenewableEnergyData(countryCode = "ITA") {
|
|
224
|
+
try {
|
|
225
|
+
const response = await axios.default.get(
|
|
226
|
+
`https://api.worldbank.org/v2/country/${countryCode}/indicator/EG.FEC.RNEW.ZS?format=json`
|
|
227
|
+
);
|
|
228
|
+
return response.data;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
logError('Renewable Energy', error);
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Fetch Climate Change Data (NOAA Climate API)
|
|
237
|
+
*/
|
|
238
|
+
export async function getClimateChangeData(lat = 41.9, lon = 12.5) {
|
|
239
|
+
try {
|
|
240
|
+
const response = await axios.default.get(
|
|
241
|
+
`https://www.ncdc.noaa.gov/cdo-web/api/v2/data?datasetid=GHCND&latitude=${lat}&longitude=${lon}&startdate=2023-01-01&enddate=2023-12-31&limit=1000`,
|
|
242
|
+
{
|
|
243
|
+
headers: {
|
|
244
|
+
'token': process.env.NOAA_API_KEY
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
return response.data;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
logError('Climate Change', error);
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
// Mock axios before importing the modules that use it
|
|
4
|
+
jest.mock('axios', () => ({
|
|
5
|
+
default: {
|
|
6
|
+
get: jest.fn()
|
|
7
|
+
}
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
// Import axios after mocking
|
|
11
|
+
import axios from 'axios';
|
|
12
|
+
|
|
13
|
+
// Import the API functions
|
|
14
|
+
import {
|
|
15
|
+
getCarbonSequestration,
|
|
16
|
+
getSoilCarbon,
|
|
17
|
+
getBiodiversityData,
|
|
18
|
+
getVegetationCover,
|
|
19
|
+
getAirQuality,
|
|
20
|
+
getWaterRetention,
|
|
21
|
+
getDeforestationData,
|
|
22
|
+
getFloodRisk,
|
|
23
|
+
getFoodSecurity,
|
|
24
|
+
getEmploymentRate,
|
|
25
|
+
getTransparencyScore,
|
|
26
|
+
getBlockchainTransactions,
|
|
27
|
+
getCircularEconomyData,
|
|
28
|
+
getRenewableEnergyData,
|
|
29
|
+
getClimateChangeData
|
|
30
|
+
} from './environmentalApi.js';
|
|
31
|
+
|
|
32
|
+
describe('Environmental API Tests', () => {
|
|
33
|
+
// Increase timeout for real API calls
|
|
34
|
+
jest.setTimeout(60000);
|
|
35
|
+
|
|
36
|
+
const ROME_LAT = 41.9;
|
|
37
|
+
const ROME_LON = 12.5;
|
|
38
|
+
const COUNTRY_CODE = 'ITA';
|
|
39
|
+
const ETH_ADDRESS = '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe';
|
|
40
|
+
|
|
41
|
+
test('getCarbonSequestration returns NASA POWER data', async () => {
|
|
42
|
+
const result = await getCarbonSequestration(ROME_LAT, ROME_LON);
|
|
43
|
+
expect(result).not.toBeNull();
|
|
44
|
+
expect(result.messages).toBeDefined();
|
|
45
|
+
expect(result.parameters).toBeDefined();
|
|
46
|
+
expect(result.parameters).toHaveProperty('T2M');
|
|
47
|
+
expect(result.parameters).toHaveProperty('PRECTOT');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('getSoilCarbon returns ISRIC soil data', async () => {
|
|
51
|
+
const result = await getSoilCarbon(ROME_LAT, ROME_LON);
|
|
52
|
+
expect(result).not.toBeNull();
|
|
53
|
+
expect(result.properties).toBeDefined();
|
|
54
|
+
expect(result.properties.soc).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('getBiodiversityData returns GBIF data', async () => {
|
|
58
|
+
const result = await getBiodiversityData(COUNTRY_CODE);
|
|
59
|
+
expect(result).not.toBeNull();
|
|
60
|
+
expect(result.count).toBeDefined();
|
|
61
|
+
expect(result.results).toBeDefined();
|
|
62
|
+
expect(Array.isArray(result.results)).toBeTruthy();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('getVegetationCover returns MODIS NDVI data', async () => {
|
|
66
|
+
const result = await getVegetationCover(ROME_LAT, ROME_LON);
|
|
67
|
+
expect(result).not.toBeNull();
|
|
68
|
+
expect(result.subset || result.data).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('getAirQuality returns OpenWeather data', async () => {
|
|
72
|
+
const result = await getAirQuality(ROME_LAT, ROME_LON);
|
|
73
|
+
expect(result).not.toBeNull();
|
|
74
|
+
expect(result.list).toBeDefined();
|
|
75
|
+
expect(result.list[0].main).toBeDefined();
|
|
76
|
+
expect(result.list[0].components).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('getWaterRetention returns USGS data', async () => {
|
|
80
|
+
const result = await getWaterRetention(ROME_LAT, ROME_LON);
|
|
81
|
+
expect(result).not.toBeNull();
|
|
82
|
+
expect(result.value || result.timeSeries).toBeDefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('getDeforestationData returns World Bank forest data', async () => {
|
|
86
|
+
const result = await getDeforestationData(COUNTRY_CODE);
|
|
87
|
+
expect(result).not.toBeNull();
|
|
88
|
+
expect(Array.isArray(result)).toBeTruthy();
|
|
89
|
+
if (result.length > 1) {
|
|
90
|
+
expect(result[1][0]).toHaveProperty('indicator');
|
|
91
|
+
expect(result[1][0].indicator.id).toBe('AG.LND.FRST.ZS');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('getFloodRisk returns USGS gauge data', async () => {
|
|
96
|
+
const result = await getFloodRisk(ROME_LAT, ROME_LON);
|
|
97
|
+
expect(result).not.toBeNull();
|
|
98
|
+
expect(result.value || result.timeSeries).toBeDefined();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('getFoodSecurity returns World Bank data', async () => {
|
|
102
|
+
const result = await getFoodSecurity(COUNTRY_CODE);
|
|
103
|
+
expect(result).not.toBeNull();
|
|
104
|
+
expect(Array.isArray(result)).toBeTruthy();
|
|
105
|
+
if (result.length > 1) {
|
|
106
|
+
expect(result[1][0]).toHaveProperty('indicator');
|
|
107
|
+
expect(result[1][0].indicator.id).toBe('SN.ITK.DEFC.ZS');
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('getEmploymentRate returns World Bank data', async () => {
|
|
112
|
+
const result = await getEmploymentRate(COUNTRY_CODE);
|
|
113
|
+
expect(result).not.toBeNull();
|
|
114
|
+
expect(Array.isArray(result)).toBeTruthy();
|
|
115
|
+
if (result.length > 1) {
|
|
116
|
+
expect(result[1][0]).toHaveProperty('indicator');
|
|
117
|
+
expect(result[1][0].indicator.id).toBe('SL.EMP.TOTL.SP.ZS');
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('getTransparencyScore returns World Bank governance data', async () => {
|
|
122
|
+
const result = await getTransparencyScore(COUNTRY_CODE);
|
|
123
|
+
expect(result).not.toBeNull();
|
|
124
|
+
expect(Array.isArray(result)).toBeTruthy();
|
|
125
|
+
if (result.length > 1) {
|
|
126
|
+
expect(result[1][0]).toHaveProperty('indicator');
|
|
127
|
+
expect(result[1][0].indicator.id).toBe('GE.EST');
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('getBlockchainTransactions returns Etherscan data', async () => {
|
|
132
|
+
const result = await getBlockchainTransactions(ETH_ADDRESS);
|
|
133
|
+
expect(result).not.toBeNull();
|
|
134
|
+
expect(result.status).toBeDefined();
|
|
135
|
+
expect(result.result).toBeDefined();
|
|
136
|
+
expect(Array.isArray(result.result)).toBeTruthy();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('getCircularEconomyData returns World Bank emissions data', async () => {
|
|
140
|
+
const result = await getCircularEconomyData(COUNTRY_CODE);
|
|
141
|
+
expect(result).not.toBeNull();
|
|
142
|
+
expect(Array.isArray(result)).toBeTruthy();
|
|
143
|
+
if (result.length > 1) {
|
|
144
|
+
expect(result[1][0]).toHaveProperty('indicator');
|
|
145
|
+
expect(result[1][0].indicator.id).toBe('EN.ATM.GHGT.KT.CE');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('getRenewableEnergyData returns World Bank energy data', async () => {
|
|
150
|
+
const result = await getRenewableEnergyData(COUNTRY_CODE);
|
|
151
|
+
expect(result).not.toBeNull();
|
|
152
|
+
expect(Array.isArray(result)).toBeTruthy();
|
|
153
|
+
if (result.length > 1) {
|
|
154
|
+
expect(result[1][0]).toHaveProperty('indicator');
|
|
155
|
+
expect(result[1][0].indicator.id).toBe('EG.FEC.RNEW.ZS');
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('getClimateChangeData returns NOAA data', async () => {
|
|
160
|
+
const result = await getClimateChangeData(ROME_LAT, ROME_LON);
|
|
161
|
+
expect(result).not.toBeNull();
|
|
162
|
+
expect(result.metadata || result.results).toBeDefined();
|
|
163
|
+
});
|
|
164
|
+
});
|
package/test/holosphere.test.js
CHANGED
|
@@ -7,10 +7,13 @@ describe('HoloSphere', () => {
|
|
|
7
7
|
spacename: 'testuser',
|
|
8
8
|
password: 'testpass'
|
|
9
9
|
};
|
|
10
|
-
let holoSphere
|
|
10
|
+
let holoSphere;
|
|
11
|
+
let strictHoloSphere;
|
|
12
|
+
|
|
11
13
|
beforeAll(async () => {
|
|
12
|
-
// Initialize HoloSphere once for all tests
|
|
13
|
-
|
|
14
|
+
// Initialize HoloSphere instances once for all tests
|
|
15
|
+
holoSphere = new HoloSphere(testAppName, false);
|
|
16
|
+
strictHoloSphere = new HoloSphere(testAppName, true);
|
|
14
17
|
|
|
15
18
|
// Set up test space and authenticate
|
|
16
19
|
try {
|
|
@@ -26,15 +29,9 @@ describe('HoloSphere', () => {
|
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
// Ensure
|
|
32
|
+
// Ensure both instances are logged in
|
|
30
33
|
await holoSphere.login(testCredentials.spacename, testCredentials.password);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
beforeEach(async () => {
|
|
34
|
-
// Ensure we're logged in before each test
|
|
35
|
-
if (!holoSphere.currentSpace || holoSphere.currentSpace.exp < Date.now()) {
|
|
36
|
-
await holoSphere.login(testCredentials.spacename, testCredentials.password);
|
|
37
|
-
}
|
|
34
|
+
await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
|
|
38
35
|
});
|
|
39
36
|
|
|
40
37
|
describe('Constructor', () => {
|
|
@@ -45,11 +42,8 @@ describe('HoloSphere', () => {
|
|
|
45
42
|
expect(holoSphere.openai).toBeUndefined();
|
|
46
43
|
});
|
|
47
44
|
|
|
48
|
-
test('should initialize with OpenAI
|
|
49
|
-
|
|
50
|
-
expect(hsWithAI.openai).toBeDefined();
|
|
51
|
-
// Clean up additional instance
|
|
52
|
-
if (hsWithAI.gun) hsWithAI.gun.off();
|
|
45
|
+
test('should initialize with OpenAI', () => {
|
|
46
|
+
expect(new HoloSphere(testAppName, false, 'fake-key').openai).toBeDefined();
|
|
53
47
|
});
|
|
54
48
|
});
|
|
55
49
|
|
|
@@ -65,10 +59,8 @@ describe('HoloSphere', () => {
|
|
|
65
59
|
};
|
|
66
60
|
|
|
67
61
|
beforeEach(async () => {
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
await holoSphere.login(testCredentials.spacename, testCredentials.password);
|
|
71
|
-
}
|
|
62
|
+
// Clean up any existing schemas
|
|
63
|
+
await holoSphere.deleteAllGlobal('schemas');
|
|
72
64
|
});
|
|
73
65
|
|
|
74
66
|
test('should set and get schema', async () => {
|
|
@@ -84,11 +76,6 @@ describe('HoloSphere', () => {
|
|
|
84
76
|
});
|
|
85
77
|
|
|
86
78
|
test('should enforce strict mode schema validation', async () => {
|
|
87
|
-
const strictHoloSphere = new HoloSphere(testAppName, true);
|
|
88
|
-
|
|
89
|
-
// Login to the strict instance
|
|
90
|
-
await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
|
|
91
|
-
|
|
92
79
|
const invalidSchema = {
|
|
93
80
|
type: 'object',
|
|
94
81
|
properties: {
|
|
@@ -98,9 +85,6 @@ describe('HoloSphere', () => {
|
|
|
98
85
|
|
|
99
86
|
await expect(strictHoloSphere.setSchema(testLens, invalidSchema))
|
|
100
87
|
.rejects.toThrow();
|
|
101
|
-
|
|
102
|
-
// Clean up
|
|
103
|
-
await strictHoloSphere.logout();
|
|
104
88
|
});
|
|
105
89
|
|
|
106
90
|
test('should handle schema retrieval for non-existent lens', async () => {
|
|
@@ -110,10 +94,6 @@ describe('HoloSphere', () => {
|
|
|
110
94
|
|
|
111
95
|
test('should maintain schema integrity across storage and retrieval', async () => {
|
|
112
96
|
const testLens = 'schemaTestLens';
|
|
113
|
-
const strictHoloSphere = new HoloSphere(testAppName, true);
|
|
114
|
-
|
|
115
|
-
// Login to the strict instance
|
|
116
|
-
await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
|
|
117
97
|
|
|
118
98
|
// Create test schemas of increasing complexity
|
|
119
99
|
const testSchemas = [
|
|
@@ -151,7 +131,6 @@ describe('HoloSphere', () => {
|
|
|
151
131
|
|
|
152
132
|
// Store schema
|
|
153
133
|
await strictHoloSphere.setSchema(testLensWithIndex, schema);
|
|
154
|
-
|
|
155
134
|
|
|
156
135
|
// Retrieve schema
|
|
157
136
|
const retrievedSchema = await strictHoloSphere.getSchema(testLensWithIndex);
|
|
@@ -187,17 +166,8 @@ describe('HoloSphere', () => {
|
|
|
187
166
|
|
|
188
167
|
// Clean up after each schema test
|
|
189
168
|
await strictHoloSphere.deleteAll('testHolon', testLensWithIndex);
|
|
190
|
-
await strictHoloSphere.gun.get(strictHoloSphere.appname)
|
|
191
|
-
.get(testLensWithIndex)
|
|
192
|
-
.get('schema')
|
|
193
|
-
.put(null);
|
|
194
169
|
}
|
|
195
|
-
|
|
196
|
-
// Clean up the strict instance
|
|
197
|
-
if (strictHoloSphere.gun) {
|
|
198
|
-
await strictHoloSphere.logout();
|
|
199
|
-
}
|
|
200
|
-
}, 10000); // Increase timeout to 10 seconds
|
|
170
|
+
}, 10000);
|
|
201
171
|
|
|
202
172
|
test('should handle concurrent schema operations', async () => {
|
|
203
173
|
const baseLens = 'concurrentSchemaTest';
|
|
@@ -272,10 +242,7 @@ describe('HoloSphere', () => {
|
|
|
272
242
|
|
|
273
243
|
afterEach(async () => {
|
|
274
244
|
// Clean up schemas after each test
|
|
275
|
-
await holoSphere.
|
|
276
|
-
.get(testLens)
|
|
277
|
-
.get('schema')
|
|
278
|
-
.put(null);
|
|
245
|
+
await holoSphere.deleteAllGlobal('schemas');
|
|
279
246
|
});
|
|
280
247
|
});
|
|
281
248
|
|
|
@@ -355,11 +322,6 @@ describe('HoloSphere', () => {
|
|
|
355
322
|
});
|
|
356
323
|
|
|
357
324
|
test('should enforce strict mode data validation', async () => {
|
|
358
|
-
const strictHoloSphere = new HoloSphere(testAppName, true);
|
|
359
|
-
|
|
360
|
-
// Login to the strict instance
|
|
361
|
-
await strictHoloSphere.login(testCredentials.spacename, testCredentials.password);
|
|
362
|
-
|
|
363
325
|
// Define schema for strict mode tests
|
|
364
326
|
const strictSchema = {
|
|
365
327
|
type: 'object',
|
|
@@ -376,9 +338,6 @@ describe('HoloSphere', () => {
|
|
|
376
338
|
// Try to put data without schema in strict mode
|
|
377
339
|
await expect(strictHoloSphere.put(testHolon, 'no-schema-lens', validData))
|
|
378
340
|
.rejects.toThrow('Schema required in strict mode');
|
|
379
|
-
|
|
380
|
-
// Clean up
|
|
381
|
-
await strictHoloSphere.logout();
|
|
382
341
|
});
|
|
383
342
|
|
|
384
343
|
test('should maintain content integrity in holon storage', async () => {
|
|
@@ -1015,14 +974,13 @@ describe('HoloSphere', () => {
|
|
|
1015
974
|
|
|
1016
975
|
describe('OpenAI Integration', () => {
|
|
1017
976
|
test('should handle missing OpenAI key', async () => {
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
expect(result).toBe('OpenAI not initialized, please specify the API key in the constructor.');
|
|
977
|
+
expect(await holoSphere.summarize('test content'))
|
|
978
|
+
.toBe('OpenAI not initialized, please specify the API key in the constructor.');
|
|
1021
979
|
});
|
|
1022
980
|
|
|
1023
981
|
test.skip('should summarize content with valid OpenAI key', async () => {
|
|
1024
|
-
const
|
|
1025
|
-
|
|
982
|
+
const summary = await new HoloSphere('test', false, process.env.OPENAI_API_KEY)
|
|
983
|
+
.summarize('Test content to summarize');
|
|
1026
984
|
expect(typeof summary).toBe('string');
|
|
1027
985
|
expect(summary.length).toBeGreaterThan(0);
|
|
1028
986
|
});
|
|
@@ -1038,13 +996,21 @@ describe('HoloSphere', () => {
|
|
|
1038
996
|
await holoSphere.deleteAllGlobal('testTable');
|
|
1039
997
|
await holoSphere.deleteAllGlobal('testGlobalTable');
|
|
1040
998
|
await holoSphere.deleteAllGlobal('concurrentGlobalTest');
|
|
999
|
+
await holoSphere.deleteAllGlobal('schemas');
|
|
1041
1000
|
|
|
1042
1001
|
// Clean up test space
|
|
1043
1002
|
await holoSphere.deleteGlobal('spaces', testCredentials.spacename);
|
|
1044
1003
|
|
|
1045
|
-
// Logout
|
|
1004
|
+
// Logout both instances
|
|
1046
1005
|
if (holoSphere.currentSpace) {
|
|
1047
1006
|
await holoSphere.logout();
|
|
1048
1007
|
}
|
|
1008
|
+
if (strictHoloSphere.currentSpace) {
|
|
1009
|
+
await strictHoloSphere.logout();
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Clean up Gun instances
|
|
1013
|
+
if (holoSphere.gun) holoSphere.gun.off();
|
|
1014
|
+
if (strictHoloSphere.gun) strictHoloSphere.gun.off();
|
|
1049
1015
|
});
|
|
1050
1016
|
});
|