decharge-scout 4.0.0 → 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.
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Vercel API Endpoint - Alpha Contributions
3
+ *
4
+ * GET /api/alpha - Get verified alpha contributions
5
+ * POST /api/alpha - Submit new alpha contribution
6
+ */
7
+
8
+ import { createClient } from '@supabase/supabase-js';
9
+
10
+ export const config = {
11
+ runtime: 'edge',
12
+ };
13
+
14
+ export default async function handler(req) {
15
+ // CORS headers
16
+ const headers = {
17
+ 'Access-Control-Allow-Origin': '*',
18
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
19
+ 'Access-Control-Allow-Headers': 'Content-Type',
20
+ 'Content-Type': 'application/json',
21
+ };
22
+
23
+ // Handle OPTIONS request for CORS
24
+ if (req.method === 'OPTIONS') {
25
+ return new Response(null, { status: 200, headers });
26
+ }
27
+
28
+ // Initialize Supabase client
29
+ const supabase = createClient(
30
+ process.env.SUPABASE_URL,
31
+ process.env.SUPABASE_ANON_KEY
32
+ );
33
+
34
+ // GET - Retrieve alpha contributions
35
+ if (req.method === 'GET') {
36
+ try {
37
+ const url = new URL(req.url);
38
+ const location = url.searchParams.get('location');
39
+ const verified = url.searchParams.get('verified') === 'true';
40
+ const minConfidence = parseFloat(url.searchParams.get('min_confidence') || '0');
41
+
42
+ let query = supabase
43
+ .from('alpha_contributions')
44
+ .select('*')
45
+ .order('created_at', { ascending: false })
46
+ .limit(100);
47
+
48
+ // Apply filters
49
+ if (location) {
50
+ query = query.ilike('location', `%${location}%`);
51
+ }
52
+
53
+ if (verified) {
54
+ query = query.eq('verified', true);
55
+ }
56
+
57
+ if (minConfidence > 0) {
58
+ query = query.gte('confidence', minConfidence);
59
+ }
60
+
61
+ const { data, error } = await query;
62
+
63
+ if (error) throw error;
64
+
65
+ // Get statistics
66
+ const { data: stats } = await supabase
67
+ .from('alpha_by_location')
68
+ .select('*')
69
+ .order('verified_count', { ascending: false })
70
+ .limit(10);
71
+
72
+ return new Response(
73
+ JSON.stringify({
74
+ contributions: data || [],
75
+ statistics: stats || [],
76
+ total: data?.length || 0,
77
+ timestamp: new Date().toISOString(),
78
+ }),
79
+ { status: 200, headers }
80
+ );
81
+ } catch (error) {
82
+ console.error('Alpha GET error:', error);
83
+ return new Response(
84
+ JSON.stringify({
85
+ error: 'Failed to fetch alpha contributions',
86
+ message: error.message,
87
+ }),
88
+ { status: 500, headers }
89
+ );
90
+ }
91
+ }
92
+
93
+ // POST - Submit alpha contribution
94
+ if (req.method === 'POST') {
95
+ try {
96
+ const data = await req.json();
97
+
98
+ // Validate required fields
99
+ if (!data.agent_name || !data.location || !data.contribution) {
100
+ return new Response(
101
+ JSON.stringify({ error: 'Missing required fields' }),
102
+ { status: 400, headers }
103
+ );
104
+ }
105
+
106
+ const { contribution } = data;
107
+
108
+ // Validate contribution structure
109
+ if (
110
+ typeof contribution.startHour !== 'number' ||
111
+ typeof contribution.endHour !== 'number' ||
112
+ !contribution.type ||
113
+ contribution.startHour < 0 ||
114
+ contribution.startHour > 23 ||
115
+ contribution.endHour < 0 ||
116
+ contribution.endHour > 23
117
+ ) {
118
+ return new Response(
119
+ JSON.stringify({ error: 'Invalid contribution data' }),
120
+ { status: 400, headers }
121
+ );
122
+ }
123
+
124
+ // Insert alpha contribution
125
+ const { data: insertedData, error: insertError } = await supabase
126
+ .from('alpha_contributions')
127
+ .insert({
128
+ agent_name: data.agent_name,
129
+ location: data.location,
130
+ contribution_type: contribution.type,
131
+ start_hour: contribution.startHour,
132
+ end_hour: contribution.endHour,
133
+ verified: data.verified || false,
134
+ confidence: data.confidence || 0.5,
135
+ verification_reasons: data.verificationReasons || [],
136
+ timestamp: new Date(data.timestamp || Date.now()).toISOString(),
137
+ })
138
+ .select()
139
+ .single();
140
+
141
+ if (insertError) throw insertError;
142
+
143
+ return new Response(
144
+ JSON.stringify({
145
+ success: true,
146
+ message: 'Alpha contribution submitted successfully',
147
+ contribution: insertedData,
148
+ }),
149
+ { status: 200, headers }
150
+ );
151
+ } catch (error) {
152
+ console.error('Alpha POST error:', error);
153
+ return new Response(
154
+ JSON.stringify({
155
+ error: 'Failed to submit alpha contribution',
156
+ message: error.message,
157
+ }),
158
+ { status: 500, headers }
159
+ );
160
+ }
161
+ }
162
+
163
+ return new Response(
164
+ JSON.stringify({ error: 'Method not allowed' }),
165
+ { status: 405, headers }
166
+ );
167
+ }
@@ -65,6 +65,29 @@ export default async function handler(req) {
65
65
 
66
66
  if (submissionError) throw submissionError;
67
67
 
68
+ // Handle alpha contribution if present
69
+ if (data.alpha_contribution) {
70
+ const alpha = data.alpha_contribution;
71
+ const { error: alphaError } = await supabase
72
+ .from('alpha_contributions')
73
+ .insert({
74
+ agent_name: data.agent_name,
75
+ location: data.location,
76
+ contribution_type: alpha.type,
77
+ start_hour: alpha.startHour,
78
+ end_hour: alpha.endHour,
79
+ verified: alpha.verified || false,
80
+ confidence: alpha.confidence || 0.5,
81
+ verification_reasons: alpha.verificationReasons || [],
82
+ timestamp: new Date(data.timestamp).toISOString(),
83
+ });
84
+
85
+ // Don't fail the entire request if alpha submission fails
86
+ if (alphaError) {
87
+ console.warn('Alpha contribution failed:', alphaError);
88
+ }
89
+ }
90
+
68
91
  // Update agent heartbeat (upsert)
69
92
  const { error: heartbeatError } = await supabase
70
93
  .from('agent_heartbeat')
@@ -0,0 +1,185 @@
1
+ -- DeCharge Scout Dashboard - Alpha Contributions Migration
2
+ -- Add this to your Supabase database to support verified local alpha contributions
3
+
4
+ -- ============================================================================
5
+ -- TABLE: alpha_contributions
6
+ -- Stores verified local knowledge contributions from agents
7
+ -- ============================================================================
8
+ CREATE TABLE IF NOT EXISTS alpha_contributions (
9
+ id SERIAL PRIMARY KEY,
10
+ agent_name VARCHAR(255) NOT NULL,
11
+ location VARCHAR(255) NOT NULL,
12
+ contribution_type VARCHAR(20) NOT NULL CHECK (contribution_type IN ('peak', 'cheap', 'general')),
13
+ start_hour INTEGER NOT NULL CHECK (start_hour >= 0 AND start_hour <= 23),
14
+ end_hour INTEGER NOT NULL CHECK (end_hour >= 0 AND end_hour <= 23),
15
+
16
+ -- Verification fields
17
+ verified BOOLEAN DEFAULT FALSE,
18
+ confidence DECIMAL(4, 3) CHECK (confidence >= 0 AND confidence <= 1),
19
+ verification_reasons TEXT[],
20
+
21
+ -- Metadata
22
+ timestamp TIMESTAMP NOT NULL,
23
+ created_at TIMESTAMP DEFAULT NOW(),
24
+
25
+ -- Indexes for performance
26
+ CONSTRAINT valid_hour_range CHECK (start_hour <= end_hour)
27
+ );
28
+
29
+ -- Indexes for efficient querying
30
+ CREATE INDEX idx_alpha_location ON alpha_contributions(location);
31
+ CREATE INDEX idx_alpha_verified ON alpha_contributions(verified);
32
+ CREATE INDEX idx_alpha_confidence ON alpha_contributions(confidence DESC);
33
+ CREATE INDEX idx_alpha_created_at ON alpha_contributions(created_at DESC);
34
+ CREATE INDEX idx_alpha_type ON alpha_contributions(contribution_type);
35
+
36
+ -- ============================================================================
37
+ -- VIEW: Verified Alpha Contributions (high confidence only)
38
+ -- ============================================================================
39
+ CREATE OR REPLACE VIEW verified_alpha_contributions AS
40
+ SELECT
41
+ id,
42
+ agent_name,
43
+ location,
44
+ contribution_type,
45
+ start_hour,
46
+ end_hour,
47
+ confidence,
48
+ verification_reasons,
49
+ timestamp,
50
+ created_at
51
+ FROM alpha_contributions
52
+ WHERE verified = TRUE AND confidence >= 0.6
53
+ ORDER BY confidence DESC, created_at DESC;
54
+
55
+ -- ============================================================================
56
+ -- VIEW: Alpha Contributions Summary by Location
57
+ -- ============================================================================
58
+ CREATE OR REPLACE VIEW alpha_by_location AS
59
+ SELECT
60
+ location,
61
+ COUNT(*) as total_contributions,
62
+ COUNT(CASE WHEN verified = TRUE THEN 1 END) as verified_count,
63
+ AVG(confidence) as avg_confidence,
64
+ MAX(created_at) as latest_contribution
65
+ FROM alpha_contributions
66
+ WHERE created_at > NOW() - INTERVAL '30 days'
67
+ GROUP BY location
68
+ ORDER BY verified_count DESC, total_contributions DESC;
69
+
70
+ -- ============================================================================
71
+ -- VIEW: Peak Hours Analysis (most commonly reported)
72
+ -- ============================================================================
73
+ CREATE OR REPLACE VIEW peak_hours_analysis AS
74
+ WITH hour_counts AS (
75
+ SELECT
76
+ location,
77
+ generate_series(start_hour, end_hour) as hour,
78
+ confidence,
79
+ contribution_type
80
+ FROM alpha_contributions
81
+ WHERE contribution_type = 'peak'
82
+ AND verified = TRUE
83
+ AND created_at > NOW() - INTERVAL '30 days'
84
+ )
85
+ SELECT
86
+ location,
87
+ hour,
88
+ COUNT(*) as report_count,
89
+ AVG(confidence) as avg_confidence,
90
+ COUNT(*) * AVG(confidence) as weighted_score
91
+ FROM hour_counts
92
+ GROUP BY location, hour
93
+ HAVING COUNT(*) >= 2 -- At least 2 reports
94
+ ORDER BY location, weighted_score DESC;
95
+
96
+ -- ============================================================================
97
+ -- FUNCTION: Get Alpha Insights for Location
98
+ -- ============================================================================
99
+ CREATE OR REPLACE FUNCTION get_alpha_insights(
100
+ p_location VARCHAR(255),
101
+ p_min_confidence DECIMAL DEFAULT 0.6
102
+ )
103
+ RETURNS TABLE (
104
+ contribution_type VARCHAR(20),
105
+ start_hour INTEGER,
106
+ end_hour INTEGER,
107
+ confidence DECIMAL(4, 3),
108
+ report_count BIGINT,
109
+ latest_report TIMESTAMP
110
+ ) AS $$
111
+ BEGIN
112
+ RETURN QUERY
113
+ SELECT
114
+ ac.contribution_type,
115
+ ac.start_hour,
116
+ ac.end_hour,
117
+ AVG(ac.confidence)::DECIMAL(4, 3) as confidence,
118
+ COUNT(*) as report_count,
119
+ MAX(ac.created_at) as latest_report
120
+ FROM alpha_contributions ac
121
+ WHERE ac.location ILIKE '%' || p_location || '%'
122
+ AND ac.verified = TRUE
123
+ AND ac.confidence >= p_min_confidence
124
+ AND ac.created_at > NOW() - INTERVAL '30 days'
125
+ GROUP BY ac.contribution_type, ac.start_hour, ac.end_hour
126
+ ORDER BY report_count DESC, confidence DESC
127
+ LIMIT 10;
128
+ END;
129
+ $$ LANGUAGE plpgsql;
130
+
131
+ -- ============================================================================
132
+ -- FUNCTION: Calculate Contribution Quality Score
133
+ -- ============================================================================
134
+ CREATE OR REPLACE FUNCTION calculate_contribution_quality(
135
+ p_agent_name VARCHAR(255)
136
+ )
137
+ RETURNS TABLE (
138
+ agent_name VARCHAR(255),
139
+ total_contributions BIGINT,
140
+ verified_contributions BIGINT,
141
+ avg_confidence DECIMAL(4, 3),
142
+ quality_score DECIMAL(6, 2)
143
+ ) AS $$
144
+ BEGIN
145
+ RETURN QUERY
146
+ SELECT
147
+ p_agent_name::VARCHAR(255),
148
+ COUNT(*)::BIGINT as total_contributions,
149
+ COUNT(CASE WHEN verified = TRUE THEN 1 END)::BIGINT as verified_contributions,
150
+ AVG(confidence)::DECIMAL(4, 3) as avg_confidence,
151
+ (
152
+ (COUNT(CASE WHEN verified = TRUE THEN 1 END)::DECIMAL / NULLIF(COUNT(*), 0)) * 100 +
153
+ (AVG(confidence) * 100)
154
+ )::DECIMAL(6, 2) as quality_score
155
+ FROM alpha_contributions
156
+ WHERE agent_name = p_agent_name;
157
+ END;
158
+ $$ LANGUAGE plpgsql;
159
+
160
+ -- ============================================================================
161
+ -- COMMENTS for documentation
162
+ -- ============================================================================
163
+ COMMENT ON TABLE alpha_contributions IS 'Stores verified local energy pricing knowledge from agents';
164
+ COMMENT ON COLUMN alpha_contributions.verified IS 'TRUE if contribution matches actual pricing data';
165
+ COMMENT ON COLUMN alpha_contributions.confidence IS 'Confidence score 0-1, higher means better match with actual data';
166
+ COMMENT ON COLUMN alpha_contributions.verification_reasons IS 'Array of reasons explaining verification result';
167
+
168
+ -- ============================================================================
169
+ -- Sample queries for testing
170
+ -- ============================================================================
171
+
172
+ -- Get all verified contributions for a location
173
+ -- SELECT * FROM verified_alpha_contributions WHERE location ILIKE '%mumbai%';
174
+
175
+ -- Get alpha insights for a specific location
176
+ -- SELECT * FROM get_alpha_insights('Mumbai', 0.7);
177
+
178
+ -- Get contribution quality for an agent
179
+ -- SELECT * FROM calculate_contribution_quality('Agent-X8DOAO');
180
+
181
+ -- Get peak hours analysis
182
+ -- SELECT * FROM peak_hours_analysis WHERE location ILIKE '%india%';
183
+
184
+ -- Get alpha summary by location
185
+ -- SELECT * FROM alpha_by_location ORDER BY verified_count DESC LIMIT 10;
@@ -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
@@ -53,6 +53,7 @@ const CYCLE_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes
53
53
  let isRunning = true;
54
54
  let totalRuns = 0;
55
55
  let stakeTransactionSignature = null;
56
+ let pendingAlphaContribution = null; // Store alpha contribution for next submission
56
57
 
57
58
  // Readline interface for prompts
58
59
  const rl = createInterface({
@@ -172,16 +173,26 @@ async function main(options) {
172
173
  const detectedLocation = await getLocation();
173
174
  locationSpinner.succeed(chalk.green(`📍 Detected Location: ${detectedLocation}`));
174
175
 
175
- // Ask user to confirm or override (no timeout - wait for input)
176
+ // Ask user to confirm or override with 10 second timeout
176
177
  console.log(chalk.blue('\nYou can use this location or enter a custom one.'));
177
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'));
178
180
 
179
181
  try {
180
- const customLocation = await question('Enter location (or press Enter for auto-detected): ');
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
+ ]);
181
192
 
182
193
  // Use custom location if provided, otherwise use detected
183
- location = customLocation.trim() || detectedLocation;
184
- if (customLocation.trim()) {
194
+ location = (customLocation || '').trim() || detectedLocation;
195
+ if ((customLocation || '').trim()) {
185
196
  console.log(chalk.green(`✓ Using custom location: ${location}`));
186
197
  } else {
187
198
  console.log(chalk.green(`✓ Using detected location: ${location}`));
@@ -341,14 +352,22 @@ async function runQueryCycle(wallet, agentName, location, options) {
341
352
  console.log(chalk.blue(`\n🌐 Dashboard API URL: ${apiUrl}`));
342
353
  console.log(chalk.gray(`📤 Submitting data...`));
343
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
+
344
367
  const response = await fetch(apiUrl, {
345
368
  method: 'POST',
346
369
  headers: { 'Content-Type': 'application/json' },
347
- body: JSON.stringify({
348
- ...submissionData,
349
- wallet: wallet, // wallet parameter from runQueryCycle
350
- run_number: totalRuns
351
- })
370
+ body: JSON.stringify(dashboardPayload)
352
371
  });
353
372
 
354
373
  const responseText = await response.text();
@@ -358,6 +377,12 @@ async function runQueryCycle(wallet, agentName, location, options) {
358
377
  dashboardSpinner.succeed(chalk.green('✅ Dashboard submission successful!'));
359
378
  console.log(chalk.green(`🎉 Data should now appear at: https://decharge-scout.vercel.app/agentone`));
360
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
+ }
361
386
  } else {
362
387
  dashboardSpinner.warn(chalk.yellow(`⚠️ Dashboard API returned: ${response.status}`));
363
388
  console.log(chalk.yellow(`Response: ${responseText}`));
@@ -420,6 +445,17 @@ async function runQueryCycle(wallet, agentName, location, options) {
420
445
  // Save contribution with verification status
421
446
  const contribution = saveAlphaContribution(parsed, agentName, location, verification);
422
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
+
423
459
  // Calculate bonus (higher for verified contributions)
424
460
  const alphaBonus = calculateAlphaBonus(parsed, verification);
425
461
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "decharge-scout",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "Global Energy Scout - Weather-powered intelligent energy price forecasting with Solana integration",
5
5
  "main": "index.js",
6
6
  "type": "module",