alpha-engine-lib 0.32.0__py3-none-any.whl
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.
- alpha_engine_lib/__init__.py +3 -0
- alpha_engine_lib/agent_schemas.py +663 -0
- alpha_engine_lib/alerts.py +576 -0
- alpha_engine_lib/arcticdb.py +340 -0
- alpha_engine_lib/collector_results.py +69 -0
- alpha_engine_lib/cost.py +665 -0
- alpha_engine_lib/dates.py +273 -0
- alpha_engine_lib/decision_capture.py +462 -0
- alpha_engine_lib/ec2_spot.py +363 -0
- alpha_engine_lib/email_sender.py +206 -0
- alpha_engine_lib/eval_artifacts.py +361 -0
- alpha_engine_lib/logging.py +303 -0
- alpha_engine_lib/model_pricing.yaml +73 -0
- alpha_engine_lib/pillars.py +756 -0
- alpha_engine_lib/pipeline_status/__init__.py +70 -0
- alpha_engine_lib/pipeline_status/read.py +541 -0
- alpha_engine_lib/pipeline_status/registry.py +368 -0
- alpha_engine_lib/pipeline_status/templates.py +120 -0
- alpha_engine_lib/preflight.py +444 -0
- alpha_engine_lib/rag/__init__.py +39 -0
- alpha_engine_lib/rag/db.py +96 -0
- alpha_engine_lib/rag/embeddings.py +63 -0
- alpha_engine_lib/rag/migrations/0001_content_tsv.sql +39 -0
- alpha_engine_lib/rag/rerank.py +377 -0
- alpha_engine_lib/rag/retrieval.py +465 -0
- alpha_engine_lib/rag/schema.sql +65 -0
- alpha_engine_lib/reconcile.py +203 -0
- alpha_engine_lib/secrets.py +186 -0
- alpha_engine_lib/sources/__init__.py +35 -0
- alpha_engine_lib/sources/protocols.py +227 -0
- alpha_engine_lib/ssm_log_capture.py +274 -0
- alpha_engine_lib/telegram.py +165 -0
- alpha_engine_lib/trading_calendar.py +236 -0
- alpha_engine_lib/transparency.py +746 -0
- alpha_engine_lib/transparency_inventory.yaml +260 -0
- alpha_engine_lib/universe.py +83 -0
- alpha_engine_lib-0.32.0.dist-info/METADATA +217 -0
- alpha_engine_lib-0.32.0.dist-info/RECORD +40 -0
- alpha_engine_lib-0.32.0.dist-info/WHEEL +5 -0
- alpha_engine_lib-0.32.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""SF-state → archive-page registry + substantive-state filter primitives.
|
|
2
|
+
|
|
3
|
+
This module is the single source of truth for two cross-consumer questions:
|
|
4
|
+
|
|
5
|
+
1. **Which SF states are substantive?** — the `Wait*` polling companions and
|
|
6
|
+
bare `Pass` / `Choice` / `Succeed` plumbing should not appear as their
|
|
7
|
+
own rows on the operator console; they're internal control flow. The
|
|
8
|
+
:data:`SUBSTANTIVE_RESOURCES` set + :data:`WAIT_GROUPING` map define
|
|
9
|
+
the filter.
|
|
10
|
+
|
|
11
|
+
2. **Where does each state's persisted artifact live on the dashboard?** —
|
|
12
|
+
each substantive Task state either produces an artifact that has a
|
|
13
|
+
dedicated archive page (deep-link target) OR it's substrate-only. Per
|
|
14
|
+
``feedback_no_silent_fails`` the registry never returns a generic "no
|
|
15
|
+
artifact" placeholder — substrate-only states carry an explicit
|
|
16
|
+
:class:`ArtifactReason` string the page renders verbatim.
|
|
17
|
+
|
|
18
|
+
The registry is materialized as a flat dict-of-dataclasses rather than a
|
|
19
|
+
walked-from-SF-JSON projection because (a) the SF JSONs live in
|
|
20
|
+
``alpha-engine-data`` (cross-repo coupling we want to avoid at the lib
|
|
21
|
+
layer), and (b) the operator-meaningful labels + page slugs are editorial
|
|
22
|
+
choices that don't belong in the SF JSON anyway. A CI test in the
|
|
23
|
+
consuming repo (alpha-engine-dashboard or alpha-engine-data) asserts every
|
|
24
|
+
substantive Task state in the live SF JSONs has a registry entry; that's
|
|
25
|
+
how the two stay in sync without a runtime coupling.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from typing import Final, Optional
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ── Substantive-state filtering (§3.2 of the plan doc) ────────────────────
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
SUBSTANTIVE_RESOURCES: Final[frozenset[str]] = frozenset(
|
|
38
|
+
{
|
|
39
|
+
# Lambda invokes
|
|
40
|
+
"arn:aws:states:::lambda:invoke",
|
|
41
|
+
# SSM sendCommand (EC2 spot + trading instance commands)
|
|
42
|
+
"arn:aws:states:::aws-sdk:ssm:sendCommand",
|
|
43
|
+
# SNS publish (terminal-state emails — kept substantive so the
|
|
44
|
+
# console shows whether the success/failure email actually fired)
|
|
45
|
+
"arn:aws:states:::sns:publish",
|
|
46
|
+
# EC2 lifecycle (StartExecutorEC2 + StopTradingInstance + ForceStopInstance)
|
|
47
|
+
"arn:aws:states:::aws-sdk:ec2:startInstances",
|
|
48
|
+
"arn:aws:states:::aws-sdk:ec2:stopInstances",
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Every ``Wait*`` state in the SF JSONs is the polling companion to a parent
|
|
54
|
+
# ``sendCommand`` Task — the parent fires the SSM command and returns
|
|
55
|
+
# instantly; the wait state polls ``getCommandInvocation`` until terminal.
|
|
56
|
+
# For console rendering we want one row per logical step, durations measured
|
|
57
|
+
# parent_entry → wait_exit (see ``read._materialize_tasks`` for the math).
|
|
58
|
+
#
|
|
59
|
+
# This map is intentionally exhaustive (every Wait* state across all 3 SF
|
|
60
|
+
# JSONs) so the read layer can absorb wait companions without a runtime
|
|
61
|
+
# fallback. New Wait* states added in future SF edits must be added here AND
|
|
62
|
+
# to the registry below; the CI test (planned in dashboard Phase 2) asserts
|
|
63
|
+
# this round-trip.
|
|
64
|
+
WAIT_GROUPING: Final[dict[str, str]] = {
|
|
65
|
+
# Saturday SF
|
|
66
|
+
"WaitForMorningEnrich": "MorningEnrich",
|
|
67
|
+
"WaitForDataPhase1": "DataPhase1",
|
|
68
|
+
"WaitForRAGIngestion": "RAGIngestion",
|
|
69
|
+
"WaitForPredictorTraining": "PredictorTraining",
|
|
70
|
+
"WaitForBacktester": "Backtester",
|
|
71
|
+
"WaitForParity": "Parity",
|
|
72
|
+
"WaitForEvaluator": "Evaluator",
|
|
73
|
+
"WaitForSaturdayHealthCheck": "SaturdayHealthCheck",
|
|
74
|
+
"WaitForWeeklySubstrateHealthCheck": "WeeklySubstrateHealthCheck",
|
|
75
|
+
# Weekday SF
|
|
76
|
+
"WaitForMorningPlanner": "RunMorningPlanner",
|
|
77
|
+
"WaitForTradingDayCheck": "CheckTradingDay",
|
|
78
|
+
"WaitForInstanceReady": "StartExecutorEC2",
|
|
79
|
+
# Note: weekday SF's MorningEnrich shares its WaitForMorningEnrich with
|
|
80
|
+
# the Saturday map above (same state name). Lookup-by-name is OK because
|
|
81
|
+
# the parent name is the same in both SFs.
|
|
82
|
+
# EOD SF
|
|
83
|
+
"WaitForPostMarketData": "PostMarketData",
|
|
84
|
+
"WaitForCaptureSnapshot": "CaptureSnapshot",
|
|
85
|
+
"WaitForEOD": "EODReconcile",
|
|
86
|
+
"WaitForDailySubstrateHealthCheck": "DailySubstrateHealthCheck",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── Pretty-label registry (mirrors sf-telegram-notifier verbatim) ─────────
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
PIPELINE_LABELS: Final[dict[str, str]] = {
|
|
94
|
+
"alpha-engine-saturday-pipeline": "Saturday SF",
|
|
95
|
+
"alpha-engine-weekday-pipeline": "Weekday SF",
|
|
96
|
+
"alpha-engine-eod-pipeline": "EOD SF",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ── Artifact registry types ───────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass(frozen=True)
|
|
104
|
+
class ArchivePageRef:
|
|
105
|
+
"""Deep-link target for a substantive Task state that produces an
|
|
106
|
+
operator-readable artifact.
|
|
107
|
+
|
|
108
|
+
The ``page`` slug is the dashboard page module name (e.g.
|
|
109
|
+
``"19_EOD_Reconcile_Archive"`` — corresponds to
|
|
110
|
+
``alpha-engine-dashboard/pages/19_EOD_Reconcile_Archive.py``). The
|
|
111
|
+
dashboard consumer constructs the full URL from its base host +
|
|
112
|
+
page slug at render time; the lib does not bake URL hosts because the
|
|
113
|
+
same page is reachable at ``console.nousergon.ai`` (private) and may
|
|
114
|
+
or may not be reachable at ``live.nousergon.ai`` (public) depending
|
|
115
|
+
on the page.
|
|
116
|
+
|
|
117
|
+
``artifact_label`` is the human-readable label for the deep-link cell
|
|
118
|
+
on page 25 — e.g. "Morning briefing" rather than the bare page slug.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
page: str
|
|
122
|
+
artifact_label: str
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class ArtifactReason:
|
|
127
|
+
"""Explicit non-generic reason a substantive Task state has no archive
|
|
128
|
+
page deep-link.
|
|
129
|
+
|
|
130
|
+
Per ``feedback_no_silent_fails`` — substrate-only states must surface
|
|
131
|
+
a specific reason ("Substrate refresh; no per-run artifact"), never
|
|
132
|
+
a generic "no artifact" placeholder. The reason text is what the
|
|
133
|
+
page 25 cell renders.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
reason: str
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# Type alias for the registry value — either a deep-link or an explicit reason.
|
|
140
|
+
RegistryEntry = "ArchivePageRef | ArtifactReason"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ── The registry ──────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Every substantive Task state across the 3 SF JSONs maps to either an
|
|
147
|
+
# ArchivePageRef (operator-readable artifact has a dedicated page) or an
|
|
148
|
+
# ArtifactReason (substrate-only — explicit reason rendered verbatim).
|
|
149
|
+
#
|
|
150
|
+
# Sourced from plan doc §2.1 inventory + a `jq` walk of all 3 SF JSONs
|
|
151
|
+
# (Saturday 89 / Weekday 36 / EOD 21 total states; nested Parallel branches
|
|
152
|
+
# walked). Reviewed against ROADMAP L3050 + the post-2026-05-15
|
|
153
|
+
# artifact-archive pages (dashboard #86: pages 16-22).
|
|
154
|
+
STATE_TO_ARCHIVE_PAGE: Final[dict[str, "ArchivePageRef | ArtifactReason"]] = {
|
|
155
|
+
# ── Saturday SF (23 substantive Task steps) ──────────────────────────
|
|
156
|
+
"MorningEnrich": ArtifactReason(
|
|
157
|
+
"Daily OHLCV write to predictor/daily_closes/{date}.parquet; "
|
|
158
|
+
"no per-run rendered artifact — substrate for downstream stages."
|
|
159
|
+
),
|
|
160
|
+
"DataPhase1": ArtifactReason(
|
|
161
|
+
"Bulk weekly write to predictor/price_cache/, archive/macro/, "
|
|
162
|
+
"ArcticDB universe library; no per-run rendered artifact — "
|
|
163
|
+
"substrate refresh."
|
|
164
|
+
),
|
|
165
|
+
"RAGIngestion": ArtifactReason(
|
|
166
|
+
"SEC/8-K/earnings/theses corpus refresh in rag/corpus/; "
|
|
167
|
+
"substrate-only — consumed at Research time."
|
|
168
|
+
),
|
|
169
|
+
"RegimeSubstrate": ArchivePageRef(
|
|
170
|
+
page="15_Regime",
|
|
171
|
+
artifact_label="Regime substrate",
|
|
172
|
+
),
|
|
173
|
+
"RegimeRetrospectiveEval": ArchivePageRef(
|
|
174
|
+
page="15_Regime",
|
|
175
|
+
artifact_label="Regime retrospective eval",
|
|
176
|
+
),
|
|
177
|
+
"Research": ArchivePageRef(
|
|
178
|
+
page="17_Research_Briefing_Archive",
|
|
179
|
+
artifact_label="Morning research briefing",
|
|
180
|
+
),
|
|
181
|
+
"DataPhase2": ArtifactReason(
|
|
182
|
+
"Alt-data + fundamentals refresh; substrate-only, no per-run "
|
|
183
|
+
"rendered artifact."
|
|
184
|
+
),
|
|
185
|
+
"EvalJudgeSubmitFirstSaturday": ArchivePageRef(
|
|
186
|
+
page="8_Eval_Quality",
|
|
187
|
+
artifact_label="Eval judge (first Saturday batch)",
|
|
188
|
+
),
|
|
189
|
+
"EvalJudgeSubmitWeekly": ArchivePageRef(
|
|
190
|
+
page="8_Eval_Quality",
|
|
191
|
+
artifact_label="Eval judge (weekly batch)",
|
|
192
|
+
),
|
|
193
|
+
"EvalJudgePoll": ArtifactReason(
|
|
194
|
+
"Polling state for the EvalJudge batch job; no per-run artifact — "
|
|
195
|
+
"see EvalJudgeProcess for the materialized rubric output."
|
|
196
|
+
),
|
|
197
|
+
"EvalJudgeProcess": ArchivePageRef(
|
|
198
|
+
page="8_Eval_Quality",
|
|
199
|
+
artifact_label="Eval judge processed rubrics",
|
|
200
|
+
),
|
|
201
|
+
"EvalRollingMean": ArchivePageRef(
|
|
202
|
+
page="8_Eval_Quality",
|
|
203
|
+
artifact_label="Eval 4-week rolling mean",
|
|
204
|
+
),
|
|
205
|
+
"RationaleClustering": ArtifactReason(
|
|
206
|
+
"Rationale cluster artifact written to S3; no dedicated page yet "
|
|
207
|
+
"(P3 follow-up — backlog)."
|
|
208
|
+
),
|
|
209
|
+
"ReplayConcordance": ArtifactReason(
|
|
210
|
+
"Concordance metric written to backtest/{date}/; surfaced inline "
|
|
211
|
+
"in Backtester evaluator report (page 21)."
|
|
212
|
+
),
|
|
213
|
+
"Counterfactual": ArtifactReason(
|
|
214
|
+
"Counterfactual artifact written to backtest/{date}/; surfaced "
|
|
215
|
+
"inline in Backtester evaluator report (page 21)."
|
|
216
|
+
),
|
|
217
|
+
"PredictorTraining": ArchivePageRef(
|
|
218
|
+
page="20_Predictor_Training_Archive",
|
|
219
|
+
artifact_label="Predictor training summary",
|
|
220
|
+
),
|
|
221
|
+
"Backtester": ArchivePageRef(
|
|
222
|
+
page="21_Backtester_Evaluator_Archive",
|
|
223
|
+
artifact_label="Backtester consolidated report",
|
|
224
|
+
),
|
|
225
|
+
"Parity": ArchivePageRef(
|
|
226
|
+
page="3_Analysis",
|
|
227
|
+
artifact_label="Parity replay diff",
|
|
228
|
+
),
|
|
229
|
+
"Evaluator": ArchivePageRef(
|
|
230
|
+
page="21_Backtester_Evaluator_Archive",
|
|
231
|
+
artifact_label="Backtester evaluator report",
|
|
232
|
+
),
|
|
233
|
+
"DriftDetection": ArchivePageRef(
|
|
234
|
+
page="4_System_Health",
|
|
235
|
+
artifact_label="SF-vs-CFN drift report",
|
|
236
|
+
),
|
|
237
|
+
"SaturdayHealthCheck": ArchivePageRef(
|
|
238
|
+
page="4_System_Health",
|
|
239
|
+
artifact_label="Saturday per-repo health check",
|
|
240
|
+
),
|
|
241
|
+
"WeeklySubstrateHealthCheck": ArchivePageRef(
|
|
242
|
+
page="4_System_Health",
|
|
243
|
+
artifact_label="Weekly substrate health check",
|
|
244
|
+
),
|
|
245
|
+
"NotifyComplete": ArtifactReason(
|
|
246
|
+
"Terminal success SNS publish to alpha-engine-alerts; "
|
|
247
|
+
"no persisted artifact (the email IS the surface)."
|
|
248
|
+
),
|
|
249
|
+
"NotifyShellRunComplete": ArtifactReason(
|
|
250
|
+
"Friday-PM shell-run dry-pass terminal SNS publish; "
|
|
251
|
+
"no persisted artifact (the email IS the surface)."
|
|
252
|
+
),
|
|
253
|
+
"HandleFailure": ArtifactReason(
|
|
254
|
+
"Terminal failure SNS publish to alpha-engine-alerts; "
|
|
255
|
+
"no persisted artifact (the email IS the surface)."
|
|
256
|
+
),
|
|
257
|
+
"PublishResearchFailureImmediate": ArtifactReason(
|
|
258
|
+
"Early-signal SNS publish fired the moment the Research branch "
|
|
259
|
+
"fails inside ResearchPredictorParallel — BEFORE the sibling "
|
|
260
|
+
"PredictorTraining branch completes its work and the parallel "
|
|
261
|
+
"aggregation joins. No persisted artifact (the email IS the "
|
|
262
|
+
"surface). Salvage-at-join semantics preserved: the branch still "
|
|
263
|
+
"terminates via BranchAFailed Pass and the SF fails at "
|
|
264
|
+
"CheckBranchOutcomes."
|
|
265
|
+
),
|
|
266
|
+
"PublishPredictorFailureImmediate": ArtifactReason(
|
|
267
|
+
"Early-signal SNS publish fired the moment the PredictorTraining "
|
|
268
|
+
"branch fails inside ResearchPredictorParallel — BEFORE the "
|
|
269
|
+
"sibling Research branch's eval-judge / RollingMean / "
|
|
270
|
+
"Counterfactual chain completes. No persisted artifact (the "
|
|
271
|
+
"email IS the surface). Salvage-at-join semantics preserved: "
|
|
272
|
+
"the branch still terminates via BranchBFailed Pass and the SF "
|
|
273
|
+
"fails at CheckBranchOutcomes."
|
|
274
|
+
),
|
|
275
|
+
# ── Weekday SF (13 substantive Task steps) ───────────────────────────
|
|
276
|
+
"DeployDriftCheck": ArchivePageRef(
|
|
277
|
+
page="4_System_Health",
|
|
278
|
+
artifact_label="Deploy-drift assertions",
|
|
279
|
+
),
|
|
280
|
+
"StartExecutorEC2": ArtifactReason(
|
|
281
|
+
"EC2 startInstances on the trading instance; no artifact — "
|
|
282
|
+
"operational only."
|
|
283
|
+
),
|
|
284
|
+
"DescribeInstanceInfo": ArtifactReason(
|
|
285
|
+
"Boot diagnostic call against the trading instance; "
|
|
286
|
+
"no artifact — operational only."
|
|
287
|
+
),
|
|
288
|
+
"CheckTradingDay": ArtifactReason(
|
|
289
|
+
"NYSE-holiday gate via SSM command; no artifact — gate outcome "
|
|
290
|
+
"is encoded in the SF branch taken."
|
|
291
|
+
),
|
|
292
|
+
"NotifyHolidaySkip": ArtifactReason(
|
|
293
|
+
"Holiday-skip SNS publish; no persisted artifact (the email IS "
|
|
294
|
+
"the surface)."
|
|
295
|
+
),
|
|
296
|
+
"StopExecutorOnHoliday": ArtifactReason(
|
|
297
|
+
"EC2 stopInstances on the trading instance after a holiday-skip; "
|
|
298
|
+
"no artifact — operational only."
|
|
299
|
+
),
|
|
300
|
+
"TradingDayCheckFailed": ArtifactReason(
|
|
301
|
+
"SF Pass state recording a holiday-skip outcome; no artifact."
|
|
302
|
+
),
|
|
303
|
+
# MorningEnrich (weekday) — same state name as Saturday; same entry above wins.
|
|
304
|
+
"PredictorInference": ArchivePageRef(
|
|
305
|
+
page="18_Predictor_Briefing_Archive",
|
|
306
|
+
artifact_label="Predictor morning briefing",
|
|
307
|
+
),
|
|
308
|
+
"CheckPredictorCoverage": ArtifactReason(
|
|
309
|
+
"Coverage-gate Lambda; outcome encoded in the SF branch taken — "
|
|
310
|
+
"see PredictorHealthCheck for any persisted health JSON."
|
|
311
|
+
),
|
|
312
|
+
"ReinvokePredictor": ArtifactReason(
|
|
313
|
+
"Re-invocation Lambda when CheckPredictorCoverage finds a gap; "
|
|
314
|
+
"no per-run artifact — replaces the PredictorInference output."
|
|
315
|
+
),
|
|
316
|
+
"RecheckCoverage": ArtifactReason(
|
|
317
|
+
"Second coverage-gate Lambda after ReinvokePredictor; outcome "
|
|
318
|
+
"encoded in the SF branch taken."
|
|
319
|
+
),
|
|
320
|
+
"PredictorHealthCheck": ArchivePageRef(
|
|
321
|
+
page="4_System_Health",
|
|
322
|
+
artifact_label="Predictor health check",
|
|
323
|
+
),
|
|
324
|
+
"RunMorningPlanner": ArchivePageRef(
|
|
325
|
+
page="16_Order_Book_Rationale",
|
|
326
|
+
artifact_label="Order book + rationale",
|
|
327
|
+
),
|
|
328
|
+
"RunDaemon": ArchivePageRef(
|
|
329
|
+
page="22_Intraday_Surveillance",
|
|
330
|
+
artifact_label="Intraday surveillance (daemon)",
|
|
331
|
+
),
|
|
332
|
+
# ── EOD SF (5 substantive Task steps) ────────────────────────────────
|
|
333
|
+
"PostMarketData": ArtifactReason(
|
|
334
|
+
"Polygon T+1 daily aggregate write to predictor/daily_closes/; "
|
|
335
|
+
"substrate-only — consumed by EODReconcile."
|
|
336
|
+
),
|
|
337
|
+
"CaptureSnapshot": ArchivePageRef(
|
|
338
|
+
page="1_Portfolio",
|
|
339
|
+
artifact_label="NAV + positions snapshot",
|
|
340
|
+
),
|
|
341
|
+
"EODReconcile": ArchivePageRef(
|
|
342
|
+
page="19_EOD_Reconcile_Archive",
|
|
343
|
+
artifact_label="EOD reconcile briefing",
|
|
344
|
+
),
|
|
345
|
+
"DailySubstrateHealthCheck": ArchivePageRef(
|
|
346
|
+
page="4_System_Health",
|
|
347
|
+
artifact_label="Daily substrate health check",
|
|
348
|
+
),
|
|
349
|
+
"StopTradingInstance": ArtifactReason(
|
|
350
|
+
"EC2 stopInstances on the trading instance; no artifact — "
|
|
351
|
+
"operational only."
|
|
352
|
+
),
|
|
353
|
+
"ForceStopInstance": ArtifactReason(
|
|
354
|
+
"EC2 stopInstances fallback on a non-graceful EOD; no artifact — "
|
|
355
|
+
"operational only."
|
|
356
|
+
),
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def lookup_registry(state_name: str) -> Optional["ArchivePageRef | ArtifactReason"]:
|
|
361
|
+
"""Return the registry entry for ``state_name`` (None if absent).
|
|
362
|
+
|
|
363
|
+
``None`` here signals "this state is not in the registry" — distinct
|
|
364
|
+
from :class:`ArtifactReason` ("registered as substrate-only with this
|
|
365
|
+
reason"). The dashboard consumer should treat ``None`` as a CI-time
|
|
366
|
+
test failure (registry drift); it should NEVER render in production.
|
|
367
|
+
"""
|
|
368
|
+
return STATE_TO_ARCHIVE_PAGE.get(state_name)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Verbatim Python parity for the SF JSON ``States.Format`` message templates.
|
|
2
|
+
|
|
3
|
+
The Step Function JSON files (touched in Phase 3 of the revamp) inline
|
|
4
|
+
the success + failure email message bodies via ``States.Format``. These
|
|
5
|
+
Python functions render the SAME bodies — used by:
|
|
6
|
+
|
|
7
|
+
1. Unit tests that assert the SF JSON template's substituted output equals
|
|
8
|
+
the Python rendering byte-for-byte (parity guard against the two
|
|
9
|
+
drifting).
|
|
10
|
+
2. Future non-SF consumers (Slack subscriber, ``ae pipeline status`` CLI)
|
|
11
|
+
that want to render the same body without re-implementing the template.
|
|
12
|
+
|
|
13
|
+
The functions never raise — bad inputs render best-effort placeholder
|
|
14
|
+
strings rather than failing the email path, mirroring the SF JSON's
|
|
15
|
+
behavior (``States.Format`` substitutes ``$.field`` even if absent).
|
|
16
|
+
|
|
17
|
+
**Console URL**: the dashboard host is hardcoded here as the lib-canonical
|
|
18
|
+
deep-link base. If the dashboard host changes (e.g., new vanity domain),
|
|
19
|
+
edit :data:`CONSOLE_BASE_URL` + the SF JSON templates in lockstep — the
|
|
20
|
+
parity tests catch the drift.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Final
|
|
26
|
+
|
|
27
|
+
# Hardcoded console base URL. The dashboard is reachable at three hosts:
|
|
28
|
+
#
|
|
29
|
+
# - console.nousergon.ai — private (Cloudflare Access gated)
|
|
30
|
+
# - live.nousergon.ai — public Streamlit page (subset of pages)
|
|
31
|
+
# - <ec2-host>:8501 — direct (debug only)
|
|
32
|
+
#
|
|
33
|
+
# Page 25 (Pipeline Status) lives on the PRIVATE console — operator-only
|
|
34
|
+
# surface. The success / failure emails are operator-only too, so
|
|
35
|
+
# console.nousergon.ai is the right deep-link target.
|
|
36
|
+
CONSOLE_BASE_URL: Final[str] = "https://console.nousergon.ai"
|
|
37
|
+
PIPELINE_STATUS_PAGE: Final[str] = "Pipeline_Status"
|
|
38
|
+
|
|
39
|
+
# Cause truncation — kept in lockstep with sf-telegram-notifier
|
|
40
|
+
# (alpha-engine-data/infrastructure/lambdas/sf-telegram-notifier/index.py L69).
|
|
41
|
+
# The SF JSON's ``States.Format`` doesn't truncate; the truncation here is
|
|
42
|
+
# only meaningful when a Python consumer (Slack, CLI) renders. The SF JSON
|
|
43
|
+
# templates in Phase 3 will use ``States.StringSplit`` + the first N chars
|
|
44
|
+
# to approximate.
|
|
45
|
+
_CAUSE_MAX_CHARS = 280
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _console_link(execution_arn: str) -> str:
|
|
49
|
+
"""Return the page-25 deep-link for a given execution ARN.
|
|
50
|
+
|
|
51
|
+
Streamlit's query-string convention is ``?<key>=<value>``; the page
|
|
52
|
+
consumes ``?run=<arn>`` and filters its rendered tables to that
|
|
53
|
+
execution.
|
|
54
|
+
"""
|
|
55
|
+
# Streamlit query-string handling tolerates colons + slashes in the
|
|
56
|
+
# value, so no URL encoding is needed for the ARN. Keep this simple
|
|
57
|
+
# so the SF JSON ``States.Format`` template renders the same string.
|
|
58
|
+
return f"{CONSOLE_BASE_URL}/{PIPELINE_STATUS_PAGE}?run={execution_arn}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def format_success_message(
|
|
62
|
+
*,
|
|
63
|
+
pretty_label: str,
|
|
64
|
+
execution_arn: str,
|
|
65
|
+
) -> str:
|
|
66
|
+
"""Render the 2-line success email body.
|
|
67
|
+
|
|
68
|
+
Body shape (verbatim — the SF JSON ``States.Format`` template renders
|
|
69
|
+
this same string):
|
|
70
|
+
|
|
71
|
+
{pretty_label} SUCCEEDED
|
|
72
|
+
Console: {console_link}
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
pretty_label:
|
|
77
|
+
Human-readable SF label, e.g. ``"Saturday SF"``. Sourced from
|
|
78
|
+
:data:`alpha_engine_lib.pipeline_status.registry.PIPELINE_LABELS`.
|
|
79
|
+
execution_arn:
|
|
80
|
+
Full SF execution ARN. Page 25 filters its tables to this ARN
|
|
81
|
+
via the ``?run=`` query string.
|
|
82
|
+
"""
|
|
83
|
+
link = _console_link(execution_arn)
|
|
84
|
+
return f"{pretty_label} SUCCEEDED\nConsole: {link}"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def format_failure_message(
|
|
88
|
+
*,
|
|
89
|
+
pretty_label: str,
|
|
90
|
+
execution_arn: str,
|
|
91
|
+
failing_state: str,
|
|
92
|
+
cause: str,
|
|
93
|
+
) -> str:
|
|
94
|
+
"""Render the 4-line failure email body.
|
|
95
|
+
|
|
96
|
+
Body shape (verbatim):
|
|
97
|
+
|
|
98
|
+
{pretty_label} FAILED at state {failing_state}
|
|
99
|
+
Console: {console_link}
|
|
100
|
+
|
|
101
|
+
Cause (first 280 chars):
|
|
102
|
+
{truncated_cause}
|
|
103
|
+
|
|
104
|
+
The Python rendering truncates ``cause`` at :data:`_CAUSE_MAX_CHARS`
|
|
105
|
+
chars with an ellipsis suffix on overflow. The SF JSON template
|
|
106
|
+
approximates via ``States.StringSplit`` + the first N chars; the
|
|
107
|
+
Phase-3 parity test asserts the two render the same string for
|
|
108
|
+
representative cause values.
|
|
109
|
+
"""
|
|
110
|
+
link = _console_link(execution_arn)
|
|
111
|
+
snippet = (cause or "").strip()
|
|
112
|
+
if len(snippet) > _CAUSE_MAX_CHARS:
|
|
113
|
+
snippet = snippet[: _CAUSE_MAX_CHARS - 1] + "…"
|
|
114
|
+
return (
|
|
115
|
+
f"{pretty_label} FAILED at state {failing_state}\n"
|
|
116
|
+
f"Console: {link}\n"
|
|
117
|
+
f"\n"
|
|
118
|
+
f"Cause (first {_CAUSE_MAX_CHARS} chars):\n"
|
|
119
|
+
f"{snippet}"
|
|
120
|
+
)
|