opencandle 0.10.0 → 0.11.1
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.
- package/README.md +12 -9
- package/dist/analysts/orchestrator.d.ts +3 -4
- package/dist/analysts/orchestrator.js +34 -44
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/cli-main.js +242 -9
- package/dist/cli-main.js.map +1 -1
- package/dist/config.d.ts +4 -9
- package/dist/config.js +7 -10
- package/dist/config.js.map +1 -1
- package/dist/doctor/report.js +1 -1
- package/dist/doctor/report.js.map +1 -1
- package/dist/infra/cache.d.ts +6 -0
- package/dist/infra/cache.js +16 -6
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/freshness.d.ts +21 -0
- package/dist/infra/freshness.js +119 -0
- package/dist/infra/freshness.js.map +1 -0
- package/dist/infra/market-calendar.d.ts +14 -0
- package/dist/infra/market-calendar.js +137 -0
- package/dist/infra/market-calendar.js.map +1 -0
- package/dist/infra/rate-limiter.js +1 -0
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/market-state/daily-report.d.ts +1 -1
- package/dist/market-state/daily-report.js +5 -16
- package/dist/market-state/daily-report.js.map +1 -1
- package/dist/market-state/service.d.ts +0 -35
- package/dist/market-state/service.js +0 -63
- package/dist/market-state/service.js.map +1 -1
- package/dist/market-state/summaries.d.ts +8 -0
- package/dist/market-state/summaries.js +70 -0
- package/dist/market-state/summaries.js.map +1 -0
- package/dist/memory/sqlite.js +20 -19
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/onboarding/providers.d.ts +25 -1
- package/dist/onboarding/providers.js +26 -0
- package/dist/onboarding/providers.js.map +1 -1
- package/dist/pi/opencandle-extension.js +57 -226
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session-action-dedupe.d.ts +6 -0
- package/dist/pi/session-action-dedupe.js +126 -0
- package/dist/pi/session-action-dedupe.js.map +1 -0
- package/dist/pi/session-writer-lock.d.ts +26 -2
- package/dist/pi/session-writer-lock.js +230 -18
- package/dist/pi/session-writer-lock.js.map +1 -1
- package/dist/pi/setup.js +5 -5
- package/dist/pi/setup.js.map +1 -1
- package/dist/pi/tui-session-coordinator.d.ts +15 -0
- package/dist/pi/tui-session-coordinator.js +283 -0
- package/dist/pi/tui-session-coordinator.js.map +1 -0
- package/dist/prompts/context-builder.js +1 -1
- package/dist/prompts/policy-cards.js +1 -1
- package/dist/prompts/policy-cards.js.map +1 -1
- package/dist/providers/alpha-vantage.js +6 -0
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/coingecko.js +1 -0
- package/dist/providers/coingecko.js.map +1 -1
- package/dist/providers/polymarket.d.ts +2 -0
- package/dist/providers/polymarket.js +163 -0
- package/dist/providers/polymarket.js.map +1 -0
- package/dist/providers/wrap-provider.js +5 -3
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +3 -0
- package/dist/providers/yahoo-finance.js +206 -10
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.js +4 -5
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/entity-extractor.js +16 -0
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/route-manifest.js +1 -2
- package/dist/routing/route-manifest.js.map +1 -1
- package/dist/routing/router-llm-client.js +18 -3
- package/dist/routing/router-llm-client.js.map +1 -1
- package/dist/routing/router-prompt.js +1 -1
- package/dist/routing/router.d.ts +1 -1
- package/dist/routing/router.js +309 -9
- package/dist/routing/router.js.map +1 -1
- package/dist/routing/slot-resolver.d.ts +1 -0
- package/dist/routing/slot-resolver.js +1 -1
- package/dist/routing/slot-resolver.js.map +1 -1
- package/dist/runtime/evidence.d.ts +1 -0
- package/dist/runtime/evidence.js.map +1 -1
- package/dist/runtime/numeric-claims.d.ts +23 -0
- package/dist/runtime/numeric-claims.js +100 -0
- package/dist/runtime/numeric-claims.js.map +1 -0
- package/dist/runtime/planning-evidence.js +1 -76
- package/dist/runtime/planning-evidence.js.map +1 -1
- package/dist/runtime/prompt-step.d.ts +10 -1
- package/dist/runtime/prompt-step.js +116 -2
- package/dist/runtime/prompt-step.js.map +1 -1
- package/dist/runtime/session-coordinator.d.ts +11 -1
- package/dist/runtime/session-coordinator.js +354 -62
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/validation.d.ts +3 -1
- package/dist/runtime/workflow-events.d.ts +1 -1
- package/dist/runtime/workflow-events.js.map +1 -1
- package/dist/runtime/workflow-types.d.ts +3 -0
- package/dist/runtime/workflow-types.js.map +1 -1
- package/dist/system-prompt.js +1 -1
- package/dist/tools/fundamentals/comps.d.ts +1 -0
- package/dist/tools/fundamentals/comps.js +20 -9
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.d.ts +1 -1
- package/dist/tools/fundamentals/dcf.js +190 -76
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/macro/event-probabilities.d.ts +8 -0
- package/dist/tools/macro/event-probabilities.js +121 -0
- package/dist/tools/macro/event-probabilities.js.map +1 -0
- package/dist/tools/market/crypto-price.d.ts +4 -1
- package/dist/tools/market/crypto-price.js +10 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/screen-stocks.js +14 -3
- package/dist/tools/market/screen-stocks.js.map +1 -1
- package/dist/tools/market/stock-quote.d.ts +4 -1
- package/dist/tools/market/stock-quote.js +9 -4
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/option-chain.d.ts +4 -1
- package/dist/tools/options/option-chain.js +12 -1
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/daily-report.js +10 -4
- package/dist/tools/portfolio/daily-report.js.map +1 -1
- package/dist/tools/portfolio/risk-analysis.js +1 -1
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +23 -5
- package/dist/tools/technical/backtest.js +131 -94
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +1 -1
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/fundamentals.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/market.d.ts +2 -0
- package/dist/types/options.d.ts +1 -0
- package/dist/types/prediction-markets.d.ts +13 -0
- package/dist/types/prediction-markets.js +2 -0
- package/dist/types/prediction-markets.js.map +1 -0
- package/gui/server/chat-event-adapter.ts +89 -5
- package/gui/server/http-routes.ts +661 -37
- package/gui/server/invoke-tool.ts +165 -16
- package/gui/server/live-chat-event-adapter.ts +37 -2
- package/gui/server/local-session-coordinator.ts +97 -0
- package/gui/server/market-state-api.ts +64 -48
- package/gui/server/projector.ts +132 -3
- package/gui/server/server.ts +51 -6
- package/gui/server/session-actions.ts +146 -45
- package/gui/server/session-entry-wait.ts +22 -3
- package/gui/server/tool-metadata.ts +3 -1
- package/gui/server/ws-hub.ts +77 -10
- package/gui/shared/chat-events.ts +10 -1
- package/gui/shared/event-reducer.ts +1 -0
- package/gui/web/dist/assets/CatalogOverlay-DZ1niyQm.js +1 -0
- package/gui/web/dist/assets/index-D4F9AJnn.css +2 -0
- package/gui/web/dist/assets/index-DtqMMBTr.js +65 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +13 -15
- package/src/analysts/orchestrator.ts +35 -56
- package/src/cli-main.ts +253 -13
- package/src/config.ts +12 -20
- package/src/doctor/report.ts +1 -1
- package/src/infra/cache.ts +24 -11
- package/src/infra/freshness.ts +165 -0
- package/src/infra/market-calendar.ts +193 -0
- package/src/infra/rate-limiter.ts +1 -0
- package/src/market-state/daily-report.ts +6 -16
- package/src/market-state/service.ts +0 -136
- package/src/market-state/summaries.ts +75 -0
- package/src/memory/sqlite.ts +23 -19
- package/src/onboarding/providers.ts +29 -1
- package/src/pi/opencandle-extension.ts +67 -270
- package/src/pi/session-action-dedupe.ts +155 -0
- package/src/pi/session-writer-lock.ts +290 -20
- package/src/pi/setup.ts +6 -6
- package/src/pi/tui-session-coordinator.ts +351 -0
- package/src/prompts/context-builder.ts +1 -1
- package/src/prompts/policy-cards.ts +1 -1
- package/src/providers/alpha-vantage.ts +6 -0
- package/src/providers/coingecko.ts +2 -0
- package/src/providers/polymarket.ts +214 -0
- package/src/providers/wrap-provider.ts +5 -3
- package/src/providers/yahoo-finance.ts +339 -27
- package/src/routing/classify-intent.ts +4 -5
- package/src/routing/entity-extractor.ts +17 -0
- package/src/routing/route-manifest.ts +1 -2
- package/src/routing/router-llm-client.ts +31 -19
- package/src/routing/router-prompt.ts +1 -1
- package/src/routing/router.ts +379 -8
- package/src/routing/slot-resolver.ts +1 -1
- package/src/runtime/evidence.ts +1 -0
- package/src/runtime/numeric-claims.ts +108 -0
- package/src/runtime/planning-evidence.ts +6 -91
- package/src/runtime/prompt-step.ts +127 -2
- package/src/runtime/session-coordinator.ts +464 -74
- package/src/runtime/validation.ts +1 -1
- package/src/runtime/workflow-events.ts +2 -0
- package/src/runtime/workflow-types.ts +3 -0
- package/src/system-prompt.ts +1 -1
- package/src/tools/AGENTS.md +1 -1
- package/src/tools/fundamentals/comps.ts +25 -14
- package/src/tools/fundamentals/dcf.ts +217 -87
- package/src/tools/index.ts +3 -3
- package/src/tools/macro/event-probabilities.ts +141 -0
- package/src/tools/market/crypto-price.ts +14 -2
- package/src/tools/market/screen-stocks.ts +14 -3
- package/src/tools/market/stock-quote.ts +49 -43
- package/src/tools/options/option-chain.ts +16 -2
- package/src/tools/portfolio/daily-report.ts +10 -4
- package/src/tools/portfolio/risk-analysis.ts +1 -1
- package/src/tools/technical/backtest.ts +167 -108
- package/src/tools/technical/indicators.ts +1 -1
- package/src/types/fundamentals.ts +1 -0
- package/src/types/index.ts +1 -0
- package/src/types/market.ts +2 -0
- package/src/types/options.ts +1 -0
- package/src/types/prediction-markets.ts +13 -0
- package/dist/tools/portfolio/predictions.d.ts +0 -55
- package/dist/tools/portfolio/predictions.js +0 -422
- package/dist/tools/portfolio/predictions.js.map +0 -1
- package/gui/server/chat-run-session.ts +0 -16
- package/gui/web/dist/assets/CatalogOverlay-CYptsda-.js +0 -1
- package/gui/web/dist/assets/index-B7QAjY5g.js +0 -65
- package/gui/web/dist/assets/index-D5dbWPfM.css +0 -2
- package/src/tools/portfolio/predictions.ts +0 -553
package/src/infra/cache.ts
CHANGED
|
@@ -12,21 +12,27 @@ export interface StaleResult<T> {
|
|
|
12
12
|
cachedAt: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
interface
|
|
16
|
-
|
|
15
|
+
interface CacheMetadata {
|
|
16
|
+
status: "none" | "cached" | "stale";
|
|
17
17
|
cachedAt: number;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const cacheMetadataStorage = new AsyncLocalStorage<CacheMetadata>();
|
|
21
21
|
|
|
22
|
-
export async function runWithStaleMetadata<T>(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
export async function runWithStaleMetadata<T>(fn: () => Promise<T>): Promise<{
|
|
23
|
+
value: T;
|
|
24
|
+
cache?: { status: "cached" | "stale"; cachedAt: number };
|
|
25
|
+
stale?: { cachedAt: number };
|
|
26
|
+
}> {
|
|
27
|
+
const metadata: CacheMetadata = { status: "none", cachedAt: 0 };
|
|
28
|
+
const value = await cacheMetadataStorage.run(metadata, fn);
|
|
27
29
|
return {
|
|
28
30
|
value,
|
|
29
|
-
|
|
31
|
+
cache:
|
|
32
|
+
metadata.status === "cached" || metadata.status === "stale"
|
|
33
|
+
? { status: metadata.status, cachedAt: metadata.cachedAt }
|
|
34
|
+
: undefined,
|
|
35
|
+
stale: metadata.status === "stale" ? { cachedAt: metadata.cachedAt } : undefined,
|
|
30
36
|
};
|
|
31
37
|
}
|
|
32
38
|
|
|
@@ -37,6 +43,11 @@ export class Cache {
|
|
|
37
43
|
const entry = this.store.get(key);
|
|
38
44
|
if (!entry) return undefined;
|
|
39
45
|
if (Date.now() > entry.expiresAt) return undefined;
|
|
46
|
+
const metadata = cacheMetadataStorage.getStore();
|
|
47
|
+
if (metadata && metadata.status === "none") {
|
|
48
|
+
metadata.status = "cached";
|
|
49
|
+
metadata.cachedAt = entry.cachedAt;
|
|
50
|
+
}
|
|
40
51
|
return entry.value as T;
|
|
41
52
|
}
|
|
42
53
|
|
|
@@ -54,9 +65,9 @@ export class Cache {
|
|
|
54
65
|
return undefined;
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
const metadata =
|
|
68
|
+
const metadata = cacheMetadataStorage.getStore();
|
|
58
69
|
if (metadata) {
|
|
59
|
-
metadata.
|
|
70
|
+
metadata.status = "stale";
|
|
60
71
|
metadata.cachedAt = entry.cachedAt;
|
|
61
72
|
}
|
|
62
73
|
return { value: entry.value as T, stale: true, cachedAt: entry.cachedAt };
|
|
@@ -98,6 +109,7 @@ export const TTL = {
|
|
|
98
109
|
CRUMB: 900_000, // 15 minutes
|
|
99
110
|
WEB_SEARCH: 300_000, // 5 minutes
|
|
100
111
|
FINNHUB_NEWS: 300_000, // 5 minutes
|
|
112
|
+
PREDICTION_MARKETS: 300_000, // 5 minutes
|
|
101
113
|
} as const;
|
|
102
114
|
|
|
103
115
|
// Stale limits — how long past TTL expiry a cached value is still useful as fallback
|
|
@@ -111,4 +123,5 @@ export const STALE_LIMIT = {
|
|
|
111
123
|
SCREENER: 15 * 60_000, // 15 minutes
|
|
112
124
|
WEB_SEARCH: 3_600_000, // 1 hour
|
|
113
125
|
FINNHUB_NEWS: 3_600_000, // 1 hour
|
|
126
|
+
PREDICTION_MARKETS: 3_600_000, // 1 hour
|
|
114
127
|
} as const;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyMarketStatusAt,
|
|
3
|
+
lastTradingDay,
|
|
4
|
+
localDateTimeParts,
|
|
5
|
+
type MarketSession,
|
|
6
|
+
} from "./market-calendar.js";
|
|
7
|
+
|
|
8
|
+
export interface FreshnessStamp {
|
|
9
|
+
fetchedAt: string;
|
|
10
|
+
providerDataAt?: string;
|
|
11
|
+
providerDataDate?: string;
|
|
12
|
+
cacheStatus: "live" | "cached" | "stale";
|
|
13
|
+
cachedAt?: string;
|
|
14
|
+
marketSession: MarketSession;
|
|
15
|
+
dataDelayMs?: number;
|
|
16
|
+
isStaleForSession: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildFreshnessStamp(input: {
|
|
20
|
+
asOf?: string | number | Date;
|
|
21
|
+
cached?: boolean;
|
|
22
|
+
stale?: boolean;
|
|
23
|
+
cachedAt?: string;
|
|
24
|
+
dataDelayMs?: number;
|
|
25
|
+
now?: Date;
|
|
26
|
+
assetClass?: "equity" | "crypto";
|
|
27
|
+
}): FreshnessStamp {
|
|
28
|
+
const now = input.now ?? new Date();
|
|
29
|
+
const parsedAsOf = normalizeAsOf(input.asOf);
|
|
30
|
+
const fetchedAt = now.toISOString();
|
|
31
|
+
const cacheStatus = input.stale ? "stale" : input.cached ? "cached" : "live";
|
|
32
|
+
const marketSession = input.assetClass === "crypto" ? "unknown" : classifyMarketStatusAt(now);
|
|
33
|
+
const cachedAt = input.cachedAt ? normalizeDate(input.cachedAt)?.toISOString() : undefined;
|
|
34
|
+
const providerDataAt = parsedAsOf?.date.toISOString();
|
|
35
|
+
const providerDataDate = parsedAsOf?.dateOnly;
|
|
36
|
+
const isStaleForSession =
|
|
37
|
+
input.assetClass === "crypto"
|
|
38
|
+
? isCryptoStale(parsedAsOf?.date, cachedAt, now, cacheStatus)
|
|
39
|
+
: isEquityStale(parsedAsOf, cacheStatus, now);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
fetchedAt,
|
|
43
|
+
providerDataAt,
|
|
44
|
+
providerDataDate,
|
|
45
|
+
cacheStatus,
|
|
46
|
+
cachedAt,
|
|
47
|
+
marketSession,
|
|
48
|
+
dataDelayMs: input.dataDelayMs,
|
|
49
|
+
isStaleForSession,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function formatAsOfLine(stamp: FreshnessStamp): string {
|
|
54
|
+
if (stamp.isStaleForSession && stamp.providerDataAt) {
|
|
55
|
+
const date = stamp.providerDataDate ?? formatEtDate(new Date(stamp.providerDataAt));
|
|
56
|
+
const parenthetical = staleSessionParenthetical(stamp.marketSession);
|
|
57
|
+
const cached = stamp.cachedAt
|
|
58
|
+
? ` Cached from ${formatEtDateTime(new Date(stamp.cachedAt))} ET.`
|
|
59
|
+
: "";
|
|
60
|
+
return `Last available price as of ${date}${parenthetical}. This is not a live quote.${cached}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (stamp.cacheStatus === "stale" && stamp.cachedAt) {
|
|
64
|
+
const notLive = stamp.isStaleForSession ? " This is not a live quote." : "";
|
|
65
|
+
return `Using cached data from ${formatEtDateTime(new Date(stamp.cachedAt))} ET (provider unavailable).${notLive}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (stamp.providerDataAt || stamp.dataDelayMs !== undefined) {
|
|
69
|
+
const reference = stamp.providerDataAt ?? stamp.cachedAt ?? stamp.fetchedAt;
|
|
70
|
+
const asOf = stamp.providerDataDate ?? formatEtDateTime(new Date(reference));
|
|
71
|
+
if (stamp.dataDelayMs !== undefined)
|
|
72
|
+
return `As of ${asOf} ET (~${Math.round(stamp.dataDelayMs / 60_000)}m delayed).`;
|
|
73
|
+
return `As of ${asOf} ET (${marketSessionLabel(stamp.marketSession)}).`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return `As of ${formatEtDateTime(new Date(stamp.fetchedAt))} ET (${marketSessionLabel(stamp.marketSession)}).`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeAsOf(
|
|
80
|
+
asOf: string | number | Date | undefined,
|
|
81
|
+
): { date: Date; dateOnly?: string } | undefined {
|
|
82
|
+
if (asOf === undefined) return undefined;
|
|
83
|
+
if (asOf instanceof Date) return Number.isNaN(asOf.getTime()) ? undefined : { date: asOf };
|
|
84
|
+
if (typeof asOf === "number") {
|
|
85
|
+
const milliseconds = asOf < 10_000_000_000 ? asOf * 1000 : asOf;
|
|
86
|
+
const date = new Date(milliseconds);
|
|
87
|
+
return Number.isNaN(date.getTime()) ? undefined : { date };
|
|
88
|
+
}
|
|
89
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(asOf)) {
|
|
90
|
+
const date = new Date(`${asOf}T00:00:00.000Z`);
|
|
91
|
+
return Number.isNaN(date.getTime()) ? undefined : { date, dateOnly: asOf };
|
|
92
|
+
}
|
|
93
|
+
const date = new Date(asOf);
|
|
94
|
+
return Number.isNaN(date.getTime()) ? undefined : { date };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizeDate(value: string): Date | undefined {
|
|
98
|
+
const date = new Date(value);
|
|
99
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isEquityStale(
|
|
103
|
+
parsedAsOf: { date: Date; dateOnly?: string } | undefined,
|
|
104
|
+
cacheStatus: FreshnessStamp["cacheStatus"],
|
|
105
|
+
now: Date,
|
|
106
|
+
): boolean {
|
|
107
|
+
if (!parsedAsOf) return cacheStatus === "stale";
|
|
108
|
+
const localNow = localDateTimeParts(now, "America/New_York");
|
|
109
|
+
const providerTradingDate =
|
|
110
|
+
parsedAsOf.dateOnly ?? localDateTimeParts(parsedAsOf.date, "America/New_York").date;
|
|
111
|
+
const marketSession = classifyMarketStatusAt(now);
|
|
112
|
+
const requiredTradingDate =
|
|
113
|
+
marketSession === "open" ||
|
|
114
|
+
marketSession === "after_close" ||
|
|
115
|
+
marketSession === "closed_after_hours"
|
|
116
|
+
? localNow.date
|
|
117
|
+
: lastTradingDay(localNow.date);
|
|
118
|
+
return providerTradingDate < requiredTradingDate;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isCryptoStale(
|
|
122
|
+
providerDate: Date | undefined,
|
|
123
|
+
cachedAt: string | undefined,
|
|
124
|
+
now: Date,
|
|
125
|
+
cacheStatus: FreshnessStamp["cacheStatus"],
|
|
126
|
+
): boolean {
|
|
127
|
+
const reference =
|
|
128
|
+
providerDate ?? (cacheStatus === "stale" && cachedAt ? new Date(cachedAt) : undefined);
|
|
129
|
+
if (!reference) return cacheStatus === "stale";
|
|
130
|
+
return now.getTime() - reference.getTime() > 15 * 60_000;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatEtDate(date: Date): string {
|
|
134
|
+
return localDateTimeParts(date, "America/New_York").date;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function formatEtDateTime(date: Date): string {
|
|
138
|
+
const parts = localDateTimeParts(date, "America/New_York");
|
|
139
|
+
return `${parts.date} ${parts.time}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function staleSessionParenthetical(marketSession: MarketSession): string {
|
|
143
|
+
if (marketSession === "closed_weekend") return " (market closed — weekend)";
|
|
144
|
+
if (marketSession === "closed_holiday") return " (market closed — holiday)";
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function marketSessionLabel(marketSession: MarketSession): string {
|
|
149
|
+
switch (marketSession) {
|
|
150
|
+
case "open":
|
|
151
|
+
return "market open";
|
|
152
|
+
case "pre_market":
|
|
153
|
+
return "pre-market";
|
|
154
|
+
case "closed_weekend":
|
|
155
|
+
return "market closed — weekend";
|
|
156
|
+
case "closed_holiday":
|
|
157
|
+
return "market closed — holiday";
|
|
158
|
+
case "after_close":
|
|
159
|
+
return "after close";
|
|
160
|
+
case "closed_after_hours":
|
|
161
|
+
return "market closed";
|
|
162
|
+
case "unknown":
|
|
163
|
+
return "market session unknown";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
export type MarketSession =
|
|
2
|
+
| "closed_weekend"
|
|
3
|
+
| "closed_holiday"
|
|
4
|
+
| "pre_market"
|
|
5
|
+
| "open"
|
|
6
|
+
| "after_close"
|
|
7
|
+
| "closed_after_hours"
|
|
8
|
+
| "unknown";
|
|
9
|
+
|
|
10
|
+
export interface LocalDateTimeParts {
|
|
11
|
+
date: string;
|
|
12
|
+
time: string;
|
|
13
|
+
weekday: string;
|
|
14
|
+
minutesSinceMidnight: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const KNOWN_US_MARKET_HOLIDAYS: Record<string, string> = new Proxy(
|
|
18
|
+
{} as Record<string, string>,
|
|
19
|
+
{
|
|
20
|
+
get(_target, property) {
|
|
21
|
+
return typeof property === "string" ? marketHolidayName(property) : undefined;
|
|
22
|
+
},
|
|
23
|
+
has(_target, property) {
|
|
24
|
+
return typeof property === "string" && marketHolidayName(property) !== undefined;
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export function marketHolidayName(key: string): string | undefined {
|
|
30
|
+
const date = dateFromKey(key);
|
|
31
|
+
const year = date.getUTCFullYear();
|
|
32
|
+
for (const candidateYear of [year - 1, year, year + 1]) {
|
|
33
|
+
const holiday = marketHolidaysForYear(candidateYear).find((entry) => entry.date === key);
|
|
34
|
+
if (holiday) return holiday.name;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function classifyMarketStatus(
|
|
40
|
+
local: LocalDateTimeParts,
|
|
41
|
+
isWeekend: boolean,
|
|
42
|
+
isMarketHoliday: boolean,
|
|
43
|
+
temporalReferences: string[],
|
|
44
|
+
): Exclude<MarketSession, "unknown"> {
|
|
45
|
+
if (isWeekend) return "closed_weekend";
|
|
46
|
+
if (isMarketHoliday) return "closed_holiday";
|
|
47
|
+
if (local.minutesSinceMidnight < 9 * 60 + 30) return "pre_market";
|
|
48
|
+
if (local.minutesSinceMidnight < 16 * 60) return "open";
|
|
49
|
+
if (temporalReferences.includes("after_close")) return "after_close";
|
|
50
|
+
return "closed_after_hours";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function classifyMarketStatusAt(now: Date): MarketSession {
|
|
54
|
+
const local = localDateTimeParts(now, "America/New_York");
|
|
55
|
+
return classifyMarketStatus(
|
|
56
|
+
local,
|
|
57
|
+
local.weekday === "Sat" || local.weekday === "Sun",
|
|
58
|
+
marketHolidayName(local.date) !== undefined,
|
|
59
|
+
[],
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function localDateTimeParts(date: Date, timezone: string): LocalDateTimeParts {
|
|
64
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
65
|
+
timeZone: timezone,
|
|
66
|
+
year: "numeric",
|
|
67
|
+
month: "2-digit",
|
|
68
|
+
day: "2-digit",
|
|
69
|
+
hour: "2-digit",
|
|
70
|
+
minute: "2-digit",
|
|
71
|
+
hour12: false,
|
|
72
|
+
weekday: "short",
|
|
73
|
+
});
|
|
74
|
+
const parts = formatter.formatToParts(date).reduce<Record<string, string>>((acc, part) => {
|
|
75
|
+
acc[part.type] = part.value;
|
|
76
|
+
return acc;
|
|
77
|
+
}, {});
|
|
78
|
+
const hour = Number(parts.hour);
|
|
79
|
+
const minute = Number(parts.minute);
|
|
80
|
+
return {
|
|
81
|
+
date: `${parts.year}-${parts.month}-${parts.day}`,
|
|
82
|
+
time: `${parts.hour}:${parts.minute}`,
|
|
83
|
+
weekday: parts.weekday ?? "",
|
|
84
|
+
minutesSinceMidnight: hour * 60 + minute,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function lastTradingDay(localDate: string): string {
|
|
89
|
+
let cursor = dateFromKey(localDate);
|
|
90
|
+
do {
|
|
91
|
+
cursor = addDays(cursor, -1);
|
|
92
|
+
} while (isWeekendOrKnownHoliday(dateKey(cursor)));
|
|
93
|
+
return dateKey(cursor);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function isWeekendOrKnownHoliday(key: string): boolean {
|
|
97
|
+
const date = dateFromKey(key);
|
|
98
|
+
const day = date.getUTCDay();
|
|
99
|
+
return day === 0 || day === 6 || marketHolidayName(key) !== undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function marketHolidaysForYear(year: number): Array<{ date: string; name: string }> {
|
|
103
|
+
return [
|
|
104
|
+
observedFixedHoliday(year, 1, 1, "New Year's Day"),
|
|
105
|
+
nthWeekdayHoliday(year, 1, 1, 3, "Martin Luther King Jr. Day"),
|
|
106
|
+
nthWeekdayHoliday(year, 2, 1, 3, "Washington's Birthday"),
|
|
107
|
+
{ date: dateKey(addDays(easterDate(year), -2)), name: "Good Friday" },
|
|
108
|
+
lastWeekdayHoliday(year, 5, 1, "Memorial Day"),
|
|
109
|
+
observedFixedHoliday(year, 6, 19, "Juneteenth"),
|
|
110
|
+
observedFixedHoliday(year, 7, 4, "Independence Day"),
|
|
111
|
+
nthWeekdayHoliday(year, 9, 1, 1, "Labor Day"),
|
|
112
|
+
nthWeekdayHoliday(year, 11, 4, 4, "Thanksgiving Day"),
|
|
113
|
+
observedFixedHoliday(year, 12, 25, "Christmas Day"),
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function observedFixedHoliday(
|
|
118
|
+
year: number,
|
|
119
|
+
month: number,
|
|
120
|
+
day: number,
|
|
121
|
+
name: string,
|
|
122
|
+
): {
|
|
123
|
+
date: string;
|
|
124
|
+
name: string;
|
|
125
|
+
} {
|
|
126
|
+
const actual = new Date(Date.UTC(year, month - 1, day));
|
|
127
|
+
const weekday = actual.getUTCDay();
|
|
128
|
+
const observed =
|
|
129
|
+
weekday === 0 ? addDays(actual, 1) : weekday === 6 ? addDays(actual, -1) : actual;
|
|
130
|
+
return {
|
|
131
|
+
date: dateKey(observed),
|
|
132
|
+
name: observed.getTime() === actual.getTime() ? name : `${name} observed`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function nthWeekdayHoliday(
|
|
137
|
+
year: number,
|
|
138
|
+
month: number,
|
|
139
|
+
weekday: number,
|
|
140
|
+
occurrence: number,
|
|
141
|
+
name: string,
|
|
142
|
+
): { date: string; name: string } {
|
|
143
|
+
const first = new Date(Date.UTC(year, month - 1, 1));
|
|
144
|
+
const offset = (weekday - first.getUTCDay() + 7) % 7;
|
|
145
|
+
return { date: dateKey(addDays(first, offset + (occurrence - 1) * 7)), name };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function lastWeekdayHoliday(
|
|
149
|
+
year: number,
|
|
150
|
+
month: number,
|
|
151
|
+
weekday: number,
|
|
152
|
+
name: string,
|
|
153
|
+
): { date: string; name: string } {
|
|
154
|
+
const last = new Date(Date.UTC(year, month, 0));
|
|
155
|
+
const offset = (last.getUTCDay() - weekday + 7) % 7;
|
|
156
|
+
return { date: dateKey(addDays(last, -offset)), name };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function easterDate(year: number): Date {
|
|
160
|
+
const a = year % 19;
|
|
161
|
+
const b = Math.floor(year / 100);
|
|
162
|
+
const c = year % 100;
|
|
163
|
+
const d = Math.floor(b / 4);
|
|
164
|
+
const e = b % 4;
|
|
165
|
+
const f = Math.floor((b + 8) / 25);
|
|
166
|
+
const g = Math.floor((b - f + 1) / 3);
|
|
167
|
+
const h = (19 * a + b - d - g + 15) % 30;
|
|
168
|
+
const i = Math.floor(c / 4);
|
|
169
|
+
const k = c % 4;
|
|
170
|
+
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
|
171
|
+
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
|
172
|
+
const easterMonth = Math.floor((h + l - 7 * m + 114) / 31);
|
|
173
|
+
const easterDay = ((h + l - 7 * m + 114) % 31) + 1;
|
|
174
|
+
return new Date(Date.UTC(year, easterMonth - 1, easterDay));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function dateFromKey(key: string): Date {
|
|
178
|
+
const [year, month, day] = key.split("-").map(Number);
|
|
179
|
+
return new Date(Date.UTC(year, month - 1, day));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function addDays(date: Date, days: number): Date {
|
|
183
|
+
const next = new Date(date);
|
|
184
|
+
next.setUTCDate(next.getUTCDate() + days);
|
|
185
|
+
return next;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function dateKey(date: Date): string {
|
|
189
|
+
const year = date.getUTCFullYear();
|
|
190
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
191
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
192
|
+
return `${year}-${month}-${day}`;
|
|
193
|
+
}
|
|
@@ -72,5 +72,6 @@ rateLimiter.configure("exa", 5, 0.1); // 5 req, ~6 req/min
|
|
|
72
72
|
rateLimiter.configure("finnhub", 60, 1); // 60 req/min (free tier)
|
|
73
73
|
// TradingView scanner is undocumented; keep usage batch-first and paced.
|
|
74
74
|
rateLimiter.configure("tradingview", 5, 1); // 5 burst, 1 req/s sustained
|
|
75
|
+
rateLimiter.configure("polymarket", 10, 5); // 10 burst, 5 req/s sustained
|
|
75
76
|
// SEC EDGAR fair-access guideline is 10 req/s; stay below it.
|
|
76
77
|
rateLimiter.configure("sec_edgar", 5, 5); // 5 burst, 5 req/s sustained
|
|
@@ -17,25 +17,18 @@ export interface DailyWatchlistReport {
|
|
|
17
17
|
dataGaps: string[];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
// Returns the configured default-watchlist morning template if one exists.
|
|
21
|
+
// Manual report runs must never create a schedule template as a side effect;
|
|
22
|
+
// only the explicit configure flow stores schedule intent.
|
|
23
|
+
export function findDefaultWatchlistReportTemplate(
|
|
21
24
|
service: MarketStateService,
|
|
22
|
-
): ReportTemplateRecord {
|
|
23
|
-
|
|
25
|
+
): ReportTemplateRecord | undefined {
|
|
26
|
+
return service
|
|
24
27
|
.listReportTemplates()
|
|
25
28
|
.find(
|
|
26
29
|
(template) =>
|
|
27
30
|
template.reportType === "watchlist_daily" && targetsDefaultWatchlist(template.configJson),
|
|
28
31
|
);
|
|
29
|
-
if (existing) return existing;
|
|
30
|
-
return service.createReportTemplate({
|
|
31
|
-
name: "Morning watchlist",
|
|
32
|
-
reportType: "watchlist_daily",
|
|
33
|
-
cadence: "daily",
|
|
34
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
35
|
-
localTime: "08:00",
|
|
36
|
-
config: { targets: { default_watchlist: true } },
|
|
37
|
-
enabled: true,
|
|
38
|
-
});
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
export async function recordDailyWatchlistReportRun(
|
|
@@ -129,9 +122,6 @@ export async function generateDailyWatchlistReport(
|
|
|
129
122
|
`Recent alerts`,
|
|
130
123
|
` ${service.listAlertEvents().length} recorded alert event(s).`,
|
|
131
124
|
``,
|
|
132
|
-
`Technical snapshot`,
|
|
133
|
-
` Deferred unless quote/history data is available through a later section builder.`,
|
|
134
|
-
``,
|
|
135
125
|
`Data gaps`,
|
|
136
126
|
...dataGapLines,
|
|
137
127
|
];
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type Database from "better-sqlite3";
|
|
2
2
|
|
|
3
3
|
export type AssetType = "equity" | "etf" | "fund" | "crypto" | "index" | "option" | "unknown";
|
|
4
|
-
export type PredictionDirection = "bullish" | "bearish" | "neutral";
|
|
5
|
-
export type PredictionStatus = "open" | "resolved" | "expired" | "cancelled";
|
|
6
4
|
export type AlertScopeType = "instrument" | "watchlist" | "portfolio";
|
|
7
5
|
|
|
8
6
|
export interface InstrumentInput {
|
|
@@ -100,23 +98,6 @@ export interface PortfolioLotRecord {
|
|
|
100
98
|
updatedAt: string;
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
export interface PredictionRecord {
|
|
104
|
-
id: number;
|
|
105
|
-
instrumentId: number;
|
|
106
|
-
symbol: string;
|
|
107
|
-
direction: PredictionDirection;
|
|
108
|
-
conviction: number;
|
|
109
|
-
entryPrice: number;
|
|
110
|
-
targetPrice: number | null;
|
|
111
|
-
openedAt: string;
|
|
112
|
-
expiresAt: string;
|
|
113
|
-
status: PredictionStatus;
|
|
114
|
-
resolvedAt: string | null;
|
|
115
|
-
resultJson: string | null;
|
|
116
|
-
createdAt: string;
|
|
117
|
-
updatedAt: string;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
101
|
export interface AlertRuleRecord {
|
|
121
102
|
id: number;
|
|
122
103
|
scopeType: AlertScopeType;
|
|
@@ -337,23 +318,6 @@ type PortfolioLotRow = {
|
|
|
337
318
|
updated_at: string;
|
|
338
319
|
};
|
|
339
320
|
|
|
340
|
-
type PredictionRow = {
|
|
341
|
-
id: number;
|
|
342
|
-
instrument_id: number;
|
|
343
|
-
symbol: string;
|
|
344
|
-
direction: PredictionDirection;
|
|
345
|
-
conviction: number;
|
|
346
|
-
entry_price: number;
|
|
347
|
-
target_price: number | null;
|
|
348
|
-
opened_at: string;
|
|
349
|
-
expires_at: string;
|
|
350
|
-
status: PredictionStatus;
|
|
351
|
-
resolved_at: string | null;
|
|
352
|
-
result_json: string | null;
|
|
353
|
-
created_at: string;
|
|
354
|
-
updated_at: string;
|
|
355
|
-
};
|
|
356
|
-
|
|
357
321
|
type AlertRuleRow = {
|
|
358
322
|
id: number;
|
|
359
323
|
scope_type: AlertScopeType;
|
|
@@ -877,75 +841,6 @@ export class MarketStateService {
|
|
|
877
841
|
});
|
|
878
842
|
}
|
|
879
843
|
|
|
880
|
-
recordPrediction(params: {
|
|
881
|
-
instrument: InstrumentInput;
|
|
882
|
-
direction: PredictionDirection;
|
|
883
|
-
conviction: number;
|
|
884
|
-
entryPrice: number;
|
|
885
|
-
targetPrice?: number;
|
|
886
|
-
timeframeDays: number;
|
|
887
|
-
now?: Date;
|
|
888
|
-
}): PredictionRecord {
|
|
889
|
-
const tx = this.db.transaction(() => {
|
|
890
|
-
const instrument = this.upsertInstrument(params.instrument);
|
|
891
|
-
const opened = params.now ?? new Date();
|
|
892
|
-
const expires = new Date(opened);
|
|
893
|
-
expires.setDate(expires.getDate() + params.timeframeDays);
|
|
894
|
-
const nowIso = opened.toISOString();
|
|
895
|
-
const result = this.db
|
|
896
|
-
.prepare(
|
|
897
|
-
`INSERT INTO prediction_records (
|
|
898
|
-
instrument_id, direction, conviction, entry_price, target_price,
|
|
899
|
-
opened_at, expires_at, status, created_at, updated_at
|
|
900
|
-
)
|
|
901
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 'open', ?, ?)`,
|
|
902
|
-
)
|
|
903
|
-
.run(
|
|
904
|
-
instrument.id,
|
|
905
|
-
params.direction,
|
|
906
|
-
params.conviction,
|
|
907
|
-
params.entryPrice,
|
|
908
|
-
params.targetPrice ?? null,
|
|
909
|
-
nowIso,
|
|
910
|
-
expires.toISOString(),
|
|
911
|
-
nowIso,
|
|
912
|
-
nowIso,
|
|
913
|
-
);
|
|
914
|
-
return Number(result.lastInsertRowid);
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
return this.getPrediction(tx());
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
listPredictions(): PredictionRecord[] {
|
|
921
|
-
const rows = this.db
|
|
922
|
-
.prepare(
|
|
923
|
-
`SELECT pr.*, i.symbol
|
|
924
|
-
FROM prediction_records pr
|
|
925
|
-
JOIN instruments i ON i.id = pr.instrument_id
|
|
926
|
-
ORDER BY pr.opened_at, pr.id`,
|
|
927
|
-
)
|
|
928
|
-
.all() as PredictionRow[];
|
|
929
|
-
return rows.map(mapPrediction);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
updatePredictionOutcome(params: {
|
|
933
|
-
id: number;
|
|
934
|
-
status: Exclude<PredictionStatus, "open">;
|
|
935
|
-
resolvedAt: string;
|
|
936
|
-
result: unknown;
|
|
937
|
-
}): PredictionRecord {
|
|
938
|
-
const now = new Date().toISOString();
|
|
939
|
-
this.db
|
|
940
|
-
.prepare(
|
|
941
|
-
`UPDATE prediction_records
|
|
942
|
-
SET status = ?, resolved_at = ?, result_json = ?, updated_at = ?
|
|
943
|
-
WHERE id = ?`,
|
|
944
|
-
)
|
|
945
|
-
.run(params.status, params.resolvedAt, JSON.stringify(params.result), now, params.id);
|
|
946
|
-
return this.getPrediction(params.id);
|
|
947
|
-
}
|
|
948
|
-
|
|
949
844
|
createAlertRule(params: {
|
|
950
845
|
scopeType: AlertScopeType;
|
|
951
846
|
scopeId?: number;
|
|
@@ -1998,18 +1893,6 @@ export class MarketStateService {
|
|
|
1998
1893
|
return row == null ? null : mapPortfolioLot(row);
|
|
1999
1894
|
}
|
|
2000
1895
|
|
|
2001
|
-
private getPrediction(id: number): PredictionRecord {
|
|
2002
|
-
const row = this.db
|
|
2003
|
-
.prepare(
|
|
2004
|
-
`SELECT pr.*, i.symbol
|
|
2005
|
-
FROM prediction_records pr
|
|
2006
|
-
JOIN instruments i ON i.id = pr.instrument_id
|
|
2007
|
-
WHERE pr.id = ?`,
|
|
2008
|
-
)
|
|
2009
|
-
.get(id) as PredictionRow;
|
|
2010
|
-
return mapPrediction(row);
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
1896
|
getAlertRule(id: number): AlertRuleRecord {
|
|
2014
1897
|
const row = this.db.prepare("SELECT * FROM alert_rules WHERE id = ?").get(id) as AlertRuleRow;
|
|
2015
1898
|
return mapAlertRule(row);
|
|
@@ -2118,25 +2001,6 @@ function mapPortfolioLot(row: PortfolioLotRow): PortfolioLotRecord {
|
|
|
2118
2001
|
};
|
|
2119
2002
|
}
|
|
2120
2003
|
|
|
2121
|
-
function mapPrediction(row: PredictionRow): PredictionRecord {
|
|
2122
|
-
return {
|
|
2123
|
-
id: row.id,
|
|
2124
|
-
instrumentId: row.instrument_id,
|
|
2125
|
-
symbol: row.symbol,
|
|
2126
|
-
direction: row.direction,
|
|
2127
|
-
conviction: row.conviction,
|
|
2128
|
-
entryPrice: row.entry_price,
|
|
2129
|
-
targetPrice: row.target_price,
|
|
2130
|
-
openedAt: row.opened_at,
|
|
2131
|
-
expiresAt: row.expires_at,
|
|
2132
|
-
status: row.status,
|
|
2133
|
-
resolvedAt: row.resolved_at,
|
|
2134
|
-
resultJson: row.result_json,
|
|
2135
|
-
createdAt: row.created_at,
|
|
2136
|
-
updatedAt: row.updated_at,
|
|
2137
|
-
};
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
2004
|
function mapAlertRule(row: AlertRuleRow): AlertRuleRecord {
|
|
2141
2005
|
return {
|
|
2142
2006
|
id: row.id,
|