bulltrackers-module 1.0.763 → 1.0.765

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,964 @@
1
+ # **Computation System v2**
2
+
3
+ Developer Manual
4
+
5
+ _A Domain-Agnostic DAG Framework for Data Computations_
6
+
7
+ # Table of Contents
8
+
9
+ This document covers:
10
+
11
+ - System Overview & Architecture
12
+ - Creating New Computations
13
+ - Configuration Reference (getConfig)
14
+ - Data Access & Context
15
+ - Business Rules System
16
+ - Testing & Debugging
17
+ - Best Practices & Common Patterns
18
+
19
+ # 1\. System Overview
20
+
21
+ The Computation System v2 is a domain-agnostic Directed Acyclic Graph (DAG) framework designed for managing complex data transformations and calculations. While the current implementation includes finance-specific business rules for the BullTrackers analytics platform, the framework itself is completely generic and can be adapted to any domain.
22
+
23
+ ## 1.1 Core Principles
24
+
25
+ - Domain Agnostic: The framework has no built-in knowledge of finance or any specific domain
26
+ - Dependency-Driven: Computations declare their dependencies; the system manages execution order
27
+ - Smart Caching: Only re-runs when code, data, or dependencies change
28
+ - Scalable: Supports both in-memory and distributed (worker pool) execution
29
+ - Resilient: Automatic checkpointing and zombie detection for long-running tasks
30
+
31
+ ## 1.2 Key Concepts
32
+
33
+ ### Computations
34
+
35
+ A computation is a single unit of data processing. It:
36
+
37
+ - Declares what data it needs (requirements)
38
+ - Declares which other computations it depends on (dependencies)
39
+ - Implements a process() method containing the transformation logic
40
+ - Produces results that can be stored and used by other computations
41
+
42
+ ### DAG (Directed Acyclic Graph)
43
+
44
+ The system automatically builds a dependency graph from computation declarations. This ensures:
45
+
46
+ - No circular dependencies (acyclic)
47
+ - Correct execution order (topological sort)
48
+ - Parallel execution of independent computations (passes)
49
+
50
+ ### Passes
51
+
52
+ Computations are organized into execution passes based on their dependencies:
53
+
54
+ - Pass 1: Computations with no dependencies (root nodes)
55
+ - Pass 2: Computations that only depend on Pass 1
56
+ - Pass N: Computations whose longest dependency chain is N-1
57
+
58
+ All computations within a pass can run in parallel.
59
+
60
+ ### Business Rules
61
+
62
+ Business rules are domain-specific functions that contain the actual business logic. They are:
63
+
64
+ - Organized into modules (e.g., portfolio, metrics, rankings)
65
+ - Automatically injected into computation contexts
66
+ - Version-tracked (changes trigger re-runs)
67
+ - Completely optional - framework works without them
68
+
69
+ # 2\. Creating New Computations
70
+
71
+ ## 2.1 Basic Structure
72
+
73
+ Every computation follows this template:
74
+
75
+ const { Computation } = require('../framework');
76
+
77
+ class MyComputation extends Computation {
78
+
79
+ static getConfig() {
80
+
81
+ return {
82
+
83
+ name: 'MyComputation',
84
+
85
+ type: 'per-entity',
86
+
87
+ category: 'analytics',
88
+
89
+ requires: { /\* data requirements \*/ },
90
+
91
+ dependencies: \[\]
92
+
93
+ };
94
+
95
+ }
96
+
97
+ async process(context) {
98
+
99
+ const { data, entityId, rules } = context;
100
+
101
+ // Your logic here
102
+
103
+ this.setResult(entityId, result);
104
+
105
+ }
106
+
107
+ }
108
+
109
+ module.exports = MyComputation;
110
+
111
+ ## 2.2 File Location
112
+
113
+ Save your computation file in:
114
+
115
+ computations/YourComputationName.js
116
+
117
+ The framework automatically discovers and loads all .js files in this directory (except those starting with underscore).
118
+
119
+ ## 2.3 Computation Types
120
+
121
+ There are two computation types:
122
+
123
+ ### Global Computations
124
+
125
+ Run once per date, processing all data together.
126
+
127
+ type: 'global'
128
+
129
+ Use cases: System-wide aggregations, rankings, summaries that need the full dataset.
130
+
131
+ async process(context) {
132
+
133
+ const { data } = context;
134
+
135
+ // Process all data at once
136
+
137
+ const result = analyzeAllData(data);
138
+
139
+ this.setGlobalResult(result);
140
+
141
+ }
142
+
143
+ ### Per-Entity Computations
144
+
145
+ Run once for each entity (user, asset, etc.), enabling massive parallelization.
146
+
147
+ type: 'per-entity'
148
+
149
+ Use cases: User-specific calculations, per-asset analytics, individual reports.
150
+
151
+ async process(context) {
152
+
153
+ const { data, entityId, rules } = context;
154
+
155
+ // Process one entity's data
156
+
157
+ const result = calculateForEntity(data, entityId);
158
+
159
+ this.setResult(entityId, result);
160
+
161
+ }
162
+
163
+ # 3\. Configuration Reference (getConfig)
164
+
165
+ The getConfig() method returns an object defining the computation's behavior.
166
+
167
+ ## 3.1 Required Fields
168
+
169
+ | **Field** | **Type** | **Description** | **Example** |
170
+ | --- | --- | --- | --- |
171
+ | name | string | Unique identifier for the computation | 'UserPortfolioSummary' |
172
+ | requires | object | Data requirements specification | See section 3.3 |
173
+ | type | string | Execution mode: 'global' or 'per-entity' | 'per-entity' |
174
+
175
+ ## 3.2 Optional Fields
176
+
177
+ | **Field** | **Type** | **Default** | **Description** |
178
+ | --- | --- | --- | --- |
179
+ | category | string | 'default' | Organizational category |
180
+ | dependencies | array | \[\] | List of computation names this depends on |
181
+ | conditionalDependencies | array | \[\] | Dependencies loaded only when condition is true |
182
+ | schedule | object\|string | 'daily' | When to run: 'daily', 'weekly', 'monthly', or custom |
183
+ | isHistorical | boolean | false | Requires continuous daily execution (no gaps) |
184
+ | isTest | boolean | false | Always re-runs on today's date (for testing) |
185
+ | storage | object | see 3.8 | Where to store results (BigQuery, Firestore) |
186
+ | ttlDays | number | null | Auto-delete results after N days |
187
+
188
+ ## 3.3 Data Requirements (requires)
189
+
190
+ The requires object declares which BigQuery tables the computation needs:
191
+
192
+ requires: {
193
+
194
+ 'portfolio_snapshots': {
195
+
196
+ lookback: 30, // Days of historical data
197
+
198
+ mandatory: true, // Fail if missing
199
+
200
+ fields: \['user_id', 'portfolio_data'\],
201
+
202
+ filter: { user_type: 'POPULAR_INVESTOR' }
203
+
204
+ },
205
+
206
+ 'asset_prices': {
207
+
208
+ lookback: 0, // Today only
209
+
210
+ mandatory: false // Optional data
211
+
212
+ }
213
+
214
+ }
215
+
216
+ ### Requirement Fields
217
+
218
+ | **Field** | **Type** | **Default** | **Description** |
219
+ | --- | --- | --- | --- |
220
+ | lookback | number | 0 | How many days of historical data to fetch (0 = today only) |
221
+ | mandatory | boolean | false | If true, computation fails if data is missing |
222
+ | fields | array | null | Specific columns to fetch (null = all columns) |
223
+ | filter | object | {} | WHERE clause conditions to apply |
224
+
225
+ ## 3.4 Dependencies
226
+
227
+ Dependencies are other computations that must complete first:
228
+
229
+ dependencies: \['UserPortfolioSummary', 'AssetPriceAnalysis'\]
230
+
231
+ The framework:
232
+
233
+ - Ensures dependencies run before this computation
234
+ - Detects circular dependencies at startup
235
+ - Re-runs this computation if a dependency's data changes
236
+ - Makes dependency results available via context.getDependency()
237
+
238
+ ## 3.5 Conditional Dependencies
239
+
240
+ For dependencies that are only needed under certain conditions:
241
+
242
+ conditionalDependencies: \[
243
+
244
+ {
245
+
246
+ computation: 'WeekendAnalysis',
247
+
248
+ condition: ({ date }) => {
249
+
250
+ const day = new Date(date).getUTCDay();
251
+
252
+ return day === 0 || day === 6; // Weekend
253
+
254
+ }
255
+
256
+ }
257
+
258
+ \]
259
+
260
+ The condition function receives { date, config } and returns true/false.
261
+
262
+ ## 3.6 Scheduling
263
+
264
+ Control when the computation runs:
265
+
266
+ // Simple schedules
267
+
268
+ schedule: 'daily' // Every day
269
+
270
+ schedule: 'weekly' // Every Sunday
271
+
272
+ schedule: 'monthly' // First of month
273
+
274
+ // Advanced schedule
275
+
276
+ schedule: {
277
+
278
+ frequency: 'weekly',
279
+
280
+ time: '02:00', // UTC time
281
+
282
+ dayOfWeek: 1, // Monday (0=Sunday)
283
+
284
+ timezone: 'UTC'
285
+
286
+ }
287
+
288
+ ## 3.7 Historical Mode
289
+
290
+ For computations that must run continuously without gaps:
291
+
292
+ isHistorical: true
293
+
294
+ This enables:
295
+
296
+ - Access to previous day's results via context.previousResult
297
+ - Re-run if there's a gap in the daily sequence
298
+ - Version continuity checks (re-runs if code changed mid-sequence)
299
+
300
+ Use cases: Running balances, cumulative metrics, time-series that depend on previous state.
301
+
302
+ ## 3.8 Storage Configuration
303
+
304
+ Control where results are stored:
305
+
306
+ storage: {
307
+
308
+ bigquery: true, // Default: Always store in BigQuery
309
+
310
+ firestore: {
311
+
312
+ enabled: true,
313
+
314
+ path: 'users/{entityId}/summary',
315
+
316
+ merge: true,
317
+
318
+ includeMetadata: true
319
+
320
+ }
321
+
322
+ }
323
+
324
+ Path variables: {entityId}, {date}, {computationName}, {category}
325
+
326
+ # 4\. Data Access & Context
327
+
328
+ The context object passed to process() contains everything the computation needs.
329
+
330
+ ## 4.1 Context Properties
331
+
332
+ | **Property** | **Type** | **Description** |
333
+ | --- | --- | --- |
334
+ | data | object | Fetched table data based on requires configuration |
335
+ | entityId | string | Current entity ID (per-entity computations only) |
336
+ | date | string | Target date in YYYY-MM-DD format |
337
+ | rules | object | Injected business rules modules |
338
+ | references | object | Global reference data (lookups, mappings) |
339
+ | getDependency | function | Fetch dependency computation results |
340
+ | previousResult | any | Yesterday's result (isHistorical only) |
341
+ | computation | object | Metadata about this computation |
342
+ | config | object | System configuration (read-only) |
343
+
344
+ ## 4.2 Accessing Table Data
345
+
346
+ Data is organized by table name, then by entity ID (for entity-keyed tables):
347
+
348
+ async process(context) {
349
+
350
+ const { data, entityId } = context;
351
+
352
+ // Per-entity table: Get current entity's data
353
+
354
+ const portfolio = data\['portfolio_snapshots'\];
355
+
356
+ // portfolio is already filtered to this entityId
357
+
358
+ // Global table: All entities
359
+
360
+ const prices = data\['asset_prices'\];
361
+
362
+ // prices = { instrumentId1: priceData, ... }
363
+
364
+ }
365
+
366
+ ### Data Structures
367
+
368
+ With lookback = 0 (today only):
369
+
370
+ data\['table_name'\] = {
371
+
372
+ 'entity1': { row data },
373
+
374
+ 'entity2': { row data }
375
+
376
+ }
377
+
378
+ With lookback > 0 (historical data):
379
+
380
+ data\['table_name'\] = {
381
+
382
+ 'entity1': \[
383
+
384
+ { date: '2026-01-01', ...data },
385
+
386
+ { date: '2026-01-02', ...data }
387
+
388
+ \]
389
+
390
+ }
391
+
392
+ ## 4.3 Using getDependency()
393
+
394
+ Access results from dependency computations:
395
+
396
+ async process(context) {
397
+
398
+ const { getDependency, entityId } = context;
399
+
400
+ // Get this entity's result from dependency
401
+
402
+ const summary = getDependency('UserPortfolioSummary');
403
+
404
+ // Or explicitly specify entity ID
405
+
406
+ const summary = getDependency('UserPortfolioSummary', entityId);
407
+
408
+ // Cross-entity lookup
409
+
410
+ const otherUser = getDependency('UserPortfolioSummary', 'user-456');
411
+
412
+ }
413
+
414
+ ## 4.4 Reference Data
415
+
416
+ Reference data is loaded once and shared across all entities:
417
+
418
+ async process(context) {
419
+
420
+ const { references } = context;
421
+
422
+ // Access pre-loaded lookup tables
423
+
424
+ const sectorMap = references\['sector_mappings'\];
425
+
426
+ const sector = sectorMap\['AAPL'\];
427
+
428
+ }
429
+
430
+ Configure in bulltrackers.config.js:
431
+
432
+ referenceData: \['sector_mappings', 'ticker_mappings'\]
433
+
434
+ # 5\. Business Rules System
435
+
436
+ Business rules separate domain logic from computation orchestration. They are completely optional - the framework works without them.
437
+
438
+ ## 5.1 Using Rules
439
+
440
+ Rules are automatically injected into the computation context:
441
+
442
+ async process(context) {
443
+
444
+ const { data, rules, entityId } = context;
445
+
446
+ // Extract data using rules
447
+
448
+ const portfolioData = rules.portfolio.extractPortfolioData(
449
+
450
+ data\['portfolio_snapshots'\]
451
+
452
+ );
453
+
454
+ const positions = rules.portfolio.extractPositions(portfolioData);
455
+
456
+ // Calculate metrics using rules
457
+
458
+ const totalValue = rules.portfolio.calculateTotalValue(positions);
459
+
460
+ const exposure = rules.portfolio.getExposure(portfolioData);
461
+
462
+ this.setResult(entityId, { totalValue, exposure });
463
+
464
+ }
465
+
466
+ ## 5.2 Available Rule Modules (BullTrackers)
467
+
468
+ The current implementation includes finance-specific rules:
469
+
470
+ | **Module** | **Purpose** | **Example Functions** |
471
+ | --- | --- | --- |
472
+ | rules.portfolio | Portfolio data extraction and analysis | extractPositions, calculateTotalValue, groupBySector |
473
+ | rules.metrics | Financial metrics calculations | calculateSharpeRatio, calculateMaxDrawdown, calculateVaR |
474
+ | rules.rankings | PI rankings data | getRiskScore, getAUM, calculateQualityScore |
475
+ | rules.trades | Trade history analysis | extractTrades, calculateTradeStats, filterProfitable |
476
+ | rules.social | Social engagement metrics | extractPosts, calculateEngagement, getAverageRating |
477
+ | rules.instruments | Asset and price data | getPrice, calculateSentimentScore, buildTickerMaps |
478
+
479
+ ## 5.3 Creating Custom Rules
480
+
481
+ To add new business rules:
482
+
483
+ - Create a new file in rules/ directory
484
+
485
+ // rules/mymodule.js
486
+
487
+ function calculateSomething(data) {
488
+
489
+ // Your logic
490
+
491
+ return result;
492
+
493
+ }
494
+
495
+ module.exports = { calculateSomething };
496
+
497
+ - Register in rules/index.js
498
+
499
+ const mymodule = require('./mymodule');
500
+
501
+ module.exports = {
502
+
503
+ portfolio,
504
+
505
+ metrics,
506
+
507
+ mymodule // Add here
508
+
509
+ };
510
+
511
+ - Use in computations
512
+
513
+ const result = rules.mymodule.calculateSomething(data);
514
+
515
+ ## 5.4 Rule Versioning
516
+
517
+ Rules are automatically version-tracked:
518
+
519
+ - Each rule function is hashed based on its source code
520
+ - Computations using a rule include its hash in their version
521
+ - Changing a rule triggers re-runs of dependent computations
522
+ - Only rules actually used (detected via code analysis) affect versioning
523
+
524
+ This ensures computation results stay fresh when business logic evolves.
525
+
526
+ # 6\. Testing & Debugging
527
+
528
+ ## 6.1 Using the Admin Test Endpoint
529
+
530
+ The system includes a protected admin endpoint for testing:
531
+
532
+ curl -X POST <https://REGION-PROJECT.cloudfunctions.net/compute-admin-test> \\
533
+
534
+ \-H "Authorization: Bearer \$(gcloud auth print-identity-token)" \\
535
+
536
+ \-H "Content-Type: application/json" \\
537
+
538
+ \-d '{"action": "run", "computation": "MyComputation"}'
539
+
540
+ ### Available Actions
541
+
542
+ | **Action** | **Purpose** | **Parameters** |
543
+ | --- | --- | --- |
544
+ | status | List all computations | None |
545
+ | analyze | Check what would run for a date | date |
546
+ | run | Execute a computation | computation, date, force, dryRun |
547
+ | run_limited | Run on sample entities only | computation, limit |
548
+ | test_worker | Test worker logic locally | computation, entityIds |
549
+
550
+ ## 6.2 Development Workflow
551
+
552
+ - Create your computation file
553
+ - Deploy to Cloud Functions
554
+ - Test with admin endpoint
555
+
556
+ {
557
+
558
+ "action": "run_limited",
559
+
560
+ "computation": "MyComputation",
561
+
562
+ "limit": 5,
563
+
564
+ "dryRun": true
565
+
566
+ }
567
+
568
+ - Verify results in BigQuery
569
+ - Iterate as needed
570
+
571
+ ## 6.3 Testing Strategies
572
+
573
+ ### Test Mode
574
+
575
+ Mark computations as test to always re-run on today:
576
+
577
+ isTest: true
578
+
579
+ Perfect for development - runs every time without manual force flags.
580
+
581
+ ### Dry Run
582
+
583
+ Execute logic without saving results:
584
+
585
+ {
586
+
587
+ "action": "run",
588
+
589
+ "computation": "MyComputation",
590
+
591
+ "dryRun": true
592
+
593
+ }
594
+
595
+ ### Entity Filtering
596
+
597
+ Test on specific entities:
598
+
599
+ {
600
+
601
+ "action": "run",
602
+
603
+ "computation": "MyComputation",
604
+
605
+ "entityIds": \["user-123", "user-456"\]
606
+
607
+ }
608
+
609
+ ## 6.4 Debugging Tips
610
+
611
+ - Use this.log() for logging (respects computation name prefix)
612
+
613
+ this.log('INFO', 'Processing entity: ' + entityId);
614
+
615
+ - Check BigQuery for stored results
616
+ - Verify dependencies completed successfully
617
+ - Use analyze action to understand scheduling
618
+ - Review Cloud Function logs for errors
619
+
620
+ # 7\. Best Practices & Common Patterns
621
+
622
+ ## 7.1 Computation Design
623
+
624
+ ### DO: Keep Computations Simple
625
+
626
+ Computations should be thin orchestration layers:
627
+
628
+ // GOOD: Simple orchestration
629
+
630
+ async process(context) {
631
+
632
+ const { data, rules, entityId } = context;
633
+
634
+ const portfolio = rules.portfolio.extractPortfolioData(data);
635
+
636
+ const positions = rules.portfolio.extractPositions(portfolio);
637
+
638
+ const summary = rules.portfolio.summarize(positions);
639
+
640
+ this.setResult(entityId, summary);
641
+
642
+ }
643
+
644
+ ### DON'T: Implement Complex Logic
645
+
646
+ // BAD: Complex logic in computation
647
+
648
+ async process(context) {
649
+
650
+ const { data } = context;
651
+
652
+ // 100 lines of complex calculation logic here...
653
+
654
+ // This should be in business rules instead!
655
+
656
+ }
657
+
658
+ ## 7.2 Dependency Management
659
+
660
+ ### DO: Declare All Dependencies
661
+
662
+ Always list computations you depend on:
663
+
664
+ dependencies: \['UserPortfolioSummary', 'AssetPriceAnalysis'\]
665
+
666
+ ### DON'T: Create Circular Dependencies
667
+
668
+ The framework will detect and reject these at startup:
669
+
670
+ // A depends on B
671
+
672
+ // B depends on C
673
+
674
+ // C depends on A <- CIRCULAR! Will fail
675
+
676
+ ## 7.3 Data Requirements
677
+
678
+ ### DO: Use Lookback Wisely
679
+
680
+ Only request the data you need:
681
+
682
+ requires: {
683
+
684
+ 'portfolio_snapshots': {
685
+
686
+ lookback: 30 // Only if you need 30 days
687
+
688
+ }
689
+
690
+ }
691
+
692
+ Note: The system has a MAX_LOOKBACK_DAYS limit (currently 30) to prevent cost spirals.
693
+
694
+ ### DO: Use Field Selection
695
+
696
+ Reduce data transfer by selecting only needed columns:
697
+
698
+ requires: {
699
+
700
+ 'portfolio_snapshots': {
701
+
702
+ fields: \['user_id', 'portfolio_data', 'date'\]
703
+
704
+ }
705
+
706
+ }
707
+
708
+ ## 7.4 Error Handling
709
+
710
+ ### DO: Handle Missing Data Gracefully
711
+
712
+ async process(context) {
713
+
714
+ const { data, entityId } = context;
715
+
716
+ const portfolio = data\['portfolio_snapshots'\];
717
+
718
+ if (!portfolio) {
719
+
720
+ this.log('WARN', 'No portfolio data for ' + entityId);
721
+
722
+ return; // Skip this entity
723
+
724
+ }
725
+
726
+ // Process...
727
+
728
+ }
729
+
730
+ ### DO: Use Mandatory for Critical Data
731
+
732
+ requires: {
733
+
734
+ 'portfolio_snapshots': {
735
+
736
+ mandatory: true // Fail if missing
737
+
738
+ }
739
+
740
+ }
741
+
742
+ ## 7.5 Performance
743
+
744
+ ### DO: Use Per-Entity for Scalability
745
+
746
+ For user/entity-specific calculations, always use per-entity mode:
747
+
748
+ type: 'per-entity'
749
+
750
+ Benefits:
751
+
752
+ - Automatic parallelization
753
+ - Memory-efficient streaming
754
+ - Worker pool support
755
+ - Checkpointing and resume
756
+
757
+ ### DON'T: Load Everything Into Memory
758
+
759
+ The framework handles batching - don't try to load all entities yourself.
760
+
761
+ ## 7.6 Common Patterns
762
+
763
+ ### Pattern: Aggregation
764
+
765
+ // Create per-entity computation first
766
+
767
+ class UserMetrics extends Computation {
768
+
769
+ static getConfig() {
770
+
771
+ return { name: 'UserMetrics', type: 'per-entity', ... };
772
+
773
+ }
774
+
775
+ }
776
+
777
+ // Then create global aggregation
778
+
779
+ class SystemSummary extends Computation {
780
+
781
+ static getConfig() {
782
+
783
+ return {
784
+
785
+ name: 'SystemSummary',
786
+
787
+ type: 'global',
788
+
789
+ dependencies: \['UserMetrics'\]
790
+
791
+ };
792
+
793
+ }
794
+
795
+ async process(context) {
796
+
797
+ const userMetrics = context.getDependency('UserMetrics');
798
+
799
+ // Aggregate all user results
800
+
801
+ }
802
+
803
+ }
804
+
805
+ ### Pattern: Time Series
806
+
807
+ class RunningBalance extends Computation {
808
+
809
+ static getConfig() {
810
+
811
+ return {
812
+
813
+ name: 'RunningBalance',
814
+
815
+ type: 'per-entity',
816
+
817
+ isHistorical: true,
818
+
819
+ requires: { 'transactions': { lookback: 1 } }
820
+
821
+ };
822
+
823
+ }
824
+
825
+ async process(context) {
826
+
827
+ const { previousResult, data, entityId } = context;
828
+
829
+ const prevBalance = previousResult?.balance || 0;
830
+
831
+ const todayChange = calculateChange(data);
832
+
833
+ this.setResult(entityId, {
834
+
835
+ balance: prevBalance + todayChange
836
+
837
+ });
838
+
839
+ }
840
+
841
+ }
842
+
843
+ # Appendix A: Quick Reference
844
+
845
+ ## Computation Template
846
+
847
+ const { Computation } = require('../framework');
848
+
849
+ class MyComputation extends Computation {
850
+
851
+ static getConfig() {
852
+
853
+ return {
854
+
855
+ name: 'MyComputation',
856
+
857
+ type: 'per-entity',
858
+
859
+ category: 'analytics',
860
+
861
+ requires: {
862
+
863
+ 'table_name': {
864
+
865
+ lookback: 0,
866
+
867
+ mandatory: true
868
+
869
+ }
870
+
871
+ },
872
+
873
+ dependencies: \[\],
874
+
875
+ schedule: 'daily'
876
+
877
+ };
878
+
879
+ }
880
+
881
+ async process(context) {
882
+
883
+ const { data, entityId, rules } = context;
884
+
885
+ // Your logic here
886
+
887
+ const result = {};
888
+
889
+ this.setResult(entityId, result);
890
+
891
+ }
892
+
893
+ }
894
+
895
+ module.exports = MyComputation;
896
+
897
+ ## Common Context Properties
898
+
899
+ context.data // Fetched table data
900
+
901
+ context.entityId // Current entity (per-entity only)
902
+
903
+ context.date // Target date (YYYY-MM-DD)
904
+
905
+ context.rules // Business rules modules
906
+
907
+ context.references // Global reference data
908
+
909
+ context.getDependency(name, id) // Get dependency result
910
+
911
+ context.previousResult // Yesterday (isHistorical only)
912
+
913
+ ## Useful Helper Methods
914
+
915
+ this.setResult(entityId, result) // Save result
916
+
917
+ this.setGlobalResult(result) // Save (global only)
918
+
919
+ this.log(level, message) // Log message
920
+
921
+ this.get(obj, 'path.to.prop', default) // Safe access
922
+
923
+ # Appendix B: Framework Architecture
924
+
925
+ The system is built on a layered architecture that separates concerns:
926
+
927
+ ## Layer 1: Core Framework
928
+
929
+ - Computation base class
930
+ - Manifest builder (DAG construction)
931
+ - Orchestrator (execution engine)
932
+ - Rules registry (business logic injection)
933
+
934
+ ## Layer 2: Data Layer
935
+
936
+ - Schema registry (dynamic schema discovery)
937
+ - Query builder (SQL generation)
938
+ - Data fetcher (BigQuery interface)
939
+ - State repository (result caching)
940
+
941
+ ## Layer 3: Storage Layer
942
+
943
+ - Storage manager (BigQuery & Firestore)
944
+ - Checkpointer (resume capability)
945
+ - Lineage tracker (data provenance)
946
+
947
+ ## Layer 4: Execution Layer
948
+
949
+ - Task runner (middleware chain)
950
+ - Remote task runner (worker pool)
951
+ - Profiler middleware (performance tracking)
952
+ - Cost tracker middleware (billing)
953
+
954
+ ## Layer 5: Scheduling Layer
955
+
956
+ - Scheduler (Cloud Tasks integration)
957
+ - Watchdog (zombie detection)
958
+ - Dispatcher (request routing)
959
+
960
+ All layers are domain-agnostic. Business logic lives entirely in the rules modules and computation implementations.
961
+
962
+ # End of Developer Manual
963
+
964
+ _For additional support, refer to the source code documentation or contact the development team._