tradeblocks-mcp 3.0.0 → 3.0.2
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/{server/chunk-JAAQMESY.js → dist/chunk-XXYOUIZY.js} +699 -111
- package/dist/chunk-XXYOUIZY.js.map +1 -0
- package/dist/iv-solver-worker.js +295 -0
- package/dist/{market-provider-GGLA7ACC.js → market-provider-VDRJUEF2.js} +2 -2
- package/dist/test-exports.js +10 -2
- package/dist/test-exports.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +4 -2
- package/{dist/chunk-CCITWAAI.js → server/chunk-PNKG7RY7.js} +455 -311
- package/server/chunk-PNKG7RY7.js.map +1 -0
- package/server/index.js +18 -6
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +295 -0
- package/server/{market-provider-D5SI47K7.js → market-provider-VOYYVYWT.js} +2 -2
- package/src/index.ts +20 -3
- package/src/market/ingestor/market-ingestor.ts +2 -2
- package/src/test-exports.ts +8 -0
- package/src/utils/iv-solver-pool.ts +396 -0
- package/src/utils/iv-solver-worker.ts +112 -0
- package/src/utils/option-quote-greeks.ts +246 -51
- package/src/utils/providers/thetadata/decode.ts +16 -10
- package/dist/chunk-CCITWAAI.js.map +0 -1
- package/server/chunk-JAAQMESY.js.map +0 -1
- /package/dist/{market-provider-GGLA7ACC.js.map → market-provider-VDRJUEF2.js.map} +0 -0
- /package/server/{market-provider-D5SI47K7.js.map → market-provider-VOYYVYWT.js.map} +0 -0
package/dist/test-exports.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/trading-dates.ts","../src/tools/shared/filters.ts","../src/utils/schema-metadata.ts","../src/utils/field-timing.ts","../src/utils/data-availability.ts","../src/utils/data-quality.ts","../src/utils/market-enricher.ts","../src/utils/market-importer.ts","../src/utils/analysis-stats.ts","../src/utils/filter-predicates.ts","../src/tools/profiles.ts","../src/utils/output-formatter.ts","../src/tools/profile-analysis.ts","../src/tools/regime-advisor.ts","../src/utils/provider-capabilities.ts","../src/tools/replay.ts","../src/market/tickers/resolver.ts","../src/tools/snapshot.ts","../src/utils/greeks-decomposition.ts","../src/utils/exit-triggers.ts","../src/tools/exit-analysis.ts","../src/utils/batch-exit-analysis.ts","../src/tools/batch-exit-analysis.ts","../src/utils/quote-enricher.ts","../src/tools/sql.ts","../src/utils/chain-loader.ts","../src/utils/providers/thetadata/backfill.ts","../src/tools/greeks-attribution.ts","../src/utils/flatfile-importer.ts","../src/market/stores/spot-store.ts","../src/market/stores/coverage.ts","../src/market/stores/enriched-store.ts","../src/market/stores/chain-sql.ts","../src/market/stores/chain-store.ts","../src/market/stores/quote-store.ts","../src/market/stores/parquet-spot-store.ts","../src/market/stores/spot-sql.ts","../src/market/stores/duckdb-spot-store.ts","../src/market/stores/parquet-enriched-store.ts","../src/market/stores/rth-aggregation.ts","../src/market/stores/enriched-sql.ts","../src/market/stores/duckdb-enriched-store.ts","../src/market/stores/parquet-chain-store.ts","../src/market/stores/duckdb-chain-store.ts","../src/market/stores/parquet-quote-store.ts","../src/market/stores/duckdb-quote-store.ts","../src/market/stores/parquet-oi-daily-store.ts","../src/market/stores/index.ts","../src/market/tickers/schemas.ts","../src/market/tickers/registry.ts","../src/market/tickers/loader.ts","../src/market/tickers/defaults.json","../src/tools/tickers.ts","../src/market/stores/quote-sql.ts","../src/market/ingestor/market-ingestor.ts","../src/utils/migrate-option-data-helpers.ts","../src/utils/sample-date-selector.ts","../src/utils/enrichment-verification.ts","../src/utils/calibration-probe.ts"],"sourcesContent":["/**\n * Compute yesterday's calendar date in America/New_York (ET) as YYYY-MM-DD.\n *\n * Used by the daily market-data refresh script: even though the homelab runs\n * in America/Chicago, the script must always target ET trading dates because\n * that's what ThetaData and the market data partitions are keyed on.\n *\n * Implementation: format `now` in ET to extract today's ET calendar date,\n * then subtract one day via UTC arithmetic on a date constructed from those\n * Y/M/D components. Subtracting via UTC avoids local-timezone DST drift on\n * the host because we never round-trip the result back through a local TZ.\n *\n * @param now Reference timestamp; defaults to current wall clock.\n */\nexport function yesterdayET(now: Date = new Date()): string {\n // en-CA gives \"YYYY-MM-DD\" formatting directly.\n const todayET = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: \"America/New_York\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(now);\n\n const [y, m, d] = todayET.split(\"-\").map(Number);\n // Subtract one day via UTC arithmetic; setUTCDate(0) on the first of the\n // month rolls back to the last day of the prior month, etc.\n const prior = new Date(Date.UTC(y, m - 1, d));\n prior.setUTCDate(prior.getUTCDate() - 1);\n\n const py = prior.getUTCFullYear();\n const pm = String(prior.getUTCMonth() + 1).padStart(2, \"0\");\n const pd = String(prior.getUTCDate()).padStart(2, \"0\");\n return `${py}-${pm}-${pd}`;\n}\n","/**\n * Shared Filter Utilities\n *\n * Common filtering functions used across block and report tools.\n */\n\nimport type { Trade, DailyLogEntry } from \"@tradeblocks/lib\";\n\n/**\n * Filter trades by strategy name (case-insensitive)\n */\nexport function filterByStrategy(trades: Trade[], strategy?: string): Trade[] {\n if (!strategy) return trades;\n return trades.filter(\n (t) => t.strategy.toLowerCase() === strategy.toLowerCase()\n );\n}\n\n/**\n * Validate that a date string is in YYYY-MM-DD format.\n * Returns the string if valid, undefined if not (skips that filter boundary).\n */\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\nfunction validateDateParam(date: string | undefined): string | undefined {\n if (!date) return undefined;\n return DATE_RE.test(date) ? date : undefined;\n}\n\n/**\n * Extract YYYY-MM-DD calendar date from a Date or string.\n * Trades are parsed via parseDatePreservingCalendarDay() which creates dates at\n * local midnight. Use local date components to preserve the calendar date,\n * avoiding timezone shift when the server runs in UTC.\n */\nfunction toCalendarDateStr(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Filter trades by date range using string comparison on Eastern Time calendar dates.\n * Avoids timezone bugs from mixing UTC Date parsing with local time setHours.\n * Malformed date inputs (not YYYY-MM-DD) are silently ignored.\n */\nexport function filterByDateRange(\n trades: Trade[],\n startDate?: string,\n endDate?: string\n): Trade[] {\n const start = validateDateParam(startDate);\n const end = validateDateParam(endDate);\n let filtered = trades;\n\n if (start) {\n filtered = filtered.filter((t) => toCalendarDateStr(t.dateOpened) >= start);\n }\n\n if (end) {\n filtered = filtered.filter((t) => toCalendarDateStr(t.dateOpened) <= end);\n }\n\n return filtered;\n}\n\n/**\n * Filter daily log entries by date range using string comparison on calendar dates.\n * Mirrors filterByDateRange but uses entry.date (Date object) instead of t.dateOpened.\n * Malformed date inputs (not YYYY-MM-DD) are silently ignored.\n */\nexport function filterDailyLogsByDateRange(\n dailyLogs: DailyLogEntry[],\n startDate?: string,\n endDate?: string\n): DailyLogEntry[] {\n const start = validateDateParam(startDate);\n const end = validateDateParam(endDate);\n let filtered = dailyLogs;\n\n if (start) {\n filtered = filtered.filter((entry) => toCalendarDateStr(entry.date) >= start);\n }\n\n if (end) {\n filtered = filtered.filter((entry) => toCalendarDateStr(entry.date) <= end);\n }\n\n return filtered;\n}\n","/**\n * Schema Metadata\n *\n * Hardcoded descriptions for DuckDB tables and columns, plus example queries.\n * Used by describe_database tool to provide context for SQL query writing.\n *\n * Tables are organized by schema:\n * - trades: Trade data from CSV files\n * - market: Canonical market datasets post-v3.0 —\n * * spot (raw minute bars, ticker-first)\n * * spot_daily (view-backed RTH-aggregated daily OHLCV)\n * * enriched (per-ticker computed indicators + ivr/ivp — NO OHLCV)\n * * enriched_context (cross-ticker derived fields: Vol_Regime, Term_Structure_State)\n * * option_chain / option_quote_minutes (option contract universe + quote cache)\n * OHLCV callers must LEFT JOIN market.spot_daily on ticker+date; enriched alone does not carry open/high/low/close.\n */\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\nexport interface ColumnDescription {\n /** Human-readable description of what this column contains */\n description: string;\n /** True if this column is useful for hypothesis testing (filtering, grouping, analysis) */\n hypothesis: boolean;\n /** When this field's value is known relative to market open.\n * - 'open': Known at/before market open (Prior_Close, Gap_Pct, VIX_Open, etc.)\n * - 'close': Only known after market close (RSI_14, Vol_Regime, Close, etc.)\n * - 'static': Calendar/metadata facts known before the day (Day_of_Week, Month, Is_Opex)\n * Only applicable to market.enriched and market.enriched_context columns. Omit for non-market tables.\n */\n timing?: 'open' | 'close' | 'static';\n}\n\nexport interface TableDescription {\n /** Human-readable description of this table's purpose */\n description: string;\n /** Key columns that are most important for analysis */\n keyColumns: string[];\n /** Column descriptions by column name */\n columns: Record<string, ColumnDescription>;\n}\n\nexport interface SchemaDescription {\n /** Human-readable description of this schema's purpose */\n description: string;\n /** Tables in this schema */\n tables: Record<string, TableDescription>;\n}\n\nexport interface SchemaMetadata {\n trades: SchemaDescription;\n market: SchemaDescription;\n}\n\nexport interface ExampleQuery {\n /** What this query does */\n description: string;\n /** The SQL query */\n sql: string;\n}\n\nexport interface ExampleQueries {\n /** Basic single-table queries */\n basic: ExampleQuery[];\n /** JOIN queries between trades and market data */\n joins: ExampleQuery[];\n /** Hypothesis testing patterns */\n hypothesis: ExampleQuery[];\n}\n\n// ============================================================================\n// Schema Descriptions\n// ============================================================================\n\nexport const SCHEMA_DESCRIPTIONS: SchemaMetadata = {\n trades: {\n description:\n \"Trading data synced from CSV files. Contains trade records from all portfolio blocks, including both backtest (trade_data) and actual/reported (reporting_data) trades.\",\n tables: {\n trade_data: {\n description:\n \"Individual backtest trade records. Each row = one trade with entry/exit details, P&L, and strategy. Filter by block_id to query specific portfolios.\",\n keyColumns: [\"block_id\", \"date_opened\", \"strategy\", \"pl\"],\n columns: {\n block_id: {\n description: \"Portfolio block ID - filter by this to query specific portfolios\",\n hypothesis: true,\n },\n date_opened: {\n description: \"Trade entry date (DATE format, use for joins with market data)\",\n hypothesis: true,\n },\n time_opened: {\n description: \"Trade entry time in Eastern Time (e.g., '09:35:00')\",\n hypothesis: false,\n },\n strategy: {\n description: \"Strategy name (e.g., 'IronCondor', 'PutSpread')\",\n hypothesis: true,\n },\n legs: {\n description: \"Option legs description (e.g., 'SPY 450P/445P')\",\n hypothesis: false,\n },\n premium: {\n description: \"Credit received (+) or debit paid (-)\",\n hypothesis: false,\n },\n num_contracts: {\n description: \"Number of contracts traded\",\n hypothesis: false,\n },\n pl: {\n description: \"Gross P&L before commissions (DOUBLE)\",\n hypothesis: true,\n },\n date_closed: {\n description: \"Trade exit date (NULL if still open)\",\n hypothesis: false,\n },\n time_closed: {\n description: \"Trade exit time in Eastern Time\",\n hypothesis: false,\n },\n reason_for_close: {\n description: \"Exit reason (e.g., 'Target', 'Stop', 'Expiration')\",\n hypothesis: true,\n },\n margin_req: {\n description: \"Margin requirement for the position ($)\",\n hypothesis: false,\n },\n opening_commissions: {\n description: \"Commissions paid at entry ($)\",\n hypothesis: false,\n },\n closing_commissions: {\n description: \"Commissions paid at exit ($)\",\n hypothesis: false,\n },\n },\n },\n reporting_data: {\n description:\n \"Actual/reported trade records from reportinglog.csv. Each row = one live trade executed. Compare with trade_data (backtest) to analyze slippage and execution differences. Filter by block_id to query specific portfolios.\",\n keyColumns: [\"block_id\", \"date_opened\", \"strategy\", \"legs\", \"pl\"],\n columns: {\n block_id: {\n description: \"Portfolio block ID - filter by this to query specific portfolios\",\n hypothesis: true,\n },\n date_opened: {\n description: \"Trade entry date (DATE format, use for joins with market data)\",\n hypothesis: true,\n },\n time_opened: {\n description: \"Trade entry time in Eastern Time (e.g., '09:35:00')\",\n hypothesis: false,\n },\n strategy: {\n description: \"Strategy name (e.g., 'IronCondor', 'PutSpread')\",\n hypothesis: true,\n },\n legs: {\n description: \"Option legs description with strikes (e.g., 'SPY 450P/445P') - compare with trade_data.legs to identify strike differences\",\n hypothesis: true,\n },\n initial_premium: {\n description: \"Credit received (+) or debit paid (-) at entry\",\n hypothesis: false,\n },\n num_contracts: {\n description: \"Number of contracts traded (often fewer than backtest)\",\n hypothesis: false,\n },\n pl: {\n description: \"Actual P&L realized (DOUBLE)\",\n hypothesis: true,\n },\n date_closed: {\n description: \"Trade exit date (NULL if still open)\",\n hypothesis: false,\n },\n time_closed: {\n description: \"Trade exit time in Eastern Time\",\n hypothesis: false,\n },\n closing_price: {\n description: \"Price at exit\",\n hypothesis: false,\n },\n avg_closing_cost: {\n description: \"Average cost to close the position\",\n hypothesis: false,\n },\n reason_for_close: {\n description: \"Exit reason (e.g., 'Target', 'Stop', 'Expiration')\",\n hypothesis: true,\n },\n opening_price: {\n description: \"Price at entry\",\n hypothesis: false,\n },\n },\n },\n },\n },\n market: {\n description:\n \"Canonical market data for hypothesis testing (v3.0 layout). Normalized into six datasets: spot (raw minute bars, ticker-first), spot_daily (view-backed RTH-aggregated daily OHLCV derived from market.spot), enriched (per-ticker computed Tier 1 indicators + ivr/ivp for VIX-family tickers; NO OHLCV), enriched_context (cross-ticker derived fields like Vol_Regime), option_chain (contract universe by date), and option_quote_minutes (dense option quote cache by minute, including persisted minute greeks when provider or computed fallback data is available). OHLCV-using queries must LEFT JOIN market.spot_daily on ticker+date because market.enriched does not carry open/high/low/close. Source: market/ Parquet files and provider imports.\",\n tables: {\n enriched: {\n description:\n \"Per-ticker computed enrichment indicators and calendar fields. One row per ticker per trading day. OHLCV is NOT stored here — LEFT JOIN market.spot_daily on ticker+date for open/high/low/close/bid/ask. JOIN with trades on ticker+date (e.g., d.ticker = 'SPX' AND t.date_opened = d.date). VIX-family tickers (VIX, VIX9D, VIX3M, etc.) also have ivr/ivp columns populated. For trade-entry queries, use LAG() on close-derived fields. Join market.enriched_context (LEFT JOIN on date) for Vol_Regime, Term_Structure_State, etc.\",\n keyColumns: [\"ticker\", \"date\", \"RSI_14\", \"ATR_Pct\", \"Realized_Vol_20D\"],\n columns: {\n ticker: {\n description: \"Underlying ticker symbol (part of composite primary key with date).\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Composite primary key with ticker.\",\n hypothesis: true,\n },\n // Raw OHLCV\n open: {\n description: \"Underlying open price\",\n hypothesis: false,\n timing: 'open',\n },\n high: {\n description: \"Underlying high price\",\n hypothesis: false,\n timing: 'close',\n },\n low: {\n description: \"Underlying low price\",\n hypothesis: false,\n timing: 'close',\n },\n close: {\n description: \"Underlying close price\",\n hypothesis: false,\n timing: 'close',\n },\n Prior_Close: {\n description: \"Previous day's close price\",\n hypothesis: false,\n timing: 'open',\n },\n // Tier 1 enrichment — open-known\n Gap_Pct: {\n description: \"Overnight gap percentage ((Open - Prior_Close) / Prior_Close * 100)\",\n hypothesis: true,\n timing: 'open',\n },\n Prev_Return_Pct: {\n description: \"Previous day's total return percentage (prior close to prior close)\",\n hypothesis: true,\n timing: 'open',\n },\n Prior_Range_vs_ATR: {\n description: \"Prior trading day's (high - low) / ATR ratio, measures prior day's range relative to average true range\",\n hypothesis: true,\n timing: 'open',\n },\n // Tier 1 enrichment — close-derived\n ATR_Pct: {\n description: \"Average True Range as percentage of price (14-day Wilder smoothing)\",\n hypothesis: true,\n timing: 'close',\n },\n RSI_14: {\n description: \"14-day RSI (0-100, >70 overbought, <30 oversold)\",\n hypothesis: true,\n timing: 'close',\n },\n Price_vs_EMA21_Pct: {\n description: \"Price vs 21-day EMA as percentage ((close - EMA21) / EMA21 * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Price_vs_SMA50_Pct: {\n description: \"Price vs 50-day SMA as percentage ((close - SMA50) / SMA50 * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Realized_Vol_5D: {\n description: \"5-day realized volatility (annualized standard deviation of log returns)\",\n hypothesis: true,\n timing: 'close',\n },\n Realized_Vol_20D: {\n description: \"20-day realized volatility (annualized standard deviation of log returns)\",\n hypothesis: true,\n timing: 'close',\n },\n Return_5D: {\n description: \"5-day cumulative return percentage\",\n hypothesis: true,\n timing: 'close',\n },\n Return_20D: {\n description: \"20-day cumulative return percentage\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Range_Pct: {\n description: \"Intraday range as percentage ((High - Low) / Open * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Return_Pct: {\n description: \"Open to close return percentage ((Close - Open) / Open * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Close_Position_In_Range: {\n description: \"Where close is in day's range (0 = low, 1 = high)\",\n hypothesis: true,\n timing: 'close',\n },\n Gap_Filled: {\n description: \"Whether overnight gap was filled (1 = yes, 0 = no)\",\n hypothesis: true,\n timing: 'close',\n },\n Consecutive_Days: {\n description: \"Consecutive up/down days (positive=up, negative=down)\",\n hypothesis: true,\n timing: 'close',\n },\n // Tier 3 intraday timing (columns exist in schema, enrichment deferred)\n High_Time: {\n description: \"Time of day high as decimal hours (e.g., 10.5 = 10:30 AM ET)\",\n hypothesis: true,\n timing: 'close',\n },\n Low_Time: {\n description: \"Time of day low as decimal hours (e.g., 14.25 = 2:15 PM ET)\",\n hypothesis: true,\n timing: 'close',\n },\n High_Before_Low: {\n description: \"Did high occur before low? (1=yes, 0=no)\",\n hypothesis: true,\n timing: 'close',\n },\n Reversal_Type: {\n description: \"Reversal pattern type (1=morning reversal up, -1=morning reversal down, 0=trend day)\",\n hypothesis: true,\n timing: 'close',\n },\n Opening_Drive_Strength: {\n description: \"First-30-min range / full-day range ratio (0-1); higher = strong opening drive\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Realized_Vol: {\n description: \"Annualized realized volatility from intraday bar returns (decimal, e.g., 0.15 = 15%)\",\n hypothesis: true,\n timing: 'close',\n },\n // Calendar fields — static\n Day_of_Week: {\n description: \"Day of week (2=Monday through 6=Friday)\",\n hypothesis: true,\n timing: 'static',\n },\n Month: {\n description: \"Month number (1-12)\",\n hypothesis: true,\n timing: 'static',\n },\n Is_Opex: {\n description: \"Options expiration day flag (1=opex, 0=not)\",\n hypothesis: true,\n timing: 'static',\n },\n // VIX-family ticker IVR/IVP (populated for VIX, VIX9D, VIX3M, etc.)\n ivr: {\n description: \"Implied Volatility Rank (252-day): where current close sits in range (0=min, 100=max). Populated for VIX-family tickers only.\",\n hypothesis: true,\n timing: 'close',\n },\n ivp: {\n description: \"Implied Volatility Percentile (252-day): percentage of prior 251 trading days where close was at or below current level (0-100). Populated for VIX-family tickers only.\",\n hypothesis: true,\n timing: 'close',\n },\n },\n },\n enriched_context: {\n description:\n \"Cross-ticker derived market context fields per trading day. Contains Vol_Regime, Term_Structure_State, and other fields derived from multiple VIX tickers. JOIN with market.enriched on date. VIX IVR/IVP live in market.enriched (ticker='VIX', 'VIX9D', etc.); VIX OHLCV lives in market.spot_daily.\",\n keyColumns: [\"date\", \"Vol_Regime\", \"Term_Structure_State\"],\n columns: {\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Primary key.\",\n hypothesis: true,\n },\n Vol_Regime: {\n description: \"Volatility regime classification based on VIX close (1=very low <10, 2=low 10-15, 3=normal 15-20, 4=elevated 20-25, 5=high 25-30, 6=extreme >30)\",\n hypothesis: true,\n timing: 'close',\n },\n Term_Structure_State: {\n description: \"VIX term structure state based on VIX9D/VIX ratio (-1=backwardation/inverted, 0=flat, 1=contango/normal). NULL when VIX9D data is absent.\",\n hypothesis: true,\n timing: 'close',\n },\n Trend_Direction: {\n description: \"Trend direction classification based on 20-day return: up (>1%), down (<-1%), flat (-1% to 1%). NULL if Return_20D unavailable.\",\n hypothesis: true,\n timing: 'close',\n },\n VIX_Spike_Pct: {\n description: \"VIX spike from open to high as percentage\",\n hypothesis: true,\n timing: 'close',\n },\n VIX_Gap_Pct: {\n description: \"VIX overnight gap percentage ((VIX_Open - prior VIX_Close) / prior VIX_Close * 100)\",\n hypothesis: true,\n timing: 'open',\n },\n },\n },\n spot: {\n description:\n \"Raw minute bars per ticker (ticker-first Hive partitioned Parquet). One row per bar. Use for ORB calculations and intraday context enrichment. Time column is Eastern Time HH:MM format (e.g., '09:30'). Filter by ticker='VIX' to get VIX intraday data.\",\n keyColumns: [\"ticker\", \"date\", \"time\"],\n columns: {\n ticker: {\n description: \"Underlying ticker symbol (part of composite primary key with date and time).\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Part of composite primary key.\",\n hypothesis: true,\n },\n time: {\n description: \"Bar time in HH:MM Eastern Time format (e.g., '09:30', '10:00'). Part of composite primary key.\",\n hypothesis: false,\n },\n open: {\n description: \"Bar open price\",\n hypothesis: false,\n },\n high: {\n description: \"Bar high price\",\n hypothesis: false,\n },\n low: {\n description: \"Bar low price\",\n hypothesis: false,\n },\n close: {\n description: \"Bar close price\",\n hypothesis: false,\n },\n bid: {\n description: \"Bar best bid\",\n hypothesis: false,\n },\n ask: {\n description: \"Bar best ask\",\n hypothesis: false,\n },\n },\n },\n spot_daily: {\n description:\n \"View-backed RTH-aggregated daily OHLCV derived from market.spot (first-open, max-high, min-low, last-close, first-bid, last-ask over 09:30–16:00 bars). One row per ticker per trading day. LEFT JOIN on ticker+date to retrieve OHLCV alongside market.enriched indicators (e.g., market.enriched d LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date).\",\n keyColumns: [\"ticker\", \"date\", \"open\", \"high\", \"low\", \"close\"],\n columns: {\n ticker: {\n description: \"Underlying ticker symbol (composite key with date).\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR YYYY-MM-DD). Composite key with ticker.\",\n hypothesis: true,\n },\n open: {\n description: \"RTH open (first bar at/after 09:30 ET).\",\n hypothesis: false,\n },\n high: {\n description: \"RTH high (max across 09:30–16:00 ET bars).\",\n hypothesis: false,\n },\n low: {\n description: \"RTH low (min across 09:30–16:00 ET bars).\",\n hypothesis: false,\n },\n close: {\n description: \"RTH close (last bar at/before 16:00 ET).\",\n hypothesis: false,\n },\n bid: {\n description: \"RTH first bid (first bar's bid at/after 09:30 ET).\",\n hypothesis: false,\n },\n ask: {\n description: \"RTH last ask (last bar's ask at/before 16:00 ET).\",\n hypothesis: false,\n },\n },\n },\n option_chain: {\n description:\n \"Option contract universe by underlying and trading date. One row per listed option contract, used for strike resolution and candidate selection in backtests.\",\n keyColumns: [\"underlying\", \"date\", \"ticker\"],\n columns: {\n underlying: {\n description: \"Underlying root symbol for the option chain snapshot.\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date for the chain snapshot (VARCHAR YYYY-MM-DD).\",\n hypothesis: true,\n },\n ticker: {\n description: \"Canonical OCC option ticker for the contract.\",\n hypothesis: true,\n },\n contract_type: {\n description: \"Option side: call or put.\",\n hypothesis: true,\n },\n strike: {\n description: \"Option strike price.\",\n hypothesis: true,\n },\n expiration: {\n description: \"Expiration date for the contract (VARCHAR YYYY-MM-DD).\",\n hypothesis: true,\n },\n dte: {\n description: \"Days to expiration as of the chain snapshot date.\",\n hypothesis: true,\n },\n exercise_style: {\n description: \"Exercise style reported by the provider when available.\",\n hypothesis: false,\n },\n },\n },\n option_quote_minutes: {\n description:\n \"Dense minute-level option quote cache keyed by ticker/date/time. Used to fill sparse option trade bars with bid/ask-derived marks during replay and backtests.\",\n keyColumns: [\"ticker\", \"date\", \"time\"],\n columns: {\n ticker: {\n description: \"Canonical OCC option ticker.\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR YYYY-MM-DD).\",\n hypothesis: true,\n },\n time: {\n description: \"Quote minute in HH:MM Eastern Time format.\",\n hypothesis: false,\n },\n bid: {\n description: \"Best bid at or carried into that minute.\",\n hypothesis: false,\n },\n ask: {\n description: \"Best ask at or carried into that minute.\",\n hypothesis: false,\n },\n mid: {\n description: \"Bid/ask midpoint for the minute.\",\n hypothesis: false,\n },\n last_updated_ns: {\n description: \"Monotonic write-order field used for quote upsert precedence.\",\n hypothesis: false,\n },\n source: {\n description: \"Quote source label used for debugging and provenance.\",\n hypothesis: false,\n },\n delta: {\n description: \"Option delta for the minute when available from provider data or computed fallback.\",\n hypothesis: true,\n },\n gamma: {\n description: \"Option gamma for the minute when available from provider data or computed fallback.\",\n hypothesis: false,\n },\n theta: {\n description: \"Option theta for the minute when available from provider data or computed fallback.\",\n hypothesis: false,\n },\n vega: {\n description: \"Option vega (per 1% IV move) for the minute when available from provider data or computed fallback.\",\n hypothesis: false,\n },\n iv: {\n description: \"Implied volatility used for the stored minute greeks when available.\",\n hypothesis: true,\n },\n greeks_source: {\n description: \"Origin of the stored minute greeks: provider-native or computed fallback.\",\n hypothesis: false,\n },\n greeks_revision: {\n description: \"Computation revision for stored computed greeks; null for provider-native values.\",\n hypothesis: false,\n },\n rate_type: {\n description: \"Interest-rate curve or rate label used by the provider or computed-greeks path.\",\n hypothesis: false,\n },\n rate_value: {\n description: \"Interest-rate value used by the provider or computed-greeks path.\",\n hypothesis: false,\n },\n gamma_source: {\n description: \"Provenance label for the stored gamma value when it differs from the broader greeks source.\",\n hypothesis: false,\n },\n },\n },\n },\n },\n};\n\n// ============================================================================\n// Example Queries\n// ============================================================================\n\nexport const EXAMPLE_QUERIES: ExampleQueries = {\n basic: [\n {\n description: \"Count trades by strategy with total P&L\",\n sql: `SELECT strategy, COUNT(*) as trades, SUM(pl) as total_pl\nFROM trades.trade_data\nGROUP BY strategy\nORDER BY total_pl DESC`,\n },\n {\n description: \"Daily P&L for a specific block\",\n sql: `SELECT date_opened, SUM(pl) as daily_pl\nFROM trades.trade_data\nWHERE block_id = 'my-block'\nGROUP BY date_opened\nORDER BY date_opened`,\n },\n {\n description: \"Recent market conditions (last 20 days)\",\n sql: `SELECT d.date, s.close, d.RSI_14, d.ATR_Pct,\n vix_s.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State, evix.ivr AS VIX_IVR, evix.ivp AS VIX_IVP\nFROM market.enriched d\nLEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\nLEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\nLEFT JOIN market.enriched evix ON evix.date = d.date AND evix.ticker = 'VIX'\nLEFT JOIN market.enriched_context cd ON cd.date = d.date\nWHERE d.ticker = 'SPX'\nORDER BY d.date DESC\nLIMIT 20`,\n },\n {\n description: \"Win/loss summary by block\",\n sql: `SELECT\n block_id,\n COUNT(*) as total_trades,\n SUM(CASE WHEN pl > 0 THEN 1 ELSE 0 END) as winners,\n SUM(CASE WHEN pl <= 0 THEN 1 ELSE 0 END) as losers,\n ROUND(100.0 * SUM(CASE WHEN pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate\nFROM trades.trade_data\nGROUP BY block_id\nORDER BY block_id`,\n },\n {\n description: \"Filter and paginate trades (replaces get_trades)\",\n sql: `SELECT date_opened, time_opened, strategy, legs, pl, num_contracts\nFROM trades.trade_data\nWHERE block_id = 'my-block'\n AND strategy ILIKE '%iron%'\n AND pl > 0\nORDER BY date_opened DESC\nLIMIT 50 OFFSET 0`,\n },\n {\n description: \"Market data query with VIX context\",\n sql: `SELECT d.date, s.close, d.Gap_Pct, vix_s.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\nFROM market.enriched d\nLEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\nLEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\nLEFT JOIN market.enriched_context cd ON cd.date = d.date\nWHERE d.ticker = 'SPX'\n AND d.date BETWEEN '2024-01-01' AND '2024-06-30'\n AND vix_s.close > 20\nORDER BY d.date`,\n },\n {\n description: \"Compare backtest vs actual trades by date/strategy\",\n sql: `SELECT\n t.date_opened, t.strategy, t.legs as bt_legs, r.legs as actual_legs,\n t.pl as bt_pl, r.pl as actual_pl, r.pl - t.pl as slippage\nFROM trades.trade_data t\nJOIN trades.reporting_data r\n ON t.block_id = r.block_id\n AND t.date_opened = r.date_opened\n AND t.strategy = r.strategy\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened`,\n },\n ],\n joins: [\n {\n description: \"Trade P&L with market context (lag-aware: multi-table JOIN before LAG for correctness)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.Gap_Pct, d.Prior_Close, d.Prev_Return_Pct,\n vix_s.open AS VIX_Open,\n d.RSI_14, d.Realized_Vol_20D,\n vix_s.close AS VIX_Close, evix.ivp AS VIX_IVP, cd.Vol_Regime, cd.Term_Structure_State\n FROM market.enriched d\n LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\n LEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\n LEFT JOIN market.enriched evix ON evix.date = d.date AND evix.ticker = 'VIX'\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(RSI_14) OVER (PARTITION BY ticker ORDER BY date) AS prev_RSI_14,\n LAG(VIX_IVP) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_IVP,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n m.Gap_Pct, m.VIX_Open,\n m.prev_RSI_14, m.prev_VIX_IVP, m.prev_VIX_Close, m.prev_Vol_Regime\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened DESC`,\n },\n {\n description: \"Trades with ORB context (opening range breakout from minute bars in market.spot)\",\n sql: `WITH orb_range AS (\n SELECT ticker, date,\n MAX(high) AS ORB_High,\n MIN(low) AS ORB_Low,\n MAX(high) - MIN(low) AS ORB_Range\n FROM market.spot\n WHERE ticker = 'SPX'\n AND time >= '09:30' AND time <= '09:45'\n -- Drop minute bars with zero/null OHLC (occasional provider gaps).\n -- Without this, MIN(low) collapses to 0 on contaminated minutes\n -- and ORB_Range balloons to ~100% of price.\n AND open IS NOT NULL AND open > 0\n AND high IS NOT NULL AND high > 0\n AND low IS NOT NULL AND low > 0\n AND close IS NOT NULL AND close > 0\n GROUP BY ticker, date\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n r.ORB_High, r.ORB_Low, r.ORB_Range\nFROM trades.trade_data t\nLEFT JOIN orb_range r ON t.date_opened = r.date\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened`,\n },\n {\n description: \"VIX intraday data for a specific date (VIX bars are in market.spot with ticker='VIX')\",\n sql: `SELECT time, open, high, low, close\nFROM market.spot\nWHERE ticker = 'VIX'\n AND date = '2024-03-15'\nORDER BY time`,\n },\n {\n description: \"Trades on reversal days (lag-aware: Reversal_Type uses prior trading day via LAG)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.High_Before_Low, d.Reversal_Type\n FROM market.enriched d\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Reversal_Type) OVER (PARTITION BY ticker ORDER BY date) AS prev_Reversal_Type,\n LAG(High_Before_Low) OVER (PARTITION BY ticker ORDER BY date) AS prev_High_Before_Low\n FROM joined\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n m.prev_Reversal_Type, m.prev_High_Before_Low\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE m.prev_Reversal_Type != 0\n AND t.block_id = 'my-block'`,\n },\n {\n description: \"Enrich trades with market data (lag-aware: use enrich_trades tool for full enrichment)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.Gap_Pct, d.Prior_Close,\n vix_s.open AS VIX_Open,\n d.RSI_14, d.ATR_Pct,\n vix_s.close AS VIX_Close, cd.Vol_Regime\n FROM market.enriched d\n LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\n LEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT t.date_opened, t.strategy, t.pl,\n m.Gap_Pct, m.VIX_Open, m.prev_VIX_Close, m.prev_Vol_Regime\nFROM trades.trade_data t\nLEFT JOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'`,\n },\n ],\n hypothesis: [\n {\n description: \"Win rate by VIX regime (lag-aware: uses prior day's Vol_Regime from market.enriched_context)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, cd.Vol_Regime\n FROM market.enriched d\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT\n m.prev_Vol_Regime AS vol_regime,\n COUNT(*) as trades,\n SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) as winners,\n ROUND(100.0 * SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate,\n SUM(t.pl) as total_pl\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_Vol_Regime IS NOT NULL\nGROUP BY m.prev_Vol_Regime\nORDER BY m.prev_Vol_Regime`,\n },\n {\n description: \"P&L by day of week\",\n sql: `SELECT\n d.Day_of_Week,\n COUNT(*) as trades,\n SUM(t.pl) as total_pl,\n ROUND(AVG(t.pl), 2) as avg_pl\nFROM trades.trade_data t\nJOIN market.enriched d ON t.date_opened = d.date AND d.ticker = 'SPX'\nWHERE t.block_id = 'my-block'\nGROUP BY d.Day_of_Week\nORDER BY d.Day_of_Week`,\n },\n {\n description: \"Performance by VIX term structure (lag-aware: uses prior day's Term_Structure_State from market.enriched_context)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, cd.Term_Structure_State\n FROM market.enriched d\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Term_Structure_State) OVER (PARTITION BY ticker ORDER BY date) AS prev_Term_Structure_State\n FROM joined\n)\nSELECT\n CASE WHEN m.prev_Term_Structure_State = -1 THEN 'Backwardation'\n WHEN m.prev_Term_Structure_State = 1 THEN 'Contango'\n ELSE 'Flat' END as term_structure,\n COUNT(*) as trades,\n SUM(t.pl) as total_pl,\n ROUND(AVG(t.pl), 2) as avg_pl,\n ROUND(100.0 * SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_Term_Structure_State IS NOT NULL\nGROUP BY term_structure`,\n },\n {\n description: \"Aggregate by VIX buckets (lag-aware: uses prior day's VIX close from market.spot_daily ticker='VIX')\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, vix_s.close AS VIX_Close\n FROM market.enriched d\n LEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close\n FROM joined\n)\nSELECT\n CASE\n WHEN m.prev_VIX_Close < 15 THEN '10-15'\n WHEN m.prev_VIX_Close < 20 THEN '15-20'\n WHEN m.prev_VIX_Close < 25 THEN '20-25'\n ELSE '25+'\n END as vix_bucket,\n COUNT(*) as trades,\n SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END)::FLOAT / COUNT(*) as win_rate,\n SUM(t.pl) as total_pl\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_VIX_Close IS NOT NULL\nGROUP BY vix_bucket\nORDER BY vix_bucket`,\n },\n {\n description: \"Find similar days by conditions\",\n sql: `WITH ref AS (\n SELECT s.close, vix_s.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\n FROM market.enriched d\n LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\n LEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = 'SPX' AND d.date = '2024-01-15'\n)\nSELECT d.date, s.close, vix_s.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\nFROM market.enriched d\nLEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\nLEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\nLEFT JOIN market.enriched_context cd ON cd.date = d.date, ref\nWHERE d.ticker = 'SPX'\n AND d.date != '2024-01-15'\n AND cd.Vol_Regime = ref.Vol_Regime\n AND ABS(vix_s.close - ref.VIX_Close) < 3\nORDER BY ABS(vix_s.close - ref.VIX_Close)\nLIMIT 20`,\n },\n ],\n};\n","/**\n * Field Timing Utilities\n *\n * Derived sets and LAG CTE builder for lookahead-free market analytics.\n * All field classifications are derived from SCHEMA_DESCRIPTIONS timing annotations\n * in schema-metadata.ts -- no hardcoded column names.\n *\n * The v3.0 market schema splits market data into:\n * - market.enriched: per-ticker computed indicators (+ ivr/ivp for VIX-family). NO OHLCV.\n * - market.spot_daily: view-backed RTH-aggregated daily OHLCV (open/high/low/close/bid/ask)\n * derived from market.spot. LEFT JOIN on ticker+date to attach OHLCV to enriched rows.\n * - market.enriched_context: cross-ticker derived fields (Vol_Regime, Term_Structure_State, etc.)\n *\n * buildLookaheadFreeQuery JOINs market.enriched d + market.spot_daily s (OHLCV for the target\n * ticker) + buildVixJoinClause (VIX-family OHLCV from spot_daily + IVR/IVP from enriched) +\n * market.enriched_context inside a CTE before applying LAG, ensuring LAG operates on the full\n * ticker history (not just trade dates). This guarantees Monday LAG returns Friday's values,\n * not the previous trade day. Per Pitfall 1 (Phase 6 RESEARCH), OHLCV column projections MUST\n * use the spot_daily alias `s` (or vix_s etc.) — `d.close` is a binder error because\n * market.enriched carries no OHLCV columns.\n *\n * Used by downstream tools (suggest_filters, analyze_regime_performance, etc.)\n * to ensure trade-entry queries only use data available at the time of trade entry.\n */\n\nimport { DEFAULT_MARKET_TICKER } from \"./ticker.ts\";\nimport { SCHEMA_DESCRIPTIONS } from \"./schema-metadata.ts\";\n\nconst dailyColumns = SCHEMA_DESCRIPTIONS.market.tables.enriched.columns;\nconst derivedColumns = SCHEMA_DESCRIPTIONS.market.tables.enriched_context.columns;\n\n// OHLCV columns live in market.spot_daily (aliased `s`), NOT market.enriched (aliased `d`).\n// When emitting SELECT projections that iterate DAILY_*_FIELDS, route these column names\n// to the spot_daily alias to avoid Binder Error on the real DuckDB schema.\nconst OHLCV_COLS = new Set([\"open\", \"high\", \"low\", \"close\"]);\nfunction aliasForDailyCol(col: string): \"s\" | \"d\" {\n return OHLCV_COLS.has(col) ? \"s\" : \"d\";\n}\n\nexport interface MarketLookupKey {\n date: string;\n ticker: string;\n}\n\n// ============================================================================\n// VIX field mapping — normalized schema\n// ============================================================================\n\n/**\n * VIX field mapping: column alias -> { table alias, source column, ticker, timing }.\n * Post Phase 6 (Pitfall 1 schema split): market.enriched has NO OHLCV columns, so\n * a single per-ticker alias cannot source both OHLCV and IVR/IVP. The VIX column\n * mappings are therefore split into:\n * - VIX_OHLCV_MAPPINGS: OHLCV fields (open/close/high/low) sourced from market.spot_daily\n * via alias {vix, vix9d, vix3m}.\n * - VIX_ENRICHED_MAPPINGS: IVR/IVP fields sourced from market.enriched via the\n * `e`-prefixed alias {evix, evix9d, evix3m}.\n */\ninterface VixFieldMapping {\n alias: string; // Column name in query output (e.g., \"VIX_Close\")\n tableAlias: string; // SQL table alias (e.g., \"vix\" for OHLCV, \"evix\" for enriched)\n sourceCol: string; // Source column (e.g., \"close\" in spot_daily, \"ivr\" in enriched)\n ticker: string; // Ticker to join on (e.g., \"VIX\")\n timing: 'open' | 'close';\n}\n\n// OHLCV-sourced VIX columns — come from market.spot_daily, aliased as vix/vix9d/vix3m\nconst VIX_OHLCV_MAPPINGS: VixFieldMapping[] = [\n // VIX\n { alias: \"VIX_Open\", tableAlias: \"vix\", sourceCol: \"open\", ticker: \"VIX\", timing: \"open\" },\n { alias: \"VIX_Close\", tableAlias: \"vix\", sourceCol: \"close\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_High\", tableAlias: \"vix\", sourceCol: \"high\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_Low\", tableAlias: \"vix\", sourceCol: \"low\", ticker: \"VIX\", timing: \"close\" },\n // VIX9D\n { alias: \"VIX9D_Open\", tableAlias: \"vix9d\", sourceCol: \"open\", ticker: \"VIX9D\", timing: \"open\" },\n { alias: \"VIX9D_Close\", tableAlias: \"vix9d\", sourceCol: \"close\", ticker: \"VIX9D\", timing: \"close\" },\n // VIX3M\n { alias: \"VIX3M_Open\", tableAlias: \"vix3m\", sourceCol: \"open\", ticker: \"VIX3M\", timing: \"open\" },\n { alias: \"VIX3M_Close\", tableAlias: \"vix3m\", sourceCol: \"close\", ticker: \"VIX3M\", timing: \"close\" },\n];\n\n// Enrichment-sourced VIX columns — come from market.enriched, aliased as evix/evix9d/evix3m\nconst VIX_ENRICHED_MAPPINGS: VixFieldMapping[] = [\n { alias: \"VIX_IVR\", tableAlias: \"evix\", sourceCol: \"ivr\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_IVP\", tableAlias: \"evix\", sourceCol: \"ivp\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX9D_IVR\", tableAlias: \"evix9d\", sourceCol: \"ivr\", ticker: \"VIX9D\", timing: \"close\" },\n { alias: \"VIX9D_IVP\", tableAlias: \"evix9d\", sourceCol: \"ivp\", ticker: \"VIX9D\", timing: \"close\" },\n { alias: \"VIX3M_IVR\", tableAlias: \"evix3m\", sourceCol: \"ivr\", ticker: \"VIX3M\", timing: \"close\" },\n { alias: \"VIX3M_IVP\", tableAlias: \"evix3m\", sourceCol: \"ivp\", ticker: \"VIX3M\", timing: \"close\" },\n];\n\n// Union of all VIX mappings (order preserved: OHLCV first, then enrichment) — used for\n// SELECT column emission and open/close field-set derivation downstream.\nconst VIX_ALL_MAPPINGS: VixFieldMapping[] = [\n ...VIX_OHLCV_MAPPINGS,\n ...VIX_ENRICHED_MAPPINGS,\n];\n\n// Unique OHLCV table aliases needed for the JOIN clause (used by buildVixJoinClause)\nconst VIX_TICKER_ALIASES = [...new Set(VIX_OHLCV_MAPPINGS.map(m => m.tableAlias))];\nconst VIX_TICKER_FOR_ALIAS: Record<string, string> = Object.fromEntries([\n ...VIX_OHLCV_MAPPINGS.map(m => [m.tableAlias, m.ticker] as const),\n ...VIX_ENRICHED_MAPPINGS.map(m => [m.tableAlias, m.ticker] as const),\n]);\n\n// Derived fields from date_context\nconst DERIVED_OPEN_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(derivedColumns)\n .filter(([, desc]) => desc.timing === 'open')\n .map(([name]) => name)\n);\n\nconst DERIVED_CLOSE_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(derivedColumns)\n .filter(([, desc]) => desc.timing === 'close')\n .map(([name]) => name)\n);\n\n// ============================================================================\n// Table-specific field sets (needed by CTE builder to know which table to alias)\n// ============================================================================\n\n/**\n * Open-known fields from market.enriched (use as d.{field} in JOIN CTE, except OHLCV\n * columns which project from the market.spot_daily alias `s`).\n * NOTE: this is still named DAILY_* for back-compat; it now reflects market.enriched\n * columns (which replaced the legacy `daily` table in the v3.0 layout).\n */\nexport const DAILY_OPEN_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'open')\n .map(([name]) => name)\n);\n\n/**\n * Close-derived fields from market.enriched (apply LAG in JOIN CTE; OHLCV columns\n * project from the market.spot_daily alias `s`).\n */\nexport const DAILY_CLOSE_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'close')\n .map(([name]) => name)\n);\n\n/**\n * Static fields from market.enriched (use as d.{field} in JOIN CTE — calendar facts)\n */\nexport const DAILY_STATIC_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'static')\n .map(([name]) => name)\n);\n\n/**\n * Open-known fields from VIX tickers + enriched_context\n */\nexport const CONTEXT_OPEN_FIELDS: ReadonlySet<string> = new Set([\n ...VIX_ALL_MAPPINGS.filter(m => m.timing === 'open').map(m => m.alias),\n ...DERIVED_OPEN_FIELDS,\n]);\n\n/**\n * Close-derived fields from VIX tickers + enriched_context\n */\nexport const CONTEXT_CLOSE_FIELDS: ReadonlySet<string> = new Set([\n ...VIX_ALL_MAPPINGS.filter(m => m.timing === 'close').map(m => m.alias),\n ...DERIVED_CLOSE_FIELDS,\n]);\n\n// ============================================================================\n// Combined field sets (for callers that don't need to know origin table)\n// ============================================================================\n\n/**\n * Fields known at or before market open (Prior_Close, Gap_Pct, VIX_Open, etc.)\n * Union of open-known fields from market.enriched, VIX tickers (spot_daily OHLCV +\n * enriched IVR/IVP), and market.enriched_context.\n * Safe to use as same-day values in trade-entry queries.\n */\nexport const OPEN_KNOWN_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_OPEN_FIELDS,\n ...CONTEXT_OPEN_FIELDS,\n]);\n\n/**\n * Fields only known after market close (RSI_14, Vol_Regime, Close, etc.)\n * Union of close-derived fields from market.enriched, VIX tickers (spot_daily +\n * enriched), and market.enriched_context.\n * Must use LAG() to get prior trading day's value in trade-entry queries.\n */\nexport const CLOSE_KNOWN_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_CLOSE_FIELDS,\n ...CONTEXT_CLOSE_FIELDS,\n]);\n\n/**\n * Calendar/metadata facts known before the trading day (Day_of_Week, Month, Is_Opex).\n * Only from market.enriched (context has no static fields).\n * Safe to use as same-day values in trade-entry queries.\n */\nexport const STATIC_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_STATIC_FIELDS,\n]);\n\n// ============================================================================\n// Query Builders\n// ============================================================================\n\n/**\n * Phase 6 (D-02 extraction) — public helper that emits the VIX JOIN clause\n * against BOTH market.spot_daily (OHLCV for open/high/low/close/bid/ask) AND\n * market.enriched (for ivr/ivp). Called by buildLookaheadFreeQuery and\n * buildOutcomeQuery as of Wave 1.\n *\n * Pitfall 1 context: market.enriched has no OHLCV columns, so a single alias\n * per ticker cannot source both sets. This helper emits TWO joins per alias —\n * the OHLCV alias (e.g. `vix`) attaches spot_daily; the `e`-prefixed alias\n * (e.g. `evix`) attaches enriched.\n *\n * @param tickerAliases e.g. ['vix', 'vix9d', 'vix3m']\n * @param baseAlias alias of the main table the joins attach to (default 'd')\n * @returns SQL fragment string with \\n + 6-space indent between JOINs\n */\nexport function buildVixJoinClause(\n tickerAliases: string[],\n baseAlias: string = \"d\",\n): string {\n return tickerAliases\n .flatMap(alias => {\n const ticker = VIX_TICKER_FOR_ALIAS[alias];\n return [\n `LEFT JOIN market.spot_daily ${alias} ON ${alias}.date = ${baseAlias}.date AND ${alias}.ticker = '${ticker}'`,\n `LEFT JOIN market.enriched e${alias} ON e${alias}.date = ${baseAlias}.date AND e${alias}.ticker = '${ticker}'`,\n ];\n })\n .join(\"\\n \");\n}\n\n// SELECT columns from VIX OHLCV + enrichment mappings, e.g. vix.\"close\" AS \"VIX_Close\",\n// evix.\"ivr\" AS \"VIX_IVR\", ...\nfunction buildVixSelectCols(): string {\n return VIX_ALL_MAPPINGS\n .map(m => `${m.tableAlias}.\"${m.sourceCol}\" AS \"${m.alias}\"`)\n .join(\", \");\n}\n\n// SELECT columns from enriched_context: cd.\"Vol_Regime\", ...\nfunction buildDerivedSelectCols(): string {\n return [...DERIVED_OPEN_FIELDS, ...DERIVED_CLOSE_FIELDS].map(f => `cd.\"${f}\"`).join(\", \");\n}\n\n/**\n * Builds a SQL query that joins trade keys to market.enriched + market.spot_daily\n * (for OHLCV) + market.enriched_context + VIX tickers (both spot_daily OHLCV and\n * enriched IVR/IVP) with lookahead bias prevention:\n * - Open-known fields: used as-is (same-day values, known before market open)\n * - Static fields: used as-is (calendar facts, known in advance)\n * - Close-derived fields: LAG(field) OVER (PARTITION BY ticker ORDER BY date)\n * gives prior trading day's value\n *\n * The post-Phase-6 JOIN pattern is:\n * market.enriched d\n * LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date -- OHLCV for target\n * LEFT JOIN market.spot_daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n * LEFT JOIN market.enriched evix ON evix.date = d.date AND evix.ticker = 'VIX'\n * LEFT JOIN market.spot_daily vix9d ON vix9d.date = d.date AND vix9d.ticker = 'VIX9D'\n * LEFT JOIN market.enriched evix9d ON evix9d.date = d.date AND evix9d.ticker = 'VIX9D'\n * LEFT JOIN market.spot_daily vix3m ON vix3m.date = d.date AND vix3m.ticker = 'VIX3M'\n * LEFT JOIN market.enriched evix3m ON evix3m.date = d.date AND evix3m.ticker = 'VIX3M'\n * LEFT JOIN market.enriched_context cd ON cd.date = d.date\n *\n * Pitfall 1: market.enriched carries NO OHLCV columns; OHLCV projections MUST use the\n * spot_daily alias (`s` for the target ticker, `vix/vix9d/vix3m` for VIX-family).\n *\n * LAG operates on the FULL ticker history (all trading days for the ticker),\n * NOT just the requested dates. This ensures LAG sees the correct prior trading day\n * across weekends, holidays, and sparse trading strategies.\n *\n * @param tradeDatesOrKeys - Array of dates (legacy string[] overload) or ticker+date keys\n * @returns Object with `sql` (the query string) and `params` (the parameter values)\n */\nexport function buildLookaheadFreeQuery(tradeDates: string[]): { sql: string; params: string[] };\nexport function buildLookaheadFreeQuery(tradeKeys: MarketLookupKey[]): { sql: string; params: string[] };\nexport function buildLookaheadFreeQuery(\n tradeDatesOrKeys: string[] | MarketLookupKey[]\n): { sql: string; params: string[] } {\n if (tradeDatesOrKeys.length === 0) {\n return { sql: `SELECT * FROM market.enriched WHERE 1=0`, params: [] };\n }\n\n // Build field lists for the joined CTE. OHLCV columns project from alias `s`\n // (market.spot_daily); enrichment columns project from alias `d` (market.enriched).\n const dailyOpenCols = [...DAILY_OPEN_FIELDS].map((f) => `${aliasForDailyCol(f)}.\"${f}\"`).join(\", \");\n const dailyStaticCols = [...DAILY_STATIC_FIELDS].map((f) => `${aliasForDailyCol(f)}.\"${f}\"`).join(\", \");\n const dailyCloseCols = [...DAILY_CLOSE_FIELDS].map((f) => `${aliasForDailyCol(f)}.\"${f}\"`).join(\", \");\n const vixSelectCols = buildVixSelectCols();\n const derivedSelectCols = buildDerivedSelectCols();\n const vixJoins = buildVixJoinClause(VIX_TICKER_ALIASES, \"d\");\n\n // LAG columns — all close-derived fields from daily-enriched and VIX/derived\n const dailyLagCols = [...DAILY_CLOSE_FIELDS]\n .map((field) => `LAG(\"${field}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${field}\"`)\n .join(\",\\n \");\n const vixLagCols = VIX_ALL_MAPPINGS\n .filter(m => m.timing === 'close')\n .map(m => `LAG(\"${m.alias}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${m.alias}\"`)\n .join(\",\\n \");\n const derivedLagCols = [...DERIVED_CLOSE_FIELDS]\n .map(f => `LAG(\"${f}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${f}\"`)\n .join(\",\\n \");\n\n // Pass-through columns for the lagged CTE (unaliased, from joined CTE output)\n const dailyOpenPassthrough = [...DAILY_OPEN_FIELDS].map((f) => `\"${f}\"`).join(\", \");\n const dailyStaticPassthrough = [...DAILY_STATIC_FIELDS].map((f) => `\"${f}\"`).join(\", \");\n const vixOpenPassthrough = VIX_ALL_MAPPINGS\n .filter(m => m.timing === 'open')\n .map(m => `\"${m.alias}\"`)\n .join(\", \");\n const derivedOpenPassthrough = [...DERIVED_OPEN_FIELDS].map(f => `\"${f}\"`).join(\", \");\n\n // Legacy path for existing date-only callers (single ticker = DEFAULT_MARKET_TICKER)\n if (typeof tradeDatesOrKeys[0] === \"string\") {\n const tradeDates = tradeDatesOrKeys as string[];\n const placeholders = tradeDates.map((_, i) => `$${i + 1}`).join(\", \");\n\n const sql = `WITH joined AS (\n SELECT\n d.ticker,\n d.date,\n ${dailyOpenCols},\n ${dailyStaticCols},\n ${vixSelectCols},\n ${derivedSelectCols ? derivedSelectCols + \",\" : \"\"}\n ${dailyCloseCols}\n FROM market.enriched d\n LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\n ${vixJoins}\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = $${tradeDates.length + 1}\n ),\n lagged AS (\n SELECT\n ticker,\n date,\n ${dailyOpenPassthrough},\n ${dailyStaticPassthrough},\n ${vixOpenPassthrough ? vixOpenPassthrough + \",\" : \"\"}\n ${derivedOpenPassthrough ? derivedOpenPassthrough + \",\" : \"\"}\n ${dailyLagCols},\n ${vixLagCols ? vixLagCols + \",\" : \"\"}\n ${derivedLagCols}\n FROM joined\n )\n SELECT * FROM lagged\n WHERE date IN (${placeholders})`;\n\n return { sql, params: [...tradeDates, DEFAULT_MARKET_TICKER] };\n }\n\n const tradeKeys = tradeDatesOrKeys as MarketLookupKey[];\n const normalizedKeys = tradeKeys.map((k) => ({\n date: k.date,\n ticker: k.ticker || DEFAULT_MARKET_TICKER,\n }));\n\n const values: string[] = [];\n const valuePlaceholders = normalizedKeys.map((key) => {\n values.push(key.ticker, key.date);\n return `($${values.length - 1}, $${values.length})`;\n });\n\n const sql = `WITH requested(ticker, date) AS (\n VALUES ${valuePlaceholders.join(\", \")}\n ),\n joined AS (\n SELECT\n d.ticker,\n d.date,\n ${dailyOpenCols},\n ${dailyStaticCols},\n ${vixSelectCols},\n ${derivedSelectCols ? derivedSelectCols + \",\" : \"\"}\n ${dailyCloseCols}\n FROM market.enriched d\n LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\n ${vixJoins}\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker IN (SELECT DISTINCT ticker FROM requested)\n ),\n lagged AS (\n SELECT\n ticker,\n date,\n ${dailyOpenPassthrough},\n ${dailyStaticPassthrough},\n ${vixOpenPassthrough ? vixOpenPassthrough + \",\" : \"\"}\n ${derivedOpenPassthrough ? derivedOpenPassthrough + \",\" : \"\"}\n ${dailyLagCols},\n ${vixLagCols ? vixLagCols + \",\" : \"\"}\n ${derivedLagCols}\n FROM joined\n )\n SELECT lagged.*\n FROM lagged\n JOIN requested\n ON lagged.ticker = requested.ticker\n AND lagged.date = requested.date`;\n\n return { sql, params: values };\n}\n\n/**\n * Builds a SQL query that returns same-day close-derived values (no LAG).\n * Used for outcome/post-hoc analysis when includeOutcomeFields=true.\n *\n * These are values that were NOT available at trade entry time --\n * they represent the end-of-day result for the trade date itself.\n * Sources from market.enriched (+ market.spot_daily for OHLCV), VIX ticker rows\n * (spot_daily + enriched), and market.enriched_context via LEFT JOIN.\n *\n * @param tradeDatesOrKeys - Array of dates or ticker+date keys\n * @returns Object with `sql` (the query string) and `params` (the date values)\n */\nexport function buildOutcomeQuery(tradeDates: string[]): { sql: string; params: string[] };\nexport function buildOutcomeQuery(tradeKeys: MarketLookupKey[]): { sql: string; params: string[] };\nexport function buildOutcomeQuery(\n tradeDatesOrKeys: string[] | MarketLookupKey[]\n): { sql: string; params: string[] } {\n if (tradeDatesOrKeys.length === 0) {\n return { sql: `SELECT * FROM market.enriched WHERE 1=0`, params: [] };\n }\n\n const vixCloseCols = VIX_ALL_MAPPINGS\n .filter(m => m.timing === 'close')\n .map(m => `${m.tableAlias}.\"${m.sourceCol}\" AS \"${m.alias}\"`)\n .join(\", \");\n const derivedCloseCols = [...DERIVED_CLOSE_FIELDS].map(f => `cd.\"${f}\"`).join(\", \");\n\n if (typeof tradeDatesOrKeys[0] === \"string\") {\n return buildOutcomeQueryForDates(tradeDatesOrKeys as string[], vixCloseCols, derivedCloseCols);\n }\n\n return buildOutcomeQueryForKeys(tradeDatesOrKeys as MarketLookupKey[], vixCloseCols, derivedCloseCols);\n}\n\n// Emit OHLCV-aware projection for the target-ticker close columns. Columns in\n// OHLCV_COLS come from the spot_daily alias `sAlias`; other enrichment-close\n// columns come from the enriched alias `eAlias`.\nfunction buildTargetCloseCols(\n eAlias: string,\n sAlias: string,\n): string {\n return [...DAILY_CLOSE_FIELDS]\n .map((f) => `${OHLCV_COLS.has(f) ? sAlias : eAlias}.\"${f}\"`)\n .join(\", \");\n}\n\nfunction buildOutcomeQueryForDates(\n tradeDates: string[], vixCloseCols: string, derivedCloseCols: string\n): { sql: string; params: string[] } {\n const eAlias = \"d\";\n const sAlias = \"s\";\n const dailyCloseCols = buildTargetCloseCols(eAlias, sAlias);\n const placeholders = tradeDates.map((_, i) => `$${i + 1}`).join(\", \");\n const sql = `SELECT ${eAlias}.date, ${dailyCloseCols}, ${vixCloseCols}, ${derivedCloseCols}\n FROM market.enriched ${eAlias}\n LEFT JOIN market.spot_daily ${sAlias} ON ${sAlias}.ticker = ${eAlias}.ticker AND ${sAlias}.date = ${eAlias}.date\n ${buildVixJoinClause(VIX_TICKER_ALIASES, eAlias)}\n LEFT JOIN market.enriched_context cd ON cd.date = ${eAlias}.date\n WHERE ${eAlias}.ticker = $${tradeDates.length + 1}\n AND ${eAlias}.date IN (${placeholders})`;\n return { sql, params: [...tradeDates, DEFAULT_MARKET_TICKER] };\n}\n\nfunction buildOutcomeQueryForKeys(\n tradeKeys: MarketLookupKey[], vixCloseCols: string, derivedCloseCols: string\n): { sql: string; params: string[] } {\n const eAlias = \"m\";\n const sAlias = \"ms\";\n const dailyCloseCols = buildTargetCloseCols(eAlias, sAlias);\n const normalizedKeys = tradeKeys.map((k) => ({\n date: k.date,\n ticker: k.ticker || DEFAULT_MARKET_TICKER,\n }));\n\n const values: string[] = [];\n const valuePlaceholders = normalizedKeys.map((key) => {\n values.push(key.ticker, key.date);\n return `($${values.length - 1}, $${values.length})`;\n });\n\n const sql = `WITH requested(ticker, date) AS (\n VALUES ${valuePlaceholders.join(\", \")}\n )\n SELECT ${eAlias}.ticker, ${eAlias}.date, ${dailyCloseCols}, ${vixCloseCols}, ${derivedCloseCols}\n FROM market.enriched ${eAlias}\n LEFT JOIN market.spot_daily ${sAlias} ON ${sAlias}.ticker = ${eAlias}.ticker AND ${sAlias}.date = ${eAlias}.date\n ${buildVixJoinClause(VIX_TICKER_ALIASES, eAlias)}\n LEFT JOIN market.enriched_context cd ON cd.date = ${eAlias}.date\n JOIN requested\n ON ${eAlias}.ticker = requested.ticker\n AND ${eAlias}.date = requested.date`;\n\n return { sql, params: values };\n}\n","/**\n * Data Availability Helper\n *\n * Checks whether canonical market data (enriched daily, VIX context, intraday\n * spot bars) is available for a given ticker and returns actionable warnings\n * when data is missing.\n *\n * Used at the start of every market tool call to surface missing data with\n * clear import instructions rather than returning silent NULLs or cryptic\n * errors.\n *\n * Phase 4 / CONSUMER-02: rewritten to consume `MarketStores` so reads NEVER\n * trigger provider fetches. Daily/context coverage flows through\n * `stores.enriched.getCoverage`; intraday coverage flows through\n * `stores.spot.getCoverage`. The previous direct raw-SQL paths against the\n * pre-Phase-6 daily / intraday views are gone (D-09 silent-empty contract).\n */\nimport type { MarketStores } from \"../market/stores/index.ts\";\n\nexport interface DataAvailabilityReport {\n /** Whether enriched data is present for the requested ticker */\n hasDailyData: boolean;\n /** Whether enriched data is present for the canonical VIX context ticker */\n hasContextData: boolean;\n /** Whether spot intraday data is present for the requested ticker */\n hasIntradayData: boolean;\n /** Date range available in enriched for the ticker, or null if no data */\n dailyDateRange: { min: string; max: string } | null;\n /** Date range of VIX enriched coverage, or null if no data */\n contextDateRange: { min: string; max: string } | null;\n /** Date range available in spot intraday for the ticker, or null if no data */\n intradayDateRange: { min: string; max: string } | null;\n /** Actionable warning messages for any missing data sources */\n warnings: string[];\n}\n\n/**\n * Sentinel \"wide\" date range used when callers want an \"any data?\" check.\n * Matches the D-09 contract — store returns empty coverage when the range has\n * no partitions; caller interprets `totalDates > 0` as \"data exists somewhere\n * in history\" without paying for an extra `MIN/MAX` query.\n */\nconst WIDE_FROM = \"2000-01-01\";\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\n/**\n * Checks data availability via the typed Phase 2 store layer.\n *\n * Calls `stores.enriched.getCoverage(...)` for the daily + VIX context probes\n * and `stores.spot.getCoverage(...)` for the optional intraday probe. Returns\n * a report with boolean flags, date ranges, and actionable warning messages.\n *\n * @param stores - MarketStores bundle (constructed once at process startup)\n * @param ticker - Ticker symbol to check (e.g., 'SPX')\n * @param options.checkIntraday - Whether to also check spot intraday (default: false)\n */\nexport async function checkDataAvailability(\n stores: MarketStores,\n ticker: string,\n options?: { checkIntraday?: boolean },\n): Promise<DataAvailabilityReport> {\n const warnings: string[] = [];\n\n // --- Daily (enriched) — ticker-only signature per EnrichedStore.getCoverage ---\n const dailyCov = await stores.enriched.getCoverage(ticker);\n const hasDailyData = dailyCov.totalDates > 0;\n const dailyDateRange =\n hasDailyData && dailyCov.earliest && dailyCov.latest\n ? { min: dailyCov.earliest, max: dailyCov.latest }\n : null;\n\n if (!hasDailyData) {\n warnings.push(\n `No enriched daily data for ticker ${ticker}. ` +\n `Import daily OHLCV with import_market_csv (target_table: \"daily\", ticker: \"${ticker}\") ` +\n `then run enrich_market_data.`,\n );\n }\n\n // --- Context (VIX enriched) — same store, fixed ticker ---\n const vixCov = await stores.enriched.getCoverage(\"VIX\");\n const hasContextData = vixCov.totalDates > 0;\n const contextDateRange =\n hasContextData && vixCov.earliest && vixCov.latest\n ? { min: vixCov.earliest, max: vixCov.latest }\n : null;\n\n if (!hasContextData) {\n warnings.push(\n `No VIX enriched data found. ` +\n `Import VIX-family data with import_from_api (target_table: \"date_context\") ` +\n `or import_market_csv for VIX/VIX9D/VIX3M daily rows, ` +\n `then run enrich_market_data for IVR/IVP and date_context enrichment.`,\n );\n }\n\n // --- Intraday (spot) — only when caller explicitly opts in ---\n let hasIntradayData = false;\n let intradayDateRange: { min: string; max: string } | null = null;\n if (options?.checkIntraday) {\n const spotCov = await stores.spot.getCoverage(ticker, WIDE_FROM, todayIso());\n hasIntradayData = spotCov.totalDates > 0;\n if (hasIntradayData && spotCov.earliest && spotCov.latest) {\n intradayDateRange = { min: spotCov.earliest, max: spotCov.latest };\n }\n if (!hasIntradayData) {\n warnings.push(\n `No spot intraday data for ticker ${ticker}. ` +\n `Import intraday bars with import_market_csv (target_table: \"intraday\", ticker: \"${ticker}\").`,\n );\n }\n }\n\n return {\n hasDailyData,\n hasContextData,\n hasIntradayData,\n dailyDateRange,\n contextDateRange,\n intradayDateRange,\n warnings,\n };\n}\n","/**\n * data-quality.ts\n *\n * Data quality scoring and coverage reporting for cached market data.\n *\n * Exports:\n * DataQuality - Interface for quality assessment result\n * DataQualityInput - Input to scoreDataQuality (pure, no I/O)\n * CoverageResult - Result from queryCoverage (store-backed)\n * scoreDataQuality - Pure function: scores data quality given bar counts\n * queryCoverage - Store function: aggregates spot + quote store coverage\n * formatCoverageReport - Pure function: produces human-readable coverage text\n *\n * Design principles:\n * - scoreDataQuality and formatCoverageReport are pure functions (no I/O)\n * - queryCoverage handles all store access — flows through\n * `stores.spot.getCoverage` + `stores.quote.getCoverage` only; no raw\n * `FROM market.*` SQL remains.\n * - Confidence scoring uses avgBarsPerDay as proxy for data density:\n * >= 200 bars/day = dense quotes (high confidence)\n * 50-199 bars/day = sparse trade bars (medium confidence)\n * < 50 bars/day = very sparse data (low confidence)\n * - Missing data (> 10% of trading days) caps confidence at medium\n *\n * Note on `barCount`:\n * The earlier `queryCoverage` returned per-date `COUNT(*)` row counts from\n * the legacy intraday view / `market.option_quote_minutes`. The current\n * store layer exposes coverage as `{ earliest, latest, totalDates }` —\n * date-level granularity only. We map \"covered date\" → `barCount = 1` so\n * downstream `scoreDataQuality` consumers continue to see a non-zero\n * density signal, and `hasQuotes` reflects whether the date is covered by\n * the quote store (the current \"dense data\" signal). True per-date\n * density is no longer exposed by the store layer.\n */\n\nimport type { MarketStores } from \"../market/stores/index.ts\";\n\n// ---------------------------------------------------------------------------\n// Interfaces\n// ---------------------------------------------------------------------------\n\n/**\n * Data quality assessment result — attached to BacktestResult and data_status output.\n */\nexport interface DataQuality {\n /** Average bars per trading day. 390 = dense quotes, 46 = sparse trade bars. */\n avgBarsPerDay: number;\n /** Number of trading days with dense quote data (>= 200 bars). */\n tradesWithQuoteData: number;\n /** Number of trading days with only sparse trade bars (1-199 bars). */\n tradesWithSparseData: number;\n /** Calendar dates where no data was found at all. */\n missingDataDates: string[];\n /** Overall confidence level based on data density and coverage. */\n confidenceLevel: 'high' | 'medium' | 'low';\n}\n\n/**\n * Input to scoreDataQuality — derived from cache queries or trade-level data.\n */\nexport interface DataQualityInput {\n /** Total bars across all trading days. */\n totalBars: number;\n /** Number of trading days in the range with at least some data. */\n tradingDays: number;\n /** Days with >= 200 bars (dense quote coverage). */\n daysWithQuotes: number;\n /** Days with 1-199 bars (sparse trade bar coverage). */\n daysWithSparseData: number;\n /** Days with 0 bars (no data at all). */\n daysWithNoData: number;\n /** Specific dates with no data. */\n missingDates: string[];\n}\n\n/**\n * Per-date bar count breakdown from queryCoverage.\n */\nexport interface DateCoverage {\n date: string;\n barCount: number;\n /** True when the date is covered by the quote store (dense quote data). */\n hasQuotes: boolean;\n}\n\n/**\n * Coverage query result including raw breakdown and a pre-formatted summary.\n */\nexport interface CoverageResult {\n totalBars: number;\n dateBreakdown: DateCoverage[];\n /** Human-readable one-liner summary (e.g., \"SPX: 252 trading days\"). */\n summary: string;\n}\n\n// ---------------------------------------------------------------------------\n// scoreDataQuality (pure)\n// ---------------------------------------------------------------------------\n\n/**\n * Score data quality based on bar density and coverage.\n *\n * Confidence level rules:\n * high — avgBarsPerDay >= 200 AND missingPct <= 5%\n * low — tradingDays == 0 OR avgBarsPerDay < 50 OR missingPct > 10%\n * medium — everything else\n */\nexport function scoreDataQuality(input: DataQualityInput): DataQuality {\n const avgBarsPerDay =\n input.tradingDays > 0\n ? Math.round(input.totalBars / input.tradingDays)\n : 0;\n\n // Total days = tradingDays (days with data) + daysWithNoData\n const totalDaysInRange = input.tradingDays + input.daysWithNoData;\n const missingPct =\n totalDaysInRange > 0 ? input.daysWithNoData / totalDaysInRange : 1;\n\n let confidenceLevel: 'high' | 'medium' | 'low';\n\n if (input.tradingDays === 0 || avgBarsPerDay < 50) {\n confidenceLevel = 'low';\n } else if (missingPct > 0.10) {\n // More than 10% of calendar trading days missing → cap at low\n confidenceLevel = 'low';\n } else if (avgBarsPerDay >= 200 && missingPct <= 0.05) {\n confidenceLevel = 'high';\n } else {\n confidenceLevel = 'medium';\n }\n\n return {\n avgBarsPerDay,\n tradesWithQuoteData: input.daysWithQuotes,\n tradesWithSparseData: input.daysWithSparseData,\n missingDataDates: input.missingDates,\n confidenceLevel,\n };\n}\n\n// ---------------------------------------------------------------------------\n// queryCoverage (store-backed)\n// ---------------------------------------------------------------------------\n\n/**\n * Aggregate spot + quote store coverage for an underlying over a date range.\n *\n * All reads flow through `stores.spot.getCoverage` and\n * `stores.quote.getCoverage`. The pre-migration LIKE pattern (e.g., 'SPX%') is\n * gone — callers pass the underlying ticker directly. Quote-store coverage\n * already aggregates over every OCC chain under the underlying.\n *\n * @param stores - MarketStores bundle constructed at process startup\n * @param underlying - Underlying ticker (e.g., 'SPX')\n * @param fromDate - Start date 'YYYY-MM-DD' inclusive\n * @param toDate - End date 'YYYY-MM-DD' inclusive\n */\nexport async function queryCoverage(\n stores: MarketStores,\n underlying: string,\n fromDate: string,\n toDate: string,\n): Promise<CoverageResult> {\n // Spot coverage — underlying intraday bars (e.g., SPX index minute bars).\n const spotCov = await stores.spot.getCoverage(underlying, fromDate, toDate);\n\n // Quote coverage — every OCC quote-minute under this underlying. The store\n // returns date-level coverage; \"covered = dense\" in the current model.\n const quoteCov = await stores.quote.getCoverage(underlying, fromDate, toDate);\n\n // Build covered-date set.\n const dates = enumerateCoveredDates(spotCov.earliest, spotCov.latest)\n .concat(enumerateCoveredDates(quoteCov.earliest, quoteCov.latest));\n const uniqueDates = [...new Set(dates)].sort();\n const quoteDateSet = new Set(\n enumerateCoveredDates(quoteCov.earliest, quoteCov.latest),\n );\n\n if (uniqueDates.length === 0) {\n return {\n totalBars: 0,\n dateBreakdown: [],\n summary: `No intraday data found for ${underlying}`,\n };\n }\n\n let totalBars = 0;\n const dateBreakdown: DateCoverage[] = [];\n for (const date of uniqueDates) {\n // Per-date row counts are no longer exposed by the store; treat each\n // covered date as 1 unit of \"bars\" so callers that compute density still\n // see a positive signal. hasQuotes flips when the quote store covers the\n // date (the current \"dense data\" predicate).\n const barCount = 1;\n totalBars += barCount;\n dateBreakdown.push({ date, barCount, hasQuotes: quoteDateSet.has(date) });\n }\n\n const tradingDays = dateBreakdown.length;\n const summary =\n tradingDays === 0\n ? `No data found for ${underlying} between ${fromDate} and ${toDate}`\n : `${underlying}: ${tradingDays} trading days covered ` +\n `(spot=${spotCov.totalDates}, quote=${quoteCov.totalDates})`;\n\n return { totalBars, dateBreakdown, summary };\n}\n\n/**\n * Enumerate the inclusive date range between two ISO dates. Returns [] when\n * either bound is null — coverage probes use null bounds to signal \"no\n * data found\" without throwing. Pure function — no IO.\n */\nfunction enumerateCoveredDates(from: string | null, to: string | null): string[] {\n if (!from || !to) return [];\n const start = new Date(from + \"T00:00:00Z\");\n const end = new Date(to + \"T00:00:00Z\");\n if (start > end) return [];\n const out: string[] = [];\n const cur = new Date(start);\n while (cur <= end) {\n out.push(cur.toISOString().slice(0, 10));\n cur.setUTCDate(cur.getUTCDate() + 1);\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// formatCoverageReport (pure)\n// ---------------------------------------------------------------------------\n\n/** Classify bar count into density label. */\nfunction densityLabel(barCount: number): 'dense' | 'sparse' | 'none' {\n if (barCount >= 200) return 'dense';\n if (barCount > 0) return 'sparse';\n return 'none';\n}\n\n/**\n * Format a human-readable coverage report from queryCoverage results.\n *\n * Groups consecutive dates with similar density into ranges:\n * ```\n * SPX% coverage: 2024-01 through 2025-06\n * 2024-01-02 to 2024-11-29: dense (avg 320 bars/day, quotes available)\n * 2024-12-02 to 2025-03-28: sparse (avg 42 bars/day, trade bars only)\n * 2025-04-01 to 2025-06-30: no data\n * ```\n */\nexport function formatCoverageReport(\n tickerPattern: string,\n coverage: CoverageResult,\n): string {\n if (coverage.dateBreakdown.length === 0) {\n return `No data found for ${tickerPattern}`;\n }\n\n const lines: string[] = [];\n const first = coverage.dateBreakdown[0].date;\n const last = coverage.dateBreakdown[coverage.dateBreakdown.length - 1].date;\n lines.push(`${tickerPattern} coverage: ${first} through ${last}`);\n\n // Group consecutive dates with same density label\n interface Group {\n fromDate: string;\n toDate: string;\n density: 'dense' | 'sparse' | 'none';\n barCounts: number[];\n hasQuotes: boolean;\n }\n\n const groups: Group[] = [];\n let current: Group | null = null;\n\n for (const entry of coverage.dateBreakdown) {\n const density = densityLabel(entry.barCount);\n\n if (!current || current.density !== density) {\n if (current) groups.push(current);\n current = {\n fromDate: entry.date,\n toDate: entry.date,\n density,\n barCounts: [entry.barCount],\n hasQuotes: entry.hasQuotes,\n };\n } else {\n current.toDate = entry.date;\n current.barCounts.push(entry.barCount);\n current.hasQuotes = current.hasQuotes || entry.hasQuotes;\n }\n }\n if (current) groups.push(current);\n\n for (const group of groups) {\n const avgBars =\n group.barCounts.length > 0\n ? Math.round(\n group.barCounts.reduce((s, v) => s + v, 0) / group.barCounts.length,\n )\n : 0;\n\n const rangeStr =\n group.fromDate === group.toDate\n ? group.fromDate\n : `${group.fromDate} to ${group.toDate}`;\n\n let detail: string;\n if (group.density === 'dense') {\n detail = `dense (avg ${avgBars} bars/day, quotes available)`;\n } else if (group.density === 'sparse') {\n detail = `sparse (avg ${avgBars} bars/day, trade bars only)`;\n } else {\n detail = 'no data';\n }\n\n lines.push(` ${rangeStr}: ${detail}`);\n }\n\n return lines.join('\\n');\n}\n","/**\n * Pure TypeScript indicator functions for the market enrichment pipeline.\n *\n * All functions are pure (no DB access, no side effects) and take number arrays\n * or structured inputs returning computed arrays or values.\n *\n * Formulas follow TradingView Pine Script conventions:\n * - RSI: Wilder smoothing seeded with SMA of first period changes\n * - ATR: Wilder smoothing seeded with SMA of first period TR values\n * - EMA: Standard EMA seeded with SMA of first period bars\n * - Realized Vol: Population stddev, annualized by sqrt(252)*100\n *\n * References:\n * - Wilder, J.W. (1978) \"New Concepts in Technical Trading Systems\"\n * - TradingView Pine Script documentation (ta.rsi, ta.atr, ta.ema)\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport { existsSync, readdirSync } from \"fs\";\nimport * as path from \"path\";\nimport { isParquetMode, writeParquetAtomic } from \"../db/parquet-writer.ts\";\nimport { resolveCanonicalMarketFile, resolveMarketDir } from \"../db/market-datasets.ts\";\nimport {\n getEnrichedThrough,\n upsertEnrichedThrough,\n} from \"../db/json-adapters.ts\";\nimport { DEFAULT_MARKET_TICKER } from \"./ticker.ts\";\nimport type { SpotStore } from \"../market/stores/spot-store.ts\";\n\n// =============================================================================\n// Interfaces\n// =============================================================================\n\nexport interface ContextRow {\n date: string;\n VIX_Open?: number | null;\n VIX_Close?: number | null;\n VIX_High?: number | null;\n VIX_RTH_Open?: number | null;\n VIX9D_Open?: number | null;\n VIX9D_Close?: number | null;\n VIX3M_Open?: number | null;\n VIX3M_Close?: number | null;\n}\n\nexport interface EnrichedContextRow extends ContextRow {\n VIX_Gap_Pct?: number | null;\n VIX_Change_Pct?: number | null;\n VIX9D_Change_Pct?: number | null;\n VIX3M_Change_Pct?: number | null;\n VIX9D_VIX_Ratio?: number | null;\n VIX_VIX3M_Ratio?: number | null;\n VIX_Spike_Pct?: number | null;\n Vol_Regime?: number | null;\n Term_Structure_State?: number | null;\n VIX_IVR?: number | null;\n VIX_IVP?: number | null;\n VIX9D_IVR?: number | null;\n VIX9D_IVP?: number | null;\n VIX3M_IVR?: number | null;\n VIX3M_IVP?: number | null;\n}\n\n// =============================================================================\n// Primitive Indicators\n// =============================================================================\n\n/**\n * Wilder's RSI (the standard formulation).\n * Input: closing prices ordered oldest→newest.\n * Returns array same length as input; first `period` entries are NaN (warmup).\n *\n * Formula:\n * - Seed avgGain/avgLoss from SMA of first `period` changes (bars 1..period)\n * - result[period] = 100 - 100/(1 + avgGain/avgLoss)\n * - Subsequent: avgGain = (prev*(period-1) + gain)/period (Wilder smoothing)\n *\n * Empirical OO calibration (9-23-dc, 37 RSI-failing missing dates): Wilder\n * smoothing reproduces OO's filter passes on 33/37 (89%) vs SMA's 13/37 (35%).\n * Earlier comments in this file claimed OO used SMA — that was incorrect.\n */\nexport function computeRSI(closes: number[], period = 14): number[] {\n const result = new Array<number>(closes.length).fill(NaN);\n if (closes.length < period + 1) return result;\n\n // Seed avgGain/avgLoss from SMA of the first `period` changes.\n let avgGain = 0;\n let avgLoss = 0;\n for (let i = 1; i <= period; i++) {\n const change = closes[i] - closes[i - 1];\n if (change > 0) avgGain += change;\n else avgLoss += -change;\n }\n avgGain /= period;\n avgLoss /= period;\n result[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n\n // Wilder smoothing: avg = (prev*(period-1) + current) / period\n for (let i = period + 1; i < closes.length; i++) {\n const change = closes[i] - closes[i - 1];\n const gain = change > 0 ? change : 0;\n const loss = change < 0 ? -change : 0;\n avgGain = (avgGain * (period - 1) + gain) / period;\n avgLoss = (avgLoss * (period - 1) + loss) / period;\n result[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n }\n\n return result;\n}\n\n/**\n * Wilder's Average True Range (ATR).\n * Returns array same length as input; first `period` entries are NaN.\n *\n * True Range = max(high - low, |high - prevClose|, |low - prevClose|)\n * TR can be computed from bar index 1 (needs prevClose).\n * First ATR = SMA of TR[1..period] (simple average of first `period` TR values).\n * ATR[i] for i > period: (ATR_prev * (period-1) + TR[i]) / period (Wilder)\n */\nexport function computeATR(\n highs: number[],\n lows: number[],\n closes: number[],\n period = 14\n): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n if (n < period + 1) return result;\n\n // Compute true ranges starting from index 1 (needs prevClose)\n const tr = new Array<number>(n).fill(NaN);\n for (let i = 1; i < n; i++) {\n const prevClose = closes[i - 1];\n tr[i] = Math.max(\n highs[i] - lows[i],\n Math.abs(highs[i] - prevClose),\n Math.abs(lows[i] - prevClose)\n );\n }\n\n // First ATR = SMA of TR[1..period]\n let atrSum = 0;\n for (let i = 1; i <= period; i++) {\n atrSum += tr[i];\n }\n let atr = atrSum / period;\n result[period] = atr;\n\n // Wilder smoothing for subsequent bars\n for (let i = period + 1; i < n; i++) {\n atr = (atr * (period - 1) + tr[i]) / period;\n result[i] = atr;\n }\n\n return result;\n}\n\n/**\n * Exponential Moving Average (EMA) with SMA seed (TradingView convention).\n * Returns array same length as input; first `period-1` entries are NaN.\n *\n * Seed: EMA[period-1] = SMA of first `period` bars\n * k = 2 / (period + 1)\n * EMA[i] = close[i] * k + EMA[i-1] * (1 - k)\n */\nexport function computeEMA(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n if (n < period) return result;\n\n // Seed from SMA of first period bars\n let seed = 0;\n for (let i = 0; i < period; i++) {\n seed += closes[i];\n }\n seed /= period;\n result[period - 1] = seed;\n\n const k = 2 / (period + 1);\n for (let i = period; i < n; i++) {\n result[i] = closes[i] * k + result[i - 1] * (1 - k);\n }\n\n return result;\n}\n\n/**\n * Simple Moving Average (SMA).\n * Returns array same length as input; first `period-1` entries are NaN.\n * SMA[i] = average of closes[i-period+1..i]\n */\nexport function computeSMA(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n\n for (let i = period - 1; i < n; i++) {\n let sum = 0;\n for (let j = i - period + 1; j <= i; j++) {\n sum += closes[j];\n }\n result[i] = sum / period;\n }\n\n return result;\n}\n\n// =============================================================================\n// Composite Indicators\n// =============================================================================\n\n/**\n * Realized Volatility using log returns, population stddev, annualized.\n * Returns array same length as input; first `period` entries are NaN\n * (need period+1 closes to compute period log returns).\n *\n * log_return[i] = ln(close[i] / close[i-1])\n * Vol[i] = stddev(log_returns[i-period+1..i], N) * sqrt(252) * 100\n */\nexport function computeRealizedVol(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n\n // Compute log returns (one less than closes count)\n const logReturns = new Array<number>(n).fill(NaN);\n for (let i = 1; i < n; i++) {\n logReturns[i] = Math.log(closes[i] / closes[i - 1]);\n }\n\n // Rolling stddev of log returns over `period` window\n // First valid: index = period (window uses log returns at i-period+1..i, earliest is i=period)\n for (let i = period; i < n; i++) {\n const window: number[] = [];\n for (let j = i - period + 1; j <= i; j++) {\n window.push(logReturns[j]);\n }\n\n const mean = window.reduce((a, b) => a + b, 0) / period;\n // Population stddev\n const variance = window.reduce((sum, v) => sum + (v - mean) ** 2, 0) / period;\n result[i] = Math.sqrt(variance) * Math.sqrt(252) * 100;\n }\n\n return result;\n}\n\n// =============================================================================\n// Row-Level Helpers\n// =============================================================================\n\n/**\n * Consecutive up/down days counter.\n * Positive = consecutive up days, negative = consecutive down days.\n * Resets to 0 on flat day.\n * First element is always 0 (no prior bar).\n */\nexport function computeConsecutiveDays(closes: number[]): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(0);\n\n for (let i = 1; i < n; i++) {\n if (closes[i] > closes[i - 1]) {\n // Up day: continue positive streak or start at +1\n result[i] = result[i - 1] >= 0 ? result[i - 1] + 1 : 1;\n } else if (closes[i] < closes[i - 1]) {\n // Down day: continue negative streak or start at -1\n result[i] = result[i - 1] <= 0 ? result[i - 1] - 1 : -1;\n } else {\n // Flat: reset to 0\n result[i] = 0;\n }\n }\n\n return result;\n}\n\n/**\n * Gap filled indicator.\n * Returns 1 if the price gap from prior close was filled intraday, 0 otherwise.\n *\n * Gap up (open > priorClose): filled if low <= priorClose\n * Gap down (open < priorClose): filled if high >= priorClose\n * No gap (open = priorClose): returns 0\n */\nexport function isGapFilled(\n open: number,\n high: number,\n low: number,\n priorClose: number\n): number {\n if (open > priorClose && low <= priorClose) return 1;\n if (open < priorClose && high >= priorClose) return 1;\n return 0;\n}\n\n/**\n * Options expiration (OPEX) detection.\n * Takes a YYYY-MM-DD string; returns 1 if 3rd Friday of month, 0 otherwise.\n *\n * Uses string parsing (not new Date(\"YYYY-MM-DD\")) to avoid timezone issues.\n * See CLAUDE.md Date Handling rules: calendar dates from CSVs use local Date constructor.\n */\nexport function isOpex(dateStr: string): number {\n // Parse via regex to avoid timezone issues (CLAUDE.md: use string parsing)\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(dateStr);\n if (!match) return 0;\n\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) - 1; // 0-indexed for Date constructor\n const day = parseInt(match[3], 10);\n\n // Use local Date constructor (avoids UTC midnight shift)\n // Check if this day is a Friday (getDay() === 5)\n const date = new Date(year, month, day);\n if (date.getDay() !== 5) return 0;\n\n // Find first Friday of month\n const firstDay = new Date(year, month, 1);\n const firstFridayDay = ((5 - firstDay.getDay() + 7) % 7) + 1; // day of month\n\n // Third Friday = first Friday + 14\n const thirdFriday = firstFridayDay + 14;\n\n return day === thirdFriday ? 1 : 0;\n}\n\n// =============================================================================\n// Tier 2 VIX Functions\n// =============================================================================\n\n/**\n * Compute VIX-derived fields for market.enriched_context rows.\n * Takes sorted context rows (oldest first) with VIX OHLCV data.\n * Returns enriched rows with pct change, ratio, and spike fields.\n *\n * Fields requiring prior row (NaN on first row):\n * - VIX_Gap_Pct: (VIX_Open - prior VIX_Close) / prior VIX_Close * 100\n * - VIX_Change_Pct: (VIX_Close - prior VIX_Close) / prior VIX_Close * 100\n * - VIX9D_Change_Pct: (VIX9D_Close - VIX9D_Open) / VIX9D_Open * 100\n * - VIX3M_Change_Pct: (VIX3M_Close - VIX3M_Open) / VIX3M_Open * 100\n *\n * Same-day fields (no lookback needed):\n * - VIX9D_VIX_Ratio: VIX9D_Close / VIX_Close\n * - VIX_VIX3M_Ratio: VIX_Close / VIX3M_Close\n * - VIX_Spike_Pct: (VIX_High - VIX_Open) / VIX_Open * 100\n */\nexport function computeVIXDerivedFields(rows: ContextRow[]): EnrichedContextRow[] {\n return rows.map((row, i): EnrichedContextRow => {\n const prev = i > 0 ? rows[i - 1] : null;\n\n // Effective open: prefer RTH open from intraday bars, fall back to daily VIX_Open\n const effectiveOpen = row.VIX_RTH_Open ?? row.VIX_Open;\n\n // Same-day ratio and spike fields\n const VIX9D_VIX_Ratio =\n row.VIX9D_Close != null && row.VIX_Close != null && row.VIX_Close !== 0\n ? row.VIX9D_Close / row.VIX_Close\n : null;\n\n const VIX_VIX3M_Ratio =\n row.VIX_Close != null && row.VIX3M_Close != null && row.VIX3M_Close !== 0\n ? row.VIX_Close / row.VIX3M_Close\n : null;\n\n const VIX_Spike_Pct =\n row.VIX_High != null && effectiveOpen != null && effectiveOpen !== 0\n ? ((row.VIX_High - effectiveOpen) / effectiveOpen) * 100\n : null;\n\n // Intraday change fields (same-day open to close)\n const VIX9D_Change_Pct =\n row.VIX9D_Close != null && row.VIX9D_Open != null && row.VIX9D_Open !== 0\n ? ((row.VIX9D_Close - row.VIX9D_Open) / row.VIX9D_Open) * 100\n : null;\n\n const VIX3M_Change_Pct =\n row.VIX3M_Close != null && row.VIX3M_Open != null && row.VIX3M_Open !== 0\n ? ((row.VIX3M_Close - row.VIX3M_Open) / row.VIX3M_Open) * 100\n : null;\n\n // Prior-row dependent fields\n const prevVIXClose = prev?.VIX_Close ?? null;\n\n const VIX_Gap_Pct =\n effectiveOpen != null && prevVIXClose != null && prevVIXClose !== 0\n ? ((effectiveOpen - prevVIXClose) / prevVIXClose) * 100\n : null;\n\n const VIX_Change_Pct =\n row.VIX_Close != null && prevVIXClose != null && prevVIXClose !== 0\n ? ((row.VIX_Close - prevVIXClose) / prevVIXClose) * 100\n : null;\n\n return {\n ...row,\n VIX_Gap_Pct,\n VIX_Change_Pct,\n VIX9D_Change_Pct,\n VIX3M_Change_Pct,\n VIX9D_VIX_Ratio,\n VIX_VIX3M_Ratio,\n VIX_Spike_Pct,\n };\n });\n}\n\n/**\n * Classify trend direction based on 20-day return percentage.\n *\n * Uses Return_20D thresholds:\n * > 1% = \"up\"\n * < -1% = \"down\"\n * else = \"flat\" (between -1% and 1% inclusive)\n *\n * Returns null for null/NaN input (no Return_20D data available).\n */\nexport function classifyTrendDirection(return20d: number | null): string | null {\n if (return20d === null || return20d === undefined || isNaN(return20d)) return null;\n if (return20d > 1) return \"up\";\n if (return20d < -1) return \"down\";\n return \"flat\";\n}\n\n/**\n * Classify VIX level into volatility regime 1-6.\n *\n * 1: Very Low VIX < 13\n * 2: Low 13 <= VIX < 16\n * 3: Normal 16 <= VIX < 20\n * 4: Elevated 20 <= VIX < 25\n * 5: High 25 <= VIX < 30\n * 6: Extreme VIX >= 30\n */\nexport function classifyVolRegime(vixClose: number): number {\n if (vixClose < 13) return 1;\n if (vixClose < 16) return 2;\n if (vixClose < 20) return 3;\n if (vixClose < 25) return 4;\n if (vixClose < 30) return 5;\n return 6;\n}\n\n/**\n * Classify VIX term structure state.\n * Returns:\n * 1 = Contango: VIX9D < VIX and VIX < VIX3M (normal, longer-dated vol is higher)\n * -1 = Backwardation: VIX9D > VIX or VIX > VIX3M (inverted — fear in front)\n * 0 = Flat: all three within ~1% tolerance of each other\n *\n * Flatness check: both ratios VIX9D/VIX and VIX/VIX3M within 1% of 1.0\n */\nexport function classifyTermStructure(\n vix9dClose: number,\n vixClose: number,\n vix3mClose: number\n): number {\n // Match PineScript: vix9dClose > vixClose ? -1 : vixClose > vix3mClose ? 0 : 1\n if (vix9dClose > vixClose) return -1;\n if (vixClose > vix3mClose) return 0;\n return 1;\n}\n\n/**\n * Implied Volatility Rank (IVR) over a rolling window.\n * IVR[i] = (current - min) / (max - min) * 100\n * Returns array same length as input; first `period-1` entries are NaN.\n * Shows where the current value sits in its 252-day range.\n * When range is 0 (all values identical), returns 50 (middle).\n */\nexport function computeIVR(values: number[], period = 252): number[] {\n const n = values.length;\n const result = new Array<number>(n).fill(NaN);\n for (let i = period - 1; i < n; i++) {\n let min = Infinity, max = -Infinity;\n for (let j = i - period + 1; j <= i; j++) {\n if (values[j] < min) min = values[j];\n if (values[j] > max) max = values[j];\n }\n const range = max - min;\n result[i] = range > 0 ? ((values[i] - min) / range) * 100 : 50;\n }\n return result;\n}\n\n/**\n * Implied Volatility Percentile (IVP) over a rolling window.\n * IVP[i] = count(prior 251 days where value <= current) / 251 * 100\n * Returns array same length as input; first `period-1` entries are NaN.\n * Shows what percentage of the past year was at or below the current value.\n * Note: divides by (period - 1) = 251 because we compare against prior days only.\n */\nexport function computeIVP(values: number[], period = 252): number[] {\n const n = values.length;\n const result = new Array<number>(n).fill(NaN);\n for (let i = period - 1; i < n; i++) {\n let countLessOrEqual = 0;\n // Compare current against prior (period-1) days (not including current day itself)\n for (let j = i - period + 1; j < i; j++) {\n if (values[j] <= values[i]) countLessOrEqual++;\n }\n result[i] = (countLessOrEqual / (period - 1)) * 100;\n }\n return result;\n}\n\n// =============================================================================\n// Enrichment Runner Types\n// =============================================================================\n\nexport interface EnrichmentOptions {\n forceFull?: boolean;\n dataDir?: string; // Required in Parquet mode for file paths\n parquetMode?: boolean;\n}\n\n/**\n * IO abstraction for runEnrichment.\n *\n * When provided, routes specific IO operations through injected stores:\n * - spotStore: Tier 2 VIX RTH open, Tier 3 hasData check, Tier 3 minute bars\n * - watermarkStore: read/write the enrichment watermark via the JSON adapter\n *\n * The legacy SQL watermark path on the metadata sync table is gone. When\n * `io.watermarkStore` is not supplied, the runner falls back to\n * `getEnrichedThrough` / `upsertEnrichedThrough` (the same JSON adapter the\n * store wrappers wire) directly using `opts.dataDir`. The fallback is kept\n * optional to support transitional callers (e.g. market-importer\n * `triggerEnrichment`) that have not been refactored to construct an IO.\n */\nexport interface EnrichmentIO {\n spotStore?: SpotStore;\n watermarkStore?: {\n get(ticker: string): Promise<string | null>;\n upsert(ticker: string, value: string): Promise<void>;\n };\n}\n\nexport interface TierStatus {\n status: \"complete\" | \"skipped\" | \"error\";\n fieldsWritten?: number;\n reason?: string;\n}\n\nexport interface EnrichmentResult {\n ticker: string;\n tier1: TierStatus;\n tier2: TierStatus;\n tier3: TierStatus;\n rowsEnriched: number;\n enrichedThrough: string | null;\n}\n\n// =============================================================================\n// Enrichment Runner Private Helpers\n// =============================================================================\n\n/** Subtract N calendar days from a YYYY-MM-DD string, returns YYYY-MM-DD */\nfunction subtractDays(dateStr: string, days: number): string {\n const d = new Date(dateStr + \"T00:00:00Z\");\n d.setUTCDate(d.getUTCDate() - days);\n return d.toISOString().split(\"T\")[0];\n}\n\n/** Parse YYYY-MM-DD to a local Date without timezone conversion */\nfunction parseDateStr(dateStr: string): Date | null {\n const m = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (!m) return null;\n return new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3]));\n}\n\n// =============================================================================\n// Parquet Enrichment Helpers\n// =============================================================================\n\n/**\n * All enrichment columns that may be written to the daily working table.\n * When a Parquet file from a fresh import lacks these columns, they must be\n * added via ALTER TABLE so that UPDATE statements don't fail.\n */\nconst DAILY_ENRICHMENT_COLUMNS: Array<{ name: string; type: string }> = [\n // Tier 1\n { name: \"Prior_Close\", type: \"DOUBLE\" },\n { name: \"Gap_Pct\", type: \"DOUBLE\" },\n { name: \"ATR_Pct\", type: \"DOUBLE\" },\n { name: \"RSI_14\", type: \"DOUBLE\" },\n { name: \"Price_vs_EMA21_Pct\", type: \"DOUBLE\" },\n { name: \"Price_vs_SMA50_Pct\", type: \"DOUBLE\" },\n { name: \"Realized_Vol_5D\", type: \"DOUBLE\" },\n { name: \"Realized_Vol_20D\", type: \"DOUBLE\" },\n { name: \"Return_5D\", type: \"DOUBLE\" },\n { name: \"Return_20D\", type: \"DOUBLE\" },\n { name: \"Intraday_Range_Pct\", type: \"DOUBLE\" },\n { name: \"Intraday_Return_Pct\", type: \"DOUBLE\" },\n { name: \"Close_Position_In_Range\", type: \"DOUBLE\" },\n { name: \"Gap_Filled\", type: \"INTEGER\" },\n { name: \"Consecutive_Days\", type: \"INTEGER\" },\n { name: \"Prev_Return_Pct\", type: \"DOUBLE\" },\n { name: \"Prior_Range_vs_ATR\", type: \"DOUBLE\" },\n // Tier 3 intraday timing\n { name: \"High_Time\", type: \"DOUBLE\" },\n { name: \"Low_Time\", type: \"DOUBLE\" },\n { name: \"High_Before_Low\", type: \"INTEGER\" },\n { name: \"Reversal_Type\", type: \"INTEGER\" },\n { name: \"Opening_Drive_Strength\", type: \"DOUBLE\" },\n { name: \"Intraday_Realized_Vol\", type: \"DOUBLE\" },\n // Calendar\n { name: \"Day_of_Week\", type: \"INTEGER\" },\n { name: \"Month\", type: \"INTEGER\" },\n { name: \"Is_Opex\", type: \"INTEGER\" },\n // IVR/IVP\n { name: \"ivr\", type: \"DOUBLE\" },\n { name: \"ivp\", type: \"DOUBLE\" },\n];\n\n/**\n * Ensure all enrichment columns exist in the working temp table.\n * Parquet files from fresh imports only contain OHLCV columns; enrichment\n * adds computed columns via UPDATE, which requires the columns to exist.\n */\nasync function alignDailyWorkingTableColumns(\n conn: DuckDBConnection,\n tableName: string,\n): Promise<void> {\n for (const col of DAILY_ENRICHMENT_COLUMNS) {\n try {\n await conn.run(`ALTER TABLE \"${tableName}\" ADD COLUMN \"${col.name}\" ${col.type}`);\n } catch {\n // Column already exists — ignore\n }\n }\n}\n\n/**\n * In Parquet mode, create working temp tables from Parquet files.\n * The enricher operates on these temp tables, then copies back to Parquet.\n * Uses a timestamp suffix for uniqueness — no user input in table names.\n *\n * Seed source priority (in order of preference):\n * 1. Legacy `daily.parquet` / `date_context.parquet` — the pre-migration\n * single-file layout, still supported for data roots that have not yet\n * been rebuilt.\n * 2. New `enriched/ticker=*\\/data.parquet` + `enriched/context/data.parquet`\n * — the canonical per-ticker enriched layout. The working table is\n * seeded from a UNION ALL across the existing per-ticker files; OHLCV\n * columns are NULL in the seed (the working table only needs OHLCV\n * for legacy callers without io.spotStore; Tier 2 with io.spotStore\n * reads VIX OHLCV from a separate temp seeded from spot/, so SPX\n * historical Return_20D is the only enrichment field the SPX JOIN\n * actually needs from the working table — and that lives in\n * enriched/ticker=SPX/data.parquet).\n * 3. Empty fallback (`market.enriched WHERE 1=0`) when neither source\n * exists — preserves fresh-clone behavior unchanged.\n */\nasync function setupParquetWorkingTables(\n conn: DuckDBConnection,\n dataDir: string,\n): Promise<{ dailyTable: string; dateContextTable: string }> {\n const ts = Date.now();\n const dailyTable = `_enrich_daily_${ts}`;\n const dateContextTable = `_enrich_date_context_${ts}`;\n\n const dailyPath = resolveCanonicalMarketFile(dataDir, \"daily\");\n const dateContextPath = resolveCanonicalMarketFile(dataDir, \"date_context\");\n const enrichedDir = path.join(resolveMarketDir(dataDir), \"enriched\");\n const enrichedTickerGlob = path.join(enrichedDir, \"ticker=*\", \"data.parquet\");\n const enrichedContextPath = path.join(enrichedDir, \"context\", \"data.parquet\");\n\n // ---- Daily working table seed ---------------------------------------------\n if (existsSync(dailyPath)) {\n // Legacy single-file seed\n await conn.run(`CREATE TEMP TABLE \"${dailyTable}\" AS SELECT * FROM read_parquet('${dailyPath}')`);\n // Parquet files from fresh imports may lack enrichment columns — add them\n await alignDailyWorkingTableColumns(conn, dailyTable);\n } else if (hasEnrichedTickerFiles(enrichedDir)) {\n // Per-ticker seed: union the existing enriched/ticker=*/data.parquet files.\n // These contain (ticker, date, 28 enrichment cols) — no OHLCV. We add NULL\n // OHLCV columns via ALTER TABLE below so that:\n // - Callers without io.spotStore reading OHLCV from the working table get\n // schema-compatible NULLs rather than a SQL error.\n // - The io.spotStore canonical path reads OHLCV from spot/ directly and\n // never touches the working table's OHLCV columns.\n // - The Tier 2 SPX JOIN reads Return_20D (enrichment, already present\n // from the seed) from the working table — the SPX JOIN does NOT use\n // OHLCV.\n await conn.run(\n `CREATE TEMP TABLE \"${dailyTable}\" AS\n SELECT * FROM read_parquet('${enrichedTickerGlob}', hive_partitioning=true)`,\n );\n for (const ohlcv of [\"open\", \"high\", \"low\", \"close\"]) {\n try {\n await conn.run(`ALTER TABLE \"${dailyTable}\" ADD COLUMN \"${ohlcv}\" DOUBLE`);\n } catch {\n // Column already exists (should not happen for enriched/ files) — ignore\n }\n }\n // alignDailyWorkingTableColumns is a near-no-op in this branch because the\n // seed already projects the full enrichment schema. Run it for safety (each\n // ALTER TABLE ADD COLUMN is wrapped in try/catch, so idempotent).\n await alignDailyWorkingTableColumns(conn, dailyTable);\n } else {\n // Fresh-clone seed path. The legacy daily-view no longer exists in the\n // catalog; seed the working table from `market.enriched` (the canonical\n // per-ticker computed-fields view) and ALTER-ADD the OHLCV columns the\n // Tier 1 math expects. Matches the shape used by the\n // enriched-ticker-files branch above.\n await conn.run(\n `CREATE TEMP TABLE \"${dailyTable}\" AS SELECT * FROM market.enriched WHERE 1=0`,\n );\n for (const ohlcv of [\"open\", \"high\", \"low\", \"close\"]) {\n try {\n await conn.run(`ALTER TABLE \"${dailyTable}\" ADD COLUMN \"${ohlcv}\" DOUBLE`);\n } catch {\n // Column already exists — ignore\n }\n }\n await alignDailyWorkingTableColumns(conn, dailyTable);\n }\n\n // Backfill missing (ticker, date) identity rows from market.spot_daily so\n // batchUpdateDaily has rows to UPDATE. Applies to ALL seed paths above:\n // - Legacy daily.parquet branch: any new (ticker, date) in\n // market.spot_daily that isn't in the seed needs to be inserted before\n // enrichment. Usually a no-op when inventories already agree.\n // - Per-ticker enriched-files branch: the seed only contains tickers with\n // an enriched/ticker=X/data.parquet file. Tickers that have spot data\n // but no enriched file yet (e.g. after a partial re-enrichment delete)\n // would otherwise be missed.\n // - Fresh branch: the working table is empty, so every (ticker, date) in\n // market.spot_daily is new.\n //\n // Without this backfill, UPDATE ... WHERE (ticker, date) matches 0 rows and\n // the enricher silently writes an empty enriched/ticker=X/data.parquet\n // file — corrupting historical enrichment on the first run after\n // enriched/ is deleted. OHLCV columns stay NULL (io.spotStore is the\n // canonical OHLCV source; the Tier 2 SPX JOIN uses enrichment fields,\n // not OHLCV).\n try {\n // CAST date to VARCHAR — market.spot_daily.date is inferred as DATE by\n // DuckDB (hive partition type inference); the working table's date column\n // is VARCHAR (per physical market.enriched fallback schema).\n // strftime produces 'YYYY-MM-DD' which matches the partition value format.\n // ANTI-JOIN: only INSERT (ticker,date) pairs that don't already exist in\n // the working table, preserving any prior enrichment data in the seed.\n await conn.run(\n `INSERT INTO \"${dailyTable}\" (ticker, date)\n SELECT s.ticker, strftime(s.date, '%Y-%m-%d') AS d\n FROM market.spot_daily s\n WHERE NOT EXISTS (\n SELECT 1 FROM \"${dailyTable}\" t\n WHERE t.ticker = s.ticker\n AND t.date = strftime(s.date, '%Y-%m-%d')\n )`,\n );\n } catch {\n // market.spot_daily absent (truly-fresh clone before any spot data) —\n // leave the working table empty; enrichment will be a no-op in that case.\n }\n\n // ---- Date-context working table seed -------------------------------------\n if (existsSync(dateContextPath)) {\n // Legacy single-file seed\n await conn.run(`CREATE TEMP TABLE \"${dateContextTable}\" AS SELECT * FROM read_parquet('${dateContextPath}')`);\n } else if (existsSync(enrichedContextPath)) {\n // Seed from the per-ticker enriched/context/data.parquet file\n await conn.run(\n `CREATE TEMP TABLE \"${dateContextTable}\" AS SELECT * FROM read_parquet('${enrichedContextPath}')`,\n );\n } else {\n await conn.run(`CREATE TEMP TABLE \"${dateContextTable}\" (\n date VARCHAR, Vol_Regime VARCHAR, Term_Structure_State VARCHAR,\n Trend_Direction VARCHAR, VIX_Spike_Pct DOUBLE, VIX_Gap_Pct DOUBLE\n )`);\n }\n // INSERT OR REPLACE in runTier2 needs a UNIQUE/PRIMARY KEY on `date`. CREATE\n // TABLE AS SELECT does not carry over PK constraints from Parquet (Parquet has\n // no constraints), so attach one explicitly here.\n await conn.run(`CREATE UNIQUE INDEX \"idx_${dateContextTable}_date\" ON \"${dateContextTable}\"(date)`);\n\n // Same rationale for the daily working table: batchUpdateDaily uses\n // INSERT OR REPLACE so first-time enrichment of a ticker (whose seed\n // contains zero rows for that ticker) populates the working table from\n // computed values rather than silently no-op-ing on UPDATE.\n await conn.run(`CREATE UNIQUE INDEX \"idx_${dailyTable}_pk\" ON \"${dailyTable}\"(ticker, date)`);\n\n return { dailyTable, dateContextTable };\n}\n\n/**\n * True if `<dir>/ticker=<X>/data.parquet` exists for at least one ticker.\n * Mirrors the helper of the same name in db/market-views.ts; copied locally to\n * avoid pulling the view layer as a dependency of the enricher.\n */\nfunction hasEnrichedTickerFiles(dir: string): boolean {\n if (!existsSync(dir)) return false;\n try {\n return readdirSync(dir).some((entry: string) => {\n if (!entry.startsWith(\"ticker=\")) return false;\n return existsSync(path.join(dir, entry, \"data.parquet\"));\n });\n } catch {\n return false;\n }\n}\n\n/**\n * Write working-table contents to the `enriched/` partition layout —\n * `enriched/ticker={ticker}/data.parquet` for per-ticker enrichment columns\n * and `enriched/context/data.parquet` for the cross-ticker context.\n *\n * This function does NOT write `daily.parquet` or `date_context.parquet`;\n * those legacy single-file outputs are retired. The `market.enriched` and\n * `market.enriched_context` views are the canonical read surfaces over the\n * per-ticker enriched layout.\n *\n * Storage split: the per-ticker enriched file contains ONLY computed\n * enrichment columns plus (ticker, date) — NO OHLCV. Raw OHLCV stays in\n * spot/. The context file contains the cross-ticker derived fields written\n * by runTier2 (Vol_Regime, Term_Structure_State, Trend_Direction,\n * VIX_Spike_Pct, VIX_Gap_Pct).\n *\n * Filtered to `WHERE ticker = $ticker` so each per-ticker enrichment call only\n * touches its own partition. Other tickers' rows in the working table\n * (carried over from the legacy seed) are not republished here.\n *\n * Paths constructed from dataDir + hardcoded suffixes — no user-controlled\n * path components; ticker is whitelisted upstream.\n */\nasync function flushEnrichedToParquet(\n conn: DuckDBConnection,\n dataDir: string,\n ticker: string,\n tables: { dailyTable: string; dateContextTable: string },\n): Promise<void> {\n const enrichedColList = DAILY_ENRICHMENT_COLUMNS.map((c) => `\"${c.name}\"`).join(\", \");\n const tickerFile = path.join(\n resolveMarketDir(dataDir),\n \"enriched\",\n `ticker=${ticker}`,\n \"data.parquet\",\n );\n await writeParquetAtomic(conn, {\n targetPath: tickerFile,\n selectQuery: `SELECT ticker, date, ${enrichedColList} FROM \"${tables.dailyTable}\" WHERE ticker = '${ticker}' ORDER BY date`,\n });\n\n const contextFile = path.join(\n resolveMarketDir(dataDir),\n \"enriched\",\n \"context\",\n \"data.parquet\",\n );\n await writeParquetAtomic(conn, {\n targetPath: contextFile,\n selectQuery: `SELECT * FROM \"${tables.dateContextTable}\" ORDER BY date`,\n });\n\n // NOTE: Working tables are NOT dropped here — cleanup is owned by the finally\n // block in runEnrichment(). This ensures tables survive for error recovery if\n // this function throws partway through (e.g., per-ticker file written but\n // context write fails). The finally block always drops them via DROP IF EXISTS.\n}\n\n/** Batch UPDATE daily table with computed enrichment fields */\nasync function batchUpdateDaily(\n conn: DuckDBConnection,\n rows: Array<Record<string, unknown>>,\n columns: string[],\n tableName: string = \"market.enriched\",\n): Promise<void> {\n if (rows.length === 0) return;\n // Build VALUES list with $N params\n const allCols = [\"ticker\", \"date\", ...columns];\n const placeholders = rows\n .map((_, rowIdx) => {\n const params = allCols.map((__, colIdx) => `$${rowIdx * allCols.length + colIdx + 1}`);\n return `(${params.join(\", \")})`;\n })\n .join(\", \");\n // INSERT OR REPLACE (relies on UNIQUE INDEX on (ticker, date) attached in\n // setupParquetWorkingTables). REPLACE semantics handle the re-enrichment\n // case identically to the prior UPDATE; INSERT semantics are needed for\n // first-time enrichment of a ticker whose seed contains no rows for it\n // (previously silently dropped via the UPDATE no-op).\n const sql = `\n INSERT OR REPLACE INTO ${tableName} (${allCols.join(\", \")})\n VALUES ${placeholders}\n `;\n const params = rows.flatMap((row) => allCols.map((col) => row[col] ?? null));\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n}\n\n/** Run Tier 2: enrich daily (ivr/ivp) and date_context with computed VIX fields */\nasync function runTier2(\n conn: DuckDBConnection,\n targets?: { daily: string; dateContext: string },\n spotStore?: SpotStore,\n): Promise<TierStatus> {\n const dailyTarget = targets?.daily ?? \"market.enriched\";\n const dateContextTarget = targets?.dateContext ?? \"market.enriched_context\";\n\n // When `spotStore` is provided, seed a TEMP table with VIX-family daily\n // OHLCV from spot/ minute bars (aggregated via SpotStore.readDailyBars).\n // The Tier 2 SQL below reads VIX/VIX9D/VIX3M from `effectiveDailyTarget`\n // (the TEMP) instead of the working `dailyTarget` view, which after the\n // legacy daily.parquet retirement no longer contains VIX-family rows.\n //\n // The SPX JOIN (for `Return_20D`) keeps reading from `dailyTarget` because\n // SPX Return_20D is a Tier 1 enriched field written to the working table\n // earlier in the runEnrichment pipeline — spot/ never holds enriched columns.\n //\n // IVR/IVP UPDATEs continue to target `dailyTarget` (legacy write path\n // unchanged). Post-retirement, those writes hit a temp table that is never\n // persisted; the legacy write path is scheduled for removal.\n let effectiveDailyTarget = dailyTarget;\n let vixTempTable: string | null = null;\n if (spotStore) {\n vixTempTable = `_phase5_tier2_daily_${Date.now()}`;\n await conn.run(\n `CREATE TEMP TABLE \"${vixTempTable}\" (ticker VARCHAR, date VARCHAR, open DOUBLE, high DOUBLE, low DOUBLE, close DOUBLE)`,\n );\n for (const vixTicker of [\"VIX\", \"VIX9D\", \"VIX3M\"]) {\n const bars = await spotStore.readDailyBars(vixTicker, \"1970-01-01\", \"9999-12-31\");\n if (bars.length === 0) continue;\n const BATCH_SIZE = 500;\n for (let start = 0; start < bars.length; start += BATCH_SIZE) {\n const batch = bars.slice(start, start + BATCH_SIZE);\n const placeholders = batch\n .map((_, i) => `($${i * 6 + 1},$${i * 6 + 2},$${i * 6 + 3},$${i * 6 + 4},$${i * 6 + 5},$${i * 6 + 6})`)\n .join(\",\");\n const params = batch.flatMap((b) => [b.ticker, b.date, b.open, b.high, b.low, b.close]);\n await conn.run(\n `INSERT INTO \"${vixTempTable}\" VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n }\n }\n effectiveDailyTarget = `\"${vixTempTable}\"`;\n }\n\n try {\n // Step 1: Discover VIX-family tickers dynamically\n const tickerResult = await conn.runAndReadAll(\n `SELECT DISTINCT ticker FROM ${effectiveDailyTarget} WHERE ticker LIKE 'VIX%' ORDER BY ticker`\n );\n const vixTickers = tickerResult.getRows().map(r => r[0] as string);\n if (vixTickers.length === 0 || !vixTickers.includes('VIX')) {\n return { status: \"skipped\", reason: \"no VIX data — import VIX ticker first\" };\n }\n\n // Step 2: Compute IVR/IVP for each VIX-family ticker and write to daily table.\n // `market.enriched` no longer carries OHLCV columns (raw bars live in spot/);\n // read close from the OHLCV source: the spotStore-seeded TEMP table when\n // io.spotStore is present, else `market.spot_daily` (RTH-aggregated view).\n const closeSource = spotStore ? effectiveDailyTarget : \"market.spot_daily\";\n for (const ticker of vixTickers) {\n const closeResult = await conn.runAndReadAll(\n `SELECT date, close FROM ${closeSource} WHERE ticker = $1 AND close IS NOT NULL ORDER BY date ASC`,\n [ticker]\n );\n const rows = closeResult.getRows();\n if (rows.length === 0) continue;\n\n const dates = rows.map(r => r[0] as string);\n const closes = rows.map(r => r[1] as number);\n const ivrValues = computeIVR(closes, 252);\n const ivpValues = computeIVP(closes, 252);\n\n // Batch UPDATE daily table SET ivr, ivp WHERE ticker = ? AND date = ?\n const BATCH_SIZE = 500;\n for (let start = 0; start < dates.length; start += BATCH_SIZE) {\n const batchDates = dates.slice(start, start + BATCH_SIZE);\n const batchIvr = ivrValues.slice(start, start + BATCH_SIZE);\n const batchIvp = ivpValues.slice(start, start + BATCH_SIZE);\n\n const placeholders = batchDates.map((_, rowIdx) => {\n const base = rowIdx * 3;\n return `($${base + 1}, $${base + 2}, $${base + 3})`;\n }).join(\", \");\n\n const sql = `\n UPDATE ${dailyTarget} AS t\n SET ivr = v.ivr, ivp = v.ivp\n FROM (VALUES ${placeholders}) AS v(date, ivr, ivp)\n WHERE t.ticker = $${batchDates.length * 3 + 1} AND t.date = v.date\n `;\n const params: (string | number | null)[] = [];\n for (let i = 0; i < batchDates.length; i++) {\n params.push(batchDates[i]);\n params.push(isNaN(batchIvr[i]) ? null : batchIvr[i]);\n params.push(isNaN(batchIvp[i]) ? null : batchIvp[i]);\n }\n params.push(ticker);\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n }\n }\n\n // Step 3: Build ContextRow objects from daily VIX tickers for derived fields\n // Query VIX close/open/high, VIX9D close/open, VIX3M close/open, plus Return_20D for Trend_Direction.\n //\n // The VIX-family OHLCV source is `effectiveDailyTarget` (the\n // spotStore-seeded TEMP when io.spotStore is present) or\n // `market.spot_daily` when io.spotStore is absent — `market.enriched` no\n // longer carries OHLCV columns. The SPX JOIN keeps `dailyTarget` because\n // Return_20D is a Tier 1 enriched column written to the working table\n // earlier in the runEnrichment pipeline; spot/ never holds enriched fields.\n const vixOhlcvSource = spotStore ? effectiveDailyTarget : \"market.spot_daily\";\n const contextQuery = `\n SELECT\n vix.date,\n vix.open AS VIX_Open,\n vix.close AS VIX_Close,\n vix.high AS VIX_High,\n vix9d.open AS VIX9D_Open,\n vix9d.close AS VIX9D_Close,\n vix3m.open AS VIX3M_Open,\n vix3m.close AS VIX3M_Close,\n spx.Return_20D\n FROM ${vixOhlcvSource} vix\n LEFT JOIN ${vixOhlcvSource} vix9d ON vix9d.date = vix.date AND vix9d.ticker = 'VIX9D'\n LEFT JOIN ${vixOhlcvSource} vix3m ON vix3m.date = vix.date AND vix3m.ticker = 'VIX3M'\n LEFT JOIN ${dailyTarget} spx ON spx.date = vix.date AND spx.ticker = $1\n WHERE vix.ticker = 'VIX' AND vix.close IS NOT NULL\n ORDER BY vix.date ASC\n `;\n\n const rawResult = await conn.runAndReadAll(contextQuery, [DEFAULT_MARKET_TICKER]);\n const rawRows = rawResult.getRows();\n if (rawRows.length === 0) return { status: \"complete\", fieldsWritten: 0 };\n\n // Query VIX RTH open from intraday bars.\n // When spotStore is provided, route through SpotStore.readBars('VIX', ...)\n // and filter to the 09:30–09:32 RTH window in TypeScript. Result is\n // bit-exact: same ticker filter, same time window, same \"first seen per\n // date\" selection (readBars sorts by (date, time)).\n const rthOpenByDate = new Map<string, number>();\n if (spotStore) {\n try {\n const vixBars = await spotStore.readBars(\n \"VIX\",\n rawRows[0][0] as string,\n rawRows[rawRows.length - 1][0] as string,\n );\n for (const bar of vixBars) {\n const timeStr = bar.time;\n if (timeStr == null || timeStr < \"09:30\" || timeStr > \"09:32\") continue;\n // Defense-in-depth: skip 09:30-09:32 bars with zero/null open. A\n // 09:30 provider gap would otherwise cache as the day's VIX_RTH_Open.\n // The first non-zero bar in the window wins.\n if (!Number.isFinite(bar.open) || bar.open <= 0) continue;\n const dateStr = bar.date;\n if (!rthOpenByDate.has(dateStr)) {\n const openVal = bar.open;\n if (openVal != null) rthOpenByDate.set(dateStr, openVal);\n }\n }\n } catch {\n // No intraday VIX data — continue\n }\n } else {\n try {\n // Canonical minute-bar view is `market.spot` — same ticker/time/open\n // schema as the earlier intraday view it replaced.\n // Defense-in-depth: skip zero/null open bars so a 09:30 provider gap\n // doesn't get cached as the day's VIX_RTH_Open. The first non-zero\n // bar in 09:30-09:32 wins.\n const rthReader = await conn.runAndReadAll(\n `SELECT date, open FROM market.spot\n WHERE ticker = 'VIX' AND time >= '09:30' AND time <= '09:32'\n AND open IS NOT NULL AND open > 0\n ORDER BY date, time ASC`\n );\n for (const r of rthReader.getRows()) {\n const dateStr = r[0] as string;\n if (!rthOpenByDate.has(dateStr)) {\n const openVal = r[1] as number | null;\n if (openVal != null && openVal > 0) rthOpenByDate.set(dateStr, openVal);\n }\n }\n } catch {\n // No intraday VIX data — continue\n }\n }\n\n const return20dByDate = new Map<string, number | null>();\n const contextRows: ContextRow[] = rawRows.map((r) => {\n const dateStr = r[0] as string;\n return20dByDate.set(dateStr, r[8] as number | null);\n return {\n date: dateStr,\n VIX_Open: r[1] as number | null,\n VIX_Close: r[2] as number | null,\n VIX_High: r[3] as number | null,\n VIX_RTH_Open: rthOpenByDate.get(dateStr) ?? null,\n VIX9D_Open: r[4] as number | null,\n VIX9D_Close: r[5] as number | null,\n VIX3M_Open: r[6] as number | null,\n VIX3M_Close: r[7] as number | null,\n };\n });\n\n // Step 4: Compute derived fields (reuse existing pure functions unchanged)\n const enrichedContext = computeVIXDerivedFields(contextRows);\n\n // Step 5: Write derived fields to market.enriched_context (INSERT OR REPLACE)\n const derivedCols = [\"date\", \"Vol_Regime\", \"Term_Structure_State\", \"Trend_Direction\", \"VIX_Spike_Pct\", \"VIX_Gap_Pct\"];\n const BATCH_SIZE = 500;\n for (let start = 0; start < enrichedContext.length; start += BATCH_SIZE) {\n const batch = enrichedContext.slice(start, start + BATCH_SIZE);\n const placeholders = batch.map((_, rowIdx) => {\n const params = derivedCols.map((__, colIdx) => `$${rowIdx * derivedCols.length + colIdx + 1}`);\n return `(${params.join(\", \")})`;\n }).join(\", \");\n\n const sql = `INSERT OR REPLACE INTO ${dateContextTarget} (${derivedCols.join(\", \")}) VALUES ${placeholders}`;\n const params = batch.flatMap((r) => {\n const vc = r.VIX_Close ?? null;\n const v9 = r.VIX9D_Close ?? null;\n const v3m = r.VIX3M_Close ?? null;\n return [\n r.date,\n vc !== null ? classifyVolRegime(vc) : null,\n v9 !== null && vc !== null && v3m !== null ? classifyTermStructure(v9, vc, v3m) : null,\n classifyTrendDirection(return20dByDate.get(r.date) ?? null),\n r.VIX_Spike_Pct ?? null,\n r.VIX_Gap_Pct ?? null,\n ];\n });\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n }\n\n return { status: \"complete\", fieldsWritten: derivedCols.length - 1 }; // -1 for date\n } finally {\n // Drop the spotStore-seeded TEMP unconditionally so it cannot leak across\n // runEnrichment calls (each call gets a fresh ts-suffixed table name, but\n // DROP-on-finally keeps DuckDB's TEMP catalog clean).\n if (vixTempTable) {\n try { await conn.run(`DROP TABLE IF EXISTS \"${vixTempTable}\"`); } catch { /* */ }\n }\n }\n}\n\n/**\n * Check if any intraday data exists for a ticker.\n *\n * When `spotStore` is provided, routes through `SpotStore.getCoverage`\n * instead of a SQL probe against `market.spot`.\n */\nasync function hasTier3Data(\n conn: DuckDBConnection,\n ticker: string,\n spotStore?: SpotStore,\n): Promise<boolean> {\n if (spotStore) {\n const cov = await spotStore.getCoverage(ticker, \"1970-01-01\", \"9999-12-31\");\n return cov.totalDates > 0;\n }\n // Canonical minute-bar view is `market.spot` — same ticker-filter schema\n // as the earlier intraday view it replaced.\n const r = await conn.runAndReadAll(\n `SELECT COUNT(*) FROM market.spot WHERE ticker = $1 LIMIT 1`,\n [ticker]\n );\n return Number(r.getRows()[0]?.[0] ?? 0) > 0;\n}\n\n// =============================================================================\n// Context Enrichment (Tier 2 standalone)\n// =============================================================================\n\n/**\n * Run Tier 2 context enrichment directly, computing VIX-derived fields\n * (VIX_Gap_Pct, VIX_Change_Pct, VIX9D_VIX_Ratio, Vol_Regime, etc.) and\n * writing them to market.enriched_context.\n *\n * Used by importFromMassive() for context table imports — after importing\n * VIX/VIX9D/VIX3M bars, Tier 2 needs to run immediately to populate derived\n * fields. Unlike runEnrichment(), this does not require a ticker with daily data.\n *\n * Returns a TierStatus describing the outcome.\n */\nexport async function runContextEnrichment(\n conn: DuckDBConnection,\n targets?: { daily: string; dateContext: string },\n): Promise<TierStatus> {\n return runTier2(conn, targets);\n}\n\n// =============================================================================\n// Enrichment Runner\n// =============================================================================\n\n/**\n * Run all three tiers of market enrichment for a given ticker.\n *\n * Tier 1: Compute and write OHLCV-derived fields to market.enriched using a\n * 200-day lookback window from the persisted watermark.\n * Tier 2: Compute and write VIX-derived fields to market.enriched_context.\n * Tier 3: Compute intraday timing fields (High_Time, Low_Time, High_Before_Low,\n * Reversal_Type, Opening_Drive_Strength, Intraday_Realized_Vol) from\n * market.spot bars; skips gracefully if no intraday data exists.\n *\n * The watermark is upserted via the JSON adapter (`upsertEnrichedThrough` from\n * `db/json-adapters.ts`) — either the supplied `io.watermarkStore` or, when\n * absent, a direct call using `opts.dataDir`. The legacy SQL watermark path\n * on the metadata sync table has been removed.\n *\n * Note: wilder_state column exists but is NOT written (superseded by 200-day lookback).\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param ticker - Normalized ticker symbol (e.g., \"SPX\")\n * @param opts - Options including forceFull (reset watermark and reprocess all rows)\n */\nexport async function runEnrichment(\n conn: DuckDBConnection,\n ticker: string,\n opts: EnrichmentOptions = {},\n io?: EnrichmentIO,\n): Promise<EnrichmentResult> {\n const { forceFull = false } = opts;\n\n // Parquet mode: create working temp tables for all writes\n const parquetMode = (opts.parquetMode ?? isParquetMode()) && !!opts.dataDir;\n let workingTables: { dailyTable: string; dateContextTable: string } | null = null;\n\n if (parquetMode) {\n workingTables = await setupParquetWorkingTables(conn, opts.dataDir!);\n }\n\n // Determine target table names (working tables in Parquet mode, schema-qualified in DuckDB mode)\n const dailyTarget = workingTables ? workingTables.dailyTable : \"market.enriched\";\n // ctxDerivedTarget and ctxTarget are passed via tier2Targets object to runTier2()\n\n try {\n // 1. Get the persisted enrichment watermark.\n // Every watermark read goes through the JSON adapter. The legacy SQL\n // SELECT against the metadata sync table has been removed — when callers\n // don't supply `io.watermarkStore` we fall back to the same JSON adapter\n // the store wrappers wire (`getEnrichedThrough(ticker, dataDir)`).\n let watermark: string | null = null;\n if (!forceFull) {\n if (io?.watermarkStore) {\n watermark = await io.watermarkStore.get(ticker);\n } else if (opts.dataDir) {\n watermark = await getEnrichedThrough(ticker, opts.dataDir);\n } else {\n // No JSON adapter path available without dataDir, and the SQL fallback\n // is gone. Treat as \"no prior watermark\" (fresh enrichment); callers that\n // need watermark continuity must supply io or dataDir.\n watermark = null;\n }\n }\n\n // 2. Compute lookback start: watermark - 200 calendar days (as string comparison)\n const lookbackStart = watermark ? subtractDays(watermark, 200) : null;\n\n // 3. Fetch OHLCV rows.\n //\n // When `io.spotStore` is provided, read daily OHLCV via\n // `SpotStore.readDailyBars` (aggregated from spot/ minute bars). This path\n // remains functional after the legacy `daily.parquet` retirement because\n // readDailyBars aggregates from spot/ticker=X/date=Y/data.parquet.\n //\n // Fallback: when `io.spotStore` is absent (legacy callers), retain a SQL\n // path against `market.spot_daily`. The fallback may be removed once all\n // callers pass io.\n let rawRows: Array<Array<unknown>>;\n if (io?.spotStore) {\n const startDate = lookbackStart ?? \"1970-01-01\";\n const endDate = \"9999-12-31\"; // readDailyBars caps internally via partition discovery\n const dailyBars = await io.spotStore.readDailyBars(ticker, startDate, endDate);\n rawRows = dailyBars.map((b) => [b.ticker, b.date, b.open, b.high, b.low, b.close]);\n } else {\n // The legacy daily-view SQL fallback path is gone — the view no longer\n // exists in the catalog. Route OHLCV reads through the canonical\n // `market.spot_daily` view (RTH-aggregated from `market.spot`). This\n // bridges callers that have not yet migrated to io.spotStore; new\n // callers SHOULD pass io.spotStore for parity with the Parquet-direct path.\n let fetchSql = `SELECT ticker, date, open, high, low, close FROM market.spot_daily WHERE ticker = $1`;\n const fetchParams: unknown[] = [ticker];\n if (lookbackStart) {\n fetchSql += ` AND date >= $2`;\n fetchParams.push(lookbackStart);\n }\n fetchSql += ` ORDER BY date ASC`;\n const rawReader = await conn.runAndReadAll(fetchSql, fetchParams as (string | number | boolean | null | bigint)[]);\n rawRows = rawReader.getRows();\n }\n\n if (rawRows.length === 0) {\n return {\n ticker,\n tier1: {\n status: \"skipped\",\n reason: io?.spotStore ? \"no data from spotStore\" : \"no data in market.spot_daily\",\n },\n tier2: { status: \"skipped\", reason: \"no daily data\" },\n tier3: { status: \"skipped\", reason: \"no daily data\" },\n rowsEnriched: 0,\n enrichedThrough: null,\n };\n }\n\n // 3b. Defensive zero-OHLC filter. Partitions should already be clean after\n // the ParquetSpotStore.writeBars guard, but this second line of defense\n // catches any future provider-outage bleed and prevents\n // RSI/ATR/EMA/SMA/RealizedVol from being poisoned by zero closes. Filter\n // at the rawRows level so date/OHLC alignment is preserved across all\n // five arrays (dates/opens/highs/lows/closes) constructed below.\n const filteredRawRows = rawRows.filter((r) => {\n const o = Number(r[2]);\n const h = Number(r[3]);\n const l = Number(r[4]);\n const c = Number(r[5]);\n return !(o === 0 && h === 0 && l === 0 && c === 0);\n });\n const zeroRowsDropped = rawRows.length - filteredRawRows.length;\n if (zeroRowsDropped > 0) {\n console.warn(\n `[market-enricher] ticker=${ticker} dropped ${zeroRowsDropped} all-zero-OHLC rows before indicator math`,\n );\n }\n rawRows = filteredRawRows;\n\n // 4. Extract typed arrays from raw rows\n // Columns: ticker(0), date(1), open(2), high(3), low(4), close(5)\n const dates = rawRows.map((r) => r[1] as string);\n const opens = rawRows.map((r) => Number(r[2]));\n const highs = rawRows.map((r) => Number(r[3]));\n const lows = rawRows.map((r) => Number(r[4]));\n const closes = rawRows.map((r) => Number(r[5]));\n\n // 5. Compute Tier 1 indicators\n const rsi14 = computeRSI(closes, 14);\n const atrArr = computeATR(highs, lows, closes, 14);\n const ema21 = computeEMA(closes, 21);\n const sma50 = computeSMA(closes, 50);\n const rvol5 = computeRealizedVol(closes, 5);\n const rvol20 = computeRealizedVol(closes, 20);\n const consecutiveDays = computeConsecutiveDays(closes);\n\n // 6. Determine which rows to write back (only rows after watermark)\n const writeRows =\n watermark && !forceFull\n ? rawRows.map((_, i) => i).filter((i) => dates[i] > watermark)\n : rawRows.map((_, i) => i);\n\n if (writeRows.length === 0) {\n const tier2Targets = workingTables ? {\n daily: workingTables.dailyTable,\n dateContext: workingTables.dateContextTable,\n } : undefined;\n const tier2Result = await runTier2(conn, tier2Targets, io?.spotStore);\n\n // Flush even if no Tier 1 rows — Tier 2 may have written to working tables\n if (parquetMode && workingTables && opts.dataDir) {\n await flushEnrichedToParquet(conn, opts.dataDir, ticker, workingTables);\n }\n\n return {\n ticker,\n tier1: { status: \"complete\", fieldsWritten: 0, reason: \"already up to date\" },\n tier2: tier2Result,\n tier3: {\n status: \"skipped\",\n reason: \"no intraday data in market.spot\",\n },\n rowsEnriched: 0,\n enrichedThrough: watermark,\n };\n }\n\n // 7. Build enriched rows for batch UPDATE\n const enrichedRows = writeRows.map((i) => {\n const atrVal = atrArr[i];\n const atrPct = !isNaN(atrVal) && closes[i] > 0 ? (atrVal / closes[i]) * 100 : null;\n const priorClose = i > 0 ? closes[i - 1] : null;\n const priorReturn =\n i > 1 ? ((closes[i - 1] - closes[i - 2]) / closes[i - 2]) * 100 : null;\n const gapPct =\n priorClose !== null && priorClose > 0\n ? ((opens[i] - priorClose) / priorClose) * 100\n : null;\n // Intraday_Range_Pct: high-low range as % of close.\n // Use close (not open) for consistency with every other \"_Pct\" column in\n // this file (ATR_Pct, Price_vs_EMA21_Pct, Return_5D, etc. all divide by\n // close). Also guards against zero-low contamination: if the day's low\n // came in as 0 from a bad minute bar, (high - 0) inflates to ~100% of\n // close — meaningless. Requiring lows[i] > 0 forces such rows to null.\n const intradayRangePct =\n closes[i] > 0 && highs[i] > 0 && lows[i] > 0\n ? ((highs[i] - lows[i]) / closes[i]) * 100\n : null;\n const intradayReturnPct =\n opens[i] > 0 ? ((closes[i] - opens[i]) / opens[i]) * 100 : null;\n const hiLoRange = highs[i] - lows[i];\n const closePosInRange =\n hiLoRange > 0 ? (closes[i] - lows[i]) / hiLoRange : null;\n const ret5d =\n i >= 5 && closes[i - 5] > 0\n ? ((closes[i] - closes[i - 5]) / closes[i - 5]) * 100\n : null;\n const ret20d =\n i >= 20 && closes[i - 20] > 0\n ? ((closes[i] - closes[i - 20]) / closes[i - 20]) * 100\n : null;\n const gapFilled =\n priorClose !== null ? isGapFilled(opens[i], highs[i], lows[i], priorClose) : null;\n const dateObj = parseDateStr(dates[i]);\n const dayOfWeek = dateObj ? dateObj.getDay() : null; // 0=Sun..6=Sat\n const monthVal = dateObj ? dateObj.getMonth() + 1 : null;\n const opex = isOpex(dates[i]);\n const ema21val = ema21[i];\n const sma50val = sma50[i];\n const priceVsEma21 =\n !isNaN(ema21val) && ema21val > 0\n ? ((closes[i] - ema21val) / ema21val) * 100\n : null;\n const priceVsSma50 =\n !isNaN(sma50val) && sma50val > 0\n ? ((closes[i] - sma50val) / sma50val) * 100\n : null;\n const rsi14val = rsi14[i];\n\n // Prior_Range_vs_ATR: ratio of prior day's intraday range (% of close) to\n // prior day's ATR (% of close). Known at market open — prior day range\n // and ATR are both available before today's trading begins.\n //\n // Algebraically (range_pct / atr_pct) = (range / atr) since the close\n // cancels, but writing it as a ratio of percents makes the intent\n // explicit and matches how downstream analysis reads the column.\n //\n // Sanity guards: prior close > 0 (otherwise the percent denominators\n // explode), prior high/low > 0 (catches zero-bar contamination from the\n // upstream spot ingester), and priorATR > 0 (avoid div-by-zero).\n // First bar (i=0) has no prior day → null.\n let priorRangeVsATR: number | null = null;\n if (i > 0) {\n const priorClose = closes[i - 1];\n const priorHigh = highs[i - 1];\n const priorLow = lows[i - 1];\n const priorATR = atrArr[i - 1];\n if (\n priorClose > 0 &&\n priorHigh > 0 &&\n priorLow > 0 &&\n !isNaN(priorATR) && priorATR > 0\n ) {\n const priorRangePct = ((priorHigh - priorLow) / priorClose) * 100;\n const priorAtrPct = (priorATR / priorClose) * 100;\n priorRangeVsATR = priorRangePct / priorAtrPct;\n }\n }\n\n return {\n ticker,\n date: dates[i],\n Prior_Close: priorClose,\n Gap_Pct: gapPct,\n RSI_14: isNaN(rsi14val) ? null : rsi14val,\n ATR_Pct: atrPct,\n Price_vs_EMA21_Pct: priceVsEma21,\n Price_vs_SMA50_Pct: priceVsSma50,\n Realized_Vol_5D: isNaN(rvol5[i]) ? null : rvol5[i],\n Realized_Vol_20D: isNaN(rvol20[i]) ? null : rvol20[i],\n Return_5D: ret5d,\n Return_20D: ret20d,\n Intraday_Range_Pct: intradayRangePct,\n Intraday_Return_Pct: intradayReturnPct,\n Close_Position_In_Range: closePosInRange,\n Gap_Filled: gapFilled,\n Consecutive_Days: consecutiveDays[i],\n Prev_Return_Pct: priorReturn,\n Day_of_Week: dayOfWeek,\n Month: monthVal,\n Is_Opex: opex,\n Prior_Range_vs_ATR: priorRangeVsATR,\n };\n });\n\n // 8. Batch UPDATE via DuckDB VALUES CTE, batches of 500\n const BATCH_SIZE = 500;\n const columns = [\n \"Prior_Close\",\n \"Gap_Pct\",\n \"RSI_14\",\n \"ATR_Pct\",\n \"Price_vs_EMA21_Pct\",\n \"Price_vs_SMA50_Pct\",\n \"Realized_Vol_5D\",\n \"Realized_Vol_20D\",\n \"Return_5D\",\n \"Return_20D\",\n \"Intraday_Range_Pct\",\n \"Intraday_Return_Pct\",\n \"Close_Position_In_Range\",\n \"Gap_Filled\",\n \"Consecutive_Days\",\n \"Prev_Return_Pct\",\n \"Day_of_Week\",\n \"Month\",\n \"Is_Opex\",\n \"Prior_Range_vs_ATR\",\n ];\n for (let start = 0; start < enrichedRows.length; start += BATCH_SIZE) {\n const batch = enrichedRows.slice(start, start + BATCH_SIZE);\n await batchUpdateDaily(conn, batch, columns, dailyTarget);\n }\n\n // 9. Run Tier 2 (VIX context enrichment) with parameterized targets\n const tier2Targets = workingTables ? {\n daily: workingTables.dailyTable,\n dateContext: workingTables.dateContextTable,\n } : undefined;\n const tier2Result = await runTier2(conn, tier2Targets, io?.spotStore);\n\n // 10. Tier 3 — intraday timing fields (routes through io.spotStore when provided)\n const tier3Result = await runTier3(conn, ticker, dates, dailyTarget, io?.spotStore);\n\n // 11. Persist the new watermark.\n // Every watermark write goes through the JSON adapter. The legacy SQL\n // UPSERT against the metadata sync table has been removed — when callers\n // don't supply `io.watermarkStore` we fall back to\n // `upsertEnrichedThrough(ticker, val, dataDir)` directly. If neither io\n // nor dataDir is supplied the watermark simply isn't persisted (math\n // still runs); callers that need watermark continuity must supply one of\n // the two.\n const newWatermark = dates[dates.length - 1];\n if (io?.watermarkStore) {\n await io.watermarkStore.upsert(ticker, newWatermark);\n } else if (opts.dataDir) {\n await upsertEnrichedThrough(ticker, newWatermark, opts.dataDir);\n }\n\n // 12. Parquet mode: write enrichment to the enriched/ partition layout\n // (legacy daily.parquet output retired)\n if (parquetMode && workingTables && opts.dataDir) {\n await flushEnrichedToParquet(conn, opts.dataDir, ticker, workingTables);\n }\n\n return {\n ticker,\n tier1: { status: \"complete\", fieldsWritten: columns.length },\n tier2: tier2Result,\n tier3: tier3Result,\n rowsEnriched: enrichedRows.length,\n enrichedThrough: newWatermark,\n };\n\n } finally {\n // Sole owner of working table cleanup — always runs on success or error.\n // On success: tables still exist (flushParquetWorkingTables does not drop them).\n // On error: tables may contain partial results useful for debugging, but we\n // clean up to avoid leaking temp tables across calls.\n if (workingTables) {\n try { await conn.run(`DROP TABLE IF EXISTS \"${workingTables.dailyTable}\"`); } catch { /* */ }\n try { await conn.run(`DROP TABLE IF EXISTS \"${workingTables.dateContextTable}\"`); } catch { /* */ }\n }\n }\n}\n\n// =============================================================================\n// Tier 3: Intraday Timing Fields\n// =============================================================================\n\n/**\n * Convert HH:MM time string to decimal hours (e.g., \"10:30\" → 10.5).\n */\nfunction hhmmToDecimalHours(time: string): number {\n const [h, m] = time.split(\":\").map(Number);\n return h + m / 60;\n}\n\n/**\n * Compute intraday timing fields from raw OHLCV bars for a single date.\n *\n * Pure function — no DB access. Exported for unit testing.\n *\n * Fields computed:\n * - highTime: Decimal hours when day high occurred (e.g., 10.5 = 10:30)\n * - lowTime: Decimal hours when day low occurred\n * - highBeforeLow: true if high occurred before low\n * - reversalType: +1 = morning high + afternoon low, -1 = morning low + afternoon high, 0 = trend day\n * - openingDriveStrength: (first 30-min range) / (full day range), 0-1 scale; 0 if day range is 0\n * - intradayRealizedVol: Annualized realized vol from intraday bar-to-bar log returns (decimal, not %)\n *\n * @param bars - Array of {time: \"HH:MM\", open, high, low, close} ordered by time (oldest first)\n * @returns Computed fields or null if bars is empty\n */\nexport function computeIntradayTimingFields(\n bars: Array<{ time: string; open: number; high: number; low: number; close: number }>\n): {\n highTime: number;\n lowTime: number;\n highBeforeLow: boolean;\n reversalType: number;\n openingDriveStrength: number;\n intradayRealizedVol: number;\n} | null {\n // Defense-in-depth: drop zero/non-finite OHLC bars before any min/max\n // scan. A single zero-low bar from a provider gap (see ParquetSpotStore\n // writer guard) would make minLow=0 and lowTimeStr point to the gap's\n // timestamp, producing a meaningless Low_Time field. The writer + SQL\n // filters in the spot read paths catch most of these upstream; this\n // makes the pure function safe regardless of caller.\n bars = bars.filter(\n (b) =>\n Number.isFinite(b.open) && b.open > 0 &&\n Number.isFinite(b.high) && b.high > 0 &&\n Number.isFinite(b.low) && b.low > 0 &&\n Number.isFinite(b.close)&& b.close> 0,\n );\n if (bars.length === 0) return null;\n\n let maxHigh = -Infinity;\n let minLow = Infinity;\n let highTimeStr = bars[0].time;\n let lowTimeStr = bars[0].time;\n\n for (const bar of bars) {\n if (bar.high > maxHigh) {\n maxHigh = bar.high;\n highTimeStr = bar.time;\n }\n if (bar.low < minLow) {\n minLow = bar.low;\n lowTimeStr = bar.time;\n }\n }\n\n const highTime = hhmmToDecimalHours(highTimeStr);\n const lowTime = hhmmToDecimalHours(lowTimeStr);\n const highBeforeLow = highTime < lowTime;\n\n // Reversal type: morning = before 12:00, afternoon = 12:00 or later\n const highInMorning = highTime < 12;\n const lowInMorning = lowTime < 12;\n const highInAfternoon = highTime >= 12;\n const lowInAfternoon = lowTime >= 12;\n\n let reversalType = 0;\n if (highInMorning && lowInAfternoon) reversalType = 1; // High morning, low afternoon\n else if (lowInMorning && highInAfternoon) reversalType = -1; // Low morning, high afternoon\n\n // Opening Drive Strength: ratio of first-30-min range to full-day range\n // First 30 min = bars with time < 10:00 (market opens 09:30)\n const openingBars = bars.filter(b => hhmmToDecimalHours(b.time) < 10);\n let openingDriveStrength = 0;\n const fullDayRange = maxHigh - minLow;\n if (openingBars.length > 0 && fullDayRange > 0) {\n const openHigh = Math.max(...openingBars.map(b => b.high));\n const openLow = Math.min(...openingBars.map(b => b.low));\n openingDriveStrength = (openHigh - openLow) / fullDayRange;\n }\n\n // Intraday Realized Vol: annualized from bar-to-bar close log returns\n // Uses sqrt(252 * barsPerDay) annualization\n let intradayRealizedVol = 0;\n if (bars.length >= 2) {\n const logReturns: number[] = [];\n for (let i = 1; i < bars.length; i++) {\n if (bars[i - 1].close > 0 && bars[i].close > 0) {\n logReturns.push(Math.log(bars[i].close / bars[i - 1].close));\n }\n }\n if (logReturns.length > 0) {\n const mean = logReturns.reduce((s, r) => s + r, 0) / logReturns.length;\n const variance = logReturns.reduce((s, r) => s + (r - mean) ** 2, 0) / logReturns.length;\n const barStdDev = Math.sqrt(variance);\n // Annualize: multiply by sqrt(barsPerDay * 252)\n // barsPerDay = number of bars we actually have (adapts to timeframe)\n intradayRealizedVol = barStdDev * Math.sqrt(bars.length * 252);\n }\n }\n\n return { highTime, lowTime, highBeforeLow, reversalType, openingDriveStrength, intradayRealizedVol };\n}\n\n/** Run Tier 3: compute intraday timing fields from market.spot and write to the daily write-target table */\nasync function runTier3(\n conn: DuckDBConnection,\n ticker: string,\n dates: string[],\n dailyTarget: string = \"market.enriched\",\n spotStore?: SpotStore,\n): Promise<TierStatus> {\n // Check if intraday data exists for this ticker\n // Routes through spotStore.getCoverage when provided\n const hasData = await hasTier3Data(conn, ticker, spotStore);\n if (!hasData) {\n return {\n status: \"skipped\",\n reason: \"no intraday data in market.spot — import intraday bars to populate Tier 3 fields\",\n };\n }\n\n // Query intraday bars for all dates in the enrichment range.\n // When spotStore is provided, route through\n // SpotStore.readBars(ticker, from, to). The downstream group-by-date math\n // is unchanged — we just reshape BarRow[] into the same tuple-index shape\n // the existing math consumes (date, time, open, high, low, close).\n let rows: unknown[][];\n let dateIdx: number;\n let timeIdx: number;\n let openIdx: number;\n let highIdx: number;\n let lowIdx: number;\n let closeIdx: number;\n\n if (spotStore) {\n const bars = await spotStore.readBars(ticker, dates[0], dates[dates.length - 1]);\n // Shape into the same tuple-array format the existing math expects below\n rows = bars.map((b) => [b.date, b.time, b.open, b.high, b.low, b.close]);\n dateIdx = 0;\n timeIdx = 1;\n openIdx = 2;\n highIdx = 3;\n lowIdx = 4;\n closeIdx = 5;\n } else {\n // Canonical minute-bar view is `market.spot` — same\n // ticker/date/time/ohlcv schema as the earlier intraday view it replaced.\n // Defense-in-depth: filter out zero/null minute bars at the SQL layer so\n // Tier 3 timing fields (High_Time, Low_Time, Opening_Drive_Strength) are\n // never seeded with provider-gap timestamps.\n const result = await conn.runAndReadAll(\n `SELECT date, time, open, high, low, close\n FROM market.spot\n WHERE ticker = $1 AND date >= $2 AND date <= $3\n AND open IS NOT NULL AND open > 0\n AND high IS NOT NULL AND high > 0\n AND low IS NOT NULL AND low > 0\n AND close IS NOT NULL AND close > 0\n ORDER BY date, time`,\n [ticker, dates[0], dates[dates.length - 1]]\n );\n\n rows = result.getRows();\n const columns = result.columnNames();\n dateIdx = columns.indexOf(\"date\");\n timeIdx = columns.indexOf(\"time\");\n openIdx = columns.indexOf(\"open\");\n highIdx = columns.indexOf(\"high\");\n lowIdx = columns.indexOf(\"low\");\n closeIdx = columns.indexOf(\"close\");\n }\n\n // Group bars by date\n const barsByDate = new Map<string, Array<{ time: string; open: number; high: number; low: number; close: number }>>();\n for (const row of rows) {\n const dateStr = String(row[dateIdx]);\n const bar = {\n time: String(row[timeIdx]),\n open: Number(row[openIdx]),\n high: Number(row[highIdx]),\n low: Number(row[lowIdx]),\n close: Number(row[closeIdx]),\n };\n if (!barsByDate.has(dateStr)) barsByDate.set(dateStr, []);\n barsByDate.get(dateStr)!.push(bar);\n }\n\n if (barsByDate.size === 0) {\n return {\n status: \"skipped\",\n reason: \"intraday data exists but no bars overlap with enrichment date range\",\n };\n }\n\n // Compute timing fields for each date and batch update the enriched table\n const tier3Cols = [\"High_Time\", \"Low_Time\", \"High_Before_Low\", \"Reversal_Type\", \"Opening_Drive_Strength\", \"Intraday_Realized_Vol\"];\n const enrichedRows: Array<Record<string, unknown>> = [];\n\n for (const [dateStr, bars] of barsByDate) {\n const timing = computeIntradayTimingFields(bars);\n if (!timing) continue;\n\n enrichedRows.push({\n ticker,\n date: dateStr,\n High_Time: timing.highTime,\n Low_Time: timing.lowTime,\n High_Before_Low: timing.highBeforeLow ? 1 : 0,\n Reversal_Type: timing.reversalType,\n Opening_Drive_Strength: timing.openingDriveStrength,\n Intraday_Realized_Vol: timing.intradayRealizedVol,\n });\n }\n\n // Batch update using the existing batchUpdateDaily helper\n const BATCH_SIZE = 500;\n for (let start = 0; start < enrichedRows.length; start += BATCH_SIZE) {\n const batch = enrichedRows.slice(start, start + BATCH_SIZE);\n await batchUpdateDaily(conn, batch, tier3Cols, dailyTarget);\n }\n\n return { status: \"complete\", fieldsWritten: tier3Cols.length };\n}\n","/**\n * Market Data Importer — stores-based ingest surface.\n *\n * Every spot-bar write flows through `SpotStore.writeBars(ticker, date,\n * BarRow[])`. The `target_table` parameter from the earlier ingest API has\n * been removed — daily / date_context outputs are derived by\n * `EnrichedStore.compute()` + `computeContext()` invoked at the tool-handler\n * layer (see `tools/market-imports.ts`).\n *\n * Exports:\n * - parseCsvToBars(filePath, ticker, columnMapping) — CSV → BarRow[]\n * - parseDatabaseRowsToBars(rows, ticker, columnMapping) — DB rows → BarRow[]\n * - importMarketCsvFile(stores, params) — convenience wrapper that parses,\n * writes, and groups by date. Pure orchestration over SpotStore.\n * - importFromDatabase(stores, conn, params) — DB-backed parallel of the\n * above. Caller supplies the analytics conn for the ATTACH/DETACH lifecycle\n * so this file does not import `getConnection`.\n * - validateColumnMapping — pure helper.\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport * as fs from \"fs/promises\";\nimport { normalizeTicker } from \"./ticker.ts\";\nimport type { MarketStores, BarRow } from \"../market/stores/index.ts\";\n\n// =============================================================================\n// Constants + types (kept local — duplicated in market-importer-api.ts so neither\n// file depends on the other for these tiny pure values).\n// =============================================================================\n\n/**\n * Required schema fields per virtual target. Kept for `validateColumnMapping`\n * compatibility — the new spot-write path always uses the `intraday` schema\n * (date + time + OHLC) but legacy callers may still validate against `daily`\n * or `date_context`.\n */\nconst REQUIRED_SCHEMA_FIELDS: Record<string, string[]> = {\n daily: [\"date\", \"open\", \"high\", \"low\", \"close\"],\n date_context: [\"date\"],\n intraday: [\"date\", \"time\", \"open\", \"high\", \"low\", \"close\"],\n};\n\nexport type MarketImportTargetTable = \"daily\" | \"date_context\" | \"intraday\";\n\nexport interface ImportMarketCsvParams {\n filePath: string;\n ticker: string;\n columnMapping: Record<string, string>;\n dryRun?: boolean;\n}\n\nexport interface ImportFromDatabaseParams {\n dbPath: string;\n query: string;\n ticker: string;\n columnMapping: Record<string, string>;\n dryRun?: boolean;\n}\n\nexport interface ImportSpotResult {\n rowsWritten: number;\n inputRowCount: number;\n parsedRows: number;\n dryRun: boolean;\n dateRange: { from: string; to: string } | null;\n ticker: string;\n}\n\n// =============================================================================\n// validateColumnMapping — pure helper\n// =============================================================================\n\n/**\n * Validate that the column mapping covers all required schema fields for the\n * target table. Intraday allows missing `time` when `date` is mapped (auto-\n * derived from Unix timestamp).\n */\nexport function validateColumnMapping(\n columnMapping: Record<string, string>,\n targetTable: MarketImportTargetTable,\n): { valid: boolean; missingFields: string[] } {\n const schemaValues = Object.values(columnMapping);\n const required = REQUIRED_SCHEMA_FIELDS[targetTable] ?? [];\n let missing = required.filter((field) => !schemaValues.includes(field));\n if (targetTable === \"intraday\" && missing.includes(\"time\") && schemaValues.includes(\"date\")) {\n missing = missing.filter((f) => f !== \"time\");\n }\n return { valid: missing.length === 0, missingFields: missing };\n}\n\n// =============================================================================\n// CSV parsing helpers (private — duplicated from -api.ts to keep this file\n// self-contained for the spot-write code path)\n// =============================================================================\n\n/** Parse CSV content into rows with header mapping. Strips UTF-8 BOM. */\nfunction parseCSV(content: string): { headers: string[]; rows: Record<string, string>[] } {\n const lines = content.replace(/^\\uFEFF/, \"\").trim().split(\"\\n\");\n if (lines.length < 2) return { headers: [], rows: [] };\n const headers = lines[0].trim().split(\",\").map((h) => h.trim());\n const rows: Record<string, string>[] = [];\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i].trim();\n if (!line) continue;\n const values = line.split(\",\");\n const row: Record<string, string> = {};\n headers.forEach((h, idx) => {\n row[h] = values[idx]?.trim() ?? \"\";\n });\n rows.push(row);\n }\n return { headers, rows };\n}\n\n/**\n * Parse a date value flexibly:\n * - Numeric > 1e8 → treat as Unix seconds; return ET YYYY-MM-DD.\n * - YYYY-MM-DD string → return as-is.\n * - Otherwise → null.\n */\nfunction parseFlexibleDate(value: string): string | null {\n const numeric = Number(value);\n if (!isNaN(numeric) && numeric > 1e8) {\n return new Date(numeric * 1000).toLocaleDateString(\"en-CA\", {\n timeZone: \"America/New_York\",\n year: \"numeric\", month: \"2-digit\", day: \"2-digit\",\n });\n }\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n return null;\n}\n\n/**\n * Extract HH:MM time from a value in Eastern Time:\n * - Numeric > 1e8 → ET HH:MM from Unix timestamp.\n * - HH:MM string → return as-is.\n * - HHMM (4 digits) → \"HH:MM\".\n * - Otherwise → null.\n */\nfunction parseFlexibleTime(value: string): string | null {\n const numeric = Number(value);\n if (!isNaN(numeric) && numeric > 1e8) {\n const d = new Date(numeric * 1000);\n return d.toLocaleTimeString(\"en-US\", {\n timeZone: \"America/New_York\",\n hour: \"2-digit\", minute: \"2-digit\", hour12: false,\n });\n }\n if (/^\\d{2}:\\d{2}$/.test(value)) return value;\n if (/^\\d{4}$/.test(value)) return `${value.slice(0, 2)}:${value.slice(2)}`;\n return null;\n}\n\n/**\n * Apply column mapping to raw rows, parsing dates/times and coercing numerics.\n * Drops rows with unparseable dates. Returns `Record<string, unknown>[]` —\n * callers can either coerce to `BarRow` (via `coerceMappedRowToBar`) or use\n * the dict shape directly.\n */\nfunction applyColumnMapping(\n rows: Record<string, string>[],\n columnMapping: Record<string, string>,\n ticker: string,\n): Array<Record<string, unknown>> {\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const result: Array<Record<string, unknown>> = [];\n for (const row of rows) {\n const mapped: Record<string, unknown> = {};\n let hasNullDate = false;\n for (const [sourceCol, schemaCol] of Object.entries(columnMapping)) {\n const rawValue = row[sourceCol] ?? \"\";\n if (schemaCol === \"date\") {\n const parsed = parseFlexibleDate(rawValue);\n if (parsed === null) { hasNullDate = true; break; }\n mapped[schemaCol] = parsed;\n } else if (schemaCol === \"time\") {\n const parsed = parseFlexibleTime(rawValue);\n if (parsed === null) { hasNullDate = true; break; }\n mapped[schemaCol] = parsed;\n } else {\n if (rawValue === \"\" || rawValue === \"NaN\" || rawValue === \"NA\") {\n mapped[schemaCol] = null;\n } else {\n const numVal = parseFloat(rawValue);\n mapped[schemaCol] = isNaN(numVal) ? rawValue : numVal;\n }\n }\n }\n if (hasNullDate) continue;\n if (!(\"date\" in mapped)) continue;\n // Auto-extract time from a Unix-timestamp date column when `time` is not mapped.\n if (!(\"time\" in mapped)) {\n const dateSourceCol = Object.entries(columnMapping).find(([, schema]) => schema === \"date\")?.[0];\n if (dateSourceCol) {\n const rawDateValue = row[dateSourceCol] ?? \"\";\n const numericDate = Number(rawDateValue);\n if (!isNaN(numericDate) && numericDate > 1e8) {\n const t = parseFlexibleTime(rawDateValue);\n if (t) mapped[\"time\"] = t;\n }\n }\n }\n mapped[\"ticker\"] = normalizedTicker;\n result.push(mapped);\n }\n return result;\n}\n\n/**\n * Coerce a `Record<string, unknown>` from `applyColumnMapping` into a `BarRow`.\n * Non-numeric values fall back to `0` so the spot store always receives well-\n * typed numbers — invalid rows are filtered upstream.\n */\nfunction coerceMappedRowToBar(\n row: Record<string, unknown>,\n ticker: string,\n): BarRow | null {\n const date = typeof row.date === \"string\" ? row.date : null;\n if (!date) return null;\n const time = typeof row.time === \"string\" ? row.time : \"09:30\";\n const num = (v: unknown): number => {\n if (typeof v === \"number\") return Number.isFinite(v) ? v : 0;\n if (typeof v === \"string\") {\n const n = Number(v);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n };\n const optNum = (v: unknown): number | undefined => {\n if (v === null || v === undefined) return undefined;\n const n = num(v);\n return Number.isFinite(n) ? n : undefined;\n };\n return {\n ticker,\n date,\n time,\n open: num(row.open),\n high: num(row.high),\n low: num(row.low),\n close: num(row.close),\n volume: typeof row.volume === \"number\" || typeof row.volume === \"string\" ? num(row.volume) : 0,\n bid: optNum(row.bid),\n ask: optNum(row.ask),\n };\n}\n\n/**\n * Group BarRow values by date, preserving insertion order so\n * `[...byDate.keys()][0]` / `[...byDate.keys()].pop()` yield min/max dates\n * when the input is sorted.\n */\nfunction groupBarsByDate(bars: BarRow[]): Map<string, BarRow[]> {\n const byDate = new Map<string, BarRow[]>();\n for (const bar of bars) {\n const arr = byDate.get(bar.date);\n if (arr) arr.push(bar);\n else byDate.set(bar.date, [bar]);\n }\n return byDate;\n}\n\n// =============================================================================\n// Public parse helpers — used by tools/market-imports.ts handlers\n// =============================================================================\n\n/**\n * Parse a CSV file into a flat `BarRow[]` using the supplied column mapping.\n * Throws on unreadable file or empty input. Returns `[]` when the mapping\n * yields no valid rows (caller decides whether that's an error).\n */\nexport async function parseCsvToBars(\n filePath: string,\n ticker: string,\n columnMapping: Record<string, string>,\n): Promise<BarRow[]> {\n let content: string;\n try {\n content = await fs.readFile(filePath, \"utf-8\");\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to read CSV file at \"${filePath}\": ${msg}`);\n }\n const { rows } = parseCSV(content);\n if (rows.length === 0) {\n throw new Error(`CSV file \"${filePath}\" has no data rows`);\n }\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const mappedRows = applyColumnMapping(rows, columnMapping, normalizedTicker);\n const bars: BarRow[] = [];\n for (const row of mappedRows) {\n const bar = coerceMappedRowToBar(row, normalizedTicker);\n if (bar) bars.push(bar);\n }\n return bars;\n}\n\n/**\n * Parse a set of pre-fetched database rows (typically from a DuckDB ATTACH +\n * SELECT) into a flat `BarRow[]` using the supplied column mapping.\n */\nexport function parseDatabaseRowsToBars(\n rawRows: Record<string, string>[],\n ticker: string,\n columnMapping: Record<string, string>,\n): BarRow[] {\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const mappedRows = applyColumnMapping(rawRows, columnMapping, normalizedTicker);\n const bars: BarRow[] = [];\n for (const row of mappedRows) {\n const bar = coerceMappedRowToBar(row, normalizedTicker);\n if (bar) bars.push(bar);\n }\n return bars;\n}\n\n// =============================================================================\n// Public ingest helpers — orchestrate parse + per-date stores.spot.writeBars\n// =============================================================================\n\n/**\n * Import a CSV file by parsing it into BarRow[] and writing per-date\n * partitions through `stores.spot.writeBars`. Pure orchestration — does NOT\n * call `EnrichedStore.compute()`; the tool-handler layer composes enrichment\n * after the spot write.\n */\nexport async function importMarketCsvFile(\n stores: MarketStores,\n params: ImportMarketCsvParams,\n): Promise<ImportSpotResult> {\n const { filePath, ticker, columnMapping, dryRun = false } = params;\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const bars = await parseCsvToBars(filePath, normalizedTicker, columnMapping);\n\n if (bars.length === 0) {\n return {\n rowsWritten: 0,\n inputRowCount: 0,\n parsedRows: 0,\n dryRun,\n dateRange: null,\n ticker: normalizedTicker,\n };\n }\n\n const byDate = groupBarsByDate(bars);\n const dates = [...byDate.keys()].sort();\n const dateRange = { from: dates[0], to: dates[dates.length - 1] };\n\n if (dryRun) {\n return {\n rowsWritten: 0,\n inputRowCount: bars.length,\n parsedRows: bars.length,\n dryRun: true,\n dateRange,\n ticker: normalizedTicker,\n };\n }\n\n let rowsWritten = 0;\n for (const [date, dayBars] of byDate) {\n await stores.spot.writeBars(normalizedTicker, date, dayBars);\n rowsWritten += dayBars.length;\n }\n\n return {\n rowsWritten,\n inputRowCount: bars.length,\n parsedRows: bars.length,\n dryRun: false,\n dateRange,\n ticker: normalizedTicker,\n };\n}\n\n/**\n * Import from an external DuckDB database by ATTACHing it on the supplied\n * `conn`, executing `params.query`, parsing rows into BarRow[], and writing\n * per-date partitions through `stores.spot.writeBars`. The caller owns the\n * analytics conn lifecycle (RW upgrade/downgrade) and the ATTACH/DETACH —\n * passing in `conn` rather than re-importing `getConnection` keeps this file\n * pure-spot-write and avoids a circular dependency with `db/connection.ts`.\n */\nexport async function importFromDatabase(\n stores: MarketStores,\n conn: DuckDBConnection,\n params: ImportFromDatabaseParams,\n): Promise<ImportSpotResult> {\n const { dbPath, query, ticker, columnMapping, dryRun = false } = params;\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const EXT_ALIAS = \"ext_import_source\";\n const escapedDbPath = dbPath.replace(/'/g, \"''\");\n await conn.run(`ATTACH '${escapedDbPath}' AS ${EXT_ALIAS} (READ_ONLY)`);\n\n let bars: BarRow[];\n try {\n const result = await conn.runAndReadAll(query);\n const colNames = result.columnNames();\n const rows = result.getRows();\n const rawRows: Record<string, string>[] = rows.map((row) => {\n const obj: Record<string, string> = {};\n colNames.forEach((name, idx) => {\n const val = row[idx];\n obj[name] = val === null || val === undefined ? \"\" : String(val);\n });\n return obj;\n });\n bars = parseDatabaseRowsToBars(rawRows, normalizedTicker, columnMapping);\n } finally {\n try { await conn.run(`DETACH ${EXT_ALIAS}`); } catch { /* best-effort */ }\n }\n\n if (bars.length === 0) {\n return {\n rowsWritten: 0,\n inputRowCount: 0,\n parsedRows: 0,\n dryRun,\n dateRange: null,\n ticker: normalizedTicker,\n };\n }\n\n const byDate = groupBarsByDate(bars);\n const dates = [...byDate.keys()].sort();\n const dateRange = { from: dates[0], to: dates[dates.length - 1] };\n\n if (dryRun) {\n return {\n rowsWritten: 0,\n inputRowCount: bars.length,\n parsedRows: bars.length,\n dryRun: true,\n dateRange,\n ticker: normalizedTicker,\n };\n }\n\n let rowsWritten = 0;\n for (const [date, dayBars] of byDate) {\n await stores.spot.writeBars(normalizedTicker, date, dayBars);\n rowsWritten += dayBars.length;\n }\n\n return {\n rowsWritten,\n inputRowCount: bars.length,\n parsedRows: bars.length,\n dryRun: false,\n dateRange,\n ticker: normalizedTicker,\n };\n}\n","/**\n * Shared Analysis Statistics\n *\n * Reusable stat computation extracted from analyze_regime_performance.\n * Used by portfolio_structure_map, analyze_structure_fit, and\n * analyze_regime_performance to compute per-slice statistics from P&L arrays.\n */\n\n/**\n * Statistics for a slice (segment, bucket, group) of trades.\n */\nexport interface SliceStats {\n tradeCount: number;\n wins: number;\n losses: number;\n /** Win rate as percentage (0-100) */\n winRate: number;\n totalPl: number;\n avgPl: number;\n avgWin: number;\n avgLoss: number;\n /** Gross wins / gross losses. null if no losses but has wins. 0 if no wins or empty. */\n profitFactor: number | null;\n}\n\n/**\n * Round a number to 2 decimal places.\n */\nfunction round2(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\n/**\n * Compute statistics for an array of P&L values.\n * Wins are P&L > 0, losses are P&L <= 0.\n * All numeric values are rounded to 2 decimal places.\n *\n * @param pls - Array of P&L values (positive = win, zero/negative = loss)\n * @returns Computed slice statistics\n */\nexport function computeSliceStats(pls: number[]): SliceStats {\n if (pls.length === 0) {\n return {\n tradeCount: 0,\n wins: 0,\n losses: 0,\n winRate: 0,\n totalPl: 0,\n avgPl: 0,\n avgWin: 0,\n avgLoss: 0,\n profitFactor: 0,\n };\n }\n\n const winPls = pls.filter((p) => p > 0);\n const lossPls = pls.filter((p) => p <= 0);\n\n const wins = winPls.length;\n const losses = lossPls.length;\n const winRate = (wins / pls.length) * 100;\n const totalPl = pls.reduce((sum, p) => sum + p, 0);\n const avgPl = totalPl / pls.length;\n\n const avgWin = wins > 0 ? winPls.reduce((s, p) => s + p, 0) / wins : 0;\n const avgLoss = losses > 0 ? lossPls.reduce((s, p) => s + p, 0) / losses : 0;\n\n const grossWins = winPls.reduce((s, p) => s + p, 0);\n const grossLosses = Math.abs(lossPls.reduce((s, p) => s + p, 0));\n\n let profitFactor: number | null;\n if (grossLosses > 0) {\n profitFactor = grossWins / grossLosses;\n } else if (grossWins > 0) {\n profitFactor = null; // All winners, no losses to divide by\n } else {\n profitFactor = 0;\n }\n\n return {\n tradeCount: pls.length,\n wins,\n losses,\n winRate: round2(winRate),\n totalPl: round2(totalPl),\n avgPl: round2(avgPl),\n avgWin: round2(avgWin),\n avgLoss: round2(avgLoss),\n profitFactor: profitFactor !== null ? round2(profitFactor) : null,\n };\n}\n","/**\n * Filter Predicate Builder\n *\n * Converts EntryFilter objects into runtime predicates that can evaluate\n * market data records. Handles field timing awareness via CLOSE_KNOWN_FIELDS\n * to automatically apply the prev_ prefix for close-derived fields.\n *\n * Used by analyze_structure_fit and portfolio_structure_map to evaluate\n * entry filters against market data rows.\n */\n\nimport { CLOSE_KNOWN_FIELDS } from \"./field-timing.ts\";\nimport type { EntryFilter } from \"../models/strategy-profile.ts\";\n\n/**\n * A compiled filter predicate with metadata about the field key used.\n */\nexport interface FilterPredicate {\n /** Evaluate this predicate against a market data record */\n test: (market: Record<string, unknown>) => boolean;\n /** The actual field key used for lookup (may have prev_ prefix) */\n fieldKey: string;\n /** Whether the field was detected as close-derived and lagged */\n isLagged: boolean;\n}\n\n/**\n * Day-of-week name to number mapping (market data uses 1=Mon to 5=Fri).\n */\nconst DAY_NAME_TO_NUM: Record<string, number> = {\n monday: 1, mon: 1,\n tuesday: 2, tue: 2, tues: 2,\n wednesday: 3, wed: 3,\n thursday: 4, thu: 4, thurs: 4,\n friday: 5, fri: 5,\n};\n\n/**\n * If a filter value is a day-of-week name and the field is Day_of_Week,\n * convert it to the corresponding number. Returns null if not applicable.\n */\nfunction resolveDayName(value: unknown): number | null {\n if (typeof value !== \"string\") return null;\n return DAY_NAME_TO_NUM[value.toLowerCase()] ?? null;\n}\n\n/**\n * Safely extract a numeric value from a record.\n * Returns NaN if the value is missing, null, undefined, or non-numeric.\n */\nfunction getNum(record: Record<string, unknown>, key: string): number {\n const val = record[key];\n if (val === null || val === undefined) return NaN;\n const num = Number(val);\n return num;\n}\n\n/**\n * Safely extract a value from a record for loose equality comparison.\n * Returns undefined if the key is missing.\n */\nfunction getRaw(record: Record<string, unknown>, key: string): unknown {\n return record[key];\n}\n\n/**\n * Check whether a filter value is a cross-field reference (a string that\n * looks like a field name rather than a pure numeric literal).\n */\nfunction isCrossFieldRef(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n // If it parses as a finite number, it's a numeric literal, not a field ref\n if (value.trim() !== \"\" && isFinite(Number(value))) return false;\n return true;\n}\n\n/**\n * Resolve a cross-field reference value. If the value is a string that\n * already exists as a key in the market record, use it as-is. Otherwise,\n * if the bare field name (without prev_ prefix) is close-derived, try\n * the prev_ prefixed version.\n */\nfunction resolveFieldRef(\n refName: string,\n market: Record<string, unknown>\n): number {\n // Direct lookup first (handles cases like \"prev_VIX_Close\" spelled out)\n if (refName in market) {\n return getNum(market, refName);\n }\n // If the ref looks like a bare close-derived field, try prev_ prefix\n if (CLOSE_KNOWN_FIELDS.has(refName)) {\n return getNum(market, `prev_${refName}`);\n }\n return NaN;\n}\n\n/**\n * Build a runtime predicate from an EntryFilter.\n *\n * Automatically detects close-derived fields via CLOSE_KNOWN_FIELDS and\n * prepends \"prev_\" to the field key for correct lookahead-free evaluation.\n *\n * For comparison operators (>, <, >=, <=, ==), if the filter value is a\n * string that looks like a field name (not a pure numeric string), it is\n * treated as a cross-field reference. The referenced field's value is\n * looked up from the market record at evaluation time.\n *\n * NaN/null/undefined values in the market record always return false\n * (missing data never matches a filter).\n *\n * @param filter - Entry filter specification\n * @returns Compiled predicate with metadata\n */\nexport function buildFilterPredicate(filter: EntryFilter): FilterPredicate {\n const isLagged = CLOSE_KNOWN_FIELDS.has(filter.field);\n const fieldKey = isLagged ? `prev_${filter.field}` : filter.field;\n\n const { operator, value } = filter;\n\n const test = (market: Record<string, unknown>): boolean => {\n // For \"in\" and \"==\" operators, we may need raw value access\n if (operator === \"in\") {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) return false;\n if (!Array.isArray(value)) return false;\n // Try day-of-week name resolution for each element\n return value.some((v) => {\n const dayNum = resolveDayName(v);\n if (dayNum !== null) return Number(raw) === dayNum;\n return v == raw;\n });\n }\n\n if (operator === \"==\") {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) return false;\n // Day-of-week name resolution (e.g., \"Tuesday\" == 2)\n const dayNum = resolveDayName(value);\n if (dayNum !== null) return Number(raw) === dayNum;\n // Cross-field reference for ==\n if (isCrossFieldRef(value)) {\n const refVal = resolveFieldRef(value, market);\n if (isNaN(refVal)) return false;\n return Number(raw) === refVal;\n }\n return value == raw;\n }\n\n // Numeric operators: >, <, >=, <=, between\n const num = getNum(market, fieldKey);\n if (isNaN(num)) return false;\n\n // For comparison operators, check if value is a cross-field reference\n if (\n isCrossFieldRef(value) &&\n (operator === \">\" || operator === \"<\" || operator === \">=\" || operator === \"<=\")\n ) {\n const refVal = resolveFieldRef(value, market);\n if (isNaN(refVal)) return false;\n switch (operator) {\n case \">\":\n return num > refVal;\n case \"<\":\n return num < refVal;\n case \">=\":\n return num >= refVal;\n case \"<=\":\n return num <= refVal;\n }\n }\n\n switch (operator) {\n case \">\":\n return num > Number(value);\n case \"<\":\n return num < Number(value);\n case \">=\":\n return num >= Number(value);\n case \"<=\":\n return num <= Number(value);\n case \"between\": {\n if (!Array.isArray(value) || value.length < 2) return false;\n const lo = Number(value[0]);\n const hi = Number(value[1]);\n return num >= lo && num <= hi;\n }\n default:\n return false;\n }\n };\n\n return { test, fieldKey, isLagged };\n}\n","/**\n * Strategy Profile Tools\n *\n * MCP tools for CRUD operations on strategy profiles stored in DuckDB.\n * Wraps the Phase 60 storage layer (db/profile-schemas.ts) as conversational tools.\n *\n * Tools registered:\n * - profile_strategy — Create or update a strategy profile\n * - get_strategy_profile — Retrieve a single profile by block + strategy name\n * - list_profiles — List profiles (optionally filtered by block)\n * - delete_profile — Remove a profile (idempotent)\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection, upgradeToReadWrite, downgradeToReadOnly } from \"../db/connection.ts\";\nimport {\n upsertProfile,\n getProfile,\n listProfiles,\n deleteProfile,\n} from \"../db/profile-schemas.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport { withSyncedBlock } from \"./middleware/sync-middleware.ts\";\n\n// ---------------------------------------------------------------------------\n// Zod schemas (exported for testability)\n// ---------------------------------------------------------------------------\n\nexport const profileStrategySchema = z.object({\n blockId: z.string().describe(\"Block ID (block_id) to associate the profile with\"),\n strategyName: z.string().describe(\"Human-readable strategy name (e.g., 'Pickle RIC v2')\"),\n structureType: z\n .string()\n .describe(\n \"Option structure type: iron_condor, calendar_spread, double_calendar, vertical_spread, \" +\n \"butterfly, reverse_iron_condor, short_put_spread, short_call_spread, straddle, strangle, etc.\"\n ),\n greeksBias: z\n .string()\n .describe(\n \"Primary greeks exposure: theta_positive, vega_negative, delta_neutral, delta_positive, \" +\n \"delta_negative, gamma_scalp, etc.\"\n ),\n thesis: z.string().default(\"\").describe(\"Free-text description of the strategy thesis\"),\n legs: z\n .array(\n z.object({\n type: z.string().describe(\"Leg type: long_put, short_call, long_call, short_put, etc.\"),\n strike: z.string().describe(\"Strike selection: ATM, 5-delta, 30-delta, etc.\"),\n expiry: z.string().describe(\"Expiry selection: same-day, weekly, 45-DTE, etc.\"),\n quantity: z.number().describe(\"Quantity (positive=long, negative=short)\"),\n strikeMethod: z.enum(['delta', 'dollar_price', 'offset', 'percentage']).optional().describe(\"How strike is selected\"),\n strikeValue: z.number().optional().describe(\"Numeric strike value (e.g., 25 for 25-delta)\"),\n })\n )\n .default([])\n .describe(\"Structured leg descriptions\"),\n entryFilters: z\n .array(\n z.object({\n field: z.string().describe(\"Market data field: VIX_Close, RSI_14, Vol_Regime, etc.\"),\n operator: z.string().describe(\"Comparison operator: >, <, >=, <=, ==, between, in\"),\n value: z\n .union([z.string(), z.number(), z.array(z.union([z.string(), z.number()]))])\n .describe(\"Filter value or array for between/in operators\"),\n description: z.string().optional().describe(\"Human-readable description of this filter\"),\n source: z.enum([\"market\", \"execution\"]).optional().describe(\"Filter source: 'market' = testable against market data columns, 'execution' = platform-level (time windows, leg ratios). Defaults to 'market'. Execution filters are documented but skipped during validate_entry_filters analysis.\"),\n })\n )\n .default([])\n .describe(\"Entry condition filters. Tag each with source: 'market' (testable in analysis) or 'execution' (OO/platform-level, skipped in analysis).\"),\n exitRules: z\n .array(\n z.object({\n type: z.string().describe(\"Rule type: stop_loss, profit_target, time_exit, conditional\"),\n trigger: z.string().describe(\"Trigger condition: '200% of credit', '50% of max profit', '15:00 ET'\"),\n description: z.string().optional().describe(\"Human-readable description\"),\n stopLossType: z.enum(['percentage', 'dollar', 'sl_ratio', 'debit_percentage']).optional().describe(\"Stop loss calculation method\"),\n stopLossValue: z.number().optional().describe(\"Stop loss numeric value\"),\n monitoring: z.object({\n granularity: z.enum(['intra_minute', 'candle_close', 'end_of_bar']).optional().describe(\"Price check frequency\"),\n priceSource: z.enum(['nbbo', 'mid', 'last']).optional().describe(\"Which price to use\"),\n }).optional().describe(\"Monitoring configuration for this rule\"),\n slippage: z.number().optional().describe(\"Per-rule slippage override\"),\n })\n )\n .default([])\n .describe(\"Exit rules and triggers\"),\n expectedRegimes: z\n .array(z.enum([\"very_low\", \"low\", \"below_avg\", \"above_avg\", \"high\", \"extreme\"]))\n .default([])\n .describe(\"VIX-based vol regimes this strategy targets. very_low=VIX<13, low=13-16, below_avg=16-20, above_avg=20-25, high=25-30, extreme=30+\"),\n keyMetrics: z\n .object({\n expectedWinRate: z.number().optional().describe(\"Expected win rate (0-1)\"),\n targetPremium: z.number().optional().describe(\"Target premium collected ($)\"),\n maxLoss: z.number().optional().describe(\"Maximum loss per contract ($)\"),\n profitTarget: z.number().optional().describe(\"Profit target ($ or %)\"),\n })\n .passthrough()\n .default({})\n .describe(\"Performance benchmarks and strategy-specific metrics\"),\n positionSizing: z\n .object({\n method: z.string().describe(\"Sizing method: pct_of_portfolio, fixed_contracts, fixed_dollar, discretionary\"),\n allocationPct: z.number().optional().describe(\"Portfolio allocation percentage (e.g., 2 for 2%)\"),\n maxContracts: z.number().optional().describe(\"Maximum contracts per trade\"),\n maxAllocationDollar: z.number().optional().describe(\"Maximum dollar allocation per trade\"),\n maxOpenPositions: z.number().optional().describe(\"Maximum concurrent open positions\"),\n description: z.string().optional().describe(\"Free-text sizing notes\"),\n backtestAllocationPct: z.number().optional().describe(\"Allocation % used in backtest\"),\n liveAllocationPct: z.number().optional().describe(\"Allocation % used in live portfolio\"),\n maxContractsPerTrade: z.number().optional().describe(\"Per-entry contract cap (distinct from maxContracts hard cap)\"),\n })\n .optional()\n .describe(\"Position sizing rules. Per-block — same strategy in backtest vs portfolio may have different sizing.\"),\n underlying: z.string().optional().describe(\"Underlying symbol: SPX, QQQ, etc.\"),\n reEntry: z.boolean().optional().describe(\"Strategy supports re-entry on same day\"),\n capProfits: z.boolean().optional().describe(\"Profits are capped by structure\"),\n capLosses: z.boolean().optional().describe(\"Losses are capped by structure\"),\n requireTwoPricesPT: z.boolean().optional().describe(\"Profit target requires two prices\"),\n closeOnCompletion: z.boolean().optional().describe(\"Close entire position when any leg hits target\"),\n ignoreMarginReq: z.boolean().optional().describe(\"Strategy ignores standard margin requirements\"),\n});\n\nexport const getStrategyProfileSchema = z.object({\n blockId: z.string().describe(\"Block ID to look up\"),\n strategyName: z.string().describe(\"Strategy name to look up\"),\n});\n\nexport const listProfilesSchema = z.object({\n blockId: z.string().optional().describe(\"Optional block ID filter. Omit to list all profiles across all blocks.\"),\n});\n\nexport const deleteProfileSchema = z.object({\n blockId: z.string().describe(\"Block ID of the profile to delete\"),\n strategyName: z.string().describe(\"Strategy name of the profile to delete\"),\n});\n\n// ---------------------------------------------------------------------------\n// Handler functions (exported for testability)\n// ---------------------------------------------------------------------------\n\n/**\n * Handle profile_strategy: create or update a strategy profile.\n */\nexport async function handleProfileStrategy(\n input: z.infer<typeof profileStrategySchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n await upgradeToReadWrite(baseDir);\n try {\n const conn = await getConnection(baseDir);\n const stored = await upsertProfile(conn, {\n blockId: input.blockId,\n strategyName: input.strategyName,\n structureType: input.structureType,\n greeksBias: input.greeksBias,\n thesis: input.thesis,\n legs: input.legs,\n entryFilters: input.entryFilters,\n exitRules: input.exitRules,\n expectedRegimes: input.expectedRegimes,\n keyMetrics: input.keyMetrics,\n positionSizing: input.positionSizing,\n underlying: input.underlying,\n reEntry: input.reEntry,\n capProfits: input.capProfits,\n capLosses: input.capLosses,\n requireTwoPricesPT: input.requireTwoPricesPT,\n closeOnCompletion: input.closeOnCompletion,\n ignoreMarginReq: input.ignoreMarginReq,\n }, baseDir);\n return createToolOutput(\n `Profile saved: ${input.strategyName} for block ${input.blockId}`,\n { profile: stored }\n );\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n}\n\n/**\n * Handle get_strategy_profile: retrieve a single profile.\n */\nexport async function handleGetStrategyProfile(\n input: z.infer<typeof getStrategyProfileSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, input.blockId, input.strategyName, baseDir);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${input.strategyName}' in block '${input.blockId}'`,\n { profile: null }\n );\n }\n return createToolOutput(\n `Profile: ${input.strategyName} in block ${input.blockId}`,\n { profile }\n );\n}\n\n/**\n * Handle list_profiles: list profiles with optional block filter.\n */\nexport async function handleListProfiles(\n input: z.infer<typeof listProfilesSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const conn = await getConnection(baseDir);\n const profiles = await listProfiles(conn, input.blockId, baseDir);\n const summaryRows = profiles.map((p) => ({\n blockId: p.blockId,\n strategyName: p.strategyName,\n structureType: p.structureType,\n greeksBias: p.greeksBias,\n underlying: p.underlying ?? null,\n positionSizing: p.positionSizing?.method ?? null,\n updatedAt: p.updatedAt,\n }));\n return createToolOutput(\n `Found ${profiles.length} profile(s)${input.blockId ? ` for block ${input.blockId}` : \"\"}`,\n { count: profiles.length, profiles: summaryRows }\n );\n}\n\n/**\n * Handle delete_profile: remove a profile (idempotent).\n */\nexport async function handleDeleteProfile(\n input: z.infer<typeof deleteProfileSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n await upgradeToReadWrite(baseDir);\n try {\n const conn = await getConnection(baseDir);\n const deleted = await deleteProfile(conn, input.blockId, input.strategyName, baseDir);\n if (deleted) {\n return createToolOutput(\n `Deleted profile: ${input.strategyName} from block ${input.blockId}`,\n { deleted: true }\n );\n }\n return createToolOutput(\n `No profile found for strategy '${input.strategyName}' in block '${input.blockId}' — nothing to delete`,\n { deleted: false }\n );\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\n/**\n * Register all profile CRUD tools on the MCP server.\n *\n * @param server - McpServer instance to register tools on\n * @param baseDir - Base data directory (passed to connection helpers)\n */\nexport function registerProfileTools(server: McpServer, baseDir: string): void {\n // -------------------------------------------------------------------------\n // Tool: profile_strategy\n // -------------------------------------------------------------------------\n server.registerTool(\n \"profile_strategy\",\n {\n description:\n \"Create or update a strategy profile for a block. Stores structure type, greeks bias, \" +\n \"legs, entry filters, exit rules, expected regimes, key metrics, and position sizing. \" +\n \"If a profile with the same block_id + strategy_name already exists, it is overwritten (upsert). \" +\n \"When profiling the same strategy across multiple blocks (e.g., backtest vs live portfolio), \" +\n \"retrieve the existing profile with get_strategy_profile and copy its fields, updating only \" +\n \"positionSizing or other block-specific params rather than re-asking the user for all details.\",\n inputSchema: profileStrategySchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleProfileStrategy(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: get_strategy_profile\n // -------------------------------------------------------------------------\n server.registerTool(\n \"get_strategy_profile\",\n {\n description:\n \"Retrieve a single strategy profile by block_id and strategy_name. \" +\n \"Returns the full profile including all schema fields, or a not-found message.\",\n inputSchema: getStrategyProfileSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleGetStrategyProfile(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: list_profiles\n // -------------------------------------------------------------------------\n server.registerTool(\n \"list_profiles\",\n {\n description:\n \"List strategy profiles. Provide block_id to filter by block, or omit to list all profiles \" +\n \"across all blocks. Returns summary rows with block_id, strategy_name, structure_type, \" +\n \"greeks_bias, and updated_at.\",\n inputSchema: listProfilesSchema,\n },\n async (input) => {\n // list_profiles has optional blockId — when provided, sync the block first;\n // when omitted, query directly without sync (no block to validate).\n if (input.blockId) {\n const syncedHandler = withSyncedBlock(baseDir, async (syncInput: { blockId: string }, ctx) => {\n return handleListProfiles({ blockId: syncInput.blockId }, ctx.baseDir);\n });\n return syncedHandler({ blockId: input.blockId });\n }\n return handleListProfiles(input, baseDir);\n }\n );\n\n // -------------------------------------------------------------------------\n // Tool: delete_profile\n // -------------------------------------------------------------------------\n server.registerTool(\n \"delete_profile\",\n {\n description:\n \"Delete a strategy profile by block_id and strategy_name. \" +\n \"Idempotent: deleting a nonexistent profile returns success with a not-found message.\",\n inputSchema: deleteProfileSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleDeleteProfile(input, ctx.baseDir);\n })\n );\n}\n","/**\n * Output Formatter\n *\n * Utilities for formatting MCP tool output.\n *\n * JSON-First Pattern:\n * Tools return structured JSON as the primary format. JSON is machine-readable,\n * enabling reliable data extraction without parsing natural language.\n *\n * A brief text summary is included for user visibility, but the JSON\n * is the authoritative source for all data and reasoning.\n */\n\n/**\n * MCP content item types\n */\nexport interface McpTextContent {\n type: \"text\";\n text: string;\n}\n\nexport interface McpResourceContent {\n type: \"resource\";\n resource: {\n uri: string;\n mimeType: string;\n text: string;\n };\n}\n\nexport type McpContent = McpTextContent | McpResourceContent;\n\nexport interface ToolOutput {\n [x: string]: unknown;\n content: McpContent[];\n}\n\n// Legacy alias for backward compatibility\nexport type DualOutput = ToolOutput;\n\n/**\n * Create JSON-first output for MCP tools.\n *\n * The structured JSON is the primary data source for Claude reasoning.\n * A brief text summary is provided for user visibility.\n *\n * @param summary - Brief text summary (1-3 lines) for user display\n * @param data - Structured data object - the authoritative data source\n * @returns MCP-compatible response with JSON as primary content\n */\nexport function createToolOutput(summary: string, data: object): ToolOutput {\n return {\n content: [\n { type: \"text\", text: summary },\n {\n type: \"resource\",\n resource: {\n uri: \"data:application/json\",\n mimeType: \"application/json\",\n text: JSON.stringify(data),\n },\n },\n ],\n };\n}\n\n/**\n * Legacy function - redirects to createToolOutput.\n * @deprecated Use createToolOutput instead\n */\nexport function createDualOutput(markdown: string, data: object): DualOutput {\n // Extract a brief summary from the markdown (first non-empty line or heading)\n const lines = markdown.split(\"\\n\").filter((l) => l.trim());\n const summary = lines[0]?.replace(/^#+\\s*/, \"\") || \"Results available\";\n return createToolOutput(summary, data);\n}\n\n/**\n * Format a number as currency ($1,234.56)\n */\nexport function formatCurrency(value: number): string {\n const isNegative = value < 0;\n const absValue = Math.abs(value);\n const formatted = absValue.toLocaleString(\"en-US\", {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n return isNegative ? `-$${formatted}` : `$${formatted}`;\n}\n\n/**\n * Format a number as percentage (12.34%)\n */\nexport function formatPercent(value: number, decimals: number = 2): string {\n return `${value.toFixed(decimals)}%`;\n}\n\n/**\n * Format a ratio with specified decimals\n */\nexport function formatRatio(\n value: number | undefined,\n decimals: number = 2\n): string {\n if (value === undefined || value === null || !isFinite(value)) {\n return \"N/A\";\n }\n return value.toFixed(decimals);\n}\n","/**\n * Profile Analysis Tools\n *\n * MCP tools that use stored strategy profiles for targeted analysis:\n * - analyze_structure_fit: Dimension-based performance breakdown using profile context\n * - validate_entry_filters: Entry filter effectiveness analysis with ablation study\n * - portfolio_structure_map: Vol_Regime x Trend_Direction matrix across strategies\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { loadBlock } from \"../utils/block-loader.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport type { Trade } from \"@tradeblocks/lib\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { getProfile, listProfiles } from \"../db/profile-schemas.ts\";\nimport { filterByStrategy } from \"./shared/filters.ts\";\nimport {\n buildLookaheadFreeQuery,\n type MarketLookupKey,\n} from \"../utils/field-timing.ts\";\nimport {\n DEFAULT_MARKET_TICKER,\n marketTickerDateKey,\n resolveTradeTicker,\n} from \"../utils/ticker.ts\";\nimport { computeSliceStats, type SliceStats } from \"../utils/analysis-stats.ts\";\nimport { buildFilterPredicate, type FilterPredicate } from \"../utils/filter-predicates.ts\";\nimport { withSyncedBlock } from \"./middleware/sync-middleware.ts\";\nimport {\n upgradeToReadWrite,\n downgradeToReadOnly,\n getConnectionMode,\n} from \"../db/connection.ts\";\nimport { syncAllBlocks } from \"../sync/index.ts\";\n\n// =============================================================================\n// Utility Functions (local to this module)\n// =============================================================================\n\n/**\n * Format trade date to YYYY-MM-DD for market data matching.\n */\nfunction formatTradeDate(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nfunction getTradeLookupKey(trade: Trade): MarketLookupKey {\n return {\n date: formatTradeDate(trade.dateOpened),\n ticker: resolveTradeTicker(trade, DEFAULT_MARKET_TICKER),\n };\n}\n\nfunction uniqueTradeLookupKeys(trades: Trade[]): MarketLookupKey[] {\n const byKey = new Map<string, MarketLookupKey>();\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n byKey.set(marketTickerDateKey(lookup.ticker, lookup.date), lookup);\n }\n return Array.from(byKey.values());\n}\n\nfunction resultToRecords(result: {\n columnCount: number;\n columnName(i: number): string;\n getRows(): Iterable<unknown[]>;\n}): Record<string, unknown>[] {\n const columnCount = result.columnCount;\n const colNames: string[] = [];\n for (let i = 0; i < columnCount; i++) {\n colNames.push(result.columnName(i));\n }\n const records: Record<string, unknown>[] = [];\n for (const row of result.getRows()) {\n const record: Record<string, unknown> = {};\n for (let i = 0; i < columnCount; i++) {\n const val = row[i];\n record[colNames[i]] = typeof val === \"bigint\" ? Number(val) : val;\n }\n records.push(record);\n }\n return records;\n}\n\nfunction recordsByTickerDate(\n records: Record<string, unknown>[]\n): Map<string, Record<string, unknown>> {\n const mapped = new Map<string, Record<string, unknown>>();\n for (const record of records) {\n const date = String(record[\"date\"] || \"\");\n const ticker = String(record[\"ticker\"] || DEFAULT_MARKET_TICKER);\n mapped.set(marketTickerDateKey(ticker, date), record);\n }\n return mapped;\n}\n\nfunction getNum(record: Record<string, unknown>, field: string): number {\n const val = record[field];\n if (val === null || val === undefined) return NaN;\n if (typeof val === \"bigint\") return Number(val);\n return val as number;\n}\n\n// =============================================================================\n// Vol Regime Labels\n// =============================================================================\n\nconst VOL_REGIME_LABELS: Record<number, string> = {\n 1: \"very_low\",\n 2: \"low\",\n 3: \"below_avg\",\n 4: \"above_avg\",\n 5: \"high\",\n 6: \"extreme\",\n};\n\nconst TREND_LABELS = [\"up\", \"down\", \"flat\"] as const;\ntype TrendLabel = (typeof TREND_LABELS)[number];\n\n/**\n * Day of week labels (market data: 1=Mon to 5=Fri)\n */\nconst DAY_LABELS: Record<number, string> = {\n 1: \"Monday\",\n 2: \"Tuesday\",\n 3: \"Wednesday\",\n 4: \"Thursday\",\n 5: \"Friday\",\n};\n\n/**\n * Determine time-of-day bucket from timeOpened string (format \"HH:MM:SS\" or \"HH:MM\").\n */\nfunction getTimeBucket(timeOpened: string | undefined): string | null {\n if (!timeOpened) return null;\n const match = timeOpened.match(/^(\\d{1,2}):(\\d{2})/);\n if (!match) return null;\n const hours = parseInt(match[1], 10);\n const minutes = parseInt(match[2], 10);\n const totalMinutes = hours * 60 + minutes;\n\n // morning: 09:30-11:00, midday: 11:00-14:00, afternoon: 14:00-16:00\n if (totalMinutes < 570) return null; // before 09:30\n if (totalMinutes < 660) return \"morning\"; // 09:30-11:00\n if (totalMinutes < 840) return \"midday\"; // 11:00-14:00\n if (totalMinutes <= 960) return \"afternoon\"; // 14:00-16:00\n return null; // after 16:00\n}\n\n/**\n * Safely get a raw value from a record.\n */\nfunction getRaw(record: Record<string, unknown>, field: string): unknown {\n return record[field];\n}\n\ninterface TradeWithMarket {\n trade: Trade;\n market: Record<string, unknown>;\n}\n\n/**\n * Load trades and market data for a strategy profile analysis.\n * Shared between analyze_structure_fit and validate_entry_filters.\n */\nasync function loadTradesAndMarket(\n baseDir: string,\n blockId: string,\n strategyName: string\n): Promise<{\n matched: TradeWithMarket[];\n unmatchedCount: number;\n allTrades: Trade[];\n}> {\n const block = await loadBlock(baseDir, blockId);\n let trades = filterByStrategy(block.trades, strategyName);\n\n // Single-strategy backtest blocks may have a different strategy name in the CSV\n // (e.g., blockId fallback \"2_3 dc\" vs profile name \"2/3 DC - v2\").\n // If no trades match by name and the block has only one unique strategy, use all trades.\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n\n if (trades.length === 0) {\n return { matched: [], unmatchedCount: 0, allTrades: [] };\n }\n\n // Collect unique trade keys for market query\n const tradeKeys = uniqueTradeLookupKeys(trades);\n\n // Query market data\n const conn = await getConnection(baseDir);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const result = await conn.runAndReadAll(sql, params);\n const marketRecords = resultToRecords(result);\n const marketMap = recordsByTickerDate(marketRecords);\n\n // Match trades to market records\n const matched: TradeWithMarket[] = [];\n let unmatchedCount = 0;\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const key = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = marketMap.get(key);\n if (market) {\n matched.push({ trade, market });\n } else {\n unmatchedCount++;\n }\n }\n\n return { matched, unmatchedCount, allTrades: trades };\n}\n\n/**\n * Create numeric bucket labels from data values.\n * Divides sorted values into ~4 quartile-based ranges.\n */\nfunction createNumericBuckets(values: number[]): { label: string; min: number; max: number }[] {\n if (values.length === 0) return [];\n const sorted = [...values].sort((a, b) => a - b);\n\n const uniqueValues = [...new Set(sorted)];\n if (uniqueValues.length <= 4) {\n return uniqueValues.map((v) => ({\n label: String(Math.round(v * 100) / 100),\n min: v,\n max: v,\n }));\n }\n\n const buckets: { label: string; min: number; max: number }[] = [];\n const quartileSize = Math.ceil(sorted.length / 4);\n for (let i = 0; i < 4; i++) {\n const start = i * quartileSize;\n const end = Math.min((i + 1) * quartileSize - 1, sorted.length - 1);\n if (start > sorted.length - 1) break;\n const min = sorted[start];\n const max = sorted[end];\n const r = (n: number) => Math.round(n * 100) / 100;\n buckets.push({\n label: min === max ? `${r(min)}` : `${r(min)} to ${r(max)}`,\n min,\n max,\n });\n }\n\n return buckets;\n}\n\n/**\n * Find which bucket a value belongs to.\n */\nfunction findBucket(\n value: number,\n buckets: { label: string; min: number; max: number }[]\n): string | null {\n for (const bucket of buckets) {\n if (value >= bucket.min && value <= bucket.max) return bucket.label;\n }\n return null;\n}\n\n// =============================================================================\n// analyze_structure_fit Schema and Handler\n// =============================================================================\n\nexport const analyzeStructureFitSchema = z.object({\n blockId: z.string().describe(\"Block ID to analyze\"),\n strategyName: z.string().describe(\"Strategy name matching a stored profile\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Minimum trades per bucket for reliable stats (thin-data warning threshold)\"),\n});\n\nexport async function handleAnalyzeStructureFit(\n input: z.infer<typeof analyzeStructureFitSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const { blockId, strategyName } = input;\n const minTrades = input.minTrades ?? 10;\n\n // Load profile\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, blockId, strategyName, baseDir);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${strategyName}' in block '${blockId}'. Create one with profile_strategy first.`,\n { error: \"profile_not_found\" }\n );\n }\n\n // Load trades + market data\n const { matched, unmatchedCount, allTrades } = await loadTradesAndMarket(\n baseDir,\n blockId,\n strategyName\n );\n\n const warnings: string[] = [];\n\n if (allTrades.length === 0) {\n return createToolOutput(\n `No trades found for strategy '${strategyName}' in block '${blockId}'.`,\n { error: \"no_trades\" }\n );\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded from market-based analysis.`\n );\n }\n\n if (matched.length === 0) {\n return createToolOutput(\n `No trades could be matched to market data for strategy '${strategyName}'.`,\n { error: \"no_market_match\", warnings }\n );\n }\n\n // Overall stats\n const allPls = matched.map((m) => m.trade.pl);\n const overall = computeSliceStats(allPls);\n\n // Dimension analysis\n const dimensions: Record<string, Record<string, SliceStats>> = {};\n\n // --- Fixed dimension: Vol_Regime ---\n const volRegimeBuckets: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"prev_Vol_Regime\");\n if (isNaN(val)) continue;\n const label = VOL_REGIME_LABELS[val] || `regime_${val}`;\n if (!volRegimeBuckets[label]) volRegimeBuckets[label] = [];\n volRegimeBuckets[label].push(trade.pl);\n }\n const volRegimeStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(volRegimeBuckets)) {\n volRegimeStats[label] = computeSliceStats(pls);\n }\n dimensions[\"Vol_Regime\"] = volRegimeStats;\n\n // --- Fixed dimension: day_of_week ---\n const dowBuckets: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"Day_of_Week\");\n if (isNaN(val)) continue;\n const label = DAY_LABELS[val] || `day_${val}`;\n if (!dowBuckets[label]) dowBuckets[label] = [];\n dowBuckets[label].push(trade.pl);\n }\n const dowStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(dowBuckets)) {\n dowStats[label] = computeSliceStats(pls);\n }\n dimensions[\"day_of_week\"] = dowStats;\n\n // --- Fixed dimension: time_of_day ---\n const todBuckets: Record<string, number[]> = {};\n for (const { trade } of matched) {\n const bucket = getTimeBucket(trade.timeOpened);\n if (!bucket) continue;\n if (!todBuckets[bucket]) todBuckets[bucket] = [];\n todBuckets[bucket].push(trade.pl);\n }\n const todStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(todBuckets)) {\n todStats[label] = computeSliceStats(pls);\n }\n dimensions[\"time_of_day\"] = todStats;\n\n // --- Profile-derived dimensions from entry_filters (market-source only) ---\n for (const filter of profile.entryFilters.filter((f) => f.source !== \"execution\")) {\n const predicate = buildFilterPredicate(filter);\n const fieldKey = predicate.fieldKey;\n\n // Collect numeric values for this field from matched trades\n const fieldValues: { val: number; pl: number }[] = [];\n for (const { trade, market } of matched) {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) continue;\n const num = Number(raw);\n if (isNaN(num)) continue;\n fieldValues.push({ val: num, pl: trade.pl });\n }\n\n if (fieldValues.length === 0) continue;\n\n // Create buckets from the data\n const buckets = createNumericBuckets(fieldValues.map((f) => f.val));\n if (buckets.length === 0) continue;\n\n const filterBuckets: Record<string, number[]> = {};\n for (const { val, pl } of fieldValues) {\n const bucketLabel = findBucket(val, buckets);\n if (!bucketLabel) continue;\n if (!filterBuckets[bucketLabel]) filterBuckets[bucketLabel] = [];\n filterBuckets[bucketLabel].push(pl);\n }\n\n const filterStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(filterBuckets)) {\n filterStats[label] = computeSliceStats(pls);\n }\n dimensions[filter.field] = filterStats;\n }\n\n // Thin-data warnings\n for (const [dimName, bucketStats] of Object.entries(dimensions)) {\n for (const [bucketLabel, stats] of Object.entries(bucketStats)) {\n if (stats.tradeCount > 0 && stats.tradeCount < minTrades) {\n warnings.push(\n `${dimName}/${bucketLabel}: only ${stats.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n }\n }\n\n // Profile update hints\n const profileUpdateHints: { field: string; suggested: string; reason: string }[] = [];\n\n // Check Vol_Regime performance vs overall\n for (const [label, stats] of Object.entries(volRegimeStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (winRateDiff >= 20) {\n profileUpdateHints.push({\n field: \"expectedRegimes\",\n suggested: label,\n reason: `Win rate ${stats.winRate.toFixed(1)}% in ${label} is ${winRateDiff.toFixed(1)}pp above overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n if (winRateDiff <= -20) {\n profileUpdateHints.push({\n field: \"expectedRegimes\",\n suggested: `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% in ${label} is ${Math.abs(winRateDiff).toFixed(1)}pp below overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Check day_of_week for stark differences\n for (const [label, stats] of Object.entries(dowStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (Math.abs(winRateDiff) >= 20) {\n profileUpdateHints.push({\n field: \"day_of_week\",\n suggested: winRateDiff > 0 ? `favor_${label}` : `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% on ${label} vs overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Check time_of_day for stark differences\n for (const [label, stats] of Object.entries(todStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (Math.abs(winRateDiff) >= 20) {\n profileUpdateHints.push({\n field: \"time_of_day\",\n suggested: winRateDiff > 0 ? `favor_${label}` : `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% during ${label} vs overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Summary text\n const dimNames = Object.keys(dimensions).join(\", \");\n const summaryText = `Structure fit analysis for '${strategyName}': ${matched.length} trades analyzed across ${Object.keys(dimensions).length} dimensions (${dimNames}). Overall win rate: ${overall.winRate.toFixed(1)}%, avg P&L: $${overall.avgPl.toFixed(2)}. ${profileUpdateHints.length} update hint(s).`;\n\n return createToolOutput(summaryText, {\n overall,\n dimensions,\n profile_update_hints: profileUpdateHints,\n warnings,\n profile: {\n strategyName: profile.strategyName,\n structureType: profile.structureType,\n greeksBias: profile.greeksBias,\n thesis: profile.thesis,\n expectedRegimes: profile.expectedRegimes,\n },\n });\n}\n\n// =============================================================================\n// validate_entry_filters Schema and Handler\n// =============================================================================\n\nexport const validateEntryFiltersSchema = z.object({\n blockId: z.string().describe(\"Block ID to analyze\"),\n strategyName: z.string().describe(\"Strategy name matching a stored profile\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Minimum trades per group for reliable stats\"),\n maxAblationFilters: z\n .number()\n .optional()\n .default(8)\n .describe(\"Maximum number of filters for pairwise ablation (cap for combinatorial explosion)\"),\n});\n\nexport async function handleValidateEntryFilters(\n input: z.infer<typeof validateEntryFiltersSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const { blockId, strategyName } = input;\n const minTrades = input.minTrades ?? 10;\n const maxAblationFilters = input.maxAblationFilters ?? 8;\n\n // Load profile\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, blockId, strategyName, baseDir);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${strategyName}' in block '${blockId}'. Create one with profile_strategy first.`,\n { error: \"profile_not_found\" }\n );\n }\n\n // Early return if no entry filters\n if (!profile.entryFilters || profile.entryFilters.length === 0) {\n return createToolOutput(\n `Profile '${strategyName}' has no entry_filters defined. Add filters via profile_strategy to enable validation.`,\n { no_filters: true }\n );\n }\n\n // Separate market-testable filters from execution-only filters\n const allFilters = profile.entryFilters;\n const marketFilters = allFilters.filter((f) => f.source !== \"execution\");\n const executionFilters = allFilters.filter((f) => f.source === \"execution\");\n\n if (marketFilters.length === 0) {\n return createToolOutput(\n `Profile '${strategyName}' has ${allFilters.length} filter(s) but all are tagged source:'execution' (platform-level). No market-data filters to validate.`,\n { no_market_filters: true, execution_filters: executionFilters }\n );\n }\n\n // Load trades + market data\n const { matched, unmatchedCount, allTrades } = await loadTradesAndMarket(\n baseDir,\n blockId,\n strategyName\n );\n\n const warnings: string[] = [];\n\n if (executionFilters.length > 0) {\n warnings.push(\n `${executionFilters.length} execution-level filter(s) skipped (not testable against market data): ${executionFilters.map((f) => f.description || f.field).join(\", \")}`\n );\n }\n\n if (allTrades.length === 0) {\n return createToolOutput(\n `No trades found for strategy '${strategyName}' in block '${blockId}'.`,\n { error: \"no_trades\" }\n );\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded.`\n );\n }\n\n if (matched.length === 0) {\n return createToolOutput(\n `No trades could be matched to market data for strategy '${strategyName}'.`,\n { error: \"no_market_match\", warnings }\n );\n }\n\n // Build predicates for market-testable filters only\n const filters = marketFilters;\n const predicates: FilterPredicate[] = filters.map((f) => buildFilterPredicate(f));\n\n // No-filters baseline: all matched trades\n const noFiltersPls = matched.map((m) => m.trade.pl);\n const noFiltersStats = computeSliceStats(noFiltersPls);\n\n // Per-filter comparison\n const perFilter: Record<\n string,\n { entered: SliceStats; filtered_out: SliceStats; no_data_count: number }\n > = {};\n\n for (let i = 0; i < filters.length; i++) {\n const filter = filters[i];\n const predicate = predicates[i];\n const filterDesc =\n filter.description || `${filter.field} ${filter.operator} ${JSON.stringify(filter.value)}`;\n\n const enteredPls: number[] = [];\n const filteredOutPls: number[] = [];\n let noDataCount = 0;\n\n for (const { trade, market } of matched) {\n const raw = getRaw(market, predicate.fieldKey);\n if (raw === null || raw === undefined) {\n noDataCount++;\n continue;\n }\n if (predicate.test(market)) {\n enteredPls.push(trade.pl);\n } else {\n filteredOutPls.push(trade.pl);\n }\n }\n\n perFilter[filterDesc] = {\n entered: computeSliceStats(enteredPls),\n filtered_out: computeSliceStats(filteredOutPls),\n no_data_count: noDataCount,\n };\n }\n\n // Ablation study\n // Baseline: all filters applied\n const baselinePls: number[] = [];\n for (const { trade, market } of matched) {\n let passesAll = true;\n let hasData = true;\n for (const predicate of predicates) {\n const raw = getRaw(market, predicate.fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicate.test(market)) {\n passesAll = false;\n break;\n }\n }\n if (hasData && passesAll) {\n baselinePls.push(trade.pl);\n }\n }\n const baseline = computeSliceStats(baselinePls);\n\n // Single removal ablation\n const ablationSingle: Record<string, SliceStats> = {};\n for (let skip = 0; skip < filters.length; skip++) {\n const filterDesc =\n filters[skip].description ||\n `${filters[skip].field} ${filters[skip].operator} ${JSON.stringify(filters[skip].value)}`;\n\n const pls: number[] = [];\n for (const { trade, market } of matched) {\n let passesRemaining = true;\n let hasData = true;\n for (let j = 0; j < predicates.length; j++) {\n if (j === skip) continue;\n const raw = getRaw(market, predicates[j].fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicates[j].test(market)) {\n passesRemaining = false;\n break;\n }\n }\n if (hasData && passesRemaining) {\n pls.push(trade.pl);\n }\n }\n ablationSingle[filterDesc] = computeSliceStats(pls);\n }\n\n // Pairwise removal ablation (only if filter count <= maxAblationFilters)\n const ablationPairs: Record<string, SliceStats> = {};\n if (filters.length <= maxAblationFilters) {\n for (let i = 0; i < filters.length; i++) {\n for (let j = i + 1; j < filters.length; j++) {\n const descI =\n filters[i].description ||\n `${filters[i].field} ${filters[i].operator} ${JSON.stringify(filters[i].value)}`;\n const descJ =\n filters[j].description ||\n `${filters[j].field} ${filters[j].operator} ${JSON.stringify(filters[j].value)}`;\n const pairKey = `${descI} + ${descJ}`;\n\n const pls: number[] = [];\n for (const { trade, market } of matched) {\n let passesRemaining = true;\n let hasData = true;\n for (let k = 0; k < predicates.length; k++) {\n if (k === i || k === j) continue;\n const raw = getRaw(market, predicates[k].fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicates[k].test(market)) {\n passesRemaining = false;\n break;\n }\n }\n if (hasData && passesRemaining) {\n pls.push(trade.pl);\n }\n }\n ablationPairs[pairKey] = computeSliceStats(pls);\n }\n }\n }\n\n // Profile update hints\n const profileUpdateHints: {\n field: string;\n action: \"remove\" | \"adjust\";\n reason: string;\n }[] = [];\n\n // Check per-filter: if entered performs worse than filtered_out, suggest removal\n for (const [filterDesc, { entered, filtered_out }] of Object.entries(perFilter)) {\n if (\n entered.tradeCount >= minTrades &&\n filtered_out.tradeCount >= minTrades\n ) {\n if (entered.avgPl < filtered_out.avgPl && filtered_out.avgPl > 0) {\n profileUpdateHints.push({\n field: filterDesc,\n action: \"remove\",\n reason: `Entered avg P&L ($${entered.avgPl.toFixed(2)}) worse than filtered-out ($${filtered_out.avgPl.toFixed(2)}) — filter may be counterproductive`,\n });\n }\n }\n }\n\n // Check ablation: if removing a filter improves over baseline\n for (const [filterDesc, stats] of Object.entries(ablationSingle)) {\n if (stats.tradeCount >= minTrades && baseline.tradeCount >= minTrades) {\n if (stats.avgPl > baseline.avgPl && stats.winRate > baseline.winRate) {\n profileUpdateHints.push({\n field: filterDesc,\n action: \"remove\",\n reason: `Removing this filter improves avg P&L ($${stats.avgPl.toFixed(2)} vs $${baseline.avgPl.toFixed(2)}) and win rate (${stats.winRate.toFixed(1)}% vs ${baseline.winRate.toFixed(1)}%)`,\n });\n }\n }\n }\n\n // Thin-data warnings\n if (baseline.tradeCount > 0 && baseline.tradeCount < minTrades) {\n warnings.push(\n `Baseline (all filters): only ${baseline.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n for (const [filterDesc, { entered, filtered_out }] of Object.entries(perFilter)) {\n if (entered.tradeCount > 0 && entered.tradeCount < minTrades) {\n warnings.push(\n `${filterDesc} entered: only ${entered.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n if (filtered_out.tradeCount > 0 && filtered_out.tradeCount < minTrades) {\n warnings.push(\n `${filterDesc} filtered_out: only ${filtered_out.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n }\n\n // Summary text\n const execNote = executionFilters.length > 0 ? ` (${executionFilters.length} execution filter(s) skipped)` : \"\";\n const summaryText = `Filter validation for '${strategyName}': ${filters.length} market filter(s) analyzed across ${matched.length} trades${execNote}. Baseline (all market filters): ${baseline.tradeCount} trades, win rate ${baseline.winRate.toFixed(1)}%, avg P&L $${baseline.avgPl.toFixed(2)}. ${profileUpdateHints.length} update hint(s).`;\n\n return createToolOutput(summaryText, {\n baseline,\n no_filters: noFiltersStats,\n per_filter: perFilter,\n ablation: {\n single: ablationSingle,\n pairs: ablationPairs,\n },\n execution_filters_skipped: executionFilters.map((f) => f.description || `${f.field} ${f.operator} ${f.value}`),\n profile_update_hints: profileUpdateHints,\n warnings,\n });\n}\n\n// =============================================================================\n// portfolio_structure_map Schema and Handler\n// =============================================================================\n\nexport const portfolioStructureMapSchema = z.object({\n blockId: z\n .string()\n .optional()\n .describe(\"Block ID to analyze. When omitted, aggregate across all blocks.\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Thin-data warning threshold (default: 10)\"),\n});\n\nexport async function handlePortfolioStructureMap(\n input: z.infer<typeof portfolioStructureMapSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput> | { content: Array<{ type: \"text\"; text: string }>; isError?: boolean }> {\n try {\n const { blockId, minTrades } = portfolioStructureMapSchema.parse(input);\n const conn = await getConnection(baseDir);\n\n // Load profiles\n const profiles = await listProfiles(conn, blockId, baseDir);\n if (profiles.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: blockId\n ? `No strategy profiles found for block '${blockId}'. Use profile_strategy to create profiles first.`\n : \"No strategy profiles found. Use profile_strategy to create profiles first.\",\n },\n ],\n };\n }\n\n // Collect all trades per strategy, matched to market data\n interface StrategyTradeMarket {\n strategyName: string;\n trade: Trade;\n market: Record<string, unknown>;\n }\n\n const allTradeMarkets: StrategyTradeMarket[] = [];\n const warnings: string[] = [];\n\n for (const profile of profiles) {\n let block;\n try {\n block = await loadBlock(baseDir, profile.blockId);\n } catch {\n warnings.push(`Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'`);\n continue;\n }\n\n let trades = filterByStrategy(block.trades, profile.strategyName);\n // Single-strategy block fallback (see loadTradesAndMarket)\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n if (trades.length === 0) {\n warnings.push(`No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'`);\n continue;\n }\n\n // Query market data for trade dates\n const tradeKeys = uniqueTradeLookupKeys(trades);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const dailyResult = await conn.runAndReadAll(sql, params);\n const dailyRecords = resultToRecords(dailyResult);\n const daily = recordsByTickerDate(dailyRecords);\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const marketKey = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = daily.get(marketKey);\n if (market) {\n allTradeMarkets.push({\n strategyName: profile.strategyName,\n trade,\n market,\n });\n }\n }\n }\n\n if (allTradeMarkets.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No trades could be matched to market data. Ensure market data is imported and enriched.\",\n },\n ],\n };\n }\n\n // Build the 18-cell matrix: Vol_Regime (6) x Trend_Direction (3)\n // Use prev_ prefix for both fields (both are close-derived, need LAG)\n const strategyNames = [...new Set(allTradeMarkets.map((t) => t.strategyName))];\n\n // Collect PLs per cell per strategy\n type CellKey = string; // \"regime:trend\"\n const cellPls = new Map<CellKey, Map<string, number[]>>();\n\n let unknownTrendCount = 0;\n const unknownTrendPls = new Map<string, number[]>(); // strategy -> pls for unknown trend\n\n for (const { strategyName, trade, market } of allTradeMarkets) {\n const volRegime = getNum(market, \"prev_Vol_Regime\");\n const trendRaw = market[\"prev_Trend_Direction\"];\n\n // Handle missing Vol_Regime\n if (isNaN(volRegime) || volRegime < 1 || volRegime > 6) continue;\n\n // Handle missing Trend_Direction\n let trend: TrendLabel | null = null;\n if (\n trendRaw === null ||\n trendRaw === undefined ||\n trendRaw === \"\"\n ) {\n unknownTrendCount++;\n if (!unknownTrendPls.has(strategyName)) {\n unknownTrendPls.set(strategyName, []);\n }\n unknownTrendPls.get(strategyName)!.push(trade.pl);\n continue;\n }\n const trendStr = String(trendRaw).toLowerCase();\n if (trendStr === \"up\" || trendStr === \"down\" || trendStr === \"flat\") {\n trend = trendStr as TrendLabel;\n } else {\n unknownTrendCount++;\n if (!unknownTrendPls.has(strategyName)) {\n unknownTrendPls.set(strategyName, []);\n }\n unknownTrendPls.get(strategyName)!.push(trade.pl);\n continue;\n }\n\n const regimeLabel = VOL_REGIME_LABELS[volRegime] || `regime_${volRegime}`;\n const cellKey = `${regimeLabel}:${trend}`;\n\n if (!cellPls.has(cellKey)) {\n cellPls.set(cellKey, new Map());\n }\n const cellMap = cellPls.get(cellKey)!;\n if (!cellMap.has(strategyName)) {\n cellMap.set(strategyName, []);\n }\n cellMap.get(strategyName)!.push(trade.pl);\n }\n\n // Build matrix output\n const matrix: Record<string, Record<string, Record<string, SliceStats>>> = {};\n const overlaps: Array<{\n regime: string;\n trend: string;\n strategies: string[];\n totalTrades: number;\n }> = [];\n const blindSpots: Array<{ regime: string; trend: string }> = [];\n let coveredCells = 0;\n let overlapCells = 0;\n\n for (const [, regimeLabel] of Object.entries(VOL_REGIME_LABELS)) {\n matrix[regimeLabel] = {};\n for (const trend of TREND_LABELS) {\n const cellKey = `${regimeLabel}:${trend}`;\n const cellMap = cellPls.get(cellKey);\n\n if (!cellMap || cellMap.size === 0) {\n blindSpots.push({ regime: regimeLabel, trend });\n matrix[regimeLabel][trend] = {};\n continue;\n }\n\n coveredCells++;\n const cellStats: Record<string, SliceStats> = {};\n const strategiesInCell: string[] = [];\n let totalTradesInCell = 0;\n\n for (const [stratName, pls] of cellMap) {\n cellStats[stratName] = computeSliceStats(pls);\n strategiesInCell.push(stratName);\n totalTradesInCell += pls.length;\n\n // Thin-data warning\n if (pls.length > 0 && pls.length < minTrades) {\n warnings.push(\n `Thin data: '${stratName}' has only ${pls.length} trades in ${regimeLabel}/${trend} (threshold: ${minTrades})`\n );\n }\n }\n\n matrix[regimeLabel][trend] = cellStats;\n\n // Overlap detection: 2+ strategies in same cell\n if (strategiesInCell.length >= 2) {\n overlapCells++;\n overlaps.push({\n regime: regimeLabel,\n trend,\n strategies: strategiesInCell,\n totalTrades: totalTradesInCell,\n });\n }\n }\n }\n\n const blindSpotCells = blindSpots.length;\n\n // Handle unknown trend trades\n if (unknownTrendCount > 0) {\n warnings.push(\n `${unknownTrendCount} trades had missing or unknown Trend_Direction. Consider running enrich_market_data to populate Trend_Direction.`\n );\n }\n\n // Build unknown trend stats if any\n const unknownTrendStats: Record<string, SliceStats> | undefined =\n unknownTrendPls.size > 0\n ? Object.fromEntries(\n [...unknownTrendPls.entries()].map(([name, pls]) => [\n name,\n computeSliceStats(pls),\n ])\n )\n : undefined;\n\n const coverageSummary = {\n totalCells: 18,\n coveredCells,\n blindSpotCells,\n overlapCells,\n };\n\n const summary = `Portfolio structure map: ${strategyNames.length} strategies | ${coveredCells}/18 cells covered | ${overlapCells} overlaps | ${blindSpotCells} blind spots`;\n\n const structuredData: Record<string, unknown> = {\n strategies: strategyNames,\n matrix,\n overlaps,\n blind_spots: blindSpots,\n coverage_summary: coverageSummary,\n warnings,\n };\n\n if (unknownTrendStats) {\n structuredData.unknown_trend = unknownTrendStats;\n }\n\n return createToolOutput(summary, structuredData);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error building portfolio structure map: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n}\n\n// =============================================================================\n// Registration\n// =============================================================================\n\n/**\n * Register all profile analysis tools.\n * This includes portfolio_structure_map (from Plan 03) and\n * analyze_structure_fit + validate_entry_filters (from Plan 02, if present).\n */\nexport function registerProfileAnalysisTools(\n server: McpServer,\n baseDir: string\n): void {\n // portfolio_structure_map: optional blockId means we can't always use withSyncedBlock.\n // When blockId is provided, sync that block. When omitted, sync all blocks.\n server.registerTool(\n \"portfolio_structure_map\",\n {\n description:\n \"Build a Vol_Regime x Trend_Direction matrix (18 cells) across all profiled strategies. \" +\n \"Shows per-strategy stats in each cell, detects overlap (2+ strategies in same cell), \" +\n \"blind spots (cells with zero trades), and thin-data warnings. \" +\n \"Optionally filter to a single block or aggregate across all blocks.\",\n inputSchema: portfolioStructureMapSchema,\n },\n async (input) => {\n // Manual sync: if blockId provided, sync just that block; otherwise sync all\n await upgradeToReadWrite(baseDir, { fallbackToReadOnly: true });\n if (getConnectionMode() === \"read_write\") {\n try {\n if (input.blockId) {\n const { syncBlock } = await import(\"../sync/index.ts\");\n await syncBlock(input.blockId, baseDir);\n } else {\n await syncAllBlocks(baseDir);\n }\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n }\n\n return handlePortfolioStructureMap(input, baseDir);\n }\n );\n\n // -------------------------------------------------------------------------\n // Tool: analyze_structure_fit\n // -------------------------------------------------------------------------\n server.registerTool(\n \"analyze_structure_fit\",\n {\n description:\n \"Analyze how well a strategy fits various market dimensions using its stored profile. \" +\n \"Returns performance breakdown by Vol_Regime, day-of-week, time-of-day, and profile-derived \" +\n \"dimensions from entry_filters. Includes profile_update_hints when data shows clear patterns \" +\n \"diverging from profile, and thin-data warnings for small buckets.\",\n inputSchema: analyzeStructureFitSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleAnalyzeStructureFit(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: validate_entry_filters\n // -------------------------------------------------------------------------\n server.registerTool(\n \"validate_entry_filters\",\n {\n description:\n \"Validate effectiveness of a strategy's entry filters. Splits trades into entered vs \" +\n \"filtered-out groups per filter and shows full stat suite for both. Runs ablation study \" +\n \"removing one filter at a time and testing all pairs. Returns profile_update_hints when \" +\n \"filters appear counterproductive.\",\n inputSchema: validateEntryFiltersSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleValidateEntryFilters(input, ctx.baseDir);\n })\n );\n}\n","/**\n * Regime Allocation Advisor Tool\n *\n * Cross-references strategy profiles' expected regimes with actual trading\n * performance per regime. Surfaces thesis violations and hidden edges as\n * structured data without prescriptive recommendations.\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { loadBlock } from \"../utils/block-loader.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport type { Trade } from \"@tradeblocks/lib\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { listProfiles } from \"../db/profile-schemas.ts\";\nimport { filterByStrategy } from \"./shared/filters.ts\";\nimport {\n buildLookaheadFreeQuery,\n type MarketLookupKey,\n} from \"../utils/field-timing.ts\";\nimport {\n DEFAULT_MARKET_TICKER,\n marketTickerDateKey,\n resolveTradeTicker,\n} from \"../utils/ticker.ts\";\nimport { computeSliceStats, type SliceStats } from \"../utils/analysis-stats.ts\";\nimport {\n upgradeToReadWrite,\n downgradeToReadOnly,\n getConnectionMode,\n} from \"../db/connection.ts\";\nimport { syncAllBlocks } from \"../sync/index.ts\";\n\n// =============================================================================\n// Utility Functions (local to this module, copied from profile-analysis.ts)\n// =============================================================================\n\nfunction formatTradeDate(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nfunction getTradeLookupKey(trade: Trade): MarketLookupKey {\n return {\n date: formatTradeDate(trade.dateOpened),\n ticker: resolveTradeTicker(trade, DEFAULT_MARKET_TICKER),\n };\n}\n\nfunction uniqueTradeLookupKeys(trades: Trade[]): MarketLookupKey[] {\n const byKey = new Map<string, MarketLookupKey>();\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n byKey.set(marketTickerDateKey(lookup.ticker, lookup.date), lookup);\n }\n return Array.from(byKey.values());\n}\n\nfunction resultToRecords(result: {\n columnCount: number;\n columnName(i: number): string;\n getRows(): Iterable<unknown[]>;\n}): Record<string, unknown>[] {\n const columnCount = result.columnCount;\n const colNames: string[] = [];\n for (let i = 0; i < columnCount; i++) {\n colNames.push(result.columnName(i));\n }\n const records: Record<string, unknown>[] = [];\n for (const row of result.getRows()) {\n const record: Record<string, unknown> = {};\n for (let i = 0; i < columnCount; i++) {\n const val = row[i];\n record[colNames[i]] = typeof val === \"bigint\" ? Number(val) : val;\n }\n records.push(record);\n }\n return records;\n}\n\nfunction recordsByTickerDate(\n records: Record<string, unknown>[]\n): Map<string, Record<string, unknown>> {\n const mapped = new Map<string, Record<string, unknown>>();\n for (const record of records) {\n const date = String(record[\"date\"] || \"\");\n const ticker = String(record[\"ticker\"] || DEFAULT_MARKET_TICKER);\n mapped.set(marketTickerDateKey(ticker, date), record);\n }\n return mapped;\n}\n\nfunction getNum(record: Record<string, unknown>, field: string): number {\n const val = record[field];\n if (val === null || val === undefined) return NaN;\n if (typeof val === \"bigint\") return Number(val);\n return val as number;\n}\n\nconst VOL_REGIME_LABELS: Record<number, string> = {\n 1: \"very_low\",\n 2: \"low\",\n 3: \"below_avg\",\n 4: \"above_avg\",\n 5: \"high\",\n 6: \"extreme\",\n};\n\n// =============================================================================\n// Types\n// =============================================================================\n\ninterface RegimeCell {\n stats: SliceStats;\n isExpected: boolean;\n classification: \"thesis_aligned\" | \"thesis_violation\" | \"hidden_edge\" | \"neutral\";\n}\n\ninterface StrategyRegimeComparison {\n strategyName: string;\n blockId: string;\n structureType: string;\n underlying?: string;\n allocationPct?: number;\n expectedRegimes: string[];\n regimePerformance: Record<string, RegimeCell>;\n tradeCount: number;\n matchedToMarket: number;\n unmatchedCount: number;\n}\n\n// =============================================================================\n// Schema\n// =============================================================================\n\nexport const regimeAllocationAdvisorSchema = z.object({\n blockId: z\n .string()\n .optional()\n .describe(\n \"Block ID to analyze. When omitted, aggregate across all profiled strategies.\"\n ),\n minTrades: z\n .number()\n .optional()\n .default(5)\n .describe(\n \"Minimum trades per regime cell for reliable stats (default: 5)\"\n ),\n});\n\n// =============================================================================\n// Handler\n// =============================================================================\n\nexport async function handleRegimeAllocationAdvisor(\n input: z.infer<typeof regimeAllocationAdvisorSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const minTrades = input.minTrades ?? 5;\n const warnings: string[] = [];\n const profileUpgradeHints: string[] = [];\n\n // Load all profiles, optionally filtered by blockId\n const conn = await getConnection(baseDir);\n const profiles = await listProfiles(conn, input.blockId, baseDir);\n\n if (profiles.length === 0) {\n return createToolOutput(\n input.blockId\n ? `No strategy profiles found for block '${input.blockId}'. Use profile_strategy to create profiles first.`\n : \"No strategy profiles found. Use profile_strategy to create profiles first.\",\n { error: \"no_profiles\" }\n );\n }\n\n const strategies: StrategyRegimeComparison[] = [];\n let skippedNoRegimes = 0;\n let skippedNoMarket = 0;\n const allThesisViolations: {\n strategyName: string;\n regime: string;\n winRate: number;\n expectedWinRate: number;\n }[] = [];\n const allHiddenEdges: {\n strategyName: string;\n regime: string;\n winRate: number;\n overallWinRate: number;\n }[] = [];\n\n // Per-regime aggregation across all strategies\n const regimeAggPls: Record<string, number[]> = {};\n\n for (const profile of profiles) {\n try {\n // Skip profiles without expectedRegimes\n if (!profile.expectedRegimes || profile.expectedRegimes.length === 0) {\n skippedNoRegimes++;\n profileUpgradeHints.push(\n `Strategy '${profile.strategyName}' (block: ${profile.blockId}) has no expectedRegimes. Add via profile_strategy.`\n );\n continue;\n }\n\n // Load trades\n let block;\n try {\n block = await loadBlock(baseDir, profile.blockId);\n } catch {\n warnings.push(\n `Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'. Skipped.`\n );\n continue;\n }\n\n let trades = filterByStrategy(block.trades, profile.strategyName);\n // Single-strategy block fallback\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n\n if (trades.length === 0) {\n warnings.push(\n `No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'. Skipped.`\n );\n continue;\n }\n\n // Query market data\n const tradeKeys = uniqueTradeLookupKeys(trades);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const result = await conn.runAndReadAll(sql, params);\n const marketRecords = resultToRecords(result);\n const marketMap = recordsByTickerDate(marketRecords);\n\n // Match trades to market records\n interface TradeWithMarket {\n trade: Trade;\n market: Record<string, unknown>;\n }\n const matched: TradeWithMarket[] = [];\n let unmatchedCount = 0;\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const key = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = marketMap.get(key);\n if (market) {\n matched.push({ trade, market });\n } else {\n unmatchedCount++;\n }\n }\n\n if (matched.length === 0) {\n skippedNoMarket++;\n warnings.push(\n `No market data matched for strategy '${profile.strategyName}' (${trades.length} trades). Import and enrich market data first.`\n );\n continue;\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `Strategy '${profile.strategyName}': ${unmatchedCount} of ${trades.length} trades had no market data match.`\n );\n }\n\n // Compute overall stats for this strategy\n const allPls = matched.map((m) => m.trade.pl);\n const overallStats = computeSliceStats(allPls);\n\n // Group trades by Vol_Regime\n const regimePls: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"prev_Vol_Regime\");\n if (isNaN(val) || val < 1 || val > 6) continue;\n const label = VOL_REGIME_LABELS[val] || `regime_${val}`;\n if (!regimePls[label]) regimePls[label] = [];\n regimePls[label].push(trade.pl);\n\n // Aggregate across strategies\n if (!regimeAggPls[label]) regimeAggPls[label] = [];\n regimeAggPls[label].push(trade.pl);\n }\n\n // Build per-regime comparison\n const expectedSet = new Set(\n profile.expectedRegimes.map((r) => r.toLowerCase())\n );\n const regimePerformance: Record<string, RegimeCell> = {};\n\n for (const [label, pls] of Object.entries(regimePls)) {\n const stats = computeSliceStats(pls);\n const isExpected = expectedSet.has(label.toLowerCase());\n\n // Classification logic:\n // thesis_aligned: isExpected AND performing reasonably (WR > 50% or > overall WR)\n // thesis_violation: isExpected AND WR significantly below overall (>10pp)\n // hidden_edge: NOT isExpected AND WR significantly above overall (>10pp) AND enough trades\n // neutral: everything else\n let classification: RegimeCell[\"classification\"];\n const wrDelta = stats.winRate - overallStats.winRate;\n\n if (isExpected) {\n if (wrDelta < -10) {\n classification = \"thesis_violation\";\n allThesisViolations.push({\n strategyName: profile.strategyName,\n regime: label,\n winRate: stats.winRate,\n expectedWinRate: overallStats.winRate,\n });\n } else {\n classification = \"thesis_aligned\";\n }\n } else {\n if (wrDelta > 10 && stats.tradeCount >= minTrades) {\n classification = \"hidden_edge\";\n allHiddenEdges.push({\n strategyName: profile.strategyName,\n regime: label,\n winRate: stats.winRate,\n overallWinRate: overallStats.winRate,\n });\n } else {\n classification = \"neutral\";\n }\n }\n\n regimePerformance[label] = { stats, isExpected, classification };\n }\n\n // Allocation from position sizing\n const allocationPct =\n profile.positionSizing?.liveAllocationPct ??\n profile.positionSizing?.allocationPct;\n\n strategies.push({\n strategyName: profile.strategyName,\n blockId: profile.blockId,\n structureType: profile.structureType,\n underlying: profile.underlying ?? undefined,\n allocationPct,\n expectedRegimes: profile.expectedRegimes,\n regimePerformance,\n tradeCount: trades.length,\n matchedToMarket: matched.length,\n unmatchedCount,\n });\n } catch (err) {\n warnings.push(\n `Error processing strategy '${profile.strategyName}' (block: ${profile.blockId}): ${(err as Error).message}`\n );\n }\n }\n\n // Build regime overview (aggregate stats per regime)\n const regimeOverview: Record<\n string,\n { strategiesActive: number; totalTrades: number; combinedStats: SliceStats }\n > = {};\n\n for (const [label, pls] of Object.entries(regimeAggPls)) {\n // Count how many strategies had trades in this regime\n let strategiesActive = 0;\n for (const strategy of strategies) {\n if (strategy.regimePerformance[label]) {\n strategiesActive++;\n }\n }\n regimeOverview[label] = {\n strategiesActive,\n totalTrades: pls.length,\n combinedStats: computeSliceStats(pls),\n };\n }\n\n const summary = {\n totalStrategies: profiles.length,\n profiled: strategies.length,\n skippedNoRegimes,\n skippedNoMarket,\n thesisViolations: allThesisViolations,\n hiddenEdges: allHiddenEdges,\n };\n\n const summaryText =\n `Regime allocation advisor: ${strategies.length}/${profiles.length} strategies analyzed. ` +\n `${allThesisViolations.length} thesis violation(s), ${allHiddenEdges.length} hidden edge(s). ` +\n (skippedNoRegimes > 0\n ? `${skippedNoRegimes} skipped (no expectedRegimes). `\n : \"\") +\n (skippedNoMarket > 0\n ? `${skippedNoMarket} skipped (no market data). `\n : \"\");\n\n return createToolOutput(summaryText, {\n strategies,\n summary,\n regimeOverview,\n warnings,\n profileUpgradeHints,\n });\n}\n\n// =============================================================================\n// Registration\n// =============================================================================\n\nexport function registerRegimeAdvisorTools(\n server: McpServer,\n baseDir: string\n): void {\n server.registerTool(\n \"regime_allocation_advisor\",\n {\n description:\n \"Cross-reference strategy profiles' expected regimes with actual trading performance. \" +\n \"Shows per-strategy, per-regime comparison with win rate, P&L, and trade count. \" +\n \"Classifications (thesis_aligned, thesis_violation, hidden_edge) emerge from data delta. \" +\n \"Optionally filter to a single block or aggregate across all profiled strategies.\",\n inputSchema: regimeAllocationAdvisorSchema,\n },\n async (input) => {\n // Manual sync pattern (same as portfolio_structure_map)\n await upgradeToReadWrite(baseDir, { fallbackToReadOnly: true });\n if (getConnectionMode() === \"read_write\") {\n try {\n if (input.blockId) {\n const { syncBlock } = await import(\"../sync/index.ts\");\n await syncBlock(input.blockId, baseDir);\n } else {\n await syncAllBlocks(baseDir);\n }\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n }\n return handleRegimeAllocationAdvisor(input, baseDir);\n }\n );\n}\n","import {\n getProvider,\n type MarketDataProvider,\n type ProviderCapabilities,\n} from \"./market-provider.ts\";\nimport {\n resolveMassiveDataTier,\n type MassiveDataTier,\n} from \"./massive-tier.ts\";\n\nexport interface ResolvedProviderCapabilities extends ProviderCapabilities {\n provider: MarketDataProvider;\n providerName: string;\n massiveDataTier: MassiveDataTier | null;\n quoteHydration: boolean;\n contractList: boolean;\n}\n\nexport function resolveProviderCapabilities(\n provider: MarketDataProvider = getProvider(),\n env: NodeJS.ProcessEnv = process.env,\n): ResolvedProviderCapabilities {\n const base = provider.capabilities();\n const massiveDataTier = provider.name === \"massive\" ? resolveMassiveDataTier(env) : null;\n // The right question for hydration dispatch is \"can I call fetchQuotes?\",\n // not \"is the data NBBO-grade?\" — provenance is captured per-row via\n // QuoteRow.source. Massive's fetchQuotes branches internally on\n // MASSIVE_DATA_TIER (true NBBO via /v3/quotes vs synthesized from /v2/aggs);\n // either path returns useful per-minute data.\n const quoteHydration = typeof provider.fetchQuotes === \"function\";\n\n return {\n ...base,\n provider,\n providerName: provider.name,\n massiveDataTier,\n quoteHydration,\n contractList: typeof provider.fetchContractList === \"function\",\n };\n}\n\nexport function getResolvedProviderCapabilities(\n env: NodeJS.ProcessEnv = process.env,\n): ResolvedProviderCapabilities {\n return resolveProviderCapabilities(getProvider(), env);\n}\n\nexport { resolveMassiveDataTier };\nexport type { MassiveDataTier };\n","/**\n * Trade Replay Tools\n *\n * MCP tool for replaying trades using historical minute-level option bars\n * read from the local market-data cache. Supports two modes:\n * A) Hypothetical replay — explicit legs with strikes/expiry/dates\n * B) Tradelog replay — block_id + trade_index to replay from existing trade data\n *\n * Tools registered:\n * - replay_trade — Replay a trade and compute minute-by-minute P&L path with MFE/MAE\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport type { MarketStores } from \"../market/stores/index.ts\";\nimport { extractRoot } from \"../market/tickers/resolver.ts\";\nimport type { QuoteRow } from \"../market/stores/types.ts\";\nimport {\n parseLegsString,\n buildOccTicker,\n computeStrategyPnlPath,\n computeReplayMfeMae,\n markPrice,\n type ReplayLeg,\n type ReplayResult,\n type GreeksConfig,\n} from \"../utils/trade-replay.ts\";\nimport type { BarRow } from \"../utils/market-provider.ts\";\n\n// ---------------------------------------------------------------------------\n// Zod schema\n// ---------------------------------------------------------------------------\n\nexport const replayTradeSchema = z.object({\n // Mode A: Hypothetical / explicit legs\n legs: z\n .array(\n z.object({\n ticker: z.string().describe(\"Underlying ticker, e.g., 'SPY', 'SPX'\"),\n strike: z.number().describe(\"Strike price\"),\n type: z.enum([\"C\", \"P\"]).describe(\"Call or Put\"),\n expiry: z.string().describe(\"Expiration date YYYY-MM-DD\"),\n quantity: z.number().describe(\"Positive = long, negative = short\"),\n entry_price: z\n .number()\n .describe(\"Per-contract entry price (premium paid/received)\"),\n })\n )\n .optional()\n .describe(\"Explicit leg definitions for hypothetical replay\"),\n\n // Mode B: Tradelog replay\n block_id: z.string().optional().describe(\"Block ID to load trade from\"),\n trade_index: z\n .number()\n .optional()\n .describe(\n \"0-based index of trade in block's tradelog (ordered by date_opened)\"\n ),\n\n // Common fields\n open_date: z\n .string()\n .optional()\n .describe(\n \"Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)\"\n ),\n close_date: z\n .string()\n .optional()\n .describe(\n \"Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)\"\n ),\n multiplier: z\n .number()\n .default(100)\n .describe(\"Contract multiplier (default 100 for standard options)\"),\n format: z\n .enum([\"full\", \"summary\", \"sampled\"])\n .default(\"sampled\")\n .describe(\n \"Output format: 'sampled' returns path sampled at ~15min intervals (default), \" +\n \"'full' returns complete minute-by-minute P&L path, \" +\n \"'summary' returns MFE/MAE/P&L without minute-level path\"\n ),\n close_at: z\n .enum([\"trade\", \"expiry\"])\n .default(\"trade\")\n .describe(\n \"When to end the P&L path: 'trade' (default) truncates at the trade's actual close time, \" +\n \"'expiry' shows full path through option expiry. Only applies to tradelog mode.\"\n ),\n skip_quotes: z\n .boolean()\n .default(false)\n .describe(\n \"Skip NBBO quote enrichment for option bars. Faster, but uses cached trade bars / HL2 marks.\"\n ),\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst MONTH_MAP: Record<string, string> = {\n Jan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06',\n Jul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12',\n};\n\n/** Convert OO expiry hint \"Mar 13\" + year \"2026\" → \"2026-03-13\" */\nfunction resolveOOExpiryHint(hint: string, year: string): string {\n const [mon, day] = hint.split(' ');\n const mm = MONTH_MAP[mon] ?? '01';\n const dd = day.padStart(2, '0');\n return `${year}-${mm}-${dd}`;\n}\n\n/**\n * Derive fetch date range from OO leg expiryHints.\n *\n * For calendar spreads (different expiries): min(expiry)→max(expiry).\n * For single-expiry trades: tradeOpenDate→expiry.\n * Returns null if no legs have expiryHint (caller falls back to trade dates).\n */\nexport function resolveOODateRange(\n parsedLegs: import(\"../utils/trade-replay.ts\").ParsedLegOO[],\n tradeYear: string,\n tradeOpenDate: string,\n): { from: string; to: string } | null {\n const hints = parsedLegs\n .filter(l => l.expiryHint)\n .map(l => resolveOOExpiryHint(l.expiryHint!, tradeYear));\n\n if (hints.length === 0) return null;\n\n const sorted = [...hints].sort();\n const maxDate = sorted[sorted.length - 1];\n\n // Always start from trade open date — bars are needed from entry, not from expiry.\n // End at the latest expiry to cover the full path.\n return { from: tradeOpenDate, to: maxDate };\n}\n\n// ---------------------------------------------------------------------------\n// Handler (exported for testing)\n// ---------------------------------------------------------------------------\n\nexport async function handleReplayTrade(\n params: z.infer<typeof replayTradeSchema>,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection\n): Promise<ReplayResult> {\n const {\n legs: inputLegs,\n block_id,\n trade_index,\n multiplier,\n close_at,\n skip_quotes,\n } = params;\n let { open_date, close_date } = params;\n let tradeCloseTimestamp: string | undefined; // \"YYYY-MM-DD HH:MM\" when trade actually closed\n\n let replayLegs: ReplayLeg[];\n\n if (inputLegs && inputLegs.length > 0) {\n // ----- Mode A: Hypothetical replay -----\n if (!open_date || !close_date) {\n throw new Error(\n \"open_date and close_date are required for hypothetical replay mode\"\n );\n }\n\n replayLegs = inputLegs.map((leg) => ({\n occTicker: buildOccTicker(leg.ticker, leg.expiry, leg.type, leg.strike),\n quantity: leg.quantity,\n entryPrice: leg.entry_price,\n multiplier,\n }));\n } else if (block_id !== undefined && trade_index !== undefined) {\n // ----- Mode B: Tradelog replay -----\n const conn = injectedConn ?? await getConnection(baseDir);\n\n const result = await conn.runAndReadAll(\n `SELECT legs, premium, date_opened, date_closed, ticker, num_contracts, time_closed\n FROM trades.trade_data\n WHERE block_id = '${block_id.replace(/'/g, \"''\")}'\n ORDER BY date_opened, rowid\n LIMIT 1 OFFSET ${trade_index}`\n );\n\n const rows = result.getRows();\n if (rows.length === 0) {\n throw new Error(\n `No trade found at index ${trade_index} in block \"${block_id}\"`\n );\n }\n\n const row = rows[0];\n const legsStr = String(row[0] ?? \"\");\n const premium = Number(row[1] ?? 0);\n const dateOpened = String(row[2] ?? \"\");\n const dateClosed = String(row[3] ?? \"\");\n const ticker = String(row[4] ?? \"\");\n const numContracts = Number(row[5] ?? 1);\n const timeClosed = String(row[6] ?? \"\");\n\n // Build actual trade close timestamp for path truncation\n if (dateClosed && timeClosed) {\n // time_closed is \"HH:MM:SS\" or \"HH:MM\" — normalize to \"HH:MM\"\n const normalizedTime = timeClosed.slice(0, 5);\n tradeCloseTimestamp = `${dateClosed} ${normalizedTime}`;\n }\n\n // Use trade dates if not provided\n open_date = open_date || dateOpened;\n close_date = close_date || dateClosed;\n\n // Parse legs from tradelog\n let parsedLegs;\n try {\n parsedLegs = parseLegsString(legsStr);\n } catch {\n throw new Error(\n `Cannot parse legs \"${legsStr}\" from tradelog — use hypothetical mode with explicit strikes`\n );\n }\n\n // Build ReplayLeg[] from parsed legs\n const root = ticker || parsedLegs[0].root;\n const perContractPremium =\n numContracts > 0 ? premium / numContracts : premium;\n\n // OO format provides per-leg entry price, contract count, and expiry hint\n const hasOOData = parsedLegs.some(l => l.entryPrice !== undefined);\n\n // Resolve per-leg expiry: OO expiryHint (\"Mar 13\") + year from trade date\n const tradeYear = (open_date || dateOpened).split('-')[0];\n\n // Override fetch date range from OO expiryHints when available\n if (hasOOData) {\n const ooRange = resolveOODateRange(parsedLegs, tradeYear, open_date || dateOpened);\n if (ooRange) {\n open_date = ooRange.from;\n close_date = ooRange.to;\n }\n }\n\n replayLegs = parsedLegs.map((leg) => {\n let legExpiry = close_date!;\n if (hasOOData && leg.expiryHint) {\n legExpiry = resolveOOExpiryHint(leg.expiryHint, tradeYear);\n }\n return {\n occTicker: buildOccTicker(root, legExpiry, leg.type, leg.strike),\n quantity: hasOOData\n ? leg.quantity * (leg.contracts ?? 1)\n : leg.quantity * (numContracts > 0 ? numContracts : 1),\n entryPrice: hasOOData\n ? leg.entryPrice!\n : perContractPremium / parsedLegs.length,\n multiplier,\n };\n });\n } else {\n throw new Error(\n \"Provide either legs[] for hypothetical mode or block_id + trade_index for tradelog mode\"\n );\n }\n\n // ----- Fetch minute quotes for each option leg via QuoteStore -----\n // Adapt QuoteRow → BarRow with mid = (bid+ask)/2 as open/high/low/close\n // (mid-price is the canonical mark for option-leg pricing).\n //\n // Group OCC tickers by underlying before each readQuotes call: the store\n // enforces a single-underlying invariant per call so partitioned reads\n // can be served from one parquet partition root. Typical replays have\n // all legs under one underlying (single SPX trade); multi-underlying\n // replays issue one readQuotes per underlying.\n //\n // Fallback root logic (SPX→SPXW etc.) is implicit: the QuoteStore's\n // tickers.resolve(extractRoot(...)) maps both SPX and SPXW to underlying\n // 'SPX'. The OCC ticker prefix in the chain is whatever the data layer\n // ingested (typically SPXW); keying on underlying makes the same data\n // reachable via either root.\n //\n // skip_quotes is a no-op for option-leg reads — quotes ARE the source of\n // truth here. Parameter remains in the schema for backward compat with\n // callers.\n void skip_quotes;\n\n const byUnderlying = new Map<string, string[]>();\n for (const leg of replayLegs) {\n const underlying = stores.quote.tickers.resolve(extractRoot(leg.occTicker));\n const arr = byUnderlying.get(underlying) ?? [];\n arr.push(leg.occTicker);\n byUnderlying.set(underlying, arr);\n }\n\n const quotesByOcc = new Map<string, QuoteRow[]>();\n for (const [, occs] of byUnderlying) {\n try {\n const result = await stores.quote.readQuotes(occs, open_date!, close_date!);\n for (const [occ, rows] of result) quotesByOcc.set(occ, rows);\n } catch {\n // Best-effort: a missing partition / read error returns empty for these legs.\n }\n }\n\n const barsByLeg: BarRow[][] = replayLegs.map((leg) => {\n const quotes = quotesByOcc.get(leg.occTicker) ?? [];\n return quotes.map((q) => {\n const [date, time] = q.timestamp.split(' ');\n const mid = (q.bid + q.ask) / 2;\n return {\n ticker: q.occ_ticker,\n date,\n time,\n open: mid,\n high: mid,\n low: mid,\n close: mid,\n bid: q.bid,\n ask: q.ask,\n volume: 0,\n };\n });\n });\n\n // ----- Fetch underlying bars + build greeks config -----\n // Reverse-map weekly roots back to standard root for underlying fetch\n const REVERSE_ROOT_MAP: Record<string, string> = {\n SPXW: 'SPX', NDXP: 'NDX', RUTW: 'RUT',\n };\n const DIVIDEND_YIELDS: Record<string, number> = {\n SPX: 0.015, SPXW: 0.015, NDX: 0.015, NDXP: 0.015,\n };\n\n // Extract root from first leg's OCC ticker\n const firstRootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);\n const rawRoot = firstRootMatch ? firstRootMatch[1] : '';\n const underlyingTicker = REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;\n const dividendYield = DIVIDEND_YIELDS[rawRoot] ?? 0;\n\n // Read underlying minute bars via SpotStore, falling back to the daily\n // aggregate (readDailyBars) when minute bars are absent. The daily\n // fallback keeps greeks computable on dates with sparse intraday\n // coverage.\n let underlyingBars: BarRow[] = await stores.spot.readBars(\n underlyingTicker,\n open_date!,\n close_date!,\n );\n if (underlyingBars.length === 0) {\n try {\n underlyingBars = await stores.spot.readDailyBars(\n underlyingTicker,\n open_date!,\n close_date!,\n );\n } catch {\n // No fallback available — greeks will be omitted\n }\n }\n // Defense-in-depth: drop any underlying bar with a zero/null OHLC value.\n // SPX/QQQ/etc. always have a real price — a zero in spot is a provider\n // gap (see ParquetSpotStore writer guard), and feeding it into Black-\n // Scholes greeks computation produces nonsense (S=0 → infinite delta etc.).\n // Raw bars are left unfiltered upstream so option tickers can keep\n // legitimate \"no trade\" zero rows; the filtering responsibility lives\n // here at the underlying-consumer site.\n if (underlyingBars.length > 0) {\n underlyingBars = underlyingBars.filter(\n (b) =>\n Number.isFinite(b.open) && b.open > 0 &&\n Number.isFinite(b.high) && b.high > 0 &&\n Number.isFinite(b.low) && b.low > 0 &&\n Number.isFinite(b.close)&& b.close> 0,\n );\n }\n\n // Build underlying price map for greeks config\n const underlyingPrices = new Map<string, number>();\n for (const b of underlyingBars) {\n const ts = `${b.date} ${b.time ?? ''}`.trim();\n underlyingPrices.set(ts, markPrice(b));\n }\n\n // Build sorted timestamps for tolerant nearest-timestamp lookup: when a\n // leg's quote timestamp doesn't have an exact underlying-price match\n // (e.g. one source skipped a minute), greeks computation falls back to\n // the nearest underlying timestamp within tolerance.\n const sortedTimestamps = Array.from(underlyingPrices.keys())\n .filter(k => k.includes(' ')) // Only intraday timestamps, not date-only keys\n .sort();\n\n // VIX IVP lookup via EnrichedStore.read — used as an optional input to\n // the greeks model when implied-vol percentile context is available.\n let ivpByDate: Map<string, number> | undefined;\n try {\n const vixEnriched = await stores.enriched.read({\n ticker: \"VIX\",\n from: open_date!,\n to: close_date!,\n includeContext: false,\n });\n const map = new Map<string, number>();\n for (const row of vixEnriched) {\n const ivp = row.ivp;\n if (ivp != null) {\n map.set(String(row.date), Number(ivp));\n }\n }\n if (map.size > 0) {\n ivpByDate = map;\n }\n } catch {\n // IVP is optional enrichment — don't fail\n }\n\n // Build GreeksConfig\n let greeksConfig: GreeksConfig | undefined;\n if (underlyingPrices.size > 0) {\n greeksConfig = {\n underlyingPrices,\n sortedTimestamps,\n legs: replayLegs.map(leg => {\n // Extract strike, type, expiry from OCC ticker: ROOT{YYMMDD}{C|P}{strike*1000}\n const occMatch = leg.occTicker.match(/^[A-Z]+(\\d{6})([CP])(\\d{8})$/);\n if (!occMatch) return { strike: 0, type: 'C' as const, expiryDate: '' };\n const yymmdd = occMatch[1];\n const type = occMatch[2] as 'C' | 'P';\n const strike = parseInt(occMatch[3], 10) / 1000;\n const expiryDate = `20${yymmdd.slice(0, 2)}-${yymmdd.slice(2, 4)}-${yymmdd.slice(4, 6)}`;\n return { strike, type, expiryDate };\n }),\n riskFreeRate: 0.045,\n dividendYield,\n ivpByDate,\n };\n }\n\n // ----- Compute P&L path + MFE/MAE -----\n let fullPath = computeStrategyPnlPath(replayLegs, barsByLeg, greeksConfig);\n let { mfe, mae, mfeTimestamp, maeTimestamp } =\n computeReplayMfeMae(fullPath);\n let totalPnl = fullPath.length > 0 ? fullPath[fullPath.length - 1].strategyPnl : 0;\n\n // Surface a warning when >50% of leg-timestamps have null greeks — the\n // most common cause is sparse IV data or 0DTE legs falling outside the\n // pricing model's valid range.\n let greeksNullCount = 0;\n let greeksTotalCount = 0;\n for (const point of fullPath) {\n if (point.legGreeks) {\n for (const lg of point.legGreeks) {\n greeksTotalCount++;\n if (lg.delta === null) greeksNullCount++;\n }\n }\n }\n const greeksWarning = greeksTotalCount > 0 && greeksNullCount / greeksTotalCount > 0.5\n ? `Greeks unavailable for ${greeksNullCount} of ${greeksTotalCount} leg-timestamps (0DTE options use Bachelier model; some legs may have insufficient time value for IV computation)`\n : null;\n\n // Apply format filter\n // Truncate path at trade close timestamp when close_at === \"trade\" (default)\n // This ensures decompose_greeks and exit triggers only analyze the actual holding period\n if (close_at === \"trade\" && tradeCloseTimestamp && fullPath.length > 0) {\n const truncIdx = fullPath.findIndex(p => p.timestamp > tradeCloseTimestamp!);\n if (truncIdx > 0) {\n fullPath = fullPath.slice(0, truncIdx);\n // Recompute MFE/MAE/totalPnl on truncated path\n mfe = -Infinity;\n mae = Infinity;\n for (const p of fullPath) {\n if (p.strategyPnl > mfe) { mfe = p.strategyPnl; mfeTimestamp = p.timestamp; }\n if (p.strategyPnl < mae) { mae = p.strategyPnl; maeTimestamp = p.timestamp; }\n }\n totalPnl = fullPath[fullPath.length - 1].strategyPnl;\n }\n }\n\n const { format } = params;\n let pnlPath: typeof fullPath;\n if (format === \"summary\") {\n // Return only MFE, MAE, and boundary points (first, last, MFE timestamp, MAE timestamp)\n const keyTimestamps = new Set([\n fullPath[0]?.timestamp,\n fullPath[fullPath.length - 1]?.timestamp,\n mfeTimestamp,\n maeTimestamp,\n ]);\n pnlPath = fullPath.filter(p => keyTimestamps.has(p.timestamp));\n } else if (format === \"sampled\") {\n // Sample at ~15min intervals (keep every 15th bar, plus first/last/MFE/MAE)\n const keyTimestamps = new Set([mfeTimestamp, maeTimestamp]);\n pnlPath = fullPath.filter((p, i) =>\n i === 0 || i === fullPath.length - 1 || i % 15 === 0 || keyTimestamps.has(p.timestamp)\n );\n } else {\n pnlPath = fullPath;\n }\n\n return {\n pnlPath,\n mfe,\n mae,\n mfeTimestamp,\n maeTimestamp,\n totalPnl,\n totalBars: fullPath.length,\n legs: replayLegs,\n greeksWarning,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerReplayTools(\n server: McpServer,\n baseDir: string,\n stores: MarketStores,\n): void {\n server.registerTool(\n \"replay_trade\",\n {\n description:\n \"Replay a trade using historical minute-level option bars. \" +\n \"Reads option-leg quotes via QuoteStore and underlying bars via SpotStore (cache only); \" +\n \"missing data yields a degenerate replay. Use the data-pipeline tools to backfill cache. \" +\n \"Returns minute-by-minute P&L path with MFE (Maximum Favorable Excursion) and MAE (Maximum Adverse Excursion). \" +\n \"Two modes: (A) Hypothetical — provide explicit legs with strikes, expiry, entry prices. \" +\n \"(B) Tradelog — provide block_id + trade_index to replay an existing trade from your data.\",\n inputSchema: replayTradeSchema,\n },\n async (params) => {\n try {\n const result = await handleReplayTrade(params, baseDir, stores);\n\n const summary =\n `Replayed ${result.legs.length}-leg strategy from ${params.open_date ?? \"trade dates\"} to ${params.close_date ?? \"trade dates\"}: ` +\n `$${result.totalPnl.toFixed(2)} P&L, MFE=$${result.mfe.toFixed(2)}, MAE=$${result.mae.toFixed(2)}, ` +\n `${result.pnlPath.length} minute bars, greeks=${result.pnlPath[0]?.legGreeks ? 'yes' : 'no'}`;\n\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error replaying trade: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * Root-to-underlying resolver.\n *\n * extractRoot: pure string manipulation — no registry lookup.\n * rootToUnderlying: extractRoot + registry.resolve (identity fallback for unknowns).\n *\n * The root-extraction regex is intentionally duplicated from an optional\n * private extension's OCC ticker parser (private-only module; shared code\n * cannot import it). If the OCC-ticker regex is ever updated in one place,\n * update the other as well.\n */\nimport type { TickerRegistry } from \"./registry.ts\"; // TYPE-ONLY — break runtime cycle with registry.ts\n\n// OCC-like option ticker shape: root + YYMMDD + C/P + 6-11 digit strike.\n// Standard OCC encodes strike × 1000 in 8 digits, but ThetaData emits wider\n// strike fields (up to 10 digits seen on adjusted/non-standard SPX series —\n// e.g. SPX240719C1262721200, SPX240719P845310800). Accept 6-11 so unusual but\n// well-formed tickers still extract their root cleanly instead of falling\n// through to the passthrough branch below (which previously leaked the full\n// OCC string as the partition key).\nconst OCC_RE = /^([A-Z]+)\\d{6}[CP]\\d{6,11}$/;\nconst LEADING_LETTERS_RE = /^([A-Z]+)/;\n\n/**\n * Extract the root from an input symbol.\n * - OCC ticker (\"SPXW251219C05000000\") → leading letters only (\"SPXW\")\n * - Bare root (\"SPXW\", \"VIX9D\", \"VIX3M\") → returned unchanged\n *\n * @throws when input has no leading alpha characters at all.\n */\nexport function extractRoot(input: string): string {\n const occMatch = input.match(OCC_RE);\n if (occMatch) return occMatch[1];\n // Bare root path — must start with at least one letter.\n if (!LEADING_LETTERS_RE.test(input)) {\n throw new Error(`Cannot extract root from \"${input}\"`);\n }\n return input;\n}\n\n/**\n * Resolve any OCC ticker or bare root to its underlying.\n * Unknown roots return themselves (identity fallback) — this is how\n * leveraged ETFs (SPXL/SPXS/SPXU/SPXC) and any new symbol stay correct\n * without explicit registry entries.\n */\nexport function rootToUnderlying(input: string, registry: TickerRegistry): string {\n const root = extractRoot(input);\n return registry.resolve(root);\n}\n","/**\n * Option Snapshot Tools\n *\n * MCP tool for fetching live option chain snapshots from Massive.com.\n * Returns current greeks, IV, open interest, and quotes for option contracts\n * on a specified underlying.\n *\n * Tools registered:\n * - get_option_snapshot — Fetch live option chain with greeks/IV/OI\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getProvider } from \"../utils/market-provider.ts\";\n\n// ---------------------------------------------------------------------------\n// Zod schema\n// ---------------------------------------------------------------------------\n\nexport const getOptionSnapshotSchema = z.object({\n underlying: z\n .string()\n .describe(\"Underlying ticker symbol (e.g., 'SPX', 'SPY', 'AAPL')\"),\n strike_price_gte: z\n .number()\n .optional()\n .describe(\"Minimum strike price filter\"),\n strike_price_lte: z\n .number()\n .optional()\n .describe(\"Maximum strike price filter\"),\n expiration_date_gte: z\n .string()\n .optional()\n .describe(\"Earliest expiration date (YYYY-MM-DD)\"),\n expiration_date_lte: z\n .string()\n .optional()\n .describe(\"Latest expiration date (YYYY-MM-DD)\"),\n contract_type: z\n .enum([\"call\", \"put\"])\n .optional()\n .describe(\"Filter by call or put\"),\n limit: z\n .number()\n .optional()\n .default(50)\n .describe(\n \"Max contracts to return (default 50, use higher for full chain)\"\n ),\n});\n\n// ---------------------------------------------------------------------------\n// Handler (exported for testing)\n// ---------------------------------------------------------------------------\n\nexport async function handleGetOptionSnapshot(\n params: z.infer<typeof getOptionSnapshotSchema>,\n): Promise<string> {\n try {\n const {\n underlying,\n strike_price_gte,\n strike_price_lte,\n expiration_date_gte,\n expiration_date_lte,\n contract_type,\n limit,\n } = params;\n\n const result = await getProvider().fetchOptionSnapshot({\n underlying,\n strike_price_gte,\n strike_price_lte,\n expiration_date_gte,\n expiration_date_lte,\n contract_type,\n });\n\n // Client-side limit truncation: API fetches all filtered contracts\n // (ensuring BS fallback runs on all), then we truncate for presentation\n const contractsTotal = result.contracts.length;\n const contracts =\n limit != null && contractsTotal > limit\n ? result.contracts.slice(0, limit)\n : result.contracts;\n\n return JSON.stringify({\n underlying_ticker: result.underlying_ticker,\n underlying_price: result.underlying_price,\n contracts_returned: contracts.length,\n contracts_total: contractsTotal,\n contracts,\n });\n } catch (error) {\n return JSON.stringify({\n error: (error as Error).message,\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerSnapshotTools(server: McpServer): void {\n server.registerTool(\n \"get_option_snapshot\",\n {\n description:\n \"Fetch live option chain snapshot with greeks, IV, open interest, and quotes from Massive.com. \" +\n \"Returns current market data for option contracts on the specified underlying. \" +\n \"Use filters to narrow by strike range, expiration range, or call/put type. \" +\n \"Replaces TastyTrade get_option_chain for analysis.\",\n inputSchema: getOptionSnapshotSchema,\n },\n async (params) => {\n const text = await handleGetOptionSnapshot(params);\n return {\n content: [{ type: \"text\" as const, text }],\n };\n }\n );\n}\n","/**\n * Greeks Decomposition Engine\n *\n * Decomposes a replay P&L path into ranked greek factor contributions\n * (delta, gamma, theta, vega, residual) using full revaluation P&L attribution.\n *\n * Full revaluation reprices each leg with one input changed at a time\n * (spot, time, vol) to capture all higher-order effects (charm, vanna, volga)\n * naturally. This produces near-zero residual for any strategy where the\n * pricing model (BS or Bachelier) can accurately price the options.\n *\n * Falls back to numerical decomposition (realized delta from price changes)\n * when full revaluation still produces >80% residual (model pricing failure).\n *\n * Pure logic module — no I/O, no DuckDB, no fetch.\n */\n\nimport type { PnlPoint, ReplayLeg } from './trade-replay.ts';\nimport { bsPrice, bachelierPrice, BACHELIER_DTE_THRESHOLD } from './black-scholes.ts';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type FactorName = 'delta' | 'gamma' | 'theta' | 'vega' | 'charm' | 'vanna' | 'residual' | 'time_and_vol';\n\nexport interface FactorContribution {\n factor: FactorName;\n totalPnl: number; // Sum of step contributions\n pctOfTotal: number; // % of total abs P&L move\n steps: number[]; // Per-step contribution values\n}\n\nexport interface LegGroupVega {\n label: string; // e.g., \"front_month\", \"back_month\"\n legIndices: number[]; // Which legs are in this group\n totalVegaPnl: number; // Sum of vega P&L for this group\n avgIvChange: number; // Average IV change for this group's legs\n steps: number[]; // Per-step vega contribution for this group\n}\n\nexport interface GreeksDecompositionResult {\n factors: FactorContribution[]; // Sorted by abs(totalPnl) descending\n legGroupVega?: LegGroupVega[]; // Per-leg-group vega attribution\n totalPnlChange: number; // Actual P&L change from first to last point\n totalAttributed: number; // Sum of factor contributions (excl residual)\n totalResidual: number; // Total residual\n stepCount: number; // Number of steps (pnlPath.length - 1)\n summary: string; // Human-readable summary\n warning?: string | null; // D-13: high residual warning\n method: 'full_reval' | 'model' | 'numerical'; // which method produced the attribution\n}\n\nexport interface LegGroupDef {\n label: string;\n legIndices: number[];\n}\n\nexport interface LegPricingInput {\n strike: number;\n type: 'C' | 'P';\n expiryDate: string; // YYYY-MM-DD\n}\n\nexport interface GreeksDecompositionConfig {\n pnlPath: PnlPoint[];\n legs: ReplayLeg[];\n underlyingPrices?: Map<string, number>; // timestamp -> underlying price\n legGroups?: LegGroupDef[]; // Optional leg grouping for per-group vega\n /** Per-leg pricing inputs for full revaluation. When provided, uses full reval\n * instead of Taylor expansion. Falls back to Taylor when missing. */\n legPricingInputs?: LegPricingInput[];\n riskFreeRate?: number; // e.g. 0.045\n dividendYield?: number; // e.g. 0.015 for SPX\n}\n\n// ---------------------------------------------------------------------------\n// Time delta helper\n// ---------------------------------------------------------------------------\n\nconst TRADING_MINUTES_PER_DAY = 390;\n\n/**\n * Compute time delta in trading days between two timestamps.\n * Format: \"YYYY-MM-DD HH:MM\"\n *\n * Same day: minutes difference / 390\n * Cross day: calendar day difference (simplified — treats each gap as 1 day)\n */\nexport function computeTimeDeltaDays(ts1: string, ts2: string): number {\n const [date1, time1] = ts1.split(' ');\n const [date2, time2] = ts2.split(' ');\n\n if (date1 === date2) {\n // Same day: count minutes difference\n const [h1, m1] = time1.split(':').map(Number);\n const [h2, m2] = time2.split(':').map(Number);\n const mins1 = h1 * 60 + m1;\n const mins2 = h2 * 60 + m2;\n const diffMins = Math.abs(mins2 - mins1);\n return diffMins / TRADING_MINUTES_PER_DAY;\n }\n\n // Cross-day: compute calendar day difference\n const d1 = new Date(date1 + 'T12:00:00'); // noon to avoid DST issues\n const d2 = new Date(date2 + 'T12:00:00');\n const diffMs = Math.abs(d2.getTime() - d1.getTime());\n const diffDays = Math.round(diffMs / (24 * 60 * 60 * 1000));\n\n // Add fractional day from time within each day\n const [h1, m1] = time1.split(':').map(Number);\n const [h2, m2] = time2.split(':').map(Number);\n // Fraction of trading day for ts2's time (from market open ~9:30)\n const minsIntoDay2 = (h2 * 60 + m2) - (9 * 60 + 30);\n const fracDay2 = Math.max(0, minsIntoDay2) / TRADING_MINUTES_PER_DAY;\n // Fraction remaining in ts1's day\n const minsIntoDay1 = (h1 * 60 + m1) - (9 * 60 + 30);\n const fracDayRemaining1 = Math.max(0, 1 - minsIntoDay1 / TRADING_MINUTES_PER_DAY);\n\n // Total: remaining fraction of day1 + (diffDays - 1) full days + fraction of day2\n if (diffDays <= 1) {\n return fracDayRemaining1 + fracDay2;\n }\n return fracDayRemaining1 + (diffDays - 1) + fracDay2;\n}\n\n// ---------------------------------------------------------------------------\n// Numerical fallback decomposition (D-09/D-10/D-11)\n// ---------------------------------------------------------------------------\n\n/**\n * Numerical decomposition: compute realized delta from price changes when\n * model-based attribution has > 80% residual.\n *\n * Splits P&L into: delta (from realized delta), gamma (from delta changes),\n * and time_and_vol (everything else — theta + vega + unexplained).\n */\nfunction numericalDecomposition(\n config: GreeksDecompositionConfig,\n totalPnlChange: number,\n stepCount: number,\n): GreeksDecompositionResult {\n const { pnlPath, underlyingPrices } = config;\n\n const numDeltaSteps: number[] = [];\n const numGammaSteps: number[] = [];\n const numResidualSteps: number[] = [];\n\n let prevRealizedDelta: number | null = null;\n\n for (let i = 0; i < stepCount; i++) {\n const cur = pnlPath[i];\n const next = pnlPath[i + 1];\n const actualChange = next.strategyPnl - cur.strategyPnl;\n\n // Underlying price change\n let underlyingChange = 0;\n if (underlyingPrices) {\n const p1 = underlyingPrices.get(cur.timestamp);\n const p2 = underlyingPrices.get(next.timestamp);\n if (p1 !== undefined && p2 !== undefined) {\n underlyingChange = p2 - p1;\n }\n }\n\n // Skip when underlying barely moves (< $0.01) — can't estimate delta\n if (Math.abs(underlyingChange) < 0.01) {\n numDeltaSteps.push(0);\n numGammaSteps.push(0);\n numResidualSteps.push(actualChange);\n // Do NOT update prevRealizedDelta — delta is unknown\n continue;\n }\n\n // Realized delta = total option PnL change / underlying change\n const realizedDelta = actualChange / underlyingChange;\n\n // Gamma from delta changes (D-10): only when we have a previous delta\n let gammaPnl = 0;\n if (prevRealizedDelta !== null) {\n const deltaChange = realizedDelta - prevRealizedDelta;\n gammaPnl = 0.5 * deltaChange * underlyingChange;\n }\n\n const pureDeltaPnl = realizedDelta * underlyingChange - gammaPnl;\n const residual = actualChange - pureDeltaPnl - gammaPnl;\n\n numDeltaSteps.push(pureDeltaPnl);\n numGammaSteps.push(gammaPnl);\n numResidualSteps.push(residual);\n\n prevRealizedDelta = realizedDelta;\n }\n\n const sumSteps = (s: number[]) => s.reduce((a, v) => a + v, 0);\n const totalDelta = sumSteps(numDeltaSteps);\n const totalGamma = sumSteps(numGammaSteps);\n const totalTimeAndVol = sumSteps(numResidualSteps);\n\n const rawFactors = [\n { factor: 'delta' as FactorName, totalPnl: totalDelta, steps: numDeltaSteps },\n { factor: 'gamma' as FactorName, totalPnl: totalGamma, steps: numGammaSteps },\n { factor: 'time_and_vol' as FactorName, totalPnl: totalTimeAndVol, steps: numResidualSteps },\n ];\n\n rawFactors.sort((a, b) => Math.abs(b.totalPnl) - Math.abs(a.totalPnl));\n const totalAbsSum = rawFactors.reduce((s, f) => s + Math.abs(f.totalPnl), 0);\n const factors: FactorContribution[] = rawFactors.map(f => ({\n ...f,\n pctOfTotal: totalAbsSum > 0 ? (Math.abs(f.totalPnl) / totalAbsSum) * 100 : 0,\n }));\n\n const summaryParts = factors.map(f => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);\n const summary = `P&L of ${totalPnlChange.toFixed(2)} (numerical): ${summaryParts.join(', ')}`;\n\n return {\n factors,\n legGroupVega: undefined, // Leg-group vega not available in numerical mode\n totalPnlChange,\n totalAttributed: totalDelta + totalGamma,\n totalResidual: totalTimeAndVol,\n stepCount,\n summary,\n warning: 'Model-based attribution had >80% residual. Switched to numerical method (realized delta from price changes).',\n method: 'numerical',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Core decomposition\n// ---------------------------------------------------------------------------\n\n/**\n * Compute DTE in days from a bar timestamp to a leg's expiry (4:00 PM ET).\n */\nfunction computeDte(timestamp: string, expiryDate: string): number {\n const dateStr = timestamp.split(' ')[0];\n const timePart = timestamp.split(' ')[1] ?? '09:30';\n const [eyy, emm, edd] = expiryDate.split('-').map(Number);\n const [byy, bmm, bdd] = dateStr.split('-').map(Number);\n const [hh, min] = timePart.split(':').map(Number);\n\n const expiryMs = new Date(eyy, emm - 1, edd).getTime() + 16 * 60 * 60 * 1000; // 4:00 PM ET\n const barMs = new Date(byy, bmm - 1, bdd).getTime() + (hh * 60 + min) * 60 * 1000;\n return (expiryMs - barMs) / (1000 * 60 * 60 * 24);\n}\n\n/**\n * Price an option using the appropriate model (BS or Bachelier) based on DTE.\n * Returns null if pricing fails (DTE <= 0 or IV missing).\n */\nfunction priceOption(\n type: 'C' | 'P',\n S: number,\n K: number,\n dte: number,\n r: number,\n q: number,\n iv: number,\n): number | null {\n if (dte <= 0 || iv <= 0) return null;\n const T = dte / 365;\n const bsType = type === 'C' ? 'call' as const : 'put' as const;\n if (dte < BACHELIER_DTE_THRESHOLD) {\n return bachelierPrice(bsType, S, K, T, r, q, iv);\n }\n return bsPrice(bsType, S, K, T, r, q, iv);\n}\n\n/**\n * Decompose a replay P&L path into ranked greek factor contributions.\n *\n * Uses full revaluation when legPricingInputs are provided:\n * For each step, reprices each leg with one input changed at a time\n * (spot only, time only, vol only) to isolate each factor's contribution.\n * This captures all higher-order effects (charm, vanna, volga) naturally.\n *\n * Falls back to numerical decomposition when full reval produces >80% residual\n * (pricing model failure for that strategy/DTE combination).\n */\nexport function decomposeGreeks(config: GreeksDecompositionConfig): GreeksDecompositionResult {\n const { pnlPath, legs, underlyingPrices, legGroups, legPricingInputs, riskFreeRate, dividendYield } = config;\n\n // Edge case: empty or single-point path\n if (pnlPath.length <= 1) {\n const emptyFactors: FactorContribution[] = [\n { factor: 'delta', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'gamma', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'theta', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'vega', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'residual', totalPnl: 0, pctOfTotal: 0, steps: [] },\n ];\n return {\n factors: emptyFactors,\n legGroupVega: legGroups ? legGroups.map(g => ({\n label: g.label,\n legIndices: g.legIndices,\n totalVegaPnl: 0,\n avgIvChange: 0,\n steps: [],\n })) : undefined,\n totalPnlChange: 0,\n totalAttributed: 0,\n totalResidual: 0,\n stepCount: 0,\n summary: 'No P&L path to decompose (0 steps)',\n warning: null,\n method: 'full_reval',\n };\n }\n\n const stepCount = pnlPath.length - 1;\n const canFullReval = legPricingInputs && legPricingInputs.length === legs.length\n && riskFreeRate !== undefined && dividendYield !== undefined && underlyingPrices;\n const r = riskFreeRate ?? 0.045;\n const q = dividendYield ?? 0.015;\n\n // Accumulators\n const deltaSteps: number[] = [];\n const thetaSteps: number[] = [];\n const vegaSteps: number[] = [];\n const charmSteps: number[] = [];\n const vannaSteps: number[] = [];\n const residualSteps: number[] = [];\n\n // Per-leg-group vega accumulators\n const groupSteps: number[][] | undefined = legGroups\n ? legGroups.map(() => [] as number[])\n : undefined;\n\n for (let i = 0; i < stepCount; i++) {\n const cur = pnlPath[i];\n const next = pnlPath[i + 1];\n\n let stepDelta = 0;\n let stepTheta = 0;\n let stepVega = 0;\n let stepCharm = 0;\n let stepVanna = 0;\n let stepResidual = 0;\n\n const groupVegaAccum: number[] | undefined = legGroups ? legGroups.map(() => 0) : undefined;\n\n const legCount = Math.min(legs.length, cur.legPrices?.length ?? 0, next.legPrices?.length ?? 0);\n\n // Underlying prices at cur and next timestamps\n const S1 = cur.underlyingPrice ?? underlyingPrices?.get(cur.timestamp);\n const S2 = next.underlyingPrice ?? underlyingPrices?.get(next.timestamp);\n\n for (let j = 0; j < legCount; j++) {\n const positionSize = legs[j].quantity * legs[j].multiplier;\n const legActualChange = ((next.legPrices?.[j] ?? 0) - (cur.legPrices?.[j] ?? 0)) * positionSize;\n\n const curIv = cur.legGreeks?.[j]?.iv;\n const nextIv = next.legGreeks?.[j]?.iv;\n const lpi = legPricingInputs?.[j];\n\n // Full revaluation: reprice with one input changed at a time\n if (canFullReval && lpi && S1 !== undefined && S2 !== undefined\n && curIv !== null && curIv !== undefined && curIv > 0\n && nextIv !== null && nextIv !== undefined && nextIv > 0) {\n\n const dte1 = computeDte(cur.timestamp, lpi.expiryDate);\n const dte2 = computeDte(next.timestamp, lpi.expiryDate);\n\n if (dte1 > 0 && dte2 > 0) {\n // Baseline: price at (S1, T1, IV1)\n const priceBase = priceOption(lpi.type, S1, lpi.strike, dte1, r, q, curIv);\n\n // Delta: price at (S2, T1, IV1) — only spot changed\n const priceDelta = priceOption(lpi.type, S2, lpi.strike, dte1, r, q, curIv);\n\n // Theta: price at (S1, T2, IV1) — only time changed\n const priceTheta = priceOption(lpi.type, S1, lpi.strike, dte2, r, q, curIv);\n\n // Vega: price at (S1, T1, IV2) — only vol changed\n const priceVega = priceOption(lpi.type, S1, lpi.strike, dte1, r, q, nextIv);\n\n // Cross-term repricing: two inputs changed at once\n // Charm (spot×time): P(S2, T2, σ1) - base - delta - theta\n const priceCharm = priceOption(lpi.type, S2, lpi.strike, dte2, r, q, curIv);\n // Vanna (spot×vol): P(S2, T1, σ2) - base - delta - vega\n const priceVanna = priceOption(lpi.type, S2, lpi.strike, dte1, r, q, nextIv);\n\n if (priceBase !== null && priceDelta !== null && priceTheta !== null\n && priceVega !== null && priceCharm !== null && priceVanna !== null) {\n const legDeltaPnl = (priceDelta - priceBase) * positionSize;\n const legThetaPnl = (priceTheta - priceBase) * positionSize;\n const legVegaPnl = (priceVega - priceBase) * positionSize;\n const legCharmPnl = ((priceCharm - priceBase) - (priceDelta - priceBase) - (priceTheta - priceBase)) * positionSize;\n const legVannaPnl = ((priceVanna - priceBase) - (priceDelta - priceBase) - (priceVega - priceBase)) * positionSize;\n const legResidual = legActualChange - legDeltaPnl - legThetaPnl - legVegaPnl - legCharmPnl - legVannaPnl;\n\n stepDelta += legDeltaPnl;\n stepTheta += legThetaPnl;\n stepVega += legVegaPnl;\n stepCharm += legCharmPnl;\n stepVanna += legVannaPnl;\n stepResidual += legResidual;\n\n // Per-leg-group vega\n if (legGroups && groupVegaAccum) {\n for (let g = 0; g < legGroups.length; g++) {\n if (legGroups[g].legIndices.includes(j)) {\n groupVegaAccum[g] += legVegaPnl;\n }\n }\n }\n continue; // leg handled by full reval\n }\n }\n }\n\n // Fallback: leg P&L goes to residual (no pricing possible)\n stepResidual += legActualChange;\n }\n\n deltaSteps.push(stepDelta);\n thetaSteps.push(stepTheta);\n vegaSteps.push(stepVega);\n charmSteps.push(stepCharm);\n vannaSteps.push(stepVanna);\n residualSteps.push(stepResidual);\n\n if (groupSteps && groupVegaAccum) {\n for (let g = 0; g < legGroups!.length; g++) {\n groupSteps[g].push(groupVegaAccum[g]);\n }\n }\n }\n\n const sumSteps = (steps: number[]): number => steps.reduce((s, v) => s + v, 0);\n\n // Full reval factors:\n // - delta: spot-only P&L (includes gamma — all spot-driven effects)\n // - theta: time-only P&L\n // - vega: vol-only P&L\n // - charm: spot×time cross-effect (delta changing with time)\n // - vanna: spot×vol cross-effect (delta changing with vol)\n // - residual: triple cross (spot+time+vol simultaneously) + model error\n const rawFactors: Array<{ factor: FactorName; totalPnl: number; steps: number[] }> = [\n { factor: 'delta', totalPnl: sumSteps(deltaSteps), steps: deltaSteps },\n { factor: 'theta', totalPnl: sumSteps(thetaSteps), steps: thetaSteps },\n { factor: 'vega', totalPnl: sumSteps(vegaSteps), steps: vegaSteps },\n { factor: 'charm', totalPnl: sumSteps(charmSteps), steps: charmSteps },\n { factor: 'vanna', totalPnl: sumSteps(vannaSteps), steps: vannaSteps },\n { factor: 'residual', totalPnl: sumSteps(residualSteps), steps: residualSteps },\n ];\n\n rawFactors.sort((a, b) => Math.abs(b.totalPnl) - Math.abs(a.totalPnl));\n const totalAbsSum = rawFactors.reduce((s, f) => s + Math.abs(f.totalPnl), 0);\n const factors: FactorContribution[] = rawFactors.map(f => ({\n ...f,\n pctOfTotal: totalAbsSum > 0 ? (Math.abs(f.totalPnl) / totalAbsSum) * 100 : 0,\n }));\n\n const totalPnlChange = pnlPath[pnlPath.length - 1].strategyPnl - pnlPath[0].strategyPnl;\n const totalResidual = sumSteps(residualSteps);\n const totalAttributed = sumSteps(deltaSteps) + sumSteps(thetaSteps) + sumSteps(vegaSteps) + sumSteps(charmSteps) + sumSteps(vannaSteps);\n\n const residualPct = Math.abs(totalPnlChange) > 0.01\n ? Math.abs(totalResidual) / Math.abs(totalPnlChange)\n : 0;\n\n // Numerical fallback when full reval still produces >80% residual\n // (model pricing failure — BS/Bachelier can't accurately price these options)\n if (residualPct > 0.8 && pnlPath.length > 2) {\n return numericalDecomposition(config, totalPnlChange, stepCount);\n }\n\n // Build leg group vega results\n let legGroupVega: LegGroupVega[] | undefined;\n if (legGroups && groupSteps) {\n legGroupVega = legGroups.map((group, g) => {\n const steps = groupSteps[g];\n const totalVegaPnl = sumSteps(steps);\n\n let totalIvChange = 0;\n let ivStepCount = 0;\n for (let si = 0; si < stepCount; si++) {\n const cur = pnlPath[si];\n const nxt = pnlPath[si + 1];\n if (!cur.legGreeks || !nxt.legGreeks) continue;\n for (const j of group.legIndices) {\n const iv1 = cur.legGreeks[j]?.iv;\n const iv2 = nxt.legGreeks[j]?.iv;\n if (iv1 !== null && iv1 !== undefined && iv2 !== null && iv2 !== undefined) {\n totalIvChange += (iv2 - iv1) * 100;\n ivStepCount++;\n }\n }\n }\n\n return {\n label: group.label,\n legIndices: group.legIndices,\n totalVegaPnl,\n avgIvChange: ivStepCount > 0 ? totalIvChange / ivStepCount : 0,\n steps,\n };\n });\n }\n\n // Build summary\n const methodLabel = canFullReval ? 'full_reval' : 'model';\n const summaryParts = factors\n .filter(f => f.factor !== 'residual')\n .map(f => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);\n const residualFactor = factors.find(f => f.factor === 'residual');\n if (residualFactor && Math.abs(residualFactor.totalPnl) > 0.01) {\n summaryParts.push(`residual ${residualFactor.totalPnl.toFixed(2)} (${residualFactor.pctOfTotal.toFixed(0)}%)`);\n }\n const summary = `P&L of ${totalPnlChange.toFixed(2)} (${methodLabel}): ${summaryParts.join(', ')}`;\n\n const warning = residualPct > 0.5\n ? `Residual ${(residualPct * 100).toFixed(0)}% — attribution limited for some legs.`\n : null;\n\n return {\n factors,\n legGroupVega,\n totalPnlChange,\n totalAttributed,\n totalResidual,\n stepCount,\n summary,\n warning,\n method: canFullReval ? 'full_reval' : 'model',\n };\n}\n","/**\n * Exit Trigger Evaluation Engine\n *\n * Pure logic module (no I/O, no DuckDB, no fetch) that evaluates 15 exit\n * trigger types against a greeks-enriched P&L path from trade replay.\n *\n * Provides the computational heart of the `analyze_exit_triggers` tool.\n */\n\nimport type { PnlPoint, ReplayLeg } from './trade-replay.ts';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type TriggerType =\n | 'profitTarget'\n | 'stopLoss'\n | 'trailingStop'\n | 'profitAction'\n | 'dteExit'\n | 'ditExit'\n | 'clockTimeExit'\n | 'underlyingPriceMove'\n | 'positionDelta'\n | 'perLegDelta'\n | 'vixMove'\n | 'vix9dMove'\n | 'vix9dVixRatio'\n | 'slRatioThreshold'\n | 'slRatioMove';\n\nexport interface PartialClose {\n index: number;\n pnlAtFire: number;\n allocation: number;\n trigger: string;\n}\n\nexport interface ExitTriggerConfig {\n type: TriggerType;\n threshold: number;\n unit?: 'percent' | 'dollar'; // D-07: default 'dollar', backwards compatible\n steps?: Array<{ armAt: number; stopAt: number; closeAllocationPct?: number }>;\n // Context-specific optional fields:\n expiry?: string; // YYYY-MM-DD for dteExit\n openDate?: string; // YYYY-MM-DD for ditExit\n clockTime?: string; // \"HH:MM\" for clockTimeExit (threshold ignored)\n trailAmount?: number; // Dollar trail for trailingStop\n // Directional delta fields (per-leg directional exits):\n legIndex?: number; // 0-based leg index for perLegDelta — targets specific leg\n exitAbove?: number; // Fire when value > exitAbove (directional, no abs)\n exitBelow?: number; // Fire when value < exitBelow (directional, no abs)\n // Data maps for triggers needing external prices:\n underlyingPrices?: Map<string, number>; // timestamp -> price\n vixPrices?: Map<string, number>; // timestamp -> VIX price\n vix9dPrices?: Map<string, number>; // timestamp -> VIX9D price\n // S/L ratio inputs:\n spreadWidth?: number; // Width of spread in dollars\n contracts?: number; // Number of contracts\n multiplier?: number; // Default 100\n // profitTarget confirmation: N synchronized-quote bars at-or-above threshold required before firing (default 1 = fire on first cross)\n requiredHits?: number;\n // Internal: set by handler when unit='percent' to compute dollar threshold\n entryCost?: number; // D-11: cost/credit of entry (negative = credit received)\n entrySLRatio?: number; // Runtime-hydrated opening short/long ratio for slRatioMove\n}\n\nexport interface TriggerFireEvent {\n type: TriggerType;\n firedAt: string; // Timestamp when trigger fired\n pnlAtFire: number; // Strategy P&L when trigger fired\n index: number; // Index into pnlPath\n detail?: string; // Human-readable description\n}\n\nexport interface ExitTriggerResult {\n triggers: TriggerFireEvent[]; // All triggers that fired (sorted by fire time)\n firstToFire: TriggerFireEvent | null; // Earliest trigger\n actualExit?: {\n timestamp: string;\n pnl: number;\n pnlDifference: number; // firstToFire.pnl - actualExit.pnl\n };\n partialCloses?: PartialClose[]; // Partial position closes from profitAction steps\n summary: string;\n}\n\nexport interface LegGroupConfig {\n label: string;\n legIndices: number[];\n triggers: ExitTriggerConfig[];\n}\n\nexport interface LegGroupResult {\n label: string;\n result: ExitTriggerResult;\n groupPnl: number[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Parse \"YYYY-MM-DD\" to a Date at local midnight. */\nfunction parseDate(dateStr: string): Date {\n const [y, m, d] = dateStr.split('-').map(Number);\n return new Date(y, m - 1, d);\n}\n\n/** Extract date portion \"YYYY-MM-DD\" from timestamp \"YYYY-MM-DD HH:MM\". */\nfunction extractDate(timestamp: string): string {\n return timestamp.slice(0, 10);\n}\n\n/** Extract time portion \"HH:MM\" from timestamp \"YYYY-MM-DD HH:MM\". */\nfunction extractTime(timestamp: string): string {\n return timestamp.slice(11, 16);\n}\n\n/** Calendar days between two dates (absolute). */\nfunction calendarDaysBetween(a: Date, b: Date): number {\n const MS_PER_DAY = 86_400_000;\n return Math.abs(Math.floor((b.getTime() - a.getTime()) / MS_PER_DAY));\n}\n\n/** Compute S/L ratio for spread positions. */\nfunction computeSLRatio(\n point: PnlPoint,\n legs: ReplayLeg[],\n spreadWidth: number,\n contracts: number,\n multiplier: number,\n): number {\n // Spread value = sum of abs(markPrice * quantity * multiplier) for short legs\n let spreadValue = 0;\n for (let i = 0; i < legs.length; i++) {\n if (legs[i].quantity < 0) {\n const markPrice = point.legPrices[i] ?? 0;\n spreadValue += Math.abs(markPrice * legs[i].quantity * legs[i].multiplier);\n }\n }\n const maxLoss = spreadWidth * contracts * multiplier;\n if (maxLoss === 0) return 0;\n return spreadValue / maxLoss;\n}\n\nfunction computeSLRatioMove(initial: number, current: number): number {\n if (initial === 0) return current === 0 ? 0 : Number.POSITIVE_INFINITY;\n return (current - initial) / initial;\n}\n\nfunction crossesDirectionalMove(pctMove: number, threshold: number): boolean {\n if (threshold < 0) return pctMove <= threshold;\n if (threshold > 0) return pctMove >= threshold;\n return pctMove !== 0;\n}\n\nfunction adjustLegDeltaForPosition(rawDelta: number, leg?: ReplayLeg): number {\n return leg != null && leg.quantity < 0 ? -rawDelta : rawDelta;\n}\n\n// ---------------------------------------------------------------------------\n// evaluateProfitAction — partial close aware evaluator\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a profitAction trigger with partial close support.\n * Steps with closeAllocationPct will close a fraction of the REMAINING position\n * when their armAt is first reached. The remaining position's P&L is scaled down.\n *\n * Returns both the fire event (stop hit on remaining) and any partial closes.\n */\nexport function evaluateProfitAction(\n trigger: ExitTriggerConfig,\n pnlPath: PnlPoint[],\n _legs: ReplayLeg[],\n): { fireEvent: TriggerFireEvent | null; partialCloses: PartialClose[] } {\n const partialCloses: PartialClose[] = [];\n\n if (pnlPath.length === 0 || !trigger.steps?.length) {\n return { fireEvent: null, partialCloses };\n }\n if (trigger.unit === 'percent' && trigger.entryCost == null) {\n return { fireEvent: null, partialCloses };\n }\n\n const scale = trigger.unit === 'percent'\n ? Math.abs(trigger.entryCost!)\n : 1;\n\n const normalizedSteps = [...trigger.steps]\n .sort((a, b) => a.armAt - b.armAt)\n .map((step) => ({\n armAt: step.armAt * scale,\n stopAt: step.stopAt * scale,\n closeAllocationPct: step.closeAllocationPct,\n }));\n\n let remainingAllocation = 1.0;\n let runningMaxPnl = -Infinity;\n // Track which steps have already triggered their partial close\n const stepPartialFired = new Array(normalizedSteps.length).fill(false);\n\n for (let i = 0; i < pnlPath.length; i++) {\n const point = pnlPath[i];\n const pnl = point.strategyPnl;\n\n if (pnl > runningMaxPnl) runningMaxPnl = pnl;\n\n // Check each step for partial close (only when armAt first reached)\n for (let s = 0; s < normalizedSteps.length; s++) {\n const step = normalizedSteps[s];\n if (!stepPartialFired[s] && step.closeAllocationPct && runningMaxPnl >= step.armAt) {\n stepPartialFired[s] = true;\n const closeAmt = remainingAllocation * step.closeAllocationPct;\n partialCloses.push({\n index: i,\n pnlAtFire: pnl * remainingAllocation * step.closeAllocationPct,\n allocation: closeAmt,\n trigger: 'profitAction',\n });\n remainingAllocation -= closeAmt;\n }\n }\n\n // Compute active stop floor (same logic as original)\n let activeFloor = -Infinity;\n for (const step of normalizedSteps) {\n if (runningMaxPnl >= step.armAt) {\n activeFloor = Math.max(activeFloor, step.stopAt);\n }\n }\n\n // Check if stop hit on remaining allocation\n // Scaled comparison: pnl * remainingAllocation <= activeFloor * remainingAllocation\n // Simplifies to: pnl <= activeFloor (when remainingAllocation > 0)\n if (activeFloor > -Infinity && remainingAllocation > 0 && pnl <= activeFloor) {\n const effectivePnl = pnl * remainingAllocation;\n const detail = trigger.unit === 'percent'\n ? `Profit action: stop adjusted to ${(activeFloor / scale * 100).toFixed(0)}% ($${activeFloor.toFixed(2)}) at max P&L $${runningMaxPnl.toFixed(2)}, hit at $${pnl.toFixed(2)} (remaining ${(remainingAllocation * 100).toFixed(0)}%)`\n : `Profit action: stop adjusted to $${activeFloor.toFixed(2)} at max P&L $${runningMaxPnl.toFixed(2)}, hit at $${pnl.toFixed(2)} (remaining ${(remainingAllocation * 100).toFixed(0)}%)`;\n\n return {\n fireEvent: {\n type: 'profitAction',\n firedAt: point.timestamp,\n pnlAtFire: effectivePnl,\n index: i,\n detail,\n },\n partialCloses,\n };\n }\n }\n\n return { fireEvent: null, partialCloses };\n}\n\n// ---------------------------------------------------------------------------\n// evaluateTrigger\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a single trigger against the full P&L path.\n * Returns the first point where it fires, or null.\n */\nexport function evaluateTrigger(\n trigger: ExitTriggerConfig,\n pnlPath: PnlPoint[],\n legs: ReplayLeg[],\n): TriggerFireEvent | null {\n if (pnlPath.length === 0) return null;\n\n const { type, threshold } = trigger;\n\n // State for triggers that track running values\n let runningMaxPnl = -Infinity;\n let profitTargetHits = 0;\n let initialSLRatio: number | null = null;\n let firstUnderlyingPrice: number | null = null;\n let firstVixPrice: number | null = null;\n let firstVix9dPrice: number | null = null;\n\n for (let i = 0; i < pnlPath.length; i++) {\n const point = pnlPath[i];\n const pnl = point.strategyPnl;\n\n // Update running max for trailingStop\n if (pnl > runningMaxPnl) runningMaxPnl = pnl;\n\n let fired = false;\n let detail: string | undefined;\n\n switch (type) {\n case 'profitTarget': {\n // unit='percent' requires entryCost; if missing, cannot compute — no fire\n if (trigger.unit === 'percent' && trigger.entryCost == null) break;\n const requiredHits = trigger.requiredHits ?? 1;\n const dollarThresholdPT = trigger.unit === 'percent'\n ? threshold * Math.abs(trigger.entryCost!)\n : threshold;\n if (pnl >= dollarThresholdPT) {\n if (point.allLegsSync !== false) profitTargetHits++;\n if (profitTargetHits < requiredHits) break;\n fired = true;\n detail = trigger.unit === 'percent'\n ? `P&L $${pnl.toFixed(2)} >= ${(threshold * 100).toFixed(0)}% of $${Math.abs(trigger.entryCost!).toFixed(2)} ($${dollarThresholdPT.toFixed(2)})`\n : `P&L $${pnl.toFixed(2)} >= target $${dollarThresholdPT.toFixed(2)}`;\n } else if (point.allLegsSync !== false) {\n profitTargetHits = 0;\n }\n break;\n }\n\n case 'stopLoss': {\n // Normalize negative threshold — users may pass -2 meaning \"stop at $2 loss\"\n const absThreshold = Math.abs(threshold);\n // unit='percent' requires entryCost; if missing, cannot compute — no fire\n if (trigger.unit === 'percent' && trigger.entryCost == null) break;\n const dollarThresholdSL = trigger.unit === 'percent'\n ? absThreshold * Math.abs(trigger.entryCost!)\n : absThreshold;\n if (pnl <= -dollarThresholdSL) {\n fired = true;\n detail = trigger.unit === 'percent'\n ? `P&L $${pnl.toFixed(2)} <= -${(absThreshold * 100).toFixed(0)}% of $${Math.abs(trigger.entryCost!).toFixed(2)} (-$${dollarThresholdSL.toFixed(2)})`\n : `P&L $${pnl.toFixed(2)} <= stop -$${dollarThresholdSL.toFixed(2)}`;\n }\n break;\n }\n\n case 'trailingStop': {\n const trailAmt = trigger.trailAmount ?? threshold;\n const dropdown = runningMaxPnl - pnl;\n if (dropdown >= trailAmt && runningMaxPnl > -Infinity) {\n fired = true;\n detail = `Dropdown $${dropdown.toFixed(2)} from max $${runningMaxPnl.toFixed(2)} >= trail $${trailAmt.toFixed(2)}`;\n }\n break;\n }\n\n case 'profitAction': {\n // Delegate to evaluateProfitAction for the full path evaluation\n // (evaluateTrigger is called point-by-point in the loop, but profitAction\n // needs full-path context for partial close tracking, so we handle it\n // by breaking out of the loop and evaluating the full path at once.)\n const paResult = evaluateProfitAction(trigger, pnlPath, legs);\n return paResult.fireEvent;\n }\n\n case 'dteExit': {\n if (!trigger.expiry) break;\n const pointDate = parseDate(extractDate(point.timestamp));\n const expiryDate = parseDate(trigger.expiry);\n const dte = calendarDaysBetween(pointDate, expiryDate);\n // Only fire if point is before/on expiry\n if (pointDate <= expiryDate && dte <= threshold) {\n fired = true;\n detail = `DTE ${dte} <= threshold ${threshold}`;\n }\n break;\n }\n\n case 'ditExit': {\n if (!trigger.openDate) break;\n const pointDate = parseDate(extractDate(point.timestamp));\n const openDate = parseDate(trigger.openDate);\n const dit = calendarDaysBetween(openDate, pointDate);\n if (dit >= threshold) {\n fired = true;\n detail = `DIT ${dit} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'clockTimeExit': {\n const clockTime = trigger.clockTime ?? '15:00';\n const pointTime = extractTime(point.timestamp);\n if (pointTime >= clockTime) {\n fired = true;\n detail = `Time ${pointTime} >= ${clockTime}`;\n }\n break;\n }\n\n case 'underlyingPriceMove': {\n if (!trigger.underlyingPrices) break;\n const price = trigger.underlyingPrices.get(point.timestamp);\n if (price == null) break;\n if (firstUnderlyingPrice === null) {\n firstUnderlyingPrice = price;\n break; // Can't compute move on first price\n }\n const pctMove = ((price - firstUnderlyingPrice) / firstUnderlyingPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `Underlying moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'positionDelta': {\n const netDelta = point.netDelta ?? 0;\n if (trigger.exitAbove != null) {\n if (netDelta > trigger.exitAbove) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} > exitAbove ${trigger.exitAbove}`;\n }\n } else if (trigger.exitBelow != null) {\n if (netDelta < trigger.exitBelow) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} < exitBelow ${trigger.exitBelow}`;\n }\n } else if (Math.abs(netDelta) >= threshold) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'perLegDelta': {\n if (!point.legGreeks) break;\n if (trigger.legIndex != null) {\n // Target a specific leg\n if (trigger.legIndex >= point.legGreeks.length) break;\n const rawDelta = point.legGreeks[trigger.legIndex].delta ?? 0;\n const legDelta = adjustLegDeltaForPosition(rawDelta, legs[trigger.legIndex]);\n if (trigger.exitAbove != null) {\n if (legDelta > trigger.exitAbove) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} > exitAbove ${trigger.exitAbove}`;\n }\n } else if (trigger.exitBelow != null) {\n if (legDelta < trigger.exitBelow) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} < exitBelow ${trigger.exitBelow}`;\n }\n } else {\n // legIndex set but no directional fields — use abs() on that single leg\n if (Math.abs(legDelta) >= threshold) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} >= threshold ${threshold}`;\n }\n }\n } else {\n // No legIndex — iterate all legs with abs() (backward compat)\n for (let li = 0; li < point.legGreeks.length; li++) {\n const rawDelta = point.legGreeks[li].delta ?? 0;\n const legDelta = adjustLegDeltaForPosition(rawDelta, legs[li]);\n if (Math.abs(legDelta) >= threshold) {\n fired = true;\n detail = `Leg ${li} delta ${legDelta.toFixed(4)} >= threshold ${threshold}`;\n break;\n }\n }\n }\n break;\n }\n\n case 'vixMove': {\n if (!trigger.vixPrices) break;\n const vix = trigger.vixPrices.get(point.timestamp);\n if (vix == null) break;\n if (firstVixPrice === null) {\n firstVixPrice = vix;\n break;\n }\n const pctMove = ((vix - firstVixPrice) / firstVixPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `VIX moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'vix9dMove': {\n if (!trigger.vix9dPrices) break;\n const vix9d = trigger.vix9dPrices.get(point.timestamp);\n if (vix9d == null) break;\n if (firstVix9dPrice === null) {\n firstVix9dPrice = vix9d;\n break;\n }\n const pctMove = ((vix9d - firstVix9dPrice) / firstVix9dPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `VIX9D moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'vix9dVixRatio': {\n if (!trigger.vixPrices || !trigger.vix9dPrices) break;\n const vix = trigger.vixPrices.get(point.timestamp);\n const vix9d = trigger.vix9dPrices.get(point.timestamp);\n if (vix == null || vix9d == null || vix === 0) break;\n const ratio = vix9d / vix;\n // If threshold >= 1, fire when ratio >= threshold (contango deepening)\n // If threshold < 1, fire when ratio <= threshold (backwardation)\n const crosses = threshold >= 1 ? ratio >= threshold : ratio <= threshold;\n if (crosses) {\n fired = true;\n detail = `VIX9D/VIX ratio ${ratio.toFixed(4)} crossed threshold ${threshold}`;\n }\n break;\n }\n\n case 'slRatioThreshold': {\n const sw = trigger.spreadWidth ?? 0;\n const ct = trigger.contracts ?? 1;\n const mp = trigger.multiplier ?? 100;\n if (sw === 0) break;\n const slRatio = computeSLRatio(point, legs, sw, ct, mp);\n if (slRatio >= threshold) {\n fired = true;\n detail = `S/L ratio ${slRatio.toFixed(4)} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'slRatioMove': {\n const sw = trigger.spreadWidth ?? 0;\n const ct = trigger.contracts ?? 1;\n const mp = trigger.multiplier ?? 100;\n if (sw === 0) break;\n const slRatio = computeSLRatio(point, legs, sw, ct, mp);\n if (initialSLRatio === null && typeof trigger.entrySLRatio === 'number') {\n initialSLRatio = trigger.entrySLRatio;\n }\n if (initialSLRatio === null) {\n initialSLRatio = slRatio;\n break; // Can't compute change on first point\n }\n const pctMove = computeSLRatioMove(initialSLRatio, slRatio);\n if (crossesDirectionalMove(pctMove, threshold)) {\n fired = true;\n detail = `S/L ratio moved ${(pctMove * 100).toFixed(2)}% from initial ${initialSLRatio.toFixed(4)} to ${slRatio.toFixed(4)} (threshold ${(threshold * 100).toFixed(2)}%)`;\n }\n break;\n }\n }\n\n if (fired) {\n return {\n type,\n firedAt: point.timestamp,\n pnlAtFire: pnl,\n index: i,\n detail,\n };\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// computeGroupPnl\n// ---------------------------------------------------------------------------\n\n/**\n * Compute per-group P&L at each timestamp from leg prices.\n * groupPnl[t] = sum over legIndices of (legPrices[i] - entryPrice[i]) * quantity[i] * multiplier[i]\n */\nfunction computeGroupPnl(\n pnlPath: PnlPoint[],\n legs: ReplayLeg[],\n legIndices: number[],\n): number[] {\n return pnlPath.map((point) => {\n let groupPnl = 0;\n for (const idx of legIndices) {\n if (idx < legs.length && idx < point.legPrices.length) {\n const leg = legs[idx];\n const markPrice = point.legPrices[idx];\n groupPnl += (markPrice - leg.entryPrice) * leg.quantity * leg.multiplier;\n }\n }\n return groupPnl;\n });\n}\n\n// ---------------------------------------------------------------------------\n// analyzeExitTriggers\n// ---------------------------------------------------------------------------\n\n/**\n * Run all triggers against the P&L path, find first-to-fire,\n * compute actual exit comparison, and evaluate leg group triggers.\n */\nexport function analyzeExitTriggers(config: {\n pnlPath: PnlPoint[];\n legs: ReplayLeg[];\n triggers: ExitTriggerConfig[];\n actualExitTimestamp?: string;\n legGroups?: LegGroupConfig[];\n}): {\n overall: ExitTriggerResult;\n legGroups?: LegGroupResult[];\n} {\n const { pnlPath, legs, triggers, actualExitTimestamp, legGroups } = config;\n\n // Evaluate all triggers\n const fireEvents: TriggerFireEvent[] = [];\n let allPartialCloses: PartialClose[] = [];\n for (const trigger of triggers) {\n if (trigger.type === 'profitAction') {\n // Use the partial-close-aware helper for profitAction\n const paResult = evaluateProfitAction(trigger, pnlPath, legs);\n if (paResult.fireEvent) {\n fireEvents.push(paResult.fireEvent);\n }\n if (paResult.partialCloses.length > 0) {\n allPartialCloses = allPartialCloses.concat(paResult.partialCloses);\n }\n } else {\n const event = evaluateTrigger(trigger, pnlPath, legs);\n if (event) {\n fireEvents.push(event);\n }\n }\n }\n\n // Sort by fire index (earliest first)\n fireEvents.sort((a, b) => a.index - b.index);\n\n const firstToFire = fireEvents.length > 0 ? fireEvents[0] : null;\n\n // Actual exit comparison\n let actualExit: ExitTriggerResult['actualExit'];\n if (actualExitTimestamp && firstToFire) {\n // Find closest point to actualExitTimestamp\n let closestIdx = 0;\n let closestDist = Infinity;\n for (let i = 0; i < pnlPath.length; i++) {\n // Simple string comparison — timestamps are lexicographically ordered\n const dist = Math.abs(pnlPath[i].timestamp.localeCompare(actualExitTimestamp));\n if (pnlPath[i].timestamp === actualExitTimestamp) {\n closestIdx = i;\n break;\n }\n if (dist < closestDist) {\n closestDist = dist;\n closestIdx = i;\n }\n }\n // Fallback: use last point if actualExitTimestamp is after all points\n if (actualExitTimestamp > pnlPath[pnlPath.length - 1].timestamp) {\n closestIdx = pnlPath.length - 1;\n }\n const actualPnl = pnlPath[closestIdx].strategyPnl;\n actualExit = {\n timestamp: pnlPath[closestIdx].timestamp,\n pnl: actualPnl,\n pnlDifference: firstToFire.pnlAtFire - actualPnl,\n };\n }\n\n // Build summary\n let summary: string;\n if (!firstToFire) {\n summary = `No triggers fired across ${pnlPath.length} data points.`;\n } else if (actualExit) {\n const betterWorse = actualExit.pnlDifference > 0 ? 'better' : 'worse';\n summary = `${firstToFire.type} fired at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). ` +\n `Actual exit at ${actualExit.timestamp} (P&L $${actualExit.pnl.toFixed(2)}). ` +\n `Trigger was $${Math.abs(actualExit.pnlDifference).toFixed(2)} ${betterWorse}.`;\n } else {\n summary = `${firstToFire.type} fired first at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). ` +\n `${fireEvents.length} trigger(s) fired total.`;\n }\n\n const overall: ExitTriggerResult = {\n triggers: fireEvents,\n firstToFire,\n actualExit,\n partialCloses: allPartialCloses.length > 0 ? allPartialCloses : undefined,\n summary,\n };\n\n // Leg group evaluation\n let legGroupResults: LegGroupResult[] | undefined;\n if (legGroups && legGroups.length > 0) {\n legGroupResults = legGroups.map((group) => {\n const groupPnlArr = computeGroupPnl(pnlPath, legs, group.legIndices);\n\n // Build a synthetic PnlPoint[] for this group with groupPnl as strategyPnl\n const groupPath: PnlPoint[] = pnlPath.map((point, idx) => ({\n ...point,\n strategyPnl: groupPnlArr[idx],\n // Filter legPrices/legGreeks to only this group's legs\n legPrices: group.legIndices.map((li) => point.legPrices[li] ?? 0),\n legGreeks: point.legGreeks\n ? group.legIndices.map((li) => point.legGreeks![li])\n : undefined,\n }));\n\n // Build group legs subset\n const groupLegs = group.legIndices.map((li) => legs[li]);\n\n // Evaluate per-group triggers\n const groupFireEvents: TriggerFireEvent[] = [];\n for (const trigger of group.triggers) {\n const event = evaluateTrigger(trigger, groupPath, groupLegs);\n if (event) groupFireEvents.push(event);\n }\n groupFireEvents.sort((a, b) => a.index - b.index);\n\n const groupFirstToFire = groupFireEvents.length > 0 ? groupFireEvents[0] : null;\n\n // Actual exit for group\n let groupActualExit: ExitTriggerResult['actualExit'];\n if (actualExitTimestamp && groupFirstToFire) {\n let closestIdx = pnlPath.length - 1;\n for (let i = 0; i < pnlPath.length; i++) {\n if (pnlPath[i].timestamp === actualExitTimestamp) {\n closestIdx = i;\n break;\n }\n }\n if (actualExitTimestamp > pnlPath[pnlPath.length - 1].timestamp) {\n closestIdx = pnlPath.length - 1;\n }\n const actualGroupPnl = groupPnlArr[closestIdx];\n groupActualExit = {\n timestamp: pnlPath[closestIdx].timestamp,\n pnl: actualGroupPnl,\n pnlDifference: groupFirstToFire.pnlAtFire - actualGroupPnl,\n };\n }\n\n const groupSummary = groupFirstToFire\n ? `${group.label}: ${groupFirstToFire.type} fired at ${groupFirstToFire.firedAt} (group P&L $${groupFirstToFire.pnlAtFire.toFixed(2)})`\n : `${group.label}: No triggers fired.`;\n\n return {\n label: group.label,\n result: {\n triggers: groupFireEvents,\n firstToFire: groupFirstToFire,\n actualExit: groupActualExit,\n summary: groupSummary,\n },\n groupPnl: groupPnlArr,\n };\n });\n }\n\n return {\n overall,\n legGroups: legGroupResults,\n };\n}\n","/**\n * Exit Analysis Tools\n *\n * MCP tools for analyzing exit triggers and decomposing P&L into greek factor\n * contributions. Both tools run trade replay internally -- a single tool call\n * fetches data, replays the trade, and analyzes the results.\n *\n * Tools registered:\n * - analyze_exit_triggers -- Evaluate 15 trigger types against a replay P&L path\n * - decompose_greeks -- Decompose P&L into delta/gamma/theta/vega/residual factors\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport { handleReplayTrade } from \"./replay.ts\";\nimport type { MarketStores } from \"../market/stores/index.ts\";\nimport {\n analyzeExitTriggers,\n type ExitTriggerConfig,\n type LegGroupConfig,\n} from \"../utils/exit-triggers.ts\";\nimport {\n decomposeGreeks,\n type LegGroupDef,\n} from \"../utils/greeks-decomposition.ts\";\nimport { markPrice } from \"../utils/trade-replay.ts\";\n\n// ---------------------------------------------------------------------------\n// Shared trigger type enum\n// ---------------------------------------------------------------------------\n\nconst triggerTypeEnum = z.enum([\n 'profitTarget', 'stopLoss', 'trailingStop', 'profitAction',\n 'dteExit', 'ditExit', 'clockTimeExit',\n 'underlyingPriceMove', 'positionDelta', 'perLegDelta',\n 'vixMove', 'vix9dMove', 'vix9dVixRatio',\n 'slRatioThreshold', 'slRatioMove',\n]);\n\nconst triggerConfigSchema = z.object({\n type: triggerTypeEnum,\n threshold: z.number(),\n unit: z.enum(['percent', 'dollar']).default('dollar').optional(),\n expiry: z.string().optional(),\n openDate: z.string().optional(),\n clockTime: z.string().optional(),\n trailAmount: z.number().optional(),\n steps: z.array(z.object({\n armAt: z.number(),\n stopAt: z.number(),\n closeAllocationPct: z.number().min(0).max(1).optional()\n .describe(\"Fraction of REMAINING position to close at this milestone (0-1)\"),\n })).optional(),\n spreadWidth: z.number().optional(),\n contracts: z.number().optional(),\n legIndex: z.number().optional()\n .describe(\"0-based leg index for perLegDelta — targets specific leg\"),\n exitAbove: z.number().optional()\n .describe(\"Fire when value exceeds this (directional, no abs)\"),\n exitBelow: z.number().optional()\n .describe(\"Fire when value drops below this (directional, no abs)\"),\n});\n\n// ---------------------------------------------------------------------------\n// Leg schema (shared between both tools)\n// ---------------------------------------------------------------------------\n\nconst legSchema = z.object({\n ticker: z.string(),\n strike: z.number(),\n type: z.enum([\"C\", \"P\"]),\n expiry: z.string(),\n quantity: z.number(),\n entry_price: z.number(),\n});\n\n// ---------------------------------------------------------------------------\n// analyze_exit_triggers schema\n// ---------------------------------------------------------------------------\n\nexport const analyzeExitTriggersSchema = z.object({\n // Replay inputs (same shape as replay_trade)\n legs: z.array(legSchema).optional(),\n block_id: z.string().optional(),\n trade_index: z.number().optional(),\n open_date: z.string().optional(),\n close_date: z.string().optional(),\n multiplier: z.number().default(100),\n\n triggers: z.array(triggerConfigSchema)\n .describe(\"Exit triggers to evaluate against the P&L path\"),\n\n actual_exit_timestamp: z.string().optional()\n .describe(\"Actual exit time for comparison (format: YYYY-MM-DD HH:MM)\"),\n\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n triggers: z.array(triggerConfigSchema),\n })).optional().describe(\"Per-leg-group exit triggers for multi-structure strategies\"),\n\n format: z.enum([\"summary\", \"full\"]).default(\"summary\")\n .describe(\"'summary' omits per-step trigger states, 'full' includes all fire events\"),\n});\n\n// ---------------------------------------------------------------------------\n// decompose_greeks schema\n// ---------------------------------------------------------------------------\n\nexport const decomposeGreeksSchema = z.object({\n // Same replay inputs\n legs: z.array(legSchema).optional(),\n block_id: z.string().optional(),\n trade_index: z.number().optional(),\n open_date: z.string().optional(),\n close_date: z.string().optional(),\n multiplier: z.number().default(100),\n\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n })).optional().describe(\"Leg grouping for per-group vega attribution (e.g., front_month vs back_month)\"),\n\n format: z.enum([\"summary\", \"full\"]).default(\"summary\")\n .describe(\"'summary' shows ranked factors, 'full' includes per-step contributions\"),\n skip_quotes: z\n .boolean()\n .default(false)\n .describe(\"Skip NBBO quote enrichment for option bars. Faster, but lower precision.\"),\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// Reverse-map weekly roots to standard root for underlying/VIX fetching\nconst REVERSE_ROOT_MAP: Record<string, string> = {\n SPXW: 'SPX', NDXP: 'NDX', RUTW: 'RUT',\n};\n\n/**\n * Extract the underlying root ticker from the first replay leg's OCC ticker.\n * Maps weekly roots (SPXW, NDXP) back to their standard root.\n */\nfunction extractUnderlyingTicker(occTicker: string): string {\n const rootMatch = occTicker.match(/^([A-Z]+)/);\n const rawRoot = rootMatch ? rootMatch[1] : '';\n return REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;\n}\n\n/**\n * Read VIX, VIX9D, or underlying minute bars via SpotStore and build a\n * timestamp->price map. Reads NEVER trigger provider calls — bars are\n * served from the local store, with a daily-aggregate fallback when\n * minute bars are absent (same pattern used in replay.ts for underlying\n * fetches). An empty map on cache miss is the silent-empty contract —\n * callers treat absent data as \"trigger inactive\" rather than as an\n * error.\n */\nasync function fetchPriceMap(\n stores: MarketStores,\n ticker: string,\n from: string,\n to: string,\n): Promise<Map<string, number>> {\n const map = new Map<string, number>();\n try {\n let bars = await stores.spot.readBars(ticker, from, to);\n if (bars.length === 0) {\n try {\n bars = await stores.spot.readDailyBars(ticker, from, to);\n } catch {\n // No daily fallback available — return empty map\n }\n }\n // Defense-in-depth: skip any underlying bar with a zero/null OHLC value.\n // The underlying ticker (SPX/QQQ/etc.) always has a real price — a zero\n // is a provider gap that would corrupt the price map and downstream\n // trigger comparisons. Raw bars are left unfiltered upstream so option\n // tickers can keep legitimate \"no trade\" zero rows; this filter is\n // applied at the underlying-consumer site.\n for (const b of bars) {\n if (\n !Number.isFinite(b.open) || b.open <= 0 ||\n !Number.isFinite(b.high) || b.high <= 0 ||\n !Number.isFinite(b.low) || b.low <= 0 ||\n !Number.isFinite(b.close) || b.close <= 0\n ) continue;\n const ts = `${b.date} ${b.time ?? ''}`.trim();\n map.set(ts, markPrice(b));\n }\n } catch {\n // Best-effort — empty map signals \"trigger data unavailable\"\n }\n return map;\n}\n\n// ---------------------------------------------------------------------------\n// handleAnalyzeExitTriggers\n// ---------------------------------------------------------------------------\n\nexport async function handleAnalyzeExitTriggers(\n params: z.infer<typeof analyzeExitTriggersSchema>,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<ReturnType<typeof analyzeExitTriggers>> {\n const {\n legs: inputLegs, block_id, trade_index,\n open_date, close_date, multiplier,\n triggers, actual_exit_timestamp, leg_groups,\n } = params;\n\n // 1. Run replay to get full P&L path with greeks\n const replayResult = await handleReplayTrade(\n {\n legs: inputLegs,\n block_id,\n trade_index,\n open_date,\n close_date,\n multiplier,\n format: 'full',\n close_at: 'trade',\n skip_quotes: false,\n },\n baseDir,\n stores,\n injectedConn,\n );\n\n const pnlPath = replayResult.pnlPath;\n const replayLegs = replayResult.legs;\n\n // Compute entry cost for percentage-based triggers\n const entryCost = replayLegs.reduce((sum, leg) => {\n return sum + leg.entryPrice * leg.quantity * leg.multiplier;\n }, 0);\n\n if (pnlPath.length === 0) {\n return {\n overall: {\n triggers: [],\n firstToFire: null,\n summary: 'No P&L data available from replay.',\n },\n };\n }\n\n // 2. Determine date range from replay path\n const firstDate = pnlPath[0].timestamp.slice(0, 10);\n const lastDate = pnlPath[pnlPath.length - 1].timestamp.slice(0, 10);\n\n // 3. Check which external data maps are needed\n const allTriggerTypes = new Set(triggers.map(t => t.type));\n const groupTriggerTypes = new Set(\n (leg_groups ?? []).flatMap(g => g.triggers.map(t => t.type))\n );\n for (const t of groupTriggerTypes) allTriggerTypes.add(t);\n\n // Determine underlying ticker for underlying price triggers\n const underlyingTicker = extractUnderlyingTicker(replayLegs[0].occTicker);\n\n // Fetch VIX/VIX9D/underlying price maps as needed\n let vixPrices: Map<string, number> | undefined;\n let vix9dPrices: Map<string, number> | undefined;\n let underlyingPrices: Map<string, number> | undefined;\n\n const needsVix = allTriggerTypes.has('vixMove') || allTriggerTypes.has('vix9dVixRatio');\n const needsVix9d = allTriggerTypes.has('vix9dMove') || allTriggerTypes.has('vix9dVixRatio');\n const needsUnderlying = allTriggerTypes.has('underlyingPriceMove');\n\n if (needsVix) {\n vixPrices = await fetchPriceMap(stores, 'VIX', firstDate, lastDate);\n }\n if (needsVix9d) {\n vix9dPrices = await fetchPriceMap(stores, 'VIX9D', firstDate, lastDate);\n }\n if (needsUnderlying) {\n underlyingPrices = await fetchPriceMap(\n stores, underlyingTicker, firstDate, lastDate,\n );\n }\n\n // 4. Map tool trigger params to ExitTriggerConfig[] with data maps\n const exitTriggers: ExitTriggerConfig[] = triggers.map(t => ({\n type: t.type,\n threshold: t.threshold,\n unit: t.unit,\n entryCost,\n expiry: t.expiry,\n openDate: t.openDate,\n clockTime: t.clockTime,\n trailAmount: t.trailAmount,\n steps: t.steps,\n spreadWidth: t.spreadWidth,\n contracts: t.contracts,\n multiplier,\n underlyingPrices,\n vixPrices,\n vix9dPrices,\n }));\n\n // 5. Map leg groups with their triggers\n const legGroupConfigs: LegGroupConfig[] | undefined = leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n triggers: g.triggers.map(t => ({\n type: t.type,\n threshold: t.threshold,\n unit: t.unit,\n entryCost,\n expiry: t.expiry,\n openDate: t.openDate,\n clockTime: t.clockTime,\n trailAmount: t.trailAmount,\n steps: t.steps,\n spreadWidth: t.spreadWidth,\n contracts: t.contracts,\n multiplier,\n underlyingPrices,\n vixPrices,\n vix9dPrices,\n })),\n }));\n\n // 6. Run the pure analysis engine\n return analyzeExitTriggers({\n pnlPath,\n legs: replayLegs,\n triggers: exitTriggers,\n actualExitTimestamp: actual_exit_timestamp,\n legGroups: legGroupConfigs,\n });\n}\n\n// ---------------------------------------------------------------------------\n// handleDecomposeGreeks\n// ---------------------------------------------------------------------------\n\nexport async function handleDecomposeGreeks(\n params: z.infer<typeof decomposeGreeksSchema>,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<import(\"../utils/greeks-decomposition.ts\").GreeksDecompositionResult> {\n const {\n legs: inputLegs, block_id, trade_index,\n open_date, close_date, multiplier,\n leg_groups, format, skip_quotes,\n } = params;\n\n // 1. Run replay to get full P&L path with greeks\n const replayResult = await handleReplayTrade(\n {\n legs: inputLegs,\n block_id,\n trade_index,\n open_date,\n close_date,\n multiplier,\n format: 'full',\n close_at: 'trade',\n skip_quotes,\n },\n baseDir,\n stores,\n injectedConn,\n );\n\n const pnlPath = replayResult.pnlPath;\n const replayLegs = replayResult.legs;\n\n // 2. Check greeks data availability\n if (pnlPath.length > 0 && !pnlPath[0].legGreeks) {\n throw new Error(\n \"No greeks data available. Use the data-pipeline tools to backfill the option-leg quotes and underlying price data into the local cache.\"\n );\n }\n\n // 3. Reuse the underlying prices already resolved during replay.\n const underlyingPrices = new Map<string, number>();\n for (const point of pnlPath) {\n if (point.underlyingPrice !== undefined) {\n underlyingPrices.set(point.timestamp, point.underlyingPrice);\n }\n }\n\n // 4. Map leg groups\n const legGroupDefs: LegGroupDef[] | undefined = leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n }));\n\n // 5. Build leg pricing inputs from OCC tickers for full revaluation\n const DIVIDEND_YIELDS: Record<string, number> = {\n SPX: 0.015, SPXW: 0.015, NDX: 0.015, NDXP: 0.015,\n };\n const rootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);\n const rawRoot = rootMatch ? rootMatch[1] : '';\n const divYield = DIVIDEND_YIELDS[rawRoot] ?? 0;\n\n const legPricingInputs = replayLegs.map(leg => {\n const m = leg.occTicker.match(/^[A-Z]+(\\d{6})([CP])(\\d{8})$/);\n if (!m) return { strike: 0, type: 'C' as const, expiryDate: '' };\n return {\n strike: parseInt(m[3], 10) / 1000,\n type: m[2] as 'C' | 'P',\n expiryDate: `20${m[1].slice(0, 2)}-${m[1].slice(2, 4)}-${m[1].slice(4, 6)}`,\n };\n });\n\n // 6. Run decomposition with full revaluation\n const result = decomposeGreeks({\n pnlPath,\n legs: replayLegs,\n underlyingPrices: underlyingPrices.size > 0 ? underlyingPrices : undefined,\n legGroups: legGroupDefs,\n legPricingInputs,\n riskFreeRate: 0.045,\n dividendYield: divYield,\n });\n\n // 7. Strip steps if format=\"summary\"\n if (format === \"summary\") {\n for (const factor of result.factors) {\n factor.steps = [];\n }\n if (result.legGroupVega) {\n for (const group of result.legGroupVega) {\n group.steps = [];\n }\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerExitAnalysisTools(\n server: McpServer,\n baseDir: string,\n stores: MarketStores,\n): void {\n server.registerTool(\n \"analyze_exit_triggers\",\n {\n description:\n \"Analyze when exit triggers would fire on a trade replay. Runs replay internally \" +\n \"-- provide block_id + trade_index or explicit legs. Evaluates 14 trigger types \" +\n \"(profit target, stop loss, trailing stop, DTE, DIT, clock time, underlying move, \" +\n \"delta, VIX moves, S/L ratio) against minute-by-minute P&L path with greeks. \" +\n \"Reads VIX/VIX9D/underlying bars from SpotStore (cache only); triggers that need \" +\n \"missing data are silently skipped. Use the data-pipeline tools to backfill cache.\",\n inputSchema: analyzeExitTriggersSchema,\n },\n async (params) => {\n try {\n const result = await handleAnalyzeExitTriggers(params, baseDir, stores);\n\n const summary = result.overall.summary;\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error analyzing exit triggers: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n server.registerTool(\n \"decompose_greeks\",\n {\n description:\n \"Decompose a trade's P&L into greek factor contributions (delta, gamma, theta, \" +\n \"vega, residual). Runs replay internally. Shows which factor drove P&L movement \" +\n \"and by how much. For calendar/double-calendar strategies, includes per-leg-group \" +\n \"vega attribution showing front vs back month IV divergence. \" +\n \"Reads option-leg quotes via QuoteStore and underlying bars via SpotStore (cache only); \" +\n \"missing data yields a degenerate replay. Use the data-pipeline tools to backfill cache.\",\n inputSchema: decomposeGreeksSchema,\n },\n async (params) => {\n try {\n const result = await handleDecomposeGreeks(params, baseDir, stores);\n\n const summary = result.summary;\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error decomposing greeks: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * Batch Exit Analysis Engine\n *\n * Pure logic module (no I/O, no DuckDB, no fetch) that takes pre-analyzed\n * trade inputs and a candidate exit policy, evaluates the policy against each\n * trade's P&L path, and computes aggregate statistics with per-trigger attribution.\n *\n * This is the computational heart of the `batch_exit_analysis` MCP tool.\n */\n\nimport {\n analyzeExitTriggers,\n type ExitTriggerConfig,\n type TriggerType,\n type LegGroupConfig,\n type PartialClose,\n} from './exit-triggers.ts';\nimport type { PnlPoint, ReplayLeg } from './trade-replay.ts';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type BaselineMode = 'actual' | 'holdToEnd';\n\nexport interface BatchExitConfig {\n /** Triggers to evaluate as candidate exit policy. */\n candidatePolicy: ExitTriggerConfig[];\n /** Optional per-group triggers (passed through to analyzeExitTriggers). */\n legGroups?: LegGroupConfig[];\n /** Baseline mode: 'actual' uses tradelog P&L, 'holdToEnd' uses last path point. */\n baselineMode: BaselineMode;\n /** Output density: 'summary' omits per-trade breakdown; 'full' includes it. */\n format: 'summary' | 'full';\n}\n\nexport interface TradeInput {\n tradeIndex: number;\n /** Trade open date YYYY-MM-DD */\n dateOpened: string;\n /** Actual P&L from tradelog pl field (used when baselineMode='actual'). */\n actualPnl: number;\n /** Full replay P&L path from trade-replay module. */\n pnlPath: PnlPoint[];\n /** Replay legs parallel to pnlPath.legPrices. */\n legs: ReplayLeg[];\n /** Entry cost for percentage-based triggers (D-11). */\n entryCost?: number;\n}\n\nexport interface TradeExitResult {\n tradeIndex: number;\n dateOpened: string;\n /** Actual P&L from tradelog. */\n actualPnl: number;\n /** P&L if candidate policy was applied. */\n candidatePnl: number;\n /** Baseline P&L (actual or holdToEnd). */\n baselinePnl: number;\n /** candidatePnl - baselinePnl */\n pnlDelta: number;\n /** Which trigger fired first, or 'noTrigger'. */\n triggerFired: TriggerType | 'noTrigger';\n /** Timestamp when trigger fired, or null. */\n fireTimestamp: string | null;\n /** Partial position closes from profitAction steps (if any). */\n partialCloses?: PartialClose[];\n}\n\nexport interface TriggerAttribution {\n trigger: TriggerType | 'noTrigger';\n /** How many trades this trigger fired first on. */\n count: number;\n /** Average candidate P&L when this trigger fired. */\n avgPnl: number;\n /** Sum candidate P&L for this trigger group. */\n totalPnl: number;\n /** Average pnlDelta vs baseline for this trigger group. */\n avgDelta: number;\n}\n\nexport interface AggregateStats {\n totalTrades: number;\n /** candidatePnl > 0 */\n winningTrades: number;\n /** candidatePnl < 0 */\n losingTrades: number;\n /** winningTrades / totalTrades */\n winRate: number;\n /** Sum of candidatePnl */\n totalPnl: number;\n /** Mean candidatePnl */\n avgPnl: number;\n /** Mean of winning candidatePnls */\n avgWin: number;\n /** Mean of losing candidatePnls */\n avgLoss: number;\n maxWin: number;\n maxLoss: number;\n /** sum(wins) / abs(sum(losses)); Infinity if no losses */\n profitFactor: number;\n /** Max sequential drawdown from equity curve (cumsum of candidatePnls) */\n maxDrawdown: number;\n /** mean/stddev of candidatePnls; null if < 2 trades */\n sharpeRatio: number | null;\n maxWinStreak: number;\n maxLossStreak: number;\n // Deltas vs baseline\n baselineTotalPnl: number;\n /** totalPnl - baselineTotalPnl */\n totalPnlDelta: number;\n baselineWinRate: number;\n}\n\nexport interface BatchExitResult {\n aggregate: AggregateStats;\n triggerAttribution: TriggerAttribution[];\n /** Empty if format='summary'. */\n perTrade: TradeExitResult[];\n baselineMode: BaselineMode;\n summary: string;\n profileContext?: {\n structureType: string;\n exitRules: string[];\n };\n /** Trades skipped due to replay errors (D-15). */\n skippedTrades?: Array<{ tradeIndex: number; dateOpened: string; error: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// computeAggregateStats\n// ---------------------------------------------------------------------------\n\n/**\n * Compute aggregate statistics from a set of per-trade exit results.\n */\nexport function computeAggregateStats(tradeResults: TradeExitResult[]): AggregateStats {\n if (tradeResults.length === 0) {\n return {\n totalTrades: 0,\n winningTrades: 0,\n losingTrades: 0,\n winRate: 0,\n totalPnl: 0,\n avgPnl: 0,\n avgWin: 0,\n avgLoss: 0,\n maxWin: 0,\n maxLoss: 0,\n profitFactor: 0,\n maxDrawdown: 0,\n sharpeRatio: null,\n maxWinStreak: 0,\n maxLossStreak: 0,\n baselineTotalPnl: 0,\n totalPnlDelta: 0,\n baselineWinRate: 0,\n };\n }\n\n const candidatePnls = tradeResults.map(r => r.candidatePnl);\n const baselinePnls = tradeResults.map(r => r.baselinePnl);\n\n const winningTrades = candidatePnls.filter(p => p > 0).length;\n const losingTrades = candidatePnls.filter(p => p < 0).length;\n const totalTrades = tradeResults.length;\n const winRate = winningTrades / totalTrades;\n\n const totalPnl = candidatePnls.reduce((sum, p) => sum + p, 0);\n const avgPnl = totalPnl / totalTrades;\n\n const wins = candidatePnls.filter(p => p > 0);\n const losses = candidatePnls.filter(p => p < 0);\n\n const avgWin = wins.length > 0 ? wins.reduce((s, p) => s + p, 0) / wins.length : 0;\n const avgLoss = losses.length > 0 ? losses.reduce((s, p) => s + p, 0) / losses.length : 0;\n const maxWin = wins.length > 0 ? Math.max(...wins) : 0;\n const maxLoss = losses.length > 0 ? Math.min(...losses) : 0;\n\n // Profit factor: sum(wins) / abs(sum(losses)), Infinity if no losses\n const sumWins = wins.reduce((s, p) => s + p, 0);\n const sumLosses = losses.reduce((s, p) => s + p, 0);\n const profitFactor = losses.length === 0\n ? Infinity\n : sumWins / Math.abs(sumLosses);\n\n // Max drawdown from equity curve (cumsum of candidatePnls)\n let runningPeak = 0;\n let equity = 0;\n let maxDrawdown = 0;\n for (const pnl of candidatePnls) {\n equity += pnl;\n if (equity > runningPeak) runningPeak = equity;\n const dd = runningPeak - equity;\n if (dd > maxDrawdown) maxDrawdown = dd;\n }\n\n // Sharpe ratio: mean / sample stddev (N-1), null if < 2 trades\n let sharpeRatio: number | null = null;\n if (totalTrades >= 2) {\n const mean = avgPnl;\n const variance =\n candidatePnls.reduce((sum, p) => sum + (p - mean) ** 2, 0) / (totalTrades - 1);\n const stddev = Math.sqrt(variance);\n sharpeRatio = stddev === 0 ? null : mean / stddev;\n }\n\n // Win/loss streaks\n let maxWinStreak = 0;\n let maxLossStreak = 0;\n let currentWinStreak = 0;\n let currentLossStreak = 0;\n for (const pnl of candidatePnls) {\n if (pnl > 0) {\n currentWinStreak++;\n currentLossStreak = 0;\n if (currentWinStreak > maxWinStreak) maxWinStreak = currentWinStreak;\n } else if (pnl < 0) {\n currentLossStreak++;\n currentWinStreak = 0;\n if (currentLossStreak > maxLossStreak) maxLossStreak = currentLossStreak;\n } else {\n // Breakeven — reset both streaks\n currentWinStreak = 0;\n currentLossStreak = 0;\n }\n }\n\n // Baseline aggregates\n const baselineTotalPnl = baselinePnls.reduce((sum, p) => sum + p, 0);\n const baselineWins = baselinePnls.filter(p => p > 0).length;\n const baselineWinRate = baselineWins / totalTrades;\n\n return {\n totalTrades,\n winningTrades,\n losingTrades,\n winRate,\n totalPnl,\n avgPnl,\n avgWin,\n avgLoss,\n maxWin,\n maxLoss,\n profitFactor,\n maxDrawdown,\n sharpeRatio,\n maxWinStreak,\n maxLossStreak,\n baselineTotalPnl,\n totalPnlDelta: totalPnl - baselineTotalPnl,\n baselineWinRate,\n };\n}\n\n// ---------------------------------------------------------------------------\n// computeTriggerAttribution\n// ---------------------------------------------------------------------------\n\n/**\n * Group trade results by which trigger fired first.\n * Returns attribution sorted by count descending.\n */\nexport function computeTriggerAttribution(\n tradeResults: TradeExitResult[],\n): TriggerAttribution[] {\n const groups = new Map<\n TriggerType | 'noTrigger',\n { count: number; totalPnl: number; totalDelta: number }\n >();\n\n for (const result of tradeResults) {\n const key = result.triggerFired;\n const existing = groups.get(key);\n if (existing) {\n existing.count++;\n existing.totalPnl += result.candidatePnl;\n existing.totalDelta += result.pnlDelta;\n } else {\n groups.set(key, {\n count: 1,\n totalPnl: result.candidatePnl,\n totalDelta: result.pnlDelta,\n });\n }\n }\n\n return Array.from(groups.entries())\n .map(([trigger, { count, totalPnl, totalDelta }]) => ({\n trigger,\n count,\n totalPnl,\n avgPnl: totalPnl / count,\n avgDelta: totalDelta / count,\n }))\n .sort((a, b) => b.count - a.count);\n}\n\n// ---------------------------------------------------------------------------\n// analyzeBatch\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a candidate exit policy against a set of trade replay results.\n *\n * For each trade:\n * 1. Run analyzeExitTriggers with the candidate policy against the trade's P&L path.\n * 2. candidatePnl = firstToFire.pnlAtFire if trigger fired, else last path point P&L.\n * 3. baselinePnl = trade.actualPnl if baselineMode='actual', else last path point P&L.\n * 4. Build TradeExitResult.\n *\n * Then compute aggregate stats and trigger attribution.\n */\nexport function analyzeBatch(\n trades: TradeInput[],\n config: BatchExitConfig,\n): BatchExitResult {\n if (trades.length === 0) {\n const emptyAggregate = computeAggregateStats([]);\n return {\n aggregate: emptyAggregate,\n triggerAttribution: [],\n perTrade: [],\n baselineMode: config.baselineMode,\n summary: 'Analyzed 0 trades: no data.',\n };\n }\n\n const { candidatePolicy, legGroups, baselineMode, format } = config;\n\n const perTradeResults: TradeExitResult[] = trades.map(trade => {\n const { pnlPath, legs, actualPnl, tradeIndex, dateOpened, entryCost } = trade;\n\n // Last path point P&L — used as holdToEnd value\n const lastPnl = pnlPath.length > 0\n ? pnlPath[pnlPath.length - 1].strategyPnl\n : 0;\n\n // Copy entryCost onto each trigger config for percentage-based triggers (D-11)\n const triggersWithCost = candidatePolicy.map(t => ({\n ...t,\n entryCost,\n }));\n\n const legGroupsWithCost = legGroups?.map(group => ({\n ...group,\n triggers: group.triggers.map(trigger => ({\n ...trigger,\n entryCost,\n })),\n }));\n\n // Run exit trigger analysis with candidate policy\n const analysisResult = analyzeExitTriggers({\n pnlPath,\n legs,\n triggers: triggersWithCost,\n legGroups: legGroupsWithCost,\n });\n\n const { firstToFire, partialCloses } = analysisResult.overall;\n\n // Candidate P&L: account for partial closes from profitAction steps\n let candidatePnl: number;\n if (partialCloses && partialCloses.length > 0) {\n // Sum of partial close P&Ls\n const partialPnl = partialCloses.reduce((sum, pc) => sum + pc.pnlAtFire, 0);\n const closedAllocation = partialCloses.reduce((sum, pc) => sum + pc.allocation, 0);\n const remainingAllocation = 1 - closedAllocation;\n // Remaining position: firstToFire.pnlAtFire already reflects remaining allocation,\n // or if no trigger fired, scale last P&L by remaining allocation\n const remainingPnl = firstToFire !== null\n ? firstToFire.pnlAtFire\n : lastPnl * remainingAllocation;\n candidatePnl = partialPnl + remainingPnl;\n } else {\n // No partial closes: original behavior\n candidatePnl = firstToFire !== null ? firstToFire.pnlAtFire : lastPnl;\n }\n\n // Baseline P&L depends on mode\n const baselinePnl = baselineMode === 'actual' ? actualPnl : lastPnl;\n\n const pnlDelta = candidatePnl - baselinePnl;\n\n const triggerFired: TriggerType | 'noTrigger' =\n firstToFire !== null ? firstToFire.type : 'noTrigger';\n const fireTimestamp = firstToFire !== null ? firstToFire.firedAt : null;\n\n return {\n tradeIndex,\n dateOpened,\n actualPnl,\n candidatePnl,\n baselinePnl,\n pnlDelta,\n triggerFired,\n fireTimestamp,\n partialCloses: partialCloses && partialCloses.length > 0 ? partialCloses : undefined,\n };\n });\n\n const aggregate = computeAggregateStats(perTradeResults);\n const triggerAttribution = computeTriggerAttribution(perTradeResults);\n\n // Build summary string\n const topTrigger = triggerAttribution.length > 0 ? triggerAttribution[0] : null;\n const topTriggerStr = topTrigger\n ? `Top trigger: ${topTrigger.trigger} fired on ${topTrigger.count} trades.`\n : 'No triggers fired.';\n\n const summary =\n `Analyzed ${trades.length} trades: candidate win rate ${(aggregate.winRate * 100).toFixed(1)}%, ` +\n `total P&L $${aggregate.totalPnl.toFixed(2)} (delta $${aggregate.totalPnlDelta.toFixed(2)} vs baseline). ` +\n topTriggerStr;\n\n return {\n aggregate,\n triggerAttribution,\n perTrade: format === 'summary' ? [] : perTradeResults,\n baselineMode,\n summary,\n };\n}\n","/**\n * Batch Exit Analysis Tool\n *\n * MCP tool that evaluates a candidate exit policy across multiple trades in a\n * block. Queries trades from DuckDB, replays each one from the local\n * market-data cache, evaluates the candidate policy via the pure batch exit\n * analysis engine, and returns aggregate statistics with per-trigger\n * attribution.\n *\n * Tools registered:\n * - batch_exit_analysis -- Evaluate a candidate exit policy across an entire block\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport { handleReplayTrade } from \"./replay.ts\";\nimport {\n analyzeBatch,\n type TradeInput,\n type BatchExitConfig,\n type BatchExitResult,\n} from \"../utils/batch-exit-analysis.ts\";\nimport { getProfile } from \"../db/profile-schemas.ts\";\nimport type { ExitTriggerConfig, LegGroupConfig } from \"../utils/exit-triggers.ts\";\nimport type { MarketStores } from \"../market/stores/index.ts\";\n\n// ---------------------------------------------------------------------------\n// Concurrency limiter — hand-rolled semaphore, no external dependency (D-15)\n// ---------------------------------------------------------------------------\n\n/**\n * Simple concurrency limiter. Runs async tasks with at most `limit` in flight.\n * No external dependency — hand-rolled semaphore pattern per D-15.\n */\nasync function mapWithLimit<T, R>(\n items: T[],\n limit: number,\n fn: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let idx = 0;\n\n async function worker(): Promise<void> {\n while (idx < items.length) {\n const i = idx++;\n results[i] = await fn(items[i]);\n }\n }\n\n const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());\n await Promise.all(workers);\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Shared trigger type enum (mirrors exit-analysis.ts)\n// ---------------------------------------------------------------------------\n\nconst triggerTypeEnum = z.enum([\n 'profitTarget', 'stopLoss', 'trailingStop', 'profitAction',\n 'dteExit', 'ditExit', 'clockTimeExit',\n 'underlyingPriceMove', 'positionDelta', 'perLegDelta',\n 'vixMove', 'vix9dMove', 'vix9dVixRatio',\n 'slRatioThreshold', 'slRatioMove',\n]);\n\nconst triggerConfigSchema = z.object({\n type: triggerTypeEnum,\n threshold: z.number(),\n unit: z.enum(['percent', 'dollar']).default('dollar').optional(),\n expiry: z.string().optional(),\n openDate: z.string().optional(),\n clockTime: z.string().optional(),\n trailAmount: z.number().optional(),\n steps: z.array(z.object({\n armAt: z.number(),\n stopAt: z.number(),\n closeAllocationPct: z.number().min(0).max(1).optional()\n .describe(\"Fraction of REMAINING position to close at this milestone (0-1)\"),\n })).optional(),\n spreadWidth: z.number().optional(),\n contracts: z.number().optional(),\n legIndex: z.number().optional()\n .describe(\"0-based leg index for perLegDelta — targets specific leg\"),\n exitAbove: z.number().optional()\n .describe(\"Fire when value exceeds this (directional, no abs)\"),\n exitBelow: z.number().optional()\n .describe(\"Fire when value drops below this (directional, no abs)\"),\n});\n\n// ---------------------------------------------------------------------------\n// Zod Schema\n// ---------------------------------------------------------------------------\n\nexport const batchExitAnalysisSchema = z.object({\n block_id: z.string().describe(\"Block ID to analyze trades from\"),\n\n strategy: z.string().optional()\n .describe(\"Filter trades by strategy name (case-insensitive ILIKE)\"),\n\n date_range: z.object({\n from: z.string().optional().describe(\"Start date YYYY-MM-DD\"),\n to: z.string().optional().describe(\"End date YYYY-MM-DD\"),\n }).optional().describe(\"Filter trades by date range\"),\n\n candidate_policy: z.array(triggerConfigSchema)\n .describe(\"Candidate exit policy triggers to evaluate -- same schema as analyze_exit_triggers\"),\n\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n triggers: z.array(triggerConfigSchema),\n })).optional().describe(\"Per-leg-group exit triggers for multi-structure strategies\"),\n\n baseline_mode: z.enum(['actual', 'holdToEnd']).default('actual')\n .describe(\"'actual' compares candidate vs trade's actual P&L; 'holdToEnd' compares vs last replay timestamp\"),\n\n limit: z.number().min(1).max(200).default(50)\n .describe(\"Max trades to analyze. Most recent trades selected\"),\n\n min_pl: z.number().optional()\n .describe(\"Only include trades with actual P&L >= this value\"),\n\n max_pl: z.number().optional()\n .describe(\"Only include trades with actual P&L <= this value\"),\n\n multiplier: z.number().default(100)\n .describe(\"Contract multiplier (default 100)\"),\n\n format: z.enum(['summary', 'full']).default('summary')\n .describe(\"'summary' returns aggregate stats + trigger attribution; 'full' adds per-trade breakdown\"),\n});\n\n// ---------------------------------------------------------------------------\n// Handler\n// ---------------------------------------------------------------------------\n\nexport async function handleBatchExitAnalysis(\n params: z.infer<typeof batchExitAnalysisSchema>,\n baseDir: string,\n stores: MarketStores, // Phase 4 CONSUMER-01 — threaded through for Wave 2+ rewrite.\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<BatchExitResult> {\n const {\n block_id,\n strategy,\n date_range,\n candidate_policy,\n leg_groups,\n baseline_mode,\n limit,\n min_pl,\n max_pl,\n multiplier,\n format,\n } = params;\n\n // 1. Query trades from DuckDB with deterministic ROW_NUMBER ordering\n const conn = injectedConn ?? await getConnection(baseDir);\n const escapedBlockId = block_id.replace(/'/g, \"''\");\n\n // Build WHERE clauses\n const whereClauses: string[] = [`block_id = '${escapedBlockId}'`];\n\n if (strategy) {\n const escapedStrategy = strategy.replace(/'/g, \"''\");\n whereClauses.push(`strategy ILIKE '%${escapedStrategy}%'`);\n }\n if (date_range?.from) {\n whereClauses.push(`date_opened >= '${date_range.from}'`);\n }\n if (date_range?.to) {\n whereClauses.push(`date_opened <= '${date_range.to}'`);\n }\n if (min_pl !== undefined) {\n whereClauses.push(`pl >= ${min_pl}`);\n }\n if (max_pl !== undefined) {\n whereClauses.push(`pl <= ${max_pl}`);\n }\n\n // ROW_NUMBER must be computed over the FULL block (no strategy/date filters)\n // because handleReplayTrade resolves trade_index as OFFSET against the full block.\n // Filters are applied AFTER numbering to preserve the global index.\n const filterClauses = whereClauses.slice(1); // drop block_id clause (already in CTE)\n const query = `\n WITH numbered AS (\n SELECT *, ROW_NUMBER() OVER (ORDER BY date_opened, rowid) - 1 AS trade_idx\n FROM trades.trade_data\n WHERE block_id = '${escapedBlockId}'\n )\n SELECT trade_idx, pl, date_opened\n FROM numbered\n ${filterClauses.length > 0 ? 'WHERE ' + filterClauses.join(' AND ') : ''}\n ORDER BY date_opened DESC\n LIMIT ${limit}\n `;\n\n const queryResult = await conn.runAndReadAll(query);\n const rows = queryResult.getRows();\n\n if (rows.length === 0) {\n const emptyResult: BatchExitResult = {\n aggregate: {\n totalTrades: 0,\n winningTrades: 0,\n losingTrades: 0,\n winRate: 0,\n totalPnl: 0,\n avgPnl: 0,\n avgWin: 0,\n avgLoss: 0,\n maxWin: 0,\n maxLoss: 0,\n profitFactor: 0,\n maxDrawdown: 0,\n sharpeRatio: null,\n maxWinStreak: 0,\n maxLossStreak: 0,\n baselineTotalPnl: 0,\n totalPnlDelta: 0,\n baselineWinRate: 0,\n },\n triggerAttribution: [],\n perTrade: [],\n baselineMode: baseline_mode,\n summary: 'Analyzed 0 trades: no matching trades found.',\n };\n return emptyResult;\n }\n\n // 2. Replay trades in parallel with concurrency limit (D-14)\n const MAX_CONCURRENT_REPLAYS = 5;\n\n type ReplayOutcome =\n | { ok: true; input: TradeInput }\n | { ok: false; tradeIndex: number; dateOpened: string; error: string };\n\n const outcomes = await mapWithLimit(\n rows,\n MAX_CONCURRENT_REPLAYS,\n async (row): Promise<ReplayOutcome> => {\n const tradeIdx = Number(row[0] ?? 0);\n const pl = Number(row[1] ?? 0);\n const dateOpened = String(row[2] ?? '');\n\n try {\n // Always pass format:'full' to get complete pnlPath for analyzeBatch.\n // params.format controls the batch output density, not the replay resolution.\n const replayResult = await handleReplayTrade(\n {\n block_id,\n trade_index: tradeIdx,\n multiplier,\n format: 'full',\n close_at: 'trade',\n skip_quotes: false,\n },\n baseDir,\n stores,\n injectedConn,\n );\n\n // Compute entry cost for percentage-based triggers (D-11)\n const tradeEntryCost = replayResult.legs.reduce((sum: number, leg) => {\n return sum + leg.entryPrice * leg.quantity * leg.multiplier;\n }, 0);\n\n return {\n ok: true,\n input: {\n tradeIndex: tradeIdx,\n dateOpened,\n actualPnl: pl,\n pnlPath: replayResult.pnlPath,\n legs: replayResult.legs,\n entryCost: tradeEntryCost,\n },\n };\n } catch (err) {\n return {\n ok: false,\n tradeIndex: Number(row[0] ?? 0),\n dateOpened: String(row[2] ?? ''),\n error: err instanceof Error ? err.message : String(err),\n };\n }\n },\n );\n\n const tradeInputs: TradeInput[] = [];\n const skippedTrades: Array<{ tradeIndex: number; dateOpened: string; error: string }> = [];\n\n for (const outcome of outcomes) {\n if (outcome.ok) {\n tradeInputs.push(outcome.input);\n } else {\n skippedTrades.push({\n tradeIndex: outcome.tradeIndex,\n dateOpened: outcome.dateOpened,\n error: outcome.error,\n });\n }\n }\n\n // 3. Build BatchExitConfig\n const config: BatchExitConfig = {\n candidatePolicy: candidate_policy as ExitTriggerConfig[],\n legGroups: leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n triggers: g.triggers as ExitTriggerConfig[],\n })) as LegGroupConfig[] | undefined,\n baselineMode: baseline_mode,\n format,\n };\n\n // 4. Run the pure batch analysis engine\n const result = analyzeBatch(tradeInputs, config);\n\n // 5. Augment summary with skip info if any trades were skipped\n if (skippedTrades.length > 0) {\n result.summary = result.summary.replace(\n /^Analyzed (\\d+) trades/,\n `Analyzed ${tradeInputs.length} trades (${skippedTrades.length} skipped due to replay errors)`,\n );\n result.skippedTrades = skippedTrades;\n }\n\n // 6. Load profile context if strategy is specified (per D-16)\n if (strategy) {\n try {\n const profileConn = injectedConn ?? await getConnection(baseDir);\n const profile = await getProfile(profileConn, block_id, strategy, baseDir);\n if (profile) {\n result.profileContext = {\n structureType: profile.structureType,\n exitRules: profile.exitRules.map(r =>\n r.description ?? `${r.type} ${r.trigger}`\n ),\n };\n }\n } catch {\n // Profile is informational context, not critical — swallow errors\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerBatchExitAnalysisTools(\n server: McpServer,\n baseDir: string,\n stores: MarketStores, // Phase 4 CONSUMER-01 — threaded through for Wave 2+ rewrite.\n): void {\n server.registerTool(\n \"batch_exit_analysis\",\n {\n description:\n \"Analyze how a candidate exit policy would perform across multiple trades in a block. \" +\n \"Replays each matching trade, evaluates exit triggers against the minute-level P&L path, \" +\n \"and returns aggregate statistics (win rate, Sharpe, profit factor, drawdown) comparable \" +\n \"to get_statistics. Includes per-trigger attribution showing which triggers drive outcomes. \" +\n \"Reads option-leg quotes via QuoteStore and underlying bars via SpotStore (cache only); \" +\n \"trades with missing data are skipped. Use the data-pipeline tools to backfill cache, \" +\n \"and strategy profiles to iterate on exit rules.\",\n inputSchema: batchExitAnalysisSchema,\n },\n async (params) => {\n try {\n const result = await handleBatchExitAnalysis(params, baseDir, stores);\n return createToolOutput(result.summary, result);\n } catch (error) {\n return {\n content: [{\n type: \"text\" as const,\n text: `Error in batch exit analysis: ${(error as Error).message}`,\n }],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * quote-enricher.ts\n *\n * Pure helpers for option-quote enrichment planning.\n *\n * The I/O fetch loop (`enrichQuotesForTickers`) and its coverage-probe\n * helper (`fetchExistingCoverage`) were deleted — reads no longer trigger\n * provider fetches. Every caller routes through `backfillQuotes(stores, ...)`\n * from `utils/quote-backfill.ts`.\n *\n * Surviving public surface (this file):\n * - shouldSkipEnrichment(barCount, threshold) pure density check\n * - buildEnrichmentPlan(input) pure planner — groups\n * (ticker, date) combos\n * needing enrichment. Reused\n * by backfillQuotes.\n * - QuoteEnrichmentResult result-shape type — preserved\n * because internal callers may\n * still reference it; structurally\n * identical to BackfillQuotesResult\n * from quote-backfill.ts.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentPlanItem {\n ticker: string;\n date: string;\n existingBarCount: number;\n}\n\n/**\n * Result shape returned by the (deleted) `enrichQuotesForTickers` function.\n * Every call site has been switched to `backfillQuotes`, whose result type\n * (`BackfillQuotesResult`) is structurally identical. This alias is kept\n * as a stable internal type name; callers may import either.\n */\nexport interface QuoteEnrichmentResult {\n tickersProcessed: number;\n tickersSkipped: number; // already dense\n rowsWritten: number;\n errors: Array<{ ticker: string; date: string; error: string }>;\n unsupportedReason?: string;\n}\n\nexport interface EnrichmentPlanInput {\n tickers: Array<{ ticker: string; fromDate: string; toDate: string }>;\n existingCoverage: Map<string, number>; // \"ticker:date\" → barCount\n providerSupportsQuotes: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Pure functions\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if ticker/date already has dense enough data (>= densityThreshold bars).\n * Default threshold is 200 bars/day — roughly half the 390 market minutes, indicating\n * that quote enrichment has already been applied.\n */\nexport function shouldSkipEnrichment(barCount: number, densityThreshold = 200): boolean {\n return barCount >= densityThreshold;\n}\n\n/**\n * Build a list of ticker+date combos that need enrichment.\n *\n * Rules:\n * - Returns empty if provider doesn't support quotes\n * - Returns empty if tickers list is empty\n * - Expands each ticker's fromDate→toDate range into individual dates\n * - Skips dates where existing coverage is already dense (>= 200 bars)\n *\n * This is a pure function — no I/O, takes coverage as a pre-fetched Map.\n */\nexport function buildEnrichmentPlan(input: EnrichmentPlanInput): EnrichmentPlanItem[] {\n if (!input.providerSupportsQuotes) return [];\n if (input.tickers.length === 0) return [];\n\n const plan: EnrichmentPlanItem[] = [];\n\n for (const tickerSpec of input.tickers) {\n const dates = expandDateRange(tickerSpec.fromDate, tickerSpec.toDate);\n for (const date of dates) {\n const key = `${tickerSpec.ticker}:${date}`;\n const existingBarCount = input.existingCoverage.get(key) ?? 0;\n if (!shouldSkipEnrichment(existingBarCount)) {\n plan.push({\n ticker: tickerSpec.ticker,\n date,\n existingBarCount,\n });\n }\n }\n }\n\n return plan;\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Expand a date range into individual YYYY-MM-DD dates (inclusive, calendar days).\n */\nfunction expandDateRange(fromDate: string, toDate: string): string[] {\n const dates: string[] = [];\n const from = new Date(fromDate + 'T00:00:00Z');\n const to = new Date(toDate + 'T00:00:00Z');\n const current = new Date(from);\n while (current <= to) {\n dates.push(current.toISOString().slice(0, 10));\n current.setUTCDate(current.getUTCDate() + 1);\n }\n return dates;\n}\n\n// ---------------------------------------------------------------------------\n// The `enrichQuotesForTickers` throw-stub has been deleted. All callers now\n// route through `backfillQuotes(stores, ...)` from `utils/quote-backfill.ts`.\n// ---------------------------------------------------------------------------\n","/**\n * SQL Query Tool\n *\n * Provides direct SQL query access to the DuckDB analytics database.\n * Enables ad-hoc analysis, hypothesis testing, and data exploration\n * across trades and market data.\n *\n * Security:\n * - SELECT queries run freely; DELETE/UPDATE require confirm=true\n * - No file access functions (read_csv, write_csv, etc.)\n * - No schema modifications (CREATE, ALTER, DROP)\n * - 30-second query timeout with clear error message\n *\n * Available tables:\n * - trades.trade_data: Trade records from all blocks (includes inferred ticker)\n * - trades.reporting_data: Reporting/actual trade records from strategy logs\n * - market.spot: Minute bars, ticker-first layout (indicators source)\n * - market.spot_daily: RTH-aggregated daily OHLCV view over market.spot (ticker, date)\n * - market.enriched: Daily technical indicators (RSI, ATR, IVR/IVP, etc.), ticker-first\n * - market.enriched_context: Cross-ticker derived fields (Vol_Regime, Term_Structure_State, etc.)\n * - market.option_chain: Option contract metadata keyed by (underlying, date)\n * - market.option_quote_minutes: Option NBBO minute bars keyed by (underlying, date)\n * - market._sync_metadata: Import/enrichment tracking metadata\n */\n\nimport * as path from \"path\";\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport { getConnection, upgradeToReadWrite, downgradeToReadOnly } from \"../db/connection.ts\";\nimport { getDataRoot } from \"../db/data-root.ts\";\nimport { withFullSync } from \"./middleware/sync-middleware.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\n\n/**\n * Available tables for reference in error messages\n */\nconst AVAILABLE_TABLES = [\n \"trades.trade_data\",\n \"trades.reporting_data\",\n \"market.spot\",\n \"market.spot_daily\",\n \"market.enriched\",\n \"market.enriched_context\",\n \"market.option_chain\",\n \"market.option_quote_minutes\",\n \"market._sync_metadata\",\n];\n\n/**\n * Always-blocked SQL patterns — external access, writes, and config changes.\n */\nconst BLOCKED_PATTERNS: Array<{ pattern: RegExp; operation: string }> = [\n // External access\n { pattern: /\\bCOPY\\b/i, operation: \"COPY\" },\n { pattern: /\\bEXPORT\\b/i, operation: \"EXPORT\" },\n { pattern: /\\bATTACH\\b/i, operation: \"ATTACH\" },\n { pattern: /\\bDETACH\\b/i, operation: \"DETACH\" },\n\n // File functions that write or read arbitrary text\n { pattern: /\\bread_text\\s*\\(/i, operation: \"read_text()\" },\n { pattern: /\\bwrite_csv\\s*\\(/i, operation: \"write_csv()\" },\n\n // Configuration changes (standalone SET, not UPDATE ... SET)\n { pattern: /^\\s*SET\\b/i, operation: \"SET\" },\n];\n\n/**\n * File-read functions allowed only when every path argument resolves under\n * --data-root. Lets debug queries inspect managed Parquet/CSV/JSON while\n * preventing filesystem traversal via SQL.\n */\nconst PATH_GATED_READ_FUNCTIONS = [\"read_parquet\", \"read_csv\", \"read_json\"] as const;\n\ninterface FileReadCall {\n fn: string;\n paths: string[];\n}\n\nfunction findMatchingParen(s: string, openIdx: number): number {\n let depth = 0;\n for (let i = openIdx; i < s.length; i++) {\n if (s[i] === \"(\") depth++;\n else if (s[i] === \")\") {\n depth--;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/**\n * Scan SQL for path-gated file-read calls. Returns null if any call can't\n * be parsed safely — the caller should treat null as a block verdict.\n */\nfunction extractFileReadCalls(sql: string): FileReadCall[] | null {\n const calls: FileReadCall[] = [];\n for (const fn of PATH_GATED_READ_FUNCTIONS) {\n const nameRegex = new RegExp(`\\\\b${fn}\\\\s*\\\\(`, \"gi\");\n let m: RegExpExecArray | null;\n while ((m = nameRegex.exec(sql)) !== null) {\n const openIdx = m.index + m[0].length - 1;\n const closeIdx = findMatchingParen(sql, openIdx);\n if (closeIdx === -1) return null;\n const args = sql.slice(openIdx + 1, closeIdx);\n const paths: string[] = [];\n const strRegex = /(['\"])((?:\\\\.|(?!\\1).)*)\\1/g;\n let s: RegExpExecArray | null;\n while ((s = strRegex.exec(args)) !== null) {\n paths.push(s[2]);\n }\n if (paths.length === 0) return null;\n calls.push({ fn, paths });\n }\n }\n return calls;\n}\n\n/**\n * Is the given path under dataRoot? Strips glob characters from the end so\n * patterns like `<root>/market/spot/ ** /*.parquet` validate against their\n * literal prefix. Resolves both paths absolutely and compares with a\n * separator guard to prevent `<root>-evil` from matching `<root>`.\n */\nexport function isUnderDataRoot(filePath: string, dataRoot: string): boolean {\n const globMatch = filePath.match(/[*?[]/);\n const prefix = globMatch?.index !== undefined ? filePath.slice(0, globMatch.index) : filePath;\n const resolved = path.resolve(prefix);\n const resolvedRoot = path.resolve(dataRoot);\n const rootWithSep = resolvedRoot.endsWith(path.sep) ? resolvedRoot : resolvedRoot + path.sep;\n return resolved === resolvedRoot || resolved.startsWith(rootWithSep);\n}\n\n/**\n * Default and maximum query timeout in milliseconds\n */\nconst DEFAULT_TIMEOUT_MS = 30000;\n\n/**\n * Maximum rows that can be returned\n */\nconst MAX_ROWS = 1000;\n\n/**\n * Mutating patterns that require confirm=true.\n * Without confirm, returns a preview of what would be affected.\n */\nconst CONFIRM_REQUIRED_PATTERNS: Array<{ pattern: RegExp; operation: string }> = [\n { pattern: /\\bDELETE\\b/i, operation: \"DELETE\" },\n { pattern: /\\bUPDATE\\b/i, operation: \"UPDATE\" },\n { pattern: /\\bINSERT\\b/i, operation: \"INSERT\" },\n { pattern: /\\bTRUNCATE\\b/i, operation: \"TRUNCATE\" },\n { pattern: /\\bDROP\\b/i, operation: \"DROP\" },\n { pattern: /\\bCREATE\\b/i, operation: \"CREATE\" },\n { pattern: /\\bALTER\\b/i, operation: \"ALTER\" },\n];\n\n/**\n * Validate SQL query for dangerous patterns.\n * Returns null if valid, or an error message if invalid.\n */\nexport function validateQuery(sql: string, dataRoot: string): string | null {\n for (const { pattern, operation } of BLOCKED_PATTERNS) {\n if (pattern.test(sql)) {\n return `${operation} operations are not allowed.`;\n }\n }\n\n const calls = extractFileReadCalls(sql);\n if (calls === null) {\n return \"File-read function calls could not be parsed safely. Use the market.* views instead.\";\n }\n for (const call of calls) {\n for (const p of call.paths) {\n if (!isUnderDataRoot(p, dataRoot)) {\n return `${call.fn}() path must be under --data-root: ${p}`;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Validate a user-supplied SELECT passed into import_flat_file.\n *\n * Keeps the hard blocks on external access, writes, and config changes —\n * but relaxes the read_parquet/read_csv/read_json path gate because the\n * purpose of the import tool is to pull data from arbitrary source files\n * the LLM has been pointed at. The output location is sandboxed by the\n * store's partition-path composer (data-root-relative, whitelisted\n * partition values), so a malicious SELECT can only pollute the store\n * it's writing to — it cannot exfiltrate or write outside the data root.\n */\nexport function validateImportSelect(sql: string): string | null {\n for (const { pattern, operation } of BLOCKED_PATTERNS) {\n if (pattern.test(sql)) {\n return `${operation} operations are not allowed in select_sql.`;\n }\n }\n if (/^\\s*SELECT\\b/i.test(sql) === false && /^\\s*WITH\\b/i.test(sql) === false) {\n return \"select_sql must be a SELECT or WITH statement.\";\n }\n return null;\n}\n\n/**\n * Check if a query is destructive (DELETE/UPDATE) and needs confirmation.\n */\nfunction isDestructiveQuery(sql: string): { destructive: boolean; operation: string } {\n for (const { pattern, operation } of CONFIRM_REQUIRED_PATTERNS) {\n if (pattern.test(sql)) {\n return { destructive: true, operation };\n }\n }\n return { destructive: false, operation: \"\" };\n}\n\n/**\n * Convert a DELETE/UPDATE statement to a SELECT COUNT(*) preview query.\n * DELETE FROM table WHERE ... → SELECT COUNT(*) as affected_rows FROM table WHERE ...\n * UPDATE table SET ... WHERE ... → SELECT COUNT(*) as affected_rows FROM table WHERE ...\n */\nfunction toPreviewQuery(sql: string): string {\n const trimmed = sql.trim().replace(/;$/, \"\");\n\n // DELETE FROM table WHERE ...\n const deleteMatch = trimmed.match(/^\\s*DELETE\\s+FROM\\s+(.+?)(?:\\s+WHERE\\s+(.+))?$/is);\n if (deleteMatch) {\n const table = deleteMatch[1].trim();\n const where = deleteMatch[2] ? ` WHERE ${deleteMatch[2]}` : \"\";\n return `SELECT COUNT(*) as affected_rows FROM ${table}${where}`;\n }\n\n // UPDATE table SET ... WHERE ...\n const updateMatch = trimmed.match(/^\\s*UPDATE\\s+(\\S+)\\s+.*?(?:WHERE\\s+(.+))?$/is);\n if (updateMatch) {\n const table = updateMatch[1].trim();\n const where = updateMatch[2] ? ` WHERE ${updateMatch[2]}` : \"\";\n return `SELECT COUNT(*) as affected_rows FROM ${table}${where}`;\n }\n\n return `SELECT 'Could not generate preview' as warning`;\n}\n\n/**\n * Check if query already has a LIMIT clause\n */\nfunction hasLimitClause(sql: string): boolean {\n // Match LIMIT at word boundary, not inside a string literal\n // This is a simple check - complex queries with LIMIT in subqueries\n // will still pass, which is fine (better to let DuckDB handle it)\n return /\\bLIMIT\\s+\\d+/i.test(sql);\n}\n\n/**\n * Query result with column metadata\n */\ninterface QueryResult {\n rows: Record<string, unknown>[];\n columns: Array<{ name: string; type: string }>;\n totalRows: number;\n}\n\n/**\n * Execute a SQL query with timeout protection.\n *\n * @param conn - DuckDB connection\n * @param sql - SQL query to execute\n * @param limit - Maximum rows to return\n * @param timeoutMs - Timeout in milliseconds\n * @returns Query results with column metadata\n */\nasync function executeWithTimeout(\n conn: DuckDBConnection,\n sql: string,\n limit: number,\n timeoutMs: number = DEFAULT_TIMEOUT_MS\n): Promise<QueryResult> {\n // Add LIMIT if not present\n let finalSql = sql.trim();\n if (finalSql.endsWith(\";\")) {\n finalSql = finalSql.slice(0, -1);\n }\n\n if (!hasLimitClause(finalSql)) {\n finalSql = `${finalSql} LIMIT ${limit}`;\n }\n\n // Create timeout promise\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(\n new Error(\n \"Query exceeded 30s timeout. Consider adding LIMIT or filtering by block_id.\"\n )\n );\n }, timeoutMs);\n });\n\n // Execute query with timeout\n const queryPromise = (async (): Promise<QueryResult> => {\n const result = await conn.runAndReadAll(finalSql);\n\n // Extract column metadata\n const columnCount = result.columnCount;\n const columns: Array<{ name: string; type: string }> = [];\n\n for (let i = 0; i < columnCount; i++) {\n columns.push({\n name: result.columnName(i),\n type: result.columnType(i).toString(),\n });\n }\n\n // Convert rows to objects\n const rows: Record<string, unknown>[] = [];\n for (const row of result.getRows()) {\n const obj: Record<string, unknown> = {};\n for (let i = 0; i < columnCount; i++) {\n const value = row[i];\n // Convert BigInt to Number for JSON serialization\n obj[columns[i].name] = typeof value === \"bigint\" ? Number(value) : value;\n }\n rows.push(obj);\n }\n\n return {\n rows,\n columns,\n totalRows: rows.length,\n };\n })();\n\n return Promise.race([queryPromise, timeoutPromise]);\n}\n\n/**\n * Enhance error messages with helpful suggestions.\n *\n * @param error - Original error\n * @returns Enhanced error message\n */\nfunction enhanceError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n\n // Table not found - suggest available tables\n if (\n message.toLowerCase().includes(\"table\") &&\n (message.toLowerCase().includes(\"not found\") ||\n message.toLowerCase().includes(\"does not exist\") ||\n message.toLowerCase().includes(\"catalog error\"))\n ) {\n return `${message}\\n\\nAvailable tables:\\n${AVAILABLE_TABLES.map((t) => ` - ${t}`).join(\"\\n\")}`;\n }\n\n // Column not found - suggest DESCRIBE\n if (\n message.toLowerCase().includes(\"column\") &&\n (message.toLowerCase().includes(\"not found\") ||\n message.toLowerCase().includes(\"does not exist\") ||\n message.toLowerCase().includes(\"binder error\"))\n ) {\n return `${message}\\n\\nTip: Use DESCRIBE trades.trade_data; to see available columns.`;\n }\n\n // Timeout messages are already helpful\n if (message.includes(\"timeout\")) {\n return message;\n }\n\n // Pass through other errors (syntax errors include line/column info from DuckDB)\n return message;\n}\n\n/**\n * Register SQL query tools with the MCP server.\n *\n * @param server - MCP server instance\n * @param baseDir - Base directory for data files\n */\nexport function registerSQLTools(server: McpServer, baseDir: string): void {\n server.registerTool(\n \"run_sql\",\n {\n description:\n \"Execute a SQL query against the DuckDB analytics database. \" +\n \"SELECT runs freely. All mutating operations (DELETE, UPDATE, INSERT, CREATE, ALTER, DROP, TRUNCATE) \" +\n \"require confirm=true — without it, returns a preview or confirmation prompt. \" +\n \"Query trades (trades.trade_data, trades.reporting_data) and market data \" +\n \"(market.spot, market.enriched, market.enriched_context, market.spot_daily, \" +\n \"market.option_chain, market.option_quote_minutes, market._sync_metadata). \" +\n \"Trade queries should filter by block_id (e.g. WHERE block_id = 'my-strategy'). \" +\n \"Call describe_database first to discover available block_ids and column names. \" +\n \"Returns up to 1000 rows for SELECT queries.\",\n inputSchema: z.object({\n query: z.string().describe(\"SQL query to execute\"),\n limit: z\n .number()\n .min(1)\n .max(MAX_ROWS)\n .default(100)\n .describe(`Maximum rows to return (default: 100, max: ${MAX_ROWS})`),\n confirm: z\n .boolean()\n .default(false)\n .describe(\"Required for all mutating operations (DELETE, UPDATE, INSERT, CREATE, ALTER, DROP, TRUNCATE). Without it, returns a preview or prompt.\"),\n }),\n },\n withFullSync(baseDir, async ({ query, limit, confirm }) => {\n // Validate query for dangerous patterns\n const validationError = validateQuery(query, getDataRoot(baseDir));\n if (validationError) {\n return {\n content: [{ type: \"text\" as const, text: validationError }],\n isError: true as const,\n };\n }\n\n // Check if mutating — require confirm\n const { destructive, operation } = isDestructiveQuery(query);\n if (destructive && !confirm) {\n // For DELETE/UPDATE, try to preview affected row count\n if (operation === \"DELETE\" || operation === \"UPDATE\") {\n try {\n const conn = await getConnection(baseDir);\n const previewSql = toPreviewQuery(query);\n const result = await executeWithTimeout(conn, previewSql, 1);\n const count = result.rows[0]?.affected_rows ?? \"unknown\";\n return createToolOutput(\n `⚠️ ${operation} would affect ${count} row(s). Re-run with confirm=true to execute.`,\n { operation, affectedRows: count, query, preview: true },\n );\n } catch (error) {\n return createToolOutput(\n `⚠️ ${operation} requires confirm=true. Could not preview: ${error instanceof Error ? error.message : String(error)}`,\n { operation, query, preview: true },\n );\n }\n }\n // For other mutating ops (INSERT, CREATE, ALTER, DROP, TRUNCATE) — no preview, just gate\n return createToolOutput(\n `⚠️ ${operation} requires confirm=true to execute.`,\n { operation, query, preview: true },\n );\n }\n\n try {\n // Get DuckDB connection\n const conn = await getConnection(baseDir);\n\n if (destructive && confirm) {\n // Upgrade to read-write for mutations\n await upgradeToReadWrite(baseDir);\n try {\n const rwConn = await getConnection(baseDir);\n const result = await rwConn.run(query);\n const changed = Number(result.rowsChanged);\n return createToolOutput(\n `${operation} completed: ${changed} row(s) affected.`,\n { operation, rowsAffected: changed },\n );\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n }\n\n // Execute SELECT query with timeout\n const result = await executeWithTimeout(conn, query, limit);\n\n // Determine if results were truncated\n const returnedRows = result.rows.length;\n const truncated = returnedRows >= limit;\n\n // Create summary\n const summary = `Query returned ${returnedRows} row(s)${truncated ? ` (limited to ${limit})` : \"\"}`;\n\n // Return structured output\n return createToolOutput(summary, {\n rows: result.rows,\n columns: result.columns,\n totalRows: result.totalRows,\n returnedRows,\n truncated,\n });\n } catch (error) {\n const enhancedMessage = enhanceError(error);\n return {\n content: [{ type: \"text\" as const, text: enhancedMessage }],\n isError: true as const,\n };\n }\n })\n );\n}\n","/**\n * chain-loader.ts\n *\n * Pure helpers for option chain filtering and deduplication.\n *\n * The three-step cache-lifecycle fetch path is gone — reads never trigger\n * provider fetches. Per-date chain reads now flow through\n * `stores.chain.readChain(underlying, date)` (ChainStore API). Empty array\n * is the skip signal — the legacy `ChainSkipResult` / `isChainSkip`\n * type-guard pair has been deleted along with the SQL builders that backed\n * the cache lookups.\n *\n * Surviving public surface (this file):\n * - filterChain(contracts, filter) pure DTE / contract-type filter\n * - deduplicateContracts(contracts) pure SPX/SPXW collision resolver\n * - ContractRow type single source of truth for the\n * on-the-wire contract shape (also\n * re-exported from market/stores/types.ts)\n *\n * Transitional surface (deprecated, scheduled for removal):\n * - ChainLoadResult interface { contracts: ContractRow[], source: 'cache' }\n * preserved until downstream consumers\n * are rewritten to accept `ContractRow[]`\n * directly.\n *\n * Anything not listed above (loadChain, loadChainsBulk, buildCachedChainQuery,\n * optionChainPartitionSource, chainColumnsSql, chainRowFromSql, ChainResult,\n * ChainSkipResult, ChainSkipReason, isChainSkip) was deleted as part of the\n * ChainStore migration.\n */\n\n// ---------------------------------------------------------------------------\n// Exported types\n// ---------------------------------------------------------------------------\n\nexport interface ContractRow {\n underlying: string;\n date: string;\n ticker: string;\n contract_type: \"call\" | \"put\";\n strike: number;\n expiration: string;\n dte: number;\n exercise_style: string;\n}\n\n/**\n * Transitional shape — see file header. The `source` field is fixed to\n * `'cache'` because reads no longer fetch from a provider. This interface\n * is scheduled for removal once downstream consumers are switched to plain\n * `ContractRow[]`.\n *\n * Do NOT add new code that constructs ChainLoadResult; use ContractRow[].\n */\nexport interface ChainLoadResult {\n contracts: ContractRow[];\n source: \"cache\";\n}\n\n// ---------------------------------------------------------------------------\n// Filter types and pure functions\n// ---------------------------------------------------------------------------\n\nexport interface ChainFilterOptions {\n dte_min?: number;\n dte_max?: number;\n contract_type?: \"call\" | \"put\";\n}\n\n/**\n * Deduplicate contracts that share the same (contract_type, strike, expiration).\n * On monthly SPX expirations, Polygon returns both SPX and SPXW tickers for the\n * same contract. The SPX (non-W) ticker often lacks bars on expiration day itself,\n * causing 0DTE trades to skip with \"no_bars_available\". Prefer SPXW over SPX.\n *\n * Exported so downstream consumers (entry-resolver, candidate selector) can\n * dedupe ContractRow lists they construct from cache reads.\n */\nexport function deduplicateContracts(contracts: ContractRow[]): ContractRow[] {\n const map = new Map<string, ContractRow>();\n for (const c of contracts) {\n const key = `${c.contract_type}|${c.strike}|${c.expiration}`;\n const existing = map.get(key);\n if (!existing) {\n map.set(key, c);\n } else if (c.ticker.startsWith('SPXW') && !existing.ticker.startsWith('SPXW')) {\n map.set(key, c);\n }\n }\n return Array.from(map.values());\n}\n\n/**\n * Filter an array of ContractRow by DTE range and/or contract type.\n * Filtering happens post-cache so the full chain is cached once, filtered many times.\n * Also deduplicates SPX/SPXW ticker collisions (see deduplicateContracts).\n */\nexport function filterChain(contracts: ContractRow[], filter: ChainFilterOptions): ContractRow[] {\n const filtered = contracts.filter((c) => {\n if (filter.dte_min != null && c.dte < filter.dte_min) return false;\n if (filter.dte_max != null && c.dte > filter.dte_max) return false;\n if (filter.contract_type != null && c.contract_type !== filter.contract_type) return false;\n return true;\n });\n return deduplicateContracts(filtered);\n}\n\n// ---------------------------------------------------------------------------\n// Transitional throw-stubs (deprecated, scheduled for removal)\n//\n// The cache-miss fetch path (Massive HTTP + INSERT OR REPLACE INTO\n// market.option_chain) is gone. The named symbols below survive ONLY as\n// throw-stubs to keep downstream consumers compiling until they have been\n// rewritten to use `stores.chain.readChain(...)` directly.\n//\n// These stubs MUST NEVER be invoked at runtime. They exist purely so static\n// type-checking and Jest module-graph resolution succeed in the interim.\n// ---------------------------------------------------------------------------\n\n/**\n * @deprecated Removed in the ChainStore migration. Use\n * `stores.chain.readChain(underlying, date)` instead — empty array is the\n * new skip signal (replaces `ChainSkipResult`). This stub throws at runtime\n * to make accidental callers loud and will be deleted once consumers have\n * been rewritten.\n */\nexport async function loadChain(\n _underlying: string,\n _asOfDate: string,\n _conn: unknown,\n _opts?: { dataDir?: string; maxDte?: number },\n): Promise<ChainLoadResult> {\n throw new Error(\n \"chain-loader.loadChain has been removed. \" +\n \"Use stores.chain.readChain(underlying, date) instead — empty array is the new skip signal.\",\n );\n}\n\n/**\n * @deprecated Removed in the ChainStore migration. Use a\n * `for (const date of dates)` loop with `stores.chain.readChain(underlying, date)`\n * instead. This stub throws at runtime and will be deleted once consumers\n * have been rewritten.\n */\nexport async function loadChainsBulk(\n _underlying: string,\n _dates: string[],\n _conn: unknown,\n _opts?: { dataDir?: string },\n): Promise<Map<string, ChainLoadResult>> {\n throw new Error(\n \"chain-loader.loadChainsBulk has been removed. \" +\n \"Use a per-date loop with stores.chain.readChain(underlying, date) instead.\",\n );\n}\n","import { open, mkdir } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nexport interface BackfillProjectionInput {\n requestCount: number;\n avgLatencyMs: number;\n concurrency: number;\n}\n\nexport interface BackfillRequestCountInput {\n partitionCount: number;\n contractCount: number;\n}\n\nexport interface BackfillBandRequestCountInput {\n bandGroupCount: number;\n fallbackContractCount?: number;\n}\n\nexport interface BackfillParsedOccTicker {\n ticker: string;\n symbol: string;\n expiration: string;\n right: \"call\" | \"put\";\n strike: number;\n strikeText: string;\n}\n\nexport interface BackfillGreekBandGroup {\n key: string;\n symbol: string;\n expiration: string;\n date: string;\n contracts: BackfillParsedOccTicker[];\n}\n\nexport interface BackfillStagedGreekRow {\n ticker: string;\n timestamp: string;\n}\n\nexport interface BackfillConcreteFallback {\n contract: BackfillParsedOccTicker;\n missingTimes: string[];\n}\n\nexport interface BackfillConcreteFallbackInput {\n group: BackfillGreekBandGroup;\n fallbackUncoveredContracts?: boolean;\n expectedTimesByTicker: ReadonlyMap<string, ReadonlySet<string>>;\n stagedRows: BackfillStagedGreekRow[];\n}\n\nexport type BackfillManifestStatus =\n | \"prepared\"\n | \"committed\"\n | \"failed\"\n | \"committed_manifest_failed\";\n\nexport interface BackfillManifestEntry {\n status: BackfillManifestStatus;\n partitionPath: string;\n underlying: string;\n date: string;\n rowCountBefore: number;\n rowCountAfter: number;\n providerFirstOrderRows: number;\n computedFallbackRows: number;\n nullGreekRows: number;\n endpointErrors: string[];\n startedAt: string;\n completedAt: string;\n}\n\nexport interface BackfillRewriteSelectInput {\n existingTable: string;\n providerGreeksTable: string;\n}\n\nexport function backfillManifestPath(dataRoot: string, runId: string): string {\n return join(\n dataRoot,\n \"market\",\n \"_manifests\",\n \"thetadata-mdds-backfill\",\n `${runId}.ndjson`,\n );\n}\n\nexport function backfillPartitionPath(\n dataRoot: string,\n underlying: string,\n date: string,\n): string {\n return join(\n dataRoot,\n \"market\",\n \"option_quote_minutes\",\n `underlying=${normalizeUnderlying(underlying)}`,\n `date=${validateDate(date)}`,\n \"data.parquet\",\n );\n}\n\nexport function backfillShadowPartitionPath(partitionPath: string): string {\n if (!partitionPath.trim()) {\n throw new Error(\"partitionPath must not be empty\");\n }\n return `${partitionPath}.shadow`;\n}\n\nexport function makeBackfillRunId(now = new Date()): string {\n if (Number.isNaN(now.getTime())) {\n throw new Error(\"run id date must be valid\");\n }\n return now.toISOString().replace(/[-:.]/g, \"\");\n}\n\nexport function enumerateBackfillDates(from: string, to: string): string[] {\n const startText = validateDate(from);\n const endText = validateDate(to);\n const cursor = parseIsoDate(startText);\n const end = parseIsoDate(endText);\n if (cursor > end) {\n throw new Error(\"from must be on or before to\");\n }\n\n const dates: string[] = [];\n while (cursor <= end) {\n dates.push(formatIsoDate(cursor));\n cursor.setUTCDate(cursor.getUTCDate() + 1);\n }\n return dates;\n}\n\nexport function estimateBackfillRequestCount(input: BackfillRequestCountInput): number {\n assertNonNegativeInteger(\"partitionCount\", input.partitionCount);\n assertNonNegativeInteger(\"contractCount\", input.contractCount);\n return input.partitionCount * input.contractCount;\n}\n\nexport function estimateBackfillBandRequestCount(input: BackfillBandRequestCountInput): number {\n assertNonNegativeInteger(\"bandGroupCount\", input.bandGroupCount);\n const fallbackContractCount = input.fallbackContractCount ?? 0;\n assertNonNegativeInteger(\"fallbackContractCount\", fallbackContractCount);\n return input.bandGroupCount + fallbackContractCount;\n}\n\nexport function projectBackfillWallTimeHours(input: BackfillProjectionInput): number {\n assertPositiveFinite(\"requestCount\", input.requestCount);\n assertPositiveFinite(\"avgLatencyMs\", input.avgLatencyMs);\n assertFinite(\"concurrency\", input.concurrency);\n\n return (input.requestCount * input.avgLatencyMs) / Math.max(1, input.concurrency) / 3_600_000;\n}\n\nexport function makeBackfillManifestEntry(entry: BackfillManifestEntry): BackfillManifestEntry {\n return {\n status: validateManifestStatus(entry.status),\n partitionPath: requireNonEmpty(\"partitionPath\", entry.partitionPath),\n underlying: normalizeUnderlying(entry.underlying),\n date: validateDate(entry.date),\n rowCountBefore: sanitizeCount(\"rowCountBefore\", entry.rowCountBefore),\n rowCountAfter: sanitizeCount(\"rowCountAfter\", entry.rowCountAfter),\n providerFirstOrderRows: sanitizeCount(\"providerFirstOrderRows\", entry.providerFirstOrderRows),\n computedFallbackRows: sanitizeCount(\"computedFallbackRows\", entry.computedFallbackRows),\n nullGreekRows: sanitizeCount(\"nullGreekRows\", entry.nullGreekRows),\n endpointErrors: entry.endpointErrors.map((error) => String(error).trim()).filter(Boolean),\n startedAt: validateIsoTimestamp(\"startedAt\", entry.startedAt),\n completedAt: validateIsoTimestamp(\"completedAt\", entry.completedAt),\n };\n}\n\nexport function formatBackfillManifestLine(entry: BackfillManifestEntry): string {\n return `${JSON.stringify(makeBackfillManifestEntry(entry))}\\n`;\n}\n\nexport function parseBackfillOccTicker(ticker: string): BackfillParsedOccTicker {\n const normalizedTicker = requireNonEmpty(\"ticker\", ticker).toUpperCase();\n const match = normalizedTicker.match(/^([A-Z]+)(\\d{2})(\\d{2})(\\d{2})(C|P)(\\d{8})$/);\n if (!match) {\n throw new Error(`Invalid OCC option ticker: ${ticker}`);\n }\n\n const strike = Number.parseInt(match[6], 10) / 1000;\n return {\n ticker: normalizedTicker,\n symbol: match[1],\n expiration: `20${match[2]}-${match[3]}-${match[4]}`,\n right: match[5] === \"C\" ? \"call\" : \"put\",\n strike,\n strikeText: strike.toFixed(3),\n };\n}\n\nexport function groupBackfillTickersByGreekBand(\n tickers: string[],\n date: string,\n): BackfillGreekBandGroup[] {\n const validatedDate = validateDate(date);\n const byKey = new Map<string, BackfillGreekBandGroup & {\n contractByTicker: Map<string, BackfillParsedOccTicker>;\n }>();\n\n for (const ticker of tickers) {\n const contract = parseBackfillOccTicker(ticker);\n const key = `${contract.symbol}|${contract.expiration}|${validatedDate}`;\n let group = byKey.get(key);\n if (!group) {\n group = {\n key,\n symbol: contract.symbol,\n expiration: contract.expiration,\n date: validatedDate,\n contracts: [],\n contractByTicker: new Map(),\n };\n byKey.set(key, group);\n }\n group.contractByTicker.set(contract.ticker, contract);\n }\n\n return [...byKey.values()]\n .map((group) => ({\n key: group.key,\n symbol: group.symbol,\n expiration: group.expiration,\n date: group.date,\n contracts: [...group.contractByTicker.values()].sort((left, right) =>\n left.ticker.localeCompare(right.ticker)\n ),\n }))\n .sort((left, right) => left.key.localeCompare(right.key));\n}\n\nexport function collectBackfillConcreteFallbacks(\n input: BackfillConcreteFallbackInput,\n): BackfillConcreteFallback[] {\n const coveredTimesByTicker = new Map<string, Set<string>>();\n for (const row of input.stagedRows) {\n const ticker = requireNonEmpty(\"staged ticker\", row.ticker).toUpperCase();\n const time = row.timestamp.slice(11, 16);\n if (!time) continue;\n let coveredTimes = coveredTimesByTicker.get(ticker);\n if (!coveredTimes) {\n coveredTimes = new Set();\n coveredTimesByTicker.set(ticker, coveredTimes);\n }\n coveredTimes.add(time);\n }\n\n const fallbacks: BackfillConcreteFallback[] = [];\n for (const contract of input.group.contracts) {\n const expectedTimes = input.expectedTimesByTicker.get(contract.ticker) ?? new Set<string>();\n const coveredTimes = coveredTimesByTicker.get(contract.ticker) ?? new Set<string>();\n if (coveredTimes.size === 0 && !input.fallbackUncoveredContracts) {\n continue;\n }\n const missingTimes = [...expectedTimes]\n .filter((time) => !coveredTimes.has(time))\n .sort();\n if (missingTimes.length > 0) {\n fallbacks.push({ contract, missingTimes });\n }\n }\n return fallbacks;\n}\n\nexport async function appendBackfillManifestLineDurable(\n manifestPath: string,\n line: string,\n): Promise<void> {\n const path = requireNonEmpty(\"manifestPath\", manifestPath);\n const text = String(line);\n if (!text.endsWith(\"\\n\")) {\n throw new Error(\"manifest line must end with a newline\");\n }\n const parentDir = dirname(path);\n await mkdir(parentDir, { recursive: true });\n const existedBeforeOpen = await pathExists(path);\n const handle = await open(path, \"a\");\n try {\n await handle.writeFile(text);\n await handle.sync();\n } finally {\n await handle.close();\n }\n if (!existedBeforeOpen) {\n await fsyncDirectoryBestEffort(parentDir);\n }\n}\n\nexport function backfillRewriteSelectSql(input: BackfillRewriteSelectInput): string {\n const existingTable = validateSqlIdentifier(input.existingTable);\n const providerGreeksTable = validateSqlIdentifier(input.providerGreeksTable);\n return `\n SELECT\n CAST(e.underlying AS VARCHAR) AS underlying,\n CAST(e.date AS VARCHAR) AS date,\n CAST(e.ticker AS VARCHAR) AS ticker,\n CAST(e.time AS VARCHAR) AS time,\n CAST(e.bid AS DOUBLE) AS bid,\n CAST(e.ask AS DOUBLE) AS ask,\n CAST(e.mid AS DOUBLE) AS mid,\n CAST(e.last_updated_ns AS BIGINT) AS last_updated_ns,\n CAST(e.source AS VARCHAR) AS source,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.delta ELSE e.delta END AS REAL) AS delta,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.gamma ELSE e.gamma END AS REAL) AS gamma,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.theta ELSE e.theta END AS REAL) AS theta,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.vega ELSE e.vega END AS REAL) AS vega,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.iv ELSE e.iv END AS REAL) AS iv,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.greeks_source ELSE e.greeks_source END AS VARCHAR) AS greeks_source,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.greeks_revision ELSE e.greeks_revision END AS INTEGER) AS greeks_revision,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.rate_type ELSE e.rate_type END AS VARCHAR) AS rate_type,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.rate_value ELSE e.rate_value END AS DOUBLE) AS rate_value,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.gamma_source ELSE e.gamma_source END AS VARCHAR) AS gamma_source\n FROM ${existingTable} e\n LEFT JOIN ${providerGreeksTable} g\n ON e.ticker = g.ticker\n AND e.time = g.time\n `.trim();\n}\n\nfunction assertPositiveFinite(name: string, value: number): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(`${name} must be a positive finite number`);\n }\n}\n\nfunction assertFinite(name: string, value: number): void {\n if (!Number.isFinite(value)) {\n throw new Error(`${name} must be a finite number`);\n }\n}\n\nfunction assertNonNegativeInteger(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 0) {\n throw new Error(`${name} must be a non-negative integer`);\n }\n}\n\nfunction sanitizeCount(name: string, value: number): number {\n assertNonNegativeInteger(name, value);\n return value;\n}\n\nfunction validateManifestStatus(value: BackfillManifestStatus): BackfillManifestStatus {\n if (\n value === \"prepared\"\n || value === \"committed\"\n || value === \"failed\"\n || value === \"committed_manifest_failed\"\n ) {\n return value;\n }\n throw new Error(\"status must be prepared, committed, failed, or committed_manifest_failed\");\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n const handle = await open(path, \"r\");\n await handle.close();\n return true;\n } catch {\n return false;\n }\n}\n\nasync function fsyncDirectoryBestEffort(dirPath: string): Promise<void> {\n let handle: Awaited<ReturnType<typeof open>> | undefined;\n try {\n handle = await open(dirPath, \"r\");\n await handle.sync();\n } catch {\n // Some platforms/filesystems do not support directory fsync. Linux does,\n // and failures elsewhere should not make manifest append unusable.\n } finally {\n if (handle) await handle.close().catch(() => {});\n }\n}\n\nfunction requireNonEmpty(name: string, value: string): string {\n const text = String(value ?? \"\").trim();\n if (!text) throw new Error(`${name} must not be empty`);\n return text;\n}\n\nfunction normalizeUnderlying(value: string): string {\n return requireNonEmpty(\"underlying\", value).toUpperCase();\n}\n\nfunction validateSqlIdentifier(value: string): string {\n const text = requireNonEmpty(\"sql identifier\", value);\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(text)) {\n throw new Error(\"sql identifier must contain only letters, numbers, and underscores\");\n }\n return text;\n}\n\nfunction validateDate(value: string): string {\n const text = requireNonEmpty(\"date\", value);\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(text)) {\n throw new Error(\"date must use YYYY-MM-DD\");\n }\n const parsed = parseIsoDate(text);\n if (Number.isNaN(parsed.getTime()) || formatIsoDate(parsed) !== text) {\n throw new Error(\"date must be a valid calendar date\");\n }\n return text;\n}\n\nfunction validateIsoTimestamp(name: string, value: string): string {\n const text = requireNonEmpty(name, value);\n const parsed = new Date(text);\n if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== text) {\n throw new Error(`${name} must be an ISO timestamp`);\n }\n return text;\n}\n\nfunction parseIsoDate(date: string): Date {\n return new Date(`${date}T12:00:00.000Z`);\n}\n\nfunction formatIsoDate(date: Date): string {\n const yyyy = date.getUTCFullYear();\n const mm = String(date.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(date.getUTCDate()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}`;\n}\n","/**\n * greeks-attribution.ts\n *\n * MCP tool: get_greeks_attribution\n *\n * Decomposes a block's P&L into Greek components. Two modes:\n * - summary: block-level attribution percentages across all trades\n * - instance: single trade time-series of Greek P&L contributions\n */\n\nimport { z } from \"zod\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { handleDecomposeGreeks } from \"./exit-analysis.ts\";\nimport type { FactorContribution } from \"../utils/greeks-decomposition.ts\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport { tradingDays } from \"../utils/flatfile-importer.ts\";\nimport type { MarketStores } from \"../market/stores/index.ts\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface AttributionEntry {\n factor: string;\n pnl: number;\n pct: number;\n pct_of_gross?: number;\n}\n\nexport interface AttributionSummaryResult {\n block_id: string;\n trades_decomposed: number;\n trades_skipped: number;\n trades_total: number;\n total_pnl: number;\n mark_total_pnl: number;\n execution_edge: number;\n gross_attribution_flow: number;\n attribution: AttributionEntry[];\n precision: \"high\" | \"low\";\n hint?: string;\n}\n\nexport interface AttributionStepEntry {\n date: string;\n delta: number;\n gamma: number;\n theta: number;\n vega: number;\n residual: number;\n time_and_vol?: number;\n charm?: number;\n vanna?: number;\n}\n\nexport interface AttributionInstanceResult {\n block_id: string;\n trade_index: number;\n trade_date: string;\n total_pnl: number;\n mark_total_pnl: number;\n execution_edge: number;\n gross_attribution_flow: number;\n steps: AttributionStepEntry[];\n attribution: AttributionEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nexport const getGreeksAttributionSchema = z.object({\n block_id: z.string().describe(\"Block ID to analyze\"),\n mode: z\n .enum([\"summary\", \"instance\"])\n .default(\"summary\")\n .describe(\"summary: block-level attribution. instance: single trade time-series.\"),\n trade_index: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe(\"Trade index (required for instance mode). Use get_block_info to find trade indices.\"),\n skip_quotes: z\n .boolean()\n .default(true)\n .describe(\"Use cached bar data only (fast). Set false to fetch NBBO quotes for higher precision.\"),\n detailed: z\n .boolean()\n .default(false)\n .describe(\"false: 5 factors (delta, gamma, theta, vega, residual). true: adds charm, vanna.\"),\n strategy: z\n .string()\n .optional()\n .describe(\"Filter to trades matching this strategy name (case-insensitive).\"),\n});\n\n// ---------------------------------------------------------------------------\n// Pure functions\n// ---------------------------------------------------------------------------\n\nconst COLLAPSE_MAP: Record<string, string> = {\n charm: \"delta\",\n vanna: \"vega\",\n};\n\nconst FACTOR_ORDER: string[] = [\"theta\", \"vega\", \"delta\", \"gamma\", \"residual\", \"time_and_vol\", \"charm\", \"vanna\"];\n\nexport function collapseFactors(\n factors: FactorContribution[],\n detailed: boolean,\n): Map<string, number> {\n const totals = new Map<string, number>();\n for (const f of factors) {\n const targetName = (!detailed && COLLAPSE_MAP[f.factor]) || f.factor;\n totals.set(targetName, (totals.get(targetName) ?? 0) + f.totalPnl);\n }\n return totals;\n}\n\nexport function computeAttribution(\n totals: Map<string, number>,\n totalPnl: number,\n grossAttributionFlow?: number,\n): AttributionEntry[] {\n const entries: AttributionEntry[] = [];\n for (const [factor, pnl] of totals) {\n entries.push({\n factor,\n pnl: Math.round(pnl * 100) / 100,\n pct: totalPnl !== 0 ? Math.round((pnl / totalPnl) * 1000) / 10 : 0,\n ...(grossAttributionFlow && grossAttributionFlow !== 0\n ? { pct_of_gross: Math.round((pnl / grossAttributionFlow) * 1000) / 10 }\n : {}),\n });\n }\n entries.sort((a, b) => {\n const ai = FACTOR_ORDER.indexOf(a.factor);\n const bi = FACTOR_ORDER.indexOf(b.factor);\n return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);\n });\n return entries;\n}\n\nexport function computeGrossAttributionFlow(totals: Map<string, number>): number {\n let gross = 0;\n for (const pnl of totals.values()) {\n gross += Math.abs(pnl);\n }\n return gross;\n}\n\nexport function assessPrecision(\n residualPnl: number,\n totalPnl: number,\n): { precision: \"high\" | \"low\"; hint?: string } {\n if (totalPnl === 0) return { precision: \"high\" };\n const residualPct = Math.abs(residualPnl / totalPnl) * 100;\n if (residualPct > 25) {\n return {\n precision: \"low\",\n hint: `Residual is ${Math.round(residualPct)}%. Re-run with skip_quotes=false for NBBO-based pricing.`,\n };\n }\n return { precision: \"high\" };\n}\n\n// ---------------------------------------------------------------------------\n// Handler\n// ---------------------------------------------------------------------------\n\nexport async function handleGetGreeksAttribution(\n params: z.infer<typeof getGreeksAttributionSchema>,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<AttributionSummaryResult | AttributionInstanceResult> {\n const { block_id, mode, trade_index, skip_quotes, detailed, strategy } = params;\n\n if (mode === \"instance\") {\n if (trade_index == null) {\n throw new Error(\"trade_index is required for instance mode\");\n }\n return handleInstanceMode(block_id, trade_index, skip_quotes, detailed, baseDir, stores, injectedConn);\n }\n\n return handleSummaryMode(block_id, skip_quotes, detailed, strategy, baseDir, stores, injectedConn);\n}\n\nasync function handleSummaryMode(\n block_id: string,\n skip_quotes: boolean,\n detailed: boolean,\n strategy: string | undefined,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<AttributionSummaryResult> {\n const conn = injectedConn ?? await getConnection(baseDir);\n\n const selectedTradesQuery = strategy\n ? `SELECT trade_index, pl\n FROM (\n SELECT ROW_NUMBER() OVER (ORDER BY date_opened, rowid) - 1 AS trade_index, pl, strategy\n FROM trades.trade_data\n WHERE block_id = $1\n )\n WHERE LOWER(strategy) = LOWER($2)\n ORDER BY trade_index`\n : `SELECT ROW_NUMBER() OVER (ORDER BY date_opened, rowid) - 1 AS trade_index, pl\n FROM trades.trade_data\n WHERE block_id = $1\n ORDER BY trade_index`;\n const selectedTradesParams = strategy ? [block_id, strategy] : [block_id];\n const selectedTradesResult = await conn.runAndReadAll(selectedTradesQuery, selectedTradesParams);\n const selectedTrades = selectedTradesResult.getRows().map((row) => ({\n tradeIndex: Number(row[0] ?? 0),\n actualPl: Number(row[1] ?? 0),\n }));\n const totalTrades = selectedTrades.length;\n\n if (totalTrades === 0) {\n throw new Error(\n strategy\n ? `No trades found for block \"${block_id}\" with strategy \"${strategy}\"`\n : `No trades found for block \"${block_id}\"`\n );\n }\n\n const accumulated = new Map<string, number>();\n let decomposed = 0;\n let skipped = 0;\n let actualTotalPnl = 0;\n let markTotalPnl = 0;\n\n // Process trades in concurrent batches for performance.\n // DuckDB supports concurrent reads; the replay engine is I/O-bound (bar cache lookups).\n const BATCH_SIZE = 10;\n for (let batch = 0; batch < totalTrades; batch += BATCH_SIZE) {\n const batchEnd = Math.min(batch + BATCH_SIZE, totalTrades);\n const promises = [];\n for (let i = batch; i < batchEnd; i++) {\n const trade = selectedTrades[i];\n promises.push(\n handleDecomposeGreeks(\n {\n block_id,\n trade_index: trade.tradeIndex,\n format: \"summary\",\n multiplier: 100,\n skip_quotes,\n },\n baseDir,\n stores,\n injectedConn,\n ).then(result => {\n for (const factor of result.factors) {\n accumulated.set(factor.factor, (accumulated.get(factor.factor) ?? 0) + factor.totalPnl);\n }\n actualTotalPnl += trade.actualPl;\n markTotalPnl += result.totalPnlChange;\n decomposed++;\n }).catch(() => {\n skipped++;\n })\n );\n }\n await Promise.allSettled(promises);\n }\n\n if (decomposed === 0) {\n return {\n block_id,\n trades_decomposed: 0,\n trades_skipped: skipped,\n trades_total: totalTrades,\n total_pnl: 0,\n mark_total_pnl: 0,\n execution_edge: 0,\n gross_attribution_flow: 0,\n attribution: [],\n precision: \"low\",\n hint: \"No trades could be decomposed. Ensure market data is cached for the trade dates.\",\n };\n }\n\n const collapsed = collapseFactors(\n [...accumulated.entries()].map(([factor, totalPnl]) => ({\n factor: factor as FactorContribution[\"factor\"],\n totalPnl,\n pctOfTotal: 0,\n steps: [],\n })),\n detailed,\n );\n\n const grossAttributionFlow = computeGrossAttributionFlow(collapsed);\n const attribution = computeAttribution(collapsed, actualTotalPnl, grossAttributionFlow);\n const residualPnl = collapsed.get(\"residual\") ?? 0;\n const precisionBase = grossAttributionFlow !== 0 ? grossAttributionFlow : markTotalPnl;\n const { precision, hint } = assessPrecision(residualPnl, precisionBase);\n const executionEdge = actualTotalPnl - markTotalPnl;\n\n // Warn when the execution edge dwarfs actual P&L — signals sparse or\n // low-quality market data rather than genuine fill advantage.\n const hints: string[] = [];\n if (hint) hints.push(hint);\n const edgeRatio = Math.abs(actualTotalPnl) > 0.01\n ? Math.abs(executionEdge) / Math.abs(actualTotalPnl)\n : 0;\n if (edgeRatio > 3) {\n hints.push(\n `Execution edge is ${Math.round(edgeRatio)}x the actual P&L — ` +\n `mark-to-market pricing may be based on sparse or low-quality data. ` +\n (skip_quotes\n ? `Re-run with skip_quotes=false for NBBO-based marks.`\n : `Consider whether intraday bar coverage is sufficient for this date range.`)\n );\n }\n\n return {\n block_id,\n trades_decomposed: decomposed,\n trades_skipped: skipped,\n trades_total: totalTrades,\n total_pnl: Math.round(actualTotalPnl * 100) / 100,\n mark_total_pnl: Math.round(markTotalPnl * 100) / 100,\n execution_edge: Math.round(executionEdge * 100) / 100,\n gross_attribution_flow: Math.round(grossAttributionFlow * 100) / 100,\n attribution,\n precision,\n ...(hints.length > 0 ? { hint: hints.join(' ') } : {}),\n };\n}\n\nasync function handleInstanceMode(\n block_id: string,\n trade_index: number,\n skip_quotes: boolean,\n detailed: boolean,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<AttributionInstanceResult> {\n const conn = injectedConn ?? await getConnection(baseDir);\n\n // Get trade date for the response\n const tradeResult = await conn.runAndReadAll(\n `SELECT date_opened, date_closed, pl FROM trades.trade_data\n WHERE block_id = $1\n ORDER BY date_opened, rowid\n LIMIT 1 OFFSET $2`,\n [block_id, trade_index]\n );\n const tradeRows = tradeResult.getRows();\n if (tradeRows.length === 0) {\n throw new Error(`Trade index ${trade_index} not found in block \"${block_id}\"`);\n }\n const tradeDate = String(tradeRows[0][0] ?? \"\");\n const closeDate = String(tradeRows[0][1] ?? tradeDate);\n const actualPnl = Number(tradeRows[0][2] ?? 0);\n\n // Run decomposition with full step data\n const result = await handleDecomposeGreeks(\n {\n block_id,\n trade_index,\n format: \"full\",\n multiplier: 100,\n skip_quotes,\n },\n baseDir,\n stores,\n injectedConn,\n );\n\n // Build per-step entries from factor step arrays\n const stepCount = result.stepCount;\n\n // Map step indices → dates via a pure Mon-Fri trading-day iterator.\n // Replays operate over date ranges with dense intraday coverage, so the\n // weekday iteration matches the set of dates the decomposition produced\n // (one step per trading day). `conn` is still used above for the trade\n // row fetch — only the date probe is store-free.\n const tradingDates = tradingDays(tradeDate, closeDate);\n const getStepDate = (i: number): string =>\n i < tradingDates.length ? tradingDates[i] : `day-${i}`;\n\n // Build factor lookup for quick access to step arrays\n const factorSteps = new Map<string, number[]>();\n for (const f of result.factors) {\n factorSteps.set(f.factor, f.steps);\n }\n\n // Pivot: for each step, collect contributions from all factors\n const steps: AttributionStepEntry[] = [];\n for (let i = 0; i <= stepCount; i++) {\n const entry: AttributionStepEntry = {\n date: getStepDate(i),\n delta: getStepValue(factorSteps, \"delta\", i, detailed ? 0 : (factorSteps.get(\"charm\")?.[i] ?? 0)),\n gamma: getStepValue(factorSteps, \"gamma\", i, 0),\n theta: getStepValue(factorSteps, \"theta\", i, 0),\n vega: getStepValue(factorSteps, \"vega\", i, detailed ? 0 : (factorSteps.get(\"vanna\")?.[i] ?? 0)),\n residual: getStepValue(factorSteps, \"residual\", i, 0),\n };\n // time_and_vol: present when numerical fallback was used (theta/vega couldn't be separated)\n if (factorSteps.has(\"time_and_vol\")) {\n entry.time_and_vol = getStepValue(factorSteps, \"time_and_vol\", i, 0);\n }\n if (detailed) {\n entry.charm = factorSteps.get(\"charm\")?.[i] ?? 0;\n entry.vanna = factorSteps.get(\"vanna\")?.[i] ?? 0;\n }\n steps.push(entry);\n }\n\n // Compute total attribution for this trade\n const collapsed = collapseFactors(result.factors, detailed);\n const grossAttributionFlow = computeGrossAttributionFlow(collapsed);\n const attribution = computeAttribution(collapsed, actualPnl, grossAttributionFlow);\n const executionEdge = actualPnl - result.totalPnlChange;\n\n const filteredSteps = filterSparseSteps(steps);\n\n return {\n block_id,\n trade_index,\n trade_date: tradeDate,\n total_pnl: Math.round(actualPnl * 100) / 100,\n mark_total_pnl: Math.round(result.totalPnlChange * 100) / 100,\n execution_edge: Math.round(executionEdge * 100) / 100,\n gross_attribution_flow: Math.round(grossAttributionFlow * 100) / 100,\n steps: filteredSteps,\n attribution,\n };\n}\n\n/**\n * Remove steps where all Greek contributions are zero (no market data for that bar).\n * Keeps the output compact and useful.\n */\nexport function filterSparseSteps(steps: AttributionStepEntry[]): AttributionStepEntry[] {\n return steps.filter(s =>\n s.delta !== 0 || s.gamma !== 0 || s.theta !== 0 || s.vega !== 0 ||\n s.residual !== 0 || (s.time_and_vol ?? 0) !== 0 ||\n (s.charm ?? 0) !== 0 || (s.vanna ?? 0) !== 0\n );\n}\n\nfunction getStepValue(\n factorSteps: Map<string, number[]>,\n factor: string,\n index: number,\n collapsedAddition: number,\n): number {\n return Math.round(((factorSteps.get(factor)?.[index] ?? 0) + collapsedAddition) * 100) / 100;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerGreeksAttributionTools(\n server: McpServer,\n baseDir: string,\n stores: MarketStores,\n): void {\n server.registerTool(\n \"get_greeks_attribution\",\n {\n description:\n \"Decompose a block's P&L into Greek components (delta, gamma, theta, vega). \" +\n \"Summary mode: attribution percentages across all trades — reveals what drives the strategy. \" +\n \"Instance mode: per-day Greek P&L time-series for a single trade. \" +\n \"Use skip_quotes=true (default) for fast analysis, false for NBBO-precision.\",\n inputSchema: getGreeksAttributionSchema,\n },\n async (params) => {\n try {\n const result = await handleGetGreeksAttribution(params, baseDir, stores);\n\n const isSummary = !(\"steps\" in result);\n const summary = isSummary\n ? `Block \"${params.block_id}\" attribution (${(result as AttributionSummaryResult).trades_decomposed}/${(result as AttributionSummaryResult).trades_total} trades): ${(result as AttributionSummaryResult).attribution.map(a => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(\", \")}, actual P&L ${(result as AttributionSummaryResult).total_pnl}, execution edge ${(result as AttributionSummaryResult).execution_edge}`\n : `Trade #${(result as AttributionInstanceResult).trade_index} attribution: ${(result as AttributionInstanceResult).attribution.map(a => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(\", \")}, actual P&L ${(result as AttributionInstanceResult).total_pnl}, execution edge ${(result as AttributionInstanceResult).execution_edge}`;\n\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error in Greeks attribution: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n}\n","/**\n * flatfile-importer.ts\n *\n * Imports option minute bars from Massive.com S3 flat files into the canonical\n * Market Data 3.0 spot store (`stores.spot.writeBars`).\n *\n * Phase 4 / CONSUMER-02: rewritten to consume `MarketStores`. Every write goes\n * through `stores.spot.writeBars(ticker, date, bars)` so there is exactly one\n * spot-write code path in the system. The legacy dual-mode branch (parquet\n * writer vs raw INSERT) plus the temp-CSV staging that lived here are gone —\n * the spot store handles staging internally via a temp DuckDB table.\n *\n * Pure parsing functions (`nanosToET`, `parseFlatFileLine`, `tradingDays`)\n * remain exported for unit testing — they have no IO dependencies.\n *\n * S3 structure: s3massive:flatfiles/us_options_opra/minute_aggs_v1/{year}/{month}/{date}.csv.gz\n * CSV format: ticker,volume,open,close,high,low,window_start,transactions\n * RTH filter: 09:30 - 16:15 ET\n */\n\nimport { createReadStream, existsSync, mkdirSync, unlinkSync } from \"fs\";\nimport { createInterface } from \"readline\";\nimport { createGunzip } from \"zlib\";\nimport { resolve } from \"path\";\nimport type { MarketStores, BarRow as MarketStoreBarRow } from \"../market/stores/index.ts\";\nimport { getFlatImportLogJson, upsertFlatImportLogJson } from \"../db/json-adapters.ts\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst TMP_DIR = \"/tmp/massive-flat\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ParsedBar {\n ticker: string; // O: prefix stripped\n date: string; // YYYY-MM-DD in ET\n time: string; // HH:MM in ET\n open: number;\n high: number;\n low: number;\n close: number;\n volume: number;\n}\n\nexport interface ImportDayResult {\n date: string;\n imported: number;\n skipped: boolean;\n error?: string;\n}\n\nexport interface ImportFlatFilesResult {\n totalImported: number;\n totalSkipped: number;\n days: ImportDayResult[];\n elapsedMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Pure functions (exported for unit testing)\n// ---------------------------------------------------------------------------\n\n/**\n * Check if a UTC date falls in US Eastern Daylight Time (EDT, UTC-4).\n * EDT runs from 2nd Sunday of March to 1st Sunday of November.\n * We use a fast approximation: March 8-31 through November 1 = EDT.\n */\nfunction isEDT(utcMonth: number, utcDay: number): boolean {\n if (utcMonth > 3 && utcMonth < 11) return true; // Apr-Oct always EDT\n if (utcMonth === 3) return utcDay >= 8; // March: after ~2nd Sunday\n if (utcMonth === 11) return utcDay < 7; // Nov: before ~1st Sunday\n return false; // Dec-Feb always EST\n}\n\n/**\n * Convert a nanosecond timestamp to ET date and HH:MM time.\n *\n * Uses fast manual UTC→ET offset instead of Intl/toLocaleString (~100x faster\n * when called millions of times during flat file parsing).\n */\nexport function nanosToET(nanos: string | number | bigint): { date: string; time: string } {\n const ms = Math.floor(Number(nanos) / 1_000_000);\n // Apply ET offset directly to ms, then extract UTC components (which are now in ET)\n const utcDate = new Date(ms);\n const offsetHours = isEDT(utcDate.getUTCMonth() + 1, utcDate.getUTCDate()) ? 4 : 5;\n const etMs = ms - offsetHours * 3600_000;\n const d = new Date(etMs);\n\n const year = d.getUTCFullYear();\n const month = String(d.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(d.getUTCDate()).padStart(2, \"0\");\n const date = `${year}-${month}-${day}`;\n const time = `${String(d.getUTCHours()).padStart(2, \"0\")}:${String(d.getUTCMinutes()).padStart(2, \"0\")}`;\n return { date, time };\n}\n\n/**\n * Parse a single CSV line from a Massive flat file.\n *\n * Returns null if:\n * - Line doesn't start with the given underlyingPrefix (e.g., \"O:SPX\")\n * - Line has fewer than 8 fields\n * - Bar timestamp is outside RTH (09:30 - 16:15 ET)\n *\n * CSV format: ticker,volume,open,close,high,low,window_start,transactions\n */\nexport function parseFlatFileLine(line: string, underlyingPrefix: string): ParsedBar | null {\n if (!line.startsWith(underlyingPrefix)) return null;\n\n const parts = line.split(\",\");\n\n const rawTicker = parts[0];\n // Index tickers: \"I:VIX\" → \"VIX\", option tickers: \"O:SPXW...\" → \"SPXW...\"\n const ticker = rawTicker.includes(':') ? rawTicker.slice(rawTicker.indexOf(':') + 1) : rawTicker;\n\n let open: number, close: number, high: number, low: number, windowStart: string, volume: number;\n\n if (parts.length >= 8) {\n // Option format: ticker,volume,open,close,high,low,window_start,transactions\n volume = Number(parts[1]);\n open = Number(parts[2]);\n close = Number(parts[3]);\n high = Number(parts[4]);\n low = Number(parts[5]);\n windowStart = parts[6];\n } else if (parts.length >= 6) {\n // Index format: ticker,open,close,high,low,window_start\n volume = 0;\n open = Number(parts[1]);\n close = Number(parts[2]);\n high = Number(parts[3]);\n low = Number(parts[4]);\n windowStart = parts[5];\n } else {\n return null;\n }\n\n const { date, time } = nanosToET(windowStart);\n\n // Filter to RTH only (09:30 - 16:15 ET)\n if (time < \"09:30\" || time > \"16:15\") return null;\n\n return { ticker, date, time, open, high, low, close, volume };\n}\n\n/**\n * Generate weekday (Mon-Fri) dates between from and to (inclusive).\n *\n * Uses UTC noon to avoid any DST/timezone ambiguity when iterating dates.\n */\nexport function tradingDays(from: string, to: string): string[] {\n const days: string[] = [];\n const d = new Date(from + \"T12:00:00Z\");\n const end = new Date(to + \"T12:00:00Z\");\n while (d <= end) {\n const dow = d.getUTCDay();\n if (dow !== 0 && dow !== 6) {\n days.push(d.toISOString().slice(0, 10));\n }\n d.setUTCDate(d.getUTCDate() + 1);\n }\n return days;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helper: write parsed rows through stores.spot.writeBars\n// ---------------------------------------------------------------------------\n\n/**\n * Group `ParsedBar[]` by (ticker, date) and persist via the spot store.\n *\n * Flat files mix tickers under a single underlying (e.g., one O:SPXW file\n * contains hundreds of distinct option contracts). The store contract is\n * one writeBars call per (ticker, date) so we group first, then write\n * serially — DuckDB is single-writer (Pitfall 9).\n */\nasync function writeRowsThroughStore(\n stores: MarketStores,\n rows: ParsedBar[],\n): Promise<void> {\n if (rows.length === 0) return;\n\n const byTickerDate = new Map<string, Map<string, MarketStoreBarRow[]>>();\n for (const r of rows) {\n let byDate = byTickerDate.get(r.ticker);\n if (!byDate) {\n byDate = new Map();\n byTickerDate.set(r.ticker, byDate);\n }\n let bars = byDate.get(r.date);\n if (!bars) {\n bars = [];\n byDate.set(r.date, bars);\n }\n bars.push({\n ticker: r.ticker,\n date: r.date,\n time: r.time,\n open: r.open,\n high: r.high,\n low: r.low,\n close: r.close,\n bid: undefined,\n ask: undefined,\n volume: r.volume,\n });\n }\n\n for (const [ticker, byDate] of byTickerDate) {\n for (const [date, bars] of byDate) {\n await stores.spot.writeBars(ticker, date, bars);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// I/O functions\n// ---------------------------------------------------------------------------\n\n/**\n * Import a single day's flat file via the spot store.\n *\n * Uses the provider's downloadFlatFile() for the download (provider-agnostic),\n * then stream-parses the gzipped CSV, filters to the specified underlying,\n * and persists via `stores.spot.writeBars` grouped by (ticker, date).\n *\n * Skips when `stores.spot.getCoverage(probeTicker, date, date).totalDates > 0`\n * for a representative probe ticker (the underlying itself for index files;\n * the underlying canonical option ticker for option files).\n */\nexport async function importFlatFileDay(\n dateStr: string,\n underlying: string,\n stores: MarketStores,\n assetClass: 'option' | 'index' = 'option',\n): Promise<ImportDayResult> {\n // Index flat files go to a separate tmp path to avoid colliding with option files\n const tmpSubdir = assetClass === 'index' ? '/tmp/massive-flat-index' : TMP_DIR;\n const localPath = resolve(tmpSubdir, `${dateStr}.csv.gz`);\n\n // Skip-check via the spot store. For options the underlying itself rarely\n // has spot bars (those go through a separate index import), so we use\n // store coverage as a best-effort signal — duplicate ingest is idempotent\n // at the store layer (writeBars overwrites the partition).\n if (assetClass === 'index') {\n const cov = await stores.spot.getCoverage(underlying, dateStr, dateStr);\n if (cov.totalDates > 0) {\n return { date: dateStr, imported: 0, skipped: true };\n }\n }\n\n // Download via provider (provider-agnostic)\n if (!existsSync(localPath)) {\n mkdirSync(tmpSubdir, { recursive: true });\n const { getProvider } = await import('./market-provider.ts');\n const provider = getProvider();\n if (provider.downloadFlatFile) {\n const downloaded = await provider.downloadFlatFile(dateStr, assetClass);\n if (!downloaded) {\n return { date: dateStr, imported: 0, skipped: false, error: 'download_failed' };\n }\n } else {\n return { date: dateStr, imported: 0, skipped: false, error: 'provider_no_flat_files' };\n }\n }\n\n if (!existsSync(localPath)) {\n return { date: dateStr, imported: 0, skipped: false, error: \"not_found\" };\n }\n\n // Stream-parse: filter to underlying tickers, convert timestamps, collect rows\n // Index tickers use \"I:\" prefix, option tickers use \"O:\" prefix\n const tickerPrefix = assetClass === 'index' ? `I:${underlying}` : (underlying === \"SPX\" ? \"O:SPX\" : `O:${underlying}`);\n\n const rows: ParsedBar[] = [];\n await new Promise<void>((resolveP, reject) => {\n const gunzip = createGunzip();\n const rl = createInterface({ input: createReadStream(localPath).pipe(gunzip) });\n let isHeader = true;\n\n rl.on(\"line\", (line: string) => {\n if (isHeader) {\n isHeader = false;\n return;\n }\n const parsed = parseFlatFileLine(line, tickerPrefix);\n if (parsed) rows.push(parsed);\n });\n\n rl.on(\"close\", resolveP);\n rl.on(\"error\", reject);\n gunzip.on(\"error\", reject);\n });\n\n // Clean up downloaded file\n try {\n unlinkSync(localPath);\n } catch {\n // best-effort cleanup\n }\n\n if (rows.length === 0) {\n return { date: dateStr, imported: 0, skipped: false };\n }\n\n await writeRowsThroughStore(stores, rows);\n return { date: dateStr, imported: rows.length, skipped: false };\n}\n\n/**\n * Download and parse a flat file for a single day. Does NOT touch the store.\n * Returns parsed rows ready for bulk insert, or an error/skip status.\n */\nasync function downloadAndParse(\n dateStr: string,\n underlying: string,\n assetClass: 'option' | 'index',\n): Promise<{ date: string; rows: ParsedBar[]; skipped?: boolean; error?: string }> {\n const tmpSubdir = assetClass === 'index' ? '/tmp/massive-flat-index' : TMP_DIR;\n const localPath = resolve(tmpSubdir, `${dateStr}.csv.gz`);\n\n // Download via provider\n if (!existsSync(localPath)) {\n mkdirSync(tmpSubdir, { recursive: true });\n const { getProvider } = await import('./market-provider.ts');\n const provider = getProvider();\n if (provider.downloadFlatFile) {\n const downloaded = await provider.downloadFlatFile(dateStr, assetClass);\n if (!downloaded) {\n return { date: dateStr, rows: [], error: 'download_failed' };\n }\n } else {\n return { date: dateStr, rows: [], error: 'provider_no_flat_files' };\n }\n }\n\n if (!existsSync(localPath)) {\n return { date: dateStr, rows: [], error: 'not_found' };\n }\n\n // Stream-parse: filter to underlying tickers, convert timestamps\n const tickerPrefix = assetClass === 'index' ? `I:${underlying}` : (underlying === \"SPX\" ? \"O:SPX\" : `O:${underlying}`);\n const rows: ParsedBar[] = [];\n await new Promise<void>((resolveP, reject) => {\n const gunzip = createGunzip();\n const rl = createInterface({ input: createReadStream(localPath).pipe(gunzip) });\n let isHeader = true;\n rl.on(\"line\", (line: string) => {\n if (isHeader) { isHeader = false; return; }\n const parsed = parseFlatFileLine(line, tickerPrefix);\n if (parsed) rows.push(parsed);\n });\n rl.on(\"close\", resolveP);\n rl.on(\"error\", reject);\n gunzip.on(\"error\", reject);\n });\n\n // Clean up downloaded file\n try { unlinkSync(localPath); } catch { /* best-effort */ }\n\n return { date: dateStr, rows };\n}\n\n/** Default concurrency for parallel download+parse. */\nconst IMPORT_CONCURRENCY = 5;\n\n/**\n * Import flat files for a date range with parallel download+parse.\n *\n * Downloads and parses up to IMPORT_CONCURRENCY days in parallel,\n * then persists via the spot store serially (DuckDB single-writer).\n *\n * Tracks already-imported (date, asset_class, underlying) tuples in the JSON\n * `flat_import_log` so re-runs are no-ops once a window is fully covered.\n */\nexport async function importFlatFiles(\n from: string,\n to: string,\n underlying: string,\n stores: MarketStores,\n dataDir: string,\n assetClass: 'option' | 'index' = 'option',\n): Promise<ImportFlatFilesResult> {\n const tmpDir = assetClass === 'index' ? '/tmp/massive-flat-index' : TMP_DIR;\n mkdirSync(tmpDir, { recursive: true });\n\n const days = tradingDays(from, to);\n const results: ImportDayResult[] = [];\n let totalImported = 0;\n let totalSkipped = 0;\n const t0 = Date.now();\n\n // Track flat file imports in the JSON log — only skip dates that were\n // actually imported from flat files, not dates that happen to have bars\n // from per-ticker API fetches.\n const importedDates = await getFlatImportLogJson(assetClass, underlying, from, to, dataDir);\n\n const daysToImport = days.filter(d => !importedDates.has(d));\n const skippedDays = days.filter(d => importedDates.has(d));\n for (const d of skippedDays) {\n results.push({ date: d, imported: 0, skipped: true });\n totalSkipped++;\n }\n\n if (daysToImport.length === 0) {\n console.log(` [importFlatFiles] all ${days.length} days already imported — nothing to do`);\n } else {\n console.log(` [importFlatFiles] ${daysToImport.length} days to import, ${skippedDays.length} already imported (${assetClass} ${underlying})`);\n }\n\n // Process in batches: parallel download+parse, serial spot-store writes\n for (let i = 0; i < daysToImport.length; i += IMPORT_CONCURRENCY) {\n const batch = daysToImport.slice(i, i + IMPORT_CONCURRENCY);\n const batchNum = Math.floor(i / IMPORT_CONCURRENCY) + 1;\n const totalBatches = Math.ceil(daysToImport.length / IMPORT_CONCURRENCY);\n console.log(` [importFlatFiles] batch ${batchNum}/${totalBatches}: ${batch[0]}..${batch[batch.length - 1]} (${Math.round((Date.now() - t0) / 1000)}s)`);\n\n // Parallel download + parse\n const parsed = await Promise.all(\n batch.map(day => downloadAndParse(day, underlying, assetClass))\n );\n\n // Serial writes through the spot store (DuckDB single-writer)\n for (const p of parsed) {\n if (p.error) {\n results.push({ date: p.date, imported: 0, skipped: false, error: p.error });\n continue;\n }\n if (p.rows.length === 0) {\n results.push({ date: p.date, imported: 0, skipped: false });\n continue;\n }\n await writeRowsThroughStore(stores, p.rows);\n results.push({ date: p.date, imported: p.rows.length, skipped: false });\n totalImported += p.rows.length;\n // Record successful import in JSON metadata log\n try {\n await upsertFlatImportLogJson({\n date: p.date,\n asset_class: assetClass,\n underlying,\n imported_at: new Date().toISOString(),\n bar_count: p.rows.length,\n }, dataDir);\n } catch { /* best-effort metadata tracking */ }\n }\n }\n console.log(` [importFlatFiles] done: ${totalImported} bars imported, ${totalSkipped} days skipped (${Math.round((Date.now() - t0) / 1000)}s)`);\n\n return {\n totalImported,\n totalSkipped,\n days: results,\n elapsedMs: Date.now() - t0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// importIndexBars — multi-ticker index import (1 download per day)\n// ---------------------------------------------------------------------------\n\n/**\n * Download and parse multiple index tickers from a single flat file.\n * Downloads once, extracts all matching tickers in one pass.\n */\nasync function downloadAndParseMulti(\n dateStr: string,\n tickers: string[],\n): Promise<{ date: string; rows: ParsedBar[]; error?: string }> {\n const tmpDir = '/tmp/massive-flat-index';\n const localPath = resolve(tmpDir, `${dateStr}.csv.gz`);\n\n if (!existsSync(localPath)) {\n mkdirSync(tmpDir, { recursive: true });\n const { getProvider } = await import('./market-provider.ts');\n const provider = getProvider();\n if (provider.downloadFlatFile) {\n const downloaded = await provider.downloadFlatFile(dateStr, 'index');\n if (!downloaded) return { date: dateStr, rows: [], error: 'download_failed' };\n } else {\n return { date: dateStr, rows: [], error: 'provider_no_flat_files' };\n }\n }\n\n if (!existsSync(localPath)) return { date: dateStr, rows: [], error: 'not_found' };\n\n // Build prefix set for fast matching: \"I:VIX,\", \"I:VIX9D,\", \"I:SPX,\"\n const prefixes = tickers.map(t => `I:${t},`);\n\n const rows: ParsedBar[] = [];\n await new Promise<void>((resolveP, reject) => {\n const gunzip = createGunzip();\n const rl = createInterface({ input: createReadStream(localPath).pipe(gunzip) });\n let isHeader = true;\n rl.on(\"line\", (line: string) => {\n if (isHeader) { isHeader = false; return; }\n for (const prefix of prefixes) {\n if (line.startsWith(prefix)) {\n const parsed = parseFlatFileLine(line, prefix.slice(0, -1)); // strip trailing comma\n if (parsed) rows.push(parsed);\n break;\n }\n }\n });\n rl.on(\"close\", resolveP);\n rl.on(\"error\", reject);\n gunzip.on(\"error\", reject);\n });\n\n try { unlinkSync(localPath); } catch { /* best-effort */ }\n return { date: dateStr, rows };\n}\n\n/** Concurrency for index imports. */\nconst INDEX_CONCURRENCY = 8;\n\n/**\n * Import multiple index tickers from flat files in a date range.\n * Downloads each day's file ONCE and extracts all tickers in a single parse pass.\n * Much faster than calling importFlatFiles per ticker.\n */\nexport async function importIndexBars(\n from: string,\n to: string,\n tickers: string[],\n stores: MarketStores,\n): Promise<ImportFlatFilesResult> {\n mkdirSync('/tmp/massive-flat-index', { recursive: true });\n\n const days = tradingDays(from, to);\n const results: ImportDayResult[] = [];\n let totalImported = 0;\n let totalSkipped = 0;\n const t0 = Date.now();\n\n // Per-day skip check via the spot store: skip a day only when EVERY requested\n // ticker already has coverage on that date (matches the legacy intersection\n // semantics).\n const skipDays = new Set<string>();\n if (tickers.length > 0) {\n const perTickerDates = await Promise.all(\n tickers.map(async (ticker) => {\n const cov = await stores.spot.getCoverage(ticker, from, to);\n if (cov.totalDates === 0 || !cov.earliest || !cov.latest) {\n return new Set<string>();\n }\n // listPartitionValues underneath getCoverage already returns the\n // exact dates with data; we approximate the \"trading days covered\"\n // set by enumerating the inclusive range bounded by earliest/latest.\n // For per-day skip we only need a contains check, not a precise\n // missing-date list.\n const dates = new Set<string>();\n const start = new Date(cov.earliest + 'T00:00:00Z');\n const end = new Date(cov.latest + 'T00:00:00Z');\n const cur = new Date(start);\n while (cur <= end) {\n dates.add(cur.toISOString().slice(0, 10));\n cur.setUTCDate(cur.getUTCDate() + 1);\n }\n return dates;\n }),\n );\n if (perTickerDates.length > 0) {\n // Intersect across tickers\n const first = perTickerDates[0];\n for (const d of first) {\n let allHave = true;\n for (let i = 1; i < perTickerDates.length; i++) {\n if (!perTickerDates[i].has(d)) {\n allHave = false;\n break;\n }\n }\n if (allHave) skipDays.add(d);\n }\n }\n }\n\n const daysToImport = days.filter(d => !skipDays.has(d));\n for (const d of days.filter(d => skipDays.has(d))) {\n results.push({ date: d, imported: 0, skipped: true });\n totalSkipped++;\n }\n\n if (daysToImport.length === 0) {\n console.log(` [importIndexBars] all ${days.length} days have data for ${tickers.join(',')} — nothing to import`);\n } else {\n console.log(` [importIndexBars] ${daysToImport.length} days to import for ${tickers.join(',')}, ${skipDays.size} skipped`);\n }\n\n // Parallel download+parse, serial store writes\n for (let i = 0; i < daysToImport.length; i += INDEX_CONCURRENCY) {\n const batch = daysToImport.slice(i, i + INDEX_CONCURRENCY);\n const batchNum = Math.floor(i / INDEX_CONCURRENCY) + 1;\n const totalBatches = Math.ceil(daysToImport.length / INDEX_CONCURRENCY);\n console.log(` [importIndexBars] batch ${batchNum}/${totalBatches}: ${batch[0]}..${batch[batch.length - 1]} (${Math.round((Date.now() - t0) / 1000)}s)`);\n\n const parsed = await Promise.all(\n batch.map(day => downloadAndParseMulti(day, tickers))\n );\n\n for (const p of parsed) {\n if (p.error) {\n results.push({ date: p.date, imported: 0, skipped: false, error: p.error });\n continue;\n }\n if (p.rows.length === 0) {\n results.push({ date: p.date, imported: 0, skipped: false });\n continue;\n }\n await writeRowsThroughStore(stores, p.rows);\n results.push({ date: p.date, imported: p.rows.length, skipped: false });\n totalImported += p.rows.length;\n }\n }\n console.log(` [importIndexBars] done: ${totalImported} bars imported, ${totalSkipped} days skipped (${Math.round((Date.now() - t0) / 1000)}s)`);\n\n return {\n totalImported,\n totalSkipped,\n days: results,\n elapsedMs: Date.now() - t0,\n };\n}\n","/**\n * SpotStore — Abstract base for spot (intraday + daily) bar storage.\n *\n * Phase 1: Signatures only. Phase 2: ParquetSpotStore / DuckdbSpotStore implement these.\n *\n * The `abstract` keyword enforces at compile time that every subclass provides\n * an implementation of all four methods (STORE-05 contract).\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport type { StoreContext, BarRow, CoverageReport } from \"./types.ts\";\nimport { resolveMarketDir } from \"../../db/market-datasets.ts\";\nimport { listPartitionValues } from \"./coverage.ts\";\n\nfunction escapeSqlLiteral(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nexport abstract class SpotStore {\n protected readonly ctx: StoreContext;\n constructor(ctx: StoreContext) {\n this.ctx = ctx;\n }\n\n /**\n * Return `read_parquet([...])` SQL over exact `ticker=X/date=Y/data.parquet`\n * files for a (ticker, from..to) range, or null if no files exist on disk.\n * Used by concrete stores to bypass the `market.spot` view's glob walk.\n */\n protected buildDirectParquetReadBarsSQL(\n ticker: string,\n from: string,\n to: string,\n opts?: { rthOnly?: boolean; dailyAgg?: boolean },\n ): { sql: string } | null {\n const tickerDir = path.join(resolveMarketDir(this.ctx.dataDir), \"spot\", `ticker=${ticker}`);\n if (!existsSync(tickerDir)) return null;\n const allDates = listPartitionValues(tickerDir, \"date\");\n const dates = allDates.filter((d) => d >= from && d <= to);\n if (dates.length === 0) return null;\n const paths: string[] = [];\n for (const d of dates) {\n const p = path.join(tickerDir, `date=${d}`, \"data.parquet\");\n if (existsSync(p)) paths.push(p);\n }\n if (paths.length === 0) return null;\n const fileList = paths.map(p => `'${escapeSqlLiteral(p)}'`).join(\", \");\n const tickerLit = `'${escapeSqlLiteral(ticker)}'`;\n if (opts?.dailyAgg) {\n return {\n sql: `SELECT ${tickerLit} AS ticker, date,\n first(open ORDER BY time) AS open,\n max(high) AS high,\n min(low) AS low,\n last(close ORDER BY time) AS close,\n first(bid ORDER BY time) AS bid,\n last(ask ORDER BY time) AS ask\n FROM read_parquet([${fileList}], hive_partitioning=true)\n WHERE time >= '09:30' AND time <= '16:00'\n -- Defense-in-depth: drop minute bars with zero/null OHLC\n -- before aggregating. Mirrors the same guard on the public\n -- market.spot_daily view (db/market-views.ts). Without this,\n -- a bad-data minute (close=0 or low=0) collapses the daily\n -- aggregate's min(low) to 0, which propagates into every\n -- enriched indicator that uses (high - low) as range.\n AND open IS NOT NULL AND open > 0\n AND high IS NOT NULL AND high > 0\n AND low IS NOT NULL AND low > 0\n AND close IS NOT NULL AND close > 0\n GROUP BY date\n ORDER BY date`,\n };\n }\n const rthClause = opts?.rthOnly ? \"AND time >= '09:30' AND time <= '16:00'\" : \"\";\n return {\n sql: `SELECT ${tickerLit} AS ticker, date, time, open, high, low, close, bid, ask\n FROM read_parquet([${fileList}], hive_partitioning=true)\n WHERE 1=1 ${rthClause}\n ORDER BY date, time`,\n };\n }\n\n /**\n * Public accessor for the data directory root (WR-03).\n *\n * Pipeline-side helpers (e.g., `executeFetchPlan`) need the absolute base\n * directory when no explicit `baseDir` is supplied — the flat-import-log\n * JSON adapter writes its dedupe ledger under `{dataDir}/market/.flat-import-log/`.\n * Exposing this through a public getter beats reaching into `store[\"ctx\"]`\n * via bracket notation, which silently bypasses TypeScript's `protected`\n * modifier and creates a hidden coupling to the internal field name.\n */\n public get dataDir(): string {\n return this.ctx.dataDir;\n }\n\n abstract writeBars(ticker: string, date: string, bars: BarRow[]): Promise<void>;\n\n /**\n * Write bars for a single (ticker, date) partition from a user-supplied SELECT.\n *\n * The SELECT must produce columns matching `market.spot`\n * (ticker, date, time, open, high, low, close, bid, ask). Rows are expected\n * to belong to the single partition named in `partition` — the caller is\n * responsible for filtering upstream; mixed partitions are not rejected\n * but will be written to the named partition's location (Parquet) or the\n * single table (DuckDB).\n *\n * Parquet mode: `COPY (select) TO spot/ticker=X/date=Y/data.parquet` via\n * the shared staging-table helper.\n *\n * DuckDB mode: `INSERT OR REPLACE INTO market.spot (cols...) <select>`.\n */\n abstract writeFromSelect(\n partition: { ticker: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }>;\n\n abstract readBars(ticker: string, from: string, to: string): Promise<BarRow[]>;\n abstract readDailyBars(ticker: string, from: string, to: string): Promise<BarRow[]>;\n abstract getCoverage(ticker: string, from: string, to: string): Promise<CoverageReport>;\n}\n","/**\n * Shared partition enumerator (Market Data 3.0 — Phase 2 Wave 1).\n *\n * Given a directory containing Hive-partition subdirectories shaped\n * `{partitionKey}=VALUE/`, return the sorted list of VALUEs for which a\n * `data.parquet` file exists. Used by every Parquet-mode store's\n * `getCoverage()` implementation (spot, enriched, chain, quote).\n *\n * Generalized from `src/db/market-views.ts::hasParquetPartitions` (PATTERNS.md\n * \"coverage.ts\"). Accepts the partition key as a parameter so both\n * `ticker=...` and `underlying=...` layouts are covered by one helper.\n *\n * Purity: synchronous, no mutation, no exceptions on missing directories —\n * returns `[]` when the directory is absent or unreadable.\n */\nimport { existsSync, readdirSync } from \"fs\";\nimport * as path from \"path\";\n\n/**\n * Enumerate Hive-partition values present under `dir`. Each matching\n * subdirectory must be shaped `{partitionKey}=VALUE/` and must contain\n * `data.parquet` to be counted.\n *\n * @param dir Partition root (e.g. `/data/market/spot/ticker=SPX`)\n * @param partitionKey Partition key name (e.g. `\"date\"`)\n * @returns Sorted array of VALUEs (empty on missing dir / IO error)\n *\n * @example\n * listPartitionValues(\"/data/market/spot/ticker=SPX\", \"date\")\n * // → [\"2025-01-06\", \"2025-01-07\", ...]\n */\nexport function listPartitionValues(dir: string, partitionKey: string): string[] {\n if (!existsSync(dir)) return [];\n try {\n const prefix = `${partitionKey}=`;\n return readdirSync(dir)\n .filter((entry) => entry.startsWith(prefix))\n .filter((entry) => existsSync(path.join(dir, entry, \"data.parquet\")))\n .map((entry) => entry.slice(prefix.length))\n .sort();\n } catch {\n return [];\n }\n}\n","/**\n * EnrichedStore — Abstract base for computed/derived market fields.\n *\n * Phase 1: Signatures only.\n *\n * Two compute entry points:\n * - `compute(ticker, from, to)` — per-ticker enriched derivations\n * (indicators, vol regimes, opening-drive metrics, etc.)\n * - `computeContext(from, to)` — cross-ticker context (VIX family,\n * term structure, realized-vol aggregates) that doesn't belong to any\n * single ticker's enriched output.\n *\n * `read(opts)` composes enriched + (optional) context + (optional) OHLCV.\n */\nimport type { StoreContext, CoverageReport } from \"./types.ts\";\n\nexport interface EnrichedReadOpts {\n ticker: string;\n from: string;\n to: string;\n includeContext?: boolean; // join enriched_context (VIX family cross-ticker fields)\n includeOhlcv?: boolean; // join spot daily for OHLCV (avoids double-storing OHLCV)\n}\n\nexport abstract class EnrichedStore {\n protected readonly ctx: StoreContext;\n constructor(ctx: StoreContext) {\n this.ctx = ctx;\n }\n\n abstract compute(ticker: string, from: string, to: string): Promise<void>;\n abstract computeContext(from: string, to: string): Promise<void>;\n abstract read(opts: EnrichedReadOpts): Promise<Record<string, unknown>[]>;\n abstract getCoverage(ticker: string): Promise<CoverageReport>;\n}\n","/**\n * Pure SQL builder for ChainStore reads.\n *\n * Option chains are partitioned by (underlying, date). A single `readChain`\n * call targets exactly one partition. Values are inlined as SQL literals\n * because `runAndReadAll(sql, params)` leaks C++ handles via DuckDB's\n * `extract_statements` path (see `spot-sql.ts` header for the full writeup).\n *\n * Purity contract: no `this`, no `ctx`, no DuckDB value-level imports. Tests\n * in `tests/unit/market/stores/chain-sql.test.ts`.\n */\nimport { escapeSqlLiteral } from \"../../utils/quote-parquet-projection.ts\";\nimport type { BuiltSQL } from \"./spot-sql.ts\";\n\nfunction lit(value: string): string {\n return `'${escapeSqlLiteral(value)}'`;\n}\n\n/**\n * Build the `SELECT ... FROM market.option_chain` SQL for a single underlying +\n * date partition. Results are ordered by `ticker` so consumer iteration is\n * deterministic across backends.\n */\nexport function buildReadChainSQL(\n underlying: string,\n date: string,\n): BuiltSQL {\n return {\n sql: `SELECT underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style\n FROM market.option_chain\n WHERE underlying = ${lit(underlying)} AND date = ${lit(date)}\n ORDER BY ticker`,\n };\n}\n\n/**\n * Build a bulk read for N dates under the same underlying via `date IN (...)`.\n *\n * DuckDB's `market.option_chain` view glob-expands `option_chain/**\\/*.parquet`\n * on every call — a ~430ms fixed cost even for a single-partition read. Issuing\n * one IN-list query instead of N per-date queries collapses that overhead.\n * Measured: 12 per-date reads = ~5.2s, one IN(12) read = ~0.43s (12x speedup).\n *\n * Throws when `dates` is empty (prevents `IN ()` which DuckDB rejects).\n */\nexport function buildReadChainDatesSQL(\n underlying: string,\n dates: string[],\n): BuiltSQL {\n if (dates.length === 0) {\n throw new Error(\"buildReadChainDatesSQL: dates must not be empty\");\n }\n const dateList = dates.map(lit).join(\", \");\n return {\n sql: `SELECT underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style\n FROM market.option_chain\n WHERE underlying = ${lit(underlying)} AND date IN (${dateList})\n ORDER BY date, ticker`,\n };\n}\n","/**\n * ChainStore — Abstract base for option chain snapshot storage.\n *\n * Phase 1: Signatures only.\n *\n * Option chains are partitioned by (underlying, date). `readChain(underlying, date)`\n * returns all contracts observed for that underlying on that trading date.\n */\nimport type { StoreContext, ContractRow, CoverageReport } from \"./types.ts\";\nimport { buildReadChainDatesSQL } from \"./chain-sql.ts\";\n\nexport abstract class ChainStore {\n protected readonly ctx: StoreContext;\n constructor(ctx: StoreContext) {\n this.ctx = ctx;\n }\n\n abstract writeChain(underlying: string, date: string, rows: ContractRow[]): Promise<void>;\n\n /**\n * Write chain rows for a single (underlying, date) partition from a user-supplied SELECT.\n *\n * The SELECT must produce columns matching `market.option_chain`\n * (underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style).\n * Single-partition semantics mirror `SpotStore.writeFromSelect`.\n */\n abstract writeFromSelect(\n partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }>;\n\n abstract readChain(underlying: string, date: string): Promise<ContractRow[]>;\n\n /**\n * Cheap chain-existence probe used by entry-pipeline snapshot reads. Returns\n * `true` when the (underlying, date) chain partition has at least one\n * contract; otherwise `false`. Lets the resolver skip a date without paying\n * the ~342ms / 39K-row cost of a full `readChain` call when only the empty\n * check matters.\n */\n async hasChain(underlying: string, date: string): Promise<boolean> {\n // Inline literals — bound-param path leaks extract_statements handles\n // (see chain-sql.ts / spot-sql.ts headers).\n const underlyingLit = underlying.replace(/'/g, \"''\");\n const dateLit = date.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT 1 FROM market.option_chain\n WHERE underlying = '${underlyingLit}' AND date = '${dateLit}'\n LIMIT 1`,\n );\n return reader.getRows().length > 0;\n }\n\n /**\n * Bulk read chains for N dates under a single underlying. Returns a flat list;\n * the caller groups by `date`. Both backends share the same SQL path since\n * `market.option_chain` resolves to either a Parquet view or a physical table\n * with identical columns. Use this instead of N per-date `readChain` calls —\n * per-call glob-expansion / planning overhead dominates for view reads.\n */\n async readChainDates(\n underlying: string,\n dates: string[],\n ): Promise<ContractRow[]> {\n if (dates.length === 0) return [];\n // Builder inlines values; unbound runAndReadAll(sql) bypasses extract_statements.\n const { sql } = buildReadChainDatesSQL(underlying, dates);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n underlying: String(r[0]),\n date: String(r[1]),\n ticker: String(r[2]),\n contract_type: String(r[3]) as ContractRow[\"contract_type\"],\n strike: Number(r[4]),\n expiration: String(r[5]),\n dte: Number(r[6]),\n exercise_style: String(r[7]),\n }));\n }\n\n abstract getCoverage(underlying: string, from: string, to: string): Promise<CoverageReport>;\n}\n","/**\n * QuoteStore — Abstract base for option minute-quote storage.\n *\n * Phase 1 shipped a single-ticker placeholder signature. Plan 02-03 Task 1\n * replaces it in-place with the multi-ticker grouped-series shape that\n * matches the primary consumer pattern: bulk\n * `ticker IN (...) AND date BETWEEN ...` → group-by-ticker. Per CONTEXT.md\n * D-06 / D-08 the signature swap is safe because no Phase 4 consumer has\n * migrated onto the Phase 1 placeholder.\n *\n * All OCC tickers in a single `readQuotes` batch MUST resolve to the same\n * underlying (D-07). Concrete subclasses validate this before issuing SQL and\n * throw clearly when a mixed batch arrives (first-iteration behavior — may\n * relax to transparent grouping if a real Phase 4 consumer needs it).\n *\n * Concrete subclasses (ParquetQuoteStore, DuckdbQuoteStore) ship in Plan\n * 02-03 Task 3.\n */\nimport type {\n StoreContext,\n QuoteRow,\n CoverageReport,\n ReadWindowParams,\n WindowQuoteRow,\n} from \"./types.ts\";\nimport { extractRoot } from \"../tickers/resolver.ts\";\n\nexport abstract class QuoteStore {\n protected readonly ctx: StoreContext;\n constructor(ctx: StoreContext) {\n this.ctx = ctx;\n }\n\n /**\n * Public accessor for the underlying TickerRegistry (WR-03).\n *\n * Several pipeline-side helpers need to resolve OCC roots → underlyings\n * BEFORE they can group calls per-underlying (Pitfall 4 — readQuotes /\n * writeQuotes both enforce single-underlying batches). Exposing the\n * registry through a public getter beats reaching into `store[\"ctx\"]`\n * via bracket notation, which silently bypasses TypeScript's `protected`\n * modifier and creates a hidden coupling to the internal field name.\n */\n public get tickers() {\n return this.ctx.tickers;\n }\n\n abstract writeQuotes(\n underlying: string,\n date: string,\n quotes: QuoteRow[],\n ): Promise<void>;\n\n /**\n * Write quotes for a single (underlying, date) partition from a user-supplied SELECT.\n *\n * The SELECT must produce columns matching `market.option_quote_minutes`\n * (underlying, date, ticker, time, bid, ask, mid, last_updated_ns, source,\n * delta, gamma, theta, vega, iv, greeks_source, greeks_revision).\n * Single-partition semantics mirror `SpotStore.writeFromSelect`.\n */\n abstract writeFromSelect(\n partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }>;\n\n /**\n * Read quotes for a batch of OCC tickers over a date range.\n *\n * All tickers MUST resolve to the same underlying via\n * `extractRoot(...)` + `ctx.tickers.resolve(...)` (validated by the concrete\n * implementation per D-07). Returns a Map keyed by OCC ticker; values are\n * timestamp-sorted arrays of QuoteRow for that contract across the range.\n */\n abstract readQuotes(\n occTickers: string[],\n from: string,\n to: string,\n ): Promise<Map<string, QuoteRow[]>>;\n\n /**\n * Group a `(date -> OCC tickers)` request map by resolved underlying.\n *\n * `readQuotes(...)` and `writeQuotes(...)` both enforce single-underlying\n * batches, so multi-date callers need this shared bucketing before they can\n * fan out to backend-specific bulk paths.\n */\n protected groupTickersByUnderlying(\n tickersByDate: Map<string, Set<string>>,\n ): Map<string, Map<string, Set<string>>> {\n const byUnderlying = new Map<string, Map<string, Set<string>>>();\n for (const [date, tickers] of tickersByDate) {\n for (const ticker of tickers) {\n const underlying = this.ctx.tickers.resolve(extractRoot(ticker));\n let perDate = byUnderlying.get(underlying);\n if (!perDate) {\n perDate = new Map();\n byUnderlying.set(underlying, perDate);\n }\n let dateTickers = perDate.get(date);\n if (!dateTickers) {\n dateTickers = new Set<string>();\n perDate.set(date, dateTickers);\n }\n dateTickers.add(ticker);\n }\n }\n return byUnderlying;\n }\n\n /**\n * Bulk-read quotes for N (date, tickers) pairs across N dates, with a\n * caller-supplied time window pushed into the query.\n *\n * The base implementation is a backend-respecting fallback that fans out to\n * per-date `readQuotes(...)` calls. Concrete backends can override it with a\n * more efficient bulk query shape without changing the public contract.\n *\n * `tickersByDate` may list the same ticker on multiple dates (e.g. a 3-DTE\n * option appearing across a Mon/Tue/Wed window). Returns a Map keyed by\n * OCC ticker whose values contain quotes for that ticker across every date\n * in which it was requested; callers filter by (ticker, date) against the\n * input map if they need date-specific isolation.\n */\n async readQuotesBulk(\n tickersByDate: Map<string, Set<string>>,\n timeStart: string,\n timeEnd: string,\n ): Promise<Map<string, QuoteRow[]>> {\n const out = new Map<string, QuoteRow[]>();\n if (tickersByDate.size === 0) return out;\n\n for (const [, perDate] of this.groupTickersByUnderlying(tickersByDate)) {\n for (const [date, occs] of perDate) {\n if (occs.size === 0) continue;\n const quotesByOcc = await this.readQuotes([...occs], date, date);\n for (const [occ, quotes] of quotesByOcc) {\n let arr = out.get(occ);\n if (!arr) {\n arr = [];\n out.set(occ, arr);\n }\n for (const quote of quotes) {\n const spaceIdx = quote.timestamp.indexOf(\" \");\n const time = spaceIdx === -1 ? \"\" : quote.timestamp.slice(spaceIdx + 1);\n if (time < timeStart || time > timeEnd) continue;\n arr.push(quote);\n }\n }\n }\n }\n return out;\n }\n\n abstract getCoverage(\n underlying: string,\n from: string,\n to: string,\n ): Promise<CoverageReport>;\n\n /**\n * Read every option-quote row in the leg-envelope union over a time window.\n * Returns rows joined back to chain metadata (contract_type, strike, expiration,\n * dte) so the caller doesn't OCC-parse. Greeks columns project as-is from the\n * quote table.\n *\n * Per P1: this is the single read primitive. Ranking + top-N selection happen\n * in JS at the call site. No SQL ranking CTE.\n */\n abstract readWindow(params: ReadWindowParams): Promise<WindowQuoteRow[]>;\n}\n","/**\n * ParquetSpotStore — spot minute bars persisted as ticker-first Hive-partitioned\n * Parquet files (spot/ticker=X/date=Y/data.parquet).\n *\n * Writes flow through `writeSpotPartition` (Phase 1 typed helper); reads use\n * the shared SQL builders from `./spot-sql.ts` against the `market.spot`\n * view that `createMarketParquetViews` registers when partitions exist.\n * Coverage uses filesystem enumeration via `listPartitionValues` (D-26).\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode` — the factory\n * chooses the backend once at construction and every method is monomorphic.\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport { SpotStore } from \"./spot-store.ts\";\nimport type { BarRow, CoverageReport } from \"./types.ts\";\nimport { buildReadBarsSQL, buildReadDailyBarsSQL } from \"./spot-sql.ts\";\nimport { listPartitionValues } from \"./coverage.ts\";\nimport {\n resolveMarketDir,\n writeSpotPartition,\n} from \"../../db/market-datasets.ts\";\n\nexport class ParquetSpotStore extends SpotStore {\n async writeBars(\n ticker: string,\n date: string,\n bars: BarRow[],\n ): Promise<void> {\n if (bars.length === 0) return;\n\n // Defense-in-depth write-side filter (per-bar).\n //\n // Reject any bar whose OHLC contains a zero or non-finite price. These\n // come from provider outages, holiday responses, or partial sessions and\n // poison every downstream aggregate that touches them: market.spot_daily\n // (min(low) → 0), enriched indicators (RSI/ATR/EMA gradient blowups),\n // and Prior_Range_vs_ATR (Intraday_Range_Pct → ~100% when low=0). The\n // earlier guard rejected only ALL-zero batches, but real-world bad data\n // tends to be partial — a few zero rows mixed into an otherwise valid\n // session. We filter those rows here so the staging table never sees them.\n //\n // Weekend dates (Sat/Sun) carry no real market activity; if the provider\n // returns rows for them, they're junk regardless of price values. Reject\n // the entire write rather than persisting a partition that downstream\n // logic will never use legitimately.\n const weekday = new Date(`${date}T00:00:00Z`).getUTCDay();\n if (weekday === 0 || weekday === 6) {\n console.warn(\n `ParquetSpotStore.writeBars: skipping weekend write ` +\n `(ticker=${ticker} date=${date} rows=${bars.length})`,\n );\n return;\n }\n\n const cleanBars = bars.filter(\n (b) =>\n Number.isFinite(b.open) && b.open > 0 &&\n Number.isFinite(b.high) && b.high > 0 &&\n Number.isFinite(b.low) && b.low > 0 &&\n Number.isFinite(b.close)&& b.close> 0,\n );\n const dropped = bars.length - cleanBars.length;\n if (dropped > 0) {\n console.warn(\n `ParquetSpotStore.writeBars: dropped ${dropped}/${bars.length} bars ` +\n `with zero/null/non-finite prices (ticker=${ticker} date=${date})`,\n );\n }\n if (cleanBars.length === 0) {\n console.warn(\n `ParquetSpotStore.writeBars: skipping write — all ${bars.length} bars ` +\n `filtered (ticker=${ticker} date=${date})`,\n );\n return;\n }\n bars = cleanBars;\n const staging = `_spot_write_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n await this.ctx.conn.run(\n `CREATE TEMP TABLE \"${staging}\" (\n ticker VARCHAR, date VARCHAR, time VARCHAR,\n open DOUBLE, high DOUBLE, low DOUBLE, close DOUBLE,\n bid DOUBLE, ask DOUBLE\n )`,\n );\n try {\n const placeholders = bars\n .map((_, i) => {\n const b = i * 9;\n return `($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6},$${b + 7},$${b + 8},$${b + 9})`;\n })\n .join(\", \");\n const params: unknown[] = bars.flatMap((b) => [\n ticker,\n date,\n b.time ?? \"09:30\",\n b.open,\n b.high,\n b.low,\n b.close,\n b.bid ?? null,\n b.ask ?? null,\n ]);\n await this.ctx.conn.run(\n `INSERT INTO \"${staging}\" VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n await writeSpotPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n ticker,\n date,\n selectQuery: `SELECT * FROM \"${staging}\"`,\n });\n } finally {\n try {\n await this.ctx.conn.run(`DROP TABLE IF EXISTS \"${staging}\"`);\n } catch {\n /* best-effort */\n }\n }\n }\n\n async writeFromSelect(\n partition: { ticker: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n return writeSpotPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n ticker: partition.ticker,\n date: partition.date,\n selectQuery: selectSql,\n });\n }\n\n async readBars(\n ticker: string,\n from: string,\n to: string,\n ): Promise<BarRow[]> {\n const direct = this.buildDirectParquetReadBarsSQL(ticker, from, to);\n // Both paths inline values — bound-param runAndReadAll(sql, values) leaks\n // extract_statements handles (parquet-quote-store.ts:327, spot-sql.ts).\n const { sql } = direct ?? buildReadBarsSQL(ticker, from, to);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n date: String(r[1]),\n time: String(r[2]),\n open: Number(r[3]),\n high: Number(r[4]),\n low: Number(r[5]),\n close: Number(r[6]),\n bid: r[7] == null ? undefined : Number(r[7]),\n ask: r[8] == null ? undefined : Number(r[8]),\n volume: 0,\n }));\n }\n\n async readDailyBars(\n ticker: string,\n from: string,\n to: string,\n ): Promise<BarRow[]> {\n const direct = this.buildDirectParquetReadBarsSQL(ticker, from, to, { dailyAgg: true });\n // Same leak rationale as readBars — both paths run via unbound query().\n const { sql } = direct ?? buildReadDailyBarsSQL(ticker, from, to);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n date: String(r[1]),\n time: \"09:30\",\n open: Number(r[2]),\n high: Number(r[3]),\n low: Number(r[4]),\n close: Number(r[5]),\n bid: r[6] == null ? undefined : Number(r[6]),\n ask: r[7] == null ? undefined : Number(r[7]),\n volume: 0,\n }));\n }\n\n async getCoverage(\n ticker: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n const tickerDir = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"spot\",\n `ticker=${ticker}`,\n );\n if (!existsSync(tickerDir)) {\n return { earliest: null, latest: null, missingDates: [], totalDates: 0 };\n }\n const allDates = listPartitionValues(tickerDir, \"date\");\n const dates = allDates.filter((d) => d >= from && d <= to);\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * Pure SQL builders for SpotStore reads.\n *\n * Every export is a pure function: given primitive inputs, it returns\n * `{ sql }` where the SQL string already has every partition-selector value\n * inlined as a SQL literal. Callers MUST invoke `runAndReadAll(sql)` with no\n * second argument.\n *\n * Why no positional parameters: the DuckDB Node-API binding routes\n * `runAndReadAll(sql, values)` through `node_bindings.extract_statements`,\n * which allocates a C++ handle with no JS-side destroy method (the wrapper\n * `DuckDBExtractedStatements` only has a constructor — see\n * `node_modules/@duckdb/node-api/lib/DuckDBExtractedStatements.js`). Handles\n * release only on JS GC, so under sustained read load the driver eventually\n * throws `Failed to execute prepared statement`. Inlining values into the SQL\n * string sends the call through `node_bindings.query()` instead, which is\n * leak-free. See `parquet-quote-store.ts:340` for the full root-cause writeup.\n *\n * Purity contract:\n * - No `this` / no `ctx` / no DB-connection value-level import\n * - No side effects; no IO\n * - Composable — concrete stores feed the result to `conn.run()`\n *\n * Security note: user-controlled values (ticker, from, to) are\n * single-quote-escaped via `escapeSqlLiteral` before interpolation. Inputs\n * arrive from typed config / partition-resolved registries (no untrusted\n * free-text), and the escape closes the residual injection vector.\n */\n\nimport { escapeSqlLiteral } from \"../../utils/quote-parquet-projection.ts\";\n\n/**\n * Shape returned by every SQL builder. Just the SQL text — values are inlined\n * as SQL literals (see file header for the why).\n */\nexport interface BuiltSQL {\n sql: string;\n}\n\nfunction lit(value: string): string {\n return `'${escapeSqlLiteral(value)}'`;\n}\n\n/**\n * Read raw minute bars from `market.spot` for a ticker over a date range.\n * Results are ordered by (date, time) so callers receive a deterministic stream.\n */\nexport function buildReadBarsSQL(\n ticker: string,\n from: string,\n to: string,\n): BuiltSQL {\n return {\n sql: `SELECT ticker, date, time, open, high, low, close, bid, ask\n FROM market.spot\n WHERE ticker = ${lit(ticker)} AND date >= ${lit(from)} AND date <= ${lit(to)}\n ORDER BY date, time`,\n };\n}\n\n/**\n * Aggregate minute bars in `market.spot` into RTH daily OHLCV rows.\n *\n * Uses DuckDB aggregate `first(col ORDER BY time)` / `last(col ORDER BY time)`\n * idioms. Window-function equivalents are explicitly avoided — they do NOT\n * coexist with `GROUP BY` and are a common source of incorrect ordering.\n */\nexport function buildReadDailyBarsSQL(\n ticker: string,\n from: string,\n to: string,\n): BuiltSQL {\n return {\n sql: `SELECT\n ticker,\n date,\n first(open ORDER BY time) AS open,\n max(high) AS high,\n min(low) AS low,\n last(close ORDER BY time) AS close,\n first(bid ORDER BY time) AS bid,\n last(ask ORDER BY time) AS ask\n FROM market.spot\n WHERE ticker = ${lit(ticker)}\n AND date >= ${lit(from)} AND date <= ${lit(to)}\n AND time >= '09:30' AND time <= '16:00'\n -- Defense-in-depth: drop minute bars with zero/null OHLC\n -- before aggregating. Mirrors market.spot_daily and the direct-\n -- parquet daily-agg path. Without it, min(low) collapses to 0\n -- on contaminated minutes and propagates into enriched indicators.\n AND open IS NOT NULL AND open > 0\n AND high IS NOT NULL AND high > 0\n AND low IS NOT NULL AND low > 0\n AND close IS NOT NULL AND close > 0\n GROUP BY ticker, date\n ORDER BY date`,\n };\n}\n\n/**\n * Project `(date, open)` using the RTH first-open aggregate.\n *\n * Used by the enricher's VIX RTH open path, where only the opening tick of\n * the VIX family is needed for term-structure context computation.\n */\nexport function buildReadRthOpensSQL(\n ticker: string,\n from: string,\n to: string,\n): BuiltSQL {\n return {\n sql: `SELECT date, first(open ORDER BY time) AS open\n FROM market.spot\n WHERE ticker = ${lit(ticker)}\n AND date >= ${lit(from)} AND date <= ${lit(to)}\n AND time >= '09:30' AND time <= '16:00'\n -- Defense-in-depth: drop bars with zero/null open before\n -- aggregating; first(open) could otherwise return 0 if a bad\n -- minute bar is the earliest in the session.\n AND open IS NOT NULL AND open > 0\n GROUP BY date\n ORDER BY date`,\n };\n}\n","/**\n * DuckdbSpotStore — spot minute bars persisted as DuckDB physical table\n * `market.spot` (schema from `ensureMarketDataTables`).\n *\n * Writes go through `INSERT OR REPLACE INTO market.spot` with positional\n * placeholders. Reads share the same SQL builders as ParquetSpotStore — the\n * `market.spot` identifier resolves to the physical table in this mode, the\n * Parquet view in the other (CONTEXT.md D-04). Coverage uses SELECT DISTINCT\n * (D-27).\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { SpotStore } from \"./spot-store.ts\";\nimport type { BarRow, CoverageReport } from \"./types.ts\";\nimport { buildReadBarsSQL, buildReadDailyBarsSQL } from \"./spot-sql.ts\";\n\nexport class DuckdbSpotStore extends SpotStore {\n async writeBars(\n ticker: string,\n date: string,\n bars: BarRow[],\n ): Promise<void> {\n if (bars.length === 0) return;\n const placeholders = bars\n .map((_, i) => {\n const b = i * 9;\n return `($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6},$${b + 7},$${b + 8},$${b + 9})`;\n })\n .join(\", \");\n const params: unknown[] = bars.flatMap((b) => [\n ticker,\n date,\n b.time ?? \"09:30\",\n b.open,\n b.high,\n b.low,\n b.close,\n b.bid ?? null,\n b.ask ?? null,\n ]);\n await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.spot\n (ticker, date, time, open, high, low, close, bid, ask)\n VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n }\n\n async writeFromSelect(\n _partition: { ticker: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n // INSERT OR REPLACE preserves idempotency semantics of writeBars.\n // Column list matches market.spot schema (ticker, date, time, open, high, low, close, bid, ask).\n const result = await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.spot\n (ticker, date, time, open, high, low, close, bid, ask)\n ${selectSql}`,\n );\n return { rowCount: Number(result.rowsChanged) };\n }\n\n async readBars(\n ticker: string,\n from: string,\n to: string,\n ): Promise<BarRow[]> {\n // Builders inline values as SQL literals; the unbound runAndReadAll(sql)\n // path bypasses extract_statements (see spot-sql.ts header).\n const { sql } = buildReadBarsSQL(ticker, from, to);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n date: String(r[1]),\n time: String(r[2]),\n open: Number(r[3]),\n high: Number(r[4]),\n low: Number(r[5]),\n close: Number(r[6]),\n bid: r[7] == null ? undefined : Number(r[7]),\n ask: r[8] == null ? undefined : Number(r[8]),\n volume: 0,\n }));\n }\n\n async readDailyBars(\n ticker: string,\n from: string,\n to: string,\n ): Promise<BarRow[]> {\n const { sql } = buildReadDailyBarsSQL(ticker, from, to);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n date: String(r[1]),\n time: \"09:30\",\n open: Number(r[2]),\n high: Number(r[3]),\n low: Number(r[4]),\n close: Number(r[5]),\n bid: r[6] == null ? undefined : Number(r[6]),\n ask: r[7] == null ? undefined : Number(r[7]),\n volume: 0,\n }));\n }\n\n async getCoverage(\n ticker: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n // Inline literals — same leak rationale as readBars (spot-sql.ts header).\n const tickerLit = ticker.replace(/'/g, \"''\");\n const fromLit = from.replace(/'/g, \"''\");\n const toLit = to.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT DISTINCT date FROM market.spot\n WHERE ticker = '${tickerLit}' AND date >= '${fromLit}' AND date <= '${toLit}'\n ORDER BY date`,\n );\n const dates = reader.getRows().map((r) => String(r[0]));\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * ParquetEnrichedStore — thin wrapper over the existing `market-enricher.ts`\n * runEnrichment pipeline (D-14 / D-15).\n *\n * Enrichment math stays in `market-enricher.ts` untouched. This store injects\n * a `SpotStore` + watermark adapter at the enricher's IO boundaries and\n * provides a typed `read()` over `market.enriched` with optional joins to\n * `market.spot` (daily OHLCV aggregate) and `market.enriched_context`.\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode` — the factory\n * chooses the backend once at construction, every method is monomorphic.\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport { EnrichedStore, type EnrichedReadOpts } from \"./enriched-store.ts\";\nimport { SpotStore } from \"./spot-store.ts\";\nimport type { StoreContext, CoverageReport } from \"./types.ts\";\nimport { buildReadEnrichedSQL } from \"./enriched-sql.ts\";\nimport { resolveMarketDir } from \"../../db/market-datasets.ts\";\nimport { runEnrichment } from \"../../utils/market-enricher.ts\";\nimport {\n getEnrichedThrough,\n upsertEnrichedThrough,\n} from \"../../db/json-adapters.ts\";\n\nexport class ParquetEnrichedStore extends EnrichedStore {\n private readonly spotStore: SpotStore;\n constructor(\n ctx: StoreContext,\n spotStore: SpotStore,\n ) {\n super(ctx);\n this.spotStore = spotStore;\n }\n\n async compute(ticker: string, _from: string, _to: string): Promise<void> {\n // _from/_to are informational — the enricher uses its own watermark plus\n // a 200-day lookback. The thin wrapper only injects IO; math stays in\n // `market-enricher.ts` (D-14).\n await runEnrichment(\n this.ctx.conn,\n ticker,\n { dataDir: this.ctx.dataDir, parquetMode: true },\n {\n spotStore: this.spotStore,\n watermarkStore: {\n get: (t) => getEnrichedThrough(t, this.ctx.dataDir),\n upsert: (t, v) => upsertEnrichedThrough(t, v, this.ctx.dataDir),\n },\n },\n );\n }\n\n async computeContext(_from: string, _to: string): Promise<void> {\n // D-16: wraps the existing Tier 2 context computation. Running\n // runEnrichment for each VIX-family ticker triggers Tier 2 internally\n // (it runs after every Tier 1 pass and is idempotent at date granularity).\n // If a ticker has no daily data yet, runEnrichment returns a skipped\n // Tier 1 status and skips Tier 2 — safe no-op.\n for (const ticker of [\"VIX\", \"VIX9D\", \"VIX3M\"]) {\n await runEnrichment(\n this.ctx.conn,\n ticker,\n { dataDir: this.ctx.dataDir, parquetMode: true },\n {\n spotStore: this.spotStore,\n watermarkStore: {\n get: (t) => getEnrichedThrough(t, this.ctx.dataDir),\n upsert: (t, v) => upsertEnrichedThrough(t, v, this.ctx.dataDir),\n },\n },\n );\n }\n }\n\n async read(opts: EnrichedReadOpts): Promise<Record<string, unknown>[]> {\n // Fast path: when the caller doesn't need cross-ticker context or RTH\n // OHLCV joins, read directly from the ticker's parquet file. Avoids\n // the `market.enriched` view glob (~430ms) AND the extract_statements\n // GC handle leak (see parquet-quote-store.ts:327). Hit on every\n // entry-pipeline date when an RSI / vol-regime filter is configured.\n const wantsJoins = !!opts.includeContext || !!opts.includeOhlcv;\n if (!wantsJoins) {\n const filePath = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"enriched\",\n `ticker=${opts.ticker}`,\n \"data.parquet\",\n );\n if (existsSync(filePath)) {\n const escaped = filePath.replace(/'/g, \"''\");\n const fromLit = opts.from.replace(/'/g, \"''\");\n const toLit = opts.to.replace(/'/g, \"''\");\n const sql = `SELECT * FROM read_parquet('${escaped}', hive_partitioning=true)\n WHERE date >= '${fromLit}' AND date <= '${toLit}'\n ORDER BY date`;\n const reader = await this.ctx.conn.runAndReadAll(sql);\n const names = reader.columnNames();\n return reader\n .getRows()\n .map((row) => Object.fromEntries(names.map((n, i) => [n, row[i]])));\n }\n }\n // Builder inlines values; unbound runAndReadAll(sql) bypasses extract_statements.\n const { sql } = buildReadEnrichedSQL({\n ticker: opts.ticker,\n from: opts.from,\n to: opts.to,\n includeContext: !!opts.includeContext,\n includeOhlcv: !!opts.includeOhlcv,\n });\n const reader = await this.ctx.conn.runAndReadAll(sql);\n const names = reader.columnNames();\n return reader\n .getRows()\n .map((row) =>\n Object.fromEntries(names.map((n, i) => [n, row[i]])),\n );\n }\n\n async getCoverage(ticker: string): Promise<CoverageReport> {\n // D-27: coverage comes from the enriched data itself (not the watermark\n // JSON) — \"what rows exist\" is independent of \"where did enrichment stop\".\n const filePath = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"enriched\",\n `ticker=${ticker}`,\n \"data.parquet\",\n );\n if (!existsSync(filePath)) {\n // No enriched Parquet file for this ticker — empty report. Querying\n // market.enriched here would surface rows from other tickers (the view\n // is a union), so we return empty early to match Parquet reality.\n return { earliest: null, latest: null, missingDates: [], totalDates: 0 };\n }\n const tickerLit = ticker.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT DISTINCT date FROM market.enriched WHERE ticker = '${tickerLit}' ORDER BY date`,\n );\n const dates = reader.getRows().map((r) => String(r[0]));\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * RTH (Regular Trading Hours) aggregation helper.\n *\n * Emits a scalar subquery / derived table that rolls up minute bars in\n * `market.spot` into daily OHLCV rows over [09:30, 16:00].\n *\n * DuckDB idiom note (PATTERNS.md \"rth-aggregation.ts\"; RESEARCH.md Pitfall 3):\n * the canonical way to get first/last per group is the `first(col ORDER BY ...)`\n * aggregate — NEVER the window-function equivalents (which cannot be combined\n * with `GROUP BY`).\n *\n * Values arrive pre-quoted from the caller so the subquery composes cleanly\n * with the surrounding inline-literal SQL (see `spot-sql.ts` header for why\n * positional params are off-limits — the extract_statements GC leak).\n *\n * Used by:\n * - `enriched-sql.ts::buildReadEnrichedSQL` when `includeOhlcv=true`\n */\n\nexport interface RthWindowOpts {\n /** SQL-literal expression for the ticker (e.g. `'SPX'`). */\n tickerLit: string;\n /** SQL-literal expression for the `from` date (e.g. `'2025-01-01'`). */\n fromLit: string;\n /** SQL-literal expression for the `to` date (e.g. `'2025-01-31'`). */\n toLit: string;\n}\n\n/**\n * Emit a derived-table expression that produces daily OHLCV rows by aggregating\n * minute bars in `market.spot` within the RTH window. Inputs are pre-escaped\n * SQL literals so the subquery embeds directly inside a larger inline-literal\n * SQL statement (no positional params anywhere in the pipeline).\n */\nexport function rthDailyAggregateSubquery(opts: RthWindowOpts): string {\n const { tickerLit, fromLit, toLit } = opts;\n return `(\n SELECT ticker, date,\n first(open ORDER BY time) AS open,\n max(high) AS high,\n min(low) AS low,\n last(close ORDER BY time) AS close\n FROM market.spot\n WHERE ticker = ${tickerLit}\n AND date >= ${fromLit} AND date <= ${toLit}\n AND time >= '09:30' AND time <= '16:00'\n -- Defense-in-depth: drop minute bars with zero/null OHLC before\n -- aggregating. Mirrors the same guard on market.spot_daily and the\n -- direct-parquet daily-agg path. Without it, min(low) collapses to 0\n -- on contaminated minutes (provider gaps in the spot ingest).\n AND open IS NOT NULL AND open > 0\n AND high IS NOT NULL AND high > 0\n AND low IS NOT NULL AND low > 0\n AND close IS NOT NULL AND close > 0\n GROUP BY ticker, date\n )`;\n}\n","/**\n * Pure SQL builder for EnrichedStore reads.\n *\n * The enriched read supports two optional joins controlled by flags:\n * - `includeOhlcv` → LEFT JOIN a daily RTH aggregate of `market.spot`\n * - `includeContext` → LEFT JOIN `market.enriched_context` for cross-ticker\n * VIX-family fields (Vol_Regime, Term_Structure_State,\n * Trend_Direction, VIX_Spike_Pct, VIX_Gap_Pct)\n *\n * Values are inlined as SQL literals — see `spot-sql.ts` header for the\n * extract_statements GC leak that ruled out positional parameters.\n *\n * Purity contract: no `this`, no `ctx`, no DB-connection value-level imports.\n * Tests live in `tests/unit/market/stores/enriched-sql.test.ts`.\n */\nimport { escapeSqlLiteral } from \"../../utils/quote-parquet-projection.ts\";\nimport { rthDailyAggregateSubquery } from \"./rth-aggregation.ts\";\nimport type { BuiltSQL } from \"./spot-sql.ts\";\n\nexport interface BuildReadEnrichedArgs {\n ticker: string;\n from: string;\n to: string;\n includeContext: boolean;\n includeOhlcv: boolean;\n}\n\nfunction lit(value: string): string {\n return `'${escapeSqlLiteral(value)}'`;\n}\n\n/**\n * Build the `SELECT ... FROM market.enriched ...` SQL, optionally joined with\n * the RTH daily aggregate from `market.spot` and the `market.enriched_context`\n * table.\n */\nexport function buildReadEnrichedSQL(\n args: BuildReadEnrichedArgs,\n): BuiltSQL {\n const { ticker, from, to, includeContext, includeOhlcv } = args;\n\n const tickerLit = lit(ticker);\n const fromLit = lit(from);\n const toLit = lit(to);\n\n const ohlcvJoin = includeOhlcv\n ? `LEFT JOIN ${rthDailyAggregateSubquery({\n tickerLit,\n fromLit,\n toLit,\n })} s_daily\n ON s_daily.ticker = e.ticker AND s_daily.date = e.date`\n : \"\";\n\n const ctxJoin = includeContext\n ? `LEFT JOIN market.enriched_context c ON c.date = e.date`\n : \"\";\n\n const ohlcvCols = includeOhlcv\n ? \", s_daily.open, s_daily.high, s_daily.low, s_daily.close\"\n : \"\";\n\n const ctxCols = includeContext\n ? \", c.Vol_Regime, c.Term_Structure_State, c.Trend_Direction, c.VIX_Spike_Pct, c.VIX_Gap_Pct\"\n : \"\";\n\n const sql = `\n SELECT e.*${ohlcvCols}${ctxCols}\n FROM market.enriched e\n ${ohlcvJoin}\n ${ctxJoin}\n WHERE e.ticker = ${tickerLit} AND e.date >= ${fromLit} AND e.date <= ${toLit}\n ORDER BY e.date\n `;\n\n return { sql };\n}\n","/**\n * DuckdbEnrichedStore — thin wrapper over the existing `market-enricher.ts`\n * runEnrichment pipeline (D-14 / D-15) for the DuckDB physical-table backend.\n *\n * Reads share the SAME SQL as ParquetEnrichedStore because the\n * `market.enriched` identifier resolves to the physical table in this mode\n * and to the Parquet view in the other (CONTEXT.md D-04). The write path\n * (compute/computeContext) injects SpotStore + the watermark adapter into\n * the enricher; the math stays in `market-enricher.ts`.\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { EnrichedStore, type EnrichedReadOpts } from \"./enriched-store.ts\";\nimport { SpotStore } from \"./spot-store.ts\";\nimport type { StoreContext, CoverageReport } from \"./types.ts\";\nimport { buildReadEnrichedSQL } from \"./enriched-sql.ts\";\nimport { runEnrichment } from \"../../utils/market-enricher.ts\";\nimport {\n getEnrichedThrough,\n upsertEnrichedThrough,\n} from \"../../db/json-adapters.ts\";\n\nexport class DuckdbEnrichedStore extends EnrichedStore {\n private readonly spotStore: SpotStore;\n constructor(\n ctx: StoreContext,\n spotStore: SpotStore,\n ) {\n super(ctx);\n this.spotStore = spotStore;\n }\n\n async compute(ticker: string, _from: string, _to: string): Promise<void> {\n await runEnrichment(\n this.ctx.conn,\n ticker,\n { dataDir: this.ctx.dataDir, parquetMode: false },\n {\n spotStore: this.spotStore,\n watermarkStore: {\n get: (t) => getEnrichedThrough(t, this.ctx.dataDir),\n upsert: (t, v) => upsertEnrichedThrough(t, v, this.ctx.dataDir),\n },\n },\n );\n }\n\n async computeContext(_from: string, _to: string): Promise<void> {\n for (const ticker of [\"VIX\", \"VIX9D\", \"VIX3M\"]) {\n await runEnrichment(\n this.ctx.conn,\n ticker,\n { dataDir: this.ctx.dataDir, parquetMode: false },\n {\n spotStore: this.spotStore,\n watermarkStore: {\n get: (t) => getEnrichedThrough(t, this.ctx.dataDir),\n upsert: (t, v) => upsertEnrichedThrough(t, v, this.ctx.dataDir),\n },\n },\n );\n }\n }\n\n async read(opts: EnrichedReadOpts): Promise<Record<string, unknown>[]> {\n // Builder inlines values; unbound runAndReadAll(sql) bypasses extract_statements\n // (see enriched-sql.ts / spot-sql.ts headers).\n const { sql } = buildReadEnrichedSQL({\n ticker: opts.ticker,\n from: opts.from,\n to: opts.to,\n includeContext: !!opts.includeContext,\n includeOhlcv: !!opts.includeOhlcv,\n });\n const reader = await this.ctx.conn.runAndReadAll(sql);\n const names = reader.columnNames();\n return reader\n .getRows()\n .map((row) =>\n Object.fromEntries(names.map((n, i) => [n, row[i]])),\n );\n }\n\n async getCoverage(ticker: string): Promise<CoverageReport> {\n // D-27: coverage answers \"what rows exist\" directly from the enriched\n // table — not from the watermark JSON. Inline literal — same leak rationale.\n const tickerLit = ticker.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT DISTINCT date FROM market.enriched WHERE ticker = '${tickerLit}' ORDER BY date`,\n );\n const dates = reader.getRows().map((r) => String(r[0]));\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * ParquetChainStore — option chain snapshots persisted as underlying-first\n * Hive-partitioned Parquet files (option_chain/underlying=X/date=Y/data.parquet).\n *\n * Writes flow through `writeChainPartition` (Phase 1 typed helper); reads use\n * `buildReadChainSQL` against the `market.option_chain` view registered by\n * `createMarketParquetViews`. Coverage via filesystem enumeration (D-26).\n *\n * Note: Phase 3 reorganizes existing `option_chain/date=Y/` directories into\n * `option_chain/underlying=X/date=Y/`. Phase 2 only writes the new layout\n * when the store is called; it does not migrate pre-existing data.\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport { ChainStore } from \"./chain-store.ts\";\nimport type { ContractRow, CoverageReport } from \"./types.ts\";\nimport { buildReadChainSQL } from \"./chain-sql.ts\";\nimport { listPartitionValues } from \"./coverage.ts\";\nimport {\n resolveMarketDir,\n writeChainPartition,\n} from \"../../db/market-datasets.ts\";\n\nfunction escapeSqlLiteral(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nexport class ParquetChainStore extends ChainStore {\n async writeChain(\n underlying: string,\n date: string,\n rows: ContractRow[],\n ): Promise<void> {\n if (rows.length === 0) return;\n const staging = `_chain_write_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n await this.ctx.conn.run(\n `CREATE TEMP TABLE \"${staging}\" (\n underlying VARCHAR, date VARCHAR, ticker VARCHAR,\n contract_type VARCHAR, strike DOUBLE, expiration VARCHAR,\n dte INTEGER, exercise_style VARCHAR\n )`,\n );\n try {\n const placeholders = rows\n .map((_, i) => {\n const b = i * 8;\n return `($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6},$${b + 7},$${b + 8})`;\n })\n .join(\", \");\n const params: unknown[] = rows.flatMap((r) => [\n underlying,\n date,\n r.ticker,\n r.contract_type,\n r.strike,\n r.expiration,\n r.dte ?? null,\n r.exercise_style ?? null,\n ]);\n await this.ctx.conn.run(\n `INSERT INTO \"${staging}\" VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n await writeChainPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n underlying,\n date,\n selectQuery: `SELECT * FROM \"${staging}\"`,\n });\n } finally {\n try {\n await this.ctx.conn.run(`DROP TABLE IF EXISTS \"${staging}\"`);\n } catch {\n /* best-effort */\n }\n }\n }\n\n async writeFromSelect(\n partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n return writeChainPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n underlying: partition.underlying,\n date: partition.date,\n selectQuery: selectSql,\n });\n }\n\n /**\n * Filesystem-based chain probe — bypasses both the `market.option_chain`\n * view glob (~430ms per call) AND the `extract_statements` GC handle leak\n * that accumulates over long-running read workloads.\n * The chain partition path is deterministic, so the probe collapses to a\n * single `existsSync` syscall.\n */\n override async hasChain(underlying: string, date: string): Promise<boolean> {\n const partitionPath = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_chain\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n return existsSync(partitionPath);\n }\n\n async readChain(\n underlying: string,\n date: string,\n ): Promise<ContractRow[]> {\n const partitionPath = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_chain\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n if (existsSync(partitionPath)) {\n // Direct-parquet path: no params → no extract_statements leak, no view\n // glob overhead. SQL string is fully literal (path is from typed config,\n // not user input — no injection vector beyond the existing data dir).\n const sql = `SELECT underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style\n FROM read_parquet('${escapeSqlLiteral(partitionPath)}', hive_partitioning=true)\n ORDER BY ticker`;\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n underlying: String(r[0]),\n date: String(r[1]),\n ticker: String(r[2]),\n contract_type: String(r[3]) as ContractRow[\"contract_type\"],\n strike: Number(r[4]),\n expiration: String(r[5]),\n dte: Number(r[6]),\n exercise_style: String(r[7]),\n }));\n }\n // Builder inlines values; unbound runAndReadAll(sql) bypasses extract_statements.\n const { sql } = buildReadChainSQL(underlying, date);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n underlying: String(r[0]),\n date: String(r[1]),\n ticker: String(r[2]),\n contract_type: String(r[3]) as ContractRow[\"contract_type\"],\n strike: Number(r[4]),\n expiration: String(r[5]),\n dte: Number(r[6]),\n exercise_style: String(r[7]),\n }));\n }\n\n override async readChainDates(\n underlying: string,\n dates: string[],\n ): Promise<ContractRow[]> {\n if (dates.length === 0) return [];\n\n const marketDir = resolveMarketDir(this.ctx.dataDir);\n const directPaths: string[] = [];\n for (const date of dates) {\n const partitionPath = path.join(\n marketDir,\n \"option_chain\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n if (existsSync(partitionPath)) directPaths.push(partitionPath);\n }\n if (directPaths.length === 0) return super.readChainDates(underlying, dates);\n\n const fileList = directPaths.map((filePath) => `'${escapeSqlLiteral(filePath)}'`).join(\", \");\n // Direct path: omit params arg entirely to bypass extract_statements\n // (parquet-quote-store.ts:327 has the full root-cause writeup).\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style\n FROM read_parquet([${fileList}], hive_partitioning=true)\n ORDER BY date, ticker`,\n );\n return reader.getRows().map((r) => ({\n underlying: String(r[0]),\n date: String(r[1]),\n ticker: String(r[2]),\n contract_type: String(r[3]) as ContractRow[\"contract_type\"],\n strike: Number(r[4]),\n expiration: String(r[5]),\n dte: Number(r[6]),\n exercise_style: String(r[7]),\n }));\n }\n\n async getCoverage(\n underlying: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n const dir = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_chain\",\n `underlying=${underlying}`,\n );\n if (!existsSync(dir)) {\n return { earliest: null, latest: null, missingDates: [], totalDates: 0 };\n }\n const allDates = listPartitionValues(dir, \"date\");\n const dates = allDates.filter((d) => d >= from && d <= to);\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * DuckdbChainStore — option chain persisted as DuckDB physical table\n * `market.option_chain`. Phase 1 schema already includes the `underlying`\n * column (D-13), so no DROP+recreate migration is needed.\n *\n * Writes via `INSERT OR REPLACE INTO market.option_chain` with positional\n * placeholders; reads via shared `buildReadChainSQL`. Coverage via\n * SELECT DISTINCT date (D-27).\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { ChainStore } from \"./chain-store.ts\";\nimport type { ContractRow, CoverageReport } from \"./types.ts\";\nimport { buildReadChainSQL } from \"./chain-sql.ts\";\n\nexport class DuckdbChainStore extends ChainStore {\n async writeChain(\n underlying: string,\n date: string,\n rows: ContractRow[],\n ): Promise<void> {\n if (rows.length === 0) return;\n const placeholders = rows\n .map((_, i) => {\n const b = i * 8;\n return `($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6},$${b + 7},$${b + 8})`;\n })\n .join(\", \");\n const params: unknown[] = rows.flatMap((r) => [\n underlying,\n date,\n r.ticker,\n r.contract_type,\n r.strike,\n r.expiration,\n r.dte ?? null,\n r.exercise_style ?? null,\n ]);\n await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.option_chain\n (underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style)\n VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n }\n\n async writeFromSelect(\n _partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n const result = await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.option_chain\n (underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style)\n ${selectSql}`,\n );\n return { rowCount: Number(result.rowsChanged) };\n }\n\n async readChain(\n underlying: string,\n date: string,\n ): Promise<ContractRow[]> {\n // Builder inlines values; unbound runAndReadAll(sql) bypasses\n // extract_statements (chain-sql.ts header).\n const { sql } = buildReadChainSQL(underlying, date);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n underlying: String(r[0]),\n date: String(r[1]),\n ticker: String(r[2]),\n contract_type: String(r[3]) as ContractRow[\"contract_type\"],\n strike: Number(r[4]),\n expiration: String(r[5]),\n dte: Number(r[6]),\n exercise_style: String(r[7]),\n }));\n }\n\n async getCoverage(\n underlying: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n // Inline literals — same leak rationale as readChain.\n const underlyingLit = underlying.replace(/'/g, \"''\");\n const fromLit = from.replace(/'/g, \"''\");\n const toLit = to.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT DISTINCT date FROM market.option_chain\n WHERE underlying = '${underlyingLit}' AND date >= '${fromLit}' AND date <= '${toLit}'\n ORDER BY date`,\n );\n const dates = reader.getRows().map((r) => String(r[0]));\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * ParquetQuoteStore — option minute NBBO quotes persisted as underlying-first\n * Hive-partitioned Parquet files\n * (option_quote_minutes/underlying=X/date=Y/data.parquet).\n *\n * D-06 / D-08: readQuotes accepts a batch of OCC tickers plus a date range and\n * returns Map<occTicker, QuoteRow[]> with timestamp-sorted values per\n * contract. Matches the primary multi-ticker consumer pattern (bulk\n * `ticker IN (...) AND date BETWEEN ...` → group-by-ticker).\n *\n * D-07 / Pitfall 4: all OCC tickers in a single call MUST resolve to the same\n * underlying. First-iteration behavior is to throw a clear error naming both\n * conflicting tickers — consumers must group reads by underlying themselves.\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport { QuoteStore } from \"./quote-store.ts\";\nimport type {\n QuoteRow,\n CoverageReport,\n ReadWindowParams,\n WindowQuoteRow,\n} from \"./types.ts\";\nimport { listPartitionValues } from \"./coverage.ts\";\nimport {\n resolveMarketDir,\n writeQuoteMinutesPartition,\n} from \"../../db/market-datasets.ts\";\nimport { extractRoot } from \"../tickers/resolver.ts\";\nimport {\n describeQueryColumns,\n describeReadParquetColumns,\n escapeSqlLiteral,\n quoteParquetCanonicalProjection,\n quoteParquetCanonicalWriteProjection,\n readParquetFilesSql,\n} from \"../../utils/quote-parquet-projection.ts\";\n\nfunction parseQuoteRow(row: unknown[]): QuoteRow {\n const occ = String(row[2]);\n const date = String(row[1]);\n const time = String(row[3]);\n return {\n occ_ticker: occ,\n timestamp: `${date} ${time}`,\n bid: Number(row[4]),\n ask: Number(row[5]),\n source: row[8] == null ? null : (String(row[8]) as QuoteRow[\"source\"]),\n delta: row[9] == null ? null : Number(row[9]),\n gamma: row[10] == null ? null : Number(row[10]),\n theta: row[11] == null ? null : Number(row[11]),\n vega: row[12] == null ? null : Number(row[12]),\n iv: row[13] == null ? null : Number(row[13]),\n greeks_source: row[14] == null ? null : String(row[14]) as QuoteRow[\"greeks_source\"],\n greeks_revision: row[15] == null ? null : Number(row[15]),\n rate_type: row[16] == null ? null : String(row[16]),\n rate_value: row[17] == null ? null : Number(row[17]),\n gamma_source: row[18] == null ? null : String(row[18]),\n };\n}\n\nexport class ParquetQuoteStore extends QuoteStore {\n async writeQuotes(\n underlying: string,\n date: string,\n quotes: QuoteRow[],\n ): Promise<void> {\n if (quotes.length === 0) return;\n // Append rows via DuckDBAppender (typed per-column, no SQL parse overhead)\n // rather than a parameterized INSERT with O(N) placeholders — the latter\n // forces DuckDB to parse a multi-megabyte SQL statement before a single\n // row lands, which was the dominant wall-clock cost on a 5M-row SPX day.\n const staging = `_quote_write_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n // Greeks persist as REAL (FLOAT32): Black-Scholes outputs sit well within\n // single-precision range, and the 4-byte-per-column layout halves per-row\n // greek cost in parquet — a full SPX backfill under DOUBLE would blow the\n // 250GB archive budget; under REAL it fits comfortably.\n await this.ctx.conn.run(\n `CREATE TEMP TABLE \"${staging}\" (\n underlying VARCHAR, date VARCHAR, ticker VARCHAR, time VARCHAR,\n bid DOUBLE, ask DOUBLE, mid DOUBLE,\n last_updated_ns BIGINT, source VARCHAR,\n delta REAL, gamma REAL, theta REAL, vega REAL, iv REAL,\n greeks_source VARCHAR, greeks_revision INTEGER,\n rate_type VARCHAR, rate_value DOUBLE, gamma_source VARCHAR\n )`,\n );\n try {\n const appender = await this.ctx.conn.createAppender(staging);\n try {\n for (const q of quotes) {\n // QuoteRow.timestamp is \"YYYY-MM-DD HH:MM\" — split into date/time.\n // If the timestamp omits the time (legacy producers), default to 09:30.\n const spaceIdx = q.timestamp.indexOf(\" \");\n const qdate = spaceIdx === -1 ? date : q.timestamp.slice(0, spaceIdx);\n const qtime = spaceIdx === -1 ? \"09:30\" : q.timestamp.slice(spaceIdx + 1);\n appender.appendVarchar(underlying);\n appender.appendVarchar(qdate);\n appender.appendVarchar(q.occ_ticker);\n appender.appendVarchar(qtime);\n appender.appendDouble(q.bid);\n appender.appendDouble(q.ask);\n appender.appendDouble((q.bid + q.ask) / 2);\n appender.appendNull(); // last_updated_ns — not tracked in QuoteRow\n if (q.source == null) appender.appendNull();\n else appender.appendVarchar(q.source);\n if (q.delta == null) appender.appendNull();\n else appender.appendFloat(q.delta);\n if (q.gamma == null) appender.appendNull();\n else appender.appendFloat(q.gamma);\n if (q.theta == null) appender.appendNull();\n else appender.appendFloat(q.theta);\n if (q.vega == null) appender.appendNull();\n else appender.appendFloat(q.vega);\n if (q.iv == null) appender.appendNull();\n else appender.appendFloat(q.iv);\n if (q.greeks_source == null) appender.appendNull();\n else appender.appendVarchar(q.greeks_source);\n if (q.greeks_revision == null) appender.appendNull();\n else appender.appendInteger(q.greeks_revision);\n if (q.rate_type == null) appender.appendNull();\n else appender.appendVarchar(q.rate_type);\n if (q.rate_value == null) appender.appendNull();\n else appender.appendDouble(q.rate_value);\n if (q.gamma_source == null) appender.appendNull();\n else appender.appendVarchar(q.gamma_source);\n appender.endRow();\n }\n appender.flushSync();\n } finally {\n appender.closeSync();\n }\n await writeQuoteMinutesPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n underlying,\n date,\n selectQuery: `SELECT * FROM \"${staging}\"`,\n });\n } finally {\n try {\n await this.ctx.conn.run(`DROP TABLE IF EXISTS \"${staging}\"`);\n } catch {\n /* best-effort */\n }\n }\n }\n\n async writeFromSelect(\n partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n const columns = await describeQueryColumns(this.ctx.conn, selectSql);\n // Force REAL greeks on write regardless of source type — keeps parquet\n // footprint half-size vs DOUBLE without depending on upstream producers\n // to cast correctly.\n const projection = quoteParquetCanonicalWriteProjection(columns, \"q\");\n return writeQuoteMinutesPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n underlying: partition.underlying,\n date: partition.date,\n selectQuery: `SELECT ${projection} FROM (${selectSql}) AS q`,\n });\n }\n\n async readQuotes(\n occTickers: string[],\n from: string,\n to: string,\n ): Promise<Map<string, QuoteRow[]>> {\n if (occTickers.length === 0) return new Map();\n // D-07: validate all tickers resolve to the same underlying BEFORE any SQL\n // runs. A mixed batch is almost always a bug in the caller; surface it\n // with both conflicting OCC tickers + resolved underlyings for debugging.\n const firstUnderlying = this.ctx.tickers.resolve(\n extractRoot(occTickers[0]),\n );\n for (const t of occTickers) {\n const u = this.ctx.tickers.resolve(extractRoot(t));\n if (u !== firstUnderlying) {\n throw new Error(\n `QuoteStore.readQuotes: mixed underlyings in batch — ` +\n `${occTickers[0]} resolves to ${firstUnderlying}, ${t} resolves to ${u}. ` +\n `Consumers must group reads by underlying.`,\n );\n }\n }\n const underlyingDir = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_quote_minutes\",\n `underlying=${firstUnderlying}`,\n );\n if (!existsSync(underlyingDir)) return new Map();\n const files = listPartitionValues(underlyingDir, \"date\")\n .filter((date) => date >= from && date <= to)\n .map((date) => path.join(underlyingDir, `date=${date}`, \"data.parquet\"))\n .filter((filePath) => existsSync(filePath));\n if (files.length === 0) return new Map();\n\n const source = readParquetFilesSql(files);\n const columns = await describeReadParquetColumns(this.ctx.conn, source);\n const projection = quoteParquetCanonicalProjection(columns, \"q\");\n // Inline every value as a SQL literal and call the unbound\n // runAndReadAll(sql) form — the bound (sql, values) path routes through\n // node_bindings.extract_statements, which leaks a non-destroyable handle\n // per call and eventually throws \"Failed to execute prepared statement\"\n // under sustained read load. See readWindow below for the full writeup.\n const fromLit = `'${escapeSqlLiteral(from)}'`;\n const toLit = `'${escapeSqlLiteral(to)}'`;\n const tickerList = occTickers\n .map((t) => `'${escapeSqlLiteral(t)}'`)\n .join(\", \");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT ${projection}\n FROM ${source} AS q\n WHERE q.date >= ${fromLit}\n AND q.date <= ${toLit}\n AND q.ticker IN (${tickerList})\n ORDER BY q.ticker, q.date, q.time`,\n );\n const out = new Map<string, QuoteRow[]>();\n for (const row of reader.getRows()) {\n const qr = parseQuoteRow(row);\n const occ = qr.occ_ticker;\n let arr = out.get(occ);\n if (!arr) {\n arr = [];\n out.set(occ, arr);\n }\n arr.push(qr);\n }\n return out;\n }\n\n override async readQuotesBulk(\n tickersByDate: Map<string, Set<string>>,\n timeStart: string,\n timeEnd: string,\n ): Promise<Map<string, QuoteRow[]>> {\n const out = new Map<string, QuoteRow[]>();\n if (tickersByDate.size === 0) return out;\n\n const perf = process.env.QUOTE_STORE_PERF_DEBUG === \"1\";\n const marketDir = resolveMarketDir(this.ctx.dataDir);\n\n for (const [underlying, perDate] of this.groupTickersByUnderlying(tickersByDate)) {\n const occUnion = new Set<string>();\n const filePaths: string[] = [];\n const wantedPairs: string[] = [];\n\n for (const [date, occs] of perDate) {\n if (occs.size === 0) continue;\n const partitionPath = path.join(\n marketDir,\n \"option_quote_minutes\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n if (!existsSync(partitionPath)) continue;\n filePaths.push(partitionPath);\n for (const occ of occs) {\n occUnion.add(occ);\n wantedPairs.push(`('${escapeSqlLiteral(date)}', '${escapeSqlLiteral(occ)}')`);\n }\n }\n\n if (filePaths.length === 0 || wantedPairs.length === 0) continue;\n\n const source = readParquetFilesSql(filePaths);\n const columns = await describeReadParquetColumns(this.ctx.conn, source);\n const projection = quoteParquetCanonicalProjection(columns, \"q\");\n // Time bounds inlined as SQL literals so the call takes the unbound\n // runAndReadAll(sql) path (the (date, ticker) VALUES are already inlined\n // above). The bound form leaks an extract_statements handle per call —\n // see readWindow for the full root-cause writeup.\n const timeStartLit = `'${escapeSqlLiteral(timeStart)}'`;\n const timeEndLit = `'${escapeSqlLiteral(timeEnd)}'`;\n const sql = `WITH wanted(date, ticker) AS (\n VALUES ${wantedPairs.join(\", \")}\n )\n SELECT ${projection}\n FROM ${source} AS q\n JOIN wanted AS w\n ON q.date = w.date AND q.ticker = w.ticker\n WHERE q.time >= ${timeStartLit} AND q.time <= ${timeEndLit}\n ORDER BY q.ticker, q.date, q.time`;\n\n const queryStart = perf ? Date.now() : 0;\n const reader = await this.ctx.conn.runAndReadAll(sql);\n const rows = reader.getRows();\n if (perf) {\n console.log(\n ` [P] readQuotesBulk underlying=${underlying} dates=${filePaths.length} ` +\n `tickers=${occUnion.size} rows=${rows.length} queryMs=${Date.now() - queryStart}`,\n );\n }\n for (const row of rows) {\n const quote = parseQuoteRow(row);\n const occ = quote.occ_ticker;\n const bucket = out.get(occ);\n if (bucket) bucket.push(quote);\n else out.set(occ, [quote]);\n }\n }\n\n return out;\n }\n\n /**\n * Read every option-quote row whose contract falls inside the union of the\n * supplied leg envelopes, between `timeStart` and `timeEnd` inclusive on a\n * single (underlying, date) partition. Joins back to the date's\n * `option_chain` partition for `contract_type`, `strike`, `expiration`,\n * `dte` so the caller doesn't OCC-parse. Greeks (delta/gamma/theta/vega/iv)\n * project as-is from the quote partition — null when not stored.\n *\n * Returns `[]` when `legEnvelopes` is empty (D-06 short-circuit pattern) or\n * when the requested (underlying, date) partition's quote / chain Parquet\n * file does not exist on disk. The OCC-parsing fallback used elsewhere when\n * `option_chain` is absent is intentionally NOT included — the entry\n * pipeline assumes chain coverage.\n */\n async readWindow(params: ReadWindowParams): Promise<WindowQuoteRow[]> {\n const { underlying, date, timeStart, timeEnd, legEnvelopes } = params;\n if (legEnvelopes.length === 0) return [];\n\n const marketDir = resolveMarketDir(this.ctx.dataDir);\n const quoteFile = path.join(\n marketDir,\n \"option_quote_minutes\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n const chainFile = path.join(\n marketDir,\n \"option_chain\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n if (!existsSync(quoteFile) || !existsSync(chainFile)) return [];\n\n const quoteSrc = readParquetFilesSql([quoteFile]);\n const chainSrc = readParquetFilesSql([chainFile]);\n\n // CRITICAL — DO NOT re-introduce parameterized binds here. Every call to\n // `runAndReadAll(sql, values)` goes through `node_bindings.extract_statements`\n // which returns a C++ handle with NO destroy method on its JS wrapper\n // (`DuckDBExtractedStatements` only has a constructor — see\n // `node_modules/@duckdb/node-api/lib/DuckDBExtractedStatements.js`). The\n // handles only release on JS GC. Because each ParquetQuoteStore.readWindow\n // embeds unique partition file paths as SQL literals, every call has\n // distinct SQL text → DuckDB caches a separate plan per call → on long\n // read workloads (~600+ calls) extract_statements handles outpace GC and\n // the driver throws `Failed to execute prepared statement` mid-run.\n //\n // The unbound `runAndReadAll(sql)` path takes `node_bindings.query()`\n // directly with no extract_statements step, so we inline every value\n // (timeStart, timeEnd, contractType, dte*, strike*) as a SQL literal.\n // Inputs are typed config (string-union / number) sourced from\n // in-process strategy definitions — no SQL injection vector.\n //\n // Phase-2 perf: project only what downstream consumers read. `underlying`\n // and `date` are pinned by the partition file paths above; `mid` is\n // derived as `(bid + ask) / 2` in `toMinuteQuoteRow`.\n const safeTimeStart = `'${escapeSqlLiteral(timeStart)}'`;\n const safeTimeEnd = `'${escapeSqlLiteral(timeEnd)}'`;\n const disjuncts: string[] = legEnvelopes.map((env) => {\n const ct = `'${escapeSqlLiteral(env.contractType)}'`;\n let clause = `(b.contract_type = ${ct} AND b.dte BETWEEN ${env.dteMin} AND ${env.dteMax}`;\n if (env.strikeMin != null) clause += ` AND b.strike >= ${env.strikeMin}`;\n if (env.strikeMax != null) clause += ` AND b.strike <= ${env.strikeMax}`;\n clause += \")\";\n return clause;\n });\n\n const sql = `\n WITH band AS (\n SELECT DISTINCT ticker, contract_type, strike, expiration, dte\n FROM ${chainSrc} AS b\n WHERE ${disjuncts.join(\" OR \")}\n )\n SELECT q.ticker, q.time,\n b.contract_type, b.strike, b.expiration, b.dte,\n q.bid, q.ask,\n q.delta, q.gamma, q.theta, q.vega, q.iv, q.greeks_source\n FROM ${quoteSrc} AS q\n JOIN band b ON q.ticker = b.ticker\n WHERE q.time BETWEEN ${safeTimeStart} AND ${safeTimeEnd}\n `;\n\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n time: String(r[1]),\n contract_type: String(r[2]) as \"call\" | \"put\",\n strike: Number(r[3]),\n expiration: String(r[4]),\n dte: Number(r[5]),\n bid: Number(r[6]),\n ask: Number(r[7]),\n delta: r[8] == null ? null : Number(r[8]),\n gamma: r[9] == null ? null : Number(r[9]),\n theta: r[10] == null ? null : Number(r[10]),\n vega: r[11] == null ? null : Number(r[11]),\n iv: r[12] == null ? null : Number(r[12]),\n greeks_source: r[13] == null ? null : String(r[13]) as WindowQuoteRow[\"greeks_source\"],\n }));\n }\n\n async getCoverage(\n underlying: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n const dir = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_quote_minutes\",\n `underlying=${underlying}`,\n );\n if (!existsSync(dir)) {\n return { earliest: null, latest: null, missingDates: [], totalDates: 0 };\n }\n const allDates = listPartitionValues(dir, \"date\");\n const dates = allDates.filter((d) => d >= from && d <= to);\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * DuckdbQuoteStore — option minute NBBO quotes persisted as DuckDB physical\n * table `market.option_quote_minutes`. Phase 1 D-12 / Pitfall 1 executed a\n * DROP+recreate with `underlying` as the first key; the Phase 1 schema already\n * has the correct shape.\n *\n * Writes via `INSERT OR REPLACE INTO market.option_quote_minutes` with\n * positional placeholders; reads project the canonical quote schema with\n * nullable greeks for older physical tables. Coverage via SELECT DISTINCT.\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { QuoteStore } from \"./quote-store.ts\";\nimport type {\n QuoteRow,\n CoverageReport,\n ReadWindowParams,\n WindowQuoteRow,\n} from \"./types.ts\";\nimport { extractRoot } from \"../tickers/resolver.ts\";\nimport {\n describeQueryColumns,\n quoteParquetCanonicalProjection,\n type ParquetColumnSet,\n} from \"../../utils/quote-parquet-projection.ts\";\n\nfunction escapeSqlLiteral(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nfunction parseQuoteRow(row: unknown[]): QuoteRow {\n const occ = String(row[2]);\n const date = String(row[1]);\n const time = String(row[3]);\n return {\n occ_ticker: occ,\n timestamp: `${date} ${time}`,\n bid: Number(row[4]),\n ask: Number(row[5]),\n source: row[8] == null ? null : (String(row[8]) as QuoteRow[\"source\"]),\n delta: row[9] == null ? null : Number(row[9]),\n gamma: row[10] == null ? null : Number(row[10]),\n theta: row[11] == null ? null : Number(row[11]),\n vega: row[12] == null ? null : Number(row[12]),\n iv: row[13] == null ? null : Number(row[13]),\n greeks_source: row[14] == null ? null : String(row[14]) as QuoteRow[\"greeks_source\"],\n greeks_revision: row[15] == null ? null : Number(row[15]),\n rate_type: row[16] == null ? null : String(row[16]),\n rate_value: row[17] == null ? null : Number(row[17]),\n gamma_source: row[18] == null ? null : String(row[18]),\n };\n}\n\nexport class DuckdbQuoteStore extends QuoteStore {\n private quoteTableColumns: Promise<ParquetColumnSet> | null = null;\n\n private getQuoteTableColumns(): Promise<ParquetColumnSet> {\n if (!this.quoteTableColumns) {\n this.quoteTableColumns = describeQueryColumns(\n this.ctx.conn,\n \"SELECT * FROM market.option_quote_minutes\",\n );\n }\n return this.quoteTableColumns;\n }\n\n private async ensureWritableQuoteSchema(): Promise<void> {\n const additions = [\n \"delta DOUBLE\",\n \"gamma DOUBLE\",\n \"theta DOUBLE\",\n \"vega DOUBLE\",\n \"iv DOUBLE\",\n \"greeks_source VARCHAR\",\n \"greeks_revision INTEGER\",\n \"rate_type VARCHAR\",\n \"rate_value DOUBLE\",\n \"gamma_source VARCHAR\",\n ];\n for (const addition of additions) {\n await this.ctx.conn.run(\n `ALTER TABLE market.option_quote_minutes ADD COLUMN IF NOT EXISTS ${addition}`,\n );\n }\n this.quoteTableColumns = null;\n }\n\n async writeQuotes(\n underlying: string,\n date: string,\n quotes: QuoteRow[],\n ): Promise<void> {\n if (quotes.length === 0) return;\n await this.ensureWritableQuoteSchema();\n const placeholders = quotes\n .map((_, i) => {\n const b = i * 19;\n return `($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6},$${b + 7},$${b + 8},$${b + 9},$${b + 10},$${b + 11},$${b + 12},$${b + 13},$${b + 14},$${b + 15},$${b + 16},$${b + 17},$${b + 18},$${b + 19})`;\n })\n .join(\", \");\n const params: unknown[] = quotes.flatMap((q) => {\n const [qdate, qtime] = q.timestamp.split(\" \");\n const mid = (q.bid + q.ask) / 2;\n return [\n underlying,\n qdate ?? date,\n q.occ_ticker,\n qtime ?? \"09:30\",\n q.bid,\n q.ask,\n mid,\n null, // last_updated_ns — not tracked in QuoteRow\n q.source ?? null, // source — populated when provider tags rows (Task 6)\n q.delta ?? null,\n q.gamma ?? null,\n q.theta ?? null,\n q.vega ?? null,\n q.iv ?? null,\n q.greeks_source ?? null,\n q.greeks_revision ?? null,\n q.rate_type ?? null,\n q.rate_value ?? null,\n q.gamma_source ?? null,\n ];\n });\n await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.option_quote_minutes\n (underlying, date, ticker, time, bid, ask, mid, last_updated_ns, source,\n delta, gamma, theta, vega, iv, greeks_source, greeks_revision,\n rate_type, rate_value, gamma_source)\n VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n }\n\n async writeFromSelect(\n _partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n await this.ensureWritableQuoteSchema();\n const columns = await describeQueryColumns(this.ctx.conn, selectSql);\n const projection = quoteParquetCanonicalProjection(columns, \"q\");\n const result = await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.option_quote_minutes\n (underlying, date, ticker, time, bid, ask, mid, last_updated_ns, source,\n delta, gamma, theta, vega, iv, greeks_source, greeks_revision,\n rate_type, rate_value, gamma_source)\n SELECT ${projection}\n FROM (${selectSql}) AS q`,\n );\n return { rowCount: Number(result.rowsChanged) };\n }\n\n async readQuotes(\n occTickers: string[],\n from: string,\n to: string,\n ): Promise<Map<string, QuoteRow[]>> {\n if (occTickers.length === 0) return new Map();\n const firstUnderlying = this.ctx.tickers.resolve(\n extractRoot(occTickers[0]),\n );\n for (const t of occTickers) {\n const u = this.ctx.tickers.resolve(extractRoot(t));\n if (u !== firstUnderlying) {\n throw new Error(\n `QuoteStore.readQuotes: mixed underlyings in batch — ` +\n `${occTickers[0]} resolves to ${firstUnderlying}, ${t} resolves to ${u}. ` +\n `Consumers must group reads by underlying.`,\n );\n }\n }\n const columns = await this.getQuoteTableColumns();\n const projection = quoteParquetCanonicalProjection(columns, \"q\");\n // Inline every value as a SQL literal and call the unbound\n // runAndReadAll(sql) form — the bound (sql, values) path routes through\n // node_bindings.extract_statements, which leaks a non-destroyable handle\n // per call and eventually throws \"Failed to execute prepared statement\"\n // under sustained read load. See spot-sql.ts header for the full writeup.\n const underlyingLit = `'${escapeSqlLiteral(firstUnderlying)}'`;\n const fromLit = `'${escapeSqlLiteral(from)}'`;\n const toLit = `'${escapeSqlLiteral(to)}'`;\n const tickerList = occTickers\n .map((t) => `'${escapeSqlLiteral(t)}'`)\n .join(\", \");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT ${projection}\n FROM market.option_quote_minutes AS q\n WHERE q.underlying = ${underlyingLit}\n AND q.date >= ${fromLit}\n AND q.date <= ${toLit}\n AND q.ticker IN (${tickerList})\n ORDER BY q.ticker, q.date, q.time`,\n );\n const out = new Map<string, QuoteRow[]>();\n for (const row of reader.getRows()) {\n const qr = parseQuoteRow(row);\n const occ = qr.occ_ticker;\n let arr = out.get(occ);\n if (!arr) {\n arr = [];\n out.set(occ, arr);\n }\n arr.push(qr);\n }\n return out;\n }\n\n override async readQuotesBulk(\n tickersByDate: Map<string, Set<string>>,\n timeStart: string,\n timeEnd: string,\n ): Promise<Map<string, QuoteRow[]>> {\n const out = new Map<string, QuoteRow[]>();\n if (tickersByDate.size === 0) return out;\n\n const perf = process.env.QUOTE_STORE_PERF_DEBUG === \"1\";\n\n for (const [underlying, perDate] of this.groupTickersByUnderlying(tickersByDate)) {\n const occUnion = new Set<string>();\n const wantedPairs: string[] = [];\n\n for (const [date, occs] of perDate) {\n if (occs.size === 0) continue;\n for (const occ of occs) {\n occUnion.add(occ);\n wantedPairs.push(`('${escapeSqlLiteral(date)}', '${escapeSqlLiteral(occ)}')`);\n }\n }\n\n if (wantedPairs.length === 0) continue;\n\n const columns = await this.getQuoteTableColumns();\n const projection = quoteParquetCanonicalProjection(columns, \"q\");\n // Underlying + time bounds inlined as SQL literals so the call takes the\n // unbound runAndReadAll(sql) path (the (date, ticker) VALUES are already\n // inlined above). The bound form leaks an extract_statements handle per\n // call — see spot-sql.ts header for the full root-cause writeup.\n const underlyingLit = `'${escapeSqlLiteral(underlying)}'`;\n const timeStartLit = `'${escapeSqlLiteral(timeStart)}'`;\n const timeEndLit = `'${escapeSqlLiteral(timeEnd)}'`;\n const sql = `WITH wanted(date, ticker) AS (\n VALUES ${wantedPairs.join(\", \")}\n )\n SELECT ${projection}\n FROM market.option_quote_minutes AS q\n JOIN wanted AS w\n ON q.date = w.date AND q.ticker = w.ticker\n WHERE q.underlying = ${underlyingLit}\n AND q.time >= ${timeStartLit} AND q.time <= ${timeEndLit}\n ORDER BY q.ticker, q.date, q.time`;\n\n const queryStart = perf ? Date.now() : 0;\n const reader = await this.ctx.conn.runAndReadAll(sql);\n const rows = reader.getRows();\n if (perf) {\n console.log(\n ` [P] readQuotesBulk underlying=${underlying} dates=${perDate.size} ` +\n `tickers=${occUnion.size} rows=${rows.length} queryMs=${Date.now() - queryStart}`,\n );\n }\n for (const row of rows) {\n const quote = parseQuoteRow(row);\n const occ = quote.occ_ticker;\n const bucket = out.get(occ);\n if (bucket) bucket.push(quote);\n else out.set(occ, [quote]);\n }\n }\n\n return out;\n }\n\n /**\n * Read every option-quote row whose contract falls inside the union of the\n * supplied leg envelopes, between `timeStart` and `timeEnd` inclusive on a\n * single (underlying, date) partition. Joins back to `market.option_chain`\n * for `contract_type`, `strike`, `expiration`, `dte` so the caller doesn't\n * OCC-parse. Greeks (delta/gamma/theta/vega/iv) project as-is from the\n * quote table — null when not stored.\n *\n * Times are 24-hour US Eastern wall-clock strings (\"HH:MM\") matching\n * `ReadWindowParams.timeStart` / `timeEnd`. Strike envelope bounds\n * (`strikeMin` / `strikeMax`) are optional; when absent the leg matches all\n * strikes within the dte band for that contract type.\n *\n * Returns `[]` when `legEnvelopes` is empty (D-06 short-circuit pattern).\n * No SQL ranking; ranking + top-N selection happen in JS at the call site.\n */\n async readWindow(params: ReadWindowParams): Promise<WindowQuoteRow[]> {\n const { underlying, date, timeStart, timeEnd, legEnvelopes } = params;\n if (legEnvelopes.length === 0) return [];\n\n // Inline every value as a SQL literal and call the unbound\n // runAndReadAll(sql) form — the bound (sql, values) path routes through\n // node_bindings.extract_statements, which leaks a non-destroyable handle\n // per call and eventually throws \"Failed to execute prepared statement\"\n // under sustained read load. See spot-sql.ts header for the full writeup.\n // String values (underlying, date, times, contract_type) are\n // single-quote-escaped; dte/strike bounds are typed numbers from\n // in-process strategy definitions, inlined directly — no injection vector.\n const pUnderlying = `'${escapeSqlLiteral(underlying)}'`;\n const pDate = `'${escapeSqlLiteral(date)}'`;\n const pTimeStart = `'${escapeSqlLiteral(timeStart)}'`;\n const pTimeEnd = `'${escapeSqlLiteral(timeEnd)}'`;\n\n const disjuncts: string[] = [];\n for (const env of legEnvelopes) {\n const ct = `'${escapeSqlLiteral(env.contractType)}'`;\n let clause = `(b.contract_type = ${ct} AND b.dte BETWEEN ${env.dteMin} AND ${env.dteMax}`;\n if (env.strikeMin != null) {\n clause += ` AND b.strike >= ${env.strikeMin}`;\n }\n if (env.strikeMax != null) {\n clause += ` AND b.strike <= ${env.strikeMax}`;\n }\n clause += \")\";\n disjuncts.push(clause);\n }\n\n // Phase-2 perf: project only what downstream consumers read. `underlying`\n // and `date` are filter-pinned by the WHERE clause, and `mid` is derived\n // as `(bid + ask) / 2` in `toMinuteQuoteRow` — fetching + decoding those\n // three columns for ~100K rows per call was wasted work.\n const sql = `\n WITH band AS (\n SELECT DISTINCT ticker, contract_type, strike, expiration, dte\n FROM market.option_chain b\n WHERE b.underlying = ${pUnderlying} AND b.date = ${pDate}\n AND (${disjuncts.join(\" OR \")})\n )\n SELECT q.ticker, q.time,\n b.contract_type, b.strike, b.expiration, b.dte,\n q.bid, q.ask,\n q.delta, q.gamma, q.theta, q.vega, q.iv, q.greeks_source\n FROM market.option_quote_minutes q\n JOIN band b ON q.ticker = b.ticker\n WHERE q.underlying = ${pUnderlying}\n AND q.date = ${pDate}\n AND q.time BETWEEN ${pTimeStart} AND ${pTimeEnd}\n `;\n\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n time: String(r[1]),\n contract_type: String(r[2]) as \"call\" | \"put\",\n strike: Number(r[3]),\n expiration: String(r[4]),\n dte: Number(r[5]),\n bid: Number(r[6]),\n ask: Number(r[7]),\n delta: r[8] == null ? null : Number(r[8]),\n gamma: r[9] == null ? null : Number(r[9]),\n theta: r[10] == null ? null : Number(r[10]),\n vega: r[11] == null ? null : Number(r[11]),\n iv: r[12] == null ? null : Number(r[12]),\n greeks_source: r[13] == null ? null : String(r[13]) as WindowQuoteRow[\"greeks_source\"],\n }));\n }\n\n async getCoverage(\n underlying: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n // Inline literal path — same leak rationale (see spot-sql.ts header).\n const underlyingLit = underlying.replace(/'/g, \"''\");\n const fromLit = from.replace(/'/g, \"''\");\n const toLit = to.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT DISTINCT date FROM market.option_quote_minutes\n WHERE underlying = '${underlyingLit}' AND date >= '${fromLit}' AND date <= '${toLit}'\n ORDER BY date`,\n );\n const dates = reader.getRows().map((r) => String(r[0]));\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * ParquetOiDailyStore — daily option open-interest persisted as\n * underlying-first Hive-partitioned Parquet files\n * (option_oi_daily/underlying=X/date=Y/data.parquet).\n *\n * Open interest is reported at daily granularity: one row per contract per\n * day. The partition layout mirrors `option_quote_minutes` (underlying then\n * date) so the same per-underlying read grouping applies.\n *\n * Columns: underlying VARCHAR, date VARCHAR, ticker VARCHAR,\n * expiration VARCHAR, strike DOUBLE, right VARCHAR, open_interest BIGINT,\n * source VARCHAR.\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport type { StoreContext } from \"./types.ts\";\nimport type { OiDailyRow } from \"./types.ts\";\nimport { listPartitionValues } from \"./coverage.ts\";\nimport {\n resolveMarketDir,\n writeOiDailyPartition,\n} from \"../../db/market-datasets.ts\";\nimport { readParquetFilesSql } from \"../../utils/quote-parquet-projection.ts\";\n\n// `right` is a reserved keyword in DuckDB (the RIGHT(string, n) function), so\n// it must be double-quoted everywhere it appears as a column identifier.\nconst OI_DAILY_COLUMNS =\n 'underlying, date, ticker, expiration, strike, \"right\", open_interest, source';\n\nfunction parseOiDailyRow(row: unknown[]): OiDailyRow {\n return {\n underlying: String(row[0]),\n date: String(row[1]),\n occ_ticker: String(row[2]),\n expiration: String(row[3]),\n strike: Number(row[4]),\n right: String(row[5]) as OiDailyRow[\"right\"],\n open_interest: Number(row[6]),\n source: row[7] == null ? null : String(row[7]),\n };\n}\n\nexport class ParquetOiDailyStore {\n protected readonly ctx: StoreContext;\n constructor(ctx: StoreContext) {\n this.ctx = ctx;\n }\n\n async writeOiDaily(\n underlying: string,\n date: string,\n rows: OiDailyRow[],\n ): Promise<void> {\n if (rows.length === 0) return;\n // Append via DuckDBAppender (typed per-column) rather than a parameterized\n // INSERT with O(N) placeholders — mirrors the quote store's write path.\n const staging = `_oi_write_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n await this.ctx.conn.run(\n `CREATE TEMP TABLE \"${staging}\" (\n underlying VARCHAR, date VARCHAR, ticker VARCHAR,\n expiration VARCHAR, strike DOUBLE, \"right\" VARCHAR,\n open_interest BIGINT, source VARCHAR\n )`,\n );\n try {\n const appender = await this.ctx.conn.createAppender(staging);\n try {\n for (const r of rows) {\n appender.appendVarchar(underlying);\n appender.appendVarchar(r.date);\n appender.appendVarchar(r.occ_ticker);\n appender.appendVarchar(r.expiration);\n appender.appendDouble(r.strike);\n appender.appendVarchar(r.right);\n appender.appendBigInt(BigInt(Math.round(r.open_interest)));\n if (r.source == null) appender.appendNull();\n else appender.appendVarchar(r.source);\n appender.endRow();\n }\n appender.flushSync();\n } finally {\n appender.closeSync();\n }\n await writeOiDailyPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n underlying,\n date,\n selectQuery: `SELECT ${OI_DAILY_COLUMNS} FROM \"${staging}\"`,\n });\n } finally {\n try {\n await this.ctx.conn.run(`DROP TABLE IF EXISTS \"${staging}\"`);\n } catch {\n /* best-effort */\n }\n }\n }\n\n async readOiDaily(\n underlying: string,\n from: string,\n to: string,\n ): Promise<OiDailyRow[]> {\n const underlyingDir = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_oi_daily\",\n `underlying=${underlying}`,\n );\n if (!existsSync(underlyingDir)) return [];\n const files = listPartitionValues(underlyingDir, \"date\")\n .filter((date) => date >= from && date <= to)\n .map((date) => path.join(underlyingDir, `date=${date}`, \"data.parquet\"))\n .filter((filePath) => existsSync(filePath));\n if (files.length === 0) return [];\n\n const source = readParquetFilesSql(files);\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT ${OI_DAILY_COLUMNS}\n FROM ${source} AS q\n WHERE q.date >= $1\n AND q.date <= $2\n ORDER BY q.date, q.ticker`,\n [from, to] as (string | number | boolean | null | bigint)[],\n );\n return reader.getRows().map(parseOiDailyRow);\n }\n}\n","/**\n * Market Data 3.0 — Store factory + barrel re-exports.\n *\n * Phase 1: createMarketStores returned typed null-cast placeholders per\n * CONTEXT.md D-04 so the compiler-typed bundle was available before concrete\n * backends shipped.\n *\n * Phase 2 (Plan 02-05): the body below replaces that placeholder with real\n * backend dispatch per D-03. The factory reads the backend flag ONCE and\n * returns monomorphic instances; concrete method bodies must NEVER re-inspect\n * it (D-02). EnrichedStore receives the SpotStore via constructor\n * injection (D-15) so the enricher's IO boundaries (minute-bar reads,\n * watermark get/upsert) are satisfied without re-plumbing.\n *\n * Downstream consumers (Phase 2 integration tests, Phase 4 tool migrations)\n * import from this barrel so only `./index.js` depends on the concrete file\n * layout.\n */\nimport { SpotStore } from \"./spot-store.ts\";\nimport { EnrichedStore } from \"./enriched-store.ts\";\nimport { ChainStore } from \"./chain-store.ts\";\nimport { QuoteStore } from \"./quote-store.ts\";\n\n// Phase 2 concrete classes (shipped in Plans 02-03 + 02-04).\nimport { ParquetSpotStore } from \"./parquet-spot-store.ts\";\nimport { DuckdbSpotStore } from \"./duckdb-spot-store.ts\";\nimport { ParquetEnrichedStore } from \"./parquet-enriched-store.ts\";\nimport { DuckdbEnrichedStore } from \"./duckdb-enriched-store.ts\";\nimport { ParquetChainStore } from \"./parquet-chain-store.ts\";\nimport { DuckdbChainStore } from \"./duckdb-chain-store.ts\";\nimport { ParquetQuoteStore } from \"./parquet-quote-store.ts\";\nimport { DuckdbQuoteStore } from \"./duckdb-quote-store.ts\";\nimport { ParquetOiDailyStore } from \"./parquet-oi-daily-store.ts\";\n\nimport type { StoreContext } from \"./types.ts\";\n\nexport interface MarketStores {\n spot: SpotStore;\n enriched: EnrichedStore;\n chain: ChainStore;\n quote: QuoteStore;\n oiDaily: ParquetOiDailyStore;\n}\n\n/**\n * Construct a MarketStores bundle using backend-appropriate concrete classes.\n *\n * D-03: reads the backend flag once and returns monomorphic instances. The\n * concrete method bodies never re-inspect the flag (D-02).\n * D-15: EnrichedStore takes `SpotStore` via constructor injection so the\n * enricher's IO refactor (Plan 02-04) receives the right backend for minute-bar\n * reads without any separate lookup.\n */\nexport function createMarketStores(ctx: StoreContext): MarketStores {\n // Open interest is daily-granularity option market data persisted Parquet-\n // native (one row per contract per day), so the same store serves both\n // modes — it always writes/reads Hive-partitioned Parquet under the data\n // archive, like the option-quote and option-chain partitions.\n const oiDaily = new ParquetOiDailyStore(ctx);\n if (ctx.parquetMode) {\n const spot = new ParquetSpotStore(ctx);\n const enriched = new ParquetEnrichedStore(ctx, spot);\n const chain = new ParquetChainStore(ctx);\n const quote = new ParquetQuoteStore(ctx);\n return { spot, enriched, chain, quote, oiDaily };\n }\n const spot = new DuckdbSpotStore(ctx);\n const enriched = new DuckdbEnrichedStore(ctx, spot);\n const chain = new DuckdbChainStore(ctx);\n const quote = new DuckdbQuoteStore(ctx);\n return { spot, enriched, chain, quote, oiDaily };\n}\n\nexport { SpotStore, EnrichedStore, ChainStore, QuoteStore };\nexport type { StoreContext };\nexport type { EnrichedReadOpts } from \"./enriched-store.ts\";\nexport type { BarRow, ContractRow, QuoteRow, CoverageReport } from \"./types.ts\";\n","/**\n * Zod schemas for the ticker registry.\n *\n * Applied at three trust boundaries (defense in depth per RESEARCH.md Pitfall 3):\n * 1. MCP tool input boundary (schemas exported here, consumed by src/tools/tickers.ts in Plan 05)\n * 2. Registry constructor / register (registry.ts applies the same regex)\n * 3. Writer partition-value whitelist (src/db/parquet-writer.ts — Plan 03)\n */\nimport { z } from \"zod\";\n\n/**\n * Whitelist for ticker-like strings. Must START with an uppercase letter\n * (forbids \"..\" and other digit/punctuation-only inputs from validating).\n *\n * Accepts after the leading letter: uppercase A-Z, digits 0-9, and `^ _ -`\n * which appear in a handful of real-world option roots and continuous-futures\n * symbols. Forbids `.` (and therefore `..`), `/`, `\\`, whitespace, null bytes,\n * newlines, and any other non-filesystem-safe characters.\n */\nexport const TICKER_RE = /^[A-Z][A-Z0-9^_-]*$/;\n\nexport const UnderlyingsFileSchema = z.object({\n version: z.literal(1),\n underlyings: z.array(\n z.object({\n underlying: z.string().min(1).max(16).regex(TICKER_RE),\n roots: z.array(z.string().min(1).max(16).regex(TICKER_RE)).min(1).max(32),\n }),\n ),\n});\n\n// MCP tool input schemas (consumed by src/tools/tickers.ts in Plan 05).\nexport const registerUnderlyingSchema = z.object({\n underlying: z\n .string()\n .min(1)\n .max(16)\n .regex(TICKER_RE)\n .describe(\"Canonical underlying symbol, e.g. SPX\"),\n roots: z\n .array(z.string().min(1).max(16).regex(TICKER_RE))\n .min(1)\n .max(32)\n .describe(\n \"OCC roots that resolve to this underlying, e.g. ['SPX','SPXW','SPXQ']\",\n ),\n});\n\nexport const unregisterUnderlyingSchema = z.object({\n underlying: z\n .string()\n .min(1)\n .max(16)\n .regex(TICKER_RE)\n .describe(\"Underlying to remove. Bundled defaults cannot be removed.\"),\n});\n\nexport const listUnderlyingsSchema = z.object({});\n\nexport const resolveRootSchema = z.object({\n input: z\n .string()\n .min(1)\n .max(32)\n .describe(\n \"Bare root ('SPXW') or full OCC ticker ('SPXW251219C05000000')\",\n ),\n});\n","/**\n * TickerRegistry — in-memory map keyed by underlying.\n *\n * Seeded from bundled defaults (defaults.json). User-added entries via register()\n * persist to {dataRoot}/market/underlyings.json via saveUserOverride (loader.ts).\n *\n * `src/market/stores/types.ts` imports `TickerRegistry`, `TickerEntry`, and\n * `EntrySource` as types — keep those exported names stable.\n *\n * Defense-in-depth: every stored value is validated against TICKER_RE at\n * construction and at register() time. The MCP-tool layer applies the same\n * regex at the Zod boundary; the writer applies its own whitelist at the\n * partition-value boundary. Each layer is independently sufficient; together\n * they prevent malformed/injected ticker strings from reaching DuckDB.\n */\nimport { TICKER_RE } from \"./schemas.ts\";\n\nexport type EntrySource = \"default\" | \"user\" | \"user-override\";\n\nexport interface TickerEntry {\n underlying: string;\n roots: string[];\n source: EntrySource;\n}\n\nfunction validate(underlying: string, roots: string[]): void {\n if (!TICKER_RE.test(underlying)) {\n throw new Error(\n `TickerRegistry: invalid underlying \"${underlying}\" — must match ${TICKER_RE.source}`,\n );\n }\n for (const r of roots) {\n if (!TICKER_RE.test(r)) {\n throw new Error(\n `TickerRegistry: invalid root \"${r}\" — must match ${TICKER_RE.source}`,\n );\n }\n }\n}\n\nexport class TickerRegistry {\n private rootMap: Map<string, { underlying: string; source: EntrySource }> =\n new Map();\n private entries: Map<string, TickerEntry> = new Map();\n // Preserved so unregister('user-override') can revert to the bundled default.\n private readonly bundledDefaults: ReadonlyMap<string, ReadonlyArray<string>>;\n\n constructor(\n defaults: Array<{ underlying: string; roots: string[] }>,\n userOverrides: Array<{ underlying: string; roots: string[] }> = [],\n ) {\n const bundled = new Map<string, ReadonlyArray<string>>();\n for (const e of defaults) {\n validate(e.underlying, e.roots);\n bundled.set(e.underlying, [...e.roots]);\n this.entries.set(e.underlying, {\n underlying: e.underlying,\n roots: [...e.roots],\n source: \"default\",\n });\n for (const r of e.roots) {\n this.rootMap.set(r, { underlying: e.underlying, source: \"default\" });\n }\n }\n this.bundledDefaults = bundled;\n for (const e of userOverrides) {\n validate(e.underlying, e.roots);\n const source: EntrySource = bundled.has(e.underlying)\n ? \"user-override\"\n : \"user\";\n // Clear stale root mappings that previously pointed at this underlying.\n for (const [r, v] of [...this.rootMap]) {\n if (v.underlying === e.underlying) this.rootMap.delete(r);\n }\n this.entries.set(e.underlying, {\n underlying: e.underlying,\n roots: [...e.roots],\n source,\n });\n for (const r of e.roots) {\n this.rootMap.set(r, { underlying: e.underlying, source });\n }\n }\n }\n\n /**\n * Resolve a root symbol to its underlying.\n * Identity fallback (returns the root unchanged) when unknown — unknown\n * roots are treated as their own underlying so single-symbol tickers\n * (e.g. leveraged ETFs) keep working without explicit registration.\n */\n resolve(root: string): string {\n return this.rootMap.get(root)?.underlying ?? root;\n }\n\n /**\n * Add or update an underlying entry.\n * - New underlying (not a bundled default): source = \"user\"\n * - Overriding a bundled default: source = \"user-override\"\n *\n * @throws on invalid characters in `underlying` or any `root` (defense-in-depth — see file header).\n */\n register(entry: { underlying: string; roots: string[] }): TickerEntry {\n validate(entry.underlying, entry.roots);\n const isDefault = this.bundledDefaults.has(entry.underlying);\n const source: EntrySource = isDefault ? \"user-override\" : \"user\";\n // Clear stale root mappings that previously pointed at this underlying.\n for (const [r, v] of [...this.rootMap]) {\n if (v.underlying === entry.underlying) this.rootMap.delete(r);\n }\n const merged: TickerEntry = {\n underlying: entry.underlying,\n roots: [...entry.roots],\n source,\n };\n this.entries.set(entry.underlying, merged);\n for (const r of entry.roots) {\n this.rootMap.set(r, { underlying: entry.underlying, source });\n }\n return merged;\n }\n\n /**\n * Remove a user entry, or revert a user-override to its bundled default.\n * Bundled defaults cannot be removed.\n *\n * @throws on unknown underlying or when attempting to remove a bundled default.\n */\n unregister(underlying: string): void {\n const entry = this.entries.get(underlying);\n if (!entry) {\n throw new Error(\n `TickerRegistry.unregister: unknown underlying \"${underlying}\"`,\n );\n }\n if (entry.source === \"default\") {\n throw new Error(\n `TickerRegistry.unregister: cannot unregister bundled default \"${underlying}\"`,\n );\n }\n // If it was a user-override of a default, revert to the bundled default.\n if (\n entry.source === \"user-override\" &&\n this.bundledDefaults.has(underlying)\n ) {\n const defaultRoots = [...(this.bundledDefaults.get(underlying) ?? [])];\n for (const [r, v] of [...this.rootMap]) {\n if (v.underlying === underlying) this.rootMap.delete(r);\n }\n this.entries.set(underlying, {\n underlying,\n roots: defaultRoots,\n source: \"default\",\n });\n for (const r of defaultRoots) {\n this.rootMap.set(r, { underlying, source: \"default\" });\n }\n return;\n }\n // Pure user entry — remove entirely.\n this.entries.delete(underlying);\n for (const [r, v] of [...this.rootMap]) {\n if (v.underlying === underlying) this.rootMap.delete(r);\n }\n }\n\n /** Return all entries (defaults + user + user-override) as defensive copies. */\n list(): TickerEntry[] {\n return Array.from(this.entries.values()).map((e) => ({\n ...e,\n roots: [...e.roots],\n }));\n }\n\n /**\n * Serialize ONLY user + user-override entries.\n * Bundled defaults are NEVER persisted — they live in defaults.json and ship with the binary.\n */\n toJSON(): {\n version: 1;\n underlyings: Array<{ underlying: string; roots: string[] }>;\n } {\n const persisted = this.list().filter(\n (e) => e.source === \"user\" || e.source === \"user-override\",\n );\n return {\n version: 1,\n underlyings: persisted.map((e) => ({\n underlying: e.underlying,\n roots: [...e.roots],\n })),\n };\n }\n}\n","/**\n * Registry loader — bundled defaults merged with optional user override at\n * {dataRoot}/market/underlyings.json.\n *\n * Reuses writeJsonFile / readJsonFile from src/db/json-store.ts (already atomic)\n * and resolveMarketDir from src/db/market-datasets.ts (single source of truth for\n * {dataDir}/market).\n *\n * Bundled defaults loading (Plan 01-05 deviation Rule 3):\n * The previous `fs.readFileSync(new URL(\"./defaults.json\", import.meta.url))`\n * approach broke under tsup bundling — the JSON file did not get copied next\n * to the bundled `server/index.js`, causing ENOENT at server boot. The path\n * only worked under Jest because Jest reads from src/ directly.\n *\n * Using a JSON import with the `with { type: 'json' }` attribute lets esbuild\n * (via tsup) inline the JSON data into the bundle at build time — no runtime\n * filesystem dependency. Node 22+ supports import attributes natively (we run\n * on Node 23), and ts-jest passes the import attribute through unchanged.\n *\n * The parsed object is module-scope and cached for the process lifetime.\n */\nimport * as path from \"path\";\nimport { readJsonFile, writeJsonFile } from \"../../db/json-store.ts\";\nimport { resolveMarketDir } from \"../../db/market-datasets.ts\";\nimport { UnderlyingsFileSchema } from \"./schemas.ts\";\nimport { TickerRegistry } from \"./registry.ts\";\nimport defaultsData from \"./defaults.json\" with { type: \"json\" };\n\nfunction userOverridePath(dataDir: string): string {\n return path.join(resolveMarketDir(dataDir), \"underlyings.json\");\n}\n\n/**\n * Load the ticker registry.\n * - Missing user-override file is OK (returns defaults-only registry).\n * - Malformed user JSON throws a clear \"Malformed {path}: ...\" error — NO silent\n * fallback (D-08, T-1-03 mitigation).\n */\nexport async function loadRegistry(args: {\n dataDir: string;\n}): Promise<TickerRegistry> {\n const overridePath = userOverridePath(args.dataDir);\n // readJsonFile returns null on ENOENT (json-store.ts:51-61) and throws on JSON\n // parse errors. We treat both Zod rejections AND parse errors as \"Malformed\"\n // so the user sees a single, consistent error shape referencing the file path.\n let raw: unknown;\n try {\n raw = await readJsonFile<unknown>(overridePath);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(`Malformed ${overridePath}: ${detail}`);\n }\n let userEntries: Array<{ underlying: string; roots: string[] }> = [];\n if (raw !== null) {\n const parsed = UnderlyingsFileSchema.safeParse(raw);\n if (!parsed.success) {\n throw new Error(`Malformed ${overridePath}: ${parsed.error.message}`);\n }\n userEntries = parsed.data.underlyings;\n }\n return new TickerRegistry(defaultsData.underlyings, userEntries);\n}\n\n/**\n * Persist user + user-override entries to {dataRoot}/market/underlyings.json.\n * Atomic tmp-then-rename via writeJsonFile (json-store.ts:36-41).\n */\nexport async function saveUserOverride(\n dataDir: string,\n registry: TickerRegistry,\n): Promise<void> {\n const overridePath = userOverridePath(dataDir);\n await writeJsonFile(overridePath, registry.toJSON());\n}\n","{\n \"version\": 1,\n \"underlyings\": [\n { \"underlying\": \"SPX\", \"roots\": [\"SPX\", \"SPXW\", \"SPXQ\"] },\n { \"underlying\": \"QQQ\", \"roots\": [\"QQQ\", \"QQQX\"] },\n { \"underlying\": \"VIX\", \"roots\": [\"VIX\"] },\n { \"underlying\": \"VIX9D\", \"roots\": [\"VIX9D\"] },\n { \"underlying\": \"VIX3M\", \"roots\": [\"VIX3M\"] },\n { \"underlying\": \"ES\", \"roots\": [\"ES\"] },\n { \"underlying\": \"NDX\", \"roots\": [\"NDX\", \"NDXP\"] },\n { \"underlying\": \"RUT\", \"roots\": [\"RUT\", \"RUTW\"] }\n ]\n}\n","/**\n * Ticker Registry MCP Tools (Market Data 3.0)\n *\n * Four MCP tools for CRUD operations on the underlying→roots mapping registry:\n * - register_underlying Create or update a user entry; persists to\n * {dataRoot}/market/underlyings.json (atomic write).\n * - unregister_underlying Remove a user entry, or revert a user-override to\n * the bundled default. Bundled defaults cannot be removed.\n * - list_underlyings Return all entries with source annotation\n * ('default' | 'user' | 'user-override').\n * - resolve_root Debug helper: returns {root, underlying, source}\n * for any bare-root or full OCC ticker input.\n *\n * Shared code. Wired into createServer() in src/index.ts — the registry\n * itself is storage infrastructure shared between the public and consumer\n * (private) repos.\n *\n * Security (defense in depth):\n * Zod schemas from ../market/tickers/schemas.ts enforce the TICKER_RE whitelist\n * on `underlying` and each `root` BEFORE any handler runs. Layer 2 (registry\n * constructor / register) and layer 3 (writer partition-value whitelist) apply\n * the same regex at their own boundaries.\n *\n * Singleton contract:\n * The `TickerRegistry` instance is constructed ONCE in src/index.ts via\n * loadRegistry(), and the same reference is passed here AND into\n * StoreContext.tickers. Two instances would diverge on register/unregister.\n */\nimport type { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n TickerRegistry,\n TickerEntry,\n} from \"../market/tickers/registry.ts\";\nimport {\n registerUnderlyingSchema,\n unregisterUnderlyingSchema,\n listUnderlyingsSchema,\n resolveRootSchema,\n} from \"../market/tickers/schemas.ts\";\nimport { saveUserOverride } from \"../market/tickers/loader.ts\";\nimport { extractRoot } from \"../market/tickers/resolver.ts\";\nimport { createToolOutput, type ToolOutput } from \"../utils/output-formatter.ts\";\n\n// ---------------------------------------------------------------------------\n// Handlers (exported for unit testing without spinning up an MCP server)\n// ---------------------------------------------------------------------------\n\n/**\n * Handle register_underlying: create or update an entry, then persist.\n * Returns the merged entry (with computed `source`) in the JSON payload.\n */\nexport async function handleRegisterUnderlying(\n input: z.infer<typeof registerUnderlyingSchema>,\n registry: TickerRegistry,\n dataDir: string,\n): Promise<ToolOutput> {\n const merged = registry.register(input);\n await saveUserOverride(dataDir, registry);\n return createToolOutput(\n `Registered underlying \"${merged.underlying}\" with ${merged.roots.length} root(s) [source=${merged.source}]`,\n { entry: merged },\n );\n}\n\n/**\n * Handle unregister_underlying: remove a user entry, or revert a user-override\n * to its bundled default. Throws clear error on bundled defaults.\n */\nexport async function handleUnregisterUnderlying(\n input: z.infer<typeof unregisterUnderlyingSchema>,\n registry: TickerRegistry,\n dataDir: string,\n): Promise<ToolOutput> {\n registry.unregister(input.underlying);\n await saveUserOverride(dataDir, registry);\n return createToolOutput(\n `Unregistered underlying \"${input.underlying}\" (or reverted to bundled default if it was a user-override)`,\n { removed: input.underlying },\n );\n}\n\n/**\n * Handle list_underlyings: return all merged entries with source annotation.\n */\nexport async function handleListUnderlyings(\n _input: z.infer<typeof listUnderlyingsSchema>,\n registry: TickerRegistry,\n): Promise<ToolOutput> {\n const entries: TickerEntry[] = registry.list();\n return createToolOutput(\n `Registry has ${entries.length} entries (defaults + user + user-override)`,\n { entries },\n );\n}\n\n/**\n * Handle resolve_root: debug helper showing how an input symbol resolves.\n * Returns {root, underlying, source} where source is one of:\n * - 'default' | 'user' | 'user-override' → matched a registry entry\n * - 'identity' → registry miss, fell back to root\n */\nexport async function handleResolveRoot(\n input: z.infer<typeof resolveRootSchema>,\n registry: TickerRegistry,\n): Promise<ToolOutput> {\n const root = extractRoot(input.input);\n const underlying = registry.resolve(root);\n // Determine source by consulting the registry's list. If the resolved entry's\n // root list contains this root, report its source; otherwise the resolve()\n // returned identity fallback (root === underlying), so source='identity'.\n const match = registry.list().find((e) => e.underlying === underlying);\n const source: \"default\" | \"user\" | \"user-override\" | \"identity\" =\n match && match.roots.includes(root) ? match.source : \"identity\";\n return createToolOutput(\n `Input \"${input.input}\" resolves: root=\"${root}\" → underlying=\"${underlying}\" [source=${source}]`,\n { root, underlying, source },\n );\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\n/**\n * Register all four ticker registry tools on the MCP server.\n *\n * @param server - McpServer instance.\n * @param registry - The SAME TickerRegistry singleton used by every other\n * consumer in the process.\n * @param dataDir - Base data directory; user override persists at\n * {dataDir}/market/underlyings.json.\n */\nexport function registerTickerTools(\n server: McpServer,\n registry: TickerRegistry,\n dataDir: string,\n): void {\n server.registerTool(\n \"register_underlying\",\n {\n description:\n \"Add or update an underlying→roots mapping in the ticker registry. \" +\n \"Persists to {dataRoot}/market/underlyings.json (user override layer; \" +\n \"bundled defaults are never modified). `underlying` is the canonical symbol \" +\n \"(e.g. SPX); `roots` are OCC roots that resolve to it (e.g. ['SPX','SPXW','SPXQ']). \" +\n \"Re-registering a bundled-default underlying creates a 'user-override' that the \" +\n \"registry uses in place of the default.\",\n inputSchema: registerUnderlyingSchema,\n },\n async (input) => handleRegisterUnderlying(input, registry, dataDir),\n );\n\n server.registerTool(\n \"unregister_underlying\",\n {\n description:\n \"Remove a user or user-override ticker entry. Bundled defaults cannot be removed; \" +\n \"removing a user-override reverts the entry back to its bundled default. \" +\n \"Persists the change to {dataRoot}/market/underlyings.json.\",\n inputSchema: unregisterUnderlyingSchema,\n },\n async (input) => handleUnregisterUnderlying(input, registry, dataDir),\n );\n\n server.registerTool(\n \"list_underlyings\",\n {\n description:\n \"List all ticker registry entries (bundled defaults + user-added + user-overrides). \" +\n \"Each entry is annotated with its source ('default' | 'user' | 'user-override'). \" +\n \"Bundled defaults ship with the binary; user entries live in {dataRoot}/market/underlyings.json.\",\n inputSchema: listUnderlyingsSchema,\n },\n async (input) => handleListUnderlyings(input, registry),\n );\n\n server.registerTool(\n \"resolve_root\",\n {\n description:\n \"Debug helper: show how a symbol resolves through the ticker registry. \" +\n \"Accepts bare roots ('SPXW') or full OCC tickers ('SPXW251219C05000000'). \" +\n \"Returns { root, underlying, source } where source is \" +\n \"'default' | 'user' | 'user-override' | 'identity'. Identity means the root \" +\n \"had no registry entry and was returned unchanged (e.g. leveraged ETFs SPXL/SPXS).\",\n inputSchema: resolveRootSchema,\n },\n async (input) => handleResolveRoot(input, registry),\n );\n}\n","/**\n * Pure SQL builder for QuoteStore reads.\n *\n * Emits a multi-ticker grouped-series read: one partition targeted by\n * underlying + date range, with an `IN (...)` filter over the OCC ticker list.\n * Callers are responsible for having validated that every OCC ticker resolves\n * to the same underlying; this builder trusts its caller on that front.\n *\n * Values are inlined as SQL literals — see `spot-sql.ts` header for the\n * extract_statements GC leak that ruled out positional parameters.\n *\n * Purity contract: pure function, no DuckDB value-level imports. Tests in\n * `tests/unit/market/stores/quote-sql.test.ts`.\n */\nimport { escapeSqlLiteral } from \"../../utils/quote-parquet-projection.ts\";\nimport type { BuiltSQL } from \"./spot-sql.ts\";\n\nfunction lit(value: string): string {\n return `'${escapeSqlLiteral(value)}'`;\n}\n\n/**\n * Build the bulk quote read. Throws if `occTickers` is empty (prevents emitting\n * an invalid `ticker IN ()` clause).\n *\n * Optional `timeStart`/`timeEnd` push an `AND time BETWEEN …` filter into SQL.\n * This is critical for prefetch where the entry-time window is often a single\n * minute: without the filter, DuckDB returns every minute bar in the\n * [from, to] range per ticker, blowing JS heap when bulking across many dates.\n */\nexport function buildReadQuotesSQL(\n underlying: string,\n occTickers: string[],\n from: string,\n to: string,\n opts?: { timeStart?: string; timeEnd?: string },\n): BuiltSQL {\n if (occTickers.length === 0) {\n throw new Error(\"buildReadQuotesSQL: occTickers must not be empty\");\n }\n const timeStart = opts?.timeStart;\n const timeEnd = opts?.timeEnd;\n const hasTimeFilter = timeStart != null && timeEnd != null;\n\n const tickerList = occTickers.map(lit).join(\", \");\n const timeClause = hasTimeFilter\n ? `AND time >= ${lit(timeStart!)} AND time <= ${lit(timeEnd!)}\\n `\n : \"\";\n return {\n sql: `SELECT ticker, date, time, bid, ask, mid, last_updated_ns,\n delta, gamma, theta, vega, iv, greeks_source, greeks_revision,\n rate_type, rate_value, gamma_source\n FROM market.option_quote_minutes\n WHERE underlying = ${lit(underlying)}\n AND date >= ${lit(from)}\n AND date <= ${lit(to)}\n ${timeClause}AND ticker IN (${tickerList})\n ORDER BY ticker, date, time`,\n };\n}\n","import type { MarketStores } from \"../stores/index.ts\";\nimport type { MarketDataProvider, BarRow, MinuteQuote } from \"../../utils/market-provider.ts\";\nimport { getProvider } from \"../../utils/market-provider.ts\";\nimport { MassiveProvider } from \"../../utils/providers/massive.ts\";\nimport { ThetaDataProvider } from \"../../utils/providers/thetadata.ts\";\nimport { extractRoot } from \"../tickers/resolver.ts\";\nimport { validateImportSelect } from \"../../tools/sql.ts\";\nimport type {\n IngestBarsOptions,\n IngestQuotesOptions,\n IngestChainOptions,\n IngestOpenInterestOptions,\n IngestFlatFileOptions,\n ComputeVixContextOptions,\n RefreshOptions,\n IngestResult,\n IngestSkippedBatch,\n IngestStatus,\n RefreshResult,\n BulkProgressReporter,\n} from \"./types.ts\";\nimport type { OiDailyRow, QuoteRow } from \"../stores/types.ts\";\nimport { applyQuoteGreeks, type QuoteGreeksStats } from \"../../utils/option-quote-greeks.ts\";\n\nexport interface MarketIngestorDeps {\n stores: MarketStores;\n dataRoot: string;\n providerFactory?: () => MarketDataProvider;\n}\n\n// When `applyQuoteGreeks` fails to resolve the underlying price for more than\n// this fraction of the rows that actually attempted underlying-price lookup,\n// the batch is suspected of a coverage gap (partial-day spot bars, missing\n// chain partition, schema-filter mismatch). Such batches are dropped — they'd\n// otherwise persist with intact bid/ask but null greeks, which silently\n// corrupts the option_quotes store.\n//\n// Denominator is `missingUnderlyingRows + computedRows` — i.e. only rows that\n// reached the underlying-lookup branch in compute mode. Rows skipped earlier\n// (provider greeks already present, missing contract meta, provider-only mode)\n// don't dilute the signal. This catches a real production leak: a mixed-source\n// partition where one provider supplies inline greeks for 60% of rows and the\n// remaining 40% all fail underlying lookup on a partial-day spot outage — a\n// `missing/visited` ratio of 0.4 wouldn't trip the 0.5 threshold, but\n// `missing/attempted` is 1.0 and does.\n//\n// Tunable from telemetry: lower → more conservative (false-positives on\n// genuinely-sparse chain reads); higher → more leakage of null-greeks rows.\n// 0.5 means \"half of the rows that needed compute failed to resolve the\n// underlying price\" — conservative enough that a few unresolved rows in a\n// large compute batch don't trip it, aggressive enough that a partial-day\n// spot outage (which sends the ratio to ~1.0) is caught immediately.\nconst COVERAGE_GAP_THRESHOLD = 0.5;\n\n// Sibling of COVERAGE_GAP_THRESHOLD, distinct failure mode. Fires when\n// underlying-price lookup SUCCEEDED but `computeQuoteGreeks` returned null for\n// the majority of the rows that actually attempted the math (zero/negative\n// option price, corrupt expiration → negative DTE, malformed strike grid).\n// Without this guard those rows would mis-attribute as `coverage_gap` because\n// they all flowed into `unresolvedRows`; coverage_gap's denominator\n// (`missingUnderlyingRows + computedRows`) excludes math failures, so a\n// math-only failure mode never tripped coverage_gap by itself, but a single\n// missing-underlying row in the same partition would — labeling the trip\n// \"coverage gap\" even though the bulk of the leak was BS-math corruption.\n// Both guards can fire on the same partition when both subsets exceed their\n// thresholds independently; see types.ts on the no-dedupe convention.\n//\n// Set to 0.5 for symmetry with COVERAGE_GAP_THRESHOLD. Tunable from telemetry\n// independently — the two failure modes have different operational meanings\n// (spot/chain coverage vs. quote/chain corruption) and may want different\n// trip points later.\nconst COMPUTE_FAILURE_THRESHOLD = 0.5;\n\nfunction coverageGapEntry(\n stats: QuoteGreeksStats,\n batch: { underlying: string; date: string; ticker?: string; rows: number },\n): IngestSkippedBatch | null {\n const attemptedRows = stats.missingUnderlyingRows + stats.computedRows;\n if (attemptedRows <= 0) return null;\n const ratio = stats.missingUnderlyingRows / attemptedRows;\n if (ratio <= COVERAGE_GAP_THRESHOLD) return null;\n const message =\n `underlying-price coverage gap: ${stats.missingUnderlyingRows}/${attemptedRows} rows ` +\n `missing underlying price (ratio=${ratio.toFixed(2)}, threshold=${COVERAGE_GAP_THRESHOLD.toFixed(2)})`;\n return {\n underlying: batch.underlying,\n date: batch.date,\n ...(batch.ticker ? { ticker: batch.ticker } : {}),\n rows: batch.rows,\n reason: \"coverage_gap\",\n error: message,\n resolveRatio: ratio,\n };\n}\n\nfunction computeFailureEntry(\n stats: QuoteGreeksStats,\n batch: { underlying: string; date: string; ticker?: string; rows: number },\n): IngestSkippedBatch | null {\n const attemptedRows = stats.mathFailedRows + stats.computedRows;\n if (attemptedRows <= 0) return null;\n const ratio = stats.mathFailedRows / attemptedRows;\n if (ratio <= COMPUTE_FAILURE_THRESHOLD) return null;\n const message =\n `compute failure: ${stats.mathFailedRows}/${attemptedRows} rows failed ` +\n `black-scholes math after underlying-price lookup succeeded ` +\n `(ratio=${ratio.toFixed(2)}, threshold=${COMPUTE_FAILURE_THRESHOLD.toFixed(2)})`;\n return {\n underlying: batch.underlying,\n date: batch.date,\n ...(batch.ticker ? { ticker: batch.ticker } : {}),\n rows: batch.rows,\n reason: \"compute_failure\",\n error: message,\n resolveRatio: ratio,\n };\n}\n\nfunction providerErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Identify dates the US options market is closed. Currently weekends only —\n * holiday list is intentional TODO. ThetaData (and likely other providers)\n * return junk data on weekends (zero-priced \"quotes\" for every contract on\n * Sundays in particular), so refresh() short-circuits these dates.\n *\n * Lower-level methods (ingestBars/ingestChain/ingestQuotes) are unchanged —\n * callers needing forensic per-weekend fetches can call them directly.\n */\nfunction isNonTradingDay(asOf: string): boolean {\n // asOf is YYYY-MM-DD. Use UTC noon to avoid TZ drift across the date\n // boundary on hosts in negative-offset timezones.\n const d = new Date(`${asOf}T12:00:00Z`);\n const dow = d.getUTCDay();\n if (dow === 0 || dow === 6) return true;\n // TODO: NYSE holiday calendar (MLK, Presidents, Good Friday, Memorial,\n // Juneteenth, Independence, Labor, Thanksgiving, Christmas, New Year).\n return false;\n}\n\nfunction unsupportedProviderResult(\n provider: MarketDataProvider,\n operation: \"bars\" | \"quotes\" | \"chain\",\n target: string,\n reason: string,\n originalError: string,\n): IngestResult {\n return {\n status: \"unsupported\",\n rowsWritten: 0,\n error: `Provider ${provider.name} does not support ${operation} for ${target}: ${reason}`,\n details: {\n provider: provider.name,\n operation,\n target,\n reason,\n originalError,\n },\n };\n}\n\nexport class MarketIngestor {\n private readonly deps: MarketIngestorDeps;\n constructor(deps: MarketIngestorDeps) {\n this.deps = deps;\n }\n\n async ingestBars(opts: IngestBarsOptions): Promise<IngestResult> {\n const provider = this.resolveProvider(opts.provider);\n const timespan = opts.timespan ?? \"1d\";\n\n if (opts.dryRun) {\n return { status: \"skipped\", rowsWritten: 0, details: { reason: \"dry_run\" } };\n }\n\n let totalRows = 0;\n let minDate: string | undefined;\n let maxDate: string | undefined;\n const providerTimespan = this.timespanToProviderArgs(timespan);\n\n for (const ticker of opts.tickers) {\n const normalizedTicker = ticker.toUpperCase();\n const assetClass = this.detectAssetClass(normalizedTicker);\n const unsupported = this.preflightProviderSupport(\n provider,\n \"bars\",\n normalizedTicker,\n assetClass,\n providerTimespan,\n );\n if (unsupported) return unsupported;\n let bars: BarRow[];\n try {\n bars = await provider.fetchBars({\n ticker: normalizedTicker,\n from: opts.from,\n to: opts.to,\n ...providerTimespan,\n assetClass,\n });\n } catch (error) {\n const mapped = this.mapProviderFailure(provider, \"bars\", normalizedTicker, error, assetClass);\n if (mapped) return mapped;\n throw error;\n }\n\n if (bars.length === 0) continue;\n\n const byDate = this.groupBarsByDate(bars);\n for (const [date, dayBars] of byDate) {\n await this.deps.stores.spot.writeBars(normalizedTicker, date, dayBars);\n totalRows += dayBars.length;\n if (!minDate || date < minDate) minDate = date;\n if (!maxDate || date > maxDate) maxDate = date;\n }\n }\n\n const enrichment = opts.skipEnrichment || !minDate\n ? null\n : await this.triggerPerTickerEnrichment(opts.tickers, minDate, maxDate!);\n\n return {\n status: \"ok\",\n rowsWritten: totalRows,\n dateRange: minDate ? { from: minDate, to: maxDate! } : undefined,\n enrichment,\n };\n }\n\n private resolveProvider(override?: \"massive\" | \"thetadata\"): MarketDataProvider {\n // Priority: per-call override > injected factory > env-driven singleton.\n if (override) {\n return override === \"thetadata\" ? new ThetaDataProvider() : new MassiveProvider();\n }\n if (this.deps.providerFactory) return this.deps.providerFactory();\n return getProvider();\n }\n\n private timespanToProviderArgs(timespan: string): { timespan: \"day\" | \"minute\" | \"hour\"; multiplier: number } {\n switch (timespan) {\n case \"1d\": return { timespan: \"day\", multiplier: 1 };\n case \"1m\": return { timespan: \"minute\", multiplier: 1 };\n case \"5m\": return { timespan: \"minute\", multiplier: 5 };\n case \"15m\": return { timespan: \"minute\", multiplier: 15 };\n case \"1h\": return { timespan: \"hour\", multiplier: 1 };\n default: throw new Error(`Unknown timespan: ${timespan}`);\n }\n }\n\n private detectAssetClass(ticker: string): \"stock\" | \"index\" | \"option\" {\n const VIX_FAMILY = new Set([\"VIX\", \"VIX9D\", \"VIX1D\", \"VIX3M\", \"VXN\", \"SPX\", \"NDX\"]);\n if (VIX_FAMILY.has(ticker)) return \"index\";\n if (/^[A-Z]{1,6}\\d{6}[CP]\\d{8}$/.test(ticker)) return \"option\";\n return \"stock\";\n }\n\n private groupBarsByDate(bars: BarRow[]): Map<string, BarRow[]> {\n const map = new Map<string, BarRow[]>();\n for (const bar of bars) {\n const list = map.get(bar.date) ?? [];\n list.push(bar);\n map.set(bar.date, list);\n }\n return map;\n }\n\n private async triggerPerTickerEnrichment(\n tickers: string[],\n from: string,\n to: string,\n ): Promise<{ from: string; to: string }> {\n for (const ticker of tickers) {\n await this.deps.stores.enriched.compute(ticker.toUpperCase(), from, to);\n }\n return { from, to };\n }\n\n private mapProviderFailure(\n provider: MarketDataProvider,\n operation: \"bars\" | \"quotes\" | \"chain\",\n target: string,\n error: unknown,\n assetClass?: \"stock\" | \"index\" | \"option\",\n ): IngestResult | null {\n const message = providerErrorMessage(error);\n if (\n provider.name === \"massive\" &&\n assetClass === \"index\" &&\n /(TimeoutError|aborted due to timeout|timed out)/i.test(message)\n ) {\n const reason = operation === \"chain\"\n ? \"current Massive provider path does not reliably support index option-chain refreshes\"\n : \"current Massive provider path does not reliably support index data for this request\";\n return unsupportedProviderResult(provider, operation, target, reason, message);\n }\n if (provider.name === \"massive\" && /HTTP 403 Forbidden/.test(message)) {\n const reason = assetClass === \"index\"\n ? \"current Massive account/tier does not permit index data for this request\"\n : \"current Massive account/tier does not permit this request\";\n return unsupportedProviderResult(provider, operation, target, reason, message);\n }\n return null;\n }\n\n private preflightProviderSupport(\n provider: MarketDataProvider,\n operation: \"bars\" | \"quotes\" | \"chain\",\n target: string,\n assetClass?: \"stock\" | \"index\" | \"option\",\n barRequest?: { timespan: \"day\" | \"minute\" | \"hour\"; multiplier: number },\n ): IngestResult | null {\n if (operation === \"bars\" && barRequest) {\n const caps = provider.capabilities();\n if (barRequest.timespan === \"day\" && !caps.dailyBars) {\n return unsupportedProviderResult(\n provider,\n operation,\n target,\n \"provider capabilities report it does not support daily bars\",\n \"preflight: dailyBars=false\",\n );\n }\n if (barRequest.timespan !== \"day\" && (!caps.tradeBars || !caps.minuteBars)) {\n return unsupportedProviderResult(\n provider,\n operation,\n target,\n \"provider capabilities report it does not support minute bars\",\n `preflight: tradeBars=${String(caps.tradeBars)} minuteBars=${String(caps.minuteBars)}`,\n );\n }\n }\n\n if (provider.name === \"massive\" && assetClass === \"index\") {\n if (operation === \"bars\") {\n return unsupportedProviderResult(\n provider,\n operation,\n target,\n \"current Massive provider path does not support index bar refreshes for this underlying\",\n \"preflight: index bar refreshes are disabled for Massive\",\n );\n }\n if (operation === \"chain\") {\n return unsupportedProviderResult(\n provider,\n operation,\n target,\n \"current Massive provider path does not support index option-chain refreshes for this underlying\",\n \"preflight: index option-chain refreshes are disabled for Massive\",\n );\n }\n }\n return null;\n }\n\n private async applyCoverageFallback(\n dataset: \"spot\" | \"chain\",\n symbol: string,\n asOf: string,\n result: IngestResult,\n ): Promise<IngestResult> {\n if (result.status !== \"unsupported\" && result.status !== \"error\") {\n return result;\n }\n\n try {\n const coverage = dataset === \"spot\"\n ? await this.deps.stores.spot.getCoverage(symbol.toUpperCase(), asOf, asOf)\n : await this.deps.stores.chain.getCoverage(symbol.toUpperCase(), asOf, asOf);\n if (coverage.totalDates <= 0) return result;\n\n return {\n status: \"skipped\",\n rowsWritten: 0,\n dateRange: { from: asOf, to: asOf },\n details: {\n ...(result.details ?? {}),\n reason: \"using_cached_coverage\",\n dataset,\n symbol: symbol.toUpperCase(),\n originalStatus: result.status,\n cachedCoverage: {\n totalDates: coverage.totalDates,\n earliest: coverage.earliest,\n latest: coverage.latest,\n },\n },\n };\n } catch {\n return result;\n }\n }\n\n private quoteGreeksSourceForProvider(\n provider: MarketDataProvider,\n ): \"massive\" | \"thetadata\" | undefined {\n if (provider.name === \"massive\" || provider.name === \"thetadata\") {\n return provider.name;\n }\n return undefined;\n }\n\n private async enrichQuoteRows(\n underlying: string,\n date: string,\n rows: QuoteRow[],\n defaultProviderSource?: \"massive\" | \"thetadata\",\n ): Promise<{ rows: QuoteRow[]; stats: QuoteGreeksStats }> {\n if (rows.length === 0) {\n return {\n rows,\n stats: {\n rowsVisited: 0,\n existingGreeksRows: 0,\n computedRows: 0,\n missingContractRows: 0,\n missingUnderlyingRows: 0,\n mathFailedRows: 0,\n unresolvedRows: 0,\n },\n };\n }\n\n const [contracts, underlyingBars] = await Promise.all([\n this.deps.stores.chain.readChain(underlying, date),\n this.deps.stores.spot.readBars(underlying, date, date),\n ]);\n\n const contractByTicker = new Map(\n contracts.map((contract) => [contract.ticker, contract] as const),\n );\n const underlyingPriceByTime = new Map<string, number>();\n for (const bar of underlyingBars) {\n if (bar.date !== date || !bar.time || !(bar.open > 0)) continue;\n const time = bar.time.slice(0, 5);\n if (!underlyingPriceByTime.has(time)) {\n underlyingPriceByTime.set(time, bar.open);\n }\n }\n\n const stats = applyQuoteGreeks({\n rows,\n getDate: (row) => row.timestamp.slice(0, 10),\n getTime: (row) => row.timestamp.slice(11, 16),\n getMid: (row) => (row.bid + row.ask) / 2,\n getContractMeta: (row) => {\n const contract = contractByTicker.get(row.occ_ticker);\n if (!contract) return undefined;\n return {\n contract_type: contract.contract_type,\n strike: contract.strike,\n expiration: contract.expiration,\n };\n },\n getUnderlyingPrice: (_rowDate, time) => underlyingPriceByTime.get(time),\n mode: \"auto\",\n defaultProviderSource,\n });\n\n return { rows, stats };\n }\n\n async ingestQuotes(opts: IngestQuotesOptions): Promise<IngestResult> {\n const hasTickers = opts.tickers && opts.tickers.length > 0;\n const hasUnderlyings = opts.underlyings && opts.underlyings.length > 0;\n if (hasTickers === hasUnderlyings) {\n return {\n status: \"error\",\n rowsWritten: 0,\n error: \"ingestQuotes requires exactly one of { tickers, underlyings } to be non-empty\",\n };\n }\n\n const provider = this.resolveProvider(opts.provider);\n\n // Dispatch decisions live on the per-mode paths below — the per-ticker\n // path gates on `typeof provider.fetchQuotes === \"function\"` (line ~377);\n // the bulk-by-underlying path gates on `caps.bulkByRoot` + `fetchBulkQuotes`\n // (line ~429). A unified `caps.quotes` gate would (a) reject Massive on\n // Developer plan even though its fetchQuotes has tier-aware fallback, and\n // (b) reject any future bulk-only provider that doesn't implement the\n // per-ticker fetchQuotes method. Provenance — \"is this NBBO-grade?\" — is\n // captured per-row via the QuoteRow.source column.\n\n if (opts.dryRun) {\n return { status: \"skipped\", rowsWritten: 0, details: { reason: \"dry_run\" } };\n }\n\n return hasUnderlyings\n ? this.ingestQuotesByUnderlying(\n provider,\n opts.underlyings!,\n opts.from,\n opts.to,\n opts.onProgress,\n )\n : this.ingestQuotesByTicker(provider, opts.tickers!, opts.from, opts.to);\n }\n\n /**\n * Per-ticker path: one provider call per OCC ticker over the full [from, to]\n * range. Works on any provider that implements `fetchQuotes`. Used by\n * callers that already know the exact contracts they care about.\n */\n private async ingestQuotesByTicker(\n provider: MarketDataProvider,\n tickers: string[],\n from: string,\n to: string,\n ): Promise<IngestResult> {\n if (typeof provider.fetchQuotes !== \"function\") {\n return {\n status: \"unsupported\",\n rowsWritten: 0,\n error: `Provider ${provider.name} does not implement fetchQuotes (per-ticker path)`,\n };\n }\n\n let totalRows = 0;\n let minDate: string | undefined;\n let maxDate: string | undefined;\n const skipped: IngestSkippedBatch[] = [];\n\n for (const ticker of tickers) {\n let quotes: Awaited<ReturnType<NonNullable<MarketDataProvider[\"fetchQuotes\"]>>>;\n try {\n quotes = await provider.fetchQuotes(ticker, from, to);\n } catch (error) {\n const mapped = this.mapProviderFailure(provider, \"quotes\", ticker, error, \"option\");\n if (mapped) return mapped;\n throw error;\n }\n const written = await this.writeQuotesForTicker(\n provider,\n ticker,\n quotes,\n );\n totalRows += written.rowsWritten;\n if (written.minDate && (!minDate || written.minDate < minDate)) minDate = written.minDate;\n if (written.maxDate && (!maxDate || written.maxDate > maxDate)) maxDate = written.maxDate;\n if (written.skipped.length > 0) skipped.push(...written.skipped);\n }\n\n return {\n status: skipped.length > 0 ? \"partial\" : \"ok\",\n rowsWritten: totalRows,\n dateRange: minDate ? { from: minDate, to: maxDate! } : undefined,\n ...(skipped.length > 0 ? { skipped } : {}),\n };\n }\n\n /**\n * Bulk path: provider-specific full-chain quote fetch for each\n * (underlying, date). ThetaData MDDS uses bounded per-contract batches;\n * other providers may use different bulk shapes. Capability-gated on\n * `bulkByRoot` + presence of `fetchBulkQuotes`.\n */\n private async ingestQuotesByUnderlying(\n provider: MarketDataProvider,\n underlyings: string[],\n from: string,\n to: string,\n onProgress?: BulkProgressReporter,\n ): Promise<IngestResult> {\n const caps = provider.capabilities();\n if (!caps.bulkByRoot || typeof provider.fetchBulkQuotes !== \"function\") {\n return {\n status: \"unsupported\",\n rowsWritten: 0,\n error: `Provider ${provider.name} does not support bulk-by-underlying quotes (capability.bulkByRoot=${caps.bulkByRoot})`,\n };\n }\n\n const dates = this.enumerateDates(from, to);\n let totalRows = 0;\n let minDate: string | undefined;\n let maxDate: string | undefined;\n const skipped: IngestSkippedBatch[] = [];\n\n for (const underlying of underlyings) {\n const upperUnderlying = underlying.toUpperCase();\n for (const date of dates) {\n const drain = await this.drainBulkQuotes(\n provider,\n upperUnderlying,\n date,\n onProgress,\n );\n if (drain.rowsWritten > 0) {\n totalRows += drain.rowsWritten;\n if (!minDate || date < minDate) minDate = date;\n if (!maxDate || date > maxDate) maxDate = date;\n }\n if (drain.skipped.length > 0) skipped.push(...drain.skipped);\n // Always emit a date-flushed event — even on 0 rows — so callers see\n // predictable progress even for empty dates (holidays, missing data).\n await this.safeEmit(onProgress, {\n kind: \"date-flushed\",\n underlying: upperUnderlying,\n date,\n rowsWritten: drain.rowsWritten,\n });\n }\n }\n\n return {\n status: skipped.length > 0 ? \"partial\" : \"ok\",\n rowsWritten: totalRows,\n dateRange: minDate ? { from: minDate, to: maxDate! } : undefined,\n ...(skipped.length > 0 ? { skipped } : {}),\n };\n }\n\n /**\n * Invoke a progress reporter without ever letting its errors fail the\n * ingest. Awaits promise-returning reporters so async back-pressure works.\n */\n private async safeEmit(\n reporter: BulkProgressReporter | undefined,\n event: Parameters<BulkProgressReporter>[0],\n ): Promise<void> {\n if (!reporter) return;\n try {\n await reporter(event);\n } catch {\n // best-effort: progress must never fail the ingest\n }\n }\n\n /**\n * Consume the entire bulk-quote stream for one (underlying, date) and write\n * once at the end. Mid-stream flushing is NOT safe here: `writeQuotes` ->\n * `writeParquetAtomic` performs `COPY ... TO '<partitionFile>'` which\n * *overwrites* the partition — splitting one day into multiple writes would\n * leave only the final flush on disk. Peak heap is ~O(rows × row-size) per\n * underlying per day (~700MB for a full SPX day), which matches what the\n * old wildcard-bulk drain script ran at before it was retired.\n */\n private async drainBulkQuotes(\n provider: MarketDataProvider,\n upperUnderlying: string,\n date: string,\n onProgress?: BulkProgressReporter,\n ): Promise<{ rowsWritten: number; skipped: IngestSkippedBatch[] }> {\n // Tickers → resolved underlying mapping is cached per call; the typical\n // case is all contracts mapping to the same underlying (e.g. SPX + SPXW\n // → \"SPX\"), so the per-underlying bucket is almost always a single key.\n const tickerRegistry = this.deps.stores.quote.tickers;\n // Resolve the request underlying through the same registry so rows are\n // compared against the canonical target — lets callers pass a root like\n // \"SPXW\" and still have rows resolving to \"SPX\" match correctly.\n const expectedUnderlying = tickerRegistry.resolve(upperUnderlying);\n const resolvedCache = new Map<string, string>();\n const bucket = new Map<string, QuoteRow[]>();\n\n // Build the per-(root,right) completion hook that fans provider-side\n // group completions out to the transport-aware reporter. The provider\n // wraps the invocation in its own try/catch so an upstream reporter\n // throw can't corrupt the stream — we still wrap here as a second\n // safety net (in case the provider forgets).\n const onGroupComplete = onProgress\n ? (info: {\n root: string;\n right: \"call\" | \"put\";\n date: string;\n status: \"ok\" | \"error\";\n phase?: \"checkpoint\" | \"complete\";\n completedContracts?: number;\n totalContracts?: number;\n }) => {\n // Fire-and-forget — provider callsite is synchronous; async work\n // is scheduled on the next microtask. Any failure is swallowed by\n // safeEmit so progress remains best-effort.\n void this.safeEmit(onProgress, {\n kind: \"group\",\n underlying: upperUnderlying,\n root: info.root,\n right: info.right,\n date: info.date,\n status: info.status,\n phase: info.phase,\n completedContracts: info.completedContracts,\n totalContracts: info.totalContracts,\n });\n }\n : undefined;\n\n const stream = provider.fetchBulkQuotes!({ underlying: upperUnderlying, date, onGroupComplete });\n for await (const chunk of stream) {\n for (const row of chunk) {\n const root = extractRoot(row.ticker);\n let resolvedUnderlying = resolvedCache.get(root);\n if (resolvedUnderlying === undefined) {\n resolvedUnderlying = tickerRegistry.resolve(root);\n resolvedCache.set(root, resolvedUnderlying);\n }\n // Defense-in-depth: a row cannot silently land in a different underlying\n // than the one we requested. This trips when extractRoot fails to parse\n // a non-standard ticker format and the identity-fallback returns the raw\n // ticker as a \"root\" that then identity-resolves to itself. Without this\n // guard, 68 partitions on 2024-07-09 leaked into underlying=SPX<OCC>/\n // folders (see resolver.ts OCC_RE for the regex that must stay in sync).\n if (resolvedUnderlying !== expectedUnderlying) {\n throw new Error(\n `[drainBulkQuotes] root resolution mismatch: row.ticker=\"${row.ticker}\" ` +\n `extractedRoot=\"${root}\" resolvedUnderlying=\"${resolvedUnderlying}\" ` +\n `expectedUnderlying=\"${expectedUnderlying}\" (request underlying=\"${upperUnderlying}\", date=\"${date}\"). ` +\n `A row must resolve to the same underlying as the ingest request. ` +\n `If this ticker format is legitimate, extend OCC_RE in resolver.ts to parse it.`,\n );\n }\n let list = bucket.get(resolvedUnderlying);\n if (!list) {\n list = [];\n bucket.set(resolvedUnderlying, list);\n }\n list.push({\n occ_ticker: row.ticker,\n timestamp: row.timestamp,\n bid: row.bid,\n ask: row.ask,\n delta: row.delta ?? null,\n gamma: row.gamma ?? null,\n theta: row.theta ?? null,\n vega: row.vega ?? null,\n iv: row.iv ?? null,\n greeks_source: row.greeks_source ?? null,\n greeks_revision: row.greeks_revision ?? null,\n rate_type: row.rate_type ?? null,\n rate_value: row.rate_value ?? null,\n gamma_source: row.gamma_source ?? null,\n });\n }\n }\n\n let totalRows = 0;\n const skipped: IngestSkippedBatch[] = [];\n for (const [resolvedUnderlying, rows] of bucket) {\n if (rows.length === 0) continue;\n let enriched: { rows: QuoteRow[]; stats: QuoteGreeksStats };\n try {\n enriched = await this.enrichQuoteRows(\n resolvedUnderlying,\n date,\n rows,\n this.quoteGreeksSourceForProvider(provider),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // Warn is still emitted for live tail-following — the load-bearing\n // signal is `result.skipped[]` / `status: \"partial\"`.\n console.warn(\n \"[drainBulkQuotes] enrichQuoteRows failed; skipping batch\",\n {\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n error: message,\n },\n );\n skipped.push({\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n reason: \"read_failed\",\n error: message,\n });\n continue;\n }\n const gap = coverageGapEntry(enriched.stats, {\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n });\n const computeFailure = computeFailureEntry(enriched.stats, {\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n });\n if (gap) {\n console.warn(\"[drainBulkQuotes] coverage gap; skipping batch\", {\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n resolveRatio: gap.resolveRatio,\n });\n skipped.push(gap);\n }\n if (computeFailure) {\n console.warn(\"[drainBulkQuotes] compute failure; skipping batch\", {\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n resolveRatio: computeFailure.resolveRatio,\n });\n skipped.push(computeFailure);\n }\n if (gap || computeFailure) continue;\n await this.deps.stores.quote.writeQuotes(resolvedUnderlying, date, enriched.rows);\n totalRows += rows.length;\n }\n return { rowsWritten: totalRows, skipped };\n }\n\n private async writeQuotesForTicker(\n provider: MarketDataProvider,\n ticker: string,\n quotes: Map<string, MinuteQuote>,\n ): Promise<{ rowsWritten: number; minDate?: string; maxDate?: string; skipped: IngestSkippedBatch[] }> {\n const root = extractRoot(ticker);\n const underlying = this.deps.stores.quote.tickers.resolve(root);\n\n const byDate = new Map<string, QuoteRow[]>();\n for (const [key, quote] of quotes) {\n const spaceIdx = key.indexOf(\" \");\n if (spaceIdx === -1) continue;\n const date = key.slice(0, spaceIdx);\n const list = byDate.get(date) ?? [];\n list.push({\n occ_ticker: ticker,\n timestamp: key,\n bid: quote.bid,\n ask: quote.ask,\n source: quote.source ?? null,\n delta: quote.delta ?? null,\n gamma: quote.gamma ?? null,\n theta: quote.theta ?? null,\n vega: quote.vega ?? null,\n iv: quote.iv ?? null,\n greeks_source: quote.greeks_source ?? null,\n greeks_revision: quote.greeks_revision ?? null,\n rate_type: quote.rate_type ?? null,\n rate_value: quote.rate_value ?? null,\n gamma_source: quote.gamma_source ?? null,\n });\n byDate.set(date, list);\n }\n\n let rowsWritten = 0;\n let minDate: string | undefined;\n let maxDate: string | undefined;\n const skipped: IngestSkippedBatch[] = [];\n for (const [date, rows] of byDate) {\n let enriched: { rows: QuoteRow[]; stats: QuoteGreeksStats };\n try {\n enriched = await this.enrichQuoteRows(\n underlying,\n date,\n rows,\n this.quoteGreeksSourceForProvider(provider),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // Warn is still emitted for live tail-following — the load-bearing\n // signal is `result.skipped[]` / `status: \"partial\"`.\n console.warn(\n \"[writeQuotesForTicker] enrichQuoteRows failed; skipping batch\",\n {\n underlying,\n date,\n ticker,\n rows: rows.length,\n error: message,\n },\n );\n skipped.push({\n underlying,\n date,\n ticker,\n rows: rows.length,\n reason: \"read_failed\",\n error: message,\n });\n continue;\n }\n const gap = coverageGapEntry(enriched.stats, {\n underlying,\n date,\n ticker,\n rows: rows.length,\n });\n const computeFailure = computeFailureEntry(enriched.stats, {\n underlying,\n date,\n ticker,\n rows: rows.length,\n });\n if (gap) {\n console.warn(\"[writeQuotesForTicker] coverage gap; skipping batch\", {\n underlying,\n date,\n ticker,\n rows: rows.length,\n resolveRatio: gap.resolveRatio,\n });\n skipped.push(gap);\n }\n if (computeFailure) {\n console.warn(\"[writeQuotesForTicker] compute failure; skipping batch\", {\n underlying,\n date,\n ticker,\n rows: rows.length,\n resolveRatio: computeFailure.resolveRatio,\n });\n skipped.push(computeFailure);\n }\n if (gap || computeFailure) continue;\n await this.deps.stores.quote.writeQuotes(underlying, date, enriched.rows);\n rowsWritten += rows.length;\n if (!minDate || date < minDate) minDate = date;\n if (!maxDate || date > maxDate) maxDate = date;\n }\n return { rowsWritten, minDate, maxDate, skipped };\n }\n\n async ingestChain(opts: IngestChainOptions): Promise<IngestResult> {\n const provider = this.resolveProvider(opts.provider);\n\n if (typeof provider.fetchContractList !== \"function\") {\n return {\n status: \"unsupported\",\n rowsWritten: 0,\n error: `Provider ${provider.name} does not support fetchContractList`,\n };\n }\n\n if (opts.dryRun) {\n return { status: \"skipped\", rowsWritten: 0, details: { reason: \"dry_run\" } };\n }\n\n let totalRows = 0;\n\n for (const underlying of opts.underlyings) {\n const upperUnderlying = underlying.toUpperCase();\n const assetClass = this.detectAssetClass(upperUnderlying);\n const unsupported = this.preflightProviderSupport(provider, \"chain\", upperUnderlying, assetClass);\n if (unsupported) return unsupported;\n // Enumerate trading dates in [from, to] and fetch the chain as-of each date.\n const dates = this.enumerateDates(opts.from, opts.to);\n for (const date of dates) {\n let result: Awaited<ReturnType<NonNullable<MarketDataProvider[\"fetchContractList\"]>>>;\n try {\n result = await provider.fetchContractList!({\n underlying: upperUnderlying,\n as_of: date,\n expired: true,\n });\n } catch (error) {\n const mapped = this.mapProviderFailure(provider, \"chain\", upperUnderlying, error, assetClass);\n if (mapped) return mapped;\n throw error;\n }\n\n if (result.contracts.length === 0) continue;\n\n // Map ContractReference → ContractRow (add underlying, date, compute dte).\n const rows = result.contracts.map((c) => ({\n underlying: upperUnderlying,\n date,\n ticker: c.ticker,\n contract_type: c.contract_type,\n strike: c.strike,\n expiration: c.expiration,\n dte: this.computeDte(date, c.expiration),\n exercise_style: c.exercise_style,\n }));\n\n await this.deps.stores.chain.writeChain(upperUnderlying, date, rows);\n totalRows += rows.length;\n }\n }\n\n return {\n status: \"ok\",\n rowsWritten: totalRows,\n dateRange: { from: opts.from, to: opts.to },\n };\n }\n\n async ingestOpenInterest(opts: IngestOpenInterestOptions): Promise<IngestResult> {\n const provider = this.resolveProvider(opts.provider);\n\n const caps = provider.capabilities();\n if (!caps.bulkByRoot || typeof provider.fetchOpenInterest !== \"function\") {\n return {\n status: \"unsupported\",\n rowsWritten: 0,\n error: `Provider ${provider.name} does not support open-interest ingest (capability.bulkByRoot=${caps.bulkByRoot})`,\n };\n }\n\n if (opts.dryRun) {\n return { status: \"skipped\", rowsWritten: 0, details: { reason: \"dry_run\" } };\n }\n\n const tickerRegistry = this.deps.stores.quote.tickers;\n let totalRows = 0;\n let minDate: string | undefined;\n let maxDate: string | undefined;\n\n for (const underlying of opts.underlyings) {\n const upperUnderlying = underlying.toUpperCase();\n let oiRows: Awaited<ReturnType<NonNullable<MarketDataProvider[\"fetchOpenInterest\"]>>>;\n try {\n oiRows = await provider.fetchOpenInterest!({\n underlying: upperUnderlying,\n from: opts.from,\n to: opts.to,\n });\n } catch (error) {\n const mapped = this.mapProviderFailure(provider, \"chain\", upperUnderlying, error, \"option\");\n if (mapped) return mapped;\n throw error;\n }\n\n // Bucket by (resolved underlying, date) — one partition per pair, matching\n // the option_oi_daily/underlying=X/date=Y layout.\n const byPartition = new Map<string, OiDailyRow[]>();\n const resolvedCache = new Map<string, string>();\n for (const row of oiRows) {\n const root = extractRoot(row.ticker);\n let resolved = resolvedCache.get(root);\n if (resolved === undefined) {\n resolved = tickerRegistry.resolve(root);\n resolvedCache.set(root, resolved);\n }\n const key = `${resolved}\u0000${row.date}`;\n let list = byPartition.get(key);\n if (!list) {\n list = [];\n byPartition.set(key, list);\n }\n list.push({\n occ_ticker: row.ticker,\n underlying: resolved,\n date: row.date,\n expiration: row.expiration,\n strike: row.strike,\n right: row.right,\n open_interest: row.open_interest,\n source: provider.name,\n });\n }\n\n for (const [key, rows] of byPartition) {\n if (rows.length === 0) continue;\n const [resolved, date] = key.split(\"\u0000\");\n await this.deps.stores.oiDaily.writeOiDaily(resolved, date, rows);\n totalRows += rows.length;\n if (!minDate || date < minDate) minDate = date;\n if (!maxDate || date > maxDate) maxDate = date;\n }\n }\n\n return {\n status: \"ok\",\n rowsWritten: totalRows,\n dateRange: minDate ? { from: minDate, to: maxDate! } : { from: opts.from, to: opts.to },\n };\n }\n\n private enumerateDates(from: string, to: string): string[] {\n const dates: string[] = [];\n const current = new Date(`${from}T12:00:00Z`);\n const end = new Date(`${to}T12:00:00Z`);\n while (current <= end) {\n const y = current.getUTCFullYear();\n const m = String(current.getUTCMonth() + 1).padStart(2, \"0\");\n const d = String(current.getUTCDate()).padStart(2, \"0\");\n dates.push(`${y}-${m}-${d}`);\n current.setUTCDate(current.getUTCDate() + 1);\n }\n return dates;\n }\n\n private computeDte(asOf: string, expiration: string): number {\n const msPerDay = 86_400_000;\n const asOfMs = new Date(`${asOf}T12:00:00Z`).getTime();\n const expMs = new Date(`${expiration}T12:00:00Z`).getTime();\n return Math.max(0, Math.round((expMs - asOfMs) / msPerDay));\n }\n\n /**\n * Generic flat-file ingest — the LLM is the parser.\n *\n * The caller (typically an LLM that has sniffed the file via run_sql +\n * read_parquet/read_csv and compared columns to describe_database) supplies:\n * - filePath: local path to a file DuckDB can read\n * - datasetType: which store to write to\n * - selectSql: a SELECT that produces the store's canonical columns\n * - partition: the target (ticker/underlying, date) partition\n *\n * No provider is called — downloads happen beforehand (via the provider's\n * own tools, rclone, or the user pasting a file). The store's writeFromSelect\n * handles mode-routing (Parquet COPY vs DuckDB INSERT) so this dispatch\n * layer stays provider-agnostic and format-agnostic.\n */\n async ingestFlatFile(opts: IngestFlatFileOptions): Promise<IngestResult> {\n const selectError = validateImportSelect(opts.selectSql);\n if (selectError) {\n return { status: \"error\", rowsWritten: 0, error: selectError };\n }\n\n if (opts.dryRun) {\n return { status: \"skipped\", rowsWritten: 0, details: { reason: \"dry_run\" } };\n }\n\n const partitionDate = opts.partition.date;\n if (!partitionDate) {\n return { status: \"error\", rowsWritten: 0, error: \"partition.date is required\" };\n }\n\n try {\n switch (opts.datasetType) {\n case \"spot_bars\": {\n const ticker = opts.partition.ticker;\n if (!ticker) {\n return { status: \"error\", rowsWritten: 0, error: \"partition.ticker is required for datasetType='spot_bars'\" };\n }\n const { rowCount } = await this.deps.stores.spot.writeFromSelect(\n { ticker: ticker.toUpperCase(), date: partitionDate },\n opts.selectSql,\n );\n return { status: \"ok\", rowsWritten: rowCount, dateRange: { from: partitionDate, to: partitionDate } };\n }\n case \"option_quotes\": {\n const underlying = opts.partition.underlying;\n if (!underlying) {\n return { status: \"error\", rowsWritten: 0, error: \"partition.underlying is required for datasetType='option_quotes'\" };\n }\n const { rowCount } = await this.deps.stores.quote.writeFromSelect(\n { underlying: underlying.toUpperCase(), date: partitionDate },\n opts.selectSql,\n );\n return { status: \"ok\", rowsWritten: rowCount, dateRange: { from: partitionDate, to: partitionDate } };\n }\n case \"option_chain\": {\n const underlying = opts.partition.underlying;\n if (!underlying) {\n return { status: \"error\", rowsWritten: 0, error: \"partition.underlying is required for datasetType='option_chain'\" };\n }\n const { rowCount } = await this.deps.stores.chain.writeFromSelect(\n { underlying: underlying.toUpperCase(), date: partitionDate },\n opts.selectSql,\n );\n return { status: \"ok\", rowsWritten: rowCount, dateRange: { from: partitionDate, to: partitionDate } };\n }\n default: {\n const _exhaustive: never = opts.datasetType;\n return { status: \"error\", rowsWritten: 0, error: `Unknown datasetType: ${String(_exhaustive)}` };\n }\n }\n } catch (err) {\n return { status: \"error\", rowsWritten: 0, error: err instanceof Error ? err.message : String(err) };\n }\n }\n\n async computeVixContext(opts: ComputeVixContextOptions): Promise<IngestResult> {\n // Pure read-from-cache + compute + write. No provider call.\n await this.deps.stores.enriched.computeContext(opts.from, opts.to);\n return {\n status: \"ok\",\n rowsWritten: 0,\n dateRange: { from: opts.from, to: opts.to },\n };\n }\n\n async refresh(opts: RefreshOptions): Promise<RefreshResult> {\n // Short-circuit non-trading days. Provider behavior on weekends is\n // inconsistent — Saturday returns nothing (good), Sunday returns the\n // prior trading day's chain plus zero-priced quote rows (junk that\n // pollutes the parquet store). Refuse to write anything when the date\n // isn't a US trading day. See isNonTradingDay() above for scope.\n if (isNonTradingDay(opts.asOf)) {\n return {\n status: \"skipped\",\n perOperation: { spot: [], chain: [], quotes: [], openInterest: [], vixContext: null },\n coverage: {},\n errors: [],\n };\n }\n\n const VIX_FAMILY = new Set([\"VIX\", \"VIX9D\", \"VIX3M\", \"VXN\"]);\n const computeCtxFlag = opts.computeVixContext ?? true;\n const errors: string[] = [];\n\n // Step 1 — spot ingest per ticker (asOf = from = to).\n // Always request minute-resolution bars: downstream option-quote enrichment\n // needs the per-minute underlying price to compute greeks and to align\n // quote rows. A daily bar (single row at implicit 09:30) leaves every\n // minute after 09:30 without an underlying-price lookup and trips the\n // coverage_gap guard for the whole partition.\n const spotResults: IngestResult[] = [];\n for (const ticker of opts.spotTickers) {\n const rawResult = await this.ingestBars({\n tickers: [ticker],\n from: opts.asOf,\n to: opts.asOf,\n timespan: \"1m\",\n provider: opts.provider,\n });\n const result = await this.applyCoverageFallback(\"spot\", ticker, opts.asOf, rawResult);\n spotResults.push(result);\n if (result.status === \"error\") errors.push(`spot ${ticker}: ${result.error}`);\n }\n\n // Step 2 — chain ingest per underlying\n const chainResults: IngestResult[] = [];\n for (const underlying of opts.chainUnderlyings ?? []) {\n const rawResult = await this.ingestChain({\n underlyings: [underlying],\n from: opts.asOf,\n to: opts.asOf,\n provider: opts.provider,\n });\n const result = await this.applyCoverageFallback(\"chain\", underlying, opts.asOf, rawResult);\n chainResults.push(result);\n if (result.status === \"error\") errors.push(`chain ${underlying}: ${result.error}`);\n }\n\n // Step 3 — quote ingest (single batch — provider handles the list)\n const quoteResults: IngestResult[] = [];\n if (opts.quoteTickers && opts.quoteTickers.length > 0) {\n const result = await this.ingestQuotes({\n tickers: opts.quoteTickers,\n from: opts.asOf,\n to: opts.asOf,\n provider: opts.provider,\n });\n quoteResults.push(result);\n if (result.status === \"error\") errors.push(`quotes: ${result.error}`);\n }\n if (opts.quoteUnderlyings && opts.quoteUnderlyings.length > 0) {\n const result = await this.ingestQuotes({\n underlyings: opts.quoteUnderlyings,\n from: opts.asOf,\n to: opts.asOf,\n provider: opts.provider,\n onProgress: opts.onProgress,\n });\n quoteResults.push(result);\n if (result.status === \"error\") errors.push(`quotes (underlyings): ${result.error}`);\n }\n\n // Step 3b — open interest (opt-in only). Daily-granularity option OI lands\n // in its own store; it does NOT run unless the caller explicitly supplies\n // openInterestUnderlyings — no silent default that would write OI on every\n // refresh.\n const openInterestResults: IngestResult[] = [];\n if (opts.openInterestUnderlyings && opts.openInterestUnderlyings.length > 0) {\n const result = await this.ingestOpenInterest({\n underlyings: opts.openInterestUnderlyings,\n from: opts.asOf,\n to: opts.asOf,\n provider: opts.provider,\n });\n openInterestResults.push(result);\n if (result.status === \"error\") errors.push(`open interest: ${result.error}`);\n }\n\n // Step 4 — VIX context (only if flag AND any VIX-family ticker in spot list)\n let vixContext: IngestResult | null = null;\n const hasVixFamily = opts.spotTickers.some((t) => VIX_FAMILY.has(t.toUpperCase()));\n if (computeCtxFlag && hasVixFamily) {\n vixContext = await this.computeVixContext({ from: opts.asOf, to: opts.asOf });\n if (vixContext.status === \"error\") errors.push(`vix context: ${vixContext.error}`);\n }\n\n // Coverage report — shallow summary per ticker\n const coverage: Record<string, { totalDates: number; dateRange?: { from: string; to: string } }> = {};\n for (const ticker of opts.spotTickers) {\n try {\n const cov = await this.deps.stores.spot.getCoverage(ticker.toUpperCase(), opts.asOf, opts.asOf);\n coverage[ticker] = {\n totalDates: cov.totalDates,\n dateRange: cov.earliest && cov.latest ? { from: cov.earliest, to: cov.latest } : undefined,\n };\n } catch {\n coverage[ticker] = { totalDates: 0 };\n }\n }\n\n // Aggregate per-operation `skipped` entries so callers don't have to\n // traverse perOperation themselves. A \"partial\" status is contagious:\n // any operation returning partial flips refresh to partial too.\n const aggregateSkipped: IngestSkippedBatch[] = [];\n const collect = (r: IngestResult | null): void => {\n if (r?.skipped && r.skipped.length > 0) aggregateSkipped.push(...r.skipped);\n };\n for (const r of spotResults) collect(r);\n for (const r of chainResults) collect(r);\n for (const r of quoteResults) collect(r);\n for (const r of openInterestResults) collect(r);\n collect(vixContext);\n\n let status: IngestStatus;\n if (errors.length > 0) status = \"error\";\n else if (aggregateSkipped.length > 0) status = \"partial\";\n else status = \"ok\";\n\n return {\n status,\n perOperation: { spot: spotResults, chain: chainResults, quotes: quoteResults, openInterest: openInterestResults, vixContext },\n coverage,\n errors,\n ...(aggregateSkipped.length > 0 ? { skipped: aggregateSkipped } : {}),\n };\n }\n}\n","/**\n * Pure helpers for the option-data migration script.\n *\n * No IO, no DuckDB, no filesystem — these functions are the unit-test\n * target. The .mjs script that consumes them is not unit-testable due to\n * filesystem effects; these helpers carry the safety net.\n */\nimport type { TickerRegistry } from \"../market/tickers/registry.ts\";\n\n/** Hardcoded skip list for leveraged-ETF roots — verbatim list, audit-friendly. */\nexport const LEVERAGED_ETFS = new Set([\"SPXL\", \"SPXS\", \"SPXU\", \"SPXC\"]);\n\nexport interface GroupResult {\n byUnderlying: Map<string, string[]>; // underlying -> roots that resolve to it\n skipped: string[]; // leveraged-ETF roots that were dropped\n}\n\n/**\n * Group source roots by their resolved underlying. Roots in `skipSet` are\n * excluded and recorded in `skipped`. Insertion order of `byUnderlying[u]`\n * matches the input root order for that underlying.\n */\nexport function groupTickersByUnderlying(\n roots: string[],\n registry: TickerRegistry,\n skipSet: Set<string> = LEVERAGED_ETFS,\n): GroupResult {\n const byUnderlying = new Map<string, string[]>();\n const skipped: string[] = [];\n for (const root of roots) {\n if (skipSet.has(root)) {\n skipped.push(root);\n continue;\n }\n const underlying = registry.resolve(root);\n const existing = byUnderlying.get(underlying) ?? [];\n existing.push(root);\n byUnderlying.set(underlying, existing);\n }\n return { byUnderlying, skipped };\n}\n\n/**\n * Build the SELECT for option_chain rewrite. Uses `* EXCLUDE (underlying)`\n * because the underlying lives in the partition path post-migration, not in\n * a row-level column. sourceGlob is interpolated raw; caller must pass a\n * trusted, migrator-composed path.\n */\nexport function buildOptionChainSelectQuery(\n sourceGlob: string,\n underlying: string,\n): string {\n return `SELECT * EXCLUDE (underlying)\n FROM read_parquet('${sourceGlob}')\n WHERE underlying = '${underlying}'`;\n}\n\n/**\n * Build the SELECT for option_quote_minutes rewrite. NO EXCLUDE — the body\n * has no `underlying` column. Filters by root via regexp_extract.\n */\nexport function buildOptionQuoteSelectQuery(\n sourceGlob: string,\n roots: string[],\n): string {\n if (roots.length === 0) {\n throw new Error(\"buildOptionQuoteSelectQuery: roots must not be empty\");\n }\n const quoted = roots.map((r) => `'${r}'`).join(\", \");\n return `SELECT * FROM read_parquet('${sourceGlob}')\n WHERE regexp_extract(ticker, '^([A-Z]+)', 1) IN (${quoted})`;\n}\n","/**\n * sample-date-selector.ts — deterministic sample-date generator for\n * enrichment-rebuild verification.\n *\n * Produces a reproducible list of ~15–20 dates (sized for fast verification\n * runs across the supported tickers). The same seed always yields the same\n * sample so re-runs of the verification harness produce comparable drift\n * reports.\n *\n * Pattern: known-event dates + structural calendar-edge dates + N\n * pseudo-random weekday draws from a Mulberry32 PRNG.\n *\n * Pure module — no filesystem, no DuckDB, no provider imports. Safe to import\n * from unit tests, operator scripts, and verification harnesses alike.\n */\n\n/**\n * One sample-date entry. `category` tags its origin so the verification report\n * can group failures by kind (e.g., \"2 known_event failures, 0 random\").\n */\nexport interface SampleDate {\n date: string; // YYYY-MM-DD\n category: \"known_event\" | \"structural\" | \"random\";\n note?: string;\n}\n\n/**\n * PRNG seed. Pinned to a fixed integer so every invocation of\n * `selectVerificationSampleDates` with default args yields the committed\n * fixture.\n */\nexport const PHASE_5_FIXTURE_SEED = 20260418;\n\n/**\n * Known-event dates — always included in the sample regardless of seed.\n * Each is a real high-volatility or calendar-significant trading day used\n * to stress the enrichment math.\n */\nexport const PHASE_5_KNOWN_EVENTS: SampleDate[] = [\n { date: \"2024-08-05\", category: \"known_event\", note: \"VIX spike, ~65% gap, Japan carry unwind\" },\n { date: \"2025-04-08\", category: \"known_event\", note: \"Tariff shock, VIX Spike 30%+, SPX down\" },\n { date: \"2024-12-18\", category: \"known_event\", note: \"FOMC decision day, VIX Spike 80%+\" },\n { date: \"2024-11-15\", category: \"known_event\", note: \"November OPEX Friday\" },\n { date: \"2024-03-28\", category: \"known_event\", note: \"Q1 end-of-quarter roll\" },\n { date: \"2024-09-18\", category: \"known_event\", note: \"FOMC — 50bps cut, trend reversal\" },\n];\n\n/**\n * Structural dates — calendar-edge dates always included to exercise first-day,\n * mid-year, and holiday-adjacent enrichment paths.\n */\nexport const PHASE_5_STRUCTURAL_DATES: SampleDate[] = [\n { date: \"2022-01-03\", category: \"structural\", note: \"First trading day of 2022 (earliest data)\" },\n { date: \"2024-07-03\", category: \"structural\", note: \"Day before July 4 (early close)\" },\n { date: \"2024-11-27\", category: \"structural\", note: \"Day before Thanksgiving\" },\n];\n\n/**\n * Mulberry32 PRNG — deterministic, 32-bit state. Small, fast, and repeatable\n * across Node versions. Reference: https://github.com/bryc/code/blob/master/jshash/PRNGs.md#mulberry32\n */\nfunction mulberry32(seed: number): () => number {\n let state = seed >>> 0;\n return () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let t = state;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Enumerate weekday dates (Mon–Fri) in [fromDate, toDate] inclusive.\n * UTC-safe — iterates via Date arithmetic with noon-UTC anchors to avoid DST\n * edge cases.\n */\nfunction enumerateWeekdays(fromDate: string, toDate: string): string[] {\n const out: string[] = [];\n const d = new Date(fromDate + \"T12:00:00Z\");\n const end = new Date(toDate + \"T12:00:00Z\");\n while (d <= end) {\n const dow = d.getUTCDay();\n if (dow !== 0 && dow !== 6) {\n out.push(d.toISOString().slice(0, 10));\n }\n d.setUTCDate(d.getUTCDate() + 1);\n }\n return out;\n}\n\n/**\n * Deterministic sample-date selector.\n *\n * Returns a merged and date-sorted list of:\n * - All PHASE_5_KNOWN_EVENTS (6 entries)\n * - All PHASE_5_STRUCTURAL_DATES (3 entries)\n * - `randomCount` pseudo-random weekdays from [fromDate, toDate] that are\n * NOT already in the known/structural sets\n *\n * Same `(fromDate, toDate, seed, randomCount)` → same output, always.\n *\n * @param fromDate - inclusive lower bound, default 2022-01-01\n * @param toDate - inclusive upper bound, typically \"yesterday\" at call time\n * @param seed - Mulberry32 PRNG seed, default PHASE_5_FIXTURE_SEED\n * @param randomCount - number of random dates to draw (default 9 → total ~18)\n */\nexport function selectVerificationSampleDates(\n fromDate: string = \"2022-01-01\",\n toDate: string = new Date().toISOString().slice(0, 10),\n seed: number = PHASE_5_FIXTURE_SEED,\n randomCount: number = 9,\n): SampleDate[] {\n const prng = mulberry32(seed);\n\n // Build candidate pool: weekdays in [fromDate, toDate] minus already-selected dates.\n const selectedSet = new Set<string>([\n ...PHASE_5_KNOWN_EVENTS.map((s) => s.date),\n ...PHASE_5_STRUCTURAL_DATES.map((s) => s.date),\n ]);\n const candidates = enumerateWeekdays(fromDate, toDate).filter(\n (d) => !selectedSet.has(d),\n );\n\n const random: SampleDate[] = [];\n const pool = [...candidates];\n for (let i = 0; i < randomCount && pool.length > 0; i++) {\n const idx = Math.floor(prng() * pool.length);\n random.push({ date: pool.splice(idx, 1)[0], category: \"random\" });\n }\n\n return [...PHASE_5_KNOWN_EVENTS, ...PHASE_5_STRUCTURAL_DATES, ...random].sort(\n (a, b) => a.date.localeCompare(b.date),\n );\n}\n","/**\n * enrichment-verification.ts — pure diff helper for enrichment-rebuild\n * verification.\n *\n * Compares two enriched rows (old from the legacy\n * `daily.parquet` / `date_context` files, new from the rebuilt\n * `market.enriched` / `market.enriched_context` views) with per-field\n * tolerance rules and returns a structured diff — not a boolean. The\n * verification harness consumes the diff, aggregates across ~15–20 sample\n * dates per ticker, and emits a report (markdown + JSON).\n *\n * Pure module — no filesystem, no DuckDB, no provider imports.\n *\n * Tolerance rules:\n * - DOUBLE: `|a - b| <= 1e-9` (boundary INCLUSIVE); NaN-vs-NaN passes;\n * NaN-vs-non-NaN fails.\n * - INTEGER: strict `Number(a) === Number(b)`.\n * - VARCHAR: case-sensitive `String(a) === String(b)`.\n * - null vs null / undefined vs undefined → pass.\n * - null vs value (or undefined vs value) → fail.\n *\n * Failure aggregation: `compareRow(...).anyFailure === true` whenever any\n * field in the row failed its tolerance test. The deletion of the legacy\n * enriched files is gated on every compared row returning\n * `anyFailure === false`.\n */\n\n/**\n * Tolerance epsilon for DOUBLE fields. Locked at 1e-9 — the enrichment\n * math is deterministic on identical OHLCV input, so anything above machine\n * precision is a real semantic change.\n */\nexport const DOUBLE_EPSILON = 1e-9;\n\n/** One of the three field-type dispatch categories. */\nexport type FieldType = \"double\" | \"integer\" | \"varchar\";\n\n/**\n * Per-field type classification for rows materialized from\n * `market.enriched` (the ticker-first enriched store).\n *\n * Source: market-enricher.ts::DAILY_ENRICHMENT_COLUMNS + the tolerance-rule\n * classification documented in the file header.\n *\n * INTEGER fields (exact match): Gap_Filled, Consecutive_Days, High_Before_Low,\n * Reversal_Type, Day_of_Week, Month, Is_Opex.\n * DOUBLE fields (1e-9 epsilon): everything else.\n *\n * NOTE: Gap_Filled and Reversal_Type can be null on no-gap / no-reversal days;\n * the compare helper handles null-vs-null as pass.\n */\nexport const ENRICHED_FIELD_TYPES: Record<string, FieldType> = {\n // Tier 1 doubles\n Prior_Close: \"double\",\n Gap_Pct: \"double\",\n ATR_Pct: \"double\",\n RSI_14: \"double\",\n Price_vs_EMA21_Pct: \"double\",\n Price_vs_SMA50_Pct: \"double\",\n Realized_Vol_5D: \"double\",\n Realized_Vol_20D: \"double\",\n Return_5D: \"double\",\n Return_20D: \"double\",\n Intraday_Range_Pct: \"double\",\n Intraday_Return_Pct: \"double\",\n Close_Position_In_Range: \"double\",\n Prev_Return_Pct: \"double\",\n Prior_Range_vs_ATR: \"double\",\n // Tier 3 intraday timing (doubles)\n High_Time: \"double\",\n Low_Time: \"double\",\n Opening_Drive_Strength: \"double\",\n Intraday_Realized_Vol: \"double\",\n // IVR / IVP\n ivr: \"double\",\n ivp: \"double\",\n // Context-bleed fields that may appear on ticker-scoped rows in some layouts\n VIX_Spike_Pct: \"double\",\n VIX_Gap_Pct: \"double\",\n // Integer fields\n Gap_Filled: \"integer\",\n Consecutive_Days: \"integer\",\n High_Before_Low: \"integer\",\n Reversal_Type: \"integer\",\n Day_of_Week: \"integer\",\n Month: \"integer\",\n Is_Opex: \"integer\",\n};\n\n/**\n * Per-field type classification for rows materialized from\n * `market.enriched_context` (the global cross-ticker context view).\n *\n * Vol_Regime / Term_Structure_State are integers (classification codes per\n * classifyVolRegime / classifyTermStructure).\n * Trend_Direction is a varchar (case-sensitive).\n * VIX_Spike_Pct / VIX_Gap_Pct are doubles.\n */\nexport const CONTEXT_FIELD_TYPES: Record<string, FieldType> = {\n Vol_Regime: \"integer\",\n Term_Structure_State: \"integer\",\n Trend_Direction: \"varchar\",\n VIX_Spike_Pct: \"double\",\n VIX_Gap_Pct: \"double\",\n};\n\n/**\n * Structured per-field diff result.\n *\n * `delta` is only populated for DOUBLE fields (where `|a - b|` is meaningful).\n * `passed` is the single source of truth for gate decisions — downstream code\n * should never re-derive pass/fail from raw `oldValue`/`newValue`.\n */\nexport interface FieldDiff {\n field: string;\n type: FieldType;\n oldValue: unknown;\n newValue: unknown;\n passed: boolean;\n delta?: number;\n}\n\n/**\n * Row-level diff with a precomputed `anyFailure` flag for fast aggregation\n * across many sample rows — any failure blocks deletion of the legacy\n * enriched files.\n */\nexport interface RowDiff {\n ticker: string;\n date: string;\n kind: \"enriched\" | \"context\";\n fields: FieldDiff[];\n anyFailure: boolean;\n}\n\n/** null or undefined — the pair both-missing is always a PASS. */\nfunction isNullish(v: unknown): boolean {\n return v === null || v === undefined;\n}\n\n/**\n * Compare the fields of two rows per a `fieldTypes` dispatch map.\n *\n * Iterates `Object.entries(fieldTypes)` so the returned array length always\n * equals `Object.keys(fieldTypes).length`. Extra keys in the input rows are\n * ignored; missing keys in the rows surface as `oldValue === undefined`\n * (null-vs-null pass if both are missing, fail if only one is missing).\n */\nexport function compareFields(\n oldRow: Record<string, unknown>,\n newRow: Record<string, unknown>,\n fieldTypes: Record<string, FieldType>,\n): FieldDiff[] {\n return Object.entries(fieldTypes).map(([field, type]) => {\n const oldValue = oldRow[field];\n const newValue = newRow[field];\n\n const oldNull = isNullish(oldValue);\n const newNull = isNullish(newValue);\n if (oldNull && newNull) {\n return { field, type, oldValue, newValue, passed: true };\n }\n if (oldNull !== newNull) {\n return { field, type, oldValue, newValue, passed: false };\n }\n\n if (type === \"double\") {\n const oldNum = Number(oldValue);\n const newNum = Number(newValue);\n const oldNaN = Number.isNaN(oldNum);\n const newNaN = Number.isNaN(newNum);\n if (oldNaN && newNaN) {\n return { field, type, oldValue, newValue, passed: true };\n }\n if (oldNaN !== newNaN) {\n return { field, type, oldValue, newValue, passed: false };\n }\n const delta = Math.abs(oldNum - newNum);\n return {\n field,\n type,\n oldValue,\n newValue,\n passed: delta <= DOUBLE_EPSILON,\n delta,\n };\n }\n\n if (type === \"integer\") {\n return {\n field,\n type,\n oldValue,\n newValue,\n passed: Number(oldValue) === Number(newValue),\n };\n }\n\n // varchar\n return {\n field,\n type,\n oldValue,\n newValue,\n passed: String(oldValue) === String(newValue),\n };\n });\n}\n\n/**\n * Compare two rows using the field-type dispatch map that matches `kind`.\n *\n * `kind: 'enriched'` uses `ENRICHED_FIELD_TYPES` (per-ticker enriched row).\n * `kind: 'context'` uses `CONTEXT_FIELD_TYPES` (global cross-ticker context row).\n *\n * The resulting `RowDiff.anyFailure` is the aggregation signal the deletion\n * gate reads — a single `true` anywhere in the sample blocks deletion of\n * the legacy enriched files.\n */\nexport function compareRow(\n oldRow: Record<string, unknown>,\n newRow: Record<string, unknown>,\n kind: \"enriched\" | \"context\",\n ticker: string,\n date: string,\n): RowDiff {\n const types = kind === \"enriched\" ? ENRICHED_FIELD_TYPES : CONTEXT_FIELD_TYPES;\n const fields = compareFields(oldRow, newRow, types);\n return {\n ticker,\n date,\n kind,\n fields,\n anyFailure: fields.some((f) => !f.passed),\n };\n}\n","/**\n * calibration-probe.ts — operator helper that de-risks provider-refetch drift\n * against the 1e-9 tolerance used by the enrichment verifier.\n *\n * Invoke before starting a spot-bar backfill. The probe fetches minute bars\n * for a set of sample dates from the active provider and compares their close\n * prices against whatever is already in `market.spot` for the same\n * (ticker, date). The operator reads the returned `maxCloseDelta` and decides:\n *\n * maxCloseDelta < 1e-6 → proceed; 1e-9 tolerance is achievable.\n * 1e-6 ≤ delta < 1e-3 → proceed only with an explicit tolerance bump\n * or a documented baseline shift.\n * maxCloseDelta ≥ 1e-3 → escalate; the tolerance is unachievable\n * without a design change.\n *\n * Manual-only — purposefully side-effectful (opens a live DuckDB, calls the\n * singleton provider). No unit test; exists for operator inspection.\n */\nimport * as path from \"path\";\nimport { DuckDBInstance } from \"@duckdb/node-api\";\nimport { getProvider } from \"./market-provider.ts\";\n\n/** Per-date probe result returned alongside the summary deltas. */\nexport interface CalibrationProbeDateResult {\n date: string;\n /** Max |new - old| close observed for this date across all aligned bars. */\n refetchDelta: number;\n /** How many minute bars from the new fetch aligned to an existing row. */\n matchedBars: number;\n}\n\n/** Summary returned by `calibrateProviderFetch`. */\nexport interface CalibrationProbeResult {\n /** Mean of the aligned per-bar close deltas across ALL dates. */\n avgCloseDelta: number;\n /** Max of the aligned per-bar close deltas across ALL dates. */\n maxCloseDelta: number;\n /** Per-date detail so the operator can spot-check specific trading days. */\n dateResults: CalibrationProbeDateResult[];\n}\n\n/**\n * Compare provider-refetched minute bars against whatever is in\n * `market.spot` for the same (ticker, date). Read-only against DuckDB.\n *\n * Opens its own DuckDB connection at `${dataRoot}/database/market.duckdb` so\n * it can run alongside a stopped MCP server. Does NOT write or modify either\n * the database or the provider cache.\n *\n * @param ticker Plain ticker (\"SPX\", \"VIX\"). No OCC contracts (probe is a spot\n * sanity check).\n * @param probeDates Array of \"YYYY-MM-DD\" — 3–7 mid-2024 dates are sufficient.\n * @param dataRoot Absolute or home-relative data root, e.g. \"~/tradeblocks-data\".\n * The probe resolves the DuckDB file at `${dataRoot}/database/market.duckdb`.\n */\nexport async function calibrateProviderFetch(\n ticker: string,\n probeDates: string[],\n dataRoot: string,\n): Promise<CalibrationProbeResult> {\n const provider = getProvider();\n const dbPath = path.join(dataRoot, \"database\", \"market.duckdb\");\n const instance = await DuckDBInstance.create(dbPath);\n const conn = await instance.connect();\n\n const deltas: number[] = [];\n const dateResults: CalibrationProbeDateResult[] = [];\n\n try {\n for (const date of probeDates) {\n const newBars = await provider.fetchBars({\n ticker,\n from: date,\n to: date,\n timespan: \"minute\",\n multiplier: 1,\n assetClass: \"index\",\n });\n\n // Canonical minute-bar view is market.spot — same schema as the\n // earlier intraday view it replaced.\n const oldReader = await conn.runAndReadAll(\n `SELECT time, close FROM market.spot WHERE ticker = $1 AND date = $2 ORDER BY time`,\n [ticker, date],\n );\n const oldRows = oldReader.getRows();\n const oldByTime = new Map<string, number>();\n for (const r of oldRows) {\n const t = String(r[0]);\n const c = Number(r[1]);\n if (Number.isFinite(c)) oldByTime.set(t, c);\n }\n\n let maxDelta = 0;\n let matched = 0;\n for (const bar of newBars) {\n if (!bar.time) continue;\n const oldClose = oldByTime.get(bar.time);\n if (oldClose === undefined) continue;\n const delta = Math.abs(bar.close - oldClose);\n if (delta > maxDelta) maxDelta = delta;\n deltas.push(delta);\n matched++;\n }\n dateResults.push({ date, refetchDelta: maxDelta, matchedBars: matched });\n }\n } finally {\n try {\n conn.closeSync();\n } catch {\n /* best-effort */\n }\n try {\n instance.closeSync();\n } catch {\n /* best-effort */\n }\n }\n\n const avgCloseDelta =\n deltas.length > 0 ? deltas.reduce((a, b) => a + b, 0) / deltas.length : 0;\n const maxCloseDelta = deltas.length > 0 ? Math.max(...deltas) : 0;\n return { avgCloseDelta, maxCloseDelta, dateResults };\n}\n"],"mappings":"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","names":["sql","tradingDays","open","params","BATCH_SIZE","tier2Targets","tier2Result","priorClose","z","getNum","getRaw","z","z","formatTradeDate","getTradeLookupKey","uniqueTradeLookupKeys","resultToRecords","recordsByTickerDate","getNum","VOL_REGIME_LABELS","z","z","z","REVERSE_ROOT_MAP","z","z","h1","m1","h2","m2","markPrice","z","z","z","triggerTypeEnum","z","triggerConfigSchema","path","z","join","path","z","z","existsSync","path","existsSync","readdirSync","path","escapeSqlLiteral","existsSync","existsSync","path","lit","existsSync","existsSync","path","lit","existsSync","sql","reader","names","existsSync","path","escapeSqlLiteral","existsSync","sql","reader","existsSync","path","existsSync","escapeSqlLiteral","parseQuoteRow","existsSync","path","existsSync","spot","enriched","chain","quote","z","path","lit","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/trading-dates.ts","../src/tools/shared/filters.ts","../src/utils/schema-metadata.ts","../src/utils/field-timing.ts","../src/utils/data-availability.ts","../src/utils/data-quality.ts","../src/utils/market-enricher.ts","../src/utils/market-importer.ts","../src/utils/analysis-stats.ts","../src/utils/filter-predicates.ts","../src/tools/profiles.ts","../src/utils/output-formatter.ts","../src/tools/profile-analysis.ts","../src/tools/regime-advisor.ts","../src/utils/provider-capabilities.ts","../src/tools/replay.ts","../src/market/tickers/resolver.ts","../src/tools/snapshot.ts","../src/utils/greeks-decomposition.ts","../src/utils/exit-triggers.ts","../src/tools/exit-analysis.ts","../src/utils/batch-exit-analysis.ts","../src/tools/batch-exit-analysis.ts","../src/utils/quote-enricher.ts","../src/tools/sql.ts","../src/utils/chain-loader.ts","../src/utils/providers/thetadata/backfill.ts","../src/tools/greeks-attribution.ts","../src/utils/flatfile-importer.ts","../src/market/stores/spot-store.ts","../src/market/stores/coverage.ts","../src/market/stores/enriched-store.ts","../src/market/stores/chain-sql.ts","../src/market/stores/chain-store.ts","../src/market/stores/quote-store.ts","../src/market/stores/parquet-spot-store.ts","../src/market/stores/spot-sql.ts","../src/market/stores/duckdb-spot-store.ts","../src/market/stores/parquet-enriched-store.ts","../src/market/stores/rth-aggregation.ts","../src/market/stores/enriched-sql.ts","../src/market/stores/duckdb-enriched-store.ts","../src/market/stores/parquet-chain-store.ts","../src/market/stores/duckdb-chain-store.ts","../src/market/stores/parquet-quote-store.ts","../src/market/stores/duckdb-quote-store.ts","../src/market/stores/parquet-oi-daily-store.ts","../src/market/stores/index.ts","../src/market/tickers/schemas.ts","../src/market/tickers/registry.ts","../src/market/tickers/loader.ts","../src/market/tickers/defaults.json","../src/tools/tickers.ts","../src/market/stores/quote-sql.ts","../src/market/ingestor/market-ingestor.ts","../src/utils/migrate-option-data-helpers.ts","../src/utils/sample-date-selector.ts","../src/utils/enrichment-verification.ts","../src/utils/calibration-probe.ts"],"sourcesContent":["/**\n * Compute yesterday's calendar date in America/New_York (ET) as YYYY-MM-DD.\n *\n * Used by the daily market-data refresh script: even though the homelab runs\n * in America/Chicago, the script must always target ET trading dates because\n * that's what ThetaData and the market data partitions are keyed on.\n *\n * Implementation: format `now` in ET to extract today's ET calendar date,\n * then subtract one day via UTC arithmetic on a date constructed from those\n * Y/M/D components. Subtracting via UTC avoids local-timezone DST drift on\n * the host because we never round-trip the result back through a local TZ.\n *\n * @param now Reference timestamp; defaults to current wall clock.\n */\nexport function yesterdayET(now: Date = new Date()): string {\n // en-CA gives \"YYYY-MM-DD\" formatting directly.\n const todayET = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: \"America/New_York\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(now);\n\n const [y, m, d] = todayET.split(\"-\").map(Number);\n // Subtract one day via UTC arithmetic; setUTCDate(0) on the first of the\n // month rolls back to the last day of the prior month, etc.\n const prior = new Date(Date.UTC(y, m - 1, d));\n prior.setUTCDate(prior.getUTCDate() - 1);\n\n const py = prior.getUTCFullYear();\n const pm = String(prior.getUTCMonth() + 1).padStart(2, \"0\");\n const pd = String(prior.getUTCDate()).padStart(2, \"0\");\n return `${py}-${pm}-${pd}`;\n}\n","/**\n * Shared Filter Utilities\n *\n * Common filtering functions used across block and report tools.\n */\n\nimport type { Trade, DailyLogEntry } from \"@tradeblocks/lib\";\n\n/**\n * Filter trades by strategy name (case-insensitive)\n */\nexport function filterByStrategy(trades: Trade[], strategy?: string): Trade[] {\n if (!strategy) return trades;\n return trades.filter(\n (t) => t.strategy.toLowerCase() === strategy.toLowerCase()\n );\n}\n\n/**\n * Validate that a date string is in YYYY-MM-DD format.\n * Returns the string if valid, undefined if not (skips that filter boundary).\n */\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\nfunction validateDateParam(date: string | undefined): string | undefined {\n if (!date) return undefined;\n return DATE_RE.test(date) ? date : undefined;\n}\n\n/**\n * Extract YYYY-MM-DD calendar date from a Date or string.\n * Trades are parsed via parseDatePreservingCalendarDay() which creates dates at\n * local midnight. Use local date components to preserve the calendar date,\n * avoiding timezone shift when the server runs in UTC.\n */\nfunction toCalendarDateStr(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Filter trades by date range using string comparison on Eastern Time calendar dates.\n * Avoids timezone bugs from mixing UTC Date parsing with local time setHours.\n * Malformed date inputs (not YYYY-MM-DD) are silently ignored.\n */\nexport function filterByDateRange(\n trades: Trade[],\n startDate?: string,\n endDate?: string\n): Trade[] {\n const start = validateDateParam(startDate);\n const end = validateDateParam(endDate);\n let filtered = trades;\n\n if (start) {\n filtered = filtered.filter((t) => toCalendarDateStr(t.dateOpened) >= start);\n }\n\n if (end) {\n filtered = filtered.filter((t) => toCalendarDateStr(t.dateOpened) <= end);\n }\n\n return filtered;\n}\n\n/**\n * Filter daily log entries by date range using string comparison on calendar dates.\n * Mirrors filterByDateRange but uses entry.date (Date object) instead of t.dateOpened.\n * Malformed date inputs (not YYYY-MM-DD) are silently ignored.\n */\nexport function filterDailyLogsByDateRange(\n dailyLogs: DailyLogEntry[],\n startDate?: string,\n endDate?: string\n): DailyLogEntry[] {\n const start = validateDateParam(startDate);\n const end = validateDateParam(endDate);\n let filtered = dailyLogs;\n\n if (start) {\n filtered = filtered.filter((entry) => toCalendarDateStr(entry.date) >= start);\n }\n\n if (end) {\n filtered = filtered.filter((entry) => toCalendarDateStr(entry.date) <= end);\n }\n\n return filtered;\n}\n","/**\n * Schema Metadata\n *\n * Hardcoded descriptions for DuckDB tables and columns, plus example queries.\n * Used by describe_database tool to provide context for SQL query writing.\n *\n * Tables are organized by schema:\n * - trades: Trade data from CSV files\n * - market: Canonical market datasets post-v3.0 —\n * * spot (raw minute bars, ticker-first)\n * * spot_daily (view-backed RTH-aggregated daily OHLCV)\n * * enriched (per-ticker computed indicators + ivr/ivp — NO OHLCV)\n * * enriched_context (cross-ticker derived fields: Vol_Regime, Term_Structure_State)\n * * option_chain / option_quote_minutes (option contract universe + quote cache)\n * OHLCV callers must LEFT JOIN market.spot_daily on ticker+date; enriched alone does not carry open/high/low/close.\n */\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\nexport interface ColumnDescription {\n /** Human-readable description of what this column contains */\n description: string;\n /** True if this column is useful for hypothesis testing (filtering, grouping, analysis) */\n hypothesis: boolean;\n /** When this field's value is known relative to market open.\n * - 'open': Known at/before market open (Prior_Close, Gap_Pct, VIX_Open, etc.)\n * - 'close': Only known after market close (RSI_14, Vol_Regime, Close, etc.)\n * - 'static': Calendar/metadata facts known before the day (Day_of_Week, Month, Is_Opex)\n * Only applicable to market.enriched and market.enriched_context columns. Omit for non-market tables.\n */\n timing?: 'open' | 'close' | 'static';\n}\n\nexport interface TableDescription {\n /** Human-readable description of this table's purpose */\n description: string;\n /** Key columns that are most important for analysis */\n keyColumns: string[];\n /** Column descriptions by column name */\n columns: Record<string, ColumnDescription>;\n}\n\nexport interface SchemaDescription {\n /** Human-readable description of this schema's purpose */\n description: string;\n /** Tables in this schema */\n tables: Record<string, TableDescription>;\n}\n\nexport interface SchemaMetadata {\n trades: SchemaDescription;\n market: SchemaDescription;\n}\n\nexport interface ExampleQuery {\n /** What this query does */\n description: string;\n /** The SQL query */\n sql: string;\n}\n\nexport interface ExampleQueries {\n /** Basic single-table queries */\n basic: ExampleQuery[];\n /** JOIN queries between trades and market data */\n joins: ExampleQuery[];\n /** Hypothesis testing patterns */\n hypothesis: ExampleQuery[];\n}\n\n// ============================================================================\n// Schema Descriptions\n// ============================================================================\n\nexport const SCHEMA_DESCRIPTIONS: SchemaMetadata = {\n trades: {\n description:\n \"Trading data synced from CSV files. Contains trade records from all portfolio blocks, including both backtest (trade_data) and actual/reported (reporting_data) trades.\",\n tables: {\n trade_data: {\n description:\n \"Individual backtest trade records. Each row = one trade with entry/exit details, P&L, and strategy. Filter by block_id to query specific portfolios.\",\n keyColumns: [\"block_id\", \"date_opened\", \"strategy\", \"pl\"],\n columns: {\n block_id: {\n description: \"Portfolio block ID - filter by this to query specific portfolios\",\n hypothesis: true,\n },\n date_opened: {\n description: \"Trade entry date (DATE format, use for joins with market data)\",\n hypothesis: true,\n },\n time_opened: {\n description: \"Trade entry time in Eastern Time (e.g., '09:35:00')\",\n hypothesis: false,\n },\n strategy: {\n description: \"Strategy name (e.g., 'IronCondor', 'PutSpread')\",\n hypothesis: true,\n },\n legs: {\n description: \"Option legs description (e.g., 'SPY 450P/445P')\",\n hypothesis: false,\n },\n premium: {\n description: \"Credit received (+) or debit paid (-)\",\n hypothesis: false,\n },\n num_contracts: {\n description: \"Number of contracts traded\",\n hypothesis: false,\n },\n pl: {\n description: \"Gross P&L before commissions (DOUBLE)\",\n hypothesis: true,\n },\n date_closed: {\n description: \"Trade exit date (NULL if still open)\",\n hypothesis: false,\n },\n time_closed: {\n description: \"Trade exit time in Eastern Time\",\n hypothesis: false,\n },\n reason_for_close: {\n description: \"Exit reason (e.g., 'Target', 'Stop', 'Expiration')\",\n hypothesis: true,\n },\n margin_req: {\n description: \"Margin requirement for the position ($)\",\n hypothesis: false,\n },\n opening_commissions: {\n description: \"Commissions paid at entry ($)\",\n hypothesis: false,\n },\n closing_commissions: {\n description: \"Commissions paid at exit ($)\",\n hypothesis: false,\n },\n },\n },\n reporting_data: {\n description:\n \"Actual/reported trade records from reportinglog.csv. Each row = one live trade executed. Compare with trade_data (backtest) to analyze slippage and execution differences. Filter by block_id to query specific portfolios.\",\n keyColumns: [\"block_id\", \"date_opened\", \"strategy\", \"legs\", \"pl\"],\n columns: {\n block_id: {\n description: \"Portfolio block ID - filter by this to query specific portfolios\",\n hypothesis: true,\n },\n date_opened: {\n description: \"Trade entry date (DATE format, use for joins with market data)\",\n hypothesis: true,\n },\n time_opened: {\n description: \"Trade entry time in Eastern Time (e.g., '09:35:00')\",\n hypothesis: false,\n },\n strategy: {\n description: \"Strategy name (e.g., 'IronCondor', 'PutSpread')\",\n hypothesis: true,\n },\n legs: {\n description: \"Option legs description with strikes (e.g., 'SPY 450P/445P') - compare with trade_data.legs to identify strike differences\",\n hypothesis: true,\n },\n initial_premium: {\n description: \"Credit received (+) or debit paid (-) at entry\",\n hypothesis: false,\n },\n num_contracts: {\n description: \"Number of contracts traded (often fewer than backtest)\",\n hypothesis: false,\n },\n pl: {\n description: \"Actual P&L realized (DOUBLE)\",\n hypothesis: true,\n },\n date_closed: {\n description: \"Trade exit date (NULL if still open)\",\n hypothesis: false,\n },\n time_closed: {\n description: \"Trade exit time in Eastern Time\",\n hypothesis: false,\n },\n closing_price: {\n description: \"Price at exit\",\n hypothesis: false,\n },\n avg_closing_cost: {\n description: \"Average cost to close the position\",\n hypothesis: false,\n },\n reason_for_close: {\n description: \"Exit reason (e.g., 'Target', 'Stop', 'Expiration')\",\n hypothesis: true,\n },\n opening_price: {\n description: \"Price at entry\",\n hypothesis: false,\n },\n },\n },\n },\n },\n market: {\n description:\n \"Canonical market data for hypothesis testing (v3.0 layout). Normalized into six datasets: spot (raw minute bars, ticker-first), spot_daily (view-backed RTH-aggregated daily OHLCV derived from market.spot), enriched (per-ticker computed Tier 1 indicators + ivr/ivp for VIX-family tickers; NO OHLCV), enriched_context (cross-ticker derived fields like Vol_Regime), option_chain (contract universe by date), and option_quote_minutes (dense option quote cache by minute, including persisted minute greeks when provider or computed fallback data is available). OHLCV-using queries must LEFT JOIN market.spot_daily on ticker+date because market.enriched does not carry open/high/low/close. Source: market/ Parquet files and provider imports.\",\n tables: {\n enriched: {\n description:\n \"Per-ticker computed enrichment indicators and calendar fields. One row per ticker per trading day. OHLCV is NOT stored here — LEFT JOIN market.spot_daily on ticker+date for open/high/low/close/bid/ask. JOIN with trades on ticker+date (e.g., d.ticker = 'SPX' AND t.date_opened = d.date). VIX-family tickers (VIX, VIX9D, VIX3M, etc.) also have ivr/ivp columns populated. For trade-entry queries, use LAG() on close-derived fields. Join market.enriched_context (LEFT JOIN on date) for Vol_Regime, Term_Structure_State, etc.\",\n keyColumns: [\"ticker\", \"date\", \"RSI_14\", \"ATR_Pct\", \"Realized_Vol_20D\"],\n columns: {\n ticker: {\n description: \"Underlying ticker symbol (part of composite primary key with date).\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Composite primary key with ticker.\",\n hypothesis: true,\n },\n // Raw OHLCV\n open: {\n description: \"Underlying open price\",\n hypothesis: false,\n timing: 'open',\n },\n high: {\n description: \"Underlying high price\",\n hypothesis: false,\n timing: 'close',\n },\n low: {\n description: \"Underlying low price\",\n hypothesis: false,\n timing: 'close',\n },\n close: {\n description: \"Underlying close price\",\n hypothesis: false,\n timing: 'close',\n },\n Prior_Close: {\n description: \"Previous day's close price\",\n hypothesis: false,\n timing: 'open',\n },\n // Tier 1 enrichment — open-known\n Gap_Pct: {\n description: \"Overnight gap percentage ((Open - Prior_Close) / Prior_Close * 100)\",\n hypothesis: true,\n timing: 'open',\n },\n Prev_Return_Pct: {\n description: \"Previous day's total return percentage (prior close to prior close)\",\n hypothesis: true,\n timing: 'open',\n },\n Prior_Range_vs_ATR: {\n description: \"Prior trading day's (high - low) / ATR ratio, measures prior day's range relative to average true range\",\n hypothesis: true,\n timing: 'open',\n },\n // Tier 1 enrichment — close-derived\n ATR_Pct: {\n description: \"Average True Range as percentage of price (14-day Wilder smoothing)\",\n hypothesis: true,\n timing: 'close',\n },\n RSI_14: {\n description: \"14-day RSI (0-100, >70 overbought, <30 oversold)\",\n hypothesis: true,\n timing: 'close',\n },\n Price_vs_EMA21_Pct: {\n description: \"Price vs 21-day EMA as percentage ((close - EMA21) / EMA21 * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Price_vs_SMA50_Pct: {\n description: \"Price vs 50-day SMA as percentage ((close - SMA50) / SMA50 * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Realized_Vol_5D: {\n description: \"5-day realized volatility (annualized standard deviation of log returns)\",\n hypothesis: true,\n timing: 'close',\n },\n Realized_Vol_20D: {\n description: \"20-day realized volatility (annualized standard deviation of log returns)\",\n hypothesis: true,\n timing: 'close',\n },\n Return_5D: {\n description: \"5-day cumulative return percentage\",\n hypothesis: true,\n timing: 'close',\n },\n Return_20D: {\n description: \"20-day cumulative return percentage\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Range_Pct: {\n description: \"Intraday range as percentage ((High - Low) / Open * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Return_Pct: {\n description: \"Open to close return percentage ((Close - Open) / Open * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Close_Position_In_Range: {\n description: \"Where close is in day's range (0 = low, 1 = high)\",\n hypothesis: true,\n timing: 'close',\n },\n Gap_Filled: {\n description: \"Whether overnight gap was filled (1 = yes, 0 = no)\",\n hypothesis: true,\n timing: 'close',\n },\n Consecutive_Days: {\n description: \"Consecutive up/down days (positive=up, negative=down)\",\n hypothesis: true,\n timing: 'close',\n },\n // Tier 3 intraday timing (columns exist in schema, enrichment deferred)\n High_Time: {\n description: \"Time of day high as decimal hours (e.g., 10.5 = 10:30 AM ET)\",\n hypothesis: true,\n timing: 'close',\n },\n Low_Time: {\n description: \"Time of day low as decimal hours (e.g., 14.25 = 2:15 PM ET)\",\n hypothesis: true,\n timing: 'close',\n },\n High_Before_Low: {\n description: \"Did high occur before low? (1=yes, 0=no)\",\n hypothesis: true,\n timing: 'close',\n },\n Reversal_Type: {\n description: \"Reversal pattern type (1=morning reversal up, -1=morning reversal down, 0=trend day)\",\n hypothesis: true,\n timing: 'close',\n },\n Opening_Drive_Strength: {\n description: \"First-30-min range / full-day range ratio (0-1); higher = strong opening drive\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Realized_Vol: {\n description: \"Annualized realized volatility from intraday bar returns (decimal, e.g., 0.15 = 15%)\",\n hypothesis: true,\n timing: 'close',\n },\n // Calendar fields — static\n Day_of_Week: {\n description: \"Day of week (2=Monday through 6=Friday)\",\n hypothesis: true,\n timing: 'static',\n },\n Month: {\n description: \"Month number (1-12)\",\n hypothesis: true,\n timing: 'static',\n },\n Is_Opex: {\n description: \"Options expiration day flag (1=opex, 0=not)\",\n hypothesis: true,\n timing: 'static',\n },\n // VIX-family ticker IVR/IVP (populated for VIX, VIX9D, VIX3M, etc.)\n ivr: {\n description: \"Implied Volatility Rank (252-day): where current close sits in range (0=min, 100=max). Populated for VIX-family tickers only.\",\n hypothesis: true,\n timing: 'close',\n },\n ivp: {\n description: \"Implied Volatility Percentile (252-day): percentage of prior 251 trading days where close was at or below current level (0-100). Populated for VIX-family tickers only.\",\n hypothesis: true,\n timing: 'close',\n },\n },\n },\n enriched_context: {\n description:\n \"Cross-ticker derived market context fields per trading day. Contains Vol_Regime, Term_Structure_State, and other fields derived from multiple VIX tickers. JOIN with market.enriched on date. VIX IVR/IVP live in market.enriched (ticker='VIX', 'VIX9D', etc.); VIX OHLCV lives in market.spot_daily.\",\n keyColumns: [\"date\", \"Vol_Regime\", \"Term_Structure_State\"],\n columns: {\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Primary key.\",\n hypothesis: true,\n },\n Vol_Regime: {\n description: \"Volatility regime classification based on VIX close (1=very low <10, 2=low 10-15, 3=normal 15-20, 4=elevated 20-25, 5=high 25-30, 6=extreme >30)\",\n hypothesis: true,\n timing: 'close',\n },\n Term_Structure_State: {\n description: \"VIX term structure state based on VIX9D/VIX ratio (-1=backwardation/inverted, 0=flat, 1=contango/normal). NULL when VIX9D data is absent.\",\n hypothesis: true,\n timing: 'close',\n },\n Trend_Direction: {\n description: \"Trend direction classification based on 20-day return: up (>1%), down (<-1%), flat (-1% to 1%). NULL if Return_20D unavailable.\",\n hypothesis: true,\n timing: 'close',\n },\n VIX_Spike_Pct: {\n description: \"VIX spike from open to high as percentage\",\n hypothesis: true,\n timing: 'close',\n },\n VIX_Gap_Pct: {\n description: \"VIX overnight gap percentage ((VIX_Open - prior VIX_Close) / prior VIX_Close * 100)\",\n hypothesis: true,\n timing: 'open',\n },\n },\n },\n spot: {\n description:\n \"Raw minute bars per ticker (ticker-first Hive partitioned Parquet). One row per bar. Use for ORB calculations and intraday context enrichment. Time column is Eastern Time HH:MM format (e.g., '09:30'). Filter by ticker='VIX' to get VIX intraday data.\",\n keyColumns: [\"ticker\", \"date\", \"time\"],\n columns: {\n ticker: {\n description: \"Underlying ticker symbol (part of composite primary key with date and time).\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Part of composite primary key.\",\n hypothesis: true,\n },\n time: {\n description: \"Bar time in HH:MM Eastern Time format (e.g., '09:30', '10:00'). Part of composite primary key.\",\n hypothesis: false,\n },\n open: {\n description: \"Bar open price\",\n hypothesis: false,\n },\n high: {\n description: \"Bar high price\",\n hypothesis: false,\n },\n low: {\n description: \"Bar low price\",\n hypothesis: false,\n },\n close: {\n description: \"Bar close price\",\n hypothesis: false,\n },\n bid: {\n description: \"Bar best bid\",\n hypothesis: false,\n },\n ask: {\n description: \"Bar best ask\",\n hypothesis: false,\n },\n },\n },\n spot_daily: {\n description:\n \"View-backed RTH-aggregated daily OHLCV derived from market.spot (first-open, max-high, min-low, last-close, first-bid, last-ask over 09:30–16:00 bars). One row per ticker per trading day. LEFT JOIN on ticker+date to retrieve OHLCV alongside market.enriched indicators (e.g., market.enriched d LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date).\",\n keyColumns: [\"ticker\", \"date\", \"open\", \"high\", \"low\", \"close\"],\n columns: {\n ticker: {\n description: \"Underlying ticker symbol (composite key with date).\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR YYYY-MM-DD). Composite key with ticker.\",\n hypothesis: true,\n },\n open: {\n description: \"RTH open (first bar at/after 09:30 ET).\",\n hypothesis: false,\n },\n high: {\n description: \"RTH high (max across 09:30–16:00 ET bars).\",\n hypothesis: false,\n },\n low: {\n description: \"RTH low (min across 09:30–16:00 ET bars).\",\n hypothesis: false,\n },\n close: {\n description: \"RTH close (last bar at/before 16:00 ET).\",\n hypothesis: false,\n },\n bid: {\n description: \"RTH first bid (first bar's bid at/after 09:30 ET).\",\n hypothesis: false,\n },\n ask: {\n description: \"RTH last ask (last bar's ask at/before 16:00 ET).\",\n hypothesis: false,\n },\n },\n },\n option_chain: {\n description:\n \"Option contract universe by underlying and trading date. One row per listed option contract, used for strike resolution and candidate selection in backtests.\",\n keyColumns: [\"underlying\", \"date\", \"ticker\"],\n columns: {\n underlying: {\n description: \"Underlying root symbol for the option chain snapshot.\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date for the chain snapshot (VARCHAR YYYY-MM-DD).\",\n hypothesis: true,\n },\n ticker: {\n description: \"Canonical OCC option ticker for the contract.\",\n hypothesis: true,\n },\n contract_type: {\n description: \"Option side: call or put.\",\n hypothesis: true,\n },\n strike: {\n description: \"Option strike price.\",\n hypothesis: true,\n },\n expiration: {\n description: \"Expiration date for the contract (VARCHAR YYYY-MM-DD).\",\n hypothesis: true,\n },\n dte: {\n description: \"Days to expiration as of the chain snapshot date.\",\n hypothesis: true,\n },\n exercise_style: {\n description: \"Exercise style reported by the provider when available.\",\n hypothesis: false,\n },\n },\n },\n option_quote_minutes: {\n description:\n \"Dense minute-level option quote cache keyed by ticker/date/time. Used to fill sparse option trade bars with bid/ask-derived marks during replay and backtests.\",\n keyColumns: [\"ticker\", \"date\", \"time\"],\n columns: {\n ticker: {\n description: \"Canonical OCC option ticker.\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR YYYY-MM-DD).\",\n hypothesis: true,\n },\n time: {\n description: \"Quote minute in HH:MM Eastern Time format.\",\n hypothesis: false,\n },\n bid: {\n description: \"Best bid at or carried into that minute.\",\n hypothesis: false,\n },\n ask: {\n description: \"Best ask at or carried into that minute.\",\n hypothesis: false,\n },\n mid: {\n description: \"Bid/ask midpoint for the minute.\",\n hypothesis: false,\n },\n last_updated_ns: {\n description: \"Monotonic write-order field used for quote upsert precedence.\",\n hypothesis: false,\n },\n source: {\n description: \"Quote source label used for debugging and provenance.\",\n hypothesis: false,\n },\n delta: {\n description: \"Option delta for the minute when available from provider data or computed fallback.\",\n hypothesis: true,\n },\n gamma: {\n description: \"Option gamma for the minute when available from provider data or computed fallback.\",\n hypothesis: false,\n },\n theta: {\n description: \"Option theta for the minute when available from provider data or computed fallback.\",\n hypothesis: false,\n },\n vega: {\n description: \"Option vega (per 1% IV move) for the minute when available from provider data or computed fallback.\",\n hypothesis: false,\n },\n iv: {\n description: \"Implied volatility used for the stored minute greeks when available.\",\n hypothesis: true,\n },\n greeks_source: {\n description: \"Origin of the stored minute greeks: provider-native or computed fallback.\",\n hypothesis: false,\n },\n greeks_revision: {\n description: \"Computation revision for stored computed greeks; null for provider-native values.\",\n hypothesis: false,\n },\n rate_type: {\n description: \"Interest-rate curve or rate label used by the provider or computed-greeks path.\",\n hypothesis: false,\n },\n rate_value: {\n description: \"Interest-rate value used by the provider or computed-greeks path.\",\n hypothesis: false,\n },\n gamma_source: {\n description: \"Provenance label for the stored gamma value when it differs from the broader greeks source.\",\n hypothesis: false,\n },\n },\n },\n },\n },\n};\n\n// ============================================================================\n// Example Queries\n// ============================================================================\n\nexport const EXAMPLE_QUERIES: ExampleQueries = {\n basic: [\n {\n description: \"Count trades by strategy with total P&L\",\n sql: `SELECT strategy, COUNT(*) as trades, SUM(pl) as total_pl\nFROM trades.trade_data\nGROUP BY strategy\nORDER BY total_pl DESC`,\n },\n {\n description: \"Daily P&L for a specific block\",\n sql: `SELECT date_opened, SUM(pl) as daily_pl\nFROM trades.trade_data\nWHERE block_id = 'my-block'\nGROUP BY date_opened\nORDER BY date_opened`,\n },\n {\n description: \"Recent market conditions (last 20 days)\",\n sql: `SELECT d.date, s.close, d.RSI_14, d.ATR_Pct,\n vix_s.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State, evix.ivr AS VIX_IVR, evix.ivp AS VIX_IVP\nFROM market.enriched d\nLEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\nLEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\nLEFT JOIN market.enriched evix ON evix.date = d.date AND evix.ticker = 'VIX'\nLEFT JOIN market.enriched_context cd ON cd.date = d.date\nWHERE d.ticker = 'SPX'\nORDER BY d.date DESC\nLIMIT 20`,\n },\n {\n description: \"Win/loss summary by block\",\n sql: `SELECT\n block_id,\n COUNT(*) as total_trades,\n SUM(CASE WHEN pl > 0 THEN 1 ELSE 0 END) as winners,\n SUM(CASE WHEN pl <= 0 THEN 1 ELSE 0 END) as losers,\n ROUND(100.0 * SUM(CASE WHEN pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate\nFROM trades.trade_data\nGROUP BY block_id\nORDER BY block_id`,\n },\n {\n description: \"Filter and paginate trades (replaces get_trades)\",\n sql: `SELECT date_opened, time_opened, strategy, legs, pl, num_contracts\nFROM trades.trade_data\nWHERE block_id = 'my-block'\n AND strategy ILIKE '%iron%'\n AND pl > 0\nORDER BY date_opened DESC\nLIMIT 50 OFFSET 0`,\n },\n {\n description: \"Market data query with VIX context\",\n sql: `SELECT d.date, s.close, d.Gap_Pct, vix_s.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\nFROM market.enriched d\nLEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\nLEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\nLEFT JOIN market.enriched_context cd ON cd.date = d.date\nWHERE d.ticker = 'SPX'\n AND d.date BETWEEN '2024-01-01' AND '2024-06-30'\n AND vix_s.close > 20\nORDER BY d.date`,\n },\n {\n description: \"Compare backtest vs actual trades by date/strategy\",\n sql: `SELECT\n t.date_opened, t.strategy, t.legs as bt_legs, r.legs as actual_legs,\n t.pl as bt_pl, r.pl as actual_pl, r.pl - t.pl as slippage\nFROM trades.trade_data t\nJOIN trades.reporting_data r\n ON t.block_id = r.block_id\n AND t.date_opened = r.date_opened\n AND t.strategy = r.strategy\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened`,\n },\n ],\n joins: [\n {\n description: \"Trade P&L with market context (lag-aware: multi-table JOIN before LAG for correctness)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.Gap_Pct, d.Prior_Close, d.Prev_Return_Pct,\n vix_s.open AS VIX_Open,\n d.RSI_14, d.Realized_Vol_20D,\n vix_s.close AS VIX_Close, evix.ivp AS VIX_IVP, cd.Vol_Regime, cd.Term_Structure_State\n FROM market.enriched d\n LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\n LEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\n LEFT JOIN market.enriched evix ON evix.date = d.date AND evix.ticker = 'VIX'\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(RSI_14) OVER (PARTITION BY ticker ORDER BY date) AS prev_RSI_14,\n LAG(VIX_IVP) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_IVP,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n m.Gap_Pct, m.VIX_Open,\n m.prev_RSI_14, m.prev_VIX_IVP, m.prev_VIX_Close, m.prev_Vol_Regime\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened DESC`,\n },\n {\n description: \"Trades with ORB context (opening range breakout from minute bars in market.spot)\",\n sql: `WITH orb_range AS (\n SELECT ticker, date,\n MAX(high) AS ORB_High,\n MIN(low) AS ORB_Low,\n MAX(high) - MIN(low) AS ORB_Range\n FROM market.spot\n WHERE ticker = 'SPX'\n AND time >= '09:30' AND time <= '09:45'\n -- Drop minute bars with zero/null OHLC (occasional provider gaps).\n -- Without this, MIN(low) collapses to 0 on contaminated minutes\n -- and ORB_Range balloons to ~100% of price.\n AND open IS NOT NULL AND open > 0\n AND high IS NOT NULL AND high > 0\n AND low IS NOT NULL AND low > 0\n AND close IS NOT NULL AND close > 0\n GROUP BY ticker, date\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n r.ORB_High, r.ORB_Low, r.ORB_Range\nFROM trades.trade_data t\nLEFT JOIN orb_range r ON t.date_opened = r.date\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened`,\n },\n {\n description: \"VIX intraday data for a specific date (VIX bars are in market.spot with ticker='VIX')\",\n sql: `SELECT time, open, high, low, close\nFROM market.spot\nWHERE ticker = 'VIX'\n AND date = '2024-03-15'\nORDER BY time`,\n },\n {\n description: \"Trades on reversal days (lag-aware: Reversal_Type uses prior trading day via LAG)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.High_Before_Low, d.Reversal_Type\n FROM market.enriched d\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Reversal_Type) OVER (PARTITION BY ticker ORDER BY date) AS prev_Reversal_Type,\n LAG(High_Before_Low) OVER (PARTITION BY ticker ORDER BY date) AS prev_High_Before_Low\n FROM joined\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n m.prev_Reversal_Type, m.prev_High_Before_Low\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE m.prev_Reversal_Type != 0\n AND t.block_id = 'my-block'`,\n },\n {\n description: \"Enrich trades with market data (lag-aware: use enrich_trades tool for full enrichment)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.Gap_Pct, d.Prior_Close,\n vix_s.open AS VIX_Open,\n d.RSI_14, d.ATR_Pct,\n vix_s.close AS VIX_Close, cd.Vol_Regime\n FROM market.enriched d\n LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\n LEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT t.date_opened, t.strategy, t.pl,\n m.Gap_Pct, m.VIX_Open, m.prev_VIX_Close, m.prev_Vol_Regime\nFROM trades.trade_data t\nLEFT JOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'`,\n },\n ],\n hypothesis: [\n {\n description: \"Win rate by VIX regime (lag-aware: uses prior day's Vol_Regime from market.enriched_context)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, cd.Vol_Regime\n FROM market.enriched d\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT\n m.prev_Vol_Regime AS vol_regime,\n COUNT(*) as trades,\n SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) as winners,\n ROUND(100.0 * SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate,\n SUM(t.pl) as total_pl\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_Vol_Regime IS NOT NULL\nGROUP BY m.prev_Vol_Regime\nORDER BY m.prev_Vol_Regime`,\n },\n {\n description: \"P&L by day of week\",\n sql: `SELECT\n d.Day_of_Week,\n COUNT(*) as trades,\n SUM(t.pl) as total_pl,\n ROUND(AVG(t.pl), 2) as avg_pl\nFROM trades.trade_data t\nJOIN market.enriched d ON t.date_opened = d.date AND d.ticker = 'SPX'\nWHERE t.block_id = 'my-block'\nGROUP BY d.Day_of_Week\nORDER BY d.Day_of_Week`,\n },\n {\n description: \"Performance by VIX term structure (lag-aware: uses prior day's Term_Structure_State from market.enriched_context)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, cd.Term_Structure_State\n FROM market.enriched d\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Term_Structure_State) OVER (PARTITION BY ticker ORDER BY date) AS prev_Term_Structure_State\n FROM joined\n)\nSELECT\n CASE WHEN m.prev_Term_Structure_State = -1 THEN 'Backwardation'\n WHEN m.prev_Term_Structure_State = 1 THEN 'Contango'\n ELSE 'Flat' END as term_structure,\n COUNT(*) as trades,\n SUM(t.pl) as total_pl,\n ROUND(AVG(t.pl), 2) as avg_pl,\n ROUND(100.0 * SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_Term_Structure_State IS NOT NULL\nGROUP BY term_structure`,\n },\n {\n description: \"Aggregate by VIX buckets (lag-aware: uses prior day's VIX close from market.spot_daily ticker='VIX')\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, vix_s.close AS VIX_Close\n FROM market.enriched d\n LEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close\n FROM joined\n)\nSELECT\n CASE\n WHEN m.prev_VIX_Close < 15 THEN '10-15'\n WHEN m.prev_VIX_Close < 20 THEN '15-20'\n WHEN m.prev_VIX_Close < 25 THEN '20-25'\n ELSE '25+'\n END as vix_bucket,\n COUNT(*) as trades,\n SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END)::FLOAT / COUNT(*) as win_rate,\n SUM(t.pl) as total_pl\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_VIX_Close IS NOT NULL\nGROUP BY vix_bucket\nORDER BY vix_bucket`,\n },\n {\n description: \"Find similar days by conditions\",\n sql: `WITH ref AS (\n SELECT s.close, vix_s.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\n FROM market.enriched d\n LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\n LEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = 'SPX' AND d.date = '2024-01-15'\n)\nSELECT d.date, s.close, vix_s.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\nFROM market.enriched d\nLEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\nLEFT JOIN market.spot_daily vix_s ON vix_s.date = d.date AND vix_s.ticker = 'VIX'\nLEFT JOIN market.enriched_context cd ON cd.date = d.date, ref\nWHERE d.ticker = 'SPX'\n AND d.date != '2024-01-15'\n AND cd.Vol_Regime = ref.Vol_Regime\n AND ABS(vix_s.close - ref.VIX_Close) < 3\nORDER BY ABS(vix_s.close - ref.VIX_Close)\nLIMIT 20`,\n },\n ],\n};\n","/**\n * Field Timing Utilities\n *\n * Derived sets and LAG CTE builder for lookahead-free market analytics.\n * All field classifications are derived from SCHEMA_DESCRIPTIONS timing annotations\n * in schema-metadata.ts -- no hardcoded column names.\n *\n * The v3.0 market schema splits market data into:\n * - market.enriched: per-ticker computed indicators (+ ivr/ivp for VIX-family). NO OHLCV.\n * - market.spot_daily: view-backed RTH-aggregated daily OHLCV (open/high/low/close/bid/ask)\n * derived from market.spot. LEFT JOIN on ticker+date to attach OHLCV to enriched rows.\n * - market.enriched_context: cross-ticker derived fields (Vol_Regime, Term_Structure_State, etc.)\n *\n * buildLookaheadFreeQuery JOINs market.enriched d + market.spot_daily s (OHLCV for the target\n * ticker) + buildVixJoinClause (VIX-family OHLCV from spot_daily + IVR/IVP from enriched) +\n * market.enriched_context inside a CTE before applying LAG, ensuring LAG operates on the full\n * ticker history (not just trade dates). This guarantees Monday LAG returns Friday's values,\n * not the previous trade day. Per Pitfall 1 (Phase 6 RESEARCH), OHLCV column projections MUST\n * use the spot_daily alias `s` (or vix_s etc.) — `d.close` is a binder error because\n * market.enriched carries no OHLCV columns.\n *\n * Used by downstream tools (suggest_filters, analyze_regime_performance, etc.)\n * to ensure trade-entry queries only use data available at the time of trade entry.\n */\n\nimport { DEFAULT_MARKET_TICKER } from \"./ticker.ts\";\nimport { SCHEMA_DESCRIPTIONS } from \"./schema-metadata.ts\";\n\nconst dailyColumns = SCHEMA_DESCRIPTIONS.market.tables.enriched.columns;\nconst derivedColumns = SCHEMA_DESCRIPTIONS.market.tables.enriched_context.columns;\n\n// OHLCV columns live in market.spot_daily (aliased `s`), NOT market.enriched (aliased `d`).\n// When emitting SELECT projections that iterate DAILY_*_FIELDS, route these column names\n// to the spot_daily alias to avoid Binder Error on the real DuckDB schema.\nconst OHLCV_COLS = new Set([\"open\", \"high\", \"low\", \"close\"]);\nfunction aliasForDailyCol(col: string): \"s\" | \"d\" {\n return OHLCV_COLS.has(col) ? \"s\" : \"d\";\n}\n\nexport interface MarketLookupKey {\n date: string;\n ticker: string;\n}\n\n// ============================================================================\n// VIX field mapping — normalized schema\n// ============================================================================\n\n/**\n * VIX field mapping: column alias -> { table alias, source column, ticker, timing }.\n * Post Phase 6 (Pitfall 1 schema split): market.enriched has NO OHLCV columns, so\n * a single per-ticker alias cannot source both OHLCV and IVR/IVP. The VIX column\n * mappings are therefore split into:\n * - VIX_OHLCV_MAPPINGS: OHLCV fields (open/close/high/low) sourced from market.spot_daily\n * via alias {vix, vix9d, vix3m}.\n * - VIX_ENRICHED_MAPPINGS: IVR/IVP fields sourced from market.enriched via the\n * `e`-prefixed alias {evix, evix9d, evix3m}.\n */\ninterface VixFieldMapping {\n alias: string; // Column name in query output (e.g., \"VIX_Close\")\n tableAlias: string; // SQL table alias (e.g., \"vix\" for OHLCV, \"evix\" for enriched)\n sourceCol: string; // Source column (e.g., \"close\" in spot_daily, \"ivr\" in enriched)\n ticker: string; // Ticker to join on (e.g., \"VIX\")\n timing: 'open' | 'close';\n}\n\n// OHLCV-sourced VIX columns — come from market.spot_daily, aliased as vix/vix9d/vix3m\nconst VIX_OHLCV_MAPPINGS: VixFieldMapping[] = [\n // VIX\n { alias: \"VIX_Open\", tableAlias: \"vix\", sourceCol: \"open\", ticker: \"VIX\", timing: \"open\" },\n { alias: \"VIX_Close\", tableAlias: \"vix\", sourceCol: \"close\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_High\", tableAlias: \"vix\", sourceCol: \"high\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_Low\", tableAlias: \"vix\", sourceCol: \"low\", ticker: \"VIX\", timing: \"close\" },\n // VIX9D\n { alias: \"VIX9D_Open\", tableAlias: \"vix9d\", sourceCol: \"open\", ticker: \"VIX9D\", timing: \"open\" },\n { alias: \"VIX9D_Close\", tableAlias: \"vix9d\", sourceCol: \"close\", ticker: \"VIX9D\", timing: \"close\" },\n // VIX3M\n { alias: \"VIX3M_Open\", tableAlias: \"vix3m\", sourceCol: \"open\", ticker: \"VIX3M\", timing: \"open\" },\n { alias: \"VIX3M_Close\", tableAlias: \"vix3m\", sourceCol: \"close\", ticker: \"VIX3M\", timing: \"close\" },\n];\n\n// Enrichment-sourced VIX columns — come from market.enriched, aliased as evix/evix9d/evix3m\nconst VIX_ENRICHED_MAPPINGS: VixFieldMapping[] = [\n { alias: \"VIX_IVR\", tableAlias: \"evix\", sourceCol: \"ivr\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_IVP\", tableAlias: \"evix\", sourceCol: \"ivp\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX9D_IVR\", tableAlias: \"evix9d\", sourceCol: \"ivr\", ticker: \"VIX9D\", timing: \"close\" },\n { alias: \"VIX9D_IVP\", tableAlias: \"evix9d\", sourceCol: \"ivp\", ticker: \"VIX9D\", timing: \"close\" },\n { alias: \"VIX3M_IVR\", tableAlias: \"evix3m\", sourceCol: \"ivr\", ticker: \"VIX3M\", timing: \"close\" },\n { alias: \"VIX3M_IVP\", tableAlias: \"evix3m\", sourceCol: \"ivp\", ticker: \"VIX3M\", timing: \"close\" },\n];\n\n// Union of all VIX mappings (order preserved: OHLCV first, then enrichment) — used for\n// SELECT column emission and open/close field-set derivation downstream.\nconst VIX_ALL_MAPPINGS: VixFieldMapping[] = [\n ...VIX_OHLCV_MAPPINGS,\n ...VIX_ENRICHED_MAPPINGS,\n];\n\n// Unique OHLCV table aliases needed for the JOIN clause (used by buildVixJoinClause)\nconst VIX_TICKER_ALIASES = [...new Set(VIX_OHLCV_MAPPINGS.map(m => m.tableAlias))];\nconst VIX_TICKER_FOR_ALIAS: Record<string, string> = Object.fromEntries([\n ...VIX_OHLCV_MAPPINGS.map(m => [m.tableAlias, m.ticker] as const),\n ...VIX_ENRICHED_MAPPINGS.map(m => [m.tableAlias, m.ticker] as const),\n]);\n\n// Derived fields from date_context\nconst DERIVED_OPEN_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(derivedColumns)\n .filter(([, desc]) => desc.timing === 'open')\n .map(([name]) => name)\n);\n\nconst DERIVED_CLOSE_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(derivedColumns)\n .filter(([, desc]) => desc.timing === 'close')\n .map(([name]) => name)\n);\n\n// ============================================================================\n// Table-specific field sets (needed by CTE builder to know which table to alias)\n// ============================================================================\n\n/**\n * Open-known fields from market.enriched (use as d.{field} in JOIN CTE, except OHLCV\n * columns which project from the market.spot_daily alias `s`).\n * NOTE: this is still named DAILY_* for back-compat; it now reflects market.enriched\n * columns (which replaced the legacy `daily` table in the v3.0 layout).\n */\nexport const DAILY_OPEN_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'open')\n .map(([name]) => name)\n);\n\n/**\n * Close-derived fields from market.enriched (apply LAG in JOIN CTE; OHLCV columns\n * project from the market.spot_daily alias `s`).\n */\nexport const DAILY_CLOSE_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'close')\n .map(([name]) => name)\n);\n\n/**\n * Static fields from market.enriched (use as d.{field} in JOIN CTE — calendar facts)\n */\nexport const DAILY_STATIC_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'static')\n .map(([name]) => name)\n);\n\n/**\n * Open-known fields from VIX tickers + enriched_context\n */\nexport const CONTEXT_OPEN_FIELDS: ReadonlySet<string> = new Set([\n ...VIX_ALL_MAPPINGS.filter(m => m.timing === 'open').map(m => m.alias),\n ...DERIVED_OPEN_FIELDS,\n]);\n\n/**\n * Close-derived fields from VIX tickers + enriched_context\n */\nexport const CONTEXT_CLOSE_FIELDS: ReadonlySet<string> = new Set([\n ...VIX_ALL_MAPPINGS.filter(m => m.timing === 'close').map(m => m.alias),\n ...DERIVED_CLOSE_FIELDS,\n]);\n\n// ============================================================================\n// Combined field sets (for callers that don't need to know origin table)\n// ============================================================================\n\n/**\n * Fields known at or before market open (Prior_Close, Gap_Pct, VIX_Open, etc.)\n * Union of open-known fields from market.enriched, VIX tickers (spot_daily OHLCV +\n * enriched IVR/IVP), and market.enriched_context.\n * Safe to use as same-day values in trade-entry queries.\n */\nexport const OPEN_KNOWN_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_OPEN_FIELDS,\n ...CONTEXT_OPEN_FIELDS,\n]);\n\n/**\n * Fields only known after market close (RSI_14, Vol_Regime, Close, etc.)\n * Union of close-derived fields from market.enriched, VIX tickers (spot_daily +\n * enriched), and market.enriched_context.\n * Must use LAG() to get prior trading day's value in trade-entry queries.\n */\nexport const CLOSE_KNOWN_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_CLOSE_FIELDS,\n ...CONTEXT_CLOSE_FIELDS,\n]);\n\n/**\n * Calendar/metadata facts known before the trading day (Day_of_Week, Month, Is_Opex).\n * Only from market.enriched (context has no static fields).\n * Safe to use as same-day values in trade-entry queries.\n */\nexport const STATIC_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_STATIC_FIELDS,\n]);\n\n// ============================================================================\n// Query Builders\n// ============================================================================\n\n/**\n * Phase 6 (D-02 extraction) — public helper that emits the VIX JOIN clause\n * against BOTH market.spot_daily (OHLCV for open/high/low/close/bid/ask) AND\n * market.enriched (for ivr/ivp). Called by buildLookaheadFreeQuery and\n * buildOutcomeQuery as of Wave 1.\n *\n * Pitfall 1 context: market.enriched has no OHLCV columns, so a single alias\n * per ticker cannot source both sets. This helper emits TWO joins per alias —\n * the OHLCV alias (e.g. `vix`) attaches spot_daily; the `e`-prefixed alias\n * (e.g. `evix`) attaches enriched.\n *\n * @param tickerAliases e.g. ['vix', 'vix9d', 'vix3m']\n * @param baseAlias alias of the main table the joins attach to (default 'd')\n * @returns SQL fragment string with \\n + 6-space indent between JOINs\n */\nexport function buildVixJoinClause(\n tickerAliases: string[],\n baseAlias: string = \"d\",\n): string {\n return tickerAliases\n .flatMap(alias => {\n const ticker = VIX_TICKER_FOR_ALIAS[alias];\n return [\n `LEFT JOIN market.spot_daily ${alias} ON ${alias}.date = ${baseAlias}.date AND ${alias}.ticker = '${ticker}'`,\n `LEFT JOIN market.enriched e${alias} ON e${alias}.date = ${baseAlias}.date AND e${alias}.ticker = '${ticker}'`,\n ];\n })\n .join(\"\\n \");\n}\n\n// SELECT columns from VIX OHLCV + enrichment mappings, e.g. vix.\"close\" AS \"VIX_Close\",\n// evix.\"ivr\" AS \"VIX_IVR\", ...\nfunction buildVixSelectCols(): string {\n return VIX_ALL_MAPPINGS\n .map(m => `${m.tableAlias}.\"${m.sourceCol}\" AS \"${m.alias}\"`)\n .join(\", \");\n}\n\n// SELECT columns from enriched_context: cd.\"Vol_Regime\", ...\nfunction buildDerivedSelectCols(): string {\n return [...DERIVED_OPEN_FIELDS, ...DERIVED_CLOSE_FIELDS].map(f => `cd.\"${f}\"`).join(\", \");\n}\n\n/**\n * Builds a SQL query that joins trade keys to market.enriched + market.spot_daily\n * (for OHLCV) + market.enriched_context + VIX tickers (both spot_daily OHLCV and\n * enriched IVR/IVP) with lookahead bias prevention:\n * - Open-known fields: used as-is (same-day values, known before market open)\n * - Static fields: used as-is (calendar facts, known in advance)\n * - Close-derived fields: LAG(field) OVER (PARTITION BY ticker ORDER BY date)\n * gives prior trading day's value\n *\n * The post-Phase-6 JOIN pattern is:\n * market.enriched d\n * LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date -- OHLCV for target\n * LEFT JOIN market.spot_daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n * LEFT JOIN market.enriched evix ON evix.date = d.date AND evix.ticker = 'VIX'\n * LEFT JOIN market.spot_daily vix9d ON vix9d.date = d.date AND vix9d.ticker = 'VIX9D'\n * LEFT JOIN market.enriched evix9d ON evix9d.date = d.date AND evix9d.ticker = 'VIX9D'\n * LEFT JOIN market.spot_daily vix3m ON vix3m.date = d.date AND vix3m.ticker = 'VIX3M'\n * LEFT JOIN market.enriched evix3m ON evix3m.date = d.date AND evix3m.ticker = 'VIX3M'\n * LEFT JOIN market.enriched_context cd ON cd.date = d.date\n *\n * Pitfall 1: market.enriched carries NO OHLCV columns; OHLCV projections MUST use the\n * spot_daily alias (`s` for the target ticker, `vix/vix9d/vix3m` for VIX-family).\n *\n * LAG operates on the FULL ticker history (all trading days for the ticker),\n * NOT just the requested dates. This ensures LAG sees the correct prior trading day\n * across weekends, holidays, and sparse trading strategies.\n *\n * @param tradeDatesOrKeys - Array of dates (legacy string[] overload) or ticker+date keys\n * @returns Object with `sql` (the query string) and `params` (the parameter values)\n */\nexport function buildLookaheadFreeQuery(tradeDates: string[]): { sql: string; params: string[] };\nexport function buildLookaheadFreeQuery(tradeKeys: MarketLookupKey[]): { sql: string; params: string[] };\nexport function buildLookaheadFreeQuery(\n tradeDatesOrKeys: string[] | MarketLookupKey[]\n): { sql: string; params: string[] } {\n if (tradeDatesOrKeys.length === 0) {\n return { sql: `SELECT * FROM market.enriched WHERE 1=0`, params: [] };\n }\n\n // Build field lists for the joined CTE. OHLCV columns project from alias `s`\n // (market.spot_daily); enrichment columns project from alias `d` (market.enriched).\n const dailyOpenCols = [...DAILY_OPEN_FIELDS].map((f) => `${aliasForDailyCol(f)}.\"${f}\"`).join(\", \");\n const dailyStaticCols = [...DAILY_STATIC_FIELDS].map((f) => `${aliasForDailyCol(f)}.\"${f}\"`).join(\", \");\n const dailyCloseCols = [...DAILY_CLOSE_FIELDS].map((f) => `${aliasForDailyCol(f)}.\"${f}\"`).join(\", \");\n const vixSelectCols = buildVixSelectCols();\n const derivedSelectCols = buildDerivedSelectCols();\n const vixJoins = buildVixJoinClause(VIX_TICKER_ALIASES, \"d\");\n\n // LAG columns — all close-derived fields from daily-enriched and VIX/derived\n const dailyLagCols = [...DAILY_CLOSE_FIELDS]\n .map((field) => `LAG(\"${field}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${field}\"`)\n .join(\",\\n \");\n const vixLagCols = VIX_ALL_MAPPINGS\n .filter(m => m.timing === 'close')\n .map(m => `LAG(\"${m.alias}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${m.alias}\"`)\n .join(\",\\n \");\n const derivedLagCols = [...DERIVED_CLOSE_FIELDS]\n .map(f => `LAG(\"${f}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${f}\"`)\n .join(\",\\n \");\n\n // Pass-through columns for the lagged CTE (unaliased, from joined CTE output)\n const dailyOpenPassthrough = [...DAILY_OPEN_FIELDS].map((f) => `\"${f}\"`).join(\", \");\n const dailyStaticPassthrough = [...DAILY_STATIC_FIELDS].map((f) => `\"${f}\"`).join(\", \");\n const vixOpenPassthrough = VIX_ALL_MAPPINGS\n .filter(m => m.timing === 'open')\n .map(m => `\"${m.alias}\"`)\n .join(\", \");\n const derivedOpenPassthrough = [...DERIVED_OPEN_FIELDS].map(f => `\"${f}\"`).join(\", \");\n\n // Legacy path for existing date-only callers (single ticker = DEFAULT_MARKET_TICKER)\n if (typeof tradeDatesOrKeys[0] === \"string\") {\n const tradeDates = tradeDatesOrKeys as string[];\n const placeholders = tradeDates.map((_, i) => `$${i + 1}`).join(\", \");\n\n const sql = `WITH joined AS (\n SELECT\n d.ticker,\n d.date,\n ${dailyOpenCols},\n ${dailyStaticCols},\n ${vixSelectCols},\n ${derivedSelectCols ? derivedSelectCols + \",\" : \"\"}\n ${dailyCloseCols}\n FROM market.enriched d\n LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\n ${vixJoins}\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker = $${tradeDates.length + 1}\n ),\n lagged AS (\n SELECT\n ticker,\n date,\n ${dailyOpenPassthrough},\n ${dailyStaticPassthrough},\n ${vixOpenPassthrough ? vixOpenPassthrough + \",\" : \"\"}\n ${derivedOpenPassthrough ? derivedOpenPassthrough + \",\" : \"\"}\n ${dailyLagCols},\n ${vixLagCols ? vixLagCols + \",\" : \"\"}\n ${derivedLagCols}\n FROM joined\n )\n SELECT * FROM lagged\n WHERE date IN (${placeholders})`;\n\n return { sql, params: [...tradeDates, DEFAULT_MARKET_TICKER] };\n }\n\n const tradeKeys = tradeDatesOrKeys as MarketLookupKey[];\n const normalizedKeys = tradeKeys.map((k) => ({\n date: k.date,\n ticker: k.ticker || DEFAULT_MARKET_TICKER,\n }));\n\n const values: string[] = [];\n const valuePlaceholders = normalizedKeys.map((key) => {\n values.push(key.ticker, key.date);\n return `($${values.length - 1}, $${values.length})`;\n });\n\n const sql = `WITH requested(ticker, date) AS (\n VALUES ${valuePlaceholders.join(\", \")}\n ),\n joined AS (\n SELECT\n d.ticker,\n d.date,\n ${dailyOpenCols},\n ${dailyStaticCols},\n ${vixSelectCols},\n ${derivedSelectCols ? derivedSelectCols + \",\" : \"\"}\n ${dailyCloseCols}\n FROM market.enriched d\n LEFT JOIN market.spot_daily s ON s.ticker = d.ticker AND s.date = d.date\n ${vixJoins}\n LEFT JOIN market.enriched_context cd ON cd.date = d.date\n WHERE d.ticker IN (SELECT DISTINCT ticker FROM requested)\n ),\n lagged AS (\n SELECT\n ticker,\n date,\n ${dailyOpenPassthrough},\n ${dailyStaticPassthrough},\n ${vixOpenPassthrough ? vixOpenPassthrough + \",\" : \"\"}\n ${derivedOpenPassthrough ? derivedOpenPassthrough + \",\" : \"\"}\n ${dailyLagCols},\n ${vixLagCols ? vixLagCols + \",\" : \"\"}\n ${derivedLagCols}\n FROM joined\n )\n SELECT lagged.*\n FROM lagged\n JOIN requested\n ON lagged.ticker = requested.ticker\n AND lagged.date = requested.date`;\n\n return { sql, params: values };\n}\n\n/**\n * Builds a SQL query that returns same-day close-derived values (no LAG).\n * Used for outcome/post-hoc analysis when includeOutcomeFields=true.\n *\n * These are values that were NOT available at trade entry time --\n * they represent the end-of-day result for the trade date itself.\n * Sources from market.enriched (+ market.spot_daily for OHLCV), VIX ticker rows\n * (spot_daily + enriched), and market.enriched_context via LEFT JOIN.\n *\n * @param tradeDatesOrKeys - Array of dates or ticker+date keys\n * @returns Object with `sql` (the query string) and `params` (the date values)\n */\nexport function buildOutcomeQuery(tradeDates: string[]): { sql: string; params: string[] };\nexport function buildOutcomeQuery(tradeKeys: MarketLookupKey[]): { sql: string; params: string[] };\nexport function buildOutcomeQuery(\n tradeDatesOrKeys: string[] | MarketLookupKey[]\n): { sql: string; params: string[] } {\n if (tradeDatesOrKeys.length === 0) {\n return { sql: `SELECT * FROM market.enriched WHERE 1=0`, params: [] };\n }\n\n const vixCloseCols = VIX_ALL_MAPPINGS\n .filter(m => m.timing === 'close')\n .map(m => `${m.tableAlias}.\"${m.sourceCol}\" AS \"${m.alias}\"`)\n .join(\", \");\n const derivedCloseCols = [...DERIVED_CLOSE_FIELDS].map(f => `cd.\"${f}\"`).join(\", \");\n\n if (typeof tradeDatesOrKeys[0] === \"string\") {\n return buildOutcomeQueryForDates(tradeDatesOrKeys as string[], vixCloseCols, derivedCloseCols);\n }\n\n return buildOutcomeQueryForKeys(tradeDatesOrKeys as MarketLookupKey[], vixCloseCols, derivedCloseCols);\n}\n\n// Emit OHLCV-aware projection for the target-ticker close columns. Columns in\n// OHLCV_COLS come from the spot_daily alias `sAlias`; other enrichment-close\n// columns come from the enriched alias `eAlias`.\nfunction buildTargetCloseCols(\n eAlias: string,\n sAlias: string,\n): string {\n return [...DAILY_CLOSE_FIELDS]\n .map((f) => `${OHLCV_COLS.has(f) ? sAlias : eAlias}.\"${f}\"`)\n .join(\", \");\n}\n\nfunction buildOutcomeQueryForDates(\n tradeDates: string[], vixCloseCols: string, derivedCloseCols: string\n): { sql: string; params: string[] } {\n const eAlias = \"d\";\n const sAlias = \"s\";\n const dailyCloseCols = buildTargetCloseCols(eAlias, sAlias);\n const placeholders = tradeDates.map((_, i) => `$${i + 1}`).join(\", \");\n const sql = `SELECT ${eAlias}.date, ${dailyCloseCols}, ${vixCloseCols}, ${derivedCloseCols}\n FROM market.enriched ${eAlias}\n LEFT JOIN market.spot_daily ${sAlias} ON ${sAlias}.ticker = ${eAlias}.ticker AND ${sAlias}.date = ${eAlias}.date\n ${buildVixJoinClause(VIX_TICKER_ALIASES, eAlias)}\n LEFT JOIN market.enriched_context cd ON cd.date = ${eAlias}.date\n WHERE ${eAlias}.ticker = $${tradeDates.length + 1}\n AND ${eAlias}.date IN (${placeholders})`;\n return { sql, params: [...tradeDates, DEFAULT_MARKET_TICKER] };\n}\n\nfunction buildOutcomeQueryForKeys(\n tradeKeys: MarketLookupKey[], vixCloseCols: string, derivedCloseCols: string\n): { sql: string; params: string[] } {\n const eAlias = \"m\";\n const sAlias = \"ms\";\n const dailyCloseCols = buildTargetCloseCols(eAlias, sAlias);\n const normalizedKeys = tradeKeys.map((k) => ({\n date: k.date,\n ticker: k.ticker || DEFAULT_MARKET_TICKER,\n }));\n\n const values: string[] = [];\n const valuePlaceholders = normalizedKeys.map((key) => {\n values.push(key.ticker, key.date);\n return `($${values.length - 1}, $${values.length})`;\n });\n\n const sql = `WITH requested(ticker, date) AS (\n VALUES ${valuePlaceholders.join(\", \")}\n )\n SELECT ${eAlias}.ticker, ${eAlias}.date, ${dailyCloseCols}, ${vixCloseCols}, ${derivedCloseCols}\n FROM market.enriched ${eAlias}\n LEFT JOIN market.spot_daily ${sAlias} ON ${sAlias}.ticker = ${eAlias}.ticker AND ${sAlias}.date = ${eAlias}.date\n ${buildVixJoinClause(VIX_TICKER_ALIASES, eAlias)}\n LEFT JOIN market.enriched_context cd ON cd.date = ${eAlias}.date\n JOIN requested\n ON ${eAlias}.ticker = requested.ticker\n AND ${eAlias}.date = requested.date`;\n\n return { sql, params: values };\n}\n","/**\n * Data Availability Helper\n *\n * Checks whether canonical market data (enriched daily, VIX context, intraday\n * spot bars) is available for a given ticker and returns actionable warnings\n * when data is missing.\n *\n * Used at the start of every market tool call to surface missing data with\n * clear import instructions rather than returning silent NULLs or cryptic\n * errors.\n *\n * Phase 4 / CONSUMER-02: rewritten to consume `MarketStores` so reads NEVER\n * trigger provider fetches. Daily/context coverage flows through\n * `stores.enriched.getCoverage`; intraday coverage flows through\n * `stores.spot.getCoverage`. The previous direct raw-SQL paths against the\n * pre-Phase-6 daily / intraday views are gone (D-09 silent-empty contract).\n */\nimport type { MarketStores } from \"../market/stores/index.ts\";\n\nexport interface DataAvailabilityReport {\n /** Whether enriched data is present for the requested ticker */\n hasDailyData: boolean;\n /** Whether enriched data is present for the canonical VIX context ticker */\n hasContextData: boolean;\n /** Whether spot intraday data is present for the requested ticker */\n hasIntradayData: boolean;\n /** Date range available in enriched for the ticker, or null if no data */\n dailyDateRange: { min: string; max: string } | null;\n /** Date range of VIX enriched coverage, or null if no data */\n contextDateRange: { min: string; max: string } | null;\n /** Date range available in spot intraday for the ticker, or null if no data */\n intradayDateRange: { min: string; max: string } | null;\n /** Actionable warning messages for any missing data sources */\n warnings: string[];\n}\n\n/**\n * Sentinel \"wide\" date range used when callers want an \"any data?\" check.\n * Matches the D-09 contract — store returns empty coverage when the range has\n * no partitions; caller interprets `totalDates > 0` as \"data exists somewhere\n * in history\" without paying for an extra `MIN/MAX` query.\n */\nconst WIDE_FROM = \"2000-01-01\";\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\n/**\n * Checks data availability via the typed Phase 2 store layer.\n *\n * Calls `stores.enriched.getCoverage(...)` for the daily + VIX context probes\n * and `stores.spot.getCoverage(...)` for the optional intraday probe. Returns\n * a report with boolean flags, date ranges, and actionable warning messages.\n *\n * @param stores - MarketStores bundle (constructed once at process startup)\n * @param ticker - Ticker symbol to check (e.g., 'SPX')\n * @param options.checkIntraday - Whether to also check spot intraday (default: false)\n */\nexport async function checkDataAvailability(\n stores: MarketStores,\n ticker: string,\n options?: { checkIntraday?: boolean },\n): Promise<DataAvailabilityReport> {\n const warnings: string[] = [];\n\n // --- Daily (enriched) — ticker-only signature per EnrichedStore.getCoverage ---\n const dailyCov = await stores.enriched.getCoverage(ticker);\n const hasDailyData = dailyCov.totalDates > 0;\n const dailyDateRange =\n hasDailyData && dailyCov.earliest && dailyCov.latest\n ? { min: dailyCov.earliest, max: dailyCov.latest }\n : null;\n\n if (!hasDailyData) {\n warnings.push(\n `No enriched daily data for ticker ${ticker}. ` +\n `Import daily OHLCV with import_market_csv (target_table: \"daily\", ticker: \"${ticker}\") ` +\n `then run enrich_market_data.`,\n );\n }\n\n // --- Context (VIX enriched) — same store, fixed ticker ---\n const vixCov = await stores.enriched.getCoverage(\"VIX\");\n const hasContextData = vixCov.totalDates > 0;\n const contextDateRange =\n hasContextData && vixCov.earliest && vixCov.latest\n ? { min: vixCov.earliest, max: vixCov.latest }\n : null;\n\n if (!hasContextData) {\n warnings.push(\n `No VIX enriched data found. ` +\n `Import VIX-family data with import_from_api (target_table: \"date_context\") ` +\n `or import_market_csv for VIX/VIX9D/VIX3M daily rows, ` +\n `then run enrich_market_data for IVR/IVP and date_context enrichment.`,\n );\n }\n\n // --- Intraday (spot) — only when caller explicitly opts in ---\n let hasIntradayData = false;\n let intradayDateRange: { min: string; max: string } | null = null;\n if (options?.checkIntraday) {\n const spotCov = await stores.spot.getCoverage(ticker, WIDE_FROM, todayIso());\n hasIntradayData = spotCov.totalDates > 0;\n if (hasIntradayData && spotCov.earliest && spotCov.latest) {\n intradayDateRange = { min: spotCov.earliest, max: spotCov.latest };\n }\n if (!hasIntradayData) {\n warnings.push(\n `No spot intraday data for ticker ${ticker}. ` +\n `Import intraday bars with import_market_csv (target_table: \"intraday\", ticker: \"${ticker}\").`,\n );\n }\n }\n\n return {\n hasDailyData,\n hasContextData,\n hasIntradayData,\n dailyDateRange,\n contextDateRange,\n intradayDateRange,\n warnings,\n };\n}\n","/**\n * data-quality.ts\n *\n * Data quality scoring and coverage reporting for cached market data.\n *\n * Exports:\n * DataQuality - Interface for quality assessment result\n * DataQualityInput - Input to scoreDataQuality (pure, no I/O)\n * CoverageResult - Result from queryCoverage (store-backed)\n * scoreDataQuality - Pure function: scores data quality given bar counts\n * queryCoverage - Store function: aggregates spot + quote store coverage\n * formatCoverageReport - Pure function: produces human-readable coverage text\n *\n * Design principles:\n * - scoreDataQuality and formatCoverageReport are pure functions (no I/O)\n * - queryCoverage handles all store access — flows through\n * `stores.spot.getCoverage` + `stores.quote.getCoverage` only; no raw\n * `FROM market.*` SQL remains.\n * - Confidence scoring uses avgBarsPerDay as proxy for data density:\n * >= 200 bars/day = dense quotes (high confidence)\n * 50-199 bars/day = sparse trade bars (medium confidence)\n * < 50 bars/day = very sparse data (low confidence)\n * - Missing data (> 10% of trading days) caps confidence at medium\n *\n * Note on `barCount`:\n * The earlier `queryCoverage` returned per-date `COUNT(*)` row counts from\n * the legacy intraday view / `market.option_quote_minutes`. The current\n * store layer exposes coverage as `{ earliest, latest, totalDates }` —\n * date-level granularity only. We map \"covered date\" → `barCount = 1` so\n * downstream `scoreDataQuality` consumers continue to see a non-zero\n * density signal, and `hasQuotes` reflects whether the date is covered by\n * the quote store (the current \"dense data\" signal). True per-date\n * density is no longer exposed by the store layer.\n */\n\nimport type { MarketStores } from \"../market/stores/index.ts\";\n\n// ---------------------------------------------------------------------------\n// Interfaces\n// ---------------------------------------------------------------------------\n\n/**\n * Data quality assessment result — attached to BacktestResult and data_status output.\n */\nexport interface DataQuality {\n /** Average bars per trading day. 390 = dense quotes, 46 = sparse trade bars. */\n avgBarsPerDay: number;\n /** Number of trading days with dense quote data (>= 200 bars). */\n tradesWithQuoteData: number;\n /** Number of trading days with only sparse trade bars (1-199 bars). */\n tradesWithSparseData: number;\n /** Calendar dates where no data was found at all. */\n missingDataDates: string[];\n /** Overall confidence level based on data density and coverage. */\n confidenceLevel: 'high' | 'medium' | 'low';\n}\n\n/**\n * Input to scoreDataQuality — derived from cache queries or trade-level data.\n */\nexport interface DataQualityInput {\n /** Total bars across all trading days. */\n totalBars: number;\n /** Number of trading days in the range with at least some data. */\n tradingDays: number;\n /** Days with >= 200 bars (dense quote coverage). */\n daysWithQuotes: number;\n /** Days with 1-199 bars (sparse trade bar coverage). */\n daysWithSparseData: number;\n /** Days with 0 bars (no data at all). */\n daysWithNoData: number;\n /** Specific dates with no data. */\n missingDates: string[];\n}\n\n/**\n * Per-date bar count breakdown from queryCoverage.\n */\nexport interface DateCoverage {\n date: string;\n barCount: number;\n /** True when the date is covered by the quote store (dense quote data). */\n hasQuotes: boolean;\n}\n\n/**\n * Coverage query result including raw breakdown and a pre-formatted summary.\n */\nexport interface CoverageResult {\n totalBars: number;\n dateBreakdown: DateCoverage[];\n /** Human-readable one-liner summary (e.g., \"SPX: 252 trading days\"). */\n summary: string;\n}\n\n// ---------------------------------------------------------------------------\n// scoreDataQuality (pure)\n// ---------------------------------------------------------------------------\n\n/**\n * Score data quality based on bar density and coverage.\n *\n * Confidence level rules:\n * high — avgBarsPerDay >= 200 AND missingPct <= 5%\n * low — tradingDays == 0 OR avgBarsPerDay < 50 OR missingPct > 10%\n * medium — everything else\n */\nexport function scoreDataQuality(input: DataQualityInput): DataQuality {\n const avgBarsPerDay =\n input.tradingDays > 0\n ? Math.round(input.totalBars / input.tradingDays)\n : 0;\n\n // Total days = tradingDays (days with data) + daysWithNoData\n const totalDaysInRange = input.tradingDays + input.daysWithNoData;\n const missingPct =\n totalDaysInRange > 0 ? input.daysWithNoData / totalDaysInRange : 1;\n\n let confidenceLevel: 'high' | 'medium' | 'low';\n\n if (input.tradingDays === 0 || avgBarsPerDay < 50) {\n confidenceLevel = 'low';\n } else if (missingPct > 0.10) {\n // More than 10% of calendar trading days missing → cap at low\n confidenceLevel = 'low';\n } else if (avgBarsPerDay >= 200 && missingPct <= 0.05) {\n confidenceLevel = 'high';\n } else {\n confidenceLevel = 'medium';\n }\n\n return {\n avgBarsPerDay,\n tradesWithQuoteData: input.daysWithQuotes,\n tradesWithSparseData: input.daysWithSparseData,\n missingDataDates: input.missingDates,\n confidenceLevel,\n };\n}\n\n// ---------------------------------------------------------------------------\n// queryCoverage (store-backed)\n// ---------------------------------------------------------------------------\n\n/**\n * Aggregate spot + quote store coverage for an underlying over a date range.\n *\n * All reads flow through `stores.spot.getCoverage` and\n * `stores.quote.getCoverage`. The pre-migration LIKE pattern (e.g., 'SPX%') is\n * gone — callers pass the underlying ticker directly. Quote-store coverage\n * already aggregates over every OCC chain under the underlying.\n *\n * @param stores - MarketStores bundle constructed at process startup\n * @param underlying - Underlying ticker (e.g., 'SPX')\n * @param fromDate - Start date 'YYYY-MM-DD' inclusive\n * @param toDate - End date 'YYYY-MM-DD' inclusive\n */\nexport async function queryCoverage(\n stores: MarketStores,\n underlying: string,\n fromDate: string,\n toDate: string,\n): Promise<CoverageResult> {\n // Spot coverage — underlying intraday bars (e.g., SPX index minute bars).\n const spotCov = await stores.spot.getCoverage(underlying, fromDate, toDate);\n\n // Quote coverage — every OCC quote-minute under this underlying. The store\n // returns date-level coverage; \"covered = dense\" in the current model.\n const quoteCov = await stores.quote.getCoverage(underlying, fromDate, toDate);\n\n // Build covered-date set.\n const dates = enumerateCoveredDates(spotCov.earliest, spotCov.latest)\n .concat(enumerateCoveredDates(quoteCov.earliest, quoteCov.latest));\n const uniqueDates = [...new Set(dates)].sort();\n const quoteDateSet = new Set(\n enumerateCoveredDates(quoteCov.earliest, quoteCov.latest),\n );\n\n if (uniqueDates.length === 0) {\n return {\n totalBars: 0,\n dateBreakdown: [],\n summary: `No intraday data found for ${underlying}`,\n };\n }\n\n let totalBars = 0;\n const dateBreakdown: DateCoverage[] = [];\n for (const date of uniqueDates) {\n // Per-date row counts are no longer exposed by the store; treat each\n // covered date as 1 unit of \"bars\" so callers that compute density still\n // see a positive signal. hasQuotes flips when the quote store covers the\n // date (the current \"dense data\" predicate).\n const barCount = 1;\n totalBars += barCount;\n dateBreakdown.push({ date, barCount, hasQuotes: quoteDateSet.has(date) });\n }\n\n const tradingDays = dateBreakdown.length;\n const summary =\n tradingDays === 0\n ? `No data found for ${underlying} between ${fromDate} and ${toDate}`\n : `${underlying}: ${tradingDays} trading days covered ` +\n `(spot=${spotCov.totalDates}, quote=${quoteCov.totalDates})`;\n\n return { totalBars, dateBreakdown, summary };\n}\n\n/**\n * Enumerate the inclusive date range between two ISO dates. Returns [] when\n * either bound is null — coverage probes use null bounds to signal \"no\n * data found\" without throwing. Pure function — no IO.\n */\nfunction enumerateCoveredDates(from: string | null, to: string | null): string[] {\n if (!from || !to) return [];\n const start = new Date(from + \"T00:00:00Z\");\n const end = new Date(to + \"T00:00:00Z\");\n if (start > end) return [];\n const out: string[] = [];\n const cur = new Date(start);\n while (cur <= end) {\n out.push(cur.toISOString().slice(0, 10));\n cur.setUTCDate(cur.getUTCDate() + 1);\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// formatCoverageReport (pure)\n// ---------------------------------------------------------------------------\n\n/** Classify bar count into density label. */\nfunction densityLabel(barCount: number): 'dense' | 'sparse' | 'none' {\n if (barCount >= 200) return 'dense';\n if (barCount > 0) return 'sparse';\n return 'none';\n}\n\n/**\n * Format a human-readable coverage report from queryCoverage results.\n *\n * Groups consecutive dates with similar density into ranges:\n * ```\n * SPX% coverage: 2024-01 through 2025-06\n * 2024-01-02 to 2024-11-29: dense (avg 320 bars/day, quotes available)\n * 2024-12-02 to 2025-03-28: sparse (avg 42 bars/day, trade bars only)\n * 2025-04-01 to 2025-06-30: no data\n * ```\n */\nexport function formatCoverageReport(\n tickerPattern: string,\n coverage: CoverageResult,\n): string {\n if (coverage.dateBreakdown.length === 0) {\n return `No data found for ${tickerPattern}`;\n }\n\n const lines: string[] = [];\n const first = coverage.dateBreakdown[0].date;\n const last = coverage.dateBreakdown[coverage.dateBreakdown.length - 1].date;\n lines.push(`${tickerPattern} coverage: ${first} through ${last}`);\n\n // Group consecutive dates with same density label\n interface Group {\n fromDate: string;\n toDate: string;\n density: 'dense' | 'sparse' | 'none';\n barCounts: number[];\n hasQuotes: boolean;\n }\n\n const groups: Group[] = [];\n let current: Group | null = null;\n\n for (const entry of coverage.dateBreakdown) {\n const density = densityLabel(entry.barCount);\n\n if (!current || current.density !== density) {\n if (current) groups.push(current);\n current = {\n fromDate: entry.date,\n toDate: entry.date,\n density,\n barCounts: [entry.barCount],\n hasQuotes: entry.hasQuotes,\n };\n } else {\n current.toDate = entry.date;\n current.barCounts.push(entry.barCount);\n current.hasQuotes = current.hasQuotes || entry.hasQuotes;\n }\n }\n if (current) groups.push(current);\n\n for (const group of groups) {\n const avgBars =\n group.barCounts.length > 0\n ? Math.round(\n group.barCounts.reduce((s, v) => s + v, 0) / group.barCounts.length,\n )\n : 0;\n\n const rangeStr =\n group.fromDate === group.toDate\n ? group.fromDate\n : `${group.fromDate} to ${group.toDate}`;\n\n let detail: string;\n if (group.density === 'dense') {\n detail = `dense (avg ${avgBars} bars/day, quotes available)`;\n } else if (group.density === 'sparse') {\n detail = `sparse (avg ${avgBars} bars/day, trade bars only)`;\n } else {\n detail = 'no data';\n }\n\n lines.push(` ${rangeStr}: ${detail}`);\n }\n\n return lines.join('\\n');\n}\n","/**\n * Pure TypeScript indicator functions for the market enrichment pipeline.\n *\n * All functions are pure (no DB access, no side effects) and take number arrays\n * or structured inputs returning computed arrays or values.\n *\n * Formulas follow TradingView Pine Script conventions:\n * - RSI: Wilder smoothing seeded with SMA of first period changes\n * - ATR: Wilder smoothing seeded with SMA of first period TR values\n * - EMA: Standard EMA seeded with SMA of first period bars\n * - Realized Vol: Population stddev, annualized by sqrt(252)*100\n *\n * References:\n * - Wilder, J.W. (1978) \"New Concepts in Technical Trading Systems\"\n * - TradingView Pine Script documentation (ta.rsi, ta.atr, ta.ema)\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport { existsSync, readdirSync } from \"fs\";\nimport * as path from \"path\";\nimport { isParquetMode, writeParquetAtomic } from \"../db/parquet-writer.ts\";\nimport { resolveCanonicalMarketFile, resolveMarketDir } from \"../db/market-datasets.ts\";\nimport {\n getEnrichedThrough,\n upsertEnrichedThrough,\n} from \"../db/json-adapters.ts\";\nimport { DEFAULT_MARKET_TICKER } from \"./ticker.ts\";\nimport type { SpotStore } from \"../market/stores/spot-store.ts\";\n\n// =============================================================================\n// Interfaces\n// =============================================================================\n\nexport interface ContextRow {\n date: string;\n VIX_Open?: number | null;\n VIX_Close?: number | null;\n VIX_High?: number | null;\n VIX_RTH_Open?: number | null;\n VIX9D_Open?: number | null;\n VIX9D_Close?: number | null;\n VIX3M_Open?: number | null;\n VIX3M_Close?: number | null;\n}\n\nexport interface EnrichedContextRow extends ContextRow {\n VIX_Gap_Pct?: number | null;\n VIX_Change_Pct?: number | null;\n VIX9D_Change_Pct?: number | null;\n VIX3M_Change_Pct?: number | null;\n VIX9D_VIX_Ratio?: number | null;\n VIX_VIX3M_Ratio?: number | null;\n VIX_Spike_Pct?: number | null;\n Vol_Regime?: number | null;\n Term_Structure_State?: number | null;\n VIX_IVR?: number | null;\n VIX_IVP?: number | null;\n VIX9D_IVR?: number | null;\n VIX9D_IVP?: number | null;\n VIX3M_IVR?: number | null;\n VIX3M_IVP?: number | null;\n}\n\n// =============================================================================\n// Primitive Indicators\n// =============================================================================\n\n/**\n * Wilder's RSI (the standard formulation).\n * Input: closing prices ordered oldest→newest.\n * Returns array same length as input; first `period` entries are NaN (warmup).\n *\n * Formula:\n * - Seed avgGain/avgLoss from SMA of first `period` changes (bars 1..period)\n * - result[period] = 100 - 100/(1 + avgGain/avgLoss)\n * - Subsequent: avgGain = (prev*(period-1) + gain)/period (Wilder smoothing)\n *\n * Empirical OO calibration (9-23-dc, 37 RSI-failing missing dates): Wilder\n * smoothing reproduces OO's filter passes on 33/37 (89%) vs SMA's 13/37 (35%).\n * Earlier comments in this file claimed OO used SMA — that was incorrect.\n */\nexport function computeRSI(closes: number[], period = 14): number[] {\n const result = new Array<number>(closes.length).fill(NaN);\n if (closes.length < period + 1) return result;\n\n // Seed avgGain/avgLoss from SMA of the first `period` changes.\n let avgGain = 0;\n let avgLoss = 0;\n for (let i = 1; i <= period; i++) {\n const change = closes[i] - closes[i - 1];\n if (change > 0) avgGain += change;\n else avgLoss += -change;\n }\n avgGain /= period;\n avgLoss /= period;\n result[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n\n // Wilder smoothing: avg = (prev*(period-1) + current) / period\n for (let i = period + 1; i < closes.length; i++) {\n const change = closes[i] - closes[i - 1];\n const gain = change > 0 ? change : 0;\n const loss = change < 0 ? -change : 0;\n avgGain = (avgGain * (period - 1) + gain) / period;\n avgLoss = (avgLoss * (period - 1) + loss) / period;\n result[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n }\n\n return result;\n}\n\n/**\n * Wilder's Average True Range (ATR).\n * Returns array same length as input; first `period` entries are NaN.\n *\n * True Range = max(high - low, |high - prevClose|, |low - prevClose|)\n * TR can be computed from bar index 1 (needs prevClose).\n * First ATR = SMA of TR[1..period] (simple average of first `period` TR values).\n * ATR[i] for i > period: (ATR_prev * (period-1) + TR[i]) / period (Wilder)\n */\nexport function computeATR(\n highs: number[],\n lows: number[],\n closes: number[],\n period = 14\n): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n if (n < period + 1) return result;\n\n // Compute true ranges starting from index 1 (needs prevClose)\n const tr = new Array<number>(n).fill(NaN);\n for (let i = 1; i < n; i++) {\n const prevClose = closes[i - 1];\n tr[i] = Math.max(\n highs[i] - lows[i],\n Math.abs(highs[i] - prevClose),\n Math.abs(lows[i] - prevClose)\n );\n }\n\n // First ATR = SMA of TR[1..period]\n let atrSum = 0;\n for (let i = 1; i <= period; i++) {\n atrSum += tr[i];\n }\n let atr = atrSum / period;\n result[period] = atr;\n\n // Wilder smoothing for subsequent bars\n for (let i = period + 1; i < n; i++) {\n atr = (atr * (period - 1) + tr[i]) / period;\n result[i] = atr;\n }\n\n return result;\n}\n\n/**\n * Exponential Moving Average (EMA) with SMA seed (TradingView convention).\n * Returns array same length as input; first `period-1` entries are NaN.\n *\n * Seed: EMA[period-1] = SMA of first `period` bars\n * k = 2 / (period + 1)\n * EMA[i] = close[i] * k + EMA[i-1] * (1 - k)\n */\nexport function computeEMA(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n if (n < period) return result;\n\n // Seed from SMA of first period bars\n let seed = 0;\n for (let i = 0; i < period; i++) {\n seed += closes[i];\n }\n seed /= period;\n result[period - 1] = seed;\n\n const k = 2 / (period + 1);\n for (let i = period; i < n; i++) {\n result[i] = closes[i] * k + result[i - 1] * (1 - k);\n }\n\n return result;\n}\n\n/**\n * Simple Moving Average (SMA).\n * Returns array same length as input; first `period-1` entries are NaN.\n * SMA[i] = average of closes[i-period+1..i]\n */\nexport function computeSMA(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n\n for (let i = period - 1; i < n; i++) {\n let sum = 0;\n for (let j = i - period + 1; j <= i; j++) {\n sum += closes[j];\n }\n result[i] = sum / period;\n }\n\n return result;\n}\n\n// =============================================================================\n// Composite Indicators\n// =============================================================================\n\n/**\n * Realized Volatility using log returns, population stddev, annualized.\n * Returns array same length as input; first `period` entries are NaN\n * (need period+1 closes to compute period log returns).\n *\n * log_return[i] = ln(close[i] / close[i-1])\n * Vol[i] = stddev(log_returns[i-period+1..i], N) * sqrt(252) * 100\n */\nexport function computeRealizedVol(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n\n // Compute log returns (one less than closes count)\n const logReturns = new Array<number>(n).fill(NaN);\n for (let i = 1; i < n; i++) {\n logReturns[i] = Math.log(closes[i] / closes[i - 1]);\n }\n\n // Rolling stddev of log returns over `period` window\n // First valid: index = period (window uses log returns at i-period+1..i, earliest is i=period)\n for (let i = period; i < n; i++) {\n const window: number[] = [];\n for (let j = i - period + 1; j <= i; j++) {\n window.push(logReturns[j]);\n }\n\n const mean = window.reduce((a, b) => a + b, 0) / period;\n // Population stddev\n const variance = window.reduce((sum, v) => sum + (v - mean) ** 2, 0) / period;\n result[i] = Math.sqrt(variance) * Math.sqrt(252) * 100;\n }\n\n return result;\n}\n\n// =============================================================================\n// Row-Level Helpers\n// =============================================================================\n\n/**\n * Consecutive up/down days counter.\n * Positive = consecutive up days, negative = consecutive down days.\n * Resets to 0 on flat day.\n * First element is always 0 (no prior bar).\n */\nexport function computeConsecutiveDays(closes: number[]): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(0);\n\n for (let i = 1; i < n; i++) {\n if (closes[i] > closes[i - 1]) {\n // Up day: continue positive streak or start at +1\n result[i] = result[i - 1] >= 0 ? result[i - 1] + 1 : 1;\n } else if (closes[i] < closes[i - 1]) {\n // Down day: continue negative streak or start at -1\n result[i] = result[i - 1] <= 0 ? result[i - 1] - 1 : -1;\n } else {\n // Flat: reset to 0\n result[i] = 0;\n }\n }\n\n return result;\n}\n\n/**\n * Gap filled indicator.\n * Returns 1 if the price gap from prior close was filled intraday, 0 otherwise.\n *\n * Gap up (open > priorClose): filled if low <= priorClose\n * Gap down (open < priorClose): filled if high >= priorClose\n * No gap (open = priorClose): returns 0\n */\nexport function isGapFilled(\n open: number,\n high: number,\n low: number,\n priorClose: number\n): number {\n if (open > priorClose && low <= priorClose) return 1;\n if (open < priorClose && high >= priorClose) return 1;\n return 0;\n}\n\n/**\n * Options expiration (OPEX) detection.\n * Takes a YYYY-MM-DD string; returns 1 if 3rd Friday of month, 0 otherwise.\n *\n * Uses string parsing (not new Date(\"YYYY-MM-DD\")) to avoid timezone issues.\n * See CLAUDE.md Date Handling rules: calendar dates from CSVs use local Date constructor.\n */\nexport function isOpex(dateStr: string): number {\n // Parse via regex to avoid timezone issues (CLAUDE.md: use string parsing)\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(dateStr);\n if (!match) return 0;\n\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) - 1; // 0-indexed for Date constructor\n const day = parseInt(match[3], 10);\n\n // Use local Date constructor (avoids UTC midnight shift)\n // Check if this day is a Friday (getDay() === 5)\n const date = new Date(year, month, day);\n if (date.getDay() !== 5) return 0;\n\n // Find first Friday of month\n const firstDay = new Date(year, month, 1);\n const firstFridayDay = ((5 - firstDay.getDay() + 7) % 7) + 1; // day of month\n\n // Third Friday = first Friday + 14\n const thirdFriday = firstFridayDay + 14;\n\n return day === thirdFriday ? 1 : 0;\n}\n\n// =============================================================================\n// Tier 2 VIX Functions\n// =============================================================================\n\n/**\n * Compute VIX-derived fields for market.enriched_context rows.\n * Takes sorted context rows (oldest first) with VIX OHLCV data.\n * Returns enriched rows with pct change, ratio, and spike fields.\n *\n * Fields requiring prior row (NaN on first row):\n * - VIX_Gap_Pct: (VIX_Open - prior VIX_Close) / prior VIX_Close * 100\n * - VIX_Change_Pct: (VIX_Close - prior VIX_Close) / prior VIX_Close * 100\n * - VIX9D_Change_Pct: (VIX9D_Close - VIX9D_Open) / VIX9D_Open * 100\n * - VIX3M_Change_Pct: (VIX3M_Close - VIX3M_Open) / VIX3M_Open * 100\n *\n * Same-day fields (no lookback needed):\n * - VIX9D_VIX_Ratio: VIX9D_Close / VIX_Close\n * - VIX_VIX3M_Ratio: VIX_Close / VIX3M_Close\n * - VIX_Spike_Pct: (VIX_High - VIX_Open) / VIX_Open * 100\n */\nexport function computeVIXDerivedFields(rows: ContextRow[]): EnrichedContextRow[] {\n return rows.map((row, i): EnrichedContextRow => {\n const prev = i > 0 ? rows[i - 1] : null;\n\n // Effective open: prefer RTH open from intraday bars, fall back to daily VIX_Open\n const effectiveOpen = row.VIX_RTH_Open ?? row.VIX_Open;\n\n // Same-day ratio and spike fields\n const VIX9D_VIX_Ratio =\n row.VIX9D_Close != null && row.VIX_Close != null && row.VIX_Close !== 0\n ? row.VIX9D_Close / row.VIX_Close\n : null;\n\n const VIX_VIX3M_Ratio =\n row.VIX_Close != null && row.VIX3M_Close != null && row.VIX3M_Close !== 0\n ? row.VIX_Close / row.VIX3M_Close\n : null;\n\n const VIX_Spike_Pct =\n row.VIX_High != null && effectiveOpen != null && effectiveOpen !== 0\n ? ((row.VIX_High - effectiveOpen) / effectiveOpen) * 100\n : null;\n\n // Intraday change fields (same-day open to close)\n const VIX9D_Change_Pct =\n row.VIX9D_Close != null && row.VIX9D_Open != null && row.VIX9D_Open !== 0\n ? ((row.VIX9D_Close - row.VIX9D_Open) / row.VIX9D_Open) * 100\n : null;\n\n const VIX3M_Change_Pct =\n row.VIX3M_Close != null && row.VIX3M_Open != null && row.VIX3M_Open !== 0\n ? ((row.VIX3M_Close - row.VIX3M_Open) / row.VIX3M_Open) * 100\n : null;\n\n // Prior-row dependent fields\n const prevVIXClose = prev?.VIX_Close ?? null;\n\n const VIX_Gap_Pct =\n effectiveOpen != null && prevVIXClose != null && prevVIXClose !== 0\n ? ((effectiveOpen - prevVIXClose) / prevVIXClose) * 100\n : null;\n\n const VIX_Change_Pct =\n row.VIX_Close != null && prevVIXClose != null && prevVIXClose !== 0\n ? ((row.VIX_Close - prevVIXClose) / prevVIXClose) * 100\n : null;\n\n return {\n ...row,\n VIX_Gap_Pct,\n VIX_Change_Pct,\n VIX9D_Change_Pct,\n VIX3M_Change_Pct,\n VIX9D_VIX_Ratio,\n VIX_VIX3M_Ratio,\n VIX_Spike_Pct,\n };\n });\n}\n\n/**\n * Classify trend direction based on 20-day return percentage.\n *\n * Uses Return_20D thresholds:\n * > 1% = \"up\"\n * < -1% = \"down\"\n * else = \"flat\" (between -1% and 1% inclusive)\n *\n * Returns null for null/NaN input (no Return_20D data available).\n */\nexport function classifyTrendDirection(return20d: number | null): string | null {\n if (return20d === null || return20d === undefined || isNaN(return20d)) return null;\n if (return20d > 1) return \"up\";\n if (return20d < -1) return \"down\";\n return \"flat\";\n}\n\n/**\n * Classify VIX level into volatility regime 1-6.\n *\n * 1: Very Low VIX < 13\n * 2: Low 13 <= VIX < 16\n * 3: Normal 16 <= VIX < 20\n * 4: Elevated 20 <= VIX < 25\n * 5: High 25 <= VIX < 30\n * 6: Extreme VIX >= 30\n */\nexport function classifyVolRegime(vixClose: number): number {\n if (vixClose < 13) return 1;\n if (vixClose < 16) return 2;\n if (vixClose < 20) return 3;\n if (vixClose < 25) return 4;\n if (vixClose < 30) return 5;\n return 6;\n}\n\n/**\n * Classify VIX term structure state.\n * Returns:\n * 1 = Contango: VIX9D < VIX and VIX < VIX3M (normal, longer-dated vol is higher)\n * -1 = Backwardation: VIX9D > VIX or VIX > VIX3M (inverted — fear in front)\n * 0 = Flat: all three within ~1% tolerance of each other\n *\n * Flatness check: both ratios VIX9D/VIX and VIX/VIX3M within 1% of 1.0\n */\nexport function classifyTermStructure(\n vix9dClose: number,\n vixClose: number,\n vix3mClose: number\n): number {\n // Match PineScript: vix9dClose > vixClose ? -1 : vixClose > vix3mClose ? 0 : 1\n if (vix9dClose > vixClose) return -1;\n if (vixClose > vix3mClose) return 0;\n return 1;\n}\n\n/**\n * Implied Volatility Rank (IVR) over a rolling window.\n * IVR[i] = (current - min) / (max - min) * 100\n * Returns array same length as input; first `period-1` entries are NaN.\n * Shows where the current value sits in its 252-day range.\n * When range is 0 (all values identical), returns 50 (middle).\n */\nexport function computeIVR(values: number[], period = 252): number[] {\n const n = values.length;\n const result = new Array<number>(n).fill(NaN);\n for (let i = period - 1; i < n; i++) {\n let min = Infinity, max = -Infinity;\n for (let j = i - period + 1; j <= i; j++) {\n if (values[j] < min) min = values[j];\n if (values[j] > max) max = values[j];\n }\n const range = max - min;\n result[i] = range > 0 ? ((values[i] - min) / range) * 100 : 50;\n }\n return result;\n}\n\n/**\n * Implied Volatility Percentile (IVP) over a rolling window.\n * IVP[i] = count(prior 251 days where value <= current) / 251 * 100\n * Returns array same length as input; first `period-1` entries are NaN.\n * Shows what percentage of the past year was at or below the current value.\n * Note: divides by (period - 1) = 251 because we compare against prior days only.\n */\nexport function computeIVP(values: number[], period = 252): number[] {\n const n = values.length;\n const result = new Array<number>(n).fill(NaN);\n for (let i = period - 1; i < n; i++) {\n let countLessOrEqual = 0;\n // Compare current against prior (period-1) days (not including current day itself)\n for (let j = i - period + 1; j < i; j++) {\n if (values[j] <= values[i]) countLessOrEqual++;\n }\n result[i] = (countLessOrEqual / (period - 1)) * 100;\n }\n return result;\n}\n\n// =============================================================================\n// Enrichment Runner Types\n// =============================================================================\n\nexport interface EnrichmentOptions {\n forceFull?: boolean;\n dataDir?: string; // Required in Parquet mode for file paths\n parquetMode?: boolean;\n}\n\n/**\n * IO abstraction for runEnrichment.\n *\n * When provided, routes specific IO operations through injected stores:\n * - spotStore: Tier 2 VIX RTH open, Tier 3 hasData check, Tier 3 minute bars\n * - watermarkStore: read/write the enrichment watermark via the JSON adapter\n *\n * The legacy SQL watermark path on the metadata sync table is gone. When\n * `io.watermarkStore` is not supplied, the runner falls back to\n * `getEnrichedThrough` / `upsertEnrichedThrough` (the same JSON adapter the\n * store wrappers wire) directly using `opts.dataDir`. The fallback is kept\n * optional to support transitional callers (e.g. market-importer\n * `triggerEnrichment`) that have not been refactored to construct an IO.\n */\nexport interface EnrichmentIO {\n spotStore?: SpotStore;\n watermarkStore?: {\n get(ticker: string): Promise<string | null>;\n upsert(ticker: string, value: string): Promise<void>;\n };\n}\n\nexport interface TierStatus {\n status: \"complete\" | \"skipped\" | \"error\";\n fieldsWritten?: number;\n reason?: string;\n}\n\nexport interface EnrichmentResult {\n ticker: string;\n tier1: TierStatus;\n tier2: TierStatus;\n tier3: TierStatus;\n rowsEnriched: number;\n enrichedThrough: string | null;\n}\n\n// =============================================================================\n// Enrichment Runner Private Helpers\n// =============================================================================\n\n/** Subtract N calendar days from a YYYY-MM-DD string, returns YYYY-MM-DD */\nfunction subtractDays(dateStr: string, days: number): string {\n const d = new Date(dateStr + \"T00:00:00Z\");\n d.setUTCDate(d.getUTCDate() - days);\n return d.toISOString().split(\"T\")[0];\n}\n\n/** Parse YYYY-MM-DD to a local Date without timezone conversion */\nfunction parseDateStr(dateStr: string): Date | null {\n const m = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (!m) return null;\n return new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3]));\n}\n\n// =============================================================================\n// Parquet Enrichment Helpers\n// =============================================================================\n\n/**\n * All enrichment columns that may be written to the daily working table.\n * When a Parquet file from a fresh import lacks these columns, they must be\n * added via ALTER TABLE so that UPDATE statements don't fail.\n */\nconst DAILY_ENRICHMENT_COLUMNS: Array<{ name: string; type: string }> = [\n // Tier 1\n { name: \"Prior_Close\", type: \"DOUBLE\" },\n { name: \"Gap_Pct\", type: \"DOUBLE\" },\n { name: \"ATR_Pct\", type: \"DOUBLE\" },\n { name: \"RSI_14\", type: \"DOUBLE\" },\n { name: \"Price_vs_EMA21_Pct\", type: \"DOUBLE\" },\n { name: \"Price_vs_SMA50_Pct\", type: \"DOUBLE\" },\n { name: \"Realized_Vol_5D\", type: \"DOUBLE\" },\n { name: \"Realized_Vol_20D\", type: \"DOUBLE\" },\n { name: \"Return_5D\", type: \"DOUBLE\" },\n { name: \"Return_20D\", type: \"DOUBLE\" },\n { name: \"Intraday_Range_Pct\", type: \"DOUBLE\" },\n { name: \"Intraday_Return_Pct\", type: \"DOUBLE\" },\n { name: \"Close_Position_In_Range\", type: \"DOUBLE\" },\n { name: \"Gap_Filled\", type: \"INTEGER\" },\n { name: \"Consecutive_Days\", type: \"INTEGER\" },\n { name: \"Prev_Return_Pct\", type: \"DOUBLE\" },\n { name: \"Prior_Range_vs_ATR\", type: \"DOUBLE\" },\n // Tier 3 intraday timing\n { name: \"High_Time\", type: \"DOUBLE\" },\n { name: \"Low_Time\", type: \"DOUBLE\" },\n { name: \"High_Before_Low\", type: \"INTEGER\" },\n { name: \"Reversal_Type\", type: \"INTEGER\" },\n { name: \"Opening_Drive_Strength\", type: \"DOUBLE\" },\n { name: \"Intraday_Realized_Vol\", type: \"DOUBLE\" },\n // Calendar\n { name: \"Day_of_Week\", type: \"INTEGER\" },\n { name: \"Month\", type: \"INTEGER\" },\n { name: \"Is_Opex\", type: \"INTEGER\" },\n // IVR/IVP\n { name: \"ivr\", type: \"DOUBLE\" },\n { name: \"ivp\", type: \"DOUBLE\" },\n];\n\n/**\n * Ensure all enrichment columns exist in the working temp table.\n * Parquet files from fresh imports only contain OHLCV columns; enrichment\n * adds computed columns via UPDATE, which requires the columns to exist.\n */\nasync function alignDailyWorkingTableColumns(\n conn: DuckDBConnection,\n tableName: string,\n): Promise<void> {\n for (const col of DAILY_ENRICHMENT_COLUMNS) {\n try {\n await conn.run(`ALTER TABLE \"${tableName}\" ADD COLUMN \"${col.name}\" ${col.type}`);\n } catch {\n // Column already exists — ignore\n }\n }\n}\n\n/**\n * In Parquet mode, create working temp tables from Parquet files.\n * The enricher operates on these temp tables, then copies back to Parquet.\n * Uses a timestamp suffix for uniqueness — no user input in table names.\n *\n * Seed source priority (in order of preference):\n * 1. Legacy `daily.parquet` / `date_context.parquet` — the pre-migration\n * single-file layout, still supported for data roots that have not yet\n * been rebuilt.\n * 2. New `enriched/ticker=*\\/data.parquet` + `enriched/context/data.parquet`\n * — the canonical per-ticker enriched layout. The working table is\n * seeded from a UNION ALL across the existing per-ticker files; OHLCV\n * columns are NULL in the seed (the working table only needs OHLCV\n * for legacy callers without io.spotStore; Tier 2 with io.spotStore\n * reads VIX OHLCV from a separate temp seeded from spot/, so SPX\n * historical Return_20D is the only enrichment field the SPX JOIN\n * actually needs from the working table — and that lives in\n * enriched/ticker=SPX/data.parquet).\n * 3. Empty fallback (`market.enriched WHERE 1=0`) when neither source\n * exists — preserves fresh-clone behavior unchanged.\n */\nasync function setupParquetWorkingTables(\n conn: DuckDBConnection,\n dataDir: string,\n): Promise<{ dailyTable: string; dateContextTable: string }> {\n const ts = Date.now();\n const dailyTable = `_enrich_daily_${ts}`;\n const dateContextTable = `_enrich_date_context_${ts}`;\n\n const dailyPath = resolveCanonicalMarketFile(dataDir, \"daily\");\n const dateContextPath = resolveCanonicalMarketFile(dataDir, \"date_context\");\n const enrichedDir = path.join(resolveMarketDir(dataDir), \"enriched\");\n const enrichedTickerGlob = path.join(enrichedDir, \"ticker=*\", \"data.parquet\");\n const enrichedContextPath = path.join(enrichedDir, \"context\", \"data.parquet\");\n\n // ---- Daily working table seed ---------------------------------------------\n if (existsSync(dailyPath)) {\n // Legacy single-file seed\n await conn.run(`CREATE TEMP TABLE \"${dailyTable}\" AS SELECT * FROM read_parquet('${dailyPath}')`);\n // Parquet files from fresh imports may lack enrichment columns — add them\n await alignDailyWorkingTableColumns(conn, dailyTable);\n } else if (hasEnrichedTickerFiles(enrichedDir)) {\n // Per-ticker seed: union the existing enriched/ticker=*/data.parquet files.\n // These contain (ticker, date, 28 enrichment cols) — no OHLCV. We add NULL\n // OHLCV columns via ALTER TABLE below so that:\n // - Callers without io.spotStore reading OHLCV from the working table get\n // schema-compatible NULLs rather than a SQL error.\n // - The io.spotStore canonical path reads OHLCV from spot/ directly and\n // never touches the working table's OHLCV columns.\n // - The Tier 2 SPX JOIN reads Return_20D (enrichment, already present\n // from the seed) from the working table — the SPX JOIN does NOT use\n // OHLCV.\n await conn.run(\n `CREATE TEMP TABLE \"${dailyTable}\" AS\n SELECT * FROM read_parquet('${enrichedTickerGlob}', hive_partitioning=true)`,\n );\n for (const ohlcv of [\"open\", \"high\", \"low\", \"close\"]) {\n try {\n await conn.run(`ALTER TABLE \"${dailyTable}\" ADD COLUMN \"${ohlcv}\" DOUBLE`);\n } catch {\n // Column already exists (should not happen for enriched/ files) — ignore\n }\n }\n // alignDailyWorkingTableColumns is a near-no-op in this branch because the\n // seed already projects the full enrichment schema. Run it for safety (each\n // ALTER TABLE ADD COLUMN is wrapped in try/catch, so idempotent).\n await alignDailyWorkingTableColumns(conn, dailyTable);\n } else {\n // Fresh-clone seed path. The legacy daily-view no longer exists in the\n // catalog; seed the working table from `market.enriched` (the canonical\n // per-ticker computed-fields view) and ALTER-ADD the OHLCV columns the\n // Tier 1 math expects. Matches the shape used by the\n // enriched-ticker-files branch above.\n await conn.run(\n `CREATE TEMP TABLE \"${dailyTable}\" AS SELECT * FROM market.enriched WHERE 1=0`,\n );\n for (const ohlcv of [\"open\", \"high\", \"low\", \"close\"]) {\n try {\n await conn.run(`ALTER TABLE \"${dailyTable}\" ADD COLUMN \"${ohlcv}\" DOUBLE`);\n } catch {\n // Column already exists — ignore\n }\n }\n await alignDailyWorkingTableColumns(conn, dailyTable);\n }\n\n // Backfill missing (ticker, date) identity rows from market.spot_daily so\n // batchUpdateDaily has rows to UPDATE. Applies to ALL seed paths above:\n // - Legacy daily.parquet branch: any new (ticker, date) in\n // market.spot_daily that isn't in the seed needs to be inserted before\n // enrichment. Usually a no-op when inventories already agree.\n // - Per-ticker enriched-files branch: the seed only contains tickers with\n // an enriched/ticker=X/data.parquet file. Tickers that have spot data\n // but no enriched file yet (e.g. after a partial re-enrichment delete)\n // would otherwise be missed.\n // - Fresh branch: the working table is empty, so every (ticker, date) in\n // market.spot_daily is new.\n //\n // Without this backfill, UPDATE ... WHERE (ticker, date) matches 0 rows and\n // the enricher silently writes an empty enriched/ticker=X/data.parquet\n // file — corrupting historical enrichment on the first run after\n // enriched/ is deleted. OHLCV columns stay NULL (io.spotStore is the\n // canonical OHLCV source; the Tier 2 SPX JOIN uses enrichment fields,\n // not OHLCV).\n try {\n // CAST date to VARCHAR — market.spot_daily.date is inferred as DATE by\n // DuckDB (hive partition type inference); the working table's date column\n // is VARCHAR (per physical market.enriched fallback schema).\n // strftime produces 'YYYY-MM-DD' which matches the partition value format.\n // ANTI-JOIN: only INSERT (ticker,date) pairs that don't already exist in\n // the working table, preserving any prior enrichment data in the seed.\n await conn.run(\n `INSERT INTO \"${dailyTable}\" (ticker, date)\n SELECT s.ticker, strftime(s.date, '%Y-%m-%d') AS d\n FROM market.spot_daily s\n WHERE NOT EXISTS (\n SELECT 1 FROM \"${dailyTable}\" t\n WHERE t.ticker = s.ticker\n AND t.date = strftime(s.date, '%Y-%m-%d')\n )`,\n );\n } catch {\n // market.spot_daily absent (truly-fresh clone before any spot data) —\n // leave the working table empty; enrichment will be a no-op in that case.\n }\n\n // ---- Date-context working table seed -------------------------------------\n if (existsSync(dateContextPath)) {\n // Legacy single-file seed\n await conn.run(`CREATE TEMP TABLE \"${dateContextTable}\" AS SELECT * FROM read_parquet('${dateContextPath}')`);\n } else if (existsSync(enrichedContextPath)) {\n // Seed from the per-ticker enriched/context/data.parquet file\n await conn.run(\n `CREATE TEMP TABLE \"${dateContextTable}\" AS SELECT * FROM read_parquet('${enrichedContextPath}')`,\n );\n } else {\n await conn.run(`CREATE TEMP TABLE \"${dateContextTable}\" (\n date VARCHAR, Vol_Regime VARCHAR, Term_Structure_State VARCHAR,\n Trend_Direction VARCHAR, VIX_Spike_Pct DOUBLE, VIX_Gap_Pct DOUBLE\n )`);\n }\n // INSERT OR REPLACE in runTier2 needs a UNIQUE/PRIMARY KEY on `date`. CREATE\n // TABLE AS SELECT does not carry over PK constraints from Parquet (Parquet has\n // no constraints), so attach one explicitly here.\n await conn.run(`CREATE UNIQUE INDEX \"idx_${dateContextTable}_date\" ON \"${dateContextTable}\"(date)`);\n\n // Same rationale for the daily working table: batchUpdateDaily uses\n // INSERT OR REPLACE so first-time enrichment of a ticker (whose seed\n // contains zero rows for that ticker) populates the working table from\n // computed values rather than silently no-op-ing on UPDATE.\n await conn.run(`CREATE UNIQUE INDEX \"idx_${dailyTable}_pk\" ON \"${dailyTable}\"(ticker, date)`);\n\n return { dailyTable, dateContextTable };\n}\n\n/**\n * True if `<dir>/ticker=<X>/data.parquet` exists for at least one ticker.\n * Mirrors the helper of the same name in db/market-views.ts; copied locally to\n * avoid pulling the view layer as a dependency of the enricher.\n */\nfunction hasEnrichedTickerFiles(dir: string): boolean {\n if (!existsSync(dir)) return false;\n try {\n return readdirSync(dir).some((entry: string) => {\n if (!entry.startsWith(\"ticker=\")) return false;\n return existsSync(path.join(dir, entry, \"data.parquet\"));\n });\n } catch {\n return false;\n }\n}\n\n/**\n * Write working-table contents to the `enriched/` partition layout —\n * `enriched/ticker={ticker}/data.parquet` for per-ticker enrichment columns\n * and `enriched/context/data.parquet` for the cross-ticker context.\n *\n * This function does NOT write `daily.parquet` or `date_context.parquet`;\n * those legacy single-file outputs are retired. The `market.enriched` and\n * `market.enriched_context` views are the canonical read surfaces over the\n * per-ticker enriched layout.\n *\n * Storage split: the per-ticker enriched file contains ONLY computed\n * enrichment columns plus (ticker, date) — NO OHLCV. Raw OHLCV stays in\n * spot/. The context file contains the cross-ticker derived fields written\n * by runTier2 (Vol_Regime, Term_Structure_State, Trend_Direction,\n * VIX_Spike_Pct, VIX_Gap_Pct).\n *\n * Filtered to `WHERE ticker = $ticker` so each per-ticker enrichment call only\n * touches its own partition. Other tickers' rows in the working table\n * (carried over from the legacy seed) are not republished here.\n *\n * Paths constructed from dataDir + hardcoded suffixes — no user-controlled\n * path components; ticker is whitelisted upstream.\n */\nasync function flushEnrichedToParquet(\n conn: DuckDBConnection,\n dataDir: string,\n ticker: string,\n tables: { dailyTable: string; dateContextTable: string },\n): Promise<void> {\n const enrichedColList = DAILY_ENRICHMENT_COLUMNS.map((c) => `\"${c.name}\"`).join(\", \");\n const tickerFile = path.join(\n resolveMarketDir(dataDir),\n \"enriched\",\n `ticker=${ticker}`,\n \"data.parquet\",\n );\n await writeParquetAtomic(conn, {\n targetPath: tickerFile,\n selectQuery: `SELECT ticker, date, ${enrichedColList} FROM \"${tables.dailyTable}\" WHERE ticker = '${ticker}' ORDER BY date`,\n });\n\n const contextFile = path.join(\n resolveMarketDir(dataDir),\n \"enriched\",\n \"context\",\n \"data.parquet\",\n );\n await writeParquetAtomic(conn, {\n targetPath: contextFile,\n selectQuery: `SELECT * FROM \"${tables.dateContextTable}\" ORDER BY date`,\n });\n\n // NOTE: Working tables are NOT dropped here — cleanup is owned by the finally\n // block in runEnrichment(). This ensures tables survive for error recovery if\n // this function throws partway through (e.g., per-ticker file written but\n // context write fails). The finally block always drops them via DROP IF EXISTS.\n}\n\n/** Batch UPDATE daily table with computed enrichment fields */\nasync function batchUpdateDaily(\n conn: DuckDBConnection,\n rows: Array<Record<string, unknown>>,\n columns: string[],\n tableName: string = \"market.enriched\",\n): Promise<void> {\n if (rows.length === 0) return;\n // Build VALUES list with $N params\n const allCols = [\"ticker\", \"date\", ...columns];\n const placeholders = rows\n .map((_, rowIdx) => {\n const params = allCols.map((__, colIdx) => `$${rowIdx * allCols.length + colIdx + 1}`);\n return `(${params.join(\", \")})`;\n })\n .join(\", \");\n // INSERT OR REPLACE (relies on UNIQUE INDEX on (ticker, date) attached in\n // setupParquetWorkingTables). REPLACE semantics handle the re-enrichment\n // case identically to the prior UPDATE; INSERT semantics are needed for\n // first-time enrichment of a ticker whose seed contains no rows for it\n // (previously silently dropped via the UPDATE no-op).\n const sql = `\n INSERT OR REPLACE INTO ${tableName} (${allCols.join(\", \")})\n VALUES ${placeholders}\n `;\n const params = rows.flatMap((row) => allCols.map((col) => row[col] ?? null));\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n}\n\n/** Run Tier 2: enrich daily (ivr/ivp) and date_context with computed VIX fields */\nasync function runTier2(\n conn: DuckDBConnection,\n targets?: { daily: string; dateContext: string },\n spotStore?: SpotStore,\n): Promise<TierStatus> {\n const dailyTarget = targets?.daily ?? \"market.enriched\";\n const dateContextTarget = targets?.dateContext ?? \"market.enriched_context\";\n\n // When `spotStore` is provided, seed a TEMP table with VIX-family daily\n // OHLCV from spot/ minute bars (aggregated via SpotStore.readDailyBars).\n // The Tier 2 SQL below reads VIX/VIX9D/VIX3M from `effectiveDailyTarget`\n // (the TEMP) instead of the working `dailyTarget` view, which after the\n // legacy daily.parquet retirement no longer contains VIX-family rows.\n //\n // The SPX JOIN (for `Return_20D`) keeps reading from `dailyTarget` because\n // SPX Return_20D is a Tier 1 enriched field written to the working table\n // earlier in the runEnrichment pipeline — spot/ never holds enriched columns.\n //\n // IVR/IVP UPDATEs continue to target `dailyTarget` (legacy write path\n // unchanged). Post-retirement, those writes hit a temp table that is never\n // persisted; the legacy write path is scheduled for removal.\n let effectiveDailyTarget = dailyTarget;\n let vixTempTable: string | null = null;\n if (spotStore) {\n vixTempTable = `_phase5_tier2_daily_${Date.now()}`;\n await conn.run(\n `CREATE TEMP TABLE \"${vixTempTable}\" (ticker VARCHAR, date VARCHAR, open DOUBLE, high DOUBLE, low DOUBLE, close DOUBLE)`,\n );\n for (const vixTicker of [\"VIX\", \"VIX9D\", \"VIX3M\"]) {\n const bars = await spotStore.readDailyBars(vixTicker, \"1970-01-01\", \"9999-12-31\");\n if (bars.length === 0) continue;\n const BATCH_SIZE = 500;\n for (let start = 0; start < bars.length; start += BATCH_SIZE) {\n const batch = bars.slice(start, start + BATCH_SIZE);\n const placeholders = batch\n .map((_, i) => `($${i * 6 + 1},$${i * 6 + 2},$${i * 6 + 3},$${i * 6 + 4},$${i * 6 + 5},$${i * 6 + 6})`)\n .join(\",\");\n const params = batch.flatMap((b) => [b.ticker, b.date, b.open, b.high, b.low, b.close]);\n await conn.run(\n `INSERT INTO \"${vixTempTable}\" VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n }\n }\n effectiveDailyTarget = `\"${vixTempTable}\"`;\n }\n\n try {\n // Step 1: Discover VIX-family tickers dynamically\n const tickerResult = await conn.runAndReadAll(\n `SELECT DISTINCT ticker FROM ${effectiveDailyTarget} WHERE ticker LIKE 'VIX%' ORDER BY ticker`\n );\n const vixTickers = tickerResult.getRows().map(r => r[0] as string);\n if (vixTickers.length === 0 || !vixTickers.includes('VIX')) {\n return { status: \"skipped\", reason: \"no VIX data — import VIX ticker first\" };\n }\n\n // Step 2: Compute IVR/IVP for each VIX-family ticker and write to daily table.\n // `market.enriched` no longer carries OHLCV columns (raw bars live in spot/);\n // read close from the OHLCV source: the spotStore-seeded TEMP table when\n // io.spotStore is present, else `market.spot_daily` (RTH-aggregated view).\n const closeSource = spotStore ? effectiveDailyTarget : \"market.spot_daily\";\n for (const ticker of vixTickers) {\n const closeResult = await conn.runAndReadAll(\n `SELECT date, close FROM ${closeSource} WHERE ticker = $1 AND close IS NOT NULL ORDER BY date ASC`,\n [ticker]\n );\n const rows = closeResult.getRows();\n if (rows.length === 0) continue;\n\n const dates = rows.map(r => r[0] as string);\n const closes = rows.map(r => r[1] as number);\n const ivrValues = computeIVR(closes, 252);\n const ivpValues = computeIVP(closes, 252);\n\n // Batch UPDATE daily table SET ivr, ivp WHERE ticker = ? AND date = ?\n const BATCH_SIZE = 500;\n for (let start = 0; start < dates.length; start += BATCH_SIZE) {\n const batchDates = dates.slice(start, start + BATCH_SIZE);\n const batchIvr = ivrValues.slice(start, start + BATCH_SIZE);\n const batchIvp = ivpValues.slice(start, start + BATCH_SIZE);\n\n const placeholders = batchDates.map((_, rowIdx) => {\n const base = rowIdx * 3;\n return `($${base + 1}, $${base + 2}, $${base + 3})`;\n }).join(\", \");\n\n const sql = `\n UPDATE ${dailyTarget} AS t\n SET ivr = v.ivr, ivp = v.ivp\n FROM (VALUES ${placeholders}) AS v(date, ivr, ivp)\n WHERE t.ticker = $${batchDates.length * 3 + 1} AND t.date = v.date\n `;\n const params: (string | number | null)[] = [];\n for (let i = 0; i < batchDates.length; i++) {\n params.push(batchDates[i]);\n params.push(isNaN(batchIvr[i]) ? null : batchIvr[i]);\n params.push(isNaN(batchIvp[i]) ? null : batchIvp[i]);\n }\n params.push(ticker);\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n }\n }\n\n // Step 3: Build ContextRow objects from daily VIX tickers for derived fields\n // Query VIX close/open/high, VIX9D close/open, VIX3M close/open, plus Return_20D for Trend_Direction.\n //\n // The VIX-family OHLCV source is `effectiveDailyTarget` (the\n // spotStore-seeded TEMP when io.spotStore is present) or\n // `market.spot_daily` when io.spotStore is absent — `market.enriched` no\n // longer carries OHLCV columns. The SPX JOIN keeps `dailyTarget` because\n // Return_20D is a Tier 1 enriched column written to the working table\n // earlier in the runEnrichment pipeline; spot/ never holds enriched fields.\n const vixOhlcvSource = spotStore ? effectiveDailyTarget : \"market.spot_daily\";\n const contextQuery = `\n SELECT\n vix.date,\n vix.open AS VIX_Open,\n vix.close AS VIX_Close,\n vix.high AS VIX_High,\n vix9d.open AS VIX9D_Open,\n vix9d.close AS VIX9D_Close,\n vix3m.open AS VIX3M_Open,\n vix3m.close AS VIX3M_Close,\n spx.Return_20D\n FROM ${vixOhlcvSource} vix\n LEFT JOIN ${vixOhlcvSource} vix9d ON vix9d.date = vix.date AND vix9d.ticker = 'VIX9D'\n LEFT JOIN ${vixOhlcvSource} vix3m ON vix3m.date = vix.date AND vix3m.ticker = 'VIX3M'\n LEFT JOIN ${dailyTarget} spx ON spx.date = vix.date AND spx.ticker = $1\n WHERE vix.ticker = 'VIX' AND vix.close IS NOT NULL\n ORDER BY vix.date ASC\n `;\n\n const rawResult = await conn.runAndReadAll(contextQuery, [DEFAULT_MARKET_TICKER]);\n const rawRows = rawResult.getRows();\n if (rawRows.length === 0) return { status: \"complete\", fieldsWritten: 0 };\n\n // Query VIX RTH open from intraday bars.\n // When spotStore is provided, route through SpotStore.readBars('VIX', ...)\n // and filter to the 09:30–09:32 RTH window in TypeScript. Result is\n // bit-exact: same ticker filter, same time window, same \"first seen per\n // date\" selection (readBars sorts by (date, time)).\n const rthOpenByDate = new Map<string, number>();\n if (spotStore) {\n try {\n const vixBars = await spotStore.readBars(\n \"VIX\",\n rawRows[0][0] as string,\n rawRows[rawRows.length - 1][0] as string,\n );\n for (const bar of vixBars) {\n const timeStr = bar.time;\n if (timeStr == null || timeStr < \"09:30\" || timeStr > \"09:32\") continue;\n // Defense-in-depth: skip 09:30-09:32 bars with zero/null open. A\n // 09:30 provider gap would otherwise cache as the day's VIX_RTH_Open.\n // The first non-zero bar in the window wins.\n if (!Number.isFinite(bar.open) || bar.open <= 0) continue;\n const dateStr = bar.date;\n if (!rthOpenByDate.has(dateStr)) {\n const openVal = bar.open;\n if (openVal != null) rthOpenByDate.set(dateStr, openVal);\n }\n }\n } catch {\n // No intraday VIX data — continue\n }\n } else {\n try {\n // Canonical minute-bar view is `market.spot` — same ticker/time/open\n // schema as the earlier intraday view it replaced.\n // Defense-in-depth: skip zero/null open bars so a 09:30 provider gap\n // doesn't get cached as the day's VIX_RTH_Open. The first non-zero\n // bar in 09:30-09:32 wins.\n const rthReader = await conn.runAndReadAll(\n `SELECT date, open FROM market.spot\n WHERE ticker = 'VIX' AND time >= '09:30' AND time <= '09:32'\n AND open IS NOT NULL AND open > 0\n ORDER BY date, time ASC`\n );\n for (const r of rthReader.getRows()) {\n const dateStr = r[0] as string;\n if (!rthOpenByDate.has(dateStr)) {\n const openVal = r[1] as number | null;\n if (openVal != null && openVal > 0) rthOpenByDate.set(dateStr, openVal);\n }\n }\n } catch {\n // No intraday VIX data — continue\n }\n }\n\n const return20dByDate = new Map<string, number | null>();\n const contextRows: ContextRow[] = rawRows.map((r) => {\n const dateStr = r[0] as string;\n return20dByDate.set(dateStr, r[8] as number | null);\n return {\n date: dateStr,\n VIX_Open: r[1] as number | null,\n VIX_Close: r[2] as number | null,\n VIX_High: r[3] as number | null,\n VIX_RTH_Open: rthOpenByDate.get(dateStr) ?? null,\n VIX9D_Open: r[4] as number | null,\n VIX9D_Close: r[5] as number | null,\n VIX3M_Open: r[6] as number | null,\n VIX3M_Close: r[7] as number | null,\n };\n });\n\n // Step 4: Compute derived fields (reuse existing pure functions unchanged)\n const enrichedContext = computeVIXDerivedFields(contextRows);\n\n // Step 5: Write derived fields to market.enriched_context (INSERT OR REPLACE)\n const derivedCols = [\"date\", \"Vol_Regime\", \"Term_Structure_State\", \"Trend_Direction\", \"VIX_Spike_Pct\", \"VIX_Gap_Pct\"];\n const BATCH_SIZE = 500;\n for (let start = 0; start < enrichedContext.length; start += BATCH_SIZE) {\n const batch = enrichedContext.slice(start, start + BATCH_SIZE);\n const placeholders = batch.map((_, rowIdx) => {\n const params = derivedCols.map((__, colIdx) => `$${rowIdx * derivedCols.length + colIdx + 1}`);\n return `(${params.join(\", \")})`;\n }).join(\", \");\n\n const sql = `INSERT OR REPLACE INTO ${dateContextTarget} (${derivedCols.join(\", \")}) VALUES ${placeholders}`;\n const params = batch.flatMap((r) => {\n const vc = r.VIX_Close ?? null;\n const v9 = r.VIX9D_Close ?? null;\n const v3m = r.VIX3M_Close ?? null;\n return [\n r.date,\n vc !== null ? classifyVolRegime(vc) : null,\n v9 !== null && vc !== null && v3m !== null ? classifyTermStructure(v9, vc, v3m) : null,\n classifyTrendDirection(return20dByDate.get(r.date) ?? null),\n r.VIX_Spike_Pct ?? null,\n r.VIX_Gap_Pct ?? null,\n ];\n });\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n }\n\n return { status: \"complete\", fieldsWritten: derivedCols.length - 1 }; // -1 for date\n } finally {\n // Drop the spotStore-seeded TEMP unconditionally so it cannot leak across\n // runEnrichment calls (each call gets a fresh ts-suffixed table name, but\n // DROP-on-finally keeps DuckDB's TEMP catalog clean).\n if (vixTempTable) {\n try { await conn.run(`DROP TABLE IF EXISTS \"${vixTempTable}\"`); } catch { /* */ }\n }\n }\n}\n\n/**\n * Check if any intraday data exists for a ticker.\n *\n * When `spotStore` is provided, routes through `SpotStore.getCoverage`\n * instead of a SQL probe against `market.spot`.\n */\nasync function hasTier3Data(\n conn: DuckDBConnection,\n ticker: string,\n spotStore?: SpotStore,\n): Promise<boolean> {\n if (spotStore) {\n const cov = await spotStore.getCoverage(ticker, \"1970-01-01\", \"9999-12-31\");\n return cov.totalDates > 0;\n }\n // Canonical minute-bar view is `market.spot` — same ticker-filter schema\n // as the earlier intraday view it replaced.\n const r = await conn.runAndReadAll(\n `SELECT COUNT(*) FROM market.spot WHERE ticker = $1 LIMIT 1`,\n [ticker]\n );\n return Number(r.getRows()[0]?.[0] ?? 0) > 0;\n}\n\n// =============================================================================\n// Context Enrichment (Tier 2 standalone)\n// =============================================================================\n\n/**\n * Run Tier 2 context enrichment directly, computing VIX-derived fields\n * (VIX_Gap_Pct, VIX_Change_Pct, VIX9D_VIX_Ratio, Vol_Regime, etc.) and\n * writing them to market.enriched_context.\n *\n * Used by importFromMassive() for context table imports — after importing\n * VIX/VIX9D/VIX3M bars, Tier 2 needs to run immediately to populate derived\n * fields. Unlike runEnrichment(), this does not require a ticker with daily data.\n *\n * Returns a TierStatus describing the outcome.\n */\nexport async function runContextEnrichment(\n conn: DuckDBConnection,\n targets?: { daily: string; dateContext: string },\n): Promise<TierStatus> {\n return runTier2(conn, targets);\n}\n\n// =============================================================================\n// Enrichment Runner\n// =============================================================================\n\n/**\n * Run all three tiers of market enrichment for a given ticker.\n *\n * Tier 1: Compute and write OHLCV-derived fields to market.enriched using a\n * 200-day lookback window from the persisted watermark.\n * Tier 2: Compute and write VIX-derived fields to market.enriched_context.\n * Tier 3: Compute intraday timing fields (High_Time, Low_Time, High_Before_Low,\n * Reversal_Type, Opening_Drive_Strength, Intraday_Realized_Vol) from\n * market.spot bars; skips gracefully if no intraday data exists.\n *\n * The watermark is upserted via the JSON adapter (`upsertEnrichedThrough` from\n * `db/json-adapters.ts`) — either the supplied `io.watermarkStore` or, when\n * absent, a direct call using `opts.dataDir`. The legacy SQL watermark path\n * on the metadata sync table has been removed.\n *\n * Note: wilder_state column exists but is NOT written (superseded by 200-day lookback).\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param ticker - Normalized ticker symbol (e.g., \"SPX\")\n * @param opts - Options including forceFull (reset watermark and reprocess all rows)\n */\nexport async function runEnrichment(\n conn: DuckDBConnection,\n ticker: string,\n opts: EnrichmentOptions = {},\n io?: EnrichmentIO,\n): Promise<EnrichmentResult> {\n const { forceFull = false } = opts;\n\n // Parquet mode: create working temp tables for all writes\n const parquetMode = (opts.parquetMode ?? isParquetMode()) && !!opts.dataDir;\n let workingTables: { dailyTable: string; dateContextTable: string } | null = null;\n\n if (parquetMode) {\n workingTables = await setupParquetWorkingTables(conn, opts.dataDir!);\n }\n\n // Determine target table names (working tables in Parquet mode, schema-qualified in DuckDB mode)\n const dailyTarget = workingTables ? workingTables.dailyTable : \"market.enriched\";\n // ctxDerivedTarget and ctxTarget are passed via tier2Targets object to runTier2()\n\n try {\n // 1. Get the persisted enrichment watermark.\n // Every watermark read goes through the JSON adapter. The legacy SQL\n // SELECT against the metadata sync table has been removed — when callers\n // don't supply `io.watermarkStore` we fall back to the same JSON adapter\n // the store wrappers wire (`getEnrichedThrough(ticker, dataDir)`).\n let watermark: string | null = null;\n if (!forceFull) {\n if (io?.watermarkStore) {\n watermark = await io.watermarkStore.get(ticker);\n } else if (opts.dataDir) {\n watermark = await getEnrichedThrough(ticker, opts.dataDir);\n } else {\n // No JSON adapter path available without dataDir, and the SQL fallback\n // is gone. Treat as \"no prior watermark\" (fresh enrichment); callers that\n // need watermark continuity must supply io or dataDir.\n watermark = null;\n }\n }\n\n // 2. Compute lookback start: watermark - 200 calendar days (as string comparison)\n const lookbackStart = watermark ? subtractDays(watermark, 200) : null;\n\n // 3. Fetch OHLCV rows.\n //\n // When `io.spotStore` is provided, read daily OHLCV via\n // `SpotStore.readDailyBars` (aggregated from spot/ minute bars). This path\n // remains functional after the legacy `daily.parquet` retirement because\n // readDailyBars aggregates from spot/ticker=X/date=Y/data.parquet.\n //\n // Fallback: when `io.spotStore` is absent (legacy callers), retain a SQL\n // path against `market.spot_daily`. The fallback may be removed once all\n // callers pass io.\n let rawRows: Array<Array<unknown>>;\n if (io?.spotStore) {\n const startDate = lookbackStart ?? \"1970-01-01\";\n const endDate = \"9999-12-31\"; // readDailyBars caps internally via partition discovery\n const dailyBars = await io.spotStore.readDailyBars(ticker, startDate, endDate);\n rawRows = dailyBars.map((b) => [b.ticker, b.date, b.open, b.high, b.low, b.close]);\n } else {\n // The legacy daily-view SQL fallback path is gone — the view no longer\n // exists in the catalog. Route OHLCV reads through the canonical\n // `market.spot_daily` view (RTH-aggregated from `market.spot`). This\n // bridges callers that have not yet migrated to io.spotStore; new\n // callers SHOULD pass io.spotStore for parity with the Parquet-direct path.\n let fetchSql = `SELECT ticker, date, open, high, low, close FROM market.spot_daily WHERE ticker = $1`;\n const fetchParams: unknown[] = [ticker];\n if (lookbackStart) {\n fetchSql += ` AND date >= $2`;\n fetchParams.push(lookbackStart);\n }\n fetchSql += ` ORDER BY date ASC`;\n const rawReader = await conn.runAndReadAll(fetchSql, fetchParams as (string | number | boolean | null | bigint)[]);\n rawRows = rawReader.getRows();\n }\n\n if (rawRows.length === 0) {\n return {\n ticker,\n tier1: {\n status: \"skipped\",\n reason: io?.spotStore ? \"no data from spotStore\" : \"no data in market.spot_daily\",\n },\n tier2: { status: \"skipped\", reason: \"no daily data\" },\n tier3: { status: \"skipped\", reason: \"no daily data\" },\n rowsEnriched: 0,\n enrichedThrough: null,\n };\n }\n\n // 3b. Defensive zero-OHLC filter. Partitions should already be clean after\n // the ParquetSpotStore.writeBars guard, but this second line of defense\n // catches any future provider-outage bleed and prevents\n // RSI/ATR/EMA/SMA/RealizedVol from being poisoned by zero closes. Filter\n // at the rawRows level so date/OHLC alignment is preserved across all\n // five arrays (dates/opens/highs/lows/closes) constructed below.\n const filteredRawRows = rawRows.filter((r) => {\n const o = Number(r[2]);\n const h = Number(r[3]);\n const l = Number(r[4]);\n const c = Number(r[5]);\n return !(o === 0 && h === 0 && l === 0 && c === 0);\n });\n const zeroRowsDropped = rawRows.length - filteredRawRows.length;\n if (zeroRowsDropped > 0) {\n console.warn(\n `[market-enricher] ticker=${ticker} dropped ${zeroRowsDropped} all-zero-OHLC rows before indicator math`,\n );\n }\n rawRows = filteredRawRows;\n\n // 4. Extract typed arrays from raw rows\n // Columns: ticker(0), date(1), open(2), high(3), low(4), close(5)\n const dates = rawRows.map((r) => r[1] as string);\n const opens = rawRows.map((r) => Number(r[2]));\n const highs = rawRows.map((r) => Number(r[3]));\n const lows = rawRows.map((r) => Number(r[4]));\n const closes = rawRows.map((r) => Number(r[5]));\n\n // 5. Compute Tier 1 indicators\n const rsi14 = computeRSI(closes, 14);\n const atrArr = computeATR(highs, lows, closes, 14);\n const ema21 = computeEMA(closes, 21);\n const sma50 = computeSMA(closes, 50);\n const rvol5 = computeRealizedVol(closes, 5);\n const rvol20 = computeRealizedVol(closes, 20);\n const consecutiveDays = computeConsecutiveDays(closes);\n\n // 6. Determine which rows to write back (only rows after watermark)\n const writeRows =\n watermark && !forceFull\n ? rawRows.map((_, i) => i).filter((i) => dates[i] > watermark)\n : rawRows.map((_, i) => i);\n\n if (writeRows.length === 0) {\n const tier2Targets = workingTables ? {\n daily: workingTables.dailyTable,\n dateContext: workingTables.dateContextTable,\n } : undefined;\n const tier2Result = await runTier2(conn, tier2Targets, io?.spotStore);\n\n // Flush even if no Tier 1 rows — Tier 2 may have written to working tables\n if (parquetMode && workingTables && opts.dataDir) {\n await flushEnrichedToParquet(conn, opts.dataDir, ticker, workingTables);\n }\n\n return {\n ticker,\n tier1: { status: \"complete\", fieldsWritten: 0, reason: \"already up to date\" },\n tier2: tier2Result,\n tier3: {\n status: \"skipped\",\n reason: \"no intraday data in market.spot\",\n },\n rowsEnriched: 0,\n enrichedThrough: watermark,\n };\n }\n\n // 7. Build enriched rows for batch UPDATE\n const enrichedRows = writeRows.map((i) => {\n const atrVal = atrArr[i];\n const atrPct = !isNaN(atrVal) && closes[i] > 0 ? (atrVal / closes[i]) * 100 : null;\n const priorClose = i > 0 ? closes[i - 1] : null;\n const priorReturn =\n i > 1 ? ((closes[i - 1] - closes[i - 2]) / closes[i - 2]) * 100 : null;\n const gapPct =\n priorClose !== null && priorClose > 0\n ? ((opens[i] - priorClose) / priorClose) * 100\n : null;\n // Intraday_Range_Pct: high-low range as % of close.\n // Use close (not open) for consistency with every other \"_Pct\" column in\n // this file (ATR_Pct, Price_vs_EMA21_Pct, Return_5D, etc. all divide by\n // close). Also guards against zero-low contamination: if the day's low\n // came in as 0 from a bad minute bar, (high - 0) inflates to ~100% of\n // close — meaningless. Requiring lows[i] > 0 forces such rows to null.\n const intradayRangePct =\n closes[i] > 0 && highs[i] > 0 && lows[i] > 0\n ? ((highs[i] - lows[i]) / closes[i]) * 100\n : null;\n const intradayReturnPct =\n opens[i] > 0 ? ((closes[i] - opens[i]) / opens[i]) * 100 : null;\n const hiLoRange = highs[i] - lows[i];\n const closePosInRange =\n hiLoRange > 0 ? (closes[i] - lows[i]) / hiLoRange : null;\n const ret5d =\n i >= 5 && closes[i - 5] > 0\n ? ((closes[i] - closes[i - 5]) / closes[i - 5]) * 100\n : null;\n const ret20d =\n i >= 20 && closes[i - 20] > 0\n ? ((closes[i] - closes[i - 20]) / closes[i - 20]) * 100\n : null;\n const gapFilled =\n priorClose !== null ? isGapFilled(opens[i], highs[i], lows[i], priorClose) : null;\n const dateObj = parseDateStr(dates[i]);\n const dayOfWeek = dateObj ? dateObj.getDay() : null; // 0=Sun..6=Sat\n const monthVal = dateObj ? dateObj.getMonth() + 1 : null;\n const opex = isOpex(dates[i]);\n const ema21val = ema21[i];\n const sma50val = sma50[i];\n const priceVsEma21 =\n !isNaN(ema21val) && ema21val > 0\n ? ((closes[i] - ema21val) / ema21val) * 100\n : null;\n const priceVsSma50 =\n !isNaN(sma50val) && sma50val > 0\n ? ((closes[i] - sma50val) / sma50val) * 100\n : null;\n const rsi14val = rsi14[i];\n\n // Prior_Range_vs_ATR: ratio of prior day's intraday range (% of close) to\n // prior day's ATR (% of close). Known at market open — prior day range\n // and ATR are both available before today's trading begins.\n //\n // Algebraically (range_pct / atr_pct) = (range / atr) since the close\n // cancels, but writing it as a ratio of percents makes the intent\n // explicit and matches how downstream analysis reads the column.\n //\n // Sanity guards: prior close > 0 (otherwise the percent denominators\n // explode), prior high/low > 0 (catches zero-bar contamination from the\n // upstream spot ingester), and priorATR > 0 (avoid div-by-zero).\n // First bar (i=0) has no prior day → null.\n let priorRangeVsATR: number | null = null;\n if (i > 0) {\n const priorClose = closes[i - 1];\n const priorHigh = highs[i - 1];\n const priorLow = lows[i - 1];\n const priorATR = atrArr[i - 1];\n if (\n priorClose > 0 &&\n priorHigh > 0 &&\n priorLow > 0 &&\n !isNaN(priorATR) && priorATR > 0\n ) {\n const priorRangePct = ((priorHigh - priorLow) / priorClose) * 100;\n const priorAtrPct = (priorATR / priorClose) * 100;\n priorRangeVsATR = priorRangePct / priorAtrPct;\n }\n }\n\n return {\n ticker,\n date: dates[i],\n Prior_Close: priorClose,\n Gap_Pct: gapPct,\n RSI_14: isNaN(rsi14val) ? null : rsi14val,\n ATR_Pct: atrPct,\n Price_vs_EMA21_Pct: priceVsEma21,\n Price_vs_SMA50_Pct: priceVsSma50,\n Realized_Vol_5D: isNaN(rvol5[i]) ? null : rvol5[i],\n Realized_Vol_20D: isNaN(rvol20[i]) ? null : rvol20[i],\n Return_5D: ret5d,\n Return_20D: ret20d,\n Intraday_Range_Pct: intradayRangePct,\n Intraday_Return_Pct: intradayReturnPct,\n Close_Position_In_Range: closePosInRange,\n Gap_Filled: gapFilled,\n Consecutive_Days: consecutiveDays[i],\n Prev_Return_Pct: priorReturn,\n Day_of_Week: dayOfWeek,\n Month: monthVal,\n Is_Opex: opex,\n Prior_Range_vs_ATR: priorRangeVsATR,\n };\n });\n\n // 8. Batch UPDATE via DuckDB VALUES CTE, batches of 500\n const BATCH_SIZE = 500;\n const columns = [\n \"Prior_Close\",\n \"Gap_Pct\",\n \"RSI_14\",\n \"ATR_Pct\",\n \"Price_vs_EMA21_Pct\",\n \"Price_vs_SMA50_Pct\",\n \"Realized_Vol_5D\",\n \"Realized_Vol_20D\",\n \"Return_5D\",\n \"Return_20D\",\n \"Intraday_Range_Pct\",\n \"Intraday_Return_Pct\",\n \"Close_Position_In_Range\",\n \"Gap_Filled\",\n \"Consecutive_Days\",\n \"Prev_Return_Pct\",\n \"Day_of_Week\",\n \"Month\",\n \"Is_Opex\",\n \"Prior_Range_vs_ATR\",\n ];\n for (let start = 0; start < enrichedRows.length; start += BATCH_SIZE) {\n const batch = enrichedRows.slice(start, start + BATCH_SIZE);\n await batchUpdateDaily(conn, batch, columns, dailyTarget);\n }\n\n // 9. Run Tier 2 (VIX context enrichment) with parameterized targets\n const tier2Targets = workingTables ? {\n daily: workingTables.dailyTable,\n dateContext: workingTables.dateContextTable,\n } : undefined;\n const tier2Result = await runTier2(conn, tier2Targets, io?.spotStore);\n\n // 10. Tier 3 — intraday timing fields (routes through io.spotStore when provided)\n const tier3Result = await runTier3(conn, ticker, dates, dailyTarget, io?.spotStore);\n\n // 11. Persist the new watermark.\n // Every watermark write goes through the JSON adapter. The legacy SQL\n // UPSERT against the metadata sync table has been removed — when callers\n // don't supply `io.watermarkStore` we fall back to\n // `upsertEnrichedThrough(ticker, val, dataDir)` directly. If neither io\n // nor dataDir is supplied the watermark simply isn't persisted (math\n // still runs); callers that need watermark continuity must supply one of\n // the two.\n const newWatermark = dates[dates.length - 1];\n if (io?.watermarkStore) {\n await io.watermarkStore.upsert(ticker, newWatermark);\n } else if (opts.dataDir) {\n await upsertEnrichedThrough(ticker, newWatermark, opts.dataDir);\n }\n\n // 12. Parquet mode: write enrichment to the enriched/ partition layout\n // (legacy daily.parquet output retired)\n if (parquetMode && workingTables && opts.dataDir) {\n await flushEnrichedToParquet(conn, opts.dataDir, ticker, workingTables);\n }\n\n return {\n ticker,\n tier1: { status: \"complete\", fieldsWritten: columns.length },\n tier2: tier2Result,\n tier3: tier3Result,\n rowsEnriched: enrichedRows.length,\n enrichedThrough: newWatermark,\n };\n\n } finally {\n // Sole owner of working table cleanup — always runs on success or error.\n // On success: tables still exist (flushParquetWorkingTables does not drop them).\n // On error: tables may contain partial results useful for debugging, but we\n // clean up to avoid leaking temp tables across calls.\n if (workingTables) {\n try { await conn.run(`DROP TABLE IF EXISTS \"${workingTables.dailyTable}\"`); } catch { /* */ }\n try { await conn.run(`DROP TABLE IF EXISTS \"${workingTables.dateContextTable}\"`); } catch { /* */ }\n }\n }\n}\n\n// =============================================================================\n// Tier 3: Intraday Timing Fields\n// =============================================================================\n\n/**\n * Convert HH:MM time string to decimal hours (e.g., \"10:30\" → 10.5).\n */\nfunction hhmmToDecimalHours(time: string): number {\n const [h, m] = time.split(\":\").map(Number);\n return h + m / 60;\n}\n\n/**\n * Compute intraday timing fields from raw OHLCV bars for a single date.\n *\n * Pure function — no DB access. Exported for unit testing.\n *\n * Fields computed:\n * - highTime: Decimal hours when day high occurred (e.g., 10.5 = 10:30)\n * - lowTime: Decimal hours when day low occurred\n * - highBeforeLow: true if high occurred before low\n * - reversalType: +1 = morning high + afternoon low, -1 = morning low + afternoon high, 0 = trend day\n * - openingDriveStrength: (first 30-min range) / (full day range), 0-1 scale; 0 if day range is 0\n * - intradayRealizedVol: Annualized realized vol from intraday bar-to-bar log returns (decimal, not %)\n *\n * @param bars - Array of {time: \"HH:MM\", open, high, low, close} ordered by time (oldest first)\n * @returns Computed fields or null if bars is empty\n */\nexport function computeIntradayTimingFields(\n bars: Array<{ time: string; open: number; high: number; low: number; close: number }>\n): {\n highTime: number;\n lowTime: number;\n highBeforeLow: boolean;\n reversalType: number;\n openingDriveStrength: number;\n intradayRealizedVol: number;\n} | null {\n // Defense-in-depth: drop zero/non-finite OHLC bars before any min/max\n // scan. A single zero-low bar from a provider gap (see ParquetSpotStore\n // writer guard) would make minLow=0 and lowTimeStr point to the gap's\n // timestamp, producing a meaningless Low_Time field. The writer + SQL\n // filters in the spot read paths catch most of these upstream; this\n // makes the pure function safe regardless of caller.\n bars = bars.filter(\n (b) =>\n Number.isFinite(b.open) && b.open > 0 &&\n Number.isFinite(b.high) && b.high > 0 &&\n Number.isFinite(b.low) && b.low > 0 &&\n Number.isFinite(b.close)&& b.close> 0,\n );\n if (bars.length === 0) return null;\n\n let maxHigh = -Infinity;\n let minLow = Infinity;\n let highTimeStr = bars[0].time;\n let lowTimeStr = bars[0].time;\n\n for (const bar of bars) {\n if (bar.high > maxHigh) {\n maxHigh = bar.high;\n highTimeStr = bar.time;\n }\n if (bar.low < minLow) {\n minLow = bar.low;\n lowTimeStr = bar.time;\n }\n }\n\n const highTime = hhmmToDecimalHours(highTimeStr);\n const lowTime = hhmmToDecimalHours(lowTimeStr);\n const highBeforeLow = highTime < lowTime;\n\n // Reversal type: morning = before 12:00, afternoon = 12:00 or later\n const highInMorning = highTime < 12;\n const lowInMorning = lowTime < 12;\n const highInAfternoon = highTime >= 12;\n const lowInAfternoon = lowTime >= 12;\n\n let reversalType = 0;\n if (highInMorning && lowInAfternoon) reversalType = 1; // High morning, low afternoon\n else if (lowInMorning && highInAfternoon) reversalType = -1; // Low morning, high afternoon\n\n // Opening Drive Strength: ratio of first-30-min range to full-day range\n // First 30 min = bars with time < 10:00 (market opens 09:30)\n const openingBars = bars.filter(b => hhmmToDecimalHours(b.time) < 10);\n let openingDriveStrength = 0;\n const fullDayRange = maxHigh - minLow;\n if (openingBars.length > 0 && fullDayRange > 0) {\n const openHigh = Math.max(...openingBars.map(b => b.high));\n const openLow = Math.min(...openingBars.map(b => b.low));\n openingDriveStrength = (openHigh - openLow) / fullDayRange;\n }\n\n // Intraday Realized Vol: annualized from bar-to-bar close log returns\n // Uses sqrt(252 * barsPerDay) annualization\n let intradayRealizedVol = 0;\n if (bars.length >= 2) {\n const logReturns: number[] = [];\n for (let i = 1; i < bars.length; i++) {\n if (bars[i - 1].close > 0 && bars[i].close > 0) {\n logReturns.push(Math.log(bars[i].close / bars[i - 1].close));\n }\n }\n if (logReturns.length > 0) {\n const mean = logReturns.reduce((s, r) => s + r, 0) / logReturns.length;\n const variance = logReturns.reduce((s, r) => s + (r - mean) ** 2, 0) / logReturns.length;\n const barStdDev = Math.sqrt(variance);\n // Annualize: multiply by sqrt(barsPerDay * 252)\n // barsPerDay = number of bars we actually have (adapts to timeframe)\n intradayRealizedVol = barStdDev * Math.sqrt(bars.length * 252);\n }\n }\n\n return { highTime, lowTime, highBeforeLow, reversalType, openingDriveStrength, intradayRealizedVol };\n}\n\n/** Run Tier 3: compute intraday timing fields from market.spot and write to the daily write-target table */\nasync function runTier3(\n conn: DuckDBConnection,\n ticker: string,\n dates: string[],\n dailyTarget: string = \"market.enriched\",\n spotStore?: SpotStore,\n): Promise<TierStatus> {\n // Check if intraday data exists for this ticker\n // Routes through spotStore.getCoverage when provided\n const hasData = await hasTier3Data(conn, ticker, spotStore);\n if (!hasData) {\n return {\n status: \"skipped\",\n reason: \"no intraday data in market.spot — import intraday bars to populate Tier 3 fields\",\n };\n }\n\n // Query intraday bars for all dates in the enrichment range.\n // When spotStore is provided, route through\n // SpotStore.readBars(ticker, from, to). The downstream group-by-date math\n // is unchanged — we just reshape BarRow[] into the same tuple-index shape\n // the existing math consumes (date, time, open, high, low, close).\n let rows: unknown[][];\n let dateIdx: number;\n let timeIdx: number;\n let openIdx: number;\n let highIdx: number;\n let lowIdx: number;\n let closeIdx: number;\n\n if (spotStore) {\n const bars = await spotStore.readBars(ticker, dates[0], dates[dates.length - 1]);\n // Shape into the same tuple-array format the existing math expects below\n rows = bars.map((b) => [b.date, b.time, b.open, b.high, b.low, b.close]);\n dateIdx = 0;\n timeIdx = 1;\n openIdx = 2;\n highIdx = 3;\n lowIdx = 4;\n closeIdx = 5;\n } else {\n // Canonical minute-bar view is `market.spot` — same\n // ticker/date/time/ohlcv schema as the earlier intraday view it replaced.\n // Defense-in-depth: filter out zero/null minute bars at the SQL layer so\n // Tier 3 timing fields (High_Time, Low_Time, Opening_Drive_Strength) are\n // never seeded with provider-gap timestamps.\n const result = await conn.runAndReadAll(\n `SELECT date, time, open, high, low, close\n FROM market.spot\n WHERE ticker = $1 AND date >= $2 AND date <= $3\n AND open IS NOT NULL AND open > 0\n AND high IS NOT NULL AND high > 0\n AND low IS NOT NULL AND low > 0\n AND close IS NOT NULL AND close > 0\n ORDER BY date, time`,\n [ticker, dates[0], dates[dates.length - 1]]\n );\n\n rows = result.getRows();\n const columns = result.columnNames();\n dateIdx = columns.indexOf(\"date\");\n timeIdx = columns.indexOf(\"time\");\n openIdx = columns.indexOf(\"open\");\n highIdx = columns.indexOf(\"high\");\n lowIdx = columns.indexOf(\"low\");\n closeIdx = columns.indexOf(\"close\");\n }\n\n // Group bars by date\n const barsByDate = new Map<string, Array<{ time: string; open: number; high: number; low: number; close: number }>>();\n for (const row of rows) {\n const dateStr = String(row[dateIdx]);\n const bar = {\n time: String(row[timeIdx]),\n open: Number(row[openIdx]),\n high: Number(row[highIdx]),\n low: Number(row[lowIdx]),\n close: Number(row[closeIdx]),\n };\n if (!barsByDate.has(dateStr)) barsByDate.set(dateStr, []);\n barsByDate.get(dateStr)!.push(bar);\n }\n\n if (barsByDate.size === 0) {\n return {\n status: \"skipped\",\n reason: \"intraday data exists but no bars overlap with enrichment date range\",\n };\n }\n\n // Compute timing fields for each date and batch update the enriched table\n const tier3Cols = [\"High_Time\", \"Low_Time\", \"High_Before_Low\", \"Reversal_Type\", \"Opening_Drive_Strength\", \"Intraday_Realized_Vol\"];\n const enrichedRows: Array<Record<string, unknown>> = [];\n\n for (const [dateStr, bars] of barsByDate) {\n const timing = computeIntradayTimingFields(bars);\n if (!timing) continue;\n\n enrichedRows.push({\n ticker,\n date: dateStr,\n High_Time: timing.highTime,\n Low_Time: timing.lowTime,\n High_Before_Low: timing.highBeforeLow ? 1 : 0,\n Reversal_Type: timing.reversalType,\n Opening_Drive_Strength: timing.openingDriveStrength,\n Intraday_Realized_Vol: timing.intradayRealizedVol,\n });\n }\n\n // Batch update using the existing batchUpdateDaily helper\n const BATCH_SIZE = 500;\n for (let start = 0; start < enrichedRows.length; start += BATCH_SIZE) {\n const batch = enrichedRows.slice(start, start + BATCH_SIZE);\n await batchUpdateDaily(conn, batch, tier3Cols, dailyTarget);\n }\n\n return { status: \"complete\", fieldsWritten: tier3Cols.length };\n}\n","/**\n * Market Data Importer — stores-based ingest surface.\n *\n * Every spot-bar write flows through `SpotStore.writeBars(ticker, date,\n * BarRow[])`. The `target_table` parameter from the earlier ingest API has\n * been removed — daily / date_context outputs are derived by\n * `EnrichedStore.compute()` + `computeContext()` invoked at the tool-handler\n * layer (see `tools/market-imports.ts`).\n *\n * Exports:\n * - parseCsvToBars(filePath, ticker, columnMapping) — CSV → BarRow[]\n * - parseDatabaseRowsToBars(rows, ticker, columnMapping) — DB rows → BarRow[]\n * - importMarketCsvFile(stores, params) — convenience wrapper that parses,\n * writes, and groups by date. Pure orchestration over SpotStore.\n * - importFromDatabase(stores, conn, params) — DB-backed parallel of the\n * above. Caller supplies the analytics conn for the ATTACH/DETACH lifecycle\n * so this file does not import `getConnection`.\n * - validateColumnMapping — pure helper.\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport * as fs from \"fs/promises\";\nimport { normalizeTicker } from \"./ticker.ts\";\nimport type { MarketStores, BarRow } from \"../market/stores/index.ts\";\n\n// =============================================================================\n// Constants + types (kept local — duplicated in market-importer-api.ts so neither\n// file depends on the other for these tiny pure values).\n// =============================================================================\n\n/**\n * Required schema fields per virtual target. Kept for `validateColumnMapping`\n * compatibility — the new spot-write path always uses the `intraday` schema\n * (date + time + OHLC) but legacy callers may still validate against `daily`\n * or `date_context`.\n */\nconst REQUIRED_SCHEMA_FIELDS: Record<string, string[]> = {\n daily: [\"date\", \"open\", \"high\", \"low\", \"close\"],\n date_context: [\"date\"],\n intraday: [\"date\", \"time\", \"open\", \"high\", \"low\", \"close\"],\n};\n\nexport type MarketImportTargetTable = \"daily\" | \"date_context\" | \"intraday\";\n\nexport interface ImportMarketCsvParams {\n filePath: string;\n ticker: string;\n columnMapping: Record<string, string>;\n dryRun?: boolean;\n}\n\nexport interface ImportFromDatabaseParams {\n dbPath: string;\n query: string;\n ticker: string;\n columnMapping: Record<string, string>;\n dryRun?: boolean;\n}\n\nexport interface ImportSpotResult {\n rowsWritten: number;\n inputRowCount: number;\n parsedRows: number;\n dryRun: boolean;\n dateRange: { from: string; to: string } | null;\n ticker: string;\n}\n\n// =============================================================================\n// validateColumnMapping — pure helper\n// =============================================================================\n\n/**\n * Validate that the column mapping covers all required schema fields for the\n * target table. Intraday allows missing `time` when `date` is mapped (auto-\n * derived from Unix timestamp).\n */\nexport function validateColumnMapping(\n columnMapping: Record<string, string>,\n targetTable: MarketImportTargetTable,\n): { valid: boolean; missingFields: string[] } {\n const schemaValues = Object.values(columnMapping);\n const required = REQUIRED_SCHEMA_FIELDS[targetTable] ?? [];\n let missing = required.filter((field) => !schemaValues.includes(field));\n if (targetTable === \"intraday\" && missing.includes(\"time\") && schemaValues.includes(\"date\")) {\n missing = missing.filter((f) => f !== \"time\");\n }\n return { valid: missing.length === 0, missingFields: missing };\n}\n\n// =============================================================================\n// CSV parsing helpers (private — duplicated from -api.ts to keep this file\n// self-contained for the spot-write code path)\n// =============================================================================\n\n/** Parse CSV content into rows with header mapping. Strips UTF-8 BOM. */\nfunction parseCSV(content: string): { headers: string[]; rows: Record<string, string>[] } {\n const lines = content.replace(/^\\uFEFF/, \"\").trim().split(\"\\n\");\n if (lines.length < 2) return { headers: [], rows: [] };\n const headers = lines[0].trim().split(\",\").map((h) => h.trim());\n const rows: Record<string, string>[] = [];\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i].trim();\n if (!line) continue;\n const values = line.split(\",\");\n const row: Record<string, string> = {};\n headers.forEach((h, idx) => {\n row[h] = values[idx]?.trim() ?? \"\";\n });\n rows.push(row);\n }\n return { headers, rows };\n}\n\n/**\n * Parse a date value flexibly:\n * - Numeric > 1e8 → treat as Unix seconds; return ET YYYY-MM-DD.\n * - YYYY-MM-DD string → return as-is.\n * - Otherwise → null.\n */\nfunction parseFlexibleDate(value: string): string | null {\n const numeric = Number(value);\n if (!isNaN(numeric) && numeric > 1e8) {\n return new Date(numeric * 1000).toLocaleDateString(\"en-CA\", {\n timeZone: \"America/New_York\",\n year: \"numeric\", month: \"2-digit\", day: \"2-digit\",\n });\n }\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n return null;\n}\n\n/**\n * Extract HH:MM time from a value in Eastern Time:\n * - Numeric > 1e8 → ET HH:MM from Unix timestamp.\n * - HH:MM string → return as-is.\n * - HHMM (4 digits) → \"HH:MM\".\n * - Otherwise → null.\n */\nfunction parseFlexibleTime(value: string): string | null {\n const numeric = Number(value);\n if (!isNaN(numeric) && numeric > 1e8) {\n const d = new Date(numeric * 1000);\n return d.toLocaleTimeString(\"en-US\", {\n timeZone: \"America/New_York\",\n hour: \"2-digit\", minute: \"2-digit\", hour12: false,\n });\n }\n if (/^\\d{2}:\\d{2}$/.test(value)) return value;\n if (/^\\d{4}$/.test(value)) return `${value.slice(0, 2)}:${value.slice(2)}`;\n return null;\n}\n\n/**\n * Apply column mapping to raw rows, parsing dates/times and coercing numerics.\n * Drops rows with unparseable dates. Returns `Record<string, unknown>[]` —\n * callers can either coerce to `BarRow` (via `coerceMappedRowToBar`) or use\n * the dict shape directly.\n */\nfunction applyColumnMapping(\n rows: Record<string, string>[],\n columnMapping: Record<string, string>,\n ticker: string,\n): Array<Record<string, unknown>> {\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const result: Array<Record<string, unknown>> = [];\n for (const row of rows) {\n const mapped: Record<string, unknown> = {};\n let hasNullDate = false;\n for (const [sourceCol, schemaCol] of Object.entries(columnMapping)) {\n const rawValue = row[sourceCol] ?? \"\";\n if (schemaCol === \"date\") {\n const parsed = parseFlexibleDate(rawValue);\n if (parsed === null) { hasNullDate = true; break; }\n mapped[schemaCol] = parsed;\n } else if (schemaCol === \"time\") {\n const parsed = parseFlexibleTime(rawValue);\n if (parsed === null) { hasNullDate = true; break; }\n mapped[schemaCol] = parsed;\n } else {\n if (rawValue === \"\" || rawValue === \"NaN\" || rawValue === \"NA\") {\n mapped[schemaCol] = null;\n } else {\n const numVal = parseFloat(rawValue);\n mapped[schemaCol] = isNaN(numVal) ? rawValue : numVal;\n }\n }\n }\n if (hasNullDate) continue;\n if (!(\"date\" in mapped)) continue;\n // Auto-extract time from a Unix-timestamp date column when `time` is not mapped.\n if (!(\"time\" in mapped)) {\n const dateSourceCol = Object.entries(columnMapping).find(([, schema]) => schema === \"date\")?.[0];\n if (dateSourceCol) {\n const rawDateValue = row[dateSourceCol] ?? \"\";\n const numericDate = Number(rawDateValue);\n if (!isNaN(numericDate) && numericDate > 1e8) {\n const t = parseFlexibleTime(rawDateValue);\n if (t) mapped[\"time\"] = t;\n }\n }\n }\n mapped[\"ticker\"] = normalizedTicker;\n result.push(mapped);\n }\n return result;\n}\n\n/**\n * Coerce a `Record<string, unknown>` from `applyColumnMapping` into a `BarRow`.\n * Non-numeric values fall back to `0` so the spot store always receives well-\n * typed numbers — invalid rows are filtered upstream.\n */\nfunction coerceMappedRowToBar(\n row: Record<string, unknown>,\n ticker: string,\n): BarRow | null {\n const date = typeof row.date === \"string\" ? row.date : null;\n if (!date) return null;\n const time = typeof row.time === \"string\" ? row.time : \"09:30\";\n const num = (v: unknown): number => {\n if (typeof v === \"number\") return Number.isFinite(v) ? v : 0;\n if (typeof v === \"string\") {\n const n = Number(v);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n };\n const optNum = (v: unknown): number | undefined => {\n if (v === null || v === undefined) return undefined;\n const n = num(v);\n return Number.isFinite(n) ? n : undefined;\n };\n return {\n ticker,\n date,\n time,\n open: num(row.open),\n high: num(row.high),\n low: num(row.low),\n close: num(row.close),\n volume: typeof row.volume === \"number\" || typeof row.volume === \"string\" ? num(row.volume) : 0,\n bid: optNum(row.bid),\n ask: optNum(row.ask),\n };\n}\n\n/**\n * Group BarRow values by date, preserving insertion order so\n * `[...byDate.keys()][0]` / `[...byDate.keys()].pop()` yield min/max dates\n * when the input is sorted.\n */\nfunction groupBarsByDate(bars: BarRow[]): Map<string, BarRow[]> {\n const byDate = new Map<string, BarRow[]>();\n for (const bar of bars) {\n const arr = byDate.get(bar.date);\n if (arr) arr.push(bar);\n else byDate.set(bar.date, [bar]);\n }\n return byDate;\n}\n\n// =============================================================================\n// Public parse helpers — used by tools/market-imports.ts handlers\n// =============================================================================\n\n/**\n * Parse a CSV file into a flat `BarRow[]` using the supplied column mapping.\n * Throws on unreadable file or empty input. Returns `[]` when the mapping\n * yields no valid rows (caller decides whether that's an error).\n */\nexport async function parseCsvToBars(\n filePath: string,\n ticker: string,\n columnMapping: Record<string, string>,\n): Promise<BarRow[]> {\n let content: string;\n try {\n content = await fs.readFile(filePath, \"utf-8\");\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to read CSV file at \"${filePath}\": ${msg}`);\n }\n const { rows } = parseCSV(content);\n if (rows.length === 0) {\n throw new Error(`CSV file \"${filePath}\" has no data rows`);\n }\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const mappedRows = applyColumnMapping(rows, columnMapping, normalizedTicker);\n const bars: BarRow[] = [];\n for (const row of mappedRows) {\n const bar = coerceMappedRowToBar(row, normalizedTicker);\n if (bar) bars.push(bar);\n }\n return bars;\n}\n\n/**\n * Parse a set of pre-fetched database rows (typically from a DuckDB ATTACH +\n * SELECT) into a flat `BarRow[]` using the supplied column mapping.\n */\nexport function parseDatabaseRowsToBars(\n rawRows: Record<string, string>[],\n ticker: string,\n columnMapping: Record<string, string>,\n): BarRow[] {\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const mappedRows = applyColumnMapping(rawRows, columnMapping, normalizedTicker);\n const bars: BarRow[] = [];\n for (const row of mappedRows) {\n const bar = coerceMappedRowToBar(row, normalizedTicker);\n if (bar) bars.push(bar);\n }\n return bars;\n}\n\n// =============================================================================\n// Public ingest helpers — orchestrate parse + per-date stores.spot.writeBars\n// =============================================================================\n\n/**\n * Import a CSV file by parsing it into BarRow[] and writing per-date\n * partitions through `stores.spot.writeBars`. Pure orchestration — does NOT\n * call `EnrichedStore.compute()`; the tool-handler layer composes enrichment\n * after the spot write.\n */\nexport async function importMarketCsvFile(\n stores: MarketStores,\n params: ImportMarketCsvParams,\n): Promise<ImportSpotResult> {\n const { filePath, ticker, columnMapping, dryRun = false } = params;\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const bars = await parseCsvToBars(filePath, normalizedTicker, columnMapping);\n\n if (bars.length === 0) {\n return {\n rowsWritten: 0,\n inputRowCount: 0,\n parsedRows: 0,\n dryRun,\n dateRange: null,\n ticker: normalizedTicker,\n };\n }\n\n const byDate = groupBarsByDate(bars);\n const dates = [...byDate.keys()].sort();\n const dateRange = { from: dates[0], to: dates[dates.length - 1] };\n\n if (dryRun) {\n return {\n rowsWritten: 0,\n inputRowCount: bars.length,\n parsedRows: bars.length,\n dryRun: true,\n dateRange,\n ticker: normalizedTicker,\n };\n }\n\n let rowsWritten = 0;\n for (const [date, dayBars] of byDate) {\n await stores.spot.writeBars(normalizedTicker, date, dayBars);\n rowsWritten += dayBars.length;\n }\n\n return {\n rowsWritten,\n inputRowCount: bars.length,\n parsedRows: bars.length,\n dryRun: false,\n dateRange,\n ticker: normalizedTicker,\n };\n}\n\n/**\n * Import from an external DuckDB database by ATTACHing it on the supplied\n * `conn`, executing `params.query`, parsing rows into BarRow[], and writing\n * per-date partitions through `stores.spot.writeBars`. The caller owns the\n * analytics conn lifecycle (RW upgrade/downgrade) and the ATTACH/DETACH —\n * passing in `conn` rather than re-importing `getConnection` keeps this file\n * pure-spot-write and avoids a circular dependency with `db/connection.ts`.\n */\nexport async function importFromDatabase(\n stores: MarketStores,\n conn: DuckDBConnection,\n params: ImportFromDatabaseParams,\n): Promise<ImportSpotResult> {\n const { dbPath, query, ticker, columnMapping, dryRun = false } = params;\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const EXT_ALIAS = \"ext_import_source\";\n const escapedDbPath = dbPath.replace(/'/g, \"''\");\n await conn.run(`ATTACH '${escapedDbPath}' AS ${EXT_ALIAS} (READ_ONLY)`);\n\n let bars: BarRow[];\n try {\n const result = await conn.runAndReadAll(query);\n const colNames = result.columnNames();\n const rows = result.getRows();\n const rawRows: Record<string, string>[] = rows.map((row) => {\n const obj: Record<string, string> = {};\n colNames.forEach((name, idx) => {\n const val = row[idx];\n obj[name] = val === null || val === undefined ? \"\" : String(val);\n });\n return obj;\n });\n bars = parseDatabaseRowsToBars(rawRows, normalizedTicker, columnMapping);\n } finally {\n try { await conn.run(`DETACH ${EXT_ALIAS}`); } catch { /* best-effort */ }\n }\n\n if (bars.length === 0) {\n return {\n rowsWritten: 0,\n inputRowCount: 0,\n parsedRows: 0,\n dryRun,\n dateRange: null,\n ticker: normalizedTicker,\n };\n }\n\n const byDate = groupBarsByDate(bars);\n const dates = [...byDate.keys()].sort();\n const dateRange = { from: dates[0], to: dates[dates.length - 1] };\n\n if (dryRun) {\n return {\n rowsWritten: 0,\n inputRowCount: bars.length,\n parsedRows: bars.length,\n dryRun: true,\n dateRange,\n ticker: normalizedTicker,\n };\n }\n\n let rowsWritten = 0;\n for (const [date, dayBars] of byDate) {\n await stores.spot.writeBars(normalizedTicker, date, dayBars);\n rowsWritten += dayBars.length;\n }\n\n return {\n rowsWritten,\n inputRowCount: bars.length,\n parsedRows: bars.length,\n dryRun: false,\n dateRange,\n ticker: normalizedTicker,\n };\n}\n","/**\n * Shared Analysis Statistics\n *\n * Reusable stat computation extracted from analyze_regime_performance.\n * Used by portfolio_structure_map, analyze_structure_fit, and\n * analyze_regime_performance to compute per-slice statistics from P&L arrays.\n */\n\n/**\n * Statistics for a slice (segment, bucket, group) of trades.\n */\nexport interface SliceStats {\n tradeCount: number;\n wins: number;\n losses: number;\n /** Win rate as percentage (0-100) */\n winRate: number;\n totalPl: number;\n avgPl: number;\n avgWin: number;\n avgLoss: number;\n /** Gross wins / gross losses. null if no losses but has wins. 0 if no wins or empty. */\n profitFactor: number | null;\n}\n\n/**\n * Round a number to 2 decimal places.\n */\nfunction round2(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\n/**\n * Compute statistics for an array of P&L values.\n * Wins are P&L > 0, losses are P&L <= 0.\n * All numeric values are rounded to 2 decimal places.\n *\n * @param pls - Array of P&L values (positive = win, zero/negative = loss)\n * @returns Computed slice statistics\n */\nexport function computeSliceStats(pls: number[]): SliceStats {\n if (pls.length === 0) {\n return {\n tradeCount: 0,\n wins: 0,\n losses: 0,\n winRate: 0,\n totalPl: 0,\n avgPl: 0,\n avgWin: 0,\n avgLoss: 0,\n profitFactor: 0,\n };\n }\n\n const winPls = pls.filter((p) => p > 0);\n const lossPls = pls.filter((p) => p <= 0);\n\n const wins = winPls.length;\n const losses = lossPls.length;\n const winRate = (wins / pls.length) * 100;\n const totalPl = pls.reduce((sum, p) => sum + p, 0);\n const avgPl = totalPl / pls.length;\n\n const avgWin = wins > 0 ? winPls.reduce((s, p) => s + p, 0) / wins : 0;\n const avgLoss = losses > 0 ? lossPls.reduce((s, p) => s + p, 0) / losses : 0;\n\n const grossWins = winPls.reduce((s, p) => s + p, 0);\n const grossLosses = Math.abs(lossPls.reduce((s, p) => s + p, 0));\n\n let profitFactor: number | null;\n if (grossLosses > 0) {\n profitFactor = grossWins / grossLosses;\n } else if (grossWins > 0) {\n profitFactor = null; // All winners, no losses to divide by\n } else {\n profitFactor = 0;\n }\n\n return {\n tradeCount: pls.length,\n wins,\n losses,\n winRate: round2(winRate),\n totalPl: round2(totalPl),\n avgPl: round2(avgPl),\n avgWin: round2(avgWin),\n avgLoss: round2(avgLoss),\n profitFactor: profitFactor !== null ? round2(profitFactor) : null,\n };\n}\n","/**\n * Filter Predicate Builder\n *\n * Converts EntryFilter objects into runtime predicates that can evaluate\n * market data records. Handles field timing awareness via CLOSE_KNOWN_FIELDS\n * to automatically apply the prev_ prefix for close-derived fields.\n *\n * Used by analyze_structure_fit and portfolio_structure_map to evaluate\n * entry filters against market data rows.\n */\n\nimport { CLOSE_KNOWN_FIELDS } from \"./field-timing.ts\";\nimport type { EntryFilter } from \"../models/strategy-profile.ts\";\n\n/**\n * A compiled filter predicate with metadata about the field key used.\n */\nexport interface FilterPredicate {\n /** Evaluate this predicate against a market data record */\n test: (market: Record<string, unknown>) => boolean;\n /** The actual field key used for lookup (may have prev_ prefix) */\n fieldKey: string;\n /** Whether the field was detected as close-derived and lagged */\n isLagged: boolean;\n}\n\n/**\n * Day-of-week name to number mapping (market data uses 1=Mon to 5=Fri).\n */\nconst DAY_NAME_TO_NUM: Record<string, number> = {\n monday: 1, mon: 1,\n tuesday: 2, tue: 2, tues: 2,\n wednesday: 3, wed: 3,\n thursday: 4, thu: 4, thurs: 4,\n friday: 5, fri: 5,\n};\n\n/**\n * If a filter value is a day-of-week name and the field is Day_of_Week,\n * convert it to the corresponding number. Returns null if not applicable.\n */\nfunction resolveDayName(value: unknown): number | null {\n if (typeof value !== \"string\") return null;\n return DAY_NAME_TO_NUM[value.toLowerCase()] ?? null;\n}\n\n/**\n * Safely extract a numeric value from a record.\n * Returns NaN if the value is missing, null, undefined, or non-numeric.\n */\nfunction getNum(record: Record<string, unknown>, key: string): number {\n const val = record[key];\n if (val === null || val === undefined) return NaN;\n const num = Number(val);\n return num;\n}\n\n/**\n * Safely extract a value from a record for loose equality comparison.\n * Returns undefined if the key is missing.\n */\nfunction getRaw(record: Record<string, unknown>, key: string): unknown {\n return record[key];\n}\n\n/**\n * Check whether a filter value is a cross-field reference (a string that\n * looks like a field name rather than a pure numeric literal).\n */\nfunction isCrossFieldRef(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n // If it parses as a finite number, it's a numeric literal, not a field ref\n if (value.trim() !== \"\" && isFinite(Number(value))) return false;\n return true;\n}\n\n/**\n * Resolve a cross-field reference value. If the value is a string that\n * already exists as a key in the market record, use it as-is. Otherwise,\n * if the bare field name (without prev_ prefix) is close-derived, try\n * the prev_ prefixed version.\n */\nfunction resolveFieldRef(\n refName: string,\n market: Record<string, unknown>\n): number {\n // Direct lookup first (handles cases like \"prev_VIX_Close\" spelled out)\n if (refName in market) {\n return getNum(market, refName);\n }\n // If the ref looks like a bare close-derived field, try prev_ prefix\n if (CLOSE_KNOWN_FIELDS.has(refName)) {\n return getNum(market, `prev_${refName}`);\n }\n return NaN;\n}\n\n/**\n * Build a runtime predicate from an EntryFilter.\n *\n * Automatically detects close-derived fields via CLOSE_KNOWN_FIELDS and\n * prepends \"prev_\" to the field key for correct lookahead-free evaluation.\n *\n * For comparison operators (>, <, >=, <=, ==), if the filter value is a\n * string that looks like a field name (not a pure numeric string), it is\n * treated as a cross-field reference. The referenced field's value is\n * looked up from the market record at evaluation time.\n *\n * NaN/null/undefined values in the market record always return false\n * (missing data never matches a filter).\n *\n * @param filter - Entry filter specification\n * @returns Compiled predicate with metadata\n */\nexport function buildFilterPredicate(filter: EntryFilter): FilterPredicate {\n const isLagged = CLOSE_KNOWN_FIELDS.has(filter.field);\n const fieldKey = isLagged ? `prev_${filter.field}` : filter.field;\n\n const { operator, value } = filter;\n\n const test = (market: Record<string, unknown>): boolean => {\n // For \"in\" and \"==\" operators, we may need raw value access\n if (operator === \"in\") {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) return false;\n if (!Array.isArray(value)) return false;\n // Try day-of-week name resolution for each element\n return value.some((v) => {\n const dayNum = resolveDayName(v);\n if (dayNum !== null) return Number(raw) === dayNum;\n return v == raw;\n });\n }\n\n if (operator === \"==\") {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) return false;\n // Day-of-week name resolution (e.g., \"Tuesday\" == 2)\n const dayNum = resolveDayName(value);\n if (dayNum !== null) return Number(raw) === dayNum;\n // Cross-field reference for ==\n if (isCrossFieldRef(value)) {\n const refVal = resolveFieldRef(value, market);\n if (isNaN(refVal)) return false;\n return Number(raw) === refVal;\n }\n return value == raw;\n }\n\n // Numeric operators: >, <, >=, <=, between\n const num = getNum(market, fieldKey);\n if (isNaN(num)) return false;\n\n // For comparison operators, check if value is a cross-field reference\n if (\n isCrossFieldRef(value) &&\n (operator === \">\" || operator === \"<\" || operator === \">=\" || operator === \"<=\")\n ) {\n const refVal = resolveFieldRef(value, market);\n if (isNaN(refVal)) return false;\n switch (operator) {\n case \">\":\n return num > refVal;\n case \"<\":\n return num < refVal;\n case \">=\":\n return num >= refVal;\n case \"<=\":\n return num <= refVal;\n }\n }\n\n switch (operator) {\n case \">\":\n return num > Number(value);\n case \"<\":\n return num < Number(value);\n case \">=\":\n return num >= Number(value);\n case \"<=\":\n return num <= Number(value);\n case \"between\": {\n if (!Array.isArray(value) || value.length < 2) return false;\n const lo = Number(value[0]);\n const hi = Number(value[1]);\n return num >= lo && num <= hi;\n }\n default:\n return false;\n }\n };\n\n return { test, fieldKey, isLagged };\n}\n","/**\n * Strategy Profile Tools\n *\n * MCP tools for CRUD operations on strategy profiles stored in DuckDB.\n * Wraps the Phase 60 storage layer (db/profile-schemas.ts) as conversational tools.\n *\n * Tools registered:\n * - profile_strategy — Create or update a strategy profile\n * - get_strategy_profile — Retrieve a single profile by block + strategy name\n * - list_profiles — List profiles (optionally filtered by block)\n * - delete_profile — Remove a profile (idempotent)\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection, upgradeToReadWrite, downgradeToReadOnly } from \"../db/connection.ts\";\nimport {\n upsertProfile,\n getProfile,\n listProfiles,\n deleteProfile,\n} from \"../db/profile-schemas.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport { withSyncedBlock } from \"./middleware/sync-middleware.ts\";\n\n// ---------------------------------------------------------------------------\n// Zod schemas (exported for testability)\n// ---------------------------------------------------------------------------\n\nexport const profileStrategySchema = z.object({\n blockId: z.string().describe(\"Block ID (block_id) to associate the profile with\"),\n strategyName: z.string().describe(\"Human-readable strategy name (e.g., 'Pickle RIC v2')\"),\n structureType: z\n .string()\n .describe(\n \"Option structure type: iron_condor, calendar_spread, double_calendar, vertical_spread, \" +\n \"butterfly, reverse_iron_condor, short_put_spread, short_call_spread, straddle, strangle, etc.\"\n ),\n greeksBias: z\n .string()\n .describe(\n \"Primary greeks exposure: theta_positive, vega_negative, delta_neutral, delta_positive, \" +\n \"delta_negative, gamma_scalp, etc.\"\n ),\n thesis: z.string().default(\"\").describe(\"Free-text description of the strategy thesis\"),\n legs: z\n .array(\n z.object({\n type: z.string().describe(\"Leg type: long_put, short_call, long_call, short_put, etc.\"),\n strike: z.string().describe(\"Strike selection: ATM, 5-delta, 30-delta, etc.\"),\n expiry: z.string().describe(\"Expiry selection: same-day, weekly, 45-DTE, etc.\"),\n quantity: z.number().describe(\"Quantity (positive=long, negative=short)\"),\n strikeMethod: z.enum(['delta', 'dollar_price', 'offset', 'percentage']).optional().describe(\"How strike is selected\"),\n strikeValue: z.number().optional().describe(\"Numeric strike value (e.g., 25 for 25-delta)\"),\n })\n )\n .default([])\n .describe(\"Structured leg descriptions\"),\n entryFilters: z\n .array(\n z.object({\n field: z.string().describe(\"Market data field: VIX_Close, RSI_14, Vol_Regime, etc.\"),\n operator: z.string().describe(\"Comparison operator: >, <, >=, <=, ==, between, in\"),\n value: z\n .union([z.string(), z.number(), z.array(z.union([z.string(), z.number()]))])\n .describe(\"Filter value or array for between/in operators\"),\n description: z.string().optional().describe(\"Human-readable description of this filter\"),\n source: z.enum([\"market\", \"execution\"]).optional().describe(\"Filter source: 'market' = testable against market data columns, 'execution' = platform-level (time windows, leg ratios). Defaults to 'market'. Execution filters are documented but skipped during validate_entry_filters analysis.\"),\n })\n )\n .default([])\n .describe(\"Entry condition filters. Tag each with source: 'market' (testable in analysis) or 'execution' (OO/platform-level, skipped in analysis).\"),\n exitRules: z\n .array(\n z.object({\n type: z.string().describe(\"Rule type: stop_loss, profit_target, time_exit, conditional\"),\n trigger: z.string().describe(\"Trigger condition: '200% of credit', '50% of max profit', '15:00 ET'\"),\n description: z.string().optional().describe(\"Human-readable description\"),\n stopLossType: z.enum(['percentage', 'dollar', 'sl_ratio', 'debit_percentage']).optional().describe(\"Stop loss calculation method\"),\n stopLossValue: z.number().optional().describe(\"Stop loss numeric value\"),\n monitoring: z.object({\n granularity: z.enum(['intra_minute', 'candle_close', 'end_of_bar']).optional().describe(\"Price check frequency\"),\n priceSource: z.enum(['nbbo', 'mid', 'last']).optional().describe(\"Which price to use\"),\n }).optional().describe(\"Monitoring configuration for this rule\"),\n slippage: z.number().optional().describe(\"Per-rule slippage override\"),\n })\n )\n .default([])\n .describe(\"Exit rules and triggers\"),\n expectedRegimes: z\n .array(z.enum([\"very_low\", \"low\", \"below_avg\", \"above_avg\", \"high\", \"extreme\"]))\n .default([])\n .describe(\"VIX-based vol regimes this strategy targets. very_low=VIX<13, low=13-16, below_avg=16-20, above_avg=20-25, high=25-30, extreme=30+\"),\n keyMetrics: z\n .object({\n expectedWinRate: z.number().optional().describe(\"Expected win rate (0-1)\"),\n targetPremium: z.number().optional().describe(\"Target premium collected ($)\"),\n maxLoss: z.number().optional().describe(\"Maximum loss per contract ($)\"),\n profitTarget: z.number().optional().describe(\"Profit target ($ or %)\"),\n })\n .passthrough()\n .default({})\n .describe(\"Performance benchmarks and strategy-specific metrics\"),\n positionSizing: z\n .object({\n method: z.string().describe(\"Sizing method: pct_of_portfolio, fixed_contracts, fixed_dollar, discretionary\"),\n allocationPct: z.number().optional().describe(\"Portfolio allocation percentage (e.g., 2 for 2%)\"),\n maxContracts: z.number().optional().describe(\"Maximum contracts per trade\"),\n maxAllocationDollar: z.number().optional().describe(\"Maximum dollar allocation per trade\"),\n maxOpenPositions: z.number().optional().describe(\"Maximum concurrent open positions\"),\n description: z.string().optional().describe(\"Free-text sizing notes\"),\n backtestAllocationPct: z.number().optional().describe(\"Allocation % used in backtest\"),\n liveAllocationPct: z.number().optional().describe(\"Allocation % used in live portfolio\"),\n maxContractsPerTrade: z.number().optional().describe(\"Per-entry contract cap (distinct from maxContracts hard cap)\"),\n })\n .optional()\n .describe(\"Position sizing rules. Per-block — same strategy in backtest vs portfolio may have different sizing.\"),\n underlying: z.string().optional().describe(\"Underlying symbol: SPX, QQQ, etc.\"),\n reEntry: z.boolean().optional().describe(\"Strategy supports re-entry on same day\"),\n capProfits: z.boolean().optional().describe(\"Profits are capped by structure\"),\n capLosses: z.boolean().optional().describe(\"Losses are capped by structure\"),\n requireTwoPricesPT: z.boolean().optional().describe(\"Profit target requires two prices\"),\n closeOnCompletion: z.boolean().optional().describe(\"Close entire position when any leg hits target\"),\n ignoreMarginReq: z.boolean().optional().describe(\"Strategy ignores standard margin requirements\"),\n});\n\nexport const getStrategyProfileSchema = z.object({\n blockId: z.string().describe(\"Block ID to look up\"),\n strategyName: z.string().describe(\"Strategy name to look up\"),\n});\n\nexport const listProfilesSchema = z.object({\n blockId: z.string().optional().describe(\"Optional block ID filter. Omit to list all profiles across all blocks.\"),\n});\n\nexport const deleteProfileSchema = z.object({\n blockId: z.string().describe(\"Block ID of the profile to delete\"),\n strategyName: z.string().describe(\"Strategy name of the profile to delete\"),\n});\n\n// ---------------------------------------------------------------------------\n// Handler functions (exported for testability)\n// ---------------------------------------------------------------------------\n\n/**\n * Handle profile_strategy: create or update a strategy profile.\n */\nexport async function handleProfileStrategy(\n input: z.infer<typeof profileStrategySchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n await upgradeToReadWrite(baseDir);\n try {\n const conn = await getConnection(baseDir);\n const stored = await upsertProfile(conn, {\n blockId: input.blockId,\n strategyName: input.strategyName,\n structureType: input.structureType,\n greeksBias: input.greeksBias,\n thesis: input.thesis,\n legs: input.legs,\n entryFilters: input.entryFilters,\n exitRules: input.exitRules,\n expectedRegimes: input.expectedRegimes,\n keyMetrics: input.keyMetrics,\n positionSizing: input.positionSizing,\n underlying: input.underlying,\n reEntry: input.reEntry,\n capProfits: input.capProfits,\n capLosses: input.capLosses,\n requireTwoPricesPT: input.requireTwoPricesPT,\n closeOnCompletion: input.closeOnCompletion,\n ignoreMarginReq: input.ignoreMarginReq,\n }, baseDir);\n return createToolOutput(\n `Profile saved: ${input.strategyName} for block ${input.blockId}`,\n { profile: stored }\n );\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n}\n\n/**\n * Handle get_strategy_profile: retrieve a single profile.\n */\nexport async function handleGetStrategyProfile(\n input: z.infer<typeof getStrategyProfileSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, input.blockId, input.strategyName, baseDir);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${input.strategyName}' in block '${input.blockId}'`,\n { profile: null }\n );\n }\n return createToolOutput(\n `Profile: ${input.strategyName} in block ${input.blockId}`,\n { profile }\n );\n}\n\n/**\n * Handle list_profiles: list profiles with optional block filter.\n */\nexport async function handleListProfiles(\n input: z.infer<typeof listProfilesSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const conn = await getConnection(baseDir);\n const profiles = await listProfiles(conn, input.blockId, baseDir);\n const summaryRows = profiles.map((p) => ({\n blockId: p.blockId,\n strategyName: p.strategyName,\n structureType: p.structureType,\n greeksBias: p.greeksBias,\n underlying: p.underlying ?? null,\n positionSizing: p.positionSizing?.method ?? null,\n updatedAt: p.updatedAt,\n }));\n return createToolOutput(\n `Found ${profiles.length} profile(s)${input.blockId ? ` for block ${input.blockId}` : \"\"}`,\n { count: profiles.length, profiles: summaryRows }\n );\n}\n\n/**\n * Handle delete_profile: remove a profile (idempotent).\n */\nexport async function handleDeleteProfile(\n input: z.infer<typeof deleteProfileSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n await upgradeToReadWrite(baseDir);\n try {\n const conn = await getConnection(baseDir);\n const deleted = await deleteProfile(conn, input.blockId, input.strategyName, baseDir);\n if (deleted) {\n return createToolOutput(\n `Deleted profile: ${input.strategyName} from block ${input.blockId}`,\n { deleted: true }\n );\n }\n return createToolOutput(\n `No profile found for strategy '${input.strategyName}' in block '${input.blockId}' — nothing to delete`,\n { deleted: false }\n );\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\n/**\n * Register all profile CRUD tools on the MCP server.\n *\n * @param server - McpServer instance to register tools on\n * @param baseDir - Base data directory (passed to connection helpers)\n */\nexport function registerProfileTools(server: McpServer, baseDir: string): void {\n // -------------------------------------------------------------------------\n // Tool: profile_strategy\n // -------------------------------------------------------------------------\n server.registerTool(\n \"profile_strategy\",\n {\n description:\n \"Create or update a strategy profile for a block. Stores structure type, greeks bias, \" +\n \"legs, entry filters, exit rules, expected regimes, key metrics, and position sizing. \" +\n \"If a profile with the same block_id + strategy_name already exists, it is overwritten (upsert). \" +\n \"When profiling the same strategy across multiple blocks (e.g., backtest vs live portfolio), \" +\n \"retrieve the existing profile with get_strategy_profile and copy its fields, updating only \" +\n \"positionSizing or other block-specific params rather than re-asking the user for all details.\",\n inputSchema: profileStrategySchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleProfileStrategy(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: get_strategy_profile\n // -------------------------------------------------------------------------\n server.registerTool(\n \"get_strategy_profile\",\n {\n description:\n \"Retrieve a single strategy profile by block_id and strategy_name. \" +\n \"Returns the full profile including all schema fields, or a not-found message.\",\n inputSchema: getStrategyProfileSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleGetStrategyProfile(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: list_profiles\n // -------------------------------------------------------------------------\n server.registerTool(\n \"list_profiles\",\n {\n description:\n \"List strategy profiles. Provide block_id to filter by block, or omit to list all profiles \" +\n \"across all blocks. Returns summary rows with block_id, strategy_name, structure_type, \" +\n \"greeks_bias, and updated_at.\",\n inputSchema: listProfilesSchema,\n },\n async (input) => {\n // list_profiles has optional blockId — when provided, sync the block first;\n // when omitted, query directly without sync (no block to validate).\n if (input.blockId) {\n const syncedHandler = withSyncedBlock(baseDir, async (syncInput: { blockId: string }, ctx) => {\n return handleListProfiles({ blockId: syncInput.blockId }, ctx.baseDir);\n });\n return syncedHandler({ blockId: input.blockId });\n }\n return handleListProfiles(input, baseDir);\n }\n );\n\n // -------------------------------------------------------------------------\n // Tool: delete_profile\n // -------------------------------------------------------------------------\n server.registerTool(\n \"delete_profile\",\n {\n description:\n \"Delete a strategy profile by block_id and strategy_name. \" +\n \"Idempotent: deleting a nonexistent profile returns success with a not-found message.\",\n inputSchema: deleteProfileSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleDeleteProfile(input, ctx.baseDir);\n })\n );\n}\n","/**\n * Output Formatter\n *\n * Utilities for formatting MCP tool output.\n *\n * JSON-First Pattern:\n * Tools return structured JSON as the primary format. JSON is machine-readable,\n * enabling reliable data extraction without parsing natural language.\n *\n * A brief text summary is included for user visibility, but the JSON\n * is the authoritative source for all data and reasoning.\n */\n\n/**\n * MCP content item types\n */\nexport interface McpTextContent {\n type: \"text\";\n text: string;\n}\n\nexport interface McpResourceContent {\n type: \"resource\";\n resource: {\n uri: string;\n mimeType: string;\n text: string;\n };\n}\n\nexport type McpContent = McpTextContent | McpResourceContent;\n\nexport interface ToolOutput {\n [x: string]: unknown;\n content: McpContent[];\n}\n\n// Legacy alias for backward compatibility\nexport type DualOutput = ToolOutput;\n\n/**\n * Create JSON-first output for MCP tools.\n *\n * The structured JSON is the primary data source for Claude reasoning.\n * A brief text summary is provided for user visibility.\n *\n * @param summary - Brief text summary (1-3 lines) for user display\n * @param data - Structured data object - the authoritative data source\n * @returns MCP-compatible response with JSON as primary content\n */\nexport function createToolOutput(summary: string, data: object): ToolOutput {\n return {\n content: [\n { type: \"text\", text: summary },\n {\n type: \"resource\",\n resource: {\n uri: \"data:application/json\",\n mimeType: \"application/json\",\n text: JSON.stringify(data),\n },\n },\n ],\n };\n}\n\n/**\n * Legacy function - redirects to createToolOutput.\n * @deprecated Use createToolOutput instead\n */\nexport function createDualOutput(markdown: string, data: object): DualOutput {\n // Extract a brief summary from the markdown (first non-empty line or heading)\n const lines = markdown.split(\"\\n\").filter((l) => l.trim());\n const summary = lines[0]?.replace(/^#+\\s*/, \"\") || \"Results available\";\n return createToolOutput(summary, data);\n}\n\n/**\n * Format a number as currency ($1,234.56)\n */\nexport function formatCurrency(value: number): string {\n const isNegative = value < 0;\n const absValue = Math.abs(value);\n const formatted = absValue.toLocaleString(\"en-US\", {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n return isNegative ? `-$${formatted}` : `$${formatted}`;\n}\n\n/**\n * Format a number as percentage (12.34%)\n */\nexport function formatPercent(value: number, decimals: number = 2): string {\n return `${value.toFixed(decimals)}%`;\n}\n\n/**\n * Format a ratio with specified decimals\n */\nexport function formatRatio(\n value: number | undefined,\n decimals: number = 2\n): string {\n if (value === undefined || value === null || !isFinite(value)) {\n return \"N/A\";\n }\n return value.toFixed(decimals);\n}\n","/**\n * Profile Analysis Tools\n *\n * MCP tools that use stored strategy profiles for targeted analysis:\n * - analyze_structure_fit: Dimension-based performance breakdown using profile context\n * - validate_entry_filters: Entry filter effectiveness analysis with ablation study\n * - portfolio_structure_map: Vol_Regime x Trend_Direction matrix across strategies\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { loadBlock } from \"../utils/block-loader.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport type { Trade } from \"@tradeblocks/lib\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { getProfile, listProfiles } from \"../db/profile-schemas.ts\";\nimport { filterByStrategy } from \"./shared/filters.ts\";\nimport {\n buildLookaheadFreeQuery,\n type MarketLookupKey,\n} from \"../utils/field-timing.ts\";\nimport {\n DEFAULT_MARKET_TICKER,\n marketTickerDateKey,\n resolveTradeTicker,\n} from \"../utils/ticker.ts\";\nimport { computeSliceStats, type SliceStats } from \"../utils/analysis-stats.ts\";\nimport { buildFilterPredicate, type FilterPredicate } from \"../utils/filter-predicates.ts\";\nimport { withSyncedBlock } from \"./middleware/sync-middleware.ts\";\nimport {\n upgradeToReadWrite,\n downgradeToReadOnly,\n getConnectionMode,\n} from \"../db/connection.ts\";\nimport { syncAllBlocks } from \"../sync/index.ts\";\n\n// =============================================================================\n// Utility Functions (local to this module)\n// =============================================================================\n\n/**\n * Format trade date to YYYY-MM-DD for market data matching.\n */\nfunction formatTradeDate(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nfunction getTradeLookupKey(trade: Trade): MarketLookupKey {\n return {\n date: formatTradeDate(trade.dateOpened),\n ticker: resolveTradeTicker(trade, DEFAULT_MARKET_TICKER),\n };\n}\n\nfunction uniqueTradeLookupKeys(trades: Trade[]): MarketLookupKey[] {\n const byKey = new Map<string, MarketLookupKey>();\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n byKey.set(marketTickerDateKey(lookup.ticker, lookup.date), lookup);\n }\n return Array.from(byKey.values());\n}\n\nfunction resultToRecords(result: {\n columnCount: number;\n columnName(i: number): string;\n getRows(): Iterable<unknown[]>;\n}): Record<string, unknown>[] {\n const columnCount = result.columnCount;\n const colNames: string[] = [];\n for (let i = 0; i < columnCount; i++) {\n colNames.push(result.columnName(i));\n }\n const records: Record<string, unknown>[] = [];\n for (const row of result.getRows()) {\n const record: Record<string, unknown> = {};\n for (let i = 0; i < columnCount; i++) {\n const val = row[i];\n record[colNames[i]] = typeof val === \"bigint\" ? Number(val) : val;\n }\n records.push(record);\n }\n return records;\n}\n\nfunction recordsByTickerDate(\n records: Record<string, unknown>[]\n): Map<string, Record<string, unknown>> {\n const mapped = new Map<string, Record<string, unknown>>();\n for (const record of records) {\n const date = String(record[\"date\"] || \"\");\n const ticker = String(record[\"ticker\"] || DEFAULT_MARKET_TICKER);\n mapped.set(marketTickerDateKey(ticker, date), record);\n }\n return mapped;\n}\n\nfunction getNum(record: Record<string, unknown>, field: string): number {\n const val = record[field];\n if (val === null || val === undefined) return NaN;\n if (typeof val === \"bigint\") return Number(val);\n return val as number;\n}\n\n// =============================================================================\n// Vol Regime Labels\n// =============================================================================\n\nconst VOL_REGIME_LABELS: Record<number, string> = {\n 1: \"very_low\",\n 2: \"low\",\n 3: \"below_avg\",\n 4: \"above_avg\",\n 5: \"high\",\n 6: \"extreme\",\n};\n\nconst TREND_LABELS = [\"up\", \"down\", \"flat\"] as const;\ntype TrendLabel = (typeof TREND_LABELS)[number];\n\n/**\n * Day of week labels (market data: 1=Mon to 5=Fri)\n */\nconst DAY_LABELS: Record<number, string> = {\n 1: \"Monday\",\n 2: \"Tuesday\",\n 3: \"Wednesday\",\n 4: \"Thursday\",\n 5: \"Friday\",\n};\n\n/**\n * Determine time-of-day bucket from timeOpened string (format \"HH:MM:SS\" or \"HH:MM\").\n */\nfunction getTimeBucket(timeOpened: string | undefined): string | null {\n if (!timeOpened) return null;\n const match = timeOpened.match(/^(\\d{1,2}):(\\d{2})/);\n if (!match) return null;\n const hours = parseInt(match[1], 10);\n const minutes = parseInt(match[2], 10);\n const totalMinutes = hours * 60 + minutes;\n\n // morning: 09:30-11:00, midday: 11:00-14:00, afternoon: 14:00-16:00\n if (totalMinutes < 570) return null; // before 09:30\n if (totalMinutes < 660) return \"morning\"; // 09:30-11:00\n if (totalMinutes < 840) return \"midday\"; // 11:00-14:00\n if (totalMinutes <= 960) return \"afternoon\"; // 14:00-16:00\n return null; // after 16:00\n}\n\n/**\n * Safely get a raw value from a record.\n */\nfunction getRaw(record: Record<string, unknown>, field: string): unknown {\n return record[field];\n}\n\ninterface TradeWithMarket {\n trade: Trade;\n market: Record<string, unknown>;\n}\n\n/**\n * Load trades and market data for a strategy profile analysis.\n * Shared between analyze_structure_fit and validate_entry_filters.\n */\nasync function loadTradesAndMarket(\n baseDir: string,\n blockId: string,\n strategyName: string\n): Promise<{\n matched: TradeWithMarket[];\n unmatchedCount: number;\n allTrades: Trade[];\n}> {\n const block = await loadBlock(baseDir, blockId);\n let trades = filterByStrategy(block.trades, strategyName);\n\n // Single-strategy backtest blocks may have a different strategy name in the CSV\n // (e.g., blockId fallback \"2_3 dc\" vs profile name \"2/3 DC - v2\").\n // If no trades match by name and the block has only one unique strategy, use all trades.\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n\n if (trades.length === 0) {\n return { matched: [], unmatchedCount: 0, allTrades: [] };\n }\n\n // Collect unique trade keys for market query\n const tradeKeys = uniqueTradeLookupKeys(trades);\n\n // Query market data\n const conn = await getConnection(baseDir);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const result = await conn.runAndReadAll(sql, params);\n const marketRecords = resultToRecords(result);\n const marketMap = recordsByTickerDate(marketRecords);\n\n // Match trades to market records\n const matched: TradeWithMarket[] = [];\n let unmatchedCount = 0;\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const key = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = marketMap.get(key);\n if (market) {\n matched.push({ trade, market });\n } else {\n unmatchedCount++;\n }\n }\n\n return { matched, unmatchedCount, allTrades: trades };\n}\n\n/**\n * Create numeric bucket labels from data values.\n * Divides sorted values into ~4 quartile-based ranges.\n */\nfunction createNumericBuckets(values: number[]): { label: string; min: number; max: number }[] {\n if (values.length === 0) return [];\n const sorted = [...values].sort((a, b) => a - b);\n\n const uniqueValues = [...new Set(sorted)];\n if (uniqueValues.length <= 4) {\n return uniqueValues.map((v) => ({\n label: String(Math.round(v * 100) / 100),\n min: v,\n max: v,\n }));\n }\n\n const buckets: { label: string; min: number; max: number }[] = [];\n const quartileSize = Math.ceil(sorted.length / 4);\n for (let i = 0; i < 4; i++) {\n const start = i * quartileSize;\n const end = Math.min((i + 1) * quartileSize - 1, sorted.length - 1);\n if (start > sorted.length - 1) break;\n const min = sorted[start];\n const max = sorted[end];\n const r = (n: number) => Math.round(n * 100) / 100;\n buckets.push({\n label: min === max ? `${r(min)}` : `${r(min)} to ${r(max)}`,\n min,\n max,\n });\n }\n\n return buckets;\n}\n\n/**\n * Find which bucket a value belongs to.\n */\nfunction findBucket(\n value: number,\n buckets: { label: string; min: number; max: number }[]\n): string | null {\n for (const bucket of buckets) {\n if (value >= bucket.min && value <= bucket.max) return bucket.label;\n }\n return null;\n}\n\n// =============================================================================\n// analyze_structure_fit Schema and Handler\n// =============================================================================\n\nexport const analyzeStructureFitSchema = z.object({\n blockId: z.string().describe(\"Block ID to analyze\"),\n strategyName: z.string().describe(\"Strategy name matching a stored profile\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Minimum trades per bucket for reliable stats (thin-data warning threshold)\"),\n});\n\nexport async function handleAnalyzeStructureFit(\n input: z.infer<typeof analyzeStructureFitSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const { blockId, strategyName } = input;\n const minTrades = input.minTrades ?? 10;\n\n // Load profile\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, blockId, strategyName, baseDir);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${strategyName}' in block '${blockId}'. Create one with profile_strategy first.`,\n { error: \"profile_not_found\" }\n );\n }\n\n // Load trades + market data\n const { matched, unmatchedCount, allTrades } = await loadTradesAndMarket(\n baseDir,\n blockId,\n strategyName\n );\n\n const warnings: string[] = [];\n\n if (allTrades.length === 0) {\n return createToolOutput(\n `No trades found for strategy '${strategyName}' in block '${blockId}'.`,\n { error: \"no_trades\" }\n );\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded from market-based analysis.`\n );\n }\n\n if (matched.length === 0) {\n return createToolOutput(\n `No trades could be matched to market data for strategy '${strategyName}'.`,\n { error: \"no_market_match\", warnings }\n );\n }\n\n // Overall stats\n const allPls = matched.map((m) => m.trade.pl);\n const overall = computeSliceStats(allPls);\n\n // Dimension analysis\n const dimensions: Record<string, Record<string, SliceStats>> = {};\n\n // --- Fixed dimension: Vol_Regime ---\n const volRegimeBuckets: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"prev_Vol_Regime\");\n if (isNaN(val)) continue;\n const label = VOL_REGIME_LABELS[val] || `regime_${val}`;\n if (!volRegimeBuckets[label]) volRegimeBuckets[label] = [];\n volRegimeBuckets[label].push(trade.pl);\n }\n const volRegimeStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(volRegimeBuckets)) {\n volRegimeStats[label] = computeSliceStats(pls);\n }\n dimensions[\"Vol_Regime\"] = volRegimeStats;\n\n // --- Fixed dimension: day_of_week ---\n const dowBuckets: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"Day_of_Week\");\n if (isNaN(val)) continue;\n const label = DAY_LABELS[val] || `day_${val}`;\n if (!dowBuckets[label]) dowBuckets[label] = [];\n dowBuckets[label].push(trade.pl);\n }\n const dowStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(dowBuckets)) {\n dowStats[label] = computeSliceStats(pls);\n }\n dimensions[\"day_of_week\"] = dowStats;\n\n // --- Fixed dimension: time_of_day ---\n const todBuckets: Record<string, number[]> = {};\n for (const { trade } of matched) {\n const bucket = getTimeBucket(trade.timeOpened);\n if (!bucket) continue;\n if (!todBuckets[bucket]) todBuckets[bucket] = [];\n todBuckets[bucket].push(trade.pl);\n }\n const todStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(todBuckets)) {\n todStats[label] = computeSliceStats(pls);\n }\n dimensions[\"time_of_day\"] = todStats;\n\n // --- Profile-derived dimensions from entry_filters (market-source only) ---\n for (const filter of profile.entryFilters.filter((f) => f.source !== \"execution\")) {\n const predicate = buildFilterPredicate(filter);\n const fieldKey = predicate.fieldKey;\n\n // Collect numeric values for this field from matched trades\n const fieldValues: { val: number; pl: number }[] = [];\n for (const { trade, market } of matched) {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) continue;\n const num = Number(raw);\n if (isNaN(num)) continue;\n fieldValues.push({ val: num, pl: trade.pl });\n }\n\n if (fieldValues.length === 0) continue;\n\n // Create buckets from the data\n const buckets = createNumericBuckets(fieldValues.map((f) => f.val));\n if (buckets.length === 0) continue;\n\n const filterBuckets: Record<string, number[]> = {};\n for (const { val, pl } of fieldValues) {\n const bucketLabel = findBucket(val, buckets);\n if (!bucketLabel) continue;\n if (!filterBuckets[bucketLabel]) filterBuckets[bucketLabel] = [];\n filterBuckets[bucketLabel].push(pl);\n }\n\n const filterStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(filterBuckets)) {\n filterStats[label] = computeSliceStats(pls);\n }\n dimensions[filter.field] = filterStats;\n }\n\n // Thin-data warnings\n for (const [dimName, bucketStats] of Object.entries(dimensions)) {\n for (const [bucketLabel, stats] of Object.entries(bucketStats)) {\n if (stats.tradeCount > 0 && stats.tradeCount < minTrades) {\n warnings.push(\n `${dimName}/${bucketLabel}: only ${stats.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n }\n }\n\n // Profile update hints\n const profileUpdateHints: { field: string; suggested: string; reason: string }[] = [];\n\n // Check Vol_Regime performance vs overall\n for (const [label, stats] of Object.entries(volRegimeStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (winRateDiff >= 20) {\n profileUpdateHints.push({\n field: \"expectedRegimes\",\n suggested: label,\n reason: `Win rate ${stats.winRate.toFixed(1)}% in ${label} is ${winRateDiff.toFixed(1)}pp above overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n if (winRateDiff <= -20) {\n profileUpdateHints.push({\n field: \"expectedRegimes\",\n suggested: `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% in ${label} is ${Math.abs(winRateDiff).toFixed(1)}pp below overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Check day_of_week for stark differences\n for (const [label, stats] of Object.entries(dowStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (Math.abs(winRateDiff) >= 20) {\n profileUpdateHints.push({\n field: \"day_of_week\",\n suggested: winRateDiff > 0 ? `favor_${label}` : `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% on ${label} vs overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Check time_of_day for stark differences\n for (const [label, stats] of Object.entries(todStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (Math.abs(winRateDiff) >= 20) {\n profileUpdateHints.push({\n field: \"time_of_day\",\n suggested: winRateDiff > 0 ? `favor_${label}` : `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% during ${label} vs overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Summary text\n const dimNames = Object.keys(dimensions).join(\", \");\n const summaryText = `Structure fit analysis for '${strategyName}': ${matched.length} trades analyzed across ${Object.keys(dimensions).length} dimensions (${dimNames}). Overall win rate: ${overall.winRate.toFixed(1)}%, avg P&L: $${overall.avgPl.toFixed(2)}. ${profileUpdateHints.length} update hint(s).`;\n\n return createToolOutput(summaryText, {\n overall,\n dimensions,\n profile_update_hints: profileUpdateHints,\n warnings,\n profile: {\n strategyName: profile.strategyName,\n structureType: profile.structureType,\n greeksBias: profile.greeksBias,\n thesis: profile.thesis,\n expectedRegimes: profile.expectedRegimes,\n },\n });\n}\n\n// =============================================================================\n// validate_entry_filters Schema and Handler\n// =============================================================================\n\nexport const validateEntryFiltersSchema = z.object({\n blockId: z.string().describe(\"Block ID to analyze\"),\n strategyName: z.string().describe(\"Strategy name matching a stored profile\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Minimum trades per group for reliable stats\"),\n maxAblationFilters: z\n .number()\n .optional()\n .default(8)\n .describe(\"Maximum number of filters for pairwise ablation (cap for combinatorial explosion)\"),\n});\n\nexport async function handleValidateEntryFilters(\n input: z.infer<typeof validateEntryFiltersSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const { blockId, strategyName } = input;\n const minTrades = input.minTrades ?? 10;\n const maxAblationFilters = input.maxAblationFilters ?? 8;\n\n // Load profile\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, blockId, strategyName, baseDir);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${strategyName}' in block '${blockId}'. Create one with profile_strategy first.`,\n { error: \"profile_not_found\" }\n );\n }\n\n // Early return if no entry filters\n if (!profile.entryFilters || profile.entryFilters.length === 0) {\n return createToolOutput(\n `Profile '${strategyName}' has no entry_filters defined. Add filters via profile_strategy to enable validation.`,\n { no_filters: true }\n );\n }\n\n // Separate market-testable filters from execution-only filters\n const allFilters = profile.entryFilters;\n const marketFilters = allFilters.filter((f) => f.source !== \"execution\");\n const executionFilters = allFilters.filter((f) => f.source === \"execution\");\n\n if (marketFilters.length === 0) {\n return createToolOutput(\n `Profile '${strategyName}' has ${allFilters.length} filter(s) but all are tagged source:'execution' (platform-level). No market-data filters to validate.`,\n { no_market_filters: true, execution_filters: executionFilters }\n );\n }\n\n // Load trades + market data\n const { matched, unmatchedCount, allTrades } = await loadTradesAndMarket(\n baseDir,\n blockId,\n strategyName\n );\n\n const warnings: string[] = [];\n\n if (executionFilters.length > 0) {\n warnings.push(\n `${executionFilters.length} execution-level filter(s) skipped (not testable against market data): ${executionFilters.map((f) => f.description || f.field).join(\", \")}`\n );\n }\n\n if (allTrades.length === 0) {\n return createToolOutput(\n `No trades found for strategy '${strategyName}' in block '${blockId}'.`,\n { error: \"no_trades\" }\n );\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded.`\n );\n }\n\n if (matched.length === 0) {\n return createToolOutput(\n `No trades could be matched to market data for strategy '${strategyName}'.`,\n { error: \"no_market_match\", warnings }\n );\n }\n\n // Build predicates for market-testable filters only\n const filters = marketFilters;\n const predicates: FilterPredicate[] = filters.map((f) => buildFilterPredicate(f));\n\n // No-filters baseline: all matched trades\n const noFiltersPls = matched.map((m) => m.trade.pl);\n const noFiltersStats = computeSliceStats(noFiltersPls);\n\n // Per-filter comparison\n const perFilter: Record<\n string,\n { entered: SliceStats; filtered_out: SliceStats; no_data_count: number }\n > = {};\n\n for (let i = 0; i < filters.length; i++) {\n const filter = filters[i];\n const predicate = predicates[i];\n const filterDesc =\n filter.description || `${filter.field} ${filter.operator} ${JSON.stringify(filter.value)}`;\n\n const enteredPls: number[] = [];\n const filteredOutPls: number[] = [];\n let noDataCount = 0;\n\n for (const { trade, market } of matched) {\n const raw = getRaw(market, predicate.fieldKey);\n if (raw === null || raw === undefined) {\n noDataCount++;\n continue;\n }\n if (predicate.test(market)) {\n enteredPls.push(trade.pl);\n } else {\n filteredOutPls.push(trade.pl);\n }\n }\n\n perFilter[filterDesc] = {\n entered: computeSliceStats(enteredPls),\n filtered_out: computeSliceStats(filteredOutPls),\n no_data_count: noDataCount,\n };\n }\n\n // Ablation study\n // Baseline: all filters applied\n const baselinePls: number[] = [];\n for (const { trade, market } of matched) {\n let passesAll = true;\n let hasData = true;\n for (const predicate of predicates) {\n const raw = getRaw(market, predicate.fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicate.test(market)) {\n passesAll = false;\n break;\n }\n }\n if (hasData && passesAll) {\n baselinePls.push(trade.pl);\n }\n }\n const baseline = computeSliceStats(baselinePls);\n\n // Single removal ablation\n const ablationSingle: Record<string, SliceStats> = {};\n for (let skip = 0; skip < filters.length; skip++) {\n const filterDesc =\n filters[skip].description ||\n `${filters[skip].field} ${filters[skip].operator} ${JSON.stringify(filters[skip].value)}`;\n\n const pls: number[] = [];\n for (const { trade, market } of matched) {\n let passesRemaining = true;\n let hasData = true;\n for (let j = 0; j < predicates.length; j++) {\n if (j === skip) continue;\n const raw = getRaw(market, predicates[j].fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicates[j].test(market)) {\n passesRemaining = false;\n break;\n }\n }\n if (hasData && passesRemaining) {\n pls.push(trade.pl);\n }\n }\n ablationSingle[filterDesc] = computeSliceStats(pls);\n }\n\n // Pairwise removal ablation (only if filter count <= maxAblationFilters)\n const ablationPairs: Record<string, SliceStats> = {};\n if (filters.length <= maxAblationFilters) {\n for (let i = 0; i < filters.length; i++) {\n for (let j = i + 1; j < filters.length; j++) {\n const descI =\n filters[i].description ||\n `${filters[i].field} ${filters[i].operator} ${JSON.stringify(filters[i].value)}`;\n const descJ =\n filters[j].description ||\n `${filters[j].field} ${filters[j].operator} ${JSON.stringify(filters[j].value)}`;\n const pairKey = `${descI} + ${descJ}`;\n\n const pls: number[] = [];\n for (const { trade, market } of matched) {\n let passesRemaining = true;\n let hasData = true;\n for (let k = 0; k < predicates.length; k++) {\n if (k === i || k === j) continue;\n const raw = getRaw(market, predicates[k].fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicates[k].test(market)) {\n passesRemaining = false;\n break;\n }\n }\n if (hasData && passesRemaining) {\n pls.push(trade.pl);\n }\n }\n ablationPairs[pairKey] = computeSliceStats(pls);\n }\n }\n }\n\n // Profile update hints\n const profileUpdateHints: {\n field: string;\n action: \"remove\" | \"adjust\";\n reason: string;\n }[] = [];\n\n // Check per-filter: if entered performs worse than filtered_out, suggest removal\n for (const [filterDesc, { entered, filtered_out }] of Object.entries(perFilter)) {\n if (\n entered.tradeCount >= minTrades &&\n filtered_out.tradeCount >= minTrades\n ) {\n if (entered.avgPl < filtered_out.avgPl && filtered_out.avgPl > 0) {\n profileUpdateHints.push({\n field: filterDesc,\n action: \"remove\",\n reason: `Entered avg P&L ($${entered.avgPl.toFixed(2)}) worse than filtered-out ($${filtered_out.avgPl.toFixed(2)}) — filter may be counterproductive`,\n });\n }\n }\n }\n\n // Check ablation: if removing a filter improves over baseline\n for (const [filterDesc, stats] of Object.entries(ablationSingle)) {\n if (stats.tradeCount >= minTrades && baseline.tradeCount >= minTrades) {\n if (stats.avgPl > baseline.avgPl && stats.winRate > baseline.winRate) {\n profileUpdateHints.push({\n field: filterDesc,\n action: \"remove\",\n reason: `Removing this filter improves avg P&L ($${stats.avgPl.toFixed(2)} vs $${baseline.avgPl.toFixed(2)}) and win rate (${stats.winRate.toFixed(1)}% vs ${baseline.winRate.toFixed(1)}%)`,\n });\n }\n }\n }\n\n // Thin-data warnings\n if (baseline.tradeCount > 0 && baseline.tradeCount < minTrades) {\n warnings.push(\n `Baseline (all filters): only ${baseline.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n for (const [filterDesc, { entered, filtered_out }] of Object.entries(perFilter)) {\n if (entered.tradeCount > 0 && entered.tradeCount < minTrades) {\n warnings.push(\n `${filterDesc} entered: only ${entered.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n if (filtered_out.tradeCount > 0 && filtered_out.tradeCount < minTrades) {\n warnings.push(\n `${filterDesc} filtered_out: only ${filtered_out.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n }\n\n // Summary text\n const execNote = executionFilters.length > 0 ? ` (${executionFilters.length} execution filter(s) skipped)` : \"\";\n const summaryText = `Filter validation for '${strategyName}': ${filters.length} market filter(s) analyzed across ${matched.length} trades${execNote}. Baseline (all market filters): ${baseline.tradeCount} trades, win rate ${baseline.winRate.toFixed(1)}%, avg P&L $${baseline.avgPl.toFixed(2)}. ${profileUpdateHints.length} update hint(s).`;\n\n return createToolOutput(summaryText, {\n baseline,\n no_filters: noFiltersStats,\n per_filter: perFilter,\n ablation: {\n single: ablationSingle,\n pairs: ablationPairs,\n },\n execution_filters_skipped: executionFilters.map((f) => f.description || `${f.field} ${f.operator} ${f.value}`),\n profile_update_hints: profileUpdateHints,\n warnings,\n });\n}\n\n// =============================================================================\n// portfolio_structure_map Schema and Handler\n// =============================================================================\n\nexport const portfolioStructureMapSchema = z.object({\n blockId: z\n .string()\n .optional()\n .describe(\"Block ID to analyze. When omitted, aggregate across all blocks.\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Thin-data warning threshold (default: 10)\"),\n});\n\nexport async function handlePortfolioStructureMap(\n input: z.infer<typeof portfolioStructureMapSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput> | { content: Array<{ type: \"text\"; text: string }>; isError?: boolean }> {\n try {\n const { blockId, minTrades } = portfolioStructureMapSchema.parse(input);\n const conn = await getConnection(baseDir);\n\n // Load profiles\n const profiles = await listProfiles(conn, blockId, baseDir);\n if (profiles.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: blockId\n ? `No strategy profiles found for block '${blockId}'. Use profile_strategy to create profiles first.`\n : \"No strategy profiles found. Use profile_strategy to create profiles first.\",\n },\n ],\n };\n }\n\n // Collect all trades per strategy, matched to market data\n interface StrategyTradeMarket {\n strategyName: string;\n trade: Trade;\n market: Record<string, unknown>;\n }\n\n const allTradeMarkets: StrategyTradeMarket[] = [];\n const warnings: string[] = [];\n\n for (const profile of profiles) {\n let block;\n try {\n block = await loadBlock(baseDir, profile.blockId);\n } catch {\n warnings.push(`Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'`);\n continue;\n }\n\n let trades = filterByStrategy(block.trades, profile.strategyName);\n // Single-strategy block fallback (see loadTradesAndMarket)\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n if (trades.length === 0) {\n warnings.push(`No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'`);\n continue;\n }\n\n // Query market data for trade dates\n const tradeKeys = uniqueTradeLookupKeys(trades);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const dailyResult = await conn.runAndReadAll(sql, params);\n const dailyRecords = resultToRecords(dailyResult);\n const daily = recordsByTickerDate(dailyRecords);\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const marketKey = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = daily.get(marketKey);\n if (market) {\n allTradeMarkets.push({\n strategyName: profile.strategyName,\n trade,\n market,\n });\n }\n }\n }\n\n if (allTradeMarkets.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No trades could be matched to market data. Ensure market data is imported and enriched.\",\n },\n ],\n };\n }\n\n // Build the 18-cell matrix: Vol_Regime (6) x Trend_Direction (3)\n // Use prev_ prefix for both fields (both are close-derived, need LAG)\n const strategyNames = [...new Set(allTradeMarkets.map((t) => t.strategyName))];\n\n // Collect PLs per cell per strategy\n type CellKey = string; // \"regime:trend\"\n const cellPls = new Map<CellKey, Map<string, number[]>>();\n\n let unknownTrendCount = 0;\n const unknownTrendPls = new Map<string, number[]>(); // strategy -> pls for unknown trend\n\n for (const { strategyName, trade, market } of allTradeMarkets) {\n const volRegime = getNum(market, \"prev_Vol_Regime\");\n const trendRaw = market[\"prev_Trend_Direction\"];\n\n // Handle missing Vol_Regime\n if (isNaN(volRegime) || volRegime < 1 || volRegime > 6) continue;\n\n // Handle missing Trend_Direction\n let trend: TrendLabel | null = null;\n if (\n trendRaw === null ||\n trendRaw === undefined ||\n trendRaw === \"\"\n ) {\n unknownTrendCount++;\n if (!unknownTrendPls.has(strategyName)) {\n unknownTrendPls.set(strategyName, []);\n }\n unknownTrendPls.get(strategyName)!.push(trade.pl);\n continue;\n }\n const trendStr = String(trendRaw).toLowerCase();\n if (trendStr === \"up\" || trendStr === \"down\" || trendStr === \"flat\") {\n trend = trendStr as TrendLabel;\n } else {\n unknownTrendCount++;\n if (!unknownTrendPls.has(strategyName)) {\n unknownTrendPls.set(strategyName, []);\n }\n unknownTrendPls.get(strategyName)!.push(trade.pl);\n continue;\n }\n\n const regimeLabel = VOL_REGIME_LABELS[volRegime] || `regime_${volRegime}`;\n const cellKey = `${regimeLabel}:${trend}`;\n\n if (!cellPls.has(cellKey)) {\n cellPls.set(cellKey, new Map());\n }\n const cellMap = cellPls.get(cellKey)!;\n if (!cellMap.has(strategyName)) {\n cellMap.set(strategyName, []);\n }\n cellMap.get(strategyName)!.push(trade.pl);\n }\n\n // Build matrix output\n const matrix: Record<string, Record<string, Record<string, SliceStats>>> = {};\n const overlaps: Array<{\n regime: string;\n trend: string;\n strategies: string[];\n totalTrades: number;\n }> = [];\n const blindSpots: Array<{ regime: string; trend: string }> = [];\n let coveredCells = 0;\n let overlapCells = 0;\n\n for (const [, regimeLabel] of Object.entries(VOL_REGIME_LABELS)) {\n matrix[regimeLabel] = {};\n for (const trend of TREND_LABELS) {\n const cellKey = `${regimeLabel}:${trend}`;\n const cellMap = cellPls.get(cellKey);\n\n if (!cellMap || cellMap.size === 0) {\n blindSpots.push({ regime: regimeLabel, trend });\n matrix[regimeLabel][trend] = {};\n continue;\n }\n\n coveredCells++;\n const cellStats: Record<string, SliceStats> = {};\n const strategiesInCell: string[] = [];\n let totalTradesInCell = 0;\n\n for (const [stratName, pls] of cellMap) {\n cellStats[stratName] = computeSliceStats(pls);\n strategiesInCell.push(stratName);\n totalTradesInCell += pls.length;\n\n // Thin-data warning\n if (pls.length > 0 && pls.length < minTrades) {\n warnings.push(\n `Thin data: '${stratName}' has only ${pls.length} trades in ${regimeLabel}/${trend} (threshold: ${minTrades})`\n );\n }\n }\n\n matrix[regimeLabel][trend] = cellStats;\n\n // Overlap detection: 2+ strategies in same cell\n if (strategiesInCell.length >= 2) {\n overlapCells++;\n overlaps.push({\n regime: regimeLabel,\n trend,\n strategies: strategiesInCell,\n totalTrades: totalTradesInCell,\n });\n }\n }\n }\n\n const blindSpotCells = blindSpots.length;\n\n // Handle unknown trend trades\n if (unknownTrendCount > 0) {\n warnings.push(\n `${unknownTrendCount} trades had missing or unknown Trend_Direction. Consider running enrich_market_data to populate Trend_Direction.`\n );\n }\n\n // Build unknown trend stats if any\n const unknownTrendStats: Record<string, SliceStats> | undefined =\n unknownTrendPls.size > 0\n ? Object.fromEntries(\n [...unknownTrendPls.entries()].map(([name, pls]) => [\n name,\n computeSliceStats(pls),\n ])\n )\n : undefined;\n\n const coverageSummary = {\n totalCells: 18,\n coveredCells,\n blindSpotCells,\n overlapCells,\n };\n\n const summary = `Portfolio structure map: ${strategyNames.length} strategies | ${coveredCells}/18 cells covered | ${overlapCells} overlaps | ${blindSpotCells} blind spots`;\n\n const structuredData: Record<string, unknown> = {\n strategies: strategyNames,\n matrix,\n overlaps,\n blind_spots: blindSpots,\n coverage_summary: coverageSummary,\n warnings,\n };\n\n if (unknownTrendStats) {\n structuredData.unknown_trend = unknownTrendStats;\n }\n\n return createToolOutput(summary, structuredData);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error building portfolio structure map: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n}\n\n// =============================================================================\n// Registration\n// =============================================================================\n\n/**\n * Register all profile analysis tools.\n * This includes portfolio_structure_map (from Plan 03) and\n * analyze_structure_fit + validate_entry_filters (from Plan 02, if present).\n */\nexport function registerProfileAnalysisTools(\n server: McpServer,\n baseDir: string\n): void {\n // portfolio_structure_map: optional blockId means we can't always use withSyncedBlock.\n // When blockId is provided, sync that block. When omitted, sync all blocks.\n server.registerTool(\n \"portfolio_structure_map\",\n {\n description:\n \"Build a Vol_Regime x Trend_Direction matrix (18 cells) across all profiled strategies. \" +\n \"Shows per-strategy stats in each cell, detects overlap (2+ strategies in same cell), \" +\n \"blind spots (cells with zero trades), and thin-data warnings. \" +\n \"Optionally filter to a single block or aggregate across all blocks.\",\n inputSchema: portfolioStructureMapSchema,\n },\n async (input) => {\n // Manual sync: if blockId provided, sync just that block; otherwise sync all\n await upgradeToReadWrite(baseDir, { fallbackToReadOnly: true });\n if (getConnectionMode() === \"read_write\") {\n try {\n if (input.blockId) {\n const { syncBlock } = await import(\"../sync/index.ts\");\n await syncBlock(input.blockId, baseDir);\n } else {\n await syncAllBlocks(baseDir);\n }\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n }\n\n return handlePortfolioStructureMap(input, baseDir);\n }\n );\n\n // -------------------------------------------------------------------------\n // Tool: analyze_structure_fit\n // -------------------------------------------------------------------------\n server.registerTool(\n \"analyze_structure_fit\",\n {\n description:\n \"Analyze how well a strategy fits various market dimensions using its stored profile. \" +\n \"Returns performance breakdown by Vol_Regime, day-of-week, time-of-day, and profile-derived \" +\n \"dimensions from entry_filters. Includes profile_update_hints when data shows clear patterns \" +\n \"diverging from profile, and thin-data warnings for small buckets.\",\n inputSchema: analyzeStructureFitSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleAnalyzeStructureFit(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: validate_entry_filters\n // -------------------------------------------------------------------------\n server.registerTool(\n \"validate_entry_filters\",\n {\n description:\n \"Validate effectiveness of a strategy's entry filters. Splits trades into entered vs \" +\n \"filtered-out groups per filter and shows full stat suite for both. Runs ablation study \" +\n \"removing one filter at a time and testing all pairs. Returns profile_update_hints when \" +\n \"filters appear counterproductive.\",\n inputSchema: validateEntryFiltersSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleValidateEntryFilters(input, ctx.baseDir);\n })\n );\n}\n","/**\n * Regime Allocation Advisor Tool\n *\n * Cross-references strategy profiles' expected regimes with actual trading\n * performance per regime. Surfaces thesis violations and hidden edges as\n * structured data without prescriptive recommendations.\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { loadBlock } from \"../utils/block-loader.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport type { Trade } from \"@tradeblocks/lib\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { listProfiles } from \"../db/profile-schemas.ts\";\nimport { filterByStrategy } from \"./shared/filters.ts\";\nimport {\n buildLookaheadFreeQuery,\n type MarketLookupKey,\n} from \"../utils/field-timing.ts\";\nimport {\n DEFAULT_MARKET_TICKER,\n marketTickerDateKey,\n resolveTradeTicker,\n} from \"../utils/ticker.ts\";\nimport { computeSliceStats, type SliceStats } from \"../utils/analysis-stats.ts\";\nimport {\n upgradeToReadWrite,\n downgradeToReadOnly,\n getConnectionMode,\n} from \"../db/connection.ts\";\nimport { syncAllBlocks } from \"../sync/index.ts\";\n\n// =============================================================================\n// Utility Functions (local to this module, copied from profile-analysis.ts)\n// =============================================================================\n\nfunction formatTradeDate(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nfunction getTradeLookupKey(trade: Trade): MarketLookupKey {\n return {\n date: formatTradeDate(trade.dateOpened),\n ticker: resolveTradeTicker(trade, DEFAULT_MARKET_TICKER),\n };\n}\n\nfunction uniqueTradeLookupKeys(trades: Trade[]): MarketLookupKey[] {\n const byKey = new Map<string, MarketLookupKey>();\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n byKey.set(marketTickerDateKey(lookup.ticker, lookup.date), lookup);\n }\n return Array.from(byKey.values());\n}\n\nfunction resultToRecords(result: {\n columnCount: number;\n columnName(i: number): string;\n getRows(): Iterable<unknown[]>;\n}): Record<string, unknown>[] {\n const columnCount = result.columnCount;\n const colNames: string[] = [];\n for (let i = 0; i < columnCount; i++) {\n colNames.push(result.columnName(i));\n }\n const records: Record<string, unknown>[] = [];\n for (const row of result.getRows()) {\n const record: Record<string, unknown> = {};\n for (let i = 0; i < columnCount; i++) {\n const val = row[i];\n record[colNames[i]] = typeof val === \"bigint\" ? Number(val) : val;\n }\n records.push(record);\n }\n return records;\n}\n\nfunction recordsByTickerDate(\n records: Record<string, unknown>[]\n): Map<string, Record<string, unknown>> {\n const mapped = new Map<string, Record<string, unknown>>();\n for (const record of records) {\n const date = String(record[\"date\"] || \"\");\n const ticker = String(record[\"ticker\"] || DEFAULT_MARKET_TICKER);\n mapped.set(marketTickerDateKey(ticker, date), record);\n }\n return mapped;\n}\n\nfunction getNum(record: Record<string, unknown>, field: string): number {\n const val = record[field];\n if (val === null || val === undefined) return NaN;\n if (typeof val === \"bigint\") return Number(val);\n return val as number;\n}\n\nconst VOL_REGIME_LABELS: Record<number, string> = {\n 1: \"very_low\",\n 2: \"low\",\n 3: \"below_avg\",\n 4: \"above_avg\",\n 5: \"high\",\n 6: \"extreme\",\n};\n\n// =============================================================================\n// Types\n// =============================================================================\n\ninterface RegimeCell {\n stats: SliceStats;\n isExpected: boolean;\n classification: \"thesis_aligned\" | \"thesis_violation\" | \"hidden_edge\" | \"neutral\";\n}\n\ninterface StrategyRegimeComparison {\n strategyName: string;\n blockId: string;\n structureType: string;\n underlying?: string;\n allocationPct?: number;\n expectedRegimes: string[];\n regimePerformance: Record<string, RegimeCell>;\n tradeCount: number;\n matchedToMarket: number;\n unmatchedCount: number;\n}\n\n// =============================================================================\n// Schema\n// =============================================================================\n\nexport const regimeAllocationAdvisorSchema = z.object({\n blockId: z\n .string()\n .optional()\n .describe(\n \"Block ID to analyze. When omitted, aggregate across all profiled strategies.\"\n ),\n minTrades: z\n .number()\n .optional()\n .default(5)\n .describe(\n \"Minimum trades per regime cell for reliable stats (default: 5)\"\n ),\n});\n\n// =============================================================================\n// Handler\n// =============================================================================\n\nexport async function handleRegimeAllocationAdvisor(\n input: z.infer<typeof regimeAllocationAdvisorSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const minTrades = input.minTrades ?? 5;\n const warnings: string[] = [];\n const profileUpgradeHints: string[] = [];\n\n // Load all profiles, optionally filtered by blockId\n const conn = await getConnection(baseDir);\n const profiles = await listProfiles(conn, input.blockId, baseDir);\n\n if (profiles.length === 0) {\n return createToolOutput(\n input.blockId\n ? `No strategy profiles found for block '${input.blockId}'. Use profile_strategy to create profiles first.`\n : \"No strategy profiles found. Use profile_strategy to create profiles first.\",\n { error: \"no_profiles\" }\n );\n }\n\n const strategies: StrategyRegimeComparison[] = [];\n let skippedNoRegimes = 0;\n let skippedNoMarket = 0;\n const allThesisViolations: {\n strategyName: string;\n regime: string;\n winRate: number;\n expectedWinRate: number;\n }[] = [];\n const allHiddenEdges: {\n strategyName: string;\n regime: string;\n winRate: number;\n overallWinRate: number;\n }[] = [];\n\n // Per-regime aggregation across all strategies\n const regimeAggPls: Record<string, number[]> = {};\n\n for (const profile of profiles) {\n try {\n // Skip profiles without expectedRegimes\n if (!profile.expectedRegimes || profile.expectedRegimes.length === 0) {\n skippedNoRegimes++;\n profileUpgradeHints.push(\n `Strategy '${profile.strategyName}' (block: ${profile.blockId}) has no expectedRegimes. Add via profile_strategy.`\n );\n continue;\n }\n\n // Load trades\n let block;\n try {\n block = await loadBlock(baseDir, profile.blockId);\n } catch {\n warnings.push(\n `Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'. Skipped.`\n );\n continue;\n }\n\n let trades = filterByStrategy(block.trades, profile.strategyName);\n // Single-strategy block fallback\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n\n if (trades.length === 0) {\n warnings.push(\n `No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'. Skipped.`\n );\n continue;\n }\n\n // Query market data\n const tradeKeys = uniqueTradeLookupKeys(trades);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const result = await conn.runAndReadAll(sql, params);\n const marketRecords = resultToRecords(result);\n const marketMap = recordsByTickerDate(marketRecords);\n\n // Match trades to market records\n interface TradeWithMarket {\n trade: Trade;\n market: Record<string, unknown>;\n }\n const matched: TradeWithMarket[] = [];\n let unmatchedCount = 0;\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const key = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = marketMap.get(key);\n if (market) {\n matched.push({ trade, market });\n } else {\n unmatchedCount++;\n }\n }\n\n if (matched.length === 0) {\n skippedNoMarket++;\n warnings.push(\n `No market data matched for strategy '${profile.strategyName}' (${trades.length} trades). Import and enrich market data first.`\n );\n continue;\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `Strategy '${profile.strategyName}': ${unmatchedCount} of ${trades.length} trades had no market data match.`\n );\n }\n\n // Compute overall stats for this strategy\n const allPls = matched.map((m) => m.trade.pl);\n const overallStats = computeSliceStats(allPls);\n\n // Group trades by Vol_Regime\n const regimePls: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"prev_Vol_Regime\");\n if (isNaN(val) || val < 1 || val > 6) continue;\n const label = VOL_REGIME_LABELS[val] || `regime_${val}`;\n if (!regimePls[label]) regimePls[label] = [];\n regimePls[label].push(trade.pl);\n\n // Aggregate across strategies\n if (!regimeAggPls[label]) regimeAggPls[label] = [];\n regimeAggPls[label].push(trade.pl);\n }\n\n // Build per-regime comparison\n const expectedSet = new Set(\n profile.expectedRegimes.map((r) => r.toLowerCase())\n );\n const regimePerformance: Record<string, RegimeCell> = {};\n\n for (const [label, pls] of Object.entries(regimePls)) {\n const stats = computeSliceStats(pls);\n const isExpected = expectedSet.has(label.toLowerCase());\n\n // Classification logic:\n // thesis_aligned: isExpected AND performing reasonably (WR > 50% or > overall WR)\n // thesis_violation: isExpected AND WR significantly below overall (>10pp)\n // hidden_edge: NOT isExpected AND WR significantly above overall (>10pp) AND enough trades\n // neutral: everything else\n let classification: RegimeCell[\"classification\"];\n const wrDelta = stats.winRate - overallStats.winRate;\n\n if (isExpected) {\n if (wrDelta < -10) {\n classification = \"thesis_violation\";\n allThesisViolations.push({\n strategyName: profile.strategyName,\n regime: label,\n winRate: stats.winRate,\n expectedWinRate: overallStats.winRate,\n });\n } else {\n classification = \"thesis_aligned\";\n }\n } else {\n if (wrDelta > 10 && stats.tradeCount >= minTrades) {\n classification = \"hidden_edge\";\n allHiddenEdges.push({\n strategyName: profile.strategyName,\n regime: label,\n winRate: stats.winRate,\n overallWinRate: overallStats.winRate,\n });\n } else {\n classification = \"neutral\";\n }\n }\n\n regimePerformance[label] = { stats, isExpected, classification };\n }\n\n // Allocation from position sizing\n const allocationPct =\n profile.positionSizing?.liveAllocationPct ??\n profile.positionSizing?.allocationPct;\n\n strategies.push({\n strategyName: profile.strategyName,\n blockId: profile.blockId,\n structureType: profile.structureType,\n underlying: profile.underlying ?? undefined,\n allocationPct,\n expectedRegimes: profile.expectedRegimes,\n regimePerformance,\n tradeCount: trades.length,\n matchedToMarket: matched.length,\n unmatchedCount,\n });\n } catch (err) {\n warnings.push(\n `Error processing strategy '${profile.strategyName}' (block: ${profile.blockId}): ${(err as Error).message}`\n );\n }\n }\n\n // Build regime overview (aggregate stats per regime)\n const regimeOverview: Record<\n string,\n { strategiesActive: number; totalTrades: number; combinedStats: SliceStats }\n > = {};\n\n for (const [label, pls] of Object.entries(regimeAggPls)) {\n // Count how many strategies had trades in this regime\n let strategiesActive = 0;\n for (const strategy of strategies) {\n if (strategy.regimePerformance[label]) {\n strategiesActive++;\n }\n }\n regimeOverview[label] = {\n strategiesActive,\n totalTrades: pls.length,\n combinedStats: computeSliceStats(pls),\n };\n }\n\n const summary = {\n totalStrategies: profiles.length,\n profiled: strategies.length,\n skippedNoRegimes,\n skippedNoMarket,\n thesisViolations: allThesisViolations,\n hiddenEdges: allHiddenEdges,\n };\n\n const summaryText =\n `Regime allocation advisor: ${strategies.length}/${profiles.length} strategies analyzed. ` +\n `${allThesisViolations.length} thesis violation(s), ${allHiddenEdges.length} hidden edge(s). ` +\n (skippedNoRegimes > 0\n ? `${skippedNoRegimes} skipped (no expectedRegimes). `\n : \"\") +\n (skippedNoMarket > 0\n ? `${skippedNoMarket} skipped (no market data). `\n : \"\");\n\n return createToolOutput(summaryText, {\n strategies,\n summary,\n regimeOverview,\n warnings,\n profileUpgradeHints,\n });\n}\n\n// =============================================================================\n// Registration\n// =============================================================================\n\nexport function registerRegimeAdvisorTools(\n server: McpServer,\n baseDir: string\n): void {\n server.registerTool(\n \"regime_allocation_advisor\",\n {\n description:\n \"Cross-reference strategy profiles' expected regimes with actual trading performance. \" +\n \"Shows per-strategy, per-regime comparison with win rate, P&L, and trade count. \" +\n \"Classifications (thesis_aligned, thesis_violation, hidden_edge) emerge from data delta. \" +\n \"Optionally filter to a single block or aggregate across all profiled strategies.\",\n inputSchema: regimeAllocationAdvisorSchema,\n },\n async (input) => {\n // Manual sync pattern (same as portfolio_structure_map)\n await upgradeToReadWrite(baseDir, { fallbackToReadOnly: true });\n if (getConnectionMode() === \"read_write\") {\n try {\n if (input.blockId) {\n const { syncBlock } = await import(\"../sync/index.ts\");\n await syncBlock(input.blockId, baseDir);\n } else {\n await syncAllBlocks(baseDir);\n }\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n }\n return handleRegimeAllocationAdvisor(input, baseDir);\n }\n );\n}\n","import {\n getProvider,\n type MarketDataProvider,\n type ProviderCapabilities,\n} from \"./market-provider.ts\";\nimport {\n resolveMassiveDataTier,\n type MassiveDataTier,\n} from \"./massive-tier.ts\";\n\nexport interface ResolvedProviderCapabilities extends ProviderCapabilities {\n provider: MarketDataProvider;\n providerName: string;\n massiveDataTier: MassiveDataTier | null;\n quoteHydration: boolean;\n contractList: boolean;\n}\n\nexport function resolveProviderCapabilities(\n provider: MarketDataProvider = getProvider(),\n env: NodeJS.ProcessEnv = process.env,\n): ResolvedProviderCapabilities {\n const base = provider.capabilities();\n const massiveDataTier = provider.name === \"massive\" ? resolveMassiveDataTier(env) : null;\n // The right question for hydration dispatch is \"can I call fetchQuotes?\",\n // not \"is the data NBBO-grade?\" — provenance is captured per-row via\n // QuoteRow.source. Massive's fetchQuotes branches internally on\n // MASSIVE_DATA_TIER (true NBBO via /v3/quotes vs synthesized from /v2/aggs);\n // either path returns useful per-minute data.\n const quoteHydration = typeof provider.fetchQuotes === \"function\";\n\n return {\n ...base,\n provider,\n providerName: provider.name,\n massiveDataTier,\n quoteHydration,\n contractList: typeof provider.fetchContractList === \"function\",\n };\n}\n\nexport function getResolvedProviderCapabilities(\n env: NodeJS.ProcessEnv = process.env,\n): ResolvedProviderCapabilities {\n return resolveProviderCapabilities(getProvider(), env);\n}\n\nexport { resolveMassiveDataTier };\nexport type { MassiveDataTier };\n","/**\n * Trade Replay Tools\n *\n * MCP tool for replaying trades using historical minute-level option bars\n * read from the local market-data cache. Supports two modes:\n * A) Hypothetical replay — explicit legs with strikes/expiry/dates\n * B) Tradelog replay — block_id + trade_index to replay from existing trade data\n *\n * Tools registered:\n * - replay_trade — Replay a trade and compute minute-by-minute P&L path with MFE/MAE\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport type { MarketStores } from \"../market/stores/index.ts\";\nimport { extractRoot } from \"../market/tickers/resolver.ts\";\nimport type { QuoteRow } from \"../market/stores/types.ts\";\nimport {\n parseLegsString,\n buildOccTicker,\n computeStrategyPnlPath,\n computeReplayMfeMae,\n markPrice,\n type ReplayLeg,\n type ReplayResult,\n type GreeksConfig,\n} from \"../utils/trade-replay.ts\";\nimport type { BarRow } from \"../utils/market-provider.ts\";\n\n// ---------------------------------------------------------------------------\n// Zod schema\n// ---------------------------------------------------------------------------\n\nexport const replayTradeSchema = z.object({\n // Mode A: Hypothetical / explicit legs\n legs: z\n .array(\n z.object({\n ticker: z.string().describe(\"Underlying ticker, e.g., 'SPY', 'SPX'\"),\n strike: z.number().describe(\"Strike price\"),\n type: z.enum([\"C\", \"P\"]).describe(\"Call or Put\"),\n expiry: z.string().describe(\"Expiration date YYYY-MM-DD\"),\n quantity: z.number().describe(\"Positive = long, negative = short\"),\n entry_price: z\n .number()\n .describe(\"Per-contract entry price (premium paid/received)\"),\n })\n )\n .optional()\n .describe(\"Explicit leg definitions for hypothetical replay\"),\n\n // Mode B: Tradelog replay\n block_id: z.string().optional().describe(\"Block ID to load trade from\"),\n trade_index: z\n .number()\n .optional()\n .describe(\n \"0-based index of trade in block's tradelog (ordered by date_opened)\"\n ),\n\n // Common fields\n open_date: z\n .string()\n .optional()\n .describe(\n \"Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)\"\n ),\n close_date: z\n .string()\n .optional()\n .describe(\n \"Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)\"\n ),\n multiplier: z\n .number()\n .default(100)\n .describe(\"Contract multiplier (default 100 for standard options)\"),\n format: z\n .enum([\"full\", \"summary\", \"sampled\"])\n .default(\"sampled\")\n .describe(\n \"Output format: 'sampled' returns path sampled at ~15min intervals (default), \" +\n \"'full' returns complete minute-by-minute P&L path, \" +\n \"'summary' returns MFE/MAE/P&L without minute-level path\"\n ),\n close_at: z\n .enum([\"trade\", \"expiry\"])\n .default(\"trade\")\n .describe(\n \"When to end the P&L path: 'trade' (default) truncates at the trade's actual close time, \" +\n \"'expiry' shows full path through option expiry. Only applies to tradelog mode.\"\n ),\n skip_quotes: z\n .boolean()\n .default(false)\n .describe(\n \"Skip NBBO quote enrichment for option bars. Faster, but uses cached trade bars / HL2 marks.\"\n ),\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst MONTH_MAP: Record<string, string> = {\n Jan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06',\n Jul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12',\n};\n\n/** Convert OO expiry hint \"Mar 13\" + year \"2026\" → \"2026-03-13\" */\nfunction resolveOOExpiryHint(hint: string, year: string): string {\n const [mon, day] = hint.split(' ');\n const mm = MONTH_MAP[mon] ?? '01';\n const dd = day.padStart(2, '0');\n return `${year}-${mm}-${dd}`;\n}\n\n/**\n * Derive fetch date range from OO leg expiryHints.\n *\n * For calendar spreads (different expiries): min(expiry)→max(expiry).\n * For single-expiry trades: tradeOpenDate→expiry.\n * Returns null if no legs have expiryHint (caller falls back to trade dates).\n */\nexport function resolveOODateRange(\n parsedLegs: import(\"../utils/trade-replay.ts\").ParsedLegOO[],\n tradeYear: string,\n tradeOpenDate: string,\n): { from: string; to: string } | null {\n const hints = parsedLegs\n .filter(l => l.expiryHint)\n .map(l => resolveOOExpiryHint(l.expiryHint!, tradeYear));\n\n if (hints.length === 0) return null;\n\n const sorted = [...hints].sort();\n const maxDate = sorted[sorted.length - 1];\n\n // Always start from trade open date — bars are needed from entry, not from expiry.\n // End at the latest expiry to cover the full path.\n return { from: tradeOpenDate, to: maxDate };\n}\n\n// ---------------------------------------------------------------------------\n// Handler (exported for testing)\n// ---------------------------------------------------------------------------\n\nexport async function handleReplayTrade(\n params: z.infer<typeof replayTradeSchema>,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection\n): Promise<ReplayResult> {\n const {\n legs: inputLegs,\n block_id,\n trade_index,\n multiplier,\n close_at,\n skip_quotes,\n } = params;\n let { open_date, close_date } = params;\n let tradeCloseTimestamp: string | undefined; // \"YYYY-MM-DD HH:MM\" when trade actually closed\n\n let replayLegs: ReplayLeg[];\n\n if (inputLegs && inputLegs.length > 0) {\n // ----- Mode A: Hypothetical replay -----\n if (!open_date || !close_date) {\n throw new Error(\n \"open_date and close_date are required for hypothetical replay mode\"\n );\n }\n\n replayLegs = inputLegs.map((leg) => ({\n occTicker: buildOccTicker(leg.ticker, leg.expiry, leg.type, leg.strike),\n quantity: leg.quantity,\n entryPrice: leg.entry_price,\n multiplier,\n }));\n } else if (block_id !== undefined && trade_index !== undefined) {\n // ----- Mode B: Tradelog replay -----\n const conn = injectedConn ?? await getConnection(baseDir);\n\n const result = await conn.runAndReadAll(\n `SELECT legs, premium, date_opened, date_closed, ticker, num_contracts, time_closed\n FROM trades.trade_data\n WHERE block_id = '${block_id.replace(/'/g, \"''\")}'\n ORDER BY date_opened, rowid\n LIMIT 1 OFFSET ${trade_index}`\n );\n\n const rows = result.getRows();\n if (rows.length === 0) {\n throw new Error(\n `No trade found at index ${trade_index} in block \"${block_id}\"`\n );\n }\n\n const row = rows[0];\n const legsStr = String(row[0] ?? \"\");\n const premium = Number(row[1] ?? 0);\n const dateOpened = String(row[2] ?? \"\");\n const dateClosed = String(row[3] ?? \"\");\n const ticker = String(row[4] ?? \"\");\n const numContracts = Number(row[5] ?? 1);\n const timeClosed = String(row[6] ?? \"\");\n\n // Build actual trade close timestamp for path truncation\n if (dateClosed && timeClosed) {\n // time_closed is \"HH:MM:SS\" or \"HH:MM\" — normalize to \"HH:MM\"\n const normalizedTime = timeClosed.slice(0, 5);\n tradeCloseTimestamp = `${dateClosed} ${normalizedTime}`;\n }\n\n // Use trade dates if not provided\n open_date = open_date || dateOpened;\n close_date = close_date || dateClosed;\n\n // Parse legs from tradelog\n let parsedLegs;\n try {\n parsedLegs = parseLegsString(legsStr);\n } catch {\n throw new Error(\n `Cannot parse legs \"${legsStr}\" from tradelog — use hypothetical mode with explicit strikes`\n );\n }\n\n // Build ReplayLeg[] from parsed legs\n const root = ticker || parsedLegs[0].root;\n const perContractPremium =\n numContracts > 0 ? premium / numContracts : premium;\n\n // OO format provides per-leg entry price, contract count, and expiry hint\n const hasOOData = parsedLegs.some(l => l.entryPrice !== undefined);\n\n // Resolve per-leg expiry: OO expiryHint (\"Mar 13\") + year from trade date\n const tradeYear = (open_date || dateOpened).split('-')[0];\n\n // Override fetch date range from OO expiryHints when available\n if (hasOOData) {\n const ooRange = resolveOODateRange(parsedLegs, tradeYear, open_date || dateOpened);\n if (ooRange) {\n open_date = ooRange.from;\n close_date = ooRange.to;\n }\n }\n\n replayLegs = parsedLegs.map((leg) => {\n let legExpiry = close_date!;\n if (hasOOData && leg.expiryHint) {\n legExpiry = resolveOOExpiryHint(leg.expiryHint, tradeYear);\n }\n return {\n occTicker: buildOccTicker(root, legExpiry, leg.type, leg.strike),\n quantity: hasOOData\n ? leg.quantity * (leg.contracts ?? 1)\n : leg.quantity * (numContracts > 0 ? numContracts : 1),\n entryPrice: hasOOData\n ? leg.entryPrice!\n : perContractPremium / parsedLegs.length,\n multiplier,\n };\n });\n } else {\n throw new Error(\n \"Provide either legs[] for hypothetical mode or block_id + trade_index for tradelog mode\"\n );\n }\n\n // ----- Fetch minute quotes for each option leg via QuoteStore -----\n // Adapt QuoteRow → BarRow with mid = (bid+ask)/2 as open/high/low/close\n // (mid-price is the canonical mark for option-leg pricing).\n //\n // Group OCC tickers by underlying before each readQuotes call: the store\n // enforces a single-underlying invariant per call so partitioned reads\n // can be served from one parquet partition root. Typical replays have\n // all legs under one underlying (single SPX trade); multi-underlying\n // replays issue one readQuotes per underlying.\n //\n // Fallback root logic (SPX→SPXW etc.) is implicit: the QuoteStore's\n // tickers.resolve(extractRoot(...)) maps both SPX and SPXW to underlying\n // 'SPX'. The OCC ticker prefix in the chain is whatever the data layer\n // ingested (typically SPXW); keying on underlying makes the same data\n // reachable via either root.\n //\n // skip_quotes is a no-op for option-leg reads — quotes ARE the source of\n // truth here. Parameter remains in the schema for backward compat with\n // callers.\n void skip_quotes;\n\n const byUnderlying = new Map<string, string[]>();\n for (const leg of replayLegs) {\n const underlying = stores.quote.tickers.resolve(extractRoot(leg.occTicker));\n const arr = byUnderlying.get(underlying) ?? [];\n arr.push(leg.occTicker);\n byUnderlying.set(underlying, arr);\n }\n\n const quotesByOcc = new Map<string, QuoteRow[]>();\n for (const [, occs] of byUnderlying) {\n try {\n const result = await stores.quote.readQuotes(occs, open_date!, close_date!);\n for (const [occ, rows] of result) quotesByOcc.set(occ, rows);\n } catch {\n // Best-effort: a missing partition / read error returns empty for these legs.\n }\n }\n\n const barsByLeg: BarRow[][] = replayLegs.map((leg) => {\n const quotes = quotesByOcc.get(leg.occTicker) ?? [];\n return quotes.map((q) => {\n const [date, time] = q.timestamp.split(' ');\n const mid = (q.bid + q.ask) / 2;\n return {\n ticker: q.occ_ticker,\n date,\n time,\n open: mid,\n high: mid,\n low: mid,\n close: mid,\n bid: q.bid,\n ask: q.ask,\n volume: 0,\n };\n });\n });\n\n // ----- Fetch underlying bars + build greeks config -----\n // Reverse-map weekly roots back to standard root for underlying fetch\n const REVERSE_ROOT_MAP: Record<string, string> = {\n SPXW: 'SPX', NDXP: 'NDX', RUTW: 'RUT',\n };\n const DIVIDEND_YIELDS: Record<string, number> = {\n SPX: 0.015, SPXW: 0.015, NDX: 0.015, NDXP: 0.015,\n };\n\n // Extract root from first leg's OCC ticker\n const firstRootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);\n const rawRoot = firstRootMatch ? firstRootMatch[1] : '';\n const underlyingTicker = REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;\n const dividendYield = DIVIDEND_YIELDS[rawRoot] ?? 0;\n\n // Read underlying minute bars via SpotStore, falling back to the daily\n // aggregate (readDailyBars) when minute bars are absent. The daily\n // fallback keeps greeks computable on dates with sparse intraday\n // coverage.\n let underlyingBars: BarRow[] = await stores.spot.readBars(\n underlyingTicker,\n open_date!,\n close_date!,\n );\n if (underlyingBars.length === 0) {\n try {\n underlyingBars = await stores.spot.readDailyBars(\n underlyingTicker,\n open_date!,\n close_date!,\n );\n } catch {\n // No fallback available — greeks will be omitted\n }\n }\n // Defense-in-depth: drop any underlying bar with a zero/null OHLC value.\n // SPX/QQQ/etc. always have a real price — a zero in spot is a provider\n // gap (see ParquetSpotStore writer guard), and feeding it into Black-\n // Scholes greeks computation produces nonsense (S=0 → infinite delta etc.).\n // Raw bars are left unfiltered upstream so option tickers can keep\n // legitimate \"no trade\" zero rows; the filtering responsibility lives\n // here at the underlying-consumer site.\n if (underlyingBars.length > 0) {\n underlyingBars = underlyingBars.filter(\n (b) =>\n Number.isFinite(b.open) && b.open > 0 &&\n Number.isFinite(b.high) && b.high > 0 &&\n Number.isFinite(b.low) && b.low > 0 &&\n Number.isFinite(b.close)&& b.close> 0,\n );\n }\n\n // Build underlying price map for greeks config\n const underlyingPrices = new Map<string, number>();\n for (const b of underlyingBars) {\n const ts = `${b.date} ${b.time ?? ''}`.trim();\n underlyingPrices.set(ts, markPrice(b));\n }\n\n // Build sorted timestamps for tolerant nearest-timestamp lookup: when a\n // leg's quote timestamp doesn't have an exact underlying-price match\n // (e.g. one source skipped a minute), greeks computation falls back to\n // the nearest underlying timestamp within tolerance.\n const sortedTimestamps = Array.from(underlyingPrices.keys())\n .filter(k => k.includes(' ')) // Only intraday timestamps, not date-only keys\n .sort();\n\n // VIX IVP lookup via EnrichedStore.read — used as an optional input to\n // the greeks model when implied-vol percentile context is available.\n let ivpByDate: Map<string, number> | undefined;\n try {\n const vixEnriched = await stores.enriched.read({\n ticker: \"VIX\",\n from: open_date!,\n to: close_date!,\n includeContext: false,\n });\n const map = new Map<string, number>();\n for (const row of vixEnriched) {\n const ivp = row.ivp;\n if (ivp != null) {\n map.set(String(row.date), Number(ivp));\n }\n }\n if (map.size > 0) {\n ivpByDate = map;\n }\n } catch {\n // IVP is optional enrichment — don't fail\n }\n\n // Build GreeksConfig\n let greeksConfig: GreeksConfig | undefined;\n if (underlyingPrices.size > 0) {\n greeksConfig = {\n underlyingPrices,\n sortedTimestamps,\n legs: replayLegs.map(leg => {\n // Extract strike, type, expiry from OCC ticker: ROOT{YYMMDD}{C|P}{strike*1000}\n const occMatch = leg.occTicker.match(/^[A-Z]+(\\d{6})([CP])(\\d{8})$/);\n if (!occMatch) return { strike: 0, type: 'C' as const, expiryDate: '' };\n const yymmdd = occMatch[1];\n const type = occMatch[2] as 'C' | 'P';\n const strike = parseInt(occMatch[3], 10) / 1000;\n const expiryDate = `20${yymmdd.slice(0, 2)}-${yymmdd.slice(2, 4)}-${yymmdd.slice(4, 6)}`;\n return { strike, type, expiryDate };\n }),\n riskFreeRate: 0.045,\n dividendYield,\n ivpByDate,\n };\n }\n\n // ----- Compute P&L path + MFE/MAE -----\n let fullPath = computeStrategyPnlPath(replayLegs, barsByLeg, greeksConfig);\n let { mfe, mae, mfeTimestamp, maeTimestamp } =\n computeReplayMfeMae(fullPath);\n let totalPnl = fullPath.length > 0 ? fullPath[fullPath.length - 1].strategyPnl : 0;\n\n // Surface a warning when >50% of leg-timestamps have null greeks — the\n // most common cause is sparse IV data or 0DTE legs falling outside the\n // pricing model's valid range.\n let greeksNullCount = 0;\n let greeksTotalCount = 0;\n for (const point of fullPath) {\n if (point.legGreeks) {\n for (const lg of point.legGreeks) {\n greeksTotalCount++;\n if (lg.delta === null) greeksNullCount++;\n }\n }\n }\n const greeksWarning = greeksTotalCount > 0 && greeksNullCount / greeksTotalCount > 0.5\n ? `Greeks unavailable for ${greeksNullCount} of ${greeksTotalCount} leg-timestamps (0DTE options use Bachelier model; some legs may have insufficient time value for IV computation)`\n : null;\n\n // Apply format filter\n // Truncate path at trade close timestamp when close_at === \"trade\" (default)\n // This ensures decompose_greeks and exit triggers only analyze the actual holding period\n if (close_at === \"trade\" && tradeCloseTimestamp && fullPath.length > 0) {\n const truncIdx = fullPath.findIndex(p => p.timestamp > tradeCloseTimestamp!);\n if (truncIdx > 0) {\n fullPath = fullPath.slice(0, truncIdx);\n // Recompute MFE/MAE/totalPnl on truncated path\n mfe = -Infinity;\n mae = Infinity;\n for (const p of fullPath) {\n if (p.strategyPnl > mfe) { mfe = p.strategyPnl; mfeTimestamp = p.timestamp; }\n if (p.strategyPnl < mae) { mae = p.strategyPnl; maeTimestamp = p.timestamp; }\n }\n totalPnl = fullPath[fullPath.length - 1].strategyPnl;\n }\n }\n\n const { format } = params;\n let pnlPath: typeof fullPath;\n if (format === \"summary\") {\n // Return only MFE, MAE, and boundary points (first, last, MFE timestamp, MAE timestamp)\n const keyTimestamps = new Set([\n fullPath[0]?.timestamp,\n fullPath[fullPath.length - 1]?.timestamp,\n mfeTimestamp,\n maeTimestamp,\n ]);\n pnlPath = fullPath.filter(p => keyTimestamps.has(p.timestamp));\n } else if (format === \"sampled\") {\n // Sample at ~15min intervals (keep every 15th bar, plus first/last/MFE/MAE)\n const keyTimestamps = new Set([mfeTimestamp, maeTimestamp]);\n pnlPath = fullPath.filter((p, i) =>\n i === 0 || i === fullPath.length - 1 || i % 15 === 0 || keyTimestamps.has(p.timestamp)\n );\n } else {\n pnlPath = fullPath;\n }\n\n return {\n pnlPath,\n mfe,\n mae,\n mfeTimestamp,\n maeTimestamp,\n totalPnl,\n totalBars: fullPath.length,\n legs: replayLegs,\n greeksWarning,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerReplayTools(\n server: McpServer,\n baseDir: string,\n stores: MarketStores,\n): void {\n server.registerTool(\n \"replay_trade\",\n {\n description:\n \"Replay a trade using historical minute-level option bars. \" +\n \"Reads option-leg quotes via QuoteStore and underlying bars via SpotStore (cache only); \" +\n \"missing data yields a degenerate replay. Use the data-pipeline tools to backfill cache. \" +\n \"Returns minute-by-minute P&L path with MFE (Maximum Favorable Excursion) and MAE (Maximum Adverse Excursion). \" +\n \"Two modes: (A) Hypothetical — provide explicit legs with strikes, expiry, entry prices. \" +\n \"(B) Tradelog — provide block_id + trade_index to replay an existing trade from your data.\",\n inputSchema: replayTradeSchema,\n },\n async (params) => {\n try {\n const result = await handleReplayTrade(params, baseDir, stores);\n\n const summary =\n `Replayed ${result.legs.length}-leg strategy from ${params.open_date ?? \"trade dates\"} to ${params.close_date ?? \"trade dates\"}: ` +\n `$${result.totalPnl.toFixed(2)} P&L, MFE=$${result.mfe.toFixed(2)}, MAE=$${result.mae.toFixed(2)}, ` +\n `${result.pnlPath.length} minute bars, greeks=${result.pnlPath[0]?.legGreeks ? 'yes' : 'no'}`;\n\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error replaying trade: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * Root-to-underlying resolver.\n *\n * extractRoot: pure string manipulation — no registry lookup.\n * rootToUnderlying: extractRoot + registry.resolve (identity fallback for unknowns).\n *\n * The root-extraction regex is intentionally duplicated from an optional\n * private extension's OCC ticker parser (private-only module; shared code\n * cannot import it). If the OCC-ticker regex is ever updated in one place,\n * update the other as well.\n */\nimport type { TickerRegistry } from \"./registry.ts\"; // TYPE-ONLY — break runtime cycle with registry.ts\n\n// OCC-like option ticker shape: root + YYMMDD + C/P + 6-11 digit strike.\n// Standard OCC encodes strike × 1000 in 8 digits, but ThetaData emits wider\n// strike fields (up to 10 digits seen on adjusted/non-standard SPX series —\n// e.g. SPX240719C1262721200, SPX240719P845310800). Accept 6-11 so unusual but\n// well-formed tickers still extract their root cleanly instead of falling\n// through to the passthrough branch below (which previously leaked the full\n// OCC string as the partition key).\nconst OCC_RE = /^([A-Z]+)\\d{6}[CP]\\d{6,11}$/;\nconst LEADING_LETTERS_RE = /^([A-Z]+)/;\n\n/**\n * Extract the root from an input symbol.\n * - OCC ticker (\"SPXW251219C05000000\") → leading letters only (\"SPXW\")\n * - Bare root (\"SPXW\", \"VIX9D\", \"VIX3M\") → returned unchanged\n *\n * @throws when input has no leading alpha characters at all.\n */\nexport function extractRoot(input: string): string {\n const occMatch = input.match(OCC_RE);\n if (occMatch) return occMatch[1];\n // Bare root path — must start with at least one letter.\n if (!LEADING_LETTERS_RE.test(input)) {\n throw new Error(`Cannot extract root from \"${input}\"`);\n }\n return input;\n}\n\n/**\n * Resolve any OCC ticker or bare root to its underlying.\n * Unknown roots return themselves (identity fallback) — this is how\n * leveraged ETFs (SPXL/SPXS/SPXU/SPXC) and any new symbol stay correct\n * without explicit registry entries.\n */\nexport function rootToUnderlying(input: string, registry: TickerRegistry): string {\n const root = extractRoot(input);\n return registry.resolve(root);\n}\n","/**\n * Option Snapshot Tools\n *\n * MCP tool for fetching live option chain snapshots from Massive.com.\n * Returns current greeks, IV, open interest, and quotes for option contracts\n * on a specified underlying.\n *\n * Tools registered:\n * - get_option_snapshot — Fetch live option chain with greeks/IV/OI\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getProvider } from \"../utils/market-provider.ts\";\n\n// ---------------------------------------------------------------------------\n// Zod schema\n// ---------------------------------------------------------------------------\n\nexport const getOptionSnapshotSchema = z.object({\n underlying: z\n .string()\n .describe(\"Underlying ticker symbol (e.g., 'SPX', 'SPY', 'AAPL')\"),\n strike_price_gte: z\n .number()\n .optional()\n .describe(\"Minimum strike price filter\"),\n strike_price_lte: z\n .number()\n .optional()\n .describe(\"Maximum strike price filter\"),\n expiration_date_gte: z\n .string()\n .optional()\n .describe(\"Earliest expiration date (YYYY-MM-DD)\"),\n expiration_date_lte: z\n .string()\n .optional()\n .describe(\"Latest expiration date (YYYY-MM-DD)\"),\n contract_type: z\n .enum([\"call\", \"put\"])\n .optional()\n .describe(\"Filter by call or put\"),\n limit: z\n .number()\n .optional()\n .default(50)\n .describe(\n \"Max contracts to return (default 50, use higher for full chain)\"\n ),\n});\n\n// ---------------------------------------------------------------------------\n// Handler (exported for testing)\n// ---------------------------------------------------------------------------\n\nexport async function handleGetOptionSnapshot(\n params: z.infer<typeof getOptionSnapshotSchema>,\n): Promise<string> {\n try {\n const {\n underlying,\n strike_price_gte,\n strike_price_lte,\n expiration_date_gte,\n expiration_date_lte,\n contract_type,\n limit,\n } = params;\n\n const result = await getProvider().fetchOptionSnapshot({\n underlying,\n strike_price_gte,\n strike_price_lte,\n expiration_date_gte,\n expiration_date_lte,\n contract_type,\n });\n\n // Client-side limit truncation: API fetches all filtered contracts\n // (ensuring BS fallback runs on all), then we truncate for presentation\n const contractsTotal = result.contracts.length;\n const contracts =\n limit != null && contractsTotal > limit\n ? result.contracts.slice(0, limit)\n : result.contracts;\n\n return JSON.stringify({\n underlying_ticker: result.underlying_ticker,\n underlying_price: result.underlying_price,\n contracts_returned: contracts.length,\n contracts_total: contractsTotal,\n contracts,\n });\n } catch (error) {\n return JSON.stringify({\n error: (error as Error).message,\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerSnapshotTools(server: McpServer): void {\n server.registerTool(\n \"get_option_snapshot\",\n {\n description:\n \"Fetch live option chain snapshot with greeks, IV, open interest, and quotes from Massive.com. \" +\n \"Returns current market data for option contracts on the specified underlying. \" +\n \"Use filters to narrow by strike range, expiration range, or call/put type. \" +\n \"Replaces TastyTrade get_option_chain for analysis.\",\n inputSchema: getOptionSnapshotSchema,\n },\n async (params) => {\n const text = await handleGetOptionSnapshot(params);\n return {\n content: [{ type: \"text\" as const, text }],\n };\n }\n );\n}\n","/**\n * Greeks Decomposition Engine\n *\n * Decomposes a replay P&L path into ranked greek factor contributions\n * (delta, gamma, theta, vega, residual) using full revaluation P&L attribution.\n *\n * Full revaluation reprices each leg with one input changed at a time\n * (spot, time, vol) to capture all higher-order effects (charm, vanna, volga)\n * naturally. This produces near-zero residual for any strategy where the\n * pricing model (BS or Bachelier) can accurately price the options.\n *\n * Falls back to numerical decomposition (realized delta from price changes)\n * when full revaluation still produces >80% residual (model pricing failure).\n *\n * Pure logic module — no I/O, no DuckDB, no fetch.\n */\n\nimport type { PnlPoint, ReplayLeg } from './trade-replay.ts';\nimport { bsPrice, bachelierPrice, BACHELIER_DTE_THRESHOLD } from './black-scholes.ts';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type FactorName = 'delta' | 'gamma' | 'theta' | 'vega' | 'charm' | 'vanna' | 'residual' | 'time_and_vol';\n\nexport interface FactorContribution {\n factor: FactorName;\n totalPnl: number; // Sum of step contributions\n pctOfTotal: number; // % of total abs P&L move\n steps: number[]; // Per-step contribution values\n}\n\nexport interface LegGroupVega {\n label: string; // e.g., \"front_month\", \"back_month\"\n legIndices: number[]; // Which legs are in this group\n totalVegaPnl: number; // Sum of vega P&L for this group\n avgIvChange: number; // Average IV change for this group's legs\n steps: number[]; // Per-step vega contribution for this group\n}\n\nexport interface GreeksDecompositionResult {\n factors: FactorContribution[]; // Sorted by abs(totalPnl) descending\n legGroupVega?: LegGroupVega[]; // Per-leg-group vega attribution\n totalPnlChange: number; // Actual P&L change from first to last point\n totalAttributed: number; // Sum of factor contributions (excl residual)\n totalResidual: number; // Total residual\n stepCount: number; // Number of steps (pnlPath.length - 1)\n summary: string; // Human-readable summary\n warning?: string | null; // D-13: high residual warning\n method: 'full_reval' | 'model' | 'numerical'; // which method produced the attribution\n}\n\nexport interface LegGroupDef {\n label: string;\n legIndices: number[];\n}\n\nexport interface LegPricingInput {\n strike: number;\n type: 'C' | 'P';\n expiryDate: string; // YYYY-MM-DD\n}\n\nexport interface GreeksDecompositionConfig {\n pnlPath: PnlPoint[];\n legs: ReplayLeg[];\n underlyingPrices?: Map<string, number>; // timestamp -> underlying price\n legGroups?: LegGroupDef[]; // Optional leg grouping for per-group vega\n /** Per-leg pricing inputs for full revaluation. When provided, uses full reval\n * instead of Taylor expansion. Falls back to Taylor when missing. */\n legPricingInputs?: LegPricingInput[];\n riskFreeRate?: number; // e.g. 0.045\n dividendYield?: number; // e.g. 0.015 for SPX\n}\n\n// ---------------------------------------------------------------------------\n// Time delta helper\n// ---------------------------------------------------------------------------\n\nconst TRADING_MINUTES_PER_DAY = 390;\n\n/**\n * Compute time delta in trading days between two timestamps.\n * Format: \"YYYY-MM-DD HH:MM\"\n *\n * Same day: minutes difference / 390\n * Cross day: calendar day difference (simplified — treats each gap as 1 day)\n */\nexport function computeTimeDeltaDays(ts1: string, ts2: string): number {\n const [date1, time1] = ts1.split(' ');\n const [date2, time2] = ts2.split(' ');\n\n if (date1 === date2) {\n // Same day: count minutes difference\n const [h1, m1] = time1.split(':').map(Number);\n const [h2, m2] = time2.split(':').map(Number);\n const mins1 = h1 * 60 + m1;\n const mins2 = h2 * 60 + m2;\n const diffMins = Math.abs(mins2 - mins1);\n return diffMins / TRADING_MINUTES_PER_DAY;\n }\n\n // Cross-day: compute calendar day difference\n const d1 = new Date(date1 + 'T12:00:00'); // noon to avoid DST issues\n const d2 = new Date(date2 + 'T12:00:00');\n const diffMs = Math.abs(d2.getTime() - d1.getTime());\n const diffDays = Math.round(diffMs / (24 * 60 * 60 * 1000));\n\n // Add fractional day from time within each day\n const [h1, m1] = time1.split(':').map(Number);\n const [h2, m2] = time2.split(':').map(Number);\n // Fraction of trading day for ts2's time (from market open ~9:30)\n const minsIntoDay2 = (h2 * 60 + m2) - (9 * 60 + 30);\n const fracDay2 = Math.max(0, minsIntoDay2) / TRADING_MINUTES_PER_DAY;\n // Fraction remaining in ts1's day\n const minsIntoDay1 = (h1 * 60 + m1) - (9 * 60 + 30);\n const fracDayRemaining1 = Math.max(0, 1 - minsIntoDay1 / TRADING_MINUTES_PER_DAY);\n\n // Total: remaining fraction of day1 + (diffDays - 1) full days + fraction of day2\n if (diffDays <= 1) {\n return fracDayRemaining1 + fracDay2;\n }\n return fracDayRemaining1 + (diffDays - 1) + fracDay2;\n}\n\n// ---------------------------------------------------------------------------\n// Numerical fallback decomposition (D-09/D-10/D-11)\n// ---------------------------------------------------------------------------\n\n/**\n * Numerical decomposition: compute realized delta from price changes when\n * model-based attribution has > 80% residual.\n *\n * Splits P&L into: delta (from realized delta), gamma (from delta changes),\n * and time_and_vol (everything else — theta + vega + unexplained).\n */\nfunction numericalDecomposition(\n config: GreeksDecompositionConfig,\n totalPnlChange: number,\n stepCount: number,\n): GreeksDecompositionResult {\n const { pnlPath, underlyingPrices } = config;\n\n const numDeltaSteps: number[] = [];\n const numGammaSteps: number[] = [];\n const numResidualSteps: number[] = [];\n\n let prevRealizedDelta: number | null = null;\n\n for (let i = 0; i < stepCount; i++) {\n const cur = pnlPath[i];\n const next = pnlPath[i + 1];\n const actualChange = next.strategyPnl - cur.strategyPnl;\n\n // Underlying price change\n let underlyingChange = 0;\n if (underlyingPrices) {\n const p1 = underlyingPrices.get(cur.timestamp);\n const p2 = underlyingPrices.get(next.timestamp);\n if (p1 !== undefined && p2 !== undefined) {\n underlyingChange = p2 - p1;\n }\n }\n\n // Skip when underlying barely moves (< $0.01) — can't estimate delta\n if (Math.abs(underlyingChange) < 0.01) {\n numDeltaSteps.push(0);\n numGammaSteps.push(0);\n numResidualSteps.push(actualChange);\n // Do NOT update prevRealizedDelta — delta is unknown\n continue;\n }\n\n // Realized delta = total option PnL change / underlying change\n const realizedDelta = actualChange / underlyingChange;\n\n // Gamma from delta changes (D-10): only when we have a previous delta\n let gammaPnl = 0;\n if (prevRealizedDelta !== null) {\n const deltaChange = realizedDelta - prevRealizedDelta;\n gammaPnl = 0.5 * deltaChange * underlyingChange;\n }\n\n const pureDeltaPnl = realizedDelta * underlyingChange - gammaPnl;\n const residual = actualChange - pureDeltaPnl - gammaPnl;\n\n numDeltaSteps.push(pureDeltaPnl);\n numGammaSteps.push(gammaPnl);\n numResidualSteps.push(residual);\n\n prevRealizedDelta = realizedDelta;\n }\n\n const sumSteps = (s: number[]) => s.reduce((a, v) => a + v, 0);\n const totalDelta = sumSteps(numDeltaSteps);\n const totalGamma = sumSteps(numGammaSteps);\n const totalTimeAndVol = sumSteps(numResidualSteps);\n\n const rawFactors = [\n { factor: 'delta' as FactorName, totalPnl: totalDelta, steps: numDeltaSteps },\n { factor: 'gamma' as FactorName, totalPnl: totalGamma, steps: numGammaSteps },\n { factor: 'time_and_vol' as FactorName, totalPnl: totalTimeAndVol, steps: numResidualSteps },\n ];\n\n rawFactors.sort((a, b) => Math.abs(b.totalPnl) - Math.abs(a.totalPnl));\n const totalAbsSum = rawFactors.reduce((s, f) => s + Math.abs(f.totalPnl), 0);\n const factors: FactorContribution[] = rawFactors.map(f => ({\n ...f,\n pctOfTotal: totalAbsSum > 0 ? (Math.abs(f.totalPnl) / totalAbsSum) * 100 : 0,\n }));\n\n const summaryParts = factors.map(f => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);\n const summary = `P&L of ${totalPnlChange.toFixed(2)} (numerical): ${summaryParts.join(', ')}`;\n\n return {\n factors,\n legGroupVega: undefined, // Leg-group vega not available in numerical mode\n totalPnlChange,\n totalAttributed: totalDelta + totalGamma,\n totalResidual: totalTimeAndVol,\n stepCount,\n summary,\n warning: 'Model-based attribution had >80% residual. Switched to numerical method (realized delta from price changes).',\n method: 'numerical',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Core decomposition\n// ---------------------------------------------------------------------------\n\n/**\n * Compute DTE in days from a bar timestamp to a leg's expiry (4:00 PM ET).\n */\nfunction computeDte(timestamp: string, expiryDate: string): number {\n const dateStr = timestamp.split(' ')[0];\n const timePart = timestamp.split(' ')[1] ?? '09:30';\n const [eyy, emm, edd] = expiryDate.split('-').map(Number);\n const [byy, bmm, bdd] = dateStr.split('-').map(Number);\n const [hh, min] = timePart.split(':').map(Number);\n\n const expiryMs = new Date(eyy, emm - 1, edd).getTime() + 16 * 60 * 60 * 1000; // 4:00 PM ET\n const barMs = new Date(byy, bmm - 1, bdd).getTime() + (hh * 60 + min) * 60 * 1000;\n return (expiryMs - barMs) / (1000 * 60 * 60 * 24);\n}\n\n/**\n * Price an option using the appropriate model (BS or Bachelier) based on DTE.\n * Returns null if pricing fails (DTE <= 0 or IV missing).\n */\nfunction priceOption(\n type: 'C' | 'P',\n S: number,\n K: number,\n dte: number,\n r: number,\n q: number,\n iv: number,\n): number | null {\n if (dte <= 0 || iv <= 0) return null;\n const T = dte / 365;\n const bsType = type === 'C' ? 'call' as const : 'put' as const;\n if (dte < BACHELIER_DTE_THRESHOLD) {\n return bachelierPrice(bsType, S, K, T, r, q, iv);\n }\n return bsPrice(bsType, S, K, T, r, q, iv);\n}\n\n/**\n * Decompose a replay P&L path into ranked greek factor contributions.\n *\n * Uses full revaluation when legPricingInputs are provided:\n * For each step, reprices each leg with one input changed at a time\n * (spot only, time only, vol only) to isolate each factor's contribution.\n * This captures all higher-order effects (charm, vanna, volga) naturally.\n *\n * Falls back to numerical decomposition when full reval produces >80% residual\n * (pricing model failure for that strategy/DTE combination).\n */\nexport function decomposeGreeks(config: GreeksDecompositionConfig): GreeksDecompositionResult {\n const { pnlPath, legs, underlyingPrices, legGroups, legPricingInputs, riskFreeRate, dividendYield } = config;\n\n // Edge case: empty or single-point path\n if (pnlPath.length <= 1) {\n const emptyFactors: FactorContribution[] = [\n { factor: 'delta', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'gamma', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'theta', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'vega', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'residual', totalPnl: 0, pctOfTotal: 0, steps: [] },\n ];\n return {\n factors: emptyFactors,\n legGroupVega: legGroups ? legGroups.map(g => ({\n label: g.label,\n legIndices: g.legIndices,\n totalVegaPnl: 0,\n avgIvChange: 0,\n steps: [],\n })) : undefined,\n totalPnlChange: 0,\n totalAttributed: 0,\n totalResidual: 0,\n stepCount: 0,\n summary: 'No P&L path to decompose (0 steps)',\n warning: null,\n method: 'full_reval',\n };\n }\n\n const stepCount = pnlPath.length - 1;\n const canFullReval = legPricingInputs && legPricingInputs.length === legs.length\n && riskFreeRate !== undefined && dividendYield !== undefined && underlyingPrices;\n const r = riskFreeRate ?? 0.045;\n const q = dividendYield ?? 0.015;\n\n // Accumulators\n const deltaSteps: number[] = [];\n const thetaSteps: number[] = [];\n const vegaSteps: number[] = [];\n const charmSteps: number[] = [];\n const vannaSteps: number[] = [];\n const residualSteps: number[] = [];\n\n // Per-leg-group vega accumulators\n const groupSteps: number[][] | undefined = legGroups\n ? legGroups.map(() => [] as number[])\n : undefined;\n\n for (let i = 0; i < stepCount; i++) {\n const cur = pnlPath[i];\n const next = pnlPath[i + 1];\n\n let stepDelta = 0;\n let stepTheta = 0;\n let stepVega = 0;\n let stepCharm = 0;\n let stepVanna = 0;\n let stepResidual = 0;\n\n const groupVegaAccum: number[] | undefined = legGroups ? legGroups.map(() => 0) : undefined;\n\n const legCount = Math.min(legs.length, cur.legPrices?.length ?? 0, next.legPrices?.length ?? 0);\n\n // Underlying prices at cur and next timestamps\n const S1 = cur.underlyingPrice ?? underlyingPrices?.get(cur.timestamp);\n const S2 = next.underlyingPrice ?? underlyingPrices?.get(next.timestamp);\n\n for (let j = 0; j < legCount; j++) {\n const positionSize = legs[j].quantity * legs[j].multiplier;\n const legActualChange = ((next.legPrices?.[j] ?? 0) - (cur.legPrices?.[j] ?? 0)) * positionSize;\n\n const curIv = cur.legGreeks?.[j]?.iv;\n const nextIv = next.legGreeks?.[j]?.iv;\n const lpi = legPricingInputs?.[j];\n\n // Full revaluation: reprice with one input changed at a time\n if (canFullReval && lpi && S1 !== undefined && S2 !== undefined\n && curIv !== null && curIv !== undefined && curIv > 0\n && nextIv !== null && nextIv !== undefined && nextIv > 0) {\n\n const dte1 = computeDte(cur.timestamp, lpi.expiryDate);\n const dte2 = computeDte(next.timestamp, lpi.expiryDate);\n\n if (dte1 > 0 && dte2 > 0) {\n // Baseline: price at (S1, T1, IV1)\n const priceBase = priceOption(lpi.type, S1, lpi.strike, dte1, r, q, curIv);\n\n // Delta: price at (S2, T1, IV1) — only spot changed\n const priceDelta = priceOption(lpi.type, S2, lpi.strike, dte1, r, q, curIv);\n\n // Theta: price at (S1, T2, IV1) — only time changed\n const priceTheta = priceOption(lpi.type, S1, lpi.strike, dte2, r, q, curIv);\n\n // Vega: price at (S1, T1, IV2) — only vol changed\n const priceVega = priceOption(lpi.type, S1, lpi.strike, dte1, r, q, nextIv);\n\n // Cross-term repricing: two inputs changed at once\n // Charm (spot×time): P(S2, T2, σ1) - base - delta - theta\n const priceCharm = priceOption(lpi.type, S2, lpi.strike, dte2, r, q, curIv);\n // Vanna (spot×vol): P(S2, T1, σ2) - base - delta - vega\n const priceVanna = priceOption(lpi.type, S2, lpi.strike, dte1, r, q, nextIv);\n\n if (priceBase !== null && priceDelta !== null && priceTheta !== null\n && priceVega !== null && priceCharm !== null && priceVanna !== null) {\n const legDeltaPnl = (priceDelta - priceBase) * positionSize;\n const legThetaPnl = (priceTheta - priceBase) * positionSize;\n const legVegaPnl = (priceVega - priceBase) * positionSize;\n const legCharmPnl = ((priceCharm - priceBase) - (priceDelta - priceBase) - (priceTheta - priceBase)) * positionSize;\n const legVannaPnl = ((priceVanna - priceBase) - (priceDelta - priceBase) - (priceVega - priceBase)) * positionSize;\n const legResidual = legActualChange - legDeltaPnl - legThetaPnl - legVegaPnl - legCharmPnl - legVannaPnl;\n\n stepDelta += legDeltaPnl;\n stepTheta += legThetaPnl;\n stepVega += legVegaPnl;\n stepCharm += legCharmPnl;\n stepVanna += legVannaPnl;\n stepResidual += legResidual;\n\n // Per-leg-group vega\n if (legGroups && groupVegaAccum) {\n for (let g = 0; g < legGroups.length; g++) {\n if (legGroups[g].legIndices.includes(j)) {\n groupVegaAccum[g] += legVegaPnl;\n }\n }\n }\n continue; // leg handled by full reval\n }\n }\n }\n\n // Fallback: leg P&L goes to residual (no pricing possible)\n stepResidual += legActualChange;\n }\n\n deltaSteps.push(stepDelta);\n thetaSteps.push(stepTheta);\n vegaSteps.push(stepVega);\n charmSteps.push(stepCharm);\n vannaSteps.push(stepVanna);\n residualSteps.push(stepResidual);\n\n if (groupSteps && groupVegaAccum) {\n for (let g = 0; g < legGroups!.length; g++) {\n groupSteps[g].push(groupVegaAccum[g]);\n }\n }\n }\n\n const sumSteps = (steps: number[]): number => steps.reduce((s, v) => s + v, 0);\n\n // Full reval factors:\n // - delta: spot-only P&L (includes gamma — all spot-driven effects)\n // - theta: time-only P&L\n // - vega: vol-only P&L\n // - charm: spot×time cross-effect (delta changing with time)\n // - vanna: spot×vol cross-effect (delta changing with vol)\n // - residual: triple cross (spot+time+vol simultaneously) + model error\n const rawFactors: Array<{ factor: FactorName; totalPnl: number; steps: number[] }> = [\n { factor: 'delta', totalPnl: sumSteps(deltaSteps), steps: deltaSteps },\n { factor: 'theta', totalPnl: sumSteps(thetaSteps), steps: thetaSteps },\n { factor: 'vega', totalPnl: sumSteps(vegaSteps), steps: vegaSteps },\n { factor: 'charm', totalPnl: sumSteps(charmSteps), steps: charmSteps },\n { factor: 'vanna', totalPnl: sumSteps(vannaSteps), steps: vannaSteps },\n { factor: 'residual', totalPnl: sumSteps(residualSteps), steps: residualSteps },\n ];\n\n rawFactors.sort((a, b) => Math.abs(b.totalPnl) - Math.abs(a.totalPnl));\n const totalAbsSum = rawFactors.reduce((s, f) => s + Math.abs(f.totalPnl), 0);\n const factors: FactorContribution[] = rawFactors.map(f => ({\n ...f,\n pctOfTotal: totalAbsSum > 0 ? (Math.abs(f.totalPnl) / totalAbsSum) * 100 : 0,\n }));\n\n const totalPnlChange = pnlPath[pnlPath.length - 1].strategyPnl - pnlPath[0].strategyPnl;\n const totalResidual = sumSteps(residualSteps);\n const totalAttributed = sumSteps(deltaSteps) + sumSteps(thetaSteps) + sumSteps(vegaSteps) + sumSteps(charmSteps) + sumSteps(vannaSteps);\n\n const residualPct = Math.abs(totalPnlChange) > 0.01\n ? Math.abs(totalResidual) / Math.abs(totalPnlChange)\n : 0;\n\n // Numerical fallback when full reval still produces >80% residual\n // (model pricing failure — BS/Bachelier can't accurately price these options)\n if (residualPct > 0.8 && pnlPath.length > 2) {\n return numericalDecomposition(config, totalPnlChange, stepCount);\n }\n\n // Build leg group vega results\n let legGroupVega: LegGroupVega[] | undefined;\n if (legGroups && groupSteps) {\n legGroupVega = legGroups.map((group, g) => {\n const steps = groupSteps[g];\n const totalVegaPnl = sumSteps(steps);\n\n let totalIvChange = 0;\n let ivStepCount = 0;\n for (let si = 0; si < stepCount; si++) {\n const cur = pnlPath[si];\n const nxt = pnlPath[si + 1];\n if (!cur.legGreeks || !nxt.legGreeks) continue;\n for (const j of group.legIndices) {\n const iv1 = cur.legGreeks[j]?.iv;\n const iv2 = nxt.legGreeks[j]?.iv;\n if (iv1 !== null && iv1 !== undefined && iv2 !== null && iv2 !== undefined) {\n totalIvChange += (iv2 - iv1) * 100;\n ivStepCount++;\n }\n }\n }\n\n return {\n label: group.label,\n legIndices: group.legIndices,\n totalVegaPnl,\n avgIvChange: ivStepCount > 0 ? totalIvChange / ivStepCount : 0,\n steps,\n };\n });\n }\n\n // Build summary\n const methodLabel = canFullReval ? 'full_reval' : 'model';\n const summaryParts = factors\n .filter(f => f.factor !== 'residual')\n .map(f => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);\n const residualFactor = factors.find(f => f.factor === 'residual');\n if (residualFactor && Math.abs(residualFactor.totalPnl) > 0.01) {\n summaryParts.push(`residual ${residualFactor.totalPnl.toFixed(2)} (${residualFactor.pctOfTotal.toFixed(0)}%)`);\n }\n const summary = `P&L of ${totalPnlChange.toFixed(2)} (${methodLabel}): ${summaryParts.join(', ')}`;\n\n const warning = residualPct > 0.5\n ? `Residual ${(residualPct * 100).toFixed(0)}% — attribution limited for some legs.`\n : null;\n\n return {\n factors,\n legGroupVega,\n totalPnlChange,\n totalAttributed,\n totalResidual,\n stepCount,\n summary,\n warning,\n method: canFullReval ? 'full_reval' : 'model',\n };\n}\n","/**\n * Exit Trigger Evaluation Engine\n *\n * Pure logic module (no I/O, no DuckDB, no fetch) that evaluates 15 exit\n * trigger types against a greeks-enriched P&L path from trade replay.\n *\n * Provides the computational heart of the `analyze_exit_triggers` tool.\n */\n\nimport type { PnlPoint, ReplayLeg } from './trade-replay.ts';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type TriggerType =\n | 'profitTarget'\n | 'stopLoss'\n | 'trailingStop'\n | 'profitAction'\n | 'dteExit'\n | 'ditExit'\n | 'clockTimeExit'\n | 'underlyingPriceMove'\n | 'positionDelta'\n | 'perLegDelta'\n | 'vixMove'\n | 'vix9dMove'\n | 'vix9dVixRatio'\n | 'slRatioThreshold'\n | 'slRatioMove';\n\nexport interface PartialClose {\n index: number;\n pnlAtFire: number;\n allocation: number;\n trigger: string;\n}\n\nexport interface ExitTriggerConfig {\n type: TriggerType;\n threshold: number;\n unit?: 'percent' | 'dollar'; // D-07: default 'dollar', backwards compatible\n steps?: Array<{ armAt: number; stopAt: number; closeAllocationPct?: number }>;\n // Context-specific optional fields:\n expiry?: string; // YYYY-MM-DD for dteExit\n openDate?: string; // YYYY-MM-DD for ditExit\n clockTime?: string; // \"HH:MM\" for clockTimeExit (threshold ignored)\n trailAmount?: number; // Dollar trail for trailingStop\n // Directional delta fields (per-leg directional exits):\n legIndex?: number; // 0-based leg index for perLegDelta — targets specific leg\n exitAbove?: number; // Fire when value > exitAbove (directional, no abs)\n exitBelow?: number; // Fire when value < exitBelow (directional, no abs)\n // Data maps for triggers needing external prices:\n underlyingPrices?: Map<string, number>; // timestamp -> price\n vixPrices?: Map<string, number>; // timestamp -> VIX price\n vix9dPrices?: Map<string, number>; // timestamp -> VIX9D price\n // S/L ratio inputs:\n spreadWidth?: number; // Width of spread in dollars\n contracts?: number; // Number of contracts\n multiplier?: number; // Default 100\n // profitTarget confirmation: N synchronized-quote bars at-or-above threshold required before firing (default 1 = fire on first cross)\n requiredHits?: number;\n // Internal: set by handler when unit='percent' to compute dollar threshold\n entryCost?: number; // D-11: cost/credit of entry (negative = credit received)\n entrySLRatio?: number; // Runtime-hydrated opening short/long ratio for slRatioMove\n}\n\nexport interface TriggerFireEvent {\n type: TriggerType;\n firedAt: string; // Timestamp when trigger fired\n pnlAtFire: number; // Strategy P&L when trigger fired\n index: number; // Index into pnlPath\n detail?: string; // Human-readable description\n}\n\nexport interface ExitTriggerResult {\n triggers: TriggerFireEvent[]; // All triggers that fired (sorted by fire time)\n firstToFire: TriggerFireEvent | null; // Earliest trigger\n actualExit?: {\n timestamp: string;\n pnl: number;\n pnlDifference: number; // firstToFire.pnl - actualExit.pnl\n };\n partialCloses?: PartialClose[]; // Partial position closes from profitAction steps\n summary: string;\n}\n\nexport interface LegGroupConfig {\n label: string;\n legIndices: number[];\n triggers: ExitTriggerConfig[];\n}\n\nexport interface LegGroupResult {\n label: string;\n result: ExitTriggerResult;\n groupPnl: number[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Parse \"YYYY-MM-DD\" to a Date at local midnight. */\nfunction parseDate(dateStr: string): Date {\n const [y, m, d] = dateStr.split('-').map(Number);\n return new Date(y, m - 1, d);\n}\n\n/** Extract date portion \"YYYY-MM-DD\" from timestamp \"YYYY-MM-DD HH:MM\". */\nfunction extractDate(timestamp: string): string {\n return timestamp.slice(0, 10);\n}\n\n/** Extract time portion \"HH:MM\" from timestamp \"YYYY-MM-DD HH:MM\". */\nfunction extractTime(timestamp: string): string {\n return timestamp.slice(11, 16);\n}\n\n/** Calendar days between two dates (absolute). */\nfunction calendarDaysBetween(a: Date, b: Date): number {\n const MS_PER_DAY = 86_400_000;\n return Math.abs(Math.floor((b.getTime() - a.getTime()) / MS_PER_DAY));\n}\n\n/** Compute S/L ratio for spread positions. */\nfunction computeSLRatio(\n point: PnlPoint,\n legs: ReplayLeg[],\n spreadWidth: number,\n contracts: number,\n multiplier: number,\n): number {\n // Spread value = sum of abs(markPrice * quantity * multiplier) for short legs\n let spreadValue = 0;\n for (let i = 0; i < legs.length; i++) {\n if (legs[i].quantity < 0) {\n const markPrice = point.legPrices[i] ?? 0;\n spreadValue += Math.abs(markPrice * legs[i].quantity * legs[i].multiplier);\n }\n }\n const maxLoss = spreadWidth * contracts * multiplier;\n if (maxLoss === 0) return 0;\n return spreadValue / maxLoss;\n}\n\nfunction computeSLRatioMove(initial: number, current: number): number {\n if (initial === 0) return current === 0 ? 0 : Number.POSITIVE_INFINITY;\n return (current - initial) / initial;\n}\n\nfunction crossesDirectionalMove(pctMove: number, threshold: number): boolean {\n if (threshold < 0) return pctMove <= threshold;\n if (threshold > 0) return pctMove >= threshold;\n return pctMove !== 0;\n}\n\nfunction adjustLegDeltaForPosition(rawDelta: number, leg?: ReplayLeg): number {\n return leg != null && leg.quantity < 0 ? -rawDelta : rawDelta;\n}\n\n// ---------------------------------------------------------------------------\n// evaluateProfitAction — partial close aware evaluator\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a profitAction trigger with partial close support.\n * Steps with closeAllocationPct will close a fraction of the REMAINING position\n * when their armAt is first reached. The remaining position's P&L is scaled down.\n *\n * Returns both the fire event (stop hit on remaining) and any partial closes.\n */\nexport function evaluateProfitAction(\n trigger: ExitTriggerConfig,\n pnlPath: PnlPoint[],\n _legs: ReplayLeg[],\n): { fireEvent: TriggerFireEvent | null; partialCloses: PartialClose[] } {\n const partialCloses: PartialClose[] = [];\n\n if (pnlPath.length === 0 || !trigger.steps?.length) {\n return { fireEvent: null, partialCloses };\n }\n if (trigger.unit === 'percent' && trigger.entryCost == null) {\n return { fireEvent: null, partialCloses };\n }\n\n const scale = trigger.unit === 'percent'\n ? Math.abs(trigger.entryCost!)\n : 1;\n\n const normalizedSteps = [...trigger.steps]\n .sort((a, b) => a.armAt - b.armAt)\n .map((step) => ({\n armAt: step.armAt * scale,\n stopAt: step.stopAt * scale,\n closeAllocationPct: step.closeAllocationPct,\n }));\n\n let remainingAllocation = 1.0;\n let runningMaxPnl = -Infinity;\n // Track which steps have already triggered their partial close\n const stepPartialFired = new Array(normalizedSteps.length).fill(false);\n\n for (let i = 0; i < pnlPath.length; i++) {\n const point = pnlPath[i];\n const pnl = point.strategyPnl;\n\n if (pnl > runningMaxPnl) runningMaxPnl = pnl;\n\n // Check each step for partial close (only when armAt first reached)\n for (let s = 0; s < normalizedSteps.length; s++) {\n const step = normalizedSteps[s];\n if (!stepPartialFired[s] && step.closeAllocationPct && runningMaxPnl >= step.armAt) {\n stepPartialFired[s] = true;\n const closeAmt = remainingAllocation * step.closeAllocationPct;\n partialCloses.push({\n index: i,\n pnlAtFire: pnl * remainingAllocation * step.closeAllocationPct,\n allocation: closeAmt,\n trigger: 'profitAction',\n });\n remainingAllocation -= closeAmt;\n }\n }\n\n // Compute active stop floor (same logic as original)\n let activeFloor = -Infinity;\n for (const step of normalizedSteps) {\n if (runningMaxPnl >= step.armAt) {\n activeFloor = Math.max(activeFloor, step.stopAt);\n }\n }\n\n // Check if stop hit on remaining allocation\n // Scaled comparison: pnl * remainingAllocation <= activeFloor * remainingAllocation\n // Simplifies to: pnl <= activeFloor (when remainingAllocation > 0)\n if (activeFloor > -Infinity && remainingAllocation > 0 && pnl <= activeFloor) {\n const effectivePnl = pnl * remainingAllocation;\n const detail = trigger.unit === 'percent'\n ? `Profit action: stop adjusted to ${(activeFloor / scale * 100).toFixed(0)}% ($${activeFloor.toFixed(2)}) at max P&L $${runningMaxPnl.toFixed(2)}, hit at $${pnl.toFixed(2)} (remaining ${(remainingAllocation * 100).toFixed(0)}%)`\n : `Profit action: stop adjusted to $${activeFloor.toFixed(2)} at max P&L $${runningMaxPnl.toFixed(2)}, hit at $${pnl.toFixed(2)} (remaining ${(remainingAllocation * 100).toFixed(0)}%)`;\n\n return {\n fireEvent: {\n type: 'profitAction',\n firedAt: point.timestamp,\n pnlAtFire: effectivePnl,\n index: i,\n detail,\n },\n partialCloses,\n };\n }\n }\n\n return { fireEvent: null, partialCloses };\n}\n\n// ---------------------------------------------------------------------------\n// evaluateTrigger\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a single trigger against the full P&L path.\n * Returns the first point where it fires, or null.\n */\nexport function evaluateTrigger(\n trigger: ExitTriggerConfig,\n pnlPath: PnlPoint[],\n legs: ReplayLeg[],\n): TriggerFireEvent | null {\n if (pnlPath.length === 0) return null;\n\n const { type, threshold } = trigger;\n\n // State for triggers that track running values\n let runningMaxPnl = -Infinity;\n let profitTargetHits = 0;\n let initialSLRatio: number | null = null;\n let firstUnderlyingPrice: number | null = null;\n let firstVixPrice: number | null = null;\n let firstVix9dPrice: number | null = null;\n\n for (let i = 0; i < pnlPath.length; i++) {\n const point = pnlPath[i];\n const pnl = point.strategyPnl;\n\n // Update running max for trailingStop\n if (pnl > runningMaxPnl) runningMaxPnl = pnl;\n\n let fired = false;\n let detail: string | undefined;\n\n switch (type) {\n case 'profitTarget': {\n // unit='percent' requires entryCost; if missing, cannot compute — no fire\n if (trigger.unit === 'percent' && trigger.entryCost == null) break;\n const requiredHits = trigger.requiredHits ?? 1;\n const dollarThresholdPT = trigger.unit === 'percent'\n ? threshold * Math.abs(trigger.entryCost!)\n : threshold;\n if (pnl >= dollarThresholdPT) {\n if (point.allLegsSync !== false) profitTargetHits++;\n if (profitTargetHits < requiredHits) break;\n fired = true;\n detail = trigger.unit === 'percent'\n ? `P&L $${pnl.toFixed(2)} >= ${(threshold * 100).toFixed(0)}% of $${Math.abs(trigger.entryCost!).toFixed(2)} ($${dollarThresholdPT.toFixed(2)})`\n : `P&L $${pnl.toFixed(2)} >= target $${dollarThresholdPT.toFixed(2)}`;\n } else if (point.allLegsSync !== false) {\n profitTargetHits = 0;\n }\n break;\n }\n\n case 'stopLoss': {\n // Normalize negative threshold — users may pass -2 meaning \"stop at $2 loss\"\n const absThreshold = Math.abs(threshold);\n // unit='percent' requires entryCost; if missing, cannot compute — no fire\n if (trigger.unit === 'percent' && trigger.entryCost == null) break;\n const dollarThresholdSL = trigger.unit === 'percent'\n ? absThreshold * Math.abs(trigger.entryCost!)\n : absThreshold;\n if (pnl <= -dollarThresholdSL) {\n fired = true;\n detail = trigger.unit === 'percent'\n ? `P&L $${pnl.toFixed(2)} <= -${(absThreshold * 100).toFixed(0)}% of $${Math.abs(trigger.entryCost!).toFixed(2)} (-$${dollarThresholdSL.toFixed(2)})`\n : `P&L $${pnl.toFixed(2)} <= stop -$${dollarThresholdSL.toFixed(2)}`;\n }\n break;\n }\n\n case 'trailingStop': {\n const trailAmt = trigger.trailAmount ?? threshold;\n const dropdown = runningMaxPnl - pnl;\n if (dropdown >= trailAmt && runningMaxPnl > -Infinity) {\n fired = true;\n detail = `Dropdown $${dropdown.toFixed(2)} from max $${runningMaxPnl.toFixed(2)} >= trail $${trailAmt.toFixed(2)}`;\n }\n break;\n }\n\n case 'profitAction': {\n // Delegate to evaluateProfitAction for the full path evaluation\n // (evaluateTrigger is called point-by-point in the loop, but profitAction\n // needs full-path context for partial close tracking, so we handle it\n // by breaking out of the loop and evaluating the full path at once.)\n const paResult = evaluateProfitAction(trigger, pnlPath, legs);\n return paResult.fireEvent;\n }\n\n case 'dteExit': {\n if (!trigger.expiry) break;\n const pointDate = parseDate(extractDate(point.timestamp));\n const expiryDate = parseDate(trigger.expiry);\n const dte = calendarDaysBetween(pointDate, expiryDate);\n // Only fire if point is before/on expiry\n if (pointDate <= expiryDate && dte <= threshold) {\n fired = true;\n detail = `DTE ${dte} <= threshold ${threshold}`;\n }\n break;\n }\n\n case 'ditExit': {\n if (!trigger.openDate) break;\n const pointDate = parseDate(extractDate(point.timestamp));\n const openDate = parseDate(trigger.openDate);\n const dit = calendarDaysBetween(openDate, pointDate);\n if (dit >= threshold) {\n fired = true;\n detail = `DIT ${dit} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'clockTimeExit': {\n const clockTime = trigger.clockTime ?? '15:00';\n const pointTime = extractTime(point.timestamp);\n if (pointTime >= clockTime) {\n fired = true;\n detail = `Time ${pointTime} >= ${clockTime}`;\n }\n break;\n }\n\n case 'underlyingPriceMove': {\n if (!trigger.underlyingPrices) break;\n const price = trigger.underlyingPrices.get(point.timestamp);\n if (price == null) break;\n if (firstUnderlyingPrice === null) {\n firstUnderlyingPrice = price;\n break; // Can't compute move on first price\n }\n const pctMove = ((price - firstUnderlyingPrice) / firstUnderlyingPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `Underlying moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'positionDelta': {\n const netDelta = point.netDelta ?? 0;\n if (trigger.exitAbove != null) {\n if (netDelta > trigger.exitAbove) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} > exitAbove ${trigger.exitAbove}`;\n }\n } else if (trigger.exitBelow != null) {\n if (netDelta < trigger.exitBelow) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} < exitBelow ${trigger.exitBelow}`;\n }\n } else if (Math.abs(netDelta) >= threshold) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'perLegDelta': {\n if (!point.legGreeks) break;\n if (trigger.legIndex != null) {\n // Target a specific leg\n if (trigger.legIndex >= point.legGreeks.length) break;\n const rawDelta = point.legGreeks[trigger.legIndex].delta ?? 0;\n const legDelta = adjustLegDeltaForPosition(rawDelta, legs[trigger.legIndex]);\n if (trigger.exitAbove != null) {\n if (legDelta > trigger.exitAbove) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} > exitAbove ${trigger.exitAbove}`;\n }\n } else if (trigger.exitBelow != null) {\n if (legDelta < trigger.exitBelow) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} < exitBelow ${trigger.exitBelow}`;\n }\n } else {\n // legIndex set but no directional fields — use abs() on that single leg\n if (Math.abs(legDelta) >= threshold) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} >= threshold ${threshold}`;\n }\n }\n } else {\n // No legIndex — iterate all legs with abs() (backward compat)\n for (let li = 0; li < point.legGreeks.length; li++) {\n const rawDelta = point.legGreeks[li].delta ?? 0;\n const legDelta = adjustLegDeltaForPosition(rawDelta, legs[li]);\n if (Math.abs(legDelta) >= threshold) {\n fired = true;\n detail = `Leg ${li} delta ${legDelta.toFixed(4)} >= threshold ${threshold}`;\n break;\n }\n }\n }\n break;\n }\n\n case 'vixMove': {\n if (!trigger.vixPrices) break;\n const vix = trigger.vixPrices.get(point.timestamp);\n if (vix == null) break;\n if (firstVixPrice === null) {\n firstVixPrice = vix;\n break;\n }\n const pctMove = ((vix - firstVixPrice) / firstVixPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `VIX moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'vix9dMove': {\n if (!trigger.vix9dPrices) break;\n const vix9d = trigger.vix9dPrices.get(point.timestamp);\n if (vix9d == null) break;\n if (firstVix9dPrice === null) {\n firstVix9dPrice = vix9d;\n break;\n }\n const pctMove = ((vix9d - firstVix9dPrice) / firstVix9dPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `VIX9D moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'vix9dVixRatio': {\n if (!trigger.vixPrices || !trigger.vix9dPrices) break;\n const vix = trigger.vixPrices.get(point.timestamp);\n const vix9d = trigger.vix9dPrices.get(point.timestamp);\n if (vix == null || vix9d == null || vix === 0) break;\n const ratio = vix9d / vix;\n // If threshold >= 1, fire when ratio >= threshold (contango deepening)\n // If threshold < 1, fire when ratio <= threshold (backwardation)\n const crosses = threshold >= 1 ? ratio >= threshold : ratio <= threshold;\n if (crosses) {\n fired = true;\n detail = `VIX9D/VIX ratio ${ratio.toFixed(4)} crossed threshold ${threshold}`;\n }\n break;\n }\n\n case 'slRatioThreshold': {\n const sw = trigger.spreadWidth ?? 0;\n const ct = trigger.contracts ?? 1;\n const mp = trigger.multiplier ?? 100;\n if (sw === 0) break;\n const slRatio = computeSLRatio(point, legs, sw, ct, mp);\n if (slRatio >= threshold) {\n fired = true;\n detail = `S/L ratio ${slRatio.toFixed(4)} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'slRatioMove': {\n const sw = trigger.spreadWidth ?? 0;\n const ct = trigger.contracts ?? 1;\n const mp = trigger.multiplier ?? 100;\n if (sw === 0) break;\n const slRatio = computeSLRatio(point, legs, sw, ct, mp);\n if (initialSLRatio === null && typeof trigger.entrySLRatio === 'number') {\n initialSLRatio = trigger.entrySLRatio;\n }\n if (initialSLRatio === null) {\n initialSLRatio = slRatio;\n break; // Can't compute change on first point\n }\n const pctMove = computeSLRatioMove(initialSLRatio, slRatio);\n if (crossesDirectionalMove(pctMove, threshold)) {\n fired = true;\n detail = `S/L ratio moved ${(pctMove * 100).toFixed(2)}% from initial ${initialSLRatio.toFixed(4)} to ${slRatio.toFixed(4)} (threshold ${(threshold * 100).toFixed(2)}%)`;\n }\n break;\n }\n }\n\n if (fired) {\n return {\n type,\n firedAt: point.timestamp,\n pnlAtFire: pnl,\n index: i,\n detail,\n };\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// computeGroupPnl\n// ---------------------------------------------------------------------------\n\n/**\n * Compute per-group P&L at each timestamp from leg prices.\n * groupPnl[t] = sum over legIndices of (legPrices[i] - entryPrice[i]) * quantity[i] * multiplier[i]\n */\nfunction computeGroupPnl(\n pnlPath: PnlPoint[],\n legs: ReplayLeg[],\n legIndices: number[],\n): number[] {\n return pnlPath.map((point) => {\n let groupPnl = 0;\n for (const idx of legIndices) {\n if (idx < legs.length && idx < point.legPrices.length) {\n const leg = legs[idx];\n const markPrice = point.legPrices[idx];\n groupPnl += (markPrice - leg.entryPrice) * leg.quantity * leg.multiplier;\n }\n }\n return groupPnl;\n });\n}\n\n// ---------------------------------------------------------------------------\n// analyzeExitTriggers\n// ---------------------------------------------------------------------------\n\n/**\n * Run all triggers against the P&L path, find first-to-fire,\n * compute actual exit comparison, and evaluate leg group triggers.\n */\nexport function analyzeExitTriggers(config: {\n pnlPath: PnlPoint[];\n legs: ReplayLeg[];\n triggers: ExitTriggerConfig[];\n actualExitTimestamp?: string;\n legGroups?: LegGroupConfig[];\n}): {\n overall: ExitTriggerResult;\n legGroups?: LegGroupResult[];\n} {\n const { pnlPath, legs, triggers, actualExitTimestamp, legGroups } = config;\n\n // Evaluate all triggers\n const fireEvents: TriggerFireEvent[] = [];\n let allPartialCloses: PartialClose[] = [];\n for (const trigger of triggers) {\n if (trigger.type === 'profitAction') {\n // Use the partial-close-aware helper for profitAction\n const paResult = evaluateProfitAction(trigger, pnlPath, legs);\n if (paResult.fireEvent) {\n fireEvents.push(paResult.fireEvent);\n }\n if (paResult.partialCloses.length > 0) {\n allPartialCloses = allPartialCloses.concat(paResult.partialCloses);\n }\n } else {\n const event = evaluateTrigger(trigger, pnlPath, legs);\n if (event) {\n fireEvents.push(event);\n }\n }\n }\n\n // Sort by fire index (earliest first)\n fireEvents.sort((a, b) => a.index - b.index);\n\n const firstToFire = fireEvents.length > 0 ? fireEvents[0] : null;\n\n // Actual exit comparison\n let actualExit: ExitTriggerResult['actualExit'];\n if (actualExitTimestamp && firstToFire) {\n // Find closest point to actualExitTimestamp\n let closestIdx = 0;\n let closestDist = Infinity;\n for (let i = 0; i < pnlPath.length; i++) {\n // Simple string comparison — timestamps are lexicographically ordered\n const dist = Math.abs(pnlPath[i].timestamp.localeCompare(actualExitTimestamp));\n if (pnlPath[i].timestamp === actualExitTimestamp) {\n closestIdx = i;\n break;\n }\n if (dist < closestDist) {\n closestDist = dist;\n closestIdx = i;\n }\n }\n // Fallback: use last point if actualExitTimestamp is after all points\n if (actualExitTimestamp > pnlPath[pnlPath.length - 1].timestamp) {\n closestIdx = pnlPath.length - 1;\n }\n const actualPnl = pnlPath[closestIdx].strategyPnl;\n actualExit = {\n timestamp: pnlPath[closestIdx].timestamp,\n pnl: actualPnl,\n pnlDifference: firstToFire.pnlAtFire - actualPnl,\n };\n }\n\n // Build summary\n let summary: string;\n if (!firstToFire) {\n summary = `No triggers fired across ${pnlPath.length} data points.`;\n } else if (actualExit) {\n const betterWorse = actualExit.pnlDifference > 0 ? 'better' : 'worse';\n summary = `${firstToFire.type} fired at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). ` +\n `Actual exit at ${actualExit.timestamp} (P&L $${actualExit.pnl.toFixed(2)}). ` +\n `Trigger was $${Math.abs(actualExit.pnlDifference).toFixed(2)} ${betterWorse}.`;\n } else {\n summary = `${firstToFire.type} fired first at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). ` +\n `${fireEvents.length} trigger(s) fired total.`;\n }\n\n const overall: ExitTriggerResult = {\n triggers: fireEvents,\n firstToFire,\n actualExit,\n partialCloses: allPartialCloses.length > 0 ? allPartialCloses : undefined,\n summary,\n };\n\n // Leg group evaluation\n let legGroupResults: LegGroupResult[] | undefined;\n if (legGroups && legGroups.length > 0) {\n legGroupResults = legGroups.map((group) => {\n const groupPnlArr = computeGroupPnl(pnlPath, legs, group.legIndices);\n\n // Build a synthetic PnlPoint[] for this group with groupPnl as strategyPnl\n const groupPath: PnlPoint[] = pnlPath.map((point, idx) => ({\n ...point,\n strategyPnl: groupPnlArr[idx],\n // Filter legPrices/legGreeks to only this group's legs\n legPrices: group.legIndices.map((li) => point.legPrices[li] ?? 0),\n legGreeks: point.legGreeks\n ? group.legIndices.map((li) => point.legGreeks![li])\n : undefined,\n }));\n\n // Build group legs subset\n const groupLegs = group.legIndices.map((li) => legs[li]);\n\n // Evaluate per-group triggers\n const groupFireEvents: TriggerFireEvent[] = [];\n for (const trigger of group.triggers) {\n const event = evaluateTrigger(trigger, groupPath, groupLegs);\n if (event) groupFireEvents.push(event);\n }\n groupFireEvents.sort((a, b) => a.index - b.index);\n\n const groupFirstToFire = groupFireEvents.length > 0 ? groupFireEvents[0] : null;\n\n // Actual exit for group\n let groupActualExit: ExitTriggerResult['actualExit'];\n if (actualExitTimestamp && groupFirstToFire) {\n let closestIdx = pnlPath.length - 1;\n for (let i = 0; i < pnlPath.length; i++) {\n if (pnlPath[i].timestamp === actualExitTimestamp) {\n closestIdx = i;\n break;\n }\n }\n if (actualExitTimestamp > pnlPath[pnlPath.length - 1].timestamp) {\n closestIdx = pnlPath.length - 1;\n }\n const actualGroupPnl = groupPnlArr[closestIdx];\n groupActualExit = {\n timestamp: pnlPath[closestIdx].timestamp,\n pnl: actualGroupPnl,\n pnlDifference: groupFirstToFire.pnlAtFire - actualGroupPnl,\n };\n }\n\n const groupSummary = groupFirstToFire\n ? `${group.label}: ${groupFirstToFire.type} fired at ${groupFirstToFire.firedAt} (group P&L $${groupFirstToFire.pnlAtFire.toFixed(2)})`\n : `${group.label}: No triggers fired.`;\n\n return {\n label: group.label,\n result: {\n triggers: groupFireEvents,\n firstToFire: groupFirstToFire,\n actualExit: groupActualExit,\n summary: groupSummary,\n },\n groupPnl: groupPnlArr,\n };\n });\n }\n\n return {\n overall,\n legGroups: legGroupResults,\n };\n}\n","/**\n * Exit Analysis Tools\n *\n * MCP tools for analyzing exit triggers and decomposing P&L into greek factor\n * contributions. Both tools run trade replay internally -- a single tool call\n * fetches data, replays the trade, and analyzes the results.\n *\n * Tools registered:\n * - analyze_exit_triggers -- Evaluate 15 trigger types against a replay P&L path\n * - decompose_greeks -- Decompose P&L into delta/gamma/theta/vega/residual factors\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport { handleReplayTrade } from \"./replay.ts\";\nimport type { MarketStores } from \"../market/stores/index.ts\";\nimport {\n analyzeExitTriggers,\n type ExitTriggerConfig,\n type LegGroupConfig,\n} from \"../utils/exit-triggers.ts\";\nimport {\n decomposeGreeks,\n type LegGroupDef,\n} from \"../utils/greeks-decomposition.ts\";\nimport { markPrice } from \"../utils/trade-replay.ts\";\n\n// ---------------------------------------------------------------------------\n// Shared trigger type enum\n// ---------------------------------------------------------------------------\n\nconst triggerTypeEnum = z.enum([\n 'profitTarget', 'stopLoss', 'trailingStop', 'profitAction',\n 'dteExit', 'ditExit', 'clockTimeExit',\n 'underlyingPriceMove', 'positionDelta', 'perLegDelta',\n 'vixMove', 'vix9dMove', 'vix9dVixRatio',\n 'slRatioThreshold', 'slRatioMove',\n]);\n\nconst triggerConfigSchema = z.object({\n type: triggerTypeEnum,\n threshold: z.number(),\n unit: z.enum(['percent', 'dollar']).default('dollar').optional(),\n expiry: z.string().optional(),\n openDate: z.string().optional(),\n clockTime: z.string().optional(),\n trailAmount: z.number().optional(),\n steps: z.array(z.object({\n armAt: z.number(),\n stopAt: z.number(),\n closeAllocationPct: z.number().min(0).max(1).optional()\n .describe(\"Fraction of REMAINING position to close at this milestone (0-1)\"),\n })).optional(),\n spreadWidth: z.number().optional(),\n contracts: z.number().optional(),\n legIndex: z.number().optional()\n .describe(\"0-based leg index for perLegDelta — targets specific leg\"),\n exitAbove: z.number().optional()\n .describe(\"Fire when value exceeds this (directional, no abs)\"),\n exitBelow: z.number().optional()\n .describe(\"Fire when value drops below this (directional, no abs)\"),\n});\n\n// ---------------------------------------------------------------------------\n// Leg schema (shared between both tools)\n// ---------------------------------------------------------------------------\n\nconst legSchema = z.object({\n ticker: z.string(),\n strike: z.number(),\n type: z.enum([\"C\", \"P\"]),\n expiry: z.string(),\n quantity: z.number(),\n entry_price: z.number(),\n});\n\n// ---------------------------------------------------------------------------\n// analyze_exit_triggers schema\n// ---------------------------------------------------------------------------\n\nexport const analyzeExitTriggersSchema = z.object({\n // Replay inputs (same shape as replay_trade)\n legs: z.array(legSchema).optional(),\n block_id: z.string().optional(),\n trade_index: z.number().optional(),\n open_date: z.string().optional(),\n close_date: z.string().optional(),\n multiplier: z.number().default(100),\n\n triggers: z.array(triggerConfigSchema)\n .describe(\"Exit triggers to evaluate against the P&L path\"),\n\n actual_exit_timestamp: z.string().optional()\n .describe(\"Actual exit time for comparison (format: YYYY-MM-DD HH:MM)\"),\n\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n triggers: z.array(triggerConfigSchema),\n })).optional().describe(\"Per-leg-group exit triggers for multi-structure strategies\"),\n\n format: z.enum([\"summary\", \"full\"]).default(\"summary\")\n .describe(\"'summary' omits per-step trigger states, 'full' includes all fire events\"),\n});\n\n// ---------------------------------------------------------------------------\n// decompose_greeks schema\n// ---------------------------------------------------------------------------\n\nexport const decomposeGreeksSchema = z.object({\n // Same replay inputs\n legs: z.array(legSchema).optional(),\n block_id: z.string().optional(),\n trade_index: z.number().optional(),\n open_date: z.string().optional(),\n close_date: z.string().optional(),\n multiplier: z.number().default(100),\n\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n })).optional().describe(\"Leg grouping for per-group vega attribution (e.g., front_month vs back_month)\"),\n\n format: z.enum([\"summary\", \"full\"]).default(\"summary\")\n .describe(\"'summary' shows ranked factors, 'full' includes per-step contributions\"),\n skip_quotes: z\n .boolean()\n .default(false)\n .describe(\"Skip NBBO quote enrichment for option bars. Faster, but lower precision.\"),\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// Reverse-map weekly roots to standard root for underlying/VIX fetching\nconst REVERSE_ROOT_MAP: Record<string, string> = {\n SPXW: 'SPX', NDXP: 'NDX', RUTW: 'RUT',\n};\n\n/**\n * Extract the underlying root ticker from the first replay leg's OCC ticker.\n * Maps weekly roots (SPXW, NDXP) back to their standard root.\n */\nfunction extractUnderlyingTicker(occTicker: string): string {\n const rootMatch = occTicker.match(/^([A-Z]+)/);\n const rawRoot = rootMatch ? rootMatch[1] : '';\n return REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;\n}\n\n/**\n * Read VIX, VIX9D, or underlying minute bars via SpotStore and build a\n * timestamp->price map. Reads NEVER trigger provider calls — bars are\n * served from the local store, with a daily-aggregate fallback when\n * minute bars are absent (same pattern used in replay.ts for underlying\n * fetches). An empty map on cache miss is the silent-empty contract —\n * callers treat absent data as \"trigger inactive\" rather than as an\n * error.\n */\nasync function fetchPriceMap(\n stores: MarketStores,\n ticker: string,\n from: string,\n to: string,\n): Promise<Map<string, number>> {\n const map = new Map<string, number>();\n try {\n let bars = await stores.spot.readBars(ticker, from, to);\n if (bars.length === 0) {\n try {\n bars = await stores.spot.readDailyBars(ticker, from, to);\n } catch {\n // No daily fallback available — return empty map\n }\n }\n // Defense-in-depth: skip any underlying bar with a zero/null OHLC value.\n // The underlying ticker (SPX/QQQ/etc.) always has a real price — a zero\n // is a provider gap that would corrupt the price map and downstream\n // trigger comparisons. Raw bars are left unfiltered upstream so option\n // tickers can keep legitimate \"no trade\" zero rows; this filter is\n // applied at the underlying-consumer site.\n for (const b of bars) {\n if (\n !Number.isFinite(b.open) || b.open <= 0 ||\n !Number.isFinite(b.high) || b.high <= 0 ||\n !Number.isFinite(b.low) || b.low <= 0 ||\n !Number.isFinite(b.close) || b.close <= 0\n ) continue;\n const ts = `${b.date} ${b.time ?? ''}`.trim();\n map.set(ts, markPrice(b));\n }\n } catch {\n // Best-effort — empty map signals \"trigger data unavailable\"\n }\n return map;\n}\n\n// ---------------------------------------------------------------------------\n// handleAnalyzeExitTriggers\n// ---------------------------------------------------------------------------\n\nexport async function handleAnalyzeExitTriggers(\n params: z.infer<typeof analyzeExitTriggersSchema>,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<ReturnType<typeof analyzeExitTriggers>> {\n const {\n legs: inputLegs, block_id, trade_index,\n open_date, close_date, multiplier,\n triggers, actual_exit_timestamp, leg_groups,\n } = params;\n\n // 1. Run replay to get full P&L path with greeks\n const replayResult = await handleReplayTrade(\n {\n legs: inputLegs,\n block_id,\n trade_index,\n open_date,\n close_date,\n multiplier,\n format: 'full',\n close_at: 'trade',\n skip_quotes: false,\n },\n baseDir,\n stores,\n injectedConn,\n );\n\n const pnlPath = replayResult.pnlPath;\n const replayLegs = replayResult.legs;\n\n // Compute entry cost for percentage-based triggers\n const entryCost = replayLegs.reduce((sum, leg) => {\n return sum + leg.entryPrice * leg.quantity * leg.multiplier;\n }, 0);\n\n if (pnlPath.length === 0) {\n return {\n overall: {\n triggers: [],\n firstToFire: null,\n summary: 'No P&L data available from replay.',\n },\n };\n }\n\n // 2. Determine date range from replay path\n const firstDate = pnlPath[0].timestamp.slice(0, 10);\n const lastDate = pnlPath[pnlPath.length - 1].timestamp.slice(0, 10);\n\n // 3. Check which external data maps are needed\n const allTriggerTypes = new Set(triggers.map(t => t.type));\n const groupTriggerTypes = new Set(\n (leg_groups ?? []).flatMap(g => g.triggers.map(t => t.type))\n );\n for (const t of groupTriggerTypes) allTriggerTypes.add(t);\n\n // Determine underlying ticker for underlying price triggers\n const underlyingTicker = extractUnderlyingTicker(replayLegs[0].occTicker);\n\n // Fetch VIX/VIX9D/underlying price maps as needed\n let vixPrices: Map<string, number> | undefined;\n let vix9dPrices: Map<string, number> | undefined;\n let underlyingPrices: Map<string, number> | undefined;\n\n const needsVix = allTriggerTypes.has('vixMove') || allTriggerTypes.has('vix9dVixRatio');\n const needsVix9d = allTriggerTypes.has('vix9dMove') || allTriggerTypes.has('vix9dVixRatio');\n const needsUnderlying = allTriggerTypes.has('underlyingPriceMove');\n\n if (needsVix) {\n vixPrices = await fetchPriceMap(stores, 'VIX', firstDate, lastDate);\n }\n if (needsVix9d) {\n vix9dPrices = await fetchPriceMap(stores, 'VIX9D', firstDate, lastDate);\n }\n if (needsUnderlying) {\n underlyingPrices = await fetchPriceMap(\n stores, underlyingTicker, firstDate, lastDate,\n );\n }\n\n // 4. Map tool trigger params to ExitTriggerConfig[] with data maps\n const exitTriggers: ExitTriggerConfig[] = triggers.map(t => ({\n type: t.type,\n threshold: t.threshold,\n unit: t.unit,\n entryCost,\n expiry: t.expiry,\n openDate: t.openDate,\n clockTime: t.clockTime,\n trailAmount: t.trailAmount,\n steps: t.steps,\n spreadWidth: t.spreadWidth,\n contracts: t.contracts,\n multiplier,\n underlyingPrices,\n vixPrices,\n vix9dPrices,\n }));\n\n // 5. Map leg groups with their triggers\n const legGroupConfigs: LegGroupConfig[] | undefined = leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n triggers: g.triggers.map(t => ({\n type: t.type,\n threshold: t.threshold,\n unit: t.unit,\n entryCost,\n expiry: t.expiry,\n openDate: t.openDate,\n clockTime: t.clockTime,\n trailAmount: t.trailAmount,\n steps: t.steps,\n spreadWidth: t.spreadWidth,\n contracts: t.contracts,\n multiplier,\n underlyingPrices,\n vixPrices,\n vix9dPrices,\n })),\n }));\n\n // 6. Run the pure analysis engine\n return analyzeExitTriggers({\n pnlPath,\n legs: replayLegs,\n triggers: exitTriggers,\n actualExitTimestamp: actual_exit_timestamp,\n legGroups: legGroupConfigs,\n });\n}\n\n// ---------------------------------------------------------------------------\n// handleDecomposeGreeks\n// ---------------------------------------------------------------------------\n\nexport async function handleDecomposeGreeks(\n params: z.infer<typeof decomposeGreeksSchema>,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<import(\"../utils/greeks-decomposition.ts\").GreeksDecompositionResult> {\n const {\n legs: inputLegs, block_id, trade_index,\n open_date, close_date, multiplier,\n leg_groups, format, skip_quotes,\n } = params;\n\n // 1. Run replay to get full P&L path with greeks\n const replayResult = await handleReplayTrade(\n {\n legs: inputLegs,\n block_id,\n trade_index,\n open_date,\n close_date,\n multiplier,\n format: 'full',\n close_at: 'trade',\n skip_quotes,\n },\n baseDir,\n stores,\n injectedConn,\n );\n\n const pnlPath = replayResult.pnlPath;\n const replayLegs = replayResult.legs;\n\n // 2. Check greeks data availability\n if (pnlPath.length > 0 && !pnlPath[0].legGreeks) {\n throw new Error(\n \"No greeks data available. Use the data-pipeline tools to backfill the option-leg quotes and underlying price data into the local cache.\"\n );\n }\n\n // 3. Reuse the underlying prices already resolved during replay.\n const underlyingPrices = new Map<string, number>();\n for (const point of pnlPath) {\n if (point.underlyingPrice !== undefined) {\n underlyingPrices.set(point.timestamp, point.underlyingPrice);\n }\n }\n\n // 4. Map leg groups\n const legGroupDefs: LegGroupDef[] | undefined = leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n }));\n\n // 5. Build leg pricing inputs from OCC tickers for full revaluation\n const DIVIDEND_YIELDS: Record<string, number> = {\n SPX: 0.015, SPXW: 0.015, NDX: 0.015, NDXP: 0.015,\n };\n const rootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);\n const rawRoot = rootMatch ? rootMatch[1] : '';\n const divYield = DIVIDEND_YIELDS[rawRoot] ?? 0;\n\n const legPricingInputs = replayLegs.map(leg => {\n const m = leg.occTicker.match(/^[A-Z]+(\\d{6})([CP])(\\d{8})$/);\n if (!m) return { strike: 0, type: 'C' as const, expiryDate: '' };\n return {\n strike: parseInt(m[3], 10) / 1000,\n type: m[2] as 'C' | 'P',\n expiryDate: `20${m[1].slice(0, 2)}-${m[1].slice(2, 4)}-${m[1].slice(4, 6)}`,\n };\n });\n\n // 6. Run decomposition with full revaluation\n const result = decomposeGreeks({\n pnlPath,\n legs: replayLegs,\n underlyingPrices: underlyingPrices.size > 0 ? underlyingPrices : undefined,\n legGroups: legGroupDefs,\n legPricingInputs,\n riskFreeRate: 0.045,\n dividendYield: divYield,\n });\n\n // 7. Strip steps if format=\"summary\"\n if (format === \"summary\") {\n for (const factor of result.factors) {\n factor.steps = [];\n }\n if (result.legGroupVega) {\n for (const group of result.legGroupVega) {\n group.steps = [];\n }\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerExitAnalysisTools(\n server: McpServer,\n baseDir: string,\n stores: MarketStores,\n): void {\n server.registerTool(\n \"analyze_exit_triggers\",\n {\n description:\n \"Analyze when exit triggers would fire on a trade replay. Runs replay internally \" +\n \"-- provide block_id + trade_index or explicit legs. Evaluates 14 trigger types \" +\n \"(profit target, stop loss, trailing stop, DTE, DIT, clock time, underlying move, \" +\n \"delta, VIX moves, S/L ratio) against minute-by-minute P&L path with greeks. \" +\n \"Reads VIX/VIX9D/underlying bars from SpotStore (cache only); triggers that need \" +\n \"missing data are silently skipped. Use the data-pipeline tools to backfill cache.\",\n inputSchema: analyzeExitTriggersSchema,\n },\n async (params) => {\n try {\n const result = await handleAnalyzeExitTriggers(params, baseDir, stores);\n\n const summary = result.overall.summary;\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error analyzing exit triggers: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n server.registerTool(\n \"decompose_greeks\",\n {\n description:\n \"Decompose a trade's P&L into greek factor contributions (delta, gamma, theta, \" +\n \"vega, residual). Runs replay internally. Shows which factor drove P&L movement \" +\n \"and by how much. For calendar/double-calendar strategies, includes per-leg-group \" +\n \"vega attribution showing front vs back month IV divergence. \" +\n \"Reads option-leg quotes via QuoteStore and underlying bars via SpotStore (cache only); \" +\n \"missing data yields a degenerate replay. Use the data-pipeline tools to backfill cache.\",\n inputSchema: decomposeGreeksSchema,\n },\n async (params) => {\n try {\n const result = await handleDecomposeGreeks(params, baseDir, stores);\n\n const summary = result.summary;\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error decomposing greeks: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * Batch Exit Analysis Engine\n *\n * Pure logic module (no I/O, no DuckDB, no fetch) that takes pre-analyzed\n * trade inputs and a candidate exit policy, evaluates the policy against each\n * trade's P&L path, and computes aggregate statistics with per-trigger attribution.\n *\n * This is the computational heart of the `batch_exit_analysis` MCP tool.\n */\n\nimport {\n analyzeExitTriggers,\n type ExitTriggerConfig,\n type TriggerType,\n type LegGroupConfig,\n type PartialClose,\n} from './exit-triggers.ts';\nimport type { PnlPoint, ReplayLeg } from './trade-replay.ts';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type BaselineMode = 'actual' | 'holdToEnd';\n\nexport interface BatchExitConfig {\n /** Triggers to evaluate as candidate exit policy. */\n candidatePolicy: ExitTriggerConfig[];\n /** Optional per-group triggers (passed through to analyzeExitTriggers). */\n legGroups?: LegGroupConfig[];\n /** Baseline mode: 'actual' uses tradelog P&L, 'holdToEnd' uses last path point. */\n baselineMode: BaselineMode;\n /** Output density: 'summary' omits per-trade breakdown; 'full' includes it. */\n format: 'summary' | 'full';\n}\n\nexport interface TradeInput {\n tradeIndex: number;\n /** Trade open date YYYY-MM-DD */\n dateOpened: string;\n /** Actual P&L from tradelog pl field (used when baselineMode='actual'). */\n actualPnl: number;\n /** Full replay P&L path from trade-replay module. */\n pnlPath: PnlPoint[];\n /** Replay legs parallel to pnlPath.legPrices. */\n legs: ReplayLeg[];\n /** Entry cost for percentage-based triggers (D-11). */\n entryCost?: number;\n}\n\nexport interface TradeExitResult {\n tradeIndex: number;\n dateOpened: string;\n /** Actual P&L from tradelog. */\n actualPnl: number;\n /** P&L if candidate policy was applied. */\n candidatePnl: number;\n /** Baseline P&L (actual or holdToEnd). */\n baselinePnl: number;\n /** candidatePnl - baselinePnl */\n pnlDelta: number;\n /** Which trigger fired first, or 'noTrigger'. */\n triggerFired: TriggerType | 'noTrigger';\n /** Timestamp when trigger fired, or null. */\n fireTimestamp: string | null;\n /** Partial position closes from profitAction steps (if any). */\n partialCloses?: PartialClose[];\n}\n\nexport interface TriggerAttribution {\n trigger: TriggerType | 'noTrigger';\n /** How many trades this trigger fired first on. */\n count: number;\n /** Average candidate P&L when this trigger fired. */\n avgPnl: number;\n /** Sum candidate P&L for this trigger group. */\n totalPnl: number;\n /** Average pnlDelta vs baseline for this trigger group. */\n avgDelta: number;\n}\n\nexport interface AggregateStats {\n totalTrades: number;\n /** candidatePnl > 0 */\n winningTrades: number;\n /** candidatePnl < 0 */\n losingTrades: number;\n /** winningTrades / totalTrades */\n winRate: number;\n /** Sum of candidatePnl */\n totalPnl: number;\n /** Mean candidatePnl */\n avgPnl: number;\n /** Mean of winning candidatePnls */\n avgWin: number;\n /** Mean of losing candidatePnls */\n avgLoss: number;\n maxWin: number;\n maxLoss: number;\n /** sum(wins) / abs(sum(losses)); Infinity if no losses */\n profitFactor: number;\n /** Max sequential drawdown from equity curve (cumsum of candidatePnls) */\n maxDrawdown: number;\n /** mean/stddev of candidatePnls; null if < 2 trades */\n sharpeRatio: number | null;\n maxWinStreak: number;\n maxLossStreak: number;\n // Deltas vs baseline\n baselineTotalPnl: number;\n /** totalPnl - baselineTotalPnl */\n totalPnlDelta: number;\n baselineWinRate: number;\n}\n\nexport interface BatchExitResult {\n aggregate: AggregateStats;\n triggerAttribution: TriggerAttribution[];\n /** Empty if format='summary'. */\n perTrade: TradeExitResult[];\n baselineMode: BaselineMode;\n summary: string;\n profileContext?: {\n structureType: string;\n exitRules: string[];\n };\n /** Trades skipped due to replay errors (D-15). */\n skippedTrades?: Array<{ tradeIndex: number; dateOpened: string; error: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// computeAggregateStats\n// ---------------------------------------------------------------------------\n\n/**\n * Compute aggregate statistics from a set of per-trade exit results.\n */\nexport function computeAggregateStats(tradeResults: TradeExitResult[]): AggregateStats {\n if (tradeResults.length === 0) {\n return {\n totalTrades: 0,\n winningTrades: 0,\n losingTrades: 0,\n winRate: 0,\n totalPnl: 0,\n avgPnl: 0,\n avgWin: 0,\n avgLoss: 0,\n maxWin: 0,\n maxLoss: 0,\n profitFactor: 0,\n maxDrawdown: 0,\n sharpeRatio: null,\n maxWinStreak: 0,\n maxLossStreak: 0,\n baselineTotalPnl: 0,\n totalPnlDelta: 0,\n baselineWinRate: 0,\n };\n }\n\n const candidatePnls = tradeResults.map(r => r.candidatePnl);\n const baselinePnls = tradeResults.map(r => r.baselinePnl);\n\n const winningTrades = candidatePnls.filter(p => p > 0).length;\n const losingTrades = candidatePnls.filter(p => p < 0).length;\n const totalTrades = tradeResults.length;\n const winRate = winningTrades / totalTrades;\n\n const totalPnl = candidatePnls.reduce((sum, p) => sum + p, 0);\n const avgPnl = totalPnl / totalTrades;\n\n const wins = candidatePnls.filter(p => p > 0);\n const losses = candidatePnls.filter(p => p < 0);\n\n const avgWin = wins.length > 0 ? wins.reduce((s, p) => s + p, 0) / wins.length : 0;\n const avgLoss = losses.length > 0 ? losses.reduce((s, p) => s + p, 0) / losses.length : 0;\n const maxWin = wins.length > 0 ? Math.max(...wins) : 0;\n const maxLoss = losses.length > 0 ? Math.min(...losses) : 0;\n\n // Profit factor: sum(wins) / abs(sum(losses)), Infinity if no losses\n const sumWins = wins.reduce((s, p) => s + p, 0);\n const sumLosses = losses.reduce((s, p) => s + p, 0);\n const profitFactor = losses.length === 0\n ? Infinity\n : sumWins / Math.abs(sumLosses);\n\n // Max drawdown from equity curve (cumsum of candidatePnls)\n let runningPeak = 0;\n let equity = 0;\n let maxDrawdown = 0;\n for (const pnl of candidatePnls) {\n equity += pnl;\n if (equity > runningPeak) runningPeak = equity;\n const dd = runningPeak - equity;\n if (dd > maxDrawdown) maxDrawdown = dd;\n }\n\n // Sharpe ratio: mean / sample stddev (N-1), null if < 2 trades\n let sharpeRatio: number | null = null;\n if (totalTrades >= 2) {\n const mean = avgPnl;\n const variance =\n candidatePnls.reduce((sum, p) => sum + (p - mean) ** 2, 0) / (totalTrades - 1);\n const stddev = Math.sqrt(variance);\n sharpeRatio = stddev === 0 ? null : mean / stddev;\n }\n\n // Win/loss streaks\n let maxWinStreak = 0;\n let maxLossStreak = 0;\n let currentWinStreak = 0;\n let currentLossStreak = 0;\n for (const pnl of candidatePnls) {\n if (pnl > 0) {\n currentWinStreak++;\n currentLossStreak = 0;\n if (currentWinStreak > maxWinStreak) maxWinStreak = currentWinStreak;\n } else if (pnl < 0) {\n currentLossStreak++;\n currentWinStreak = 0;\n if (currentLossStreak > maxLossStreak) maxLossStreak = currentLossStreak;\n } else {\n // Breakeven — reset both streaks\n currentWinStreak = 0;\n currentLossStreak = 0;\n }\n }\n\n // Baseline aggregates\n const baselineTotalPnl = baselinePnls.reduce((sum, p) => sum + p, 0);\n const baselineWins = baselinePnls.filter(p => p > 0).length;\n const baselineWinRate = baselineWins / totalTrades;\n\n return {\n totalTrades,\n winningTrades,\n losingTrades,\n winRate,\n totalPnl,\n avgPnl,\n avgWin,\n avgLoss,\n maxWin,\n maxLoss,\n profitFactor,\n maxDrawdown,\n sharpeRatio,\n maxWinStreak,\n maxLossStreak,\n baselineTotalPnl,\n totalPnlDelta: totalPnl - baselineTotalPnl,\n baselineWinRate,\n };\n}\n\n// ---------------------------------------------------------------------------\n// computeTriggerAttribution\n// ---------------------------------------------------------------------------\n\n/**\n * Group trade results by which trigger fired first.\n * Returns attribution sorted by count descending.\n */\nexport function computeTriggerAttribution(\n tradeResults: TradeExitResult[],\n): TriggerAttribution[] {\n const groups = new Map<\n TriggerType | 'noTrigger',\n { count: number; totalPnl: number; totalDelta: number }\n >();\n\n for (const result of tradeResults) {\n const key = result.triggerFired;\n const existing = groups.get(key);\n if (existing) {\n existing.count++;\n existing.totalPnl += result.candidatePnl;\n existing.totalDelta += result.pnlDelta;\n } else {\n groups.set(key, {\n count: 1,\n totalPnl: result.candidatePnl,\n totalDelta: result.pnlDelta,\n });\n }\n }\n\n return Array.from(groups.entries())\n .map(([trigger, { count, totalPnl, totalDelta }]) => ({\n trigger,\n count,\n totalPnl,\n avgPnl: totalPnl / count,\n avgDelta: totalDelta / count,\n }))\n .sort((a, b) => b.count - a.count);\n}\n\n// ---------------------------------------------------------------------------\n// analyzeBatch\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a candidate exit policy against a set of trade replay results.\n *\n * For each trade:\n * 1. Run analyzeExitTriggers with the candidate policy against the trade's P&L path.\n * 2. candidatePnl = firstToFire.pnlAtFire if trigger fired, else last path point P&L.\n * 3. baselinePnl = trade.actualPnl if baselineMode='actual', else last path point P&L.\n * 4. Build TradeExitResult.\n *\n * Then compute aggregate stats and trigger attribution.\n */\nexport function analyzeBatch(\n trades: TradeInput[],\n config: BatchExitConfig,\n): BatchExitResult {\n if (trades.length === 0) {\n const emptyAggregate = computeAggregateStats([]);\n return {\n aggregate: emptyAggregate,\n triggerAttribution: [],\n perTrade: [],\n baselineMode: config.baselineMode,\n summary: 'Analyzed 0 trades: no data.',\n };\n }\n\n const { candidatePolicy, legGroups, baselineMode, format } = config;\n\n const perTradeResults: TradeExitResult[] = trades.map(trade => {\n const { pnlPath, legs, actualPnl, tradeIndex, dateOpened, entryCost } = trade;\n\n // Last path point P&L — used as holdToEnd value\n const lastPnl = pnlPath.length > 0\n ? pnlPath[pnlPath.length - 1].strategyPnl\n : 0;\n\n // Copy entryCost onto each trigger config for percentage-based triggers (D-11)\n const triggersWithCost = candidatePolicy.map(t => ({\n ...t,\n entryCost,\n }));\n\n const legGroupsWithCost = legGroups?.map(group => ({\n ...group,\n triggers: group.triggers.map(trigger => ({\n ...trigger,\n entryCost,\n })),\n }));\n\n // Run exit trigger analysis with candidate policy\n const analysisResult = analyzeExitTriggers({\n pnlPath,\n legs,\n triggers: triggersWithCost,\n legGroups: legGroupsWithCost,\n });\n\n const { firstToFire, partialCloses } = analysisResult.overall;\n\n // Candidate P&L: account for partial closes from profitAction steps\n let candidatePnl: number;\n if (partialCloses && partialCloses.length > 0) {\n // Sum of partial close P&Ls\n const partialPnl = partialCloses.reduce((sum, pc) => sum + pc.pnlAtFire, 0);\n const closedAllocation = partialCloses.reduce((sum, pc) => sum + pc.allocation, 0);\n const remainingAllocation = 1 - closedAllocation;\n // Remaining position: firstToFire.pnlAtFire already reflects remaining allocation,\n // or if no trigger fired, scale last P&L by remaining allocation\n const remainingPnl = firstToFire !== null\n ? firstToFire.pnlAtFire\n : lastPnl * remainingAllocation;\n candidatePnl = partialPnl + remainingPnl;\n } else {\n // No partial closes: original behavior\n candidatePnl = firstToFire !== null ? firstToFire.pnlAtFire : lastPnl;\n }\n\n // Baseline P&L depends on mode\n const baselinePnl = baselineMode === 'actual' ? actualPnl : lastPnl;\n\n const pnlDelta = candidatePnl - baselinePnl;\n\n const triggerFired: TriggerType | 'noTrigger' =\n firstToFire !== null ? firstToFire.type : 'noTrigger';\n const fireTimestamp = firstToFire !== null ? firstToFire.firedAt : null;\n\n return {\n tradeIndex,\n dateOpened,\n actualPnl,\n candidatePnl,\n baselinePnl,\n pnlDelta,\n triggerFired,\n fireTimestamp,\n partialCloses: partialCloses && partialCloses.length > 0 ? partialCloses : undefined,\n };\n });\n\n const aggregate = computeAggregateStats(perTradeResults);\n const triggerAttribution = computeTriggerAttribution(perTradeResults);\n\n // Build summary string\n const topTrigger = triggerAttribution.length > 0 ? triggerAttribution[0] : null;\n const topTriggerStr = topTrigger\n ? `Top trigger: ${topTrigger.trigger} fired on ${topTrigger.count} trades.`\n : 'No triggers fired.';\n\n const summary =\n `Analyzed ${trades.length} trades: candidate win rate ${(aggregate.winRate * 100).toFixed(1)}%, ` +\n `total P&L $${aggregate.totalPnl.toFixed(2)} (delta $${aggregate.totalPnlDelta.toFixed(2)} vs baseline). ` +\n topTriggerStr;\n\n return {\n aggregate,\n triggerAttribution,\n perTrade: format === 'summary' ? [] : perTradeResults,\n baselineMode,\n summary,\n };\n}\n","/**\n * Batch Exit Analysis Tool\n *\n * MCP tool that evaluates a candidate exit policy across multiple trades in a\n * block. Queries trades from DuckDB, replays each one from the local\n * market-data cache, evaluates the candidate policy via the pure batch exit\n * analysis engine, and returns aggregate statistics with per-trigger\n * attribution.\n *\n * Tools registered:\n * - batch_exit_analysis -- Evaluate a candidate exit policy across an entire block\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport { handleReplayTrade } from \"./replay.ts\";\nimport {\n analyzeBatch,\n type TradeInput,\n type BatchExitConfig,\n type BatchExitResult,\n} from \"../utils/batch-exit-analysis.ts\";\nimport { getProfile } from \"../db/profile-schemas.ts\";\nimport type { ExitTriggerConfig, LegGroupConfig } from \"../utils/exit-triggers.ts\";\nimport type { MarketStores } from \"../market/stores/index.ts\";\n\n// ---------------------------------------------------------------------------\n// Concurrency limiter — hand-rolled semaphore, no external dependency (D-15)\n// ---------------------------------------------------------------------------\n\n/**\n * Simple concurrency limiter. Runs async tasks with at most `limit` in flight.\n * No external dependency — hand-rolled semaphore pattern per D-15.\n */\nasync function mapWithLimit<T, R>(\n items: T[],\n limit: number,\n fn: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let idx = 0;\n\n async function worker(): Promise<void> {\n while (idx < items.length) {\n const i = idx++;\n results[i] = await fn(items[i]);\n }\n }\n\n const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());\n await Promise.all(workers);\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Shared trigger type enum (mirrors exit-analysis.ts)\n// ---------------------------------------------------------------------------\n\nconst triggerTypeEnum = z.enum([\n 'profitTarget', 'stopLoss', 'trailingStop', 'profitAction',\n 'dteExit', 'ditExit', 'clockTimeExit',\n 'underlyingPriceMove', 'positionDelta', 'perLegDelta',\n 'vixMove', 'vix9dMove', 'vix9dVixRatio',\n 'slRatioThreshold', 'slRatioMove',\n]);\n\nconst triggerConfigSchema = z.object({\n type: triggerTypeEnum,\n threshold: z.number(),\n unit: z.enum(['percent', 'dollar']).default('dollar').optional(),\n expiry: z.string().optional(),\n openDate: z.string().optional(),\n clockTime: z.string().optional(),\n trailAmount: z.number().optional(),\n steps: z.array(z.object({\n armAt: z.number(),\n stopAt: z.number(),\n closeAllocationPct: z.number().min(0).max(1).optional()\n .describe(\"Fraction of REMAINING position to close at this milestone (0-1)\"),\n })).optional(),\n spreadWidth: z.number().optional(),\n contracts: z.number().optional(),\n legIndex: z.number().optional()\n .describe(\"0-based leg index for perLegDelta — targets specific leg\"),\n exitAbove: z.number().optional()\n .describe(\"Fire when value exceeds this (directional, no abs)\"),\n exitBelow: z.number().optional()\n .describe(\"Fire when value drops below this (directional, no abs)\"),\n});\n\n// ---------------------------------------------------------------------------\n// Zod Schema\n// ---------------------------------------------------------------------------\n\nexport const batchExitAnalysisSchema = z.object({\n block_id: z.string().describe(\"Block ID to analyze trades from\"),\n\n strategy: z.string().optional()\n .describe(\"Filter trades by strategy name (case-insensitive ILIKE)\"),\n\n date_range: z.object({\n from: z.string().optional().describe(\"Start date YYYY-MM-DD\"),\n to: z.string().optional().describe(\"End date YYYY-MM-DD\"),\n }).optional().describe(\"Filter trades by date range\"),\n\n candidate_policy: z.array(triggerConfigSchema)\n .describe(\"Candidate exit policy triggers to evaluate -- same schema as analyze_exit_triggers\"),\n\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n triggers: z.array(triggerConfigSchema),\n })).optional().describe(\"Per-leg-group exit triggers for multi-structure strategies\"),\n\n baseline_mode: z.enum(['actual', 'holdToEnd']).default('actual')\n .describe(\"'actual' compares candidate vs trade's actual P&L; 'holdToEnd' compares vs last replay timestamp\"),\n\n limit: z.number().min(1).max(200).default(50)\n .describe(\"Max trades to analyze. Most recent trades selected\"),\n\n min_pl: z.number().optional()\n .describe(\"Only include trades with actual P&L >= this value\"),\n\n max_pl: z.number().optional()\n .describe(\"Only include trades with actual P&L <= this value\"),\n\n multiplier: z.number().default(100)\n .describe(\"Contract multiplier (default 100)\"),\n\n format: z.enum(['summary', 'full']).default('summary')\n .describe(\"'summary' returns aggregate stats + trigger attribution; 'full' adds per-trade breakdown\"),\n});\n\n// ---------------------------------------------------------------------------\n// Handler\n// ---------------------------------------------------------------------------\n\nexport async function handleBatchExitAnalysis(\n params: z.infer<typeof batchExitAnalysisSchema>,\n baseDir: string,\n stores: MarketStores, // Phase 4 CONSUMER-01 — threaded through for Wave 2+ rewrite.\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<BatchExitResult> {\n const {\n block_id,\n strategy,\n date_range,\n candidate_policy,\n leg_groups,\n baseline_mode,\n limit,\n min_pl,\n max_pl,\n multiplier,\n format,\n } = params;\n\n // 1. Query trades from DuckDB with deterministic ROW_NUMBER ordering\n const conn = injectedConn ?? await getConnection(baseDir);\n const escapedBlockId = block_id.replace(/'/g, \"''\");\n\n // Build WHERE clauses\n const whereClauses: string[] = [`block_id = '${escapedBlockId}'`];\n\n if (strategy) {\n const escapedStrategy = strategy.replace(/'/g, \"''\");\n whereClauses.push(`strategy ILIKE '%${escapedStrategy}%'`);\n }\n if (date_range?.from) {\n whereClauses.push(`date_opened >= '${date_range.from}'`);\n }\n if (date_range?.to) {\n whereClauses.push(`date_opened <= '${date_range.to}'`);\n }\n if (min_pl !== undefined) {\n whereClauses.push(`pl >= ${min_pl}`);\n }\n if (max_pl !== undefined) {\n whereClauses.push(`pl <= ${max_pl}`);\n }\n\n // ROW_NUMBER must be computed over the FULL block (no strategy/date filters)\n // because handleReplayTrade resolves trade_index as OFFSET against the full block.\n // Filters are applied AFTER numbering to preserve the global index.\n const filterClauses = whereClauses.slice(1); // drop block_id clause (already in CTE)\n const query = `\n WITH numbered AS (\n SELECT *, ROW_NUMBER() OVER (ORDER BY date_opened, rowid) - 1 AS trade_idx\n FROM trades.trade_data\n WHERE block_id = '${escapedBlockId}'\n )\n SELECT trade_idx, pl, date_opened\n FROM numbered\n ${filterClauses.length > 0 ? 'WHERE ' + filterClauses.join(' AND ') : ''}\n ORDER BY date_opened DESC\n LIMIT ${limit}\n `;\n\n const queryResult = await conn.runAndReadAll(query);\n const rows = queryResult.getRows();\n\n if (rows.length === 0) {\n const emptyResult: BatchExitResult = {\n aggregate: {\n totalTrades: 0,\n winningTrades: 0,\n losingTrades: 0,\n winRate: 0,\n totalPnl: 0,\n avgPnl: 0,\n avgWin: 0,\n avgLoss: 0,\n maxWin: 0,\n maxLoss: 0,\n profitFactor: 0,\n maxDrawdown: 0,\n sharpeRatio: null,\n maxWinStreak: 0,\n maxLossStreak: 0,\n baselineTotalPnl: 0,\n totalPnlDelta: 0,\n baselineWinRate: 0,\n },\n triggerAttribution: [],\n perTrade: [],\n baselineMode: baseline_mode,\n summary: 'Analyzed 0 trades: no matching trades found.',\n };\n return emptyResult;\n }\n\n // 2. Replay trades in parallel with concurrency limit (D-14)\n const MAX_CONCURRENT_REPLAYS = 5;\n\n type ReplayOutcome =\n | { ok: true; input: TradeInput }\n | { ok: false; tradeIndex: number; dateOpened: string; error: string };\n\n const outcomes = await mapWithLimit(\n rows,\n MAX_CONCURRENT_REPLAYS,\n async (row): Promise<ReplayOutcome> => {\n const tradeIdx = Number(row[0] ?? 0);\n const pl = Number(row[1] ?? 0);\n const dateOpened = String(row[2] ?? '');\n\n try {\n // Always pass format:'full' to get complete pnlPath for analyzeBatch.\n // params.format controls the batch output density, not the replay resolution.\n const replayResult = await handleReplayTrade(\n {\n block_id,\n trade_index: tradeIdx,\n multiplier,\n format: 'full',\n close_at: 'trade',\n skip_quotes: false,\n },\n baseDir,\n stores,\n injectedConn,\n );\n\n // Compute entry cost for percentage-based triggers (D-11)\n const tradeEntryCost = replayResult.legs.reduce((sum: number, leg) => {\n return sum + leg.entryPrice * leg.quantity * leg.multiplier;\n }, 0);\n\n return {\n ok: true,\n input: {\n tradeIndex: tradeIdx,\n dateOpened,\n actualPnl: pl,\n pnlPath: replayResult.pnlPath,\n legs: replayResult.legs,\n entryCost: tradeEntryCost,\n },\n };\n } catch (err) {\n return {\n ok: false,\n tradeIndex: Number(row[0] ?? 0),\n dateOpened: String(row[2] ?? ''),\n error: err instanceof Error ? err.message : String(err),\n };\n }\n },\n );\n\n const tradeInputs: TradeInput[] = [];\n const skippedTrades: Array<{ tradeIndex: number; dateOpened: string; error: string }> = [];\n\n for (const outcome of outcomes) {\n if (outcome.ok) {\n tradeInputs.push(outcome.input);\n } else {\n skippedTrades.push({\n tradeIndex: outcome.tradeIndex,\n dateOpened: outcome.dateOpened,\n error: outcome.error,\n });\n }\n }\n\n // 3. Build BatchExitConfig\n const config: BatchExitConfig = {\n candidatePolicy: candidate_policy as ExitTriggerConfig[],\n legGroups: leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n triggers: g.triggers as ExitTriggerConfig[],\n })) as LegGroupConfig[] | undefined,\n baselineMode: baseline_mode,\n format,\n };\n\n // 4. Run the pure batch analysis engine\n const result = analyzeBatch(tradeInputs, config);\n\n // 5. Augment summary with skip info if any trades were skipped\n if (skippedTrades.length > 0) {\n result.summary = result.summary.replace(\n /^Analyzed (\\d+) trades/,\n `Analyzed ${tradeInputs.length} trades (${skippedTrades.length} skipped due to replay errors)`,\n );\n result.skippedTrades = skippedTrades;\n }\n\n // 6. Load profile context if strategy is specified (per D-16)\n if (strategy) {\n try {\n const profileConn = injectedConn ?? await getConnection(baseDir);\n const profile = await getProfile(profileConn, block_id, strategy, baseDir);\n if (profile) {\n result.profileContext = {\n structureType: profile.structureType,\n exitRules: profile.exitRules.map(r =>\n r.description ?? `${r.type} ${r.trigger}`\n ),\n };\n }\n } catch {\n // Profile is informational context, not critical — swallow errors\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerBatchExitAnalysisTools(\n server: McpServer,\n baseDir: string,\n stores: MarketStores, // Phase 4 CONSUMER-01 — threaded through for Wave 2+ rewrite.\n): void {\n server.registerTool(\n \"batch_exit_analysis\",\n {\n description:\n \"Analyze how a candidate exit policy would perform across multiple trades in a block. \" +\n \"Replays each matching trade, evaluates exit triggers against the minute-level P&L path, \" +\n \"and returns aggregate statistics (win rate, Sharpe, profit factor, drawdown) comparable \" +\n \"to get_statistics. Includes per-trigger attribution showing which triggers drive outcomes. \" +\n \"Reads option-leg quotes via QuoteStore and underlying bars via SpotStore (cache only); \" +\n \"trades with missing data are skipped. Use the data-pipeline tools to backfill cache, \" +\n \"and strategy profiles to iterate on exit rules.\",\n inputSchema: batchExitAnalysisSchema,\n },\n async (params) => {\n try {\n const result = await handleBatchExitAnalysis(params, baseDir, stores);\n return createToolOutput(result.summary, result);\n } catch (error) {\n return {\n content: [{\n type: \"text\" as const,\n text: `Error in batch exit analysis: ${(error as Error).message}`,\n }],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * quote-enricher.ts\n *\n * Pure helpers for option-quote enrichment planning.\n *\n * The I/O fetch loop (`enrichQuotesForTickers`) and its coverage-probe\n * helper (`fetchExistingCoverage`) were deleted — reads no longer trigger\n * provider fetches. Every caller routes through `backfillQuotes(stores, ...)`\n * from `utils/quote-backfill.ts`.\n *\n * Surviving public surface (this file):\n * - shouldSkipEnrichment(barCount, threshold) pure density check\n * - buildEnrichmentPlan(input) pure planner — groups\n * (ticker, date) combos\n * needing enrichment. Reused\n * by backfillQuotes.\n * - QuoteEnrichmentResult result-shape type — preserved\n * because internal callers may\n * still reference it; structurally\n * identical to BackfillQuotesResult\n * from quote-backfill.ts.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentPlanItem {\n ticker: string;\n date: string;\n existingBarCount: number;\n}\n\n/**\n * Result shape returned by the (deleted) `enrichQuotesForTickers` function.\n * Every call site has been switched to `backfillQuotes`, whose result type\n * (`BackfillQuotesResult`) is structurally identical. This alias is kept\n * as a stable internal type name; callers may import either.\n */\nexport interface QuoteEnrichmentResult {\n tickersProcessed: number;\n tickersSkipped: number; // already dense\n rowsWritten: number;\n errors: Array<{ ticker: string; date: string; error: string }>;\n unsupportedReason?: string;\n}\n\nexport interface EnrichmentPlanInput {\n tickers: Array<{ ticker: string; fromDate: string; toDate: string }>;\n existingCoverage: Map<string, number>; // \"ticker:date\" → barCount\n providerSupportsQuotes: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Pure functions\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if ticker/date already has dense enough data (>= densityThreshold bars).\n * Default threshold is 200 bars/day — roughly half the 390 market minutes, indicating\n * that quote enrichment has already been applied.\n */\nexport function shouldSkipEnrichment(barCount: number, densityThreshold = 200): boolean {\n return barCount >= densityThreshold;\n}\n\n/**\n * Build a list of ticker+date combos that need enrichment.\n *\n * Rules:\n * - Returns empty if provider doesn't support quotes\n * - Returns empty if tickers list is empty\n * - Expands each ticker's fromDate→toDate range into individual dates\n * - Skips dates where existing coverage is already dense (>= 200 bars)\n *\n * This is a pure function — no I/O, takes coverage as a pre-fetched Map.\n */\nexport function buildEnrichmentPlan(input: EnrichmentPlanInput): EnrichmentPlanItem[] {\n if (!input.providerSupportsQuotes) return [];\n if (input.tickers.length === 0) return [];\n\n const plan: EnrichmentPlanItem[] = [];\n\n for (const tickerSpec of input.tickers) {\n const dates = expandDateRange(tickerSpec.fromDate, tickerSpec.toDate);\n for (const date of dates) {\n const key = `${tickerSpec.ticker}:${date}`;\n const existingBarCount = input.existingCoverage.get(key) ?? 0;\n if (!shouldSkipEnrichment(existingBarCount)) {\n plan.push({\n ticker: tickerSpec.ticker,\n date,\n existingBarCount,\n });\n }\n }\n }\n\n return plan;\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Expand a date range into individual YYYY-MM-DD dates (inclusive, calendar days).\n */\nfunction expandDateRange(fromDate: string, toDate: string): string[] {\n const dates: string[] = [];\n const from = new Date(fromDate + 'T00:00:00Z');\n const to = new Date(toDate + 'T00:00:00Z');\n const current = new Date(from);\n while (current <= to) {\n dates.push(current.toISOString().slice(0, 10));\n current.setUTCDate(current.getUTCDate() + 1);\n }\n return dates;\n}\n\n// ---------------------------------------------------------------------------\n// The `enrichQuotesForTickers` throw-stub has been deleted. All callers now\n// route through `backfillQuotes(stores, ...)` from `utils/quote-backfill.ts`.\n// ---------------------------------------------------------------------------\n","/**\n * SQL Query Tool\n *\n * Provides direct SQL query access to the DuckDB analytics database.\n * Enables ad-hoc analysis, hypothesis testing, and data exploration\n * across trades and market data.\n *\n * Security:\n * - SELECT queries run freely; DELETE/UPDATE require confirm=true\n * - No file access functions (read_csv, write_csv, etc.)\n * - No schema modifications (CREATE, ALTER, DROP)\n * - 30-second query timeout with clear error message\n *\n * Available tables:\n * - trades.trade_data: Trade records from all blocks (includes inferred ticker)\n * - trades.reporting_data: Reporting/actual trade records from strategy logs\n * - market.spot: Minute bars, ticker-first layout (indicators source)\n * - market.spot_daily: RTH-aggregated daily OHLCV view over market.spot (ticker, date)\n * - market.enriched: Daily technical indicators (RSI, ATR, IVR/IVP, etc.), ticker-first\n * - market.enriched_context: Cross-ticker derived fields (Vol_Regime, Term_Structure_State, etc.)\n * - market.option_chain: Option contract metadata keyed by (underlying, date)\n * - market.option_quote_minutes: Option NBBO minute bars keyed by (underlying, date)\n * - market._sync_metadata: Import/enrichment tracking metadata\n */\n\nimport * as path from \"path\";\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport { getConnection, upgradeToReadWrite, downgradeToReadOnly } from \"../db/connection.ts\";\nimport { getDataRoot } from \"../db/data-root.ts\";\nimport { withFullSync } from \"./middleware/sync-middleware.ts\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\n\n/**\n * Available tables for reference in error messages\n */\nconst AVAILABLE_TABLES = [\n \"trades.trade_data\",\n \"trades.reporting_data\",\n \"market.spot\",\n \"market.spot_daily\",\n \"market.enriched\",\n \"market.enriched_context\",\n \"market.option_chain\",\n \"market.option_quote_minutes\",\n \"market._sync_metadata\",\n];\n\n/**\n * Always-blocked SQL patterns — external access, writes, and config changes.\n */\nconst BLOCKED_PATTERNS: Array<{ pattern: RegExp; operation: string }> = [\n // External access\n { pattern: /\\bCOPY\\b/i, operation: \"COPY\" },\n { pattern: /\\bEXPORT\\b/i, operation: \"EXPORT\" },\n { pattern: /\\bATTACH\\b/i, operation: \"ATTACH\" },\n { pattern: /\\bDETACH\\b/i, operation: \"DETACH\" },\n\n // File functions that write or read arbitrary text\n { pattern: /\\bread_text\\s*\\(/i, operation: \"read_text()\" },\n { pattern: /\\bwrite_csv\\s*\\(/i, operation: \"write_csv()\" },\n\n // Configuration changes (standalone SET, not UPDATE ... SET)\n { pattern: /^\\s*SET\\b/i, operation: \"SET\" },\n];\n\n/**\n * File-read functions allowed only when every path argument resolves under\n * --data-root. Lets debug queries inspect managed Parquet/CSV/JSON while\n * preventing filesystem traversal via SQL.\n */\nconst PATH_GATED_READ_FUNCTIONS = [\"read_parquet\", \"read_csv\", \"read_json\"] as const;\n\ninterface FileReadCall {\n fn: string;\n paths: string[];\n}\n\nfunction findMatchingParen(s: string, openIdx: number): number {\n let depth = 0;\n for (let i = openIdx; i < s.length; i++) {\n if (s[i] === \"(\") depth++;\n else if (s[i] === \")\") {\n depth--;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/**\n * Scan SQL for path-gated file-read calls. Returns null if any call can't\n * be parsed safely — the caller should treat null as a block verdict.\n */\nfunction extractFileReadCalls(sql: string): FileReadCall[] | null {\n const calls: FileReadCall[] = [];\n for (const fn of PATH_GATED_READ_FUNCTIONS) {\n const nameRegex = new RegExp(`\\\\b${fn}\\\\s*\\\\(`, \"gi\");\n let m: RegExpExecArray | null;\n while ((m = nameRegex.exec(sql)) !== null) {\n const openIdx = m.index + m[0].length - 1;\n const closeIdx = findMatchingParen(sql, openIdx);\n if (closeIdx === -1) return null;\n const args = sql.slice(openIdx + 1, closeIdx);\n const paths: string[] = [];\n const strRegex = /(['\"])((?:\\\\.|(?!\\1).)*)\\1/g;\n let s: RegExpExecArray | null;\n while ((s = strRegex.exec(args)) !== null) {\n paths.push(s[2]);\n }\n if (paths.length === 0) return null;\n calls.push({ fn, paths });\n }\n }\n return calls;\n}\n\n/**\n * Is the given path under dataRoot? Strips glob characters from the end so\n * patterns like `<root>/market/spot/ ** /*.parquet` validate against their\n * literal prefix. Resolves both paths absolutely and compares with a\n * separator guard to prevent `<root>-evil` from matching `<root>`.\n */\nexport function isUnderDataRoot(filePath: string, dataRoot: string): boolean {\n const globMatch = filePath.match(/[*?[]/);\n const prefix = globMatch?.index !== undefined ? filePath.slice(0, globMatch.index) : filePath;\n const resolved = path.resolve(prefix);\n const resolvedRoot = path.resolve(dataRoot);\n const rootWithSep = resolvedRoot.endsWith(path.sep) ? resolvedRoot : resolvedRoot + path.sep;\n return resolved === resolvedRoot || resolved.startsWith(rootWithSep);\n}\n\n/**\n * Default and maximum query timeout in milliseconds\n */\nconst DEFAULT_TIMEOUT_MS = 30000;\n\n/**\n * Maximum rows that can be returned\n */\nconst MAX_ROWS = 1000;\n\n/**\n * Mutating patterns that require confirm=true.\n * Without confirm, returns a preview of what would be affected.\n */\nconst CONFIRM_REQUIRED_PATTERNS: Array<{ pattern: RegExp; operation: string }> = [\n { pattern: /\\bDELETE\\b/i, operation: \"DELETE\" },\n { pattern: /\\bUPDATE\\b/i, operation: \"UPDATE\" },\n { pattern: /\\bINSERT\\b/i, operation: \"INSERT\" },\n { pattern: /\\bTRUNCATE\\b/i, operation: \"TRUNCATE\" },\n { pattern: /\\bDROP\\b/i, operation: \"DROP\" },\n { pattern: /\\bCREATE\\b/i, operation: \"CREATE\" },\n { pattern: /\\bALTER\\b/i, operation: \"ALTER\" },\n];\n\n/**\n * Validate SQL query for dangerous patterns.\n * Returns null if valid, or an error message if invalid.\n */\nexport function validateQuery(sql: string, dataRoot: string): string | null {\n for (const { pattern, operation } of BLOCKED_PATTERNS) {\n if (pattern.test(sql)) {\n return `${operation} operations are not allowed.`;\n }\n }\n\n const calls = extractFileReadCalls(sql);\n if (calls === null) {\n return \"File-read function calls could not be parsed safely. Use the market.* views instead.\";\n }\n for (const call of calls) {\n for (const p of call.paths) {\n if (!isUnderDataRoot(p, dataRoot)) {\n return `${call.fn}() path must be under --data-root: ${p}`;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Validate a user-supplied SELECT passed into import_flat_file.\n *\n * Keeps the hard blocks on external access, writes, and config changes —\n * but relaxes the read_parquet/read_csv/read_json path gate because the\n * purpose of the import tool is to pull data from arbitrary source files\n * the LLM has been pointed at. The output location is sandboxed by the\n * store's partition-path composer (data-root-relative, whitelisted\n * partition values), so a malicious SELECT can only pollute the store\n * it's writing to — it cannot exfiltrate or write outside the data root.\n */\nexport function validateImportSelect(sql: string): string | null {\n for (const { pattern, operation } of BLOCKED_PATTERNS) {\n if (pattern.test(sql)) {\n return `${operation} operations are not allowed in select_sql.`;\n }\n }\n if (/^\\s*SELECT\\b/i.test(sql) === false && /^\\s*WITH\\b/i.test(sql) === false) {\n return \"select_sql must be a SELECT or WITH statement.\";\n }\n return null;\n}\n\n/**\n * Check if a query is destructive (DELETE/UPDATE) and needs confirmation.\n */\nfunction isDestructiveQuery(sql: string): { destructive: boolean; operation: string } {\n for (const { pattern, operation } of CONFIRM_REQUIRED_PATTERNS) {\n if (pattern.test(sql)) {\n return { destructive: true, operation };\n }\n }\n return { destructive: false, operation: \"\" };\n}\n\n/**\n * Convert a DELETE/UPDATE statement to a SELECT COUNT(*) preview query.\n * DELETE FROM table WHERE ... → SELECT COUNT(*) as affected_rows FROM table WHERE ...\n * UPDATE table SET ... WHERE ... → SELECT COUNT(*) as affected_rows FROM table WHERE ...\n */\nfunction toPreviewQuery(sql: string): string {\n const trimmed = sql.trim().replace(/;$/, \"\");\n\n // DELETE FROM table WHERE ...\n const deleteMatch = trimmed.match(/^\\s*DELETE\\s+FROM\\s+(.+?)(?:\\s+WHERE\\s+(.+))?$/is);\n if (deleteMatch) {\n const table = deleteMatch[1].trim();\n const where = deleteMatch[2] ? ` WHERE ${deleteMatch[2]}` : \"\";\n return `SELECT COUNT(*) as affected_rows FROM ${table}${where}`;\n }\n\n // UPDATE table SET ... WHERE ...\n const updateMatch = trimmed.match(/^\\s*UPDATE\\s+(\\S+)\\s+.*?(?:WHERE\\s+(.+))?$/is);\n if (updateMatch) {\n const table = updateMatch[1].trim();\n const where = updateMatch[2] ? ` WHERE ${updateMatch[2]}` : \"\";\n return `SELECT COUNT(*) as affected_rows FROM ${table}${where}`;\n }\n\n return `SELECT 'Could not generate preview' as warning`;\n}\n\n/**\n * Check if query already has a LIMIT clause\n */\nfunction hasLimitClause(sql: string): boolean {\n // Match LIMIT at word boundary, not inside a string literal\n // This is a simple check - complex queries with LIMIT in subqueries\n // will still pass, which is fine (better to let DuckDB handle it)\n return /\\bLIMIT\\s+\\d+/i.test(sql);\n}\n\n/**\n * Query result with column metadata\n */\ninterface QueryResult {\n rows: Record<string, unknown>[];\n columns: Array<{ name: string; type: string }>;\n totalRows: number;\n}\n\n/**\n * Execute a SQL query with timeout protection.\n *\n * @param conn - DuckDB connection\n * @param sql - SQL query to execute\n * @param limit - Maximum rows to return\n * @param timeoutMs - Timeout in milliseconds\n * @returns Query results with column metadata\n */\nasync function executeWithTimeout(\n conn: DuckDBConnection,\n sql: string,\n limit: number,\n timeoutMs: number = DEFAULT_TIMEOUT_MS\n): Promise<QueryResult> {\n // Add LIMIT if not present\n let finalSql = sql.trim();\n if (finalSql.endsWith(\";\")) {\n finalSql = finalSql.slice(0, -1);\n }\n\n if (!hasLimitClause(finalSql)) {\n finalSql = `${finalSql} LIMIT ${limit}`;\n }\n\n // Create timeout promise\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(\n new Error(\n \"Query exceeded 30s timeout. Consider adding LIMIT or filtering by block_id.\"\n )\n );\n }, timeoutMs);\n });\n\n // Execute query with timeout\n const queryPromise = (async (): Promise<QueryResult> => {\n const result = await conn.runAndReadAll(finalSql);\n\n // Extract column metadata\n const columnCount = result.columnCount;\n const columns: Array<{ name: string; type: string }> = [];\n\n for (let i = 0; i < columnCount; i++) {\n columns.push({\n name: result.columnName(i),\n type: result.columnType(i).toString(),\n });\n }\n\n // Convert rows to objects\n const rows: Record<string, unknown>[] = [];\n for (const row of result.getRows()) {\n const obj: Record<string, unknown> = {};\n for (let i = 0; i < columnCount; i++) {\n const value = row[i];\n // Convert BigInt to Number for JSON serialization\n obj[columns[i].name] = typeof value === \"bigint\" ? Number(value) : value;\n }\n rows.push(obj);\n }\n\n return {\n rows,\n columns,\n totalRows: rows.length,\n };\n })();\n\n return Promise.race([queryPromise, timeoutPromise]);\n}\n\n/**\n * Enhance error messages with helpful suggestions.\n *\n * @param error - Original error\n * @returns Enhanced error message\n */\nfunction enhanceError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n\n // Table not found - suggest available tables\n if (\n message.toLowerCase().includes(\"table\") &&\n (message.toLowerCase().includes(\"not found\") ||\n message.toLowerCase().includes(\"does not exist\") ||\n message.toLowerCase().includes(\"catalog error\"))\n ) {\n return `${message}\\n\\nAvailable tables:\\n${AVAILABLE_TABLES.map((t) => ` - ${t}`).join(\"\\n\")}`;\n }\n\n // Column not found - suggest DESCRIBE\n if (\n message.toLowerCase().includes(\"column\") &&\n (message.toLowerCase().includes(\"not found\") ||\n message.toLowerCase().includes(\"does not exist\") ||\n message.toLowerCase().includes(\"binder error\"))\n ) {\n return `${message}\\n\\nTip: Use DESCRIBE trades.trade_data; to see available columns.`;\n }\n\n // Timeout messages are already helpful\n if (message.includes(\"timeout\")) {\n return message;\n }\n\n // Pass through other errors (syntax errors include line/column info from DuckDB)\n return message;\n}\n\n/**\n * Register SQL query tools with the MCP server.\n *\n * @param server - MCP server instance\n * @param baseDir - Base directory for data files\n */\nexport function registerSQLTools(server: McpServer, baseDir: string): void {\n server.registerTool(\n \"run_sql\",\n {\n description:\n \"Execute a SQL query against the DuckDB analytics database. \" +\n \"SELECT runs freely. All mutating operations (DELETE, UPDATE, INSERT, CREATE, ALTER, DROP, TRUNCATE) \" +\n \"require confirm=true — without it, returns a preview or confirmation prompt. \" +\n \"Query trades (trades.trade_data, trades.reporting_data) and market data \" +\n \"(market.spot, market.enriched, market.enriched_context, market.spot_daily, \" +\n \"market.option_chain, market.option_quote_minutes, market._sync_metadata). \" +\n \"Trade queries should filter by block_id (e.g. WHERE block_id = 'my-strategy'). \" +\n \"Call describe_database first to discover available block_ids and column names. \" +\n \"Returns up to 1000 rows for SELECT queries.\",\n inputSchema: z.object({\n query: z.string().describe(\"SQL query to execute\"),\n limit: z\n .number()\n .min(1)\n .max(MAX_ROWS)\n .default(100)\n .describe(`Maximum rows to return (default: 100, max: ${MAX_ROWS})`),\n confirm: z\n .boolean()\n .default(false)\n .describe(\"Required for all mutating operations (DELETE, UPDATE, INSERT, CREATE, ALTER, DROP, TRUNCATE). Without it, returns a preview or prompt.\"),\n }),\n },\n withFullSync(baseDir, async ({ query, limit, confirm }) => {\n // Validate query for dangerous patterns\n const validationError = validateQuery(query, getDataRoot(baseDir));\n if (validationError) {\n return {\n content: [{ type: \"text\" as const, text: validationError }],\n isError: true as const,\n };\n }\n\n // Check if mutating — require confirm\n const { destructive, operation } = isDestructiveQuery(query);\n if (destructive && !confirm) {\n // For DELETE/UPDATE, try to preview affected row count\n if (operation === \"DELETE\" || operation === \"UPDATE\") {\n try {\n const conn = await getConnection(baseDir);\n const previewSql = toPreviewQuery(query);\n const result = await executeWithTimeout(conn, previewSql, 1);\n const count = result.rows[0]?.affected_rows ?? \"unknown\";\n return createToolOutput(\n `⚠️ ${operation} would affect ${count} row(s). Re-run with confirm=true to execute.`,\n { operation, affectedRows: count, query, preview: true },\n );\n } catch (error) {\n return createToolOutput(\n `⚠️ ${operation} requires confirm=true. Could not preview: ${error instanceof Error ? error.message : String(error)}`,\n { operation, query, preview: true },\n );\n }\n }\n // For other mutating ops (INSERT, CREATE, ALTER, DROP, TRUNCATE) — no preview, just gate\n return createToolOutput(\n `⚠️ ${operation} requires confirm=true to execute.`,\n { operation, query, preview: true },\n );\n }\n\n try {\n // Get DuckDB connection\n const conn = await getConnection(baseDir);\n\n if (destructive && confirm) {\n // Upgrade to read-write for mutations\n await upgradeToReadWrite(baseDir);\n try {\n const rwConn = await getConnection(baseDir);\n const result = await rwConn.run(query);\n const changed = Number(result.rowsChanged);\n return createToolOutput(\n `${operation} completed: ${changed} row(s) affected.`,\n { operation, rowsAffected: changed },\n );\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n }\n\n // Execute SELECT query with timeout\n const result = await executeWithTimeout(conn, query, limit);\n\n // Determine if results were truncated\n const returnedRows = result.rows.length;\n const truncated = returnedRows >= limit;\n\n // Create summary\n const summary = `Query returned ${returnedRows} row(s)${truncated ? ` (limited to ${limit})` : \"\"}`;\n\n // Return structured output\n return createToolOutput(summary, {\n rows: result.rows,\n columns: result.columns,\n totalRows: result.totalRows,\n returnedRows,\n truncated,\n });\n } catch (error) {\n const enhancedMessage = enhanceError(error);\n return {\n content: [{ type: \"text\" as const, text: enhancedMessage }],\n isError: true as const,\n };\n }\n })\n );\n}\n","/**\n * chain-loader.ts\n *\n * Pure helpers for option chain filtering and deduplication.\n *\n * The three-step cache-lifecycle fetch path is gone — reads never trigger\n * provider fetches. Per-date chain reads now flow through\n * `stores.chain.readChain(underlying, date)` (ChainStore API). Empty array\n * is the skip signal — the legacy `ChainSkipResult` / `isChainSkip`\n * type-guard pair has been deleted along with the SQL builders that backed\n * the cache lookups.\n *\n * Surviving public surface (this file):\n * - filterChain(contracts, filter) pure DTE / contract-type filter\n * - deduplicateContracts(contracts) pure SPX/SPXW collision resolver\n * - ContractRow type single source of truth for the\n * on-the-wire contract shape (also\n * re-exported from market/stores/types.ts)\n *\n * Transitional surface (deprecated, scheduled for removal):\n * - ChainLoadResult interface { contracts: ContractRow[], source: 'cache' }\n * preserved until downstream consumers\n * are rewritten to accept `ContractRow[]`\n * directly.\n *\n * Anything not listed above (loadChain, loadChainsBulk, buildCachedChainQuery,\n * optionChainPartitionSource, chainColumnsSql, chainRowFromSql, ChainResult,\n * ChainSkipResult, ChainSkipReason, isChainSkip) was deleted as part of the\n * ChainStore migration.\n */\n\n// ---------------------------------------------------------------------------\n// Exported types\n// ---------------------------------------------------------------------------\n\nexport interface ContractRow {\n underlying: string;\n date: string;\n ticker: string;\n contract_type: \"call\" | \"put\";\n strike: number;\n expiration: string;\n dte: number;\n exercise_style: string;\n}\n\n/**\n * Transitional shape — see file header. The `source` field is fixed to\n * `'cache'` because reads no longer fetch from a provider. This interface\n * is scheduled for removal once downstream consumers are switched to plain\n * `ContractRow[]`.\n *\n * Do NOT add new code that constructs ChainLoadResult; use ContractRow[].\n */\nexport interface ChainLoadResult {\n contracts: ContractRow[];\n source: \"cache\";\n}\n\n// ---------------------------------------------------------------------------\n// Filter types and pure functions\n// ---------------------------------------------------------------------------\n\nexport interface ChainFilterOptions {\n dte_min?: number;\n dte_max?: number;\n contract_type?: \"call\" | \"put\";\n}\n\n/**\n * Deduplicate contracts that share the same (contract_type, strike, expiration).\n * On monthly SPX expirations, Polygon returns both SPX and SPXW tickers for the\n * same contract. The SPX (non-W) ticker often lacks bars on expiration day itself,\n * causing 0DTE trades to skip with \"no_bars_available\". Prefer SPXW over SPX.\n *\n * Exported so downstream consumers (entry-resolver, candidate selector) can\n * dedupe ContractRow lists they construct from cache reads.\n */\nexport function deduplicateContracts(contracts: ContractRow[]): ContractRow[] {\n const map = new Map<string, ContractRow>();\n for (const c of contracts) {\n const key = `${c.contract_type}|${c.strike}|${c.expiration}`;\n const existing = map.get(key);\n if (!existing) {\n map.set(key, c);\n } else if (c.ticker.startsWith('SPXW') && !existing.ticker.startsWith('SPXW')) {\n map.set(key, c);\n }\n }\n return Array.from(map.values());\n}\n\n/**\n * Filter an array of ContractRow by DTE range and/or contract type.\n * Filtering happens post-cache so the full chain is cached once, filtered many times.\n * Also deduplicates SPX/SPXW ticker collisions (see deduplicateContracts).\n */\nexport function filterChain(contracts: ContractRow[], filter: ChainFilterOptions): ContractRow[] {\n const filtered = contracts.filter((c) => {\n if (filter.dte_min != null && c.dte < filter.dte_min) return false;\n if (filter.dte_max != null && c.dte > filter.dte_max) return false;\n if (filter.contract_type != null && c.contract_type !== filter.contract_type) return false;\n return true;\n });\n return deduplicateContracts(filtered);\n}\n\n// ---------------------------------------------------------------------------\n// Transitional throw-stubs (deprecated, scheduled for removal)\n//\n// The cache-miss fetch path (Massive HTTP + INSERT OR REPLACE INTO\n// market.option_chain) is gone. The named symbols below survive ONLY as\n// throw-stubs to keep downstream consumers compiling until they have been\n// rewritten to use `stores.chain.readChain(...)` directly.\n//\n// These stubs MUST NEVER be invoked at runtime. They exist purely so static\n// type-checking and Jest module-graph resolution succeed in the interim.\n// ---------------------------------------------------------------------------\n\n/**\n * @deprecated Removed in the ChainStore migration. Use\n * `stores.chain.readChain(underlying, date)` instead — empty array is the\n * new skip signal (replaces `ChainSkipResult`). This stub throws at runtime\n * to make accidental callers loud and will be deleted once consumers have\n * been rewritten.\n */\nexport async function loadChain(\n _underlying: string,\n _asOfDate: string,\n _conn: unknown,\n _opts?: { dataDir?: string; maxDte?: number },\n): Promise<ChainLoadResult> {\n throw new Error(\n \"chain-loader.loadChain has been removed. \" +\n \"Use stores.chain.readChain(underlying, date) instead — empty array is the new skip signal.\",\n );\n}\n\n/**\n * @deprecated Removed in the ChainStore migration. Use a\n * `for (const date of dates)` loop with `stores.chain.readChain(underlying, date)`\n * instead. This stub throws at runtime and will be deleted once consumers\n * have been rewritten.\n */\nexport async function loadChainsBulk(\n _underlying: string,\n _dates: string[],\n _conn: unknown,\n _opts?: { dataDir?: string },\n): Promise<Map<string, ChainLoadResult>> {\n throw new Error(\n \"chain-loader.loadChainsBulk has been removed. \" +\n \"Use a per-date loop with stores.chain.readChain(underlying, date) instead.\",\n );\n}\n","import { open, mkdir } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nexport interface BackfillProjectionInput {\n requestCount: number;\n avgLatencyMs: number;\n concurrency: number;\n}\n\nexport interface BackfillRequestCountInput {\n partitionCount: number;\n contractCount: number;\n}\n\nexport interface BackfillBandRequestCountInput {\n bandGroupCount: number;\n fallbackContractCount?: number;\n}\n\nexport interface BackfillParsedOccTicker {\n ticker: string;\n symbol: string;\n expiration: string;\n right: \"call\" | \"put\";\n strike: number;\n strikeText: string;\n}\n\nexport interface BackfillGreekBandGroup {\n key: string;\n symbol: string;\n expiration: string;\n date: string;\n contracts: BackfillParsedOccTicker[];\n}\n\nexport interface BackfillStagedGreekRow {\n ticker: string;\n timestamp: string;\n}\n\nexport interface BackfillConcreteFallback {\n contract: BackfillParsedOccTicker;\n missingTimes: string[];\n}\n\nexport interface BackfillConcreteFallbackInput {\n group: BackfillGreekBandGroup;\n fallbackUncoveredContracts?: boolean;\n expectedTimesByTicker: ReadonlyMap<string, ReadonlySet<string>>;\n stagedRows: BackfillStagedGreekRow[];\n}\n\nexport type BackfillManifestStatus =\n | \"prepared\"\n | \"committed\"\n | \"failed\"\n | \"committed_manifest_failed\";\n\nexport interface BackfillManifestEntry {\n status: BackfillManifestStatus;\n partitionPath: string;\n underlying: string;\n date: string;\n rowCountBefore: number;\n rowCountAfter: number;\n providerFirstOrderRows: number;\n computedFallbackRows: number;\n nullGreekRows: number;\n endpointErrors: string[];\n startedAt: string;\n completedAt: string;\n}\n\nexport interface BackfillRewriteSelectInput {\n existingTable: string;\n providerGreeksTable: string;\n}\n\nexport function backfillManifestPath(dataRoot: string, runId: string): string {\n return join(\n dataRoot,\n \"market\",\n \"_manifests\",\n \"thetadata-mdds-backfill\",\n `${runId}.ndjson`,\n );\n}\n\nexport function backfillPartitionPath(\n dataRoot: string,\n underlying: string,\n date: string,\n): string {\n return join(\n dataRoot,\n \"market\",\n \"option_quote_minutes\",\n `underlying=${normalizeUnderlying(underlying)}`,\n `date=${validateDate(date)}`,\n \"data.parquet\",\n );\n}\n\nexport function backfillShadowPartitionPath(partitionPath: string): string {\n if (!partitionPath.trim()) {\n throw new Error(\"partitionPath must not be empty\");\n }\n return `${partitionPath}.shadow`;\n}\n\nexport function makeBackfillRunId(now = new Date()): string {\n if (Number.isNaN(now.getTime())) {\n throw new Error(\"run id date must be valid\");\n }\n return now.toISOString().replace(/[-:.]/g, \"\");\n}\n\nexport function enumerateBackfillDates(from: string, to: string): string[] {\n const startText = validateDate(from);\n const endText = validateDate(to);\n const cursor = parseIsoDate(startText);\n const end = parseIsoDate(endText);\n if (cursor > end) {\n throw new Error(\"from must be on or before to\");\n }\n\n const dates: string[] = [];\n while (cursor <= end) {\n dates.push(formatIsoDate(cursor));\n cursor.setUTCDate(cursor.getUTCDate() + 1);\n }\n return dates;\n}\n\nexport function estimateBackfillRequestCount(input: BackfillRequestCountInput): number {\n assertNonNegativeInteger(\"partitionCount\", input.partitionCount);\n assertNonNegativeInteger(\"contractCount\", input.contractCount);\n return input.partitionCount * input.contractCount;\n}\n\nexport function estimateBackfillBandRequestCount(input: BackfillBandRequestCountInput): number {\n assertNonNegativeInteger(\"bandGroupCount\", input.bandGroupCount);\n const fallbackContractCount = input.fallbackContractCount ?? 0;\n assertNonNegativeInteger(\"fallbackContractCount\", fallbackContractCount);\n return input.bandGroupCount + fallbackContractCount;\n}\n\nexport function projectBackfillWallTimeHours(input: BackfillProjectionInput): number {\n assertPositiveFinite(\"requestCount\", input.requestCount);\n assertPositiveFinite(\"avgLatencyMs\", input.avgLatencyMs);\n assertFinite(\"concurrency\", input.concurrency);\n\n return (input.requestCount * input.avgLatencyMs) / Math.max(1, input.concurrency) / 3_600_000;\n}\n\nexport function makeBackfillManifestEntry(entry: BackfillManifestEntry): BackfillManifestEntry {\n return {\n status: validateManifestStatus(entry.status),\n partitionPath: requireNonEmpty(\"partitionPath\", entry.partitionPath),\n underlying: normalizeUnderlying(entry.underlying),\n date: validateDate(entry.date),\n rowCountBefore: sanitizeCount(\"rowCountBefore\", entry.rowCountBefore),\n rowCountAfter: sanitizeCount(\"rowCountAfter\", entry.rowCountAfter),\n providerFirstOrderRows: sanitizeCount(\"providerFirstOrderRows\", entry.providerFirstOrderRows),\n computedFallbackRows: sanitizeCount(\"computedFallbackRows\", entry.computedFallbackRows),\n nullGreekRows: sanitizeCount(\"nullGreekRows\", entry.nullGreekRows),\n endpointErrors: entry.endpointErrors.map((error) => String(error).trim()).filter(Boolean),\n startedAt: validateIsoTimestamp(\"startedAt\", entry.startedAt),\n completedAt: validateIsoTimestamp(\"completedAt\", entry.completedAt),\n };\n}\n\nexport function formatBackfillManifestLine(entry: BackfillManifestEntry): string {\n return `${JSON.stringify(makeBackfillManifestEntry(entry))}\\n`;\n}\n\nexport function parseBackfillOccTicker(ticker: string): BackfillParsedOccTicker {\n const normalizedTicker = requireNonEmpty(\"ticker\", ticker).toUpperCase();\n const match = normalizedTicker.match(/^([A-Z]+)(\\d{2})(\\d{2})(\\d{2})(C|P)(\\d{8})$/);\n if (!match) {\n throw new Error(`Invalid OCC option ticker: ${ticker}`);\n }\n\n const strike = Number.parseInt(match[6], 10) / 1000;\n return {\n ticker: normalizedTicker,\n symbol: match[1],\n expiration: `20${match[2]}-${match[3]}-${match[4]}`,\n right: match[5] === \"C\" ? \"call\" : \"put\",\n strike,\n strikeText: strike.toFixed(3),\n };\n}\n\nexport function groupBackfillTickersByGreekBand(\n tickers: string[],\n date: string,\n): BackfillGreekBandGroup[] {\n const validatedDate = validateDate(date);\n const byKey = new Map<string, BackfillGreekBandGroup & {\n contractByTicker: Map<string, BackfillParsedOccTicker>;\n }>();\n\n for (const ticker of tickers) {\n const contract = parseBackfillOccTicker(ticker);\n const key = `${contract.symbol}|${contract.expiration}|${validatedDate}`;\n let group = byKey.get(key);\n if (!group) {\n group = {\n key,\n symbol: contract.symbol,\n expiration: contract.expiration,\n date: validatedDate,\n contracts: [],\n contractByTicker: new Map(),\n };\n byKey.set(key, group);\n }\n group.contractByTicker.set(contract.ticker, contract);\n }\n\n return [...byKey.values()]\n .map((group) => ({\n key: group.key,\n symbol: group.symbol,\n expiration: group.expiration,\n date: group.date,\n contracts: [...group.contractByTicker.values()].sort((left, right) =>\n left.ticker.localeCompare(right.ticker)\n ),\n }))\n .sort((left, right) => left.key.localeCompare(right.key));\n}\n\nexport function collectBackfillConcreteFallbacks(\n input: BackfillConcreteFallbackInput,\n): BackfillConcreteFallback[] {\n const coveredTimesByTicker = new Map<string, Set<string>>();\n for (const row of input.stagedRows) {\n const ticker = requireNonEmpty(\"staged ticker\", row.ticker).toUpperCase();\n const time = row.timestamp.slice(11, 16);\n if (!time) continue;\n let coveredTimes = coveredTimesByTicker.get(ticker);\n if (!coveredTimes) {\n coveredTimes = new Set();\n coveredTimesByTicker.set(ticker, coveredTimes);\n }\n coveredTimes.add(time);\n }\n\n const fallbacks: BackfillConcreteFallback[] = [];\n for (const contract of input.group.contracts) {\n const expectedTimes = input.expectedTimesByTicker.get(contract.ticker) ?? new Set<string>();\n const coveredTimes = coveredTimesByTicker.get(contract.ticker) ?? new Set<string>();\n if (coveredTimes.size === 0 && !input.fallbackUncoveredContracts) {\n continue;\n }\n const missingTimes = [...expectedTimes]\n .filter((time) => !coveredTimes.has(time))\n .sort();\n if (missingTimes.length > 0) {\n fallbacks.push({ contract, missingTimes });\n }\n }\n return fallbacks;\n}\n\nexport async function appendBackfillManifestLineDurable(\n manifestPath: string,\n line: string,\n): Promise<void> {\n const path = requireNonEmpty(\"manifestPath\", manifestPath);\n const text = String(line);\n if (!text.endsWith(\"\\n\")) {\n throw new Error(\"manifest line must end with a newline\");\n }\n const parentDir = dirname(path);\n await mkdir(parentDir, { recursive: true });\n const existedBeforeOpen = await pathExists(path);\n const handle = await open(path, \"a\");\n try {\n await handle.writeFile(text);\n await handle.sync();\n } finally {\n await handle.close();\n }\n if (!existedBeforeOpen) {\n await fsyncDirectoryBestEffort(parentDir);\n }\n}\n\nexport function backfillRewriteSelectSql(input: BackfillRewriteSelectInput): string {\n const existingTable = validateSqlIdentifier(input.existingTable);\n const providerGreeksTable = validateSqlIdentifier(input.providerGreeksTable);\n return `\n SELECT\n CAST(e.underlying AS VARCHAR) AS underlying,\n CAST(e.date AS VARCHAR) AS date,\n CAST(e.ticker AS VARCHAR) AS ticker,\n CAST(e.time AS VARCHAR) AS time,\n CAST(e.bid AS DOUBLE) AS bid,\n CAST(e.ask AS DOUBLE) AS ask,\n CAST(e.mid AS DOUBLE) AS mid,\n CAST(e.last_updated_ns AS BIGINT) AS last_updated_ns,\n CAST(e.source AS VARCHAR) AS source,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.delta ELSE e.delta END AS REAL) AS delta,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.gamma ELSE e.gamma END AS REAL) AS gamma,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.theta ELSE e.theta END AS REAL) AS theta,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.vega ELSE e.vega END AS REAL) AS vega,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.iv ELSE e.iv END AS REAL) AS iv,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.greeks_source ELSE e.greeks_source END AS VARCHAR) AS greeks_source,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.greeks_revision ELSE e.greeks_revision END AS INTEGER) AS greeks_revision,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.rate_type ELSE e.rate_type END AS VARCHAR) AS rate_type,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.rate_value ELSE e.rate_value END AS DOUBLE) AS rate_value,\n CAST(CASE WHEN g.greeks_source = 'thetadata' THEN g.gamma_source ELSE e.gamma_source END AS VARCHAR) AS gamma_source\n FROM ${existingTable} e\n LEFT JOIN ${providerGreeksTable} g\n ON e.ticker = g.ticker\n AND e.time = g.time\n `.trim();\n}\n\nfunction assertPositiveFinite(name: string, value: number): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(`${name} must be a positive finite number`);\n }\n}\n\nfunction assertFinite(name: string, value: number): void {\n if (!Number.isFinite(value)) {\n throw new Error(`${name} must be a finite number`);\n }\n}\n\nfunction assertNonNegativeInteger(name: string, value: number): void {\n if (!Number.isInteger(value) || value < 0) {\n throw new Error(`${name} must be a non-negative integer`);\n }\n}\n\nfunction sanitizeCount(name: string, value: number): number {\n assertNonNegativeInteger(name, value);\n return value;\n}\n\nfunction validateManifestStatus(value: BackfillManifestStatus): BackfillManifestStatus {\n if (\n value === \"prepared\"\n || value === \"committed\"\n || value === \"failed\"\n || value === \"committed_manifest_failed\"\n ) {\n return value;\n }\n throw new Error(\"status must be prepared, committed, failed, or committed_manifest_failed\");\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n const handle = await open(path, \"r\");\n await handle.close();\n return true;\n } catch {\n return false;\n }\n}\n\nasync function fsyncDirectoryBestEffort(dirPath: string): Promise<void> {\n let handle: Awaited<ReturnType<typeof open>> | undefined;\n try {\n handle = await open(dirPath, \"r\");\n await handle.sync();\n } catch {\n // Some platforms/filesystems do not support directory fsync. Linux does,\n // and failures elsewhere should not make manifest append unusable.\n } finally {\n if (handle) await handle.close().catch(() => {});\n }\n}\n\nfunction requireNonEmpty(name: string, value: string): string {\n const text = String(value ?? \"\").trim();\n if (!text) throw new Error(`${name} must not be empty`);\n return text;\n}\n\nfunction normalizeUnderlying(value: string): string {\n return requireNonEmpty(\"underlying\", value).toUpperCase();\n}\n\nfunction validateSqlIdentifier(value: string): string {\n const text = requireNonEmpty(\"sql identifier\", value);\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(text)) {\n throw new Error(\"sql identifier must contain only letters, numbers, and underscores\");\n }\n return text;\n}\n\nfunction validateDate(value: string): string {\n const text = requireNonEmpty(\"date\", value);\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(text)) {\n throw new Error(\"date must use YYYY-MM-DD\");\n }\n const parsed = parseIsoDate(text);\n if (Number.isNaN(parsed.getTime()) || formatIsoDate(parsed) !== text) {\n throw new Error(\"date must be a valid calendar date\");\n }\n return text;\n}\n\nfunction validateIsoTimestamp(name: string, value: string): string {\n const text = requireNonEmpty(name, value);\n const parsed = new Date(text);\n if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== text) {\n throw new Error(`${name} must be an ISO timestamp`);\n }\n return text;\n}\n\nfunction parseIsoDate(date: string): Date {\n return new Date(`${date}T12:00:00.000Z`);\n}\n\nfunction formatIsoDate(date: Date): string {\n const yyyy = date.getUTCFullYear();\n const mm = String(date.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(date.getUTCDate()).padStart(2, \"0\");\n return `${yyyy}-${mm}-${dd}`;\n}\n","/**\n * greeks-attribution.ts\n *\n * MCP tool: get_greeks_attribution\n *\n * Decomposes a block's P&L into Greek components. Two modes:\n * - summary: block-level attribution percentages across all trades\n * - instance: single trade time-series of Greek P&L contributions\n */\n\nimport { z } from \"zod\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { handleDecomposeGreeks } from \"./exit-analysis.ts\";\nimport type { FactorContribution } from \"../utils/greeks-decomposition.ts\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createToolOutput } from \"../utils/output-formatter.ts\";\nimport { tradingDays } from \"../utils/flatfile-importer.ts\";\nimport type { MarketStores } from \"../market/stores/index.ts\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface AttributionEntry {\n factor: string;\n pnl: number;\n pct: number;\n pct_of_gross?: number;\n}\n\nexport interface AttributionSummaryResult {\n block_id: string;\n trades_decomposed: number;\n trades_skipped: number;\n trades_total: number;\n total_pnl: number;\n mark_total_pnl: number;\n execution_edge: number;\n gross_attribution_flow: number;\n attribution: AttributionEntry[];\n precision: \"high\" | \"low\";\n hint?: string;\n}\n\nexport interface AttributionStepEntry {\n date: string;\n delta: number;\n gamma: number;\n theta: number;\n vega: number;\n residual: number;\n time_and_vol?: number;\n charm?: number;\n vanna?: number;\n}\n\nexport interface AttributionInstanceResult {\n block_id: string;\n trade_index: number;\n trade_date: string;\n total_pnl: number;\n mark_total_pnl: number;\n execution_edge: number;\n gross_attribution_flow: number;\n steps: AttributionStepEntry[];\n attribution: AttributionEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nexport const getGreeksAttributionSchema = z.object({\n block_id: z.string().describe(\"Block ID to analyze\"),\n mode: z\n .enum([\"summary\", \"instance\"])\n .default(\"summary\")\n .describe(\"summary: block-level attribution. instance: single trade time-series.\"),\n trade_index: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe(\"Trade index (required for instance mode). Use get_block_info to find trade indices.\"),\n skip_quotes: z\n .boolean()\n .default(true)\n .describe(\"Use cached bar data only (fast). Set false to fetch NBBO quotes for higher precision.\"),\n detailed: z\n .boolean()\n .default(false)\n .describe(\"false: 5 factors (delta, gamma, theta, vega, residual). true: adds charm, vanna.\"),\n strategy: z\n .string()\n .optional()\n .describe(\"Filter to trades matching this strategy name (case-insensitive).\"),\n});\n\n// ---------------------------------------------------------------------------\n// Pure functions\n// ---------------------------------------------------------------------------\n\nconst COLLAPSE_MAP: Record<string, string> = {\n charm: \"delta\",\n vanna: \"vega\",\n};\n\nconst FACTOR_ORDER: string[] = [\"theta\", \"vega\", \"delta\", \"gamma\", \"residual\", \"time_and_vol\", \"charm\", \"vanna\"];\n\nexport function collapseFactors(\n factors: FactorContribution[],\n detailed: boolean,\n): Map<string, number> {\n const totals = new Map<string, number>();\n for (const f of factors) {\n const targetName = (!detailed && COLLAPSE_MAP[f.factor]) || f.factor;\n totals.set(targetName, (totals.get(targetName) ?? 0) + f.totalPnl);\n }\n return totals;\n}\n\nexport function computeAttribution(\n totals: Map<string, number>,\n totalPnl: number,\n grossAttributionFlow?: number,\n): AttributionEntry[] {\n const entries: AttributionEntry[] = [];\n for (const [factor, pnl] of totals) {\n entries.push({\n factor,\n pnl: Math.round(pnl * 100) / 100,\n pct: totalPnl !== 0 ? Math.round((pnl / totalPnl) * 1000) / 10 : 0,\n ...(grossAttributionFlow && grossAttributionFlow !== 0\n ? { pct_of_gross: Math.round((pnl / grossAttributionFlow) * 1000) / 10 }\n : {}),\n });\n }\n entries.sort((a, b) => {\n const ai = FACTOR_ORDER.indexOf(a.factor);\n const bi = FACTOR_ORDER.indexOf(b.factor);\n return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);\n });\n return entries;\n}\n\nexport function computeGrossAttributionFlow(totals: Map<string, number>): number {\n let gross = 0;\n for (const pnl of totals.values()) {\n gross += Math.abs(pnl);\n }\n return gross;\n}\n\nexport function assessPrecision(\n residualPnl: number,\n totalPnl: number,\n): { precision: \"high\" | \"low\"; hint?: string } {\n if (totalPnl === 0) return { precision: \"high\" };\n const residualPct = Math.abs(residualPnl / totalPnl) * 100;\n if (residualPct > 25) {\n return {\n precision: \"low\",\n hint: `Residual is ${Math.round(residualPct)}%. Re-run with skip_quotes=false for NBBO-based pricing.`,\n };\n }\n return { precision: \"high\" };\n}\n\n// ---------------------------------------------------------------------------\n// Handler\n// ---------------------------------------------------------------------------\n\nexport async function handleGetGreeksAttribution(\n params: z.infer<typeof getGreeksAttributionSchema>,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<AttributionSummaryResult | AttributionInstanceResult> {\n const { block_id, mode, trade_index, skip_quotes, detailed, strategy } = params;\n\n if (mode === \"instance\") {\n if (trade_index == null) {\n throw new Error(\"trade_index is required for instance mode\");\n }\n return handleInstanceMode(block_id, trade_index, skip_quotes, detailed, baseDir, stores, injectedConn);\n }\n\n return handleSummaryMode(block_id, skip_quotes, detailed, strategy, baseDir, stores, injectedConn);\n}\n\nasync function handleSummaryMode(\n block_id: string,\n skip_quotes: boolean,\n detailed: boolean,\n strategy: string | undefined,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<AttributionSummaryResult> {\n const conn = injectedConn ?? await getConnection(baseDir);\n\n const selectedTradesQuery = strategy\n ? `SELECT trade_index, pl\n FROM (\n SELECT ROW_NUMBER() OVER (ORDER BY date_opened, rowid) - 1 AS trade_index, pl, strategy\n FROM trades.trade_data\n WHERE block_id = $1\n )\n WHERE LOWER(strategy) = LOWER($2)\n ORDER BY trade_index`\n : `SELECT ROW_NUMBER() OVER (ORDER BY date_opened, rowid) - 1 AS trade_index, pl\n FROM trades.trade_data\n WHERE block_id = $1\n ORDER BY trade_index`;\n const selectedTradesParams = strategy ? [block_id, strategy] : [block_id];\n const selectedTradesResult = await conn.runAndReadAll(selectedTradesQuery, selectedTradesParams);\n const selectedTrades = selectedTradesResult.getRows().map((row) => ({\n tradeIndex: Number(row[0] ?? 0),\n actualPl: Number(row[1] ?? 0),\n }));\n const totalTrades = selectedTrades.length;\n\n if (totalTrades === 0) {\n throw new Error(\n strategy\n ? `No trades found for block \"${block_id}\" with strategy \"${strategy}\"`\n : `No trades found for block \"${block_id}\"`\n );\n }\n\n const accumulated = new Map<string, number>();\n let decomposed = 0;\n let skipped = 0;\n let actualTotalPnl = 0;\n let markTotalPnl = 0;\n\n // Process trades in concurrent batches for performance.\n // DuckDB supports concurrent reads; the replay engine is I/O-bound (bar cache lookups).\n const BATCH_SIZE = 10;\n for (let batch = 0; batch < totalTrades; batch += BATCH_SIZE) {\n const batchEnd = Math.min(batch + BATCH_SIZE, totalTrades);\n const promises = [];\n for (let i = batch; i < batchEnd; i++) {\n const trade = selectedTrades[i];\n promises.push(\n handleDecomposeGreeks(\n {\n block_id,\n trade_index: trade.tradeIndex,\n format: \"summary\",\n multiplier: 100,\n skip_quotes,\n },\n baseDir,\n stores,\n injectedConn,\n ).then(result => {\n for (const factor of result.factors) {\n accumulated.set(factor.factor, (accumulated.get(factor.factor) ?? 0) + factor.totalPnl);\n }\n actualTotalPnl += trade.actualPl;\n markTotalPnl += result.totalPnlChange;\n decomposed++;\n }).catch(() => {\n skipped++;\n })\n );\n }\n await Promise.allSettled(promises);\n }\n\n if (decomposed === 0) {\n return {\n block_id,\n trades_decomposed: 0,\n trades_skipped: skipped,\n trades_total: totalTrades,\n total_pnl: 0,\n mark_total_pnl: 0,\n execution_edge: 0,\n gross_attribution_flow: 0,\n attribution: [],\n precision: \"low\",\n hint: \"No trades could be decomposed. Ensure market data is cached for the trade dates.\",\n };\n }\n\n const collapsed = collapseFactors(\n [...accumulated.entries()].map(([factor, totalPnl]) => ({\n factor: factor as FactorContribution[\"factor\"],\n totalPnl,\n pctOfTotal: 0,\n steps: [],\n })),\n detailed,\n );\n\n const grossAttributionFlow = computeGrossAttributionFlow(collapsed);\n const attribution = computeAttribution(collapsed, actualTotalPnl, grossAttributionFlow);\n const residualPnl = collapsed.get(\"residual\") ?? 0;\n const precisionBase = grossAttributionFlow !== 0 ? grossAttributionFlow : markTotalPnl;\n const { precision, hint } = assessPrecision(residualPnl, precisionBase);\n const executionEdge = actualTotalPnl - markTotalPnl;\n\n // Warn when the execution edge dwarfs actual P&L — signals sparse or\n // low-quality market data rather than genuine fill advantage.\n const hints: string[] = [];\n if (hint) hints.push(hint);\n const edgeRatio = Math.abs(actualTotalPnl) > 0.01\n ? Math.abs(executionEdge) / Math.abs(actualTotalPnl)\n : 0;\n if (edgeRatio > 3) {\n hints.push(\n `Execution edge is ${Math.round(edgeRatio)}x the actual P&L — ` +\n `mark-to-market pricing may be based on sparse or low-quality data. ` +\n (skip_quotes\n ? `Re-run with skip_quotes=false for NBBO-based marks.`\n : `Consider whether intraday bar coverage is sufficient for this date range.`)\n );\n }\n\n return {\n block_id,\n trades_decomposed: decomposed,\n trades_skipped: skipped,\n trades_total: totalTrades,\n total_pnl: Math.round(actualTotalPnl * 100) / 100,\n mark_total_pnl: Math.round(markTotalPnl * 100) / 100,\n execution_edge: Math.round(executionEdge * 100) / 100,\n gross_attribution_flow: Math.round(grossAttributionFlow * 100) / 100,\n attribution,\n precision,\n ...(hints.length > 0 ? { hint: hints.join(' ') } : {}),\n };\n}\n\nasync function handleInstanceMode(\n block_id: string,\n trade_index: number,\n skip_quotes: boolean,\n detailed: boolean,\n baseDir: string,\n stores: MarketStores,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<AttributionInstanceResult> {\n const conn = injectedConn ?? await getConnection(baseDir);\n\n // Get trade date for the response\n const tradeResult = await conn.runAndReadAll(\n `SELECT date_opened, date_closed, pl FROM trades.trade_data\n WHERE block_id = $1\n ORDER BY date_opened, rowid\n LIMIT 1 OFFSET $2`,\n [block_id, trade_index]\n );\n const tradeRows = tradeResult.getRows();\n if (tradeRows.length === 0) {\n throw new Error(`Trade index ${trade_index} not found in block \"${block_id}\"`);\n }\n const tradeDate = String(tradeRows[0][0] ?? \"\");\n const closeDate = String(tradeRows[0][1] ?? tradeDate);\n const actualPnl = Number(tradeRows[0][2] ?? 0);\n\n // Run decomposition with full step data\n const result = await handleDecomposeGreeks(\n {\n block_id,\n trade_index,\n format: \"full\",\n multiplier: 100,\n skip_quotes,\n },\n baseDir,\n stores,\n injectedConn,\n );\n\n // Build per-step entries from factor step arrays\n const stepCount = result.stepCount;\n\n // Map step indices → dates via a pure Mon-Fri trading-day iterator.\n // Replays operate over date ranges with dense intraday coverage, so the\n // weekday iteration matches the set of dates the decomposition produced\n // (one step per trading day). `conn` is still used above for the trade\n // row fetch — only the date probe is store-free.\n const tradingDates = tradingDays(tradeDate, closeDate);\n const getStepDate = (i: number): string =>\n i < tradingDates.length ? tradingDates[i] : `day-${i}`;\n\n // Build factor lookup for quick access to step arrays\n const factorSteps = new Map<string, number[]>();\n for (const f of result.factors) {\n factorSteps.set(f.factor, f.steps);\n }\n\n // Pivot: for each step, collect contributions from all factors\n const steps: AttributionStepEntry[] = [];\n for (let i = 0; i <= stepCount; i++) {\n const entry: AttributionStepEntry = {\n date: getStepDate(i),\n delta: getStepValue(factorSteps, \"delta\", i, detailed ? 0 : (factorSteps.get(\"charm\")?.[i] ?? 0)),\n gamma: getStepValue(factorSteps, \"gamma\", i, 0),\n theta: getStepValue(factorSteps, \"theta\", i, 0),\n vega: getStepValue(factorSteps, \"vega\", i, detailed ? 0 : (factorSteps.get(\"vanna\")?.[i] ?? 0)),\n residual: getStepValue(factorSteps, \"residual\", i, 0),\n };\n // time_and_vol: present when numerical fallback was used (theta/vega couldn't be separated)\n if (factorSteps.has(\"time_and_vol\")) {\n entry.time_and_vol = getStepValue(factorSteps, \"time_and_vol\", i, 0);\n }\n if (detailed) {\n entry.charm = factorSteps.get(\"charm\")?.[i] ?? 0;\n entry.vanna = factorSteps.get(\"vanna\")?.[i] ?? 0;\n }\n steps.push(entry);\n }\n\n // Compute total attribution for this trade\n const collapsed = collapseFactors(result.factors, detailed);\n const grossAttributionFlow = computeGrossAttributionFlow(collapsed);\n const attribution = computeAttribution(collapsed, actualPnl, grossAttributionFlow);\n const executionEdge = actualPnl - result.totalPnlChange;\n\n const filteredSteps = filterSparseSteps(steps);\n\n return {\n block_id,\n trade_index,\n trade_date: tradeDate,\n total_pnl: Math.round(actualPnl * 100) / 100,\n mark_total_pnl: Math.round(result.totalPnlChange * 100) / 100,\n execution_edge: Math.round(executionEdge * 100) / 100,\n gross_attribution_flow: Math.round(grossAttributionFlow * 100) / 100,\n steps: filteredSteps,\n attribution,\n };\n}\n\n/**\n * Remove steps where all Greek contributions are zero (no market data for that bar).\n * Keeps the output compact and useful.\n */\nexport function filterSparseSteps(steps: AttributionStepEntry[]): AttributionStepEntry[] {\n return steps.filter(s =>\n s.delta !== 0 || s.gamma !== 0 || s.theta !== 0 || s.vega !== 0 ||\n s.residual !== 0 || (s.time_and_vol ?? 0) !== 0 ||\n (s.charm ?? 0) !== 0 || (s.vanna ?? 0) !== 0\n );\n}\n\nfunction getStepValue(\n factorSteps: Map<string, number[]>,\n factor: string,\n index: number,\n collapsedAddition: number,\n): number {\n return Math.round(((factorSteps.get(factor)?.[index] ?? 0) + collapsedAddition) * 100) / 100;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerGreeksAttributionTools(\n server: McpServer,\n baseDir: string,\n stores: MarketStores,\n): void {\n server.registerTool(\n \"get_greeks_attribution\",\n {\n description:\n \"Decompose a block's P&L into Greek components (delta, gamma, theta, vega). \" +\n \"Summary mode: attribution percentages across all trades — reveals what drives the strategy. \" +\n \"Instance mode: per-day Greek P&L time-series for a single trade. \" +\n \"Use skip_quotes=true (default) for fast analysis, false for NBBO-precision.\",\n inputSchema: getGreeksAttributionSchema,\n },\n async (params) => {\n try {\n const result = await handleGetGreeksAttribution(params, baseDir, stores);\n\n const isSummary = !(\"steps\" in result);\n const summary = isSummary\n ? `Block \"${params.block_id}\" attribution (${(result as AttributionSummaryResult).trades_decomposed}/${(result as AttributionSummaryResult).trades_total} trades): ${(result as AttributionSummaryResult).attribution.map(a => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(\", \")}, actual P&L ${(result as AttributionSummaryResult).total_pnl}, execution edge ${(result as AttributionSummaryResult).execution_edge}`\n : `Trade #${(result as AttributionInstanceResult).trade_index} attribution: ${(result as AttributionInstanceResult).attribution.map(a => `${a.factor} ${a.pct_of_gross ?? a.pct}%`).join(\", \")}, actual P&L ${(result as AttributionInstanceResult).total_pnl}, execution edge ${(result as AttributionInstanceResult).execution_edge}`;\n\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error in Greeks attribution: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n}\n","/**\n * flatfile-importer.ts\n *\n * Imports option minute bars from Massive.com S3 flat files into the canonical\n * Market Data 3.0 spot store (`stores.spot.writeBars`).\n *\n * Phase 4 / CONSUMER-02: rewritten to consume `MarketStores`. Every write goes\n * through `stores.spot.writeBars(ticker, date, bars)` so there is exactly one\n * spot-write code path in the system. The legacy dual-mode branch (parquet\n * writer vs raw INSERT) plus the temp-CSV staging that lived here are gone —\n * the spot store handles staging internally via a temp DuckDB table.\n *\n * Pure parsing functions (`nanosToET`, `parseFlatFileLine`, `tradingDays`)\n * remain exported for unit testing — they have no IO dependencies.\n *\n * S3 structure: s3massive:flatfiles/us_options_opra/minute_aggs_v1/{year}/{month}/{date}.csv.gz\n * CSV format: ticker,volume,open,close,high,low,window_start,transactions\n * RTH filter: 09:30 - 16:15 ET\n */\n\nimport { createReadStream, existsSync, mkdirSync, unlinkSync } from \"fs\";\nimport { createInterface } from \"readline\";\nimport { createGunzip } from \"zlib\";\nimport { resolve } from \"path\";\nimport type { MarketStores, BarRow as MarketStoreBarRow } from \"../market/stores/index.ts\";\nimport { getFlatImportLogJson, upsertFlatImportLogJson } from \"../db/json-adapters.ts\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst TMP_DIR = \"/tmp/massive-flat\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ParsedBar {\n ticker: string; // O: prefix stripped\n date: string; // YYYY-MM-DD in ET\n time: string; // HH:MM in ET\n open: number;\n high: number;\n low: number;\n close: number;\n volume: number;\n}\n\nexport interface ImportDayResult {\n date: string;\n imported: number;\n skipped: boolean;\n error?: string;\n}\n\nexport interface ImportFlatFilesResult {\n totalImported: number;\n totalSkipped: number;\n days: ImportDayResult[];\n elapsedMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Pure functions (exported for unit testing)\n// ---------------------------------------------------------------------------\n\n/**\n * Check if a UTC date falls in US Eastern Daylight Time (EDT, UTC-4).\n * EDT runs from 2nd Sunday of March to 1st Sunday of November.\n * We use a fast approximation: March 8-31 through November 1 = EDT.\n */\nfunction isEDT(utcMonth: number, utcDay: number): boolean {\n if (utcMonth > 3 && utcMonth < 11) return true; // Apr-Oct always EDT\n if (utcMonth === 3) return utcDay >= 8; // March: after ~2nd Sunday\n if (utcMonth === 11) return utcDay < 7; // Nov: before ~1st Sunday\n return false; // Dec-Feb always EST\n}\n\n/**\n * Convert a nanosecond timestamp to ET date and HH:MM time.\n *\n * Uses fast manual UTC→ET offset instead of Intl/toLocaleString (~100x faster\n * when called millions of times during flat file parsing).\n */\nexport function nanosToET(nanos: string | number | bigint): { date: string; time: string } {\n const ms = Math.floor(Number(nanos) / 1_000_000);\n // Apply ET offset directly to ms, then extract UTC components (which are now in ET)\n const utcDate = new Date(ms);\n const offsetHours = isEDT(utcDate.getUTCMonth() + 1, utcDate.getUTCDate()) ? 4 : 5;\n const etMs = ms - offsetHours * 3600_000;\n const d = new Date(etMs);\n\n const year = d.getUTCFullYear();\n const month = String(d.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(d.getUTCDate()).padStart(2, \"0\");\n const date = `${year}-${month}-${day}`;\n const time = `${String(d.getUTCHours()).padStart(2, \"0\")}:${String(d.getUTCMinutes()).padStart(2, \"0\")}`;\n return { date, time };\n}\n\n/**\n * Parse a single CSV line from a Massive flat file.\n *\n * Returns null if:\n * - Line doesn't start with the given underlyingPrefix (e.g., \"O:SPX\")\n * - Line has fewer than 8 fields\n * - Bar timestamp is outside RTH (09:30 - 16:15 ET)\n *\n * CSV format: ticker,volume,open,close,high,low,window_start,transactions\n */\nexport function parseFlatFileLine(line: string, underlyingPrefix: string): ParsedBar | null {\n if (!line.startsWith(underlyingPrefix)) return null;\n\n const parts = line.split(\",\");\n\n const rawTicker = parts[0];\n // Index tickers: \"I:VIX\" → \"VIX\", option tickers: \"O:SPXW...\" → \"SPXW...\"\n const ticker = rawTicker.includes(':') ? rawTicker.slice(rawTicker.indexOf(':') + 1) : rawTicker;\n\n let open: number, close: number, high: number, low: number, windowStart: string, volume: number;\n\n if (parts.length >= 8) {\n // Option format: ticker,volume,open,close,high,low,window_start,transactions\n volume = Number(parts[1]);\n open = Number(parts[2]);\n close = Number(parts[3]);\n high = Number(parts[4]);\n low = Number(parts[5]);\n windowStart = parts[6];\n } else if (parts.length >= 6) {\n // Index format: ticker,open,close,high,low,window_start\n volume = 0;\n open = Number(parts[1]);\n close = Number(parts[2]);\n high = Number(parts[3]);\n low = Number(parts[4]);\n windowStart = parts[5];\n } else {\n return null;\n }\n\n const { date, time } = nanosToET(windowStart);\n\n // Filter to RTH only (09:30 - 16:15 ET)\n if (time < \"09:30\" || time > \"16:15\") return null;\n\n return { ticker, date, time, open, high, low, close, volume };\n}\n\n/**\n * Generate weekday (Mon-Fri) dates between from and to (inclusive).\n *\n * Uses UTC noon to avoid any DST/timezone ambiguity when iterating dates.\n */\nexport function tradingDays(from: string, to: string): string[] {\n const days: string[] = [];\n const d = new Date(from + \"T12:00:00Z\");\n const end = new Date(to + \"T12:00:00Z\");\n while (d <= end) {\n const dow = d.getUTCDay();\n if (dow !== 0 && dow !== 6) {\n days.push(d.toISOString().slice(0, 10));\n }\n d.setUTCDate(d.getUTCDate() + 1);\n }\n return days;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helper: write parsed rows through stores.spot.writeBars\n// ---------------------------------------------------------------------------\n\n/**\n * Group `ParsedBar[]` by (ticker, date) and persist via the spot store.\n *\n * Flat files mix tickers under a single underlying (e.g., one O:SPXW file\n * contains hundreds of distinct option contracts). The store contract is\n * one writeBars call per (ticker, date) so we group first, then write\n * serially — DuckDB is single-writer (Pitfall 9).\n */\nasync function writeRowsThroughStore(\n stores: MarketStores,\n rows: ParsedBar[],\n): Promise<void> {\n if (rows.length === 0) return;\n\n const byTickerDate = new Map<string, Map<string, MarketStoreBarRow[]>>();\n for (const r of rows) {\n let byDate = byTickerDate.get(r.ticker);\n if (!byDate) {\n byDate = new Map();\n byTickerDate.set(r.ticker, byDate);\n }\n let bars = byDate.get(r.date);\n if (!bars) {\n bars = [];\n byDate.set(r.date, bars);\n }\n bars.push({\n ticker: r.ticker,\n date: r.date,\n time: r.time,\n open: r.open,\n high: r.high,\n low: r.low,\n close: r.close,\n bid: undefined,\n ask: undefined,\n volume: r.volume,\n });\n }\n\n for (const [ticker, byDate] of byTickerDate) {\n for (const [date, bars] of byDate) {\n await stores.spot.writeBars(ticker, date, bars);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// I/O functions\n// ---------------------------------------------------------------------------\n\n/**\n * Import a single day's flat file via the spot store.\n *\n * Uses the provider's downloadFlatFile() for the download (provider-agnostic),\n * then stream-parses the gzipped CSV, filters to the specified underlying,\n * and persists via `stores.spot.writeBars` grouped by (ticker, date).\n *\n * Skips when `stores.spot.getCoverage(probeTicker, date, date).totalDates > 0`\n * for a representative probe ticker (the underlying itself for index files;\n * the underlying canonical option ticker for option files).\n */\nexport async function importFlatFileDay(\n dateStr: string,\n underlying: string,\n stores: MarketStores,\n assetClass: 'option' | 'index' = 'option',\n): Promise<ImportDayResult> {\n // Index flat files go to a separate tmp path to avoid colliding with option files\n const tmpSubdir = assetClass === 'index' ? '/tmp/massive-flat-index' : TMP_DIR;\n const localPath = resolve(tmpSubdir, `${dateStr}.csv.gz`);\n\n // Skip-check via the spot store. For options the underlying itself rarely\n // has spot bars (those go through a separate index import), so we use\n // store coverage as a best-effort signal — duplicate ingest is idempotent\n // at the store layer (writeBars overwrites the partition).\n if (assetClass === 'index') {\n const cov = await stores.spot.getCoverage(underlying, dateStr, dateStr);\n if (cov.totalDates > 0) {\n return { date: dateStr, imported: 0, skipped: true };\n }\n }\n\n // Download via provider (provider-agnostic)\n if (!existsSync(localPath)) {\n mkdirSync(tmpSubdir, { recursive: true });\n const { getProvider } = await import('./market-provider.ts');\n const provider = getProvider();\n if (provider.downloadFlatFile) {\n const downloaded = await provider.downloadFlatFile(dateStr, assetClass);\n if (!downloaded) {\n return { date: dateStr, imported: 0, skipped: false, error: 'download_failed' };\n }\n } else {\n return { date: dateStr, imported: 0, skipped: false, error: 'provider_no_flat_files' };\n }\n }\n\n if (!existsSync(localPath)) {\n return { date: dateStr, imported: 0, skipped: false, error: \"not_found\" };\n }\n\n // Stream-parse: filter to underlying tickers, convert timestamps, collect rows\n // Index tickers use \"I:\" prefix, option tickers use \"O:\" prefix\n const tickerPrefix = assetClass === 'index' ? `I:${underlying}` : (underlying === \"SPX\" ? \"O:SPX\" : `O:${underlying}`);\n\n const rows: ParsedBar[] = [];\n await new Promise<void>((resolveP, reject) => {\n const gunzip = createGunzip();\n const rl = createInterface({ input: createReadStream(localPath).pipe(gunzip) });\n let isHeader = true;\n\n rl.on(\"line\", (line: string) => {\n if (isHeader) {\n isHeader = false;\n return;\n }\n const parsed = parseFlatFileLine(line, tickerPrefix);\n if (parsed) rows.push(parsed);\n });\n\n rl.on(\"close\", resolveP);\n rl.on(\"error\", reject);\n gunzip.on(\"error\", reject);\n });\n\n // Clean up downloaded file\n try {\n unlinkSync(localPath);\n } catch {\n // best-effort cleanup\n }\n\n if (rows.length === 0) {\n return { date: dateStr, imported: 0, skipped: false };\n }\n\n await writeRowsThroughStore(stores, rows);\n return { date: dateStr, imported: rows.length, skipped: false };\n}\n\n/**\n * Download and parse a flat file for a single day. Does NOT touch the store.\n * Returns parsed rows ready for bulk insert, or an error/skip status.\n */\nasync function downloadAndParse(\n dateStr: string,\n underlying: string,\n assetClass: 'option' | 'index',\n): Promise<{ date: string; rows: ParsedBar[]; skipped?: boolean; error?: string }> {\n const tmpSubdir = assetClass === 'index' ? '/tmp/massive-flat-index' : TMP_DIR;\n const localPath = resolve(tmpSubdir, `${dateStr}.csv.gz`);\n\n // Download via provider\n if (!existsSync(localPath)) {\n mkdirSync(tmpSubdir, { recursive: true });\n const { getProvider } = await import('./market-provider.ts');\n const provider = getProvider();\n if (provider.downloadFlatFile) {\n const downloaded = await provider.downloadFlatFile(dateStr, assetClass);\n if (!downloaded) {\n return { date: dateStr, rows: [], error: 'download_failed' };\n }\n } else {\n return { date: dateStr, rows: [], error: 'provider_no_flat_files' };\n }\n }\n\n if (!existsSync(localPath)) {\n return { date: dateStr, rows: [], error: 'not_found' };\n }\n\n // Stream-parse: filter to underlying tickers, convert timestamps\n const tickerPrefix = assetClass === 'index' ? `I:${underlying}` : (underlying === \"SPX\" ? \"O:SPX\" : `O:${underlying}`);\n const rows: ParsedBar[] = [];\n await new Promise<void>((resolveP, reject) => {\n const gunzip = createGunzip();\n const rl = createInterface({ input: createReadStream(localPath).pipe(gunzip) });\n let isHeader = true;\n rl.on(\"line\", (line: string) => {\n if (isHeader) { isHeader = false; return; }\n const parsed = parseFlatFileLine(line, tickerPrefix);\n if (parsed) rows.push(parsed);\n });\n rl.on(\"close\", resolveP);\n rl.on(\"error\", reject);\n gunzip.on(\"error\", reject);\n });\n\n // Clean up downloaded file\n try { unlinkSync(localPath); } catch { /* best-effort */ }\n\n return { date: dateStr, rows };\n}\n\n/** Default concurrency for parallel download+parse. */\nconst IMPORT_CONCURRENCY = 5;\n\n/**\n * Import flat files for a date range with parallel download+parse.\n *\n * Downloads and parses up to IMPORT_CONCURRENCY days in parallel,\n * then persists via the spot store serially (DuckDB single-writer).\n *\n * Tracks already-imported (date, asset_class, underlying) tuples in the JSON\n * `flat_import_log` so re-runs are no-ops once a window is fully covered.\n */\nexport async function importFlatFiles(\n from: string,\n to: string,\n underlying: string,\n stores: MarketStores,\n dataDir: string,\n assetClass: 'option' | 'index' = 'option',\n): Promise<ImportFlatFilesResult> {\n const tmpDir = assetClass === 'index' ? '/tmp/massive-flat-index' : TMP_DIR;\n mkdirSync(tmpDir, { recursive: true });\n\n const days = tradingDays(from, to);\n const results: ImportDayResult[] = [];\n let totalImported = 0;\n let totalSkipped = 0;\n const t0 = Date.now();\n\n // Track flat file imports in the JSON log — only skip dates that were\n // actually imported from flat files, not dates that happen to have bars\n // from per-ticker API fetches.\n const importedDates = await getFlatImportLogJson(assetClass, underlying, from, to, dataDir);\n\n const daysToImport = days.filter(d => !importedDates.has(d));\n const skippedDays = days.filter(d => importedDates.has(d));\n for (const d of skippedDays) {\n results.push({ date: d, imported: 0, skipped: true });\n totalSkipped++;\n }\n\n if (daysToImport.length === 0) {\n console.log(` [importFlatFiles] all ${days.length} days already imported — nothing to do`);\n } else {\n console.log(` [importFlatFiles] ${daysToImport.length} days to import, ${skippedDays.length} already imported (${assetClass} ${underlying})`);\n }\n\n // Process in batches: parallel download+parse, serial spot-store writes\n for (let i = 0; i < daysToImport.length; i += IMPORT_CONCURRENCY) {\n const batch = daysToImport.slice(i, i + IMPORT_CONCURRENCY);\n const batchNum = Math.floor(i / IMPORT_CONCURRENCY) + 1;\n const totalBatches = Math.ceil(daysToImport.length / IMPORT_CONCURRENCY);\n console.log(` [importFlatFiles] batch ${batchNum}/${totalBatches}: ${batch[0]}..${batch[batch.length - 1]} (${Math.round((Date.now() - t0) / 1000)}s)`);\n\n // Parallel download + parse\n const parsed = await Promise.all(\n batch.map(day => downloadAndParse(day, underlying, assetClass))\n );\n\n // Serial writes through the spot store (DuckDB single-writer)\n for (const p of parsed) {\n if (p.error) {\n results.push({ date: p.date, imported: 0, skipped: false, error: p.error });\n continue;\n }\n if (p.rows.length === 0) {\n results.push({ date: p.date, imported: 0, skipped: false });\n continue;\n }\n await writeRowsThroughStore(stores, p.rows);\n results.push({ date: p.date, imported: p.rows.length, skipped: false });\n totalImported += p.rows.length;\n // Record successful import in JSON metadata log\n try {\n await upsertFlatImportLogJson({\n date: p.date,\n asset_class: assetClass,\n underlying,\n imported_at: new Date().toISOString(),\n bar_count: p.rows.length,\n }, dataDir);\n } catch { /* best-effort metadata tracking */ }\n }\n }\n console.log(` [importFlatFiles] done: ${totalImported} bars imported, ${totalSkipped} days skipped (${Math.round((Date.now() - t0) / 1000)}s)`);\n\n return {\n totalImported,\n totalSkipped,\n days: results,\n elapsedMs: Date.now() - t0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// importIndexBars — multi-ticker index import (1 download per day)\n// ---------------------------------------------------------------------------\n\n/**\n * Download and parse multiple index tickers from a single flat file.\n * Downloads once, extracts all matching tickers in one pass.\n */\nasync function downloadAndParseMulti(\n dateStr: string,\n tickers: string[],\n): Promise<{ date: string; rows: ParsedBar[]; error?: string }> {\n const tmpDir = '/tmp/massive-flat-index';\n const localPath = resolve(tmpDir, `${dateStr}.csv.gz`);\n\n if (!existsSync(localPath)) {\n mkdirSync(tmpDir, { recursive: true });\n const { getProvider } = await import('./market-provider.ts');\n const provider = getProvider();\n if (provider.downloadFlatFile) {\n const downloaded = await provider.downloadFlatFile(dateStr, 'index');\n if (!downloaded) return { date: dateStr, rows: [], error: 'download_failed' };\n } else {\n return { date: dateStr, rows: [], error: 'provider_no_flat_files' };\n }\n }\n\n if (!existsSync(localPath)) return { date: dateStr, rows: [], error: 'not_found' };\n\n // Build prefix set for fast matching: \"I:VIX,\", \"I:VIX9D,\", \"I:SPX,\"\n const prefixes = tickers.map(t => `I:${t},`);\n\n const rows: ParsedBar[] = [];\n await new Promise<void>((resolveP, reject) => {\n const gunzip = createGunzip();\n const rl = createInterface({ input: createReadStream(localPath).pipe(gunzip) });\n let isHeader = true;\n rl.on(\"line\", (line: string) => {\n if (isHeader) { isHeader = false; return; }\n for (const prefix of prefixes) {\n if (line.startsWith(prefix)) {\n const parsed = parseFlatFileLine(line, prefix.slice(0, -1)); // strip trailing comma\n if (parsed) rows.push(parsed);\n break;\n }\n }\n });\n rl.on(\"close\", resolveP);\n rl.on(\"error\", reject);\n gunzip.on(\"error\", reject);\n });\n\n try { unlinkSync(localPath); } catch { /* best-effort */ }\n return { date: dateStr, rows };\n}\n\n/** Concurrency for index imports. */\nconst INDEX_CONCURRENCY = 8;\n\n/**\n * Import multiple index tickers from flat files in a date range.\n * Downloads each day's file ONCE and extracts all tickers in a single parse pass.\n * Much faster than calling importFlatFiles per ticker.\n */\nexport async function importIndexBars(\n from: string,\n to: string,\n tickers: string[],\n stores: MarketStores,\n): Promise<ImportFlatFilesResult> {\n mkdirSync('/tmp/massive-flat-index', { recursive: true });\n\n const days = tradingDays(from, to);\n const results: ImportDayResult[] = [];\n let totalImported = 0;\n let totalSkipped = 0;\n const t0 = Date.now();\n\n // Per-day skip check via the spot store: skip a day only when EVERY requested\n // ticker already has coverage on that date (matches the legacy intersection\n // semantics).\n const skipDays = new Set<string>();\n if (tickers.length > 0) {\n const perTickerDates = await Promise.all(\n tickers.map(async (ticker) => {\n const cov = await stores.spot.getCoverage(ticker, from, to);\n if (cov.totalDates === 0 || !cov.earliest || !cov.latest) {\n return new Set<string>();\n }\n // listPartitionValues underneath getCoverage already returns the\n // exact dates with data; we approximate the \"trading days covered\"\n // set by enumerating the inclusive range bounded by earliest/latest.\n // For per-day skip we only need a contains check, not a precise\n // missing-date list.\n const dates = new Set<string>();\n const start = new Date(cov.earliest + 'T00:00:00Z');\n const end = new Date(cov.latest + 'T00:00:00Z');\n const cur = new Date(start);\n while (cur <= end) {\n dates.add(cur.toISOString().slice(0, 10));\n cur.setUTCDate(cur.getUTCDate() + 1);\n }\n return dates;\n }),\n );\n if (perTickerDates.length > 0) {\n // Intersect across tickers\n const first = perTickerDates[0];\n for (const d of first) {\n let allHave = true;\n for (let i = 1; i < perTickerDates.length; i++) {\n if (!perTickerDates[i].has(d)) {\n allHave = false;\n break;\n }\n }\n if (allHave) skipDays.add(d);\n }\n }\n }\n\n const daysToImport = days.filter(d => !skipDays.has(d));\n for (const d of days.filter(d => skipDays.has(d))) {\n results.push({ date: d, imported: 0, skipped: true });\n totalSkipped++;\n }\n\n if (daysToImport.length === 0) {\n console.log(` [importIndexBars] all ${days.length} days have data for ${tickers.join(',')} — nothing to import`);\n } else {\n console.log(` [importIndexBars] ${daysToImport.length} days to import for ${tickers.join(',')}, ${skipDays.size} skipped`);\n }\n\n // Parallel download+parse, serial store writes\n for (let i = 0; i < daysToImport.length; i += INDEX_CONCURRENCY) {\n const batch = daysToImport.slice(i, i + INDEX_CONCURRENCY);\n const batchNum = Math.floor(i / INDEX_CONCURRENCY) + 1;\n const totalBatches = Math.ceil(daysToImport.length / INDEX_CONCURRENCY);\n console.log(` [importIndexBars] batch ${batchNum}/${totalBatches}: ${batch[0]}..${batch[batch.length - 1]} (${Math.round((Date.now() - t0) / 1000)}s)`);\n\n const parsed = await Promise.all(\n batch.map(day => downloadAndParseMulti(day, tickers))\n );\n\n for (const p of parsed) {\n if (p.error) {\n results.push({ date: p.date, imported: 0, skipped: false, error: p.error });\n continue;\n }\n if (p.rows.length === 0) {\n results.push({ date: p.date, imported: 0, skipped: false });\n continue;\n }\n await writeRowsThroughStore(stores, p.rows);\n results.push({ date: p.date, imported: p.rows.length, skipped: false });\n totalImported += p.rows.length;\n }\n }\n console.log(` [importIndexBars] done: ${totalImported} bars imported, ${totalSkipped} days skipped (${Math.round((Date.now() - t0) / 1000)}s)`);\n\n return {\n totalImported,\n totalSkipped,\n days: results,\n elapsedMs: Date.now() - t0,\n };\n}\n","/**\n * SpotStore — Abstract base for spot (intraday + daily) bar storage.\n *\n * Phase 1: Signatures only. Phase 2: ParquetSpotStore / DuckdbSpotStore implement these.\n *\n * The `abstract` keyword enforces at compile time that every subclass provides\n * an implementation of all four methods (STORE-05 contract).\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport type { StoreContext, BarRow, CoverageReport } from \"./types.ts\";\nimport { resolveMarketDir } from \"../../db/market-datasets.ts\";\nimport { listPartitionValues } from \"./coverage.ts\";\n\nfunction escapeSqlLiteral(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nexport abstract class SpotStore {\n protected readonly ctx: StoreContext;\n constructor(ctx: StoreContext) {\n this.ctx = ctx;\n }\n\n /**\n * Return `read_parquet([...])` SQL over exact `ticker=X/date=Y/data.parquet`\n * files for a (ticker, from..to) range, or null if no files exist on disk.\n * Used by concrete stores to bypass the `market.spot` view's glob walk.\n */\n protected buildDirectParquetReadBarsSQL(\n ticker: string,\n from: string,\n to: string,\n opts?: { rthOnly?: boolean; dailyAgg?: boolean },\n ): { sql: string } | null {\n const tickerDir = path.join(resolveMarketDir(this.ctx.dataDir), \"spot\", `ticker=${ticker}`);\n if (!existsSync(tickerDir)) return null;\n const allDates = listPartitionValues(tickerDir, \"date\");\n const dates = allDates.filter((d) => d >= from && d <= to);\n if (dates.length === 0) return null;\n const paths: string[] = [];\n for (const d of dates) {\n const p = path.join(tickerDir, `date=${d}`, \"data.parquet\");\n if (existsSync(p)) paths.push(p);\n }\n if (paths.length === 0) return null;\n const fileList = paths.map(p => `'${escapeSqlLiteral(p)}'`).join(\", \");\n const tickerLit = `'${escapeSqlLiteral(ticker)}'`;\n if (opts?.dailyAgg) {\n return {\n sql: `SELECT ${tickerLit} AS ticker, date,\n first(open ORDER BY time) AS open,\n max(high) AS high,\n min(low) AS low,\n last(close ORDER BY time) AS close,\n first(bid ORDER BY time) AS bid,\n last(ask ORDER BY time) AS ask\n FROM read_parquet([${fileList}], hive_partitioning=true)\n WHERE time >= '09:30' AND time <= '16:00'\n -- Defense-in-depth: drop minute bars with zero/null OHLC\n -- before aggregating. Mirrors the same guard on the public\n -- market.spot_daily view (db/market-views.ts). Without this,\n -- a bad-data minute (close=0 or low=0) collapses the daily\n -- aggregate's min(low) to 0, which propagates into every\n -- enriched indicator that uses (high - low) as range.\n AND open IS NOT NULL AND open > 0\n AND high IS NOT NULL AND high > 0\n AND low IS NOT NULL AND low > 0\n AND close IS NOT NULL AND close > 0\n GROUP BY date\n ORDER BY date`,\n };\n }\n const rthClause = opts?.rthOnly ? \"AND time >= '09:30' AND time <= '16:00'\" : \"\";\n return {\n sql: `SELECT ${tickerLit} AS ticker, date, time, open, high, low, close, bid, ask\n FROM read_parquet([${fileList}], hive_partitioning=true)\n WHERE 1=1 ${rthClause}\n ORDER BY date, time`,\n };\n }\n\n /**\n * Public accessor for the data directory root (WR-03).\n *\n * Pipeline-side helpers (e.g., `executeFetchPlan`) need the absolute base\n * directory when no explicit `baseDir` is supplied — the flat-import-log\n * JSON adapter writes its dedupe ledger under `{dataDir}/market/.flat-import-log/`.\n * Exposing this through a public getter beats reaching into `store[\"ctx\"]`\n * via bracket notation, which silently bypasses TypeScript's `protected`\n * modifier and creates a hidden coupling to the internal field name.\n */\n public get dataDir(): string {\n return this.ctx.dataDir;\n }\n\n abstract writeBars(ticker: string, date: string, bars: BarRow[]): Promise<void>;\n\n /**\n * Write bars for a single (ticker, date) partition from a user-supplied SELECT.\n *\n * The SELECT must produce columns matching `market.spot`\n * (ticker, date, time, open, high, low, close, bid, ask). Rows are expected\n * to belong to the single partition named in `partition` — the caller is\n * responsible for filtering upstream; mixed partitions are not rejected\n * but will be written to the named partition's location (Parquet) or the\n * single table (DuckDB).\n *\n * Parquet mode: `COPY (select) TO spot/ticker=X/date=Y/data.parquet` via\n * the shared staging-table helper.\n *\n * DuckDB mode: `INSERT OR REPLACE INTO market.spot (cols...) <select>`.\n */\n abstract writeFromSelect(\n partition: { ticker: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }>;\n\n abstract readBars(ticker: string, from: string, to: string): Promise<BarRow[]>;\n abstract readDailyBars(ticker: string, from: string, to: string): Promise<BarRow[]>;\n abstract getCoverage(ticker: string, from: string, to: string): Promise<CoverageReport>;\n}\n","/**\n * Shared partition enumerator (Market Data 3.0 — Phase 2 Wave 1).\n *\n * Given a directory containing Hive-partition subdirectories shaped\n * `{partitionKey}=VALUE/`, return the sorted list of VALUEs for which a\n * `data.parquet` file exists. Used by every Parquet-mode store's\n * `getCoverage()` implementation (spot, enriched, chain, quote).\n *\n * Generalized from `src/db/market-views.ts::hasParquetPartitions` (PATTERNS.md\n * \"coverage.ts\"). Accepts the partition key as a parameter so both\n * `ticker=...` and `underlying=...` layouts are covered by one helper.\n *\n * Purity: synchronous, no mutation, no exceptions on missing directories —\n * returns `[]` when the directory is absent or unreadable.\n */\nimport { existsSync, readdirSync } from \"fs\";\nimport * as path from \"path\";\n\n/**\n * Enumerate Hive-partition values present under `dir`. Each matching\n * subdirectory must be shaped `{partitionKey}=VALUE/` and must contain\n * `data.parquet` to be counted.\n *\n * @param dir Partition root (e.g. `/data/market/spot/ticker=SPX`)\n * @param partitionKey Partition key name (e.g. `\"date\"`)\n * @returns Sorted array of VALUEs (empty on missing dir / IO error)\n *\n * @example\n * listPartitionValues(\"/data/market/spot/ticker=SPX\", \"date\")\n * // → [\"2025-01-06\", \"2025-01-07\", ...]\n */\nexport function listPartitionValues(dir: string, partitionKey: string): string[] {\n if (!existsSync(dir)) return [];\n try {\n const prefix = `${partitionKey}=`;\n return readdirSync(dir)\n .filter((entry) => entry.startsWith(prefix))\n .filter((entry) => existsSync(path.join(dir, entry, \"data.parquet\")))\n .map((entry) => entry.slice(prefix.length))\n .sort();\n } catch {\n return [];\n }\n}\n","/**\n * EnrichedStore — Abstract base for computed/derived market fields.\n *\n * Phase 1: Signatures only.\n *\n * Two compute entry points:\n * - `compute(ticker, from, to)` — per-ticker enriched derivations\n * (indicators, vol regimes, opening-drive metrics, etc.)\n * - `computeContext(from, to)` — cross-ticker context (VIX family,\n * term structure, realized-vol aggregates) that doesn't belong to any\n * single ticker's enriched output.\n *\n * `read(opts)` composes enriched + (optional) context + (optional) OHLCV.\n */\nimport type { StoreContext, CoverageReport } from \"./types.ts\";\n\nexport interface EnrichedReadOpts {\n ticker: string;\n from: string;\n to: string;\n includeContext?: boolean; // join enriched_context (VIX family cross-ticker fields)\n includeOhlcv?: boolean; // join spot daily for OHLCV (avoids double-storing OHLCV)\n}\n\nexport abstract class EnrichedStore {\n protected readonly ctx: StoreContext;\n constructor(ctx: StoreContext) {\n this.ctx = ctx;\n }\n\n abstract compute(ticker: string, from: string, to: string): Promise<void>;\n abstract computeContext(from: string, to: string): Promise<void>;\n abstract read(opts: EnrichedReadOpts): Promise<Record<string, unknown>[]>;\n abstract getCoverage(ticker: string): Promise<CoverageReport>;\n}\n","/**\n * Pure SQL builder for ChainStore reads.\n *\n * Option chains are partitioned by (underlying, date). A single `readChain`\n * call targets exactly one partition. Values are inlined as SQL literals\n * because `runAndReadAll(sql, params)` leaks C++ handles via DuckDB's\n * `extract_statements` path (see `spot-sql.ts` header for the full writeup).\n *\n * Purity contract: no `this`, no `ctx`, no DuckDB value-level imports. Tests\n * in `tests/unit/market/stores/chain-sql.test.ts`.\n */\nimport { escapeSqlLiteral } from \"../../utils/quote-parquet-projection.ts\";\nimport type { BuiltSQL } from \"./spot-sql.ts\";\n\nfunction lit(value: string): string {\n return `'${escapeSqlLiteral(value)}'`;\n}\n\n/**\n * Build the `SELECT ... FROM market.option_chain` SQL for a single underlying +\n * date partition. Results are ordered by `ticker` so consumer iteration is\n * deterministic across backends.\n */\nexport function buildReadChainSQL(\n underlying: string,\n date: string,\n): BuiltSQL {\n return {\n sql: `SELECT underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style\n FROM market.option_chain\n WHERE underlying = ${lit(underlying)} AND date = ${lit(date)}\n ORDER BY ticker`,\n };\n}\n\n/**\n * Build a bulk read for N dates under the same underlying via `date IN (...)`.\n *\n * DuckDB's `market.option_chain` view glob-expands `option_chain/**\\/*.parquet`\n * on every call — a ~430ms fixed cost even for a single-partition read. Issuing\n * one IN-list query instead of N per-date queries collapses that overhead.\n * Measured: 12 per-date reads = ~5.2s, one IN(12) read = ~0.43s (12x speedup).\n *\n * Throws when `dates` is empty (prevents `IN ()` which DuckDB rejects).\n */\nexport function buildReadChainDatesSQL(\n underlying: string,\n dates: string[],\n): BuiltSQL {\n if (dates.length === 0) {\n throw new Error(\"buildReadChainDatesSQL: dates must not be empty\");\n }\n const dateList = dates.map(lit).join(\", \");\n return {\n sql: `SELECT underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style\n FROM market.option_chain\n WHERE underlying = ${lit(underlying)} AND date IN (${dateList})\n ORDER BY date, ticker`,\n };\n}\n","/**\n * ChainStore — Abstract base for option chain snapshot storage.\n *\n * Phase 1: Signatures only.\n *\n * Option chains are partitioned by (underlying, date). `readChain(underlying, date)`\n * returns all contracts observed for that underlying on that trading date.\n */\nimport type { StoreContext, ContractRow, CoverageReport } from \"./types.ts\";\nimport { buildReadChainDatesSQL } from \"./chain-sql.ts\";\n\nexport abstract class ChainStore {\n protected readonly ctx: StoreContext;\n constructor(ctx: StoreContext) {\n this.ctx = ctx;\n }\n\n abstract writeChain(underlying: string, date: string, rows: ContractRow[]): Promise<void>;\n\n /**\n * Write chain rows for a single (underlying, date) partition from a user-supplied SELECT.\n *\n * The SELECT must produce columns matching `market.option_chain`\n * (underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style).\n * Single-partition semantics mirror `SpotStore.writeFromSelect`.\n */\n abstract writeFromSelect(\n partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }>;\n\n abstract readChain(underlying: string, date: string): Promise<ContractRow[]>;\n\n /**\n * Cheap chain-existence probe used by entry-pipeline snapshot reads. Returns\n * `true` when the (underlying, date) chain partition has at least one\n * contract; otherwise `false`. Lets the resolver skip a date without paying\n * the ~342ms / 39K-row cost of a full `readChain` call when only the empty\n * check matters.\n */\n async hasChain(underlying: string, date: string): Promise<boolean> {\n // Inline literals — bound-param path leaks extract_statements handles\n // (see chain-sql.ts / spot-sql.ts headers).\n const underlyingLit = underlying.replace(/'/g, \"''\");\n const dateLit = date.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT 1 FROM market.option_chain\n WHERE underlying = '${underlyingLit}' AND date = '${dateLit}'\n LIMIT 1`,\n );\n return reader.getRows().length > 0;\n }\n\n /**\n * Bulk read chains for N dates under a single underlying. Returns a flat list;\n * the caller groups by `date`. Both backends share the same SQL path since\n * `market.option_chain` resolves to either a Parquet view or a physical table\n * with identical columns. Use this instead of N per-date `readChain` calls —\n * per-call glob-expansion / planning overhead dominates for view reads.\n */\n async readChainDates(\n underlying: string,\n dates: string[],\n ): Promise<ContractRow[]> {\n if (dates.length === 0) return [];\n // Builder inlines values; unbound runAndReadAll(sql) bypasses extract_statements.\n const { sql } = buildReadChainDatesSQL(underlying, dates);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n underlying: String(r[0]),\n date: String(r[1]),\n ticker: String(r[2]),\n contract_type: String(r[3]) as ContractRow[\"contract_type\"],\n strike: Number(r[4]),\n expiration: String(r[5]),\n dte: Number(r[6]),\n exercise_style: String(r[7]),\n }));\n }\n\n abstract getCoverage(underlying: string, from: string, to: string): Promise<CoverageReport>;\n}\n","/**\n * QuoteStore — Abstract base for option minute-quote storage.\n *\n * Phase 1 shipped a single-ticker placeholder signature. Plan 02-03 Task 1\n * replaces it in-place with the multi-ticker grouped-series shape that\n * matches the primary consumer pattern: bulk\n * `ticker IN (...) AND date BETWEEN ...` → group-by-ticker. Per CONTEXT.md\n * D-06 / D-08 the signature swap is safe because no Phase 4 consumer has\n * migrated onto the Phase 1 placeholder.\n *\n * All OCC tickers in a single `readQuotes` batch MUST resolve to the same\n * underlying (D-07). Concrete subclasses validate this before issuing SQL and\n * throw clearly when a mixed batch arrives (first-iteration behavior — may\n * relax to transparent grouping if a real Phase 4 consumer needs it).\n *\n * Concrete subclasses (ParquetQuoteStore, DuckdbQuoteStore) ship in Plan\n * 02-03 Task 3.\n */\nimport type {\n StoreContext,\n QuoteRow,\n CoverageReport,\n ReadWindowParams,\n WindowQuoteRow,\n} from \"./types.ts\";\nimport { extractRoot } from \"../tickers/resolver.ts\";\n\nexport abstract class QuoteStore {\n protected readonly ctx: StoreContext;\n constructor(ctx: StoreContext) {\n this.ctx = ctx;\n }\n\n /**\n * Public accessor for the underlying TickerRegistry (WR-03).\n *\n * Several pipeline-side helpers need to resolve OCC roots → underlyings\n * BEFORE they can group calls per-underlying (Pitfall 4 — readQuotes /\n * writeQuotes both enforce single-underlying batches). Exposing the\n * registry through a public getter beats reaching into `store[\"ctx\"]`\n * via bracket notation, which silently bypasses TypeScript's `protected`\n * modifier and creates a hidden coupling to the internal field name.\n */\n public get tickers() {\n return this.ctx.tickers;\n }\n\n abstract writeQuotes(\n underlying: string,\n date: string,\n quotes: QuoteRow[],\n ): Promise<void>;\n\n /**\n * Write quotes for a single (underlying, date) partition from a user-supplied SELECT.\n *\n * The SELECT must produce columns matching `market.option_quote_minutes`\n * (underlying, date, ticker, time, bid, ask, mid, last_updated_ns, source,\n * delta, gamma, theta, vega, iv, greeks_source, greeks_revision).\n * Single-partition semantics mirror `SpotStore.writeFromSelect`.\n */\n abstract writeFromSelect(\n partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }>;\n\n /**\n * Read quotes for a batch of OCC tickers over a date range.\n *\n * All tickers MUST resolve to the same underlying via\n * `extractRoot(...)` + `ctx.tickers.resolve(...)` (validated by the concrete\n * implementation per D-07). Returns a Map keyed by OCC ticker; values are\n * timestamp-sorted arrays of QuoteRow for that contract across the range.\n */\n abstract readQuotes(\n occTickers: string[],\n from: string,\n to: string,\n ): Promise<Map<string, QuoteRow[]>>;\n\n /**\n * Group a `(date -> OCC tickers)` request map by resolved underlying.\n *\n * `readQuotes(...)` and `writeQuotes(...)` both enforce single-underlying\n * batches, so multi-date callers need this shared bucketing before they can\n * fan out to backend-specific bulk paths.\n */\n protected groupTickersByUnderlying(\n tickersByDate: Map<string, Set<string>>,\n ): Map<string, Map<string, Set<string>>> {\n const byUnderlying = new Map<string, Map<string, Set<string>>>();\n for (const [date, tickers] of tickersByDate) {\n for (const ticker of tickers) {\n const underlying = this.ctx.tickers.resolve(extractRoot(ticker));\n let perDate = byUnderlying.get(underlying);\n if (!perDate) {\n perDate = new Map();\n byUnderlying.set(underlying, perDate);\n }\n let dateTickers = perDate.get(date);\n if (!dateTickers) {\n dateTickers = new Set<string>();\n perDate.set(date, dateTickers);\n }\n dateTickers.add(ticker);\n }\n }\n return byUnderlying;\n }\n\n /**\n * Bulk-read quotes for N (date, tickers) pairs across N dates, with a\n * caller-supplied time window pushed into the query.\n *\n * The base implementation is a backend-respecting fallback that fans out to\n * per-date `readQuotes(...)` calls. Concrete backends can override it with a\n * more efficient bulk query shape without changing the public contract.\n *\n * `tickersByDate` may list the same ticker on multiple dates (e.g. a 3-DTE\n * option appearing across a Mon/Tue/Wed window). Returns a Map keyed by\n * OCC ticker whose values contain quotes for that ticker across every date\n * in which it was requested; callers filter by (ticker, date) against the\n * input map if they need date-specific isolation.\n */\n async readQuotesBulk(\n tickersByDate: Map<string, Set<string>>,\n timeStart: string,\n timeEnd: string,\n ): Promise<Map<string, QuoteRow[]>> {\n const out = new Map<string, QuoteRow[]>();\n if (tickersByDate.size === 0) return out;\n\n for (const [, perDate] of this.groupTickersByUnderlying(tickersByDate)) {\n for (const [date, occs] of perDate) {\n if (occs.size === 0) continue;\n const quotesByOcc = await this.readQuotes([...occs], date, date);\n for (const [occ, quotes] of quotesByOcc) {\n let arr = out.get(occ);\n if (!arr) {\n arr = [];\n out.set(occ, arr);\n }\n for (const quote of quotes) {\n const spaceIdx = quote.timestamp.indexOf(\" \");\n const time = spaceIdx === -1 ? \"\" : quote.timestamp.slice(spaceIdx + 1);\n if (time < timeStart || time > timeEnd) continue;\n arr.push(quote);\n }\n }\n }\n }\n return out;\n }\n\n abstract getCoverage(\n underlying: string,\n from: string,\n to: string,\n ): Promise<CoverageReport>;\n\n /**\n * Read every option-quote row in the leg-envelope union over a time window.\n * Returns rows joined back to chain metadata (contract_type, strike, expiration,\n * dte) so the caller doesn't OCC-parse. Greeks columns project as-is from the\n * quote table.\n *\n * Per P1: this is the single read primitive. Ranking + top-N selection happen\n * in JS at the call site. No SQL ranking CTE.\n */\n abstract readWindow(params: ReadWindowParams): Promise<WindowQuoteRow[]>;\n}\n","/**\n * ParquetSpotStore — spot minute bars persisted as ticker-first Hive-partitioned\n * Parquet files (spot/ticker=X/date=Y/data.parquet).\n *\n * Writes flow through `writeSpotPartition` (Phase 1 typed helper); reads use\n * the shared SQL builders from `./spot-sql.ts` against the `market.spot`\n * view that `createMarketParquetViews` registers when partitions exist.\n * Coverage uses filesystem enumeration via `listPartitionValues` (D-26).\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode` — the factory\n * chooses the backend once at construction and every method is monomorphic.\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport { SpotStore } from \"./spot-store.ts\";\nimport type { BarRow, CoverageReport } from \"./types.ts\";\nimport { buildReadBarsSQL, buildReadDailyBarsSQL } from \"./spot-sql.ts\";\nimport { listPartitionValues } from \"./coverage.ts\";\nimport {\n resolveMarketDir,\n writeSpotPartition,\n} from \"../../db/market-datasets.ts\";\n\nexport class ParquetSpotStore extends SpotStore {\n async writeBars(\n ticker: string,\n date: string,\n bars: BarRow[],\n ): Promise<void> {\n if (bars.length === 0) return;\n\n // Defense-in-depth write-side filter (per-bar).\n //\n // Reject any bar whose OHLC contains a zero or non-finite price. These\n // come from provider outages, holiday responses, or partial sessions and\n // poison every downstream aggregate that touches them: market.spot_daily\n // (min(low) → 0), enriched indicators (RSI/ATR/EMA gradient blowups),\n // and Prior_Range_vs_ATR (Intraday_Range_Pct → ~100% when low=0). The\n // earlier guard rejected only ALL-zero batches, but real-world bad data\n // tends to be partial — a few zero rows mixed into an otherwise valid\n // session. We filter those rows here so the staging table never sees them.\n //\n // Weekend dates (Sat/Sun) carry no real market activity; if the provider\n // returns rows for them, they're junk regardless of price values. Reject\n // the entire write rather than persisting a partition that downstream\n // logic will never use legitimately.\n const weekday = new Date(`${date}T00:00:00Z`).getUTCDay();\n if (weekday === 0 || weekday === 6) {\n console.warn(\n `ParquetSpotStore.writeBars: skipping weekend write ` +\n `(ticker=${ticker} date=${date} rows=${bars.length})`,\n );\n return;\n }\n\n const cleanBars = bars.filter(\n (b) =>\n Number.isFinite(b.open) && b.open > 0 &&\n Number.isFinite(b.high) && b.high > 0 &&\n Number.isFinite(b.low) && b.low > 0 &&\n Number.isFinite(b.close)&& b.close> 0,\n );\n const dropped = bars.length - cleanBars.length;\n if (dropped > 0) {\n console.warn(\n `ParquetSpotStore.writeBars: dropped ${dropped}/${bars.length} bars ` +\n `with zero/null/non-finite prices (ticker=${ticker} date=${date})`,\n );\n }\n if (cleanBars.length === 0) {\n console.warn(\n `ParquetSpotStore.writeBars: skipping write — all ${bars.length} bars ` +\n `filtered (ticker=${ticker} date=${date})`,\n );\n return;\n }\n bars = cleanBars;\n const staging = `_spot_write_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n await this.ctx.conn.run(\n `CREATE TEMP TABLE \"${staging}\" (\n ticker VARCHAR, date VARCHAR, time VARCHAR,\n open DOUBLE, high DOUBLE, low DOUBLE, close DOUBLE,\n bid DOUBLE, ask DOUBLE\n )`,\n );\n try {\n const placeholders = bars\n .map((_, i) => {\n const b = i * 9;\n return `($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6},$${b + 7},$${b + 8},$${b + 9})`;\n })\n .join(\", \");\n const params: unknown[] = bars.flatMap((b) => [\n ticker,\n date,\n b.time ?? \"09:30\",\n b.open,\n b.high,\n b.low,\n b.close,\n b.bid ?? null,\n b.ask ?? null,\n ]);\n await this.ctx.conn.run(\n `INSERT INTO \"${staging}\" VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n await writeSpotPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n ticker,\n date,\n selectQuery: `SELECT * FROM \"${staging}\"`,\n });\n } finally {\n try {\n await this.ctx.conn.run(`DROP TABLE IF EXISTS \"${staging}\"`);\n } catch {\n /* best-effort */\n }\n }\n }\n\n async writeFromSelect(\n partition: { ticker: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n return writeSpotPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n ticker: partition.ticker,\n date: partition.date,\n selectQuery: selectSql,\n });\n }\n\n async readBars(\n ticker: string,\n from: string,\n to: string,\n ): Promise<BarRow[]> {\n const direct = this.buildDirectParquetReadBarsSQL(ticker, from, to);\n // Both paths inline values — bound-param runAndReadAll(sql, values) leaks\n // extract_statements handles (parquet-quote-store.ts:327, spot-sql.ts).\n const { sql } = direct ?? buildReadBarsSQL(ticker, from, to);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n date: String(r[1]),\n time: String(r[2]),\n open: Number(r[3]),\n high: Number(r[4]),\n low: Number(r[5]),\n close: Number(r[6]),\n bid: r[7] == null ? undefined : Number(r[7]),\n ask: r[8] == null ? undefined : Number(r[8]),\n volume: 0,\n }));\n }\n\n async readDailyBars(\n ticker: string,\n from: string,\n to: string,\n ): Promise<BarRow[]> {\n const direct = this.buildDirectParquetReadBarsSQL(ticker, from, to, { dailyAgg: true });\n // Same leak rationale as readBars — both paths run via unbound query().\n const { sql } = direct ?? buildReadDailyBarsSQL(ticker, from, to);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n date: String(r[1]),\n time: \"09:30\",\n open: Number(r[2]),\n high: Number(r[3]),\n low: Number(r[4]),\n close: Number(r[5]),\n bid: r[6] == null ? undefined : Number(r[6]),\n ask: r[7] == null ? undefined : Number(r[7]),\n volume: 0,\n }));\n }\n\n async getCoverage(\n ticker: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n const tickerDir = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"spot\",\n `ticker=${ticker}`,\n );\n if (!existsSync(tickerDir)) {\n return { earliest: null, latest: null, missingDates: [], totalDates: 0 };\n }\n const allDates = listPartitionValues(tickerDir, \"date\");\n const dates = allDates.filter((d) => d >= from && d <= to);\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * Pure SQL builders for SpotStore reads.\n *\n * Every export is a pure function: given primitive inputs, it returns\n * `{ sql }` where the SQL string already has every partition-selector value\n * inlined as a SQL literal. Callers MUST invoke `runAndReadAll(sql)` with no\n * second argument.\n *\n * Why no positional parameters: the DuckDB Node-API binding routes\n * `runAndReadAll(sql, values)` through `node_bindings.extract_statements`,\n * which allocates a C++ handle with no JS-side destroy method (the wrapper\n * `DuckDBExtractedStatements` only has a constructor — see\n * `node_modules/@duckdb/node-api/lib/DuckDBExtractedStatements.js`). Handles\n * release only on JS GC, so under sustained read load the driver eventually\n * throws `Failed to execute prepared statement`. Inlining values into the SQL\n * string sends the call through `node_bindings.query()` instead, which is\n * leak-free. See `parquet-quote-store.ts:340` for the full root-cause writeup.\n *\n * Purity contract:\n * - No `this` / no `ctx` / no DB-connection value-level import\n * - No side effects; no IO\n * - Composable — concrete stores feed the result to `conn.run()`\n *\n * Security note: user-controlled values (ticker, from, to) are\n * single-quote-escaped via `escapeSqlLiteral` before interpolation. Inputs\n * arrive from typed config / partition-resolved registries (no untrusted\n * free-text), and the escape closes the residual injection vector.\n */\n\nimport { escapeSqlLiteral } from \"../../utils/quote-parquet-projection.ts\";\n\n/**\n * Shape returned by every SQL builder. Just the SQL text — values are inlined\n * as SQL literals (see file header for the why).\n */\nexport interface BuiltSQL {\n sql: string;\n}\n\nfunction lit(value: string): string {\n return `'${escapeSqlLiteral(value)}'`;\n}\n\n/**\n * Read raw minute bars from `market.spot` for a ticker over a date range.\n * Results are ordered by (date, time) so callers receive a deterministic stream.\n */\nexport function buildReadBarsSQL(\n ticker: string,\n from: string,\n to: string,\n): BuiltSQL {\n return {\n sql: `SELECT ticker, date, time, open, high, low, close, bid, ask\n FROM market.spot\n WHERE ticker = ${lit(ticker)} AND date >= ${lit(from)} AND date <= ${lit(to)}\n ORDER BY date, time`,\n };\n}\n\n/**\n * Aggregate minute bars in `market.spot` into RTH daily OHLCV rows.\n *\n * Uses DuckDB aggregate `first(col ORDER BY time)` / `last(col ORDER BY time)`\n * idioms. Window-function equivalents are explicitly avoided — they do NOT\n * coexist with `GROUP BY` and are a common source of incorrect ordering.\n */\nexport function buildReadDailyBarsSQL(\n ticker: string,\n from: string,\n to: string,\n): BuiltSQL {\n return {\n sql: `SELECT\n ticker,\n date,\n first(open ORDER BY time) AS open,\n max(high) AS high,\n min(low) AS low,\n last(close ORDER BY time) AS close,\n first(bid ORDER BY time) AS bid,\n last(ask ORDER BY time) AS ask\n FROM market.spot\n WHERE ticker = ${lit(ticker)}\n AND date >= ${lit(from)} AND date <= ${lit(to)}\n AND time >= '09:30' AND time <= '16:00'\n -- Defense-in-depth: drop minute bars with zero/null OHLC\n -- before aggregating. Mirrors market.spot_daily and the direct-\n -- parquet daily-agg path. Without it, min(low) collapses to 0\n -- on contaminated minutes and propagates into enriched indicators.\n AND open IS NOT NULL AND open > 0\n AND high IS NOT NULL AND high > 0\n AND low IS NOT NULL AND low > 0\n AND close IS NOT NULL AND close > 0\n GROUP BY ticker, date\n ORDER BY date`,\n };\n}\n\n/**\n * Project `(date, open)` using the RTH first-open aggregate.\n *\n * Used by the enricher's VIX RTH open path, where only the opening tick of\n * the VIX family is needed for term-structure context computation.\n */\nexport function buildReadRthOpensSQL(\n ticker: string,\n from: string,\n to: string,\n): BuiltSQL {\n return {\n sql: `SELECT date, first(open ORDER BY time) AS open\n FROM market.spot\n WHERE ticker = ${lit(ticker)}\n AND date >= ${lit(from)} AND date <= ${lit(to)}\n AND time >= '09:30' AND time <= '16:00'\n -- Defense-in-depth: drop bars with zero/null open before\n -- aggregating; first(open) could otherwise return 0 if a bad\n -- minute bar is the earliest in the session.\n AND open IS NOT NULL AND open > 0\n GROUP BY date\n ORDER BY date`,\n };\n}\n","/**\n * DuckdbSpotStore — spot minute bars persisted as DuckDB physical table\n * `market.spot` (schema from `ensureMarketDataTables`).\n *\n * Writes go through `INSERT OR REPLACE INTO market.spot` with positional\n * placeholders. Reads share the same SQL builders as ParquetSpotStore — the\n * `market.spot` identifier resolves to the physical table in this mode, the\n * Parquet view in the other (CONTEXT.md D-04). Coverage uses SELECT DISTINCT\n * (D-27).\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { SpotStore } from \"./spot-store.ts\";\nimport type { BarRow, CoverageReport } from \"./types.ts\";\nimport { buildReadBarsSQL, buildReadDailyBarsSQL } from \"./spot-sql.ts\";\n\nexport class DuckdbSpotStore extends SpotStore {\n async writeBars(\n ticker: string,\n date: string,\n bars: BarRow[],\n ): Promise<void> {\n if (bars.length === 0) return;\n const placeholders = bars\n .map((_, i) => {\n const b = i * 9;\n return `($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6},$${b + 7},$${b + 8},$${b + 9})`;\n })\n .join(\", \");\n const params: unknown[] = bars.flatMap((b) => [\n ticker,\n date,\n b.time ?? \"09:30\",\n b.open,\n b.high,\n b.low,\n b.close,\n b.bid ?? null,\n b.ask ?? null,\n ]);\n await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.spot\n (ticker, date, time, open, high, low, close, bid, ask)\n VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n }\n\n async writeFromSelect(\n _partition: { ticker: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n // INSERT OR REPLACE preserves idempotency semantics of writeBars.\n // Column list matches market.spot schema (ticker, date, time, open, high, low, close, bid, ask).\n const result = await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.spot\n (ticker, date, time, open, high, low, close, bid, ask)\n ${selectSql}`,\n );\n return { rowCount: Number(result.rowsChanged) };\n }\n\n async readBars(\n ticker: string,\n from: string,\n to: string,\n ): Promise<BarRow[]> {\n // Builders inline values as SQL literals; the unbound runAndReadAll(sql)\n // path bypasses extract_statements (see spot-sql.ts header).\n const { sql } = buildReadBarsSQL(ticker, from, to);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n date: String(r[1]),\n time: String(r[2]),\n open: Number(r[3]),\n high: Number(r[4]),\n low: Number(r[5]),\n close: Number(r[6]),\n bid: r[7] == null ? undefined : Number(r[7]),\n ask: r[8] == null ? undefined : Number(r[8]),\n volume: 0,\n }));\n }\n\n async readDailyBars(\n ticker: string,\n from: string,\n to: string,\n ): Promise<BarRow[]> {\n const { sql } = buildReadDailyBarsSQL(ticker, from, to);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n date: String(r[1]),\n time: \"09:30\",\n open: Number(r[2]),\n high: Number(r[3]),\n low: Number(r[4]),\n close: Number(r[5]),\n bid: r[6] == null ? undefined : Number(r[6]),\n ask: r[7] == null ? undefined : Number(r[7]),\n volume: 0,\n }));\n }\n\n async getCoverage(\n ticker: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n // Inline literals — same leak rationale as readBars (spot-sql.ts header).\n const tickerLit = ticker.replace(/'/g, \"''\");\n const fromLit = from.replace(/'/g, \"''\");\n const toLit = to.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT DISTINCT date FROM market.spot\n WHERE ticker = '${tickerLit}' AND date >= '${fromLit}' AND date <= '${toLit}'\n ORDER BY date`,\n );\n const dates = reader.getRows().map((r) => String(r[0]));\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * ParquetEnrichedStore — thin wrapper over the existing `market-enricher.ts`\n * runEnrichment pipeline (D-14 / D-15).\n *\n * Enrichment math stays in `market-enricher.ts` untouched. This store injects\n * a `SpotStore` + watermark adapter at the enricher's IO boundaries and\n * provides a typed `read()` over `market.enriched` with optional joins to\n * `market.spot` (daily OHLCV aggregate) and `market.enriched_context`.\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode` — the factory\n * chooses the backend once at construction, every method is monomorphic.\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport { EnrichedStore, type EnrichedReadOpts } from \"./enriched-store.ts\";\nimport { SpotStore } from \"./spot-store.ts\";\nimport type { StoreContext, CoverageReport } from \"./types.ts\";\nimport { buildReadEnrichedSQL } from \"./enriched-sql.ts\";\nimport { resolveMarketDir } from \"../../db/market-datasets.ts\";\nimport { runEnrichment } from \"../../utils/market-enricher.ts\";\nimport {\n getEnrichedThrough,\n upsertEnrichedThrough,\n} from \"../../db/json-adapters.ts\";\n\nexport class ParquetEnrichedStore extends EnrichedStore {\n private readonly spotStore: SpotStore;\n constructor(\n ctx: StoreContext,\n spotStore: SpotStore,\n ) {\n super(ctx);\n this.spotStore = spotStore;\n }\n\n async compute(ticker: string, _from: string, _to: string): Promise<void> {\n // _from/_to are informational — the enricher uses its own watermark plus\n // a 200-day lookback. The thin wrapper only injects IO; math stays in\n // `market-enricher.ts` (D-14).\n await runEnrichment(\n this.ctx.conn,\n ticker,\n { dataDir: this.ctx.dataDir, parquetMode: true },\n {\n spotStore: this.spotStore,\n watermarkStore: {\n get: (t) => getEnrichedThrough(t, this.ctx.dataDir),\n upsert: (t, v) => upsertEnrichedThrough(t, v, this.ctx.dataDir),\n },\n },\n );\n }\n\n async computeContext(_from: string, _to: string): Promise<void> {\n // D-16: wraps the existing Tier 2 context computation. Running\n // runEnrichment for each VIX-family ticker triggers Tier 2 internally\n // (it runs after every Tier 1 pass and is idempotent at date granularity).\n // If a ticker has no daily data yet, runEnrichment returns a skipped\n // Tier 1 status and skips Tier 2 — safe no-op.\n for (const ticker of [\"VIX\", \"VIX9D\", \"VIX3M\"]) {\n await runEnrichment(\n this.ctx.conn,\n ticker,\n { dataDir: this.ctx.dataDir, parquetMode: true },\n {\n spotStore: this.spotStore,\n watermarkStore: {\n get: (t) => getEnrichedThrough(t, this.ctx.dataDir),\n upsert: (t, v) => upsertEnrichedThrough(t, v, this.ctx.dataDir),\n },\n },\n );\n }\n }\n\n async read(opts: EnrichedReadOpts): Promise<Record<string, unknown>[]> {\n // Fast path: when the caller doesn't need cross-ticker context or RTH\n // OHLCV joins, read directly from the ticker's parquet file. Avoids\n // the `market.enriched` view glob (~430ms) AND the extract_statements\n // GC handle leak (see parquet-quote-store.ts:327). Hit on every\n // entry-pipeline date when an RSI / vol-regime filter is configured.\n const wantsJoins = !!opts.includeContext || !!opts.includeOhlcv;\n if (!wantsJoins) {\n const filePath = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"enriched\",\n `ticker=${opts.ticker}`,\n \"data.parquet\",\n );\n if (existsSync(filePath)) {\n const escaped = filePath.replace(/'/g, \"''\");\n const fromLit = opts.from.replace(/'/g, \"''\");\n const toLit = opts.to.replace(/'/g, \"''\");\n const sql = `SELECT * FROM read_parquet('${escaped}', hive_partitioning=true)\n WHERE date >= '${fromLit}' AND date <= '${toLit}'\n ORDER BY date`;\n const reader = await this.ctx.conn.runAndReadAll(sql);\n const names = reader.columnNames();\n return reader\n .getRows()\n .map((row) => Object.fromEntries(names.map((n, i) => [n, row[i]])));\n }\n }\n // Builder inlines values; unbound runAndReadAll(sql) bypasses extract_statements.\n const { sql } = buildReadEnrichedSQL({\n ticker: opts.ticker,\n from: opts.from,\n to: opts.to,\n includeContext: !!opts.includeContext,\n includeOhlcv: !!opts.includeOhlcv,\n });\n const reader = await this.ctx.conn.runAndReadAll(sql);\n const names = reader.columnNames();\n return reader\n .getRows()\n .map((row) =>\n Object.fromEntries(names.map((n, i) => [n, row[i]])),\n );\n }\n\n async getCoverage(ticker: string): Promise<CoverageReport> {\n // D-27: coverage comes from the enriched data itself (not the watermark\n // JSON) — \"what rows exist\" is independent of \"where did enrichment stop\".\n const filePath = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"enriched\",\n `ticker=${ticker}`,\n \"data.parquet\",\n );\n if (!existsSync(filePath)) {\n // No enriched Parquet file for this ticker — empty report. Querying\n // market.enriched here would surface rows from other tickers (the view\n // is a union), so we return empty early to match Parquet reality.\n return { earliest: null, latest: null, missingDates: [], totalDates: 0 };\n }\n const tickerLit = ticker.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT DISTINCT date FROM market.enriched WHERE ticker = '${tickerLit}' ORDER BY date`,\n );\n const dates = reader.getRows().map((r) => String(r[0]));\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * RTH (Regular Trading Hours) aggregation helper.\n *\n * Emits a scalar subquery / derived table that rolls up minute bars in\n * `market.spot` into daily OHLCV rows over [09:30, 16:00].\n *\n * DuckDB idiom note (PATTERNS.md \"rth-aggregation.ts\"; RESEARCH.md Pitfall 3):\n * the canonical way to get first/last per group is the `first(col ORDER BY ...)`\n * aggregate — NEVER the window-function equivalents (which cannot be combined\n * with `GROUP BY`).\n *\n * Values arrive pre-quoted from the caller so the subquery composes cleanly\n * with the surrounding inline-literal SQL (see `spot-sql.ts` header for why\n * positional params are off-limits — the extract_statements GC leak).\n *\n * Used by:\n * - `enriched-sql.ts::buildReadEnrichedSQL` when `includeOhlcv=true`\n */\n\nexport interface RthWindowOpts {\n /** SQL-literal expression for the ticker (e.g. `'SPX'`). */\n tickerLit: string;\n /** SQL-literal expression for the `from` date (e.g. `'2025-01-01'`). */\n fromLit: string;\n /** SQL-literal expression for the `to` date (e.g. `'2025-01-31'`). */\n toLit: string;\n}\n\n/**\n * Emit a derived-table expression that produces daily OHLCV rows by aggregating\n * minute bars in `market.spot` within the RTH window. Inputs are pre-escaped\n * SQL literals so the subquery embeds directly inside a larger inline-literal\n * SQL statement (no positional params anywhere in the pipeline).\n */\nexport function rthDailyAggregateSubquery(opts: RthWindowOpts): string {\n const { tickerLit, fromLit, toLit } = opts;\n return `(\n SELECT ticker, date,\n first(open ORDER BY time) AS open,\n max(high) AS high,\n min(low) AS low,\n last(close ORDER BY time) AS close\n FROM market.spot\n WHERE ticker = ${tickerLit}\n AND date >= ${fromLit} AND date <= ${toLit}\n AND time >= '09:30' AND time <= '16:00'\n -- Defense-in-depth: drop minute bars with zero/null OHLC before\n -- aggregating. Mirrors the same guard on market.spot_daily and the\n -- direct-parquet daily-agg path. Without it, min(low) collapses to 0\n -- on contaminated minutes (provider gaps in the spot ingest).\n AND open IS NOT NULL AND open > 0\n AND high IS NOT NULL AND high > 0\n AND low IS NOT NULL AND low > 0\n AND close IS NOT NULL AND close > 0\n GROUP BY ticker, date\n )`;\n}\n","/**\n * Pure SQL builder for EnrichedStore reads.\n *\n * The enriched read supports two optional joins controlled by flags:\n * - `includeOhlcv` → LEFT JOIN a daily RTH aggregate of `market.spot`\n * - `includeContext` → LEFT JOIN `market.enriched_context` for cross-ticker\n * VIX-family fields (Vol_Regime, Term_Structure_State,\n * Trend_Direction, VIX_Spike_Pct, VIX_Gap_Pct)\n *\n * Values are inlined as SQL literals — see `spot-sql.ts` header for the\n * extract_statements GC leak that ruled out positional parameters.\n *\n * Purity contract: no `this`, no `ctx`, no DB-connection value-level imports.\n * Tests live in `tests/unit/market/stores/enriched-sql.test.ts`.\n */\nimport { escapeSqlLiteral } from \"../../utils/quote-parquet-projection.ts\";\nimport { rthDailyAggregateSubquery } from \"./rth-aggregation.ts\";\nimport type { BuiltSQL } from \"./spot-sql.ts\";\n\nexport interface BuildReadEnrichedArgs {\n ticker: string;\n from: string;\n to: string;\n includeContext: boolean;\n includeOhlcv: boolean;\n}\n\nfunction lit(value: string): string {\n return `'${escapeSqlLiteral(value)}'`;\n}\n\n/**\n * Build the `SELECT ... FROM market.enriched ...` SQL, optionally joined with\n * the RTH daily aggregate from `market.spot` and the `market.enriched_context`\n * table.\n */\nexport function buildReadEnrichedSQL(\n args: BuildReadEnrichedArgs,\n): BuiltSQL {\n const { ticker, from, to, includeContext, includeOhlcv } = args;\n\n const tickerLit = lit(ticker);\n const fromLit = lit(from);\n const toLit = lit(to);\n\n const ohlcvJoin = includeOhlcv\n ? `LEFT JOIN ${rthDailyAggregateSubquery({\n tickerLit,\n fromLit,\n toLit,\n })} s_daily\n ON s_daily.ticker = e.ticker AND s_daily.date = e.date`\n : \"\";\n\n const ctxJoin = includeContext\n ? `LEFT JOIN market.enriched_context c ON c.date = e.date`\n : \"\";\n\n const ohlcvCols = includeOhlcv\n ? \", s_daily.open, s_daily.high, s_daily.low, s_daily.close\"\n : \"\";\n\n const ctxCols = includeContext\n ? \", c.Vol_Regime, c.Term_Structure_State, c.Trend_Direction, c.VIX_Spike_Pct, c.VIX_Gap_Pct\"\n : \"\";\n\n const sql = `\n SELECT e.*${ohlcvCols}${ctxCols}\n FROM market.enriched e\n ${ohlcvJoin}\n ${ctxJoin}\n WHERE e.ticker = ${tickerLit} AND e.date >= ${fromLit} AND e.date <= ${toLit}\n ORDER BY e.date\n `;\n\n return { sql };\n}\n","/**\n * DuckdbEnrichedStore — thin wrapper over the existing `market-enricher.ts`\n * runEnrichment pipeline (D-14 / D-15) for the DuckDB physical-table backend.\n *\n * Reads share the SAME SQL as ParquetEnrichedStore because the\n * `market.enriched` identifier resolves to the physical table in this mode\n * and to the Parquet view in the other (CONTEXT.md D-04). The write path\n * (compute/computeContext) injects SpotStore + the watermark adapter into\n * the enricher; the math stays in `market-enricher.ts`.\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { EnrichedStore, type EnrichedReadOpts } from \"./enriched-store.ts\";\nimport { SpotStore } from \"./spot-store.ts\";\nimport type { StoreContext, CoverageReport } from \"./types.ts\";\nimport { buildReadEnrichedSQL } from \"./enriched-sql.ts\";\nimport { runEnrichment } from \"../../utils/market-enricher.ts\";\nimport {\n getEnrichedThrough,\n upsertEnrichedThrough,\n} from \"../../db/json-adapters.ts\";\n\nexport class DuckdbEnrichedStore extends EnrichedStore {\n private readonly spotStore: SpotStore;\n constructor(\n ctx: StoreContext,\n spotStore: SpotStore,\n ) {\n super(ctx);\n this.spotStore = spotStore;\n }\n\n async compute(ticker: string, _from: string, _to: string): Promise<void> {\n await runEnrichment(\n this.ctx.conn,\n ticker,\n { dataDir: this.ctx.dataDir, parquetMode: false },\n {\n spotStore: this.spotStore,\n watermarkStore: {\n get: (t) => getEnrichedThrough(t, this.ctx.dataDir),\n upsert: (t, v) => upsertEnrichedThrough(t, v, this.ctx.dataDir),\n },\n },\n );\n }\n\n async computeContext(_from: string, _to: string): Promise<void> {\n for (const ticker of [\"VIX\", \"VIX9D\", \"VIX3M\"]) {\n await runEnrichment(\n this.ctx.conn,\n ticker,\n { dataDir: this.ctx.dataDir, parquetMode: false },\n {\n spotStore: this.spotStore,\n watermarkStore: {\n get: (t) => getEnrichedThrough(t, this.ctx.dataDir),\n upsert: (t, v) => upsertEnrichedThrough(t, v, this.ctx.dataDir),\n },\n },\n );\n }\n }\n\n async read(opts: EnrichedReadOpts): Promise<Record<string, unknown>[]> {\n // Builder inlines values; unbound runAndReadAll(sql) bypasses extract_statements\n // (see enriched-sql.ts / spot-sql.ts headers).\n const { sql } = buildReadEnrichedSQL({\n ticker: opts.ticker,\n from: opts.from,\n to: opts.to,\n includeContext: !!opts.includeContext,\n includeOhlcv: !!opts.includeOhlcv,\n });\n const reader = await this.ctx.conn.runAndReadAll(sql);\n const names = reader.columnNames();\n return reader\n .getRows()\n .map((row) =>\n Object.fromEntries(names.map((n, i) => [n, row[i]])),\n );\n }\n\n async getCoverage(ticker: string): Promise<CoverageReport> {\n // D-27: coverage answers \"what rows exist\" directly from the enriched\n // table — not from the watermark JSON. Inline literal — same leak rationale.\n const tickerLit = ticker.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT DISTINCT date FROM market.enriched WHERE ticker = '${tickerLit}' ORDER BY date`,\n );\n const dates = reader.getRows().map((r) => String(r[0]));\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * ParquetChainStore — option chain snapshots persisted as underlying-first\n * Hive-partitioned Parquet files (option_chain/underlying=X/date=Y/data.parquet).\n *\n * Writes flow through `writeChainPartition` (Phase 1 typed helper); reads use\n * `buildReadChainSQL` against the `market.option_chain` view registered by\n * `createMarketParquetViews`. Coverage via filesystem enumeration (D-26).\n *\n * Note: Phase 3 reorganizes existing `option_chain/date=Y/` directories into\n * `option_chain/underlying=X/date=Y/`. Phase 2 only writes the new layout\n * when the store is called; it does not migrate pre-existing data.\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport { ChainStore } from \"./chain-store.ts\";\nimport type { ContractRow, CoverageReport } from \"./types.ts\";\nimport { buildReadChainSQL } from \"./chain-sql.ts\";\nimport { listPartitionValues } from \"./coverage.ts\";\nimport {\n resolveMarketDir,\n writeChainPartition,\n} from \"../../db/market-datasets.ts\";\n\nfunction escapeSqlLiteral(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nexport class ParquetChainStore extends ChainStore {\n async writeChain(\n underlying: string,\n date: string,\n rows: ContractRow[],\n ): Promise<void> {\n if (rows.length === 0) return;\n const staging = `_chain_write_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n await this.ctx.conn.run(\n `CREATE TEMP TABLE \"${staging}\" (\n underlying VARCHAR, date VARCHAR, ticker VARCHAR,\n contract_type VARCHAR, strike DOUBLE, expiration VARCHAR,\n dte INTEGER, exercise_style VARCHAR\n )`,\n );\n try {\n const placeholders = rows\n .map((_, i) => {\n const b = i * 8;\n return `($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6},$${b + 7},$${b + 8})`;\n })\n .join(\", \");\n const params: unknown[] = rows.flatMap((r) => [\n underlying,\n date,\n r.ticker,\n r.contract_type,\n r.strike,\n r.expiration,\n r.dte ?? null,\n r.exercise_style ?? null,\n ]);\n await this.ctx.conn.run(\n `INSERT INTO \"${staging}\" VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n await writeChainPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n underlying,\n date,\n selectQuery: `SELECT * FROM \"${staging}\"`,\n });\n } finally {\n try {\n await this.ctx.conn.run(`DROP TABLE IF EXISTS \"${staging}\"`);\n } catch {\n /* best-effort */\n }\n }\n }\n\n async writeFromSelect(\n partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n return writeChainPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n underlying: partition.underlying,\n date: partition.date,\n selectQuery: selectSql,\n });\n }\n\n /**\n * Filesystem-based chain probe — bypasses both the `market.option_chain`\n * view glob (~430ms per call) AND the `extract_statements` GC handle leak\n * that accumulates over long-running read workloads.\n * The chain partition path is deterministic, so the probe collapses to a\n * single `existsSync` syscall.\n */\n override async hasChain(underlying: string, date: string): Promise<boolean> {\n const partitionPath = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_chain\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n return existsSync(partitionPath);\n }\n\n async readChain(\n underlying: string,\n date: string,\n ): Promise<ContractRow[]> {\n const partitionPath = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_chain\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n if (existsSync(partitionPath)) {\n // Direct-parquet path: no params → no extract_statements leak, no view\n // glob overhead. SQL string is fully literal (path is from typed config,\n // not user input — no injection vector beyond the existing data dir).\n const sql = `SELECT underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style\n FROM read_parquet('${escapeSqlLiteral(partitionPath)}', hive_partitioning=true)\n ORDER BY ticker`;\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n underlying: String(r[0]),\n date: String(r[1]),\n ticker: String(r[2]),\n contract_type: String(r[3]) as ContractRow[\"contract_type\"],\n strike: Number(r[4]),\n expiration: String(r[5]),\n dte: Number(r[6]),\n exercise_style: String(r[7]),\n }));\n }\n // Builder inlines values; unbound runAndReadAll(sql) bypasses extract_statements.\n const { sql } = buildReadChainSQL(underlying, date);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n underlying: String(r[0]),\n date: String(r[1]),\n ticker: String(r[2]),\n contract_type: String(r[3]) as ContractRow[\"contract_type\"],\n strike: Number(r[4]),\n expiration: String(r[5]),\n dte: Number(r[6]),\n exercise_style: String(r[7]),\n }));\n }\n\n override async readChainDates(\n underlying: string,\n dates: string[],\n ): Promise<ContractRow[]> {\n if (dates.length === 0) return [];\n\n const marketDir = resolveMarketDir(this.ctx.dataDir);\n const directPaths: string[] = [];\n for (const date of dates) {\n const partitionPath = path.join(\n marketDir,\n \"option_chain\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n if (existsSync(partitionPath)) directPaths.push(partitionPath);\n }\n if (directPaths.length === 0) return super.readChainDates(underlying, dates);\n\n const fileList = directPaths.map((filePath) => `'${escapeSqlLiteral(filePath)}'`).join(\", \");\n // Direct path: omit params arg entirely to bypass extract_statements\n // (parquet-quote-store.ts:327 has the full root-cause writeup).\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style\n FROM read_parquet([${fileList}], hive_partitioning=true)\n ORDER BY date, ticker`,\n );\n return reader.getRows().map((r) => ({\n underlying: String(r[0]),\n date: String(r[1]),\n ticker: String(r[2]),\n contract_type: String(r[3]) as ContractRow[\"contract_type\"],\n strike: Number(r[4]),\n expiration: String(r[5]),\n dte: Number(r[6]),\n exercise_style: String(r[7]),\n }));\n }\n\n async getCoverage(\n underlying: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n const dir = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_chain\",\n `underlying=${underlying}`,\n );\n if (!existsSync(dir)) {\n return { earliest: null, latest: null, missingDates: [], totalDates: 0 };\n }\n const allDates = listPartitionValues(dir, \"date\");\n const dates = allDates.filter((d) => d >= from && d <= to);\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * DuckdbChainStore — option chain persisted as DuckDB physical table\n * `market.option_chain`. Phase 1 schema already includes the `underlying`\n * column (D-13), so no DROP+recreate migration is needed.\n *\n * Writes via `INSERT OR REPLACE INTO market.option_chain` with positional\n * placeholders; reads via shared `buildReadChainSQL`. Coverage via\n * SELECT DISTINCT date (D-27).\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { ChainStore } from \"./chain-store.ts\";\nimport type { ContractRow, CoverageReport } from \"./types.ts\";\nimport { buildReadChainSQL } from \"./chain-sql.ts\";\n\nexport class DuckdbChainStore extends ChainStore {\n async writeChain(\n underlying: string,\n date: string,\n rows: ContractRow[],\n ): Promise<void> {\n if (rows.length === 0) return;\n const placeholders = rows\n .map((_, i) => {\n const b = i * 8;\n return `($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6},$${b + 7},$${b + 8})`;\n })\n .join(\", \");\n const params: unknown[] = rows.flatMap((r) => [\n underlying,\n date,\n r.ticker,\n r.contract_type,\n r.strike,\n r.expiration,\n r.dte ?? null,\n r.exercise_style ?? null,\n ]);\n await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.option_chain\n (underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style)\n VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n }\n\n async writeFromSelect(\n _partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n const result = await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.option_chain\n (underlying, date, ticker, contract_type, strike, expiration, dte, exercise_style)\n ${selectSql}`,\n );\n return { rowCount: Number(result.rowsChanged) };\n }\n\n async readChain(\n underlying: string,\n date: string,\n ): Promise<ContractRow[]> {\n // Builder inlines values; unbound runAndReadAll(sql) bypasses\n // extract_statements (chain-sql.ts header).\n const { sql } = buildReadChainSQL(underlying, date);\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n underlying: String(r[0]),\n date: String(r[1]),\n ticker: String(r[2]),\n contract_type: String(r[3]) as ContractRow[\"contract_type\"],\n strike: Number(r[4]),\n expiration: String(r[5]),\n dte: Number(r[6]),\n exercise_style: String(r[7]),\n }));\n }\n\n async getCoverage(\n underlying: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n // Inline literals — same leak rationale as readChain.\n const underlyingLit = underlying.replace(/'/g, \"''\");\n const fromLit = from.replace(/'/g, \"''\");\n const toLit = to.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT DISTINCT date FROM market.option_chain\n WHERE underlying = '${underlyingLit}' AND date >= '${fromLit}' AND date <= '${toLit}'\n ORDER BY date`,\n );\n const dates = reader.getRows().map((r) => String(r[0]));\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * ParquetQuoteStore — option minute NBBO quotes persisted as underlying-first\n * Hive-partitioned Parquet files\n * (option_quote_minutes/underlying=X/date=Y/data.parquet).\n *\n * D-06 / D-08: readQuotes accepts a batch of OCC tickers plus a date range and\n * returns Map<occTicker, QuoteRow[]> with timestamp-sorted values per\n * contract. Matches the primary multi-ticker consumer pattern (bulk\n * `ticker IN (...) AND date BETWEEN ...` → group-by-ticker).\n *\n * D-07 / Pitfall 4: all OCC tickers in a single call MUST resolve to the same\n * underlying. First-iteration behavior is to throw a clear error naming both\n * conflicting tickers — consumers must group reads by underlying themselves.\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport { QuoteStore } from \"./quote-store.ts\";\nimport type {\n QuoteRow,\n CoverageReport,\n ReadWindowParams,\n WindowQuoteRow,\n} from \"./types.ts\";\nimport { listPartitionValues } from \"./coverage.ts\";\nimport {\n resolveMarketDir,\n writeQuoteMinutesPartition,\n} from \"../../db/market-datasets.ts\";\nimport { extractRoot } from \"../tickers/resolver.ts\";\nimport {\n describeQueryColumns,\n describeReadParquetColumns,\n escapeSqlLiteral,\n quoteParquetCanonicalProjection,\n quoteParquetCanonicalWriteProjection,\n readParquetFilesSql,\n} from \"../../utils/quote-parquet-projection.ts\";\n\nfunction parseQuoteRow(row: unknown[]): QuoteRow {\n const occ = String(row[2]);\n const date = String(row[1]);\n const time = String(row[3]);\n return {\n occ_ticker: occ,\n timestamp: `${date} ${time}`,\n bid: Number(row[4]),\n ask: Number(row[5]),\n source: row[8] == null ? null : (String(row[8]) as QuoteRow[\"source\"]),\n delta: row[9] == null ? null : Number(row[9]),\n gamma: row[10] == null ? null : Number(row[10]),\n theta: row[11] == null ? null : Number(row[11]),\n vega: row[12] == null ? null : Number(row[12]),\n iv: row[13] == null ? null : Number(row[13]),\n greeks_source: row[14] == null ? null : String(row[14]) as QuoteRow[\"greeks_source\"],\n greeks_revision: row[15] == null ? null : Number(row[15]),\n rate_type: row[16] == null ? null : String(row[16]),\n rate_value: row[17] == null ? null : Number(row[17]),\n gamma_source: row[18] == null ? null : String(row[18]),\n };\n}\n\nexport class ParquetQuoteStore extends QuoteStore {\n async writeQuotes(\n underlying: string,\n date: string,\n quotes: QuoteRow[],\n ): Promise<void> {\n if (quotes.length === 0) return;\n // Append rows via DuckDBAppender (typed per-column, no SQL parse overhead)\n // rather than a parameterized INSERT with O(N) placeholders — the latter\n // forces DuckDB to parse a multi-megabyte SQL statement before a single\n // row lands, which was the dominant wall-clock cost on a 5M-row SPX day.\n const staging = `_quote_write_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n // Greeks persist as REAL (FLOAT32): Black-Scholes outputs sit well within\n // single-precision range, and the 4-byte-per-column layout halves per-row\n // greek cost in parquet — a full SPX backfill under DOUBLE would blow the\n // 250GB archive budget; under REAL it fits comfortably.\n await this.ctx.conn.run(\n `CREATE TEMP TABLE \"${staging}\" (\n underlying VARCHAR, date VARCHAR, ticker VARCHAR, time VARCHAR,\n bid DOUBLE, ask DOUBLE, mid DOUBLE,\n last_updated_ns BIGINT, source VARCHAR,\n delta REAL, gamma REAL, theta REAL, vega REAL, iv REAL,\n greeks_source VARCHAR, greeks_revision INTEGER,\n rate_type VARCHAR, rate_value DOUBLE, gamma_source VARCHAR\n )`,\n );\n try {\n const appender = await this.ctx.conn.createAppender(staging);\n try {\n for (const q of quotes) {\n // QuoteRow.timestamp is \"YYYY-MM-DD HH:MM\" — split into date/time.\n // If the timestamp omits the time (legacy producers), default to 09:30.\n const spaceIdx = q.timestamp.indexOf(\" \");\n const qdate = spaceIdx === -1 ? date : q.timestamp.slice(0, spaceIdx);\n const qtime = spaceIdx === -1 ? \"09:30\" : q.timestamp.slice(spaceIdx + 1);\n appender.appendVarchar(underlying);\n appender.appendVarchar(qdate);\n appender.appendVarchar(q.occ_ticker);\n appender.appendVarchar(qtime);\n appender.appendDouble(q.bid);\n appender.appendDouble(q.ask);\n appender.appendDouble((q.bid + q.ask) / 2);\n appender.appendNull(); // last_updated_ns — not tracked in QuoteRow\n if (q.source == null) appender.appendNull();\n else appender.appendVarchar(q.source);\n if (q.delta == null) appender.appendNull();\n else appender.appendFloat(q.delta);\n if (q.gamma == null) appender.appendNull();\n else appender.appendFloat(q.gamma);\n if (q.theta == null) appender.appendNull();\n else appender.appendFloat(q.theta);\n if (q.vega == null) appender.appendNull();\n else appender.appendFloat(q.vega);\n if (q.iv == null) appender.appendNull();\n else appender.appendFloat(q.iv);\n if (q.greeks_source == null) appender.appendNull();\n else appender.appendVarchar(q.greeks_source);\n if (q.greeks_revision == null) appender.appendNull();\n else appender.appendInteger(q.greeks_revision);\n if (q.rate_type == null) appender.appendNull();\n else appender.appendVarchar(q.rate_type);\n if (q.rate_value == null) appender.appendNull();\n else appender.appendDouble(q.rate_value);\n if (q.gamma_source == null) appender.appendNull();\n else appender.appendVarchar(q.gamma_source);\n appender.endRow();\n }\n appender.flushSync();\n } finally {\n appender.closeSync();\n }\n await writeQuoteMinutesPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n underlying,\n date,\n selectQuery: `SELECT * FROM \"${staging}\"`,\n });\n } finally {\n try {\n await this.ctx.conn.run(`DROP TABLE IF EXISTS \"${staging}\"`);\n } catch {\n /* best-effort */\n }\n }\n }\n\n async writeFromSelect(\n partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n const columns = await describeQueryColumns(this.ctx.conn, selectSql);\n // Force REAL greeks on write regardless of source type — keeps parquet\n // footprint half-size vs DOUBLE without depending on upstream producers\n // to cast correctly.\n const projection = quoteParquetCanonicalWriteProjection(columns, \"q\");\n return writeQuoteMinutesPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n underlying: partition.underlying,\n date: partition.date,\n selectQuery: `SELECT ${projection} FROM (${selectSql}) AS q`,\n });\n }\n\n async readQuotes(\n occTickers: string[],\n from: string,\n to: string,\n ): Promise<Map<string, QuoteRow[]>> {\n if (occTickers.length === 0) return new Map();\n // D-07: validate all tickers resolve to the same underlying BEFORE any SQL\n // runs. A mixed batch is almost always a bug in the caller; surface it\n // with both conflicting OCC tickers + resolved underlyings for debugging.\n const firstUnderlying = this.ctx.tickers.resolve(\n extractRoot(occTickers[0]),\n );\n for (const t of occTickers) {\n const u = this.ctx.tickers.resolve(extractRoot(t));\n if (u !== firstUnderlying) {\n throw new Error(\n `QuoteStore.readQuotes: mixed underlyings in batch — ` +\n `${occTickers[0]} resolves to ${firstUnderlying}, ${t} resolves to ${u}. ` +\n `Consumers must group reads by underlying.`,\n );\n }\n }\n const underlyingDir = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_quote_minutes\",\n `underlying=${firstUnderlying}`,\n );\n if (!existsSync(underlyingDir)) return new Map();\n const files = listPartitionValues(underlyingDir, \"date\")\n .filter((date) => date >= from && date <= to)\n .map((date) => path.join(underlyingDir, `date=${date}`, \"data.parquet\"))\n .filter((filePath) => existsSync(filePath));\n if (files.length === 0) return new Map();\n\n const source = readParquetFilesSql(files);\n const columns = await describeReadParquetColumns(this.ctx.conn, source);\n const projection = quoteParquetCanonicalProjection(columns, \"q\");\n // Inline every value as a SQL literal and call the unbound\n // runAndReadAll(sql) form — the bound (sql, values) path routes through\n // node_bindings.extract_statements, which leaks a non-destroyable handle\n // per call and eventually throws \"Failed to execute prepared statement\"\n // under sustained read load. See readWindow below for the full writeup.\n const fromLit = `'${escapeSqlLiteral(from)}'`;\n const toLit = `'${escapeSqlLiteral(to)}'`;\n const tickerList = occTickers\n .map((t) => `'${escapeSqlLiteral(t)}'`)\n .join(\", \");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT ${projection}\n FROM ${source} AS q\n WHERE q.date >= ${fromLit}\n AND q.date <= ${toLit}\n AND q.ticker IN (${tickerList})\n ORDER BY q.ticker, q.date, q.time`,\n );\n const out = new Map<string, QuoteRow[]>();\n for (const row of reader.getRows()) {\n const qr = parseQuoteRow(row);\n const occ = qr.occ_ticker;\n let arr = out.get(occ);\n if (!arr) {\n arr = [];\n out.set(occ, arr);\n }\n arr.push(qr);\n }\n return out;\n }\n\n override async readQuotesBulk(\n tickersByDate: Map<string, Set<string>>,\n timeStart: string,\n timeEnd: string,\n ): Promise<Map<string, QuoteRow[]>> {\n const out = new Map<string, QuoteRow[]>();\n if (tickersByDate.size === 0) return out;\n\n const perf = process.env.QUOTE_STORE_PERF_DEBUG === \"1\";\n const marketDir = resolveMarketDir(this.ctx.dataDir);\n\n for (const [underlying, perDate] of this.groupTickersByUnderlying(tickersByDate)) {\n const occUnion = new Set<string>();\n const filePaths: string[] = [];\n const wantedPairs: string[] = [];\n\n for (const [date, occs] of perDate) {\n if (occs.size === 0) continue;\n const partitionPath = path.join(\n marketDir,\n \"option_quote_minutes\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n if (!existsSync(partitionPath)) continue;\n filePaths.push(partitionPath);\n for (const occ of occs) {\n occUnion.add(occ);\n wantedPairs.push(`('${escapeSqlLiteral(date)}', '${escapeSqlLiteral(occ)}')`);\n }\n }\n\n if (filePaths.length === 0 || wantedPairs.length === 0) continue;\n\n const source = readParquetFilesSql(filePaths);\n const columns = await describeReadParquetColumns(this.ctx.conn, source);\n const projection = quoteParquetCanonicalProjection(columns, \"q\");\n // Time bounds inlined as SQL literals so the call takes the unbound\n // runAndReadAll(sql) path (the (date, ticker) VALUES are already inlined\n // above). The bound form leaks an extract_statements handle per call —\n // see readWindow for the full root-cause writeup.\n const timeStartLit = `'${escapeSqlLiteral(timeStart)}'`;\n const timeEndLit = `'${escapeSqlLiteral(timeEnd)}'`;\n const sql = `WITH wanted(date, ticker) AS (\n VALUES ${wantedPairs.join(\", \")}\n )\n SELECT ${projection}\n FROM ${source} AS q\n JOIN wanted AS w\n ON q.date = w.date AND q.ticker = w.ticker\n WHERE q.time >= ${timeStartLit} AND q.time <= ${timeEndLit}\n ORDER BY q.ticker, q.date, q.time`;\n\n const queryStart = perf ? Date.now() : 0;\n const reader = await this.ctx.conn.runAndReadAll(sql);\n const rows = reader.getRows();\n if (perf) {\n console.log(\n ` [P] readQuotesBulk underlying=${underlying} dates=${filePaths.length} ` +\n `tickers=${occUnion.size} rows=${rows.length} queryMs=${Date.now() - queryStart}`,\n );\n }\n for (const row of rows) {\n const quote = parseQuoteRow(row);\n const occ = quote.occ_ticker;\n const bucket = out.get(occ);\n if (bucket) bucket.push(quote);\n else out.set(occ, [quote]);\n }\n }\n\n return out;\n }\n\n /**\n * Read every option-quote row whose contract falls inside the union of the\n * supplied leg envelopes, between `timeStart` and `timeEnd` inclusive on a\n * single (underlying, date) partition. Joins back to the date's\n * `option_chain` partition for `contract_type`, `strike`, `expiration`,\n * `dte` so the caller doesn't OCC-parse. Greeks (delta/gamma/theta/vega/iv)\n * project as-is from the quote partition — null when not stored.\n *\n * Returns `[]` when `legEnvelopes` is empty (D-06 short-circuit pattern) or\n * when the requested (underlying, date) partition's quote / chain Parquet\n * file does not exist on disk. The OCC-parsing fallback used elsewhere when\n * `option_chain` is absent is intentionally NOT included — the entry\n * pipeline assumes chain coverage.\n */\n async readWindow(params: ReadWindowParams): Promise<WindowQuoteRow[]> {\n const { underlying, date, timeStart, timeEnd, legEnvelopes } = params;\n if (legEnvelopes.length === 0) return [];\n\n const marketDir = resolveMarketDir(this.ctx.dataDir);\n const quoteFile = path.join(\n marketDir,\n \"option_quote_minutes\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n const chainFile = path.join(\n marketDir,\n \"option_chain\",\n `underlying=${underlying}`,\n `date=${date}`,\n \"data.parquet\",\n );\n if (!existsSync(quoteFile) || !existsSync(chainFile)) return [];\n\n const quoteSrc = readParquetFilesSql([quoteFile]);\n const chainSrc = readParquetFilesSql([chainFile]);\n\n // CRITICAL — DO NOT re-introduce parameterized binds here. Every call to\n // `runAndReadAll(sql, values)` goes through `node_bindings.extract_statements`\n // which returns a C++ handle with NO destroy method on its JS wrapper\n // (`DuckDBExtractedStatements` only has a constructor — see\n // `node_modules/@duckdb/node-api/lib/DuckDBExtractedStatements.js`). The\n // handles only release on JS GC. Because each ParquetQuoteStore.readWindow\n // embeds unique partition file paths as SQL literals, every call has\n // distinct SQL text → DuckDB caches a separate plan per call → on long\n // read workloads (~600+ calls) extract_statements handles outpace GC and\n // the driver throws `Failed to execute prepared statement` mid-run.\n //\n // The unbound `runAndReadAll(sql)` path takes `node_bindings.query()`\n // directly with no extract_statements step, so we inline every value\n // (timeStart, timeEnd, contractType, dte*, strike*) as a SQL literal.\n // Inputs are typed config (string-union / number) sourced from\n // in-process strategy definitions — no SQL injection vector.\n //\n // Phase-2 perf: project only what downstream consumers read. `underlying`\n // and `date` are pinned by the partition file paths above; `mid` is\n // derived as `(bid + ask) / 2` in `toMinuteQuoteRow`.\n const safeTimeStart = `'${escapeSqlLiteral(timeStart)}'`;\n const safeTimeEnd = `'${escapeSqlLiteral(timeEnd)}'`;\n const disjuncts: string[] = legEnvelopes.map((env) => {\n const ct = `'${escapeSqlLiteral(env.contractType)}'`;\n let clause = `(b.contract_type = ${ct} AND b.dte BETWEEN ${env.dteMin} AND ${env.dteMax}`;\n if (env.strikeMin != null) clause += ` AND b.strike >= ${env.strikeMin}`;\n if (env.strikeMax != null) clause += ` AND b.strike <= ${env.strikeMax}`;\n clause += \")\";\n return clause;\n });\n\n const sql = `\n WITH band AS (\n SELECT DISTINCT ticker, contract_type, strike, expiration, dte\n FROM ${chainSrc} AS b\n WHERE ${disjuncts.join(\" OR \")}\n )\n SELECT q.ticker, q.time,\n b.contract_type, b.strike, b.expiration, b.dte,\n q.bid, q.ask,\n q.delta, q.gamma, q.theta, q.vega, q.iv, q.greeks_source\n FROM ${quoteSrc} AS q\n JOIN band b ON q.ticker = b.ticker\n WHERE q.time BETWEEN ${safeTimeStart} AND ${safeTimeEnd}\n `;\n\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n time: String(r[1]),\n contract_type: String(r[2]) as \"call\" | \"put\",\n strike: Number(r[3]),\n expiration: String(r[4]),\n dte: Number(r[5]),\n bid: Number(r[6]),\n ask: Number(r[7]),\n delta: r[8] == null ? null : Number(r[8]),\n gamma: r[9] == null ? null : Number(r[9]),\n theta: r[10] == null ? null : Number(r[10]),\n vega: r[11] == null ? null : Number(r[11]),\n iv: r[12] == null ? null : Number(r[12]),\n greeks_source: r[13] == null ? null : String(r[13]) as WindowQuoteRow[\"greeks_source\"],\n }));\n }\n\n async getCoverage(\n underlying: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n const dir = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_quote_minutes\",\n `underlying=${underlying}`,\n );\n if (!existsSync(dir)) {\n return { earliest: null, latest: null, missingDates: [], totalDates: 0 };\n }\n const allDates = listPartitionValues(dir, \"date\");\n const dates = allDates.filter((d) => d >= from && d <= to);\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * DuckdbQuoteStore — option minute NBBO quotes persisted as DuckDB physical\n * table `market.option_quote_minutes`. Phase 1 D-12 / Pitfall 1 executed a\n * DROP+recreate with `underlying` as the first key; the Phase 1 schema already\n * has the correct shape.\n *\n * Writes via `INSERT OR REPLACE INTO market.option_quote_minutes` with\n * positional placeholders; reads project the canonical quote schema with\n * nullable greeks for older physical tables. Coverage via SELECT DISTINCT.\n *\n * D-02 reminder: no method body inspects `ctx.parquetMode`.\n */\nimport { QuoteStore } from \"./quote-store.ts\";\nimport type {\n QuoteRow,\n CoverageReport,\n ReadWindowParams,\n WindowQuoteRow,\n} from \"./types.ts\";\nimport { extractRoot } from \"../tickers/resolver.ts\";\nimport {\n describeQueryColumns,\n quoteParquetCanonicalProjection,\n type ParquetColumnSet,\n} from \"../../utils/quote-parquet-projection.ts\";\n\nfunction escapeSqlLiteral(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nfunction parseQuoteRow(row: unknown[]): QuoteRow {\n const occ = String(row[2]);\n const date = String(row[1]);\n const time = String(row[3]);\n return {\n occ_ticker: occ,\n timestamp: `${date} ${time}`,\n bid: Number(row[4]),\n ask: Number(row[5]),\n source: row[8] == null ? null : (String(row[8]) as QuoteRow[\"source\"]),\n delta: row[9] == null ? null : Number(row[9]),\n gamma: row[10] == null ? null : Number(row[10]),\n theta: row[11] == null ? null : Number(row[11]),\n vega: row[12] == null ? null : Number(row[12]),\n iv: row[13] == null ? null : Number(row[13]),\n greeks_source: row[14] == null ? null : String(row[14]) as QuoteRow[\"greeks_source\"],\n greeks_revision: row[15] == null ? null : Number(row[15]),\n rate_type: row[16] == null ? null : String(row[16]),\n rate_value: row[17] == null ? null : Number(row[17]),\n gamma_source: row[18] == null ? null : String(row[18]),\n };\n}\n\nexport class DuckdbQuoteStore extends QuoteStore {\n private quoteTableColumns: Promise<ParquetColumnSet> | null = null;\n\n private getQuoteTableColumns(): Promise<ParquetColumnSet> {\n if (!this.quoteTableColumns) {\n this.quoteTableColumns = describeQueryColumns(\n this.ctx.conn,\n \"SELECT * FROM market.option_quote_minutes\",\n );\n }\n return this.quoteTableColumns;\n }\n\n private async ensureWritableQuoteSchema(): Promise<void> {\n const additions = [\n \"delta DOUBLE\",\n \"gamma DOUBLE\",\n \"theta DOUBLE\",\n \"vega DOUBLE\",\n \"iv DOUBLE\",\n \"greeks_source VARCHAR\",\n \"greeks_revision INTEGER\",\n \"rate_type VARCHAR\",\n \"rate_value DOUBLE\",\n \"gamma_source VARCHAR\",\n ];\n for (const addition of additions) {\n await this.ctx.conn.run(\n `ALTER TABLE market.option_quote_minutes ADD COLUMN IF NOT EXISTS ${addition}`,\n );\n }\n this.quoteTableColumns = null;\n }\n\n async writeQuotes(\n underlying: string,\n date: string,\n quotes: QuoteRow[],\n ): Promise<void> {\n if (quotes.length === 0) return;\n await this.ensureWritableQuoteSchema();\n const placeholders = quotes\n .map((_, i) => {\n const b = i * 19;\n return `($${b + 1},$${b + 2},$${b + 3},$${b + 4},$${b + 5},$${b + 6},$${b + 7},$${b + 8},$${b + 9},$${b + 10},$${b + 11},$${b + 12},$${b + 13},$${b + 14},$${b + 15},$${b + 16},$${b + 17},$${b + 18},$${b + 19})`;\n })\n .join(\", \");\n const params: unknown[] = quotes.flatMap((q) => {\n const [qdate, qtime] = q.timestamp.split(\" \");\n const mid = (q.bid + q.ask) / 2;\n return [\n underlying,\n qdate ?? date,\n q.occ_ticker,\n qtime ?? \"09:30\",\n q.bid,\n q.ask,\n mid,\n null, // last_updated_ns — not tracked in QuoteRow\n q.source ?? null, // source — populated when provider tags rows (Task 6)\n q.delta ?? null,\n q.gamma ?? null,\n q.theta ?? null,\n q.vega ?? null,\n q.iv ?? null,\n q.greeks_source ?? null,\n q.greeks_revision ?? null,\n q.rate_type ?? null,\n q.rate_value ?? null,\n q.gamma_source ?? null,\n ];\n });\n await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.option_quote_minutes\n (underlying, date, ticker, time, bid, ask, mid, last_updated_ns, source,\n delta, gamma, theta, vega, iv, greeks_source, greeks_revision,\n rate_type, rate_value, gamma_source)\n VALUES ${placeholders}`,\n params as (string | number | boolean | null | bigint)[],\n );\n }\n\n async writeFromSelect(\n _partition: { underlying: string; date: string },\n selectSql: string,\n ): Promise<{ rowCount: number }> {\n await this.ensureWritableQuoteSchema();\n const columns = await describeQueryColumns(this.ctx.conn, selectSql);\n const projection = quoteParquetCanonicalProjection(columns, \"q\");\n const result = await this.ctx.conn.run(\n `INSERT OR REPLACE INTO market.option_quote_minutes\n (underlying, date, ticker, time, bid, ask, mid, last_updated_ns, source,\n delta, gamma, theta, vega, iv, greeks_source, greeks_revision,\n rate_type, rate_value, gamma_source)\n SELECT ${projection}\n FROM (${selectSql}) AS q`,\n );\n return { rowCount: Number(result.rowsChanged) };\n }\n\n async readQuotes(\n occTickers: string[],\n from: string,\n to: string,\n ): Promise<Map<string, QuoteRow[]>> {\n if (occTickers.length === 0) return new Map();\n const firstUnderlying = this.ctx.tickers.resolve(\n extractRoot(occTickers[0]),\n );\n for (const t of occTickers) {\n const u = this.ctx.tickers.resolve(extractRoot(t));\n if (u !== firstUnderlying) {\n throw new Error(\n `QuoteStore.readQuotes: mixed underlyings in batch — ` +\n `${occTickers[0]} resolves to ${firstUnderlying}, ${t} resolves to ${u}. ` +\n `Consumers must group reads by underlying.`,\n );\n }\n }\n const columns = await this.getQuoteTableColumns();\n const projection = quoteParquetCanonicalProjection(columns, \"q\");\n // Inline every value as a SQL literal and call the unbound\n // runAndReadAll(sql) form — the bound (sql, values) path routes through\n // node_bindings.extract_statements, which leaks a non-destroyable handle\n // per call and eventually throws \"Failed to execute prepared statement\"\n // under sustained read load. See spot-sql.ts header for the full writeup.\n const underlyingLit = `'${escapeSqlLiteral(firstUnderlying)}'`;\n const fromLit = `'${escapeSqlLiteral(from)}'`;\n const toLit = `'${escapeSqlLiteral(to)}'`;\n const tickerList = occTickers\n .map((t) => `'${escapeSqlLiteral(t)}'`)\n .join(\", \");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT ${projection}\n FROM market.option_quote_minutes AS q\n WHERE q.underlying = ${underlyingLit}\n AND q.date >= ${fromLit}\n AND q.date <= ${toLit}\n AND q.ticker IN (${tickerList})\n ORDER BY q.ticker, q.date, q.time`,\n );\n const out = new Map<string, QuoteRow[]>();\n for (const row of reader.getRows()) {\n const qr = parseQuoteRow(row);\n const occ = qr.occ_ticker;\n let arr = out.get(occ);\n if (!arr) {\n arr = [];\n out.set(occ, arr);\n }\n arr.push(qr);\n }\n return out;\n }\n\n override async readQuotesBulk(\n tickersByDate: Map<string, Set<string>>,\n timeStart: string,\n timeEnd: string,\n ): Promise<Map<string, QuoteRow[]>> {\n const out = new Map<string, QuoteRow[]>();\n if (tickersByDate.size === 0) return out;\n\n const perf = process.env.QUOTE_STORE_PERF_DEBUG === \"1\";\n\n for (const [underlying, perDate] of this.groupTickersByUnderlying(tickersByDate)) {\n const occUnion = new Set<string>();\n const wantedPairs: string[] = [];\n\n for (const [date, occs] of perDate) {\n if (occs.size === 0) continue;\n for (const occ of occs) {\n occUnion.add(occ);\n wantedPairs.push(`('${escapeSqlLiteral(date)}', '${escapeSqlLiteral(occ)}')`);\n }\n }\n\n if (wantedPairs.length === 0) continue;\n\n const columns = await this.getQuoteTableColumns();\n const projection = quoteParquetCanonicalProjection(columns, \"q\");\n // Underlying + time bounds inlined as SQL literals so the call takes the\n // unbound runAndReadAll(sql) path (the (date, ticker) VALUES are already\n // inlined above). The bound form leaks an extract_statements handle per\n // call — see spot-sql.ts header for the full root-cause writeup.\n const underlyingLit = `'${escapeSqlLiteral(underlying)}'`;\n const timeStartLit = `'${escapeSqlLiteral(timeStart)}'`;\n const timeEndLit = `'${escapeSqlLiteral(timeEnd)}'`;\n const sql = `WITH wanted(date, ticker) AS (\n VALUES ${wantedPairs.join(\", \")}\n )\n SELECT ${projection}\n FROM market.option_quote_minutes AS q\n JOIN wanted AS w\n ON q.date = w.date AND q.ticker = w.ticker\n WHERE q.underlying = ${underlyingLit}\n AND q.time >= ${timeStartLit} AND q.time <= ${timeEndLit}\n ORDER BY q.ticker, q.date, q.time`;\n\n const queryStart = perf ? Date.now() : 0;\n const reader = await this.ctx.conn.runAndReadAll(sql);\n const rows = reader.getRows();\n if (perf) {\n console.log(\n ` [P] readQuotesBulk underlying=${underlying} dates=${perDate.size} ` +\n `tickers=${occUnion.size} rows=${rows.length} queryMs=${Date.now() - queryStart}`,\n );\n }\n for (const row of rows) {\n const quote = parseQuoteRow(row);\n const occ = quote.occ_ticker;\n const bucket = out.get(occ);\n if (bucket) bucket.push(quote);\n else out.set(occ, [quote]);\n }\n }\n\n return out;\n }\n\n /**\n * Read every option-quote row whose contract falls inside the union of the\n * supplied leg envelopes, between `timeStart` and `timeEnd` inclusive on a\n * single (underlying, date) partition. Joins back to `market.option_chain`\n * for `contract_type`, `strike`, `expiration`, `dte` so the caller doesn't\n * OCC-parse. Greeks (delta/gamma/theta/vega/iv) project as-is from the\n * quote table — null when not stored.\n *\n * Times are 24-hour US Eastern wall-clock strings (\"HH:MM\") matching\n * `ReadWindowParams.timeStart` / `timeEnd`. Strike envelope bounds\n * (`strikeMin` / `strikeMax`) are optional; when absent the leg matches all\n * strikes within the dte band for that contract type.\n *\n * Returns `[]` when `legEnvelopes` is empty (D-06 short-circuit pattern).\n * No SQL ranking; ranking + top-N selection happen in JS at the call site.\n */\n async readWindow(params: ReadWindowParams): Promise<WindowQuoteRow[]> {\n const { underlying, date, timeStart, timeEnd, legEnvelopes } = params;\n if (legEnvelopes.length === 0) return [];\n\n // Inline every value as a SQL literal and call the unbound\n // runAndReadAll(sql) form — the bound (sql, values) path routes through\n // node_bindings.extract_statements, which leaks a non-destroyable handle\n // per call and eventually throws \"Failed to execute prepared statement\"\n // under sustained read load. See spot-sql.ts header for the full writeup.\n // String values (underlying, date, times, contract_type) are\n // single-quote-escaped; dte/strike bounds are typed numbers from\n // in-process strategy definitions, inlined directly — no injection vector.\n const pUnderlying = `'${escapeSqlLiteral(underlying)}'`;\n const pDate = `'${escapeSqlLiteral(date)}'`;\n const pTimeStart = `'${escapeSqlLiteral(timeStart)}'`;\n const pTimeEnd = `'${escapeSqlLiteral(timeEnd)}'`;\n\n const disjuncts: string[] = [];\n for (const env of legEnvelopes) {\n const ct = `'${escapeSqlLiteral(env.contractType)}'`;\n let clause = `(b.contract_type = ${ct} AND b.dte BETWEEN ${env.dteMin} AND ${env.dteMax}`;\n if (env.strikeMin != null) {\n clause += ` AND b.strike >= ${env.strikeMin}`;\n }\n if (env.strikeMax != null) {\n clause += ` AND b.strike <= ${env.strikeMax}`;\n }\n clause += \")\";\n disjuncts.push(clause);\n }\n\n // Phase-2 perf: project only what downstream consumers read. `underlying`\n // and `date` are filter-pinned by the WHERE clause, and `mid` is derived\n // as `(bid + ask) / 2` in `toMinuteQuoteRow` — fetching + decoding those\n // three columns for ~100K rows per call was wasted work.\n const sql = `\n WITH band AS (\n SELECT DISTINCT ticker, contract_type, strike, expiration, dte\n FROM market.option_chain b\n WHERE b.underlying = ${pUnderlying} AND b.date = ${pDate}\n AND (${disjuncts.join(\" OR \")})\n )\n SELECT q.ticker, q.time,\n b.contract_type, b.strike, b.expiration, b.dte,\n q.bid, q.ask,\n q.delta, q.gamma, q.theta, q.vega, q.iv, q.greeks_source\n FROM market.option_quote_minutes q\n JOIN band b ON q.ticker = b.ticker\n WHERE q.underlying = ${pUnderlying}\n AND q.date = ${pDate}\n AND q.time BETWEEN ${pTimeStart} AND ${pTimeEnd}\n `;\n\n const reader = await this.ctx.conn.runAndReadAll(sql);\n return reader.getRows().map((r) => ({\n ticker: String(r[0]),\n time: String(r[1]),\n contract_type: String(r[2]) as \"call\" | \"put\",\n strike: Number(r[3]),\n expiration: String(r[4]),\n dte: Number(r[5]),\n bid: Number(r[6]),\n ask: Number(r[7]),\n delta: r[8] == null ? null : Number(r[8]),\n gamma: r[9] == null ? null : Number(r[9]),\n theta: r[10] == null ? null : Number(r[10]),\n vega: r[11] == null ? null : Number(r[11]),\n iv: r[12] == null ? null : Number(r[12]),\n greeks_source: r[13] == null ? null : String(r[13]) as WindowQuoteRow[\"greeks_source\"],\n }));\n }\n\n async getCoverage(\n underlying: string,\n from: string,\n to: string,\n ): Promise<CoverageReport> {\n // Inline literal path — same leak rationale (see spot-sql.ts header).\n const underlyingLit = underlying.replace(/'/g, \"''\");\n const fromLit = from.replace(/'/g, \"''\");\n const toLit = to.replace(/'/g, \"''\");\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT DISTINCT date FROM market.option_quote_minutes\n WHERE underlying = '${underlyingLit}' AND date >= '${fromLit}' AND date <= '${toLit}'\n ORDER BY date`,\n );\n const dates = reader.getRows().map((r) => String(r[0]));\n return {\n earliest: dates[0] ?? null,\n latest: dates[dates.length - 1] ?? null,\n missingDates: [],\n totalDates: dates.length,\n };\n }\n}\n","/**\n * ParquetOiDailyStore — daily option open-interest persisted as\n * underlying-first Hive-partitioned Parquet files\n * (option_oi_daily/underlying=X/date=Y/data.parquet).\n *\n * Open interest is reported at daily granularity: one row per contract per\n * day. The partition layout mirrors `option_quote_minutes` (underlying then\n * date) so the same per-underlying read grouping applies.\n *\n * Columns: underlying VARCHAR, date VARCHAR, ticker VARCHAR,\n * expiration VARCHAR, strike DOUBLE, right VARCHAR, open_interest BIGINT,\n * source VARCHAR.\n */\nimport { existsSync } from \"fs\";\nimport * as path from \"path\";\nimport type { StoreContext } from \"./types.ts\";\nimport type { OiDailyRow } from \"./types.ts\";\nimport { listPartitionValues } from \"./coverage.ts\";\nimport {\n resolveMarketDir,\n writeOiDailyPartition,\n} from \"../../db/market-datasets.ts\";\nimport { readParquetFilesSql } from \"../../utils/quote-parquet-projection.ts\";\n\n// `right` is a reserved keyword in DuckDB (the RIGHT(string, n) function), so\n// it must be double-quoted everywhere it appears as a column identifier.\nconst OI_DAILY_COLUMNS =\n 'underlying, date, ticker, expiration, strike, \"right\", open_interest, source';\n\nfunction parseOiDailyRow(row: unknown[]): OiDailyRow {\n return {\n underlying: String(row[0]),\n date: String(row[1]),\n occ_ticker: String(row[2]),\n expiration: String(row[3]),\n strike: Number(row[4]),\n right: String(row[5]) as OiDailyRow[\"right\"],\n open_interest: Number(row[6]),\n source: row[7] == null ? null : String(row[7]),\n };\n}\n\nexport class ParquetOiDailyStore {\n protected readonly ctx: StoreContext;\n constructor(ctx: StoreContext) {\n this.ctx = ctx;\n }\n\n async writeOiDaily(\n underlying: string,\n date: string,\n rows: OiDailyRow[],\n ): Promise<void> {\n if (rows.length === 0) return;\n // Append via DuckDBAppender (typed per-column) rather than a parameterized\n // INSERT with O(N) placeholders — mirrors the quote store's write path.\n const staging = `_oi_write_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n await this.ctx.conn.run(\n `CREATE TEMP TABLE \"${staging}\" (\n underlying VARCHAR, date VARCHAR, ticker VARCHAR,\n expiration VARCHAR, strike DOUBLE, \"right\" VARCHAR,\n open_interest BIGINT, source VARCHAR\n )`,\n );\n try {\n const appender = await this.ctx.conn.createAppender(staging);\n try {\n for (const r of rows) {\n appender.appendVarchar(underlying);\n appender.appendVarchar(r.date);\n appender.appendVarchar(r.occ_ticker);\n appender.appendVarchar(r.expiration);\n appender.appendDouble(r.strike);\n appender.appendVarchar(r.right);\n appender.appendBigInt(BigInt(Math.round(r.open_interest)));\n if (r.source == null) appender.appendNull();\n else appender.appendVarchar(r.source);\n appender.endRow();\n }\n appender.flushSync();\n } finally {\n appender.closeSync();\n }\n await writeOiDailyPartition(this.ctx.conn, {\n dataDir: this.ctx.dataDir,\n underlying,\n date,\n selectQuery: `SELECT ${OI_DAILY_COLUMNS} FROM \"${staging}\"`,\n });\n } finally {\n try {\n await this.ctx.conn.run(`DROP TABLE IF EXISTS \"${staging}\"`);\n } catch {\n /* best-effort */\n }\n }\n }\n\n async readOiDaily(\n underlying: string,\n from: string,\n to: string,\n ): Promise<OiDailyRow[]> {\n const underlyingDir = path.join(\n resolveMarketDir(this.ctx.dataDir),\n \"option_oi_daily\",\n `underlying=${underlying}`,\n );\n if (!existsSync(underlyingDir)) return [];\n const files = listPartitionValues(underlyingDir, \"date\")\n .filter((date) => date >= from && date <= to)\n .map((date) => path.join(underlyingDir, `date=${date}`, \"data.parquet\"))\n .filter((filePath) => existsSync(filePath));\n if (files.length === 0) return [];\n\n const source = readParquetFilesSql(files);\n const reader = await this.ctx.conn.runAndReadAll(\n `SELECT ${OI_DAILY_COLUMNS}\n FROM ${source} AS q\n WHERE q.date >= $1\n AND q.date <= $2\n ORDER BY q.date, q.ticker`,\n [from, to] as (string | number | boolean | null | bigint)[],\n );\n return reader.getRows().map(parseOiDailyRow);\n }\n}\n","/**\n * Market Data 3.0 — Store factory + barrel re-exports.\n *\n * Phase 1: createMarketStores returned typed null-cast placeholders per\n * CONTEXT.md D-04 so the compiler-typed bundle was available before concrete\n * backends shipped.\n *\n * Phase 2 (Plan 02-05): the body below replaces that placeholder with real\n * backend dispatch per D-03. The factory reads the backend flag ONCE and\n * returns monomorphic instances; concrete method bodies must NEVER re-inspect\n * it (D-02). EnrichedStore receives the SpotStore via constructor\n * injection (D-15) so the enricher's IO boundaries (minute-bar reads,\n * watermark get/upsert) are satisfied without re-plumbing.\n *\n * Downstream consumers (Phase 2 integration tests, Phase 4 tool migrations)\n * import from this barrel so only `./index.js` depends on the concrete file\n * layout.\n */\nimport { SpotStore } from \"./spot-store.ts\";\nimport { EnrichedStore } from \"./enriched-store.ts\";\nimport { ChainStore } from \"./chain-store.ts\";\nimport { QuoteStore } from \"./quote-store.ts\";\n\n// Phase 2 concrete classes (shipped in Plans 02-03 + 02-04).\nimport { ParquetSpotStore } from \"./parquet-spot-store.ts\";\nimport { DuckdbSpotStore } from \"./duckdb-spot-store.ts\";\nimport { ParquetEnrichedStore } from \"./parquet-enriched-store.ts\";\nimport { DuckdbEnrichedStore } from \"./duckdb-enriched-store.ts\";\nimport { ParquetChainStore } from \"./parquet-chain-store.ts\";\nimport { DuckdbChainStore } from \"./duckdb-chain-store.ts\";\nimport { ParquetQuoteStore } from \"./parquet-quote-store.ts\";\nimport { DuckdbQuoteStore } from \"./duckdb-quote-store.ts\";\nimport { ParquetOiDailyStore } from \"./parquet-oi-daily-store.ts\";\n\nimport type { StoreContext } from \"./types.ts\";\n\nexport interface MarketStores {\n spot: SpotStore;\n enriched: EnrichedStore;\n chain: ChainStore;\n quote: QuoteStore;\n oiDaily: ParquetOiDailyStore;\n}\n\n/**\n * Construct a MarketStores bundle using backend-appropriate concrete classes.\n *\n * D-03: reads the backend flag once and returns monomorphic instances. The\n * concrete method bodies never re-inspect the flag (D-02).\n * D-15: EnrichedStore takes `SpotStore` via constructor injection so the\n * enricher's IO refactor (Plan 02-04) receives the right backend for minute-bar\n * reads without any separate lookup.\n */\nexport function createMarketStores(ctx: StoreContext): MarketStores {\n // Open interest is daily-granularity option market data persisted Parquet-\n // native (one row per contract per day), so the same store serves both\n // modes — it always writes/reads Hive-partitioned Parquet under the data\n // archive, like the option-quote and option-chain partitions.\n const oiDaily = new ParquetOiDailyStore(ctx);\n if (ctx.parquetMode) {\n const spot = new ParquetSpotStore(ctx);\n const enriched = new ParquetEnrichedStore(ctx, spot);\n const chain = new ParquetChainStore(ctx);\n const quote = new ParquetQuoteStore(ctx);\n return { spot, enriched, chain, quote, oiDaily };\n }\n const spot = new DuckdbSpotStore(ctx);\n const enriched = new DuckdbEnrichedStore(ctx, spot);\n const chain = new DuckdbChainStore(ctx);\n const quote = new DuckdbQuoteStore(ctx);\n return { spot, enriched, chain, quote, oiDaily };\n}\n\nexport { SpotStore, EnrichedStore, ChainStore, QuoteStore };\nexport type { StoreContext };\nexport type { EnrichedReadOpts } from \"./enriched-store.ts\";\nexport type { BarRow, ContractRow, QuoteRow, CoverageReport } from \"./types.ts\";\n","/**\n * Zod schemas for the ticker registry.\n *\n * Applied at three trust boundaries (defense in depth per RESEARCH.md Pitfall 3):\n * 1. MCP tool input boundary (schemas exported here, consumed by src/tools/tickers.ts in Plan 05)\n * 2. Registry constructor / register (registry.ts applies the same regex)\n * 3. Writer partition-value whitelist (src/db/parquet-writer.ts — Plan 03)\n */\nimport { z } from \"zod\";\n\n/**\n * Whitelist for ticker-like strings. Must START with an uppercase letter\n * (forbids \"..\" and other digit/punctuation-only inputs from validating).\n *\n * Accepts after the leading letter: uppercase A-Z, digits 0-9, and `^ _ -`\n * which appear in a handful of real-world option roots and continuous-futures\n * symbols. Forbids `.` (and therefore `..`), `/`, `\\`, whitespace, null bytes,\n * newlines, and any other non-filesystem-safe characters.\n */\nexport const TICKER_RE = /^[A-Z][A-Z0-9^_-]*$/;\n\nexport const UnderlyingsFileSchema = z.object({\n version: z.literal(1),\n underlyings: z.array(\n z.object({\n underlying: z.string().min(1).max(16).regex(TICKER_RE),\n roots: z.array(z.string().min(1).max(16).regex(TICKER_RE)).min(1).max(32),\n }),\n ),\n});\n\n// MCP tool input schemas (consumed by src/tools/tickers.ts in Plan 05).\nexport const registerUnderlyingSchema = z.object({\n underlying: z\n .string()\n .min(1)\n .max(16)\n .regex(TICKER_RE)\n .describe(\"Canonical underlying symbol, e.g. SPX\"),\n roots: z\n .array(z.string().min(1).max(16).regex(TICKER_RE))\n .min(1)\n .max(32)\n .describe(\n \"OCC roots that resolve to this underlying, e.g. ['SPX','SPXW','SPXQ']\",\n ),\n});\n\nexport const unregisterUnderlyingSchema = z.object({\n underlying: z\n .string()\n .min(1)\n .max(16)\n .regex(TICKER_RE)\n .describe(\"Underlying to remove. Bundled defaults cannot be removed.\"),\n});\n\nexport const listUnderlyingsSchema = z.object({});\n\nexport const resolveRootSchema = z.object({\n input: z\n .string()\n .min(1)\n .max(32)\n .describe(\n \"Bare root ('SPXW') or full OCC ticker ('SPXW251219C05000000')\",\n ),\n});\n","/**\n * TickerRegistry — in-memory map keyed by underlying.\n *\n * Seeded from bundled defaults (defaults.json). User-added entries via register()\n * persist to {dataRoot}/market/underlyings.json via saveUserOverride (loader.ts).\n *\n * `src/market/stores/types.ts` imports `TickerRegistry`, `TickerEntry`, and\n * `EntrySource` as types — keep those exported names stable.\n *\n * Defense-in-depth: every stored value is validated against TICKER_RE at\n * construction and at register() time. The MCP-tool layer applies the same\n * regex at the Zod boundary; the writer applies its own whitelist at the\n * partition-value boundary. Each layer is independently sufficient; together\n * they prevent malformed/injected ticker strings from reaching DuckDB.\n */\nimport { TICKER_RE } from \"./schemas.ts\";\n\nexport type EntrySource = \"default\" | \"user\" | \"user-override\";\n\nexport interface TickerEntry {\n underlying: string;\n roots: string[];\n source: EntrySource;\n}\n\nfunction validate(underlying: string, roots: string[]): void {\n if (!TICKER_RE.test(underlying)) {\n throw new Error(\n `TickerRegistry: invalid underlying \"${underlying}\" — must match ${TICKER_RE.source}`,\n );\n }\n for (const r of roots) {\n if (!TICKER_RE.test(r)) {\n throw new Error(\n `TickerRegistry: invalid root \"${r}\" — must match ${TICKER_RE.source}`,\n );\n }\n }\n}\n\nexport class TickerRegistry {\n private rootMap: Map<string, { underlying: string; source: EntrySource }> =\n new Map();\n private entries: Map<string, TickerEntry> = new Map();\n // Preserved so unregister('user-override') can revert to the bundled default.\n private readonly bundledDefaults: ReadonlyMap<string, ReadonlyArray<string>>;\n\n constructor(\n defaults: Array<{ underlying: string; roots: string[] }>,\n userOverrides: Array<{ underlying: string; roots: string[] }> = [],\n ) {\n const bundled = new Map<string, ReadonlyArray<string>>();\n for (const e of defaults) {\n validate(e.underlying, e.roots);\n bundled.set(e.underlying, [...e.roots]);\n this.entries.set(e.underlying, {\n underlying: e.underlying,\n roots: [...e.roots],\n source: \"default\",\n });\n for (const r of e.roots) {\n this.rootMap.set(r, { underlying: e.underlying, source: \"default\" });\n }\n }\n this.bundledDefaults = bundled;\n for (const e of userOverrides) {\n validate(e.underlying, e.roots);\n const source: EntrySource = bundled.has(e.underlying)\n ? \"user-override\"\n : \"user\";\n // Clear stale root mappings that previously pointed at this underlying.\n for (const [r, v] of [...this.rootMap]) {\n if (v.underlying === e.underlying) this.rootMap.delete(r);\n }\n this.entries.set(e.underlying, {\n underlying: e.underlying,\n roots: [...e.roots],\n source,\n });\n for (const r of e.roots) {\n this.rootMap.set(r, { underlying: e.underlying, source });\n }\n }\n }\n\n /**\n * Resolve a root symbol to its underlying.\n * Identity fallback (returns the root unchanged) when unknown — unknown\n * roots are treated as their own underlying so single-symbol tickers\n * (e.g. leveraged ETFs) keep working without explicit registration.\n */\n resolve(root: string): string {\n return this.rootMap.get(root)?.underlying ?? root;\n }\n\n /**\n * Add or update an underlying entry.\n * - New underlying (not a bundled default): source = \"user\"\n * - Overriding a bundled default: source = \"user-override\"\n *\n * @throws on invalid characters in `underlying` or any `root` (defense-in-depth — see file header).\n */\n register(entry: { underlying: string; roots: string[] }): TickerEntry {\n validate(entry.underlying, entry.roots);\n const isDefault = this.bundledDefaults.has(entry.underlying);\n const source: EntrySource = isDefault ? \"user-override\" : \"user\";\n // Clear stale root mappings that previously pointed at this underlying.\n for (const [r, v] of [...this.rootMap]) {\n if (v.underlying === entry.underlying) this.rootMap.delete(r);\n }\n const merged: TickerEntry = {\n underlying: entry.underlying,\n roots: [...entry.roots],\n source,\n };\n this.entries.set(entry.underlying, merged);\n for (const r of entry.roots) {\n this.rootMap.set(r, { underlying: entry.underlying, source });\n }\n return merged;\n }\n\n /**\n * Remove a user entry, or revert a user-override to its bundled default.\n * Bundled defaults cannot be removed.\n *\n * @throws on unknown underlying or when attempting to remove a bundled default.\n */\n unregister(underlying: string): void {\n const entry = this.entries.get(underlying);\n if (!entry) {\n throw new Error(\n `TickerRegistry.unregister: unknown underlying \"${underlying}\"`,\n );\n }\n if (entry.source === \"default\") {\n throw new Error(\n `TickerRegistry.unregister: cannot unregister bundled default \"${underlying}\"`,\n );\n }\n // If it was a user-override of a default, revert to the bundled default.\n if (\n entry.source === \"user-override\" &&\n this.bundledDefaults.has(underlying)\n ) {\n const defaultRoots = [...(this.bundledDefaults.get(underlying) ?? [])];\n for (const [r, v] of [...this.rootMap]) {\n if (v.underlying === underlying) this.rootMap.delete(r);\n }\n this.entries.set(underlying, {\n underlying,\n roots: defaultRoots,\n source: \"default\",\n });\n for (const r of defaultRoots) {\n this.rootMap.set(r, { underlying, source: \"default\" });\n }\n return;\n }\n // Pure user entry — remove entirely.\n this.entries.delete(underlying);\n for (const [r, v] of [...this.rootMap]) {\n if (v.underlying === underlying) this.rootMap.delete(r);\n }\n }\n\n /** Return all entries (defaults + user + user-override) as defensive copies. */\n list(): TickerEntry[] {\n return Array.from(this.entries.values()).map((e) => ({\n ...e,\n roots: [...e.roots],\n }));\n }\n\n /**\n * Serialize ONLY user + user-override entries.\n * Bundled defaults are NEVER persisted — they live in defaults.json and ship with the binary.\n */\n toJSON(): {\n version: 1;\n underlyings: Array<{ underlying: string; roots: string[] }>;\n } {\n const persisted = this.list().filter(\n (e) => e.source === \"user\" || e.source === \"user-override\",\n );\n return {\n version: 1,\n underlyings: persisted.map((e) => ({\n underlying: e.underlying,\n roots: [...e.roots],\n })),\n };\n }\n}\n","/**\n * Registry loader — bundled defaults merged with optional user override at\n * {dataRoot}/market/underlyings.json.\n *\n * Reuses writeJsonFile / readJsonFile from src/db/json-store.ts (already atomic)\n * and resolveMarketDir from src/db/market-datasets.ts (single source of truth for\n * {dataDir}/market).\n *\n * Bundled defaults loading (Plan 01-05 deviation Rule 3):\n * The previous `fs.readFileSync(new URL(\"./defaults.json\", import.meta.url))`\n * approach broke under tsup bundling — the JSON file did not get copied next\n * to the bundled `server/index.js`, causing ENOENT at server boot. The path\n * only worked under Jest because Jest reads from src/ directly.\n *\n * Using a JSON import with the `with { type: 'json' }` attribute lets esbuild\n * (via tsup) inline the JSON data into the bundle at build time — no runtime\n * filesystem dependency. Node 22+ supports import attributes natively (we run\n * on Node 23), and ts-jest passes the import attribute through unchanged.\n *\n * The parsed object is module-scope and cached for the process lifetime.\n */\nimport * as path from \"path\";\nimport { readJsonFile, writeJsonFile } from \"../../db/json-store.ts\";\nimport { resolveMarketDir } from \"../../db/market-datasets.ts\";\nimport { UnderlyingsFileSchema } from \"./schemas.ts\";\nimport { TickerRegistry } from \"./registry.ts\";\nimport defaultsData from \"./defaults.json\" with { type: \"json\" };\n\nfunction userOverridePath(dataDir: string): string {\n return path.join(resolveMarketDir(dataDir), \"underlyings.json\");\n}\n\n/**\n * Load the ticker registry.\n * - Missing user-override file is OK (returns defaults-only registry).\n * - Malformed user JSON throws a clear \"Malformed {path}: ...\" error — NO silent\n * fallback (D-08, T-1-03 mitigation).\n */\nexport async function loadRegistry(args: {\n dataDir: string;\n}): Promise<TickerRegistry> {\n const overridePath = userOverridePath(args.dataDir);\n // readJsonFile returns null on ENOENT (json-store.ts:51-61) and throws on JSON\n // parse errors. We treat both Zod rejections AND parse errors as \"Malformed\"\n // so the user sees a single, consistent error shape referencing the file path.\n let raw: unknown;\n try {\n raw = await readJsonFile<unknown>(overridePath);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(`Malformed ${overridePath}: ${detail}`);\n }\n let userEntries: Array<{ underlying: string; roots: string[] }> = [];\n if (raw !== null) {\n const parsed = UnderlyingsFileSchema.safeParse(raw);\n if (!parsed.success) {\n throw new Error(`Malformed ${overridePath}: ${parsed.error.message}`);\n }\n userEntries = parsed.data.underlyings;\n }\n return new TickerRegistry(defaultsData.underlyings, userEntries);\n}\n\n/**\n * Persist user + user-override entries to {dataRoot}/market/underlyings.json.\n * Atomic tmp-then-rename via writeJsonFile (json-store.ts:36-41).\n */\nexport async function saveUserOverride(\n dataDir: string,\n registry: TickerRegistry,\n): Promise<void> {\n const overridePath = userOverridePath(dataDir);\n await writeJsonFile(overridePath, registry.toJSON());\n}\n","{\n \"version\": 1,\n \"underlyings\": [\n { \"underlying\": \"SPX\", \"roots\": [\"SPX\", \"SPXW\", \"SPXQ\"] },\n { \"underlying\": \"QQQ\", \"roots\": [\"QQQ\", \"QQQX\"] },\n { \"underlying\": \"VIX\", \"roots\": [\"VIX\"] },\n { \"underlying\": \"VIX9D\", \"roots\": [\"VIX9D\"] },\n { \"underlying\": \"VIX3M\", \"roots\": [\"VIX3M\"] },\n { \"underlying\": \"ES\", \"roots\": [\"ES\"] },\n { \"underlying\": \"NDX\", \"roots\": [\"NDX\", \"NDXP\"] },\n { \"underlying\": \"RUT\", \"roots\": [\"RUT\", \"RUTW\"] }\n ]\n}\n","/**\n * Ticker Registry MCP Tools (Market Data 3.0)\n *\n * Four MCP tools for CRUD operations on the underlying→roots mapping registry:\n * - register_underlying Create or update a user entry; persists to\n * {dataRoot}/market/underlyings.json (atomic write).\n * - unregister_underlying Remove a user entry, or revert a user-override to\n * the bundled default. Bundled defaults cannot be removed.\n * - list_underlyings Return all entries with source annotation\n * ('default' | 'user' | 'user-override').\n * - resolve_root Debug helper: returns {root, underlying, source}\n * for any bare-root or full OCC ticker input.\n *\n * Shared code. Wired into createServer() in src/index.ts — the registry\n * itself is storage infrastructure shared between the public and consumer\n * (private) repos.\n *\n * Security (defense in depth):\n * Zod schemas from ../market/tickers/schemas.ts enforce the TICKER_RE whitelist\n * on `underlying` and each `root` BEFORE any handler runs. Layer 2 (registry\n * constructor / register) and layer 3 (writer partition-value whitelist) apply\n * the same regex at their own boundaries.\n *\n * Singleton contract:\n * The `TickerRegistry` instance is constructed ONCE in src/index.ts via\n * loadRegistry(), and the same reference is passed here AND into\n * StoreContext.tickers. Two instances would diverge on register/unregister.\n */\nimport type { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type {\n TickerRegistry,\n TickerEntry,\n} from \"../market/tickers/registry.ts\";\nimport {\n registerUnderlyingSchema,\n unregisterUnderlyingSchema,\n listUnderlyingsSchema,\n resolveRootSchema,\n} from \"../market/tickers/schemas.ts\";\nimport { saveUserOverride } from \"../market/tickers/loader.ts\";\nimport { extractRoot } from \"../market/tickers/resolver.ts\";\nimport { createToolOutput, type ToolOutput } from \"../utils/output-formatter.ts\";\n\n// ---------------------------------------------------------------------------\n// Handlers (exported for unit testing without spinning up an MCP server)\n// ---------------------------------------------------------------------------\n\n/**\n * Handle register_underlying: create or update an entry, then persist.\n * Returns the merged entry (with computed `source`) in the JSON payload.\n */\nexport async function handleRegisterUnderlying(\n input: z.infer<typeof registerUnderlyingSchema>,\n registry: TickerRegistry,\n dataDir: string,\n): Promise<ToolOutput> {\n const merged = registry.register(input);\n await saveUserOverride(dataDir, registry);\n return createToolOutput(\n `Registered underlying \"${merged.underlying}\" with ${merged.roots.length} root(s) [source=${merged.source}]`,\n { entry: merged },\n );\n}\n\n/**\n * Handle unregister_underlying: remove a user entry, or revert a user-override\n * to its bundled default. Throws clear error on bundled defaults.\n */\nexport async function handleUnregisterUnderlying(\n input: z.infer<typeof unregisterUnderlyingSchema>,\n registry: TickerRegistry,\n dataDir: string,\n): Promise<ToolOutput> {\n registry.unregister(input.underlying);\n await saveUserOverride(dataDir, registry);\n return createToolOutput(\n `Unregistered underlying \"${input.underlying}\" (or reverted to bundled default if it was a user-override)`,\n { removed: input.underlying },\n );\n}\n\n/**\n * Handle list_underlyings: return all merged entries with source annotation.\n */\nexport async function handleListUnderlyings(\n _input: z.infer<typeof listUnderlyingsSchema>,\n registry: TickerRegistry,\n): Promise<ToolOutput> {\n const entries: TickerEntry[] = registry.list();\n return createToolOutput(\n `Registry has ${entries.length} entries (defaults + user + user-override)`,\n { entries },\n );\n}\n\n/**\n * Handle resolve_root: debug helper showing how an input symbol resolves.\n * Returns {root, underlying, source} where source is one of:\n * - 'default' | 'user' | 'user-override' → matched a registry entry\n * - 'identity' → registry miss, fell back to root\n */\nexport async function handleResolveRoot(\n input: z.infer<typeof resolveRootSchema>,\n registry: TickerRegistry,\n): Promise<ToolOutput> {\n const root = extractRoot(input.input);\n const underlying = registry.resolve(root);\n // Determine source by consulting the registry's list. If the resolved entry's\n // root list contains this root, report its source; otherwise the resolve()\n // returned identity fallback (root === underlying), so source='identity'.\n const match = registry.list().find((e) => e.underlying === underlying);\n const source: \"default\" | \"user\" | \"user-override\" | \"identity\" =\n match && match.roots.includes(root) ? match.source : \"identity\";\n return createToolOutput(\n `Input \"${input.input}\" resolves: root=\"${root}\" → underlying=\"${underlying}\" [source=${source}]`,\n { root, underlying, source },\n );\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\n/**\n * Register all four ticker registry tools on the MCP server.\n *\n * @param server - McpServer instance.\n * @param registry - The SAME TickerRegistry singleton used by every other\n * consumer in the process.\n * @param dataDir - Base data directory; user override persists at\n * {dataDir}/market/underlyings.json.\n */\nexport function registerTickerTools(\n server: McpServer,\n registry: TickerRegistry,\n dataDir: string,\n): void {\n server.registerTool(\n \"register_underlying\",\n {\n description:\n \"Add or update an underlying→roots mapping in the ticker registry. \" +\n \"Persists to {dataRoot}/market/underlyings.json (user override layer; \" +\n \"bundled defaults are never modified). `underlying` is the canonical symbol \" +\n \"(e.g. SPX); `roots` are OCC roots that resolve to it (e.g. ['SPX','SPXW','SPXQ']). \" +\n \"Re-registering a bundled-default underlying creates a 'user-override' that the \" +\n \"registry uses in place of the default.\",\n inputSchema: registerUnderlyingSchema,\n },\n async (input) => handleRegisterUnderlying(input, registry, dataDir),\n );\n\n server.registerTool(\n \"unregister_underlying\",\n {\n description:\n \"Remove a user or user-override ticker entry. Bundled defaults cannot be removed; \" +\n \"removing a user-override reverts the entry back to its bundled default. \" +\n \"Persists the change to {dataRoot}/market/underlyings.json.\",\n inputSchema: unregisterUnderlyingSchema,\n },\n async (input) => handleUnregisterUnderlying(input, registry, dataDir),\n );\n\n server.registerTool(\n \"list_underlyings\",\n {\n description:\n \"List all ticker registry entries (bundled defaults + user-added + user-overrides). \" +\n \"Each entry is annotated with its source ('default' | 'user' | 'user-override'). \" +\n \"Bundled defaults ship with the binary; user entries live in {dataRoot}/market/underlyings.json.\",\n inputSchema: listUnderlyingsSchema,\n },\n async (input) => handleListUnderlyings(input, registry),\n );\n\n server.registerTool(\n \"resolve_root\",\n {\n description:\n \"Debug helper: show how a symbol resolves through the ticker registry. \" +\n \"Accepts bare roots ('SPXW') or full OCC tickers ('SPXW251219C05000000'). \" +\n \"Returns { root, underlying, source } where source is \" +\n \"'default' | 'user' | 'user-override' | 'identity'. Identity means the root \" +\n \"had no registry entry and was returned unchanged (e.g. leveraged ETFs SPXL/SPXS).\",\n inputSchema: resolveRootSchema,\n },\n async (input) => handleResolveRoot(input, registry),\n );\n}\n","/**\n * Pure SQL builder for QuoteStore reads.\n *\n * Emits a multi-ticker grouped-series read: one partition targeted by\n * underlying + date range, with an `IN (...)` filter over the OCC ticker list.\n * Callers are responsible for having validated that every OCC ticker resolves\n * to the same underlying; this builder trusts its caller on that front.\n *\n * Values are inlined as SQL literals — see `spot-sql.ts` header for the\n * extract_statements GC leak that ruled out positional parameters.\n *\n * Purity contract: pure function, no DuckDB value-level imports. Tests in\n * `tests/unit/market/stores/quote-sql.test.ts`.\n */\nimport { escapeSqlLiteral } from \"../../utils/quote-parquet-projection.ts\";\nimport type { BuiltSQL } from \"./spot-sql.ts\";\n\nfunction lit(value: string): string {\n return `'${escapeSqlLiteral(value)}'`;\n}\n\n/**\n * Build the bulk quote read. Throws if `occTickers` is empty (prevents emitting\n * an invalid `ticker IN ()` clause).\n *\n * Optional `timeStart`/`timeEnd` push an `AND time BETWEEN …` filter into SQL.\n * This is critical for prefetch where the entry-time window is often a single\n * minute: without the filter, DuckDB returns every minute bar in the\n * [from, to] range per ticker, blowing JS heap when bulking across many dates.\n */\nexport function buildReadQuotesSQL(\n underlying: string,\n occTickers: string[],\n from: string,\n to: string,\n opts?: { timeStart?: string; timeEnd?: string },\n): BuiltSQL {\n if (occTickers.length === 0) {\n throw new Error(\"buildReadQuotesSQL: occTickers must not be empty\");\n }\n const timeStart = opts?.timeStart;\n const timeEnd = opts?.timeEnd;\n const hasTimeFilter = timeStart != null && timeEnd != null;\n\n const tickerList = occTickers.map(lit).join(\", \");\n const timeClause = hasTimeFilter\n ? `AND time >= ${lit(timeStart!)} AND time <= ${lit(timeEnd!)}\\n `\n : \"\";\n return {\n sql: `SELECT ticker, date, time, bid, ask, mid, last_updated_ns,\n delta, gamma, theta, vega, iv, greeks_source, greeks_revision,\n rate_type, rate_value, gamma_source\n FROM market.option_quote_minutes\n WHERE underlying = ${lit(underlying)}\n AND date >= ${lit(from)}\n AND date <= ${lit(to)}\n ${timeClause}AND ticker IN (${tickerList})\n ORDER BY ticker, date, time`,\n };\n}\n","import type { MarketStores } from \"../stores/index.ts\";\nimport type { MarketDataProvider, BarRow, MinuteQuote } from \"../../utils/market-provider.ts\";\nimport { getProvider } from \"../../utils/market-provider.ts\";\nimport { MassiveProvider } from \"../../utils/providers/massive.ts\";\nimport { ThetaDataProvider } from \"../../utils/providers/thetadata.ts\";\nimport { extractRoot } from \"../tickers/resolver.ts\";\nimport { validateImportSelect } from \"../../tools/sql.ts\";\nimport type {\n IngestBarsOptions,\n IngestQuotesOptions,\n IngestChainOptions,\n IngestOpenInterestOptions,\n IngestFlatFileOptions,\n ComputeVixContextOptions,\n RefreshOptions,\n IngestResult,\n IngestSkippedBatch,\n IngestStatus,\n RefreshResult,\n BulkProgressReporter,\n} from \"./types.ts\";\nimport type { OiDailyRow, QuoteRow } from \"../stores/types.ts\";\nimport { applyQuoteGreeksParallel, type QuoteGreeksStats } from \"../../utils/option-quote-greeks.ts\";\n\nexport interface MarketIngestorDeps {\n stores: MarketStores;\n dataRoot: string;\n providerFactory?: () => MarketDataProvider;\n}\n\n// When `applyQuoteGreeks` fails to resolve the underlying price for more than\n// this fraction of the rows that actually attempted underlying-price lookup,\n// the batch is suspected of a coverage gap (partial-day spot bars, missing\n// chain partition, schema-filter mismatch). Such batches are dropped — they'd\n// otherwise persist with intact bid/ask but null greeks, which silently\n// corrupts the option_quotes store.\n//\n// Denominator is `missingUnderlyingRows + computedRows` — i.e. only rows that\n// reached the underlying-lookup branch in compute mode. Rows skipped earlier\n// (provider greeks already present, missing contract meta, provider-only mode)\n// don't dilute the signal. This catches a real production leak: a mixed-source\n// partition where one provider supplies inline greeks for 60% of rows and the\n// remaining 40% all fail underlying lookup on a partial-day spot outage — a\n// `missing/visited` ratio of 0.4 wouldn't trip the 0.5 threshold, but\n// `missing/attempted` is 1.0 and does.\n//\n// Tunable from telemetry: lower → more conservative (false-positives on\n// genuinely-sparse chain reads); higher → more leakage of null-greeks rows.\n// 0.5 means \"half of the rows that needed compute failed to resolve the\n// underlying price\" — conservative enough that a few unresolved rows in a\n// large compute batch don't trip it, aggressive enough that a partial-day\n// spot outage (which sends the ratio to ~1.0) is caught immediately.\nconst COVERAGE_GAP_THRESHOLD = 0.5;\n\n// Sibling of COVERAGE_GAP_THRESHOLD, distinct failure mode. Fires when\n// underlying-price lookup SUCCEEDED but `computeQuoteGreeks` returned null for\n// the majority of the rows that actually attempted the math (zero/negative\n// option price, corrupt expiration → negative DTE, malformed strike grid).\n// Without this guard those rows would mis-attribute as `coverage_gap` because\n// they all flowed into `unresolvedRows`; coverage_gap's denominator\n// (`missingUnderlyingRows + computedRows`) excludes math failures, so a\n// math-only failure mode never tripped coverage_gap by itself, but a single\n// missing-underlying row in the same partition would — labeling the trip\n// \"coverage gap\" even though the bulk of the leak was BS-math corruption.\n// Both guards can fire on the same partition when both subsets exceed their\n// thresholds independently; see types.ts on the no-dedupe convention.\n//\n// Set to 0.5 for symmetry with COVERAGE_GAP_THRESHOLD. Tunable from telemetry\n// independently — the two failure modes have different operational meanings\n// (spot/chain coverage vs. quote/chain corruption) and may want different\n// trip points later.\nconst COMPUTE_FAILURE_THRESHOLD = 0.5;\n\nfunction coverageGapEntry(\n stats: QuoteGreeksStats,\n batch: { underlying: string; date: string; ticker?: string; rows: number },\n): IngestSkippedBatch | null {\n const attemptedRows = stats.missingUnderlyingRows + stats.computedRows;\n if (attemptedRows <= 0) return null;\n const ratio = stats.missingUnderlyingRows / attemptedRows;\n if (ratio <= COVERAGE_GAP_THRESHOLD) return null;\n const message =\n `underlying-price coverage gap: ${stats.missingUnderlyingRows}/${attemptedRows} rows ` +\n `missing underlying price (ratio=${ratio.toFixed(2)}, threshold=${COVERAGE_GAP_THRESHOLD.toFixed(2)})`;\n return {\n underlying: batch.underlying,\n date: batch.date,\n ...(batch.ticker ? { ticker: batch.ticker } : {}),\n rows: batch.rows,\n reason: \"coverage_gap\",\n error: message,\n resolveRatio: ratio,\n };\n}\n\nfunction computeFailureEntry(\n stats: QuoteGreeksStats,\n batch: { underlying: string; date: string; ticker?: string; rows: number },\n): IngestSkippedBatch | null {\n const attemptedRows = stats.mathFailedRows + stats.computedRows;\n if (attemptedRows <= 0) return null;\n const ratio = stats.mathFailedRows / attemptedRows;\n if (ratio <= COMPUTE_FAILURE_THRESHOLD) return null;\n const message =\n `compute failure: ${stats.mathFailedRows}/${attemptedRows} rows failed ` +\n `black-scholes math after underlying-price lookup succeeded ` +\n `(ratio=${ratio.toFixed(2)}, threshold=${COMPUTE_FAILURE_THRESHOLD.toFixed(2)})`;\n return {\n underlying: batch.underlying,\n date: batch.date,\n ...(batch.ticker ? { ticker: batch.ticker } : {}),\n rows: batch.rows,\n reason: \"compute_failure\",\n error: message,\n resolveRatio: ratio,\n };\n}\n\nfunction providerErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Identify dates the US options market is closed. Currently weekends only —\n * holiday list is intentional TODO. ThetaData (and likely other providers)\n * return junk data on weekends (zero-priced \"quotes\" for every contract on\n * Sundays in particular), so refresh() short-circuits these dates.\n *\n * Lower-level methods (ingestBars/ingestChain/ingestQuotes) are unchanged —\n * callers needing forensic per-weekend fetches can call them directly.\n */\nfunction isNonTradingDay(asOf: string): boolean {\n // asOf is YYYY-MM-DD. Use UTC noon to avoid TZ drift across the date\n // boundary on hosts in negative-offset timezones.\n const d = new Date(`${asOf}T12:00:00Z`);\n const dow = d.getUTCDay();\n if (dow === 0 || dow === 6) return true;\n // TODO: NYSE holiday calendar (MLK, Presidents, Good Friday, Memorial,\n // Juneteenth, Independence, Labor, Thanksgiving, Christmas, New Year).\n return false;\n}\n\nfunction unsupportedProviderResult(\n provider: MarketDataProvider,\n operation: \"bars\" | \"quotes\" | \"chain\",\n target: string,\n reason: string,\n originalError: string,\n): IngestResult {\n return {\n status: \"unsupported\",\n rowsWritten: 0,\n error: `Provider ${provider.name} does not support ${operation} for ${target}: ${reason}`,\n details: {\n provider: provider.name,\n operation,\n target,\n reason,\n originalError,\n },\n };\n}\n\nexport class MarketIngestor {\n private readonly deps: MarketIngestorDeps;\n constructor(deps: MarketIngestorDeps) {\n this.deps = deps;\n }\n\n async ingestBars(opts: IngestBarsOptions): Promise<IngestResult> {\n const provider = this.resolveProvider(opts.provider);\n const timespan = opts.timespan ?? \"1d\";\n\n if (opts.dryRun) {\n return { status: \"skipped\", rowsWritten: 0, details: { reason: \"dry_run\" } };\n }\n\n let totalRows = 0;\n let minDate: string | undefined;\n let maxDate: string | undefined;\n const providerTimespan = this.timespanToProviderArgs(timespan);\n\n for (const ticker of opts.tickers) {\n const normalizedTicker = ticker.toUpperCase();\n const assetClass = this.detectAssetClass(normalizedTicker);\n const unsupported = this.preflightProviderSupport(\n provider,\n \"bars\",\n normalizedTicker,\n assetClass,\n providerTimespan,\n );\n if (unsupported) return unsupported;\n let bars: BarRow[];\n try {\n bars = await provider.fetchBars({\n ticker: normalizedTicker,\n from: opts.from,\n to: opts.to,\n ...providerTimespan,\n assetClass,\n });\n } catch (error) {\n const mapped = this.mapProviderFailure(provider, \"bars\", normalizedTicker, error, assetClass);\n if (mapped) return mapped;\n throw error;\n }\n\n if (bars.length === 0) continue;\n\n const byDate = this.groupBarsByDate(bars);\n for (const [date, dayBars] of byDate) {\n await this.deps.stores.spot.writeBars(normalizedTicker, date, dayBars);\n totalRows += dayBars.length;\n if (!minDate || date < minDate) minDate = date;\n if (!maxDate || date > maxDate) maxDate = date;\n }\n }\n\n const enrichment = opts.skipEnrichment || !minDate\n ? null\n : await this.triggerPerTickerEnrichment(opts.tickers, minDate, maxDate!);\n\n return {\n status: \"ok\",\n rowsWritten: totalRows,\n dateRange: minDate ? { from: minDate, to: maxDate! } : undefined,\n enrichment,\n };\n }\n\n private resolveProvider(override?: \"massive\" | \"thetadata\"): MarketDataProvider {\n // Priority: per-call override > injected factory > env-driven singleton.\n if (override) {\n return override === \"thetadata\" ? new ThetaDataProvider() : new MassiveProvider();\n }\n if (this.deps.providerFactory) return this.deps.providerFactory();\n return getProvider();\n }\n\n private timespanToProviderArgs(timespan: string): { timespan: \"day\" | \"minute\" | \"hour\"; multiplier: number } {\n switch (timespan) {\n case \"1d\": return { timespan: \"day\", multiplier: 1 };\n case \"1m\": return { timespan: \"minute\", multiplier: 1 };\n case \"5m\": return { timespan: \"minute\", multiplier: 5 };\n case \"15m\": return { timespan: \"minute\", multiplier: 15 };\n case \"1h\": return { timespan: \"hour\", multiplier: 1 };\n default: throw new Error(`Unknown timespan: ${timespan}`);\n }\n }\n\n private detectAssetClass(ticker: string): \"stock\" | \"index\" | \"option\" {\n const VIX_FAMILY = new Set([\"VIX\", \"VIX9D\", \"VIX1D\", \"VIX3M\", \"VXN\", \"SPX\", \"NDX\"]);\n if (VIX_FAMILY.has(ticker)) return \"index\";\n if (/^[A-Z]{1,6}\\d{6}[CP]\\d{8}$/.test(ticker)) return \"option\";\n return \"stock\";\n }\n\n private groupBarsByDate(bars: BarRow[]): Map<string, BarRow[]> {\n const map = new Map<string, BarRow[]>();\n for (const bar of bars) {\n const list = map.get(bar.date) ?? [];\n list.push(bar);\n map.set(bar.date, list);\n }\n return map;\n }\n\n private async triggerPerTickerEnrichment(\n tickers: string[],\n from: string,\n to: string,\n ): Promise<{ from: string; to: string }> {\n for (const ticker of tickers) {\n await this.deps.stores.enriched.compute(ticker.toUpperCase(), from, to);\n }\n return { from, to };\n }\n\n private mapProviderFailure(\n provider: MarketDataProvider,\n operation: \"bars\" | \"quotes\" | \"chain\",\n target: string,\n error: unknown,\n assetClass?: \"stock\" | \"index\" | \"option\",\n ): IngestResult | null {\n const message = providerErrorMessage(error);\n if (\n provider.name === \"massive\" &&\n assetClass === \"index\" &&\n /(TimeoutError|aborted due to timeout|timed out)/i.test(message)\n ) {\n const reason = operation === \"chain\"\n ? \"current Massive provider path does not reliably support index option-chain refreshes\"\n : \"current Massive provider path does not reliably support index data for this request\";\n return unsupportedProviderResult(provider, operation, target, reason, message);\n }\n if (provider.name === \"massive\" && /HTTP 403 Forbidden/.test(message)) {\n const reason = assetClass === \"index\"\n ? \"current Massive account/tier does not permit index data for this request\"\n : \"current Massive account/tier does not permit this request\";\n return unsupportedProviderResult(provider, operation, target, reason, message);\n }\n return null;\n }\n\n private preflightProviderSupport(\n provider: MarketDataProvider,\n operation: \"bars\" | \"quotes\" | \"chain\",\n target: string,\n assetClass?: \"stock\" | \"index\" | \"option\",\n barRequest?: { timespan: \"day\" | \"minute\" | \"hour\"; multiplier: number },\n ): IngestResult | null {\n if (operation === \"bars\" && barRequest) {\n const caps = provider.capabilities();\n if (barRequest.timespan === \"day\" && !caps.dailyBars) {\n return unsupportedProviderResult(\n provider,\n operation,\n target,\n \"provider capabilities report it does not support daily bars\",\n \"preflight: dailyBars=false\",\n );\n }\n if (barRequest.timespan !== \"day\" && (!caps.tradeBars || !caps.minuteBars)) {\n return unsupportedProviderResult(\n provider,\n operation,\n target,\n \"provider capabilities report it does not support minute bars\",\n `preflight: tradeBars=${String(caps.tradeBars)} minuteBars=${String(caps.minuteBars)}`,\n );\n }\n }\n\n if (provider.name === \"massive\" && assetClass === \"index\") {\n if (operation === \"bars\") {\n return unsupportedProviderResult(\n provider,\n operation,\n target,\n \"current Massive provider path does not support index bar refreshes for this underlying\",\n \"preflight: index bar refreshes are disabled for Massive\",\n );\n }\n if (operation === \"chain\") {\n return unsupportedProviderResult(\n provider,\n operation,\n target,\n \"current Massive provider path does not support index option-chain refreshes for this underlying\",\n \"preflight: index option-chain refreshes are disabled for Massive\",\n );\n }\n }\n return null;\n }\n\n private async applyCoverageFallback(\n dataset: \"spot\" | \"chain\",\n symbol: string,\n asOf: string,\n result: IngestResult,\n ): Promise<IngestResult> {\n if (result.status !== \"unsupported\" && result.status !== \"error\") {\n return result;\n }\n\n try {\n const coverage = dataset === \"spot\"\n ? await this.deps.stores.spot.getCoverage(symbol.toUpperCase(), asOf, asOf)\n : await this.deps.stores.chain.getCoverage(symbol.toUpperCase(), asOf, asOf);\n if (coverage.totalDates <= 0) return result;\n\n return {\n status: \"skipped\",\n rowsWritten: 0,\n dateRange: { from: asOf, to: asOf },\n details: {\n ...(result.details ?? {}),\n reason: \"using_cached_coverage\",\n dataset,\n symbol: symbol.toUpperCase(),\n originalStatus: result.status,\n cachedCoverage: {\n totalDates: coverage.totalDates,\n earliest: coverage.earliest,\n latest: coverage.latest,\n },\n },\n };\n } catch {\n return result;\n }\n }\n\n private quoteGreeksSourceForProvider(\n provider: MarketDataProvider,\n ): \"massive\" | \"thetadata\" | undefined {\n if (provider.name === \"massive\" || provider.name === \"thetadata\") {\n return provider.name;\n }\n return undefined;\n }\n\n private async enrichQuoteRows(\n underlying: string,\n date: string,\n rows: QuoteRow[],\n defaultProviderSource?: \"massive\" | \"thetadata\",\n ): Promise<{ rows: QuoteRow[]; stats: QuoteGreeksStats }> {\n if (rows.length === 0) {\n return {\n rows,\n stats: {\n rowsVisited: 0,\n existingGreeksRows: 0,\n computedRows: 0,\n missingContractRows: 0,\n missingUnderlyingRows: 0,\n mathFailedRows: 0,\n unresolvedRows: 0,\n },\n };\n }\n\n const [contracts, underlyingBars] = await Promise.all([\n this.deps.stores.chain.readChain(underlying, date),\n this.deps.stores.spot.readBars(underlying, date, date),\n ]);\n\n const contractByTicker = new Map(\n contracts.map((contract) => [contract.ticker, contract] as const),\n );\n const underlyingPriceByTime = new Map<string, number>();\n for (const bar of underlyingBars) {\n if (bar.date !== date || !bar.time || !(bar.open > 0)) continue;\n const time = bar.time.slice(0, 5);\n if (!underlyingPriceByTime.has(time)) {\n underlyingPriceByTime.set(time, bar.open);\n }\n }\n\n const stats = await applyQuoteGreeksParallel({\n rows,\n getDate: (row) => row.timestamp.slice(0, 10),\n getTime: (row) => row.timestamp.slice(11, 16),\n getMid: (row) => (row.bid + row.ask) / 2,\n getContractMeta: (row) => {\n const contract = contractByTicker.get(row.occ_ticker);\n if (!contract) return undefined;\n return {\n contract_type: contract.contract_type,\n strike: contract.strike,\n expiration: contract.expiration,\n };\n },\n getUnderlyingPrice: (_rowDate, time) => underlyingPriceByTime.get(time),\n mode: \"auto\",\n defaultProviderSource,\n });\n\n return { rows, stats };\n }\n\n async ingestQuotes(opts: IngestQuotesOptions): Promise<IngestResult> {\n const hasTickers = opts.tickers && opts.tickers.length > 0;\n const hasUnderlyings = opts.underlyings && opts.underlyings.length > 0;\n if (hasTickers === hasUnderlyings) {\n return {\n status: \"error\",\n rowsWritten: 0,\n error: \"ingestQuotes requires exactly one of { tickers, underlyings } to be non-empty\",\n };\n }\n\n const provider = this.resolveProvider(opts.provider);\n\n // Dispatch decisions live on the per-mode paths below — the per-ticker\n // path gates on `typeof provider.fetchQuotes === \"function\"` (line ~377);\n // the bulk-by-underlying path gates on `caps.bulkByRoot` + `fetchBulkQuotes`\n // (line ~429). A unified `caps.quotes` gate would (a) reject Massive on\n // Developer plan even though its fetchQuotes has tier-aware fallback, and\n // (b) reject any future bulk-only provider that doesn't implement the\n // per-ticker fetchQuotes method. Provenance — \"is this NBBO-grade?\" — is\n // captured per-row via the QuoteRow.source column.\n\n if (opts.dryRun) {\n return { status: \"skipped\", rowsWritten: 0, details: { reason: \"dry_run\" } };\n }\n\n return hasUnderlyings\n ? this.ingestQuotesByUnderlying(\n provider,\n opts.underlyings!,\n opts.from,\n opts.to,\n opts.onProgress,\n )\n : this.ingestQuotesByTicker(provider, opts.tickers!, opts.from, opts.to);\n }\n\n /**\n * Per-ticker path: one provider call per OCC ticker over the full [from, to]\n * range. Works on any provider that implements `fetchQuotes`. Used by\n * callers that already know the exact contracts they care about.\n */\n private async ingestQuotesByTicker(\n provider: MarketDataProvider,\n tickers: string[],\n from: string,\n to: string,\n ): Promise<IngestResult> {\n if (typeof provider.fetchQuotes !== \"function\") {\n return {\n status: \"unsupported\",\n rowsWritten: 0,\n error: `Provider ${provider.name} does not implement fetchQuotes (per-ticker path)`,\n };\n }\n\n let totalRows = 0;\n let minDate: string | undefined;\n let maxDate: string | undefined;\n const skipped: IngestSkippedBatch[] = [];\n\n for (const ticker of tickers) {\n let quotes: Awaited<ReturnType<NonNullable<MarketDataProvider[\"fetchQuotes\"]>>>;\n try {\n quotes = await provider.fetchQuotes(ticker, from, to);\n } catch (error) {\n const mapped = this.mapProviderFailure(provider, \"quotes\", ticker, error, \"option\");\n if (mapped) return mapped;\n throw error;\n }\n const written = await this.writeQuotesForTicker(\n provider,\n ticker,\n quotes,\n );\n totalRows += written.rowsWritten;\n if (written.minDate && (!minDate || written.minDate < minDate)) minDate = written.minDate;\n if (written.maxDate && (!maxDate || written.maxDate > maxDate)) maxDate = written.maxDate;\n if (written.skipped.length > 0) skipped.push(...written.skipped);\n }\n\n return {\n status: skipped.length > 0 ? \"partial\" : \"ok\",\n rowsWritten: totalRows,\n dateRange: minDate ? { from: minDate, to: maxDate! } : undefined,\n ...(skipped.length > 0 ? { skipped } : {}),\n };\n }\n\n /**\n * Bulk path: provider-specific full-chain quote fetch for each\n * (underlying, date). ThetaData MDDS uses bounded per-contract batches;\n * other providers may use different bulk shapes. Capability-gated on\n * `bulkByRoot` + presence of `fetchBulkQuotes`.\n */\n private async ingestQuotesByUnderlying(\n provider: MarketDataProvider,\n underlyings: string[],\n from: string,\n to: string,\n onProgress?: BulkProgressReporter,\n ): Promise<IngestResult> {\n const caps = provider.capabilities();\n if (!caps.bulkByRoot || typeof provider.fetchBulkQuotes !== \"function\") {\n return {\n status: \"unsupported\",\n rowsWritten: 0,\n error: `Provider ${provider.name} does not support bulk-by-underlying quotes (capability.bulkByRoot=${caps.bulkByRoot})`,\n };\n }\n\n const dates = this.enumerateDates(from, to);\n let totalRows = 0;\n let minDate: string | undefined;\n let maxDate: string | undefined;\n const skipped: IngestSkippedBatch[] = [];\n\n for (const underlying of underlyings) {\n const upperUnderlying = underlying.toUpperCase();\n for (const date of dates) {\n const drain = await this.drainBulkQuotes(\n provider,\n upperUnderlying,\n date,\n onProgress,\n );\n if (drain.rowsWritten > 0) {\n totalRows += drain.rowsWritten;\n if (!minDate || date < minDate) minDate = date;\n if (!maxDate || date > maxDate) maxDate = date;\n }\n if (drain.skipped.length > 0) skipped.push(...drain.skipped);\n // Always emit a date-flushed event — even on 0 rows — so callers see\n // predictable progress even for empty dates (holidays, missing data).\n await this.safeEmit(onProgress, {\n kind: \"date-flushed\",\n underlying: upperUnderlying,\n date,\n rowsWritten: drain.rowsWritten,\n });\n }\n }\n\n return {\n status: skipped.length > 0 ? \"partial\" : \"ok\",\n rowsWritten: totalRows,\n dateRange: minDate ? { from: minDate, to: maxDate! } : undefined,\n ...(skipped.length > 0 ? { skipped } : {}),\n };\n }\n\n /**\n * Invoke a progress reporter without ever letting its errors fail the\n * ingest. Awaits promise-returning reporters so async back-pressure works.\n */\n private async safeEmit(\n reporter: BulkProgressReporter | undefined,\n event: Parameters<BulkProgressReporter>[0],\n ): Promise<void> {\n if (!reporter) return;\n try {\n await reporter(event);\n } catch {\n // best-effort: progress must never fail the ingest\n }\n }\n\n /**\n * Consume the entire bulk-quote stream for one (underlying, date) and write\n * once at the end. Mid-stream flushing is NOT safe here: `writeQuotes` ->\n * `writeParquetAtomic` performs `COPY ... TO '<partitionFile>'` which\n * *overwrites* the partition — splitting one day into multiple writes would\n * leave only the final flush on disk. Peak heap is ~O(rows × row-size) per\n * underlying per day (~700MB for a full SPX day), which matches what the\n * old wildcard-bulk drain script ran at before it was retired.\n */\n private async drainBulkQuotes(\n provider: MarketDataProvider,\n upperUnderlying: string,\n date: string,\n onProgress?: BulkProgressReporter,\n ): Promise<{ rowsWritten: number; skipped: IngestSkippedBatch[] }> {\n // Tickers → resolved underlying mapping is cached per call; the typical\n // case is all contracts mapping to the same underlying (e.g. SPX + SPXW\n // → \"SPX\"), so the per-underlying bucket is almost always a single key.\n const tickerRegistry = this.deps.stores.quote.tickers;\n // Resolve the request underlying through the same registry so rows are\n // compared against the canonical target — lets callers pass a root like\n // \"SPXW\" and still have rows resolving to \"SPX\" match correctly.\n const expectedUnderlying = tickerRegistry.resolve(upperUnderlying);\n const resolvedCache = new Map<string, string>();\n const bucket = new Map<string, QuoteRow[]>();\n\n // Build the per-(root,right) completion hook that fans provider-side\n // group completions out to the transport-aware reporter. The provider\n // wraps the invocation in its own try/catch so an upstream reporter\n // throw can't corrupt the stream — we still wrap here as a second\n // safety net (in case the provider forgets).\n const onGroupComplete = onProgress\n ? (info: {\n root: string;\n right: \"call\" | \"put\";\n date: string;\n status: \"ok\" | \"error\";\n phase?: \"checkpoint\" | \"complete\";\n completedContracts?: number;\n totalContracts?: number;\n }) => {\n // Fire-and-forget — provider callsite is synchronous; async work\n // is scheduled on the next microtask. Any failure is swallowed by\n // safeEmit so progress remains best-effort.\n void this.safeEmit(onProgress, {\n kind: \"group\",\n underlying: upperUnderlying,\n root: info.root,\n right: info.right,\n date: info.date,\n status: info.status,\n phase: info.phase,\n completedContracts: info.completedContracts,\n totalContracts: info.totalContracts,\n });\n }\n : undefined;\n\n const stream = provider.fetchBulkQuotes!({ underlying: upperUnderlying, date, onGroupComplete });\n for await (const chunk of stream) {\n for (const row of chunk) {\n const root = extractRoot(row.ticker);\n let resolvedUnderlying = resolvedCache.get(root);\n if (resolvedUnderlying === undefined) {\n resolvedUnderlying = tickerRegistry.resolve(root);\n resolvedCache.set(root, resolvedUnderlying);\n }\n // Defense-in-depth: a row cannot silently land in a different underlying\n // than the one we requested. This trips when extractRoot fails to parse\n // a non-standard ticker format and the identity-fallback returns the raw\n // ticker as a \"root\" that then identity-resolves to itself. Without this\n // guard, 68 partitions on 2024-07-09 leaked into underlying=SPX<OCC>/\n // folders (see resolver.ts OCC_RE for the regex that must stay in sync).\n if (resolvedUnderlying !== expectedUnderlying) {\n throw new Error(\n `[drainBulkQuotes] root resolution mismatch: row.ticker=\"${row.ticker}\" ` +\n `extractedRoot=\"${root}\" resolvedUnderlying=\"${resolvedUnderlying}\" ` +\n `expectedUnderlying=\"${expectedUnderlying}\" (request underlying=\"${upperUnderlying}\", date=\"${date}\"). ` +\n `A row must resolve to the same underlying as the ingest request. ` +\n `If this ticker format is legitimate, extend OCC_RE in resolver.ts to parse it.`,\n );\n }\n let list = bucket.get(resolvedUnderlying);\n if (!list) {\n list = [];\n bucket.set(resolvedUnderlying, list);\n }\n list.push({\n occ_ticker: row.ticker,\n timestamp: row.timestamp,\n bid: row.bid,\n ask: row.ask,\n delta: row.delta ?? null,\n gamma: row.gamma ?? null,\n theta: row.theta ?? null,\n vega: row.vega ?? null,\n iv: row.iv ?? null,\n greeks_source: row.greeks_source ?? null,\n greeks_revision: row.greeks_revision ?? null,\n rate_type: row.rate_type ?? null,\n rate_value: row.rate_value ?? null,\n gamma_source: row.gamma_source ?? null,\n });\n }\n }\n\n let totalRows = 0;\n const skipped: IngestSkippedBatch[] = [];\n for (const [resolvedUnderlying, rows] of bucket) {\n if (rows.length === 0) continue;\n let enriched: { rows: QuoteRow[]; stats: QuoteGreeksStats };\n try {\n enriched = await this.enrichQuoteRows(\n resolvedUnderlying,\n date,\n rows,\n this.quoteGreeksSourceForProvider(provider),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // Warn is still emitted for live tail-following — the load-bearing\n // signal is `result.skipped[]` / `status: \"partial\"`.\n console.warn(\n \"[drainBulkQuotes] enrichQuoteRows failed; skipping batch\",\n {\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n error: message,\n },\n );\n skipped.push({\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n reason: \"read_failed\",\n error: message,\n });\n continue;\n }\n const gap = coverageGapEntry(enriched.stats, {\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n });\n const computeFailure = computeFailureEntry(enriched.stats, {\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n });\n if (gap) {\n console.warn(\"[drainBulkQuotes] coverage gap; skipping batch\", {\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n resolveRatio: gap.resolveRatio,\n });\n skipped.push(gap);\n }\n if (computeFailure) {\n console.warn(\"[drainBulkQuotes] compute failure; skipping batch\", {\n underlying: resolvedUnderlying,\n date,\n rows: rows.length,\n resolveRatio: computeFailure.resolveRatio,\n });\n skipped.push(computeFailure);\n }\n if (gap || computeFailure) continue;\n await this.deps.stores.quote.writeQuotes(resolvedUnderlying, date, enriched.rows);\n totalRows += rows.length;\n }\n return { rowsWritten: totalRows, skipped };\n }\n\n private async writeQuotesForTicker(\n provider: MarketDataProvider,\n ticker: string,\n quotes: Map<string, MinuteQuote>,\n ): Promise<{ rowsWritten: number; minDate?: string; maxDate?: string; skipped: IngestSkippedBatch[] }> {\n const root = extractRoot(ticker);\n const underlying = this.deps.stores.quote.tickers.resolve(root);\n\n const byDate = new Map<string, QuoteRow[]>();\n for (const [key, quote] of quotes) {\n const spaceIdx = key.indexOf(\" \");\n if (spaceIdx === -1) continue;\n const date = key.slice(0, spaceIdx);\n const list = byDate.get(date) ?? [];\n list.push({\n occ_ticker: ticker,\n timestamp: key,\n bid: quote.bid,\n ask: quote.ask,\n source: quote.source ?? null,\n delta: quote.delta ?? null,\n gamma: quote.gamma ?? null,\n theta: quote.theta ?? null,\n vega: quote.vega ?? null,\n iv: quote.iv ?? null,\n greeks_source: quote.greeks_source ?? null,\n greeks_revision: quote.greeks_revision ?? null,\n rate_type: quote.rate_type ?? null,\n rate_value: quote.rate_value ?? null,\n gamma_source: quote.gamma_source ?? null,\n });\n byDate.set(date, list);\n }\n\n let rowsWritten = 0;\n let minDate: string | undefined;\n let maxDate: string | undefined;\n const skipped: IngestSkippedBatch[] = [];\n for (const [date, rows] of byDate) {\n let enriched: { rows: QuoteRow[]; stats: QuoteGreeksStats };\n try {\n enriched = await this.enrichQuoteRows(\n underlying,\n date,\n rows,\n this.quoteGreeksSourceForProvider(provider),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // Warn is still emitted for live tail-following — the load-bearing\n // signal is `result.skipped[]` / `status: \"partial\"`.\n console.warn(\n \"[writeQuotesForTicker] enrichQuoteRows failed; skipping batch\",\n {\n underlying,\n date,\n ticker,\n rows: rows.length,\n error: message,\n },\n );\n skipped.push({\n underlying,\n date,\n ticker,\n rows: rows.length,\n reason: \"read_failed\",\n error: message,\n });\n continue;\n }\n const gap = coverageGapEntry(enriched.stats, {\n underlying,\n date,\n ticker,\n rows: rows.length,\n });\n const computeFailure = computeFailureEntry(enriched.stats, {\n underlying,\n date,\n ticker,\n rows: rows.length,\n });\n if (gap) {\n console.warn(\"[writeQuotesForTicker] coverage gap; skipping batch\", {\n underlying,\n date,\n ticker,\n rows: rows.length,\n resolveRatio: gap.resolveRatio,\n });\n skipped.push(gap);\n }\n if (computeFailure) {\n console.warn(\"[writeQuotesForTicker] compute failure; skipping batch\", {\n underlying,\n date,\n ticker,\n rows: rows.length,\n resolveRatio: computeFailure.resolveRatio,\n });\n skipped.push(computeFailure);\n }\n if (gap || computeFailure) continue;\n await this.deps.stores.quote.writeQuotes(underlying, date, enriched.rows);\n rowsWritten += rows.length;\n if (!minDate || date < minDate) minDate = date;\n if (!maxDate || date > maxDate) maxDate = date;\n }\n return { rowsWritten, minDate, maxDate, skipped };\n }\n\n async ingestChain(opts: IngestChainOptions): Promise<IngestResult> {\n const provider = this.resolveProvider(opts.provider);\n\n if (typeof provider.fetchContractList !== \"function\") {\n return {\n status: \"unsupported\",\n rowsWritten: 0,\n error: `Provider ${provider.name} does not support fetchContractList`,\n };\n }\n\n if (opts.dryRun) {\n return { status: \"skipped\", rowsWritten: 0, details: { reason: \"dry_run\" } };\n }\n\n let totalRows = 0;\n\n for (const underlying of opts.underlyings) {\n const upperUnderlying = underlying.toUpperCase();\n const assetClass = this.detectAssetClass(upperUnderlying);\n const unsupported = this.preflightProviderSupport(provider, \"chain\", upperUnderlying, assetClass);\n if (unsupported) return unsupported;\n // Enumerate trading dates in [from, to] and fetch the chain as-of each date.\n const dates = this.enumerateDates(opts.from, opts.to);\n for (const date of dates) {\n let result: Awaited<ReturnType<NonNullable<MarketDataProvider[\"fetchContractList\"]>>>;\n try {\n result = await provider.fetchContractList!({\n underlying: upperUnderlying,\n as_of: date,\n expired: true,\n });\n } catch (error) {\n const mapped = this.mapProviderFailure(provider, \"chain\", upperUnderlying, error, assetClass);\n if (mapped) return mapped;\n throw error;\n }\n\n if (result.contracts.length === 0) continue;\n\n // Map ContractReference → ContractRow (add underlying, date, compute dte).\n const rows = result.contracts.map((c) => ({\n underlying: upperUnderlying,\n date,\n ticker: c.ticker,\n contract_type: c.contract_type,\n strike: c.strike,\n expiration: c.expiration,\n dte: this.computeDte(date, c.expiration),\n exercise_style: c.exercise_style,\n }));\n\n await this.deps.stores.chain.writeChain(upperUnderlying, date, rows);\n totalRows += rows.length;\n }\n }\n\n return {\n status: \"ok\",\n rowsWritten: totalRows,\n dateRange: { from: opts.from, to: opts.to },\n };\n }\n\n async ingestOpenInterest(opts: IngestOpenInterestOptions): Promise<IngestResult> {\n const provider = this.resolveProvider(opts.provider);\n\n const caps = provider.capabilities();\n if (!caps.bulkByRoot || typeof provider.fetchOpenInterest !== \"function\") {\n return {\n status: \"unsupported\",\n rowsWritten: 0,\n error: `Provider ${provider.name} does not support open-interest ingest (capability.bulkByRoot=${caps.bulkByRoot})`,\n };\n }\n\n if (opts.dryRun) {\n return { status: \"skipped\", rowsWritten: 0, details: { reason: \"dry_run\" } };\n }\n\n const tickerRegistry = this.deps.stores.quote.tickers;\n let totalRows = 0;\n let minDate: string | undefined;\n let maxDate: string | undefined;\n\n for (const underlying of opts.underlyings) {\n const upperUnderlying = underlying.toUpperCase();\n let oiRows: Awaited<ReturnType<NonNullable<MarketDataProvider[\"fetchOpenInterest\"]>>>;\n try {\n oiRows = await provider.fetchOpenInterest!({\n underlying: upperUnderlying,\n from: opts.from,\n to: opts.to,\n });\n } catch (error) {\n const mapped = this.mapProviderFailure(provider, \"chain\", upperUnderlying, error, \"option\");\n if (mapped) return mapped;\n throw error;\n }\n\n // Bucket by (resolved underlying, date) — one partition per pair, matching\n // the option_oi_daily/underlying=X/date=Y layout.\n const byPartition = new Map<string, OiDailyRow[]>();\n const resolvedCache = new Map<string, string>();\n for (const row of oiRows) {\n const root = extractRoot(row.ticker);\n let resolved = resolvedCache.get(root);\n if (resolved === undefined) {\n resolved = tickerRegistry.resolve(root);\n resolvedCache.set(root, resolved);\n }\n const key = `${resolved}\u0000${row.date}`;\n let list = byPartition.get(key);\n if (!list) {\n list = [];\n byPartition.set(key, list);\n }\n list.push({\n occ_ticker: row.ticker,\n underlying: resolved,\n date: row.date,\n expiration: row.expiration,\n strike: row.strike,\n right: row.right,\n open_interest: row.open_interest,\n source: provider.name,\n });\n }\n\n for (const [key, rows] of byPartition) {\n if (rows.length === 0) continue;\n const [resolved, date] = key.split(\"\u0000\");\n await this.deps.stores.oiDaily.writeOiDaily(resolved, date, rows);\n totalRows += rows.length;\n if (!minDate || date < minDate) minDate = date;\n if (!maxDate || date > maxDate) maxDate = date;\n }\n }\n\n return {\n status: \"ok\",\n rowsWritten: totalRows,\n dateRange: minDate ? { from: minDate, to: maxDate! } : { from: opts.from, to: opts.to },\n };\n }\n\n private enumerateDates(from: string, to: string): string[] {\n const dates: string[] = [];\n const current = new Date(`${from}T12:00:00Z`);\n const end = new Date(`${to}T12:00:00Z`);\n while (current <= end) {\n const y = current.getUTCFullYear();\n const m = String(current.getUTCMonth() + 1).padStart(2, \"0\");\n const d = String(current.getUTCDate()).padStart(2, \"0\");\n dates.push(`${y}-${m}-${d}`);\n current.setUTCDate(current.getUTCDate() + 1);\n }\n return dates;\n }\n\n private computeDte(asOf: string, expiration: string): number {\n const msPerDay = 86_400_000;\n const asOfMs = new Date(`${asOf}T12:00:00Z`).getTime();\n const expMs = new Date(`${expiration}T12:00:00Z`).getTime();\n return Math.max(0, Math.round((expMs - asOfMs) / msPerDay));\n }\n\n /**\n * Generic flat-file ingest — the LLM is the parser.\n *\n * The caller (typically an LLM that has sniffed the file via run_sql +\n * read_parquet/read_csv and compared columns to describe_database) supplies:\n * - filePath: local path to a file DuckDB can read\n * - datasetType: which store to write to\n * - selectSql: a SELECT that produces the store's canonical columns\n * - partition: the target (ticker/underlying, date) partition\n *\n * No provider is called — downloads happen beforehand (via the provider's\n * own tools, rclone, or the user pasting a file). The store's writeFromSelect\n * handles mode-routing (Parquet COPY vs DuckDB INSERT) so this dispatch\n * layer stays provider-agnostic and format-agnostic.\n */\n async ingestFlatFile(opts: IngestFlatFileOptions): Promise<IngestResult> {\n const selectError = validateImportSelect(opts.selectSql);\n if (selectError) {\n return { status: \"error\", rowsWritten: 0, error: selectError };\n }\n\n if (opts.dryRun) {\n return { status: \"skipped\", rowsWritten: 0, details: { reason: \"dry_run\" } };\n }\n\n const partitionDate = opts.partition.date;\n if (!partitionDate) {\n return { status: \"error\", rowsWritten: 0, error: \"partition.date is required\" };\n }\n\n try {\n switch (opts.datasetType) {\n case \"spot_bars\": {\n const ticker = opts.partition.ticker;\n if (!ticker) {\n return { status: \"error\", rowsWritten: 0, error: \"partition.ticker is required for datasetType='spot_bars'\" };\n }\n const { rowCount } = await this.deps.stores.spot.writeFromSelect(\n { ticker: ticker.toUpperCase(), date: partitionDate },\n opts.selectSql,\n );\n return { status: \"ok\", rowsWritten: rowCount, dateRange: { from: partitionDate, to: partitionDate } };\n }\n case \"option_quotes\": {\n const underlying = opts.partition.underlying;\n if (!underlying) {\n return { status: \"error\", rowsWritten: 0, error: \"partition.underlying is required for datasetType='option_quotes'\" };\n }\n const { rowCount } = await this.deps.stores.quote.writeFromSelect(\n { underlying: underlying.toUpperCase(), date: partitionDate },\n opts.selectSql,\n );\n return { status: \"ok\", rowsWritten: rowCount, dateRange: { from: partitionDate, to: partitionDate } };\n }\n case \"option_chain\": {\n const underlying = opts.partition.underlying;\n if (!underlying) {\n return { status: \"error\", rowsWritten: 0, error: \"partition.underlying is required for datasetType='option_chain'\" };\n }\n const { rowCount } = await this.deps.stores.chain.writeFromSelect(\n { underlying: underlying.toUpperCase(), date: partitionDate },\n opts.selectSql,\n );\n return { status: \"ok\", rowsWritten: rowCount, dateRange: { from: partitionDate, to: partitionDate } };\n }\n default: {\n const _exhaustive: never = opts.datasetType;\n return { status: \"error\", rowsWritten: 0, error: `Unknown datasetType: ${String(_exhaustive)}` };\n }\n }\n } catch (err) {\n return { status: \"error\", rowsWritten: 0, error: err instanceof Error ? err.message : String(err) };\n }\n }\n\n async computeVixContext(opts: ComputeVixContextOptions): Promise<IngestResult> {\n // Pure read-from-cache + compute + write. No provider call.\n await this.deps.stores.enriched.computeContext(opts.from, opts.to);\n return {\n status: \"ok\",\n rowsWritten: 0,\n dateRange: { from: opts.from, to: opts.to },\n };\n }\n\n async refresh(opts: RefreshOptions): Promise<RefreshResult> {\n // Short-circuit non-trading days. Provider behavior on weekends is\n // inconsistent — Saturday returns nothing (good), Sunday returns the\n // prior trading day's chain plus zero-priced quote rows (junk that\n // pollutes the parquet store). Refuse to write anything when the date\n // isn't a US trading day. See isNonTradingDay() above for scope.\n if (isNonTradingDay(opts.asOf)) {\n return {\n status: \"skipped\",\n perOperation: { spot: [], chain: [], quotes: [], openInterest: [], vixContext: null },\n coverage: {},\n errors: [],\n };\n }\n\n const VIX_FAMILY = new Set([\"VIX\", \"VIX9D\", \"VIX3M\", \"VXN\"]);\n const computeCtxFlag = opts.computeVixContext ?? true;\n const errors: string[] = [];\n\n // Step 1 — spot ingest per ticker (asOf = from = to).\n // Always request minute-resolution bars: downstream option-quote enrichment\n // needs the per-minute underlying price to compute greeks and to align\n // quote rows. A daily bar (single row at implicit 09:30) leaves every\n // minute after 09:30 without an underlying-price lookup and trips the\n // coverage_gap guard for the whole partition.\n const spotResults: IngestResult[] = [];\n for (const ticker of opts.spotTickers) {\n const rawResult = await this.ingestBars({\n tickers: [ticker],\n from: opts.asOf,\n to: opts.asOf,\n timespan: \"1m\",\n provider: opts.provider,\n });\n const result = await this.applyCoverageFallback(\"spot\", ticker, opts.asOf, rawResult);\n spotResults.push(result);\n if (result.status === \"error\") errors.push(`spot ${ticker}: ${result.error}`);\n }\n\n // Step 2 — chain ingest per underlying\n const chainResults: IngestResult[] = [];\n for (const underlying of opts.chainUnderlyings ?? []) {\n const rawResult = await this.ingestChain({\n underlyings: [underlying],\n from: opts.asOf,\n to: opts.asOf,\n provider: opts.provider,\n });\n const result = await this.applyCoverageFallback(\"chain\", underlying, opts.asOf, rawResult);\n chainResults.push(result);\n if (result.status === \"error\") errors.push(`chain ${underlying}: ${result.error}`);\n }\n\n // Step 3 — quote ingest (single batch — provider handles the list)\n const quoteResults: IngestResult[] = [];\n if (opts.quoteTickers && opts.quoteTickers.length > 0) {\n const result = await this.ingestQuotes({\n tickers: opts.quoteTickers,\n from: opts.asOf,\n to: opts.asOf,\n provider: opts.provider,\n });\n quoteResults.push(result);\n if (result.status === \"error\") errors.push(`quotes: ${result.error}`);\n }\n if (opts.quoteUnderlyings && opts.quoteUnderlyings.length > 0) {\n const result = await this.ingestQuotes({\n underlyings: opts.quoteUnderlyings,\n from: opts.asOf,\n to: opts.asOf,\n provider: opts.provider,\n onProgress: opts.onProgress,\n });\n quoteResults.push(result);\n if (result.status === \"error\") errors.push(`quotes (underlyings): ${result.error}`);\n }\n\n // Step 3b — open interest (opt-in only). Daily-granularity option OI lands\n // in its own store; it does NOT run unless the caller explicitly supplies\n // openInterestUnderlyings — no silent default that would write OI on every\n // refresh.\n const openInterestResults: IngestResult[] = [];\n if (opts.openInterestUnderlyings && opts.openInterestUnderlyings.length > 0) {\n const result = await this.ingestOpenInterest({\n underlyings: opts.openInterestUnderlyings,\n from: opts.asOf,\n to: opts.asOf,\n provider: opts.provider,\n });\n openInterestResults.push(result);\n if (result.status === \"error\") errors.push(`open interest: ${result.error}`);\n }\n\n // Step 4 — VIX context (only if flag AND any VIX-family ticker in spot list)\n let vixContext: IngestResult | null = null;\n const hasVixFamily = opts.spotTickers.some((t) => VIX_FAMILY.has(t.toUpperCase()));\n if (computeCtxFlag && hasVixFamily) {\n vixContext = await this.computeVixContext({ from: opts.asOf, to: opts.asOf });\n if (vixContext.status === \"error\") errors.push(`vix context: ${vixContext.error}`);\n }\n\n // Coverage report — shallow summary per ticker\n const coverage: Record<string, { totalDates: number; dateRange?: { from: string; to: string } }> = {};\n for (const ticker of opts.spotTickers) {\n try {\n const cov = await this.deps.stores.spot.getCoverage(ticker.toUpperCase(), opts.asOf, opts.asOf);\n coverage[ticker] = {\n totalDates: cov.totalDates,\n dateRange: cov.earliest && cov.latest ? { from: cov.earliest, to: cov.latest } : undefined,\n };\n } catch {\n coverage[ticker] = { totalDates: 0 };\n }\n }\n\n // Aggregate per-operation `skipped` entries so callers don't have to\n // traverse perOperation themselves. A \"partial\" status is contagious:\n // any operation returning partial flips refresh to partial too.\n const aggregateSkipped: IngestSkippedBatch[] = [];\n const collect = (r: IngestResult | null): void => {\n if (r?.skipped && r.skipped.length > 0) aggregateSkipped.push(...r.skipped);\n };\n for (const r of spotResults) collect(r);\n for (const r of chainResults) collect(r);\n for (const r of quoteResults) collect(r);\n for (const r of openInterestResults) collect(r);\n collect(vixContext);\n\n let status: IngestStatus;\n if (errors.length > 0) status = \"error\";\n else if (aggregateSkipped.length > 0) status = \"partial\";\n else status = \"ok\";\n\n return {\n status,\n perOperation: { spot: spotResults, chain: chainResults, quotes: quoteResults, openInterest: openInterestResults, vixContext },\n coverage,\n errors,\n ...(aggregateSkipped.length > 0 ? { skipped: aggregateSkipped } : {}),\n };\n }\n}\n","/**\n * Pure helpers for the option-data migration script.\n *\n * No IO, no DuckDB, no filesystem — these functions are the unit-test\n * target. The .mjs script that consumes them is not unit-testable due to\n * filesystem effects; these helpers carry the safety net.\n */\nimport type { TickerRegistry } from \"../market/tickers/registry.ts\";\n\n/** Hardcoded skip list for leveraged-ETF roots — verbatim list, audit-friendly. */\nexport const LEVERAGED_ETFS = new Set([\"SPXL\", \"SPXS\", \"SPXU\", \"SPXC\"]);\n\nexport interface GroupResult {\n byUnderlying: Map<string, string[]>; // underlying -> roots that resolve to it\n skipped: string[]; // leveraged-ETF roots that were dropped\n}\n\n/**\n * Group source roots by their resolved underlying. Roots in `skipSet` are\n * excluded and recorded in `skipped`. Insertion order of `byUnderlying[u]`\n * matches the input root order for that underlying.\n */\nexport function groupTickersByUnderlying(\n roots: string[],\n registry: TickerRegistry,\n skipSet: Set<string> = LEVERAGED_ETFS,\n): GroupResult {\n const byUnderlying = new Map<string, string[]>();\n const skipped: string[] = [];\n for (const root of roots) {\n if (skipSet.has(root)) {\n skipped.push(root);\n continue;\n }\n const underlying = registry.resolve(root);\n const existing = byUnderlying.get(underlying) ?? [];\n existing.push(root);\n byUnderlying.set(underlying, existing);\n }\n return { byUnderlying, skipped };\n}\n\n/**\n * Build the SELECT for option_chain rewrite. Uses `* EXCLUDE (underlying)`\n * because the underlying lives in the partition path post-migration, not in\n * a row-level column. sourceGlob is interpolated raw; caller must pass a\n * trusted, migrator-composed path.\n */\nexport function buildOptionChainSelectQuery(\n sourceGlob: string,\n underlying: string,\n): string {\n return `SELECT * EXCLUDE (underlying)\n FROM read_parquet('${sourceGlob}')\n WHERE underlying = '${underlying}'`;\n}\n\n/**\n * Build the SELECT for option_quote_minutes rewrite. NO EXCLUDE — the body\n * has no `underlying` column. Filters by root via regexp_extract.\n */\nexport function buildOptionQuoteSelectQuery(\n sourceGlob: string,\n roots: string[],\n): string {\n if (roots.length === 0) {\n throw new Error(\"buildOptionQuoteSelectQuery: roots must not be empty\");\n }\n const quoted = roots.map((r) => `'${r}'`).join(\", \");\n return `SELECT * FROM read_parquet('${sourceGlob}')\n WHERE regexp_extract(ticker, '^([A-Z]+)', 1) IN (${quoted})`;\n}\n","/**\n * sample-date-selector.ts — deterministic sample-date generator for\n * enrichment-rebuild verification.\n *\n * Produces a reproducible list of ~15–20 dates (sized for fast verification\n * runs across the supported tickers). The same seed always yields the same\n * sample so re-runs of the verification harness produce comparable drift\n * reports.\n *\n * Pattern: known-event dates + structural calendar-edge dates + N\n * pseudo-random weekday draws from a Mulberry32 PRNG.\n *\n * Pure module — no filesystem, no DuckDB, no provider imports. Safe to import\n * from unit tests, operator scripts, and verification harnesses alike.\n */\n\n/**\n * One sample-date entry. `category` tags its origin so the verification report\n * can group failures by kind (e.g., \"2 known_event failures, 0 random\").\n */\nexport interface SampleDate {\n date: string; // YYYY-MM-DD\n category: \"known_event\" | \"structural\" | \"random\";\n note?: string;\n}\n\n/**\n * PRNG seed. Pinned to a fixed integer so every invocation of\n * `selectVerificationSampleDates` with default args yields the committed\n * fixture.\n */\nexport const PHASE_5_FIXTURE_SEED = 20260418;\n\n/**\n * Known-event dates — always included in the sample regardless of seed.\n * Each is a real high-volatility or calendar-significant trading day used\n * to stress the enrichment math.\n */\nexport const PHASE_5_KNOWN_EVENTS: SampleDate[] = [\n { date: \"2024-08-05\", category: \"known_event\", note: \"VIX spike, ~65% gap, Japan carry unwind\" },\n { date: \"2025-04-08\", category: \"known_event\", note: \"Tariff shock, VIX Spike 30%+, SPX down\" },\n { date: \"2024-12-18\", category: \"known_event\", note: \"FOMC decision day, VIX Spike 80%+\" },\n { date: \"2024-11-15\", category: \"known_event\", note: \"November OPEX Friday\" },\n { date: \"2024-03-28\", category: \"known_event\", note: \"Q1 end-of-quarter roll\" },\n { date: \"2024-09-18\", category: \"known_event\", note: \"FOMC — 50bps cut, trend reversal\" },\n];\n\n/**\n * Structural dates — calendar-edge dates always included to exercise first-day,\n * mid-year, and holiday-adjacent enrichment paths.\n */\nexport const PHASE_5_STRUCTURAL_DATES: SampleDate[] = [\n { date: \"2022-01-03\", category: \"structural\", note: \"First trading day of 2022 (earliest data)\" },\n { date: \"2024-07-03\", category: \"structural\", note: \"Day before July 4 (early close)\" },\n { date: \"2024-11-27\", category: \"structural\", note: \"Day before Thanksgiving\" },\n];\n\n/**\n * Mulberry32 PRNG — deterministic, 32-bit state. Small, fast, and repeatable\n * across Node versions. Reference: https://github.com/bryc/code/blob/master/jshash/PRNGs.md#mulberry32\n */\nfunction mulberry32(seed: number): () => number {\n let state = seed >>> 0;\n return () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let t = state;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Enumerate weekday dates (Mon–Fri) in [fromDate, toDate] inclusive.\n * UTC-safe — iterates via Date arithmetic with noon-UTC anchors to avoid DST\n * edge cases.\n */\nfunction enumerateWeekdays(fromDate: string, toDate: string): string[] {\n const out: string[] = [];\n const d = new Date(fromDate + \"T12:00:00Z\");\n const end = new Date(toDate + \"T12:00:00Z\");\n while (d <= end) {\n const dow = d.getUTCDay();\n if (dow !== 0 && dow !== 6) {\n out.push(d.toISOString().slice(0, 10));\n }\n d.setUTCDate(d.getUTCDate() + 1);\n }\n return out;\n}\n\n/**\n * Deterministic sample-date selector.\n *\n * Returns a merged and date-sorted list of:\n * - All PHASE_5_KNOWN_EVENTS (6 entries)\n * - All PHASE_5_STRUCTURAL_DATES (3 entries)\n * - `randomCount` pseudo-random weekdays from [fromDate, toDate] that are\n * NOT already in the known/structural sets\n *\n * Same `(fromDate, toDate, seed, randomCount)` → same output, always.\n *\n * @param fromDate - inclusive lower bound, default 2022-01-01\n * @param toDate - inclusive upper bound, typically \"yesterday\" at call time\n * @param seed - Mulberry32 PRNG seed, default PHASE_5_FIXTURE_SEED\n * @param randomCount - number of random dates to draw (default 9 → total ~18)\n */\nexport function selectVerificationSampleDates(\n fromDate: string = \"2022-01-01\",\n toDate: string = new Date().toISOString().slice(0, 10),\n seed: number = PHASE_5_FIXTURE_SEED,\n randomCount: number = 9,\n): SampleDate[] {\n const prng = mulberry32(seed);\n\n // Build candidate pool: weekdays in [fromDate, toDate] minus already-selected dates.\n const selectedSet = new Set<string>([\n ...PHASE_5_KNOWN_EVENTS.map((s) => s.date),\n ...PHASE_5_STRUCTURAL_DATES.map((s) => s.date),\n ]);\n const candidates = enumerateWeekdays(fromDate, toDate).filter(\n (d) => !selectedSet.has(d),\n );\n\n const random: SampleDate[] = [];\n const pool = [...candidates];\n for (let i = 0; i < randomCount && pool.length > 0; i++) {\n const idx = Math.floor(prng() * pool.length);\n random.push({ date: pool.splice(idx, 1)[0], category: \"random\" });\n }\n\n return [...PHASE_5_KNOWN_EVENTS, ...PHASE_5_STRUCTURAL_DATES, ...random].sort(\n (a, b) => a.date.localeCompare(b.date),\n );\n}\n","/**\n * enrichment-verification.ts — pure diff helper for enrichment-rebuild\n * verification.\n *\n * Compares two enriched rows (old from the legacy\n * `daily.parquet` / `date_context` files, new from the rebuilt\n * `market.enriched` / `market.enriched_context` views) with per-field\n * tolerance rules and returns a structured diff — not a boolean. The\n * verification harness consumes the diff, aggregates across ~15–20 sample\n * dates per ticker, and emits a report (markdown + JSON).\n *\n * Pure module — no filesystem, no DuckDB, no provider imports.\n *\n * Tolerance rules:\n * - DOUBLE: `|a - b| <= 1e-9` (boundary INCLUSIVE); NaN-vs-NaN passes;\n * NaN-vs-non-NaN fails.\n * - INTEGER: strict `Number(a) === Number(b)`.\n * - VARCHAR: case-sensitive `String(a) === String(b)`.\n * - null vs null / undefined vs undefined → pass.\n * - null vs value (or undefined vs value) → fail.\n *\n * Failure aggregation: `compareRow(...).anyFailure === true` whenever any\n * field in the row failed its tolerance test. The deletion of the legacy\n * enriched files is gated on every compared row returning\n * `anyFailure === false`.\n */\n\n/**\n * Tolerance epsilon for DOUBLE fields. Locked at 1e-9 — the enrichment\n * math is deterministic on identical OHLCV input, so anything above machine\n * precision is a real semantic change.\n */\nexport const DOUBLE_EPSILON = 1e-9;\n\n/** One of the three field-type dispatch categories. */\nexport type FieldType = \"double\" | \"integer\" | \"varchar\";\n\n/**\n * Per-field type classification for rows materialized from\n * `market.enriched` (the ticker-first enriched store).\n *\n * Source: market-enricher.ts::DAILY_ENRICHMENT_COLUMNS + the tolerance-rule\n * classification documented in the file header.\n *\n * INTEGER fields (exact match): Gap_Filled, Consecutive_Days, High_Before_Low,\n * Reversal_Type, Day_of_Week, Month, Is_Opex.\n * DOUBLE fields (1e-9 epsilon): everything else.\n *\n * NOTE: Gap_Filled and Reversal_Type can be null on no-gap / no-reversal days;\n * the compare helper handles null-vs-null as pass.\n */\nexport const ENRICHED_FIELD_TYPES: Record<string, FieldType> = {\n // Tier 1 doubles\n Prior_Close: \"double\",\n Gap_Pct: \"double\",\n ATR_Pct: \"double\",\n RSI_14: \"double\",\n Price_vs_EMA21_Pct: \"double\",\n Price_vs_SMA50_Pct: \"double\",\n Realized_Vol_5D: \"double\",\n Realized_Vol_20D: \"double\",\n Return_5D: \"double\",\n Return_20D: \"double\",\n Intraday_Range_Pct: \"double\",\n Intraday_Return_Pct: \"double\",\n Close_Position_In_Range: \"double\",\n Prev_Return_Pct: \"double\",\n Prior_Range_vs_ATR: \"double\",\n // Tier 3 intraday timing (doubles)\n High_Time: \"double\",\n Low_Time: \"double\",\n Opening_Drive_Strength: \"double\",\n Intraday_Realized_Vol: \"double\",\n // IVR / IVP\n ivr: \"double\",\n ivp: \"double\",\n // Context-bleed fields that may appear on ticker-scoped rows in some layouts\n VIX_Spike_Pct: \"double\",\n VIX_Gap_Pct: \"double\",\n // Integer fields\n Gap_Filled: \"integer\",\n Consecutive_Days: \"integer\",\n High_Before_Low: \"integer\",\n Reversal_Type: \"integer\",\n Day_of_Week: \"integer\",\n Month: \"integer\",\n Is_Opex: \"integer\",\n};\n\n/**\n * Per-field type classification for rows materialized from\n * `market.enriched_context` (the global cross-ticker context view).\n *\n * Vol_Regime / Term_Structure_State are integers (classification codes per\n * classifyVolRegime / classifyTermStructure).\n * Trend_Direction is a varchar (case-sensitive).\n * VIX_Spike_Pct / VIX_Gap_Pct are doubles.\n */\nexport const CONTEXT_FIELD_TYPES: Record<string, FieldType> = {\n Vol_Regime: \"integer\",\n Term_Structure_State: \"integer\",\n Trend_Direction: \"varchar\",\n VIX_Spike_Pct: \"double\",\n VIX_Gap_Pct: \"double\",\n};\n\n/**\n * Structured per-field diff result.\n *\n * `delta` is only populated for DOUBLE fields (where `|a - b|` is meaningful).\n * `passed` is the single source of truth for gate decisions — downstream code\n * should never re-derive pass/fail from raw `oldValue`/`newValue`.\n */\nexport interface FieldDiff {\n field: string;\n type: FieldType;\n oldValue: unknown;\n newValue: unknown;\n passed: boolean;\n delta?: number;\n}\n\n/**\n * Row-level diff with a precomputed `anyFailure` flag for fast aggregation\n * across many sample rows — any failure blocks deletion of the legacy\n * enriched files.\n */\nexport interface RowDiff {\n ticker: string;\n date: string;\n kind: \"enriched\" | \"context\";\n fields: FieldDiff[];\n anyFailure: boolean;\n}\n\n/** null or undefined — the pair both-missing is always a PASS. */\nfunction isNullish(v: unknown): boolean {\n return v === null || v === undefined;\n}\n\n/**\n * Compare the fields of two rows per a `fieldTypes` dispatch map.\n *\n * Iterates `Object.entries(fieldTypes)` so the returned array length always\n * equals `Object.keys(fieldTypes).length`. Extra keys in the input rows are\n * ignored; missing keys in the rows surface as `oldValue === undefined`\n * (null-vs-null pass if both are missing, fail if only one is missing).\n */\nexport function compareFields(\n oldRow: Record<string, unknown>,\n newRow: Record<string, unknown>,\n fieldTypes: Record<string, FieldType>,\n): FieldDiff[] {\n return Object.entries(fieldTypes).map(([field, type]) => {\n const oldValue = oldRow[field];\n const newValue = newRow[field];\n\n const oldNull = isNullish(oldValue);\n const newNull = isNullish(newValue);\n if (oldNull && newNull) {\n return { field, type, oldValue, newValue, passed: true };\n }\n if (oldNull !== newNull) {\n return { field, type, oldValue, newValue, passed: false };\n }\n\n if (type === \"double\") {\n const oldNum = Number(oldValue);\n const newNum = Number(newValue);\n const oldNaN = Number.isNaN(oldNum);\n const newNaN = Number.isNaN(newNum);\n if (oldNaN && newNaN) {\n return { field, type, oldValue, newValue, passed: true };\n }\n if (oldNaN !== newNaN) {\n return { field, type, oldValue, newValue, passed: false };\n }\n const delta = Math.abs(oldNum - newNum);\n return {\n field,\n type,\n oldValue,\n newValue,\n passed: delta <= DOUBLE_EPSILON,\n delta,\n };\n }\n\n if (type === \"integer\") {\n return {\n field,\n type,\n oldValue,\n newValue,\n passed: Number(oldValue) === Number(newValue),\n };\n }\n\n // varchar\n return {\n field,\n type,\n oldValue,\n newValue,\n passed: String(oldValue) === String(newValue),\n };\n });\n}\n\n/**\n * Compare two rows using the field-type dispatch map that matches `kind`.\n *\n * `kind: 'enriched'` uses `ENRICHED_FIELD_TYPES` (per-ticker enriched row).\n * `kind: 'context'` uses `CONTEXT_FIELD_TYPES` (global cross-ticker context row).\n *\n * The resulting `RowDiff.anyFailure` is the aggregation signal the deletion\n * gate reads — a single `true` anywhere in the sample blocks deletion of\n * the legacy enriched files.\n */\nexport function compareRow(\n oldRow: Record<string, unknown>,\n newRow: Record<string, unknown>,\n kind: \"enriched\" | \"context\",\n ticker: string,\n date: string,\n): RowDiff {\n const types = kind === \"enriched\" ? ENRICHED_FIELD_TYPES : CONTEXT_FIELD_TYPES;\n const fields = compareFields(oldRow, newRow, types);\n return {\n ticker,\n date,\n kind,\n fields,\n anyFailure: fields.some((f) => !f.passed),\n };\n}\n","/**\n * calibration-probe.ts — operator helper that de-risks provider-refetch drift\n * against the 1e-9 tolerance used by the enrichment verifier.\n *\n * Invoke before starting a spot-bar backfill. The probe fetches minute bars\n * for a set of sample dates from the active provider and compares their close\n * prices against whatever is already in `market.spot` for the same\n * (ticker, date). The operator reads the returned `maxCloseDelta` and decides:\n *\n * maxCloseDelta < 1e-6 → proceed; 1e-9 tolerance is achievable.\n * 1e-6 ≤ delta < 1e-3 → proceed only with an explicit tolerance bump\n * or a documented baseline shift.\n * maxCloseDelta ≥ 1e-3 → escalate; the tolerance is unachievable\n * without a design change.\n *\n * Manual-only — purposefully side-effectful (opens a live DuckDB, calls the\n * singleton provider). No unit test; exists for operator inspection.\n */\nimport * as path from \"path\";\nimport { DuckDBInstance } from \"@duckdb/node-api\";\nimport { getProvider } from \"./market-provider.ts\";\n\n/** Per-date probe result returned alongside the summary deltas. */\nexport interface CalibrationProbeDateResult {\n date: string;\n /** Max |new - old| close observed for this date across all aligned bars. */\n refetchDelta: number;\n /** How many minute bars from the new fetch aligned to an existing row. */\n matchedBars: number;\n}\n\n/** Summary returned by `calibrateProviderFetch`. */\nexport interface CalibrationProbeResult {\n /** Mean of the aligned per-bar close deltas across ALL dates. */\n avgCloseDelta: number;\n /** Max of the aligned per-bar close deltas across ALL dates. */\n maxCloseDelta: number;\n /** Per-date detail so the operator can spot-check specific trading days. */\n dateResults: CalibrationProbeDateResult[];\n}\n\n/**\n * Compare provider-refetched minute bars against whatever is in\n * `market.spot` for the same (ticker, date). Read-only against DuckDB.\n *\n * Opens its own DuckDB connection at `${dataRoot}/database/market.duckdb` so\n * it can run alongside a stopped MCP server. Does NOT write or modify either\n * the database or the provider cache.\n *\n * @param ticker Plain ticker (\"SPX\", \"VIX\"). No OCC contracts (probe is a spot\n * sanity check).\n * @param probeDates Array of \"YYYY-MM-DD\" — 3–7 mid-2024 dates are sufficient.\n * @param dataRoot Absolute or home-relative data root, e.g. \"~/tradeblocks-data\".\n * The probe resolves the DuckDB file at `${dataRoot}/database/market.duckdb`.\n */\nexport async function calibrateProviderFetch(\n ticker: string,\n probeDates: string[],\n dataRoot: string,\n): Promise<CalibrationProbeResult> {\n const provider = getProvider();\n const dbPath = path.join(dataRoot, \"database\", \"market.duckdb\");\n const instance = await DuckDBInstance.create(dbPath);\n const conn = await instance.connect();\n\n const deltas: number[] = [];\n const dateResults: CalibrationProbeDateResult[] = [];\n\n try {\n for (const date of probeDates) {\n const newBars = await provider.fetchBars({\n ticker,\n from: date,\n to: date,\n timespan: \"minute\",\n multiplier: 1,\n assetClass: \"index\",\n });\n\n // Canonical minute-bar view is market.spot — same schema as the\n // earlier intraday view it replaced.\n const oldReader = await conn.runAndReadAll(\n `SELECT time, close FROM market.spot WHERE ticker = $1 AND date = $2 ORDER BY time`,\n [ticker, date],\n );\n const oldRows = oldReader.getRows();\n const oldByTime = new Map<string, number>();\n for (const r of oldRows) {\n const t = String(r[0]);\n const c = Number(r[1]);\n if (Number.isFinite(c)) oldByTime.set(t, c);\n }\n\n let maxDelta = 0;\n let matched = 0;\n for (const bar of newBars) {\n if (!bar.time) continue;\n const oldClose = oldByTime.get(bar.time);\n if (oldClose === undefined) continue;\n const delta = Math.abs(bar.close - oldClose);\n if (delta > maxDelta) maxDelta = delta;\n deltas.push(delta);\n matched++;\n }\n dateResults.push({ date, refetchDelta: maxDelta, matchedBars: matched });\n }\n } finally {\n try {\n conn.closeSync();\n } catch {\n /* best-effort */\n }\n try {\n instance.closeSync();\n } catch {\n /* best-effort */\n }\n }\n\n const avgCloseDelta =\n deltas.length > 0 ? deltas.reduce((a, b) => a + b, 0) / deltas.length : 0;\n const maxCloseDelta = deltas.length > 0 ? Math.max(...deltas) : 0;\n return { avgCloseDelta, maxCloseDelta, dateResults };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcO,SAAS,YAAY,MAAY,oBAAI,KAAK,GAAW;AAE1D,QAAM,UAAU,IAAI,KAAK,eAAe,SAAS;AAAA,IAC/C,UAAU;AAAA,IACV,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,EACP,CAAC,EAAE,OAAO,GAAG;AAEb,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AAG/C,QAAM,QAAQ,IAAI,KAAK,KAAK,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;AAC5C,QAAM,WAAW,MAAM,WAAW,IAAI,CAAC;AAEvC,QAAM,KAAK,MAAM,eAAe;AAChC,QAAM,KAAK,OAAO,MAAM,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AAC1D,QAAM,KAAK,OAAO,MAAM,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACrD,SAAO,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE;AAC1B;;;ACtBO,SAAS,iBAAiB,QAAiB,UAA4B;AAC5E,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,OAAO;AAAA,IACZ,CAAC,MAAM,EAAE,SAAS,YAAY,MAAM,SAAS,YAAY;AAAA,EAC3D;AACF;AAMA,IAAM,UAAU;AAChB,SAAS,kBAAkB,MAA8C;AACvE,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,QAAQ,KAAK,IAAI,IAAI,OAAO;AACrC;AAQA,SAAS,kBAAkB,MAA6B;AACtD,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,QAAQ,KAAK,MAAM,0BAA0B;AACnD,QAAI,MAAO,QAAO,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAAA,EACvD;AACA,QAAM,IAAI,OAAO,SAAS,WAAW,IAAI,KAAK,IAAI,IAAI;AACtD,QAAM,OAAO,EAAE,YAAY;AAC3B,QAAM,QAAQ,OAAO,EAAE,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACtD,QAAM,MAAM,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG;AAC/C,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG;AAChC;AAOO,SAAS,kBACd,QACA,WACA,SACS;AACT,QAAM,QAAQ,kBAAkB,SAAS;AACzC,QAAM,MAAM,kBAAkB,OAAO;AACrC,MAAI,WAAW;AAEf,MAAI,OAAO;AACT,eAAW,SAAS,OAAO,CAAC,MAAM,kBAAkB,EAAE,UAAU,KAAK,KAAK;AAAA,EAC5E;AAEA,MAAI,KAAK;AACP,eAAW,SAAS,OAAO,CAAC,MAAM,kBAAkB,EAAE,UAAU,KAAK,GAAG;AAAA,EAC1E;AAEA,SAAO;AACT;AAOO,SAAS,2BACd,WACA,WACA,SACiB;AACjB,QAAM,QAAQ,kBAAkB,SAAS;AACzC,QAAM,MAAM,kBAAkB,OAAO;AACrC,MAAI,WAAW;AAEf,MAAI,OAAO;AACT,eAAW,SAAS,OAAO,CAAC,UAAU,kBAAkB,MAAM,IAAI,KAAK,KAAK;AAAA,EAC9E;AAEA,MAAI,KAAK;AACP,eAAW,SAAS,OAAO,CAAC,UAAU,kBAAkB,MAAM,IAAI,KAAK,GAAG;AAAA,EAC5E;AAEA,SAAO;AACT;;;AClBO,IAAM,sBAAsC;AAAA,EACjD,QAAQ;AAAA,IACN,aACE;AAAA,IACF,QAAQ;AAAA,MACN,YAAY;AAAA,QACV,aACE;AAAA,QACF,YAAY,CAAC,YAAY,eAAe,YAAY,IAAI;AAAA,QACxD,SAAS;AAAA,UACP,UAAU;AAAA,YACR,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,UAAU;AAAA,YACR,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,SAAS;AAAA,YACP,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,eAAe;AAAA,YACb,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,IAAI;AAAA,YACF,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,kBAAkB;AAAA,YAChB,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,YAAY;AAAA,YACV,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,qBAAqB;AAAA,YACnB,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,qBAAqB;AAAA,YACnB,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,MACA,gBAAgB;AAAA,QACd,aACE;AAAA,QACF,YAAY,CAAC,YAAY,eAAe,YAAY,QAAQ,IAAI;AAAA,QAChE,SAAS;AAAA,UACP,UAAU;AAAA,YACR,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,UAAU;AAAA,YACR,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,iBAAiB;AAAA,YACf,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,eAAe;AAAA,YACb,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,IAAI;AAAA,YACF,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,eAAe;AAAA,YACb,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,kBAAkB;AAAA,YAChB,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,kBAAkB;AAAA,YAChB,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,eAAe;AAAA,YACb,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,aACE;AAAA,IACF,QAAQ;AAAA,MACN,UAAU;AAAA,QACR,aACE;AAAA,QACF,YAAY,CAAC,UAAU,QAAQ,UAAU,WAAW,kBAAkB;AAAA,QACtE,SAAS;AAAA,UACP,QAAQ;AAAA,YACN,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA;AAAA,UAEA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,OAAO;AAAA,YACL,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA;AAAA,UAEA,SAAS;AAAA,YACP,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,iBAAiB;AAAA,YACf,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,oBAAoB;AAAA,YAClB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA;AAAA,UAEA,SAAS;AAAA,YACP,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,QAAQ;AAAA,YACN,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,oBAAoB;AAAA,YAClB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,oBAAoB;AAAA,YAClB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,iBAAiB;AAAA,YACf,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,kBAAkB;AAAA,YAChB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,WAAW;AAAA,YACT,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,YAAY;AAAA,YACV,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,oBAAoB;AAAA,YAClB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,qBAAqB;AAAA,YACnB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,yBAAyB;AAAA,YACvB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,YAAY;AAAA,YACV,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,kBAAkB;AAAA,YAChB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA;AAAA,UAEA,WAAW;AAAA,YACT,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,UAAU;AAAA,YACR,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,iBAAiB;AAAA,YACf,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,eAAe;AAAA,YACb,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,wBAAwB;AAAA,YACtB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,uBAAuB;AAAA,YACrB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA;AAAA,UAEA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,OAAO;AAAA,YACL,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,SAAS;AAAA,YACP,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA;AAAA,UAEA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,MACA,kBAAkB;AAAA,QAChB,aACE;AAAA,QACF,YAAY,CAAC,QAAQ,cAAc,sBAAsB;AAAA,QACzD,SAAS;AAAA,UACP,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,YAAY;AAAA,YACV,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,sBAAsB;AAAA,YACpB,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,iBAAiB;AAAA,YACf,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,eAAe;AAAA,YACb,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,UACA,aAAa;AAAA,YACX,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,MACA,MAAM;AAAA,QACJ,aACE;AAAA,QACF,YAAY,CAAC,UAAU,QAAQ,MAAM;AAAA,QACrC,SAAS;AAAA,UACP,QAAQ;AAAA,YACN,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,OAAO;AAAA,YACL,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,MACA,YAAY;AAAA,QACV,aACE;AAAA,QACF,YAAY,CAAC,UAAU,QAAQ,QAAQ,QAAQ,OAAO,OAAO;AAAA,QAC7D,SAAS;AAAA,UACP,QAAQ;AAAA,YACN,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,OAAO;AAAA,YACL,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,MACA,cAAc;AAAA,QACZ,aACE;AAAA,QACF,YAAY,CAAC,cAAc,QAAQ,QAAQ;AAAA,QAC3C,SAAS;AAAA,UACP,YAAY;AAAA,YACV,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,QAAQ;AAAA,YACN,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,eAAe;AAAA,YACb,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,QAAQ;AAAA,YACN,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,YAAY;AAAA,YACV,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,gBAAgB;AAAA,YACd,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,QACpB,aACE;AAAA,QACF,YAAY,CAAC,UAAU,QAAQ,MAAM;AAAA,QACrC,SAAS;AAAA,UACP,QAAQ;AAAA,YACN,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,KAAK;AAAA,YACH,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,iBAAiB;AAAA,YACf,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,QAAQ;AAAA,YACN,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,OAAO;AAAA,YACL,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,OAAO;AAAA,YACL,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,OAAO;AAAA,YACL,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,MAAM;AAAA,YACJ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,IAAI;AAAA,YACF,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,eAAe;AAAA,YACb,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,iBAAiB;AAAA,YACf,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,WAAW;AAAA,YACT,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,YAAY;AAAA,YACV,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,UACA,cAAc;AAAA,YACZ,aAAa;AAAA,YACb,YAAY;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC5lBA,IAAM,eAAe,oBAAoB,OAAO,OAAO,SAAS;AAChE,IAAM,iBAAiB,oBAAoB,OAAO,OAAO,iBAAiB;AAK1E,IAAM,aAAa,oBAAI,IAAI,CAAC,QAAQ,QAAQ,OAAO,OAAO,CAAC;AAC3D,SAAS,iBAAiB,KAAwB;AAChD,SAAO,WAAW,IAAI,GAAG,IAAI,MAAM;AACrC;AA8BA,IAAM,qBAAwC;AAAA;AAAA,EAE5C,EAAE,OAAO,YAAa,YAAY,OAAQ,WAAW,QAAS,QAAQ,OAAQ,QAAQ,OAAO;AAAA,EAC7F,EAAE,OAAO,aAAa,YAAY,OAAQ,WAAW,SAAS,QAAQ,OAAQ,QAAQ,QAAQ;AAAA,EAC9F,EAAE,OAAO,YAAa,YAAY,OAAQ,WAAW,QAAS,QAAQ,OAAQ,QAAQ,QAAQ;AAAA,EAC9F,EAAE,OAAO,WAAa,YAAY,OAAQ,WAAW,OAAS,QAAQ,OAAQ,QAAQ,QAAQ;AAAA;AAAA,EAE9F,EAAE,OAAO,cAAe,YAAY,SAAS,WAAW,QAAS,QAAQ,SAAS,QAAQ,OAAO;AAAA,EACjG,EAAE,OAAO,eAAe,YAAY,SAAS,WAAW,SAAS,QAAQ,SAAS,QAAQ,QAAQ;AAAA;AAAA,EAElG,EAAE,OAAO,cAAe,YAAY,SAAS,WAAW,QAAS,QAAQ,SAAS,QAAQ,OAAO;AAAA,EACjG,EAAE,OAAO,eAAe,YAAY,SAAS,WAAW,SAAS,QAAQ,SAAS,QAAQ,QAAQ;AACpG;AAGA,IAAM,wBAA2C;AAAA,EAC/C,EAAE,OAAO,WAAc,YAAY,QAAU,WAAW,OAAO,QAAQ,OAAS,QAAQ,QAAQ;AAAA,EAChG,EAAE,OAAO,WAAc,YAAY,QAAU,WAAW,OAAO,QAAQ,OAAS,QAAQ,QAAQ;AAAA,EAChG,EAAE,OAAO,aAAc,YAAY,UAAU,WAAW,OAAO,QAAQ,SAAS,QAAQ,QAAQ;AAAA,EAChG,EAAE,OAAO,aAAc,YAAY,UAAU,WAAW,OAAO,QAAQ,SAAS,QAAQ,QAAQ;AAAA,EAChG,EAAE,OAAO,aAAc,YAAY,UAAU,WAAW,OAAO,QAAQ,SAAS,QAAQ,QAAQ;AAAA,EAChG,EAAE,OAAO,aAAc,YAAY,UAAU,WAAW,OAAO,QAAQ,SAAS,QAAQ,QAAQ;AAClG;AAIA,IAAM,mBAAsC;AAAA,EAC1C,GAAG;AAAA,EACH,GAAG;AACL;AAGA,IAAM,qBAAqB,CAAC,GAAG,IAAI,IAAI,mBAAmB,IAAI,OAAK,EAAE,UAAU,CAAC,CAAC;AACjF,IAAM,uBAA+C,OAAO,YAAY;AAAA,EACtE,GAAG,mBAAmB,IAAI,OAAK,CAAC,EAAE,YAAY,EAAE,MAAM,CAAU;AAAA,EAChE,GAAG,sBAAsB,IAAI,OAAK,CAAC,EAAE,YAAY,EAAE,MAAM,CAAU;AACrE,CAAC;AAGD,IAAM,sBAA2C,IAAI;AAAA,EACnD,OAAO,QAAQ,cAAc,EAC1B,OAAO,CAAC,CAAC,EAAE,IAAI,MAAM,KAAK,WAAW,MAAM,EAC3C,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AACzB;AAEA,IAAM,uBAA4C,IAAI;AAAA,EACpD,OAAO,QAAQ,cAAc,EAC1B,OAAO,CAAC,CAAC,EAAE,IAAI,MAAM,KAAK,WAAW,OAAO,EAC5C,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AACzB;AAYO,IAAM,oBAAyC,IAAI;AAAA,EACxD,OAAO,QAAQ,YAAY,EACxB,OAAO,CAAC,CAAC,EAAE,IAAI,MAAM,KAAK,WAAW,MAAM,EAC3C,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AACzB;AAMO,IAAM,qBAA0C,IAAI;AAAA,EACzD,OAAO,QAAQ,YAAY,EACxB,OAAO,CAAC,CAAC,EAAE,IAAI,MAAM,KAAK,WAAW,OAAO,EAC5C,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AACzB;AAKO,IAAM,sBAA2C,IAAI;AAAA,EAC1D,OAAO,QAAQ,YAAY,EACxB,OAAO,CAAC,CAAC,EAAE,IAAI,MAAM,KAAK,WAAW,QAAQ,EAC7C,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AACzB;AAKO,IAAM,sBAA2C,oBAAI,IAAI;AAAA,EAC9D,GAAG,iBAAiB,OAAO,OAAK,EAAE,WAAW,MAAM,EAAE,IAAI,OAAK,EAAE,KAAK;AAAA,EACrE,GAAG;AACL,CAAC;AAKM,IAAM,uBAA4C,oBAAI,IAAI;AAAA,EAC/D,GAAG,iBAAiB,OAAO,OAAK,EAAE,WAAW,OAAO,EAAE,IAAI,OAAK,EAAE,KAAK;AAAA,EACtE,GAAG;AACL,CAAC;AAYM,IAAM,oBAAyC,oBAAI,IAAI;AAAA,EAC5D,GAAG;AAAA,EACH,GAAG;AACL,CAAC;AAQM,IAAM,qBAA0C,oBAAI,IAAI;AAAA,EAC7D,GAAG;AAAA,EACH,GAAG;AACL,CAAC;AAOM,IAAM,gBAAqC,oBAAI,IAAI;AAAA,EACxD,GAAG;AACL,CAAC;AAqBM,SAAS,mBACd,eACA,YAAoB,KACZ;AACR,SAAO,cACJ,QAAQ,WAAS;AAChB,UAAM,SAAS,qBAAqB,KAAK;AACzC,WAAO;AAAA,MACL,+BAA+B,KAAK,OAAO,KAAK,WAAW,SAAS,aAAa,KAAK,cAAc,MAAM;AAAA,MAC1G,8BAA8B,KAAK,QAAQ,KAAK,WAAW,SAAS,cAAc,KAAK,cAAc,MAAM;AAAA,IAC7G;AAAA,EACF,CAAC,EACA,KAAK,UAAU;AACpB;AAIA,SAAS,qBAA6B;AACpC,SAAO,iBACJ,IAAI,OAAK,GAAG,EAAE,UAAU,KAAK,EAAE,SAAS,SAAS,EAAE,KAAK,GAAG,EAC3D,KAAK,IAAI;AACd;AAGA,SAAS,yBAAiC;AACxC,SAAO,CAAC,GAAG,qBAAqB,GAAG,oBAAoB,EAAE,IAAI,OAAK,OAAO,CAAC,GAAG,EAAE,KAAK,IAAI;AAC1F;AAkCO,SAAS,wBACd,kBACmC;AACnC,MAAI,iBAAiB,WAAW,GAAG;AACjC,WAAO,EAAE,KAAK,2CAA2C,QAAQ,CAAC,EAAE;AAAA,EACtE;AAIA,QAAM,gBAAgB,CAAC,GAAG,iBAAiB,EAAE,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,IAAI;AAClG,QAAM,kBAAkB,CAAC,GAAG,mBAAmB,EAAE,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,IAAI;AACtG,QAAM,iBAAiB,CAAC,GAAG,kBAAkB,EAAE,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,IAAI;AACpG,QAAM,gBAAgB,mBAAmB;AACzC,QAAM,oBAAoB,uBAAuB;AACjD,QAAM,WAAW,mBAAmB,oBAAoB,GAAG;AAG3D,QAAM,eAAe,CAAC,GAAG,kBAAkB,EACxC,IAAI,CAAC,UAAU,QAAQ,KAAK,wDAAwD,KAAK,GAAG,EAC5F,KAAK,aAAa;AACrB,QAAM,aAAa,iBAChB,OAAO,OAAK,EAAE,WAAW,OAAO,EAChC,IAAI,OAAK,QAAQ,EAAE,KAAK,wDAAwD,EAAE,KAAK,GAAG,EAC1F,KAAK,aAAa;AACrB,QAAM,iBAAiB,CAAC,GAAG,oBAAoB,EAC5C,IAAI,OAAK,QAAQ,CAAC,wDAAwD,CAAC,GAAG,EAC9E,KAAK,aAAa;AAGrB,QAAM,uBAAuB,CAAC,GAAG,iBAAiB,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI;AAClF,QAAM,yBAAyB,CAAC,GAAG,mBAAmB,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI;AACtF,QAAM,qBAAqB,iBACxB,OAAO,OAAK,EAAE,WAAW,MAAM,EAC/B,IAAI,OAAK,IAAI,EAAE,KAAK,GAAG,EACvB,KAAK,IAAI;AACZ,QAAM,yBAAyB,CAAC,GAAG,mBAAmB,EAAE,IAAI,OAAK,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI;AAGpF,MAAI,OAAO,iBAAiB,CAAC,MAAM,UAAU;AAC3C,UAAM,aAAa;AACnB,UAAM,eAAe,WAAW,IAAI,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAEpE,UAAMA,OAAM;AAAA;AAAA;AAAA;AAAA,UAIN,aAAa;AAAA,UACb,eAAe;AAAA,UACf,aAAa;AAAA,UACb,oBAAoB,oBAAoB,MAAM,EAAE;AAAA,UAChD,cAAc;AAAA;AAAA;AAAA,QAGhB,QAAQ;AAAA;AAAA,0BAEU,WAAW,SAAS,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMrC,oBAAoB;AAAA,UACpB,sBAAsB;AAAA,UACtB,qBAAqB,qBAAqB,MAAM,EAAE;AAAA,UAClD,yBAAyB,yBAAyB,MAAM,EAAE;AAAA,UAC1D,YAAY;AAAA,UACZ,aAAa,aAAa,MAAM,EAAE;AAAA,UAClC,cAAc;AAAA;AAAA;AAAA;AAAA,qBAIH,YAAY;AAE7B,WAAO,EAAE,KAAAA,MAAK,QAAQ,CAAC,GAAG,YAAY,qBAAqB,EAAE;AAAA,EAC/D;AAEA,QAAM,YAAY;AAClB,QAAM,iBAAiB,UAAU,IAAI,CAAC,OAAO;AAAA,IAC3C,MAAM,EAAE;AAAA,IACR,QAAQ,EAAE,UAAU;AAAA,EACtB,EAAE;AAEF,QAAM,SAAmB,CAAC;AAC1B,QAAM,oBAAoB,eAAe,IAAI,CAAC,QAAQ;AACpD,WAAO,KAAK,IAAI,QAAQ,IAAI,IAAI;AAChC,WAAO,KAAK,OAAO,SAAS,CAAC,MAAM,OAAO,MAAM;AAAA,EAClD,CAAC;AAED,QAAM,MAAM;AAAA,eACC,kBAAkB,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMjC,aAAa;AAAA,UACb,eAAe;AAAA,UACf,aAAa;AAAA,UACb,oBAAoB,oBAAoB,MAAM,EAAE;AAAA,UAChD,cAAc;AAAA;AAAA;AAAA,QAGhB,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAQN,oBAAoB;AAAA,UACpB,sBAAsB;AAAA,UACtB,qBAAqB,qBAAqB,MAAM,EAAE;AAAA,UAClD,yBAAyB,yBAAyB,MAAM,EAAE;AAAA,UAC1D,YAAY;AAAA,UACZ,aAAa,aAAa,MAAM,EAAE;AAAA,UAClC,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAStB,SAAO,EAAE,KAAK,QAAQ,OAAO;AAC/B;AAgBO,SAAS,kBACd,kBACmC;AACnC,MAAI,iBAAiB,WAAW,GAAG;AACjC,WAAO,EAAE,KAAK,2CAA2C,QAAQ,CAAC,EAAE;AAAA,EACtE;AAEA,QAAM,eAAe,iBAClB,OAAO,OAAK,EAAE,WAAW,OAAO,EAChC,IAAI,OAAK,GAAG,EAAE,UAAU,KAAK,EAAE,SAAS,SAAS,EAAE,KAAK,GAAG,EAC3D,KAAK,IAAI;AACZ,QAAM,mBAAmB,CAAC,GAAG,oBAAoB,EAAE,IAAI,OAAK,OAAO,CAAC,GAAG,EAAE,KAAK,IAAI;AAElF,MAAI,OAAO,iBAAiB,CAAC,MAAM,UAAU;AAC3C,WAAO,0BAA0B,kBAA8B,cAAc,gBAAgB;AAAA,EAC/F;AAEA,SAAO,yBAAyB,kBAAuC,cAAc,gBAAgB;AACvG;AAKA,SAAS,qBACP,QACA,QACQ;AACR,SAAO,CAAC,GAAG,kBAAkB,EAC1B,IAAI,CAAC,MAAM,GAAG,WAAW,IAAI,CAAC,IAAI,SAAS,MAAM,KAAK,CAAC,GAAG,EAC1D,KAAK,IAAI;AACd;AAEA,SAAS,0BACP,YAAsB,cAAsB,kBACT;AACnC,QAAM,SAAS;AACf,QAAM,SAAS;AACf,QAAM,iBAAiB,qBAAqB,QAAQ,MAAM;AAC1D,QAAM,eAAe,WAAW,IAAI,CAAC,GAAG,MAAM,IAAI,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AACpE,QAAM,MAAM,UAAU,MAAM,UAAU,cAAc,KAAK,YAAY,KAAK,gBAAgB;AAAA,2BACjE,MAAM;AAAA,kCACC,MAAM,OAAO,MAAM,aAAa,MAAM,eAAe,MAAM,WAAW,MAAM;AAAA,MACxG,mBAAmB,oBAAoB,MAAM,CAAC;AAAA,wDACI,MAAM;AAAA,YAClD,MAAM,cAAc,WAAW,SAAS,CAAC;AAAA,YACzC,MAAM,aAAa,YAAY;AACzC,SAAO,EAAE,KAAK,QAAQ,CAAC,GAAG,YAAY,qBAAqB,EAAE;AAC/D;AAEA,SAAS,yBACP,WAA8B,cAAsB,kBACjB;AACnC,QAAM,SAAS;AACf,QAAM,SAAS;AACf,QAAM,iBAAiB,qBAAqB,QAAQ,MAAM;AAC1D,QAAM,iBAAiB,UAAU,IAAI,CAAC,OAAO;AAAA,IAC3C,MAAM,EAAE;AAAA,IACR,QAAQ,EAAE,UAAU;AAAA,EACtB,EAAE;AAEF,QAAM,SAAmB,CAAC;AAC1B,QAAM,oBAAoB,eAAe,IAAI,CAAC,QAAQ;AACpD,WAAO,KAAK,IAAI,QAAQ,IAAI,IAAI;AAChC,WAAO,KAAK,OAAO,SAAS,CAAC,MAAM,OAAO,MAAM;AAAA,EAClD,CAAC;AAED,QAAM,MAAM;AAAA,eACC,kBAAkB,KAAK,IAAI,CAAC;AAAA;AAAA,aAE9B,MAAM,YAAY,MAAM,UAAU,cAAc,KAAK,YAAY,KAAK,gBAAgB;AAAA,2BACxE,MAAM;AAAA,kCACC,MAAM,OAAO,MAAM,aAAa,MAAM,eAAe,MAAM,WAAW,MAAM;AAAA,MACxG,mBAAmB,oBAAoB,MAAM,CAAC;AAAA,wDACI,MAAM;AAAA;AAAA,WAEnD,MAAM;AAAA,WACN,MAAM;AAEf,SAAO,EAAE,KAAK,QAAQ,OAAO;AAC/B;;;AC9cA,IAAM,YAAY;AAClB,SAAS,WAAmB;AAC1B,UAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAC7C;AAaA,eAAsB,sBACpB,QACA,QACA,SACiC;AACjC,QAAM,WAAqB,CAAC;AAG5B,QAAM,WAAW,MAAM,OAAO,SAAS,YAAY,MAAM;AACzD,QAAM,eAAe,SAAS,aAAa;AAC3C,QAAM,iBACJ,gBAAgB,SAAS,YAAY,SAAS,SAC1C,EAAE,KAAK,SAAS,UAAU,KAAK,SAAS,OAAO,IAC/C;AAEN,MAAI,CAAC,cAAc;AACjB,aAAS;AAAA,MACP,qCAAqC,MAAM,gFACqC,MAAM;AAAA,IAExF;AAAA,EACF;AAGA,QAAM,SAAS,MAAM,OAAO,SAAS,YAAY,KAAK;AACtD,QAAM,iBAAiB,OAAO,aAAa;AAC3C,QAAM,mBACJ,kBAAkB,OAAO,YAAY,OAAO,SACxC,EAAE,KAAK,OAAO,UAAU,KAAK,OAAO,OAAO,IAC3C;AAEN,MAAI,CAAC,gBAAgB;AACnB,aAAS;AAAA,MACP;AAAA,IAIF;AAAA,EACF;AAGA,MAAI,kBAAkB;AACtB,MAAI,oBAAyD;AAC7D,MAAI,SAAS,eAAe;AAC1B,UAAM,UAAU,MAAM,OAAO,KAAK,YAAY,QAAQ,WAAW,SAAS,CAAC;AAC3E,sBAAkB,QAAQ,aAAa;AACvC,QAAI,mBAAmB,QAAQ,YAAY,QAAQ,QAAQ;AACzD,0BAAoB,EAAE,KAAK,QAAQ,UAAU,KAAK,QAAQ,OAAO;AAAA,IACnE;AACA,QAAI,CAAC,iBAAiB;AACpB,eAAS;AAAA,QACP,oCAAoC,MAAM,qFAC2C,MAAM;AAAA,MAC7F;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACjBO,SAAS,iBAAiB,OAAsC;AACrE,QAAM,gBACJ,MAAM,cAAc,IAChB,KAAK,MAAM,MAAM,YAAY,MAAM,WAAW,IAC9C;AAGN,QAAM,mBAAmB,MAAM,cAAc,MAAM;AACnD,QAAM,aACJ,mBAAmB,IAAI,MAAM,iBAAiB,mBAAmB;AAEnE,MAAI;AAEJ,MAAI,MAAM,gBAAgB,KAAK,gBAAgB,IAAI;AACjD,sBAAkB;AAAA,EACpB,WAAW,aAAa,KAAM;AAE5B,sBAAkB;AAAA,EACpB,WAAW,iBAAiB,OAAO,cAAc,MAAM;AACrD,sBAAkB;AAAA,EACpB,OAAO;AACL,sBAAkB;AAAA,EACpB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,qBAAqB,MAAM;AAAA,IAC3B,sBAAsB,MAAM;AAAA,IAC5B,kBAAkB,MAAM;AAAA,IACxB;AAAA,EACF;AACF;AAmBA,eAAsB,cACpB,QACA,YACA,UACA,QACyB;AAEzB,QAAM,UAAU,MAAM,OAAO,KAAK,YAAY,YAAY,UAAU,MAAM;AAI1E,QAAM,WAAW,MAAM,OAAO,MAAM,YAAY,YAAY,UAAU,MAAM;AAG5E,QAAM,QAAQ,sBAAsB,QAAQ,UAAU,QAAQ,MAAM,EACjE,OAAO,sBAAsB,SAAS,UAAU,SAAS,MAAM,CAAC;AACnE,QAAM,cAAc,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,EAAE,KAAK;AAC7C,QAAM,eAAe,IAAI;AAAA,IACvB,sBAAsB,SAAS,UAAU,SAAS,MAAM;AAAA,EAC1D;AAEA,MAAI,YAAY,WAAW,GAAG;AAC5B,WAAO;AAAA,MACL,WAAW;AAAA,MACX,eAAe,CAAC;AAAA,MAChB,SAAS,8BAA8B,UAAU;AAAA,IACnD;AAAA,EACF;AAEA,MAAI,YAAY;AAChB,QAAM,gBAAgC,CAAC;AACvC,aAAW,QAAQ,aAAa;AAK9B,UAAM,WAAW;AACjB,iBAAa;AACb,kBAAc,KAAK,EAAE,MAAM,UAAU,WAAW,aAAa,IAAI,IAAI,EAAE,CAAC;AAAA,EAC1E;AAEA,QAAMC,eAAc,cAAc;AAClC,QAAM,UACJA,iBAAgB,IACZ,qBAAqB,UAAU,YAAY,QAAQ,QAAQ,MAAM,KACjE,GAAG,UAAU,KAAKA,YAAW,+BACpB,QAAQ,UAAU,WAAW,SAAS,UAAU;AAE/D,SAAO,EAAE,WAAW,eAAe,QAAQ;AAC7C;AAOA,SAAS,sBAAsB,MAAqB,IAA6B;AAC/E,MAAI,CAAC,QAAQ,CAAC,GAAI,QAAO,CAAC;AAC1B,QAAM,QAAQ,oBAAI,KAAK,OAAO,YAAY;AAC1C,QAAM,MAAM,oBAAI,KAAK,KAAK,YAAY;AACtC,MAAI,QAAQ,IAAK,QAAO,CAAC;AACzB,QAAM,MAAgB,CAAC;AACvB,QAAM,MAAM,IAAI,KAAK,KAAK;AAC1B,SAAO,OAAO,KAAK;AACjB,QAAI,KAAK,IAAI,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AACvC,QAAI,WAAW,IAAI,WAAW,IAAI,CAAC;AAAA,EACrC;AACA,SAAO;AACT;AAOA,SAAS,aAAa,UAA+C;AACnE,MAAI,YAAY,IAAK,QAAO;AAC5B,MAAI,WAAW,EAAG,QAAO;AACzB,SAAO;AACT;AAaO,SAAS,qBACd,eACA,UACQ;AACR,MAAI,SAAS,cAAc,WAAW,GAAG;AACvC,WAAO,qBAAqB,aAAa;AAAA,EAC3C;AAEA,QAAM,QAAkB,CAAC;AACzB,QAAM,QAAQ,SAAS,cAAc,CAAC,EAAE;AACxC,QAAM,OAAO,SAAS,cAAc,SAAS,cAAc,SAAS,CAAC,EAAE;AACvE,QAAM,KAAK,GAAG,aAAa,cAAc,KAAK,YAAY,IAAI,EAAE;AAWhE,QAAM,SAAkB,CAAC;AACzB,MAAI,UAAwB;AAE5B,aAAW,SAAS,SAAS,eAAe;AAC1C,UAAM,UAAU,aAAa,MAAM,QAAQ;AAE3C,QAAI,CAAC,WAAW,QAAQ,YAAY,SAAS;AAC3C,UAAI,QAAS,QAAO,KAAK,OAAO;AAChC,gBAAU;AAAA,QACR,UAAU,MAAM;AAAA,QAChB,QAAQ,MAAM;AAAA,QACd;AAAA,QACA,WAAW,CAAC,MAAM,QAAQ;AAAA,QAC1B,WAAW,MAAM;AAAA,MACnB;AAAA,IACF,OAAO;AACL,cAAQ,SAAS,MAAM;AACvB,cAAQ,UAAU,KAAK,MAAM,QAAQ;AACrC,cAAQ,YAAY,QAAQ,aAAa,MAAM;AAAA,IACjD;AAAA,EACF;AACA,MAAI,QAAS,QAAO,KAAK,OAAO;AAEhC,aAAW,SAAS,QAAQ;AAC1B,UAAM,UACJ,MAAM,UAAU,SAAS,IACrB,KAAK;AAAA,MACH,MAAM,UAAU,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,MAAM,UAAU;AAAA,IAC/D,IACA;AAEN,UAAM,WACJ,MAAM,aAAa,MAAM,SACrB,MAAM,WACN,GAAG,MAAM,QAAQ,OAAO,MAAM,MAAM;AAE1C,QAAI;AACJ,QAAI,MAAM,YAAY,SAAS;AAC7B,eAAS,cAAc,OAAO;AAAA,IAChC,WAAW,MAAM,YAAY,UAAU;AACrC,eAAS,eAAe,OAAO;AAAA,IACjC,OAAO;AACL,eAAS;AAAA,IACX;AAEA,UAAM,KAAK,KAAK,QAAQ,KAAK,MAAM,EAAE;AAAA,EACvC;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;AC9SA,SAAS,YAAY,mBAAmB;AACxC,YAAY,UAAU;AA8Df,SAAS,WAAW,QAAkB,SAAS,IAAc;AAClE,QAAM,SAAS,IAAI,MAAc,OAAO,MAAM,EAAE,KAAK,GAAG;AACxD,MAAI,OAAO,SAAS,SAAS,EAAG,QAAO;AAGvC,MAAI,UAAU;AACd,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,KAAK,QAAQ,KAAK;AAChC,UAAM,SAAS,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC;AACvC,QAAI,SAAS,EAAG,YAAW;AAAA,QACtB,YAAW,CAAC;AAAA,EACnB;AACA,aAAW;AACX,aAAW;AACX,SAAO,MAAM,IAAI,YAAY,IAAI,MAAM,MAAM,OAAO,IAAI,UAAU;AAGlE,WAAS,IAAI,SAAS,GAAG,IAAI,OAAO,QAAQ,KAAK;AAC/C,UAAM,SAAS,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC;AACvC,UAAM,OAAO,SAAS,IAAI,SAAS;AACnC,UAAM,OAAO,SAAS,IAAI,CAAC,SAAS;AACpC,eAAW,WAAW,SAAS,KAAK,QAAQ;AAC5C,eAAW,WAAW,SAAS,KAAK,QAAQ;AAC5C,WAAO,CAAC,IAAI,YAAY,IAAI,MAAM,MAAM,OAAO,IAAI,UAAU;AAAA,EAC/D;AAEA,SAAO;AACT;AAWO,SAAS,WACd,OACA,MACA,QACA,SAAS,IACC;AACV,QAAM,IAAI,OAAO;AACjB,QAAM,SAAS,IAAI,MAAc,CAAC,EAAE,KAAK,GAAG;AAC5C,MAAI,IAAI,SAAS,EAAG,QAAO;AAG3B,QAAM,KAAK,IAAI,MAAc,CAAC,EAAE,KAAK,GAAG;AACxC,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,YAAY,OAAO,IAAI,CAAC;AAC9B,OAAG,CAAC,IAAI,KAAK;AAAA,MACX,MAAM,CAAC,IAAI,KAAK,CAAC;AAAA,MACjB,KAAK,IAAI,MAAM,CAAC,IAAI,SAAS;AAAA,MAC7B,KAAK,IAAI,KAAK,CAAC,IAAI,SAAS;AAAA,IAC9B;AAAA,EACF;AAGA,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,KAAK,QAAQ,KAAK;AAChC,cAAU,GAAG,CAAC;AAAA,EAChB;AACA,MAAI,MAAM,SAAS;AACnB,SAAO,MAAM,IAAI;AAGjB,WAAS,IAAI,SAAS,GAAG,IAAI,GAAG,KAAK;AACnC,WAAO,OAAO,SAAS,KAAK,GAAG,CAAC,KAAK;AACrC,WAAO,CAAC,IAAI;AAAA,EACd;AAEA,SAAO;AACT;AAUO,SAAS,WAAW,QAAkB,QAA0B;AACrE,QAAM,IAAI,OAAO;AACjB,QAAM,SAAS,IAAI,MAAc,CAAC,EAAE,KAAK,GAAG;AAC5C,MAAI,IAAI,OAAQ,QAAO;AAGvB,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,YAAQ,OAAO,CAAC;AAAA,EAClB;AACA,UAAQ;AACR,SAAO,SAAS,CAAC,IAAI;AAErB,QAAM,IAAI,KAAK,SAAS;AACxB,WAAS,IAAI,QAAQ,IAAI,GAAG,KAAK;AAC/B,WAAO,CAAC,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,IAAI;AAAA,EACnD;AAEA,SAAO;AACT;AAOO,SAAS,WAAW,QAAkB,QAA0B;AACrE,QAAM,IAAI,OAAO;AACjB,QAAM,SAAS,IAAI,MAAc,CAAC,EAAE,KAAK,GAAG;AAE5C,WAAS,IAAI,SAAS,GAAG,IAAI,GAAG,KAAK;AACnC,QAAI,MAAM;AACV,aAAS,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK;AACxC,aAAO,OAAO,CAAC;AAAA,IACjB;AACA,WAAO,CAAC,IAAI,MAAM;AAAA,EACpB;AAEA,SAAO;AACT;AAcO,SAAS,mBAAmB,QAAkB,QAA0B;AAC7E,QAAM,IAAI,OAAO;AACjB,QAAM,SAAS,IAAI,MAAc,CAAC,EAAE,KAAK,GAAG;AAG5C,QAAM,aAAa,IAAI,MAAc,CAAC,EAAE,KAAK,GAAG;AAChD,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,eAAW,CAAC,IAAI,KAAK,IAAI,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC;AAAA,EACpD;AAIA,WAAS,IAAI,QAAQ,IAAI,GAAG,KAAK;AAC/B,UAAM,SAAmB,CAAC;AAC1B,aAAS,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK;AACxC,aAAO,KAAK,WAAW,CAAC,CAAC;AAAA,IAC3B;AAEA,UAAM,OAAO,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI;AAEjD,UAAM,WAAW,OAAO,OAAO,CAAC,KAAK,MAAM,OAAO,IAAI,SAAS,GAAG,CAAC,IAAI;AACvE,WAAO,CAAC,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,GAAG,IAAI;AAAA,EACrD;AAEA,SAAO;AACT;AAYO,SAAS,uBAAuB,QAA4B;AACjE,QAAM,IAAI,OAAO;AACjB,QAAM,SAAS,IAAI,MAAc,CAAC,EAAE,KAAK,CAAC;AAE1C,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,GAAG;AAE7B,aAAO,CAAC,IAAI,OAAO,IAAI,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,IAAI,IAAI;AAAA,IACvD,WAAW,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,GAAG;AAEpC,aAAO,CAAC,IAAI,OAAO,IAAI,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,IAAI,IAAI;AAAA,IACvD,OAAO;AAEL,aAAO,CAAC,IAAI;AAAA,IACd;AAAA,EACF;AAEA,SAAO;AACT;AAUO,SAAS,YACdC,OACA,MACA,KACA,YACQ;AACR,MAAIA,QAAO,cAAc,OAAO,WAAY,QAAO;AACnD,MAAIA,QAAO,cAAc,QAAQ,WAAY,QAAO;AACpD,SAAO;AACT;AASO,SAAS,OAAO,SAAyB;AAE9C,QAAM,QAAQ,2BAA2B,KAAK,OAAO;AACrD,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAClC,QAAM,QAAQ,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AACvC,QAAM,MAAM,SAAS,MAAM,CAAC,GAAG,EAAE;AAIjC,QAAM,OAAO,IAAI,KAAK,MAAM,OAAO,GAAG;AACtC,MAAI,KAAK,OAAO,MAAM,EAAG,QAAO;AAGhC,QAAM,WAAW,IAAI,KAAK,MAAM,OAAO,CAAC;AACxC,QAAM,kBAAmB,IAAI,SAAS,OAAO,IAAI,KAAK,IAAK;AAG3D,QAAM,cAAc,iBAAiB;AAErC,SAAO,QAAQ,cAAc,IAAI;AACnC;AAsBO,SAAS,wBAAwB,MAA0C;AAChF,SAAO,KAAK,IAAI,CAAC,KAAK,MAA0B;AAC9C,UAAM,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC,IAAI;AAGnC,UAAM,gBAAgB,IAAI,gBAAgB,IAAI;AAG9C,UAAM,kBACJ,IAAI,eAAe,QAAQ,IAAI,aAAa,QAAQ,IAAI,cAAc,IAClE,IAAI,cAAc,IAAI,YACtB;AAEN,UAAM,kBACJ,IAAI,aAAa,QAAQ,IAAI,eAAe,QAAQ,IAAI,gBAAgB,IACpE,IAAI,YAAY,IAAI,cACpB;AAEN,UAAM,gBACJ,IAAI,YAAY,QAAQ,iBAAiB,QAAQ,kBAAkB,KAC7D,IAAI,WAAW,iBAAiB,gBAAiB,MACnD;AAGN,UAAM,mBACJ,IAAI,eAAe,QAAQ,IAAI,cAAc,QAAQ,IAAI,eAAe,KAClE,IAAI,cAAc,IAAI,cAAc,IAAI,aAAc,MACxD;AAEN,UAAM,mBACJ,IAAI,eAAe,QAAQ,IAAI,cAAc,QAAQ,IAAI,eAAe,KAClE,IAAI,cAAc,IAAI,cAAc,IAAI,aAAc,MACxD;AAGN,UAAM,eAAe,MAAM,aAAa;AAExC,UAAM,cACJ,iBAAiB,QAAQ,gBAAgB,QAAQ,iBAAiB,KAC5D,gBAAgB,gBAAgB,eAAgB,MAClD;AAEN,UAAM,iBACJ,IAAI,aAAa,QAAQ,gBAAgB,QAAQ,iBAAiB,KAC5D,IAAI,YAAY,gBAAgB,eAAgB,MAClD;AAEN,WAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAYO,SAAS,uBAAuB,WAAyC;AAC9E,MAAI,cAAc,QAAQ,cAAc,UAAa,MAAM,SAAS,EAAG,QAAO;AAC9E,MAAI,YAAY,EAAG,QAAO;AAC1B,MAAI,YAAY,GAAI,QAAO;AAC3B,SAAO;AACT;AAYO,SAAS,kBAAkB,UAA0B;AAC1D,MAAI,WAAW,GAAI,QAAO;AAC1B,MAAI,WAAW,GAAI,QAAO;AAC1B,MAAI,WAAW,GAAI,QAAO;AAC1B,MAAI,WAAW,GAAI,QAAO;AAC1B,MAAI,WAAW,GAAI,QAAO;AAC1B,SAAO;AACT;AAWO,SAAS,sBACd,YACA,UACA,YACQ;AAER,MAAI,aAAa,SAAU,QAAO;AAClC,MAAI,WAAW,WAAY,QAAO;AAClC,SAAO;AACT;AASO,SAAS,WAAW,QAAkB,SAAS,KAAe;AACnE,QAAM,IAAI,OAAO;AACjB,QAAM,SAAS,IAAI,MAAc,CAAC,EAAE,KAAK,GAAG;AAC5C,WAAS,IAAI,SAAS,GAAG,IAAI,GAAG,KAAK;AACnC,QAAI,MAAM,UAAU,MAAM;AAC1B,aAAS,IAAI,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK;AACxC,UAAI,OAAO,CAAC,IAAI,IAAK,OAAM,OAAO,CAAC;AACnC,UAAI,OAAO,CAAC,IAAI,IAAK,OAAM,OAAO,CAAC;AAAA,IACrC;AACA,UAAM,QAAQ,MAAM;AACpB,WAAO,CAAC,IAAI,QAAQ,KAAM,OAAO,CAAC,IAAI,OAAO,QAAS,MAAM;AAAA,EAC9D;AACA,SAAO;AACT;AASO,SAAS,WAAW,QAAkB,SAAS,KAAe;AACnE,QAAM,IAAI,OAAO;AACjB,QAAM,SAAS,IAAI,MAAc,CAAC,EAAE,KAAK,GAAG;AAC5C,WAAS,IAAI,SAAS,GAAG,IAAI,GAAG,KAAK;AACnC,QAAI,mBAAmB;AAEvB,aAAS,IAAI,IAAI,SAAS,GAAG,IAAI,GAAG,KAAK;AACvC,UAAI,OAAO,CAAC,KAAK,OAAO,CAAC,EAAG;AAAA,IAC9B;AACA,WAAO,CAAC,IAAK,oBAAoB,SAAS,KAAM;AAAA,EAClD;AACA,SAAO;AACT;AAsDA,SAAS,aAAa,SAAiB,MAAsB;AAC3D,QAAM,IAAI,oBAAI,KAAK,UAAU,YAAY;AACzC,IAAE,WAAW,EAAE,WAAW,IAAI,IAAI;AAClC,SAAO,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AACrC;AAGA,SAAS,aAAa,SAA8B;AAClD,QAAM,IAAI,QAAQ,MAAM,2BAA2B;AACnD,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,IAAI,KAAK,SAAS,EAAE,CAAC,CAAC,GAAG,SAAS,EAAE,CAAC,CAAC,IAAI,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC;AACpE;AAWA,IAAM,2BAAkE;AAAA;AAAA,EAEtE,EAAE,MAAM,eAAe,MAAM,SAAS;AAAA,EACtC,EAAE,MAAM,WAAW,MAAM,SAAS;AAAA,EAClC,EAAE,MAAM,WAAW,MAAM,SAAS;AAAA,EAClC,EAAE,MAAM,UAAU,MAAM,SAAS;AAAA,EACjC,EAAE,MAAM,sBAAsB,MAAM,SAAS;AAAA,EAC7C,EAAE,MAAM,sBAAsB,MAAM,SAAS;AAAA,EAC7C,EAAE,MAAM,mBAAmB,MAAM,SAAS;AAAA,EAC1C,EAAE,MAAM,oBAAoB,MAAM,SAAS;AAAA,EAC3C,EAAE,MAAM,aAAa,MAAM,SAAS;AAAA,EACpC,EAAE,MAAM,cAAc,MAAM,SAAS;AAAA,EACrC,EAAE,MAAM,sBAAsB,MAAM,SAAS;AAAA,EAC7C,EAAE,MAAM,uBAAuB,MAAM,SAAS;AAAA,EAC9C,EAAE,MAAM,2BAA2B,MAAM,SAAS;AAAA,EAClD,EAAE,MAAM,cAAc,MAAM,UAAU;AAAA,EACtC,EAAE,MAAM,oBAAoB,MAAM,UAAU;AAAA,EAC5C,EAAE,MAAM,mBAAmB,MAAM,SAAS;AAAA,EAC1C,EAAE,MAAM,sBAAsB,MAAM,SAAS;AAAA;AAAA,EAE7C,EAAE,MAAM,aAAa,MAAM,SAAS;AAAA,EACpC,EAAE,MAAM,YAAY,MAAM,SAAS;AAAA,EACnC,EAAE,MAAM,mBAAmB,MAAM,UAAU;AAAA,EAC3C,EAAE,MAAM,iBAAiB,MAAM,UAAU;AAAA,EACzC,EAAE,MAAM,0BAA0B,MAAM,SAAS;AAAA,EACjD,EAAE,MAAM,yBAAyB,MAAM,SAAS;AAAA;AAAA,EAEhD,EAAE,MAAM,eAAe,MAAM,UAAU;AAAA,EACvC,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,EACjC,EAAE,MAAM,WAAW,MAAM,UAAU;AAAA;AAAA,EAEnC,EAAE,MAAM,OAAO,MAAM,SAAS;AAAA,EAC9B,EAAE,MAAM,OAAO,MAAM,SAAS;AAChC;AAOA,eAAe,8BACb,MACA,WACe;AACf,aAAW,OAAO,0BAA0B;AAC1C,QAAI;AACF,YAAM,KAAK,IAAI,gBAAgB,SAAS,iBAAiB,IAAI,IAAI,KAAK,IAAI,IAAI,EAAE;AAAA,IAClF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAuBA,eAAe,0BACb,MACA,SAC2D;AAC3D,QAAM,KAAK,KAAK,IAAI;AACpB,QAAM,aAAa,iBAAiB,EAAE;AACtC,QAAM,mBAAmB,wBAAwB,EAAE;AAEnD,QAAM,YAAY,2BAA2B,SAAS,OAAO;AAC7D,QAAM,kBAAkB,2BAA2B,SAAS,cAAc;AAC1E,QAAM,cAAmB,UAAK,iBAAiB,OAAO,GAAG,UAAU;AACnE,QAAM,qBAA0B,UAAK,aAAa,YAAY,cAAc;AAC5E,QAAM,sBAA2B,UAAK,aAAa,WAAW,cAAc;AAG5E,MAAI,WAAW,SAAS,GAAG;AAEzB,UAAM,KAAK,IAAI,sBAAsB,UAAU,oCAAoC,SAAS,IAAI;AAEhG,UAAM,8BAA8B,MAAM,UAAU;AAAA,EACtD,WAAW,uBAAuB,WAAW,GAAG;AAW9C,UAAM,KAAK;AAAA,MACT,sBAAsB,UAAU;AAAA,qCACD,kBAAkB;AAAA,IACnD;AACA,eAAW,SAAS,CAAC,QAAQ,QAAQ,OAAO,OAAO,GAAG;AACpD,UAAI;AACF,cAAM,KAAK,IAAI,gBAAgB,UAAU,iBAAiB,KAAK,UAAU;AAAA,MAC3E,QAAQ;AAAA,MAER;AAAA,IACF;AAIA,UAAM,8BAA8B,MAAM,UAAU;AAAA,EACtD,OAAO;AAML,UAAM,KAAK;AAAA,MACT,sBAAsB,UAAU;AAAA,IAClC;AACA,eAAW,SAAS,CAAC,QAAQ,QAAQ,OAAO,OAAO,GAAG;AACpD,UAAI;AACF,cAAM,KAAK,IAAI,gBAAgB,UAAU,iBAAiB,KAAK,UAAU;AAAA,MAC3E,QAAQ;AAAA,MAER;AAAA,IACF;AACA,UAAM,8BAA8B,MAAM,UAAU;AAAA,EACtD;AAoBA,MAAI;AAOF,UAAM,KAAK;AAAA,MACT,gBAAgB,UAAU;AAAA;AAAA;AAAA;AAAA,0BAIN,UAAU;AAAA;AAAA;AAAA;AAAA,IAIhC;AAAA,EACF,QAAQ;AAAA,EAGR;AAGA,MAAI,WAAW,eAAe,GAAG;AAE/B,UAAM,KAAK,IAAI,sBAAsB,gBAAgB,oCAAoC,eAAe,IAAI;AAAA,EAC9G,WAAW,WAAW,mBAAmB,GAAG;AAE1C,UAAM,KAAK;AAAA,MACT,sBAAsB,gBAAgB,oCAAoC,mBAAmB;AAAA,IAC/F;AAAA,EACF,OAAO;AACL,UAAM,KAAK,IAAI,sBAAsB,gBAAgB;AAAA;AAAA;AAAA,MAGnD;AAAA,EACJ;AAIA,QAAM,KAAK,IAAI,4BAA4B,gBAAgB,cAAc,gBAAgB,SAAS;AAMlG,QAAM,KAAK,IAAI,4BAA4B,UAAU,YAAY,UAAU,iBAAiB;AAE5F,SAAO,EAAE,YAAY,iBAAiB;AACxC;AAOA,SAAS,uBAAuB,KAAsB;AACpD,MAAI,CAAC,WAAW,GAAG,EAAG,QAAO;AAC7B,MAAI;AACF,WAAO,YAAY,GAAG,EAAE,KAAK,CAAC,UAAkB;AAC9C,UAAI,CAAC,MAAM,WAAW,SAAS,EAAG,QAAO;AACzC,aAAO,WAAgB,UAAK,KAAK,OAAO,cAAc,CAAC;AAAA,IACzD,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAyBA,eAAe,uBACb,MACA,SACA,QACA,QACe;AACf,QAAM,kBAAkB,yBAAyB,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,GAAG,EAAE,KAAK,IAAI;AACpF,QAAM,aAAkB;AAAA,IACtB,iBAAiB,OAAO;AAAA,IACxB;AAAA,IACA,UAAU,MAAM;AAAA,IAChB;AAAA,EACF;AACA,QAAM,mBAAmB,MAAM;AAAA,IAC7B,YAAY;AAAA,IACZ,aAAa,wBAAwB,eAAe,UAAU,OAAO,UAAU,qBAAqB,MAAM;AAAA,EAC5G,CAAC;AAED,QAAM,cAAmB;AAAA,IACvB,iBAAiB,OAAO;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,mBAAmB,MAAM;AAAA,IAC7B,YAAY;AAAA,IACZ,aAAa,kBAAkB,OAAO,gBAAgB;AAAA,EACxD,CAAC;AAMH;AAGA,eAAe,iBACb,MACA,MACA,SACA,YAAoB,mBACL;AACf,MAAI,KAAK,WAAW,EAAG;AAEvB,QAAM,UAAU,CAAC,UAAU,QAAQ,GAAG,OAAO;AAC7C,QAAM,eAAe,KAClB,IAAI,CAAC,GAAG,WAAW;AAClB,UAAMC,UAAS,QAAQ,IAAI,CAAC,IAAI,WAAW,IAAI,SAAS,QAAQ,SAAS,SAAS,CAAC,EAAE;AACrF,WAAO,IAAIA,QAAO,KAAK,IAAI,CAAC;AAAA,EAC9B,CAAC,EACA,KAAK,IAAI;AAMZ,QAAM,MAAM;AAAA,6BACe,SAAS,KAAK,QAAQ,KAAK,IAAI,CAAC;AAAA,aAChD,YAAY;AAAA;AAEvB,QAAM,SAAS,KAAK,QAAQ,CAAC,QAAQ,QAAQ,IAAI,CAAC,QAAQ,IAAI,GAAG,KAAK,IAAI,CAAC;AAC3E,QAAM,KAAK,IAAI,KAAK,MAAuD;AAC7E;AAGA,eAAe,SACb,MACA,SACA,WACqB;AACrB,QAAM,cAAc,SAAS,SAAS;AACtC,QAAM,oBAAoB,SAAS,eAAe;AAelD,MAAI,uBAAuB;AAC3B,MAAI,eAA8B;AAClC,MAAI,WAAW;AACb,mBAAe,uBAAuB,KAAK,IAAI,CAAC;AAChD,UAAM,KAAK;AAAA,MACT,sBAAsB,YAAY;AAAA,IACpC;AACA,eAAW,aAAa,CAAC,OAAO,SAAS,OAAO,GAAG;AACjD,YAAM,OAAO,MAAM,UAAU,cAAc,WAAW,cAAc,YAAY;AAChF,UAAI,KAAK,WAAW,EAAG;AACvB,YAAM,aAAa;AACnB,eAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,YAAY;AAC5D,cAAM,QAAQ,KAAK,MAAM,OAAO,QAAQ,UAAU;AAClD,cAAM,eAAe,MAClB,IAAI,CAAC,GAAG,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EACrG,KAAK,GAAG;AACX,cAAM,SAAS,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC;AACtF,cAAM,KAAK;AAAA,UACT,gBAAgB,YAAY,YAAY,YAAY;AAAA,UACpD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,2BAAuB,IAAI,YAAY;AAAA,EACzC;AAEA,MAAI;AAEJ,UAAM,eAAe,MAAM,KAAK;AAAA,MAC9B,+BAA+B,oBAAoB;AAAA,IACrD;AACA,UAAM,aAAa,aAAa,QAAQ,EAAE,IAAI,OAAK,EAAE,CAAC,CAAW;AACjE,QAAI,WAAW,WAAW,KAAK,CAAC,WAAW,SAAS,KAAK,GAAG;AAC1D,aAAO,EAAE,QAAQ,WAAW,QAAQ,6CAAwC;AAAA,IAC9E;AAMA,UAAM,cAAc,YAAY,uBAAuB;AACvD,eAAW,UAAU,YAAY;AAC/B,YAAM,cAAc,MAAM,KAAK;AAAA,QAC7B,2BAA2B,WAAW;AAAA,QACtC,CAAC,MAAM;AAAA,MACT;AACA,YAAM,OAAO,YAAY,QAAQ;AACjC,UAAI,KAAK,WAAW,EAAG;AAEvB,YAAM,QAAQ,KAAK,IAAI,OAAK,EAAE,CAAC,CAAW;AAC1C,YAAM,SAAS,KAAK,IAAI,OAAK,EAAE,CAAC,CAAW;AAC3C,YAAM,YAAY,WAAW,QAAQ,GAAG;AACxC,YAAM,YAAY,WAAW,QAAQ,GAAG;AAGxC,YAAMC,cAAa;AACnB,eAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAASA,aAAY;AAC7D,cAAM,aAAa,MAAM,MAAM,OAAO,QAAQA,WAAU;AACxD,cAAM,WAAW,UAAU,MAAM,OAAO,QAAQA,WAAU;AAC1D,cAAM,WAAW,UAAU,MAAM,OAAO,QAAQA,WAAU;AAE1D,cAAM,eAAe,WAAW,IAAI,CAAC,GAAG,WAAW;AACjD,gBAAM,OAAO,SAAS;AACtB,iBAAO,KAAK,OAAO,CAAC,MAAM,OAAO,CAAC,MAAM,OAAO,CAAC;AAAA,QAClD,CAAC,EAAE,KAAK,IAAI;AAEZ,cAAM,MAAM;AAAA,iBACD,WAAW;AAAA;AAAA,uBAEL,YAAY;AAAA,4BACP,WAAW,SAAS,IAAI,CAAC;AAAA;AAE/C,cAAM,SAAqC,CAAC;AAC5C,iBAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,iBAAO,KAAK,WAAW,CAAC,CAAC;AACzB,iBAAO,KAAK,MAAM,SAAS,CAAC,CAAC,IAAI,OAAO,SAAS,CAAC,CAAC;AACnD,iBAAO,KAAK,MAAM,SAAS,CAAC,CAAC,IAAI,OAAO,SAAS,CAAC,CAAC;AAAA,QACrD;AACA,eAAO,KAAK,MAAM;AAClB,cAAM,KAAK,IAAI,KAAK,MAAuD;AAAA,MAC7E;AAAA,IACF;AAWA,UAAM,iBAAiB,YAAY,uBAAuB;AAC1D,UAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAWZ,cAAc;AAAA,gBACT,cAAc;AAAA,gBACd,cAAc;AAAA,gBACd,WAAW;AAAA;AAAA;AAAA;AAKzB,UAAM,YAAY,MAAM,KAAK,cAAc,cAAc,CAAC,qBAAqB,CAAC;AAChF,UAAM,UAAU,UAAU,QAAQ;AAClC,QAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,QAAQ,YAAY,eAAe,EAAE;AAOxE,UAAM,gBAAgB,oBAAI,IAAoB;AAC9C,QAAI,WAAW;AACb,UAAI;AACF,cAAM,UAAU,MAAM,UAAU;AAAA,UAC9B;AAAA,UACA,QAAQ,CAAC,EAAE,CAAC;AAAA,UACZ,QAAQ,QAAQ,SAAS,CAAC,EAAE,CAAC;AAAA,QAC/B;AACA,mBAAW,OAAO,SAAS;AACzB,gBAAM,UAAU,IAAI;AACpB,cAAI,WAAW,QAAQ,UAAU,WAAW,UAAU,QAAS;AAI/D,cAAI,CAAC,OAAO,SAAS,IAAI,IAAI,KAAK,IAAI,QAAQ,EAAG;AACjD,gBAAM,UAAU,IAAI;AACpB,cAAI,CAAC,cAAc,IAAI,OAAO,GAAG;AAC/B,kBAAM,UAAU,IAAI;AACpB,gBAAI,WAAW,KAAM,eAAc,IAAI,SAAS,OAAO;AAAA,UACzD;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,OAAO;AACL,UAAI;AAMF,cAAM,YAAY,MAAM,KAAK;AAAA,UAC3B;AAAA;AAAA;AAAA;AAAA,QAIF;AACA,mBAAW,KAAK,UAAU,QAAQ,GAAG;AACnC,gBAAM,UAAU,EAAE,CAAC;AACnB,cAAI,CAAC,cAAc,IAAI,OAAO,GAAG;AAC/B,kBAAM,UAAU,EAAE,CAAC;AACnB,gBAAI,WAAW,QAAQ,UAAU,EAAG,eAAc,IAAI,SAAS,OAAO;AAAA,UACxE;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,kBAAkB,oBAAI,IAA2B;AACvD,UAAM,cAA4B,QAAQ,IAAI,CAAC,MAAM;AACnD,YAAM,UAAU,EAAE,CAAC;AACnB,sBAAgB,IAAI,SAAS,EAAE,CAAC,CAAkB;AAClD,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,EAAE,CAAC;AAAA,QACb,WAAW,EAAE,CAAC;AAAA,QACd,UAAU,EAAE,CAAC;AAAA,QACb,cAAc,cAAc,IAAI,OAAO,KAAK;AAAA,QAC5C,YAAY,EAAE,CAAC;AAAA,QACf,aAAa,EAAE,CAAC;AAAA,QAChB,YAAY,EAAE,CAAC;AAAA,QACf,aAAa,EAAE,CAAC;AAAA,MAClB;AAAA,IACF,CAAC;AAGD,UAAM,kBAAkB,wBAAwB,WAAW;AAG3D,UAAM,cAAc,CAAC,QAAQ,cAAc,wBAAwB,mBAAmB,iBAAiB,aAAa;AACpH,UAAM,aAAa;AACnB,aAAS,QAAQ,GAAG,QAAQ,gBAAgB,QAAQ,SAAS,YAAY;AACvE,YAAM,QAAQ,gBAAgB,MAAM,OAAO,QAAQ,UAAU;AAC7D,YAAM,eAAe,MAAM,IAAI,CAAC,GAAG,WAAW;AAC5C,cAAMD,UAAS,YAAY,IAAI,CAAC,IAAI,WAAW,IAAI,SAAS,YAAY,SAAS,SAAS,CAAC,EAAE;AAC7F,eAAO,IAAIA,QAAO,KAAK,IAAI,CAAC;AAAA,MAC9B,CAAC,EAAE,KAAK,IAAI;AAEZ,YAAM,MAAM,0BAA0B,iBAAiB,KAAK,YAAY,KAAK,IAAI,CAAC,YAAY,YAAY;AAC1G,YAAM,SAAS,MAAM,QAAQ,CAAC,MAAM;AAClC,cAAM,KAAK,EAAE,aAAa;AAC1B,cAAM,KAAK,EAAE,eAAe;AAC5B,cAAM,MAAM,EAAE,eAAe;AAC7B,eAAO;AAAA,UACL,EAAE;AAAA,UACF,OAAO,OAAO,kBAAkB,EAAE,IAAI;AAAA,UACtC,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,sBAAsB,IAAI,IAAI,GAAG,IAAI;AAAA,UAClF,uBAAuB,gBAAgB,IAAI,EAAE,IAAI,KAAK,IAAI;AAAA,UAC1D,EAAE,iBAAiB;AAAA,UACnB,EAAE,eAAe;AAAA,QACnB;AAAA,MACF,CAAC;AACD,YAAM,KAAK,IAAI,KAAK,MAAuD;AAAA,IAC7E;AAEA,WAAO,EAAE,QAAQ,YAAY,eAAe,YAAY,SAAS,EAAE;AAAA,EACnE,UAAE;AAIA,QAAI,cAAc;AAChB,UAAI;AAAE,cAAM,KAAK,IAAI,yBAAyB,YAAY,GAAG;AAAA,MAAG,QAAQ;AAAA,MAAQ;AAAA,IAClF;AAAA,EACF;AACF;AAQA,eAAe,aACb,MACA,QACA,WACkB;AAClB,MAAI,WAAW;AACb,UAAM,MAAM,MAAM,UAAU,YAAY,QAAQ,cAAc,YAAY;AAC1E,WAAO,IAAI,aAAa;AAAA,EAC1B;AAGA,QAAM,IAAI,MAAM,KAAK;AAAA,IACnB;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AACA,SAAO,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;AAC5C;AAiBA,eAAsB,qBACpB,MACA,SACqB;AACrB,SAAO,SAAS,MAAM,OAAO;AAC/B;AA2BA,eAAsB,cACpB,MACA,QACA,OAA0B,CAAC,GAC3B,IAC2B;AAC3B,QAAM,EAAE,YAAY,MAAM,IAAI;AAG9B,QAAM,eAAe,KAAK,eAAe,cAAc,MAAM,CAAC,CAAC,KAAK;AACpE,MAAI,gBAAyE;AAE7E,MAAI,aAAa;AACf,oBAAgB,MAAM,0BAA0B,MAAM,KAAK,OAAQ;AAAA,EACrE;AAGA,QAAM,cAAc,gBAAgB,cAAc,aAAa;AAG/D,MAAI;AAMJ,QAAI,YAA2B;AAC/B,QAAI,CAAC,WAAW;AACd,UAAI,IAAI,gBAAgB;AACtB,oBAAY,MAAM,GAAG,eAAe,IAAI,MAAM;AAAA,MAChD,WAAW,KAAK,SAAS;AACvB,oBAAY,MAAM,mBAAmB,QAAQ,KAAK,OAAO;AAAA,MAC3D,OAAO;AAIL,oBAAY;AAAA,MACd;AAAA,IACF;AAGA,UAAM,gBAAgB,YAAY,aAAa,WAAW,GAAG,IAAI;AAYjE,QAAI;AACJ,QAAI,IAAI,WAAW;AACjB,YAAM,YAAY,iBAAiB;AACnC,YAAM,UAAU;AAChB,YAAM,YAAY,MAAM,GAAG,UAAU,cAAc,QAAQ,WAAW,OAAO;AAC7E,gBAAU,UAAU,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC;AAAA,IACnF,OAAO;AAML,UAAI,WAAW;AACf,YAAM,cAAyB,CAAC,MAAM;AACtC,UAAI,eAAe;AACjB,oBAAY;AACZ,oBAAY,KAAK,aAAa;AAAA,MAChC;AACA,kBAAY;AACZ,YAAM,YAAY,MAAM,KAAK,cAAc,UAAU,WAA4D;AACjH,gBAAU,UAAU,QAAQ;AAAA,IAC9B;AAEA,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO;AAAA,QACL;AAAA,QACA,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,QAAQ,IAAI,YAAY,2BAA2B;AAAA,QACrD;AAAA,QACA,OAAO,EAAE,QAAQ,WAAW,QAAQ,gBAAgB;AAAA,QACpD,OAAO,EAAE,QAAQ,WAAW,QAAQ,gBAAgB;AAAA,QACpD,cAAc;AAAA,QACd,iBAAiB;AAAA,MACnB;AAAA,IACF;AAQA,UAAM,kBAAkB,QAAQ,OAAO,CAAC,MAAM;AAC5C,YAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,YAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,YAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,YAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,aAAO,EAAE,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM;AAAA,IAClD,CAAC;AACD,UAAM,kBAAkB,QAAQ,SAAS,gBAAgB;AACzD,QAAI,kBAAkB,GAAG;AACvB,cAAQ;AAAA,QACN,4BAA4B,MAAM,YAAY,eAAe;AAAA,MAC/D;AAAA,IACF;AACA,cAAU;AAIV,UAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,CAAW;AAC/C,UAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC;AAC7C,UAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC;AAC7C,UAAM,OAAO,QAAQ,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC;AAC5C,UAAM,SAAS,QAAQ,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC;AAG9C,UAAM,QAAQ,WAAW,QAAQ,EAAE;AACnC,UAAM,SAAS,WAAW,OAAO,MAAM,QAAQ,EAAE;AACjD,UAAM,QAAQ,WAAW,QAAQ,EAAE;AACnC,UAAM,QAAQ,WAAW,QAAQ,EAAE;AACnC,UAAM,QAAQ,mBAAmB,QAAQ,CAAC;AAC1C,UAAM,SAAS,mBAAmB,QAAQ,EAAE;AAC5C,UAAM,kBAAkB,uBAAuB,MAAM;AAGrD,UAAM,YACJ,aAAa,CAAC,YACV,QAAQ,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,MAAM,CAAC,IAAI,SAAS,IAC3D,QAAQ,IAAI,CAAC,GAAG,MAAM,CAAC;AAE7B,QAAI,UAAU,WAAW,GAAG;AAC1B,YAAME,gBAAe,gBAAgB;AAAA,QACnC,OAAO,cAAc;AAAA,QACrB,aAAa,cAAc;AAAA,MAC7B,IAAI;AACJ,YAAMC,eAAc,MAAM,SAAS,MAAMD,eAAc,IAAI,SAAS;AAGpE,UAAI,eAAe,iBAAiB,KAAK,SAAS;AAChD,cAAM,uBAAuB,MAAM,KAAK,SAAS,QAAQ,aAAa;AAAA,MACxE;AAEA,aAAO;AAAA,QACL;AAAA,QACA,OAAO,EAAE,QAAQ,YAAY,eAAe,GAAG,QAAQ,qBAAqB;AAAA,QAC5E,OAAOC;AAAA,QACP,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV;AAAA,QACA,cAAc;AAAA,QACd,iBAAiB;AAAA,MACnB;AAAA,IACF;AAGA,UAAM,eAAe,UAAU,IAAI,CAAC,MAAM;AACxC,YAAM,SAAS,OAAO,CAAC;AACvB,YAAM,SAAS,CAAC,MAAM,MAAM,KAAK,OAAO,CAAC,IAAI,IAAK,SAAS,OAAO,CAAC,IAAK,MAAM;AAC9E,YAAM,aAAa,IAAI,IAAI,OAAO,IAAI,CAAC,IAAI;AAC3C,YAAM,cACJ,IAAI,KAAM,OAAO,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,IAAK,MAAM;AACpE,YAAM,SACJ,eAAe,QAAQ,aAAa,KAC9B,MAAM,CAAC,IAAI,cAAc,aAAc,MACzC;AAON,YAAM,mBACJ,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,KACrC,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,OAAO,CAAC,IAAK,MACrC;AACN,YAAM,oBACJ,MAAM,CAAC,IAAI,KAAM,OAAO,CAAC,IAAI,MAAM,CAAC,KAAK,MAAM,CAAC,IAAK,MAAM;AAC7D,YAAM,YAAY,MAAM,CAAC,IAAI,KAAK,CAAC;AACnC,YAAM,kBACJ,YAAY,KAAK,OAAO,CAAC,IAAI,KAAK,CAAC,KAAK,YAAY;AACtD,YAAM,QACJ,KAAK,KAAK,OAAO,IAAI,CAAC,IAAI,KACpB,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,IAAK,MAChD;AACN,YAAM,SACJ,KAAK,MAAM,OAAO,IAAI,EAAE,IAAI,KACtB,OAAO,CAAC,IAAI,OAAO,IAAI,EAAE,KAAK,OAAO,IAAI,EAAE,IAAK,MAClD;AACN,YAAM,YACJ,eAAe,OAAO,YAAY,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,UAAU,IAAI;AAC/E,YAAM,UAAU,aAAa,MAAM,CAAC,CAAC;AACrC,YAAM,YAAY,UAAU,QAAQ,OAAO,IAAI;AAC/C,YAAM,WAAW,UAAU,QAAQ,SAAS,IAAI,IAAI;AACpD,YAAM,OAAO,OAAO,MAAM,CAAC,CAAC;AAC5B,YAAM,WAAW,MAAM,CAAC;AACxB,YAAM,WAAW,MAAM,CAAC;AACxB,YAAM,eACJ,CAAC,MAAM,QAAQ,KAAK,WAAW,KACzB,OAAO,CAAC,IAAI,YAAY,WAAY,MACtC;AACN,YAAM,eACJ,CAAC,MAAM,QAAQ,KAAK,WAAW,KACzB,OAAO,CAAC,IAAI,YAAY,WAAY,MACtC;AACN,YAAM,WAAW,MAAM,CAAC;AAcxB,UAAI,kBAAiC;AACrC,UAAI,IAAI,GAAG;AACT,cAAMC,cAAa,OAAO,IAAI,CAAC;AAC/B,cAAM,YAAa,MAAM,IAAI,CAAC;AAC9B,cAAM,WAAa,KAAK,IAAI,CAAC;AAC7B,cAAM,WAAa,OAAO,IAAI,CAAC;AAC/B,YACEA,cAAa,KACb,YAAY,KACZ,WAAW,KACX,CAAC,MAAM,QAAQ,KAAK,WAAW,GAC/B;AACA,gBAAM,iBAAkB,YAAY,YAAYA,cAAc;AAC9D,gBAAM,cAAe,WAAWA,cAAc;AAC9C,4BAAkB,gBAAgB;AAAA,QACpC;AAAA,MACF;AAEA,aAAO;AAAA,QACL;AAAA,QACA,MAAM,MAAM,CAAC;AAAA,QACb,aAAa;AAAA,QACb,SAAS;AAAA,QACT,QAAQ,MAAM,QAAQ,IAAI,OAAO;AAAA,QACjC,SAAS;AAAA,QACT,oBAAoB;AAAA,QACpB,oBAAoB;AAAA,QACpB,iBAAiB,MAAM,MAAM,CAAC,CAAC,IAAI,OAAO,MAAM,CAAC;AAAA,QACjD,kBAAkB,MAAM,OAAO,CAAC,CAAC,IAAI,OAAO,OAAO,CAAC;AAAA,QACpD,WAAW;AAAA,QACX,YAAY;AAAA,QACZ,oBAAoB;AAAA,QACpB,qBAAqB;AAAA,QACrB,yBAAyB;AAAA,QACzB,YAAY;AAAA,QACZ,kBAAkB,gBAAgB,CAAC;AAAA,QACnC,iBAAiB;AAAA,QACjB,aAAa;AAAA,QACb,OAAO;AAAA,QACP,SAAS;AAAA,QACT,oBAAoB;AAAA,MACtB;AAAA,IACF,CAAC;AAGD,UAAM,aAAa;AACnB,UAAM,UAAU;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,aAAS,QAAQ,GAAG,QAAQ,aAAa,QAAQ,SAAS,YAAY;AACpE,YAAM,QAAQ,aAAa,MAAM,OAAO,QAAQ,UAAU;AAC1D,YAAM,iBAAiB,MAAM,OAAO,SAAS,WAAW;AAAA,IAC1D;AAGA,UAAM,eAAe,gBAAgB;AAAA,MACnC,OAAO,cAAc;AAAA,MACrB,aAAa,cAAc;AAAA,IAC7B,IAAI;AACJ,UAAM,cAAc,MAAM,SAAS,MAAM,cAAc,IAAI,SAAS;AAGpE,UAAM,cAAc,MAAM,SAAS,MAAM,QAAQ,OAAO,aAAa,IAAI,SAAS;AAUlF,UAAM,eAAe,MAAM,MAAM,SAAS,CAAC;AAC3C,QAAI,IAAI,gBAAgB;AACtB,YAAM,GAAG,eAAe,OAAO,QAAQ,YAAY;AAAA,IACrD,WAAW,KAAK,SAAS;AACvB,YAAM,sBAAsB,QAAQ,cAAc,KAAK,OAAO;AAAA,IAChE;AAIA,QAAI,eAAe,iBAAiB,KAAK,SAAS;AAChD,YAAM,uBAAuB,MAAM,KAAK,SAAS,QAAQ,aAAa;AAAA,IACxE;AAEA,WAAO;AAAA,MACL;AAAA,MACA,OAAO,EAAE,QAAQ,YAAY,eAAe,QAAQ,OAAO;AAAA,MAC3D,OAAO;AAAA,MACP,OAAO;AAAA,MACP,cAAc,aAAa;AAAA,MAC3B,iBAAiB;AAAA,IACnB;AAAA,EAEA,UAAE;AAKA,QAAI,eAAe;AACjB,UAAI;AAAE,cAAM,KAAK,IAAI,yBAAyB,cAAc,UAAU,GAAG;AAAA,MAAG,QAAQ;AAAA,MAAQ;AAC5F,UAAI;AAAE,cAAM,KAAK,IAAI,yBAAyB,cAAc,gBAAgB,GAAG;AAAA,MAAG,QAAQ;AAAA,MAAQ;AAAA,IACpG;AAAA,EACF;AACF;AASA,SAAS,mBAAmB,MAAsB;AAChD,QAAM,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI,MAAM;AACzC,SAAO,IAAI,IAAI;AACjB;AAkBO,SAAS,4BACd,MAQO;AAOP,SAAO,KAAK;AAAA,IACV,CAAC,MACC,OAAO,SAAS,EAAE,IAAI,KAAK,EAAE,OAAO,KACpC,OAAO,SAAS,EAAE,IAAI,KAAK,EAAE,OAAO,KACpC,OAAO,SAAS,EAAE,GAAG,KAAM,EAAE,MAAO,KACpC,OAAO,SAAS,EAAE,KAAK,KAAI,EAAE,QAAO;AAAA,EACxC;AACA,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,MAAI,UAAU;AACd,MAAI,SAAS;AACb,MAAI,cAAc,KAAK,CAAC,EAAE;AAC1B,MAAI,aAAa,KAAK,CAAC,EAAE;AAEzB,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,OAAO,SAAS;AACtB,gBAAU,IAAI;AACd,oBAAc,IAAI;AAAA,IACpB;AACA,QAAI,IAAI,MAAM,QAAQ;AACpB,eAAS,IAAI;AACb,mBAAa,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,WAAW,mBAAmB,WAAW;AAC/C,QAAM,UAAU,mBAAmB,UAAU;AAC7C,QAAM,gBAAgB,WAAW;AAGjC,QAAM,gBAAgB,WAAW;AACjC,QAAM,eAAe,UAAU;AAC/B,QAAM,kBAAkB,YAAY;AACpC,QAAM,iBAAiB,WAAW;AAElC,MAAI,eAAe;AACnB,MAAI,iBAAiB,eAAgB,gBAAe;AAAA,WAC3C,gBAAgB,gBAAiB,gBAAe;AAIzD,QAAM,cAAc,KAAK,OAAO,OAAK,mBAAmB,EAAE,IAAI,IAAI,EAAE;AACpE,MAAI,uBAAuB;AAC3B,QAAM,eAAe,UAAU;AAC/B,MAAI,YAAY,SAAS,KAAK,eAAe,GAAG;AAC9C,UAAM,WAAW,KAAK,IAAI,GAAG,YAAY,IAAI,OAAK,EAAE,IAAI,CAAC;AACzD,UAAM,UAAU,KAAK,IAAI,GAAG,YAAY,IAAI,OAAK,EAAE,GAAG,CAAC;AACvD,4BAAwB,WAAW,WAAW;AAAA,EAChD;AAIA,MAAI,sBAAsB;AAC1B,MAAI,KAAK,UAAU,GAAG;AACpB,UAAM,aAAuB,CAAC;AAC9B,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAI,KAAK,IAAI,CAAC,EAAE,QAAQ,KAAK,KAAK,CAAC,EAAE,QAAQ,GAAG;AAC9C,mBAAW,KAAK,KAAK,IAAI,KAAK,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAC,EAAE,KAAK,CAAC;AAAA,MAC7D;AAAA,IACF;AACA,QAAI,WAAW,SAAS,GAAG;AACzB,YAAM,OAAO,WAAW,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,WAAW;AAChE,YAAM,WAAW,WAAW,OAAO,CAAC,GAAG,MAAM,KAAK,IAAI,SAAS,GAAG,CAAC,IAAI,WAAW;AAClF,YAAM,YAAY,KAAK,KAAK,QAAQ;AAGpC,4BAAsB,YAAY,KAAK,KAAK,KAAK,SAAS,GAAG;AAAA,IAC/D;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,SAAS,eAAe,cAAc,sBAAsB,oBAAoB;AACrG;AAGA,eAAe,SACb,MACA,QACA,OACA,cAAsB,mBACtB,WACqB;AAGrB,QAAM,UAAU,MAAM,aAAa,MAAM,QAAQ,SAAS;AAC1D,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAOA,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,MAAI,WAAW;AACb,UAAM,OAAO,MAAM,UAAU,SAAS,QAAQ,MAAM,CAAC,GAAG,MAAM,MAAM,SAAS,CAAC,CAAC;AAE/E,WAAO,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC;AACvE,cAAU;AACV,cAAU;AACV,cAAU;AACV,cAAU;AACV,aAAS;AACT,eAAW;AAAA,EACb,OAAO;AAML,UAAM,SAAS,MAAM,KAAK;AAAA,MACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,CAAC,QAAQ,MAAM,CAAC,GAAG,MAAM,MAAM,SAAS,CAAC,CAAC;AAAA,IAC5C;AAEA,WAAO,OAAO,QAAQ;AACtB,UAAM,UAAU,OAAO,YAAY;AACnC,cAAU,QAAQ,QAAQ,MAAM;AAChC,cAAU,QAAQ,QAAQ,MAAM;AAChC,cAAU,QAAQ,QAAQ,MAAM;AAChC,cAAU,QAAQ,QAAQ,MAAM;AAChC,aAAS,QAAQ,QAAQ,KAAK;AAC9B,eAAW,QAAQ,QAAQ,OAAO;AAAA,EACpC;AAGA,QAAM,aAAa,oBAAI,IAA6F;AACpH,aAAW,OAAO,MAAM;AACtB,UAAM,UAAU,OAAO,IAAI,OAAO,CAAC;AACnC,UAAM,MAAM;AAAA,MACV,MAAM,OAAO,IAAI,OAAO,CAAC;AAAA,MACzB,MAAM,OAAO,IAAI,OAAO,CAAC;AAAA,MACzB,MAAM,OAAO,IAAI,OAAO,CAAC;AAAA,MACzB,KAAK,OAAO,IAAI,MAAM,CAAC;AAAA,MACvB,OAAO,OAAO,IAAI,QAAQ,CAAC;AAAA,IAC7B;AACA,QAAI,CAAC,WAAW,IAAI,OAAO,EAAG,YAAW,IAAI,SAAS,CAAC,CAAC;AACxD,eAAW,IAAI,OAAO,EAAG,KAAK,GAAG;AAAA,EACnC;AAEA,MAAI,WAAW,SAAS,GAAG;AACzB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAGA,QAAM,YAAY,CAAC,aAAa,YAAY,mBAAmB,iBAAiB,0BAA0B,uBAAuB;AACjI,QAAM,eAA+C,CAAC;AAEtD,aAAW,CAAC,SAAS,IAAI,KAAK,YAAY;AACxC,UAAM,SAAS,4BAA4B,IAAI;AAC/C,QAAI,CAAC,OAAQ;AAEb,iBAAa,KAAK;AAAA,MAChB;AAAA,MACA,MAAM;AAAA,MACN,WAAW,OAAO;AAAA,MAClB,UAAU,OAAO;AAAA,MACjB,iBAAiB,OAAO,gBAAgB,IAAI;AAAA,MAC5C,eAAe,OAAO;AAAA,MACtB,wBAAwB,OAAO;AAAA,MAC/B,uBAAuB,OAAO;AAAA,IAChC,CAAC;AAAA,EACH;AAGA,QAAM,aAAa;AACnB,WAAS,QAAQ,GAAG,QAAQ,aAAa,QAAQ,SAAS,YAAY;AACpE,UAAM,QAAQ,aAAa,MAAM,OAAO,QAAQ,UAAU;AAC1D,UAAM,iBAAiB,MAAM,OAAO,WAAW,WAAW;AAAA,EAC5D;AAEA,SAAO,EAAE,QAAQ,YAAY,eAAe,UAAU,OAAO;AAC/D;;;AC9uDA,YAAY,QAAQ;AAepB,IAAM,yBAAmD;AAAA,EACvD,OAAO,CAAC,QAAQ,QAAQ,QAAQ,OAAO,OAAO;AAAA,EAC9C,cAAc,CAAC,MAAM;AAAA,EACrB,UAAU,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO,OAAO;AAC3D;AAqCO,SAAS,sBACd,eACA,aAC6C;AAC7C,QAAM,eAAe,OAAO,OAAO,aAAa;AAChD,QAAM,WAAW,uBAAuB,WAAW,KAAK,CAAC;AACzD,MAAI,UAAU,SAAS,OAAO,CAAC,UAAU,CAAC,aAAa,SAAS,KAAK,CAAC;AACtE,MAAI,gBAAgB,cAAc,QAAQ,SAAS,MAAM,KAAK,aAAa,SAAS,MAAM,GAAG;AAC3F,cAAU,QAAQ,OAAO,CAAC,MAAM,MAAM,MAAM;AAAA,EAC9C;AACA,SAAO,EAAE,OAAO,QAAQ,WAAW,GAAG,eAAe,QAAQ;AAC/D;AAQA,SAAS,SAAS,SAAwE;AACxF,QAAM,QAAQ,QAAQ,QAAQ,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,IAAI;AAC9D,MAAI,MAAM,SAAS,EAAG,QAAO,EAAE,SAAS,CAAC,GAAG,MAAM,CAAC,EAAE;AACrD,QAAM,UAAU,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAC9D,QAAM,OAAiC,CAAC;AACxC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC,EAAE,KAAK;AAC3B,QAAI,CAAC,KAAM;AACX,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,UAAM,MAA8B,CAAC;AACrC,YAAQ,QAAQ,CAAC,GAAG,QAAQ;AAC1B,UAAI,CAAC,IAAI,OAAO,GAAG,GAAG,KAAK,KAAK;AAAA,IAClC,CAAC;AACD,SAAK,KAAK,GAAG;AAAA,EACf;AACA,SAAO,EAAE,SAAS,KAAK;AACzB;AAQA,SAAS,kBAAkB,OAA8B;AACvD,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,CAAC,MAAM,OAAO,KAAK,UAAU,KAAK;AACpC,WAAO,IAAI,KAAK,UAAU,GAAI,EAAE,mBAAmB,SAAS;AAAA,MAC1D,UAAU;AAAA,MACV,MAAM;AAAA,MAAW,OAAO;AAAA,MAAW,KAAK;AAAA,IAC1C,CAAC;AAAA,EACH;AACA,MAAI,sBAAsB,KAAK,KAAK,EAAG,QAAO;AAC9C,SAAO;AACT;AASA,SAAS,kBAAkB,OAA8B;AACvD,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,CAAC,MAAM,OAAO,KAAK,UAAU,KAAK;AACpC,UAAM,IAAI,IAAI,KAAK,UAAU,GAAI;AACjC,WAAO,EAAE,mBAAmB,SAAS;AAAA,MACnC,UAAU;AAAA,MACV,MAAM;AAAA,MAAW,QAAQ;AAAA,MAAW,QAAQ;AAAA,IAC9C,CAAC;AAAA,EACH;AACA,MAAI,gBAAgB,KAAK,KAAK,EAAG,QAAO;AACxC,MAAI,UAAU,KAAK,KAAK,EAAG,QAAO,GAAG,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC;AACxE,SAAO;AACT;AAQA,SAAS,mBACP,MACA,eACA,QACgC;AAChC,QAAM,mBAAmB,gBAAgB,MAAM,KAAK,OAAO,YAAY;AACvE,QAAM,SAAyC,CAAC;AAChD,aAAW,OAAO,MAAM;AACtB,UAAM,SAAkC,CAAC;AACzC,QAAI,cAAc;AAClB,eAAW,CAAC,WAAW,SAAS,KAAK,OAAO,QAAQ,aAAa,GAAG;AAClE,YAAM,WAAW,IAAI,SAAS,KAAK;AACnC,UAAI,cAAc,QAAQ;AACxB,cAAM,SAAS,kBAAkB,QAAQ;AACzC,YAAI,WAAW,MAAM;AAAE,wBAAc;AAAM;AAAA,QAAO;AAClD,eAAO,SAAS,IAAI;AAAA,MACtB,WAAW,cAAc,QAAQ;AAC/B,cAAM,SAAS,kBAAkB,QAAQ;AACzC,YAAI,WAAW,MAAM;AAAE,wBAAc;AAAM;AAAA,QAAO;AAClD,eAAO,SAAS,IAAI;AAAA,MACtB,OAAO;AACL,YAAI,aAAa,MAAM,aAAa,SAAS,aAAa,MAAM;AAC9D,iBAAO,SAAS,IAAI;AAAA,QACtB,OAAO;AACL,gBAAM,SAAS,WAAW,QAAQ;AAClC,iBAAO,SAAS,IAAI,MAAM,MAAM,IAAI,WAAW;AAAA,QACjD;AAAA,MACF;AAAA,IACF;AACA,QAAI,YAAa;AACjB,QAAI,EAAE,UAAU,QAAS;AAEzB,QAAI,EAAE,UAAU,SAAS;AACvB,YAAM,gBAAgB,OAAO,QAAQ,aAAa,EAAE,KAAK,CAAC,CAAC,EAAE,MAAM,MAAM,WAAW,MAAM,IAAI,CAAC;AAC/F,UAAI,eAAe;AACjB,cAAM,eAAe,IAAI,aAAa,KAAK;AAC3C,cAAM,cAAc,OAAO,YAAY;AACvC,YAAI,CAAC,MAAM,WAAW,KAAK,cAAc,KAAK;AAC5C,gBAAM,IAAI,kBAAkB,YAAY;AACxC,cAAI,EAAG,QAAO,MAAM,IAAI;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AACA,WAAO,QAAQ,IAAI;AACnB,WAAO,KAAK,MAAM;AAAA,EACpB;AACA,SAAO;AACT;AAOA,SAAS,qBACP,KACA,QACe;AACf,QAAM,OAAO,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AACvD,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,OAAO,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AACvD,QAAM,MAAM,CAAC,MAAuB;AAClC,QAAI,OAAO,MAAM,SAAU,QAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAC3D,QAAI,OAAO,MAAM,UAAU;AACzB,YAAM,IAAI,OAAO,CAAC;AAClB,aAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AACA,QAAM,SAAS,CAAC,MAAmC;AACjD,QAAI,MAAM,QAAQ,MAAM,OAAW,QAAO;AAC1C,UAAM,IAAI,IAAI,CAAC;AACf,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,IAAI,IAAI,IAAI;AAAA,IAClB,MAAM,IAAI,IAAI,IAAI;AAAA,IAClB,KAAK,IAAI,IAAI,GAAG;AAAA,IAChB,OAAO,IAAI,IAAI,KAAK;AAAA,IACpB,QAAQ,OAAO,IAAI,WAAW,YAAY,OAAO,IAAI,WAAW,WAAW,IAAI,IAAI,MAAM,IAAI;AAAA,IAC7F,KAAK,OAAO,IAAI,GAAG;AAAA,IACnB,KAAK,OAAO,IAAI,GAAG;AAAA,EACrB;AACF;AAOA,SAAS,gBAAgB,MAAuC;AAC9D,QAAM,SAAS,oBAAI,IAAsB;AACzC,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,OAAO,IAAI,IAAI,IAAI;AAC/B,QAAI,IAAK,KAAI,KAAK,GAAG;AAAA,QAChB,QAAO,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC;AAAA,EACjC;AACA,SAAO;AACT;AAWA,eAAsB,eACpB,UACA,QACA,eACmB;AACnB,MAAI;AACJ,MAAI;AACF,cAAU,MAAS,YAAS,UAAU,OAAO;AAAA,EAC/C,SAAS,OAAO;AACd,UAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,UAAM,IAAI,MAAM,+BAA+B,QAAQ,MAAM,GAAG,EAAE;AAAA,EACpE;AACA,QAAM,EAAE,KAAK,IAAI,SAAS,OAAO;AACjC,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,IAAI,MAAM,aAAa,QAAQ,oBAAoB;AAAA,EAC3D;AACA,QAAM,mBAAmB,gBAAgB,MAAM,KAAK,OAAO,YAAY;AACvE,QAAM,aAAa,mBAAmB,MAAM,eAAe,gBAAgB;AAC3E,QAAM,OAAiB,CAAC;AACxB,aAAW,OAAO,YAAY;AAC5B,UAAM,MAAM,qBAAqB,KAAK,gBAAgB;AACtD,QAAI,IAAK,MAAK,KAAK,GAAG;AAAA,EACxB;AACA,SAAO;AACT;AAMO,SAAS,wBACd,SACA,QACA,eACU;AACV,QAAM,mBAAmB,gBAAgB,MAAM,KAAK,OAAO,YAAY;AACvE,QAAM,aAAa,mBAAmB,SAAS,eAAe,gBAAgB;AAC9E,QAAM,OAAiB,CAAC;AACxB,aAAW,OAAO,YAAY;AAC5B,UAAM,MAAM,qBAAqB,KAAK,gBAAgB;AACtD,QAAI,IAAK,MAAK,KAAK,GAAG;AAAA,EACxB;AACA,SAAO;AACT;AAYA,eAAsB,oBACpB,QACA,QAC2B;AAC3B,QAAM,EAAE,UAAU,QAAQ,eAAe,SAAS,MAAM,IAAI;AAC5D,QAAM,mBAAmB,gBAAgB,MAAM,KAAK,OAAO,YAAY;AACvE,QAAM,OAAO,MAAM,eAAe,UAAU,kBAAkB,aAAa;AAE3E,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO;AAAA,MACL,aAAa;AAAA,MACb,eAAe;AAAA,MACf,YAAY;AAAA,MACZ;AAAA,MACA,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,SAAS,gBAAgB,IAAI;AACnC,QAAM,QAAQ,CAAC,GAAG,OAAO,KAAK,CAAC,EAAE,KAAK;AACtC,QAAM,YAAY,EAAE,MAAM,MAAM,CAAC,GAAG,IAAI,MAAM,MAAM,SAAS,CAAC,EAAE;AAEhE,MAAI,QAAQ;AACV,WAAO;AAAA,MACL,aAAa;AAAA,MACb,eAAe,KAAK;AAAA,MACpB,YAAY,KAAK;AAAA,MACjB,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,cAAc;AAClB,aAAW,CAAC,MAAM,OAAO,KAAK,QAAQ;AACpC,UAAM,OAAO,KAAK,UAAU,kBAAkB,MAAM,OAAO;AAC3D,mBAAe,QAAQ;AAAA,EACzB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,eAAe,KAAK;AAAA,IACpB,YAAY,KAAK;AAAA,IACjB,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,EACV;AACF;AAUA,eAAsB,mBACpB,QACA,MACA,QAC2B;AAC3B,QAAM,EAAE,QAAQ,OAAO,QAAQ,eAAe,SAAS,MAAM,IAAI;AACjE,QAAM,mBAAmB,gBAAgB,MAAM,KAAK,OAAO,YAAY;AACvE,QAAM,YAAY;AAClB,QAAM,gBAAgB,OAAO,QAAQ,MAAM,IAAI;AAC/C,QAAM,KAAK,IAAI,WAAW,aAAa,QAAQ,SAAS,cAAc;AAEtE,MAAI;AACJ,MAAI;AACF,UAAM,SAAS,MAAM,KAAK,cAAc,KAAK;AAC7C,UAAM,WAAW,OAAO,YAAY;AACpC,UAAM,OAAO,OAAO,QAAQ;AAC5B,UAAM,UAAoC,KAAK,IAAI,CAAC,QAAQ;AAC1D,YAAM,MAA8B,CAAC;AACrC,eAAS,QAAQ,CAAC,MAAM,QAAQ;AAC9B,cAAM,MAAM,IAAI,GAAG;AACnB,YAAI,IAAI,IAAI,QAAQ,QAAQ,QAAQ,SAAY,KAAK,OAAO,GAAG;AAAA,MACjE,CAAC;AACD,aAAO;AAAA,IACT,CAAC;AACD,WAAO,wBAAwB,SAAS,kBAAkB,aAAa;AAAA,EACzE,UAAE;AACA,QAAI;AAAE,YAAM,KAAK,IAAI,UAAU,SAAS,EAAE;AAAA,IAAG,QAAQ;AAAA,IAAoB;AAAA,EAC3E;AAEA,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO;AAAA,MACL,aAAa;AAAA,MACb,eAAe;AAAA,MACf,YAAY;AAAA,MACZ;AAAA,MACA,WAAW;AAAA,MACX,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,SAAS,gBAAgB,IAAI;AACnC,QAAM,QAAQ,CAAC,GAAG,OAAO,KAAK,CAAC,EAAE,KAAK;AACtC,QAAM,YAAY,EAAE,MAAM,MAAM,CAAC,GAAG,IAAI,MAAM,MAAM,SAAS,CAAC,EAAE;AAEhE,MAAI,QAAQ;AACV,WAAO;AAAA,MACL,aAAa;AAAA,MACb,eAAe,KAAK;AAAA,MACpB,YAAY,KAAK;AAAA,MACjB,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,cAAc;AAClB,aAAW,CAAC,MAAM,OAAO,KAAK,QAAQ;AACpC,UAAM,OAAO,KAAK,UAAU,kBAAkB,MAAM,OAAO;AAC3D,mBAAe,QAAQ;AAAA,EACzB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,eAAe,KAAK;AAAA,IACpB,YAAY,KAAK;AAAA,IACjB,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,EACV;AACF;;;ACzaA,SAAS,OAAO,GAAmB;AACjC,SAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;AAUO,SAAS,kBAAkB,KAA2B;AAC3D,MAAI,IAAI,WAAW,GAAG;AACpB,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,cAAc;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;AACtC,QAAM,UAAU,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;AAExC,QAAM,OAAO,OAAO;AACpB,QAAM,SAAS,QAAQ;AACvB,QAAM,UAAW,OAAO,IAAI,SAAU;AACtC,QAAM,UAAU,IAAI,OAAO,CAAC,KAAK,MAAM,MAAM,GAAG,CAAC;AACjD,QAAM,QAAQ,UAAU,IAAI;AAE5B,QAAM,SAAS,OAAO,IAAI,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,OAAO;AACrE,QAAM,UAAU,SAAS,IAAI,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,SAAS;AAE3E,QAAM,YAAY,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAClD,QAAM,cAAc,KAAK,IAAI,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,CAAC;AAE/D,MAAI;AACJ,MAAI,cAAc,GAAG;AACnB,mBAAe,YAAY;AAAA,EAC7B,WAAW,YAAY,GAAG;AACxB,mBAAe;AAAA,EACjB,OAAO;AACL,mBAAe;AAAA,EACjB;AAEA,SAAO;AAAA,IACL,YAAY,IAAI;AAAA,IAChB;AAAA,IACA;AAAA,IACA,SAAS,OAAO,OAAO;AAAA,IACvB,SAAS,OAAO,OAAO;AAAA,IACvB,OAAO,OAAO,KAAK;AAAA,IACnB,QAAQ,OAAO,MAAM;AAAA,IACrB,SAAS,OAAO,OAAO;AAAA,IACvB,cAAc,iBAAiB,OAAO,OAAO,YAAY,IAAI;AAAA,EAC/D;AACF;;;AC7DA,IAAM,kBAA0C;AAAA,EAC9C,QAAQ;AAAA,EAAG,KAAK;AAAA,EAChB,SAAS;AAAA,EAAG,KAAK;AAAA,EAAG,MAAM;AAAA,EAC1B,WAAW;AAAA,EAAG,KAAK;AAAA,EACnB,UAAU;AAAA,EAAG,KAAK;AAAA,EAAG,OAAO;AAAA,EAC5B,QAAQ;AAAA,EAAG,KAAK;AAClB;AAMA,SAAS,eAAe,OAA+B;AACrD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO,gBAAgB,MAAM,YAAY,CAAC,KAAK;AACjD;AAMA,SAAS,OAAO,QAAiC,KAAqB;AACpE,QAAM,MAAM,OAAO,GAAG;AACtB,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,QAAM,MAAM,OAAO,GAAG;AACtB,SAAO;AACT;AAMA,SAAS,OAAO,QAAiC,KAAsB;AACrE,SAAO,OAAO,GAAG;AACnB;AAMA,SAAS,gBAAgB,OAAiC;AACxD,MAAI,OAAO,UAAU,SAAU,QAAO;AAEtC,MAAI,MAAM,KAAK,MAAM,MAAM,SAAS,OAAO,KAAK,CAAC,EAAG,QAAO;AAC3D,SAAO;AACT;AAQA,SAAS,gBACP,SACA,QACQ;AAER,MAAI,WAAW,QAAQ;AACrB,WAAO,OAAO,QAAQ,OAAO;AAAA,EAC/B;AAEA,MAAI,mBAAmB,IAAI,OAAO,GAAG;AACnC,WAAO,OAAO,QAAQ,QAAQ,OAAO,EAAE;AAAA,EACzC;AACA,SAAO;AACT;AAmBO,SAAS,qBAAqB,QAAsC;AACzE,QAAM,WAAW,mBAAmB,IAAI,OAAO,KAAK;AACpD,QAAM,WAAW,WAAW,QAAQ,OAAO,KAAK,KAAK,OAAO;AAE5D,QAAM,EAAE,UAAU,MAAM,IAAI;AAE5B,QAAM,OAAO,CAAC,WAA6C;AAEzD,QAAI,aAAa,MAAM;AACrB,YAAM,MAAM,OAAO,QAAQ,QAAQ;AACnC,UAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,UAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAElC,aAAO,MAAM,KAAK,CAAC,MAAM;AACvB,cAAM,SAAS,eAAe,CAAC;AAC/B,YAAI,WAAW,KAAM,QAAO,OAAO,GAAG,MAAM;AAC5C,eAAO,KAAK;AAAA,MACd,CAAC;AAAA,IACH;AAEA,QAAI,aAAa,MAAM;AACrB,YAAM,MAAM,OAAO,QAAQ,QAAQ;AACnC,UAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAE9C,YAAM,SAAS,eAAe,KAAK;AACnC,UAAI,WAAW,KAAM,QAAO,OAAO,GAAG,MAAM;AAE5C,UAAI,gBAAgB,KAAK,GAAG;AAC1B,cAAM,SAAS,gBAAgB,OAAO,MAAM;AAC5C,YAAI,MAAM,MAAM,EAAG,QAAO;AAC1B,eAAO,OAAO,GAAG,MAAM;AAAA,MACzB;AACA,aAAO,SAAS;AAAA,IAClB;AAGA,UAAM,MAAM,OAAO,QAAQ,QAAQ;AACnC,QAAI,MAAM,GAAG,EAAG,QAAO;AAGvB,QACE,gBAAgB,KAAK,MACpB,aAAa,OAAO,aAAa,OAAO,aAAa,QAAQ,aAAa,OAC3E;AACA,YAAM,SAAS,gBAAgB,OAAO,MAAM;AAC5C,UAAI,MAAM,MAAM,EAAG,QAAO;AAC1B,cAAQ,UAAU;AAAA,QAChB,KAAK;AACH,iBAAO,MAAM;AAAA,QACf,KAAK;AACH,iBAAO,MAAM;AAAA,QACf,KAAK;AACH,iBAAO,OAAO;AAAA,QAChB,KAAK;AACH,iBAAO,OAAO;AAAA,MAClB;AAAA,IACF;AAEA,YAAQ,UAAU;AAAA,MAChB,KAAK;AACH,eAAO,MAAM,OAAO,KAAK;AAAA,MAC3B,KAAK;AACH,eAAO,MAAM,OAAO,KAAK;AAAA,MAC3B,KAAK;AACH,eAAO,OAAO,OAAO,KAAK;AAAA,MAC5B,KAAK;AACH,eAAO,OAAO,OAAO,KAAK;AAAA,MAC5B,KAAK,WAAW;AACd,YAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,EAAG,QAAO;AACtD,cAAM,KAAK,OAAO,MAAM,CAAC,CAAC;AAC1B,cAAM,KAAK,OAAO,MAAM,CAAC,CAAC;AAC1B,eAAO,OAAO,MAAM,OAAO;AAAA,MAC7B;AAAA,MACA;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,UAAU,SAAS;AACpC;;;ACpLA,SAAS,SAAS;;;ACqCX,SAAS,iBAAiB,SAAiB,MAA0B;AAC1E,SAAO;AAAA,IACL,SAAS;AAAA,MACP,EAAE,MAAM,QAAQ,MAAM,QAAQ;AAAA,MAC9B;AAAA,QACE,MAAM;AAAA,QACN,UAAU;AAAA,UACR,KAAK;AAAA,UACL,UAAU;AAAA,UACV,MAAM,KAAK,UAAU,IAAI;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ADnCO,IAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,SAAS,EAAE,OAAO,EAAE,SAAS,mDAAmD;AAAA,EAChF,cAAc,EAAE,OAAO,EAAE,SAAS,sDAAsD;AAAA,EACxF,eAAe,EACZ,OAAO,EACP;AAAA,IACC;AAAA,EAEF;AAAA,EACF,YAAY,EACT,OAAO,EACP;AAAA,IACC;AAAA,EAEF;AAAA,EACF,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,SAAS,8CAA8C;AAAA,EACtF,MAAM,EACH;AAAA,IACC,EAAE,OAAO;AAAA,MACP,MAAM,EAAE,OAAO,EAAE,SAAS,4DAA4D;AAAA,MACtF,QAAQ,EAAE,OAAO,EAAE,SAAS,gDAAgD;AAAA,MAC5E,QAAQ,EAAE,OAAO,EAAE,SAAS,kDAAkD;AAAA,MAC9E,UAAU,EAAE,OAAO,EAAE,SAAS,0CAA0C;AAAA,MACxE,cAAc,EAAE,KAAK,CAAC,SAAS,gBAAgB,UAAU,YAAY,CAAC,EAAE,SAAS,EAAE,SAAS,wBAAwB;AAAA,MACpH,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,8CAA8C;AAAA,IAC5F,CAAC;AAAA,EACH,EACC,QAAQ,CAAC,CAAC,EACV,SAAS,6BAA6B;AAAA,EACzC,cAAc,EACX;AAAA,IACC,EAAE,OAAO;AAAA,MACP,OAAO,EAAE,OAAO,EAAE,SAAS,wDAAwD;AAAA,MACnF,UAAU,EAAE,OAAO,EAAE,SAAS,oDAAoD;AAAA,MAClF,OAAO,EACJ,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAC1E,SAAS,gDAAgD;AAAA,MAC5D,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,2CAA2C;AAAA,MACvF,QAAQ,EAAE,KAAK,CAAC,UAAU,WAAW,CAAC,EAAE,SAAS,EAAE,SAAS,qOAAqO;AAAA,IACnS,CAAC;AAAA,EACH,EACC,QAAQ,CAAC,CAAC,EACV,SAAS,yIAAyI;AAAA,EACrJ,WAAW,EACR;AAAA,IACC,EAAE,OAAO;AAAA,MACP,MAAM,EAAE,OAAO,EAAE,SAAS,6DAA6D;AAAA,MACvF,SAAS,EAAE,OAAO,EAAE,SAAS,sEAAsE;AAAA,MACnG,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,4BAA4B;AAAA,MACxE,cAAc,EAAE,KAAK,CAAC,cAAc,UAAU,YAAY,kBAAkB,CAAC,EAAE,SAAS,EAAE,SAAS,8BAA8B;AAAA,MACjI,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,yBAAyB;AAAA,MACvE,YAAY,EAAE,OAAO;AAAA,QACnB,aAAa,EAAE,KAAK,CAAC,gBAAgB,gBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,SAAS,uBAAuB;AAAA,QAC/G,aAAa,EAAE,KAAK,CAAC,QAAQ,OAAO,MAAM,CAAC,EAAE,SAAS,EAAE,SAAS,oBAAoB;AAAA,MACvF,CAAC,EAAE,SAAS,EAAE,SAAS,wCAAwC;AAAA,MAC/D,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,4BAA4B;AAAA,IACvE,CAAC;AAAA,EACH,EACC,QAAQ,CAAC,CAAC,EACV,SAAS,yBAAyB;AAAA,EACrC,iBAAiB,EACd,MAAM,EAAE,KAAK,CAAC,YAAY,OAAO,aAAa,aAAa,QAAQ,SAAS,CAAC,CAAC,EAC9E,QAAQ,CAAC,CAAC,EACV,SAAS,oIAAoI;AAAA,EAChJ,YAAY,EACT,OAAO;AAAA,IACN,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,yBAAyB;AAAA,IACzE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,8BAA8B;AAAA,IAC5E,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,+BAA+B;AAAA,IACvE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,wBAAwB;AAAA,EACvE,CAAC,EACA,YAAY,EACZ,QAAQ,CAAC,CAAC,EACV,SAAS,sDAAsD;AAAA,EAClE,gBAAgB,EACb,OAAO;AAAA,IACN,QAAQ,EAAE,OAAO,EAAE,SAAS,+EAA+E;AAAA,IAC3G,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kDAAkD;AAAA,IAChG,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,6BAA6B;AAAA,IAC1E,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qCAAqC;AAAA,IACzF,kBAAkB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,mCAAmC;AAAA,IACpF,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,wBAAwB;AAAA,IACpE,uBAAuB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,+BAA+B;AAAA,IACrF,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qCAAqC;AAAA,IACvF,sBAAsB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,8DAA8D;AAAA,EACrH,CAAC,EACA,SAAS,EACT,SAAS,2GAAsG;AAAA,EAClH,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,mCAAmC;AAAA,EAC9E,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,wCAAwC;AAAA,EACjF,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,iCAAiC;AAAA,EAC7E,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,gCAAgC;AAAA,EAC3E,oBAAoB,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,mCAAmC;AAAA,EACvF,mBAAmB,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,gDAAgD;AAAA,EACnG,iBAAiB,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,+CAA+C;AAClG,CAAC;AAEM,IAAM,2BAA2B,EAAE,OAAO;AAAA,EAC/C,SAAS,EAAE,OAAO,EAAE,SAAS,qBAAqB;AAAA,EAClD,cAAc,EAAE,OAAO,EAAE,SAAS,0BAA0B;AAC9D,CAAC;AAEM,IAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,wEAAwE;AAClH,CAAC;AAEM,IAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,SAAS,EAAE,OAAO,EAAE,SAAS,mCAAmC;AAAA,EAChE,cAAc,EAAE,OAAO,EAAE,SAAS,wCAAwC;AAC5E,CAAC;AASD,eAAsB,sBACpB,OACA,SAC8C;AAC9C,QAAM,mBAAmB,OAAO;AAChC,MAAI;AACF,UAAM,OAAO,MAAM,cAAc,OAAO;AACxC,UAAM,SAAS,MAAM,cAAc,MAAM;AAAA,MACvC,SAAS,MAAM;AAAA,MACf,cAAc,MAAM;AAAA,MACpB,eAAe,MAAM;AAAA,MACrB,YAAY,MAAM;AAAA,MAClB,QAAQ,MAAM;AAAA,MACd,MAAM,MAAM;AAAA,MACZ,cAAc,MAAM;AAAA,MACpB,WAAW,MAAM;AAAA,MACjB,iBAAiB,MAAM;AAAA,MACvB,YAAY,MAAM;AAAA,MAClB,gBAAgB,MAAM;AAAA,MACtB,YAAY,MAAM;AAAA,MAClB,SAAS,MAAM;AAAA,MACf,YAAY,MAAM;AAAA,MAClB,WAAW,MAAM;AAAA,MACjB,oBAAoB,MAAM;AAAA,MAC1B,mBAAmB,MAAM;AAAA,MACzB,iBAAiB,MAAM;AAAA,IACzB,GAAG,OAAO;AACV,WAAO;AAAA,MACL,kBAAkB,MAAM,YAAY,cAAc,MAAM,OAAO;AAAA,MAC/D,EAAE,SAAS,OAAO;AAAA,IACpB;AAAA,EACF,UAAE;AACA,UAAM,oBAAoB,OAAO;AAAA,EACnC;AACF;AAKA,eAAsB,yBACpB,OACA,SAC8C;AAC9C,QAAM,OAAO,MAAM,cAAc,OAAO;AACxC,QAAM,UAAU,MAAM,WAAW,MAAM,MAAM,SAAS,MAAM,cAAc,OAAO;AACjF,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,kCAAkC,MAAM,YAAY,eAAe,MAAM,OAAO;AAAA,MAChF,EAAE,SAAS,KAAK;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AAAA,IACL,YAAY,MAAM,YAAY,aAAa,MAAM,OAAO;AAAA,IACxD,EAAE,QAAQ;AAAA,EACZ;AACF;AAKA,eAAsB,mBACpB,OACA,SAC8C;AAC9C,QAAM,OAAO,MAAM,cAAc,OAAO;AACxC,QAAM,WAAW,MAAM,aAAa,MAAM,MAAM,SAAS,OAAO;AAChE,QAAM,cAAc,SAAS,IAAI,CAAC,OAAO;AAAA,IACvC,SAAS,EAAE;AAAA,IACX,cAAc,EAAE;AAAA,IAChB,eAAe,EAAE;AAAA,IACjB,YAAY,EAAE;AAAA,IACd,YAAY,EAAE,cAAc;AAAA,IAC5B,gBAAgB,EAAE,gBAAgB,UAAU;AAAA,IAC5C,WAAW,EAAE;AAAA,EACf,EAAE;AACF,SAAO;AAAA,IACL,SAAS,SAAS,MAAM,cAAc,MAAM,UAAU,cAAc,MAAM,OAAO,KAAK,EAAE;AAAA,IACxF,EAAE,OAAO,SAAS,QAAQ,UAAU,YAAY;AAAA,EAClD;AACF;AAKA,eAAsB,oBACpB,OACA,SAC8C;AAC9C,QAAM,mBAAmB,OAAO;AAChC,MAAI;AACF,UAAM,OAAO,MAAM,cAAc,OAAO;AACxC,UAAM,UAAU,MAAM,cAAc,MAAM,MAAM,SAAS,MAAM,cAAc,OAAO;AACpF,QAAI,SAAS;AACX,aAAO;AAAA,QACL,oBAAoB,MAAM,YAAY,eAAe,MAAM,OAAO;AAAA,QAClE,EAAE,SAAS,KAAK;AAAA,MAClB;AAAA,IACF;AACA,WAAO;AAAA,MACL,kCAAkC,MAAM,YAAY,eAAe,MAAM,OAAO;AAAA,MAChF,EAAE,SAAS,MAAM;AAAA,IACnB;AAAA,EACF,UAAE;AACA,UAAM,oBAAoB,OAAO;AAAA,EACnC;AACF;;;AEnPA,SAAS,KAAAC,UAAS;AAkClB,SAAS,gBAAgB,MAA6B;AACpD,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,QAAQ,KAAK,MAAM,0BAA0B;AACnD,QAAI,MAAO,QAAO,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAAA,EACvD;AACA,QAAM,IAAI,OAAO,SAAS,WAAW,IAAI,KAAK,IAAI,IAAI;AACtD,QAAM,OAAO,EAAE,YAAY;AAC3B,QAAM,QAAQ,OAAO,EAAE,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACtD,QAAM,MAAM,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG;AAC/C,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG;AAChC;AAEA,SAAS,kBAAkB,OAA+B;AACxD,SAAO;AAAA,IACL,MAAM,gBAAgB,MAAM,UAAU;AAAA,IACtC,QAAQ,mBAAmB,OAAO,qBAAqB;AAAA,EACzD;AACF;AAEA,SAAS,sBAAsB,QAAoC;AACjE,QAAM,QAAQ,oBAAI,IAA6B;AAC/C,aAAW,SAAS,QAAQ;AAC1B,UAAM,SAAS,kBAAkB,KAAK;AACtC,UAAM,IAAI,oBAAoB,OAAO,QAAQ,OAAO,IAAI,GAAG,MAAM;AAAA,EACnE;AACA,SAAO,MAAM,KAAK,MAAM,OAAO,CAAC;AAClC;AAEA,SAAS,gBAAgB,QAIK;AAC5B,QAAM,cAAc,OAAO;AAC3B,QAAM,WAAqB,CAAC;AAC5B,WAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,aAAS,KAAK,OAAO,WAAW,CAAC,CAAC;AAAA,EACpC;AACA,QAAM,UAAqC,CAAC;AAC5C,aAAW,OAAO,OAAO,QAAQ,GAAG;AAClC,UAAM,SAAkC,CAAC;AACzC,aAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,YAAM,MAAM,IAAI,CAAC;AACjB,aAAO,SAAS,CAAC,CAAC,IAAI,OAAO,QAAQ,WAAW,OAAO,GAAG,IAAI;AAAA,IAChE;AACA,YAAQ,KAAK,MAAM;AAAA,EACrB;AACA,SAAO;AACT;AAEA,SAAS,oBACP,SACsC;AACtC,QAAM,SAAS,oBAAI,IAAqC;AACxD,aAAW,UAAU,SAAS;AAC5B,UAAM,OAAO,OAAO,OAAO,MAAM,KAAK,EAAE;AACxC,UAAM,SAAS,OAAO,OAAO,QAAQ,KAAK,qBAAqB;AAC/D,WAAO,IAAI,oBAAoB,QAAQ,IAAI,GAAG,MAAM;AAAA,EACtD;AACA,SAAO;AACT;AAEA,SAASC,QAAO,QAAiC,OAAuB;AACtE,QAAM,MAAM,OAAO,KAAK;AACxB,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,MAAI,OAAO,QAAQ,SAAU,QAAO,OAAO,GAAG;AAC9C,SAAO;AACT;AAMA,IAAM,oBAA4C;AAAA,EAChD,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEA,IAAM,eAAe,CAAC,MAAM,QAAQ,MAAM;AAM1C,IAAM,aAAqC;AAAA,EACzC,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAKA,SAAS,cAAc,YAA+C;AACpE,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,QAAQ,WAAW,MAAM,oBAAoB;AACnD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,QAAQ,SAAS,MAAM,CAAC,GAAG,EAAE;AACnC,QAAM,UAAU,SAAS,MAAM,CAAC,GAAG,EAAE;AACrC,QAAM,eAAe,QAAQ,KAAK;AAGlC,MAAI,eAAe,IAAK,QAAO;AAC/B,MAAI,eAAe,IAAK,QAAO;AAC/B,MAAI,eAAe,IAAK,QAAO;AAC/B,MAAI,gBAAgB,IAAK,QAAO;AAChC,SAAO;AACT;AAKA,SAASC,QAAO,QAAiC,OAAwB;AACvE,SAAO,OAAO,KAAK;AACrB;AAWA,eAAe,oBACb,SACA,SACA,cAKC;AACD,QAAM,QAAQ,MAAM,UAAU,SAAS,OAAO;AAC9C,MAAI,SAAS,iBAAiB,MAAM,QAAQ,YAAY;AAKxD,MAAI,OAAO,WAAW,KAAK,MAAM,OAAO,SAAS,GAAG;AAClD,UAAM,mBAAmB,IAAI,IAAI,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AACpE,QAAI,iBAAiB,SAAS,GAAG;AAC/B,eAAS,MAAM;AAAA,IACjB;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,EAAE,SAAS,CAAC,GAAG,gBAAgB,GAAG,WAAW,CAAC,EAAE;AAAA,EACzD;AAGA,QAAM,YAAY,sBAAsB,MAAM;AAG9C,QAAM,OAAO,MAAM,cAAc,OAAO;AACxC,QAAM,EAAE,KAAK,OAAO,IAAI,wBAAwB,SAAS;AACzD,QAAM,SAAS,MAAM,KAAK,cAAc,KAAK,MAAM;AACnD,QAAM,gBAAgB,gBAAgB,MAAM;AAC5C,QAAM,YAAY,oBAAoB,aAAa;AAGnD,QAAM,UAA6B,CAAC;AACpC,MAAI,iBAAiB;AAErB,aAAW,SAAS,QAAQ;AAC1B,UAAM,SAAS,kBAAkB,KAAK;AACtC,UAAM,MAAM,oBAAoB,OAAO,QAAQ,OAAO,IAAI;AAC1D,UAAM,SAAS,UAAU,IAAI,GAAG;AAChC,QAAI,QAAQ;AACV,cAAQ,KAAK,EAAE,OAAO,OAAO,CAAC;AAAA,IAChC,OAAO;AACL;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,gBAAgB,WAAW,OAAO;AACtD;AAMA,SAAS,qBAAqB,QAAiE;AAC7F,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,QAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAE/C,QAAM,eAAe,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;AACxC,MAAI,aAAa,UAAU,GAAG;AAC5B,WAAO,aAAa,IAAI,CAAC,OAAO;AAAA,MAC9B,OAAO,OAAO,KAAK,MAAM,IAAI,GAAG,IAAI,GAAG;AAAA,MACvC,KAAK;AAAA,MACL,KAAK;AAAA,IACP,EAAE;AAAA,EACJ;AAEA,QAAM,UAAyD,CAAC;AAChE,QAAM,eAAe,KAAK,KAAK,OAAO,SAAS,CAAC;AAChD,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,QAAQ,IAAI;AAClB,UAAM,MAAM,KAAK,KAAK,IAAI,KAAK,eAAe,GAAG,OAAO,SAAS,CAAC;AAClE,QAAI,QAAQ,OAAO,SAAS,EAAG;AAC/B,UAAM,MAAM,OAAO,KAAK;AACxB,UAAM,MAAM,OAAO,GAAG;AACtB,UAAM,IAAI,CAAC,MAAc,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/C,YAAQ,KAAK;AAAA,MACX,OAAO,QAAQ,MAAM,GAAG,EAAE,GAAG,CAAC,KAAK,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC;AAAA,MACzD;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAKA,SAAS,WACP,OACA,SACe;AACf,aAAW,UAAU,SAAS;AAC5B,QAAI,SAAS,OAAO,OAAO,SAAS,OAAO,IAAK,QAAO,OAAO;AAAA,EAChE;AACA,SAAO;AACT;AAMO,IAAM,4BAA4BC,GAAE,OAAO;AAAA,EAChD,SAASA,GAAE,OAAO,EAAE,SAAS,qBAAqB;AAAA,EAClD,cAAcA,GAAE,OAAO,EAAE,SAAS,yCAAyC;AAAA,EAC3E,WAAWA,GACR,OAAO,EACP,SAAS,EACT,QAAQ,EAAE,EACV,SAAS,4EAA4E;AAC1F,CAAC;AAED,eAAsB,0BACpB,OACA,SAC8C;AAC9C,QAAM,EAAE,SAAS,aAAa,IAAI;AAClC,QAAM,YAAY,MAAM,aAAa;AAGrC,QAAM,OAAO,MAAM,cAAc,OAAO;AACxC,QAAM,UAAU,MAAM,WAAW,MAAM,SAAS,cAAc,OAAO;AACrE,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,kCAAkC,YAAY,eAAe,OAAO;AAAA,MACpE,EAAE,OAAO,oBAAoB;AAAA,IAC/B;AAAA,EACF;AAGA,QAAM,EAAE,SAAS,gBAAgB,UAAU,IAAI,MAAM;AAAA,IACnD;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,WAAqB,CAAC;AAE5B,MAAI,UAAU,WAAW,GAAG;AAC1B,WAAO;AAAA,MACL,iCAAiC,YAAY,eAAe,OAAO;AAAA,MACnE,EAAE,OAAO,YAAY;AAAA,IACvB;AAAA,EACF;AAEA,MAAI,iBAAiB,GAAG;AACtB,aAAS;AAAA,MACP,GAAG,cAAc,OAAO,UAAU,MAAM;AAAA,IAC1C;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,MACL,2DAA2D,YAAY;AAAA,MACvE,EAAE,OAAO,mBAAmB,SAAS;AAAA,IACvC;AAAA,EACF;AAGA,QAAM,SAAS,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE;AAC5C,QAAM,UAAU,kBAAkB,MAAM;AAGxC,QAAM,aAAyD,CAAC;AAGhE,QAAM,mBAA6C,CAAC;AACpD,aAAW,EAAE,OAAO,OAAO,KAAK,SAAS;AACvC,UAAM,MAAMF,QAAO,QAAQ,iBAAiB;AAC5C,QAAI,MAAM,GAAG,EAAG;AAChB,UAAM,QAAQ,kBAAkB,GAAG,KAAK,UAAU,GAAG;AACrD,QAAI,CAAC,iBAAiB,KAAK,EAAG,kBAAiB,KAAK,IAAI,CAAC;AACzD,qBAAiB,KAAK,EAAE,KAAK,MAAM,EAAE;AAAA,EACvC;AACA,QAAM,iBAA6C,CAAC;AACpD,aAAW,CAAC,OAAO,GAAG,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AAC3D,mBAAe,KAAK,IAAI,kBAAkB,GAAG;AAAA,EAC/C;AACA,aAAW,YAAY,IAAI;AAG3B,QAAM,aAAuC,CAAC;AAC9C,aAAW,EAAE,OAAO,OAAO,KAAK,SAAS;AACvC,UAAM,MAAMA,QAAO,QAAQ,aAAa;AACxC,QAAI,MAAM,GAAG,EAAG;AAChB,UAAM,QAAQ,WAAW,GAAG,KAAK,OAAO,GAAG;AAC3C,QAAI,CAAC,WAAW,KAAK,EAAG,YAAW,KAAK,IAAI,CAAC;AAC7C,eAAW,KAAK,EAAE,KAAK,MAAM,EAAE;AAAA,EACjC;AACA,QAAM,WAAuC,CAAC;AAC9C,aAAW,CAAC,OAAO,GAAG,KAAK,OAAO,QAAQ,UAAU,GAAG;AACrD,aAAS,KAAK,IAAI,kBAAkB,GAAG;AAAA,EACzC;AACA,aAAW,aAAa,IAAI;AAG5B,QAAM,aAAuC,CAAC;AAC9C,aAAW,EAAE,MAAM,KAAK,SAAS;AAC/B,UAAM,SAAS,cAAc,MAAM,UAAU;AAC7C,QAAI,CAAC,OAAQ;AACb,QAAI,CAAC,WAAW,MAAM,EAAG,YAAW,MAAM,IAAI,CAAC;AAC/C,eAAW,MAAM,EAAE,KAAK,MAAM,EAAE;AAAA,EAClC;AACA,QAAM,WAAuC,CAAC;AAC9C,aAAW,CAAC,OAAO,GAAG,KAAK,OAAO,QAAQ,UAAU,GAAG;AACrD,aAAS,KAAK,IAAI,kBAAkB,GAAG;AAAA,EACzC;AACA,aAAW,aAAa,IAAI;AAG5B,aAAW,UAAU,QAAQ,aAAa,OAAO,CAAC,MAAM,EAAE,WAAW,WAAW,GAAG;AACjF,UAAM,YAAY,qBAAqB,MAAM;AAC7C,UAAM,WAAW,UAAU;AAG3B,UAAM,cAA6C,CAAC;AACpD,eAAW,EAAE,OAAO,OAAO,KAAK,SAAS;AACvC,YAAM,MAAMC,QAAO,QAAQ,QAAQ;AACnC,UAAI,QAAQ,QAAQ,QAAQ,OAAW;AACvC,YAAM,MAAM,OAAO,GAAG;AACtB,UAAI,MAAM,GAAG,EAAG;AAChB,kBAAY,KAAK,EAAE,KAAK,KAAK,IAAI,MAAM,GAAG,CAAC;AAAA,IAC7C;AAEA,QAAI,YAAY,WAAW,EAAG;AAG9B,UAAM,UAAU,qBAAqB,YAAY,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;AAClE,QAAI,QAAQ,WAAW,EAAG;AAE1B,UAAM,gBAA0C,CAAC;AACjD,eAAW,EAAE,KAAK,GAAG,KAAK,aAAa;AACrC,YAAM,cAAc,WAAW,KAAK,OAAO;AAC3C,UAAI,CAAC,YAAa;AAClB,UAAI,CAAC,cAAc,WAAW,EAAG,eAAc,WAAW,IAAI,CAAC;AAC/D,oBAAc,WAAW,EAAE,KAAK,EAAE;AAAA,IACpC;AAEA,UAAM,cAA0C,CAAC;AACjD,eAAW,CAAC,OAAO,GAAG,KAAK,OAAO,QAAQ,aAAa,GAAG;AACxD,kBAAY,KAAK,IAAI,kBAAkB,GAAG;AAAA,IAC5C;AACA,eAAW,OAAO,KAAK,IAAI;AAAA,EAC7B;AAGA,aAAW,CAAC,SAAS,WAAW,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC/D,eAAW,CAAC,aAAa,KAAK,KAAK,OAAO,QAAQ,WAAW,GAAG;AAC9D,UAAI,MAAM,aAAa,KAAK,MAAM,aAAa,WAAW;AACxD,iBAAS;AAAA,UACP,GAAG,OAAO,IAAI,WAAW,UAAU,MAAM,UAAU,cAAc,SAAS;AAAA,QAC5E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,qBAA6E,CAAC;AAGpF,aAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,cAAc,GAAG;AAC3D,QAAI,MAAM,cAAc,WAAW;AACjC,YAAM,cAAc,MAAM,UAAU,QAAQ;AAC5C,UAAI,eAAe,IAAI;AACrB,2BAAmB,KAAK;AAAA,UACtB,OAAO;AAAA,UACP,WAAW;AAAA,UACX,QAAQ,YAAY,MAAM,QAAQ,QAAQ,CAAC,CAAC,QAAQ,KAAK,OAAO,YAAY,QAAQ,CAAC,CAAC,oBAAoB,QAAQ,QAAQ,QAAQ,CAAC,CAAC;AAAA,QACtI,CAAC;AAAA,MACH;AACA,UAAI,eAAe,KAAK;AACtB,2BAAmB,KAAK;AAAA,UACtB,OAAO;AAAA,UACP,WAAW,SAAS,KAAK;AAAA,UACzB,QAAQ,YAAY,MAAM,QAAQ,QAAQ,CAAC,CAAC,QAAQ,KAAK,OAAO,KAAK,IAAI,WAAW,EAAE,QAAQ,CAAC,CAAC,oBAAoB,QAAQ,QAAQ,QAAQ,CAAC,CAAC;AAAA,QAChJ,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,aAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACrD,QAAI,MAAM,cAAc,WAAW;AACjC,YAAM,cAAc,MAAM,UAAU,QAAQ;AAC5C,UAAI,KAAK,IAAI,WAAW,KAAK,IAAI;AAC/B,2BAAmB,KAAK;AAAA,UACtB,OAAO;AAAA,UACP,WAAW,cAAc,IAAI,SAAS,KAAK,KAAK,SAAS,KAAK;AAAA,UAC9D,QAAQ,YAAY,MAAM,QAAQ,QAAQ,CAAC,CAAC,QAAQ,KAAK,eAAe,QAAQ,QAAQ,QAAQ,CAAC,CAAC;AAAA,QACpG,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,aAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACrD,QAAI,MAAM,cAAc,WAAW;AACjC,YAAM,cAAc,MAAM,UAAU,QAAQ;AAC5C,UAAI,KAAK,IAAI,WAAW,KAAK,IAAI;AAC/B,2BAAmB,KAAK;AAAA,UACtB,OAAO;AAAA,UACP,WAAW,cAAc,IAAI,SAAS,KAAK,KAAK,SAAS,KAAK;AAAA,UAC9D,QAAQ,YAAY,MAAM,QAAQ,QAAQ,CAAC,CAAC,YAAY,KAAK,eAAe,QAAQ,QAAQ,QAAQ,CAAC,CAAC;AAAA,QACxG,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,OAAO,KAAK,UAAU,EAAE,KAAK,IAAI;AAClD,QAAM,cAAc,+BAA+B,YAAY,MAAM,QAAQ,MAAM,2BAA2B,OAAO,KAAK,UAAU,EAAE,MAAM,gBAAgB,QAAQ,wBAAwB,QAAQ,QAAQ,QAAQ,CAAC,CAAC,gBAAgB,QAAQ,MAAM,QAAQ,CAAC,CAAC,KAAK,mBAAmB,MAAM;AAE5R,SAAO,iBAAiB,aAAa;AAAA,IACnC;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB;AAAA,IACA,SAAS;AAAA,MACP,cAAc,QAAQ;AAAA,MACtB,eAAe,QAAQ;AAAA,MACvB,YAAY,QAAQ;AAAA,MACpB,QAAQ,QAAQ;AAAA,MAChB,iBAAiB,QAAQ;AAAA,IAC3B;AAAA,EACF,CAAC;AACH;AAMO,IAAM,6BAA6BC,GAAE,OAAO;AAAA,EACjD,SAASA,GAAE,OAAO,EAAE,SAAS,qBAAqB;AAAA,EAClD,cAAcA,GAAE,OAAO,EAAE,SAAS,yCAAyC;AAAA,EAC3E,WAAWA,GACR,OAAO,EACP,SAAS,EACT,QAAQ,EAAE,EACV,SAAS,6CAA6C;AAAA,EACzD,oBAAoBA,GACjB,OAAO,EACP,SAAS,EACT,QAAQ,CAAC,EACT,SAAS,mFAAmF;AACjG,CAAC;AAED,eAAsB,2BACpB,OACA,SAC8C;AAC9C,QAAM,EAAE,SAAS,aAAa,IAAI;AAClC,QAAM,YAAY,MAAM,aAAa;AACrC,QAAM,qBAAqB,MAAM,sBAAsB;AAGvD,QAAM,OAAO,MAAM,cAAc,OAAO;AACxC,QAAM,UAAU,MAAM,WAAW,MAAM,SAAS,cAAc,OAAO;AACrE,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,kCAAkC,YAAY,eAAe,OAAO;AAAA,MACpE,EAAE,OAAO,oBAAoB;AAAA,IAC/B;AAAA,EACF;AAGA,MAAI,CAAC,QAAQ,gBAAgB,QAAQ,aAAa,WAAW,GAAG;AAC9D,WAAO;AAAA,MACL,YAAY,YAAY;AAAA,MACxB,EAAE,YAAY,KAAK;AAAA,IACrB;AAAA,EACF;AAGA,QAAM,aAAa,QAAQ;AAC3B,QAAM,gBAAgB,WAAW,OAAO,CAAC,MAAM,EAAE,WAAW,WAAW;AACvE,QAAM,mBAAmB,WAAW,OAAO,CAAC,MAAM,EAAE,WAAW,WAAW;AAE1E,MAAI,cAAc,WAAW,GAAG;AAC9B,WAAO;AAAA,MACL,YAAY,YAAY,SAAS,WAAW,MAAM;AAAA,MAClD,EAAE,mBAAmB,MAAM,mBAAmB,iBAAiB;AAAA,IACjE;AAAA,EACF;AAGA,QAAM,EAAE,SAAS,gBAAgB,UAAU,IAAI,MAAM;AAAA,IACnD;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,WAAqB,CAAC;AAE5B,MAAI,iBAAiB,SAAS,GAAG;AAC/B,aAAS;AAAA,MACP,GAAG,iBAAiB,MAAM,0EAA0E,iBAAiB,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA,IACtK;AAAA,EACF;AAEA,MAAI,UAAU,WAAW,GAAG;AAC1B,WAAO;AAAA,MACL,iCAAiC,YAAY,eAAe,OAAO;AAAA,MACnE,EAAE,OAAO,YAAY;AAAA,IACvB;AAAA,EACF;AAEA,MAAI,iBAAiB,GAAG;AACtB,aAAS;AAAA,MACP,GAAG,cAAc,OAAO,UAAU,MAAM;AAAA,IAC1C;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,MACL,2DAA2D,YAAY;AAAA,MACvE,EAAE,OAAO,mBAAmB,SAAS;AAAA,IACvC;AAAA,EACF;AAGA,QAAM,UAAU;AAChB,QAAM,aAAgC,QAAQ,IAAI,CAAC,MAAM,qBAAqB,CAAC,CAAC;AAGhF,QAAM,eAAe,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE;AAClD,QAAM,iBAAiB,kBAAkB,YAAY;AAGrD,QAAM,YAGF,CAAC;AAEL,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,SAAS,QAAQ,CAAC;AACxB,UAAM,YAAY,WAAW,CAAC;AAC9B,UAAM,aACJ,OAAO,eAAe,GAAG,OAAO,KAAK,IAAI,OAAO,QAAQ,IAAI,KAAK,UAAU,OAAO,KAAK,CAAC;AAE1F,UAAM,aAAuB,CAAC;AAC9B,UAAM,iBAA2B,CAAC;AAClC,QAAI,cAAc;AAElB,eAAW,EAAE,OAAO,OAAO,KAAK,SAAS;AACvC,YAAM,MAAMD,QAAO,QAAQ,UAAU,QAAQ;AAC7C,UAAI,QAAQ,QAAQ,QAAQ,QAAW;AACrC;AACA;AAAA,MACF;AACA,UAAI,UAAU,KAAK,MAAM,GAAG;AAC1B,mBAAW,KAAK,MAAM,EAAE;AAAA,MAC1B,OAAO;AACL,uBAAe,KAAK,MAAM,EAAE;AAAA,MAC9B;AAAA,IACF;AAEA,cAAU,UAAU,IAAI;AAAA,MACtB,SAAS,kBAAkB,UAAU;AAAA,MACrC,cAAc,kBAAkB,cAAc;AAAA,MAC9C,eAAe;AAAA,IACjB;AAAA,EACF;AAIA,QAAM,cAAwB,CAAC;AAC/B,aAAW,EAAE,OAAO,OAAO,KAAK,SAAS;AACvC,QAAI,YAAY;AAChB,QAAI,UAAU;AACd,eAAW,aAAa,YAAY;AAClC,YAAM,MAAMA,QAAO,QAAQ,UAAU,QAAQ;AAC7C,UAAI,QAAQ,QAAQ,QAAQ,QAAW;AACrC,kBAAU;AACV;AAAA,MACF;AACA,UAAI,CAAC,UAAU,KAAK,MAAM,GAAG;AAC3B,oBAAY;AACZ;AAAA,MACF;AAAA,IACF;AACA,QAAI,WAAW,WAAW;AACxB,kBAAY,KAAK,MAAM,EAAE;AAAA,IAC3B;AAAA,EACF;AACA,QAAM,WAAW,kBAAkB,WAAW;AAG9C,QAAM,iBAA6C,CAAC;AACpD,WAAS,OAAO,GAAG,OAAO,QAAQ,QAAQ,QAAQ;AAChD,UAAM,aACJ,QAAQ,IAAI,EAAE,eACd,GAAG,QAAQ,IAAI,EAAE,KAAK,IAAI,QAAQ,IAAI,EAAE,QAAQ,IAAI,KAAK,UAAU,QAAQ,IAAI,EAAE,KAAK,CAAC;AAEzF,UAAM,MAAgB,CAAC;AACvB,eAAW,EAAE,OAAO,OAAO,KAAK,SAAS;AACvC,UAAI,kBAAkB;AACtB,UAAI,UAAU;AACd,eAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,YAAI,MAAM,KAAM;AAChB,cAAM,MAAMA,QAAO,QAAQ,WAAW,CAAC,EAAE,QAAQ;AACjD,YAAI,QAAQ,QAAQ,QAAQ,QAAW;AACrC,oBAAU;AACV;AAAA,QACF;AACA,YAAI,CAAC,WAAW,CAAC,EAAE,KAAK,MAAM,GAAG;AAC/B,4BAAkB;AAClB;AAAA,QACF;AAAA,MACF;AACA,UAAI,WAAW,iBAAiB;AAC9B,YAAI,KAAK,MAAM,EAAE;AAAA,MACnB;AAAA,IACF;AACA,mBAAe,UAAU,IAAI,kBAAkB,GAAG;AAAA,EACpD;AAGA,QAAM,gBAA4C,CAAC;AACnD,MAAI,QAAQ,UAAU,oBAAoB;AACxC,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,eAAS,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AAC3C,cAAM,QACJ,QAAQ,CAAC,EAAE,eACX,GAAG,QAAQ,CAAC,EAAE,KAAK,IAAI,QAAQ,CAAC,EAAE,QAAQ,IAAI,KAAK,UAAU,QAAQ,CAAC,EAAE,KAAK,CAAC;AAChF,cAAM,QACJ,QAAQ,CAAC,EAAE,eACX,GAAG,QAAQ,CAAC,EAAE,KAAK,IAAI,QAAQ,CAAC,EAAE,QAAQ,IAAI,KAAK,UAAU,QAAQ,CAAC,EAAE,KAAK,CAAC;AAChF,cAAM,UAAU,GAAG,KAAK,MAAM,KAAK;AAEnC,cAAM,MAAgB,CAAC;AACvB,mBAAW,EAAE,OAAO,OAAO,KAAK,SAAS;AACvC,cAAI,kBAAkB;AACtB,cAAI,UAAU;AACd,mBAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,gBAAI,MAAM,KAAK,MAAM,EAAG;AACxB,kBAAM,MAAMA,QAAO,QAAQ,WAAW,CAAC,EAAE,QAAQ;AACjD,gBAAI,QAAQ,QAAQ,QAAQ,QAAW;AACrC,wBAAU;AACV;AAAA,YACF;AACA,gBAAI,CAAC,WAAW,CAAC,EAAE,KAAK,MAAM,GAAG;AAC/B,gCAAkB;AAClB;AAAA,YACF;AAAA,UACF;AACA,cAAI,WAAW,iBAAiB;AAC9B,gBAAI,KAAK,MAAM,EAAE;AAAA,UACnB;AAAA,QACF;AACA,sBAAc,OAAO,IAAI,kBAAkB,GAAG;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAGA,QAAM,qBAIA,CAAC;AAGP,aAAW,CAAC,YAAY,EAAE,SAAS,aAAa,CAAC,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC/E,QACE,QAAQ,cAAc,aACtB,aAAa,cAAc,WAC3B;AACA,UAAI,QAAQ,QAAQ,aAAa,SAAS,aAAa,QAAQ,GAAG;AAChE,2BAAmB,KAAK;AAAA,UACtB,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,QAAQ,qBAAqB,QAAQ,MAAM,QAAQ,CAAC,CAAC,+BAA+B,aAAa,MAAM,QAAQ,CAAC,CAAC;AAAA,QACnH,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,aAAW,CAAC,YAAY,KAAK,KAAK,OAAO,QAAQ,cAAc,GAAG;AAChE,QAAI,MAAM,cAAc,aAAa,SAAS,cAAc,WAAW;AACrE,UAAI,MAAM,QAAQ,SAAS,SAAS,MAAM,UAAU,SAAS,SAAS;AACpE,2BAAmB,KAAK;AAAA,UACtB,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,QAAQ,2CAA2C,MAAM,MAAM,QAAQ,CAAC,CAAC,QAAQ,SAAS,MAAM,QAAQ,CAAC,CAAC,mBAAmB,MAAM,QAAQ,QAAQ,CAAC,CAAC,QAAQ,SAAS,QAAQ,QAAQ,CAAC,CAAC;AAAA,QAC1L,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,MAAI,SAAS,aAAa,KAAK,SAAS,aAAa,WAAW;AAC9D,aAAS;AAAA,MACP,gCAAgC,SAAS,UAAU,cAAc,SAAS;AAAA,IAC5E;AAAA,EACF;AACA,aAAW,CAAC,YAAY,EAAE,SAAS,aAAa,CAAC,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC/E,QAAI,QAAQ,aAAa,KAAK,QAAQ,aAAa,WAAW;AAC5D,eAAS;AAAA,QACP,GAAG,UAAU,kBAAkB,QAAQ,UAAU,cAAc,SAAS;AAAA,MAC1E;AAAA,IACF;AACA,QAAI,aAAa,aAAa,KAAK,aAAa,aAAa,WAAW;AACtE,eAAS;AAAA,QACP,GAAG,UAAU,uBAAuB,aAAa,UAAU,cAAc,SAAS;AAAA,MACpF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,iBAAiB,SAAS,IAAI,KAAK,iBAAiB,MAAM,kCAAkC;AAC7G,QAAM,cAAc,0BAA0B,YAAY,MAAM,QAAQ,MAAM,qCAAqC,QAAQ,MAAM,UAAU,QAAQ,oCAAoC,SAAS,UAAU,qBAAqB,SAAS,QAAQ,QAAQ,CAAC,CAAC,eAAe,SAAS,MAAM,QAAQ,CAAC,CAAC,KAAK,mBAAmB,MAAM;AAEhU,SAAO,iBAAiB,aAAa;AAAA,IACnC;AAAA,IACA,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,UAAU;AAAA,MACR,QAAQ;AAAA,MACR,OAAO;AAAA,IACT;AAAA,IACA,2BAA2B,iBAAiB,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,EAAE,KAAK,IAAI,EAAE,QAAQ,IAAI,EAAE,KAAK,EAAE;AAAA,IAC7G,sBAAsB;AAAA,IACtB;AAAA,EACF,CAAC;AACH;AAMO,IAAM,8BAA8BC,GAAE,OAAO;AAAA,EAClD,SAASA,GACN,OAAO,EACP,SAAS,EACT,SAAS,iEAAiE;AAAA,EAC7E,WAAWA,GACR,OAAO,EACP,SAAS,EACT,QAAQ,EAAE,EACV,SAAS,2CAA2C;AACzD,CAAC;AAED,eAAsB,4BACpB,OACA,SACsH;AACtH,MAAI;AACF,UAAM,EAAE,SAAS,UAAU,IAAI,4BAA4B,MAAM,KAAK;AACtE,UAAM,OAAO,MAAM,cAAc,OAAO;AAGxC,UAAM,WAAW,MAAM,aAAa,MAAM,SAAS,OAAO;AAC1D,QAAI,SAAS,WAAW,GAAG;AACzB,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,UACF,yCAAyC,OAAO,sDAChD;AAAA,UACN;AAAA,QACF;AAAA,MACF;AAAA,IACF;AASA,UAAM,kBAAyC,CAAC;AAChD,UAAM,WAAqB,CAAC;AAE5B,eAAW,WAAW,UAAU;AAC9B,UAAI;AACJ,UAAI;AACF,gBAAQ,MAAM,UAAU,SAAS,QAAQ,OAAO;AAAA,MAClD,QAAQ;AACN,iBAAS,KAAK,yBAAyB,QAAQ,OAAO,mBAAmB,QAAQ,YAAY,GAAG;AAChG;AAAA,MACF;AAEA,UAAI,SAAS,iBAAiB,MAAM,QAAQ,QAAQ,YAAY;AAEhE,UAAI,OAAO,WAAW,KAAK,MAAM,OAAO,SAAS,GAAG;AAClD,cAAM,mBAAmB,IAAI,IAAI,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AACpE,YAAI,iBAAiB,SAAS,GAAG;AAC/B,mBAAS,MAAM;AAAA,QACjB;AAAA,MACF;AACA,UAAI,OAAO,WAAW,GAAG;AACvB,iBAAS,KAAK,iCAAiC,QAAQ,YAAY,eAAe,QAAQ,OAAO,GAAG;AACpG;AAAA,MACF;AAGA,YAAM,YAAY,sBAAsB,MAAM;AAC9C,YAAM,EAAE,KAAK,OAAO,IAAI,wBAAwB,SAAS;AACzD,YAAM,cAAc,MAAM,KAAK,cAAc,KAAK,MAAM;AACxD,YAAM,eAAe,gBAAgB,WAAW;AAChD,YAAM,QAAQ,oBAAoB,YAAY;AAE9C,iBAAW,SAAS,QAAQ;AAC1B,cAAM,SAAS,kBAAkB,KAAK;AACtC,cAAM,YAAY,oBAAoB,OAAO,QAAQ,OAAO,IAAI;AAChE,cAAM,SAAS,MAAM,IAAI,SAAS;AAClC,YAAI,QAAQ;AACV,0BAAgB,KAAK;AAAA,YACnB,cAAc,QAAQ;AAAA,YACtB;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,QAAI,gBAAgB,WAAW,GAAG;AAChC,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAIA,UAAM,gBAAgB,CAAC,GAAG,IAAI,IAAI,gBAAgB,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAI7E,UAAM,UAAU,oBAAI,IAAoC;AAExD,QAAI,oBAAoB;AACxB,UAAM,kBAAkB,oBAAI,IAAsB;AAElD,eAAW,EAAE,cAAc,OAAO,OAAO,KAAK,iBAAiB;AAC7D,YAAM,YAAYF,QAAO,QAAQ,iBAAiB;AAClD,YAAM,WAAW,OAAO,sBAAsB;AAG9C,UAAI,MAAM,SAAS,KAAK,YAAY,KAAK,YAAY,EAAG;AAGxD,UAAI,QAA2B;AAC/B,UACE,aAAa,QACb,aAAa,UACb,aAAa,IACb;AACA;AACA,YAAI,CAAC,gBAAgB,IAAI,YAAY,GAAG;AACtC,0BAAgB,IAAI,cAAc,CAAC,CAAC;AAAA,QACtC;AACA,wBAAgB,IAAI,YAAY,EAAG,KAAK,MAAM,EAAE;AAChD;AAAA,MACF;AACA,YAAM,WAAW,OAAO,QAAQ,EAAE,YAAY;AAC9C,UAAI,aAAa,QAAQ,aAAa,UAAU,aAAa,QAAQ;AACnE,gBAAQ;AAAA,MACV,OAAO;AACL;AACA,YAAI,CAAC,gBAAgB,IAAI,YAAY,GAAG;AACtC,0BAAgB,IAAI,cAAc,CAAC,CAAC;AAAA,QACtC;AACA,wBAAgB,IAAI,YAAY,EAAG,KAAK,MAAM,EAAE;AAChD;AAAA,MACF;AAEA,YAAM,cAAc,kBAAkB,SAAS,KAAK,UAAU,SAAS;AACvE,YAAM,UAAU,GAAG,WAAW,IAAI,KAAK;AAEvC,UAAI,CAAC,QAAQ,IAAI,OAAO,GAAG;AACzB,gBAAQ,IAAI,SAAS,oBAAI,IAAI,CAAC;AAAA,MAChC;AACA,YAAM,UAAU,QAAQ,IAAI,OAAO;AACnC,UAAI,CAAC,QAAQ,IAAI,YAAY,GAAG;AAC9B,gBAAQ,IAAI,cAAc,CAAC,CAAC;AAAA,MAC9B;AACA,cAAQ,IAAI,YAAY,EAAG,KAAK,MAAM,EAAE;AAAA,IAC1C;AAGA,UAAM,SAAqE,CAAC;AAC5E,UAAM,WAKD,CAAC;AACN,UAAM,aAAuD,CAAC;AAC9D,QAAI,eAAe;AACnB,QAAI,eAAe;AAEnB,eAAW,CAAC,EAAE,WAAW,KAAK,OAAO,QAAQ,iBAAiB,GAAG;AAC/D,aAAO,WAAW,IAAI,CAAC;AACvB,iBAAW,SAAS,cAAc;AAChC,cAAM,UAAU,GAAG,WAAW,IAAI,KAAK;AACvC,cAAM,UAAU,QAAQ,IAAI,OAAO;AAEnC,YAAI,CAAC,WAAW,QAAQ,SAAS,GAAG;AAClC,qBAAW,KAAK,EAAE,QAAQ,aAAa,MAAM,CAAC;AAC9C,iBAAO,WAAW,EAAE,KAAK,IAAI,CAAC;AAC9B;AAAA,QACF;AAEA;AACA,cAAM,YAAwC,CAAC;AAC/C,cAAM,mBAA6B,CAAC;AACpC,YAAI,oBAAoB;AAExB,mBAAW,CAAC,WAAW,GAAG,KAAK,SAAS;AACtC,oBAAU,SAAS,IAAI,kBAAkB,GAAG;AAC5C,2BAAiB,KAAK,SAAS;AAC/B,+BAAqB,IAAI;AAGzB,cAAI,IAAI,SAAS,KAAK,IAAI,SAAS,WAAW;AAC5C,qBAAS;AAAA,cACP,eAAe,SAAS,cAAc,IAAI,MAAM,cAAc,WAAW,IAAI,KAAK,gBAAgB,SAAS;AAAA,YAC7G;AAAA,UACF;AAAA,QACF;AAEA,eAAO,WAAW,EAAE,KAAK,IAAI;AAG7B,YAAI,iBAAiB,UAAU,GAAG;AAChC;AACA,mBAAS,KAAK;AAAA,YACZ,QAAQ;AAAA,YACR;AAAA,YACA,YAAY;AAAA,YACZ,aAAa;AAAA,UACf,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,UAAM,iBAAiB,WAAW;AAGlC,QAAI,oBAAoB,GAAG;AACzB,eAAS;AAAA,QACP,GAAG,iBAAiB;AAAA,MACtB;AAAA,IACF;AAGA,UAAM,oBACJ,gBAAgB,OAAO,IACnB,OAAO;AAAA,MACL,CAAC,GAAG,gBAAgB,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM;AAAA,QAClD;AAAA,QACA,kBAAkB,GAAG;AAAA,MACvB,CAAC;AAAA,IACH,IACA;AAEN,UAAM,kBAAkB;AAAA,MACtB,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,UAAU,4BAA4B,cAAc,MAAM,iBAAiB,YAAY,uBAAuB,YAAY,eAAe,cAAc;AAE7J,UAAM,iBAA0C;AAAA,MAC9C,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,mBAAmB;AACrB,qBAAe,gBAAgB;AAAA,IACjC;AAEA,WAAO,iBAAiB,SAAS,cAAc;AAAA,EACjD,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,MAAM,2CAA4C,MAAgB,OAAO;AAAA,QAC3E;AAAA,MACF;AAAA,MACA,SAAS;AAAA,IACX;AAAA,EACF;AACF;;;AC7iCA,SAAS,KAAAG,UAAS;AA6BlB,SAASC,iBAAgB,MAA6B;AACpD,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,QAAQ,KAAK,MAAM,0BAA0B;AACnD,QAAI,MAAO,QAAO,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAAA,EACvD;AACA,QAAM,IAAI,OAAO,SAAS,WAAW,IAAI,KAAK,IAAI,IAAI;AACtD,QAAM,OAAO,EAAE,YAAY;AAC3B,QAAM,QAAQ,OAAO,EAAE,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACtD,QAAM,MAAM,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG;AAC/C,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG;AAChC;AAEA,SAASC,mBAAkB,OAA+B;AACxD,SAAO;AAAA,IACL,MAAMD,iBAAgB,MAAM,UAAU;AAAA,IACtC,QAAQ,mBAAmB,OAAO,qBAAqB;AAAA,EACzD;AACF;AAEA,SAASE,uBAAsB,QAAoC;AACjE,QAAM,QAAQ,oBAAI,IAA6B;AAC/C,aAAW,SAAS,QAAQ;AAC1B,UAAM,SAASD,mBAAkB,KAAK;AACtC,UAAM,IAAI,oBAAoB,OAAO,QAAQ,OAAO,IAAI,GAAG,MAAM;AAAA,EACnE;AACA,SAAO,MAAM,KAAK,MAAM,OAAO,CAAC;AAClC;AAEA,SAASE,iBAAgB,QAIK;AAC5B,QAAM,cAAc,OAAO;AAC3B,QAAM,WAAqB,CAAC;AAC5B,WAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,aAAS,KAAK,OAAO,WAAW,CAAC,CAAC;AAAA,EACpC;AACA,QAAM,UAAqC,CAAC;AAC5C,aAAW,OAAO,OAAO,QAAQ,GAAG;AAClC,UAAM,SAAkC,CAAC;AACzC,aAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,YAAM,MAAM,IAAI,CAAC;AACjB,aAAO,SAAS,CAAC,CAAC,IAAI,OAAO,QAAQ,WAAW,OAAO,GAAG,IAAI;AAAA,IAChE;AACA,YAAQ,KAAK,MAAM;AAAA,EACrB;AACA,SAAO;AACT;AAEA,SAASC,qBACP,SACsC;AACtC,QAAM,SAAS,oBAAI,IAAqC;AACxD,aAAW,UAAU,SAAS;AAC5B,UAAM,OAAO,OAAO,OAAO,MAAM,KAAK,EAAE;AACxC,UAAM,SAAS,OAAO,OAAO,QAAQ,KAAK,qBAAqB;AAC/D,WAAO,IAAI,oBAAoB,QAAQ,IAAI,GAAG,MAAM;AAAA,EACtD;AACA,SAAO;AACT;AAEA,SAASC,QAAO,QAAiC,OAAuB;AACtE,QAAM,MAAM,OAAO,KAAK;AACxB,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,MAAI,OAAO,QAAQ,SAAU,QAAO,OAAO,GAAG;AAC9C,SAAO;AACT;AAEA,IAAMC,qBAA4C;AAAA,EAChD,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AA6BO,IAAM,gCAAgCC,GAAE,OAAO;AAAA,EACpD,SAASA,GACN,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,WAAWA,GACR,OAAO,EACP,SAAS,EACT,QAAQ,CAAC,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC;AAMD,eAAsB,8BACpB,OACA,SAC8C;AAC9C,QAAM,YAAY,MAAM,aAAa;AACrC,QAAM,WAAqB,CAAC;AAC5B,QAAM,sBAAgC,CAAC;AAGvC,QAAM,OAAO,MAAM,cAAc,OAAO;AACxC,QAAM,WAAW,MAAM,aAAa,MAAM,MAAM,SAAS,OAAO;AAEhE,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO;AAAA,MACL,MAAM,UACF,yCAAyC,MAAM,OAAO,sDACtD;AAAA,MACJ,EAAE,OAAO,cAAc;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,aAAyC,CAAC;AAChD,MAAI,mBAAmB;AACvB,MAAI,kBAAkB;AACtB,QAAM,sBAKA,CAAC;AACP,QAAM,iBAKA,CAAC;AAGP,QAAM,eAAyC,CAAC;AAEhD,aAAW,WAAW,UAAU;AAC9B,QAAI;AAEF,UAAI,CAAC,QAAQ,mBAAmB,QAAQ,gBAAgB,WAAW,GAAG;AACpE;AACA,4BAAoB;AAAA,UAClB,aAAa,QAAQ,YAAY,aAAa,QAAQ,OAAO;AAAA,QAC/D;AACA;AAAA,MACF;AAGA,UAAI;AACJ,UAAI;AACF,gBAAQ,MAAM,UAAU,SAAS,QAAQ,OAAO;AAAA,MAClD,QAAQ;AACN,iBAAS;AAAA,UACP,yBAAyB,QAAQ,OAAO,mBAAmB,QAAQ,YAAY;AAAA,QACjF;AACA;AAAA,MACF;AAEA,UAAI,SAAS,iBAAiB,MAAM,QAAQ,QAAQ,YAAY;AAEhE,UAAI,OAAO,WAAW,KAAK,MAAM,OAAO,SAAS,GAAG;AAClD,cAAM,mBAAmB,IAAI,IAAI,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AACpE,YAAI,iBAAiB,SAAS,GAAG;AAC/B,mBAAS,MAAM;AAAA,QACjB;AAAA,MACF;AAEA,UAAI,OAAO,WAAW,GAAG;AACvB,iBAAS;AAAA,UACP,iCAAiC,QAAQ,YAAY,eAAe,QAAQ,OAAO;AAAA,QACrF;AACA;AAAA,MACF;AAGA,YAAM,YAAYL,uBAAsB,MAAM;AAC9C,YAAM,EAAE,KAAK,OAAO,IAAI,wBAAwB,SAAS;AACzD,YAAM,SAAS,MAAM,KAAK,cAAc,KAAK,MAAM;AACnD,YAAM,gBAAgBC,iBAAgB,MAAM;AAC5C,YAAM,YAAYC,qBAAoB,aAAa;AAOnD,YAAM,UAA6B,CAAC;AACpC,UAAI,iBAAiB;AAErB,iBAAW,SAAS,QAAQ;AAC1B,cAAM,SAASH,mBAAkB,KAAK;AACtC,cAAM,MAAM,oBAAoB,OAAO,QAAQ,OAAO,IAAI;AAC1D,cAAM,SAAS,UAAU,IAAI,GAAG;AAChC,YAAI,QAAQ;AACV,kBAAQ,KAAK,EAAE,OAAO,OAAO,CAAC;AAAA,QAChC,OAAO;AACL;AAAA,QACF;AAAA,MACF;AAEA,UAAI,QAAQ,WAAW,GAAG;AACxB;AACA,iBAAS;AAAA,UACP,wCAAwC,QAAQ,YAAY,MAAM,OAAO,MAAM;AAAA,QACjF;AACA;AAAA,MACF;AAEA,UAAI,iBAAiB,GAAG;AACtB,iBAAS;AAAA,UACP,aAAa,QAAQ,YAAY,MAAM,cAAc,OAAO,OAAO,MAAM;AAAA,QAC3E;AAAA,MACF;AAGA,YAAM,SAAS,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE;AAC5C,YAAM,eAAe,kBAAkB,MAAM;AAG7C,YAAM,YAAsC,CAAC;AAC7C,iBAAW,EAAE,OAAO,OAAO,KAAK,SAAS;AACvC,cAAM,MAAMI,QAAO,QAAQ,iBAAiB;AAC5C,YAAI,MAAM,GAAG,KAAK,MAAM,KAAK,MAAM,EAAG;AACtC,cAAM,QAAQC,mBAAkB,GAAG,KAAK,UAAU,GAAG;AACrD,YAAI,CAAC,UAAU,KAAK,EAAG,WAAU,KAAK,IAAI,CAAC;AAC3C,kBAAU,KAAK,EAAE,KAAK,MAAM,EAAE;AAG9B,YAAI,CAAC,aAAa,KAAK,EAAG,cAAa,KAAK,IAAI,CAAC;AACjD,qBAAa,KAAK,EAAE,KAAK,MAAM,EAAE;AAAA,MACnC;AAGA,YAAM,cAAc,IAAI;AAAA,QACtB,QAAQ,gBAAgB,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AAAA,MACpD;AACA,YAAM,oBAAgD,CAAC;AAEvD,iBAAW,CAAC,OAAO,GAAG,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,cAAM,QAAQ,kBAAkB,GAAG;AACnC,cAAM,aAAa,YAAY,IAAI,MAAM,YAAY,CAAC;AAOtD,YAAI;AACJ,cAAM,UAAU,MAAM,UAAU,aAAa;AAE7C,YAAI,YAAY;AACd,cAAI,UAAU,KAAK;AACjB,6BAAiB;AACjB,gCAAoB,KAAK;AAAA,cACvB,cAAc,QAAQ;AAAA,cACtB,QAAQ;AAAA,cACR,SAAS,MAAM;AAAA,cACf,iBAAiB,aAAa;AAAA,YAChC,CAAC;AAAA,UACH,OAAO;AACL,6BAAiB;AAAA,UACnB;AAAA,QACF,OAAO;AACL,cAAI,UAAU,MAAM,MAAM,cAAc,WAAW;AACjD,6BAAiB;AACjB,2BAAe,KAAK;AAAA,cAClB,cAAc,QAAQ;AAAA,cACtB,QAAQ;AAAA,cACR,SAAS,MAAM;AAAA,cACf,gBAAgB,aAAa;AAAA,YAC/B,CAAC;AAAA,UACH,OAAO;AACL,6BAAiB;AAAA,UACnB;AAAA,QACF;AAEA,0BAAkB,KAAK,IAAI,EAAE,OAAO,YAAY,eAAe;AAAA,MACjE;AAGA,YAAM,gBACJ,QAAQ,gBAAgB,qBACxB,QAAQ,gBAAgB;AAE1B,iBAAW,KAAK;AAAA,QACd,cAAc,QAAQ;AAAA,QACtB,SAAS,QAAQ;AAAA,QACjB,eAAe,QAAQ;AAAA,QACvB,YAAY,QAAQ,cAAc;AAAA,QAClC;AAAA,QACA,iBAAiB,QAAQ;AAAA,QACzB;AAAA,QACA,YAAY,OAAO;AAAA,QACnB,iBAAiB,QAAQ;AAAA,QACzB;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,eAAS;AAAA,QACP,8BAA8B,QAAQ,YAAY,aAAa,QAAQ,OAAO,MAAO,IAAc,OAAO;AAAA,MAC5G;AAAA,IACF;AAAA,EACF;AAGA,QAAM,iBAGF,CAAC;AAEL,aAAW,CAAC,OAAO,GAAG,KAAK,OAAO,QAAQ,YAAY,GAAG;AAEvD,QAAI,mBAAmB;AACvB,eAAW,YAAY,YAAY;AACjC,UAAI,SAAS,kBAAkB,KAAK,GAAG;AACrC;AAAA,MACF;AAAA,IACF;AACA,mBAAe,KAAK,IAAI;AAAA,MACtB;AAAA,MACA,aAAa,IAAI;AAAA,MACjB,eAAe,kBAAkB,GAAG;AAAA,IACtC;AAAA,EACF;AAEA,QAAM,UAAU;AAAA,IACd,iBAAiB,SAAS;AAAA,IAC1B,UAAU,WAAW;AAAA,IACrB;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,IAClB,aAAa;AAAA,EACf;AAEA,QAAM,cACJ,8BAA8B,WAAW,MAAM,IAAI,SAAS,MAAM,yBAC/D,oBAAoB,MAAM,yBAAyB,eAAe,MAAM,uBAC1E,mBAAmB,IAChB,GAAG,gBAAgB,oCACnB,OACH,kBAAkB,IACf,GAAG,eAAe,gCAClB;AAEN,SAAO,iBAAiB,aAAa;AAAA,IACnC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;;;AC9YO,SAAS,4BACd,WAA+B,YAAY,GAC3C,MAAyB,QAAQ,KACH;AAC9B,QAAM,OAAO,SAAS,aAAa;AACnC,QAAM,kBAAkB,SAAS,SAAS,YAAY,uBAAuB,GAAG,IAAI;AAMpF,QAAM,iBAAiB,OAAO,SAAS,gBAAgB;AAEvD,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,cAAc,SAAS;AAAA,IACvB;AAAA,IACA;AAAA,IACA,cAAc,OAAO,SAAS,sBAAsB;AAAA,EACtD;AACF;AAEO,SAAS,gCACd,MAAyB,QAAQ,KACH;AAC9B,SAAO,4BAA4B,YAAY,GAAG,GAAG;AACvD;;;ACjCA,SAAS,KAAAE,UAAS;;;ACQlB,IAAM,SAAS;AACf,IAAM,qBAAqB;AASpB,SAAS,YAAY,OAAuB;AACjD,QAAM,WAAW,MAAM,MAAM,MAAM;AACnC,MAAI,SAAU,QAAO,SAAS,CAAC;AAE/B,MAAI,CAAC,mBAAmB,KAAK,KAAK,GAAG;AACnC,UAAM,IAAI,MAAM,6BAA6B,KAAK,GAAG;AAAA,EACvD;AACA,SAAO;AACT;AAQO,SAAS,iBAAiB,OAAe,UAAkC;AAChF,QAAM,OAAO,YAAY,KAAK;AAC9B,SAAO,SAAS,QAAQ,IAAI;AAC9B;;;ADdO,IAAM,oBAAoBC,GAAE,OAAO;AAAA;AAAA,EAExC,MAAMA,GACH;AAAA,IACCA,GAAE,OAAO;AAAA,MACP,QAAQA,GAAE,OAAO,EAAE,SAAS,uCAAuC;AAAA,MACnE,QAAQA,GAAE,OAAO,EAAE,SAAS,cAAc;AAAA,MAC1C,MAAMA,GAAE,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,SAAS,aAAa;AAAA,MAC/C,QAAQA,GAAE,OAAO,EAAE,SAAS,4BAA4B;AAAA,MACxD,UAAUA,GAAE,OAAO,EAAE,SAAS,mCAAmC;AAAA,MACjE,aAAaA,GACV,OAAO,EACP,SAAS,kDAAkD;AAAA,IAChE,CAAC;AAAA,EACH,EACC,SAAS,EACT,SAAS,kDAAkD;AAAA;AAAA,EAG9D,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,6BAA6B;AAAA,EACtE,aAAaA,GACV,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA;AAAA,EAGF,WAAWA,GACR,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,YAAYA,GACT,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,YAAYA,GACT,OAAO,EACP,QAAQ,GAAG,EACX,SAAS,wDAAwD;AAAA,EACpE,QAAQA,GACL,KAAK,CAAC,QAAQ,WAAW,SAAS,CAAC,EACnC,QAAQ,SAAS,EACjB;AAAA,IACC;AAAA,EAGF;AAAA,EACF,UAAUA,GACP,KAAK,CAAC,SAAS,QAAQ,CAAC,EACxB,QAAQ,OAAO,EACf;AAAA,IACC;AAAA,EAEF;AAAA,EACF,aAAaA,GACV,QAAQ,EACR,QAAQ,KAAK,EACb;AAAA,IACC;AAAA,EACF;AACJ,CAAC;AAMD,IAAM,YAAoC;AAAA,EACxC,KAAK;AAAA,EAAM,KAAK;AAAA,EAAM,KAAK;AAAA,EAAM,KAAK;AAAA,EAAM,KAAK;AAAA,EAAM,KAAK;AAAA,EAC5D,KAAK;AAAA,EAAM,KAAK;AAAA,EAAM,KAAK;AAAA,EAAM,KAAK;AAAA,EAAM,KAAK;AAAA,EAAM,KAAK;AAC9D;AAGA,SAAS,oBAAoB,MAAc,MAAsB;AAC/D,QAAM,CAAC,KAAK,GAAG,IAAI,KAAK,MAAM,GAAG;AACjC,QAAM,KAAK,UAAU,GAAG,KAAK;AAC7B,QAAM,KAAK,IAAI,SAAS,GAAG,GAAG;AAC9B,SAAO,GAAG,IAAI,IAAI,EAAE,IAAI,EAAE;AAC5B;AASO,SAAS,mBACd,YACA,WACA,eACqC;AACrC,QAAM,QAAQ,WACX,OAAO,OAAK,EAAE,UAAU,EACxB,IAAI,OAAK,oBAAoB,EAAE,YAAa,SAAS,CAAC;AAEzD,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK;AAC/B,QAAM,UAAU,OAAO,OAAO,SAAS,CAAC;AAIxC,SAAO,EAAE,MAAM,eAAe,IAAI,QAAQ;AAC5C;AAMA,eAAsB,kBACpB,QACA,SACA,QACA,cACuB;AACvB,QAAM;AAAA,IACJ,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AACJ,MAAI,EAAE,WAAW,WAAW,IAAI;AAChC,MAAI;AAEJ,MAAI;AAEJ,MAAI,aAAa,UAAU,SAAS,GAAG;AAErC,QAAI,CAAC,aAAa,CAAC,YAAY;AAC7B,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,iBAAa,UAAU,IAAI,CAAC,SAAS;AAAA,MACnC,WAAW,eAAe,IAAI,QAAQ,IAAI,QAAQ,IAAI,MAAM,IAAI,MAAM;AAAA,MACtE,UAAU,IAAI;AAAA,MACd,YAAY,IAAI;AAAA,MAChB;AAAA,IACF,EAAE;AAAA,EACJ,WAAW,aAAa,UAAa,gBAAgB,QAAW;AAE9D,UAAM,OAAO,gBAAgB,MAAM,cAAc,OAAO;AAExD,UAAM,SAAS,MAAM,KAAK;AAAA,MACxB;AAAA;AAAA,2BAEqB,SAAS,QAAQ,MAAM,IAAI,CAAC;AAAA;AAAA,wBAE/B,WAAW;AAAA,IAC/B;AAEA,UAAM,OAAO,OAAO,QAAQ;AAC5B,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,IAAI;AAAA,QACR,2BAA2B,WAAW,cAAc,QAAQ;AAAA,MAC9D;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,UAAU,OAAO,IAAI,CAAC,KAAK,EAAE;AACnC,UAAM,UAAU,OAAO,IAAI,CAAC,KAAK,CAAC;AAClC,UAAM,aAAa,OAAO,IAAI,CAAC,KAAK,EAAE;AACtC,UAAM,aAAa,OAAO,IAAI,CAAC,KAAK,EAAE;AACtC,UAAM,SAAS,OAAO,IAAI,CAAC,KAAK,EAAE;AAClC,UAAM,eAAe,OAAO,IAAI,CAAC,KAAK,CAAC;AACvC,UAAM,aAAa,OAAO,IAAI,CAAC,KAAK,EAAE;AAGtC,QAAI,cAAc,YAAY;AAE5B,YAAM,iBAAiB,WAAW,MAAM,GAAG,CAAC;AAC5C,4BAAsB,GAAG,UAAU,IAAI,cAAc;AAAA,IACvD;AAGA,gBAAY,aAAa;AACzB,iBAAa,cAAc;AAG3B,QAAI;AACJ,QAAI;AACF,mBAAa,gBAAgB,OAAO;AAAA,IACtC,QAAQ;AACN,YAAM,IAAI;AAAA,QACR,sBAAsB,OAAO;AAAA,MAC/B;AAAA,IACF;AAGA,UAAM,OAAO,UAAU,WAAW,CAAC,EAAE;AACrC,UAAM,qBACJ,eAAe,IAAI,UAAU,eAAe;AAG9C,UAAM,YAAY,WAAW,KAAK,OAAK,EAAE,eAAe,MAAS;AAGjE,UAAM,aAAa,aAAa,YAAY,MAAM,GAAG,EAAE,CAAC;AAGxD,QAAI,WAAW;AACb,YAAM,UAAU,mBAAmB,YAAY,WAAW,aAAa,UAAU;AACjF,UAAI,SAAS;AACX,oBAAY,QAAQ;AACpB,qBAAa,QAAQ;AAAA,MACvB;AAAA,IACF;AAEA,iBAAa,WAAW,IAAI,CAAC,QAAQ;AACnC,UAAI,YAAY;AAChB,UAAI,aAAa,IAAI,YAAY;AAC/B,oBAAY,oBAAoB,IAAI,YAAY,SAAS;AAAA,MAC3D;AACA,aAAO;AAAA,QACL,WAAW,eAAe,MAAM,WAAW,IAAI,MAAM,IAAI,MAAM;AAAA,QAC/D,UAAU,YACN,IAAI,YAAY,IAAI,aAAa,KACjC,IAAI,YAAY,eAAe,IAAI,eAAe;AAAA,QACtD,YAAY,YACR,IAAI,aACJ,qBAAqB,WAAW;AAAA,QACpC;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,OAAO;AACL,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAqBA,OAAK;AAEL,QAAM,eAAe,oBAAI,IAAsB;AAC/C,aAAW,OAAO,YAAY;AAC5B,UAAM,aAAa,OAAO,MAAM,QAAQ,QAAQ,YAAY,IAAI,SAAS,CAAC;AAC1E,UAAM,MAAM,aAAa,IAAI,UAAU,KAAK,CAAC;AAC7C,QAAI,KAAK,IAAI,SAAS;AACtB,iBAAa,IAAI,YAAY,GAAG;AAAA,EAClC;AAEA,QAAM,cAAc,oBAAI,IAAwB;AAChD,aAAW,CAAC,EAAE,IAAI,KAAK,cAAc;AACnC,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,MAAM,WAAW,MAAM,WAAY,UAAW;AAC1E,iBAAW,CAAC,KAAK,IAAI,KAAK,OAAQ,aAAY,IAAI,KAAK,IAAI;AAAA,IAC7D,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,YAAwB,WAAW,IAAI,CAAC,QAAQ;AACpD,UAAM,SAAS,YAAY,IAAI,IAAI,SAAS,KAAK,CAAC;AAClD,WAAO,OAAO,IAAI,CAAC,MAAM;AACvB,YAAM,CAAC,MAAM,IAAI,IAAI,EAAE,UAAU,MAAM,GAAG;AAC1C,YAAM,OAAO,EAAE,MAAM,EAAE,OAAO;AAC9B,aAAO;AAAA,QACL,QAAQ,EAAE;AAAA,QACV;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,MAAM;AAAA,QACN,KAAK;AAAA,QACL,OAAO;AAAA,QACP,KAAK,EAAE;AAAA,QACP,KAAK,EAAE;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAID,QAAMC,oBAA2C;AAAA,IAC/C,MAAM;AAAA,IAAO,MAAM;AAAA,IAAO,MAAM;AAAA,EAClC;AACA,QAAM,kBAA0C;AAAA,IAC9C,KAAK;AAAA,IAAO,MAAM;AAAA,IAAO,KAAK;AAAA,IAAO,MAAM;AAAA,EAC7C;AAGA,QAAM,iBAAiB,WAAW,CAAC,GAAG,UAAU,MAAM,WAAW;AACjE,QAAM,UAAU,iBAAiB,eAAe,CAAC,IAAI;AACrD,QAAM,mBAAmBA,kBAAiB,OAAO,KAAK;AACtD,QAAM,gBAAgB,gBAAgB,OAAO,KAAK;AAMlD,MAAI,iBAA2B,MAAM,OAAO,KAAK;AAAA,IAC/C;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,eAAe,WAAW,GAAG;AAC/B,QAAI;AACF,uBAAiB,MAAM,OAAO,KAAK;AAAA,QACjC;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAQA,MAAI,eAAe,SAAS,GAAG;AAC7B,qBAAiB,eAAe;AAAA,MAC9B,CAAC,MACC,OAAO,SAAS,EAAE,IAAI,KAAK,EAAE,OAAO,KACpC,OAAO,SAAS,EAAE,IAAI,KAAK,EAAE,OAAO,KACpC,OAAO,SAAS,EAAE,GAAG,KAAM,EAAE,MAAO,KACpC,OAAO,SAAS,EAAE,KAAK,KAAI,EAAE,QAAO;AAAA,IACxC;AAAA,EACF;AAGA,QAAM,mBAAmB,oBAAI,IAAoB;AACjD,aAAW,KAAK,gBAAgB;AAC9B,UAAM,KAAK,GAAG,EAAE,IAAI,IAAI,EAAE,QAAQ,EAAE,GAAG,KAAK;AAC5C,qBAAiB,IAAI,IAAI,UAAU,CAAC,CAAC;AAAA,EACvC;AAMA,QAAM,mBAAmB,MAAM,KAAK,iBAAiB,KAAK,CAAC,EACxD,OAAO,OAAK,EAAE,SAAS,GAAG,CAAC,EAC3B,KAAK;AAIR,MAAI;AACJ,MAAI;AACF,UAAM,cAAc,MAAM,OAAO,SAAS,KAAK;AAAA,MAC7C,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,gBAAgB;AAAA,IAClB,CAAC;AACD,UAAM,MAAM,oBAAI,IAAoB;AACpC,eAAW,OAAO,aAAa;AAC7B,YAAM,MAAM,IAAI;AAChB,UAAI,OAAO,MAAM;AACf,YAAI,IAAI,OAAO,IAAI,IAAI,GAAG,OAAO,GAAG,CAAC;AAAA,MACvC;AAAA,IACF;AACA,QAAI,IAAI,OAAO,GAAG;AAChB,kBAAY;AAAA,IACd;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,MAAI;AACJ,MAAI,iBAAiB,OAAO,GAAG;AAC7B,mBAAe;AAAA,MACb;AAAA,MACA;AAAA,MACA,MAAM,WAAW,IAAI,SAAO;AAE1B,cAAM,WAAW,IAAI,UAAU,MAAM,8BAA8B;AACnE,YAAI,CAAC,SAAU,QAAO,EAAE,QAAQ,GAAG,MAAM,KAAc,YAAY,GAAG;AACtE,cAAM,SAAS,SAAS,CAAC;AACzB,cAAM,OAAO,SAAS,CAAC;AACvB,cAAM,SAAS,SAAS,SAAS,CAAC,GAAG,EAAE,IAAI;AAC3C,cAAM,aAAa,KAAK,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC,IAAI,OAAO,MAAM,GAAG,CAAC,CAAC;AACtF,eAAO,EAAE,QAAQ,MAAM,WAAW;AAAA,MACpC,CAAC;AAAA,MACD,cAAc;AAAA,MACd;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,WAAW,uBAAuB,YAAY,WAAW,YAAY;AACzE,MAAI,EAAE,KAAK,KAAK,cAAc,aAAa,IACzC,oBAAoB,QAAQ;AAC9B,MAAI,WAAW,SAAS,SAAS,IAAI,SAAS,SAAS,SAAS,CAAC,EAAE,cAAc;AAKjF,MAAI,kBAAkB;AACtB,MAAI,mBAAmB;AACvB,aAAW,SAAS,UAAU;AAC5B,QAAI,MAAM,WAAW;AACnB,iBAAW,MAAM,MAAM,WAAW;AAChC;AACA,YAAI,GAAG,UAAU,KAAM;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AACA,QAAM,gBAAgB,mBAAmB,KAAK,kBAAkB,mBAAmB,MAC/E,0BAA0B,eAAe,OAAO,gBAAgB,sHAChE;AAKJ,MAAI,aAAa,WAAW,uBAAuB,SAAS,SAAS,GAAG;AACtE,UAAM,WAAW,SAAS,UAAU,OAAK,EAAE,YAAY,mBAAoB;AAC3E,QAAI,WAAW,GAAG;AAChB,iBAAW,SAAS,MAAM,GAAG,QAAQ;AAErC,YAAM;AACN,YAAM;AACN,iBAAW,KAAK,UAAU;AACxB,YAAI,EAAE,cAAc,KAAK;AAAE,gBAAM,EAAE;AAAa,yBAAe,EAAE;AAAA,QAAW;AAC5E,YAAI,EAAE,cAAc,KAAK;AAAE,gBAAM,EAAE;AAAa,yBAAe,EAAE;AAAA,QAAW;AAAA,MAC9E;AACA,iBAAW,SAAS,SAAS,SAAS,CAAC,EAAE;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI;AACJ,MAAI,WAAW,WAAW;AAExB,UAAM,gBAAgB,oBAAI,IAAI;AAAA,MAC5B,SAAS,CAAC,GAAG;AAAA,MACb,SAAS,SAAS,SAAS,CAAC,GAAG;AAAA,MAC/B;AAAA,MACA;AAAA,IACF,CAAC;AACD,cAAU,SAAS,OAAO,OAAK,cAAc,IAAI,EAAE,SAAS,CAAC;AAAA,EAC/D,WAAW,WAAW,WAAW;AAE/B,UAAM,gBAAgB,oBAAI,IAAI,CAAC,cAAc,YAAY,CAAC;AAC1D,cAAU,SAAS;AAAA,MAAO,CAAC,GAAG,MAC5B,MAAM,KAAK,MAAM,SAAS,SAAS,KAAK,IAAI,OAAO,KAAK,cAAc,IAAI,EAAE,SAAS;AAAA,IACvF;AAAA,EACF,OAAO;AACL,cAAU;AAAA,EACZ;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,SAAS;AAAA,IACpB,MAAM;AAAA,IACN;AAAA,EACF;AACF;;;AE3fA,SAAS,KAAAC,UAAS;AAQX,IAAM,0BAA0BC,GAAE,OAAO;AAAA,EAC9C,YAAYA,GACT,OAAO,EACP,SAAS,uDAAuD;AAAA,EACnE,kBAAkBA,GACf,OAAO,EACP,SAAS,EACT,SAAS,6BAA6B;AAAA,EACzC,kBAAkBA,GACf,OAAO,EACP,SAAS,EACT,SAAS,6BAA6B;AAAA,EACzC,qBAAqBA,GAClB,OAAO,EACP,SAAS,EACT,SAAS,uCAAuC;AAAA,EACnD,qBAAqBA,GAClB,OAAO,EACP,SAAS,EACT,SAAS,qCAAqC;AAAA,EACjD,eAAeA,GACZ,KAAK,CAAC,QAAQ,KAAK,CAAC,EACpB,SAAS,EACT,SAAS,uBAAuB;AAAA,EACnC,OAAOA,GACJ,OAAO,EACP,SAAS,EACT,QAAQ,EAAE,EACV;AAAA,IACC;AAAA,EACF;AACJ,CAAC;AAMD,eAAsB,wBACpB,QACiB;AACjB,MAAI;AACF,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI;AAEJ,UAAM,SAAS,MAAM,YAAY,EAAE,oBAAoB;AAAA,MACrD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAID,UAAM,iBAAiB,OAAO,UAAU;AACxC,UAAM,YACJ,SAAS,QAAQ,iBAAiB,QAC9B,OAAO,UAAU,MAAM,GAAG,KAAK,IAC/B,OAAO;AAEb,WAAO,KAAK,UAAU;AAAA,MACpB,mBAAmB,OAAO;AAAA,MAC1B,kBAAkB,OAAO;AAAA,MACzB,oBAAoB,UAAU;AAAA,MAC9B,iBAAiB;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,KAAK,UAAU;AAAA,MACpB,OAAQ,MAAgB;AAAA,IAC1B,CAAC;AAAA,EACH;AACF;;;ACnBA,IAAM,0BAA0B;AASzB,SAAS,qBAAqB,KAAa,KAAqB;AACrE,QAAM,CAAC,OAAO,KAAK,IAAI,IAAI,MAAM,GAAG;AACpC,QAAM,CAAC,OAAO,KAAK,IAAI,IAAI,MAAM,GAAG;AAEpC,MAAI,UAAU,OAAO;AAEnB,UAAM,CAACC,KAAIC,GAAE,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI,MAAM;AAC5C,UAAM,CAACC,KAAIC,GAAE,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI,MAAM;AAC5C,UAAM,QAAQH,MAAK,KAAKC;AACxB,UAAM,QAAQC,MAAK,KAAKC;AACxB,UAAM,WAAW,KAAK,IAAI,QAAQ,KAAK;AACvC,WAAO,WAAW;AAAA,EACpB;AAGA,QAAM,KAAK,oBAAI,KAAK,QAAQ,WAAW;AACvC,QAAM,KAAK,oBAAI,KAAK,QAAQ,WAAW;AACvC,QAAM,SAAS,KAAK,IAAI,GAAG,QAAQ,IAAI,GAAG,QAAQ,CAAC;AACnD,QAAM,WAAW,KAAK,MAAM,UAAU,KAAK,KAAK,KAAK,IAAK;AAG1D,QAAM,CAAC,IAAI,EAAE,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI,MAAM;AAC5C,QAAM,CAAC,IAAI,EAAE,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI,MAAM;AAE5C,QAAM,eAAgB,KAAK,KAAK,MAAO,IAAI,KAAK;AAChD,QAAM,WAAW,KAAK,IAAI,GAAG,YAAY,IAAI;AAE7C,QAAM,eAAgB,KAAK,KAAK,MAAO,IAAI,KAAK;AAChD,QAAM,oBAAoB,KAAK,IAAI,GAAG,IAAI,eAAe,uBAAuB;AAGhF,MAAI,YAAY,GAAG;AACjB,WAAO,oBAAoB;AAAA,EAC7B;AACA,SAAO,qBAAqB,WAAW,KAAK;AAC9C;AAaA,SAAS,uBACP,QACA,gBACA,WAC2B;AAC3B,QAAM,EAAE,SAAS,iBAAiB,IAAI;AAEtC,QAAM,gBAA0B,CAAC;AACjC,QAAM,gBAA0B,CAAC;AACjC,QAAM,mBAA6B,CAAC;AAEpC,MAAI,oBAAmC;AAEvC,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,MAAM,QAAQ,CAAC;AACrB,UAAM,OAAO,QAAQ,IAAI,CAAC;AAC1B,UAAM,eAAe,KAAK,cAAc,IAAI;AAG5C,QAAI,mBAAmB;AACvB,QAAI,kBAAkB;AACpB,YAAM,KAAK,iBAAiB,IAAI,IAAI,SAAS;AAC7C,YAAM,KAAK,iBAAiB,IAAI,KAAK,SAAS;AAC9C,UAAI,OAAO,UAAa,OAAO,QAAW;AACxC,2BAAmB,KAAK;AAAA,MAC1B;AAAA,IACF;AAGA,QAAI,KAAK,IAAI,gBAAgB,IAAI,MAAM;AACrC,oBAAc,KAAK,CAAC;AACpB,oBAAc,KAAK,CAAC;AACpB,uBAAiB,KAAK,YAAY;AAElC;AAAA,IACF;AAGA,UAAM,gBAAgB,eAAe;AAGrC,QAAI,WAAW;AACf,QAAI,sBAAsB,MAAM;AAC9B,YAAM,cAAc,gBAAgB;AACpC,iBAAW,MAAM,cAAc;AAAA,IACjC;AAEA,UAAM,eAAe,gBAAgB,mBAAmB;AACxD,UAAM,WAAW,eAAe,eAAe;AAE/C,kBAAc,KAAK,YAAY;AAC/B,kBAAc,KAAK,QAAQ;AAC3B,qBAAiB,KAAK,QAAQ;AAE9B,wBAAoB;AAAA,EACtB;AAEA,QAAM,WAAW,CAAC,MAAgB,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAC7D,QAAM,aAAa,SAAS,aAAa;AACzC,QAAM,aAAa,SAAS,aAAa;AACzC,QAAM,kBAAkB,SAAS,gBAAgB;AAEjD,QAAM,aAAa;AAAA,IACjB,EAAE,QAAQ,SAAuB,UAAU,YAAY,OAAO,cAAc;AAAA,IAC5E,EAAE,QAAQ,SAAuB,UAAU,YAAY,OAAO,cAAc;AAAA,IAC5E,EAAE,QAAQ,gBAA8B,UAAU,iBAAiB,OAAO,iBAAiB;AAAA,EAC7F;AAEA,aAAW,KAAK,CAAC,GAAG,MAAM,KAAK,IAAI,EAAE,QAAQ,IAAI,KAAK,IAAI,EAAE,QAAQ,CAAC;AACrE,QAAM,cAAc,WAAW,OAAO,CAAC,GAAG,MAAM,IAAI,KAAK,IAAI,EAAE,QAAQ,GAAG,CAAC;AAC3E,QAAM,UAAgC,WAAW,IAAI,QAAM;AAAA,IACzD,GAAG;AAAA,IACH,YAAY,cAAc,IAAK,KAAK,IAAI,EAAE,QAAQ,IAAI,cAAe,MAAM;AAAA,EAC7E,EAAE;AAEF,QAAM,eAAe,QAAQ,IAAI,OAAK,GAAG,EAAE,MAAM,IAAI,EAAE,SAAS,QAAQ,CAAC,CAAC,KAAK,EAAE,WAAW,QAAQ,CAAC,CAAC,IAAI;AAC1G,QAAM,UAAU,UAAU,eAAe,QAAQ,CAAC,CAAC,iBAAiB,aAAa,KAAK,IAAI,CAAC;AAE3F,SAAO;AAAA,IACL;AAAA,IACA,cAAc;AAAA;AAAA,IACd;AAAA,IACA,iBAAiB,aAAa;AAAA,IAC9B,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AACF;AASA,SAAS,WAAW,WAAmB,YAA4B;AACjE,QAAM,UAAU,UAAU,MAAM,GAAG,EAAE,CAAC;AACtC,QAAM,WAAW,UAAU,MAAM,GAAG,EAAE,CAAC,KAAK;AAC5C,QAAM,CAAC,KAAK,KAAK,GAAG,IAAI,WAAW,MAAM,GAAG,EAAE,IAAI,MAAM;AACxD,QAAM,CAAC,KAAK,KAAK,GAAG,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AACrD,QAAM,CAAC,IAAI,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI,MAAM;AAEhD,QAAM,WAAW,IAAI,KAAK,KAAK,MAAM,GAAG,GAAG,EAAE,QAAQ,IAAI,KAAK,KAAK,KAAK;AACxE,QAAM,QAAQ,IAAI,KAAK,KAAK,MAAM,GAAG,GAAG,EAAE,QAAQ,KAAK,KAAK,KAAK,OAAO,KAAK;AAC7E,UAAQ,WAAW,UAAU,MAAO,KAAK,KAAK;AAChD;AAMA,SAAS,YACP,MACA,GACA,GACA,KACA,GACA,GACA,IACe;AACf,MAAI,OAAO,KAAK,MAAM,EAAG,QAAO;AAChC,QAAM,IAAI,MAAM;AAChB,QAAM,SAAS,SAAS,MAAM,SAAkB;AAChD,MAAI,MAAM,yBAAyB;AACjC,WAAO,eAAe,QAAQ,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAAA,EACjD;AACA,SAAO,QAAQ,QAAQ,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAC1C;AAaO,SAAS,gBAAgB,QAA8D;AAC5F,QAAM,EAAE,SAAS,MAAM,kBAAkB,WAAW,kBAAkB,cAAc,cAAc,IAAI;AAGtG,MAAI,QAAQ,UAAU,GAAG;AACvB,UAAM,eAAqC;AAAA,MACzC,EAAE,QAAQ,SAAS,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC,EAAE;AAAA,MACzD,EAAE,QAAQ,SAAS,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC,EAAE;AAAA,MACzD,EAAE,QAAQ,SAAS,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC,EAAE;AAAA,MACzD,EAAE,QAAQ,QAAQ,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC,EAAE;AAAA,MACxD,EAAE,QAAQ,YAAY,UAAU,GAAG,YAAY,GAAG,OAAO,CAAC,EAAE;AAAA,IAC9D;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,cAAc,YAAY,UAAU,IAAI,QAAM;AAAA,QAC5C,OAAO,EAAE;AAAA,QACT,YAAY,EAAE;AAAA,QACd,cAAc;AAAA,QACd,aAAa;AAAA,QACb,OAAO,CAAC;AAAA,MACV,EAAE,IAAI;AAAA,MACN,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,eAAe;AAAA,MACf,WAAW;AAAA,MACX,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ,SAAS;AACnC,QAAM,eAAe,oBAAoB,iBAAiB,WAAW,KAAK,UACrE,iBAAiB,UAAa,kBAAkB,UAAa;AAClE,QAAM,IAAI,gBAAgB;AAC1B,QAAM,IAAI,iBAAiB;AAG3B,QAAM,aAAuB,CAAC;AAC9B,QAAM,aAAuB,CAAC;AAC9B,QAAM,YAAsB,CAAC;AAC7B,QAAM,aAAuB,CAAC;AAC9B,QAAM,aAAuB,CAAC;AAC9B,QAAM,gBAA0B,CAAC;AAGjC,QAAM,aAAqC,YACvC,UAAU,IAAI,MAAM,CAAC,CAAa,IAClC;AAEJ,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,MAAM,QAAQ,CAAC;AACrB,UAAM,OAAO,QAAQ,IAAI,CAAC;AAE1B,QAAI,YAAY;AAChB,QAAI,YAAY;AAChB,QAAI,WAAW;AACf,QAAI,YAAY;AAChB,QAAI,YAAY;AAChB,QAAI,eAAe;AAEnB,UAAM,iBAAuC,YAAY,UAAU,IAAI,MAAM,CAAC,IAAI;AAElF,UAAM,WAAW,KAAK,IAAI,KAAK,QAAQ,IAAI,WAAW,UAAU,GAAG,KAAK,WAAW,UAAU,CAAC;AAG9F,UAAM,KAAK,IAAI,mBAAmB,kBAAkB,IAAI,IAAI,SAAS;AACrE,UAAM,KAAK,KAAK,mBAAmB,kBAAkB,IAAI,KAAK,SAAS;AAEvE,aAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,YAAM,eAAe,KAAK,CAAC,EAAE,WAAW,KAAK,CAAC,EAAE;AAChD,YAAM,oBAAoB,KAAK,YAAY,CAAC,KAAK,MAAM,IAAI,YAAY,CAAC,KAAK,MAAM;AAEnF,YAAM,QAAQ,IAAI,YAAY,CAAC,GAAG;AAClC,YAAM,SAAS,KAAK,YAAY,CAAC,GAAG;AACpC,YAAM,MAAM,mBAAmB,CAAC;AAGhC,UAAI,gBAAgB,OAAO,OAAO,UAAa,OAAO,UAC/C,UAAU,QAAQ,UAAU,UAAa,QAAQ,KACjD,WAAW,QAAQ,WAAW,UAAa,SAAS,GAAG;AAE5D,cAAM,OAAO,WAAW,IAAI,WAAW,IAAI,UAAU;AACrD,cAAM,OAAO,WAAW,KAAK,WAAW,IAAI,UAAU;AAEtD,YAAI,OAAO,KAAK,OAAO,GAAG;AAExB,gBAAM,YAAY,YAAY,IAAI,MAAM,IAAI,IAAI,QAAQ,MAAM,GAAG,GAAG,KAAK;AAGzE,gBAAM,aAAa,YAAY,IAAI,MAAM,IAAI,IAAI,QAAQ,MAAM,GAAG,GAAG,KAAK;AAG1E,gBAAM,aAAa,YAAY,IAAI,MAAM,IAAI,IAAI,QAAQ,MAAM,GAAG,GAAG,KAAK;AAG1E,gBAAM,YAAY,YAAY,IAAI,MAAM,IAAI,IAAI,QAAQ,MAAM,GAAG,GAAG,MAAM;AAI1E,gBAAM,aAAa,YAAY,IAAI,MAAM,IAAI,IAAI,QAAQ,MAAM,GAAG,GAAG,KAAK;AAE1E,gBAAM,aAAa,YAAY,IAAI,MAAM,IAAI,IAAI,QAAQ,MAAM,GAAG,GAAG,MAAM;AAE3E,cAAI,cAAc,QAAQ,eAAe,QAAQ,eAAe,QACzD,cAAc,QAAQ,eAAe,QAAQ,eAAe,MAAM;AACvE,kBAAM,eAAe,aAAa,aAAa;AAC/C,kBAAM,eAAe,aAAa,aAAa;AAC/C,kBAAM,cAAc,YAAY,aAAa;AAC7C,kBAAM,eAAgB,aAAa,aAAc,aAAa,cAAc,aAAa,cAAc;AACvG,kBAAM,eAAgB,aAAa,aAAc,aAAa,cAAc,YAAY,cAAc;AACtG,kBAAM,cAAc,kBAAkB,cAAc,cAAc,aAAa,cAAc;AAE7F,yBAAa;AACb,yBAAa;AACb,wBAAY;AACZ,yBAAa;AACb,yBAAa;AACb,4BAAgB;AAGhB,gBAAI,aAAa,gBAAgB;AAC/B,uBAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,oBAAI,UAAU,CAAC,EAAE,WAAW,SAAS,CAAC,GAAG;AACvC,iCAAe,CAAC,KAAK;AAAA,gBACvB;AAAA,cACF;AAAA,YACF;AACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,sBAAgB;AAAA,IAClB;AAEA,eAAW,KAAK,SAAS;AACzB,eAAW,KAAK,SAAS;AACzB,cAAU,KAAK,QAAQ;AACvB,eAAW,KAAK,SAAS;AACzB,eAAW,KAAK,SAAS;AACzB,kBAAc,KAAK,YAAY;AAE/B,QAAI,cAAc,gBAAgB;AAChC,eAAS,IAAI,GAAG,IAAI,UAAW,QAAQ,KAAK;AAC1C,mBAAW,CAAC,EAAE,KAAK,eAAe,CAAC,CAAC;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW,CAAC,UAA4B,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAS7E,QAAM,aAA+E;AAAA,IACnF,EAAE,QAAQ,SAAS,UAAU,SAAS,UAAU,GAAG,OAAO,WAAW;AAAA,IACrE,EAAE,QAAQ,SAAS,UAAU,SAAS,UAAU,GAAG,OAAO,WAAW;AAAA,IACrE,EAAE,QAAQ,QAAQ,UAAU,SAAS,SAAS,GAAG,OAAO,UAAU;AAAA,IAClE,EAAE,QAAQ,SAAS,UAAU,SAAS,UAAU,GAAG,OAAO,WAAW;AAAA,IACrE,EAAE,QAAQ,SAAS,UAAU,SAAS,UAAU,GAAG,OAAO,WAAW;AAAA,IACrE,EAAE,QAAQ,YAAY,UAAU,SAAS,aAAa,GAAG,OAAO,cAAc;AAAA,EAChF;AAEA,aAAW,KAAK,CAAC,GAAG,MAAM,KAAK,IAAI,EAAE,QAAQ,IAAI,KAAK,IAAI,EAAE,QAAQ,CAAC;AACrE,QAAM,cAAc,WAAW,OAAO,CAAC,GAAG,MAAM,IAAI,KAAK,IAAI,EAAE,QAAQ,GAAG,CAAC;AAC3E,QAAM,UAAgC,WAAW,IAAI,QAAM;AAAA,IACzD,GAAG;AAAA,IACH,YAAY,cAAc,IAAK,KAAK,IAAI,EAAE,QAAQ,IAAI,cAAe,MAAM;AAAA,EAC7E,EAAE;AAEF,QAAM,iBAAiB,QAAQ,QAAQ,SAAS,CAAC,EAAE,cAAc,QAAQ,CAAC,EAAE;AAC5E,QAAM,gBAAgB,SAAS,aAAa;AAC5C,QAAM,kBAAkB,SAAS,UAAU,IAAI,SAAS,UAAU,IAAI,SAAS,SAAS,IAAI,SAAS,UAAU,IAAI,SAAS,UAAU;AAEtI,QAAM,cAAc,KAAK,IAAI,cAAc,IAAI,OAC3C,KAAK,IAAI,aAAa,IAAI,KAAK,IAAI,cAAc,IACjD;AAIJ,MAAI,cAAc,OAAO,QAAQ,SAAS,GAAG;AAC3C,WAAO,uBAAuB,QAAQ,gBAAgB,SAAS;AAAA,EACjE;AAGA,MAAI;AACJ,MAAI,aAAa,YAAY;AAC3B,mBAAe,UAAU,IAAI,CAAC,OAAO,MAAM;AACzC,YAAM,QAAQ,WAAW,CAAC;AAC1B,YAAM,eAAe,SAAS,KAAK;AAEnC,UAAI,gBAAgB;AACpB,UAAI,cAAc;AAClB,eAAS,KAAK,GAAG,KAAK,WAAW,MAAM;AACrC,cAAM,MAAM,QAAQ,EAAE;AACtB,cAAM,MAAM,QAAQ,KAAK,CAAC;AAC1B,YAAI,CAAC,IAAI,aAAa,CAAC,IAAI,UAAW;AACtC,mBAAW,KAAK,MAAM,YAAY;AAChC,gBAAM,MAAM,IAAI,UAAU,CAAC,GAAG;AAC9B,gBAAM,MAAM,IAAI,UAAU,CAAC,GAAG;AAC9B,cAAI,QAAQ,QAAQ,QAAQ,UAAa,QAAQ,QAAQ,QAAQ,QAAW;AAC1E,8BAAkB,MAAM,OAAO;AAC/B;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,QACL,OAAO,MAAM;AAAA,QACb,YAAY,MAAM;AAAA,QAClB;AAAA,QACA,aAAa,cAAc,IAAI,gBAAgB,cAAc;AAAA,QAC7D;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAGA,QAAM,cAAc,eAAe,eAAe;AAClD,QAAM,eAAe,QAClB,OAAO,OAAK,EAAE,WAAW,UAAU,EACnC,IAAI,OAAK,GAAG,EAAE,MAAM,IAAI,EAAE,SAAS,QAAQ,CAAC,CAAC,KAAK,EAAE,WAAW,QAAQ,CAAC,CAAC,IAAI;AAChF,QAAM,iBAAiB,QAAQ,KAAK,OAAK,EAAE,WAAW,UAAU;AAChE,MAAI,kBAAkB,KAAK,IAAI,eAAe,QAAQ,IAAI,MAAM;AAC9D,iBAAa,KAAK,YAAY,eAAe,SAAS,QAAQ,CAAC,CAAC,KAAK,eAAe,WAAW,QAAQ,CAAC,CAAC,IAAI;AAAA,EAC/G;AACA,QAAM,UAAU,UAAU,eAAe,QAAQ,CAAC,CAAC,KAAK,WAAW,MAAM,aAAa,KAAK,IAAI,CAAC;AAEhG,QAAM,UAAU,cAAc,MAC1B,aAAa,cAAc,KAAK,QAAQ,CAAC,CAAC,gDAC1C;AAEJ,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,eAAe,eAAe;AAAA,EACxC;AACF;;;ACxaA,SAAS,UAAU,SAAuB;AACxC,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AAC/C,SAAO,IAAI,KAAK,GAAG,IAAI,GAAG,CAAC;AAC7B;AAGA,SAAS,YAAY,WAA2B;AAC9C,SAAO,UAAU,MAAM,GAAG,EAAE;AAC9B;AAGA,SAAS,YAAY,WAA2B;AAC9C,SAAO,UAAU,MAAM,IAAI,EAAE;AAC/B;AAGA,SAAS,oBAAoB,GAAS,GAAiB;AACrD,QAAM,aAAa;AACnB,SAAO,KAAK,IAAI,KAAK,OAAO,EAAE,QAAQ,IAAI,EAAE,QAAQ,KAAK,UAAU,CAAC;AACtE;AAGA,SAAS,eACP,OACA,MACA,aACA,WACA,YACQ;AAER,MAAI,cAAc;AAClB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,KAAK,CAAC,EAAE,WAAW,GAAG;AACxB,YAAMC,aAAY,MAAM,UAAU,CAAC,KAAK;AACxC,qBAAe,KAAK,IAAIA,aAAY,KAAK,CAAC,EAAE,WAAW,KAAK,CAAC,EAAE,UAAU;AAAA,IAC3E;AAAA,EACF;AACA,QAAM,UAAU,cAAc,YAAY;AAC1C,MAAI,YAAY,EAAG,QAAO;AAC1B,SAAO,cAAc;AACvB;AAEA,SAAS,mBAAmB,SAAiB,SAAyB;AACpE,MAAI,YAAY,EAAG,QAAO,YAAY,IAAI,IAAI,OAAO;AACrD,UAAQ,UAAU,WAAW;AAC/B;AAEA,SAAS,uBAAuB,SAAiB,WAA4B;AAC3E,MAAI,YAAY,EAAG,QAAO,WAAW;AACrC,MAAI,YAAY,EAAG,QAAO,WAAW;AACrC,SAAO,YAAY;AACrB;AAEA,SAAS,0BAA0B,UAAkB,KAAyB;AAC5E,SAAO,OAAO,QAAQ,IAAI,WAAW,IAAI,CAAC,WAAW;AACvD;AAaO,SAAS,qBACd,SACA,SACA,OACuE;AACvE,QAAM,gBAAgC,CAAC;AAEvC,MAAI,QAAQ,WAAW,KAAK,CAAC,QAAQ,OAAO,QAAQ;AAClD,WAAO,EAAE,WAAW,MAAM,cAAc;AAAA,EAC1C;AACA,MAAI,QAAQ,SAAS,aAAa,QAAQ,aAAa,MAAM;AAC3D,WAAO,EAAE,WAAW,MAAM,cAAc;AAAA,EAC1C;AAEA,QAAM,QAAQ,QAAQ,SAAS,YAC3B,KAAK,IAAI,QAAQ,SAAU,IAC3B;AAEJ,QAAM,kBAAkB,CAAC,GAAG,QAAQ,KAAK,EACtC,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,IAAI,CAAC,UAAU;AAAA,IACd,OAAO,KAAK,QAAQ;AAAA,IACpB,QAAQ,KAAK,SAAS;AAAA,IACtB,oBAAoB,KAAK;AAAA,EAC3B,EAAE;AAEJ,MAAI,sBAAsB;AAC1B,MAAI,gBAAgB;AAEpB,QAAM,mBAAmB,IAAI,MAAM,gBAAgB,MAAM,EAAE,KAAK,KAAK;AAErE,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,QAAQ,QAAQ,CAAC;AACvB,UAAM,MAAM,MAAM;AAElB,QAAI,MAAM,cAAe,iBAAgB;AAGzC,aAAS,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;AAC/C,YAAM,OAAO,gBAAgB,CAAC;AAC9B,UAAI,CAAC,iBAAiB,CAAC,KAAK,KAAK,sBAAsB,iBAAiB,KAAK,OAAO;AAClF,yBAAiB,CAAC,IAAI;AACtB,cAAM,WAAW,sBAAsB,KAAK;AAC5C,sBAAc,KAAK;AAAA,UACjB,OAAO;AAAA,UACP,WAAW,MAAM,sBAAsB,KAAK;AAAA,UAC5C,YAAY;AAAA,UACZ,SAAS;AAAA,QACX,CAAC;AACD,+BAAuB;AAAA,MACzB;AAAA,IACF;AAGA,QAAI,cAAc;AAClB,eAAW,QAAQ,iBAAiB;AAClC,UAAI,iBAAiB,KAAK,OAAO;AAC/B,sBAAc,KAAK,IAAI,aAAa,KAAK,MAAM;AAAA,MACjD;AAAA,IACF;AAKA,QAAI,cAAc,aAAa,sBAAsB,KAAK,OAAO,aAAa;AAC5E,YAAM,eAAe,MAAM;AAC3B,YAAM,SAAS,QAAQ,SAAS,YAC5B,oCAAoC,cAAc,QAAQ,KAAK,QAAQ,CAAC,CAAC,OAAO,YAAY,QAAQ,CAAC,CAAC,iBAAiB,cAAc,QAAQ,CAAC,CAAC,aAAa,IAAI,QAAQ,CAAC,CAAC,gBAAgB,sBAAsB,KAAK,QAAQ,CAAC,CAAC,OAC/N,oCAAoC,YAAY,QAAQ,CAAC,CAAC,gBAAgB,cAAc,QAAQ,CAAC,CAAC,aAAa,IAAI,QAAQ,CAAC,CAAC,gBAAgB,sBAAsB,KAAK,QAAQ,CAAC,CAAC;AAEtL,aAAO;AAAA,QACL,WAAW;AAAA,UACT,MAAM;AAAA,UACN,SAAS,MAAM;AAAA,UACf,WAAW;AAAA,UACX,OAAO;AAAA,UACP;AAAA,QACF;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,WAAW,MAAM,cAAc;AAC1C;AAUO,SAAS,gBACd,SACA,SACA,MACyB;AACzB,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,QAAM,EAAE,MAAM,UAAU,IAAI;AAG5B,MAAI,gBAAgB;AACpB,MAAI,mBAAmB;AACvB,MAAI,iBAAgC;AACpC,MAAI,uBAAsC;AAC1C,MAAI,gBAA+B;AACnC,MAAI,kBAAiC;AAErC,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,QAAQ,QAAQ,CAAC;AACvB,UAAM,MAAM,MAAM;AAGlB,QAAI,MAAM,cAAe,iBAAgB;AAEzC,QAAI,QAAQ;AACZ,QAAI;AAEJ,YAAQ,MAAM;AAAA,MACZ,KAAK,gBAAgB;AAEnB,YAAI,QAAQ,SAAS,aAAa,QAAQ,aAAa,KAAM;AAC7D,cAAM,eAAe,QAAQ,gBAAgB;AAC7C,cAAM,oBAAoB,QAAQ,SAAS,YACvC,YAAY,KAAK,IAAI,QAAQ,SAAU,IACvC;AACJ,YAAI,OAAO,mBAAmB;AAC5B,cAAI,MAAM,gBAAgB,MAAO;AACjC,cAAI,mBAAmB,aAAc;AACrC,kBAAQ;AACR,mBAAS,QAAQ,SAAS,YACtB,QAAQ,IAAI,QAAQ,CAAC,CAAC,QAAQ,YAAY,KAAK,QAAQ,CAAC,CAAC,SAAS,KAAK,IAAI,QAAQ,SAAU,EAAE,QAAQ,CAAC,CAAC,MAAM,kBAAkB,QAAQ,CAAC,CAAC,MAC3I,QAAQ,IAAI,QAAQ,CAAC,CAAC,eAAe,kBAAkB,QAAQ,CAAC,CAAC;AAAA,QACvE,WAAW,MAAM,gBAAgB,OAAO;AACtC,6BAAmB;AAAA,QACrB;AACA;AAAA,MACF;AAAA,MAEA,KAAK,YAAY;AAEf,cAAM,eAAe,KAAK,IAAI,SAAS;AAEvC,YAAI,QAAQ,SAAS,aAAa,QAAQ,aAAa,KAAM;AAC7D,cAAM,oBAAoB,QAAQ,SAAS,YACvC,eAAe,KAAK,IAAI,QAAQ,SAAU,IAC1C;AACJ,YAAI,OAAO,CAAC,mBAAmB;AAC7B,kBAAQ;AACR,mBAAS,QAAQ,SAAS,YACtB,QAAQ,IAAI,QAAQ,CAAC,CAAC,SAAS,eAAe,KAAK,QAAQ,CAAC,CAAC,SAAS,KAAK,IAAI,QAAQ,SAAU,EAAE,QAAQ,CAAC,CAAC,OAAO,kBAAkB,QAAQ,CAAC,CAAC,MAChJ,QAAQ,IAAI,QAAQ,CAAC,CAAC,cAAc,kBAAkB,QAAQ,CAAC,CAAC;AAAA,QACtE;AACA;AAAA,MACF;AAAA,MAEA,KAAK,gBAAgB;AACnB,cAAM,WAAW,QAAQ,eAAe;AACxC,cAAM,WAAW,gBAAgB;AACjC,YAAI,YAAY,YAAY,gBAAgB,WAAW;AACrD,kBAAQ;AACR,mBAAS,aAAa,SAAS,QAAQ,CAAC,CAAC,cAAc,cAAc,QAAQ,CAAC,CAAC,cAAc,SAAS,QAAQ,CAAC,CAAC;AAAA,QAClH;AACA;AAAA,MACF;AAAA,MAEA,KAAK,gBAAgB;AAKnB,cAAM,WAAW,qBAAqB,SAAS,SAAS,IAAI;AAC5D,eAAO,SAAS;AAAA,MAClB;AAAA,MAEA,KAAK,WAAW;AACd,YAAI,CAAC,QAAQ,OAAQ;AACrB,cAAM,YAAY,UAAU,YAAY,MAAM,SAAS,CAAC;AACxD,cAAM,aAAa,UAAU,QAAQ,MAAM;AAC3C,cAAM,MAAM,oBAAoB,WAAW,UAAU;AAErD,YAAI,aAAa,cAAc,OAAO,WAAW;AAC/C,kBAAQ;AACR,mBAAS,OAAO,GAAG,iBAAiB,SAAS;AAAA,QAC/C;AACA;AAAA,MACF;AAAA,MAEA,KAAK,WAAW;AACd,YAAI,CAAC,QAAQ,SAAU;AACvB,cAAM,YAAY,UAAU,YAAY,MAAM,SAAS,CAAC;AACxD,cAAM,WAAW,UAAU,QAAQ,QAAQ;AAC3C,cAAM,MAAM,oBAAoB,UAAU,SAAS;AACnD,YAAI,OAAO,WAAW;AACpB,kBAAQ;AACR,mBAAS,OAAO,GAAG,iBAAiB,SAAS;AAAA,QAC/C;AACA;AAAA,MACF;AAAA,MAEA,KAAK,iBAAiB;AACpB,cAAM,YAAY,QAAQ,aAAa;AACvC,cAAM,YAAY,YAAY,MAAM,SAAS;AAC7C,YAAI,aAAa,WAAW;AAC1B,kBAAQ;AACR,mBAAS,QAAQ,SAAS,OAAO,SAAS;AAAA,QAC5C;AACA;AAAA,MACF;AAAA,MAEA,KAAK,uBAAuB;AAC1B,YAAI,CAAC,QAAQ,iBAAkB;AAC/B,cAAM,QAAQ,QAAQ,iBAAiB,IAAI,MAAM,SAAS;AAC1D,YAAI,SAAS,KAAM;AACnB,YAAI,yBAAyB,MAAM;AACjC,iCAAuB;AACvB;AAAA,QACF;AACA,cAAM,WAAY,QAAQ,wBAAwB,uBAAwB;AAC1E,YAAI,KAAK,IAAI,OAAO,KAAK,WAAW;AAClC,kBAAQ;AACR,mBAAS,oBAAoB,QAAQ,QAAQ,CAAC,CAAC,gBAAgB,SAAS;AAAA,QAC1E;AACA;AAAA,MACF;AAAA,MAEA,KAAK,iBAAiB;AACpB,cAAM,WAAW,MAAM,YAAY;AACnC,YAAI,QAAQ,aAAa,MAAM;AAC7B,cAAI,WAAW,QAAQ,WAAW;AAChC,oBAAQ;AACR,qBAAS,aAAa,SAAS,QAAQ,CAAC,CAAC,gBAAgB,QAAQ,SAAS;AAAA,UAC5E;AAAA,QACF,WAAW,QAAQ,aAAa,MAAM;AACpC,cAAI,WAAW,QAAQ,WAAW;AAChC,oBAAQ;AACR,qBAAS,aAAa,SAAS,QAAQ,CAAC,CAAC,gBAAgB,QAAQ,SAAS;AAAA,UAC5E;AAAA,QACF,WAAW,KAAK,IAAI,QAAQ,KAAK,WAAW;AAC1C,kBAAQ;AACR,mBAAS,aAAa,SAAS,QAAQ,CAAC,CAAC,iBAAiB,SAAS;AAAA,QACrE;AACA;AAAA,MACF;AAAA,MAEA,KAAK,eAAe;AAClB,YAAI,CAAC,MAAM,UAAW;AACtB,YAAI,QAAQ,YAAY,MAAM;AAE5B,cAAI,QAAQ,YAAY,MAAM,UAAU,OAAQ;AAChD,gBAAM,WAAW,MAAM,UAAU,QAAQ,QAAQ,EAAE,SAAS;AAC5D,gBAAM,WAAW,0BAA0B,UAAU,KAAK,QAAQ,QAAQ,CAAC;AAC3E,cAAI,QAAQ,aAAa,MAAM;AAC7B,gBAAI,WAAW,QAAQ,WAAW;AAChC,sBAAQ;AACR,uBAAS,OAAO,QAAQ,QAAQ,UAAU,SAAS,QAAQ,CAAC,CAAC,gBAAgB,QAAQ,SAAS;AAAA,YAChG;AAAA,UACF,WAAW,QAAQ,aAAa,MAAM;AACpC,gBAAI,WAAW,QAAQ,WAAW;AAChC,sBAAQ;AACR,uBAAS,OAAO,QAAQ,QAAQ,UAAU,SAAS,QAAQ,CAAC,CAAC,gBAAgB,QAAQ,SAAS;AAAA,YAChG;AAAA,UACF,OAAO;AAEL,gBAAI,KAAK,IAAI,QAAQ,KAAK,WAAW;AACnC,sBAAQ;AACR,uBAAS,OAAO,QAAQ,QAAQ,UAAU,SAAS,QAAQ,CAAC,CAAC,iBAAiB,SAAS;AAAA,YACzF;AAAA,UACF;AAAA,QACF,OAAO;AAEL,mBAAS,KAAK,GAAG,KAAK,MAAM,UAAU,QAAQ,MAAM;AAClD,kBAAM,WAAW,MAAM,UAAU,EAAE,EAAE,SAAS;AAC9C,kBAAM,WAAW,0BAA0B,UAAU,KAAK,EAAE,CAAC;AAC7D,gBAAI,KAAK,IAAI,QAAQ,KAAK,WAAW;AACnC,sBAAQ;AACR,uBAAS,OAAO,EAAE,UAAU,SAAS,QAAQ,CAAC,CAAC,iBAAiB,SAAS;AACzE;AAAA,YACF;AAAA,UACF;AAAA,QACF;AACA;AAAA,MACF;AAAA,MAEA,KAAK,WAAW;AACd,YAAI,CAAC,QAAQ,UAAW;AACxB,cAAM,MAAM,QAAQ,UAAU,IAAI,MAAM,SAAS;AACjD,YAAI,OAAO,KAAM;AACjB,YAAI,kBAAkB,MAAM;AAC1B,0BAAgB;AAChB;AAAA,QACF;AACA,cAAM,WAAY,MAAM,iBAAiB,gBAAiB;AAC1D,YAAI,KAAK,IAAI,OAAO,KAAK,WAAW;AAClC,kBAAQ;AACR,mBAAS,aAAa,QAAQ,QAAQ,CAAC,CAAC,gBAAgB,SAAS;AAAA,QACnE;AACA;AAAA,MACF;AAAA,MAEA,KAAK,aAAa;AAChB,YAAI,CAAC,QAAQ,YAAa;AAC1B,cAAM,QAAQ,QAAQ,YAAY,IAAI,MAAM,SAAS;AACrD,YAAI,SAAS,KAAM;AACnB,YAAI,oBAAoB,MAAM;AAC5B,4BAAkB;AAClB;AAAA,QACF;AACA,cAAM,WAAY,QAAQ,mBAAmB,kBAAmB;AAChE,YAAI,KAAK,IAAI,OAAO,KAAK,WAAW;AAClC,kBAAQ;AACR,mBAAS,eAAe,QAAQ,QAAQ,CAAC,CAAC,gBAAgB,SAAS;AAAA,QACrE;AACA;AAAA,MACF;AAAA,MAEA,KAAK,iBAAiB;AACpB,YAAI,CAAC,QAAQ,aAAa,CAAC,QAAQ,YAAa;AAChD,cAAM,MAAM,QAAQ,UAAU,IAAI,MAAM,SAAS;AACjD,cAAM,QAAQ,QAAQ,YAAY,IAAI,MAAM,SAAS;AACrD,YAAI,OAAO,QAAQ,SAAS,QAAQ,QAAQ,EAAG;AAC/C,cAAM,QAAQ,QAAQ;AAGtB,cAAM,UAAU,aAAa,IAAI,SAAS,YAAY,SAAS;AAC/D,YAAI,SAAS;AACX,kBAAQ;AACR,mBAAS,mBAAmB,MAAM,QAAQ,CAAC,CAAC,sBAAsB,SAAS;AAAA,QAC7E;AACA;AAAA,MACF;AAAA,MAEA,KAAK,oBAAoB;AACvB,cAAM,KAAK,QAAQ,eAAe;AAClC,cAAM,KAAK,QAAQ,aAAa;AAChC,cAAM,KAAK,QAAQ,cAAc;AACjC,YAAI,OAAO,EAAG;AACd,cAAM,UAAU,eAAe,OAAO,MAAM,IAAI,IAAI,EAAE;AACtD,YAAI,WAAW,WAAW;AACxB,kBAAQ;AACR,mBAAS,aAAa,QAAQ,QAAQ,CAAC,CAAC,iBAAiB,SAAS;AAAA,QACpE;AACA;AAAA,MACF;AAAA,MAEA,KAAK,eAAe;AAClB,cAAM,KAAK,QAAQ,eAAe;AAClC,cAAM,KAAK,QAAQ,aAAa;AAChC,cAAM,KAAK,QAAQ,cAAc;AACjC,YAAI,OAAO,EAAG;AACd,cAAM,UAAU,eAAe,OAAO,MAAM,IAAI,IAAI,EAAE;AACtD,YAAI,mBAAmB,QAAQ,OAAO,QAAQ,iBAAiB,UAAU;AACvE,2BAAiB,QAAQ;AAAA,QAC3B;AACA,YAAI,mBAAmB,MAAM;AAC3B,2BAAiB;AACjB;AAAA,QACF;AACA,cAAM,UAAU,mBAAmB,gBAAgB,OAAO;AAC1D,YAAI,uBAAuB,SAAS,SAAS,GAAG;AAC9C,kBAAQ;AACR,mBAAS,oBAAoB,UAAU,KAAK,QAAQ,CAAC,CAAC,kBAAkB,eAAe,QAAQ,CAAC,CAAC,OAAO,QAAQ,QAAQ,CAAC,CAAC,gBAAgB,YAAY,KAAK,QAAQ,CAAC,CAAC;AAAA,QACvK;AACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO;AACT,aAAO;AAAA,QACL;AAAA,QACA,SAAS,MAAM;AAAA,QACf,WAAW;AAAA,QACX,OAAO;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAUA,SAAS,gBACP,SACA,MACA,YACU;AACV,SAAO,QAAQ,IAAI,CAAC,UAAU;AAC5B,QAAI,WAAW;AACf,eAAW,OAAO,YAAY;AAC5B,UAAI,MAAM,KAAK,UAAU,MAAM,MAAM,UAAU,QAAQ;AACrD,cAAM,MAAM,KAAK,GAAG;AACpB,cAAMA,aAAY,MAAM,UAAU,GAAG;AACrC,qBAAaA,aAAY,IAAI,cAAc,IAAI,WAAW,IAAI;AAAA,MAChE;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAUO,SAAS,oBAAoB,QASlC;AACA,QAAM,EAAE,SAAS,MAAM,UAAU,qBAAqB,UAAU,IAAI;AAGpE,QAAM,aAAiC,CAAC;AACxC,MAAI,mBAAmC,CAAC;AACxC,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,SAAS,gBAAgB;AAEnC,YAAM,WAAW,qBAAqB,SAAS,SAAS,IAAI;AAC5D,UAAI,SAAS,WAAW;AACtB,mBAAW,KAAK,SAAS,SAAS;AAAA,MACpC;AACA,UAAI,SAAS,cAAc,SAAS,GAAG;AACrC,2BAAmB,iBAAiB,OAAO,SAAS,aAAa;AAAA,MACnE;AAAA,IACF,OAAO;AACL,YAAM,QAAQ,gBAAgB,SAAS,SAAS,IAAI;AACpD,UAAI,OAAO;AACT,mBAAW,KAAK,KAAK;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAGA,aAAW,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAE3C,QAAM,cAAc,WAAW,SAAS,IAAI,WAAW,CAAC,IAAI;AAG5D,MAAI;AACJ,MAAI,uBAAuB,aAAa;AAEtC,QAAI,aAAa;AACjB,QAAI,cAAc;AAClB,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AAEvC,YAAM,OAAO,KAAK,IAAI,QAAQ,CAAC,EAAE,UAAU,cAAc,mBAAmB,CAAC;AAC7E,UAAI,QAAQ,CAAC,EAAE,cAAc,qBAAqB;AAChD,qBAAa;AACb;AAAA,MACF;AACA,UAAI,OAAO,aAAa;AACtB,sBAAc;AACd,qBAAa;AAAA,MACf;AAAA,IACF;AAEA,QAAI,sBAAsB,QAAQ,QAAQ,SAAS,CAAC,EAAE,WAAW;AAC/D,mBAAa,QAAQ,SAAS;AAAA,IAChC;AACA,UAAM,YAAY,QAAQ,UAAU,EAAE;AACtC,iBAAa;AAAA,MACX,WAAW,QAAQ,UAAU,EAAE;AAAA,MAC/B,KAAK;AAAA,MACL,eAAe,YAAY,YAAY;AAAA,IACzC;AAAA,EACF;AAGA,MAAI;AACJ,MAAI,CAAC,aAAa;AAChB,cAAU,4BAA4B,QAAQ,MAAM;AAAA,EACtD,WAAW,YAAY;AACrB,UAAM,cAAc,WAAW,gBAAgB,IAAI,WAAW;AAC9D,cAAU,GAAG,YAAY,IAAI,aAAa,YAAY,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,CAAC,qBACnF,WAAW,SAAS,UAAU,WAAW,IAAI,QAAQ,CAAC,CAAC,mBACzD,KAAK,IAAI,WAAW,aAAa,EAAE,QAAQ,CAAC,CAAC,IAAI,WAAW;AAAA,EAChF,OAAO;AACL,cAAU,GAAG,YAAY,IAAI,mBAAmB,YAAY,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,CAAC,MACxG,WAAW,MAAM;AAAA,EACxB;AAEA,QAAM,UAA6B;AAAA,IACjC,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,eAAe,iBAAiB,SAAS,IAAI,mBAAmB;AAAA,IAChE;AAAA,EACF;AAGA,MAAI;AACJ,MAAI,aAAa,UAAU,SAAS,GAAG;AACrC,sBAAkB,UAAU,IAAI,CAAC,UAAU;AACzC,YAAM,cAAc,gBAAgB,SAAS,MAAM,MAAM,UAAU;AAGnE,YAAM,YAAwB,QAAQ,IAAI,CAAC,OAAO,SAAS;AAAA,QACzD,GAAG;AAAA,QACH,aAAa,YAAY,GAAG;AAAA;AAAA,QAE5B,WAAW,MAAM,WAAW,IAAI,CAAC,OAAO,MAAM,UAAU,EAAE,KAAK,CAAC;AAAA,QAChE,WAAW,MAAM,YACb,MAAM,WAAW,IAAI,CAAC,OAAO,MAAM,UAAW,EAAE,CAAC,IACjD;AAAA,MACN,EAAE;AAGF,YAAM,YAAY,MAAM,WAAW,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC;AAGvD,YAAM,kBAAsC,CAAC;AAC7C,iBAAW,WAAW,MAAM,UAAU;AACpC,cAAM,QAAQ,gBAAgB,SAAS,WAAW,SAAS;AAC3D,YAAI,MAAO,iBAAgB,KAAK,KAAK;AAAA,MACvC;AACA,sBAAgB,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAEhD,YAAM,mBAAmB,gBAAgB,SAAS,IAAI,gBAAgB,CAAC,IAAI;AAG3E,UAAI;AACJ,UAAI,uBAAuB,kBAAkB;AAC3C,YAAI,aAAa,QAAQ,SAAS;AAClC,iBAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,cAAI,QAAQ,CAAC,EAAE,cAAc,qBAAqB;AAChD,yBAAa;AACb;AAAA,UACF;AAAA,QACF;AACA,YAAI,sBAAsB,QAAQ,QAAQ,SAAS,CAAC,EAAE,WAAW;AAC/D,uBAAa,QAAQ,SAAS;AAAA,QAChC;AACA,cAAM,iBAAiB,YAAY,UAAU;AAC7C,0BAAkB;AAAA,UAChB,WAAW,QAAQ,UAAU,EAAE;AAAA,UAC/B,KAAK;AAAA,UACL,eAAe,iBAAiB,YAAY;AAAA,QAC9C;AAAA,MACF;AAEA,YAAM,eAAe,mBACjB,GAAG,MAAM,KAAK,KAAK,iBAAiB,IAAI,aAAa,iBAAiB,OAAO,gBAAgB,iBAAiB,UAAU,QAAQ,CAAC,CAAC,MAClI,GAAG,MAAM,KAAK;AAElB,aAAO;AAAA,QACL,OAAO,MAAM;AAAA,QACb,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,aAAa;AAAA,UACb,YAAY;AAAA,UACZ,SAAS;AAAA,QACX;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA,WAAW;AAAA,EACb;AACF;;;ACruBA,SAAS,KAAAC,UAAS;AAoBlB,IAAM,kBAAkBC,GAAE,KAAK;AAAA,EAC7B;AAAA,EAAgB;AAAA,EAAY;AAAA,EAAgB;AAAA,EAC5C;AAAA,EAAW;AAAA,EAAW;AAAA,EACtB;AAAA,EAAuB;AAAA,EAAiB;AAAA,EACxC;AAAA,EAAW;AAAA,EAAa;AAAA,EACxB;AAAA,EAAoB;AACtB,CAAC;AAED,IAAM,sBAAsBA,GAAE,OAAO;AAAA,EACnC,MAAM;AAAA,EACN,WAAWA,GAAE,OAAO;AAAA,EACpB,MAAMA,GAAE,KAAK,CAAC,WAAW,QAAQ,CAAC,EAAE,QAAQ,QAAQ,EAAE,SAAS;AAAA,EAC/D,QAAQA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,UAAUA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,WAAWA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,aAAaA,GAAE,OAAO,EAAE,SAAS;AAAA,EACjC,OAAOA,GAAE,MAAMA,GAAE,OAAO;AAAA,IACtB,OAAOA,GAAE,OAAO;AAAA,IAChB,QAAQA,GAAE,OAAO;AAAA,IACjB,oBAAoBA,GAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS,EACnD,SAAS,iEAAiE;AAAA,EAC/E,CAAC,CAAC,EAAE,SAAS;AAAA,EACb,aAAaA,GAAE,OAAO,EAAE,SAAS;AAAA,EACjC,WAAWA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,UAAUA,GAAE,OAAO,EAAE,SAAS,EAC3B,SAAS,+DAA0D;AAAA,EACtE,WAAWA,GAAE,OAAO,EAAE,SAAS,EAC5B,SAAS,oDAAoD;AAAA,EAChE,WAAWA,GAAE,OAAO,EAAE,SAAS,EAC5B,SAAS,wDAAwD;AACtE,CAAC;AAMD,IAAM,YAAYA,GAAE,OAAO;AAAA,EACzB,QAAQA,GAAE,OAAO;AAAA,EACjB,QAAQA,GAAE,OAAO;AAAA,EACjB,MAAMA,GAAE,KAAK,CAAC,KAAK,GAAG,CAAC;AAAA,EACvB,QAAQA,GAAE,OAAO;AAAA,EACjB,UAAUA,GAAE,OAAO;AAAA,EACnB,aAAaA,GAAE,OAAO;AACxB,CAAC;AAMM,IAAM,4BAA4BA,GAAE,OAAO;AAAA;AAAA,EAEhD,MAAMA,GAAE,MAAM,SAAS,EAAE,SAAS;AAAA,EAClC,UAAUA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,aAAaA,GAAE,OAAO,EAAE,SAAS;AAAA,EACjC,WAAWA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,YAAYA,GAAE,OAAO,EAAE,SAAS;AAAA,EAChC,YAAYA,GAAE,OAAO,EAAE,QAAQ,GAAG;AAAA,EAElC,UAAUA,GAAE,MAAM,mBAAmB,EAClC,SAAS,gDAAgD;AAAA,EAE5D,uBAAuBA,GAAE,OAAO,EAAE,SAAS,EACxC,SAAS,4DAA4D;AAAA,EAExE,YAAYA,GAAE,MAAMA,GAAE,OAAO;AAAA,IAC3B,OAAOA,GAAE,OAAO;AAAA,IAChB,aAAaA,GAAE,MAAMA,GAAE,OAAO,CAAC;AAAA,IAC/B,UAAUA,GAAE,MAAM,mBAAmB;AAAA,EACvC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,4DAA4D;AAAA,EAEpF,QAAQA,GAAE,KAAK,CAAC,WAAW,MAAM,CAAC,EAAE,QAAQ,SAAS,EAClD,SAAS,0EAA0E;AACxF,CAAC;AAMM,IAAM,wBAAwBA,GAAE,OAAO;AAAA;AAAA,EAE5C,MAAMA,GAAE,MAAM,SAAS,EAAE,SAAS;AAAA,EAClC,UAAUA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,aAAaA,GAAE,OAAO,EAAE,SAAS;AAAA,EACjC,WAAWA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,YAAYA,GAAE,OAAO,EAAE,SAAS;AAAA,EAChC,YAAYA,GAAE,OAAO,EAAE,QAAQ,GAAG;AAAA,EAElC,YAAYA,GAAE,MAAMA,GAAE,OAAO;AAAA,IAC3B,OAAOA,GAAE,OAAO;AAAA,IAChB,aAAaA,GAAE,MAAMA,GAAE,OAAO,CAAC;AAAA,EACjC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,+EAA+E;AAAA,EAEvG,QAAQA,GAAE,KAAK,CAAC,WAAW,MAAM,CAAC,EAAE,QAAQ,SAAS,EAClD,SAAS,wEAAwE;AAAA,EACpF,aAAaA,GACV,QAAQ,EACR,QAAQ,KAAK,EACb,SAAS,0EAA0E;AACxF,CAAC;AAOD,IAAM,mBAA2C;AAAA,EAC/C,MAAM;AAAA,EAAO,MAAM;AAAA,EAAO,MAAM;AAClC;AAMA,SAAS,wBAAwB,WAA2B;AAC1D,QAAM,YAAY,UAAU,MAAM,WAAW;AAC7C,QAAM,UAAU,YAAY,UAAU,CAAC,IAAI;AAC3C,SAAO,iBAAiB,OAAO,KAAK;AACtC;AAWA,eAAe,cACb,QACA,QACA,MACA,IAC8B;AAC9B,QAAM,MAAM,oBAAI,IAAoB;AACpC,MAAI;AACF,QAAI,OAAO,MAAM,OAAO,KAAK,SAAS,QAAQ,MAAM,EAAE;AACtD,QAAI,KAAK,WAAW,GAAG;AACrB,UAAI;AACF,eAAO,MAAM,OAAO,KAAK,cAAc,QAAQ,MAAM,EAAE;AAAA,MACzD,QAAQ;AAAA,MAER;AAAA,IACF;AAOA,eAAW,KAAK,MAAM;AACpB,UACE,CAAC,OAAO,SAAS,EAAE,IAAI,KAAM,EAAE,QAAS,KACxC,CAAC,OAAO,SAAS,EAAE,IAAI,KAAM,EAAE,QAAS,KACxC,CAAC,OAAO,SAAS,EAAE,GAAG,KAAO,EAAE,OAAS,KACxC,CAAC,OAAO,SAAS,EAAE,KAAK,KAAK,EAAE,SAAS,EACxC;AACF,YAAM,KAAK,GAAG,EAAE,IAAI,IAAI,EAAE,QAAQ,EAAE,GAAG,KAAK;AAC5C,UAAI,IAAI,IAAI,UAAU,CAAC,CAAC;AAAA,IAC1B;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAMA,eAAsB,0BACpB,QACA,SACA,QACA,cACiD;AACjD,QAAM;AAAA,IACJ,MAAM;AAAA,IAAW;AAAA,IAAU;AAAA,IAC3B;AAAA,IAAW;AAAA,IAAY;AAAA,IACvB;AAAA,IAAU;AAAA,IAAuB;AAAA,EACnC,IAAI;AAGJ,QAAM,eAAe,MAAM;AAAA,IACzB;AAAA,MACE,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,aAAa;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,UAAU,aAAa;AAC7B,QAAM,aAAa,aAAa;AAGhC,QAAM,YAAY,WAAW,OAAO,CAAC,KAAK,QAAQ;AAChD,WAAO,MAAM,IAAI,aAAa,IAAI,WAAW,IAAI;AAAA,EACnD,GAAG,CAAC;AAEJ,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,MACL,SAAS;AAAA,QACP,UAAU,CAAC;AAAA,QACX,aAAa;AAAA,QACb,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,QAAQ,CAAC,EAAE,UAAU,MAAM,GAAG,EAAE;AAClD,QAAM,WAAW,QAAQ,QAAQ,SAAS,CAAC,EAAE,UAAU,MAAM,GAAG,EAAE;AAGlE,QAAM,kBAAkB,IAAI,IAAI,SAAS,IAAI,OAAK,EAAE,IAAI,CAAC;AACzD,QAAM,oBAAoB,IAAI;AAAA,KAC3B,cAAc,CAAC,GAAG,QAAQ,OAAK,EAAE,SAAS,IAAI,OAAK,EAAE,IAAI,CAAC;AAAA,EAC7D;AACA,aAAW,KAAK,kBAAmB,iBAAgB,IAAI,CAAC;AAGxD,QAAM,mBAAmB,wBAAwB,WAAW,CAAC,EAAE,SAAS;AAGxE,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,QAAM,WAAW,gBAAgB,IAAI,SAAS,KAAK,gBAAgB,IAAI,eAAe;AACtF,QAAM,aAAa,gBAAgB,IAAI,WAAW,KAAK,gBAAgB,IAAI,eAAe;AAC1F,QAAM,kBAAkB,gBAAgB,IAAI,qBAAqB;AAEjE,MAAI,UAAU;AACZ,gBAAY,MAAM,cAAc,QAAQ,OAAO,WAAW,QAAQ;AAAA,EACpE;AACA,MAAI,YAAY;AACd,kBAAc,MAAM,cAAc,QAAQ,SAAS,WAAW,QAAQ;AAAA,EACxE;AACA,MAAI,iBAAiB;AACnB,uBAAmB,MAAM;AAAA,MACvB;AAAA,MAAQ;AAAA,MAAkB;AAAA,MAAW;AAAA,IACvC;AAAA,EACF;AAGA,QAAM,eAAoC,SAAS,IAAI,QAAM;AAAA,IAC3D,MAAM,EAAE;AAAA,IACR,WAAW,EAAE;AAAA,IACb,MAAM,EAAE;AAAA,IACR;AAAA,IACA,QAAQ,EAAE;AAAA,IACV,UAAU,EAAE;AAAA,IACZ,WAAW,EAAE;AAAA,IACb,aAAa,EAAE;AAAA,IACf,OAAO,EAAE;AAAA,IACT,aAAa,EAAE;AAAA,IACf,WAAW,EAAE;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE;AAGF,QAAM,kBAAgD,YAAY,IAAI,QAAM;AAAA,IAC1E,OAAO,EAAE;AAAA,IACT,YAAY,EAAE;AAAA,IACd,UAAU,EAAE,SAAS,IAAI,QAAM;AAAA,MAC7B,MAAM,EAAE;AAAA,MACR,WAAW,EAAE;AAAA,MACb,MAAM,EAAE;AAAA,MACR;AAAA,MACA,QAAQ,EAAE;AAAA,MACV,UAAU,EAAE;AAAA,MACZ,WAAW,EAAE;AAAA,MACb,aAAa,EAAE;AAAA,MACf,OAAO,EAAE;AAAA,MACT,aAAa,EAAE;AAAA,MACf,WAAW,EAAE;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE;AAAA,EACJ,EAAE;AAGF,SAAO,oBAAoB;AAAA,IACzB;AAAA,IACA,MAAM;AAAA,IACN,UAAU;AAAA,IACV,qBAAqB;AAAA,IACrB,WAAW;AAAA,EACb,CAAC;AACH;AAMA,eAAsB,sBACpB,QACA,SACA,QACA,cAC+E;AAC/E,QAAM;AAAA,IACJ,MAAM;AAAA,IAAW;AAAA,IAAU;AAAA,IAC3B;AAAA,IAAW;AAAA,IAAY;AAAA,IACvB;AAAA,IAAY;AAAA,IAAQ;AAAA,EACtB,IAAI;AAGJ,QAAM,eAAe,MAAM;AAAA,IACzB;AAAA,MACE,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,UAAU;AAAA,MACV;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,UAAU,aAAa;AAC7B,QAAM,aAAa,aAAa;AAGhC,MAAI,QAAQ,SAAS,KAAK,CAAC,QAAQ,CAAC,EAAE,WAAW;AAC/C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,mBAAmB,oBAAI,IAAoB;AACjD,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,oBAAoB,QAAW;AACvC,uBAAiB,IAAI,MAAM,WAAW,MAAM,eAAe;AAAA,IAC7D;AAAA,EACF;AAGA,QAAM,eAA0C,YAAY,IAAI,QAAM;AAAA,IACpE,OAAO,EAAE;AAAA,IACT,YAAY,EAAE;AAAA,EAChB,EAAE;AAGF,QAAM,kBAA0C;AAAA,IAC9C,KAAK;AAAA,IAAO,MAAM;AAAA,IAAO,KAAK;AAAA,IAAO,MAAM;AAAA,EAC7C;AACA,QAAM,YAAY,WAAW,CAAC,GAAG,UAAU,MAAM,WAAW;AAC5D,QAAM,UAAU,YAAY,UAAU,CAAC,IAAI;AAC3C,QAAM,WAAW,gBAAgB,OAAO,KAAK;AAE7C,QAAM,mBAAmB,WAAW,IAAI,SAAO;AAC7C,UAAM,IAAI,IAAI,UAAU,MAAM,8BAA8B;AAC5D,QAAI,CAAC,EAAG,QAAO,EAAE,QAAQ,GAAG,MAAM,KAAc,YAAY,GAAG;AAC/D,WAAO;AAAA,MACL,QAAQ,SAAS,EAAE,CAAC,GAAG,EAAE,IAAI;AAAA,MAC7B,MAAM,EAAE,CAAC;AAAA,MACT,YAAY,KAAK,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IAC3E;AAAA,EACF,CAAC;AAGD,QAAM,SAAS,gBAAgB;AAAA,IAC7B;AAAA,IACA,MAAM;AAAA,IACN,kBAAkB,iBAAiB,OAAO,IAAI,mBAAmB;AAAA,IACjE,WAAW;AAAA,IACX;AAAA,IACA,cAAc;AAAA,IACd,eAAe;AAAA,EACjB,CAAC;AAGD,MAAI,WAAW,WAAW;AACxB,eAAW,UAAU,OAAO,SAAS;AACnC,aAAO,QAAQ,CAAC;AAAA,IAClB;AACA,QAAI,OAAO,cAAc;AACvB,iBAAW,SAAS,OAAO,cAAc;AACvC,cAAM,QAAQ,CAAC;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;;;AC7SO,SAAS,sBAAsB,cAAiD;AACrF,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAO;AAAA,MACL,aAAa;AAAA,MACb,eAAe;AAAA,MACf,cAAc;AAAA,MACd,SAAS;AAAA,MACT,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,cAAc;AAAA,MACd,aAAa;AAAA,MACb,aAAa;AAAA,MACb,cAAc;AAAA,MACd,eAAe;AAAA,MACf,kBAAkB;AAAA,MAClB,eAAe;AAAA,MACf,iBAAiB;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,gBAAgB,aAAa,IAAI,OAAK,EAAE,YAAY;AAC1D,QAAM,eAAe,aAAa,IAAI,OAAK,EAAE,WAAW;AAExD,QAAM,gBAAgB,cAAc,OAAO,OAAK,IAAI,CAAC,EAAE;AACvD,QAAM,eAAe,cAAc,OAAO,OAAK,IAAI,CAAC,EAAE;AACtD,QAAM,cAAc,aAAa;AACjC,QAAM,UAAU,gBAAgB;AAEhC,QAAM,WAAW,cAAc,OAAO,CAAC,KAAK,MAAM,MAAM,GAAG,CAAC;AAC5D,QAAM,SAAS,WAAW;AAE1B,QAAM,OAAO,cAAc,OAAO,OAAK,IAAI,CAAC;AAC5C,QAAM,SAAS,cAAc,OAAO,OAAK,IAAI,CAAC;AAE9C,QAAM,SAAS,KAAK,SAAS,IAAI,KAAK,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS;AACjF,QAAM,UAAU,OAAO,SAAS,IAAI,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,OAAO,SAAS;AACxF,QAAM,SAAS,KAAK,SAAS,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI;AACrD,QAAM,UAAU,OAAO,SAAS,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI;AAG1D,QAAM,UAAU,KAAK,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAC9C,QAAM,YAAY,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAClD,QAAM,eAAe,OAAO,WAAW,IACnC,WACA,UAAU,KAAK,IAAI,SAAS;AAGhC,MAAI,cAAc;AAClB,MAAI,SAAS;AACb,MAAI,cAAc;AAClB,aAAW,OAAO,eAAe;AAC/B,cAAU;AACV,QAAI,SAAS,YAAa,eAAc;AACxC,UAAM,KAAK,cAAc;AACzB,QAAI,KAAK,YAAa,eAAc;AAAA,EACtC;AAGA,MAAI,cAA6B;AACjC,MAAI,eAAe,GAAG;AACpB,UAAM,OAAO;AACb,UAAM,WACJ,cAAc,OAAO,CAAC,KAAK,MAAM,OAAO,IAAI,SAAS,GAAG,CAAC,KAAK,cAAc;AAC9E,UAAM,SAAS,KAAK,KAAK,QAAQ;AACjC,kBAAc,WAAW,IAAI,OAAO,OAAO;AAAA,EAC7C;AAGA,MAAI,eAAe;AACnB,MAAI,gBAAgB;AACpB,MAAI,mBAAmB;AACvB,MAAI,oBAAoB;AACxB,aAAW,OAAO,eAAe;AAC/B,QAAI,MAAM,GAAG;AACX;AACA,0BAAoB;AACpB,UAAI,mBAAmB,aAAc,gBAAe;AAAA,IACtD,WAAW,MAAM,GAAG;AAClB;AACA,yBAAmB;AACnB,UAAI,oBAAoB,cAAe,iBAAgB;AAAA,IACzD,OAAO;AAEL,yBAAmB;AACnB,0BAAoB;AAAA,IACtB;AAAA,EACF;AAGA,QAAM,mBAAmB,aAAa,OAAO,CAAC,KAAK,MAAM,MAAM,GAAG,CAAC;AACnE,QAAM,eAAe,aAAa,OAAO,OAAK,IAAI,CAAC,EAAE;AACrD,QAAM,kBAAkB,eAAe;AAEvC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,WAAW;AAAA,IAC1B;AAAA,EACF;AACF;AAUO,SAAS,0BACd,cACsB;AACtB,QAAM,SAAS,oBAAI,IAGjB;AAEF,aAAW,UAAU,cAAc;AACjC,UAAM,MAAM,OAAO;AACnB,UAAM,WAAW,OAAO,IAAI,GAAG;AAC/B,QAAI,UAAU;AACZ,eAAS;AACT,eAAS,YAAY,OAAO;AAC5B,eAAS,cAAc,OAAO;AAAA,IAChC,OAAO;AACL,aAAO,IAAI,KAAK;AAAA,QACd,OAAO;AAAA,QACP,UAAU,OAAO;AAAA,QACjB,YAAY,OAAO;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,OAAO,QAAQ,CAAC,EAC/B,IAAI,CAAC,CAAC,SAAS,EAAE,OAAO,UAAU,WAAW,CAAC,OAAO;AAAA,IACpD;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,WAAW;AAAA,IACnB,UAAU,aAAa;AAAA,EACzB,EAAE,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACrC;AAiBO,SAAS,aACd,QACA,QACiB;AACjB,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,iBAAiB,sBAAsB,CAAC,CAAC;AAC/C,WAAO;AAAA,MACL,WAAW;AAAA,MACX,oBAAoB,CAAC;AAAA,MACrB,UAAU,CAAC;AAAA,MACX,cAAc,OAAO;AAAA,MACrB,SAAS;AAAA,IACX;AAAA,EACF;AAEA,QAAM,EAAE,iBAAiB,WAAW,cAAc,OAAO,IAAI;AAE7D,QAAM,kBAAqC,OAAO,IAAI,WAAS;AAC7D,UAAM,EAAE,SAAS,MAAM,WAAW,YAAY,YAAY,UAAU,IAAI;AAGxE,UAAM,UAAU,QAAQ,SAAS,IAC7B,QAAQ,QAAQ,SAAS,CAAC,EAAE,cAC5B;AAGJ,UAAM,mBAAmB,gBAAgB,IAAI,QAAM;AAAA,MACjD,GAAG;AAAA,MACH;AAAA,IACF,EAAE;AAEF,UAAM,oBAAoB,WAAW,IAAI,YAAU;AAAA,MACjD,GAAG;AAAA,MACH,UAAU,MAAM,SAAS,IAAI,cAAY;AAAA,QACvC,GAAG;AAAA,QACH;AAAA,MACF,EAAE;AAAA,IACJ,EAAE;AAGF,UAAM,iBAAiB,oBAAoB;AAAA,MACzC;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AAED,UAAM,EAAE,aAAa,cAAc,IAAI,eAAe;AAGtD,QAAI;AACJ,QAAI,iBAAiB,cAAc,SAAS,GAAG;AAE7C,YAAM,aAAa,cAAc,OAAO,CAAC,KAAK,OAAO,MAAM,GAAG,WAAW,CAAC;AAC1E,YAAM,mBAAmB,cAAc,OAAO,CAAC,KAAK,OAAO,MAAM,GAAG,YAAY,CAAC;AACjF,YAAM,sBAAsB,IAAI;AAGhC,YAAM,eAAe,gBAAgB,OACjC,YAAY,YACZ,UAAU;AACd,qBAAe,aAAa;AAAA,IAC9B,OAAO;AAEL,qBAAe,gBAAgB,OAAO,YAAY,YAAY;AAAA,IAChE;AAGA,UAAM,cAAc,iBAAiB,WAAW,YAAY;AAE5D,UAAM,WAAW,eAAe;AAEhC,UAAM,eACJ,gBAAgB,OAAO,YAAY,OAAO;AAC5C,UAAM,gBAAgB,gBAAgB,OAAO,YAAY,UAAU;AAEnE,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe,iBAAiB,cAAc,SAAS,IAAI,gBAAgB;AAAA,IAC7E;AAAA,EACF,CAAC;AAED,QAAM,YAAY,sBAAsB,eAAe;AACvD,QAAM,qBAAqB,0BAA0B,eAAe;AAGpE,QAAM,aAAa,mBAAmB,SAAS,IAAI,mBAAmB,CAAC,IAAI;AAC3E,QAAM,gBAAgB,aAClB,gBAAgB,WAAW,OAAO,aAAa,WAAW,KAAK,aAC/D;AAEJ,QAAM,UACJ,YAAY,OAAO,MAAM,gCAAgC,UAAU,UAAU,KAAK,QAAQ,CAAC,CAAC,iBAC9E,UAAU,SAAS,QAAQ,CAAC,CAAC,YAAY,UAAU,cAAc,QAAQ,CAAC,CAAC,oBACzF;AAEF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,WAAW,YAAY,CAAC,IAAI;AAAA,IACtC;AAAA,IACA;AAAA,EACF;AACF;;;AC1ZA,SAAS,KAAAC,UAAS;AAuBlB,eAAe,aACb,OACA,OACA,IACc;AACd,QAAM,UAAe,IAAI,MAAM,MAAM,MAAM;AAC3C,MAAI,MAAM;AAEV,iBAAe,SAAwB;AACrC,WAAO,MAAM,MAAM,QAAQ;AACzB,YAAM,IAAI;AACV,cAAQ,CAAC,IAAI,MAAM,GAAG,MAAM,CAAC,CAAC;AAAA,IAChC;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,OAAO,MAAM,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC;AACpF,QAAM,QAAQ,IAAI,OAAO;AACzB,SAAO;AACT;AAMA,IAAMC,mBAAkBC,GAAE,KAAK;AAAA,EAC7B;AAAA,EAAgB;AAAA,EAAY;AAAA,EAAgB;AAAA,EAC5C;AAAA,EAAW;AAAA,EAAW;AAAA,EACtB;AAAA,EAAuB;AAAA,EAAiB;AAAA,EACxC;AAAA,EAAW;AAAA,EAAa;AAAA,EACxB;AAAA,EAAoB;AACtB,CAAC;AAED,IAAMC,uBAAsBD,GAAE,OAAO;AAAA,EACnC,MAAMD;AAAA,EACN,WAAWC,GAAE,OAAO;AAAA,EACpB,MAAMA,GAAE,KAAK,CAAC,WAAW,QAAQ,CAAC,EAAE,QAAQ,QAAQ,EAAE,SAAS;AAAA,EAC/D,QAAQA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,UAAUA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,WAAWA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,aAAaA,GAAE,OAAO,EAAE,SAAS;AAAA,EACjC,OAAOA,GAAE,MAAMA,GAAE,OAAO;AAAA,IACtB,OAAOA,GAAE,OAAO;AAAA,IAChB,QAAQA,GAAE,OAAO;AAAA,IACjB,oBAAoBA,GAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS,EACnD,SAAS,iEAAiE;AAAA,EAC/E,CAAC,CAAC,EAAE,SAAS;AAAA,EACb,aAAaA,GAAE,OAAO,EAAE,SAAS;AAAA,EACjC,WAAWA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,UAAUA,GAAE,OAAO,EAAE,SAAS,EAC3B,SAAS,+DAA0D;AAAA,EACtE,WAAWA,GAAE,OAAO,EAAE,SAAS,EAC5B,SAAS,oDAAoD;AAAA,EAChE,WAAWA,GAAE,OAAO,EAAE,SAAS,EAC5B,SAAS,wDAAwD;AACtE,CAAC;AAMM,IAAM,0BAA0BA,GAAE,OAAO;AAAA,EAC9C,UAAUA,GAAE,OAAO,EAAE,SAAS,iCAAiC;AAAA,EAE/D,UAAUA,GAAE,OAAO,EAAE,SAAS,EAC3B,SAAS,yDAAyD;AAAA,EAErE,YAAYA,GAAE,OAAO;AAAA,IACnB,MAAMA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,uBAAuB;AAAA,IAC5D,IAAIA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS,qBAAqB;AAAA,EAC1D,CAAC,EAAE,SAAS,EAAE,SAAS,6BAA6B;AAAA,EAEpD,kBAAkBA,GAAE,MAAMC,oBAAmB,EAC1C,SAAS,oFAAoF;AAAA,EAEhG,YAAYD,GAAE,MAAMA,GAAE,OAAO;AAAA,IAC3B,OAAOA,GAAE,OAAO;AAAA,IAChB,aAAaA,GAAE,MAAMA,GAAE,OAAO,CAAC;AAAA,IAC/B,UAAUA,GAAE,MAAMC,oBAAmB;AAAA,EACvC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,4DAA4D;AAAA,EAEpF,eAAeD,GAAE,KAAK,CAAC,UAAU,WAAW,CAAC,EAAE,QAAQ,QAAQ,EAC5D,SAAS,kGAAkG;AAAA,EAE9G,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE,EACzC,SAAS,oDAAoD;AAAA,EAEhE,QAAQA,GAAE,OAAO,EAAE,SAAS,EACzB,SAAS,mDAAmD;AAAA,EAE/D,QAAQA,GAAE,OAAO,EAAE,SAAS,EACzB,SAAS,mDAAmD;AAAA,EAE/D,YAAYA,GAAE,OAAO,EAAE,QAAQ,GAAG,EAC/B,SAAS,mCAAmC;AAAA,EAE/C,QAAQA,GAAE,KAAK,CAAC,WAAW,MAAM,CAAC,EAAE,QAAQ,SAAS,EAClD,SAAS,0FAA0F;AACxG,CAAC;AAMD,eAAsB,wBACpB,QACA,SACA,QACA,cAC0B;AAC1B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,QAAM,OAAO,gBAAgB,MAAM,cAAc,OAAO;AACxD,QAAM,iBAAiB,SAAS,QAAQ,MAAM,IAAI;AAGlD,QAAM,eAAyB,CAAC,eAAe,cAAc,GAAG;AAEhE,MAAI,UAAU;AACZ,UAAM,kBAAkB,SAAS,QAAQ,MAAM,IAAI;AACnD,iBAAa,KAAK,oBAAoB,eAAe,IAAI;AAAA,EAC3D;AACA,MAAI,YAAY,MAAM;AACpB,iBAAa,KAAK,mBAAmB,WAAW,IAAI,GAAG;AAAA,EACzD;AACA,MAAI,YAAY,IAAI;AAClB,iBAAa,KAAK,mBAAmB,WAAW,EAAE,GAAG;AAAA,EACvD;AACA,MAAI,WAAW,QAAW;AACxB,iBAAa,KAAK,SAAS,MAAM,EAAE;AAAA,EACrC;AACA,MAAI,WAAW,QAAW;AACxB,iBAAa,KAAK,SAAS,MAAM,EAAE;AAAA,EACrC;AAKA,QAAM,gBAAgB,aAAa,MAAM,CAAC;AAC1C,QAAM,QAAQ;AAAA;AAAA;AAAA;AAAA,0BAIU,cAAc;AAAA;AAAA;AAAA;AAAA,MAIlC,cAAc,SAAS,IAAI,WAAW,cAAc,KAAK,OAAO,IAAI,EAAE;AAAA;AAAA,YAEhE,KAAK;AAAA;AAGf,QAAM,cAAc,MAAM,KAAK,cAAc,KAAK;AAClD,QAAM,OAAO,YAAY,QAAQ;AAEjC,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,cAA+B;AAAA,MACnC,WAAW;AAAA,QACT,aAAa;AAAA,QACb,eAAe;AAAA,QACf,cAAc;AAAA,QACd,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,cAAc;AAAA,QACd,aAAa;AAAA,QACb,aAAa;AAAA,QACb,cAAc;AAAA,QACd,eAAe;AAAA,QACf,kBAAkB;AAAA,QAClB,eAAe;AAAA,QACf,iBAAiB;AAAA,MACnB;AAAA,MACA,oBAAoB,CAAC;AAAA,MACrB,UAAU,CAAC;AAAA,MACX,cAAc;AAAA,MACd,SAAS;AAAA,IACX;AACA,WAAO;AAAA,EACT;AAGA,QAAM,yBAAyB;AAM/B,QAAM,WAAW,MAAM;AAAA,IACrB;AAAA,IACA;AAAA,IACA,OAAO,QAAgC;AACrC,YAAM,WAAW,OAAO,IAAI,CAAC,KAAK,CAAC;AACnC,YAAM,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC;AAC7B,YAAM,aAAa,OAAO,IAAI,CAAC,KAAK,EAAE;AAEtC,UAAI;AAGF,cAAM,eAAe,MAAM;AAAA,UACzB;AAAA,YACE;AAAA,YACA,aAAa;AAAA,YACb;AAAA,YACA,QAAQ;AAAA,YACR,UAAU;AAAA,YACV,aAAa;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAGA,cAAM,iBAAiB,aAAa,KAAK,OAAO,CAAC,KAAa,QAAQ;AACpE,iBAAO,MAAM,IAAI,aAAa,IAAI,WAAW,IAAI;AAAA,QACnD,GAAG,CAAC;AAEJ,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,OAAO;AAAA,YACL,YAAY;AAAA,YACZ;AAAA,YACA,WAAW;AAAA,YACX,SAAS,aAAa;AAAA,YACtB,MAAM,aAAa;AAAA,YACnB,WAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,YAAY,OAAO,IAAI,CAAC,KAAK,CAAC;AAAA,UAC9B,YAAY,OAAO,IAAI,CAAC,KAAK,EAAE;AAAA,UAC/B,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAA4B,CAAC;AACnC,QAAM,gBAAkF,CAAC;AAEzF,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,IAAI;AACd,kBAAY,KAAK,QAAQ,KAAK;AAAA,IAChC,OAAO;AACL,oBAAc,KAAK;AAAA,QACjB,YAAY,QAAQ;AAAA,QACpB,YAAY,QAAQ;AAAA,QACpB,OAAO,QAAQ;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,SAA0B;AAAA,IAC9B,iBAAiB;AAAA,IACjB,WAAW,YAAY,IAAI,QAAM;AAAA,MAC/B,OAAO,EAAE;AAAA,MACT,YAAY,EAAE;AAAA,MACd,UAAU,EAAE;AAAA,IACd,EAAE;AAAA,IACF,cAAc;AAAA,IACd;AAAA,EACF;AAGA,QAAM,SAAS,aAAa,aAAa,MAAM;AAG/C,MAAI,cAAc,SAAS,GAAG;AAC5B,WAAO,UAAU,OAAO,QAAQ;AAAA,MAC9B;AAAA,MACA,YAAY,YAAY,MAAM,YAAY,cAAc,MAAM;AAAA,IAChE;AACA,WAAO,gBAAgB;AAAA,EACzB;AAGA,MAAI,UAAU;AACZ,QAAI;AACF,YAAM,cAAc,gBAAgB,MAAM,cAAc,OAAO;AAC/D,YAAM,UAAU,MAAM,WAAW,aAAa,UAAU,UAAU,OAAO;AACzE,UAAI,SAAS;AACX,eAAO,iBAAiB;AAAA,UACtB,eAAe,QAAQ;AAAA,UACvB,WAAW,QAAQ,UAAU;AAAA,YAAI,OAC/B,EAAE,eAAe,GAAG,EAAE,IAAI,IAAI,EAAE,OAAO;AAAA,UACzC;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;;;AChSO,SAAS,qBAAqB,UAAkB,mBAAmB,KAAc;AACtF,SAAO,YAAY;AACrB;AAaO,SAAS,oBAAoB,OAAkD;AACpF,MAAI,CAAC,MAAM,uBAAwB,QAAO,CAAC;AAC3C,MAAI,MAAM,QAAQ,WAAW,EAAG,QAAO,CAAC;AAExC,QAAM,OAA6B,CAAC;AAEpC,aAAW,cAAc,MAAM,SAAS;AACtC,UAAM,QAAQ,gBAAgB,WAAW,UAAU,WAAW,MAAM;AACpE,eAAW,QAAQ,OAAO;AACxB,YAAM,MAAM,GAAG,WAAW,MAAM,IAAI,IAAI;AACxC,YAAM,mBAAmB,MAAM,iBAAiB,IAAI,GAAG,KAAK;AAC5D,UAAI,CAAC,qBAAqB,gBAAgB,GAAG;AAC3C,aAAK,KAAK;AAAA,UACR,QAAQ,WAAW;AAAA,UACnB;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AASA,SAAS,gBAAgB,UAAkB,QAA0B;AACnE,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAO,oBAAI,KAAK,WAAW,YAAY;AAC7C,QAAM,KAAK,oBAAI,KAAK,SAAS,YAAY;AACzC,QAAM,UAAU,IAAI,KAAK,IAAI;AAC7B,SAAO,WAAW,IAAI;AACpB,UAAM,KAAK,QAAQ,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAC7C,YAAQ,WAAW,QAAQ,WAAW,IAAI,CAAC;AAAA,EAC7C;AACA,SAAO;AACT;;;AC7FA,YAAYE,WAAU;AACtB,SAAS,KAAAC,UAAS;AA0BlB,IAAM,mBAAkE;AAAA;AAAA,EAEtE,EAAE,SAAS,aAAa,WAAW,OAAO;AAAA,EAC1C,EAAE,SAAS,eAAe,WAAW,SAAS;AAAA,EAC9C,EAAE,SAAS,eAAe,WAAW,SAAS;AAAA,EAC9C,EAAE,SAAS,eAAe,WAAW,SAAS;AAAA;AAAA,EAG9C,EAAE,SAAS,qBAAqB,WAAW,cAAc;AAAA,EACzD,EAAE,SAAS,qBAAqB,WAAW,cAAc;AAAA;AAAA,EAGzD,EAAE,SAAS,cAAc,WAAW,MAAM;AAC5C;AAOA,IAAM,4BAA4B,CAAC,gBAAgB,YAAY,WAAW;AAO1E,SAAS,kBAAkB,GAAW,SAAyB;AAC7D,MAAI,QAAQ;AACZ,WAAS,IAAI,SAAS,IAAI,EAAE,QAAQ,KAAK;AACvC,QAAI,EAAE,CAAC,MAAM,IAAK;AAAA,aACT,EAAE,CAAC,MAAM,KAAK;AACrB;AACA,UAAI,UAAU,EAAG,QAAO;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,qBAAqB,KAAoC;AAChE,QAAM,QAAwB,CAAC;AAC/B,aAAW,MAAM,2BAA2B;AAC1C,UAAM,YAAY,IAAI,OAAO,MAAM,EAAE,WAAW,IAAI;AACpD,QAAI;AACJ,YAAQ,IAAI,UAAU,KAAK,GAAG,OAAO,MAAM;AACzC,YAAM,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;AACxC,YAAM,WAAW,kBAAkB,KAAK,OAAO;AAC/C,UAAI,aAAa,GAAI,QAAO;AAC5B,YAAM,OAAO,IAAI,MAAM,UAAU,GAAG,QAAQ;AAC5C,YAAM,QAAkB,CAAC;AACzB,YAAM,WAAW;AACjB,UAAI;AACJ,cAAQ,IAAI,SAAS,KAAK,IAAI,OAAO,MAAM;AACzC,cAAM,KAAK,EAAE,CAAC,CAAC;AAAA,MACjB;AACA,UAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,YAAM,KAAK,EAAE,IAAI,MAAM,CAAC;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,gBAAgB,UAAkB,UAA2B;AAC3E,QAAM,YAAY,SAAS,MAAM,OAAO;AACxC,QAAM,SAAS,WAAW,UAAU,SAAY,SAAS,MAAM,GAAG,UAAU,KAAK,IAAI;AACrF,QAAM,WAAgB,cAAQ,MAAM;AACpC,QAAM,eAAoB,cAAQ,QAAQ;AAC1C,QAAM,cAAc,aAAa,SAAc,SAAG,IAAI,eAAe,eAAoB;AACzF,SAAO,aAAa,gBAAgB,SAAS,WAAW,WAAW;AACrE;AA8BO,SAAS,cAAc,KAAa,UAAiC;AAC1E,aAAW,EAAE,SAAS,UAAU,KAAK,kBAAkB;AACrD,QAAI,QAAQ,KAAK,GAAG,GAAG;AACrB,aAAO,GAAG,SAAS;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,QAAQ,qBAAqB,GAAG;AACtC,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AACA,aAAW,QAAQ,OAAO;AACxB,eAAW,KAAK,KAAK,OAAO;AAC1B,UAAI,CAAC,gBAAgB,GAAG,QAAQ,GAAG;AACjC,eAAO,GAAG,KAAK,EAAE,sCAAsC,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAaO,SAAS,qBAAqB,KAA4B;AAC/D,aAAW,EAAE,SAAS,UAAU,KAAK,kBAAkB;AACrD,QAAI,QAAQ,KAAK,GAAG,GAAG;AACrB,aAAO,GAAG,SAAS;AAAA,IACrB;AAAA,EACF;AACA,MAAI,gBAAgB,KAAK,GAAG,MAAM,SAAS,cAAc,KAAK,GAAG,MAAM,OAAO;AAC5E,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AC9HO,SAAS,qBAAqB,WAAyC;AAC5E,QAAM,MAAM,oBAAI,IAAyB;AACzC,aAAW,KAAK,WAAW;AACzB,UAAM,MAAM,GAAG,EAAE,aAAa,IAAI,EAAE,MAAM,IAAI,EAAE,UAAU;AAC1D,UAAM,WAAW,IAAI,IAAI,GAAG;AAC5B,QAAI,CAAC,UAAU;AACb,UAAI,IAAI,KAAK,CAAC;AAAA,IAChB,WAAW,EAAE,OAAO,WAAW,MAAM,KAAK,CAAC,SAAS,OAAO,WAAW,MAAM,GAAG;AAC7E,UAAI,IAAI,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI,OAAO,CAAC;AAChC;AAOO,SAAS,YAAY,WAA0B,QAA2C;AAC/F,QAAM,WAAW,UAAU,OAAO,CAAC,MAAM;AACvC,QAAI,OAAO,WAAW,QAAQ,EAAE,MAAM,OAAO,QAAS,QAAO;AAC7D,QAAI,OAAO,WAAW,QAAQ,EAAE,MAAM,OAAO,QAAS,QAAO;AAC7D,QAAI,OAAO,iBAAiB,QAAQ,EAAE,kBAAkB,OAAO,cAAe,QAAO;AACrF,WAAO;AAAA,EACT,CAAC;AACD,SAAO,qBAAqB,QAAQ;AACtC;;;ACzGA,SAAS,MAAM,aAAa;AAC5B,SAAS,SAAS,QAAAC,aAAY;AA8EvB,SAAS,qBAAqB,UAAkB,OAAuB;AAC5E,SAAOA;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,KAAK;AAAA,EACV;AACF;AAEO,SAAS,sBACd,UACA,YACA,MACQ;AACR,SAAOA;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,oBAAoB,UAAU,CAAC;AAAA,IAC7C,QAAQ,aAAa,IAAI,CAAC;AAAA,IAC1B;AAAA,EACF;AACF;AAEO,SAAS,4BAA4B,eAA+B;AACzE,MAAI,CAAC,cAAc,KAAK,GAAG;AACzB,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AACA,SAAO,GAAG,aAAa;AACzB;AAEO,SAAS,kBAAkB,MAAM,oBAAI,KAAK,GAAW;AAC1D,MAAI,OAAO,MAAM,IAAI,QAAQ,CAAC,GAAG;AAC/B,UAAM,IAAI,MAAM,2BAA2B;AAAA,EAC7C;AACA,SAAO,IAAI,YAAY,EAAE,QAAQ,UAAU,EAAE;AAC/C;AAEO,SAAS,uBAAuB,MAAc,IAAsB;AACzE,QAAM,YAAY,aAAa,IAAI;AACnC,QAAM,UAAU,aAAa,EAAE;AAC/B,QAAM,SAAS,aAAa,SAAS;AACrC,QAAM,MAAM,aAAa,OAAO;AAChC,MAAI,SAAS,KAAK;AAChB,UAAM,IAAI,MAAM,8BAA8B;AAAA,EAChD;AAEA,QAAM,QAAkB,CAAC;AACzB,SAAO,UAAU,KAAK;AACpB,UAAM,KAAK,cAAc,MAAM,CAAC;AAChC,WAAO,WAAW,OAAO,WAAW,IAAI,CAAC;AAAA,EAC3C;AACA,SAAO;AACT;AAEO,SAAS,6BAA6B,OAA0C;AACrF,2BAAyB,kBAAkB,MAAM,cAAc;AAC/D,2BAAyB,iBAAiB,MAAM,aAAa;AAC7D,SAAO,MAAM,iBAAiB,MAAM;AACtC;AAEO,SAAS,iCAAiC,OAA8C;AAC7F,2BAAyB,kBAAkB,MAAM,cAAc;AAC/D,QAAM,wBAAwB,MAAM,yBAAyB;AAC7D,2BAAyB,yBAAyB,qBAAqB;AACvE,SAAO,MAAM,iBAAiB;AAChC;AAEO,SAAS,6BAA6B,OAAwC;AACnF,uBAAqB,gBAAgB,MAAM,YAAY;AACvD,uBAAqB,gBAAgB,MAAM,YAAY;AACvD,eAAa,eAAe,MAAM,WAAW;AAE7C,SAAQ,MAAM,eAAe,MAAM,eAAgB,KAAK,IAAI,GAAG,MAAM,WAAW,IAAI;AACtF;AAEO,SAAS,0BAA0B,OAAqD;AAC7F,SAAO;AAAA,IACL,QAAQ,uBAAuB,MAAM,MAAM;AAAA,IAC3C,eAAe,gBAAgB,iBAAiB,MAAM,aAAa;AAAA,IACnE,YAAY,oBAAoB,MAAM,UAAU;AAAA,IAChD,MAAM,aAAa,MAAM,IAAI;AAAA,IAC7B,gBAAgB,cAAc,kBAAkB,MAAM,cAAc;AAAA,IACpE,eAAe,cAAc,iBAAiB,MAAM,aAAa;AAAA,IACjE,wBAAwB,cAAc,0BAA0B,MAAM,sBAAsB;AAAA,IAC5F,sBAAsB,cAAc,wBAAwB,MAAM,oBAAoB;AAAA,IACtF,eAAe,cAAc,iBAAiB,MAAM,aAAa;AAAA,IACjE,gBAAgB,MAAM,eAAe,IAAI,CAAC,UAAU,OAAO,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAAA,IACxF,WAAW,qBAAqB,aAAa,MAAM,SAAS;AAAA,IAC5D,aAAa,qBAAqB,eAAe,MAAM,WAAW;AAAA,EACpE;AACF;AAEO,SAAS,2BAA2B,OAAsC;AAC/E,SAAO,GAAG,KAAK,UAAU,0BAA0B,KAAK,CAAC,CAAC;AAAA;AAC5D;AAEO,SAAS,uBAAuB,QAAyC;AAC9E,QAAM,mBAAmB,gBAAgB,UAAU,MAAM,EAAE,YAAY;AACvE,QAAM,QAAQ,iBAAiB,MAAM,6CAA6C;AAClF,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,8BAA8B,MAAM,EAAE;AAAA,EACxD;AAEA,QAAM,SAAS,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAC/C,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,MAAM,CAAC;AAAA,IACf,YAAY,KAAK,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAAA,IACjD,OAAO,MAAM,CAAC,MAAM,MAAM,SAAS;AAAA,IACnC;AAAA,IACA,YAAY,OAAO,QAAQ,CAAC;AAAA,EAC9B;AACF;AAEO,SAAS,gCACd,SACA,MAC0B;AAC1B,QAAM,gBAAgB,aAAa,IAAI;AACvC,QAAM,QAAQ,oBAAI,IAEf;AAEH,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,uBAAuB,MAAM;AAC9C,UAAM,MAAM,GAAG,SAAS,MAAM,IAAI,SAAS,UAAU,IAAI,aAAa;AACtE,QAAI,QAAQ,MAAM,IAAI,GAAG;AACzB,QAAI,CAAC,OAAO;AACV,cAAQ;AAAA,QACN;AAAA,QACA,QAAQ,SAAS;AAAA,QACjB,YAAY,SAAS;AAAA,QACrB,MAAM;AAAA,QACN,WAAW,CAAC;AAAA,QACZ,kBAAkB,oBAAI,IAAI;AAAA,MAC5B;AACA,YAAM,IAAI,KAAK,KAAK;AAAA,IACtB;AACA,UAAM,iBAAiB,IAAI,SAAS,QAAQ,QAAQ;AAAA,EACtD;AAEA,SAAO,CAAC,GAAG,MAAM,OAAO,CAAC,EACtB,IAAI,CAAC,WAAW;AAAA,IACf,KAAK,MAAM;AAAA,IACX,QAAQ,MAAM;AAAA,IACd,YAAY,MAAM;AAAA,IAClB,MAAM,MAAM;AAAA,IACZ,WAAW,CAAC,GAAG,MAAM,iBAAiB,OAAO,CAAC,EAAE;AAAA,MAAK,CAAC,MAAM,UAC1D,KAAK,OAAO,cAAc,MAAM,MAAM;AAAA,IACxC;AAAA,EACF,EAAE,EACD,KAAK,CAAC,MAAM,UAAU,KAAK,IAAI,cAAc,MAAM,GAAG,CAAC;AAC5D;AAEO,SAAS,iCACd,OAC4B;AAC5B,QAAM,uBAAuB,oBAAI,IAAyB;AAC1D,aAAW,OAAO,MAAM,YAAY;AAClC,UAAM,SAAS,gBAAgB,iBAAiB,IAAI,MAAM,EAAE,YAAY;AACxE,UAAM,OAAO,IAAI,UAAU,MAAM,IAAI,EAAE;AACvC,QAAI,CAAC,KAAM;AACX,QAAI,eAAe,qBAAqB,IAAI,MAAM;AAClD,QAAI,CAAC,cAAc;AACjB,qBAAe,oBAAI,IAAI;AACvB,2BAAqB,IAAI,QAAQ,YAAY;AAAA,IAC/C;AACA,iBAAa,IAAI,IAAI;AAAA,EACvB;AAEA,QAAM,YAAwC,CAAC;AAC/C,aAAW,YAAY,MAAM,MAAM,WAAW;AAC5C,UAAM,gBAAgB,MAAM,sBAAsB,IAAI,SAAS,MAAM,KAAK,oBAAI,IAAY;AAC1F,UAAM,eAAe,qBAAqB,IAAI,SAAS,MAAM,KAAK,oBAAI,IAAY;AAClF,QAAI,aAAa,SAAS,KAAK,CAAC,MAAM,4BAA4B;AAChE;AAAA,IACF;AACA,UAAM,eAAe,CAAC,GAAG,aAAa,EACnC,OAAO,CAAC,SAAS,CAAC,aAAa,IAAI,IAAI,CAAC,EACxC,KAAK;AACR,QAAI,aAAa,SAAS,GAAG;AAC3B,gBAAU,KAAK,EAAE,UAAU,aAAa,CAAC;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,kCACpB,cACA,MACe;AACf,QAAMC,SAAO,gBAAgB,gBAAgB,YAAY;AACzD,QAAM,OAAO,OAAO,IAAI;AACxB,MAAI,CAAC,KAAK,SAAS,IAAI,GAAG;AACxB,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AACA,QAAM,YAAY,QAAQA,MAAI;AAC9B,QAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,QAAM,oBAAoB,MAAM,WAAWA,MAAI;AAC/C,QAAM,SAAS,MAAM,KAAKA,QAAM,GAAG;AACnC,MAAI;AACF,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,OAAO,KAAK;AAAA,EACpB,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACA,MAAI,CAAC,mBAAmB;AACtB,UAAM,yBAAyB,SAAS;AAAA,EAC1C;AACF;AAEO,SAAS,yBAAyB,OAA2C;AAClF,QAAM,gBAAgB,sBAAsB,MAAM,aAAa;AAC/D,QAAM,sBAAsB,sBAAsB,MAAM,mBAAmB;AAC3E,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAqBE,aAAa;AAAA,gBACR,mBAAmB;AAAA;AAAA;AAAA,IAG/B,KAAK;AACT;AAEA,SAAS,qBAAqB,MAAc,OAAqB;AAC/D,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,GAAG;AACzC,UAAM,IAAI,MAAM,GAAG,IAAI,mCAAmC;AAAA,EAC5D;AACF;AAEA,SAAS,aAAa,MAAc,OAAqB;AACvD,MAAI,CAAC,OAAO,SAAS,KAAK,GAAG;AAC3B,UAAM,IAAI,MAAM,GAAG,IAAI,0BAA0B;AAAA,EACnD;AACF;AAEA,SAAS,yBAAyB,MAAc,OAAqB;AACnE,MAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAG;AACzC,UAAM,IAAI,MAAM,GAAG,IAAI,iCAAiC;AAAA,EAC1D;AACF;AAEA,SAAS,cAAc,MAAc,OAAuB;AAC1D,2BAAyB,MAAM,KAAK;AACpC,SAAO;AACT;AAEA,SAAS,uBAAuB,OAAuD;AACrF,MACE,UAAU,cACP,UAAU,eACV,UAAU,YACV,UAAU,6BACb;AACA,WAAO;AAAA,EACT;AACA,QAAM,IAAI,MAAM,0EAA0E;AAC5F;AAEA,eAAe,WAAWA,QAAgC;AACxD,MAAI;AACF,UAAM,SAAS,MAAM,KAAKA,QAAM,GAAG;AACnC,UAAM,OAAO,MAAM;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,yBAAyB,SAAgC;AACtE,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,KAAK,SAAS,GAAG;AAChC,UAAM,OAAO,KAAK;AAAA,EACpB,QAAQ;AAAA,EAGR,UAAE;AACA,QAAI,OAAQ,OAAM,OAAO,MAAM,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACjD;AACF;AAEA,SAAS,gBAAgB,MAAc,OAAuB;AAC5D,QAAM,OAAO,OAAO,SAAS,EAAE,EAAE,KAAK;AACtC,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,GAAG,IAAI,oBAAoB;AACtD,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAuB;AAClD,SAAO,gBAAgB,cAAc,KAAK,EAAE,YAAY;AAC1D;AAEA,SAAS,sBAAsB,OAAuB;AACpD,QAAM,OAAO,gBAAgB,kBAAkB,KAAK;AACpD,MAAI,CAAC,2BAA2B,KAAK,IAAI,GAAG;AAC1C,UAAM,IAAI,MAAM,oEAAoE;AAAA,EACtF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,OAAuB;AAC3C,QAAM,OAAO,gBAAgB,QAAQ,KAAK;AAC1C,MAAI,CAAC,sBAAsB,KAAK,IAAI,GAAG;AACrC,UAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AACA,QAAM,SAAS,aAAa,IAAI;AAChC,MAAI,OAAO,MAAM,OAAO,QAAQ,CAAC,KAAK,cAAc,MAAM,MAAM,MAAM;AACpE,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,MAAc,OAAuB;AACjE,QAAM,OAAO,gBAAgB,MAAM,KAAK;AACxC,QAAM,SAAS,IAAI,KAAK,IAAI;AAC5B,MAAI,OAAO,MAAM,OAAO,QAAQ,CAAC,KAAK,OAAO,YAAY,MAAM,MAAM;AACnE,UAAM,IAAI,MAAM,GAAG,IAAI,2BAA2B;AAAA,EACpD;AACA,SAAO;AACT;AAEA,SAAS,aAAa,MAAoB;AACxC,SAAO,oBAAI,KAAK,GAAG,IAAI,gBAAgB;AACzC;AAEA,SAAS,cAAc,MAAoB;AACzC,QAAM,OAAO,KAAK,eAAe;AACjC,QAAM,KAAK,OAAO,KAAK,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACzD,QAAM,KAAK,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACpD,SAAO,GAAG,IAAI,IAAI,EAAE,IAAI,EAAE;AAC5B;;;ACnaA,SAAS,KAAAC,UAAS;;;ACgJX,SAAS,YAAY,MAAc,IAAsB;AAC9D,QAAM,OAAiB,CAAC;AACxB,QAAM,IAAI,oBAAI,KAAK,OAAO,YAAY;AACtC,QAAM,MAAM,oBAAI,KAAK,KAAK,YAAY;AACtC,SAAO,KAAK,KAAK;AACf,UAAM,MAAM,EAAE,UAAU;AACxB,QAAI,QAAQ,KAAK,QAAQ,GAAG;AAC1B,WAAK,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,IACxC;AACA,MAAE,WAAW,EAAE,WAAW,IAAI,CAAC;AAAA,EACjC;AACA,SAAO;AACT;;;AD9FO,IAAM,6BAA6BC,GAAE,OAAO;AAAA,EACjD,UAAUA,GAAE,OAAO,EAAE,SAAS,qBAAqB;AAAA,EACnD,MAAMA,GACH,KAAK,CAAC,WAAW,UAAU,CAAC,EAC5B,QAAQ,SAAS,EACjB,SAAS,uEAAuE;AAAA,EACnF,aAAaA,GACV,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,SAAS,EACT,SAAS,qFAAqF;AAAA,EACjG,aAAaA,GACV,QAAQ,EACR,QAAQ,IAAI,EACZ,SAAS,uFAAuF;AAAA,EACnG,UAAUA,GACP,QAAQ,EACR,QAAQ,KAAK,EACb,SAAS,kFAAkF;AAAA,EAC9F,UAAUA,GACP,OAAO,EACP,SAAS,EACT,SAAS,kEAAkE;AAChF,CAAC;AAMD,IAAM,eAAuC;AAAA,EAC3C,OAAO;AAAA,EACP,OAAO;AACT;AAEA,IAAM,eAAyB,CAAC,SAAS,QAAQ,SAAS,SAAS,YAAY,gBAAgB,SAAS,OAAO;AAExG,SAAS,gBACd,SACA,UACqB;AACrB,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,KAAK,SAAS;AACvB,UAAM,aAAc,CAAC,YAAY,aAAa,EAAE,MAAM,KAAM,EAAE;AAC9D,WAAO,IAAI,aAAa,OAAO,IAAI,UAAU,KAAK,KAAK,EAAE,QAAQ;AAAA,EACnE;AACA,SAAO;AACT;AAEO,SAAS,mBACd,QACA,UACA,sBACoB;AACpB,QAAM,UAA8B,CAAC;AACrC,aAAW,CAAC,QAAQ,GAAG,KAAK,QAAQ;AAClC,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,KAAK,KAAK,MAAM,MAAM,GAAG,IAAI;AAAA,MAC7B,KAAK,aAAa,IAAI,KAAK,MAAO,MAAM,WAAY,GAAI,IAAI,KAAK;AAAA,MACjE,GAAI,wBAAwB,yBAAyB,IACjD,EAAE,cAAc,KAAK,MAAO,MAAM,uBAAwB,GAAI,IAAI,GAAG,IACrE,CAAC;AAAA,IACP,CAAC;AAAA,EACH;AACA,UAAQ,KAAK,CAAC,GAAG,MAAM;AACrB,UAAM,KAAK,aAAa,QAAQ,EAAE,MAAM;AACxC,UAAM,KAAK,aAAa,QAAQ,EAAE,MAAM;AACxC,YAAQ,OAAO,KAAK,KAAK,OAAO,OAAO,KAAK,KAAK;AAAA,EACnD,CAAC;AACD,SAAO;AACT;AAEO,SAAS,4BAA4B,QAAqC;AAC/E,MAAI,QAAQ;AACZ,aAAW,OAAO,OAAO,OAAO,GAAG;AACjC,aAAS,KAAK,IAAI,GAAG;AAAA,EACvB;AACA,SAAO;AACT;AAEO,SAAS,gBACd,aACA,UAC8C;AAC9C,MAAI,aAAa,EAAG,QAAO,EAAE,WAAW,OAAO;AAC/C,QAAM,cAAc,KAAK,IAAI,cAAc,QAAQ,IAAI;AACvD,MAAI,cAAc,IAAI;AACpB,WAAO;AAAA,MACL,WAAW;AAAA,MACX,MAAM,eAAe,KAAK,MAAM,WAAW,CAAC;AAAA,IAC9C;AAAA,EACF;AACA,SAAO,EAAE,WAAW,OAAO;AAC7B;AAMA,eAAsB,2BACpB,QACA,SACA,QACA,cAC+D;AAC/D,QAAM,EAAE,UAAU,MAAM,aAAa,aAAa,UAAU,SAAS,IAAI;AAEzE,MAAI,SAAS,YAAY;AACvB,QAAI,eAAe,MAAM;AACvB,YAAM,IAAI,MAAM,2CAA2C;AAAA,IAC7D;AACA,WAAO,mBAAmB,UAAU,aAAa,aAAa,UAAU,SAAS,QAAQ,YAAY;AAAA,EACvG;AAEA,SAAO,kBAAkB,UAAU,aAAa,UAAU,UAAU,SAAS,QAAQ,YAAY;AACnG;AAEA,eAAe,kBACb,UACA,aACA,UACA,UACA,SACA,QACA,cACmC;AACnC,QAAM,OAAO,gBAAgB,MAAM,cAAc,OAAO;AAExD,QAAM,sBAAsB,WACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAQA;AAAA;AAAA;AAAA;AAIJ,QAAM,uBAAuB,WAAW,CAAC,UAAU,QAAQ,IAAI,CAAC,QAAQ;AACxE,QAAM,uBAAuB,MAAM,KAAK,cAAc,qBAAqB,oBAAoB;AAC/F,QAAM,iBAAiB,qBAAqB,QAAQ,EAAE,IAAI,CAAC,SAAS;AAAA,IAClE,YAAY,OAAO,IAAI,CAAC,KAAK,CAAC;AAAA,IAC9B,UAAU,OAAO,IAAI,CAAC,KAAK,CAAC;AAAA,EAC9B,EAAE;AACF,QAAM,cAAc,eAAe;AAEnC,MAAI,gBAAgB,GAAG;AACrB,UAAM,IAAI;AAAA,MACR,WACI,8BAA8B,QAAQ,oBAAoB,QAAQ,MAClE,8BAA8B,QAAQ;AAAA,IAC5C;AAAA,EACF;AAEA,QAAM,cAAc,oBAAI,IAAoB;AAC5C,MAAI,aAAa;AACjB,MAAI,UAAU;AACd,MAAI,iBAAiB;AACrB,MAAI,eAAe;AAInB,QAAM,aAAa;AACnB,WAAS,QAAQ,GAAG,QAAQ,aAAa,SAAS,YAAY;AAC5D,UAAM,WAAW,KAAK,IAAI,QAAQ,YAAY,WAAW;AACzD,UAAM,WAAW,CAAC;AAClB,aAAS,IAAI,OAAO,IAAI,UAAU,KAAK;AACrC,YAAM,QAAQ,eAAe,CAAC;AAC9B,eAAS;AAAA,QACP;AAAA,UACE;AAAA,YACE;AAAA,YACA,aAAa,MAAM;AAAA,YACnB,QAAQ;AAAA,YACR,YAAY;AAAA,YACZ;AAAA,UACF;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,EAAE,KAAK,YAAU;AACf,qBAAW,UAAU,OAAO,SAAS;AACnC,wBAAY,IAAI,OAAO,SAAS,YAAY,IAAI,OAAO,MAAM,KAAK,KAAK,OAAO,QAAQ;AAAA,UACxF;AACA,4BAAkB,MAAM;AACxB,0BAAgB,OAAO;AACvB;AAAA,QACF,CAAC,EAAE,MAAM,MAAM;AACb;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,QAAQ,WAAW,QAAQ;AAAA,EACnC;AAEA,MAAI,eAAe,GAAG;AACpB,WAAO;AAAA,MACL;AAAA,MACA,mBAAmB;AAAA,MACnB,gBAAgB;AAAA,MAChB,cAAc;AAAA,MACd,WAAW;AAAA,MACX,gBAAgB;AAAA,MAChB,gBAAgB;AAAA,MAChB,wBAAwB;AAAA,MACxB,aAAa,CAAC;AAAA,MACd,WAAW;AAAA,MACX,MAAM;AAAA,IACR;AAAA,EACF;AAEA,QAAM,YAAY;AAAA,IAChB,CAAC,GAAG,YAAY,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,QAAQ,QAAQ,OAAO;AAAA,MACtD;AAAA,MACA;AAAA,MACA,YAAY;AAAA,MACZ,OAAO,CAAC;AAAA,IACV,EAAE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,uBAAuB,4BAA4B,SAAS;AAClE,QAAM,cAAc,mBAAmB,WAAW,gBAAgB,oBAAoB;AACtF,QAAM,cAAc,UAAU,IAAI,UAAU,KAAK;AACjD,QAAM,gBAAgB,yBAAyB,IAAI,uBAAuB;AAC1E,QAAM,EAAE,WAAW,KAAK,IAAI,gBAAgB,aAAa,aAAa;AACtE,QAAM,gBAAgB,iBAAiB;AAIvC,QAAM,QAAkB,CAAC;AACzB,MAAI,KAAM,OAAM,KAAK,IAAI;AACzB,QAAM,YAAY,KAAK,IAAI,cAAc,IAAI,OACzC,KAAK,IAAI,aAAa,IAAI,KAAK,IAAI,cAAc,IACjD;AACJ,MAAI,YAAY,GAAG;AACjB,UAAM;AAAA,MACJ,qBAAqB,KAAK,MAAM,SAAS,CAAC,iGAEzC,cACG,wDACA;AAAA,IACN;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,mBAAmB;AAAA,IACnB,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,WAAW,KAAK,MAAM,iBAAiB,GAAG,IAAI;AAAA,IAC9C,gBAAgB,KAAK,MAAM,eAAe,GAAG,IAAI;AAAA,IACjD,gBAAgB,KAAK,MAAM,gBAAgB,GAAG,IAAI;AAAA,IAClD,wBAAwB,KAAK,MAAM,uBAAuB,GAAG,IAAI;AAAA,IACjE;AAAA,IACA;AAAA,IACA,GAAI,MAAM,SAAS,IAAI,EAAE,MAAM,MAAM,KAAK,GAAG,EAAE,IAAI,CAAC;AAAA,EACtD;AACF;AAEA,eAAe,mBACb,UACA,aACA,aACA,UACA,SACA,QACA,cACoC;AACpC,QAAM,OAAO,gBAAgB,MAAM,cAAc,OAAO;AAGxD,QAAM,cAAc,MAAM,KAAK;AAAA,IAC7B;AAAA;AAAA;AAAA;AAAA,IAIA,CAAC,UAAU,WAAW;AAAA,EACxB;AACA,QAAM,YAAY,YAAY,QAAQ;AACtC,MAAI,UAAU,WAAW,GAAG;AAC1B,UAAM,IAAI,MAAM,eAAe,WAAW,wBAAwB,QAAQ,GAAG;AAAA,EAC/E;AACA,QAAM,YAAY,OAAO,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE;AAC9C,QAAM,YAAY,OAAO,UAAU,CAAC,EAAE,CAAC,KAAK,SAAS;AACrD,QAAM,YAAY,OAAO,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC;AAG7C,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,MACE;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,YAAY,OAAO;AAOzB,QAAM,eAAe,YAAY,WAAW,SAAS;AACrD,QAAM,cAAc,CAAC,MACnB,IAAI,aAAa,SAAS,aAAa,CAAC,IAAI,OAAO,CAAC;AAGtD,QAAM,cAAc,oBAAI,IAAsB;AAC9C,aAAW,KAAK,OAAO,SAAS;AAC9B,gBAAY,IAAI,EAAE,QAAQ,EAAE,KAAK;AAAA,EACnC;AAGA,QAAM,QAAgC,CAAC;AACvC,WAAS,IAAI,GAAG,KAAK,WAAW,KAAK;AACnC,UAAM,QAA8B;AAAA,MAClC,MAAM,YAAY,CAAC;AAAA,MACnB,OAAO,aAAa,aAAa,SAAS,GAAG,WAAW,IAAK,YAAY,IAAI,OAAO,IAAI,CAAC,KAAK,CAAE;AAAA,MAChG,OAAO,aAAa,aAAa,SAAS,GAAG,CAAC;AAAA,MAC9C,OAAO,aAAa,aAAa,SAAS,GAAG,CAAC;AAAA,MAC9C,MAAM,aAAa,aAAa,QAAQ,GAAG,WAAW,IAAK,YAAY,IAAI,OAAO,IAAI,CAAC,KAAK,CAAE;AAAA,MAC9F,UAAU,aAAa,aAAa,YAAY,GAAG,CAAC;AAAA,IACtD;AAEA,QAAI,YAAY,IAAI,cAAc,GAAG;AACnC,YAAM,eAAe,aAAa,aAAa,gBAAgB,GAAG,CAAC;AAAA,IACrE;AACA,QAAI,UAAU;AACZ,YAAM,QAAQ,YAAY,IAAI,OAAO,IAAI,CAAC,KAAK;AAC/C,YAAM,QAAQ,YAAY,IAAI,OAAO,IAAI,CAAC,KAAK;AAAA,IACjD;AACA,UAAM,KAAK,KAAK;AAAA,EAClB;AAGA,QAAM,YAAY,gBAAgB,OAAO,SAAS,QAAQ;AAC1D,QAAM,uBAAuB,4BAA4B,SAAS;AAClE,QAAM,cAAc,mBAAmB,WAAW,WAAW,oBAAoB;AACjF,QAAM,gBAAgB,YAAY,OAAO;AAEzC,QAAM,gBAAgB,kBAAkB,KAAK;AAE7C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,WAAW,KAAK,MAAM,YAAY,GAAG,IAAI;AAAA,IACzC,gBAAgB,KAAK,MAAM,OAAO,iBAAiB,GAAG,IAAI;AAAA,IAC1D,gBAAgB,KAAK,MAAM,gBAAgB,GAAG,IAAI;AAAA,IAClD,wBAAwB,KAAK,MAAM,uBAAuB,GAAG,IAAI;AAAA,IACjE,OAAO;AAAA,IACP;AAAA,EACF;AACF;AAMO,SAAS,kBAAkB,OAAuD;AACvF,SAAO,MAAM;AAAA,IAAO,OAClB,EAAE,UAAU,KAAK,EAAE,UAAU,KAAK,EAAE,UAAU,KAAK,EAAE,SAAS,KAC9D,EAAE,aAAa,MAAM,EAAE,gBAAgB,OAAO,MAC7C,EAAE,SAAS,OAAO,MAAM,EAAE,SAAS,OAAO;AAAA,EAC7C;AACF;AAEA,SAAS,aACP,aACA,QACA,OACA,mBACQ;AACR,SAAO,KAAK,QAAQ,YAAY,IAAI,MAAM,IAAI,KAAK,KAAK,KAAK,qBAAqB,GAAG,IAAI;AAC3F;;;AEjcA,SAAS,cAAAC,mBAAkB;AAC3B,YAAYC,WAAU;;;ACMtB,SAAS,cAAAC,aAAY,eAAAC,oBAAmB;AACxC,YAAYC,WAAU;AAef,SAAS,oBAAoB,KAAa,cAAgC;AAC/E,MAAI,CAACF,YAAW,GAAG,EAAG,QAAO,CAAC;AAC9B,MAAI;AACF,UAAM,SAAS,GAAG,YAAY;AAC9B,WAAOC,aAAY,GAAG,EACnB,OAAO,CAAC,UAAU,MAAM,WAAW,MAAM,CAAC,EAC1C,OAAO,CAAC,UAAUD,YAAgB,WAAK,KAAK,OAAO,cAAc,CAAC,CAAC,EACnE,IAAI,CAAC,UAAU,MAAM,MAAM,OAAO,MAAM,CAAC,EACzC,KAAK;AAAA,EACV,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;;;AD7BA,SAASG,kBAAiB,OAAuB;AAC/C,SAAO,MAAM,QAAQ,MAAM,IAAI;AACjC;AAEO,IAAe,YAAf,MAAyB;AAAA,EACX;AAAA,EACnB,YAAY,KAAmB;AAC7B,SAAK,MAAM;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,8BACR,QACA,MACA,IACA,MACwB;AACxB,UAAM,YAAiB,WAAK,iBAAiB,KAAK,IAAI,OAAO,GAAG,QAAQ,UAAU,MAAM,EAAE;AAC1F,QAAI,CAACC,YAAW,SAAS,EAAG,QAAO;AACnC,UAAM,WAAW,oBAAoB,WAAW,MAAM;AACtD,UAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,KAAK,QAAQ,KAAK,EAAE;AACzD,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,QAAkB,CAAC;AACzB,eAAW,KAAK,OAAO;AACrB,YAAM,IAAS,WAAK,WAAW,QAAQ,CAAC,IAAI,cAAc;AAC1D,UAAIA,YAAW,CAAC,EAAG,OAAM,KAAK,CAAC;AAAA,IACjC;AACA,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,WAAW,MAAM,IAAI,OAAK,IAAID,kBAAiB,CAAC,CAAC,GAAG,EAAE,KAAK,IAAI;AACrE,UAAM,YAAY,IAAIA,kBAAiB,MAAM,CAAC;AAC9C,QAAI,MAAM,UAAU;AAClB,aAAO;AAAA,QACL,KAAK,UAAU,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAOG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAcrC;AAAA,IACF;AACA,UAAM,YAAY,MAAM,UAAU,4CAA4C;AAC9E,WAAO;AAAA,MACL,KAAK,UAAU,SAAS;AAAA,iCACG,QAAQ;AAAA,wBACjB,SAAS;AAAA;AAAA,IAE7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,IAAW,UAAkB;AAC3B,WAAO,KAAK,IAAI;AAAA,EAClB;AA2BF;;;AEjGO,IAAe,gBAAf,MAA6B;AAAA,EACf;AAAA,EACnB,YAAY,KAAmB;AAC7B,SAAK,MAAM;AAAA,EACb;AAMF;;;ACpBA,SAAS,IAAI,OAAuB;AAClC,SAAO,IAAI,iBAAiB,KAAK,CAAC;AACpC;AAOO,SAAS,kBACd,YACA,MACU;AACV,SAAO;AAAA,IACL,KAAK;AAAA;AAAA,+BAEsB,IAAI,UAAU,CAAC,eAAe,IAAI,IAAI,CAAC;AAAA;AAAA,EAEpE;AACF;AAYO,SAAS,uBACd,YACA,OACU;AACV,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,QAAM,WAAW,MAAM,IAAI,GAAG,EAAE,KAAK,IAAI;AACzC,SAAO;AAAA,IACL,KAAK;AAAA;AAAA,+BAEsB,IAAI,UAAU,CAAC,iBAAiB,QAAQ;AAAA;AAAA,EAErE;AACF;;;AChDO,IAAe,aAAf,MAA0B;AAAA,EACZ;AAAA,EACnB,YAAY,KAAmB;AAC7B,SAAK,MAAM;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBA,MAAM,SAAS,YAAoB,MAAgC;AAGjE,UAAM,gBAAgB,WAAW,QAAQ,MAAM,IAAI;AACnD,UAAM,UAAU,KAAK,QAAQ,MAAM,IAAI;AACvC,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC;AAAA,8BACwB,aAAa,iBAAiB,OAAO;AAAA;AAAA,IAE/D;AACA,WAAO,OAAO,QAAQ,EAAE,SAAS;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,eACJ,YACA,OACwB;AACxB,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,UAAM,EAAE,IAAI,IAAI,uBAAuB,YAAY,KAAK;AACxD,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,WAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,MAClC,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MACvB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,eAAe,OAAO,EAAE,CAAC,CAAC;AAAA,MAC1B,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MACvB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,gBAAgB,OAAO,EAAE,CAAC,CAAC;AAAA,IAC7B,EAAE;AAAA,EACJ;AAGF;;;ACtDO,IAAe,aAAf,MAA0B;AAAA,EACZ;AAAA,EACnB,YAAY,KAAmB;AAC7B,SAAK,MAAM;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,IAAW,UAAU;AACnB,WAAO,KAAK,IAAI;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0CU,yBACR,eACuC;AACvC,UAAM,eAAe,oBAAI,IAAsC;AAC/D,eAAW,CAAC,MAAM,OAAO,KAAK,eAAe;AAC3C,iBAAW,UAAU,SAAS;AAC5B,cAAM,aAAa,KAAK,IAAI,QAAQ,QAAQ,YAAY,MAAM,CAAC;AAC/D,YAAI,UAAU,aAAa,IAAI,UAAU;AACzC,YAAI,CAAC,SAAS;AACZ,oBAAU,oBAAI,IAAI;AAClB,uBAAa,IAAI,YAAY,OAAO;AAAA,QACtC;AACA,YAAI,cAAc,QAAQ,IAAI,IAAI;AAClC,YAAI,CAAC,aAAa;AAChB,wBAAc,oBAAI,IAAY;AAC9B,kBAAQ,IAAI,MAAM,WAAW;AAAA,QAC/B;AACA,oBAAY,IAAI,MAAM;AAAA,MACxB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,eACJ,eACA,WACA,SACkC;AAClC,UAAM,MAAM,oBAAI,IAAwB;AACxC,QAAI,cAAc,SAAS,EAAG,QAAO;AAErC,eAAW,CAAC,EAAE,OAAO,KAAK,KAAK,yBAAyB,aAAa,GAAG;AACtE,iBAAW,CAAC,MAAM,IAAI,KAAK,SAAS;AAClC,YAAI,KAAK,SAAS,EAAG;AACrB,cAAM,cAAc,MAAM,KAAK,WAAW,CAAC,GAAG,IAAI,GAAG,MAAM,IAAI;AAC/D,mBAAW,CAAC,KAAK,MAAM,KAAK,aAAa;AACvC,cAAI,MAAM,IAAI,IAAI,GAAG;AACrB,cAAI,CAAC,KAAK;AACR,kBAAM,CAAC;AACP,gBAAI,IAAI,KAAK,GAAG;AAAA,UAClB;AACA,qBAAW,SAAS,QAAQ;AAC1B,kBAAM,WAAW,MAAM,UAAU,QAAQ,GAAG;AAC5C,kBAAM,OAAO,aAAa,KAAK,KAAK,MAAM,UAAU,MAAM,WAAW,CAAC;AACtE,gBAAI,OAAO,aAAa,OAAO,QAAS;AACxC,gBAAI,KAAK,KAAK;AAAA,UAChB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAkBF;;;AC9JA,SAAS,cAAAE,mBAAkB;AAC3B,YAAYC,WAAU;;;AC0BtB,SAASC,KAAI,OAAuB;AAClC,SAAO,IAAI,iBAAiB,KAAK,CAAC;AACpC;AAMO,SAAS,iBACd,QACA,MACA,IACU;AACV,SAAO;AAAA,IACL,KAAK;AAAA;AAAA,2BAEkBA,KAAI,MAAM,CAAC,gBAAgBA,KAAI,IAAI,CAAC,gBAAgBA,KAAI,EAAE,CAAC;AAAA;AAAA,EAEpF;AACF;AASO,SAAS,sBACd,QACA,MACA,IACU;AACV,SAAO;AAAA,IACL,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAUkBA,KAAI,MAAM,CAAC;AAAA,0BACZA,KAAI,IAAI,CAAC,gBAAgBA,KAAI,EAAE,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYxD;AACF;AAQO,SAAS,qBACd,QACA,MACA,IACU;AACV,SAAO;AAAA,IACL,KAAK;AAAA;AAAA,2BAEkBA,KAAI,MAAM,CAAC;AAAA,0BACZA,KAAI,IAAI,CAAC,gBAAgBA,KAAI,EAAE,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQxD;AACF;;;ADpGO,IAAM,mBAAN,cAA+B,UAAU;AAAA,EAC9C,MAAM,UACJ,QACA,MACA,MACe;AACf,QAAI,KAAK,WAAW,EAAG;AAiBvB,UAAM,WAAU,oBAAI,KAAK,GAAG,IAAI,YAAY,GAAE,UAAU;AACxD,QAAI,YAAY,KAAK,YAAY,GAAG;AAClC,cAAQ;AAAA,QACN,8DACa,MAAM,SAAS,IAAI,SAAS,KAAK,MAAM;AAAA,MACtD;AACA;AAAA,IACF;AAEA,UAAM,YAAY,KAAK;AAAA,MACrB,CAAC,MACC,OAAO,SAAS,EAAE,IAAI,KAAK,EAAE,OAAO,KACpC,OAAO,SAAS,EAAE,IAAI,KAAK,EAAE,OAAO,KACpC,OAAO,SAAS,EAAE,GAAG,KAAM,EAAE,MAAO,KACpC,OAAO,SAAS,EAAE,KAAK,KAAI,EAAE,QAAO;AAAA,IACxC;AACA,UAAM,UAAU,KAAK,SAAS,UAAU;AACxC,QAAI,UAAU,GAAG;AACf,cAAQ;AAAA,QACN,uCAAuC,OAAO,IAAI,KAAK,MAAM,kDACf,MAAM,SAAS,IAAI;AAAA,MACnE;AAAA,IACF;AACA,QAAI,UAAU,WAAW,GAAG;AAC1B,cAAQ;AAAA,QACN,yDAAoD,KAAK,MAAM,0BACzC,MAAM,SAAS,IAAI;AAAA,MAC3C;AACA;AAAA,IACF;AACA,WAAO;AACP,UAAM,UAAU,eAAe,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAChF,UAAM,KAAK,IAAI,KAAK;AAAA,MAClB,sBAAsB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAK/B;AACA,QAAI;AACF,YAAM,eAAe,KAClB,IAAI,CAAC,GAAG,MAAM;AACb,cAAM,IAAI,IAAI;AACd,eAAO,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC;AAAA,MACnG,CAAC,EACA,KAAK,IAAI;AACZ,YAAM,SAAoB,KAAK,QAAQ,CAAC,MAAM;AAAA,QAC5C;AAAA,QACA;AAAA,QACA,EAAE,QAAQ;AAAA,QACV,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE,OAAO;AAAA,QACT,EAAE,OAAO;AAAA,MACX,CAAC;AACD,YAAM,KAAK,IAAI,KAAK;AAAA,QAClB,gBAAgB,OAAO,YAAY,YAAY;AAAA,QAC/C;AAAA,MACF;AACA,YAAM,mBAAmB,KAAK,IAAI,MAAM;AAAA,QACtC,SAAS,KAAK,IAAI;AAAA,QAClB;AAAA,QACA;AAAA,QACA,aAAa,kBAAkB,OAAO;AAAA,MACxC,CAAC;AAAA,IACH,UAAE;AACA,UAAI;AACF,cAAM,KAAK,IAAI,KAAK,IAAI,yBAAyB,OAAO,GAAG;AAAA,MAC7D,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,WACA,WAC+B;AAC/B,WAAO,mBAAmB,KAAK,IAAI,MAAM;AAAA,MACvC,SAAS,KAAK,IAAI;AAAA,MAClB,QAAQ,UAAU;AAAA,MAClB,MAAM,UAAU;AAAA,MAChB,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SACJ,QACA,MACA,IACmB;AACnB,UAAM,SAAS,KAAK,8BAA8B,QAAQ,MAAM,EAAE;AAGlE,UAAM,EAAE,IAAI,IAAI,UAAU,iBAAiB,QAAQ,MAAM,EAAE;AAC3D,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,WAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,MAClC,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,OAAO,OAAO,EAAE,CAAC,CAAC;AAAA,MAClB,KAAK,EAAE,CAAC,KAAK,OAAO,SAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MAC3C,KAAK,EAAE,CAAC,KAAK,OAAO,SAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MAC3C,QAAQ;AAAA,IACV,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,cACJ,QACA,MACA,IACmB;AACnB,UAAM,SAAS,KAAK,8BAA8B,QAAQ,MAAM,IAAI,EAAE,UAAU,KAAK,CAAC;AAEtF,UAAM,EAAE,IAAI,IAAI,UAAU,sBAAsB,QAAQ,MAAM,EAAE;AAChE,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,WAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,MAClC,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,MAAM;AAAA,MACN,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,OAAO,OAAO,EAAE,CAAC,CAAC;AAAA,MAClB,KAAK,EAAE,CAAC,KAAK,OAAO,SAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MAC3C,KAAK,EAAE,CAAC,KAAK,OAAO,SAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MAC3C,QAAQ;AAAA,IACV,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,YACJ,QACA,MACA,IACyB;AACzB,UAAM,YAAiB;AAAA,MACrB,iBAAiB,KAAK,IAAI,OAAO;AAAA,MACjC;AAAA,MACA,UAAU,MAAM;AAAA,IAClB;AACA,QAAI,CAACC,YAAW,SAAS,GAAG;AAC1B,aAAO,EAAE,UAAU,MAAM,QAAQ,MAAM,cAAc,CAAC,GAAG,YAAY,EAAE;AAAA,IACzE;AACA,UAAM,WAAW,oBAAoB,WAAW,MAAM;AACtD,UAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,KAAK,QAAQ,KAAK,EAAE;AACzD,WAAO;AAAA,MACL,UAAU,MAAM,CAAC,KAAK;AAAA,MACtB,QAAQ,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,MACnC,cAAc,CAAC;AAAA,MACf,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACF;;;AE3LO,IAAM,kBAAN,cAA8B,UAAU;AAAA,EAC7C,MAAM,UACJ,QACA,MACA,MACe;AACf,QAAI,KAAK,WAAW,EAAG;AACvB,UAAM,eAAe,KAClB,IAAI,CAAC,GAAG,MAAM;AACb,YAAM,IAAI,IAAI;AACd,aAAO,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC;AAAA,IACnG,CAAC,EACA,KAAK,IAAI;AACZ,UAAM,SAAoB,KAAK,QAAQ,CAAC,MAAM;AAAA,MAC5C;AAAA,MACA;AAAA,MACA,EAAE,QAAQ;AAAA,MACV,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE,OAAO;AAAA,MACT,EAAE,OAAO;AAAA,IACX,CAAC;AACD,UAAM,KAAK,IAAI,KAAK;AAAA,MAClB;AAAA;AAAA,gBAEU,YAAY;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,YACA,WAC+B;AAG/B,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC;AAAA;AAAA,SAEG,SAAS;AAAA,IACd;AACA,WAAO,EAAE,UAAU,OAAO,OAAO,WAAW,EAAE;AAAA,EAChD;AAAA,EAEA,MAAM,SACJ,QACA,MACA,IACmB;AAGnB,UAAM,EAAE,IAAI,IAAI,iBAAiB,QAAQ,MAAM,EAAE;AACjD,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,WAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,MAClC,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,OAAO,OAAO,EAAE,CAAC,CAAC;AAAA,MAClB,KAAK,EAAE,CAAC,KAAK,OAAO,SAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MAC3C,KAAK,EAAE,CAAC,KAAK,OAAO,SAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MAC3C,QAAQ;AAAA,IACV,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,cACJ,QACA,MACA,IACmB;AACnB,UAAM,EAAE,IAAI,IAAI,sBAAsB,QAAQ,MAAM,EAAE;AACtD,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,WAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,MAClC,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,MAAM;AAAA,MACN,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,OAAO,OAAO,EAAE,CAAC,CAAC;AAAA,MAClB,KAAK,EAAE,CAAC,KAAK,OAAO,SAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MAC3C,KAAK,EAAE,CAAC,KAAK,OAAO,SAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MAC3C,QAAQ;AAAA,IACV,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,YACJ,QACA,MACA,IACyB;AAEzB,UAAM,YAAY,OAAO,QAAQ,MAAM,IAAI;AAC3C,UAAM,UAAU,KAAK,QAAQ,MAAM,IAAI;AACvC,UAAM,QAAQ,GAAG,QAAQ,MAAM,IAAI;AACnC,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC;AAAA,2BACqB,SAAS,kBAAkB,OAAO,kBAAkB,KAAK;AAAA;AAAA,IAEhF;AACA,UAAM,QAAQ,OAAO,QAAQ,EAAE,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC;AACtD,WAAO;AAAA,MACL,UAAU,MAAM,CAAC,KAAK;AAAA,MACtB,QAAQ,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,MACnC,cAAc,CAAC;AAAA,MACf,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACF;;;ACpHA,SAAS,cAAAC,mBAAkB;AAC3B,YAAYC,WAAU;;;ACqBf,SAAS,0BAA0B,MAA6B;AACrE,QAAM,EAAE,WAAW,SAAS,MAAM,IAAI;AACtC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAOY,SAAS;AAAA,oBACV,OAAO,gBAAgB,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYhD;;;AC7BA,SAASC,KAAI,OAAuB;AAClC,SAAO,IAAI,iBAAiB,KAAK,CAAC;AACpC;AAOO,SAAS,qBACd,MACU;AACV,QAAM,EAAE,QAAQ,MAAM,IAAI,gBAAgB,aAAa,IAAI;AAE3D,QAAM,YAAYA,KAAI,MAAM;AAC5B,QAAM,UAAUA,KAAI,IAAI;AACxB,QAAM,QAAQA,KAAI,EAAE;AAEpB,QAAM,YAAY,eACd,aAAa,0BAA0B;AAAA,IACrC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC,CAAC;AAAA,mEAEF;AAEJ,QAAM,UAAU,iBACZ,2DACA;AAEJ,QAAM,YAAY,eACd,6DACA;AAEJ,QAAM,UAAU,iBACZ,8FACA;AAEJ,QAAM,MAAM;AAAA,gBACE,SAAS,GAAG,OAAO;AAAA;AAAA,MAE7B,SAAS;AAAA,MACT,OAAO;AAAA,uBACU,SAAS,kBAAkB,OAAO,kBAAkB,KAAK;AAAA;AAAA;AAI9E,SAAO,EAAE,IAAI;AACf;;;AFnDO,IAAM,uBAAN,cAAmC,cAAc;AAAA,EACrC;AAAA,EACjB,YACE,KACA,WACA;AACA,UAAM,GAAG;AACT,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,QAAQ,QAAgB,OAAe,KAA4B;AAIvE,UAAM;AAAA,MACJ,KAAK,IAAI;AAAA,MACT;AAAA,MACA,EAAE,SAAS,KAAK,IAAI,SAAS,aAAa,KAAK;AAAA,MAC/C;AAAA,QACE,WAAW,KAAK;AAAA,QAChB,gBAAgB;AAAA,UACd,KAAK,CAAC,MAAM,mBAAmB,GAAG,KAAK,IAAI,OAAO;AAAA,UAClD,QAAQ,CAAC,GAAG,MAAM,sBAAsB,GAAG,GAAG,KAAK,IAAI,OAAO;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,OAAe,KAA4B;AAM9D,eAAW,UAAU,CAAC,OAAO,SAAS,OAAO,GAAG;AAC9C,YAAM;AAAA,QACJ,KAAK,IAAI;AAAA,QACT;AAAA,QACA,EAAE,SAAS,KAAK,IAAI,SAAS,aAAa,KAAK;AAAA,QAC/C;AAAA,UACE,WAAW,KAAK;AAAA,UAChB,gBAAgB;AAAA,YACd,KAAK,CAAC,MAAM,mBAAmB,GAAG,KAAK,IAAI,OAAO;AAAA,YAClD,QAAQ,CAAC,GAAG,MAAM,sBAAsB,GAAG,GAAG,KAAK,IAAI,OAAO;AAAA,UAChE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,MAA4D;AAMrE,UAAM,aAAa,CAAC,CAAC,KAAK,kBAAkB,CAAC,CAAC,KAAK;AACnD,QAAI,CAAC,YAAY;AACf,YAAM,WAAgB;AAAA,QACpB,iBAAiB,KAAK,IAAI,OAAO;AAAA,QACjC;AAAA,QACA,UAAU,KAAK,MAAM;AAAA,QACrB;AAAA,MACF;AACA,UAAIC,YAAW,QAAQ,GAAG;AACxB,cAAM,UAAU,SAAS,QAAQ,MAAM,IAAI;AAC3C,cAAM,UAAU,KAAK,KAAK,QAAQ,MAAM,IAAI;AAC5C,cAAM,QAAQ,KAAK,GAAG,QAAQ,MAAM,IAAI;AACxC,cAAMC,OAAM,+BAA+B,OAAO;AAAA,sCACpB,OAAO,kBAAkB,KAAK;AAAA;AAE5D,cAAMC,UAAS,MAAM,KAAK,IAAI,KAAK,cAAcD,IAAG;AACpD,cAAME,SAAQD,QAAO,YAAY;AACjC,eAAOA,QACJ,QAAQ,EACR,IAAI,CAAC,QAAQ,OAAO,YAAYC,OAAM,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAAA,MACtE;AAAA,IACF;AAEA,UAAM,EAAE,IAAI,IAAI,qBAAqB;AAAA,MACnC,QAAQ,KAAK;AAAA,MACb,MAAM,KAAK;AAAA,MACX,IAAI,KAAK;AAAA,MACT,gBAAgB,CAAC,CAAC,KAAK;AAAA,MACvB,cAAc,CAAC,CAAC,KAAK;AAAA,IACvB,CAAC;AACD,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,UAAM,QAAQ,OAAO,YAAY;AACjC,WAAO,OACJ,QAAQ,EACR;AAAA,MAAI,CAAC,QACJ,OAAO,YAAY,MAAM,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAAA,IACrD;AAAA,EACJ;AAAA,EAEA,MAAM,YAAY,QAAyC;AAGzD,UAAM,WAAgB;AAAA,MACpB,iBAAiB,KAAK,IAAI,OAAO;AAAA,MACjC;AAAA,MACA,UAAU,MAAM;AAAA,MAChB;AAAA,IACF;AACA,QAAI,CAACH,YAAW,QAAQ,GAAG;AAIzB,aAAO,EAAE,UAAU,MAAM,QAAQ,MAAM,cAAc,CAAC,GAAG,YAAY,EAAE;AAAA,IACzE;AACA,UAAM,YAAY,OAAO,QAAQ,MAAM,IAAI;AAC3C,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC,6DAA6D,SAAS;AAAA,IACxE;AACA,UAAM,QAAQ,OAAO,QAAQ,EAAE,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC;AACtD,WAAO;AAAA,MACL,UAAU,MAAM,CAAC,KAAK;AAAA,MACtB,QAAQ,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,MACnC,cAAc,CAAC;AAAA,MACf,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACF;;;AG7HO,IAAM,sBAAN,cAAkC,cAAc;AAAA,EACpC;AAAA,EACjB,YACE,KACA,WACA;AACA,UAAM,GAAG;AACT,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,QAAQ,QAAgB,OAAe,KAA4B;AACvE,UAAM;AAAA,MACJ,KAAK,IAAI;AAAA,MACT;AAAA,MACA,EAAE,SAAS,KAAK,IAAI,SAAS,aAAa,MAAM;AAAA,MAChD;AAAA,QACE,WAAW,KAAK;AAAA,QAChB,gBAAgB;AAAA,UACd,KAAK,CAAC,MAAM,mBAAmB,GAAG,KAAK,IAAI,OAAO;AAAA,UAClD,QAAQ,CAAC,GAAG,MAAM,sBAAsB,GAAG,GAAG,KAAK,IAAI,OAAO;AAAA,QAChE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,OAAe,KAA4B;AAC9D,eAAW,UAAU,CAAC,OAAO,SAAS,OAAO,GAAG;AAC9C,YAAM;AAAA,QACJ,KAAK,IAAI;AAAA,QACT;AAAA,QACA,EAAE,SAAS,KAAK,IAAI,SAAS,aAAa,MAAM;AAAA,QAChD;AAAA,UACE,WAAW,KAAK;AAAA,UAChB,gBAAgB;AAAA,YACd,KAAK,CAAC,MAAM,mBAAmB,GAAG,KAAK,IAAI,OAAO;AAAA,YAClD,QAAQ,CAAC,GAAG,MAAM,sBAAsB,GAAG,GAAG,KAAK,IAAI,OAAO;AAAA,UAChE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,MAA4D;AAGrE,UAAM,EAAE,IAAI,IAAI,qBAAqB;AAAA,MACnC,QAAQ,KAAK;AAAA,MACb,MAAM,KAAK;AAAA,MACX,IAAI,KAAK;AAAA,MACT,gBAAgB,CAAC,CAAC,KAAK;AAAA,MACvB,cAAc,CAAC,CAAC,KAAK;AAAA,IACvB,CAAC;AACD,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,UAAM,QAAQ,OAAO,YAAY;AACjC,WAAO,OACJ,QAAQ,EACR;AAAA,MAAI,CAAC,QACJ,OAAO,YAAY,MAAM,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAAA,IACrD;AAAA,EACJ;AAAA,EAEA,MAAM,YAAY,QAAyC;AAGzD,UAAM,YAAY,OAAO,QAAQ,MAAM,IAAI;AAC3C,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC,6DAA6D,SAAS;AAAA,IACxE;AACA,UAAM,QAAQ,OAAO,QAAQ,EAAE,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC;AACtD,WAAO;AAAA,MACL,UAAU,MAAM,CAAC,KAAK;AAAA,MACtB,QAAQ,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,MACnC,cAAc,CAAC;AAAA,MACf,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACF;;;ACpFA,SAAS,cAAAI,mBAAkB;AAC3B,YAAYC,WAAU;AAUtB,SAASC,kBAAiB,OAAuB;AAC/C,SAAO,MAAM,QAAQ,MAAM,IAAI;AACjC;AAEO,IAAM,oBAAN,cAAgC,WAAW;AAAA,EAChD,MAAM,WACJ,YACA,MACA,MACe;AACf,QAAI,KAAK,WAAW,EAAG;AACvB,UAAM,UAAU,gBAAgB,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AACjF,UAAM,KAAK,IAAI,KAAK;AAAA,MAClB,sBAAsB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAK/B;AACA,QAAI;AACF,YAAM,eAAe,KAClB,IAAI,CAAC,GAAG,MAAM;AACb,cAAM,IAAI,IAAI;AACd,eAAO,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC;AAAA,MACzF,CAAC,EACA,KAAK,IAAI;AACZ,YAAM,SAAoB,KAAK,QAAQ,CAAC,MAAM;AAAA,QAC5C;AAAA,QACA;AAAA,QACA,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE,OAAO;AAAA,QACT,EAAE,kBAAkB;AAAA,MACtB,CAAC;AACD,YAAM,KAAK,IAAI,KAAK;AAAA,QAClB,gBAAgB,OAAO,YAAY,YAAY;AAAA,QAC/C;AAAA,MACF;AACA,YAAM,oBAAoB,KAAK,IAAI,MAAM;AAAA,QACvC,SAAS,KAAK,IAAI;AAAA,QAClB;AAAA,QACA;AAAA,QACA,aAAa,kBAAkB,OAAO;AAAA,MACxC,CAAC;AAAA,IACH,UAAE;AACA,UAAI;AACF,cAAM,KAAK,IAAI,KAAK,IAAI,yBAAyB,OAAO,GAAG;AAAA,MAC7D,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,WACA,WAC+B;AAC/B,WAAO,oBAAoB,KAAK,IAAI,MAAM;AAAA,MACxC,SAAS,KAAK,IAAI;AAAA,MAClB,YAAY,UAAU;AAAA,MACtB,MAAM,UAAU;AAAA,MAChB,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAe,SAAS,YAAoB,MAAgC;AAC1E,UAAM,gBAAqB;AAAA,MACzB,iBAAiB,KAAK,IAAI,OAAO;AAAA,MACjC;AAAA,MACA,cAAc,UAAU;AAAA,MACxB,QAAQ,IAAI;AAAA,MACZ;AAAA,IACF;AACA,WAAOC,YAAW,aAAa;AAAA,EACjC;AAAA,EAEA,MAAM,UACJ,YACA,MACwB;AACxB,UAAM,gBAAqB;AAAA,MACzB,iBAAiB,KAAK,IAAI,OAAO;AAAA,MACjC;AAAA,MACA,cAAc,UAAU;AAAA,MACxB,QAAQ,IAAI;AAAA,MACZ;AAAA,IACF;AACA,QAAIA,YAAW,aAAa,GAAG;AAI7B,YAAMC,OAAM;AAAA,0CACwBF,kBAAiB,aAAa,CAAC;AAAA;AAEnE,YAAMG,UAAS,MAAM,KAAK,IAAI,KAAK,cAAcD,IAAG;AACpD,aAAOC,QAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,QAClC,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,QACvB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,QACjB,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,QACnB,eAAe,OAAO,EAAE,CAAC,CAAC;AAAA,QAC1B,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,QACnB,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,QACvB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,QAChB,gBAAgB,OAAO,EAAE,CAAC,CAAC;AAAA,MAC7B,EAAE;AAAA,IACJ;AAEA,UAAM,EAAE,IAAI,IAAI,kBAAkB,YAAY,IAAI;AAClD,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,WAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,MAClC,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MACvB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,eAAe,OAAO,EAAE,CAAC,CAAC;AAAA,MAC1B,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MACvB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,gBAAgB,OAAO,EAAE,CAAC,CAAC;AAAA,IAC7B,EAAE;AAAA,EACJ;AAAA,EAEA,MAAe,eACb,YACA,OACwB;AACxB,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,UAAM,YAAY,iBAAiB,KAAK,IAAI,OAAO;AACnD,UAAM,cAAwB,CAAC;AAC/B,eAAW,QAAQ,OAAO;AACxB,YAAM,gBAAqB;AAAA,QACzB;AAAA,QACA;AAAA,QACA,cAAc,UAAU;AAAA,QACxB,QAAQ,IAAI;AAAA,QACZ;AAAA,MACF;AACA,UAAIF,YAAW,aAAa,EAAG,aAAY,KAAK,aAAa;AAAA,IAC/D;AACA,QAAI,YAAY,WAAW,EAAG,QAAO,MAAM,eAAe,YAAY,KAAK;AAE3E,UAAM,WAAW,YAAY,IAAI,CAAC,aAAa,IAAID,kBAAiB,QAAQ,CAAC,GAAG,EAAE,KAAK,IAAI;AAG3F,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC;AAAA,8BACwB,QAAQ;AAAA;AAAA,IAElC;AACA,WAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,MAClC,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MACvB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,eAAe,OAAO,EAAE,CAAC,CAAC;AAAA,MAC1B,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MACvB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,gBAAgB,OAAO,EAAE,CAAC,CAAC;AAAA,IAC7B,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,YACJ,YACA,MACA,IACyB;AACzB,UAAM,MAAW;AAAA,MACf,iBAAiB,KAAK,IAAI,OAAO;AAAA,MACjC;AAAA,MACA,cAAc,UAAU;AAAA,IAC1B;AACA,QAAI,CAACC,YAAW,GAAG,GAAG;AACpB,aAAO,EAAE,UAAU,MAAM,QAAQ,MAAM,cAAc,CAAC,GAAG,YAAY,EAAE;AAAA,IACzE;AACA,UAAM,WAAW,oBAAoB,KAAK,MAAM;AAChD,UAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,KAAK,QAAQ,KAAK,EAAE;AACzD,WAAO;AAAA,MACL,UAAU,MAAM,CAAC,KAAK;AAAA,MACtB,QAAQ,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,MACnC,cAAc,CAAC;AAAA,MACf,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACF;;;AC1MO,IAAM,mBAAN,cAA+B,WAAW;AAAA,EAC/C,MAAM,WACJ,YACA,MACA,MACe;AACf,QAAI,KAAK,WAAW,EAAG;AACvB,UAAM,eAAe,KAClB,IAAI,CAAC,GAAG,MAAM;AACb,YAAM,IAAI,IAAI;AACd,aAAO,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC;AAAA,IACzF,CAAC,EACA,KAAK,IAAI;AACZ,UAAM,SAAoB,KAAK,QAAQ,CAAC,MAAM;AAAA,MAC5C;AAAA,MACA;AAAA,MACA,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE,OAAO;AAAA,MACT,EAAE,kBAAkB;AAAA,IACtB,CAAC;AACD,UAAM,KAAK,IAAI,KAAK;AAAA,MAClB;AAAA;AAAA,gBAEU,YAAY;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,YACA,WAC+B;AAC/B,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC;AAAA;AAAA,SAEG,SAAS;AAAA,IACd;AACA,WAAO,EAAE,UAAU,OAAO,OAAO,WAAW,EAAE;AAAA,EAChD;AAAA,EAEA,MAAM,UACJ,YACA,MACwB;AAGxB,UAAM,EAAE,IAAI,IAAI,kBAAkB,YAAY,IAAI;AAClD,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,WAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,MAClC,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MACvB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,eAAe,OAAO,EAAE,CAAC,CAAC;AAAA,MAC1B,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MACvB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,gBAAgB,OAAO,EAAE,CAAC,CAAC;AAAA,IAC7B,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,YACJ,YACA,MACA,IACyB;AAEzB,UAAM,gBAAgB,WAAW,QAAQ,MAAM,IAAI;AACnD,UAAM,UAAU,KAAK,QAAQ,MAAM,IAAI;AACvC,UAAM,QAAQ,GAAG,QAAQ,MAAM,IAAI;AACnC,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC;AAAA,+BACyB,aAAa,kBAAkB,OAAO,kBAAkB,KAAK;AAAA;AAAA,IAExF;AACA,UAAM,QAAQ,OAAO,QAAQ,EAAE,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC;AACtD,WAAO;AAAA,MACL,UAAU,MAAM,CAAC,KAAK;AAAA,MACtB,QAAQ,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,MACnC,cAAc,CAAC;AAAA,MACf,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACF;;;ACpFA,SAAS,cAAAG,mBAAkB;AAC3B,YAAYC,WAAU;AAuBtB,SAAS,cAAc,KAA0B;AAC/C,QAAM,MAAM,OAAO,IAAI,CAAC,CAAC;AACzB,QAAM,OAAO,OAAO,IAAI,CAAC,CAAC;AAC1B,QAAM,OAAO,OAAO,IAAI,CAAC,CAAC;AAC1B,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,WAAW,GAAG,IAAI,IAAI,IAAI;AAAA,IAC1B,KAAK,OAAO,IAAI,CAAC,CAAC;AAAA,IAClB,KAAK,OAAO,IAAI,CAAC,CAAC;AAAA,IAClB,QAAQ,IAAI,CAAC,KAAK,OAAO,OAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,IAC9C,OAAO,IAAI,CAAC,KAAK,OAAO,OAAO,OAAO,IAAI,CAAC,CAAC;AAAA,IAC5C,OAAO,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IAC9C,OAAO,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IAC9C,MAAM,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IAC7C,IAAI,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IAC3C,eAAe,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IACtD,iBAAiB,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IACxD,WAAW,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IAClD,YAAY,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IACnD,cAAc,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,EACvD;AACF;AAEO,IAAM,oBAAN,cAAgC,WAAW;AAAA,EAChD,MAAM,YACJ,YACA,MACA,QACe;AACf,QAAI,OAAO,WAAW,EAAG;AAKzB,UAAM,UAAU,gBAAgB,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAKjF,UAAM,KAAK,IAAI,KAAK;AAAA,MAClB,sBAAsB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQ/B;AACA,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,IAAI,KAAK,eAAe,OAAO;AAC3D,UAAI;AACF,mBAAW,KAAK,QAAQ;AAGtB,gBAAM,WAAW,EAAE,UAAU,QAAQ,GAAG;AACxC,gBAAM,QAAQ,aAAa,KAAK,OAAO,EAAE,UAAU,MAAM,GAAG,QAAQ;AACpE,gBAAM,QAAQ,aAAa,KAAK,UAAU,EAAE,UAAU,MAAM,WAAW,CAAC;AACxE,mBAAS,cAAc,UAAU;AACjC,mBAAS,cAAc,KAAK;AAC5B,mBAAS,cAAc,EAAE,UAAU;AACnC,mBAAS,cAAc,KAAK;AAC5B,mBAAS,aAAa,EAAE,GAAG;AAC3B,mBAAS,aAAa,EAAE,GAAG;AAC3B,mBAAS,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC;AACzC,mBAAS,WAAW;AACpB,cAAI,EAAE,UAAU,KAAM,UAAS,WAAW;AAAA,cACrC,UAAS,cAAc,EAAE,MAAM;AACpC,cAAI,EAAE,SAAS,KAAM,UAAS,WAAW;AAAA,cACpC,UAAS,YAAY,EAAE,KAAK;AACjC,cAAI,EAAE,SAAS,KAAM,UAAS,WAAW;AAAA,cACpC,UAAS,YAAY,EAAE,KAAK;AACjC,cAAI,EAAE,SAAS,KAAM,UAAS,WAAW;AAAA,cACpC,UAAS,YAAY,EAAE,KAAK;AACjC,cAAI,EAAE,QAAQ,KAAM,UAAS,WAAW;AAAA,cACnC,UAAS,YAAY,EAAE,IAAI;AAChC,cAAI,EAAE,MAAM,KAAM,UAAS,WAAW;AAAA,cACjC,UAAS,YAAY,EAAE,EAAE;AAC9B,cAAI,EAAE,iBAAiB,KAAM,UAAS,WAAW;AAAA,cAC5C,UAAS,cAAc,EAAE,aAAa;AAC3C,cAAI,EAAE,mBAAmB,KAAM,UAAS,WAAW;AAAA,cAC9C,UAAS,cAAc,EAAE,eAAe;AAC7C,cAAI,EAAE,aAAa,KAAM,UAAS,WAAW;AAAA,cACxC,UAAS,cAAc,EAAE,SAAS;AACvC,cAAI,EAAE,cAAc,KAAM,UAAS,WAAW;AAAA,cACzC,UAAS,aAAa,EAAE,UAAU;AACvC,cAAI,EAAE,gBAAgB,KAAM,UAAS,WAAW;AAAA,cAC3C,UAAS,cAAc,EAAE,YAAY;AAC1C,mBAAS,OAAO;AAAA,QAClB;AACA,iBAAS,UAAU;AAAA,MACrB,UAAE;AACA,iBAAS,UAAU;AAAA,MACrB;AACA,YAAM,2BAA2B,KAAK,IAAI,MAAM;AAAA,QAC9C,SAAS,KAAK,IAAI;AAAA,QAClB;AAAA,QACA;AAAA,QACA,aAAa,kBAAkB,OAAO;AAAA,MACxC,CAAC;AAAA,IACH,UAAE;AACA,UAAI;AACF,cAAM,KAAK,IAAI,KAAK,IAAI,yBAAyB,OAAO,GAAG;AAAA,MAC7D,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,WACA,WAC+B;AAC/B,UAAM,UAAU,MAAM,qBAAqB,KAAK,IAAI,MAAM,SAAS;AAInE,UAAM,aAAa,qCAAqC,SAAS,GAAG;AACpE,WAAO,2BAA2B,KAAK,IAAI,MAAM;AAAA,MAC/C,SAAS,KAAK,IAAI;AAAA,MAClB,YAAY,UAAU;AAAA,MACtB,MAAM,UAAU;AAAA,MAChB,aAAa,UAAU,UAAU,UAAU,SAAS;AAAA,IACtD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,WACJ,YACA,MACA,IACkC;AAClC,QAAI,WAAW,WAAW,EAAG,QAAO,oBAAI,IAAI;AAI5C,UAAM,kBAAkB,KAAK,IAAI,QAAQ;AAAA,MACvC,YAAY,WAAW,CAAC,CAAC;AAAA,IAC3B;AACA,eAAW,KAAK,YAAY;AAC1B,YAAM,IAAI,KAAK,IAAI,QAAQ,QAAQ,YAAY,CAAC,CAAC;AACjD,UAAI,MAAM,iBAAiB;AACzB,cAAM,IAAI;AAAA,UACR,4DACK,WAAW,CAAC,CAAC,gBAAgB,eAAe,KAAK,CAAC,gBAAgB,CAAC;AAAA,QAE1E;AAAA,MACF;AAAA,IACF;AACA,UAAM,gBAAqB;AAAA,MACzB,iBAAiB,KAAK,IAAI,OAAO;AAAA,MACjC;AAAA,MACA,cAAc,eAAe;AAAA,IAC/B;AACA,QAAI,CAACC,YAAW,aAAa,EAAG,QAAO,oBAAI,IAAI;AAC/C,UAAM,QAAQ,oBAAoB,eAAe,MAAM,EACpD,OAAO,CAAC,SAAS,QAAQ,QAAQ,QAAQ,EAAE,EAC3C,IAAI,CAAC,SAAc,WAAK,eAAe,QAAQ,IAAI,IAAI,cAAc,CAAC,EACtE,OAAO,CAAC,aAAaA,YAAW,QAAQ,CAAC;AAC5C,QAAI,MAAM,WAAW,EAAG,QAAO,oBAAI,IAAI;AAEvC,UAAM,SAAS,oBAAoB,KAAK;AACxC,UAAM,UAAU,MAAM,2BAA2B,KAAK,IAAI,MAAM,MAAM;AACtE,UAAM,aAAa,gCAAgC,SAAS,GAAG;AAM/D,UAAM,UAAU,IAAI,iBAAiB,IAAI,CAAC;AAC1C,UAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC;AACtC,UAAM,aAAa,WAChB,IAAI,CAAC,MAAM,IAAI,iBAAiB,CAAC,CAAC,GAAG,EACrC,KAAK,IAAI;AACZ,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC,UAAU,UAAU;AAAA,gBACV,MAAM;AAAA,0BACI,OAAO;AAAA,0BACP,KAAK;AAAA,6BACF,UAAU;AAAA;AAAA,IAEnC;AACA,UAAM,MAAM,oBAAI,IAAwB;AACxC,eAAW,OAAO,OAAO,QAAQ,GAAG;AAClC,YAAM,KAAK,cAAc,GAAG;AAC5B,YAAM,MAAM,GAAG;AACf,UAAI,MAAM,IAAI,IAAI,GAAG;AACrB,UAAI,CAAC,KAAK;AACR,cAAM,CAAC;AACP,YAAI,IAAI,KAAK,GAAG;AAAA,MAClB;AACA,UAAI,KAAK,EAAE;AAAA,IACb;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAe,eACb,eACA,WACA,SACkC;AAClC,UAAM,MAAM,oBAAI,IAAwB;AACxC,QAAI,cAAc,SAAS,EAAG,QAAO;AAErC,UAAM,OAAO,QAAQ,IAAI,2BAA2B;AACpD,UAAM,YAAY,iBAAiB,KAAK,IAAI,OAAO;AAEnD,eAAW,CAAC,YAAY,OAAO,KAAK,KAAK,yBAAyB,aAAa,GAAG;AAChF,YAAM,WAAW,oBAAI,IAAY;AACjC,YAAM,YAAsB,CAAC;AAC7B,YAAM,cAAwB,CAAC;AAE/B,iBAAW,CAAC,MAAM,IAAI,KAAK,SAAS;AAClC,YAAI,KAAK,SAAS,EAAG;AACrB,cAAM,gBAAqB;AAAA,UACzB;AAAA,UACA;AAAA,UACA,cAAc,UAAU;AAAA,UACxB,QAAQ,IAAI;AAAA,UACZ;AAAA,QACF;AACA,YAAI,CAACA,YAAW,aAAa,EAAG;AAChC,kBAAU,KAAK,aAAa;AAC5B,mBAAW,OAAO,MAAM;AACtB,mBAAS,IAAI,GAAG;AAChB,sBAAY,KAAK,KAAK,iBAAiB,IAAI,CAAC,OAAO,iBAAiB,GAAG,CAAC,IAAI;AAAA,QAC9E;AAAA,MACF;AAEA,UAAI,UAAU,WAAW,KAAK,YAAY,WAAW,EAAG;AAExD,YAAM,SAAS,oBAAoB,SAAS;AAC5C,YAAM,UAAU,MAAM,2BAA2B,KAAK,IAAI,MAAM,MAAM;AACtE,YAAM,aAAa,gCAAgC,SAAS,GAAG;AAK/D,YAAM,eAAe,IAAI,iBAAiB,SAAS,CAAC;AACpD,YAAM,aAAa,IAAI,iBAAiB,OAAO,CAAC;AAChD,YAAM,MAAM;AAAA,8BACY,YAAY,KAAK,IAAI,CAAC;AAAA;AAAA,4BAExB,UAAU;AAAA,0BACZ,MAAM;AAAA;AAAA;AAAA,qCAGK,YAAY,kBAAkB,UAAU;AAAA;AAGvE,YAAM,aAAa,OAAO,KAAK,IAAI,IAAI;AACvC,YAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,YAAM,OAAO,OAAO,QAAQ;AAC5B,UAAI,MAAM;AACR,gBAAQ;AAAA,UACN,qCAAqC,UAAU,UAAU,UAAU,MAAM,YAC9D,SAAS,IAAI,SAAS,KAAK,MAAM,YAAY,KAAK,IAAI,IAAI,UAAU;AAAA,QACjF;AAAA,MACF;AACA,iBAAW,OAAO,MAAM;AACtB,cAAM,QAAQ,cAAc,GAAG;AAC/B,cAAM,MAAM,MAAM;AAClB,cAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,YAAI,OAAQ,QAAO,KAAK,KAAK;AAAA,YACxB,KAAI,IAAI,KAAK,CAAC,KAAK,CAAC;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,WAAW,QAAqD;AACpE,UAAM,EAAE,YAAY,MAAM,WAAW,SAAS,aAAa,IAAI;AAC/D,QAAI,aAAa,WAAW,EAAG,QAAO,CAAC;AAEvC,UAAM,YAAY,iBAAiB,KAAK,IAAI,OAAO;AACnD,UAAM,YAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA,cAAc,UAAU;AAAA,MACxB,QAAQ,IAAI;AAAA,MACZ;AAAA,IACF;AACA,UAAM,YAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA,cAAc,UAAU;AAAA,MACxB,QAAQ,IAAI;AAAA,MACZ;AAAA,IACF;AACA,QAAI,CAACA,YAAW,SAAS,KAAK,CAACA,YAAW,SAAS,EAAG,QAAO,CAAC;AAE9D,UAAM,WAAW,oBAAoB,CAAC,SAAS,CAAC;AAChD,UAAM,WAAW,oBAAoB,CAAC,SAAS,CAAC;AAsBhD,UAAM,gBAAgB,IAAI,iBAAiB,SAAS,CAAC;AACrD,UAAM,cAAc,IAAI,iBAAiB,OAAO,CAAC;AACjD,UAAM,YAAsB,aAAa,IAAI,CAAC,QAAQ;AACpD,YAAM,KAAK,IAAI,iBAAiB,IAAI,YAAY,CAAC;AACjD,UAAI,SAAS,sBAAsB,EAAE,sBAAsB,IAAI,MAAM,QAAQ,IAAI,MAAM;AACvF,UAAI,IAAI,aAAa,KAAM,WAAU,oBAAoB,IAAI,SAAS;AACtE,UAAI,IAAI,aAAa,KAAM,WAAU,oBAAoB,IAAI,SAAS;AACtE,gBAAU;AACV,aAAO;AAAA,IACT,CAAC;AAED,UAAM,MAAM;AAAA;AAAA;AAAA,iBAGC,QAAQ;AAAA,iBACR,UAAU,KAAK,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAMxB,QAAQ;AAAA;AAAA,8BAEO,aAAa,QAAQ,WAAW;AAAA;AAG1D,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,WAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,MAClC,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,eAAe,OAAO,EAAE,CAAC,CAAC;AAAA,MAC1B,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MACvB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,OAAO,EAAE,CAAC,KAAK,OAAO,OAAO,OAAO,EAAE,CAAC,CAAC;AAAA,MACxC,OAAO,EAAE,CAAC,KAAK,OAAO,OAAO,OAAO,EAAE,CAAC,CAAC;AAAA,MACxC,OAAO,EAAE,EAAE,KAAK,OAAO,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,MAC1C,MAAM,EAAE,EAAE,KAAK,OAAO,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,MACzC,IAAI,EAAE,EAAE,KAAK,OAAO,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,MACvC,eAAe,EAAE,EAAE,KAAK,OAAO,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,IACpD,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,YACJ,YACA,MACA,IACyB;AACzB,UAAM,MAAW;AAAA,MACf,iBAAiB,KAAK,IAAI,OAAO;AAAA,MACjC;AAAA,MACA,cAAc,UAAU;AAAA,IAC1B;AACA,QAAI,CAACA,YAAW,GAAG,GAAG;AACpB,aAAO,EAAE,UAAU,MAAM,QAAQ,MAAM,cAAc,CAAC,GAAG,YAAY,EAAE;AAAA,IACzE;AACA,UAAM,WAAW,oBAAoB,KAAK,MAAM;AAChD,UAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,KAAK,QAAQ,KAAK,EAAE;AACzD,WAAO;AAAA,MACL,UAAU,MAAM,CAAC,KAAK;AAAA,MACtB,QAAQ,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,MACnC,cAAc,CAAC;AAAA,MACf,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACF;;;ACzZA,SAASC,kBAAiB,OAAuB;AAC/C,SAAO,MAAM,QAAQ,MAAM,IAAI;AACjC;AAEA,SAASC,eAAc,KAA0B;AAC/C,QAAM,MAAM,OAAO,IAAI,CAAC,CAAC;AACzB,QAAM,OAAO,OAAO,IAAI,CAAC,CAAC;AAC1B,QAAM,OAAO,OAAO,IAAI,CAAC,CAAC;AAC1B,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,WAAW,GAAG,IAAI,IAAI,IAAI;AAAA,IAC1B,KAAK,OAAO,IAAI,CAAC,CAAC;AAAA,IAClB,KAAK,OAAO,IAAI,CAAC,CAAC;AAAA,IAClB,QAAQ,IAAI,CAAC,KAAK,OAAO,OAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,IAC9C,OAAO,IAAI,CAAC,KAAK,OAAO,OAAO,OAAO,IAAI,CAAC,CAAC;AAAA,IAC5C,OAAO,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IAC9C,OAAO,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IAC9C,MAAM,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IAC7C,IAAI,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IAC3C,eAAe,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IACtD,iBAAiB,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IACxD,WAAW,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IAClD,YAAY,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,IACnD,cAAc,IAAI,EAAE,KAAK,OAAO,OAAO,OAAO,IAAI,EAAE,CAAC;AAAA,EACvD;AACF;AAEO,IAAM,mBAAN,cAA+B,WAAW;AAAA,EACvC,oBAAsD;AAAA,EAEtD,uBAAkD;AACxD,QAAI,CAAC,KAAK,mBAAmB;AAC3B,WAAK,oBAAoB;AAAA,QACvB,KAAK,IAAI;AAAA,QACT;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,4BAA2C;AACvD,UAAM,YAAY;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,eAAW,YAAY,WAAW;AAChC,YAAM,KAAK,IAAI,KAAK;AAAA,QAClB,oEAAoE,QAAQ;AAAA,MAC9E;AAAA,IACF;AACA,SAAK,oBAAoB;AAAA,EAC3B;AAAA,EAEA,MAAM,YACJ,YACA,MACA,QACe;AACf,QAAI,OAAO,WAAW,EAAG;AACzB,UAAM,KAAK,0BAA0B;AACrC,UAAM,eAAe,OAClB,IAAI,CAAC,GAAG,MAAM;AACb,YAAM,IAAI,IAAI;AACd,aAAO,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE;AAAA,IACjN,CAAC,EACA,KAAK,IAAI;AACZ,UAAM,SAAoB,OAAO,QAAQ,CAAC,MAAM;AAC9C,YAAM,CAAC,OAAO,KAAK,IAAI,EAAE,UAAU,MAAM,GAAG;AAC5C,YAAM,OAAO,EAAE,MAAM,EAAE,OAAO;AAC9B,aAAO;AAAA,QACL;AAAA,QACA,SAAS;AAAA,QACT,EAAE;AAAA,QACF,SAAS;AAAA,QACT,EAAE;AAAA,QACF,EAAE;AAAA,QACF;AAAA,QACA;AAAA;AAAA,QACA,EAAE,UAAU;AAAA;AAAA,QACZ,EAAE,SAAS;AAAA,QACX,EAAE,SAAS;AAAA,QACX,EAAE,SAAS;AAAA,QACX,EAAE,QAAQ;AAAA,QACV,EAAE,MAAM;AAAA,QACR,EAAE,iBAAiB;AAAA,QACnB,EAAE,mBAAmB;AAAA,QACrB,EAAE,aAAa;AAAA,QACf,EAAE,cAAc;AAAA,QAChB,EAAE,gBAAgB;AAAA,MACpB;AAAA,IACF,CAAC;AACD,UAAM,KAAK,IAAI,KAAK;AAAA,MAClB;AAAA;AAAA;AAAA;AAAA,gBAIU,YAAY;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,YACA,WAC+B;AAC/B,UAAM,KAAK,0BAA0B;AACrC,UAAM,UAAU,MAAM,qBAAqB,KAAK,IAAI,MAAM,SAAS;AACnE,UAAM,aAAa,gCAAgC,SAAS,GAAG;AAC/D,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC;AAAA;AAAA;AAAA;AAAA,gBAIU,UAAU;AAAA,iBACT,SAAS;AAAA,IACtB;AACA,WAAO,EAAE,UAAU,OAAO,OAAO,WAAW,EAAE;AAAA,EAChD;AAAA,EAEA,MAAM,WACJ,YACA,MACA,IACkC;AAClC,QAAI,WAAW,WAAW,EAAG,QAAO,oBAAI,IAAI;AAC5C,UAAM,kBAAkB,KAAK,IAAI,QAAQ;AAAA,MACvC,YAAY,WAAW,CAAC,CAAC;AAAA,IAC3B;AACA,eAAW,KAAK,YAAY;AAC1B,YAAM,IAAI,KAAK,IAAI,QAAQ,QAAQ,YAAY,CAAC,CAAC;AACjD,UAAI,MAAM,iBAAiB;AACzB,cAAM,IAAI;AAAA,UACR,4DACK,WAAW,CAAC,CAAC,gBAAgB,eAAe,KAAK,CAAC,gBAAgB,CAAC;AAAA,QAE1E;AAAA,MACF;AAAA,IACF;AACA,UAAM,UAAU,MAAM,KAAK,qBAAqB;AAChD,UAAM,aAAa,gCAAgC,SAAS,GAAG;AAM/D,UAAM,gBAAgB,IAAID,kBAAiB,eAAe,CAAC;AAC3D,UAAM,UAAU,IAAIA,kBAAiB,IAAI,CAAC;AAC1C,UAAM,QAAQ,IAAIA,kBAAiB,EAAE,CAAC;AACtC,UAAM,aAAa,WAChB,IAAI,CAAC,MAAM,IAAIA,kBAAiB,CAAC,CAAC,GAAG,EACrC,KAAK,IAAI;AACZ,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC,UAAU,UAAU;AAAA;AAAA,+BAEK,aAAa;AAAA,0BAClB,OAAO;AAAA,0BACP,KAAK;AAAA,6BACF,UAAU;AAAA;AAAA,IAEnC;AACA,UAAM,MAAM,oBAAI,IAAwB;AACxC,eAAW,OAAO,OAAO,QAAQ,GAAG;AAClC,YAAM,KAAKC,eAAc,GAAG;AAC5B,YAAM,MAAM,GAAG;AACf,UAAI,MAAM,IAAI,IAAI,GAAG;AACrB,UAAI,CAAC,KAAK;AACR,cAAM,CAAC;AACP,YAAI,IAAI,KAAK,GAAG;AAAA,MAClB;AACA,UAAI,KAAK,EAAE;AAAA,IACb;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAe,eACb,eACA,WACA,SACkC;AAClC,UAAM,MAAM,oBAAI,IAAwB;AACxC,QAAI,cAAc,SAAS,EAAG,QAAO;AAErC,UAAM,OAAO,QAAQ,IAAI,2BAA2B;AAEpD,eAAW,CAAC,YAAY,OAAO,KAAK,KAAK,yBAAyB,aAAa,GAAG;AAChF,YAAM,WAAW,oBAAI,IAAY;AACjC,YAAM,cAAwB,CAAC;AAE/B,iBAAW,CAAC,MAAM,IAAI,KAAK,SAAS;AAClC,YAAI,KAAK,SAAS,EAAG;AACrB,mBAAW,OAAO,MAAM;AACtB,mBAAS,IAAI,GAAG;AAChB,sBAAY,KAAK,KAAKD,kBAAiB,IAAI,CAAC,OAAOA,kBAAiB,GAAG,CAAC,IAAI;AAAA,QAC9E;AAAA,MACF;AAEA,UAAI,YAAY,WAAW,EAAG;AAE9B,YAAM,UAAU,MAAM,KAAK,qBAAqB;AAChD,YAAM,aAAa,gCAAgC,SAAS,GAAG;AAK/D,YAAM,gBAAgB,IAAIA,kBAAiB,UAAU,CAAC;AACtD,YAAM,eAAe,IAAIA,kBAAiB,SAAS,CAAC;AACpD,YAAM,aAAa,IAAIA,kBAAiB,OAAO,CAAC;AAChD,YAAM,MAAM;AAAA,8BACY,YAAY,KAAK,IAAI,CAAC;AAAA;AAAA,4BAExB,UAAU;AAAA;AAAA;AAAA;AAAA,0CAII,aAAa;AAAA,qCAClB,YAAY,kBAAkB,UAAU;AAAA;AAGvE,YAAM,aAAa,OAAO,KAAK,IAAI,IAAI;AACvC,YAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,YAAM,OAAO,OAAO,QAAQ;AAC5B,UAAI,MAAM;AACR,gBAAQ;AAAA,UACN,qCAAqC,UAAU,UAAU,QAAQ,IAAI,YAC1D,SAAS,IAAI,SAAS,KAAK,MAAM,YAAY,KAAK,IAAI,IAAI,UAAU;AAAA,QACjF;AAAA,MACF;AACA,iBAAW,OAAO,MAAM;AACtB,cAAM,QAAQC,eAAc,GAAG;AAC/B,cAAM,MAAM,MAAM;AAClB,cAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,YAAI,OAAQ,QAAO,KAAK,KAAK;AAAA,YACxB,KAAI,IAAI,KAAK,CAAC,KAAK,CAAC;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,WAAW,QAAqD;AACpE,UAAM,EAAE,YAAY,MAAM,WAAW,SAAS,aAAa,IAAI;AAC/D,QAAI,aAAa,WAAW,EAAG,QAAO,CAAC;AAUvC,UAAM,cAAc,IAAID,kBAAiB,UAAU,CAAC;AACpD,UAAM,QAAQ,IAAIA,kBAAiB,IAAI,CAAC;AACxC,UAAM,aAAa,IAAIA,kBAAiB,SAAS,CAAC;AAClD,UAAM,WAAW,IAAIA,kBAAiB,OAAO,CAAC;AAE9C,UAAM,YAAsB,CAAC;AAC7B,eAAW,OAAO,cAAc;AAC9B,YAAM,KAAK,IAAIA,kBAAiB,IAAI,YAAY,CAAC;AACjD,UAAI,SAAS,sBAAsB,EAAE,sBAAsB,IAAI,MAAM,QAAQ,IAAI,MAAM;AACvF,UAAI,IAAI,aAAa,MAAM;AACzB,kBAAU,oBAAoB,IAAI,SAAS;AAAA,MAC7C;AACA,UAAI,IAAI,aAAa,MAAM;AACzB,kBAAU,oBAAoB,IAAI,SAAS;AAAA,MAC7C;AACA,gBAAU;AACV,gBAAU,KAAK,MAAM;AAAA,IACvB;AAMA,UAAM,MAAM;AAAA;AAAA;AAAA;AAAA,gCAIgB,WAAW,iBAAiB,KAAK;AAAA,kBAC/C,UAAU,KAAK,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAQV,WAAW;AAAA,wBACjB,KAAK;AAAA,8BACC,UAAU,QAAQ,QAAQ;AAAA;AAGpD,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAc,GAAG;AACpD,WAAO,OAAO,QAAQ,EAAE,IAAI,CAAC,OAAO;AAAA,MAClC,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,MAAM,OAAO,EAAE,CAAC,CAAC;AAAA,MACjB,eAAe,OAAO,EAAE,CAAC,CAAC;AAAA,MAC1B,QAAQ,OAAO,EAAE,CAAC,CAAC;AAAA,MACnB,YAAY,OAAO,EAAE,CAAC,CAAC;AAAA,MACvB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,MAChB,OAAO,EAAE,CAAC,KAAK,OAAO,OAAO,OAAO,EAAE,CAAC,CAAC;AAAA,MACxC,OAAO,EAAE,CAAC,KAAK,OAAO,OAAO,OAAO,EAAE,CAAC,CAAC;AAAA,MACxC,OAAO,EAAE,EAAE,KAAK,OAAO,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,MAC1C,MAAM,EAAE,EAAE,KAAK,OAAO,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,MACzC,IAAI,EAAE,EAAE,KAAK,OAAO,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,MACvC,eAAe,EAAE,EAAE,KAAK,OAAO,OAAO,OAAO,EAAE,EAAE,CAAC;AAAA,IACpD,EAAE;AAAA,EACJ;AAAA,EAEA,MAAM,YACJ,YACA,MACA,IACyB;AAEzB,UAAM,gBAAgB,WAAW,QAAQ,MAAM,IAAI;AACnD,UAAM,UAAU,KAAK,QAAQ,MAAM,IAAI;AACvC,UAAM,QAAQ,GAAG,QAAQ,MAAM,IAAI;AACnC,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC;AAAA,+BACyB,aAAa,kBAAkB,OAAO,kBAAkB,KAAK;AAAA;AAAA,IAExF;AACA,UAAM,QAAQ,OAAO,QAAQ,EAAE,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC;AACtD,WAAO;AAAA,MACL,UAAU,MAAM,CAAC,KAAK;AAAA,MACtB,QAAQ,MAAM,MAAM,SAAS,CAAC,KAAK;AAAA,MACnC,cAAc,CAAC;AAAA,MACf,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACF;;;AClXA,SAAS,cAAAE,mBAAkB;AAC3B,YAAYC,WAAU;AAYtB,IAAM,mBACJ;AAEF,SAAS,gBAAgB,KAA4B;AACnD,SAAO;AAAA,IACL,YAAY,OAAO,IAAI,CAAC,CAAC;AAAA,IACzB,MAAM,OAAO,IAAI,CAAC,CAAC;AAAA,IACnB,YAAY,OAAO,IAAI,CAAC,CAAC;AAAA,IACzB,YAAY,OAAO,IAAI,CAAC,CAAC;AAAA,IACzB,QAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,IACrB,OAAO,OAAO,IAAI,CAAC,CAAC;AAAA,IACpB,eAAe,OAAO,IAAI,CAAC,CAAC;AAAA,IAC5B,QAAQ,IAAI,CAAC,KAAK,OAAO,OAAO,OAAO,IAAI,CAAC,CAAC;AAAA,EAC/C;AACF;AAEO,IAAM,sBAAN,MAA0B;AAAA,EACZ;AAAA,EACnB,YAAY,KAAmB;AAC7B,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,MAAM,aACJ,YACA,MACA,MACe;AACf,QAAI,KAAK,WAAW,EAAG;AAGvB,UAAM,UAAU,aAAa,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAC9E,UAAM,KAAK,IAAI,KAAK;AAAA,MAClB,sBAAsB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAK/B;AACA,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,IAAI,KAAK,eAAe,OAAO;AAC3D,UAAI;AACF,mBAAW,KAAK,MAAM;AACpB,mBAAS,cAAc,UAAU;AACjC,mBAAS,cAAc,EAAE,IAAI;AAC7B,mBAAS,cAAc,EAAE,UAAU;AACnC,mBAAS,cAAc,EAAE,UAAU;AACnC,mBAAS,aAAa,EAAE,MAAM;AAC9B,mBAAS,cAAc,EAAE,KAAK;AAC9B,mBAAS,aAAa,OAAO,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC;AACzD,cAAI,EAAE,UAAU,KAAM,UAAS,WAAW;AAAA,cACrC,UAAS,cAAc,EAAE,MAAM;AACpC,mBAAS,OAAO;AAAA,QAClB;AACA,iBAAS,UAAU;AAAA,MACrB,UAAE;AACA,iBAAS,UAAU;AAAA,MACrB;AACA,YAAM,sBAAsB,KAAK,IAAI,MAAM;AAAA,QACzC,SAAS,KAAK,IAAI;AAAA,QAClB;AAAA,QACA;AAAA,QACA,aAAa,UAAU,gBAAgB,UAAU,OAAO;AAAA,MAC1D,CAAC;AAAA,IACH,UAAE;AACA,UAAI;AACF,cAAM,KAAK,IAAI,KAAK,IAAI,yBAAyB,OAAO,GAAG;AAAA,MAC7D,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,YACJ,YACA,MACA,IACuB;AACvB,UAAM,gBAAqB;AAAA,MACzB,iBAAiB,KAAK,IAAI,OAAO;AAAA,MACjC;AAAA,MACA,cAAc,UAAU;AAAA,IAC1B;AACA,QAAI,CAACC,YAAW,aAAa,EAAG,QAAO,CAAC;AACxC,UAAM,QAAQ,oBAAoB,eAAe,MAAM,EACpD,OAAO,CAAC,SAAS,QAAQ,QAAQ,QAAQ,EAAE,EAC3C,IAAI,CAAC,SAAc,WAAK,eAAe,QAAQ,IAAI,IAAI,cAAc,CAAC,EACtE,OAAO,CAAC,aAAaA,YAAW,QAAQ,CAAC;AAC5C,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAEhC,UAAM,SAAS,oBAAoB,KAAK;AACxC,UAAM,SAAS,MAAM,KAAK,IAAI,KAAK;AAAA,MACjC,UAAU,gBAAgB;AAAA,gBAChB,MAAM;AAAA;AAAA;AAAA;AAAA,MAIhB,CAAC,MAAM,EAAE;AAAA,IACX;AACA,WAAO,OAAO,QAAQ,EAAE,IAAI,eAAe;AAAA,EAC7C;AACF;;;ACzEO,SAAS,mBAAmB,KAAiC;AAKlE,QAAM,UAAU,IAAI,oBAAoB,GAAG;AAC3C,MAAI,IAAI,aAAa;AACnB,UAAMC,QAAO,IAAI,iBAAiB,GAAG;AACrC,UAAMC,YAAW,IAAI,qBAAqB,KAAKD,KAAI;AACnD,UAAME,SAAQ,IAAI,kBAAkB,GAAG;AACvC,UAAMC,SAAQ,IAAI,kBAAkB,GAAG;AACvC,WAAO,EAAE,MAAAH,OAAM,UAAAC,WAAU,OAAAC,QAAO,OAAAC,QAAO,QAAQ;AAAA,EACjD;AACA,QAAM,OAAO,IAAI,gBAAgB,GAAG;AACpC,QAAM,WAAW,IAAI,oBAAoB,KAAK,IAAI;AAClD,QAAM,QAAQ,IAAI,iBAAiB,GAAG;AACtC,QAAM,QAAQ,IAAI,iBAAiB,GAAG;AACtC,SAAO,EAAE,MAAM,UAAU,OAAO,OAAO,QAAQ;AACjD;;;AC/DA,SAAS,KAAAC,WAAS;AAWX,IAAM,YAAY;AAElB,IAAM,wBAAwBA,IAAE,OAAO;AAAA,EAC5C,SAASA,IAAE,QAAQ,CAAC;AAAA,EACpB,aAAaA,IAAE;AAAA,IACbA,IAAE,OAAO;AAAA,MACP,YAAYA,IAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,SAAS;AAAA,MACrD,OAAOA,IAAE,MAAMA,IAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,IAC1E,CAAC;AAAA,EACH;AACF,CAAC;AAGM,IAAM,2BAA2BA,IAAE,OAAO;AAAA,EAC/C,YAAYA,IACT,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN,MAAM,SAAS,EACf,SAAS,uCAAuC;AAAA,EACnD,OAAOA,IACJ,MAAMA,IAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC,EAChD,IAAI,CAAC,EACL,IAAI,EAAE,EACN;AAAA,IACC;AAAA,EACF;AACJ,CAAC;AAEM,IAAM,6BAA6BA,IAAE,OAAO;AAAA,EACjD,YAAYA,IACT,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN,MAAM,SAAS,EACf,SAAS,2DAA2D;AACzE,CAAC;AAEM,IAAM,wBAAwBA,IAAE,OAAO,CAAC,CAAC;AAEzC,IAAM,oBAAoBA,IAAE,OAAO;AAAA,EACxC,OAAOA,IACJ,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN;AAAA,IACC;AAAA,EACF;AACJ,CAAC;;;AC1CD,SAAS,SAAS,YAAoB,OAAuB;AAC3D,MAAI,CAAC,UAAU,KAAK,UAAU,GAAG;AAC/B,UAAM,IAAI;AAAA,MACR,uCAAuC,UAAU,uBAAkB,UAAU,MAAM;AAAA,IACrF;AAAA,EACF;AACA,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,UAAU,KAAK,CAAC,GAAG;AACtB,YAAM,IAAI;AAAA,QACR,iCAAiC,CAAC,uBAAkB,UAAU,MAAM;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,iBAAN,MAAqB;AAAA,EAClB,UACN,oBAAI,IAAI;AAAA,EACF,UAAoC,oBAAI,IAAI;AAAA;AAAA,EAEnC;AAAA,EAEjB,YACE,UACA,gBAAgE,CAAC,GACjE;AACA,UAAM,UAAU,oBAAI,IAAmC;AACvD,eAAW,KAAK,UAAU;AACxB,eAAS,EAAE,YAAY,EAAE,KAAK;AAC9B,cAAQ,IAAI,EAAE,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC;AACtC,WAAK,QAAQ,IAAI,EAAE,YAAY;AAAA,QAC7B,YAAY,EAAE;AAAA,QACd,OAAO,CAAC,GAAG,EAAE,KAAK;AAAA,QAClB,QAAQ;AAAA,MACV,CAAC;AACD,iBAAW,KAAK,EAAE,OAAO;AACvB,aAAK,QAAQ,IAAI,GAAG,EAAE,YAAY,EAAE,YAAY,QAAQ,UAAU,CAAC;AAAA,MACrE;AAAA,IACF;AACA,SAAK,kBAAkB;AACvB,eAAW,KAAK,eAAe;AAC7B,eAAS,EAAE,YAAY,EAAE,KAAK;AAC9B,YAAM,SAAsB,QAAQ,IAAI,EAAE,UAAU,IAChD,kBACA;AAEJ,iBAAW,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,OAAO,GAAG;AACtC,YAAI,EAAE,eAAe,EAAE,WAAY,MAAK,QAAQ,OAAO,CAAC;AAAA,MAC1D;AACA,WAAK,QAAQ,IAAI,EAAE,YAAY;AAAA,QAC7B,YAAY,EAAE;AAAA,QACd,OAAO,CAAC,GAAG,EAAE,KAAK;AAAA,QAClB;AAAA,MACF,CAAC;AACD,iBAAW,KAAK,EAAE,OAAO;AACvB,aAAK,QAAQ,IAAI,GAAG,EAAE,YAAY,EAAE,YAAY,OAAO,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAQ,MAAsB;AAC5B,WAAO,KAAK,QAAQ,IAAI,IAAI,GAAG,cAAc;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAAS,OAA6D;AACpE,aAAS,MAAM,YAAY,MAAM,KAAK;AACtC,UAAM,YAAY,KAAK,gBAAgB,IAAI,MAAM,UAAU;AAC3D,UAAM,SAAsB,YAAY,kBAAkB;AAE1D,eAAW,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,OAAO,GAAG;AACtC,UAAI,EAAE,eAAe,MAAM,WAAY,MAAK,QAAQ,OAAO,CAAC;AAAA,IAC9D;AACA,UAAM,SAAsB;AAAA,MAC1B,YAAY,MAAM;AAAA,MAClB,OAAO,CAAC,GAAG,MAAM,KAAK;AAAA,MACtB;AAAA,IACF;AACA,SAAK,QAAQ,IAAI,MAAM,YAAY,MAAM;AACzC,eAAW,KAAK,MAAM,OAAO;AAC3B,WAAK,QAAQ,IAAI,GAAG,EAAE,YAAY,MAAM,YAAY,OAAO,CAAC;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,WAAW,YAA0B;AACnC,UAAM,QAAQ,KAAK,QAAQ,IAAI,UAAU;AACzC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,kDAAkD,UAAU;AAAA,MAC9D;AAAA,IACF;AACA,QAAI,MAAM,WAAW,WAAW;AAC9B,YAAM,IAAI;AAAA,QACR,iEAAiE,UAAU;AAAA,MAC7E;AAAA,IACF;AAEA,QACE,MAAM,WAAW,mBACjB,KAAK,gBAAgB,IAAI,UAAU,GACnC;AACA,YAAM,eAAe,CAAC,GAAI,KAAK,gBAAgB,IAAI,UAAU,KAAK,CAAC,CAAE;AACrE,iBAAW,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,OAAO,GAAG;AACtC,YAAI,EAAE,eAAe,WAAY,MAAK,QAAQ,OAAO,CAAC;AAAA,MACxD;AACA,WAAK,QAAQ,IAAI,YAAY;AAAA,QAC3B;AAAA,QACA,OAAO;AAAA,QACP,QAAQ;AAAA,MACV,CAAC;AACD,iBAAW,KAAK,cAAc;AAC5B,aAAK,QAAQ,IAAI,GAAG,EAAE,YAAY,QAAQ,UAAU,CAAC;AAAA,MACvD;AACA;AAAA,IACF;AAEA,SAAK,QAAQ,OAAO,UAAU;AAC9B,eAAW,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,OAAO,GAAG;AACtC,UAAI,EAAE,eAAe,WAAY,MAAK,QAAQ,OAAO,CAAC;AAAA,IACxD;AAAA,EACF;AAAA;AAAA,EAGA,OAAsB;AACpB,WAAO,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,MACnD,GAAG;AAAA,MACH,OAAO,CAAC,GAAG,EAAE,KAAK;AAAA,IACpB,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAGE;AACA,UAAM,YAAY,KAAK,KAAK,EAAE;AAAA,MAC5B,CAAC,MAAM,EAAE,WAAW,UAAU,EAAE,WAAW;AAAA,IAC7C;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,aAAa,UAAU,IAAI,CAAC,OAAO;AAAA,QACjC,YAAY,EAAE;AAAA,QACd,OAAO,CAAC,GAAG,EAAE,KAAK;AAAA,MACpB,EAAE;AAAA,IACJ;AAAA,EACF;AACF;;;AC5KA,YAAYC,YAAU;;;ACrBtB;AAAA,EACE,SAAW;AAAA,EACX,aAAe;AAAA,IACb,EAAE,YAAc,OAAS,OAAS,CAAC,OAAO,QAAQ,MAAM,EAAE;AAAA,IAC1D,EAAE,YAAc,OAAS,OAAS,CAAC,OAAO,MAAM,EAAE;AAAA,IAClD,EAAE,YAAc,OAAS,OAAS,CAAC,KAAK,EAAE;AAAA,IAC1C,EAAE,YAAc,SAAS,OAAS,CAAC,OAAO,EAAE;AAAA,IAC5C,EAAE,YAAc,SAAS,OAAS,CAAC,OAAO,EAAE;AAAA,IAC5C,EAAE,YAAc,MAAS,OAAS,CAAC,IAAI,EAAE;AAAA,IACzC,EAAE,YAAc,OAAS,OAAS,CAAC,OAAO,MAAM,EAAE;AAAA,IAClD,EAAE,YAAc,OAAS,OAAS,CAAC,OAAO,MAAM,EAAE;AAAA,EACpD;AACF;;;ADgBA,SAAS,iBAAiB,SAAyB;AACjD,SAAY,YAAK,iBAAiB,OAAO,GAAG,kBAAkB;AAChE;AAQA,eAAsB,aAAa,MAEP;AAC1B,QAAM,eAAe,iBAAiB,KAAK,OAAO;AAIlD,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,aAAsB,YAAY;AAAA,EAChD,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,MAAM,aAAa,YAAY,KAAK,MAAM,EAAE;AAAA,EACxD;AACA,MAAI,cAA8D,CAAC;AACnE,MAAI,QAAQ,MAAM;AAChB,UAAM,SAAS,sBAAsB,UAAU,GAAG;AAClD,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,aAAa,YAAY,KAAK,OAAO,MAAM,OAAO,EAAE;AAAA,IACtE;AACA,kBAAc,OAAO,KAAK;AAAA,EAC5B;AACA,SAAO,IAAI,eAAe,iBAAa,aAAa,WAAW;AACjE;AAMA,eAAsB,iBACpB,SACA,UACe;AACf,QAAM,eAAe,iBAAiB,OAAO;AAC7C,QAAM,cAAc,cAAc,SAAS,OAAO,CAAC;AACrD;;;AErBA,eAAsB,yBACpB,OACA,UACA,SACqB;AACrB,QAAM,SAAS,SAAS,SAAS,KAAK;AACtC,QAAM,iBAAiB,SAAS,QAAQ;AACxC,SAAO;AAAA,IACL,0BAA0B,OAAO,UAAU,UAAU,OAAO,MAAM,MAAM,oBAAoB,OAAO,MAAM;AAAA,IACzG,EAAE,OAAO,OAAO;AAAA,EAClB;AACF;AAMA,eAAsB,2BACpB,OACA,UACA,SACqB;AACrB,WAAS,WAAW,MAAM,UAAU;AACpC,QAAM,iBAAiB,SAAS,QAAQ;AACxC,SAAO;AAAA,IACL,4BAA4B,MAAM,UAAU;AAAA,IAC5C,EAAE,SAAS,MAAM,WAAW;AAAA,EAC9B;AACF;AAKA,eAAsB,sBACpB,QACA,UACqB;AACrB,QAAM,UAAyB,SAAS,KAAK;AAC7C,SAAO;AAAA,IACL,gBAAgB,QAAQ,MAAM;AAAA,IAC9B,EAAE,QAAQ;AAAA,EACZ;AACF;AAQA,eAAsB,kBACpB,OACA,UACqB;AACrB,QAAM,OAAO,YAAY,MAAM,KAAK;AACpC,QAAM,aAAa,SAAS,QAAQ,IAAI;AAIxC,QAAM,QAAQ,SAAS,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,eAAe,UAAU;AACrE,QAAM,SACJ,SAAS,MAAM,MAAM,SAAS,IAAI,IAAI,MAAM,SAAS;AACvD,SAAO;AAAA,IACL,UAAU,MAAM,KAAK,qBAAqB,IAAI,wBAAmB,UAAU,aAAa,MAAM;AAAA,IAC9F,EAAE,MAAM,YAAY,OAAO;AAAA,EAC7B;AACF;AAeO,SAAS,oBACd,QACA,UACA,SACM;AACN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAMF,aAAa;AAAA,IACf;AAAA,IACA,OAAO,UAAU,yBAAyB,OAAO,UAAU,OAAO;AAAA,EACpE;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAGF,aAAa;AAAA,IACf;AAAA,IACA,OAAO,UAAU,2BAA2B,OAAO,UAAU,OAAO;AAAA,EACtE;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAGF,aAAa;AAAA,IACf;AAAA,IACA,OAAO,UAAU,sBAAsB,OAAO,QAAQ;AAAA,EACxD;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,aACE;AAAA,MAKF,aAAa;AAAA,IACf;AAAA,IACA,OAAO,UAAU,kBAAkB,OAAO,QAAQ;AAAA,EACpD;AACF;;;AC7KA,SAASC,KAAI,OAAuB;AAClC,SAAO,IAAI,iBAAiB,KAAK,CAAC;AACpC;AAWO,SAAS,mBACd,YACA,YACA,MACA,IACA,MACU;AACV,MAAI,WAAW,WAAW,GAAG;AAC3B,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AACA,QAAM,YAAY,MAAM;AACxB,QAAM,UAAU,MAAM;AACtB,QAAM,gBAAgB,aAAa,QAAQ,WAAW;AAEtD,QAAM,aAAa,WAAW,IAAIA,IAAG,EAAE,KAAK,IAAI;AAChD,QAAM,aAAa,gBACf,eAAeA,KAAI,SAAU,CAAC,gBAAgBA,KAAI,OAAQ,CAAC;AAAA,eAC3D;AACJ,SAAO;AAAA,IACL,KAAK;AAAA;AAAA;AAAA;AAAA,+BAIsBA,KAAI,UAAU,CAAC;AAAA,0BACpBA,KAAI,IAAI,CAAC;AAAA,0BACTA,KAAI,EAAE,CAAC;AAAA,cACnB,UAAU,kBAAkB,UAAU;AAAA;AAAA,EAElD;AACF;;;ACPA,IAAM,yBAAyB;AAmB/B,IAAM,4BAA4B;AAElC,SAAS,iBACP,OACA,OAC2B;AAC3B,QAAM,gBAAgB,MAAM,wBAAwB,MAAM;AAC1D,MAAI,iBAAiB,EAAG,QAAO;AAC/B,QAAM,QAAQ,MAAM,wBAAwB;AAC5C,MAAI,SAAS,uBAAwB,QAAO;AAC5C,QAAM,UACJ,kCAAkC,MAAM,qBAAqB,IAAI,aAAa,yCAC3C,MAAM,QAAQ,CAAC,CAAC,eAAe,uBAAuB,QAAQ,CAAC,CAAC;AACrG,SAAO;AAAA,IACL,YAAY,MAAM;AAAA,IAClB,MAAM,MAAM;AAAA,IACZ,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;AAAA,IAC/C,MAAM,MAAM;AAAA,IACZ,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,cAAc;AAAA,EAChB;AACF;AAEA,SAAS,oBACP,OACA,OAC2B;AAC3B,QAAM,gBAAgB,MAAM,iBAAiB,MAAM;AACnD,MAAI,iBAAiB,EAAG,QAAO;AAC/B,QAAM,QAAQ,MAAM,iBAAiB;AACrC,MAAI,SAAS,0BAA2B,QAAO;AAC/C,QAAM,UACJ,oBAAoB,MAAM,cAAc,IAAI,aAAa,kFAE/C,MAAM,QAAQ,CAAC,CAAC,eAAe,0BAA0B,QAAQ,CAAC,CAAC;AAC/E,SAAO;AAAA,IACL,YAAY,MAAM;AAAA,IAClB,MAAM,MAAM;AAAA,IACZ,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;AAAA,IAC/C,MAAM,MAAM;AAAA,IACZ,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,cAAc;AAAA,EAChB;AACF;AAEA,SAAS,qBAAqB,OAAwB;AACpD,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAWA,SAAS,gBAAgB,MAAuB;AAG9C,QAAM,IAAI,oBAAI,KAAK,GAAG,IAAI,YAAY;AACtC,QAAM,MAAM,EAAE,UAAU;AACxB,MAAI,QAAQ,KAAK,QAAQ,EAAG,QAAO;AAGnC,SAAO;AACT;AAEA,SAAS,0BACP,UACA,WACA,QACA,QACA,eACc;AACd,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,OAAO,YAAY,SAAS,IAAI,qBAAqB,SAAS,QAAQ,MAAM,KAAK,MAAM;AAAA,IACvF,SAAS;AAAA,MACP,UAAU,SAAS;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EACjB,YAAY,MAA0B;AACpC,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,MAAM,WAAW,MAAgD;AAC/D,UAAM,WAAW,KAAK,gBAAgB,KAAK,QAAQ;AACnD,UAAM,WAAW,KAAK,YAAY;AAElC,QAAI,KAAK,QAAQ;AACf,aAAO,EAAE,QAAQ,WAAW,aAAa,GAAG,SAAS,EAAE,QAAQ,UAAU,EAAE;AAAA,IAC7E;AAEA,QAAI,YAAY;AAChB,QAAI;AACJ,QAAI;AACJ,UAAM,mBAAmB,KAAK,uBAAuB,QAAQ;AAE7D,eAAW,UAAU,KAAK,SAAS;AACjC,YAAM,mBAAmB,OAAO,YAAY;AAC5C,YAAM,aAAa,KAAK,iBAAiB,gBAAgB;AACzD,YAAM,cAAc,KAAK;AAAA,QACvB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,UAAI,YAAa,QAAO;AACxB,UAAI;AACJ,UAAI;AACF,eAAO,MAAM,SAAS,UAAU;AAAA,UAC9B,QAAQ;AAAA,UACR,MAAM,KAAK;AAAA,UACX,IAAI,KAAK;AAAA,UACT,GAAG;AAAA,UACH;AAAA,QACF,CAAC;AAAA,MACH,SAAS,OAAO;AACd,cAAM,SAAS,KAAK,mBAAmB,UAAU,QAAQ,kBAAkB,OAAO,UAAU;AAC5F,YAAI,OAAQ,QAAO;AACnB,cAAM;AAAA,MACR;AAEA,UAAI,KAAK,WAAW,EAAG;AAEvB,YAAM,SAAS,KAAK,gBAAgB,IAAI;AACxC,iBAAW,CAAC,MAAM,OAAO,KAAK,QAAQ;AACpC,cAAM,KAAK,KAAK,OAAO,KAAK,UAAU,kBAAkB,MAAM,OAAO;AACrE,qBAAa,QAAQ;AACrB,YAAI,CAAC,WAAW,OAAO,QAAS,WAAU;AAC1C,YAAI,CAAC,WAAW,OAAO,QAAS,WAAU;AAAA,MAC5C;AAAA,IACF;AAEA,UAAM,aAAa,KAAK,kBAAkB,CAAC,UACvC,OACA,MAAM,KAAK,2BAA2B,KAAK,SAAS,SAAS,OAAQ;AAEzE,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,WAAW,UAAU,EAAE,MAAM,SAAS,IAAI,QAAS,IAAI;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,gBAAgB,UAAwD;AAE9E,QAAI,UAAU;AACZ,aAAO,aAAa,cAAc,IAAI,kBAAkB,IAAI,IAAI,gBAAgB;AAAA,IAClF;AACA,QAAI,KAAK,KAAK,gBAAiB,QAAO,KAAK,KAAK,gBAAgB;AAChE,WAAO,YAAY;AAAA,EACrB;AAAA,EAEQ,uBAAuB,UAA+E;AAC5G,YAAQ,UAAU;AAAA,MAChB,KAAK;AAAM,eAAO,EAAE,UAAU,OAAO,YAAY,EAAE;AAAA,MACnD,KAAK;AAAM,eAAO,EAAE,UAAU,UAAU,YAAY,EAAE;AAAA,MACtD,KAAK;AAAM,eAAO,EAAE,UAAU,UAAU,YAAY,EAAE;AAAA,MACtD,KAAK;AAAO,eAAO,EAAE,UAAU,UAAU,YAAY,GAAG;AAAA,MACxD,KAAK;AAAM,eAAO,EAAE,UAAU,QAAQ,YAAY,EAAE;AAAA,MACpD;AAAS,cAAM,IAAI,MAAM,qBAAqB,QAAQ,EAAE;AAAA,IAC1D;AAAA,EACF;AAAA,EAEQ,iBAAiB,QAA8C;AACrE,UAAM,aAAa,oBAAI,IAAI,CAAC,OAAO,SAAS,SAAS,SAAS,OAAO,OAAO,KAAK,CAAC;AAClF,QAAI,WAAW,IAAI,MAAM,EAAG,QAAO;AACnC,QAAI,6BAA6B,KAAK,MAAM,EAAG,QAAO;AACtD,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,MAAuC;AAC7D,UAAM,MAAM,oBAAI,IAAsB;AACtC,eAAW,OAAO,MAAM;AACtB,YAAM,OAAO,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC;AACnC,WAAK,KAAK,GAAG;AACb,UAAI,IAAI,IAAI,MAAM,IAAI;AAAA,IACxB;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,2BACZ,SACA,MACA,IACuC;AACvC,eAAW,UAAU,SAAS;AAC5B,YAAM,KAAK,KAAK,OAAO,SAAS,QAAQ,OAAO,YAAY,GAAG,MAAM,EAAE;AAAA,IACxE;AACA,WAAO,EAAE,MAAM,GAAG;AAAA,EACpB;AAAA,EAEQ,mBACN,UACA,WACA,QACA,OACA,YACqB;AACrB,UAAM,UAAU,qBAAqB,KAAK;AAC1C,QACE,SAAS,SAAS,aAClB,eAAe,WACf,mDAAmD,KAAK,OAAO,GAC/D;AACA,YAAM,SAAS,cAAc,UACzB,yFACA;AACJ,aAAO,0BAA0B,UAAU,WAAW,QAAQ,QAAQ,OAAO;AAAA,IAC/E;AACA,QAAI,SAAS,SAAS,aAAa,qBAAqB,KAAK,OAAO,GAAG;AACrE,YAAM,SAAS,eAAe,UAC1B,6EACA;AACJ,aAAO,0BAA0B,UAAU,WAAW,QAAQ,QAAQ,OAAO;AAAA,IAC/E;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,yBACN,UACA,WACA,QACA,YACA,YACqB;AACrB,QAAI,cAAc,UAAU,YAAY;AACtC,YAAM,OAAO,SAAS,aAAa;AACnC,UAAI,WAAW,aAAa,SAAS,CAAC,KAAK,WAAW;AACpD,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,UAAI,WAAW,aAAa,UAAU,CAAC,KAAK,aAAa,CAAC,KAAK,aAAa;AAC1E,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,wBAAwB,OAAO,KAAK,SAAS,CAAC,eAAe,OAAO,KAAK,UAAU,CAAC;AAAA,QACtF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,SAAS,SAAS,aAAa,eAAe,SAAS;AACzD,UAAI,cAAc,QAAQ;AACxB,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,UAAI,cAAc,SAAS;AACzB,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,sBACZ,SACA,QACA,MACA,QACuB;AACvB,QAAI,OAAO,WAAW,iBAAiB,OAAO,WAAW,SAAS;AAChE,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,WAAW,YAAY,SACzB,MAAM,KAAK,KAAK,OAAO,KAAK,YAAY,OAAO,YAAY,GAAG,MAAM,IAAI,IACxE,MAAM,KAAK,KAAK,OAAO,MAAM,YAAY,OAAO,YAAY,GAAG,MAAM,IAAI;AAC7E,UAAI,SAAS,cAAc,EAAG,QAAO;AAErC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,WAAW,EAAE,MAAM,MAAM,IAAI,KAAK;AAAA,QAClC,SAAS;AAAA,UACP,GAAI,OAAO,WAAW,CAAC;AAAA,UACvB,QAAQ;AAAA,UACR;AAAA,UACA,QAAQ,OAAO,YAAY;AAAA,UAC3B,gBAAgB,OAAO;AAAA,UACvB,gBAAgB;AAAA,YACd,YAAY,SAAS;AAAA,YACrB,UAAU,SAAS;AAAA,YACnB,QAAQ,SAAS;AAAA,UACnB;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,6BACN,UACqC;AACrC,QAAI,SAAS,SAAS,aAAa,SAAS,SAAS,aAAa;AAChE,aAAO,SAAS;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,gBACZ,YACA,MACA,MACA,uBACwD;AACxD,QAAI,KAAK,WAAW,GAAG;AACrB,aAAO;AAAA,QACL;AAAA,QACA,OAAO;AAAA,UACL,aAAa;AAAA,UACb,oBAAoB;AAAA,UACpB,cAAc;AAAA,UACd,qBAAqB;AAAA,UACrB,uBAAuB;AAAA,UACvB,gBAAgB;AAAA,UAChB,gBAAgB;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,CAAC,WAAW,cAAc,IAAI,MAAM,QAAQ,IAAI;AAAA,MACpD,KAAK,KAAK,OAAO,MAAM,UAAU,YAAY,IAAI;AAAA,MACjD,KAAK,KAAK,OAAO,KAAK,SAAS,YAAY,MAAM,IAAI;AAAA,IACvD,CAAC;AAED,UAAM,mBAAmB,IAAI;AAAA,MAC3B,UAAU,IAAI,CAAC,aAAa,CAAC,SAAS,QAAQ,QAAQ,CAAU;AAAA,IAClE;AACA,UAAM,wBAAwB,oBAAI,IAAoB;AACtD,eAAW,OAAO,gBAAgB;AAChC,UAAI,IAAI,SAAS,QAAQ,CAAC,IAAI,QAAQ,EAAE,IAAI,OAAO,GAAI;AACvD,YAAM,OAAO,IAAI,KAAK,MAAM,GAAG,CAAC;AAChC,UAAI,CAAC,sBAAsB,IAAI,IAAI,GAAG;AACpC,8BAAsB,IAAI,MAAM,IAAI,IAAI;AAAA,MAC1C;AAAA,IACF;AAEA,UAAM,QAAQ,MAAM,yBAAyB;AAAA,MAC3C;AAAA,MACA,SAAS,CAAC,QAAQ,IAAI,UAAU,MAAM,GAAG,EAAE;AAAA,MAC3C,SAAS,CAAC,QAAQ,IAAI,UAAU,MAAM,IAAI,EAAE;AAAA,MAC5C,QAAQ,CAAC,SAAS,IAAI,MAAM,IAAI,OAAO;AAAA,MACvC,iBAAiB,CAAC,QAAQ;AACxB,cAAM,WAAW,iBAAiB,IAAI,IAAI,UAAU;AACpD,YAAI,CAAC,SAAU,QAAO;AACtB,eAAO;AAAA,UACL,eAAe,SAAS;AAAA,UACxB,QAAQ,SAAS;AAAA,UACjB,YAAY,SAAS;AAAA,QACvB;AAAA,MACF;AAAA,MACA,oBAAoB,CAAC,UAAU,SAAS,sBAAsB,IAAI,IAAI;AAAA,MACtE,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AAED,WAAO,EAAE,MAAM,MAAM;AAAA,EACvB;AAAA,EAEA,MAAM,aAAa,MAAkD;AACnE,UAAM,aAAa,KAAK,WAAW,KAAK,QAAQ,SAAS;AACzD,UAAM,iBAAiB,KAAK,eAAe,KAAK,YAAY,SAAS;AACrE,QAAI,eAAe,gBAAgB;AACjC,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,gBAAgB,KAAK,QAAQ;AAWnD,QAAI,KAAK,QAAQ;AACf,aAAO,EAAE,QAAQ,WAAW,aAAa,GAAG,SAAS,EAAE,QAAQ,UAAU,EAAE;AAAA,IAC7E;AAEA,WAAO,iBACH,KAAK;AAAA,MACH;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,IACP,IACA,KAAK,qBAAqB,UAAU,KAAK,SAAU,KAAK,MAAM,KAAK,EAAE;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,qBACZ,UACA,SACA,MACA,IACuB;AACvB,QAAI,OAAO,SAAS,gBAAgB,YAAY;AAC9C,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,OAAO,YAAY,SAAS,IAAI;AAAA,MAClC;AAAA,IACF;AAEA,QAAI,YAAY;AAChB,QAAI;AACJ,QAAI;AACJ,UAAM,UAAgC,CAAC;AAEvC,eAAW,UAAU,SAAS;AAC5B,UAAI;AACJ,UAAI;AACF,iBAAS,MAAM,SAAS,YAAY,QAAQ,MAAM,EAAE;AAAA,MACtD,SAAS,OAAO;AACd,cAAM,SAAS,KAAK,mBAAmB,UAAU,UAAU,QAAQ,OAAO,QAAQ;AAClF,YAAI,OAAQ,QAAO;AACnB,cAAM;AAAA,MACR;AACA,YAAM,UAAU,MAAM,KAAK;AAAA,QACzB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,mBAAa,QAAQ;AACrB,UAAI,QAAQ,YAAY,CAAC,WAAW,QAAQ,UAAU,SAAU,WAAU,QAAQ;AAClF,UAAI,QAAQ,YAAY,CAAC,WAAW,QAAQ,UAAU,SAAU,WAAU,QAAQ;AAClF,UAAI,QAAQ,QAAQ,SAAS,EAAG,SAAQ,KAAK,GAAG,QAAQ,OAAO;AAAA,IACjE;AAEA,WAAO;AAAA,MACL,QAAQ,QAAQ,SAAS,IAAI,YAAY;AAAA,MACzC,aAAa;AAAA,MACb,WAAW,UAAU,EAAE,MAAM,SAAS,IAAI,QAAS,IAAI;AAAA,MACvD,GAAI,QAAQ,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,yBACZ,UACA,aACA,MACA,IACA,YACuB;AACvB,UAAM,OAAO,SAAS,aAAa;AACnC,QAAI,CAAC,KAAK,cAAc,OAAO,SAAS,oBAAoB,YAAY;AACtE,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,OAAO,YAAY,SAAS,IAAI,sEAAsE,KAAK,UAAU;AAAA,MACvH;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,eAAe,MAAM,EAAE;AAC1C,QAAI,YAAY;AAChB,QAAI;AACJ,QAAI;AACJ,UAAM,UAAgC,CAAC;AAEvC,eAAW,cAAc,aAAa;AACpC,YAAM,kBAAkB,WAAW,YAAY;AAC/C,iBAAW,QAAQ,OAAO;AACxB,cAAM,QAAQ,MAAM,KAAK;AAAA,UACvB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,YAAI,MAAM,cAAc,GAAG;AACzB,uBAAa,MAAM;AACnB,cAAI,CAAC,WAAW,OAAO,QAAS,WAAU;AAC1C,cAAI,CAAC,WAAW,OAAO,QAAS,WAAU;AAAA,QAC5C;AACA,YAAI,MAAM,QAAQ,SAAS,EAAG,SAAQ,KAAK,GAAG,MAAM,OAAO;AAG3D,cAAM,KAAK,SAAS,YAAY;AAAA,UAC9B,MAAM;AAAA,UACN,YAAY;AAAA,UACZ;AAAA,UACA,aAAa,MAAM;AAAA,QACrB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ,QAAQ,SAAS,IAAI,YAAY;AAAA,MACzC,aAAa;AAAA,MACb,WAAW,UAAU,EAAE,MAAM,SAAS,IAAI,QAAS,IAAI;AAAA,MACvD,GAAI,QAAQ,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,SACZ,UACA,OACe;AACf,QAAI,CAAC,SAAU;AACf,QAAI;AACF,YAAM,SAAS,KAAK;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,gBACZ,UACA,iBACA,MACA,YACiE;AAIjE,UAAM,iBAAiB,KAAK,KAAK,OAAO,MAAM;AAI9C,UAAM,qBAAqB,eAAe,QAAQ,eAAe;AACjE,UAAM,gBAAgB,oBAAI,IAAoB;AAC9C,UAAM,SAAS,oBAAI,IAAwB;AAO3C,UAAM,kBAAkB,aACpB,CAAC,SAQK;AAIJ,WAAK,KAAK,SAAS,YAAY;AAAA,QAC7B,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,MAAM,KAAK;AAAA,QACX,OAAO,KAAK;AAAA,QACZ,MAAM,KAAK;AAAA,QACX,QAAQ,KAAK;AAAA,QACb,OAAO,KAAK;AAAA,QACZ,oBAAoB,KAAK;AAAA,QACzB,gBAAgB,KAAK;AAAA,MACvB,CAAC;AAAA,IACH,IACA;AAEJ,UAAM,SAAS,SAAS,gBAAiB,EAAE,YAAY,iBAAiB,MAAM,gBAAgB,CAAC;AAC/F,qBAAiB,SAAS,QAAQ;AAChC,iBAAW,OAAO,OAAO;AACvB,cAAM,OAAO,YAAY,IAAI,MAAM;AACnC,YAAI,qBAAqB,cAAc,IAAI,IAAI;AAC/C,YAAI,uBAAuB,QAAW;AACpC,+BAAqB,eAAe,QAAQ,IAAI;AAChD,wBAAc,IAAI,MAAM,kBAAkB;AAAA,QAC5C;AAOA,YAAI,uBAAuB,oBAAoB;AAC7C,gBAAM,IAAI;AAAA,YACR,2DAA2D,IAAI,MAAM,oBACjD,IAAI,yBAAyB,kBAAkB,yBAC1C,kBAAkB,0BAA0B,eAAe,YAAY,IAAI;AAAA,UAGtG;AAAA,QACF;AACA,YAAI,OAAO,OAAO,IAAI,kBAAkB;AACxC,YAAI,CAAC,MAAM;AACT,iBAAO,CAAC;AACR,iBAAO,IAAI,oBAAoB,IAAI;AAAA,QACrC;AACA,aAAK,KAAK;AAAA,UACR,YAAY,IAAI;AAAA,UAChB,WAAW,IAAI;AAAA,UACf,KAAK,IAAI;AAAA,UACT,KAAK,IAAI;AAAA,UACT,OAAO,IAAI,SAAS;AAAA,UACpB,OAAO,IAAI,SAAS;AAAA,UACpB,OAAO,IAAI,SAAS;AAAA,UACpB,MAAM,IAAI,QAAQ;AAAA,UAClB,IAAI,IAAI,MAAM;AAAA,UACd,eAAe,IAAI,iBAAiB;AAAA,UACpC,iBAAiB,IAAI,mBAAmB;AAAA,UACxC,WAAW,IAAI,aAAa;AAAA,UAC5B,YAAY,IAAI,cAAc;AAAA,UAC9B,cAAc,IAAI,gBAAgB;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI,YAAY;AAChB,UAAM,UAAgC,CAAC;AACvC,eAAW,CAAC,oBAAoB,IAAI,KAAK,QAAQ;AAC/C,UAAI,KAAK,WAAW,EAAG;AACvB,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,KAAK;AAAA,UACpB;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,6BAA6B,QAAQ;AAAA,QAC5C;AAAA,MACF,SAAS,OAAO;AACd,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAGrE,gBAAQ;AAAA,UACN;AAAA,UACA;AAAA,YACE,YAAY;AAAA,YACZ;AAAA,YACA,MAAM,KAAK;AAAA,YACX,OAAO;AAAA,UACT;AAAA,QACF;AACA,gBAAQ,KAAK;AAAA,UACX,YAAY;AAAA,UACZ;AAAA,UACA,MAAM,KAAK;AAAA,UACX,QAAQ;AAAA,UACR,OAAO;AAAA,QACT,CAAC;AACD;AAAA,MACF;AACA,YAAM,MAAM,iBAAiB,SAAS,OAAO;AAAA,QAC3C,YAAY;AAAA,QACZ;AAAA,QACA,MAAM,KAAK;AAAA,MACb,CAAC;AACD,YAAM,iBAAiB,oBAAoB,SAAS,OAAO;AAAA,QACzD,YAAY;AAAA,QACZ;AAAA,QACA,MAAM,KAAK;AAAA,MACb,CAAC;AACD,UAAI,KAAK;AACP,gBAAQ,KAAK,kDAAkD;AAAA,UAC7D,YAAY;AAAA,UACZ;AAAA,UACA,MAAM,KAAK;AAAA,UACX,cAAc,IAAI;AAAA,QACpB,CAAC;AACD,gBAAQ,KAAK,GAAG;AAAA,MAClB;AACA,UAAI,gBAAgB;AAClB,gBAAQ,KAAK,qDAAqD;AAAA,UAChE,YAAY;AAAA,UACZ;AAAA,UACA,MAAM,KAAK;AAAA,UACX,cAAc,eAAe;AAAA,QAC/B,CAAC;AACD,gBAAQ,KAAK,cAAc;AAAA,MAC7B;AACA,UAAI,OAAO,eAAgB;AAC3B,YAAM,KAAK,KAAK,OAAO,MAAM,YAAY,oBAAoB,MAAM,SAAS,IAAI;AAChF,mBAAa,KAAK;AAAA,IACpB;AACA,WAAO,EAAE,aAAa,WAAW,QAAQ;AAAA,EAC3C;AAAA,EAEA,MAAc,qBACZ,UACA,QACA,QACqG;AACrG,UAAM,OAAO,YAAY,MAAM;AAC/B,UAAM,aAAa,KAAK,KAAK,OAAO,MAAM,QAAQ,QAAQ,IAAI;AAE9D,UAAM,SAAS,oBAAI,IAAwB;AAC3C,eAAW,CAAC,KAAK,KAAK,KAAK,QAAQ;AACjC,YAAM,WAAW,IAAI,QAAQ,GAAG;AAChC,UAAI,aAAa,GAAI;AACrB,YAAM,OAAO,IAAI,MAAM,GAAG,QAAQ;AAClC,YAAM,OAAO,OAAO,IAAI,IAAI,KAAK,CAAC;AAClC,WAAK,KAAK;AAAA,QACR,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,KAAK,MAAM;AAAA,QACX,KAAK,MAAM;AAAA,QACX,QAAQ,MAAM,UAAU;AAAA,QACxB,OAAO,MAAM,SAAS;AAAA,QACtB,OAAO,MAAM,SAAS;AAAA,QACtB,OAAO,MAAM,SAAS;AAAA,QACtB,MAAM,MAAM,QAAQ;AAAA,QACpB,IAAI,MAAM,MAAM;AAAA,QAChB,eAAe,MAAM,iBAAiB;AAAA,QACtC,iBAAiB,MAAM,mBAAmB;AAAA,QAC1C,WAAW,MAAM,aAAa;AAAA,QAC9B,YAAY,MAAM,cAAc;AAAA,QAChC,cAAc,MAAM,gBAAgB;AAAA,MACtC,CAAC;AACD,aAAO,IAAI,MAAM,IAAI;AAAA,IACvB;AAEA,QAAI,cAAc;AAClB,QAAI;AACJ,QAAI;AACJ,UAAM,UAAgC,CAAC;AACvC,eAAW,CAAC,MAAM,IAAI,KAAK,QAAQ;AACjC,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,KAAK;AAAA,UACpB;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,6BAA6B,QAAQ;AAAA,QAC5C;AAAA,MACF,SAAS,OAAO;AACd,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAGrE,gBAAQ;AAAA,UACN;AAAA,UACA;AAAA,YACE;AAAA,YACA;AAAA,YACA;AAAA,YACA,MAAM,KAAK;AAAA,YACX,OAAO;AAAA,UACT;AAAA,QACF;AACA,gBAAQ,KAAK;AAAA,UACX;AAAA,UACA;AAAA,UACA;AAAA,UACA,MAAM,KAAK;AAAA,UACX,QAAQ;AAAA,UACR,OAAO;AAAA,QACT,CAAC;AACD;AAAA,MACF;AACA,YAAM,MAAM,iBAAiB,SAAS,OAAO;AAAA,QAC3C;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM,KAAK;AAAA,MACb,CAAC;AACD,YAAM,iBAAiB,oBAAoB,SAAS,OAAO;AAAA,QACzD;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM,KAAK;AAAA,MACb,CAAC;AACD,UAAI,KAAK;AACP,gBAAQ,KAAK,uDAAuD;AAAA,UAClE;AAAA,UACA;AAAA,UACA;AAAA,UACA,MAAM,KAAK;AAAA,UACX,cAAc,IAAI;AAAA,QACpB,CAAC;AACD,gBAAQ,KAAK,GAAG;AAAA,MAClB;AACA,UAAI,gBAAgB;AAClB,gBAAQ,KAAK,0DAA0D;AAAA,UACrE;AAAA,UACA;AAAA,UACA;AAAA,UACA,MAAM,KAAK;AAAA,UACX,cAAc,eAAe;AAAA,QAC/B,CAAC;AACD,gBAAQ,KAAK,cAAc;AAAA,MAC7B;AACA,UAAI,OAAO,eAAgB;AAC3B,YAAM,KAAK,KAAK,OAAO,MAAM,YAAY,YAAY,MAAM,SAAS,IAAI;AACxE,qBAAe,KAAK;AACpB,UAAI,CAAC,WAAW,OAAO,QAAS,WAAU;AAC1C,UAAI,CAAC,WAAW,OAAO,QAAS,WAAU;AAAA,IAC5C;AACA,WAAO,EAAE,aAAa,SAAS,SAAS,QAAQ;AAAA,EAClD;AAAA,EAEA,MAAM,YAAY,MAAiD;AACjE,UAAM,WAAW,KAAK,gBAAgB,KAAK,QAAQ;AAEnD,QAAI,OAAO,SAAS,sBAAsB,YAAY;AACpD,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,OAAO,YAAY,SAAS,IAAI;AAAA,MAClC;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ;AACf,aAAO,EAAE,QAAQ,WAAW,aAAa,GAAG,SAAS,EAAE,QAAQ,UAAU,EAAE;AAAA,IAC7E;AAEA,QAAI,YAAY;AAEhB,eAAW,cAAc,KAAK,aAAa;AACzC,YAAM,kBAAkB,WAAW,YAAY;AAC/C,YAAM,aAAa,KAAK,iBAAiB,eAAe;AACxD,YAAM,cAAc,KAAK,yBAAyB,UAAU,SAAS,iBAAiB,UAAU;AAChG,UAAI,YAAa,QAAO;AAExB,YAAM,QAAQ,KAAK,eAAe,KAAK,MAAM,KAAK,EAAE;AACpD,iBAAW,QAAQ,OAAO;AACxB,YAAI;AACJ,YAAI;AACF,mBAAS,MAAM,SAAS,kBAAmB;AAAA,YACzC,YAAY;AAAA,YACZ,OAAO;AAAA,YACP,SAAS;AAAA,UACX,CAAC;AAAA,QACH,SAAS,OAAO;AACd,gBAAM,SAAS,KAAK,mBAAmB,UAAU,SAAS,iBAAiB,OAAO,UAAU;AAC5F,cAAI,OAAQ,QAAO;AACnB,gBAAM;AAAA,QACR;AAEA,YAAI,OAAO,UAAU,WAAW,EAAG;AAGnC,cAAM,OAAO,OAAO,UAAU,IAAI,CAAC,OAAO;AAAA,UACxC,YAAY;AAAA,UACZ;AAAA,UACA,QAAQ,EAAE;AAAA,UACV,eAAe,EAAE;AAAA,UACjB,QAAQ,EAAE;AAAA,UACV,YAAY,EAAE;AAAA,UACd,KAAK,KAAK,WAAW,MAAM,EAAE,UAAU;AAAA,UACvC,gBAAgB,EAAE;AAAA,QACpB,EAAE;AAEF,cAAM,KAAK,KAAK,OAAO,MAAM,WAAW,iBAAiB,MAAM,IAAI;AACnE,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,WAAW,EAAE,MAAM,KAAK,MAAM,IAAI,KAAK,GAAG;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,MAAM,mBAAmB,MAAwD;AAC/E,UAAM,WAAW,KAAK,gBAAgB,KAAK,QAAQ;AAEnD,UAAM,OAAO,SAAS,aAAa;AACnC,QAAI,CAAC,KAAK,cAAc,OAAO,SAAS,sBAAsB,YAAY;AACxE,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,OAAO,YAAY,SAAS,IAAI,iEAAiE,KAAK,UAAU;AAAA,MAClH;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ;AACf,aAAO,EAAE,QAAQ,WAAW,aAAa,GAAG,SAAS,EAAE,QAAQ,UAAU,EAAE;AAAA,IAC7E;AAEA,UAAM,iBAAiB,KAAK,KAAK,OAAO,MAAM;AAC9C,QAAI,YAAY;AAChB,QAAI;AACJ,QAAI;AAEJ,eAAW,cAAc,KAAK,aAAa;AACzC,YAAM,kBAAkB,WAAW,YAAY;AAC/C,UAAI;AACJ,UAAI;AACF,iBAAS,MAAM,SAAS,kBAAmB;AAAA,UACzC,YAAY;AAAA,UACZ,MAAM,KAAK;AAAA,UACX,IAAI,KAAK;AAAA,QACX,CAAC;AAAA,MACH,SAAS,OAAO;AACd,cAAM,SAAS,KAAK,mBAAmB,UAAU,SAAS,iBAAiB,OAAO,QAAQ;AAC1F,YAAI,OAAQ,QAAO;AACnB,cAAM;AAAA,MACR;AAIA,YAAM,cAAc,oBAAI,IAA0B;AAClD,YAAM,gBAAgB,oBAAI,IAAoB;AAC9C,iBAAW,OAAO,QAAQ;AACxB,cAAM,OAAO,YAAY,IAAI,MAAM;AACnC,YAAI,WAAW,cAAc,IAAI,IAAI;AACrC,YAAI,aAAa,QAAW;AAC1B,qBAAW,eAAe,QAAQ,IAAI;AACtC,wBAAc,IAAI,MAAM,QAAQ;AAAA,QAClC;AACA,cAAM,MAAM,GAAG,QAAQ,KAAI,IAAI,IAAI;AACnC,YAAI,OAAO,YAAY,IAAI,GAAG;AAC9B,YAAI,CAAC,MAAM;AACT,iBAAO,CAAC;AACR,sBAAY,IAAI,KAAK,IAAI;AAAA,QAC3B;AACA,aAAK,KAAK;AAAA,UACR,YAAY,IAAI;AAAA,UAChB,YAAY;AAAA,UACZ,MAAM,IAAI;AAAA,UACV,YAAY,IAAI;AAAA,UAChB,QAAQ,IAAI;AAAA,UACZ,OAAO,IAAI;AAAA,UACX,eAAe,IAAI;AAAA,UACnB,QAAQ,SAAS;AAAA,QACnB,CAAC;AAAA,MACH;AAEA,iBAAW,CAAC,KAAK,IAAI,KAAK,aAAa;AACrC,YAAI,KAAK,WAAW,EAAG;AACvB,cAAM,CAAC,UAAU,IAAI,IAAI,IAAI,MAAM,IAAG;AACtC,cAAM,KAAK,KAAK,OAAO,QAAQ,aAAa,UAAU,MAAM,IAAI;AAChE,qBAAa,KAAK;AAClB,YAAI,CAAC,WAAW,OAAO,QAAS,WAAU;AAC1C,YAAI,CAAC,WAAW,OAAO,QAAS,WAAU;AAAA,MAC5C;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,WAAW,UAAU,EAAE,MAAM,SAAS,IAAI,QAAS,IAAI,EAAE,MAAM,KAAK,MAAM,IAAI,KAAK,GAAG;AAAA,IACxF;AAAA,EACF;AAAA,EAEQ,eAAe,MAAc,IAAsB;AACzD,UAAM,QAAkB,CAAC;AACzB,UAAM,UAAU,oBAAI,KAAK,GAAG,IAAI,YAAY;AAC5C,UAAM,MAAM,oBAAI,KAAK,GAAG,EAAE,YAAY;AACtC,WAAO,WAAW,KAAK;AACrB,YAAM,IAAI,QAAQ,eAAe;AACjC,YAAM,IAAI,OAAO,QAAQ,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AAC3D,YAAM,IAAI,OAAO,QAAQ,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACtD,YAAM,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;AAC3B,cAAQ,WAAW,QAAQ,WAAW,IAAI,CAAC;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,WAAW,MAAc,YAA4B;AAC3D,UAAM,WAAW;AACjB,UAAM,UAAS,oBAAI,KAAK,GAAG,IAAI,YAAY,GAAE,QAAQ;AACrD,UAAM,SAAQ,oBAAI,KAAK,GAAG,UAAU,YAAY,GAAE,QAAQ;AAC1D,WAAO,KAAK,IAAI,GAAG,KAAK,OAAO,QAAQ,UAAU,QAAQ,CAAC;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,eAAe,MAAoD;AACvE,UAAM,cAAc,qBAAqB,KAAK,SAAS;AACvD,QAAI,aAAa;AACf,aAAO,EAAE,QAAQ,SAAS,aAAa,GAAG,OAAO,YAAY;AAAA,IAC/D;AAEA,QAAI,KAAK,QAAQ;AACf,aAAO,EAAE,QAAQ,WAAW,aAAa,GAAG,SAAS,EAAE,QAAQ,UAAU,EAAE;AAAA,IAC7E;AAEA,UAAM,gBAAgB,KAAK,UAAU;AACrC,QAAI,CAAC,eAAe;AAClB,aAAO,EAAE,QAAQ,SAAS,aAAa,GAAG,OAAO,6BAA6B;AAAA,IAChF;AAEA,QAAI;AACF,cAAQ,KAAK,aAAa;AAAA,QACxB,KAAK,aAAa;AAChB,gBAAM,SAAS,KAAK,UAAU;AAC9B,cAAI,CAAC,QAAQ;AACX,mBAAO,EAAE,QAAQ,SAAS,aAAa,GAAG,OAAO,2DAA2D;AAAA,UAC9G;AACA,gBAAM,EAAE,SAAS,IAAI,MAAM,KAAK,KAAK,OAAO,KAAK;AAAA,YAC/C,EAAE,QAAQ,OAAO,YAAY,GAAG,MAAM,cAAc;AAAA,YACpD,KAAK;AAAA,UACP;AACA,iBAAO,EAAE,QAAQ,MAAM,aAAa,UAAU,WAAW,EAAE,MAAM,eAAe,IAAI,cAAc,EAAE;AAAA,QACtG;AAAA,QACA,KAAK,iBAAiB;AACpB,gBAAM,aAAa,KAAK,UAAU;AAClC,cAAI,CAAC,YAAY;AACf,mBAAO,EAAE,QAAQ,SAAS,aAAa,GAAG,OAAO,mEAAmE;AAAA,UACtH;AACA,gBAAM,EAAE,SAAS,IAAI,MAAM,KAAK,KAAK,OAAO,MAAM;AAAA,YAChD,EAAE,YAAY,WAAW,YAAY,GAAG,MAAM,cAAc;AAAA,YAC5D,KAAK;AAAA,UACP;AACA,iBAAO,EAAE,QAAQ,MAAM,aAAa,UAAU,WAAW,EAAE,MAAM,eAAe,IAAI,cAAc,EAAE;AAAA,QACtG;AAAA,QACA,KAAK,gBAAgB;AACnB,gBAAM,aAAa,KAAK,UAAU;AAClC,cAAI,CAAC,YAAY;AACf,mBAAO,EAAE,QAAQ,SAAS,aAAa,GAAG,OAAO,kEAAkE;AAAA,UACrH;AACA,gBAAM,EAAE,SAAS,IAAI,MAAM,KAAK,KAAK,OAAO,MAAM;AAAA,YAChD,EAAE,YAAY,WAAW,YAAY,GAAG,MAAM,cAAc;AAAA,YAC5D,KAAK;AAAA,UACP;AACA,iBAAO,EAAE,QAAQ,MAAM,aAAa,UAAU,WAAW,EAAE,MAAM,eAAe,IAAI,cAAc,EAAE;AAAA,QACtG;AAAA,QACA,SAAS;AACP,gBAAM,cAAqB,KAAK;AAChC,iBAAO,EAAE,QAAQ,SAAS,aAAa,GAAG,OAAO,wBAAwB,OAAO,WAAW,CAAC,GAAG;AAAA,QACjG;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,aAAO,EAAE,QAAQ,SAAS,aAAa,GAAG,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IACpG;AAAA,EACF;AAAA,EAEA,MAAM,kBAAkB,MAAuD;AAE7E,UAAM,KAAK,KAAK,OAAO,SAAS,eAAe,KAAK,MAAM,KAAK,EAAE;AACjE,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,WAAW,EAAE,MAAM,KAAK,MAAM,IAAI,KAAK,GAAG;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,MAA8C;AAM1D,QAAI,gBAAgB,KAAK,IAAI,GAAG;AAC9B,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,cAAc,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,QAAQ,CAAC,GAAG,cAAc,CAAC,GAAG,YAAY,KAAK;AAAA,QACpF,UAAU,CAAC;AAAA,QACX,QAAQ,CAAC;AAAA,MACX;AAAA,IACF;AAEA,UAAM,aAAa,oBAAI,IAAI,CAAC,OAAO,SAAS,SAAS,KAAK,CAAC;AAC3D,UAAM,iBAAiB,KAAK,qBAAqB;AACjD,UAAM,SAAmB,CAAC;AAQ1B,UAAM,cAA8B,CAAC;AACrC,eAAW,UAAU,KAAK,aAAa;AACrC,YAAM,YAAY,MAAM,KAAK,WAAW;AAAA,QACtC,SAAS,CAAC,MAAM;AAAA,QAChB,MAAM,KAAK;AAAA,QACX,IAAI,KAAK;AAAA,QACT,UAAU;AAAA,QACV,UAAU,KAAK;AAAA,MACjB,CAAC;AACD,YAAM,SAAS,MAAM,KAAK,sBAAsB,QAAQ,QAAQ,KAAK,MAAM,SAAS;AACpF,kBAAY,KAAK,MAAM;AACvB,UAAI,OAAO,WAAW,QAAS,QAAO,KAAK,QAAQ,MAAM,KAAK,OAAO,KAAK,EAAE;AAAA,IAC9E;AAGA,UAAM,eAA+B,CAAC;AACtC,eAAW,cAAc,KAAK,oBAAoB,CAAC,GAAG;AACpD,YAAM,YAAY,MAAM,KAAK,YAAY;AAAA,QACvC,aAAa,CAAC,UAAU;AAAA,QACxB,MAAM,KAAK;AAAA,QACX,IAAI,KAAK;AAAA,QACT,UAAU,KAAK;AAAA,MACjB,CAAC;AACD,YAAM,SAAS,MAAM,KAAK,sBAAsB,SAAS,YAAY,KAAK,MAAM,SAAS;AACzF,mBAAa,KAAK,MAAM;AACxB,UAAI,OAAO,WAAW,QAAS,QAAO,KAAK,SAAS,UAAU,KAAK,OAAO,KAAK,EAAE;AAAA,IACnF;AAGA,UAAM,eAA+B,CAAC;AACtC,QAAI,KAAK,gBAAgB,KAAK,aAAa,SAAS,GAAG;AACrD,YAAM,SAAS,MAAM,KAAK,aAAa;AAAA,QACrC,SAAS,KAAK;AAAA,QACd,MAAM,KAAK;AAAA,QACX,IAAI,KAAK;AAAA,QACT,UAAU,KAAK;AAAA,MACjB,CAAC;AACD,mBAAa,KAAK,MAAM;AACxB,UAAI,OAAO,WAAW,QAAS,QAAO,KAAK,WAAW,OAAO,KAAK,EAAE;AAAA,IACtE;AACA,QAAI,KAAK,oBAAoB,KAAK,iBAAiB,SAAS,GAAG;AAC7D,YAAM,SAAS,MAAM,KAAK,aAAa;AAAA,QACrC,aAAa,KAAK;AAAA,QAClB,MAAM,KAAK;AAAA,QACX,IAAI,KAAK;AAAA,QACT,UAAU,KAAK;AAAA,QACf,YAAY,KAAK;AAAA,MACnB,CAAC;AACD,mBAAa,KAAK,MAAM;AACxB,UAAI,OAAO,WAAW,QAAS,QAAO,KAAK,yBAAyB,OAAO,KAAK,EAAE;AAAA,IACpF;AAMA,UAAM,sBAAsC,CAAC;AAC7C,QAAI,KAAK,2BAA2B,KAAK,wBAAwB,SAAS,GAAG;AAC3E,YAAM,SAAS,MAAM,KAAK,mBAAmB;AAAA,QAC3C,aAAa,KAAK;AAAA,QAClB,MAAM,KAAK;AAAA,QACX,IAAI,KAAK;AAAA,QACT,UAAU,KAAK;AAAA,MACjB,CAAC;AACD,0BAAoB,KAAK,MAAM;AAC/B,UAAI,OAAO,WAAW,QAAS,QAAO,KAAK,kBAAkB,OAAO,KAAK,EAAE;AAAA,IAC7E;AAGA,QAAI,aAAkC;AACtC,UAAM,eAAe,KAAK,YAAY,KAAK,CAAC,MAAM,WAAW,IAAI,EAAE,YAAY,CAAC,CAAC;AACjF,QAAI,kBAAkB,cAAc;AAClC,mBAAa,MAAM,KAAK,kBAAkB,EAAE,MAAM,KAAK,MAAM,IAAI,KAAK,KAAK,CAAC;AAC5E,UAAI,WAAW,WAAW,QAAS,QAAO,KAAK,gBAAgB,WAAW,KAAK,EAAE;AAAA,IACnF;AAGA,UAAM,WAA6F,CAAC;AACpG,eAAW,UAAU,KAAK,aAAa;AACrC,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,KAAK,OAAO,KAAK,YAAY,OAAO,YAAY,GAAG,KAAK,MAAM,KAAK,IAAI;AAC9F,iBAAS,MAAM,IAAI;AAAA,UACjB,YAAY,IAAI;AAAA,UAChB,WAAW,IAAI,YAAY,IAAI,SAAS,EAAE,MAAM,IAAI,UAAU,IAAI,IAAI,OAAO,IAAI;AAAA,QACnF;AAAA,MACF,QAAQ;AACN,iBAAS,MAAM,IAAI,EAAE,YAAY,EAAE;AAAA,MACrC;AAAA,IACF;AAKA,UAAM,mBAAyC,CAAC;AAChD,UAAM,UAAU,CAAC,MAAiC;AAChD,UAAI,GAAG,WAAW,EAAE,QAAQ,SAAS,EAAG,kBAAiB,KAAK,GAAG,EAAE,OAAO;AAAA,IAC5E;AACA,eAAW,KAAK,YAAa,SAAQ,CAAC;AACtC,eAAW,KAAK,aAAc,SAAQ,CAAC;AACvC,eAAW,KAAK,aAAc,SAAQ,CAAC;AACvC,eAAW,KAAK,oBAAqB,SAAQ,CAAC;AAC9C,YAAQ,UAAU;AAElB,QAAI;AACJ,QAAI,OAAO,SAAS,EAAG,UAAS;AAAA,aACvB,iBAAiB,SAAS,EAAG,UAAS;AAAA,QAC1C,UAAS;AAEd,WAAO;AAAA,MACL;AAAA,MACA,cAAc,EAAE,MAAM,aAAa,OAAO,cAAc,QAAQ,cAAc,cAAc,qBAAqB,WAAW;AAAA,MAC5H;AAAA,MACA;AAAA,MACA,GAAI,iBAAiB,SAAS,IAAI,EAAE,SAAS,iBAAiB,IAAI,CAAC;AAAA,IACrE;AAAA,EACF;AACF;;;ACtxCO,IAAM,iBAAiB,oBAAI,IAAI,CAAC,QAAQ,QAAQ,QAAQ,MAAM,CAAC;AAY/D,SAAS,yBACd,OACA,UACA,UAAuB,gBACV;AACb,QAAM,eAAe,oBAAI,IAAsB;AAC/C,QAAM,UAAoB,CAAC;AAC3B,aAAW,QAAQ,OAAO;AACxB,QAAI,QAAQ,IAAI,IAAI,GAAG;AACrB,cAAQ,KAAK,IAAI;AACjB;AAAA,IACF;AACA,UAAM,aAAa,SAAS,QAAQ,IAAI;AACxC,UAAM,WAAW,aAAa,IAAI,UAAU,KAAK,CAAC;AAClD,aAAS,KAAK,IAAI;AAClB,iBAAa,IAAI,YAAY,QAAQ;AAAA,EACvC;AACA,SAAO,EAAE,cAAc,QAAQ;AACjC;AAQO,SAAS,4BACd,YACA,YACQ;AACR,SAAO;AAAA,mCAC0B,UAAU;AAAA,oCACT,UAAU;AAC9C;AAMO,SAAS,4BACd,YACA,OACQ;AACR,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACxE;AACA,QAAM,SAAS,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI;AACnD,SAAO,+BAA+B,UAAU;AAAA,iEACe,MAAM;AACvE;;;ACxCO,IAAM,uBAAuB;AAO7B,IAAM,uBAAqC;AAAA,EAChD,EAAE,MAAM,cAAc,UAAU,eAAe,MAAM,0CAA0C;AAAA,EAC/F,EAAE,MAAM,cAAc,UAAU,eAAe,MAAM,yCAAyC;AAAA,EAC9F,EAAE,MAAM,cAAc,UAAU,eAAe,MAAM,oCAAoC;AAAA,EACzF,EAAE,MAAM,cAAc,UAAU,eAAe,MAAM,uBAAuB;AAAA,EAC5E,EAAE,MAAM,cAAc,UAAU,eAAe,MAAM,yBAAyB;AAAA,EAC9E,EAAE,MAAM,cAAc,UAAU,eAAe,MAAM,wCAAmC;AAC1F;AAMO,IAAM,2BAAyC;AAAA,EACpD,EAAE,MAAM,cAAc,UAAU,cAAc,MAAM,4CAA4C;AAAA,EAChG,EAAE,MAAM,cAAc,UAAU,cAAc,MAAM,kCAAkC;AAAA,EACtF,EAAE,MAAM,cAAc,UAAU,cAAc,MAAM,0BAA0B;AAChF;AAMA,SAAS,WAAW,MAA4B;AAC9C,MAAI,QAAQ,SAAS;AACrB,SAAO,MAAM;AACX,YAAS,QAAQ,eAAgB;AACjC,QAAI,IAAI;AACR,QAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC;AACnC,SAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,IAAI,EAAE;AACxC,aAAS,IAAK,MAAM,QAAS,KAAK;AAAA,EACpC;AACF;AAOA,SAAS,kBAAkB,UAAkB,QAA0B;AACrE,QAAM,MAAgB,CAAC;AACvB,QAAM,IAAI,oBAAI,KAAK,WAAW,YAAY;AAC1C,QAAM,MAAM,oBAAI,KAAK,SAAS,YAAY;AAC1C,SAAO,KAAK,KAAK;AACf,UAAM,MAAM,EAAE,UAAU;AACxB,QAAI,QAAQ,KAAK,QAAQ,GAAG;AAC1B,UAAI,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAAA,IACvC;AACA,MAAE,WAAW,EAAE,WAAW,IAAI,CAAC;AAAA,EACjC;AACA,SAAO;AACT;AAkBO,SAAS,8BACd,WAAmB,cACnB,UAAiB,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,GACrD,OAAe,sBACf,cAAsB,GACR;AACd,QAAM,OAAO,WAAW,IAAI;AAG5B,QAAM,cAAc,oBAAI,IAAY;AAAA,IAClC,GAAG,qBAAqB,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IACzC,GAAG,yBAAyB,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,EAC/C,CAAC;AACD,QAAM,aAAa,kBAAkB,UAAU,MAAM,EAAE;AAAA,IACrD,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC;AAAA,EAC3B;AAEA,QAAM,SAAuB,CAAC;AAC9B,QAAM,OAAO,CAAC,GAAG,UAAU;AAC3B,WAAS,IAAI,GAAG,IAAI,eAAe,KAAK,SAAS,GAAG,KAAK;AACvD,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,KAAK,MAAM;AAC3C,WAAO,KAAK,EAAE,MAAM,KAAK,OAAO,KAAK,CAAC,EAAE,CAAC,GAAG,UAAU,SAAS,CAAC;AAAA,EAClE;AAEA,SAAO,CAAC,GAAG,sBAAsB,GAAG,0BAA0B,GAAG,MAAM,EAAE;AAAA,IACvE,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,EACvC;AACF;;;ACtGO,IAAM,iBAAiB;AAmBvB,IAAM,uBAAkD;AAAA;AAAA,EAE7D,aAAa;AAAA,EACb,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,yBAAyB;AAAA,EACzB,iBAAiB;AAAA,EACjB,oBAAoB;AAAA;AAAA,EAEpB,WAAW;AAAA,EACX,UAAU;AAAA,EACV,wBAAwB;AAAA,EACxB,uBAAuB;AAAA;AAAA,EAEvB,KAAK;AAAA,EACL,KAAK;AAAA;AAAA,EAEL,eAAe;AAAA,EACf,aAAa;AAAA;AAAA,EAEb,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,aAAa;AAAA,EACb,OAAO;AAAA,EACP,SAAS;AACX;AAWO,IAAM,sBAAiD;AAAA,EAC5D,YAAY;AAAA,EACZ,sBAAsB;AAAA,EACtB,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,aAAa;AACf;AAgCA,SAAS,UAAU,GAAqB;AACtC,SAAO,MAAM,QAAQ,MAAM;AAC7B;AAUO,SAAS,cACd,QACA,QACA,YACa;AACb,SAAO,OAAO,QAAQ,UAAU,EAAE,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM;AACvD,UAAM,WAAW,OAAO,KAAK;AAC7B,UAAM,WAAW,OAAO,KAAK;AAE7B,UAAM,UAAU,UAAU,QAAQ;AAClC,UAAM,UAAU,UAAU,QAAQ;AAClC,QAAI,WAAW,SAAS;AACtB,aAAO,EAAE,OAAO,MAAM,UAAU,UAAU,QAAQ,KAAK;AAAA,IACzD;AACA,QAAI,YAAY,SAAS;AACvB,aAAO,EAAE,OAAO,MAAM,UAAU,UAAU,QAAQ,MAAM;AAAA,IAC1D;AAEA,QAAI,SAAS,UAAU;AACrB,YAAM,SAAS,OAAO,QAAQ;AAC9B,YAAM,SAAS,OAAO,QAAQ;AAC9B,YAAM,SAAS,OAAO,MAAM,MAAM;AAClC,YAAM,SAAS,OAAO,MAAM,MAAM;AAClC,UAAI,UAAU,QAAQ;AACpB,eAAO,EAAE,OAAO,MAAM,UAAU,UAAU,QAAQ,KAAK;AAAA,MACzD;AACA,UAAI,WAAW,QAAQ;AACrB,eAAO,EAAE,OAAO,MAAM,UAAU,UAAU,QAAQ,MAAM;AAAA,MAC1D;AACA,YAAM,QAAQ,KAAK,IAAI,SAAS,MAAM;AACtC,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,SAAS;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,SAAS,WAAW;AACtB,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,OAAO,QAAQ,MAAM,OAAO,QAAQ;AAAA,MAC9C;AAAA,IACF;AAGA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,OAAO,QAAQ,MAAM,OAAO,QAAQ;AAAA,IAC9C;AAAA,EACF,CAAC;AACH;AAYO,SAAS,WACd,QACA,QACA,MACA,QACA,MACS;AACT,QAAM,QAAQ,SAAS,aAAa,uBAAuB;AAC3D,QAAM,SAAS,cAAc,QAAQ,QAAQ,KAAK;AAClD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,OAAO,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM;AAAA,EAC1C;AACF;;;ACzNA,YAAYC,YAAU;AACtB,SAAS,sBAAsB;AAoC/B,eAAsB,uBACpB,QACA,YACA,UACiC;AACjC,QAAM,WAAW,YAAY;AAC7B,QAAM,SAAc,YAAK,UAAU,YAAY,eAAe;AAC9D,QAAM,WAAW,MAAM,eAAe,OAAO,MAAM;AACnD,QAAM,OAAO,MAAM,SAAS,QAAQ;AAEpC,QAAM,SAAmB,CAAC;AAC1B,QAAM,cAA4C,CAAC;AAEnD,MAAI;AACF,eAAW,QAAQ,YAAY;AAC7B,YAAM,UAAU,MAAM,SAAS,UAAU;AAAA,QACvC;AAAA,QACA,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,YAAY;AAAA,MACd,CAAC;AAID,YAAM,YAAY,MAAM,KAAK;AAAA,QAC3B;AAAA,QACA,CAAC,QAAQ,IAAI;AAAA,MACf;AACA,YAAM,UAAU,UAAU,QAAQ;AAClC,YAAM,YAAY,oBAAI,IAAoB;AAC1C,iBAAW,KAAK,SAAS;AACvB,cAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,cAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,YAAI,OAAO,SAAS,CAAC,EAAG,WAAU,IAAI,GAAG,CAAC;AAAA,MAC5C;AAEA,UAAI,WAAW;AACf,UAAI,UAAU;AACd,iBAAW,OAAO,SAAS;AACzB,YAAI,CAAC,IAAI,KAAM;AACf,cAAM,WAAW,UAAU,IAAI,IAAI,IAAI;AACvC,YAAI,aAAa,OAAW;AAC5B,cAAM,QAAQ,KAAK,IAAI,IAAI,QAAQ,QAAQ;AAC3C,YAAI,QAAQ,SAAU,YAAW;AACjC,eAAO,KAAK,KAAK;AACjB;AAAA,MACF;AACA,kBAAY,KAAK,EAAE,MAAM,cAAc,UAAU,aAAa,QAAQ,CAAC;AAAA,IACzE;AAAA,EACF,UAAE;AACA,QAAI;AACF,WAAK,UAAU;AAAA,IACjB,QAAQ;AAAA,IAER;AACA,QAAI;AACF,eAAS,UAAU;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,gBACJ,OAAO,SAAS,IAAI,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,OAAO,SAAS;AAC1E,QAAM,gBAAgB,OAAO,SAAS,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI;AAChE,SAAO,EAAE,eAAe,eAAe,YAAY;AACrD;","names":["sql","tradingDays","open","params","BATCH_SIZE","tier2Targets","tier2Result","priorClose","z","getNum","getRaw","z","z","formatTradeDate","getTradeLookupKey","uniqueTradeLookupKeys","resultToRecords","recordsByTickerDate","getNum","VOL_REGIME_LABELS","z","z","z","REVERSE_ROOT_MAP","z","z","h1","m1","h2","m2","markPrice","z","z","z","triggerTypeEnum","z","triggerConfigSchema","path","z","join","path","z","z","existsSync","path","existsSync","readdirSync","path","escapeSqlLiteral","existsSync","existsSync","path","lit","existsSync","existsSync","path","lit","existsSync","sql","reader","names","existsSync","path","escapeSqlLiteral","existsSync","sql","reader","existsSync","path","existsSync","escapeSqlLiteral","parseQuoteRow","existsSync","path","existsSync","spot","enriched","chain","quote","z","path","lit","path"]}
|