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.
@@ -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
- * DeCharge Scout - Energy Grid Data Scout CLI
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 { fetchEnergyData, fetchElectricityMapsData } from './src/energy-data.js';
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 = `# EIA API Key (Required - Get from https://www.eia.gov/opendata/register.php)
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
- // Check for EIA API key
101
- if (!process.env.EIA_API_KEY || process.env.EIA_API_KEY === 'your_eia_api_key_here') {
102
- console.log(chalk.yellow('\nāš ļø EIA_API_KEY not configured'));
103
- console.log(chalk.blue('\nšŸ“ How to get a FREE EIA API key:'));
104
- console.log(chalk.gray(' 1. Visit: https://www.eia.gov/opendata/register.php'));
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šŸ”‹ DeCharge Scout - Energy Grid Data Scout\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 (with timeout)
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('You have 60 seconds to respond...'));
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
- let timeoutId;
192
- let hasResolved = false;
193
-
194
- // Create a promise that auto-resolves after 60 seconds
195
- const timeoutPromise = new Promise((resolve) => {
196
- timeoutId = setTimeout(() => {
197
- if (!hasResolved) {
198
- hasResolved = true;
199
- console.log(chalk.yellow('\nā±ļø Timeout - using detected location...'));
200
- resolve('__TIMEOUT__');
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
- // If timeout occurred, use detected location
217
- if (customLocation === '__TIMEOUT__') {
218
- location = detectedLocation;
219
- console.log(chalk.green(`āœ“ Using detected location: ${location}`));
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
- // Use custom location if provided, otherwise use detected
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 energy data
297
- const dataSpinner = ora('Fetching energy grid data...').start();
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
- energyData = await fetchEnergyData(location);
302
- const gridRegion = energyData[0]?.source?.split('-')[1] || 'Unknown';
303
- dataSpinner.succeed(chalk.green(`Fetched ${energyData.length} data points from ${energyData[0]?.source || 'EIA'}`));
304
- } catch (error) {
305
- dataSpinner.warn(chalk.yellow(`EIA API failed, trying Electricity Maps...`));
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
- try {
308
- energyData = await fetchElectricityMapsData();
309
- dataSpinner.succeed(chalk.green(`Fetched forecast data from Electricity Maps`));
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!'));