bulltrackers-module 1.0.327 → 1.0.328

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.
@@ -2,7 +2,7 @@
2
2
  * @fileoverview Admin API Router
3
3
  * Sub-module for system observability, debugging, and visualization.
4
4
  * Mounted at /admin within the Generic API.
5
- * UPDATED: Added advanced cost, performance, and live monitoring endpoints.
5
+ * UPDATED: Added Stale Task Detection, Root Data Health, and SimHash Inspection.
6
6
  */
7
7
 
8
8
  const express = require('express');
@@ -526,6 +526,108 @@ const createAdminRouter = (config, dependencies, unifiedCalculations) => {
526
526
  } catch (e) { res.status(500).json({ error: e.message }); }
527
527
  });
528
528
 
529
+ // =========================================================================
530
+ // NEW DEBUGGING ROUTES (Added 2025-12-19)
531
+ // =========================================================================
532
+
533
+ // --- 11. STALE COMPUTATION DETECTOR ("Zombie Hunter") ---
534
+ router.get('/debug/stale', async (req, res) => {
535
+ const thresholdMins = parseInt(String(req.query.threshold)) || 15;
536
+ const cutoffTime = Date.now() - (thresholdMins * 60 * 1000);
537
+
538
+ try {
539
+ // Scanning collectionGroup 'tasks' is expensive, so we focus on 'IN_PROGRESS'
540
+ // and filter in memory if necessary, or rely on composite index if available.
541
+ const snapshot = await db.collectionGroup('tasks')
542
+ .where('status', '==', 'IN_PROGRESS')
543
+ .get();
544
+
545
+ const zombies = [];
546
+ snapshot.forEach(doc => {
547
+ const data = doc.data();
548
+
549
+ // Determine last activity (Heartbeat > StartedAt > 0)
550
+ const lastActivity = data.telemetry?.lastHeartbeat
551
+ ? new Date(data.telemetry.lastHeartbeat).getTime()
552
+ : (data.startedAt ? new Date(data.startedAt).getTime() : 0);
553
+
554
+ if (lastActivity < cutoffTime) {
555
+ zombies.push({
556
+ computation: data.computation || doc.id,
557
+ workerId: data.workerId || 'unknown',
558
+ startedAt: data.startedAt,
559
+ lastHeartbeat: data.telemetry?.lastHeartbeat,
560
+ stagnantMinutes: Math.floor((Date.now() - lastActivity) / 60000),
561
+ path: doc.ref.path // Useful for manual deletion
562
+ });
563
+ }
564
+ });
565
+
566
+ // Sort by most stagnant
567
+ zombies.sort((a, b) => b.stagnantMinutes - a.stagnantMinutes);
568
+
569
+ res.json({
570
+ count: zombies.length,
571
+ thresholdMins,
572
+ zombies
573
+ });
574
+ } catch (e) {
575
+ res.status(500).json({ error: e.message });
576
+ }
577
+ });
578
+
579
+ // --- 12. ROOT DATA HEALTH CHECK ---
580
+ // Debugs the "Available but marked missing" issue
581
+ router.get('/debug/root-data-health', async (req, res) => {
582
+ const { start, end } = req.query;
583
+ if (!start || !end) return res.status(400).json({ error: "Start and End dates required." });
584
+
585
+ try {
586
+ const startDate = new Date(String(start));
587
+ const endDate = new Date(String(end));
588
+ const dates = [];
589
+ for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
590
+ dates.push(d.toISOString().slice(0, 10));
591
+ }
592
+
593
+ const report = {};
594
+ const limit = pLimit(10);
595
+
596
+ await Promise.all(dates.map(date => limit(async () => {
597
+ const rootDoc = await db.collection('system_root_data_index').doc(date).get();
598
+ if (!rootDoc.exists) {
599
+ report[date] = { exists: false, status: 'MISSING_INDEX' };
600
+ } else {
601
+ const data = rootDoc.data();
602
+ report[date] = {
603
+ exists: true,
604
+ status: 'OK',
605
+ flags: data.status, // hasHistory, hasPortfolio, etc.
606
+ lastUpdated: data.lastUpdated
607
+ };
608
+ }
609
+ })));
610
+
611
+ res.json(report);
612
+ } catch (e) {
613
+ res.status(500).json({ error: e.message });
614
+ }
615
+ });
616
+
617
+ // --- 13. SIMHASH INSPECTOR ---
618
+ // Debugs "Why did this run? Code Changed?"
619
+ router.get('/debug/simhash/:hash', async (req, res) => {
620
+ const { hash } = req.params;
621
+ try {
622
+ const doc = await db.collection('system_simhash_registry').doc(hash).get();
623
+ if (!doc.exists) return res.status(404).json({ error: "Hash not found in registry." });
624
+
625
+ res.json(doc.data());
626
+ } catch (e) {
627
+ res.status(500).json({ error: e.message });
628
+ }
629
+ });
630
+
529
631
  return router;
530
632
  };
531
633
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.327",
3
+ "version": "1.0.328",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [