bulltrackers-module 1.0.269 → 1.0.270

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.
@@ -1,20 +1,48 @@
1
1
  /**
2
2
  * @fileoverview Build Reporter & Auto-Runner.
3
3
  * Generates a "Pre-Flight" report of what the computation system WILL do.
4
- * UPDATED: Shards report details to subcollections to bypass 1MB limit.
5
- * UPDATED: Explicitly reports SKIPPED items for 100% visibility.
4
+ * REFACTORED: Strict 5-category reporting with date-based exclusion logic.
5
+ * UPDATED: Added meta stats to compare Included vs Expected computation counts per date.
6
6
  */
7
7
 
8
8
  const { analyzeDateExecution } = require('../WorkflowOrchestrator');
9
9
  const { fetchComputationStatus } = require('../persistence/StatusRepository');
10
10
  const { normalizeName, getExpectedDateStrings, DEFINITIVE_EARLIEST_DATES } = require('../utils/utils');
11
11
  const { checkRootDataAvailability } = require('../data/AvailabilityChecker');
12
- const { commitBatchInChunks } = require('../persistence/FirestoreUtils'); // Reuse chunker
12
+ const { commitBatchInChunks } = require('../persistence/FirestoreUtils');
13
13
  const pLimit = require('p-limit');
14
14
  const path = require('path');
15
15
  const packageJson = require(path.join(__dirname, '..', '..', '..', 'package.json'));
16
16
  const packageVersion = packageJson.version;
17
17
 
18
+ /**
19
+ * Helper: Determines if a calculation should be excluded from the report
20
+ * because the date is prior to the earliest possible data existence.
21
+ */
22
+ function isDateBeforeAvailability(dateStr, calcManifest) {
23
+ const targetDate = new Date(dateStr + 'T00:00:00Z');
24
+ const deps = calcManifest.rootDataDependencies || [];
25
+
26
+ // If no data dependencies, it's always valid (e.g., pure math)
27
+ if (deps.length === 0) return false;
28
+
29
+ for (const dep of deps) {
30
+ // Map dependency name to start date
31
+ let startDate = null;
32
+ if (dep === 'portfolio') startDate = DEFINITIVE_EARLIEST_DATES.portfolio;
33
+ else if (dep === 'history') startDate = DEFINITIVE_EARLIEST_DATES.history;
34
+ else if (dep === 'social') startDate = DEFINITIVE_EARLIEST_DATES.social;
35
+ else if (dep === 'insights') startDate = DEFINITIVE_EARLIEST_DATES.insights;
36
+ else if (dep === 'price') startDate = DEFINITIVE_EARLIEST_DATES.price;
37
+
38
+ // If we have a start date and the target is BEFORE it, exclude this calc.
39
+ if (startDate && targetDate < startDate) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
44
+ }
45
+
18
46
  /**
19
47
  * AUTO-RUN ENTRY POINT
20
48
  */
@@ -57,17 +85,17 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
57
85
  const datesToCheck = getExpectedDateStrings(startDate, today);
58
86
  const manifestMap = new Map(manifest.map(c => [normalizeName(c.name), c]));
59
87
 
60
- // Main Report Header (Summary Only)
88
+ // Main Report Header
61
89
  const reportHeader = {
62
90
  buildId,
63
91
  packageVersion: packageVersion,
64
92
  generatedAt: new Date().toISOString(),
65
- summary: {},
93
+ summary: {},
66
94
  _sharded: true
67
95
  };
68
96
 
69
- let totalReRuns = 0;
70
- let totalNew = 0;
97
+ let totalRun = 0;
98
+ let totalReRun = 0;
71
99
  const detailWrites = [];
72
100
 
73
101
  const limit = pLimit(20);
@@ -97,31 +125,78 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
97
125
 
98
126
  const analysis = analyzeDateExecution(dateStr, manifest, rootDataStatus, dailyStatus, manifestMap, prevDailyStatus);
99
127
 
100
- // [NEW] Added 'skipped' to the summary object
101
- const dateSummary = { willRun: [], willReRun: [], blocked: [], impossible: [], skipped: [] };
102
-
103
- analysis.runnable.forEach (item => dateSummary.willRun.push ({ name: item.name, reason: "New / No Previous Record" }));
104
- analysis.reRuns.forEach (item => dateSummary.willReRun.push ({ name: item.name, reason: item.reason || "Hash Mismatch" }));
105
- analysis.impossible.forEach (item => dateSummary.impossible.push ({ name: item.name, reason: item.reason }));
106
- [...analysis.blocked, ...analysis.failedDependency].forEach(item => dateSummary.blocked.push({ name: item.name, reason: item.reason || 'Dependency' }));
107
-
108
- // [NEW] Map skipped items so the math adds up to 92
109
- analysis.skipped.forEach (item => dateSummary.skipped.push ({ name: item.name, reason: item.reason || "Up To Date" }));
110
-
111
- // Update: We write the report if there is ANY data, not just updates
112
- // This ensures full visibility into the day's state
113
- if (dateSummary.willRun.length || dateSummary.willReRun.length || dateSummary.blocked.length || dateSummary.impossible.length || dateSummary.skipped.length) {
114
- const detailRef = db.collection('computation_build_records').doc(buildId).collection('details').doc(dateStr);
115
- detailWrites.push({
116
- ref: detailRef,
117
- data: dateSummary
118
- });
119
-
120
- return {
121
- stats: { new: dateSummary.willRun.length, rerun: dateSummary.willReRun.length }
122
- };
128
+ // ---------------------------------------------------------
129
+ // STRICT 5-CATEGORY MAPPING
130
+ // ---------------------------------------------------------
131
+ const dateSummary = {
132
+ run: [], // New / No Hash / "Runnable"
133
+ rerun: [], // Hash Mismatch / Category Migration
134
+ blocked: [], // Missing Data (Today) / Dependency Missing
135
+ impossible: [], // Missing Data (Historical) / Impossible Dependency
136
+ uptodate: [], // Hash Match (Previously "Skipped")
137
+
138
+ // [NEW] Metadata for Verification
139
+ meta: {
140
+ totalIncluded: 0,
141
+ totalExpected: 0,
142
+ match: false
143
+ }
144
+ };
145
+
146
+ // Calculate Expected Count (Computations in manifest that exist for this date)
147
+ const expectedCount = manifest.filter(c => !isDateBeforeAvailability(dateStr, c)).length;
148
+ dateSummary.meta.totalExpected = expectedCount;
149
+
150
+ // Helper to push only if date is valid for this specific calc
151
+ const pushIfValid = (targetArray, item, extraReason = null) => {
152
+ const calcManifest = manifestMap.get(item.name);
153
+ if (calcManifest && isDateBeforeAvailability(dateStr, calcManifest)) {
154
+ return; // EXCLUDED: Date is before data exists
155
+ }
156
+ targetArray.push({ name: item.name, reason: item.reason || extraReason });
157
+ };
158
+
159
+ // 1. RUN (New)
160
+ analysis.runnable.forEach(item => pushIfValid(dateSummary.run, item, "New Calculation"));
161
+
162
+ // 2. RE-RUN (Hash Mismatch)
163
+ analysis.reRuns.forEach(item => pushIfValid(dateSummary.rerun, item, "Hash Mismatch"));
164
+
165
+ // 3. BLOCKED (Temporary Issues)
166
+ // Merging 'blocked' and 'failedDependency' as both are temporary blocks
167
+ analysis.blocked.forEach(item => pushIfValid(dateSummary.blocked, item));
168
+ analysis.failedDependency.forEach(item => pushIfValid(dateSummary.blocked, item, "Dependency Missing"));
169
+
170
+ // 4. IMPOSSIBLE (Permanent Issues)
171
+ analysis.impossible.forEach(item => pushIfValid(dateSummary.impossible, item));
172
+
173
+ // 5. UP-TO-DATE (Previously "Skipped")
174
+ analysis.skipped.forEach(item => pushIfValid(dateSummary.uptodate, item, "Up To Date"));
175
+
176
+ // Calculate Included Count
177
+ const includedCount = dateSummary.run.length +
178
+ dateSummary.rerun.length +
179
+ dateSummary.blocked.length +
180
+ dateSummary.impossible.length +
181
+ dateSummary.uptodate.length;
182
+
183
+ dateSummary.meta.totalIncluded = includedCount;
184
+ dateSummary.meta.match = (includedCount === expectedCount);
185
+
186
+ if (!dateSummary.meta.match) {
187
+ logger.log('WARN', `[BuildReporter] ⚠️ Mismatch on ${dateStr}: Expected ${expectedCount} but got ${includedCount}.`);
123
188
  }
124
- return null;
189
+
190
+ // ALWAYS WRITE THE REPORT (No filtering based on activity)
191
+ const detailRef = db.collection('computation_build_records').doc(buildId).collection('details').doc(dateStr);
192
+ detailWrites.push({
193
+ ref: detailRef,
194
+ data: dateSummary
195
+ });
196
+
197
+ return {
198
+ stats: { run: dateSummary.run.length, rerun: dateSummary.rerun.length }
199
+ };
125
200
 
126
201
  } catch (err) {
127
202
  logger.log('ERROR', `[BuildReporter] Error analyzing date ${dateStr}: ${err.message}`);
@@ -133,12 +208,16 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
133
208
 
134
209
  results.forEach(res => {
135
210
  if (res) {
136
- totalNew += res.stats.new;
137
- totalReRuns += res.stats.rerun;
211
+ totalRun += res.stats.run;
212
+ totalReRun += res.stats.rerun;
138
213
  }
139
214
  });
140
215
 
141
- reportHeader.summary = { totalReRuns, totalNew, scanRange: `${datesToCheck[0]} to ${datesToCheck[datesToCheck.length-1]}` };
216
+ reportHeader.summary = {
217
+ totalReRuns: totalReRun,
218
+ totalNew: totalRun,
219
+ scanRange: `${datesToCheck[0]} to ${datesToCheck[datesToCheck.length-1]}`
220
+ };
142
221
 
143
222
  const reportRef = db.collection('computation_build_records').doc(buildId);
144
223
  await reportRef.set(reportHeader);
@@ -150,7 +229,7 @@ async function generateBuildReport(config, dependencies, manifest, daysBack = 90
150
229
 
151
230
  await db.collection('computation_build_records').doc('latest').set({ ...reportHeader, note: "Latest build report pointer (See subcollection for details)." });
152
231
 
153
- logger.log('SUCCESS', `[BuildReporter] Report ${buildId} saved. Re-runs: ${totalReRuns}, New: ${totalNew}.`);
232
+ logger.log('SUCCESS', `[BuildReporter] Report ${buildId} saved. Re-runs: ${totalReRun}, New: ${totalRun}.`);
154
233
 
155
234
  return {
156
235
  success: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.269",
3
+ "version": "1.0.270",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [