decharge-scout 2.5.6 ā 4.0.2
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 +18 -0
- package/.env.example +12 -1
- package/README.md +93 -29
- package/dashboard/api/alpha.js +167 -0
- package/dashboard/api/submit.js +23 -0
- package/dashboard/lib/migration-alpha-contributions.sql +185 -0
- package/dashboard/public/index.html +236 -0
- package/index.js +200 -89
- package/package.json +8 -3
- package/src/energy-data.js +264 -8
- package/src/local-alpha.js +325 -0
- package/src/smart-pricing.js +251 -0
- package/src/weather-data.js +117 -0
|
@@ -213,6 +213,132 @@
|
|
|
213
213
|
font-weight: bold;
|
|
214
214
|
color: #667eea;
|
|
215
215
|
}
|
|
216
|
+
|
|
217
|
+
.alpha-grid {
|
|
218
|
+
display: grid;
|
|
219
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
220
|
+
gap: 20px;
|
|
221
|
+
margin-top: 20px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.alpha-card {
|
|
225
|
+
background: #f8f9fa;
|
|
226
|
+
padding: 20px;
|
|
227
|
+
border-radius: 8px;
|
|
228
|
+
border-left: 4px solid #4caf50;
|
|
229
|
+
transition: transform 0.2s;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.alpha-card:hover {
|
|
233
|
+
transform: translateY(-2px);
|
|
234
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.alpha-card.unverified {
|
|
238
|
+
border-left-color: #ff9800;
|
|
239
|
+
opacity: 0.8;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.alpha-header {
|
|
243
|
+
display: flex;
|
|
244
|
+
justify-content: space-between;
|
|
245
|
+
align-items: center;
|
|
246
|
+
margin-bottom: 12px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.alpha-type {
|
|
250
|
+
display: inline-block;
|
|
251
|
+
padding: 4px 10px;
|
|
252
|
+
background: #4caf50;
|
|
253
|
+
color: white;
|
|
254
|
+
border-radius: 12px;
|
|
255
|
+
font-size: 0.75em;
|
|
256
|
+
font-weight: 600;
|
|
257
|
+
text-transform: uppercase;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.alpha-type.cheap {
|
|
261
|
+
background: #2196f3;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.confidence-badge {
|
|
265
|
+
display: inline-block;
|
|
266
|
+
padding: 4px 10px;
|
|
267
|
+
border-radius: 12px;
|
|
268
|
+
font-size: 0.75em;
|
|
269
|
+
font-weight: 600;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.confidence-high {
|
|
273
|
+
background: #c8e6c9;
|
|
274
|
+
color: #2e7d32;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.confidence-medium {
|
|
278
|
+
background: #fff9c4;
|
|
279
|
+
color: #f57f17;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.confidence-low {
|
|
283
|
+
background: #ffccbc;
|
|
284
|
+
color: #d84315;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.alpha-hours {
|
|
288
|
+
font-size: 1.5em;
|
|
289
|
+
font-weight: bold;
|
|
290
|
+
color: #333;
|
|
291
|
+
margin: 10px 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.alpha-location {
|
|
295
|
+
color: #666;
|
|
296
|
+
font-size: 0.9em;
|
|
297
|
+
margin-bottom: 8px;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.alpha-agent {
|
|
301
|
+
color: #999;
|
|
302
|
+
font-size: 0.8em;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.verification-reason {
|
|
306
|
+
background: white;
|
|
307
|
+
padding: 8px;
|
|
308
|
+
border-radius: 4px;
|
|
309
|
+
margin-top: 10px;
|
|
310
|
+
font-size: 0.85em;
|
|
311
|
+
color: #666;
|
|
312
|
+
border-left: 3px solid #4caf50;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.verification-reason.warning {
|
|
316
|
+
border-left-color: #ff9800;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.verified-badge {
|
|
320
|
+
display: inline-flex;
|
|
321
|
+
align-items: center;
|
|
322
|
+
gap: 4px;
|
|
323
|
+
padding: 4px 8px;
|
|
324
|
+
background: #c8e6c9;
|
|
325
|
+
color: #2e7d32;
|
|
326
|
+
border-radius: 12px;
|
|
327
|
+
font-size: 0.75em;
|
|
328
|
+
font-weight: 600;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.unverified-badge {
|
|
332
|
+
display: inline-flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
gap: 4px;
|
|
335
|
+
padding: 4px 8px;
|
|
336
|
+
background: #ffccbc;
|
|
337
|
+
color: #d84315;
|
|
338
|
+
border-radius: 12px;
|
|
339
|
+
font-size: 0.75em;
|
|
340
|
+
font-weight: 600;
|
|
341
|
+
}
|
|
216
342
|
</style>
|
|
217
343
|
</head>
|
|
218
344
|
<body>
|
|
@@ -255,6 +381,11 @@
|
|
|
255
381
|
<div id="locations-map" class="map-grid"></div>
|
|
256
382
|
</div>
|
|
257
383
|
|
|
384
|
+
<div class="section">
|
|
385
|
+
<h2>š” Verified Local Alpha (Community Knowledge)</h2>
|
|
386
|
+
<div id="alpha-container"></div>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
258
389
|
<div class="section">
|
|
259
390
|
<h2>š Recent Submissions</h2>
|
|
260
391
|
<button class="refresh-btn" onclick="loadData()">Refresh Data</button>
|
|
@@ -295,6 +426,9 @@
|
|
|
295
426
|
// Render submissions table
|
|
296
427
|
renderSubmissionsTable(data.recentSubmissions || []);
|
|
297
428
|
|
|
429
|
+
// Load alpha contributions
|
|
430
|
+
await loadAlphaContributions();
|
|
431
|
+
|
|
298
432
|
// Update timestamp
|
|
299
433
|
document.getElementById('last-updated').textContent =
|
|
300
434
|
`Last updated: ${new Date().toLocaleTimeString()}`;
|
|
@@ -308,6 +442,24 @@
|
|
|
308
442
|
}
|
|
309
443
|
}
|
|
310
444
|
|
|
445
|
+
async function loadAlphaContributions() {
|
|
446
|
+
try {
|
|
447
|
+
const response = await fetch(`${API_BASE}/api/alpha?verified=true&min_confidence=0.6`);
|
|
448
|
+
|
|
449
|
+
if (!response.ok) {
|
|
450
|
+
console.warn('Alpha API not available yet');
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const data = await response.json();
|
|
455
|
+
renderAlphaContributions(data.contributions || [], data.statistics || []);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.warn('Error loading alpha contributions:', error);
|
|
458
|
+
document.getElementById('alpha-container').innerHTML =
|
|
459
|
+
'<div class="loading">Alpha contributions will appear here once the database is migrated</div>';
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
311
463
|
function renderLocationsMap(locations) {
|
|
312
464
|
const container = document.getElementById('locations-map');
|
|
313
465
|
|
|
@@ -325,6 +477,90 @@
|
|
|
325
477
|
`).join('');
|
|
326
478
|
}
|
|
327
479
|
|
|
480
|
+
function renderAlphaContributions(contributions, statistics) {
|
|
481
|
+
const container = document.getElementById('alpha-container');
|
|
482
|
+
|
|
483
|
+
if (contributions.length === 0) {
|
|
484
|
+
container.innerHTML = '<div class="loading">No verified alpha contributions yet. Share your local knowledge to be the first!</div>';
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Show top locations first
|
|
489
|
+
let statsHtml = '';
|
|
490
|
+
if (statistics.length > 0) {
|
|
491
|
+
statsHtml = `
|
|
492
|
+
<div class="map-grid" style="margin-bottom: 20px;">
|
|
493
|
+
${statistics.slice(0, 5).map(stat => `
|
|
494
|
+
<div class="location-card">
|
|
495
|
+
<h4>${escapeHtml(stat.location)}</h4>
|
|
496
|
+
<div class="count">${stat.verified_count}</div>
|
|
497
|
+
<div class="label">verified contributions</div>
|
|
498
|
+
<div style="margin-top: 8px; color: #666; font-size: 0.85em;">
|
|
499
|
+
Avg confidence: ${(stat.avg_confidence * 100).toFixed(0)}%
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
`).join('')}
|
|
503
|
+
</div>
|
|
504
|
+
`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
container.innerHTML = statsHtml + `
|
|
508
|
+
<div class="alpha-grid">
|
|
509
|
+
${contributions.slice(0, 12).map(alpha => {
|
|
510
|
+
const confidence = parseFloat(alpha.confidence);
|
|
511
|
+
const confidenceClass = confidence >= 0.8 ? 'confidence-high' :
|
|
512
|
+
confidence >= 0.6 ? 'confidence-medium' : 'confidence-low';
|
|
513
|
+
const confidenceLabel = confidence >= 0.8 ? 'High' :
|
|
514
|
+
confidence >= 0.6 ? 'Medium' : 'Low';
|
|
515
|
+
|
|
516
|
+
const formatHour = (h) => {
|
|
517
|
+
const hour = parseInt(h);
|
|
518
|
+
if (hour === 0) return '12 AM';
|
|
519
|
+
if (hour < 12) return `${hour} AM`;
|
|
520
|
+
if (hour === 12) return '12 PM';
|
|
521
|
+
return `${hour - 12} PM`;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const typeClass = alpha.contribution_type === 'peak' ? 'alpha-type' : 'alpha-type cheap';
|
|
525
|
+
|
|
526
|
+
return `
|
|
527
|
+
<div class="alpha-card ${alpha.verified ? '' : 'unverified'}">
|
|
528
|
+
<div class="alpha-header">
|
|
529
|
+
<span class="${typeClass}">${alpha.contribution_type}</span>
|
|
530
|
+
${alpha.verified
|
|
531
|
+
? `<span class="verified-badge">ā Verified</span>`
|
|
532
|
+
: `<span class="unverified-badge">ā Unverified</span>`
|
|
533
|
+
}
|
|
534
|
+
</div>
|
|
535
|
+
|
|
536
|
+
<div class="alpha-hours">
|
|
537
|
+
${formatHour(alpha.start_hour)} - ${formatHour(alpha.end_hour)}
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
<div class="alpha-location">š ${escapeHtml(alpha.location)}</div>
|
|
541
|
+
|
|
542
|
+
<div class="alpha-agent">
|
|
543
|
+
By ${escapeHtml(alpha.agent_name)} ⢠${new Date(alpha.created_at).toLocaleDateString()}
|
|
544
|
+
</div>
|
|
545
|
+
|
|
546
|
+
<div style="margin-top: 12px;">
|
|
547
|
+
<span class="confidence-badge ${confidenceClass}">
|
|
548
|
+
${confidenceLabel} Confidence (${(confidence * 100).toFixed(0)}%)
|
|
549
|
+
</span>
|
|
550
|
+
</div>
|
|
551
|
+
|
|
552
|
+
${alpha.verification_reasons && alpha.verification_reasons.length > 0 ? `
|
|
553
|
+
<div class="verification-reason ${confidence < 0.6 ? 'warning' : ''}">
|
|
554
|
+
${escapeHtml(alpha.verification_reasons[0])}
|
|
555
|
+
</div>
|
|
556
|
+
` : ''}
|
|
557
|
+
</div>
|
|
558
|
+
`;
|
|
559
|
+
}).join('')}
|
|
560
|
+
</div>
|
|
561
|
+
`;
|
|
562
|
+
}
|
|
563
|
+
|
|
328
564
|
function renderSubmissionsTable(submissions) {
|
|
329
565
|
const container = document.getElementById('submissions-container');
|
|
330
566
|
|
package/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Global Energy Scout - Intelligent Energy Price Forecasting CLI
|
|
5
|
+
* Powered by real-time weather data and smart simulation
|
|
5
6
|
*
|
|
6
7
|
* Main entry point for the CLI application that scouts energy grid data,
|
|
7
8
|
* performs optimizations, and submits results to Solana blockchain.
|
|
@@ -32,12 +33,14 @@ dotenv.config({ path: path.join(process.cwd(), '.env') });
|
|
|
32
33
|
// Import modules
|
|
33
34
|
import { startWalletServer, openWalletConnection, waitForWalletConnection, getConnectedWallet } from './src/wallet-server.js';
|
|
34
35
|
import { setConnectedWallet, getBalance, checkBalance, mockStake, refundStake } from './src/browser-wallet.js';
|
|
35
|
-
import {
|
|
36
|
+
import { getWeatherForLocation } from './src/weather-data.js';
|
|
37
|
+
import { generateSmartPricing, getPricingInsights, getRegionalPricing } from './src/smart-pricing.js';
|
|
36
38
|
import { findCheapestWindow, calculateSavings } from './src/optimizer.js';
|
|
37
39
|
import { submitToOracle } from './src/oracle.js';
|
|
38
40
|
import { initializePoints, awardPoints, getPoints, savePoints } from './src/points.js';
|
|
39
41
|
import { getLocation } from './src/geolocation.js';
|
|
40
42
|
import { purchasePremiumData } from './src/x402.js';
|
|
43
|
+
import { hasBeenAskedForAlpha, markAskedForAlpha, parseAlphaContribution, saveAlphaContribution, calculateAlphaBonus, getAlphaInsights, verifyContribution, getInformationSources } from './src/local-alpha.js';
|
|
41
44
|
|
|
42
45
|
const __filename = fileURLToPath(import.meta.url);
|
|
43
46
|
const __dirname = dirname(__filename);
|
|
@@ -50,6 +53,7 @@ const CYCLE_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes
|
|
|
50
53
|
let isRunning = true;
|
|
51
54
|
let totalRuns = 0;
|
|
52
55
|
let stakeTransactionSignature = null;
|
|
56
|
+
let pendingAlphaContribution = null; // Store alpha contribution for next submission
|
|
53
57
|
|
|
54
58
|
// Readline interface for prompts
|
|
55
59
|
const rl = createInterface({
|
|
@@ -85,51 +89,38 @@ async function ensureEnvironment() {
|
|
|
85
89
|
console.log(chalk.green(`ā .env file created at ${envPath}`));
|
|
86
90
|
} else {
|
|
87
91
|
// Create a basic .env file if no example exists
|
|
88
|
-
const basicEnv = `#
|
|
92
|
+
const basicEnv = `# Energy Data API Keys
|
|
93
|
+
# Choose ONE based on your location:
|
|
94
|
+
# - EIA (FREE, US-only): Get from https://www.eia.gov/opendata/register.php
|
|
95
|
+
# - Electricity Maps (GLOBAL, paid): Get from https://www.electricitymaps.com/
|
|
89
96
|
EIA_API_KEY=your_eia_api_key_here
|
|
97
|
+
ELECTRICITY_MAPS_API_KEY=your_electricity_maps_api_key_here
|
|
90
98
|
|
|
91
99
|
# Solana Configuration
|
|
92
100
|
SOLANA_NETWORK=devnet
|
|
93
101
|
SOLANA_RPC_URL=https://api.devnet.solana.com
|
|
102
|
+
|
|
103
|
+
# Stake Amount (in SOL)
|
|
104
|
+
STAKE_AMOUNT=0.01
|
|
94
105
|
`;
|
|
95
106
|
writeFileSync(envPath, basicEnv);
|
|
96
107
|
console.log(chalk.green(`ā .env file created at ${envPath}`));
|
|
97
108
|
}
|
|
98
109
|
}
|
|
99
110
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
console.log(chalk.gray(' 2. Fill out the registration form'));
|
|
106
|
-
console.log(chalk.gray(' 3. Check your email and verify your email address'));
|
|
107
|
-
console.log(chalk.gray(' 4. Your API key will be sent to your email'));
|
|
108
|
-
console.log(chalk.gray(' 5. Copy the API key and paste it below\n'));
|
|
109
|
-
|
|
110
|
-
const answer = await question('Enter your EIA API key (or press Enter to skip): ');
|
|
111
|
-
|
|
112
|
-
if (answer.trim()) {
|
|
113
|
-
// Update .env file
|
|
114
|
-
let envContent = readFileSync(envPath, 'utf-8');
|
|
115
|
-
envContent = envContent.replace(/EIA_API_KEY=.*/g, `EIA_API_KEY=${answer.trim()}`);
|
|
116
|
-
writeFileSync(envPath, envContent);
|
|
117
|
-
|
|
118
|
-
// Update process.env
|
|
119
|
-
process.env.EIA_API_KEY = answer.trim();
|
|
120
|
-
|
|
121
|
-
console.log(chalk.green('ā API key saved to .env'));
|
|
122
|
-
} else {
|
|
123
|
-
console.log(chalk.yellow('ā ļø Running without EIA API key (will use fallback data sources)'));
|
|
124
|
-
}
|
|
125
|
-
}
|
|
111
|
+
// Info: No API keys needed for weather-based simulation!
|
|
112
|
+
console.log(chalk.green('\nā
Using FREE weather-based simulation (no API keys required!)'));
|
|
113
|
+
console.log(chalk.gray(' ⢠Real-time weather data from Open-Meteo'));
|
|
114
|
+
console.log(chalk.gray(' ⢠Smart pricing based on temperature, wind, solar radiation'));
|
|
115
|
+
console.log(chalk.gray(' ⢠Regional patterns for accurate predictions\n'));
|
|
126
116
|
}
|
|
127
117
|
|
|
128
118
|
/**
|
|
129
119
|
* Main CLI function
|
|
130
120
|
*/
|
|
131
121
|
async function main(options) {
|
|
132
|
-
console.log(chalk.cyan.bold('\n
|
|
122
|
+
console.log(chalk.cyan.bold('\nš Global Energy Scout - Intelligent Price Forecasting\n'));
|
|
123
|
+
console.log(chalk.gray(' Powered by real-time weather data & smart simulation'));
|
|
133
124
|
|
|
134
125
|
try {
|
|
135
126
|
// Auto-configure environment
|
|
@@ -182,49 +173,29 @@ async function main(options) {
|
|
|
182
173
|
const detectedLocation = await getLocation();
|
|
183
174
|
locationSpinner.succeed(chalk.green(`š Detected Location: ${detectedLocation}`));
|
|
184
175
|
|
|
185
|
-
// Ask user to confirm or override
|
|
176
|
+
// Ask user to confirm or override with 10 second timeout
|
|
186
177
|
console.log(chalk.blue('\nYou can use this location or enter a custom one.'));
|
|
187
|
-
console.log(chalk.gray('(Press Enter to use detected location, or type custom location)'));
|
|
188
|
-
console.log(chalk.gray('
|
|
178
|
+
console.log(chalk.gray('(Press Enter to use detected location, or type a custom location like "Dallas,TX,USA")'));
|
|
179
|
+
console.log(chalk.gray('(Will auto-proceed in 10 seconds if no input)\n'));
|
|
189
180
|
|
|
190
181
|
try {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
}, 60000); // 60 seconds for npx readline delay
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
const questionPromise = question('Enter custom location (or press Enter): ').then(answer => {
|
|
206
|
-
if (!hasResolved) {
|
|
207
|
-
hasResolved = true;
|
|
208
|
-
clearTimeout(timeoutId);
|
|
209
|
-
return answer;
|
|
210
|
-
}
|
|
211
|
-
return '__TIMEOUT__'; // If timeout already occurred, ignore this answer
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
const customLocation = await Promise.race([questionPromise, timeoutPromise]);
|
|
182
|
+
// Create a promise race between user input and timeout
|
|
183
|
+
const customLocation = await Promise.race([
|
|
184
|
+
question('Enter location (or press Enter for auto-detected): '),
|
|
185
|
+
new Promise((resolve) => {
|
|
186
|
+
setTimeout(() => {
|
|
187
|
+
console.log(chalk.yellow('\nā±ļø Timeout - using detected location'));
|
|
188
|
+
resolve('');
|
|
189
|
+
}, 10000); // 10 second timeout
|
|
190
|
+
})
|
|
191
|
+
]);
|
|
215
192
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
console.log(chalk.green(`ā Using
|
|
193
|
+
// Use custom location if provided, otherwise use detected
|
|
194
|
+
location = (customLocation || '').trim() || detectedLocation;
|
|
195
|
+
if ((customLocation || '').trim()) {
|
|
196
|
+
console.log(chalk.green(`ā Using custom location: ${location}`));
|
|
220
197
|
} else {
|
|
221
|
-
|
|
222
|
-
location = customLocation.trim() || detectedLocation;
|
|
223
|
-
if (customLocation.trim()) {
|
|
224
|
-
console.log(chalk.green(`ā Using custom location: ${location}`));
|
|
225
|
-
} else {
|
|
226
|
-
console.log(chalk.green(`ā Using detected location: ${location}`));
|
|
227
|
-
}
|
|
198
|
+
console.log(chalk.green(`ā Using detected location: ${location}`));
|
|
228
199
|
}
|
|
229
200
|
} catch (error) {
|
|
230
201
|
console.log(chalk.yellow(`\nā ļø Prompt error, using detected location: ${detectedLocation}`));
|
|
@@ -293,24 +264,31 @@ async function runQueryCycle(wallet, agentName, location, options) {
|
|
|
293
264
|
console.log(chalk.cyan.bold(`š Run #${totalRuns} - ${new Date().toLocaleString()}`));
|
|
294
265
|
console.log(chalk.cyan(`āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n`));
|
|
295
266
|
|
|
296
|
-
// Fetch
|
|
297
|
-
const
|
|
298
|
-
let energyData;
|
|
267
|
+
// Fetch weather data and generate smart pricing simulation
|
|
268
|
+
const weatherSpinner = ora('Fetching real-time weather forecast...').start();
|
|
269
|
+
let weatherData, energyData, countryCode;
|
|
299
270
|
|
|
300
271
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
272
|
+
// Get real weather forecast from Open-Meteo (FREE!)
|
|
273
|
+
weatherData = await getWeatherForLocation(location);
|
|
274
|
+
countryCode = weatherData.location.country || 'DEFAULT';
|
|
275
|
+
weatherSpinner.succeed(chalk.green(`ā Fetched ${weatherData.forecast.length}h weather forecast (FREE!)`));
|
|
276
|
+
|
|
277
|
+
// Generate intelligent pricing based on weather + regional patterns
|
|
278
|
+
const pricingSpinner = ora('Simulating energy grid pricing...').start();
|
|
279
|
+
energyData = generateSmartPricing(weatherData, countryCode);
|
|
280
|
+
pricingSpinner.succeed(chalk.green(`ā Generated ${energyData.length} hours of smart pricing data`));
|
|
281
|
+
|
|
282
|
+
// Show pricing insights
|
|
283
|
+
const insights = getPricingInsights(energyData, countryCode);
|
|
284
|
+
console.log(chalk.blue(`\nš” Smart Simulation Insights:`));
|
|
285
|
+
console.log(chalk.gray(` Region: ${insights.region}`));
|
|
286
|
+
console.log(chalk.gray(` Price range: $${insights.minPrice}-$${insights.maxPrice}/kWh (${insights.priceRange}% variation)`));
|
|
287
|
+
console.log(chalk.gray(` Savings potential: ${insights.savingsPotential}% by timing your charge`));
|
|
306
288
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
} catch (fallbackError) {
|
|
311
|
-
dataSpinner.fail(chalk.red('All data sources failed'));
|
|
312
|
-
throw new Error('Unable to fetch energy data from any source');
|
|
313
|
-
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
weatherSpinner.fail(chalk.red('Failed to fetch weather data'));
|
|
291
|
+
throw new Error(`Unable to generate pricing simulation: ${error.message}`);
|
|
314
292
|
}
|
|
315
293
|
|
|
316
294
|
// Run optimization
|
|
@@ -319,12 +297,31 @@ async function runQueryCycle(wallet, agentName, location, options) {
|
|
|
319
297
|
const savings = calculateSavings(energyData, cheapestWindow);
|
|
320
298
|
optSpinner.succeed(chalk.green('Optimization complete'));
|
|
321
299
|
|
|
322
|
-
// Display results
|
|
300
|
+
// Display results with intelligent insights
|
|
323
301
|
console.log(chalk.green.bold('\n⨠Optimization Results:'));
|
|
324
302
|
console.log(chalk.white(` Cheapest charge window: ${cheapestWindow.timeWindow}`));
|
|
325
303
|
console.log(chalk.white(` Price: $${cheapestWindow.price.toFixed(4)}/kWh`));
|
|
326
304
|
console.log(chalk.white(` Savings: ${savings.toFixed(1)}%`));
|
|
327
305
|
|
|
306
|
+
// Show why this time is cheap (weather-based reasons)
|
|
307
|
+
const cheapestHourData = energyData.find(d => d.hour === cheapestWindow.hour);
|
|
308
|
+
if (cheapestHourData && cheapestHourData.reasons) {
|
|
309
|
+
console.log(chalk.blue('\nš§ Why this time is optimal:'));
|
|
310
|
+
cheapestHourData.reasons.forEach(reason => {
|
|
311
|
+
console.log(chalk.gray(` ⢠${reason}`));
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Show weather conditions
|
|
315
|
+
if (cheapestHourData.weather) {
|
|
316
|
+
console.log(chalk.blue('\nš¤ļø Weather conditions at optimal time:'));
|
|
317
|
+
console.log(chalk.gray(` Temperature: ${cheapestHourData.weather.temperature.toFixed(1)}°C`));
|
|
318
|
+
console.log(chalk.gray(` Wind: ${cheapestHourData.weather.windSpeed.toFixed(1)} m/s`));
|
|
319
|
+
if (cheapestHourData.hour >= 6 && cheapestHourData.hour <= 18) {
|
|
320
|
+
console.log(chalk.gray(` Solar: ${cheapestHourData.weather.solarRadiation.toFixed(0)} W/m²`));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
328
325
|
// Prepare submission data
|
|
329
326
|
const submissionData = {
|
|
330
327
|
agent_name: agentName,
|
|
@@ -355,14 +352,22 @@ async function runQueryCycle(wallet, agentName, location, options) {
|
|
|
355
352
|
console.log(chalk.blue(`\nš Dashboard API URL: ${apiUrl}`));
|
|
356
353
|
console.log(chalk.gray(`š¤ Submitting data...`));
|
|
357
354
|
|
|
355
|
+
const dashboardPayload = {
|
|
356
|
+
...submissionData,
|
|
357
|
+
wallet: wallet, // wallet parameter from runQueryCycle
|
|
358
|
+
run_number: totalRuns
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// Include alpha contribution if available
|
|
362
|
+
if (pendingAlphaContribution) {
|
|
363
|
+
dashboardPayload.alpha_contribution = pendingAlphaContribution;
|
|
364
|
+
console.log(chalk.blue(`š Including verified alpha contribution (${(pendingAlphaContribution.confidence * 100).toFixed(0)}% confidence)`));
|
|
365
|
+
}
|
|
366
|
+
|
|
358
367
|
const response = await fetch(apiUrl, {
|
|
359
368
|
method: 'POST',
|
|
360
369
|
headers: { 'Content-Type': 'application/json' },
|
|
361
|
-
body: JSON.stringify(
|
|
362
|
-
...submissionData,
|
|
363
|
-
wallet: wallet, // wallet parameter from runQueryCycle
|
|
364
|
-
run_number: totalRuns
|
|
365
|
-
})
|
|
370
|
+
body: JSON.stringify(dashboardPayload)
|
|
366
371
|
});
|
|
367
372
|
|
|
368
373
|
const responseText = await response.text();
|
|
@@ -372,6 +377,12 @@ async function runQueryCycle(wallet, agentName, location, options) {
|
|
|
372
377
|
dashboardSpinner.succeed(chalk.green('ā
Dashboard submission successful!'));
|
|
373
378
|
console.log(chalk.green(`š Data should now appear at: https://decharge-scout.vercel.app/agentone`));
|
|
374
379
|
console.log(chalk.gray(`Response: ${responseText}`));
|
|
380
|
+
|
|
381
|
+
// Clear pending alpha contribution after successful submission
|
|
382
|
+
if (pendingAlphaContribution) {
|
|
383
|
+
console.log(chalk.green(` ā Alpha contribution synced to dashboard`));
|
|
384
|
+
pendingAlphaContribution = null;
|
|
385
|
+
}
|
|
375
386
|
} else {
|
|
376
387
|
dashboardSpinner.warn(chalk.yellow(`ā ļø Dashboard API returned: ${response.status}`));
|
|
377
388
|
console.log(chalk.yellow(`Response: ${responseText}`));
|
|
@@ -392,6 +403,106 @@ async function runQueryCycle(wallet, agentName, location, options) {
|
|
|
392
403
|
console.log(chalk.magenta(`\nā Earned ${totalPointsEarned} points! (${basePoints} base${bonusPoints > 0 ? ` + ${bonusPoints} bonus` : ''})`));
|
|
393
404
|
console.log(chalk.magenta(`ā Total Points: ${currentPoints}`));
|
|
394
405
|
|
|
406
|
+
// Local Alpha Contribution (ask once after first run)
|
|
407
|
+
if (totalRuns === 1 && !hasBeenAskedForAlpha()) {
|
|
408
|
+
console.log(chalk.cyan('\nš” Local Alpha Contribution - Help Improve Global Energy Data!'));
|
|
409
|
+
console.log(chalk.gray(' Share your local electricity peak time knowledge for bonus points.\n'));
|
|
410
|
+
|
|
411
|
+
// Show where to find this information
|
|
412
|
+
console.log(chalk.blue('š Where to find peak time information:'));
|
|
413
|
+
const sources = getInformationSources(location);
|
|
414
|
+
|
|
415
|
+
// General sources
|
|
416
|
+
sources.general.forEach(source => {
|
|
417
|
+
console.log(chalk.gray(` ${source}`));
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// Region-specific sources
|
|
421
|
+
const regionKeys = Object.keys(sources.byRegion);
|
|
422
|
+
if (regionKeys.length > 0) {
|
|
423
|
+
console.log(chalk.blue('\nš For your region:'));
|
|
424
|
+
regionKeys.forEach(region => {
|
|
425
|
+
sources.byRegion[region].forEach(source => {
|
|
426
|
+
console.log(chalk.gray(` ${source}`));
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
console.log(chalk.blue('\nš¬ Examples of good contributions:'));
|
|
432
|
+
console.log(chalk.gray(' ⢠"7-9PM peak in Lagos" (evening peak)'));
|
|
433
|
+
console.log(chalk.gray(' ⢠"1-5AM cheap in Berlin" (off-peak)'));
|
|
434
|
+
console.log(chalk.gray(' ⢠"5-8PM peak in Mumbai" (dinner time surge)'));
|
|
435
|
+
console.log(chalk.gray(' ⢠"2-6AM cheap in Texas" (wind energy overnight)\n'));
|
|
436
|
+
|
|
437
|
+
const alphaInput = await question(chalk.blue('Share local peak times (or press Enter to skip): '));
|
|
438
|
+
|
|
439
|
+
if (alphaInput.trim()) {
|
|
440
|
+
const parsed = parseAlphaContribution(alphaInput, location);
|
|
441
|
+
if (parsed) {
|
|
442
|
+
// Verify contribution against current pricing data
|
|
443
|
+
const verification = verifyContribution(parsed, energyData);
|
|
444
|
+
|
|
445
|
+
// Save contribution with verification status
|
|
446
|
+
const contribution = saveAlphaContribution(parsed, agentName, location, verification);
|
|
447
|
+
|
|
448
|
+
// Store for dashboard submission
|
|
449
|
+
pendingAlphaContribution = {
|
|
450
|
+
type: parsed.type,
|
|
451
|
+
startHour: parsed.startHour,
|
|
452
|
+
endHour: parsed.endHour,
|
|
453
|
+
location: parsed.location,
|
|
454
|
+
verified: verification.verified,
|
|
455
|
+
confidence: verification.confidence,
|
|
456
|
+
verificationReasons: verification.reasons
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// Calculate bonus (higher for verified contributions)
|
|
460
|
+
const alphaBonus = calculateAlphaBonus(parsed, verification);
|
|
461
|
+
|
|
462
|
+
awardPoints(wallet, alphaBonus);
|
|
463
|
+
|
|
464
|
+
console.log(chalk.green(`\nā
Thanks for contributing! Earned ${alphaBonus} bonus points!`));
|
|
465
|
+
console.log(chalk.gray(` Contribution: ${parsed.type} hours ${parsed.startHour}-${parsed.endHour} in ${parsed.location}`));
|
|
466
|
+
|
|
467
|
+
// Show verification results
|
|
468
|
+
if (verification.verified) {
|
|
469
|
+
console.log(chalk.green(` šÆ Verified! Confidence: ${(verification.confidence * 100).toFixed(0)}%`));
|
|
470
|
+
verification.reasons.forEach(reason => {
|
|
471
|
+
console.log(chalk.gray(` ${reason}`));
|
|
472
|
+
});
|
|
473
|
+
console.log(chalk.green(` +${alphaBonus - 10} extra points for verified contribution!`));
|
|
474
|
+
} else {
|
|
475
|
+
console.log(chalk.yellow(` ā ļø Low confidence: ${(verification.confidence * 100).toFixed(0)}%`));
|
|
476
|
+
verification.reasons.forEach(reason => {
|
|
477
|
+
console.log(chalk.gray(` ${reason}`));
|
|
478
|
+
});
|
|
479
|
+
console.log(chalk.gray(` Tip: Contributions that match actual price patterns earn more points!`));
|
|
480
|
+
}
|
|
481
|
+
} else {
|
|
482
|
+
console.log(chalk.yellow('ā ļø Could not parse contribution.'));
|
|
483
|
+
console.log(chalk.gray(' Accepted formats:'));
|
|
484
|
+
console.log(chalk.gray(' ⢠"7-9PM peak in Lagos"'));
|
|
485
|
+
console.log(chalk.gray(' ⢠"1-5AM cheap in Berlin"'));
|
|
486
|
+
console.log(chalk.gray(' ⢠"19-21 peak Mumbai" (24-hour format also works)'));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
markAskedForAlpha();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Show alpha insights if available
|
|
494
|
+
if (totalRuns === 1) {
|
|
495
|
+
const alphaInsights = getAlphaInsights(location);
|
|
496
|
+
if (alphaInsights && alphaInsights.contributions > 0) {
|
|
497
|
+
console.log(chalk.cyan(`\nš Community Knowledge for ${location}:`));
|
|
498
|
+
console.log(chalk.gray(` ${alphaInsights.contributions} local contribution(s)`));
|
|
499
|
+
if (alphaInsights.commonPeakHours.length > 0) {
|
|
500
|
+
const topPeaks = alphaInsights.commonPeakHours.map(p => `${p.hour}:00`).join(', ');
|
|
501
|
+
console.log(chalk.gray(` Most reported peak hours: ${topPeaks}`));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
395
506
|
// Premium upgrade option
|
|
396
507
|
if (options.premium && totalRuns % 3 === 0) {
|
|
397
508
|
console.log(chalk.yellow('\nš Premium Feature Available!'));
|