bulltrackers-module 1.0.193 → 1.0.194
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.
|
@@ -20,6 +20,57 @@ const pLimit = require('p-limit'); // Ensure p-limit is required
|
|
|
20
20
|
*/
|
|
21
21
|
function groupByPass(manifest) { return manifest.reduce((acc, calc) => { (acc[calc.pass] = acc[calc.pass] || []).push(calc); return acc; }, {}); }
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* --- NEW HELPER: PASSIVE DATA VALIDATION ---
|
|
25
|
+
* Scans a result set for suspicious patterns (e.g., a field is NULL for 100% of tickers).
|
|
26
|
+
* Logs warnings but DOES NOT block the commit.
|
|
27
|
+
*/
|
|
28
|
+
function validateResultPatterns(logger, calcName, results, category) {
|
|
29
|
+
// 1. Skip Speculators (Too sparse, nulls are expected)
|
|
30
|
+
if (category === 'speculator' || category === 'speculators') return;
|
|
31
|
+
|
|
32
|
+
const tickers = Object.keys(results);
|
|
33
|
+
const totalItems = tickers.length;
|
|
34
|
+
|
|
35
|
+
// 2. Need a decent sample size to judge patterns
|
|
36
|
+
if (totalItems < 5) return;
|
|
37
|
+
|
|
38
|
+
// 3. Get all keys from the first valid object
|
|
39
|
+
// We assume schema is roughly consistent across tickers
|
|
40
|
+
const sampleTicker = tickers.find(t => results[t] && typeof results[t] === 'object');
|
|
41
|
+
if (!sampleTicker) return;
|
|
42
|
+
|
|
43
|
+
const keys = Object.keys(results[sampleTicker]);
|
|
44
|
+
|
|
45
|
+
keys.forEach(key => {
|
|
46
|
+
// Skip internal keys or metadata
|
|
47
|
+
if (key.startsWith('_')) return;
|
|
48
|
+
|
|
49
|
+
let nullCount = 0;
|
|
50
|
+
let nanCount = 0;
|
|
51
|
+
let undefinedCount = 0;
|
|
52
|
+
|
|
53
|
+
for (const t of tickers) {
|
|
54
|
+
const val = results[t][key];
|
|
55
|
+
if (val === null) nullCount++;
|
|
56
|
+
if (val === undefined) undefinedCount++;
|
|
57
|
+
if (typeof val === 'number' && isNaN(val)) nanCount++;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 4. Define Thresholds
|
|
61
|
+
// If 100% of data is NaN or Undefined, that's almost certainly a bug.
|
|
62
|
+
if (nanCount === totalItems) {
|
|
63
|
+
logger.log('ERROR', `[DataQuality] Calc '${calcName}' field '${key}' is NaN for 100% of ${totalItems} items. Code bug likely.`);
|
|
64
|
+
} else if (undefinedCount === totalItems) {
|
|
65
|
+
logger.log('ERROR', `[DataQuality] Calc '${calcName}' field '${key}' is UNDEFINED for 100% of ${totalItems} items. Code bug likely.`);
|
|
66
|
+
}
|
|
67
|
+
// 5. Nulls are tricky. warn if >90%, but don't error (might be valid logic like "no shorts")
|
|
68
|
+
else if (nullCount > (totalItems * 0.9)) {
|
|
69
|
+
logger.log('WARN', `[DataQuality] Calc '${calcName}' field '${key}' is NULL for ${nullCount}/${totalItems} items. Check logic if this is unexpected.`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
23
74
|
/**
|
|
24
75
|
* Checks if all root data dependencies for a given calculation are met.
|
|
25
76
|
*/
|
|
@@ -277,6 +328,11 @@ async function commitResults(stateObj, dStr, passName, config, deps, skipStatusW
|
|
|
277
328
|
}
|
|
278
329
|
|
|
279
330
|
if (Object.keys(standardRes).length) {
|
|
331
|
+
// --- NEW: Run Passive Validation ---
|
|
332
|
+
// We do this BEFORE marking it completed, but we do NOT stop the write.
|
|
333
|
+
validateResultPatterns(deps.logger, name, standardRes, calc.manifest.category);
|
|
334
|
+
// -----------------------------------
|
|
335
|
+
|
|
280
336
|
standardRes._completed = true;
|
|
281
337
|
writes.push({
|
|
282
338
|
ref: deps.db.collection(config.resultsCollection).doc(dStr)
|