bulltrackers-module 1.0.196 → 1.0.198

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.
@@ -7,8 +7,9 @@
7
7
  * It validates metadata, resolves dependencies, and performs a topological sort
8
8
  * to determine execution passes.
9
9
  *
10
- * It explicitly skips the 'legacy' package.
11
- * It has removed all logic for deprecated folder structures (e.g., /historical/).
10
+ * UPDATED LOGIC:
11
+ * - Core Computations: Metadata 'category' determines the output path/grouping.
12
+ * - Product Lines: Folder name STRICTLY determines the category (metadata ignored).
12
13
  */
13
14
 
14
15
  /* --------------------------------------------------
@@ -31,7 +32,7 @@ const log = {
31
32
  const normalizeName = (name) => { if (typeof name !== 'string') return name; return name.trim().replace(/,$/, '').replace(/_/g, '-').toLowerCase(); };
32
33
 
33
34
  /**
34
- * Finds the closest string matches for a typo. // https://medium.com/@ethannam/understanding-the-levenshtein-distance-equation-for-beginners-c4285a5604f0
35
+ * Finds the closest string matches for a typo.
35
36
  */
36
37
  function suggestClosest(name, candidates, n = 3) {
37
38
  const levenshtein = (a = '', b = '') => {
@@ -97,30 +98,53 @@ function buildManifest(productLinesToRun = [], calculations) {
97
98
  /* ---------------- 1. Load All Calculations ---------------- */
98
99
  log.step('Loading and validating all calculation classes…');
99
100
  const allCalculationClasses = new Map();
101
+
100
102
  /**
101
103
  * Processes a single calculation class, validates it, and adds to maps.
102
- * --- REFACTOR: Simplified signature, isHistorical is now read from metadata ---
104
+ * @param {Class} Class - The calculation class structure.
105
+ * @param {string} name - The filename/key.
106
+ * @param {string} folderName - The folder this file came from (e.g. 'core', 'gem').
103
107
  */
104
- function processCalc(Class, name, category) {
108
+ function processCalc(Class, name, folderName) {
105
109
  if (!Class || typeof Class !== 'function') return;
106
110
  const normalizedName = normalizeName(name);
107
111
  allCalculationClasses.set(normalizedName, Class);
112
+
108
113
  // --- RULE 1: Check for static getMetadata() ---
109
114
  if (typeof Class.getMetadata !== 'function') { log.fatal(`Calculation "${normalizedName}" is missing the static getMetadata() method. Build FAILED.`); hasFatalError = true; return; }
110
115
  // --- RULE 2: Check for static getDependencies() ---
111
116
  if (typeof Class.getDependencies !== 'function') { log.fatal(`Calculation "${normalizedName}" is missing the static getDependencies() method. Build FAILED.`); hasFatalError = true;return; }
112
117
  // --- RULE 3: Check for static getSchema() ---
113
118
  if (typeof Class.getSchema !== 'function') {log.warn(`Calculation "${normalizedName}" is missing the static getSchema() method. (Recommended)`); }
119
+
114
120
  const metadata = Class.getMetadata();
115
121
  const dependencies = Class.getDependencies().map(normalizeName);
122
+
116
123
  // --- RULE 4: Check for isHistorical mismatch ---
117
- if (metadata.isHistorical === true && !Class.toString().includes('yesterday')) { // UPDATED FOR MATH LAYER, TODO THIS IS A LITTLE BRITTLE, BUT FOR NOW FINE...
124
+ if (metadata.isHistorical === true && !Class.toString().includes('yesterday')) {
118
125
  log.warn(`Calculation "${normalizedName}" is marked 'isHistorical: true' but does not seem to reference 'yesterday' data.`);
119
126
  }
127
+
128
+ // --- RULE 5: Category Enforcement Logic ---
129
+ let finalCategory = folderName; // Default to folder name
130
+
131
+ if (folderName === 'core') {
132
+ // For CORE: We respect metadata.category if it exists.
133
+ // This allows core computations to write to specific output paths (e.g. 'market_stats').
134
+ if (metadata.category) {
135
+ finalCategory = metadata.category;
136
+ }
137
+ } else {
138
+ // For PRODUCT LINES: We IGNORE metadata.category and enforce the folder name.
139
+ // This ensures product lines always group correctly regardless of typos in metadata.
140
+ finalCategory = folderName;
141
+ }
142
+
120
143
  const manifestEntry = {
121
144
  name: normalizedName,
122
145
  class: Class,
123
- category: metadata.category || category,
146
+ category: finalCategory, // The logic category / output path
147
+ sourcePackage: folderName, // The physical source folder (used for "Core Always Run" checks)
124
148
  type: metadata.type,
125
149
  isHistorical: metadata.isHistorical,
126
150
  rootDataDependencies: metadata.rootDataDependencies || [],
@@ -131,19 +155,23 @@ function buildManifest(productLinesToRun = [], calculations) {
131
155
  manifestMap.set(normalizedName, manifestEntry);
132
156
  adjacency.set(normalizedName, dependencies);
133
157
  inDegree.set(normalizedName, dependencies.length);
134
- dependencies.forEach(dep => { if (!reverseAdjacency.has(dep)) reverseAdjacency.set(dep, []); reverseAdjacency.get(dep).push(normalizedName); }); }
158
+ dependencies.forEach(dep => { if (!reverseAdjacency.has(dep)) reverseAdjacency.set(dep, []); reverseAdjacency.get(dep).push(normalizedName); });
159
+ }
135
160
 
136
161
  if (!calculations || typeof calculations !== 'object') {
137
162
  log.fatal('Calculations object was not provided or is invalid.');
138
163
  throw new Error('Manifest build failed: Invalid calculations object.');
139
164
  }
140
165
 
141
- for (const category in calculations) {
142
- if (category === 'legacy') { log.info('Skipping "legacy" calculations package.'); continue; }
143
- const group = calculations[category];
166
+ // Iterate over folders (folderName becomes the default category)
167
+ for (const folderName in calculations) {
168
+ if (folderName === 'legacy') { log.info('Skipping "legacy" calculations package.'); continue; }
169
+ const group = calculations[folderName];
144
170
  for (const key in group) {
145
171
  const entry = group[key];
146
- if (typeof entry === 'function') { processCalc(entry, key, category); } } }
172
+ if (typeof entry === 'function') { processCalc(entry, key, folderName); }
173
+ }
174
+ }
147
175
 
148
176
  if (hasFatalError) { throw new Error('Manifest build failed due to missing static methods in calculations.'); }
149
177
  log.success(`Loaded and validated ${manifestMap.size} total calculations.`);
@@ -171,13 +199,24 @@ function buildManifest(productLinesToRun = [], calculations) {
171
199
 
172
200
  /* ---------------- 3. Filter for Product Lines ---------------- */
173
201
  log.divider('Filtering by Product Line');
174
- // 1. Find all "endpoint" calculations (the final signals) in the target product lines.
175
-
202
+
203
+ // 1. Find all "endpoint" calculations in the target product lines.
204
+ // This checks the assigned category (which now matches the folder for all non-core lines).
176
205
  const productLineEndpoints = [];
177
- for (const [name, entry] of manifestMap.entries()) { if (productLinesToRun.includes(entry.category)) { productLineEndpoints.push(name); } }
206
+ for (const [name, entry] of manifestMap.entries()) {
207
+ if (productLinesToRun.includes(entry.category)) {
208
+ productLineEndpoints.push(name);
209
+ }
210
+ }
178
211
 
179
212
  // 2. Add 'core' calculations as they are always included.
180
- for (const [name, entry] of manifestMap.entries()) { if (entry.category === 'core') { productLineEndpoints.push(name); } }
213
+ // We check 'sourcePackage' here so that even if a core calculation has a custom category
214
+ // (like 'market_stats'), it is still recognized as Core and included.
215
+ for (const [name, entry] of manifestMap.entries()) {
216
+ if (entry.sourcePackage === 'core') {
217
+ productLineEndpoints.push(name);
218
+ }
219
+ }
181
220
 
182
221
  // 3. Trace all dependencies upwards from these endpoints.
183
222
  const requiredCalcs = getDependencySet(productLineEndpoints, adjacency);
@@ -227,9 +266,6 @@ function buildManifest(productLinesToRun = [], calculations) {
227
266
  return sortedManifest;
228
267
  }
229
268
 
230
-
231
-
232
-
233
269
  /**
234
270
  * Main entry point for building and exporting the manifest.
235
271
  * @param {string[]} productLinesToRun - Array of product line categories (folder names) to build for.
@@ -245,5 +281,4 @@ function build(productLinesToRun, calculations) {
245
281
  }
246
282
  }
247
283
 
248
-
249
- module.exports = { build};
284
+ module.exports = { build };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.196",
3
+ "version": "1.0.198",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [