# Three-Layer Signal Merging ```mermaid flowchart TD subgraph Layer1["Layer 1 — Company Signals"] DIR["document_impact_records\n(per-company extraction output)"] DIR -->|"build_weighted_signals()"| WS1["WeightedSignal[]\nweight = 1.0 (full)"] end subgraph Layer2["Layer 2 — Macro Signals"] MIR["macro_impact_records\n(global event interpolation)"] MIR -->|"build_macro_weighted_signals()"| WS2["WeightedSignal[]\nimpact × MACRO_SIGNAL_WEIGHT\n(0.3)"] TOGGLE_M{"macro_enabled\nin risk_configs?"} TOGGLE_M -->|"true"| MIR TOGGLE_M -->|"false"| SKIP_M["Layer skipped\ngraceful degradation"] end subgraph Layer3["Layer 3 — Competitive Signals"] CSR["competitive_signal_records\n(pattern mining + propagation)"] CSR -->|"build_pattern_weighted_signals()\nservices/aggregation/signal_propagation.py"| WS3["WeightedSignal[]\nimpact × COMPETITIVE_SIGNAL_WEIGHT\n(0.2)"] TOGGLE_C{"competitive_enabled\nin risk_configs?"} TOGGLE_C -->|"true"| CSR TOGGLE_C -->|"false"| SKIP_C["Layer skipped\ngraceful degradation"] end WS1 --> MERGE["Concatenate all WeightedSignal lists"] WS2 --> MERGE WS3 --> MERGE MERGE --> AGG subgraph AGG["Aggregation Engine\nservices/aggregation/worker.py"] A1["weighted_sentiment_average()"] A2["detect_contradictions()\nservices/aggregation/contradiction.py"] A3["derive_trend_direction()"] A4["compute_trend_confidence()"] A5["rank_evidence()"] A1 --> A2 --> A3 --> A4 --> A5 end AGG -->|"assemble_trend_summary()"| TS["TrendSummary\nservices/shared/schemas.py"] TS -->|"persist_trend_summary()"| PG_TREND subgraph PG_TREND["PostgreSQL"] TW["trend_windows\n(upserted each cycle)"] TH["trend_history\n(time-series snapshots)"] TE["trend_evidence\n(per-document rankings)"] end AGG -->|"rpush"| Q_REC["stonks:queue:recommendation"] ```