tradeblocks-mcp 3.0.2 → 3.0.3

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.
Files changed (214) hide show
  1. package/README.md +82 -68
  2. package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
  3. package/dist/chunk-27S67XW3.js.map +1 -0
  4. package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
  5. package/dist/chunk-2E63THNI.js.map +1 -0
  6. package/dist/{chunk-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
  7. package/dist/chunk-6S37CXUA.js.map +1 -0
  8. package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
  9. package/dist/chunk-AP7IHUUR.js.map +1 -0
  10. package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
  11. package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
  12. package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
  13. package/dist/chunk-SEUZYQGQ.js.map +1 -0
  14. package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
  15. package/dist/chunk-UBUC5A66.js.map +1 -0
  16. package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
  17. package/dist/chunk-VDU25Z6X.js.map +1 -0
  18. package/dist/daily-log-processor-BY3ISY6K.js +9 -0
  19. package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
  20. package/dist/iv-solver-worker.js +9 -1
  21. package/dist/market-provider-H3ARIVZ4.js +16 -0
  22. package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
  23. package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
  24. package/dist/test-exports.js +358 -254
  25. package/dist/test-exports.js.map +1 -1
  26. package/dist/trade-processor-EYA3I3XB.js +9 -0
  27. package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
  28. package/manifest.json +3 -11
  29. package/package.json +1 -1
  30. package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
  31. package/server/chunk-4P7D7YZP.js.map +1 -0
  32. package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
  33. package/server/chunk-BOPHW5M6.js.map +1 -0
  34. package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
  35. package/server/chunk-GH2552SE.js.map +1 -0
  36. package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
  37. package/server/chunk-OBYKFW2B.js.map +1 -0
  38. package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
  39. package/server/chunk-T66KH2XH.js.map +1 -0
  40. package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
  41. package/server/chunk-W5E7FHC4.js.map +1 -0
  42. package/server/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
  43. package/server/chunk-YUCOAJ4Z.js.map +1 -0
  44. package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
  45. package/server/chunk-ZBJCF4ZG.js.map +1 -0
  46. package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
  47. package/server/config-DK7KOMNL.js.map +1 -0
  48. package/server/daily-log-processor-ENEUT22A.js +10 -0
  49. package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
  50. package/server/http-server.js +2 -5
  51. package/server/index.js +1771 -1538
  52. package/server/index.js.map +1 -1
  53. package/server/iv-solver-worker.js +9 -1
  54. package/server/market-provider-B437HKLW.js +17 -0
  55. package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
  56. package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
  57. package/server/trade-processor-L3PIQ5TG.js +10 -0
  58. package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
  59. package/src/auth/clients-store.ts +4 -4
  60. package/src/auth/code-store.ts +1 -1
  61. package/src/auth/config.ts +11 -12
  62. package/src/auth/login-page.ts +8 -10
  63. package/src/auth/provider.ts +35 -30
  64. package/src/auth/token.ts +17 -15
  65. package/src/db/backtest-schemas.ts +12 -4
  66. package/src/db/connection.ts +107 -40
  67. package/src/db/index.ts +37 -5
  68. package/src/db/json-adapters.ts +22 -31
  69. package/src/db/json-migration.ts +11 -15
  70. package/src/db/json-store.ts +5 -3
  71. package/src/db/market-datasets.ts +48 -15
  72. package/src/db/market-schemas.ts +11 -5
  73. package/src/db/market-views.ts +119 -32
  74. package/src/db/parquet-writer.ts +7 -7
  75. package/src/db/profile-schemas.ts +39 -22
  76. package/src/db/schemas.ts +2 -2
  77. package/src/http-server.ts +7 -12
  78. package/src/index.ts +8 -9
  79. package/src/market/ingestor/index.ts +1 -4
  80. package/src/market/ingestor/market-ingestor.ts +140 -67
  81. package/src/market/ingestor/types.ts +1 -3
  82. package/src/market/stores/chain-sql.ts +2 -8
  83. package/src/market/stores/chain-store.ts +1 -4
  84. package/src/market/stores/duckdb-chain-store.ts +3 -14
  85. package/src/market/stores/duckdb-enriched-store.ts +3 -13
  86. package/src/market/stores/duckdb-quote-store.ts +10 -27
  87. package/src/market/stores/duckdb-spot-store.ts +4 -20
  88. package/src/market/stores/enriched-sql.ts +3 -9
  89. package/src/market/stores/enriched-store.ts +2 -2
  90. package/src/market/stores/parquet-chain-store.ts +5 -22
  91. package/src/market/stores/parquet-enriched-store.ts +4 -16
  92. package/src/market/stores/parquet-oi-daily-store.ts +3 -14
  93. package/src/market/stores/parquet-quote-store.ts +9 -29
  94. package/src/market/stores/parquet-spot-store.ts +14 -33
  95. package/src/market/stores/quote-store.ts +2 -10
  96. package/src/market/stores/spot-sql.ts +3 -15
  97. package/src/market/stores/spot-store.ts +1 -1
  98. package/src/market/tickers/defaults.json +6 -6
  99. package/src/market/tickers/loader.ts +2 -7
  100. package/src/market/tickers/registry.ts +5 -15
  101. package/src/market/tickers/schemas.ts +2 -6
  102. package/src/models/strategy-profile.ts +39 -39
  103. package/src/sync/block-sync.ts +44 -65
  104. package/src/sync/index.ts +1 -4
  105. package/src/sync/metadata.ts +14 -20
  106. package/src/test-exports.ts +127 -123
  107. package/src/tools/analysis.ts +180 -159
  108. package/src/tools/batch-exit-analysis.ts +100 -59
  109. package/src/tools/blocks/analysis.ts +116 -101
  110. package/src/tools/blocks/comparison.ts +281 -364
  111. package/src/tools/blocks/core.ts +208 -253
  112. package/src/tools/blocks/health.ts +571 -563
  113. package/src/tools/blocks/similarity.ts +316 -283
  114. package/src/tools/edge-decay.ts +124 -197
  115. package/src/tools/exit-analysis.ts +130 -77
  116. package/src/tools/greeks-attribution.ts +84 -35
  117. package/src/tools/guides.ts +4 -6
  118. package/src/tools/imports.ts +9 -12
  119. package/src/tools/market-data.ts +1067 -757
  120. package/src/tools/market-enrichment.ts +3 -3
  121. package/src/tools/market-fetch.ts +148 -67
  122. package/src/tools/market-imports.ts +12 -12
  123. package/src/tools/middleware/sync-middleware.ts +5 -6
  124. package/src/tools/performance.ts +185 -302
  125. package/src/tools/profile-analysis.ts +52 -66
  126. package/src/tools/profiles.ts +106 -69
  127. package/src/tools/regime-advisor.ts +20 -45
  128. package/src/tools/replay.ts +81 -77
  129. package/src/tools/reports/discrepancies.ts +298 -328
  130. package/src/tools/reports/fields.ts +7 -25
  131. package/src/tools/reports/helpers.ts +18 -49
  132. package/src/tools/reports/predictive.ts +27 -70
  133. package/src/tools/reports/slippage-trends.ts +315 -345
  134. package/src/tools/reports/slippage.ts +1 -4
  135. package/src/tools/reports/strategy-matches.ts +399 -441
  136. package/src/tools/schema.ts +43 -40
  137. package/src/tools/shared/filters.ts +3 -9
  138. package/src/tools/snapshot.ts +9 -30
  139. package/src/tools/sql.ts +15 -14
  140. package/src/tools/tickers.ts +1 -4
  141. package/src/utils/batch-exit-analysis.ts +31 -42
  142. package/src/utils/black-scholes.ts +39 -29
  143. package/src/utils/block-loader.ts +68 -82
  144. package/src/utils/calibration-probe.ts +1 -2
  145. package/src/utils/chain-loader.ts +3 -3
  146. package/src/utils/csv-discovery.ts +16 -22
  147. package/src/utils/data-quality.ts +24 -36
  148. package/src/utils/exit-triggers.ts +91 -96
  149. package/src/utils/field-timing.ts +94 -79
  150. package/src/utils/filter-predicates.ts +13 -9
  151. package/src/utils/flatfile-importer.ts +94 -64
  152. package/src/utils/greeks-decomposition.ts +152 -100
  153. package/src/utils/iv-solver-pool.ts +55 -25
  154. package/src/utils/iv-solver-worker.ts +5 -5
  155. package/src/utils/market-enricher.ts +528 -497
  156. package/src/utils/market-importer.ts +31 -12
  157. package/src/utils/market-provider.ts +21 -23
  158. package/src/utils/massive-tier.ts +5 -7
  159. package/src/utils/migrate-option-data-helpers.ts +2 -8
  160. package/src/utils/option-quote-greeks.ts +25 -31
  161. package/src/utils/option-time.ts +4 -8
  162. package/src/utils/output-formatter.ts +1 -4
  163. package/src/utils/provider-capabilities.ts +1 -4
  164. package/src/utils/providers/massive.ts +59 -93
  165. package/src/utils/providers/thetadata/backfill.ts +14 -23
  166. package/src/utils/providers/thetadata/client.ts +12 -8
  167. package/src/utils/providers/thetadata/decode.ts +2 -20
  168. package/src/utils/providers/thetadata/endpoints.ts +32 -40
  169. package/src/utils/providers/thetadata/join.ts +11 -10
  170. package/src/utils/providers/thetadata/proto.ts +12 -10
  171. package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
  172. package/src/utils/providers/thetadata.ts +11 -10
  173. package/src/utils/quote-enricher.ts +4 -4
  174. package/src/utils/quote-parquet-projection.ts +3 -11
  175. package/src/utils/sample-date-selector.ts +3 -5
  176. package/src/utils/schema-metadata.ts +102 -70
  177. package/src/utils/ticker.ts +5 -9
  178. package/src/utils/trade-replay.ts +77 -68
  179. package/dist/chunk-4BLCXNQ6.js.map +0 -1
  180. package/dist/chunk-BKQ4PM4Y.js.map +0 -1
  181. package/dist/chunk-FGZH632F.js.map +0 -1
  182. package/dist/chunk-LDKTV7GW.js.map +0 -1
  183. package/dist/chunk-PRAYH3RT.js.map +0 -1
  184. package/dist/chunk-W2PP3LEH.js.map +0 -1
  185. package/dist/chunk-XXYOUIZY.js.map +0 -1
  186. package/dist/daily-log-processor-6MWJ23JK.js +0 -9
  187. package/dist/market-provider-VDRJUEF2.js +0 -16
  188. package/dist/trade-processor-NHU4VWRX.js +0 -9
  189. package/server/chunk-5EBXHT6C.js.map +0 -1
  190. package/server/chunk-72GKJE2U.js.map +0 -1
  191. package/server/chunk-CSDVJPBB.js.map +0 -1
  192. package/server/chunk-FBNDMCT5.js.map +0 -1
  193. package/server/chunk-NRFXAJF7.js.map +0 -1
  194. package/server/chunk-NZO6PT64.js.map +0 -1
  195. package/server/chunk-PNKG7RY7.js.map +0 -1
  196. package/server/chunk-WA5AAPCH.js.map +0 -1
  197. package/server/config-6IZXEFEX.js.map +0 -1
  198. package/server/daily-log-processor-Y3PVSVBM.js +0 -10
  199. package/server/market-provider-VOYYVYWT.js +0 -17
  200. package/server/trade-processor-JWVS37KM.js +0 -10
  201. /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-BY3ISY6K.js.map} +0 -0
  202. /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
  203. /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-H3ARIVZ4.js.map} +0 -0
  204. /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
  205. /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
  206. /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.js.map} +0 -0
  207. /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
  208. /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-ENEUT22A.js.map} +0 -0
  209. /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
  210. /package/server/{market-provider-VOYYVYWT.js.map → market-provider-B437HKLW.js.map} +0 -0
  211. /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
  212. /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
  213. /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
  214. /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sync/index.ts","../src/db/connection.ts","../src/db/schemas.ts","../src/db/market-schemas.ts","../src/db/parquet-writer.ts","../src/db/market-datasets.ts","../src/db/data-root.ts","../src/db/json-adapters.ts","../src/db/json-store.ts","../src/db/profile-schemas.ts","../src/db/market-views.ts","../src/utils/quote-parquet-projection.ts","../src/db/json-migration.ts","../src/sync/block-sync.ts","../src/sync/hasher.ts","../src/sync/metadata.ts","../src/utils/ticker.ts","../src/utils/block-loader.ts","../src/utils/csv-discovery.ts"],"sourcesContent":["/**\n * Sync Layer Public API\n *\n * Provides synchronization between CSV files and DuckDB analytics database.\n * Exports hashing utilities, metadata operations, and sync functions.\n */\n\nimport { existsSync } from \"fs\";\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport { getConnection } from \"../db/connection.ts\";\nimport {\n syncBlockInternal,\n detectBlockChanges,\n cleanupDeletedBlocks,\n type BlockSyncResult,\n} from \"./block-sync.ts\";\nimport { getSyncMetadata } from \"./metadata.ts\";\n\n// Re-export hasher utilities\nexport { hashFileContent } from \"./hasher.ts\";\n\n// Re-export metadata operations and types\nexport {\n getSyncMetadata,\n upsertSyncMetadata,\n deleteSyncMetadata,\n getAllSyncedBlockIds,\n type BlockSyncMetadata,\n} from \"./metadata.ts\";\n\n// Re-export block sync types and internal functions (for testing)\nexport { type BlockSyncResult } from \"./block-sync.ts\";\n\n// --- Result Types ---\n\n/**\n * Result of syncing all blocks\n */\nexport interface SyncResult {\n blocksProcessed: number;\n blocksSynced: number;\n blocksUnchanged: number;\n blocksDeleted: number;\n errors: Array<{ blockId: string; error: string }>;\n results: BlockSyncResult[];\n /** True when the middleware could not acquire the RW lock and fell back to RO without running sync. */\n syncSkipped?: boolean;\n /** Machine-readable reason for skipping sync. Only populated when syncSkipped === true. */\n skipReason?: \"could_not_acquire_write_lock\";\n}\n\n// --- Blocks Directory Override ---\n\n/**\n * Optional override for where CSV block folders live.\n * When set, sync functions scan this directory for blocks instead of baseDir.\n * DuckDB connections still use baseDir.\n *\n * Set via --blocks-dir CLI flag or BLOCKS_DIRECTORY env var.\n */\nlet _blocksDir: string | null = null;\n\nexport function setBlocksDir(dir: string): void {\n _blocksDir = dir;\n}\n\nexport function getBlocksDir(baseDir: string): string {\n if (_blocksDir) return _blocksDir;\n const nestedBlocksDir = path.join(baseDir, \"blocks\");\n return existsSync(nestedBlocksDir) ? nestedBlocksDir : baseDir;\n}\n\n// --- Sync Functions ---\n\n/**\n * Sync all blocks from the data directory to DuckDB.\n *\n * Scans all block folders, computes content hashes, and syncs\n * blocks that have changed since last sync. Also removes data\n * for blocks that no longer exist.\n *\n * @param baseDir - Base data directory containing block folders\n * @returns Sync result with counts and any errors\n */\nexport async function syncAllBlocks(baseDir: string): Promise<SyncResult> {\n const conn = await getConnection(baseDir);\n const blocksDir = getBlocksDir(baseDir);\n const results: BlockSyncResult[] = [];\n const errors: Array<{ blockId: string; error: string }> = [];\n\n // 1. Detect changes (scan blocksDir for CSV folders)\n const { toSync, toDelete } = await detectBlockChanges(conn, blocksDir);\n\n // 2. Delete orphaned blocks\n for (const blockId of toDelete) {\n try {\n await cleanupDeletedBlocks(conn, [blockId], blocksDir);\n results.push({ blockId, status: \"deleted\" });\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n errors.push({ blockId, error: `Failed to delete: ${errorMsg}` });\n }\n }\n\n // 3. Sync changed/new blocks\n for (const blockId of toSync) {\n const blockPath = path.join(blocksDir, blockId);\n const result = await syncBlockInternal(conn, blockId, blockPath);\n results.push(result);\n if (result.status === \"error\" && result.error) {\n errors.push({ blockId, error: result.error });\n }\n }\n\n return {\n blocksProcessed: results.length,\n blocksSynced: results.filter((r) => r.status === \"synced\").length,\n blocksUnchanged: results.filter((r) => r.status === \"unchanged\").length,\n blocksDeleted: results.filter((r) => r.status === \"deleted\").length,\n errors,\n results,\n };\n}\n\n/**\n * Sync a single block to DuckDB.\n *\n * Computes content hash for the block's CSV files and syncs\n * if changes are detected. Used for lazy per-block syncing.\n *\n * @param blockId - Block identifier (folder name)\n * @param baseDir - Base data directory containing block folders\n * @returns Sync result for the block\n */\nexport async function syncBlock(blockId: string, baseDir: string): Promise<BlockSyncResult> {\n const conn = await getConnection(baseDir);\n const blocksDir = getBlocksDir(baseDir);\n const blockPath = path.join(blocksDir, blockId);\n\n // Check if folder exists\n try {\n await fs.access(blockPath);\n } catch {\n // Block folder doesn't exist - if it was synced before, clean it up\n const existing = await getSyncMetadata(conn, blockId, blocksDir);\n if (existing) {\n await cleanupDeletedBlocks(conn, [blockId], blocksDir);\n return { blockId, status: \"deleted\" };\n }\n return { blockId, status: \"error\", error: `Block folder not found: ${blockId}` };\n }\n\n return syncBlockInternal(conn, blockId, blockPath);\n}\n","/**\n * DuckDB Connection Manager\n *\n * Provides lazy singleton connection to DuckDB analytics database (analytics.duckdb)\n * with a second database (market.duckdb) ATTACHed as the `market` catalog.\n *\n * Startup sequence on first RW open:\n * 1. Open analytics.duckdb\n * 2. DROP SCHEMA IF EXISTS market CASCADE (removes legacy inline market tables,\n * prevents DuckDB #14421 naming conflict with the upcoming ATTACH)\n * 3. ATTACH market.duckdb AS market\n * 4. ensureMarketDataTables() — physical canonical market tables when Parquet views are absent\n * 5. ensureMutableMarketTables() — _sync_metadata, data_coverage\n * 6. createMarketParquetViews() — views over shared Parquet files (opportunistic)\n * 7. ensureSyncTables() / ensureTradeDataTable() / ensureReportingDataTable()\n *\n * On close: CHECKPOINT → DETACH market → closeSync() to flush WAL reliably.\n * On RO open: ATTACH market.duckdb READ_ONLY (no table creation).\n *\n * DuckDB is single-process: only one process can open a database file at a time\n * (even read-only fails when another process holds a write lock with an active WAL).\n * Lock recovery handles stale processes from crashed Claude Code sessions by detecting\n * orphaned MCP processes (PPID=1) and terminating them before retrying.\n *\n * Configuration via environment variables:\n * DUCKDB_MEMORY_LIMIT - Memory limit (default: 75% of system RAM, floor 1GB)\n * DUCKDB_THREADS - Number of threads (default: 2 — higher counts cause driver flakiness)\n * DUCKDB_LOCK_RECOVERY - Force-kill ANY lock-holding tradeblocks-mcp (default: false)\n * DUCKDB_LOCK_RECOVERY_TIMEOUT_MS - Wait time for SIGTERM (default: 1500)\n * MARKET_DB_PATH - Path to market.duckdb (overrides default, overridden by --market-db)\n *\n * Security:\n * - enable_external_access: \"true\" at DuckDBInstance creation allows local ATTACH\n * - We do NOT call SET enable_external_access = false because testing confirmed it also\n * blocks local file ATTACH operations (not just HTTP), which breaks importFromDatabase\n * - No HTTP URLs are used in this application — local ATTACH is the only external access needed\n *\n * Schemas created on first RW connection:\n * - trades: For block/trade data (in analytics.duckdb)\n * - market: ATTACHed from market.duckdb (spot, spot_daily, enriched, enriched_context, option_chain, option_quote_minutes, _sync_metadata)\n */\n\nimport { DuckDBInstance, DuckDBConnection } from \"@duckdb/node-api\";\nimport { execFile } from \"child_process\";\nimport * as fs from \"fs/promises\";\nimport * as os from \"os\";\nimport * as path from \"path\";\nimport { promisify } from \"util\";\nimport { ensureSyncTables, ensureTradeDataTable, ensureReportingDataTable } from \"./schemas.ts\";\nimport { ensureMutableMarketTables, ensureMarketDataTables } from \"./market-schemas.ts\";\nimport { ensureProfilesSchema } from \"./profile-schemas.ts\";\nimport { createMarketParquetViews } from \"./market-views.ts\";\nimport { migrateMetadataToJson } from \"./json-migration.ts\";\nimport { getDataRoot } from \"./data-root.ts\";\n\n// Module-level singleton state\nlet instance: DuckDBInstance | null = null;\nlet connection: DuckDBConnection | null = null;\nlet connectionMode: \"read_write\" | \"read_only\" | null = null;\nlet storedDbPath: string | null = null;\nlet storedThreads: string | null = null;\nlet storedMemoryLimit: string | null = null;\nlet storedMarketDbPath: string | null = null;\nconst execFileAsync = promisify(execFile);\nconst isWindows = process.platform === \"win32\";\n\n/**\n * Default DuckDB memory limit when `DUCKDB_MEMORY_LIMIT` is unset.\n *\n * Scales to 75% of total system RAM — DuckDB's own native default is 80%; we\n * keep a small headroom for Node's heap, the OS, and other processes. Floored\n * at 1GB so very small VMs / CI containers still work; no upper cap. Returns\n * a DuckDB-compatible string like \"90GB\".\n */\nfunction defaultMemoryLimit(): string {\n const totalGB = os.totalmem() / 1024 ** 3;\n const targetGB = Math.max(1, Math.floor(totalGB * 0.75));\n return `${targetGB}GB`;\n}\n\n/**\n * Default DuckDB thread count when `DUCKDB_THREADS` is unset.\n *\n * Stays at 2 — empirically, higher counts trigger intermittent\n * `Failed to execute prepared statement` errors mid-run on long parquet-mode\n * read workloads (tested 4, 8, 32 — all flaky). The hot path is per-date\n * partition reads which are I/O-bound, not CPU-bound. Users with large\n * parallel-read workloads can override via the env var.\n */\nfunction defaultThreads(): string {\n return \"2\";\n}\n\nfunction isLockError(message: string): boolean {\n const lower = message.toLowerCase();\n return (\n lower.includes(\"could not set lock on file\") ||\n lower.includes(\"conflicting lock is held\") ||\n lower.includes(\"io error: could not set lock\") ||\n lower.includes(\"being used by another process\") // Windows OS error\n );\n}\n\nfunction parseLockHolderPid(message: string): number | null {\n const match = message.match(/PID\\s+(\\d+)/i);\n if (!match) return null;\n const pid = Number.parseInt(match[1], 10);\n return Number.isFinite(pid) ? pid : null;\n}\n\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function getProcessParentPid(pid: number): Promise<number | null> {\n try {\n if (isWindows) {\n const { stdout } = await execFileAsync(\"wmic\", [\n \"process\",\n \"where\",\n `ProcessId=${pid}`,\n \"get\",\n \"ParentProcessId\",\n \"/value\",\n ]);\n const match = stdout.match(/ParentProcessId=(\\d+)/);\n if (!match) return null;\n const ppid = parseInt(match[1], 10);\n return Number.isFinite(ppid) ? ppid : null;\n }\n const { stdout } = await execFileAsync(\"ps\", [\"-p\", String(pid), \"-o\", \"ppid=\"]);\n const ppid = parseInt(stdout.trim(), 10);\n return Number.isFinite(ppid) ? ppid : null;\n } catch {\n return null;\n }\n}\n\nasync function getProcessCommand(pid: number): Promise<string | null> {\n try {\n if (isWindows) {\n const { stdout } = await execFileAsync(\"wmic\", [\n \"process\",\n \"where\",\n `ProcessId=${pid}`,\n \"get\",\n \"CommandLine\",\n \"/value\",\n ]);\n const match = stdout.match(/CommandLine=(.+)/);\n if (!match) return null;\n const command = match[1].trim();\n return command.length > 0 ? command : null;\n }\n const { stdout } = await execFileAsync(\"ps\", [\"-p\", String(pid), \"-o\", \"command=\"]);\n const command = stdout.trim();\n return command.length > 0 ? command : null;\n } catch {\n return null;\n }\n}\n\nasync function waitForProcessExit(pid: number, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (!isProcessAlive(pid)) {\n return true;\n }\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n return !isProcessAlive(pid);\n}\n\nasync function tryRecoverLockByTerminatingStaleProcess(\n errorMessage: string,\n dbPath: string,\n forceRecovery: boolean,\n): Promise<boolean> {\n const lockHolderPid = parseLockHolderPid(errorMessage);\n if (!lockHolderPid || lockHolderPid === process.pid) {\n return false;\n }\n\n const command = await getProcessCommand(lockHolderPid);\n if (!command) {\n return false;\n }\n\n // Only terminate lock holders that look like another tradeblocks-mcp session for this data dir.\n const normalizedDbPath = dbPath.replace(/\\\\/g, \"/\");\n const normalizedDbDir = path.dirname(normalizedDbPath);\n const isTradeblocksProcess =\n command.includes(\"tradeblocks-mcp\") ||\n command.includes(\"/mcp-server/server/index.js\") ||\n command.includes(\"packages/mcp-server/server/index.js\") ||\n command.includes(\"\\\\mcp-server\\\\server\\\\index.js\") ||\n command.includes(\"packages\\\\mcp-server\\\\server\\\\index.js\");\n // Normalize command paths for consistent comparison (Windows backslashes → forward slashes)\n const normalizedCommand = command.replace(/\\\\/g, \"/\");\n const targetsSameDb =\n normalizedCommand.includes(normalizedDbPath) || normalizedCommand.includes(normalizedDbDir);\n\n if (!isTradeblocksProcess || !targetsSameDb) {\n return false;\n }\n\n // Check if the lock holder is orphaned (parent session is gone).\n // Unix: orphaned processes get reparented to PID 1 (init/launchd).\n // Windows: child keeps original PPID even after parent dies — check if parent is still alive.\n // Only kill non-orphaned processes if forceRecovery is explicitly enabled.\n const ppid = await getProcessParentPid(lockHolderPid);\n const orphaned = isWindows ? ppid !== null && !isProcessAlive(ppid) : ppid === 1;\n if (!orphaned && !forceRecovery) {\n return false;\n }\n\n const reason = orphaned ? \"orphaned\" : \"force-recovery\";\n const timeoutMs = Number.parseInt(process.env.DUCKDB_LOCK_RECOVERY_TIMEOUT_MS || \"1500\", 10);\n\n try {\n process.kill(lockHolderPid, \"SIGTERM\");\n } catch {\n return false;\n }\n\n const exited = await waitForProcessExit(\n lockHolderPid,\n Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 1500,\n );\n\n if (exited) {\n console.error(\n `Recovered DuckDB lock at ${dbPath} by stopping ${reason} tradeblocks-mcp process PID ${lockHolderPid}.`,\n );\n }\n\n return exited;\n}\n\n/**\n * Resolve the path to market.duckdb.\n *\n * Precedence: CLI --market-db > MARKET_DB_PATH env > default (<dataDir>/market.duckdb)\n *\n * @param dataDir - Directory where analytics.duckdb lives (used as default parent)\n */\nfunction resolveMarketDbPath(dataDir: string): string {\n // 1. CLI argument: --market-db /path/to/market.duckdb\n const args = process.argv;\n for (let i = 0; i < args.length; i++) {\n if (args[i] === \"--market-db\" && args[i + 1]) {\n return path.resolve(args[i + 1]);\n }\n }\n // 2. Environment variable\n if (process.env.MARKET_DB_PATH) {\n return path.resolve(process.env.MARKET_DB_PATH);\n }\n // 3. Default: alongside analytics.duckdb\n return path.join(dataDir, \"market.duckdb\");\n}\n\n/**\n * ATTACH market.duckdb to an existing connection.\n *\n * Creates the parent directory if needed. Auto-recreates market.duckdb on\n * corruption (market data is re-importable from source CSVs).\n *\n * Hard fails on any non-corruption ATTACH error — market access is required.\n */\nasync function attachMarketDb(\n conn: DuckDBConnection,\n marketDbPath: string,\n mode: \"read_write\" | \"read_only\",\n): Promise<void> {\n await fs.mkdir(path.dirname(marketDbPath), { recursive: true });\n const readOnlyClause = mode === \"read_only\" ? \" (READ_ONLY)\" : \"\";\n const escapedPath = marketDbPath.replace(/'/g, \"''\");\n try {\n await conn.run(`ATTACH '${escapedPath}' AS market${readOnlyClause}`);\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (msg.includes(\"corrupt\") || msg.includes(\"Invalid\") || msg.includes(\"cannot open\")) {\n console.error(`market.duckdb appears corrupted at ${marketDbPath}. Recreating.`);\n try {\n await fs.unlink(marketDbPath);\n } catch {\n /* file may not exist */\n }\n // Also try removing WAL file\n try {\n await fs.unlink(marketDbPath + \".wal\");\n } catch {\n /* ignore */\n }\n await conn.run(`ATTACH '${escapedPath}' AS market${readOnlyClause}`);\n } else {\n throw new Error(`Failed to attach market.duckdb at ${marketDbPath}: ${msg}`);\n }\n }\n}\n\n/**\n * DETACH market.duckdb from a connection.\n * Non-fatal: may already be detached or market was never attached.\n */\nasync function detachMarketDb(conn: DuckDBConnection): Promise<void> {\n try {\n await conn.run(\"DETACH market\");\n } catch {\n // Non-fatal: may already be detached or market never attached\n }\n}\n\nasync function openReadWriteConnection(\n dbPath: string,\n threads: string,\n memoryLimit: string,\n): Promise<DuckDBConnection> {\n // enable_external_access must be \"true\" at instance creation to allow ATTACH of local files.\n // DuckDB 1.4+ blocks all filesystem operations (including local ATTACH) when set to \"false\"\n // at the instance level. After ATTACH, we lock it down via SET to prevent remote HTTP access.\n instance = await DuckDBInstance.create(dbPath, {\n threads,\n memory_limit: memoryLimit,\n enable_external_access: \"true\",\n });\n connection = await instance.connect();\n\n // Drop legacy market schema from analytics.duckdb before ATTACH.\n // Prevents DuckDB #14421 naming conflict: having tables in both the main DB\n // and an ATTACHed DB under the same catalog name causes corruption.\n try {\n await connection.run(\"DROP SCHEMA IF EXISTS market CASCADE\");\n } catch {\n // Non-fatal: schema may not exist (fresh DB or already dropped)\n }\n\n // Attach separate market.duckdb\n await attachMarketDb(connection, storedMarketDbPath!, \"read_write\");\n\n // NOTE: We intentionally do NOT call SET enable_external_access = false here.\n // Testing confirmed that SET blocks ALL new ATTACH operations (including local file ATTACH),\n // not just remote HTTP/HTTPS access. This would prevent importFromDatabase from ATTACHing\n // external DuckDB files, making the import_from_database MCP tool non-functional.\n // The enable_external_access: \"true\" at DuckDBInstance creation is the intended security\n // boundary — no HTTP URLs are used in this application.\n\n // Create schemas/tables every RW open. This keeps the process resilient if\n // analytics.duckdb is deleted/recreated while the process remains alive.\n await connection.run(\"CREATE SCHEMA IF NOT EXISTS trades\");\n await ensureSyncTables(connection);\n await ensureTradeDataTable(connection);\n await ensureReportingDataTable(connection);\n await ensureMutableMarketTables(connection);\n await ensureProfilesSchema(connection);\n\n const dataDir = path.dirname(dbPath);\n const dataRoot = getDataRoot(dataDir);\n\n // Parquet view overlay: create views over shared Parquet files when present.\n // The env var controls WRITE path; the read path is always opportunistic —\n // views are registered whenever the Parquet files exist.\n // Runs BEFORE ensureMarketDataTables so stale views from a previous data path\n // are dropped first — otherwise CREATE TABLE IF NOT EXISTS is a no-op against\n // the existing view name, leaving a broken view referencing a missing file.\n await createMarketParquetViews(connection, dataRoot);\n\n // Physical market data tables as fallback for datasets not covered by Parquet views.\n //\n // IMPORTANT — lifecycle ordering:\n // Because createMarketParquetViews (above) runs FIRST, by the time we get here\n // a VIEW may already occupy any of the canonical names (e.g. market.option_quote_minutes\n // over legacy-layout Parquet files that lack the new `underlying` column). Any\n // migration-style logic inside ensureMarketDataTables that wants to DROP+recreate\n // a physical table MUST filter information_schema.tables by\n // `table_type = 'BASE TABLE'` so it does not accidentally drop a legitimate VIEW.\n // VIEWs are owned by the Parquet-view layer, not by this schema layer.\n await ensureMarketDataTables(connection);\n\n // One-time metadata migration: DuckDB -> JSON files.\n // Runs only when TRADEBLOCKS_PARQUET=true and JSON files don't yet exist.\n // Must run AFTER all DuckDB tables are created (profiles, sync, market schemas).\n try {\n const blocksDir = (await import(\"../sync/index.ts\")).getBlocksDir(dataRoot);\n await migrateMetadataToJson(connection, dataRoot, blocksDir);\n } catch (err) {\n console.warn(\n \"[json-migration] Migration failed (non-fatal):\",\n err instanceof Error ? err.message : err,\n );\n }\n\n connectionMode = \"read_write\";\n\n return connection;\n}\n\nasync function openReadOnlyConnection(\n dbPath: string,\n threads: string,\n memoryLimit: string,\n): Promise<DuckDBConnection> {\n // enable_external_access must be \"true\" at instance creation to allow ATTACH.\n // We do NOT call SET enable_external_access = false because it also blocks local\n // file ATTACH operations, not just HTTP. See openReadWriteConnection for details.\n instance = await DuckDBInstance.create(dbPath, {\n threads,\n memory_limit: memoryLimit,\n enable_external_access: \"true\",\n access_mode: \"READ_ONLY\",\n });\n connection = await instance.connect();\n if (storedMarketDbPath) {\n await attachMarketDb(connection, storedMarketDbPath, \"read_only\");\n }\n connectionMode = \"read_only\";\n return connection;\n}\n\nfunction resetConnectionState(): void {\n if (connection) {\n try {\n connection.closeSync();\n } catch {\n /* non-fatal */\n }\n }\n connection = null;\n if (instance) {\n try {\n instance.closeSync();\n } catch {\n /* non-fatal */\n }\n }\n instance = null;\n connectionMode = null;\n}\n\n/**\n * Get or create a DuckDB connection.\n *\n * On first call:\n * - Creates DuckDBInstance at `<dataDir>/analytics.duckdb`\n * - Applies memory, thread, and security configuration\n * - Drops legacy inline market schema from analytics.duckdb\n * - ATTACHes market.duckdb as `market` catalog\n * - Creates 'trades' schema and market tables\n * - Stores connection for reuse\n *\n * Subsequent calls return the existing connection.\n *\n * @param dataDir - Directory where analytics.duckdb will be stored\n * @returns Promise<DuckDBConnection> - The DuckDB connection\n * @throws Error if database is corrupted or cannot be opened\n */\nexport async function getConnection(dataDir: string): Promise<DuckDBConnection> {\n // Return existing connection if available (singleton pattern)\n if (connection) {\n return connection;\n }\n\n const dbPath = path.join(dataDir, \"analytics.duckdb\");\n\n // Configuration from environment with sensible defaults — thread count and\n // memory limit auto-scale to host capacity (see helpers above).\n const threads = process.env.DUCKDB_THREADS || defaultThreads();\n const memoryLimit = process.env.DUCKDB_MEMORY_LIMIT || defaultMemoryLimit();\n\n // Store config for reuse by upgrade/downgrade\n storedDbPath = dbPath;\n storedThreads = threads;\n storedMemoryLimit = memoryLimit;\n storedMarketDbPath = resolveMarketDbPath(dataDir);\n // Lock recovery: kill other tradeblocks-mcp processes that hold the write lock.\n // Enabled by default — safe because market data is re-importable and lock holders\n // are just other Claude sessions that can lazily restart their MCP server.\n // Set DUCKDB_LOCK_RECOVERY=false to disable (only kill orphaned processes).\n const forceRecovery = (process.env.DUCKDB_LOCK_RECOVERY ?? \"true\") !== \"false\";\n\n try {\n await openReadWriteConnection(dbPath, threads, memoryLimit);\n // Release write lock after initialization — idle state is read-only.\n // Write tools call upgradeToReadWrite() when they need writes.\n await downgradeToReadOnly(dataDir);\n return connection!; // downgradeToReadOnly reopened as RO\n } catch (error) {\n // Provide clear error message for common issues\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n // Lock recovery: auto-kill orphaned tradeblocks-mcp processes (PPID=1) that hold the lock.\n // With DUCKDB_LOCK_RECOVERY=true, also kills non-orphaned holders (force mode).\n if (isLockError(errorMessage)) {\n const recovered = await tryRecoverLockByTerminatingStaleProcess(\n errorMessage,\n dbPath,\n forceRecovery,\n );\n if (recovered) {\n // DuckDB file locks may linger briefly after process death — retry with backoff\n for (let attempt = 0; attempt < 3; attempt++) {\n await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));\n try {\n await openReadWriteConnection(dbPath, threads, memoryLimit);\n // Release write lock after initialization\n await downgradeToReadOnly(dataDir);\n return connection!;\n } catch (retryError) {\n const retryMsg = retryError instanceof Error ? retryError.message : String(retryError);\n if (attempt < 2 && isLockError(retryMsg)) continue;\n resetConnectionState();\n throw new Error(\n `Failed to initialize DuckDB at ${dbPath} after lock recovery: ${retryMsg}`,\n );\n }\n }\n }\n }\n\n // Reset state on failure\n resetConnectionState();\n\n // Check for corruption indicators\n if (\n errorMessage.includes(\"corrupt\") ||\n errorMessage.includes(\"Invalid\") ||\n errorMessage.includes(\"cannot open\")\n ) {\n throw new Error(\n `DuckDB database appears corrupted at ${dbPath}. ` +\n `Please delete the file manually and restart. ` +\n `Original error: ${errorMessage}`,\n );\n }\n\n throw new Error(`Failed to initialize DuckDB at ${dbPath}: ${errorMessage}`);\n }\n}\n\n/**\n * Close the DuckDB connection and release resources.\n *\n * DETACHes market.duckdb before closing to ensure WAL is checkpointed cleanly.\n * Should be called during graceful shutdown (SIGINT, SIGTERM).\n * Safe to call multiple times or when no connection exists.\n */\nexport async function closeConnection(): Promise<void> {\n if (connection) {\n try {\n await connection.run(\"CHECKPOINT\");\n } catch {\n /* non-fatal */\n }\n try {\n await detachMarketDb(connection);\n } catch {\n /* non-fatal, log debug */\n }\n try {\n // closeSync is the synchronous close method for DuckDB connections\n connection.closeSync();\n } catch (error) {\n // Log but don't throw during shutdown\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`Warning: Error closing DuckDB connection: ${msg}`);\n }\n connection = null;\n }\n\n // Close the DuckDB instance to release the file lock.\n // Without this, the native handle leaks until GC and blocks subsequent RW opens.\n if (instance) {\n try {\n instance.closeSync();\n } catch {\n /* non-fatal */\n }\n }\n instance = null;\n connectionMode = null;\n}\n\n/**\n * Upgrade the connection to read-write mode for write operations.\n * No-op if already in read-write mode.\n * Retries with backoff if another session briefly holds the write lock.\n *\n * @param dataDir - Directory where analytics.duckdb lives\n * @param options.fallbackToReadOnly - If true, fall back to read-only on lock failure\n * instead of throwing. Used by sync middleware where RO is acceptable (just skip sync).\n * Default: false — callers that need writes get a clear error instead of a silent RO surprise.\n */\nexport async function upgradeToReadWrite(\n dataDir: string,\n options?: { fallbackToReadOnly?: boolean },\n): Promise<DuckDBConnection> {\n if (connectionMode === \"read_write\" && connection) return connection;\n await closeConnection();\n\n // Open directly in RW mode — do NOT go through getConnection() which would\n // downgrade back to RO immediately after init.\n // storedDbPath/storedThreads/storedMemoryLimit are set by the initial getConnection() call.\n const dbPath = storedDbPath || path.join(dataDir, \"analytics.duckdb\");\n const threads = storedThreads || process.env.DUCKDB_THREADS || defaultThreads();\n const memoryLimit = storedMemoryLimit || process.env.DUCKDB_MEMORY_LIMIT || defaultMemoryLimit();\n if (!storedMarketDbPath) {\n storedMarketDbPath = resolveMarketDbPath(dataDir);\n }\n\n // Try RW with retries — another session may briefly hold the lock during its own sync.\n // After /mcp reconnect, the old process may not have released the DuckDB file lock yet,\n // so we retry with increasing delays to allow the lock to fully release.\n const maxRetries = 4;\n const retryDelayMs = 1000;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await openReadWriteConnection(dbPath, threads, memoryLimit);\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (!isLockError(msg)) throw error;\n lastError = error instanceof Error ? error : new Error(msg);\n if (attempt < maxRetries) {\n await new Promise((r) => setTimeout(r, retryDelayMs));\n }\n }\n }\n\n // RW retries exhausted\n if (options?.fallbackToReadOnly && storedDbPath && storedThreads && storedMemoryLimit) {\n try {\n await openReadOnlyConnection(storedDbPath, storedThreads, storedMemoryLimit);\n if (connection) return connection;\n } catch {\n // RO also failed (WAL may still exist from active writer)\n }\n }\n\n throw (\n lastError ||\n new Error(\n \"Cannot acquire DuckDB write lock. Another process holds it. \" +\n \"Kill other tradeblocks-mcp processes or restart Claude Code.\",\n )\n );\n}\n\n/**\n * Downgrade the connection to read-only mode after sync/write operations.\n * No-op if already in read-only mode.\n * Closes the RW connection (checkpoints WAL, releases write lock) and reopens as RO.\n * Multiple processes can hold RO connections simultaneously.\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport async function downgradeToReadOnly(dataDir: string): Promise<void> {\n if (connectionMode === \"read_only\") return;\n if (!connection) return;\n await closeConnection();\n if (storedDbPath && storedThreads && storedMemoryLimit) {\n await openReadOnlyConnection(storedDbPath, storedThreads, storedMemoryLimit);\n }\n}\n\n/**\n * Open a DuckDB connection scoped to writes against market.duckdb only, with\n * NO host file opened against analytics.duckdb.\n *\n * Shape: in-memory host instance, market.duckdb ATTACHed as `market` in\n * READ_WRITE mode, market parquet views registered on the connection. Writes\n * resolve to the attached market catalog; reads against the `market.*` views\n * are served from the canonical parquet files under `<dataRoot>/market/`.\n *\n * Why this helper exists: callers whose only job is to write market data\n * (intraday bar ingest, option-chain refresh, quote backfill) should not\n * acquire any lock on analytics.duckdb. Holding analytics RW for a\n * long-running market refresh blocks every parallel reader (other shells,\n * dashboards, evaluation processes) from even opening analytics READ_ONLY —\n * DuckDB rejects RO opens against a file that has an active WAL written by\n * another process. Routing market writes through a `:memory:` host with\n * market attached RW leaves analytics.duckdb completely untouched for the\n * duration of the ingest, so concurrent processes keep their normal RO\n * access. Market writes are still single-writer (the OS-level file lock on\n * market.duckdb is unchanged) — this helper trades only the analytics lock.\n *\n * Important: the returned connection is NOT shared via the module-level\n * singleton. `getCurrentConnection()` is not affected. The caller owns the\n * lifecycle and must call `close()` to flush the market WAL and release the\n * market.duckdb file lock.\n *\n * @param baseDir - Directory passed to the rest of the db/ module (the same\n * directory that `getConnection(baseDir)` would receive). Used as the\n * fallback parent for market.duckdb when neither `--market-db` nor\n * `MARKET_DB_PATH` is set, and as the fallback for `getDataRoot()`.\n */\nexport interface MarketOnlyConnection {\n /** The active DuckDB connection. Backed by a `:memory:` host with `market` attached RW. */\n conn: DuckDBConnection;\n /** Resolved path to the market.duckdb file that was attached. */\n marketDbPath: string;\n /**\n * Flush the market WAL, detach the market catalog, and close the connection\n * + in-memory host instance. Best-effort on each step — surfaces no errors;\n * the goal is to release the market.duckdb file lock for the next writer.\n * Safe to call multiple times (subsequent calls are no-ops).\n */\n close(): Promise<void>;\n}\n\nexport async function openMarketOnlyConnection(baseDir: string): Promise<MarketOnlyConnection> {\n const marketDbPath = resolveMarketDbPath(baseDir);\n\n // `:memory:` host means the connection does not open any on-disk database\n // as the catalog root — analytics.duckdb is never touched by this code\n // path. `enable_external_access: \"true\"` is required at instance creation\n // to permit the ATTACH of a local file (DuckDB 1.4+ otherwise blocks all\n // filesystem operations from within the connection).\n const memoryInstance = await DuckDBInstance.create(\":memory:\", {\n enable_external_access: \"true\",\n });\n const conn = await memoryInstance.connect();\n\n // ATTACH market.duckdb as RW. Reuses the same path-resolution +\n // corruption-recovery logic as the regular RW path so callers see\n // consistent behavior.\n await attachMarketDb(conn, marketDbPath, \"read_write\");\n\n // Register views over the canonical market parquet partitions on this\n // fresh connection. Without this, `SELECT ... FROM market.option_chain`\n // (and friends) resolve only against the physical tables inside the\n // attached market.duckdb — which is empty in the parquet-mode deployment\n // where the partition files are the source of truth. createMarketParquetViews\n // uses CREATE OR REPLACE so this is idempotent against pre-existing views\n // inside market.duckdb.\n const dataRoot = getDataRoot(baseDir);\n await createMarketParquetViews(conn, dataRoot);\n\n let closed = false;\n const close = async (): Promise<void> => {\n if (closed) return;\n closed = true;\n // Flush the market WAL before detach so the file on disk is consistent\n // for the next reader. DETACH is best-effort — if it fails (e.g. an\n // in-flight statement still references the catalog), we still want to\n // close the handle so subsequent processes can acquire the RW lock.\n try {\n await conn.run(\"CHECKPOINT market\");\n } catch {\n /* non-fatal */\n }\n try {\n await detachMarketDb(conn);\n } catch {\n /* non-fatal */\n }\n try {\n conn.closeSync();\n } catch {\n /* non-fatal */\n }\n try {\n memoryInstance.closeSync();\n } catch {\n /* non-fatal */\n }\n };\n\n return { conn, marketDbPath, close };\n}\n\n/**\n * Open a parquet-backed market-data connection: an in-memory host with the\n * `market.*` views registered over the canonical parquet partitions under\n * `<dataRoot>/market/`, and WITHOUT attaching the shared market database file.\n * No host file is opened against the analytics database either.\n *\n * This is the canonical helper for every parquet-mode consumer — read AND\n * write. Once the shared market database file is out of the picture there is no\n * read/write distinction for the *connection*: both inputs and outputs are\n * parquet. Reads resolve against the in-memory `market.*` views (or direct\n * `read_parquet` on absolute file paths, which store callers prefer). Writes go\n * through `COPY ... TO '<file>' (FORMAT PARQUET)` staged in a per-connection\n * `TEMP` table — a filesystem write that needs no attached catalog. The store\n * write path stages and copies; it never `INSERT`s into the `market.*` views.\n *\n * Shape: in-memory host instance, a `market` schema created in-memory, and the\n * market parquet views registered on that schema. Nothing is attached, so no\n * OS-level file lock is taken on the shared market database — multiple readers,\n * multiple parquet writers, and a legacy attach-based market writer all coexist\n * without contention.\n *\n * Why this helper exists: the parquet-mode path's only inputs and outputs are\n * the parquet partitions. The shared market database file is never the source\n * of truth on this path, so attaching it is pure liability — it makes the\n * caller block (or be blocked by) any other process holding the database file\n * lock. Routing through a `:memory:` host with parquet views registered leaves\n * the shared market database completely untouched. This is the parquet analog\n * of `openMarketOnlyConnection` (the attach-based RW helper that non-parquet\n * deployments still require for physical-table `INSERT`s into `market.*`); the\n * one structural difference is that this path must CREATE the `market` schema\n * itself (no attach creates it) before registering the views.\n *\n * Important: the returned connection is NOT shared via the module-level\n * singleton. `getCurrentConnection()` is not affected. The caller owns the\n * lifecycle and must call `close()`.\n *\n * @param baseDir - Directory passed to the rest of the db/ module (the same\n * directory that `getConnection(baseDir)` would receive). Used as the\n * fallback for `getDataRoot()` when neither `--data-root` nor the data-root\n * env var is set.\n */\nexport interface MarketParquetConnection {\n /** The active DuckDB connection. Backed by a `:memory:` host with `market.*` parquet views. */\n conn: DuckDBConnection;\n /** Resolved data root the parquet views were registered against (for logging/parity). */\n dataRoot: string;\n /**\n * Close the connection + in-memory host instance. Best-effort on each step.\n * Nothing is attached on this path, so there is no WAL to flush and no\n * catalog to detach. Safe to call multiple times (subsequent calls are\n * no-ops).\n */\n close(): Promise<void>;\n}\n\nexport async function openMarketParquetConnection(\n baseDir: string,\n): Promise<MarketParquetConnection> {\n // `:memory:` host means the connection does not open any on-disk database as\n // the catalog root — neither the analytics database nor the shared market\n // database file is touched by this code path. `enable_external_access:\n // \"true\"` is required at instance creation to permit reads of local parquet\n // files AND `COPY ... TO '<file>'` writes (DuckDB 1.4+ otherwise blocks all\n // filesystem operations from within the connection).\n const memoryInstance = await DuckDBInstance.create(\":memory:\", {\n enable_external_access: \"true\",\n });\n const conn = await memoryInstance.connect();\n\n // The `market.*` views target the `market` schema. On the attach-based\n // paths the ATTACH creates that schema; here there is no attach, so we must\n // create it before registering the views or every CREATE VIEW market.* fails\n // with a catalog error.\n await conn.run(\"CREATE SCHEMA IF NOT EXISTS market\");\n\n // Register views over the canonical market parquet partitions. These are the\n // source of truth for reads; no physical market tables are consulted. The\n // ingest write path uses these views for read-back during enrichment (e.g.\n // the `market.spot_daily` identity-row backfill) and writes its output via\n // `COPY ... TO` to parquet files — never an INSERT into a view.\n const dataRoot = getDataRoot(baseDir);\n await createMarketParquetViews(conn, dataRoot);\n\n let closed = false;\n const close = async (): Promise<void> => {\n if (closed) return;\n closed = true;\n try {\n conn.closeSync();\n } catch {\n /* non-fatal */\n }\n try {\n memoryInstance.closeSync();\n } catch {\n /* non-fatal */\n }\n };\n\n return { conn, dataRoot, close };\n}\n\n/**\n * Read-side name for {@link openMarketParquetConnection}. Retained as the\n * canonical handle for read-only parquet consumers; the underlying connection\n * is identical (parquet has no read/write distinction once the shared market\n * database file is out of the picture).\n */\nexport type MarketReadOnlyConnection = MarketParquetConnection;\n\nexport function openMarketReadOnlyConnection(baseDir: string): Promise<MarketReadOnlyConnection> {\n return openMarketParquetConnection(baseDir);\n}\n\n/**\n * Open a fresh read-only connection without going through `getConnection()`'s\n * RW-init phase. The standard `getConnection()` flow always opens RW briefly\n * to create schemas + parquet views before downgrading; that brief RW window\n * is exclusive across processes (DuckDB is single-process-write) and races\n * fatally with sibling readers when multiple consumers spin up at once.\n *\n * Use this when:\n * - Schemas + market views already exist (some prior RW caller initialized them)\n * - The caller only needs to *read* — no write tools, no schema setup\n * - Multiple processes need concurrent access to the same database\n *\n * The fork-pool in `self-improve.mjs --score all` is the canonical caller —\n * each child worker reads strategy JSON + OO trades + parquet partitions and\n * writes nothing back. Two RO connections never conflict.\n *\n * Returns a connection that's NOT shared via the module-level singleton —\n * the caller owns it and is responsible for closing. (The internal\n * `connection`/`instance` module state is still mutated for compatibility\n * with `getCurrentConnection()` / store contexts that read from it; in a\n * subprocess that's fine since the module state is per-worker.)\n */\nexport async function getReadOnlyConnection(dataDir: string): Promise<DuckDBConnection> {\n if (connection) return connection;\n const dbPath = path.join(dataDir, \"analytics.duckdb\");\n const threads = process.env.DUCKDB_THREADS || defaultThreads();\n const memoryLimit = process.env.DUCKDB_MEMORY_LIMIT || defaultMemoryLimit();\n storedDbPath = dbPath;\n storedThreads = threads;\n storedMemoryLimit = memoryLimit;\n storedMarketDbPath = resolveMarketDbPath(dataDir);\n await openReadOnlyConnection(dbPath, threads, memoryLimit);\n return connection!;\n}\n\n/**\n * Get the current connection mode.\n * Used by middleware to determine if sync should be skipped (RO fallback).\n */\nexport function getConnectionMode(): \"read_write\" | \"read_only\" | null {\n return connectionMode;\n}\n\n/**\n * Check if a connection is currently active.\n * Useful for diagnostics and testing.\n */\nexport function isConnected(): boolean {\n return connection !== null;\n}\n\n/**\n * Sync accessor for the currently-active module-level connection.\n *\n * Resolves the *current* DuckDBConnection at call time. Designed to back a\n * `get conn()` getter on `StoreContext` so stores that hold the ctx forever\n * still see the connection that `upgradeToReadWrite` / `downgradeToReadOnly`\n * swap in after init (the old handle is `closeSync()`-ed and would otherwise\n * surface as \"connection disconnected\" on any subsequent read/write).\n *\n * Throws if no connection is open — callers should have already awaited\n * `getConnection(dataDir)` during server init.\n */\nexport function getCurrentConnection(): DuckDBConnection {\n if (!connection) {\n throw new Error(\n \"No active DuckDB connection. Call getConnection(dataDir) during server init before accessing store contexts.\",\n );\n }\n return connection;\n}\n\n// Note: the legacy intraday write-target getter / module-state variable and\n// the consumer override hook have been removed. Every spot write now flows\n// through SpotStore.writeBars (the canonical Hive-partitioned\n// `spot/ticker=X/date=Y/` layout); there is no longer a per-process override\n// of the write target.\n","/**\n * DuckDB Schema Definitions\n *\n * Creates and manages table schemas for sync metadata and trade data\n * in analytics.duckdb (the trades database).\n *\n * Market table schemas live in market-schemas.ts and are applied to the\n * separate market.duckdb after ATTACH.\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\n\nexport async function tableExists(\n conn: DuckDBConnection,\n schemaName: string,\n tableName: string,\n): Promise<boolean> {\n const result = await conn.runAndReadAll(`\n SELECT 1\n FROM duckdb_tables()\n WHERE schema_name = '${schemaName}' AND table_name = '${tableName}'\n `);\n return result.getRows().length > 0;\n}\n\nasync function hasColumn(\n conn: DuckDBConnection,\n schemaName: string,\n tableName: string,\n columnName: string,\n): Promise<boolean> {\n const result = await conn.runAndReadAll(`\n SELECT 1\n FROM duckdb_columns()\n WHERE schema_name = '${schemaName}'\n AND table_name = '${tableName}'\n AND column_name = '${columnName}'\n `);\n return result.getRows().length > 0;\n}\n\n/**\n * Create sync metadata table for the trades schema.\n *\n * trades._sync_metadata: Tracks sync state for each block\n *\n * Note: market._sync_metadata is created by ensureMutableMarketTables() in\n * market-schemas.ts and lives in the separate market.duckdb.\n *\n * @param conn - Active DuckDB connection\n */\nexport async function ensureSyncTables(conn: DuckDBConnection): Promise<void> {\n // Block sync metadata - tracks which blocks are synced and their file hashes\n await conn.run(`\n CREATE TABLE IF NOT EXISTS trades._sync_metadata (\n block_id VARCHAR PRIMARY KEY,\n tradelog_hash VARCHAR NOT NULL,\n dailylog_hash VARCHAR,\n reportinglog_hash VARCHAR,\n synced_at TIMESTAMP NOT NULL,\n sync_version INTEGER DEFAULT 1\n )\n `);\n\n // Migration: sync-import.mjs creates this table from Parquet (no constraints).\n // Detect missing PK and recreate so INSERT OR REPLACE works.\n const result = await conn.run(`\n SELECT COUNT(*) AS cnt FROM duckdb_constraints()\n WHERE schema_name = 'trades' AND table_name = '_sync_metadata'\n AND constraint_type = 'PRIMARY KEY'\n `);\n const rows = await result.getRows();\n if (Number(rows[0][0]) === 0) {\n await conn.run(`DROP TABLE trades._sync_metadata`);\n await conn.run(`\n CREATE TABLE trades._sync_metadata (\n block_id VARCHAR PRIMARY KEY,\n tradelog_hash VARCHAR NOT NULL,\n dailylog_hash VARCHAR,\n reportinglog_hash VARCHAR,\n synced_at TIMESTAMP NOT NULL,\n sync_version INTEGER DEFAULT 1\n )\n `);\n }\n}\n\n/**\n * Create the trade data table for storing synced trade records.\n *\n * Note: No PRIMARY KEY constraint - trades can have duplicates per day\n * (e.g., multiple trades opened at same time with same strategy).\n *\n * @param conn - Active DuckDB connection\n */\nexport async function ensureTradeDataTable(conn: DuckDBConnection): Promise<void> {\n await conn.run(`\n CREATE TABLE IF NOT EXISTS trades.trade_data (\n block_id VARCHAR NOT NULL,\n date_opened DATE NOT NULL,\n time_opened VARCHAR,\n strategy VARCHAR,\n legs VARCHAR,\n premium DOUBLE,\n num_contracts INTEGER,\n pl DOUBLE NOT NULL,\n date_closed DATE,\n time_closed VARCHAR,\n reason_for_close VARCHAR,\n margin_req DOUBLE,\n opening_commissions DOUBLE,\n closing_commissions DOUBLE,\n ticker VARCHAR\n )\n `);\n\n // Backfill schema upgrades on existing databases.\n if (!(await hasColumn(conn, \"trades\", \"trade_data\", \"ticker\"))) {\n await conn.run(`ALTER TABLE trades.trade_data ADD COLUMN ticker VARCHAR`);\n }\n // Migration: add source column for trade provenance tracking.\n // 'csv' = imported from Option Omega CSV; other values may be\n // populated by additional importers when present.\n if (!(await hasColumn(conn, \"trades\", \"trade_data\", \"source\"))) {\n await conn.run(`ALTER TABLE trades.trade_data ADD COLUMN source VARCHAR DEFAULT 'csv'`);\n await conn.run(`UPDATE trades.trade_data SET source = 'csv' WHERE source IS NULL`);\n }\n // Migration: add dte column for DTE-at-entry.\n if (!(await hasColumn(conn, \"trades\", \"trade_data\", \"dte\"))) {\n await conn.run(`ALTER TABLE trades.trade_data ADD COLUMN dte INTEGER`);\n }\n // Migration: add entry_greeks_json column for per-trade entry greeks.\n if (!(await hasColumn(conn, \"trades\", \"trade_data\", \"entry_greeks_json\"))) {\n await conn.run(`ALTER TABLE trades.trade_data ADD COLUMN entry_greeks_json JSON`);\n }\n}\n\n/**\n * Create the reporting data table for storing synced reporting log records.\n *\n * Note: No PRIMARY KEY constraint - trades can have duplicates per day\n * (same pattern as trade_data).\n *\n * @param conn - Active DuckDB connection\n */\nexport async function ensureReportingDataTable(conn: DuckDBConnection): Promise<void> {\n await conn.run(`\n CREATE TABLE IF NOT EXISTS trades.reporting_data (\n block_id VARCHAR NOT NULL,\n date_opened DATE NOT NULL,\n time_opened VARCHAR,\n strategy VARCHAR,\n legs VARCHAR,\n initial_premium DOUBLE,\n num_contracts INTEGER,\n pl DOUBLE NOT NULL,\n date_closed DATE,\n time_closed VARCHAR,\n closing_price DOUBLE,\n avg_closing_cost DOUBLE,\n reason_for_close VARCHAR,\n opening_price DOUBLE,\n ticker VARCHAR\n )\n `);\n\n // Backfill schema upgrades on existing databases.\n if (!(await hasColumn(conn, \"trades\", \"reporting_data\", \"ticker\"))) {\n await conn.run(`ALTER TABLE trades.reporting_data ADD COLUMN ticker VARCHAR`);\n }\n}\n","/**\n * Market DuckDB Schema Definitions\n *\n * Split into two concerns:\n * 1. ensureMutableMarketTables() — metadata tables that receive INSERTs\n * (_sync_metadata). Always created regardless of Parquet mode.\n * 2. ensureMarketDataTables() — physical data tables (spot, enriched,\n * enriched_context). Only created as fallback when the corresponding\n * Parquet views are not active.\n *\n * Called after ATTACH in openReadWriteConnection() to ensure market tables exist.\n *\n * Tables use CREATE TABLE IF NOT EXISTS for idempotency — safe to call on\n * every RW open regardless of whether market.duckdb already exists.\n *\n * Table naming: market.spot resolves to catalog=market, schema=main, table=spot\n * after ATTACH '...' AS market. Do NOT create a schema within market.duckdb.\n *\n * The legacy fallback CREATE TABLE blocks for daily / date_context / intraday /\n * option_chain / option_quote_minutes have been removed. option_chain and\n * option_quote_minutes live only as Parquet views (no physical fallback);\n * spot / enriched / enriched_context are the only physical fallback tables\n * that remain.\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\n\n// =============================================================================\n// Mutable metadata tables (always created, receive INSERTs)\n// =============================================================================\n\n/**\n * Ensure mutable metadata tables exist in the attached market.duckdb.\n *\n * These tables receive INSERTs regardless of whether market data is served from\n * Parquet views or physical tables. Always call this on every RW open.\n *\n * @param conn - Active DuckDB connection with market catalog attached\n */\nexport async function ensureMutableMarketTables(conn: DuckDBConnection): Promise<void> {\n // Legacy coverage-tracking table CREATE removed — grep-verified zero readers/writers today.\n // Coverage is now derived from store.getCoverage() (Parquet: readdirSync; DuckDB: SELECT DISTINCT).\n\n // Sync state tracking for market data imports\n // PK: (source, ticker, target_table) — tracks per-source, per-ticker, per-table sync state\n await conn.run(`\n CREATE TABLE IF NOT EXISTS market._sync_metadata (\n source VARCHAR NOT NULL,\n ticker VARCHAR NOT NULL,\n target_table VARCHAR NOT NULL,\n\n content_hash VARCHAR,\n max_date VARCHAR,\n wilder_state JSON,\n synced_at TIMESTAMP NOT NULL,\n\n PRIMARY KEY (source, ticker, target_table)\n )\n `);\n}\n\n// =============================================================================\n// Physical data tables (fallback when Parquet views are not active)\n// =============================================================================\n\n/**\n * Ensure the v3.0 physical market data tables exist in the attached market.duckdb.\n *\n * This is the FALLBACK path — only called when Parquet views are not active\n * (public repo, fresh clones without Parquet files).\n *\n * Must be called AFTER `ATTACH '...' AS market` in openReadWriteConnection.\n * Creates all columns upfront so writers do not need ALTER TABLE.\n *\n * The legacy fallback CREATE TABLE blocks for daily / date_context / intraday /\n * option_chain / option_quote_minutes have been retired; only the three\n * canonical tables remain below.\n *\n * @param conn - Active DuckDB connection with market catalog attached\n */\nexport async function ensureMarketDataTables(conn: DuckDBConnection): Promise<void> {\n // ============================================================================\n // One-time cleanup: drop legacy physical tables that persist from pre-v3.0\n // runs. Idempotent (IF EXISTS).\n //\n // The legacy CREATE TABLE blocks have been removed, but existing\n // market.duckdb files may still contain these physical tables from earlier\n // sessions. Drop them here so SELECTs against\n // market.(daily|intraday|date_context) error cleanly instead of returning\n // stale data. data_coverage is dropped as well — coverage is now derived\n // from store.getCoverage().\n // ============================================================================\n // NOTE: option_chain + option_quote_minutes are NOT in this list — those names\n // are still valid in v3.0 as Parquet-backed VIEWs (registered by\n // createMarketParquetViews earlier in the connection sequence). Dropping them\n // here would destroy legitimate v3.0 views.\n for (const name of [\"daily\", \"date_context\", \"intraday\", \"data_coverage\"]) {\n try {\n await conn.run(`DROP VIEW IF EXISTS market.${name}`);\n } catch {\n /* wrong type */\n }\n try {\n await conn.run(`DROP TABLE IF EXISTS market.${name}`);\n } catch {\n /* wrong type */\n }\n }\n\n // ============================================================================\n // Canonical physical fallback tables for market data — the only physical\n // fallback tables created. Schemas match the corresponding Parquet schemas\n // exactly.\n // ============================================================================\n\n // market.spot — raw minute bars, ticker-first.\n // PK: (ticker, date, time)\n await conn.run(`\n CREATE TABLE IF NOT EXISTS market.spot (\n ticker VARCHAR NOT NULL,\n date VARCHAR NOT NULL,\n time VARCHAR NOT NULL,\n open DOUBLE,\n high DOUBLE,\n low DOUBLE,\n close DOUBLE,\n bid DOUBLE,\n ask DOUBLE,\n PRIMARY KEY (ticker, date, time)\n )\n `);\n\n // market.enriched — per-ticker computed fields, NO OHLCV.\n // PK: (ticker, date). OHLCV is joined at read time from market.spot.\n await conn.run(`\n CREATE TABLE IF NOT EXISTS market.enriched (\n ticker VARCHAR NOT NULL,\n date VARCHAR NOT NULL,\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 Gap_Filled INTEGER,\n Consecutive_Days INTEGER,\n Prev_Return_Pct DOUBLE,\n Prior_Range_vs_ATR DOUBLE,\n High_Time DOUBLE,\n Low_Time DOUBLE,\n High_Before_Low INTEGER,\n Reversal_Type INTEGER,\n Opening_Drive_Strength DOUBLE,\n Intraday_Realized_Vol DOUBLE,\n Day_of_Week INTEGER,\n Month INTEGER,\n Is_Opex INTEGER,\n ivr DOUBLE,\n ivp DOUBLE,\n PRIMARY KEY (ticker, date)\n )\n `);\n\n // market.enriched_context — cross-ticker derived context fields, one row per date.\n // PK: (date)\n await conn.run(`\n CREATE TABLE IF NOT EXISTS market.enriched_context (\n date VARCHAR NOT NULL,\n Vol_Regime INTEGER,\n Term_Structure_State INTEGER,\n Trend_Direction VARCHAR,\n VIX_Spike_Pct DOUBLE,\n VIX_Gap_Pct DOUBLE,\n PRIMARY KEY (date)\n )\n `);\n}\n","/**\n * Parquet Writer Utility\n *\n * Provides atomic Parquet file writing via DuckDB's COPY TO command.\n * Used by all market data importers and enrichers when TRADEBLOCKS_PARQUET=true.\n *\n * Pattern: INSERT into staging temp table -> COPY TO Parquet -> DROP staging.\n * This ensures atomicity: if COPY TO fails, the original Parquet file is untouched.\n *\n * Exports:\n * - isParquetMode() - Check if Parquet write mode is enabled\n * - resolveMarketDir() - Centralized market directory path\n * - writeParquetAtomic() - Atomic single-file Parquet write via staging table\n * - writeParquetPartition() - Hive-partitioned write. Two overloads:\n * (a) legacy { baseDir, date, selectQuery } → baseDir/date=Y/data.parquet\n * (b) generic { baseDir, partitions: {...}, ... } → baseDir/k1=v1/k2=v2/.../data.parquet\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\nexport { resolveMarketDir } from \"./market-datasets.ts\";\n\n// ---------------------------------------------------------------------------\n// Parquet mode detection\n// ---------------------------------------------------------------------------\n\n/**\n * Check if Parquet write mode is enabled.\n * Reads process.env.TRADEBLOCKS_PARQUET on each call for testability.\n *\n * @returns true when TRADEBLOCKS_PARQUET === 'true', false otherwise\n */\nexport function isParquetMode(): boolean {\n return process.env.TRADEBLOCKS_PARQUET === \"true\";\n}\n\n// ---------------------------------------------------------------------------\n// Atomic Parquet write\n// ---------------------------------------------------------------------------\n\nexport interface WriteParquetAtomicOpts {\n /** Target Parquet file path (e.g., '{dataDir}/market/daily.parquet') */\n targetPath: string;\n /** SELECT query that produces the final dataset */\n selectQuery: string;\n /** Temp table name (default: _staging_{Date.now()}) */\n stagingName?: string;\n /** Parquet compression codec (default: 'ZSTD') */\n compression?: string;\n}\n\n/**\n * Atomic single-file Parquet write via DuckDB staging table.\n *\n * 1. CREATE TEMP TABLE {staging} AS {selectQuery}\n * 2. mkdir -p parent directory\n * 3. COPY {staging} TO '{targetPath}' (FORMAT PARQUET, COMPRESSION {compression})\n * 4. COUNT(*) for rowCount\n * 5. DROP staging in finally block\n *\n * Path-traversal mitigation: targetPath is always constructed by callers via\n * path.join(dataDir, 'market', ...) with no user-supplied path components.\n * Identifier-injection mitigation: staging table names use Date.now() — no user input.\n *\n * @param conn - Active DuckDB connection\n * @param opts - Write options\n * @returns Object with rowCount of written rows\n */\nexport async function writeParquetAtomic(\n conn: DuckDBConnection,\n opts: WriteParquetAtomicOpts,\n): Promise<{ rowCount: number }> {\n const {\n targetPath,\n selectQuery,\n stagingName = `_staging_${Date.now()}`,\n compression = \"ZSTD\",\n } = opts;\n\n // Write to a temp sibling path, then atomic-rename into place. DuckDB's\n // COPY ... TO 'data.parquet' writes directly to the target — concurrent\n // readers (e.g. createMarketParquetViews globbing the archive) can observe\n // the file as empty / truncated / missing during the write. fs.rename is\n // atomic on local filesystems (APFS, ext4), so after this block a reader\n // either sees the old file or the new file, never a partially-written or\n // absent one. The temp name ends in `.tmp-PID-TS` (not `.parquet`) so\n // `**/data.parquet` and `**/*.parquet` globs both skip it.\n const tempPath = `${targetPath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n try {\n // Stage data into a temp table\n await conn.run(`CREATE TEMP TABLE \"${stagingName}\" AS ${selectQuery}`);\n\n // Ensure parent directory exists\n await fs.mkdir(path.dirname(targetPath), { recursive: true });\n\n // Write to sidecar temp path\n await conn.run(\n `COPY \"${stagingName}\" TO '${tempPath}' (FORMAT PARQUET, COMPRESSION ${compression})`,\n );\n\n // Atomic replace\n await fs.rename(tempPath, targetPath);\n\n // Get row count from staging table\n const reader = await conn.runAndReadAll(\n `SELECT COUNT(*)::INTEGER AS cnt FROM \"${stagingName}\"`,\n );\n const rowCount = Number(reader.getRows()[0][0]);\n\n return { rowCount };\n } catch (writeErr) {\n // Clean up sidecar on failure so the next attempt starts fresh\n try {\n await fs.unlink(tempPath);\n } catch {\n /* may not exist */\n }\n throw writeErr;\n } finally {\n // Always clean up staging table\n try {\n await conn.run(`DROP TABLE IF EXISTS \"${stagingName}\"`);\n } catch {\n // Non-fatal: table may not exist if creation failed\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Hive-partitioned Parquet write\n// ---------------------------------------------------------------------------\n\nexport interface WriteParquetPartitionOpts {\n /** Base directory for Hive partitions (e.g., '{dataDir}/market/intraday') */\n baseDir: string;\n /** Partition date (e.g., '2025-01-06') */\n date: string;\n /** SELECT query that produces rows for this date */\n selectQuery: string;\n /** Parquet compression codec (default: 'ZSTD') */\n compression?: string;\n}\n\n/**\n * Generic multi-level Hive partition writer options.\n *\n * Insertion order of `partitions` determines the Hive directory component order.\n * ES2015 guarantees string-keyed object insertion order is preserved by `Object.entries`.\n *\n * Examples:\n * { partitions: { ticker: 'SPX', date: '2025-01-06' } }\n * → baseDir/ticker=SPX/date=2025-01-06/data.parquet\n * { partitions: { underlying: 'SPX', date: '2025-01-06' } }\n * → baseDir/underlying=SPX/date=2025-01-06/data.parquet\n * { partitions: { ticker: 'VIX' } }\n * → baseDir/ticker=VIX/data.parquet\n * { partitions: {} }\n * → baseDir/data.parquet (callers that need this path should prefer writeParquetAtomic)\n */\nexport interface WriteParquetPartitionOptsV3 {\n baseDir: string;\n /** Key=value pairs composed in insertion order into Hive directory segments. */\n partitions: Record<string, string>;\n selectQuery: string;\n compression?: string;\n /** Defaults to 'data.parquet'. */\n filename?: string;\n}\n\n// Partition-value whitelist — deepest defense-in-depth boundary against path\n// traversal. Allows uppercase/lowercase alnum, dot, underscore, hyphen.\n// Rejects / \\ .. null whitespace and newlines. Zod schemas and the ticker\n// registry also validate upstream.\nconst PARTITION_VALUE_RE = /^[A-Za-z0-9._-]+$/;\n// Partition-key whitelist — keys become `key=` prefix in directory names.\n// Must start with letter or underscore (no leading digit), then alnum/underscore.\nconst PARTITION_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Write a single Hive partition to the correct directory layout.\n *\n * Two supported shapes (via TypeScript overloads):\n * 1. Generic multi-level:\n * writeParquetPartition(conn, { baseDir, partitions: {ticker,date}, selectQuery })\n * → baseDir/ticker=X/date=Y/data.parquet\n * 2. Legacy single-{date} shim (retained for existing callers):\n * writeParquetPartition(conn, { baseDir, date, selectQuery })\n * → baseDir/date=Y/data.parquet (byte-identical to historical behavior)\n *\n * Overwrites existing partition file (idempotent — safe to re-run).\n *\n * Security: partition keys and values are validated against strict whitelists\n * BEFORE any filesystem operation. Unsafe input throws immediately.\n *\n * @param conn - Active DuckDB connection\n * @param opts - Partition write options (either shape)\n * @returns Object with rowCount of written rows\n */\n// Overload: generic multi-level signature\nexport function writeParquetPartition(\n conn: DuckDBConnection,\n opts: WriteParquetPartitionOptsV3,\n): Promise<{ rowCount: number }>;\n// Overload: legacy single-{date} signature\nexport function writeParquetPartition(\n conn: DuckDBConnection,\n opts: WriteParquetPartitionOpts,\n): Promise<{ rowCount: number }>;\n// Implementation\nexport async function writeParquetPartition(\n conn: DuckDBConnection,\n opts: WriteParquetPartitionOpts | WriteParquetPartitionOptsV3,\n): Promise<{ rowCount: number }> {\n // Runtime dispatch — this boolean is load-bearing.\n // The legacy shape has `date` but no `partitions`; the generic shape has `partitions`.\n const isLegacy = \"date\" in opts && !(\"partitions\" in opts);\n const partitions: Record<string, string> = isLegacy\n ? { date: (opts as WriteParquetPartitionOpts).date }\n : (opts as WriteParquetPartitionOptsV3).partitions;\n const filename = isLegacy\n ? \"data.parquet\"\n : ((opts as WriteParquetPartitionOptsV3).filename ?? \"data.parquet\");\n\n // Path-traversal mitigation: whitelist every partition key and value BEFORE\n // composing any path. Reject unsafe input (separators, whitespace, nulls).\n for (const [k, v] of Object.entries(partitions)) {\n if (!PARTITION_KEY_RE.test(k)) {\n throw new Error(`writeParquetPartition: unsafe partition key: ${JSON.stringify(k)}`);\n }\n if (!PARTITION_VALUE_RE.test(v)) {\n throw new Error(\n `writeParquetPartition: unsafe partition value for ${k}: ${JSON.stringify(v)}`,\n );\n }\n }\n\n // Preserve insertion order (ES2015 guarantees for string keys).\n const partitionSegments = Object.entries(partitions).map(([k, v]) => `${k}=${v}`);\n const targetPath = path.join(opts.baseDir, ...partitionSegments, filename);\n\n // writeParquetAtomic (existing, unchanged) handles: staging table → mkdir -p\n // (arbitrary depth) → COPY TO → rowCount → cleanup in finally.\n return writeParquetAtomic(conn, {\n targetPath,\n selectQuery: opts.selectQuery,\n compression: opts.compression,\n });\n}\n","import type { DuckDBConnection } from \"@duckdb/node-api\";\nimport * as path from \"path\";\nimport { getDataRoot } from \"./data-root.ts\";\nimport { writeParquetAtomic, writeParquetPartition } from \"./parquet-writer.ts\";\n\nexport type CanonicalSingleFileDataset = \"daily\" | \"date_context\";\nexport type CanonicalPartitionedDataset = \"intraday\" | \"option_chain\" | \"option_quote_minutes\";\nexport type CanonicalMarketDataset = CanonicalSingleFileDataset | CanonicalPartitionedDataset;\n\nconst SINGLE_FILE_DATASETS: Record<CanonicalSingleFileDataset, string> = {\n daily: \"daily.parquet\",\n date_context: \"date_context.parquet\",\n};\n\nconst PARTITIONED_DATASETS: Record<CanonicalPartitionedDataset, string> = {\n intraday: \"intraday\",\n option_chain: \"option_chain\",\n option_quote_minutes: \"option_quote_minutes\",\n};\n\nexport function resolveMarketDir(dataDir: string): string {\n return path.join(getDataRoot(dataDir), \"market\");\n}\n\nexport function resolveCanonicalMarketFile(\n dataDir: string,\n dataset: CanonicalSingleFileDataset,\n): string {\n return path.join(resolveMarketDir(dataDir), SINGLE_FILE_DATASETS[dataset]);\n}\n\nexport function resolveCanonicalMarketPartitionDir(\n dataDir: string,\n dataset: CanonicalPartitionedDataset,\n): string {\n return path.join(resolveMarketDir(dataDir), PARTITIONED_DATASETS[dataset]);\n}\n\nexport function resolveCanonicalMarketPartitionPath(\n dataDir: string,\n dataset: CanonicalPartitionedDataset,\n date: string,\n): string {\n return path.join(resolveCanonicalMarketPartitionDir(dataDir, dataset), `date=${date}`);\n}\n\nexport function resolveCanonicalMarketPartitionFile(\n dataDir: string,\n dataset: CanonicalPartitionedDataset,\n date: string,\n): string {\n return path.join(resolveCanonicalMarketPartitionPath(dataDir, dataset, date), \"data.parquet\");\n}\n\nexport function canonicalMarketTableName(dataset: CanonicalMarketDataset): string {\n return `market.${dataset}`;\n}\n\n// ============================================================================\n// Declarative dataset registry — canonical Parquet layout\n//\n// spot: spot/ticker=X/date=Y/data.parquet\n// enriched: enriched/ticker=X/data.parquet\n// enriched_context: enriched/context/data.parquet\n// option_chain: option_chain/underlying=X/date=Y/data.parquet\n// option_quote_minutes: option_quote_minutes/underlying=X/date=Y/data.parquet\n//\n// This registry is SEPARATE from the legacy CanonicalPartitionedDataset enum\n// and PARTITIONED_DATASETS map above. Both coexist while consumer migration\n// is ongoing: legacy resolvers serve existing callers (date-only paths) and\n// DATASETS_V3 describes the canonical target state.\n// ============================================================================\n\nexport interface DatasetDef {\n subdir: string;\n partitionKeys: string[];\n filename: string;\n}\n\nexport const DATASETS_V3: Record<string, DatasetDef> = {\n spot: { subdir: \"spot\", partitionKeys: [\"ticker\", \"date\"], filename: \"data.parquet\" },\n enriched: { subdir: \"enriched\", partitionKeys: [\"ticker\"], filename: \"data.parquet\" },\n enriched_context: { subdir: \"enriched/context\", partitionKeys: [], filename: \"data.parquet\" },\n option_chain: {\n subdir: \"option_chain\",\n partitionKeys: [\"underlying\", \"date\"],\n filename: \"data.parquet\",\n },\n option_quote_minutes: {\n subdir: \"option_quote_minutes\",\n partitionKeys: [\"underlying\", \"date\"],\n filename: \"data.parquet\",\n },\n option_oi_daily: {\n subdir: \"option_oi_daily\",\n partitionKeys: [\"underlying\", \"date\"],\n filename: \"data.parquet\",\n },\n};\n\n// ------ Per-dataset write helpers ------\n//\n// Each helper reads its DatasetDef, composes the partitions map in the order\n// given by DatasetDef.partitionKeys, and delegates to writeParquetPartition\n// (the generic multi-level writer from parquet-writer.ts).\n//\n// Security note: writeParquetPartition applies a whitelist to every partition\n// key and value — /^[A-Za-z0-9._-]+$/ on values, /^[A-Za-z_][A-Za-z0-9_]*$/ on keys.\n// That is the deepest defense-in-depth layer against path traversal. Helpers\n// do not re-validate.\n\nexport async function writeSpotPartition(\n conn: DuckDBConnection,\n args: {\n dataDir: string;\n ticker: string;\n date: string;\n selectQuery: string;\n compression?: string;\n },\n): Promise<{ rowCount: number }> {\n const def = DATASETS_V3.spot;\n return writeParquetPartition(conn, {\n baseDir: path.join(resolveMarketDir(args.dataDir), def.subdir),\n partitions: { ticker: args.ticker, date: args.date }, // order matches def.partitionKeys\n selectQuery: args.selectQuery,\n compression: args.compression,\n filename: def.filename,\n });\n}\n\nexport async function writeChainPartition(\n conn: DuckDBConnection,\n args: {\n dataDir: string;\n underlying: string;\n date: string;\n selectQuery: string;\n compression?: string;\n },\n): Promise<{ rowCount: number }> {\n const def = DATASETS_V3.option_chain;\n return writeParquetPartition(conn, {\n baseDir: path.join(resolveMarketDir(args.dataDir), def.subdir),\n partitions: { underlying: args.underlying, date: args.date },\n selectQuery: args.selectQuery,\n compression: args.compression,\n filename: def.filename,\n });\n}\n\nexport async function writeQuoteMinutesPartition(\n conn: DuckDBConnection,\n args: {\n dataDir: string;\n underlying: string;\n date: string;\n selectQuery: string;\n compression?: string;\n },\n): Promise<{ rowCount: number }> {\n const def = DATASETS_V3.option_quote_minutes;\n // Sort rows by (ticker, time) before writing so DuckDB row groups in the\n // resulting parquet have tight min/max statistics on `ticker`. The\n // dominant read pattern is ticker-windowed scans across a time range\n // (querying a specific symbol across many minutes/days), so sorting\n // partitions by `(ticker, time)` lets DuckDB prune row groups by ticker\n // first and gives column-statistics benefits within each ticker.\n //\n // The previous `(time, ticker)` order favored single-minute multi-ticker\n // scans; that shape regresses slightly under the new sort, but it is no\n // longer the dominant consumer pattern.\n //\n // Wrapping the caller's selectQuery is safer than asking every caller to\n // remember to ORDER BY: writes go through one funnel.\n //\n // If you change this, also re-run `tools/sort-quote-parquet.mjs` to\n // re-sort existing partitions. The tool is idempotent (skips\n // already-sorted files via row-group ticker stats).\n const sortedSelect = `SELECT * FROM (${args.selectQuery}) AS q ORDER BY q.ticker, q.time`;\n return writeParquetPartition(conn, {\n baseDir: path.join(resolveMarketDir(args.dataDir), def.subdir),\n partitions: { underlying: args.underlying, date: args.date },\n selectQuery: sortedSelect,\n compression: args.compression,\n filename: def.filename,\n });\n}\n\nexport async function writeOiDailyPartition(\n conn: DuckDBConnection,\n args: {\n dataDir: string;\n underlying: string;\n date: string;\n selectQuery: string;\n compression?: string;\n },\n): Promise<{ rowCount: number }> {\n const def = DATASETS_V3.option_oi_daily;\n // Sort rows by ticker before writing so DuckDB row groups carry tight\n // min/max statistics on `ticker` — the dominant read pattern is a\n // ticker-windowed scan within a (underlying, date) partition, matching the\n // quote store's (ticker, time) sort rationale (one value per ticker per day\n // here, so ticker alone is the sort key).\n const sortedSelect = `SELECT * FROM (${args.selectQuery}) AS q ORDER BY q.ticker`;\n return writeParquetPartition(conn, {\n baseDir: path.join(resolveMarketDir(args.dataDir), def.subdir),\n partitions: { underlying: args.underlying, date: args.date },\n selectQuery: sortedSelect,\n compression: args.compression,\n filename: def.filename,\n });\n}\n\n/**\n * Writes the single file for a ticker: enriched/ticker=X/data.parquet.\n * Single-level partitioning (only `ticker`).\n */\nexport async function writeEnrichedTickerFile(\n conn: DuckDBConnection,\n args: { dataDir: string; ticker: string; selectQuery: string; compression?: string },\n): Promise<{ rowCount: number }> {\n const def = DATASETS_V3.enriched;\n return writeParquetPartition(conn, {\n baseDir: path.join(resolveMarketDir(args.dataDir), def.subdir),\n partitions: { ticker: args.ticker },\n selectQuery: args.selectQuery,\n compression: args.compression,\n filename: def.filename,\n });\n}\n\n/**\n * Writes the zero-partition enriched context file: enriched/context/data.parquet.\n *\n * SPECIAL CASE: partitionKeys=[] would cause writeParquetPartition's partition\n * loop to no-op and compose {baseDir}/data.parquet (one directory too shallow).\n * Bypass the generic writer and call writeParquetAtomic directly with the full target path.\n */\nexport async function writeEnrichedContext(\n conn: DuckDBConnection,\n args: { dataDir: string; selectQuery: string; compression?: string },\n): Promise<{ rowCount: number }> {\n const def = DATASETS_V3.enriched_context;\n const targetPath = path.join(resolveMarketDir(args.dataDir), def.subdir, def.filename);\n return writeParquetAtomic(conn, {\n targetPath,\n selectQuery: args.selectQuery,\n compression: args.compression,\n });\n}\n","/**\n * Data Root Directory Override\n *\n * When --data-root is set, shared data (market/, market-meta/, strategies/, blocks/)\n * lives under this path instead of alongside DuckDB files.\n *\n * Set via --data-root CLI flag or TRADEBLOCKS_DATA_ROOT env var.\n * When not set, getDataRoot() falls back to its argument (backward compat).\n *\n * Stored on globalThis so all bundle instances in the same process share state.\n * tsup duplicates this module across separate bundles; a module-scoped variable\n * would give each bundle its own _dataRoot, leaving one path correct (set by\n * the CLI parser) and the other null (a consumer reads its own copy and falls\n * back to the default → wrong path).\n *\n * Note: globalThis is per-realm. Consumers running this across Workers, VM\n * contexts, or sandboxed iframes will see the same divergence across realm\n * boundaries and must coordinate state another way.\n */\nconst KEY = \"__tradeblocks_data_root__\";\ntype GlobalSlot = { [KEY]?: string | null };\n\nexport function setDataRoot(dir: string): void {\n (globalThis as GlobalSlot)[KEY] = dir;\n}\n\nexport function getDataRoot(fallback: string): string {\n return (globalThis as GlobalSlot)[KEY] ?? fallback;\n}\n\n/** Reset for testing. Not used in production. */\nexport function resetDataRoot(): void {\n (globalThis as GlobalSlot)[KEY] = null;\n}\n","/**\n * JSON Adapters for Metadata Stores\n *\n * Provides CRUD operations for five metadata stores backed by JSON files:\n * 1. Strategy profiles — per-block, per-strategy JSON files\n * 2. Sync metadata — per-block .sync-meta.json files\n * 3. Market import metadata — single aggregate file\n * 4. Flat import log — single aggregate file\n * 5. Enrichment watermarks — single aggregate file\n *\n * All adapters use json-store.ts for atomic write-then-rename operations.\n * File paths follow the canonical data-root layout.\n */\n\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport { z } from \"zod\";\nimport {\n readJsonFile,\n writeJsonFile,\n deleteJsonFile,\n listJsonFiles,\n toFileSlug,\n} from \"./json-store.ts\";\nimport { getDataRoot } from \"./data-root.ts\";\nimport type { StrategyProfile } from \"../models/strategy-profile.ts\";\nimport type { BlockSyncMetadata, MarketImportMetadata } from \"../sync/metadata.ts\";\n\n// =============================================================================\n// 1. Profile Adapter\n// Path: {blocksDir}/{blockId}/profiles/{toFileSlug(strategyName)}.json\n// =============================================================================\n\n/** Profile as stored in JSON (dates as ISO strings) */\ninterface ProfileJson extends Omit<StrategyProfile, \"createdAt\" | \"updatedAt\"> {\n createdAt: string;\n updatedAt: string;\n}\n\n/** Convert StrategyProfile to JSON-safe format */\nfunction profileToJson(profile: StrategyProfile): ProfileJson {\n return {\n ...profile,\n createdAt: profile.createdAt.toISOString(),\n updatedAt: profile.updatedAt.toISOString(),\n };\n}\n\n/** Convert JSON-stored profile back to StrategyProfile with Date objects */\nfunction jsonToProfile(json: ProfileJson): StrategyProfile {\n return {\n ...json,\n createdAt: new Date(json.createdAt),\n updatedAt: new Date(json.updatedAt),\n };\n}\n\n/** Resolve file path for a profile */\nfunction profilePath(blockId: string, strategyName: string, blocksDir: string): string {\n return path.join(blocksDir, blockId, \"profiles\", toFileSlug(strategyName) + \".json\");\n}\n\n/**\n * Create or update a profile JSON file.\n * Preserves existing createdAt on update; always sets new updatedAt.\n *\n * @param profile - Profile data (createdAt/updatedAt are managed automatically)\n * @param blocksDir - Root blocks directory\n * @returns The saved StrategyProfile with Date objects\n */\nexport async function upsertProfileJson(\n profile: Omit<StrategyProfile, \"createdAt\" | \"updatedAt\">,\n blocksDir: string,\n): Promise<StrategyProfile> {\n const filePath = profilePath(profile.blockId, profile.strategyName, blocksDir);\n const now = new Date();\n\n // Preserve existing createdAt\n const existing = await readJsonFile<ProfileJson>(filePath);\n const createdAt = existing ? new Date(existing.createdAt) : now;\n\n const full: StrategyProfile = {\n ...profile,\n createdAt,\n updatedAt: now,\n };\n\n await writeJsonFile(filePath, profileToJson(full));\n return full;\n}\n\n/**\n * Get a profile by blockId and strategy name.\n *\n * @returns StrategyProfile with Date objects, or null if not found\n */\nexport async function getProfileJson(\n blockId: string,\n strategyName: string,\n blocksDir: string,\n): Promise<StrategyProfile | null> {\n const filePath = profilePath(blockId, strategyName, blocksDir);\n const json = await readJsonFile<ProfileJson>(filePath);\n return json ? jsonToProfile(json) : null;\n}\n\n/**\n * List profiles for a block, or all profiles across all blocks.\n *\n * @param blocksDir - Root blocks directory\n * @param blockId - Optional block ID. If omitted, scans all block directories.\n * @returns Array of StrategyProfile with Date objects\n */\nexport async function listProfilesJson(\n blocksDir: string,\n blockId?: string,\n): Promise<StrategyProfile[]> {\n if (blockId) {\n const profileDir = path.join(blocksDir, blockId, \"profiles\");\n const files = await listJsonFiles(profileDir);\n const profiles: StrategyProfile[] = [];\n for (const file of files) {\n const json = await readJsonFile<ProfileJson>(file);\n if (json) profiles.push(jsonToProfile(json));\n }\n return profiles;\n }\n\n // Scan all block directories\n const profiles: StrategyProfile[] = [];\n let entries: import(\"fs\").Dirent[];\n try {\n entries = await fs.readdir(blocksDir, { withFileTypes: true });\n } catch {\n return [];\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const blockProfiles = await listProfilesJson(blocksDir, entry.name);\n profiles.push(...blockProfiles);\n }\n return profiles;\n}\n\n/**\n * Delete a profile JSON file.\n *\n * @returns true if deleted, false if not found\n */\nexport async function deleteProfileJson(\n blockId: string,\n strategyName: string,\n blocksDir: string,\n): Promise<boolean> {\n const filePath = profilePath(blockId, strategyName, blocksDir);\n return deleteJsonFile(filePath);\n}\n\n// =============================================================================\n// 2. Sync Metadata Adapter\n// Path: {blocksDir}/{blockId}/.sync-meta.json\n// =============================================================================\n\n/** Sync metadata as stored in JSON (synced_at as ISO string) */\ninterface SyncMetadataJson extends Omit<BlockSyncMetadata, \"synced_at\"> {\n synced_at: string;\n}\n\n/** Convert BlockSyncMetadata to JSON-safe format */\nfunction syncMetaToJson(meta: BlockSyncMetadata): SyncMetadataJson {\n return {\n ...meta,\n synced_at: meta.synced_at.toISOString(),\n };\n}\n\n/** Convert JSON-stored sync metadata back to BlockSyncMetadata with Date */\nfunction jsonToSyncMeta(json: SyncMetadataJson): BlockSyncMetadata {\n return {\n ...json,\n synced_at: new Date(json.synced_at),\n };\n}\n\n/** Resolve file path for sync metadata */\nfunction syncMetaPath(blockId: string, blocksDir: string): string {\n return path.join(blocksDir, blockId, \".sync-meta.json\");\n}\n\n/**\n * Get sync metadata for a block.\n *\n * @returns BlockSyncMetadata with synced_at as Date, or null if not found\n */\nexport async function getSyncMetadataJson(\n blockId: string,\n blocksDir: string,\n): Promise<BlockSyncMetadata | null> {\n const filePath = syncMetaPath(blockId, blocksDir);\n const json = await readJsonFile<SyncMetadataJson>(filePath);\n return json ? jsonToSyncMeta(json) : null;\n}\n\n/**\n * Write sync metadata for a block.\n */\nexport async function upsertSyncMetadataJson(\n metadata: BlockSyncMetadata,\n blocksDir: string,\n): Promise<void> {\n const filePath = syncMetaPath(metadata.block_id, blocksDir);\n await writeJsonFile(filePath, syncMetaToJson(metadata));\n}\n\n/**\n * Delete sync metadata for a block.\n *\n * @returns true if deleted, false if not found\n */\nexport async function deleteSyncMetadataJson(blockId: string, blocksDir: string): Promise<boolean> {\n const filePath = syncMetaPath(blockId, blocksDir);\n return deleteJsonFile(filePath);\n}\n\n/**\n * Scan blocksDir for directories containing .sync-meta.json.\n *\n * @returns Array of block IDs that have sync metadata\n */\nexport async function getAllSyncedBlockIdsJson(blocksDir: string): Promise<string[]> {\n let entries: import(\"fs\").Dirent[];\n try {\n entries = await fs.readdir(blocksDir, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const ids: string[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const metaPath = syncMetaPath(entry.name, blocksDir);\n try {\n await fs.access(metaPath);\n ids.push(entry.name);\n } catch {\n // No .sync-meta.json in this directory\n }\n }\n return ids;\n}\n\n// =============================================================================\n// 3. Market Import Metadata Adapter\n// Path: {dataDir}/market-meta/sync-metadata.json\n// =============================================================================\n\n/** Market import metadata as stored in JSON (synced_at as ISO string) */\ntype MarketImportMetadataJson = Omit<MarketImportMetadata, \"synced_at\"> & { synced_at: string };\n\n/** Aggregate file shape for market import metadata */\ninterface MarketSyncMetadataFile {\n entries: Record<string, MarketImportMetadataJson>;\n}\n\n/** Build composite key for market import metadata */\nfunction marketMetaKey(source: string, ticker: string, targetTable: string): string {\n return `${source}|${ticker}|${targetTable}`;\n}\n\n/** Resolve file path for market import metadata aggregate file */\nfunction marketMetaFilePath(dataDir: string): string {\n return path.join(getDataRoot(dataDir), \"market-meta\", \"sync-metadata.json\");\n}\n\n/**\n * Get market import metadata for a specific source/ticker/table combination.\n *\n * @returns MarketImportMetadata with synced_at as Date, or null if not found\n */\nexport async function getMarketImportMetadataJson(\n source: string,\n ticker: string,\n targetTable: string,\n dataDir: string,\n): Promise<MarketImportMetadata | null> {\n const filePath = marketMetaFilePath(dataDir);\n const file = await readJsonFile<MarketSyncMetadataFile>(filePath);\n if (!file) return null;\n\n const key = marketMetaKey(source, ticker, targetTable);\n const entry = file.entries[key];\n if (!entry) return null;\n\n return {\n ...entry,\n synced_at: new Date(entry.synced_at),\n };\n}\n\n/**\n * Create or update market import metadata entry.\n * Reads the aggregate file, updates the entry, writes back atomically.\n */\nexport async function upsertMarketImportMetadataJson(\n metadata: MarketImportMetadata,\n dataDir: string,\n): Promise<void> {\n const filePath = marketMetaFilePath(dataDir);\n const file = (await readJsonFile<MarketSyncMetadataFile>(filePath)) ?? { entries: {} };\n\n const key = marketMetaKey(metadata.source, metadata.ticker, metadata.target_table);\n file.entries[key] = {\n ...metadata,\n synced_at: metadata.synced_at.toISOString(),\n };\n\n await writeJsonFile(filePath, file);\n}\n\n// =============================================================================\n// 4. Flat Import Log Adapter\n// Path: {dataDir}/market-meta/flat-import-log.json\n// =============================================================================\n\n/** Single entry in the flat import log */\nexport interface FlatImportLogEntry {\n date: string;\n asset_class: string;\n underlying: string;\n imported_at: string;\n bar_count: number;\n}\n\n/** Aggregate file shape for flat import log */\ninterface FlatImportLogFile {\n entries: FlatImportLogEntry[];\n}\n\n/** Build composite key for deduplication */\nfunction flatLogKey(entry: FlatImportLogEntry): string {\n return `${entry.date}|${entry.asset_class}|${entry.underlying}`;\n}\n\n/** Resolve file path for flat import log */\nfunction flatLogFilePath(dataDir: string): string {\n return path.join(getDataRoot(dataDir), \"market-meta\", \"flat-import-log.json\");\n}\n\n/**\n * Get imported dates from the flat import log, filtered by asset_class, underlying, and date range.\n *\n * @returns Set of imported date strings\n */\nexport async function getFlatImportLogJson(\n assetClass: string,\n underlying: string,\n from: string,\n to: string,\n dataDir: string,\n): Promise<Set<string>> {\n const filePath = flatLogFilePath(dataDir);\n const file = await readJsonFile<FlatImportLogFile>(filePath);\n if (!file) return new Set();\n\n const dates = new Set<string>();\n for (const entry of file.entries) {\n if (\n entry.asset_class === assetClass &&\n entry.underlying === underlying &&\n entry.date >= from &&\n entry.date <= to\n ) {\n dates.add(entry.date);\n }\n }\n return dates;\n}\n\n/**\n * Create or update a flat import log entry.\n * Replaces by composite key (date, asset_class, underlying).\n */\nexport async function upsertFlatImportLogJson(\n entry: FlatImportLogEntry,\n dataDir: string,\n): Promise<void> {\n const filePath = flatLogFilePath(dataDir);\n const file = (await readJsonFile<FlatImportLogFile>(filePath)) ?? { entries: [] };\n\n const key = flatLogKey(entry);\n const idx = file.entries.findIndex((e) => flatLogKey(e) === key);\n if (idx >= 0) {\n file.entries[idx] = entry;\n } else {\n file.entries.push(entry);\n }\n\n await writeJsonFile(filePath, file);\n}\n\n// =============================================================================\n// 5. Enrichment Watermarks Adapter\n// Path: {dataRoot}/market-meta/enrichment-watermarks.json\n//\n// Tracks per-ticker `enriched_through` watermark for the market enricher.\n// Replaces the legacy `market._sync_metadata.enriched_through` SQL\n// reads/writes. Backend-independent — the same JSON file is written\n// whether Parquet mode is true or false.\n//\n// Storage shape:\n// {\n// \"version\": 1,\n// \"watermarks\": {\n// \"SPX\": { \"enriched_through\": \"2026-04-15\" },\n// \"VIX\": { \"enriched_through\": \"2026-04-14\" }\n// }\n// }\n//\n// Each entry is an object (not a bare string) so future additions\n// (`wilder_state`, `synced_at`, `last_source`) do not require a schema bump.\n// =============================================================================\n\nconst TickerEntrySchema = z\n .object({\n // ISO calendar date `YYYY-MM-DD` or null when the ticker has been\n // registered but not yet enriched through any date.\n enriched_through: z\n .string()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/)\n .nullable(),\n })\n // Passthrough keeps forward compatibility — fields like `wilder_state` added\n // by future phases survive load/upsert round-trips without schema changes.\n .passthrough();\n\nexport const EnrichmentWatermarksSchema = z.object({\n version: z.literal(1),\n // Defense-in-depth ticker whitelist: mirrors the TICKER_RE used by\n // src/market/tickers/schemas.ts and rejects anything not shaped like a ticker.\n watermarks: z.record(z.string().regex(/^[A-Z0-9._-]+$/), TickerEntrySchema),\n});\n\nexport type EnrichmentWatermarks = z.infer<typeof EnrichmentWatermarksSchema>;\n\n/** Resolve file path for the aggregate enrichment-watermarks file. */\nfunction watermarksFilePath(dataDir: string): string {\n return path.join(getDataRoot(dataDir), \"market-meta\", \"enrichment-watermarks.json\");\n}\n\n/**\n * Load the enrichment watermarks file.\n *\n * Missing file resolves to an empty structure — absent means \"nothing\n * enriched yet\", not an error. Malformed JSON or schema violations throw a\n * clear error; we never silently reset to empty, which would lose data\n * invisibly.\n */\nexport async function loadEnrichmentWatermarks(dataDir: string): Promise<EnrichmentWatermarks> {\n const raw = await readJsonFile<unknown>(watermarksFilePath(dataDir));\n if (raw === null) return { version: 1, watermarks: {} };\n const parsed = EnrichmentWatermarksSchema.safeParse(raw);\n if (!parsed.success) {\n throw new Error(`enrichment-watermarks.json is malformed: ${parsed.error.message}`);\n }\n return parsed.data;\n}\n\n/**\n * Return the `enriched_through` date for a ticker, or null if no entry exists.\n *\n * Callers that treat \"missing entry\" and \"entry with null enriched_through\" the\n * same (most common case) can use the value directly; callers that need to\n * distinguish can read the raw file via `loadEnrichmentWatermarks`.\n */\nexport async function getEnrichedThrough(ticker: string, dataDir: string): Promise<string | null> {\n const { watermarks } = await loadEnrichmentWatermarks(dataDir);\n return watermarks[ticker]?.enriched_through ?? null;\n}\n\n/**\n * Upsert a ticker's `enriched_through` date.\n *\n * Preserves other tickers' entries and any passthrough fields (e.g.,\n * `wilder_state`) already stored on the target ticker.\n *\n * Concurrency note: atomic at the FS level (tmp+rename via writeJsonFile);\n * this is NOT read-modify-write atomic at the application level. Serial\n * callers (the current enricher flow) are safe. If concurrent enrichers of\n * different tickers become real, add an async-mutex.\n */\nexport async function upsertEnrichedThrough(\n ticker: string,\n enrichedThrough: string,\n dataDir: string,\n): Promise<void> {\n const current = await loadEnrichmentWatermarks(dataDir);\n current.watermarks[ticker] = {\n ...(current.watermarks[ticker] ?? {}),\n enriched_through: enrichedThrough,\n };\n await writeJsonFile(watermarksFilePath(dataDir), current);\n}\n","/**\n * JSON Store Utility\n *\n * Provides atomic JSON file operations for metadata storage.\n * Used by json-adapters.ts (and consumer-side adapters) to persist\n * strategy profiles, sync metadata, market import metadata,\n * flat import logs, and strategy definitions as JSON files.\n *\n * Write atomicity: all writes use write-then-rename pattern.\n * Write to {path}.tmp, then rename() to final path. This prevents\n * partial writes from being picked up by Syncthing or concurrent readers.\n *\n * Exports:\n * - readJsonFile<T>() - Read and parse a JSON file (null if ENOENT)\n * - writeJsonFile<T>() - Atomic write with 2-space indent + trailing newline\n * - deleteJsonFile() - Delete a JSON file (false if ENOENT)\n * - listJsonFiles() - List .json files in a directory (empty array if ENOENT)\n * - toFileSlug() - Convert a human name to a filesystem-safe slug\n */\n\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\n\n/** Check for Node.js ENOENT errors */\nfunction isEnoent(err: unknown): boolean {\n return (\n typeof err === \"object\" && err !== null && (err as Record<string, unknown>).code === \"ENOENT\"\n );\n}\n\n/**\n * Atomically write a JSON file using write-then-rename.\n * Creates parent directories if they don't exist.\n *\n * @param filePath - Final file path\n * @param data - Object to serialize (2-space indent, trailing newline)\n */\nexport async function writeJsonFile<T>(filePath: string, data: T): Promise<void> {\n const tmpPath = filePath + \".tmp\";\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(tmpPath, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n await fs.rename(tmpPath, filePath);\n}\n\n/**\n * Read and parse a JSON file.\n * Returns null if file doesn't exist (ENOENT).\n * Throws on parse errors or permission errors.\n *\n * @param filePath - Path to JSON file\n * @returns Parsed object or null if file not found\n */\nexport async function readJsonFile<T>(filePath: string): Promise<T | null> {\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n return JSON.parse(content) as T;\n } catch (err: unknown) {\n if (isEnoent(err)) {\n return null;\n }\n throw err;\n }\n}\n\n/**\n * Delete a JSON file.\n * Returns true if file existed and was deleted.\n * Returns false if file did not exist (ENOENT, no throw).\n *\n * @param filePath - Path to JSON file\n * @returns true if deleted, false if not found\n */\nexport async function deleteJsonFile(filePath: string): Promise<boolean> {\n try {\n await fs.unlink(filePath);\n return true;\n } catch (err: unknown) {\n if (isEnoent(err)) {\n return false;\n }\n throw err;\n }\n}\n\n/**\n * List JSON files in a directory.\n * Returns array of full file paths for files ending in the given suffix.\n * Returns empty array if directory does not exist (ENOENT, no throw).\n * Filters to only files (ignores subdirectories and non-matching files).\n *\n * @param dirPath - Directory to scan\n * @param suffix - File extension to filter by (default: \".json\")\n * @returns Sorted array of full file paths\n */\nexport async function listJsonFiles(dirPath: string, suffix = \".json\"): Promise<string[]> {\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n return entries\n .filter((e) => e.isFile() && e.name.endsWith(suffix))\n .map((e) => path.join(dirPath, e.name))\n .sort();\n } catch (err: unknown) {\n if (isEnoent(err)) {\n return [];\n }\n throw err;\n }\n}\n\n/**\n * Convert a human-readable name to a filesystem-safe slug.\n * Lowercases, replaces non-alphanumeric sequences with hyphens,\n * and strips leading/trailing hyphens.\n *\n * @param name - Human-readable name (e.g., \"Pickle RIC v2\")\n * @returns Slug string (e.g., \"pickle-ric-v2\")\n */\nexport function toFileSlug(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n","/**\n * Strategy Profile Schema Definitions\n *\n * Creates and manages the `profiles` schema and `profiles.strategy_profiles` table\n * in the analytics DuckDB database. Provides CRUD utilities for strategy profile storage.\n *\n * Schema: profiles\n * Table: profiles.strategy_profiles\n * Primary Key: (block_id, strategy_name) — composite key allows multiple strategies per block\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport type { StrategyProfile } from \"../models/strategy-profile.ts\";\nimport { isParquetMode } from \"./parquet-writer.ts\";\nimport {\n upsertProfileJson,\n getProfileJson,\n listProfilesJson,\n deleteProfileJson,\n} from \"./json-adapters.ts\";\nimport { getBlocksDir } from \"../sync/index.ts\";\n\n/**\n * Ensure the profiles schema and strategy_profiles table exist.\n * Safe to call multiple times (CREATE IF NOT EXISTS semantics).\n *\n * @param conn - Active DuckDB connection\n */\nexport async function ensureProfilesSchema(conn: DuckDBConnection): Promise<void> {\n await conn.run(\"CREATE SCHEMA IF NOT EXISTS profiles\");\n await conn.run(`\n CREATE TABLE IF NOT EXISTS profiles.strategy_profiles (\n block_id VARCHAR NOT NULL,\n strategy_name VARCHAR NOT NULL,\n structure_type VARCHAR NOT NULL,\n greeks_bias VARCHAR NOT NULL,\n thesis TEXT NOT NULL DEFAULT '',\n legs JSON,\n entry_filters JSON,\n exit_rules JSON,\n expected_regimes JSON,\n key_metrics JSON,\n position_sizing JSON,\n created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,\n updated_at TIMESTAMP NOT NULL DEFAULT current_timestamp,\n PRIMARY KEY (block_id, strategy_name)\n )\n `);\n // Migration: add position_sizing column if table existed before it was added\n await conn.run(`\n ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS position_sizing JSON\n `);\n // Migration: add schema v2 top-level columns (nullable for backward compat)\n await conn.run(\n `ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS underlying VARCHAR`,\n );\n await conn.run(\n `ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS re_entry BOOLEAN`,\n );\n await conn.run(\n `ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS cap_profits BOOLEAN`,\n );\n await conn.run(\n `ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS cap_losses BOOLEAN`,\n );\n await conn.run(\n `ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS require_two_prices_pt BOOLEAN`,\n );\n await conn.run(\n `ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS close_on_completion BOOLEAN`,\n );\n await conn.run(\n `ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS ignore_margin_req BOOLEAN`,\n );\n\n // Migration: add strategy execution param columns.\n // block_id stays NOT NULL in the PRIMARY KEY. Template profiles\n // (definitions without a live block) use block_id = '_template' sentinel value.\n // DuckDB does not support ALTER COLUMN ... DROP NOT NULL.\n const strategyCols: Array<{ name: string; type: string }> = [\n { name: \"slippage_entry\", type: \"DOUBLE\" },\n { name: \"slippage_exit\", type: \"DOUBLE\" },\n { name: \"slippage_stop_exit\", type: \"DOUBLE\" },\n { name: \"opening_commission\", type: \"DOUBLE\" },\n { name: \"closing_commission\", type: \"DOUBLE\" },\n { name: \"starting_capital\", type: \"DOUBLE\" },\n { name: \"margin_per_spread\", type: \"DOUBLE\" },\n { name: \"entry_frequency\", type: \"VARCHAR\" },\n { name: \"default_from_date\", type: \"VARCHAR\" },\n { name: \"default_to_date\", type: \"VARCHAR\" },\n ];\n for (const col of strategyCols) {\n await conn.run(\n `ALTER TABLE profiles.strategy_profiles ADD COLUMN IF NOT EXISTS ${col.name} ${col.type}`,\n );\n }\n\n // Migration: normalize expected_regimes to canonical Vol_Regime labels\n // Old values: \"low_vol\", \"moderate_vol\", \"high_vol\", \"normal\", \"low\" (free-text)\n // Canonical: \"very_low\", \"low\", \"below_avg\", \"above_avg\", \"high\", \"extreme\"\n await migrateExpectedRegimes(conn);\n}\n\n/**\n * One-shot migration: rewrite expected_regimes JSON arrays from free-text\n * vocabulary to canonical Vol_Regime labels.\n *\n * Mapping:\n * low_vol → [\"very_low\", \"low\"]\n * moderate_vol → [\"below_avg\", \"above_avg\"]\n * normal → [\"below_avg\", \"above_avg\"]\n * high_vol → [\"high\", \"extreme\"]\n *\n * Values already in canonical form are kept as-is. Duplicates are removed.\n * Runs every connection open but only UPDATEs rows containing non-canonical values.\n */\nasync function migrateExpectedRegimes(conn: DuckDBConnection): Promise<void> {\n const CANONICAL = new Set([\"very_low\", \"low\", \"below_avg\", \"above_avg\", \"high\", \"extreme\"]);\n const MAPPING: Record<string, string[]> = {\n low_vol: [\"very_low\", \"low\"],\n moderate_vol: [\"below_avg\", \"above_avg\"],\n normal: [\"below_avg\", \"above_avg\"],\n high_vol: [\"high\", \"extreme\"],\n };\n\n // Read all profiles with non-empty expected_regimes\n const result = await conn.runAndReadAll(\n `SELECT block_id, strategy_name, expected_regimes\n FROM profiles.strategy_profiles\n WHERE expected_regimes IS NOT NULL AND expected_regimes != '[]'`,\n );\n\n for (const row of result.getRows()) {\n const blockId = String(row[0]);\n const strategyName = String(row[1]);\n const rawJson = String(row[2]);\n\n let regimes: string[];\n try {\n regimes = JSON.parse(rawJson);\n } catch {\n continue;\n }\n\n // Check if any value needs mapping\n const needsMigration = regimes.some((r) => !CANONICAL.has(r));\n if (!needsMigration) continue;\n\n // Expand free-text values to canonical labels\n const expanded = new Set<string>();\n for (const r of regimes) {\n if (CANONICAL.has(r)) {\n expanded.add(r);\n } else if (MAPPING[r]) {\n for (const mapped of MAPPING[r]) expanded.add(mapped);\n }\n // Unknown values are dropped silently\n }\n\n const newJson = JSON.stringify([...expanded].sort());\n await conn.run(\n `UPDATE profiles.strategy_profiles\n SET expected_regimes = '${escSql(newJson)}', updated_at = current_timestamp\n WHERE block_id = '${escSql(blockId)}' AND strategy_name = '${escSql(strategyName)}'`,\n );\n }\n}\n\n/**\n * Escape a single-quoted string for safe inclusion in DuckDB SQL.\n * Doubles any embedded single quotes.\n */\nfunction escSql(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\n/**\n * Convert a DuckDB row value to a JS Date.\n * DuckDB timestamps may come back as numbers (microseconds since epoch) or Date objects.\n */\nfunction toDate(value: unknown): Date {\n if (value instanceof Date) return value;\n if (typeof value === \"bigint\") {\n // DuckDB timestamps are microseconds since Unix epoch\n return new Date(Number(value) / 1000);\n }\n if (typeof value === \"number\") {\n // May be microseconds or milliseconds depending on DuckDB version\n // Values larger than ~9e12 are likely microseconds\n return value > 9e12 ? new Date(value / 1000) : new Date(value);\n }\n if (typeof value === \"string\") {\n return new Date(value);\n }\n return new Date();\n}\n\n/**\n * Map a DuckDB row array to a StrategyProfile object.\n * Column order must match the SELECT in query functions.\n *\n * Column order: block_id, strategy_name, structure_type, greeks_bias, thesis,\n * legs, entry_filters, exit_rules, expected_regimes, key_metrics,\n * position_sizing, underlying, re_entry, cap_profits, cap_losses,\n * require_two_prices_pt, close_on_completion, ignore_margin_req,\n * created_at, updated_at\n */\nfunction rowToProfile(row: unknown[]): StrategyProfile {\n const parseJson = (v: unknown) => {\n if (v === null || v === undefined) return [];\n if (typeof v === \"string\") return JSON.parse(v);\n return v; // DuckDB may auto-parse JSON columns\n };\n\n const parseJsonObj = (v: unknown) => {\n if (v === null || v === undefined) return {};\n if (typeof v === \"string\") return JSON.parse(v);\n return v;\n };\n\n const toBoolOrUndef = (v: unknown): boolean | undefined =>\n v === null || v === undefined ? undefined : Boolean(v);\n\n return {\n blockId: row[0] as string,\n strategyName: row[1] as string,\n structureType: row[2] as string,\n greeksBias: row[3] as string,\n thesis: row[4] as string,\n legs: parseJson(row[5]),\n entryFilters: parseJson(row[6]),\n exitRules: parseJson(row[7]),\n expectedRegimes: parseJson(row[8]),\n keyMetrics: parseJsonObj(row[9]),\n positionSizing: (() => {\n const ps = parseJsonObj(row[10]);\n return ps && Object.keys(ps).length > 0 ? ps : undefined;\n })(),\n underlying: (row[11] as string | undefined) ?? undefined,\n reEntry: toBoolOrUndef(row[12]),\n capProfits: toBoolOrUndef(row[13]),\n capLosses: toBoolOrUndef(row[14]),\n requireTwoPricesPT: toBoolOrUndef(row[15]),\n closeOnCompletion: toBoolOrUndef(row[16]),\n ignoreMarginReq: toBoolOrUndef(row[17]),\n createdAt: toDate(row[18]),\n updatedAt: toDate(row[19]),\n };\n}\n\nconst SELECT_COLUMNS = `\n block_id, strategy_name, structure_type, greeks_bias, thesis,\n legs, entry_filters, exit_rules, expected_regimes, key_metrics,\n position_sizing, underlying, re_entry, cap_profits, cap_losses,\n require_two_prices_pt, close_on_completion, ignore_margin_req,\n created_at, updated_at\n`.trim();\n\n/**\n * Upsert a strategy profile.\n * If a profile with the same (block_id, strategy_name) exists, it is overwritten.\n * created_at is preserved on update; updated_at is set to the current timestamp.\n *\n * @param conn - Active DuckDB connection\n * @param profile - Profile to insert or update (createdAt/updatedAt are managed by DB)\n * @returns The stored profile with DB-assigned timestamps\n */\nexport async function upsertProfile(\n conn: DuckDBConnection,\n profile: Omit<StrategyProfile, \"createdAt\" | \"updatedAt\">,\n baseDir?: string,\n): Promise<StrategyProfile> {\n if (isParquetMode() && baseDir) {\n return upsertProfileJson(profile, getBlocksDir(baseDir));\n }\n const legsJson = escSql(JSON.stringify(profile.legs));\n const entryFiltersJson = escSql(JSON.stringify(profile.entryFilters));\n const exitRulesJson = escSql(JSON.stringify(profile.exitRules));\n const expectedRegimesJson = escSql(JSON.stringify(profile.expectedRegimes));\n const keyMetricsJson = escSql(JSON.stringify(profile.keyMetrics));\n const positionSizingJson = profile.positionSizing\n ? escSql(JSON.stringify(profile.positionSizing))\n : null;\n\n const nowTs = new Date().toISOString().replace(\"T\", \" \").replace(\"Z\", \"\");\n\n const underlyingSql = profile.underlying ? `'${escSql(profile.underlying)}'` : \"NULL\";\n const reEntrySql = profile.reEntry === undefined ? \"NULL\" : String(profile.reEntry);\n const capProfitsSql = profile.capProfits === undefined ? \"NULL\" : String(profile.capProfits);\n const capLossesSql = profile.capLosses === undefined ? \"NULL\" : String(profile.capLosses);\n const requireTwoPricesPTSql =\n profile.requireTwoPricesPT === undefined ? \"NULL\" : String(profile.requireTwoPricesPT);\n const closeOnCompletionSql =\n profile.closeOnCompletion === undefined ? \"NULL\" : String(profile.closeOnCompletion);\n const ignoreMarginReqSql =\n profile.ignoreMarginReq === undefined ? \"NULL\" : String(profile.ignoreMarginReq);\n\n await conn.run(`\n INSERT INTO profiles.strategy_profiles\n (block_id, strategy_name, structure_type, greeks_bias, thesis,\n legs, entry_filters, exit_rules, expected_regimes, key_metrics,\n position_sizing, underlying, re_entry, cap_profits, cap_losses,\n require_two_prices_pt, close_on_completion, ignore_margin_req,\n created_at, updated_at)\n VALUES (\n '${escSql(profile.blockId)}',\n '${escSql(profile.strategyName)}',\n '${escSql(profile.structureType)}',\n '${escSql(profile.greeksBias)}',\n '${escSql(profile.thesis)}',\n '${legsJson}'::JSON,\n '${entryFiltersJson}'::JSON,\n '${exitRulesJson}'::JSON,\n '${expectedRegimesJson}'::JSON,\n '${keyMetricsJson}'::JSON,\n ${positionSizingJson ? `'${positionSizingJson}'::JSON` : \"NULL\"},\n ${underlyingSql},\n ${reEntrySql},\n ${capProfitsSql},\n ${capLossesSql},\n ${requireTwoPricesPTSql},\n ${closeOnCompletionSql},\n ${ignoreMarginReqSql},\n TIMESTAMPTZ '${nowTs}',\n TIMESTAMPTZ '${nowTs}'\n )\n ON CONFLICT (block_id, strategy_name) DO UPDATE SET\n structure_type = excluded.structure_type,\n greeks_bias = excluded.greeks_bias,\n thesis = excluded.thesis,\n legs = excluded.legs,\n entry_filters = excluded.entry_filters,\n exit_rules = excluded.exit_rules,\n expected_regimes = excluded.expected_regimes,\n key_metrics = excluded.key_metrics,\n position_sizing = excluded.position_sizing,\n underlying = excluded.underlying,\n re_entry = excluded.re_entry,\n cap_profits = excluded.cap_profits,\n cap_losses = excluded.cap_losses,\n require_two_prices_pt = excluded.require_two_prices_pt,\n close_on_completion = excluded.close_on_completion,\n ignore_margin_req = excluded.ignore_margin_req,\n updated_at = TIMESTAMPTZ '${nowTs}'\n `);\n\n const stored = await getProfile(conn, profile.blockId, profile.strategyName, baseDir);\n if (!stored) {\n throw new Error(\n `Failed to retrieve profile after upsert: ${profile.blockId}/${profile.strategyName}`,\n );\n }\n return stored;\n}\n\n/**\n * Retrieve a single strategy profile by block_id and strategy_name.\n *\n * @param conn - Active DuckDB connection\n * @param blockId - Block identifier\n * @param strategyName - Strategy name\n * @returns The profile, or null if not found\n */\nexport async function getProfile(\n conn: DuckDBConnection,\n blockId: string,\n strategyName: string,\n baseDir?: string,\n): Promise<StrategyProfile | null> {\n if (isParquetMode() && baseDir) {\n return getProfileJson(blockId, strategyName, getBlocksDir(baseDir));\n }\n const result = await conn.runAndReadAll(`\n SELECT ${SELECT_COLUMNS}\n FROM profiles.strategy_profiles\n WHERE block_id = '${escSql(blockId)}'\n AND strategy_name = '${escSql(strategyName)}'\n `);\n const rows = result.getRows();\n if (rows.length === 0) return null;\n return rowToProfile(rows[0]);\n}\n\n/**\n * List strategy profiles.\n *\n * @param conn - Active DuckDB connection\n * @param blockId - Optional filter; if provided, only profiles for that block are returned\n * @returns Array of matching profiles\n */\nexport async function listProfiles(\n conn: DuckDBConnection,\n blockId?: string,\n baseDir?: string,\n): Promise<StrategyProfile[]> {\n if (isParquetMode() && baseDir) {\n return listProfilesJson(getBlocksDir(baseDir), blockId);\n }\n const whereClause = blockId ? `WHERE block_id = '${escSql(blockId)}'` : \"\";\n\n const result = await conn.runAndReadAll(`\n SELECT ${SELECT_COLUMNS}\n FROM profiles.strategy_profiles\n ${whereClause}\n ORDER BY block_id, strategy_name\n `);\n\n return result.getRows().map(rowToProfile);\n}\n\n/**\n * Delete a strategy profile by block_id and strategy_name.\n *\n * @param conn - Active DuckDB connection\n * @param blockId - Block identifier\n * @param strategyName - Strategy name\n * @returns true if a row was deleted, false if no matching row existed\n */\nexport async function deleteProfile(\n conn: DuckDBConnection,\n blockId: string,\n strategyName: string,\n baseDir?: string,\n): Promise<boolean> {\n if (isParquetMode() && baseDir) {\n return deleteProfileJson(blockId, strategyName, getBlocksDir(baseDir));\n }\n // Check existence before delete so we can return accurate boolean\n const existing = await getProfile(conn, blockId, strategyName);\n if (!existing) return false;\n\n await conn.run(`\n DELETE FROM profiles.strategy_profiles\n WHERE block_id = '${escSql(blockId)}'\n AND strategy_name = '${escSql(strategyName)}'\n `);\n\n return true;\n}\n","/**\n * Market Parquet View Layer\n *\n * Creates DuckDB views over shared Parquet files in the market/ directory.\n * When Parquet files exist, views replace physical tables for reads —\n * DuckDB becomes a query engine over immutable Parquet rather than a data store.\n *\n * Falls back gracefully: missing files are tracked in `tablesKept` so the caller\n * can create physical tables for those datasets instead.\n *\n * Parquet directory layout (v3.0 — produced by import pipelines when TRADEBLOCKS_PARQUET=true):\n * {dataDir}/market/\n * spot/ticker=X/date=Y/data.parquet (ticker-first Hive-partitioned minute bars)\n * enriched/ticker=X/data.parquet (per-ticker daily indicator Parquet)\n * enriched/context/data.parquet (cross-ticker derived-fields Parquet)\n * option_chain/date=YYYY-MM-DD/data.parquet (Hive-partitioned)\n * option_quote_minutes/date=YYYY-MM-DD/data.parquet (Hive-partitioned)\n *\n * View surface registered over these files: market.spot, market.spot_daily (RTH aggregation),\n * market.enriched, market.enriched_context, market.option_chain, market.option_quote_minutes.\n *\n * The legacy view-registration blocks for the retired daily / date_context /\n * intraday names have been removed; reads against those names now fail with\n * a DuckDB Binder/Catalog error by design.\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport { existsSync, readdirSync } from \"fs\";\nimport * as path from \"path\";\nimport { resolveCanonicalMarketPartitionDir, resolveMarketDir } from \"./market-datasets.ts\";\nimport {\n describeReadParquetColumns,\n quoteParquetCanonicalProjection,\n readParquetGlobSql,\n} from \"../utils/quote-parquet-projection.ts\";\n\nexport interface ViewCreationResult {\n viewsCreated: string[];\n tablesKept: string[];\n parquetActive: boolean;\n}\n\n/**\n * Check if a Hive-partitioned directory has at least one top-level partition\n * containing a Parquet file.\n *\n * The `partitionKey` parameter defaults to \"date\" so existing callers (intraday,\n * option_chain, option_quote_minutes — all date-only) continue working unchanged.\n * Pass \"ticker\" for ticker-first partitioning (the 3.0 spot directory).\n */\nfunction hasParquetPartitions(dir: string, partitionKey: string = \"date\"): boolean {\n if (!existsSync(dir)) return false;\n try {\n const prefix = `${partitionKey}=`;\n return readdirSync(dir).some((entry) => {\n if (!entry.startsWith(prefix)) return false;\n const partDir = path.join(dir, entry);\n // Accept both data.parquet (manual/drain-queue) and data_0.parquet (COPY TO PARTITION_BY).\n // Also recurse one level for ticker-first layouts where partDir = ticker=X containing\n // date=Y subdirectories; in that case look for any .parquet file in the nested tree.\n try {\n const entries = readdirSync(partDir);\n for (const sub of entries) {\n if (sub.endsWith(\".parquet\")) return true;\n const nested = path.join(partDir, sub);\n try {\n if (readdirSync(nested).some((f) => f.endsWith(\".parquet\"))) return true;\n } catch {\n /* not a directory */\n }\n }\n return false;\n } catch {\n return false;\n }\n });\n } catch {\n return false;\n }\n}\n\n/**\n * True if `<dir>/ticker=<X>/data.parquet` exists for at least one ticker.\n * Used by the enriched view (no date partition under ticker — single file per ticker).\n */\nfunction hasEnrichedTickerFiles(dir: string): boolean {\n if (!existsSync(dir)) return false;\n try {\n return readdirSync(dir).some((entry) => {\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/** True if `<dir>/context/data.parquet` exists (single global file). */\nfunction hasEnrichedContextFile(dir: string): boolean {\n return existsSync(path.join(dir, \"context\", \"data.parquet\"));\n}\n\n/**\n * Create DuckDB views over canonical market Parquet files.\n *\n * For each v3.0 market dataset (spot, enriched, enriched_context, option_chain,\n * option_quote_minutes, spot_daily):\n * - If the Parquet file/directory exists: DROP any existing physical TABLE, then CREATE VIEW\n * - If missing: record in tablesKept (caller should create physical table as fallback)\n *\n * The legacy single-file (daily, date_context) and Hive-partitioned\n * (intraday) view registrations have been retired; reads against those\n * names now fail with a DuckDB Binder/Catalog error by design.\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param dataDir - Base data directory (parent of market/ subdirectory)\n * @returns ViewCreationResult with lists of views created vs tables kept\n */\nexport async function createMarketParquetViews(\n conn: DuckDBConnection,\n dataDir: string,\n): Promise<ViewCreationResult> {\n const viewsCreated: string[] = [];\n const tablesKept: string[] = [];\n\n // --- Hive-partitioned views ---\n\n // `option_chain` accepts either the legacy date-only layout\n // (`option_chain/date=Y/...`) or the canonical underlying-first layout\n // (`option_chain/underlying=X/date=Y/...`) — the current store writers\n // produce the underlying-first layout.\n const hiveViews: Array<{ name: string; subdir: string; partitionKey: string }> = [\n {\n name: \"option_chain\",\n subdir: resolveCanonicalMarketPartitionDir(dataDir, \"option_chain\"),\n partitionKey: \"underlying\",\n },\n ];\n\n for (const { name, subdir, partitionKey } of hiveViews) {\n const dirPath = subdir;\n // Accept either the dataset's primary partition key (e.g. `underlying` for\n // Market Data 3.0 option_chain) or the legacy `date` top-level partition.\n const hasNewLayout = existsSync(dirPath) && hasParquetPartitions(dirPath, partitionKey);\n const hasLegacyLayout =\n partitionKey !== \"date\" && existsSync(dirPath) && hasParquetPartitions(dirPath, \"date\");\n if (hasNewLayout || hasLegacyLayout) {\n try {\n await conn.run(`DROP VIEW IF EXISTS market.${name}`);\n } catch {\n /* wrong type */\n }\n try {\n await conn.run(`DROP TABLE IF EXISTS market.${name}`);\n } catch {\n /* wrong type */\n }\n // Glob on data.parquet (not *.parquet) so DuckDB's in-flight\n // tmp_data.parquet files — created by concurrent COPY ... TO writers\n // during atomic parquet replacement — never match. Matching a mid-write\n // temp file causes \"No magic bytes\" in DESCRIBE / read_parquet and the\n // error gets mis-reported upstream as analytics.duckdb corruption.\n await conn.run(\n `CREATE OR REPLACE VIEW market.${name} AS SELECT * FROM read_parquet('${dirPath}/**/data.parquet', hive_partitioning=true)`,\n );\n viewsCreated.push(name);\n } else {\n try {\n await conn.run(`DROP VIEW IF EXISTS market.${name}`);\n } catch {\n /* not a view */\n }\n tablesKept.push(name);\n }\n }\n\n // --- Option quote minutes view ---\n //\n // Accepts both the legacy date-only layout (`date=Y/...`) and the canonical\n // underlying-first layout (`underlying=X/date=Y/...`) produced by the\n // current Parquet writers. The view projects the canonical quote schema\n // explicitly so older partitions without greeks columns still read with\n // null greeks instead of failing at bind time.\n\n const optionMinuteQuoteDir = resolveCanonicalMarketPartitionDir(dataDir, \"option_quote_minutes\");\n const optionQuoteHasNewLayout = hasParquetPartitions(optionMinuteQuoteDir, \"underlying\");\n const optionQuoteHasLegacyLayout = hasParquetPartitions(optionMinuteQuoteDir, \"date\");\n if (optionQuoteHasNewLayout || optionQuoteHasLegacyLayout) {\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.option_quote_minutes\");\n } catch {\n /* wrong type */\n }\n try {\n await conn.run(\"DROP TABLE IF EXISTS market.option_quote_minutes\");\n } catch {\n /* wrong type */\n }\n // See hive-views glob comment: scope to data.parquet so concurrent\n // COPY ... TO writers' tmp_data.parquet never trips DESCRIBE.\n const quoteSource = readParquetGlobSql(`${optionMinuteQuoteDir}/**/data.parquet`);\n const quoteColumns = await describeReadParquetColumns(conn, quoteSource);\n const quoteProjection = quoteParquetCanonicalProjection(quoteColumns, \"q\");\n await conn.run(\n `CREATE OR REPLACE VIEW market.option_quote_minutes AS\n SELECT ${quoteProjection}\n FROM ${quoteSource} AS q`,\n );\n viewsCreated.push(\"option_quote_minutes\");\n } else {\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.option_quote_minutes\");\n } catch {\n /* not a view */\n }\n tablesKept.push(\"option_quote_minutes\");\n }\n\n // Remove the retired greeks dataset from the market schema if it exists from\n // an older run. This keeps the public SQL surface aligned with the current\n // architecture where quote greeks live inline on option_quote_minutes and\n // missing values are computed in memory at query time.\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.option_greeks_minutes\");\n } catch {\n /* wrong type */\n }\n try {\n await conn.run(\"DROP TABLE IF EXISTS market.option_greeks_minutes\");\n } catch {\n /* wrong type */\n }\n\n // Remove the retired delta-index surface from the market schema if it exists\n // from an older run. Delta selection now reads directly from\n // market.option_quote_minutes greeks instead of a second persisted dataset.\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.option_delta_index\");\n } catch {\n /* wrong type */\n }\n try {\n await conn.run(\"DROP TABLE IF EXISTS market.option_delta_index\");\n } catch {\n /* wrong type */\n }\n\n // ============================================================================\n // Canonical store views (spot, enriched, enriched_context, spot_daily).\n // These coexist with legacy view registrations elsewhere while consumer\n // migration is ongoing.\n // ============================================================================\n\n // market.spot — ticker-first Hive partitioning: spot/ticker=X/date=Y/data.parquet\n const spotDir = path.join(resolveMarketDir(dataDir), \"spot\");\n if (hasParquetPartitions(spotDir, \"ticker\")) {\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.spot\");\n } catch {\n /* wrong type */\n }\n try {\n await conn.run(\"DROP TABLE IF EXISTS market.spot\");\n } catch {\n /* wrong type */\n }\n // Scope glob to data.parquet (see tmp_data.parquet race note above).\n await conn.run(\n `CREATE OR REPLACE VIEW market.spot AS\n SELECT * FROM read_parquet('${spotDir}/**/data.parquet', hive_partitioning=true)`,\n );\n viewsCreated.push(\"spot\");\n } else {\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.spot\");\n } catch {\n /* not a view */\n }\n tablesKept.push(\"spot\");\n }\n\n // market.enriched — per-ticker single file: enriched/ticker=X/data.parquet (no date partition)\n const enrichedDir = path.join(resolveMarketDir(dataDir), \"enriched\");\n if (hasEnrichedTickerFiles(enrichedDir)) {\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.enriched\");\n } catch {\n /* wrong type */\n }\n try {\n await conn.run(\"DROP TABLE IF EXISTS market.enriched\");\n } catch {\n /* wrong type */\n }\n await conn.run(\n `CREATE OR REPLACE VIEW market.enriched AS\n SELECT * FROM read_parquet('${enrichedDir}/ticker=*/data.parquet', hive_partitioning=true)`,\n );\n viewsCreated.push(\"enriched\");\n } else {\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.enriched\");\n } catch {\n /* not a view */\n }\n tablesKept.push(\"enriched\");\n }\n\n // market.enriched_context — global single file: enriched/context/data.parquet (no partition)\n if (hasEnrichedContextFile(enrichedDir)) {\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.enriched_context\");\n } catch {\n /* wrong type */\n }\n try {\n await conn.run(\"DROP TABLE IF EXISTS market.enriched_context\");\n } catch {\n /* wrong type */\n }\n await conn.run(\n `CREATE OR REPLACE VIEW market.enriched_context AS\n SELECT * FROM read_parquet('${path.join(enrichedDir, \"context\", \"data.parquet\")}')`,\n );\n viewsCreated.push(\"enriched_context\");\n } else {\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.enriched_context\");\n } catch {\n /* not a view */\n }\n tablesKept.push(\"enriched_context\");\n }\n\n // market.spot_daily — view over market.spot with RTH aggregation. Bridge\n // for SQL callers that need daily OHLCV after the legacy-daily-view\n // retirement. Semantics match SpotStore.readDailyBars: first(open),\n // max(high), min(low), last(close), first(bid), last(ask),\n // RTH 09:30–16:00, GROUP BY ticker+date.\n //\n // CREATE VIEW binds the underlying reference immediately, so if\n // market.spot exists in NEITHER form (empty dir AND no pre-existing\n // fallback table) we skip the registration and push to tablesKept. In\n // production, connection.ts calls ensureMarketDataTables() which creates\n // a market.spot fallback table when the view is absent, so this skip\n // branch applies only to unit-test fixtures that deliberately avoid\n // BOTH paths.\n const spotExists = await (async () => {\n try {\n await conn.run(\"SELECT * FROM market.spot WHERE 1=0\");\n return true;\n } catch {\n return false;\n }\n })();\n if (spotExists) {\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.spot_daily\");\n } catch {\n /* wrong type */\n }\n try {\n await conn.run(\"DROP TABLE IF EXISTS market.spot_daily\");\n } catch {\n /* wrong type */\n }\n await conn.run(`\n CREATE OR REPLACE VIEW market.spot_daily AS\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 first(bid ORDER BY time) AS bid,\n last(ask ORDER BY time) AS ask\n FROM market.spot\n WHERE time >= '09:30' AND time <= '16:00'\n -- Defense-in-depth: drop any minute bar with a zero/null OHLC value\n -- before aggregation. These come from provider outages, partial\n -- sessions, or weekend rows that slipped past the writer guard;\n -- without this filter min(low) collapses to 0 and propagates into\n -- every downstream indicator (Intraday_Range_Pct, ATR, etc.).\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 viewsCreated.push(\"spot_daily\");\n } else {\n try {\n await conn.run(\"DROP VIEW IF EXISTS market.spot_daily\");\n } catch {\n /* not a view */\n }\n tablesKept.push(\"spot_daily\");\n }\n\n return {\n viewsCreated,\n tablesKept,\n parquetActive: viewsCreated.length > 0,\n };\n}\n","import type { DuckDBConnection } from \"@duckdb/node-api\";\n\nexport type ParquetColumnSet = Set<string>;\n\nconst describeCache = new Map<string, Promise<ParquetColumnSet>>();\n\nexport function escapeSqlLiteral(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nexport function readParquetGlobSql(glob: string): string {\n return `read_parquet('${escapeSqlLiteral(glob)}', hive_partitioning=true, union_by_name=true)`;\n}\n\nexport function readParquetFilesSql(files: string[]): string {\n if (files.length === 0) {\n throw new Error(\"readParquetFilesSql: files must not be empty\");\n }\n const fileList = files.map((filePath) => `'${escapeSqlLiteral(filePath)}'`).join(\", \");\n return `read_parquet([${fileList}], hive_partitioning=true, union_by_name=true)`;\n}\n\nexport async function describeReadParquetColumns(\n conn: DuckDBConnection,\n readParquetSql: string,\n): Promise<ParquetColumnSet> {\n let pending = describeCache.get(readParquetSql);\n if (!pending) {\n pending = (async () => {\n const reader = await conn.runAndReadAll(`DESCRIBE SELECT * FROM ${readParquetSql}`);\n return new Set(reader.getRows().map((row) => String(row[0]).toLowerCase()));\n })();\n describeCache.set(readParquetSql, pending);\n }\n return pending;\n}\n\nexport async function describeQueryColumns(\n conn: DuckDBConnection,\n querySql: string,\n): Promise<ParquetColumnSet> {\n const reader = await conn.runAndReadAll(`DESCRIBE ${querySql}`);\n return new Set(reader.getRows().map((row) => String(row[0]).toLowerCase()));\n}\n\nfunction hasColumn(columns: ParquetColumnSet, name: string): boolean {\n return columns.has(name.toLowerCase());\n}\n\nexport function quoteParquetColumnExpr(\n columns: ParquetColumnSet,\n alias: string,\n name: string,\n fallbackType: string,\n): string {\n return hasColumn(columns, name) ? `${alias}.${name}` : `NULL::${fallbackType}`;\n}\n\nexport function quoteParquetMidExpr(columns: ParquetColumnSet, alias: string): string {\n return hasColumn(columns, \"mid\")\n ? `${alias}.mid`\n : `((CAST(${alias}.bid AS DOUBLE) + CAST(${alias}.ask AS DOUBLE)) / 2)`;\n}\n\nexport type GreekColumn = \"delta\" | \"gamma\" | \"theta\" | \"vega\" | \"iv\";\n\nconst ALL_GREEKS: readonly GreekColumn[] = [\"delta\", \"gamma\", \"theta\", \"vega\", \"iv\"] as const;\n\nexport function quoteParquetGreekProjection(\n columns: ParquetColumnSet,\n alias = \"q\",\n needed: readonly GreekColumn[] = ALL_GREEKS,\n): string {\n // When `needed` excludes a greek, project NULL with the same DuckDB type that\n // quoteParquetColumnExpr would have used. Downstream row parsing reads each\n // column position-wise, so the projection must emit a value at every\n // position (just NULL when not requested) to keep the row shape stable.\n const wantSet = new Set<GreekColumn>(needed);\n const projectGreek = (name: GreekColumn): string =>\n wantSet.has(name)\n ? `${quoteParquetColumnExpr(columns, alias, name, \"DOUBLE\")} AS ${name}`\n : `NULL::DOUBLE AS ${name}`;\n return [\n projectGreek(\"delta\"),\n projectGreek(\"gamma\"),\n projectGreek(\"theta\"),\n projectGreek(\"vega\"),\n projectGreek(\"iv\"),\n `${quoteParquetColumnExpr(columns, alias, \"greeks_source\", \"VARCHAR\")} AS greeks_source`,\n `${quoteParquetColumnExpr(columns, alias, \"greeks_revision\", \"INTEGER\")} AS greeks_revision`,\n `${quoteParquetColumnExpr(columns, alias, \"rate_type\", \"VARCHAR\")} AS rate_type`,\n `${quoteParquetColumnExpr(columns, alias, \"rate_value\", \"DOUBLE\")} AS rate_value`,\n `${quoteParquetColumnExpr(columns, alias, \"gamma_source\", \"VARCHAR\")} AS gamma_source`,\n ].join(\",\\n \");\n}\n\n/**\n * Write-side greek projection: casts delta/gamma/theta/vega/iv to REAL\n * (FLOAT32) so new or rewritten option_quote_minutes parquets halve the\n * per-row greek footprint vs storing them as DOUBLE. Black-Scholes outputs\n * (bounded derivatives, implied vols) are well within FLOAT32's ~7 decimal\n * digits of precision — the measured size win on a representative SPX day\n * is ~180MB/date vs DOUBLE (~4.1x overhead vs no-greeks instead of 7.6x),\n * bringing a full SPX archive backfill under the 250GB budget.\n * Read path (`quoteParquetGreekProjection` above) stays untouched so\n * existing DOUBLE partitions continue to read without widening cost.\n */\nexport function quoteParquetGreekWriteProjection(columns: ParquetColumnSet, alias = \"q\"): string {\n const castReal = (name: string) =>\n hasColumn(columns, name) ? `CAST(${alias}.${name} AS REAL)` : `NULL::REAL`;\n return [\n `${castReal(\"delta\")} AS delta`,\n `${castReal(\"gamma\")} AS gamma`,\n `${castReal(\"theta\")} AS theta`,\n `${castReal(\"vega\")} AS vega`,\n `${castReal(\"iv\")} AS iv`,\n `${quoteParquetColumnExpr(columns, alias, \"greeks_source\", \"VARCHAR\")} AS greeks_source`,\n `${quoteParquetColumnExpr(columns, alias, \"greeks_revision\", \"INTEGER\")} AS greeks_revision`,\n `${quoteParquetColumnExpr(columns, alias, \"rate_type\", \"VARCHAR\")} AS rate_type`,\n `${quoteParquetColumnExpr(columns, alias, \"rate_value\", \"DOUBLE\")} AS rate_value`,\n `${quoteParquetColumnExpr(columns, alias, \"gamma_source\", \"VARCHAR\")} AS gamma_source`,\n ].join(\",\\n \");\n}\n\nexport function quoteParquetCanonicalProjection(columns: ParquetColumnSet, alias = \"q\"): string {\n return [\n `${quoteParquetColumnExpr(columns, alias, \"underlying\", \"VARCHAR\")} AS underlying`,\n `${quoteParquetColumnExpr(columns, alias, \"date\", \"VARCHAR\")} AS date`,\n `${quoteParquetColumnExpr(columns, alias, \"ticker\", \"VARCHAR\")} AS ticker`,\n `${quoteParquetColumnExpr(columns, alias, \"time\", \"VARCHAR\")} AS time`,\n `${quoteParquetColumnExpr(columns, alias, \"bid\", \"DOUBLE\")} AS bid`,\n `${quoteParquetColumnExpr(columns, alias, \"ask\", \"DOUBLE\")} AS ask`,\n `${quoteParquetMidExpr(columns, alias)} AS mid`,\n `${quoteParquetColumnExpr(columns, alias, \"last_updated_ns\", \"BIGINT\")} AS last_updated_ns`,\n `${quoteParquetColumnExpr(columns, alias, \"source\", \"VARCHAR\")} AS source`,\n quoteParquetGreekProjection(columns, alias),\n ].join(\",\\n \");\n}\n\n/**\n * Canonical projection for parquet writes. Identical to\n * quoteParquetCanonicalProjection but substitutes the REAL-cast greek\n * projection so the written partition's physical column types are\n * FLOAT32 for delta/gamma/theta/vega/iv.\n */\nexport function quoteParquetCanonicalWriteProjection(\n columns: ParquetColumnSet,\n alias = \"q\",\n): string {\n return [\n `${quoteParquetColumnExpr(columns, alias, \"underlying\", \"VARCHAR\")} AS underlying`,\n `${quoteParquetColumnExpr(columns, alias, \"date\", \"VARCHAR\")} AS date`,\n `${quoteParquetColumnExpr(columns, alias, \"ticker\", \"VARCHAR\")} AS ticker`,\n `${quoteParquetColumnExpr(columns, alias, \"time\", \"VARCHAR\")} AS time`,\n `${quoteParquetColumnExpr(columns, alias, \"bid\", \"DOUBLE\")} AS bid`,\n `${quoteParquetColumnExpr(columns, alias, \"ask\", \"DOUBLE\")} AS ask`,\n `${quoteParquetMidExpr(columns, alias)} AS mid`,\n `${quoteParquetColumnExpr(columns, alias, \"last_updated_ns\", \"BIGINT\")} AS last_updated_ns`,\n `${quoteParquetColumnExpr(columns, alias, \"source\", \"VARCHAR\")} AS source`,\n quoteParquetGreekWriteProjection(columns, alias),\n ].join(\",\\n \");\n}\n","/**\n * One-Time DuckDB-to-JSON Metadata Migration\n *\n * Migrates profiles, sync metadata, market import metadata, and flat import log\n * from DuckDB tables to JSON files. Called once during connection startup when\n * TRADEBLOCKS_PARQUET=true.\n *\n * Idempotency: Each sub-migration checks if JSON files already exist for that store.\n * If yes, skips. If DuckDB table is empty, skips. Only migrates when DuckDB has data\n * and JSON doesn't.\n *\n * DuckDB tables are left as-is after migration.\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport * as path from \"path\";\nimport { isParquetMode } from \"./parquet-writer.ts\";\nimport { readJsonFile, writeJsonFile, toFileSlug } from \"./json-store.ts\";\nimport {\n listProfilesJson,\n upsertSyncMetadataJson,\n getAllSyncedBlockIdsJson,\n upsertMarketImportMetadataJson,\n upsertFlatImportLogJson,\n} from \"./json-adapters.ts\";\nimport type { BlockSyncMetadata, MarketImportMetadata } from \"../sync/metadata.ts\";\nimport type { FlatImportLogEntry } from \"./json-adapters.ts\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MigrationResult {\n migrated: boolean;\n profiles: number;\n syncMeta: number;\n marketMeta: number;\n flatLog: number;\n strategies: number;\n}\n\n// ---------------------------------------------------------------------------\n// DuckDB timestamp helper (same logic as profile-schemas.ts toDate)\n// ---------------------------------------------------------------------------\n\nfunction toDate(value: unknown): Date {\n if (value instanceof Date) return value;\n // DuckDB timestamps may come as DuckDBTimestampValue { micros: bigint }\n if (typeof value === \"object\" && value !== null && \"micros\" in value) {\n const micros = (value as { micros: bigint }).micros;\n return new Date(Number(micros) / 1000);\n }\n if (typeof value === \"bigint\") {\n return new Date(Number(value) / 1000);\n }\n if (typeof value === \"number\") {\n return value > 9e12 ? new Date(value / 1000) : new Date(value);\n }\n if (typeof value === \"string\") {\n return new Date(value);\n }\n return new Date();\n}\n\n// ---------------------------------------------------------------------------\n// Sub-migrations\n// ---------------------------------------------------------------------------\n\n/**\n * Migrate strategy profiles from DuckDB to JSON.\n *\n * Uses writeJsonFile directly (not upsertProfileJson) to preserve original\n * createdAt/updatedAt timestamps from DuckDB.\n *\n * @returns Count of migrated profiles\n */\nasync function migrateProfiles(conn: DuckDBConnection, blocksDir: string): Promise<number> {\n // Idempotency: skip if JSON profiles already exist\n const existing = await listProfilesJson(blocksDir);\n if (existing.length > 0) return 0;\n\n let rows: unknown[][];\n try {\n const result = await conn.runAndReadAll(`\n SELECT block_id, strategy_name, structure_type, greeks_bias, thesis,\n legs, entry_filters, exit_rules, expected_regimes, key_metrics,\n position_sizing, underlying, re_entry, cap_profits, cap_losses,\n require_two_prices_pt, close_on_completion, ignore_margin_req,\n created_at, updated_at\n FROM profiles.strategy_profiles\n `);\n rows = result.getRows();\n } catch {\n // Table doesn't exist or not accessible\n return 0;\n }\n\n if (rows.length === 0) return 0;\n\n const parseJson = (v: unknown) => {\n if (v === null || v === undefined) return [];\n if (typeof v === \"string\") return JSON.parse(v);\n return v;\n };\n\n const parseJsonObj = (v: unknown) => {\n if (v === null || v === undefined) return {};\n if (typeof v === \"string\") return JSON.parse(v);\n return v;\n };\n\n const toBoolOrUndef = (v: unknown): boolean | undefined =>\n v === null || v === undefined ? undefined : Boolean(v);\n\n for (const row of rows) {\n const blockId = row[0] as string;\n const strategyName = row[1] as string;\n const createdAt = toDate(row[18]);\n const updatedAt = toDate(row[19]);\n\n const profileJson = {\n blockId,\n strategyName,\n structureType: row[2] as string,\n greeksBias: row[3] as string,\n thesis: row[4] as string,\n legs: parseJson(row[5]),\n entryFilters: parseJson(row[6]),\n exitRules: parseJson(row[7]),\n expectedRegimes: parseJson(row[8]),\n keyMetrics: parseJsonObj(row[9]),\n positionSizing: (() => {\n const ps = parseJsonObj(row[10]);\n return ps && Object.keys(ps).length > 0 ? ps : undefined;\n })(),\n underlying: (row[11] as string | undefined) ?? undefined,\n reEntry: toBoolOrUndef(row[12]),\n capProfits: toBoolOrUndef(row[13]),\n capLosses: toBoolOrUndef(row[14]),\n requireTwoPricesPT: toBoolOrUndef(row[15]),\n closeOnCompletion: toBoolOrUndef(row[16]),\n ignoreMarginReq: toBoolOrUndef(row[17]),\n createdAt: createdAt.toISOString(),\n updatedAt: updatedAt.toISOString(),\n };\n\n // Write directly using json-store to preserve original timestamps\n const filePath = path.join(blocksDir, blockId, \"profiles\", toFileSlug(strategyName) + \".json\");\n await writeJsonFile(filePath, profileJson);\n }\n\n return rows.length;\n}\n\n/**\n * Migrate block sync metadata from DuckDB to JSON.\n * @returns Count of migrated entries\n */\nasync function migrateSyncMetadata(conn: DuckDBConnection, blocksDir: string): Promise<number> {\n // Idempotency: skip if any JSON sync metadata exists\n const existing = await getAllSyncedBlockIdsJson(blocksDir);\n if (existing.length > 0) return 0;\n\n let rows: unknown[][];\n try {\n const result = await conn.runAndReadAll(`\n SELECT block_id, tradelog_hash, dailylog_hash, reportinglog_hash, synced_at, sync_version\n FROM trades._sync_metadata\n `);\n rows = result.getRows();\n } catch {\n return 0;\n }\n\n if (rows.length === 0) return 0;\n\n for (const row of rows) {\n const metadata: BlockSyncMetadata = {\n block_id: row[0] as string,\n tradelog_hash: row[1] as string,\n dailylog_hash: row[2] as string | null,\n reportinglog_hash: row[3] as string | null,\n synced_at: toDate(row[4]),\n sync_version: row[5] as number,\n };\n await upsertSyncMetadataJson(metadata, blocksDir);\n }\n\n return rows.length;\n}\n\n/**\n * Migrate market import metadata from DuckDB to JSON.\n * @returns Count of migrated entries\n */\nasync function migrateMarketImportMetadata(\n conn: DuckDBConnection,\n dataDir: string,\n): Promise<number> {\n // Idempotency: skip if aggregate JSON file already exists\n const existingFile = await readJsonFile(path.join(dataDir, \"market-meta\", \"sync-metadata.json\"));\n if (existingFile !== null) return 0;\n\n let rows: unknown[][];\n try {\n const result = await conn.runAndReadAll(`\n SELECT source, ticker, target_table, max_date, synced_at\n FROM market._sync_metadata\n `);\n rows = result.getRows();\n } catch {\n return 0;\n }\n\n if (rows.length === 0) return 0;\n\n for (const row of rows) {\n const metadata: MarketImportMetadata = {\n source: row[0] as string,\n ticker: row[1] as string,\n target_table: row[2] as string,\n max_date: row[3] as string | null,\n synced_at: toDate(row[4]),\n };\n await upsertMarketImportMetadataJson(metadata, dataDir);\n }\n\n return rows.length;\n}\n\n/**\n * Migrate flat import log from DuckDB to JSON.\n * @returns Count of migrated entries\n */\nasync function migrateFlatImportLog(conn: DuckDBConnection, dataDir: string): Promise<number> {\n // Idempotency: skip if aggregate JSON file already exists\n const existingFile = await readJsonFile(\n path.join(dataDir, \"market-meta\", \"flat-import-log.json\"),\n );\n if (existingFile !== null) return 0;\n\n let rows: unknown[][];\n try {\n const result = await conn.runAndReadAll(`\n SELECT date, asset_class, underlying, imported_at, bar_count\n FROM market.flat_import_log\n `);\n rows = result.getRows();\n } catch {\n return 0;\n }\n\n if (rows.length === 0) return 0;\n\n for (const row of rows) {\n const entry: FlatImportLogEntry = {\n date: row[0] as string,\n asset_class: row[1] as string,\n underlying: row[2] as string,\n imported_at: toDate(row[3]).toISOString(),\n bar_count: Number(row[4]),\n };\n await upsertFlatImportLogJson(entry, dataDir);\n }\n\n return rows.length;\n}\n\n// ---------------------------------------------------------------------------\n// Main migration entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Migrate all shared metadata from DuckDB tables to JSON files.\n *\n * Called during connection startup when TRADEBLOCKS_PARQUET=true.\n * Each sub-migration is independent and idempotent:\n * - Skips if JSON files already exist for that store\n * - Skips if DuckDB tables are empty or don't exist\n * - DuckDB tables are left as-is (not dropped or modified)\n *\n * Strategy definitions are migrated separately by consumers.\n *\n * @param conn - Active DuckDB connection (must be read-write)\n * @param dataDir - Root data directory (e.g., ~/tradeblocks-data)\n * @param blocksDir - Blocks directory (e.g., ~/tradeblocks-data/blocks)\n * @returns MigrationResult with counts per store\n */\nexport async function migrateMetadataToJson(\n conn: DuckDBConnection,\n dataDir: string,\n blocksDir: string,\n): Promise<MigrationResult> {\n // Guard: only run in Parquet mode\n if (!isParquetMode()) {\n return { migrated: false, profiles: 0, syncMeta: 0, marketMeta: 0, flatLog: 0, strategies: 0 };\n }\n\n const profiles = await migrateProfiles(conn, blocksDir);\n const syncMeta = await migrateSyncMetadata(conn, blocksDir);\n const marketMeta = await migrateMarketImportMetadata(conn, dataDir);\n const flatLog = await migrateFlatImportLog(conn, dataDir);\n\n const migrated = profiles + syncMeta + marketMeta + flatLog > 0;\n\n if (migrated) {\n console.log(\n `[json-migration] Migrated: ${profiles} profiles, ${syncMeta} sync metadata, ` +\n `${marketMeta} market import records, ${flatLog} flat import records`,\n );\n }\n\n return { migrated, profiles, syncMeta, marketMeta, flatLog, strategies: 0 };\n}\n","/**\n * Block Sync Logic\n *\n * Core synchronization logic for syncing block CSV data to DuckDB.\n * Handles hash-based change detection, atomic transactions, and error recovery.\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport { hashFileContent } from \"./hasher.ts\";\nimport {\n getSyncMetadata,\n upsertSyncMetadata,\n deleteSyncMetadata,\n getAllSyncedBlockIds,\n type BlockSyncMetadata,\n} from \"./metadata.ts\";\nimport { resolveTickerFromCsvRow } from \"../utils/ticker.ts\";\nimport { convertToReportingTrade } from \"../utils/block-loader.ts\";\nimport { discoverCsvFiles } from \"../utils/csv-discovery.ts\";\nimport type { ReportingTrade } from \"@tradeblocks/lib\";\n\n/**\n * Result of syncing a single block\n */\nexport interface BlockSyncResult {\n blockId: string;\n status: \"synced\" | \"unchanged\" | \"deleted\" | \"error\";\n tradeCount?: number;\n error?: string;\n}\n\n// --- CSV Parsing Helpers (copied from block-loader.ts to avoid circular imports) ---\n\n/**\n * Parse CSV content into array of record objects\n */\nfunction parseCSV(content: string): Record<string, string>[] {\n // Strip UTF-8 BOM if present (common in Windows/Excel CSV exports)\n const lines = content\n .replace(/^\\uFEFF/, \"\")\n .trim()\n .split(\"\\n\");\n if (lines.length < 2) return [];\n\n const headers = parseCSVLine(lines[0]);\n const records: Record<string, string>[] = [];\n\n for (let i = 1; i < lines.length; i++) {\n const values = parseCSVLine(lines[i]);\n if (values.length === 0) continue;\n\n const record: Record<string, string> = {};\n headers.forEach((header, idx) => {\n record[header] = values[idx] || \"\";\n });\n records.push(record);\n }\n\n return records;\n}\n\n/**\n * Parse a single CSV line handling quoted fields\n */\nfunction parseCSVLine(line: string): string[] {\n const result: string[] = [];\n let current = \"\";\n let inQuotes = false;\n\n for (let i = 0; i < line.length; i++) {\n const char = line[i];\n\n if (char === '\"') {\n if (inQuotes && line[i + 1] === '\"') {\n current += '\"';\n i++;\n } else {\n inQuotes = !inQuotes;\n }\n } else if (char === \",\" && !inQuotes) {\n result.push(current.trim());\n current = \"\";\n } else {\n current += char;\n }\n }\n\n result.push(current.trim());\n return result;\n}\n\n/**\n * Normalize CSV date strings into DuckDB-friendly YYYY-MM-DD format.\n *\n * Supports:\n * - YYYY-MM-DD (already normalized)\n * - M/D/YY, MM/DD/YY\n * - M/D/YYYY, MM/DD/YYYY\n */\nfunction normalizeCsvDate(value: string | undefined): string | null {\n if (!value) return null;\n const raw = value.trim();\n if (!raw) return null;\n\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(raw)) return raw;\n\n const mdyMatch = raw.match(/^(\\d{1,2})[/-](\\d{1,2})[/-](\\d{2}|\\d{4})$/);\n if (!mdyMatch) return raw;\n\n const month = Number(mdyMatch[1]);\n const day = Number(mdyMatch[2]);\n let year = Number(mdyMatch[3]);\n\n if (Number.isNaN(month) || Number.isNaN(day) || Number.isNaN(year)) {\n return raw;\n }\n if (year < 100) {\n year += year >= 70 ? 1900 : 2000;\n }\n\n const dt = new Date(Date.UTC(year, month - 1, day));\n if (dt.getUTCFullYear() !== year || dt.getUTCMonth() + 1 !== month || dt.getUTCDate() !== day) {\n return raw;\n }\n\n const mm = String(month).padStart(2, \"0\");\n const dd = String(day).padStart(2, \"0\");\n return `${year}-${mm}-${dd}`;\n}\n\n// --- Tradelog Discovery ---\n\n/**\n * Find the tradelog CSV file for a block using header-sniffing discovery.\n * No longer reads block.json — uses discoverCsvFiles from csv-discovery.ts.\n */\nasync function findTradelogFile(blockPath: string): Promise<string | null> {\n const { mappings } = await discoverCsvFiles(blockPath);\n return mappings.tradelog || null;\n}\n\n/**\n * Find optional log files (dailylog, reportinglog) for a block using header-sniffing discovery.\n * No longer reads block.json — uses discoverCsvFiles from csv-discovery.ts.\n */\nasync function findOptionalLogFiles(\n blockPath: string,\n): Promise<{ dailylog: string | null; reportinglog: string | null }> {\n const { mappings } = await discoverCsvFiles(blockPath);\n return {\n dailylog: mappings.dailylog || null,\n reportinglog: mappings.reportinglog || null,\n };\n}\n\n// --- Database Operations ---\n\n/**\n * Insert trades in batches to avoid parameter limits.\n *\n * @param conn - DuckDB connection\n * @param blockId - Block identifier\n * @param records - Parsed CSV records\n * @param startIdx - Starting index in records array\n * @param batchSize - Number of records per batch\n */\nasync function insertTradeBatch(\n conn: DuckDBConnection,\n blockId: string,\n records: Record<string, string>[],\n startIdx: number,\n batchSize: number,\n): Promise<void> {\n const batch = records.slice(startIdx, startIdx + batchSize);\n if (batch.length === 0) return;\n\n // Build VALUES placeholders: ($1, $2, $3, ...), ($15, $16, $17, ...), ...\n // Each row has 15 columns: block_id + 13 trade fields + ticker\n const columnsPerRow = 15;\n const placeholders: string[] = [];\n const params: (string | number | null)[] = [];\n\n for (let rowIdx = 0; rowIdx < batch.length; rowIdx++) {\n const record = batch[rowIdx];\n const baseParam = rowIdx * columnsPerRow + 1;\n const rowPlaceholders = Array.from({ length: columnsPerRow }, (_, i) => `$${baseParam + i}`);\n placeholders.push(`(${rowPlaceholders.join(\", \")})`);\n\n // Parse numeric values safely\n const premium = parseFloat(record[\"Premium\"]);\n const numContracts = parseInt(record[\"No. of Contracts\"], 10);\n const pl = parseFloat(record[\"P/L\"]);\n const marginReq = parseFloat(record[\"Margin Req.\"]);\n const openingCommissions = parseFloat(record[\"Opening Commissions + Fees\"]);\n const closingCommissions = parseFloat(record[\"Closing Commissions + Fees\"]);\n const ticker = resolveTickerFromCsvRow(record);\n\n // Map CSV record to column values\n params.push(\n blockId, // block_id\n normalizeCsvDate(record[\"Date Opened\"]), // date_opened\n record[\"Time Opened\"] || null, // time_opened\n (record[\"Strategy\"] || \"\").trim() || blockId, // strategy (fallback to blockId)\n record[\"Legs\"] || null, // legs\n isNaN(premium) ? null : premium, // premium\n isNaN(numContracts) ? 1 : numContracts, // num_contracts\n isNaN(pl) ? 0 : pl, // pl\n normalizeCsvDate(record[\"Date Closed\"]), // date_closed\n record[\"Time Closed\"] || null, // time_closed\n record[\"Reason For Close\"] || null, // reason_for_close\n isNaN(marginReq) ? null : marginReq, // margin_req\n isNaN(openingCommissions) ? 0 : openingCommissions, // opening_commissions\n isNaN(closingCommissions) ? 0 : closingCommissions, // closing_commissions\n ticker, // ticker\n );\n }\n\n const sql = `\n INSERT INTO trades.trade_data (\n block_id, date_opened, time_opened, strategy, legs, premium,\n num_contracts, pl, date_closed, time_closed, reason_for_close,\n margin_req, opening_commissions, closing_commissions, ticker\n ) VALUES ${placeholders.join(\", \")}\n `;\n\n await conn.run(sql, params);\n}\n\n/**\n * Format a Date to YYYY-MM-DD using local timezone components.\n * Preserves the calendar day stored in the Date object (see CLAUDE.md date rules).\n */\nfunction formatDateForDb(date: Date | undefined): string | null {\n if (!date || isNaN(date.getTime())) return null;\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Insert reporting trades in batches to avoid parameter limits.\n * Accepts format-agnostic ReportingTrade objects — all CSV format detection\n * is handled upstream by convertToReportingTrade().\n *\n * @param conn - DuckDB connection\n * @param blockId - Block identifier\n * @param trades - Converted ReportingTrade objects\n * @param tickers - Parallel array of resolved ticker strings\n * @param startIdx - Starting index in arrays\n * @param batchSize - Number of records per batch\n */\nasync function insertReportingBatch(\n conn: DuckDBConnection,\n blockId: string,\n trades: ReportingTrade[],\n tickers: string[],\n startIdx: number,\n batchSize: number,\n): Promise<void> {\n const batch = trades.slice(startIdx, startIdx + batchSize);\n const batchTickers = tickers.slice(startIdx, startIdx + batchSize);\n if (batch.length === 0) return;\n\n // Each row has 15 columns: block_id + 13 reporting fields + ticker\n const columnsPerRow = 15;\n const placeholders: string[] = [];\n const params: (string | number | null)[] = [];\n\n for (let rowIdx = 0; rowIdx < batch.length; rowIdx++) {\n const trade = batch[rowIdx];\n const baseParam = rowIdx * columnsPerRow + 1;\n const rowPlaceholders = Array.from({ length: columnsPerRow }, (_, i) => `$${baseParam + i}`);\n placeholders.push(`(${rowPlaceholders.join(\", \")})`);\n\n params.push(\n blockId,\n formatDateForDb(trade.dateOpened),\n trade.timeOpened || null,\n trade.strategy || null,\n trade.legs || null,\n trade.initialPremium ?? null,\n trade.numContracts ?? 1,\n trade.pl ?? 0,\n formatDateForDb(trade.dateClosed),\n trade.timeClosed || null,\n trade.closingPrice ?? null,\n trade.avgClosingCost ?? null,\n trade.reasonForClose || null,\n trade.openingPrice ?? null,\n batchTickers[rowIdx],\n );\n }\n\n const sql = `\n INSERT INTO trades.reporting_data (\n block_id, date_opened, time_opened, strategy, legs, initial_premium,\n num_contracts, pl, date_closed, time_closed, closing_price,\n avg_closing_cost, reason_for_close, opening_price, ticker\n ) VALUES ${placeholders.join(\", \")}\n `;\n\n await conn.run(sql, params);\n}\n\n// --- Parse Versioning ---\n\n/**\n * Bump this when parsing logic changes to force re-sync of all blocks.\n * Appended to content hashes so stored hashes will mismatch.\n *\n * v2: Use blockId as strategy fallback for empty Strategy columns\n */\nconst PARSE_VERSION = \"v2\";\n\nfunction versionedHash(hash: string): string {\n return `${hash}:${PARSE_VERSION}`;\n}\n\n// --- Core Sync Functions ---\n\n/**\n * Sync a single block's trade data to DuckDB.\n *\n * Performs hash-based change detection and atomic insert:\n * 1. Find and hash the tradelog CSV\n * 2. Compare with stored hash\n * 3. If changed: DELETE old + INSERT new in transaction\n * 4. Update sync metadata\n *\n * @param conn - DuckDB connection\n * @param blockId - Block identifier (folder name)\n * @param blockPath - Absolute path to block folder\n * @returns Sync result with status\n */\nexport async function syncBlockInternal(\n conn: DuckDBConnection,\n blockId: string,\n blockPath: string,\n): Promise<BlockSyncResult> {\n const blocksDir = path.dirname(blockPath);\n try {\n // Get existing metadata early (needed for missing-file cleanup logic)\n const existingMetadata = await getSyncMetadata(conn, blockId, blocksDir);\n\n // Find the tradelog file via header-sniffing discovery\n const tradelogFilename = await findTradelogFile(blockPath);\n if (!tradelogFilename) {\n // Previously-synced block lost its tradelog: remove stale data/metadata\n if (existingMetadata) {\n await conn.run(\"BEGIN TRANSACTION\");\n try {\n await conn.run(\"DELETE FROM trades.trade_data WHERE block_id = $1\", [blockId]);\n await conn.run(\"DELETE FROM trades.reporting_data WHERE block_id = $1\", [blockId]);\n await deleteSyncMetadata(conn, blockId, blocksDir);\n await conn.run(\"COMMIT\");\n return { blockId, status: \"deleted\" };\n } catch (err) {\n await conn.run(\"ROLLBACK\");\n throw err;\n }\n }\n\n return {\n blockId,\n status: \"error\",\n error: \"No tradelog CSV found in block\",\n };\n }\n\n const tradelogPath = path.join(blockPath, tradelogFilename);\n\n // Hash the tradelog file (versioned to force re-sync on parse logic changes)\n const tradelogHash = versionedHash(await hashFileContent(tradelogPath));\n\n // Check if hash matches (unchanged)\n // Also check if reportinglog exists but hasn't been synced yet\n if (existingMetadata && existingMetadata.tradelog_hash === tradelogHash) {\n // Check if reporting log needs to be synced (new file or changed)\n const optionalLogs = await findOptionalLogFiles(blockPath);\n if (optionalLogs.reportinglog) {\n const reportinglogPath = path.join(blockPath, optionalLogs.reportinglog);\n const reportinglogHash = versionedHash(await hashFileContent(reportinglogPath));\n if (existingMetadata.reportinglog_hash !== reportinglogHash) {\n // Reportinglog changed or was never synced - fall through to sync\n } else {\n return { blockId, status: \"unchanged\" };\n }\n } else {\n // Reporting log was removed after previously being synced - clear stale data\n if (existingMetadata.reportinglog_hash !== null) {\n // Fall through to sync path, which will delete reporting_data and write null hash\n } else {\n return { blockId, status: \"unchanged\" };\n }\n }\n }\n\n // Hash differs or no metadata - need to sync\n // Start transaction for atomic update\n await conn.run(\"BEGIN TRANSACTION\");\n\n try {\n // Delete old trade data for this block\n await conn.run(\"DELETE FROM trades.trade_data WHERE block_id = $1\", [blockId]);\n\n // Read and parse CSV\n const csvContent = await fs.readFile(tradelogPath, \"utf-8\");\n const records = parseCSV(csvContent);\n\n // Insert trades in batches of 500\n const batchSize = 500;\n for (let i = 0; i < records.length; i += batchSize) {\n await insertTradeBatch(conn, blockId, records, i, batchSize);\n }\n\n // Hash optional log files if they exist\n const optionalLogs = await findOptionalLogFiles(blockPath);\n let dailylogHash: string | null = null;\n let reportinglogHash: string | null = null;\n\n if (optionalLogs.dailylog) {\n try {\n dailylogHash = versionedHash(\n await hashFileContent(path.join(blockPath, optionalLogs.dailylog)),\n );\n } catch {\n // Dailylog file can't be read, leave hash null\n }\n }\n\n if (optionalLogs.reportinglog) {\n try {\n reportinglogHash = versionedHash(\n await hashFileContent(path.join(blockPath, optionalLogs.reportinglog)),\n );\n } catch {\n // Reportinglog file can't be read, leave hash null\n }\n }\n\n // Sync reporting log if it exists and has changed\n // Always delete old reporting data for this block (same pattern as trade_data)\n await conn.run(\"DELETE FROM trades.reporting_data WHERE block_id = $1\", [blockId]);\n\n if (optionalLogs.reportinglog && reportinglogHash) {\n // Read and parse reporting CSV, then convert to ReportingTrade objects.\n // convertToReportingTrade handles all format detection (OO, TAT, etc.)\n const reportingPath = path.join(blockPath, optionalLogs.reportinglog);\n const reportingContent = await fs.readFile(reportingPath, \"utf-8\");\n const reportingRecords = parseCSV(reportingContent);\n\n const reportingTrades: ReportingTrade[] = [];\n const reportingTickers: string[] = [];\n for (const record of reportingRecords) {\n const trade = convertToReportingTrade(record);\n if (trade) {\n reportingTrades.push(trade);\n reportingTickers.push(resolveTickerFromCsvRow(record));\n }\n }\n\n // Insert reporting trades in batches of 500\n for (let i = 0; i < reportingTrades.length; i += batchSize) {\n await insertReportingBatch(\n conn,\n blockId,\n reportingTrades,\n reportingTickers,\n i,\n batchSize,\n );\n }\n }\n\n // Update sync metadata\n const newMetadata: BlockSyncMetadata = {\n block_id: blockId,\n tradelog_hash: tradelogHash,\n dailylog_hash: dailylogHash,\n reportinglog_hash: reportinglogHash,\n synced_at: new Date(),\n sync_version: (existingMetadata?.sync_version ?? 0) + 1,\n };\n await upsertSyncMetadata(conn, newMetadata, blocksDir);\n\n // Commit transaction\n await conn.run(\"COMMIT\");\n\n return {\n blockId,\n status: \"synced\",\n tradeCount: records.length,\n };\n } catch (err) {\n // Rollback on any error\n await conn.run(\"ROLLBACK\");\n\n // If this block was previously synced, remove its data to avoid stale state\n // (Per CONTEXT.md: \"If sync fails for a previously-synced block, REMOVE its data\")\n if (existingMetadata) {\n try {\n await conn.run(\"BEGIN TRANSACTION\");\n await conn.run(\"DELETE FROM trades.trade_data WHERE block_id = $1\", [blockId]);\n await conn.run(\"DELETE FROM trades.reporting_data WHERE block_id = $1\", [blockId]);\n await deleteSyncMetadata(conn, blockId, blocksDir);\n await conn.run(\"COMMIT\");\n } catch {\n // Best effort cleanup failed, but we'll report the original error\n try {\n await conn.run(\"ROLLBACK\");\n } catch {\n // Ignore rollback errors\n }\n }\n }\n\n throw err;\n }\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n return {\n blockId,\n status: \"error\",\n error: errorMsg,\n };\n }\n}\n\n/**\n * Detect which blocks need syncing and which should be deleted.\n *\n * Compares filesystem folders with sync metadata:\n * - toSync: Folders that exist but aren't synced or have changed\n * - toDelete: Block IDs in metadata but folder no longer exists\n *\n * @param conn - DuckDB connection\n * @param baseDir - Base data directory\n * @returns Object with toSync and toDelete arrays\n */\nexport async function detectBlockChanges(\n conn: DuckDBConnection,\n baseDir: string,\n): Promise<{ toSync: string[]; toDelete: string[] }> {\n const toSync: string[] = [];\n const toDelete: string[] = [];\n\n // Get all synced block IDs from metadata\n const syncedBlockIds = new Set(await getAllSyncedBlockIds(conn, baseDir));\n\n // List all directories in baseDir\n const entries = await fs.readdir(baseDir, { withFileTypes: true });\n const folderNames = new Set<string>();\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name.startsWith(\".\") || entry.name.startsWith(\"_\")) continue; // Skip hidden/internal folders\n if (\n entry.name.endsWith(\".tmp\") ||\n entry.name.endsWith(\".duckdb\") ||\n entry.name.endsWith(\".duckdb.tmp\")\n )\n continue; // Skip DuckDB/temp files\n\n const blockId = entry.name;\n folderNames.add(blockId);\n\n const blockPath = path.join(baseDir, blockId);\n\n // Check if this is a new block (not in metadata)\n if (!syncedBlockIds.has(blockId)) {\n // Only sync if folder contains a tradelog CSV\n const tradelog = await findTradelogFile(blockPath);\n if (tradelog) {\n toSync.push(blockId);\n }\n continue;\n }\n\n // Block exists in metadata - check if hash changed\n const tradelogFilename = await findTradelogFile(blockPath);\n if (!tradelogFilename) {\n // Previously-synced block lost tradelog: mark for cleanup\n toDelete.push(blockId);\n continue;\n }\n\n try {\n const tradelogPath = path.join(blockPath, tradelogFilename);\n const currentHash = versionedHash(await hashFileContent(tradelogPath));\n const metadata = await getSyncMetadata(conn, blockId, baseDir);\n\n if (!metadata || metadata.tradelog_hash !== currentHash) {\n toSync.push(blockId);\n } else {\n // Tradelog unchanged - check if reportinglog needs syncing\n const optionalLogs = await findOptionalLogFiles(blockPath);\n if (optionalLogs.reportinglog) {\n const reportinglogPath = path.join(blockPath, optionalLogs.reportinglog);\n const reportingHash = versionedHash(await hashFileContent(reportinglogPath));\n if (metadata.reportinglog_hash !== reportingHash) {\n // Reportinglog changed or was never synced\n toSync.push(blockId);\n }\n } else if (metadata?.reportinglog_hash !== null) {\n // Reportinglog was removed after being previously synced\n toSync.push(blockId);\n }\n }\n } catch {\n // Can't hash file - mark for sync (will fail during sync with proper error)\n toSync.push(blockId);\n }\n }\n\n // Find deleted blocks (in metadata but folder doesn't exist)\n for (const syncedBlockId of syncedBlockIds) {\n if (!folderNames.has(syncedBlockId)) {\n toDelete.push(syncedBlockId);\n }\n }\n\n return { toSync, toDelete };\n}\n\n/**\n * Remove data for deleted blocks from DuckDB.\n *\n * Performs atomic cleanup: deletes trade data and sync metadata.\n *\n * @param conn - DuckDB connection\n * @param deletedBlockIds - Array of block IDs to clean up\n */\nexport async function cleanupDeletedBlocks(\n conn: DuckDBConnection,\n deletedBlockIds: string[],\n blocksDir?: string,\n): Promise<void> {\n for (const blockId of deletedBlockIds) {\n await conn.run(\"BEGIN TRANSACTION\");\n try {\n await conn.run(\"DELETE FROM trades.trade_data WHERE block_id = $1\", [blockId]);\n await conn.run(\"DELETE FROM trades.reporting_data WHERE block_id = $1\", [blockId]);\n await deleteSyncMetadata(conn, blockId, blocksDir);\n await conn.run(\"COMMIT\");\n } catch (err) {\n await conn.run(\"ROLLBACK\");\n throw err;\n }\n }\n}\n","/**\n * File Content Hashing\n *\n * Provides SHA-256 hashing for file content to detect changes.\n * Used by the sync layer to determine if CSV files need re-syncing.\n */\n\nimport * as crypto from \"crypto\";\nimport * as fs from \"fs/promises\";\n\n/**\n * Compute SHA-256 hash of file content.\n *\n * @param filePath - Absolute path to the file\n * @returns Promise<string> - Hex-encoded SHA-256 hash\n * @throws Error if file cannot be read\n */\nexport async function hashFileContent(filePath: string): Promise<string> {\n const buffer = await fs.readFile(filePath);\n return crypto.createHash(\"sha256\").update(buffer).digest(\"hex\");\n}\n","/**\n * Sync Metadata Operations\n *\n * CRUD operations for sync metadata stored in DuckDB.\n * Used to track which blocks/files are synced and their content hashes.\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport { isParquetMode } from \"../db/parquet-writer.ts\";\nimport {\n getSyncMetadataJson,\n upsertSyncMetadataJson,\n deleteSyncMetadataJson,\n getAllSyncedBlockIdsJson,\n getMarketImportMetadataJson,\n upsertMarketImportMetadataJson,\n} from \"../db/json-adapters.ts\";\n\n/**\n * Sync metadata for a block (trades._sync_metadata)\n */\nexport interface BlockSyncMetadata {\n block_id: string;\n tradelog_hash: string;\n dailylog_hash: string | null;\n reportinglog_hash: string | null;\n synced_at: Date;\n sync_version: number;\n}\n\n/**\n * Get sync metadata for a block.\n *\n * @param conn - DuckDB connection\n * @param blockId - Block identifier\n * @returns Metadata if block is synced, null otherwise\n */\nexport async function getSyncMetadata(\n conn: DuckDBConnection,\n blockId: string,\n blocksDir?: string,\n): Promise<BlockSyncMetadata | null> {\n if (isParquetMode() && blocksDir) {\n return getSyncMetadataJson(blockId, blocksDir);\n }\n const reader = await conn.runAndReadAll(\n `SELECT block_id, tradelog_hash, dailylog_hash, reportinglog_hash, synced_at, sync_version\n FROM trades._sync_metadata\n WHERE block_id = $1`,\n [blockId],\n );\n\n const rows = reader.getRows();\n if (rows.length === 0) {\n return null;\n }\n\n const row = rows[0];\n return {\n block_id: row[0] as string,\n tradelog_hash: row[1] as string,\n dailylog_hash: row[2] as string | null,\n reportinglog_hash: row[3] as string | null,\n synced_at: new Date(row[4] as string),\n sync_version: row[5] as number,\n };\n}\n\n/**\n * Insert or update sync metadata for a block.\n *\n * @param conn - DuckDB connection\n * @param metadata - Block sync metadata to upsert\n */\nexport async function upsertSyncMetadata(\n conn: DuckDBConnection,\n metadata: BlockSyncMetadata,\n blocksDir?: string,\n): Promise<void> {\n if (isParquetMode() && blocksDir) {\n return upsertSyncMetadataJson(metadata, blocksDir);\n }\n await conn.run(\n `INSERT OR REPLACE INTO trades._sync_metadata\n (block_id, tradelog_hash, dailylog_hash, reportinglog_hash, synced_at, sync_version)\n VALUES ($1, $2, $3, $4, $5, $6)`,\n [\n metadata.block_id,\n metadata.tradelog_hash,\n metadata.dailylog_hash,\n metadata.reportinglog_hash,\n metadata.synced_at.toISOString(),\n metadata.sync_version,\n ],\n );\n}\n\n/**\n * Delete sync metadata for a block.\n *\n * @param conn - DuckDB connection\n * @param blockId - Block identifier to delete\n */\nexport async function deleteSyncMetadata(\n conn: DuckDBConnection,\n blockId: string,\n blocksDir?: string,\n): Promise<void> {\n if (isParquetMode() && blocksDir) {\n await deleteSyncMetadataJson(blockId, blocksDir);\n return;\n }\n await conn.run(`DELETE FROM trades._sync_metadata WHERE block_id = $1`, [blockId]);\n}\n\n/**\n * Get all synced block IDs.\n *\n * @param conn - DuckDB connection\n * @returns Array of block IDs that have sync metadata\n */\nexport async function getAllSyncedBlockIds(\n conn: DuckDBConnection,\n blocksDir?: string,\n): Promise<string[]> {\n if (isParquetMode() && blocksDir) {\n return getAllSyncedBlockIdsJson(blocksDir);\n }\n const reader = await conn.runAndReadAll(`SELECT block_id FROM trades._sync_metadata`);\n\n const rows = reader.getRows();\n return rows.map((row) => row[0] as string);\n}\n\n// =============================================================================\n// Phase 60 Market Import Metadata Helpers\n// =============================================================================\n\n/**\n * Market import metadata aligned to Phase 60 market._sync_metadata schema.\n * PK: (source, ticker, target_table)\n *\n * Distinct from MarketSyncMetadata which uses the old file_name-based schema.\n * Use these helpers for all Phase 61+ import tool calls.\n */\nexport interface MarketImportMetadata {\n source: string; // e.g., \"import_market_csv:/abs/path/to/file.csv\"\n ticker: string; // Normalized ticker e.g. \"SPX\"\n target_table: string; // \"daily\" | \"date_context\" | \"intraday\" | other canonical datasets\n max_date: string | null;\n synced_at: Date;\n}\n\n/**\n * Get market import metadata for a specific source/ticker/table combination.\n */\nexport async function getMarketImportMetadata(\n conn: DuckDBConnection,\n source: string,\n ticker: string,\n targetTable: string,\n dataDir?: string,\n): Promise<MarketImportMetadata | null> {\n if (isParquetMode() && dataDir) {\n return getMarketImportMetadataJson(source, ticker, targetTable, dataDir);\n }\n const reader = await conn.runAndReadAll(\n `SELECT source, ticker, target_table, max_date, synced_at\n FROM market._sync_metadata\n WHERE source = $1 AND ticker = $2 AND target_table = $3`,\n [source, ticker, targetTable],\n );\n const rows = reader.getRows();\n if (rows.length === 0) return null;\n const row = rows[0];\n return {\n source: row[0] as string,\n ticker: row[1] as string,\n target_table: row[2] as string,\n max_date: row[3] as string | null,\n synced_at: new Date(row[4] as string),\n };\n}\n\n/**\n * Upsert market import metadata using the Phase 60 schema PK (source, ticker, target_table).\n * Updates max_date and synced_at on conflict.\n */\nexport async function upsertMarketImportMetadata(\n conn: DuckDBConnection,\n metadata: MarketImportMetadata,\n dataDir?: string,\n): Promise<void> {\n if (isParquetMode() && dataDir) {\n return upsertMarketImportMetadataJson(metadata, dataDir);\n }\n await conn.run(\n `INSERT INTO market._sync_metadata\n (source, ticker, target_table, max_date, synced_at)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (source, ticker, target_table) DO UPDATE SET\n max_date = EXCLUDED.max_date,\n synced_at = EXCLUDED.synced_at`,\n [\n metadata.source,\n metadata.ticker,\n metadata.target_table,\n metadata.max_date,\n metadata.synced_at.toISOString(),\n ],\n );\n}\n","import type { Trade } from \"@tradeblocks/lib\";\n\nexport const DEFAULT_MARKET_TICKER = \"SPX\";\nexport const GLOBAL_MARKET_TICKER = \"ALL\";\n\nconst TICKER_FIELD_CANDIDATES = [\n \"ticker\",\n \"Ticker\",\n \"symbol\",\n \"Symbol\",\n \"underlying\",\n \"Underlying\",\n \"underlyingSymbol\",\n \"UnderlyingSymbol\",\n \"Underlying Symbol\",\n];\n\n/**\n * Normalize ticker strings into a stable uppercase symbol.\n */\nexport function normalizeTicker(value: string | null | undefined): string | null {\n if (!value) return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n\n const firstToken = trimmed.split(/\\s+/)[0];\n const stripped = firstToken.replace(/^[\\^$]+/, \"\");\n const normalized = stripped.toUpperCase().replace(/[^A-Z0-9._-]/g, \"\");\n if (!normalized) return null;\n // Guard against contract-count tokens like \"1\" from Symbol/legs strings.\n // Valid tickers should include at least one alphabetic character.\n if (!/[A-Z]/.test(normalized)) return null;\n return normalized;\n}\n\n/**\n * Resolve ticker from arbitrary object fields (case/alias tolerant).\n */\nexport function resolveTickerFromFields(\n fields: Record<string, unknown> | null | undefined,\n): string | null {\n if (!fields) return null;\n for (const field of TICKER_FIELD_CANDIDATES) {\n const raw = fields[field];\n if (typeof raw === \"string\") {\n const normalized = normalizeTicker(raw);\n if (normalized) return normalized;\n }\n }\n return null;\n}\n\n/**\n * Resolve ticker for a loaded trade.\n */\nexport function resolveTradeTicker(\n trade: Pick<Trade, \"customFields\">,\n fallback: string = DEFAULT_MARKET_TICKER,\n): string {\n return (\n resolveTickerFromFields(trade.customFields as Record<string, unknown> | undefined) ?? fallback\n );\n}\n\n/**\n * Resolve ticker from a CSV row plus optional fallback.\n */\nexport function resolveTickerFromCsvRow(\n row: Record<string, string>,\n fallback: string = DEFAULT_MARKET_TICKER,\n): string {\n return resolveTickerFromFields(row as Record<string, unknown>) ?? fallback;\n}\n\n/**\n * Stable composite key for ticker+date maps.\n */\nexport function marketTickerDateKey(ticker: string, date: string): string {\n return `${ticker}|${date}`;\n}\n","/**\n * Block Data Loader\n *\n * Utilities for loading and managing block data from folder-based structure.\n * Blocks are directories containing tradelog.csv (required) and optional dailylog.csv.\n *\n * Stats for listBlocks are computed from DuckDB (synced by middleware before tool calls).\n * File resolution for loadBlock/loadReportingLog uses csv-discovery header sniffing.\n * No block.json files are read or written.\n */\n\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport type { Trade, DailyLogEntry, ReportingTrade } from \"@tradeblocks/lib\";\nimport {\n REPORTING_TRADE_COLUMN_ALIASES,\n isTatFormat,\n convertTatRowToReportingTrade,\n} from \"@tradeblocks/lib\";\nimport { getConnection } from \"../db/connection.ts\";\nimport { isParquetMode } from \"../db/parquet-writer.ts\";\nimport { getSyncMetadataJson } from \"../db/json-adapters.ts\";\nimport { getBlocksDir } from \"../sync/index.ts\";\n\n// Re-export CSV discovery types and functions from shared module\nexport {\n type CsvMappings,\n type CsvType,\n detectCsvType,\n discoverCsvFiles,\n logCsvDiscoveryWarning,\n} from \"./csv-discovery.ts\";\nimport { type CsvType, discoverCsvFiles } from \"./csv-discovery.ts\";\n\nfunction resolveBlocksBaseDir(baseDir: string): string {\n return getBlocksDir(baseDir);\n}\n\n/**\n * Block info summary for listing\n */\nexport interface BlockInfo {\n blockId: string;\n name: string;\n tradeCount: number;\n hasDailyLog: boolean;\n hasReportingLog: boolean;\n dateRange: {\n start: Date | null;\n end: Date | null;\n };\n strategies: string[];\n totalPl: number;\n netPl: number;\n /** Summary of reporting log data if available */\n reportingLog?: {\n tradeCount: number;\n strategyCount: number;\n totalPL: number;\n dateRange: { start: string | null; end: string | null };\n stale: boolean;\n };\n}\n\n/**\n * Loaded block data\n */\nexport interface LoadedBlock {\n blockId: string;\n trades: Trade[];\n dailyLogs?: DailyLogEntry[];\n}\n\n/**\n * Parse a YYYY-MM-DD date string preserving the calendar date.\n * Same approach as lib/processing for consistency.\n */\nfunction parseDatePreservingCalendarDay(dateStr: string): Date {\n const match = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (match) {\n const [, year, month, day] = match;\n return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));\n }\n return new Date(dateStr);\n}\n\n/**\n * Parse numeric value from CSV string\n */\nfunction parseNumber(value: string | undefined, defaultValue?: number): number {\n if (!value || value.trim() === \"\" || value.toLowerCase() === \"nan\") {\n if (defaultValue !== undefined) return defaultValue;\n return 0;\n }\n const cleaned = value.replace(/[$,%]/g, \"\").trim();\n const parsed = parseFloat(cleaned);\n return isNaN(parsed) ? (defaultValue ?? 0) : parsed;\n}\n\nconst KNOWN_TRADE_COLUMNS = new Set([\n \"Date Opened\",\n \"Time Opened\",\n \"Opening Price\",\n \"Legs\",\n \"Premium\",\n \"Closing Price\",\n \"Date Closed\",\n \"Time Closed\",\n \"Avg. Closing Cost\",\n \"Reason For Close\",\n \"P/L\",\n \"No. of Contracts\",\n \"Funds at Close\",\n \"Margin Req.\",\n \"Strategy\",\n \"Opening Commissions + Fees\",\n \"Opening comms & fees\",\n \"Closing Commissions + Fees\",\n \"Closing comms & fees\",\n \"Opening Short/Long Ratio\",\n \"Closing Short/Long Ratio\",\n \"Opening VIX\",\n \"Closing VIX\",\n \"Gap\",\n \"Movement\",\n \"Max Profit\",\n \"Max Loss\",\n]);\n\n/**\n * Parse CSV content into array of record objects\n */\nfunction parseCSV(content: string): Record<string, string>[] {\n // Strip UTF-8 BOM if present (common in Windows/Excel CSV exports)\n const lines = content\n .replace(/^\\uFEFF/, \"\")\n .trim()\n .split(\"\\n\");\n if (lines.length < 2) return [];\n\n const headers = parseCSVLine(lines[0]);\n const records: Record<string, string>[] = [];\n\n for (let i = 1; i < lines.length; i++) {\n const values = parseCSVLine(lines[i]);\n if (values.length === 0) continue;\n\n const record: Record<string, string> = {};\n headers.forEach((header, idx) => {\n record[header] = values[idx] || \"\";\n });\n records.push(record);\n }\n\n return records;\n}\n\n/**\n * Parse a single CSV line handling quoted fields\n */\nfunction parseCSVLine(line: string): string[] {\n const result: string[] = [];\n let current = \"\";\n let inQuotes = false;\n\n for (let i = 0; i < line.length; i++) {\n const char = line[i];\n\n if (char === '\"') {\n if (inQuotes && line[i + 1] === '\"') {\n current += '\"';\n i++;\n } else {\n inQuotes = !inQuotes;\n }\n } else if (char === \",\" && !inQuotes) {\n result.push(current.trim());\n current = \"\";\n } else {\n current += char;\n }\n }\n\n result.push(current.trim());\n return result;\n}\n\n/**\n * Convert raw CSV record to Trade object\n */\nfunction convertToTrade(raw: Record<string, string>, blockId?: string): Trade | null {\n try {\n const dateOpened = parseDatePreservingCalendarDay(raw[\"Date Opened\"]);\n if (isNaN(dateOpened.getTime())) return null;\n\n const dateClosed = raw[\"Date Closed\"]\n ? parseDatePreservingCalendarDay(raw[\"Date Closed\"])\n : undefined;\n\n const strategy = (raw[\"Strategy\"] || \"\").trim() || blockId || \"Unknown\";\n\n const rawPremium = (raw[\"Premium\"] || \"\").replace(/[$,]/g, \"\").trim();\n const premiumPrecision: Trade[\"premiumPrecision\"] =\n rawPremium && !rawPremium.includes(\".\") ? \"cents\" : \"dollars\";\n const legs = raw[\"Legs\"] || raw[\"Symbol\"] || \"\";\n\n const trade: Trade = {\n dateOpened,\n timeOpened: raw[\"Time Opened\"] || \"00:00:00\",\n openingPrice: parseNumber(raw[\"Opening Price\"]),\n legs,\n premium: parseNumber(raw[\"Premium\"]),\n premiumPrecision,\n closingPrice: raw[\"Closing Price\"] ? parseNumber(raw[\"Closing Price\"]) : undefined,\n dateClosed,\n timeClosed: raw[\"Time Closed\"] || undefined,\n avgClosingCost: raw[\"Avg. Closing Cost\"] ? parseNumber(raw[\"Avg. Closing Cost\"]) : undefined,\n reasonForClose: raw[\"Reason For Close\"] || undefined,\n pl: parseNumber(raw[\"P/L\"]),\n numContracts: Math.round(parseNumber(raw[\"No. of Contracts\"], 1)),\n fundsAtClose: parseNumber(raw[\"Funds at Close\"]),\n marginReq: parseNumber(raw[\"Margin Req.\"]),\n strategy,\n openingCommissionsFees: parseNumber(\n raw[\"Opening Commissions + Fees\"] || raw[\"Opening comms & fees\"],\n 0,\n ),\n closingCommissionsFees: parseNumber(\n raw[\"Closing Commissions + Fees\"] || raw[\"Closing comms & fees\"],\n 0,\n ),\n openingShortLongRatio: parseNumber(raw[\"Opening Short/Long Ratio\"], 0),\n closingShortLongRatio: raw[\"Closing Short/Long Ratio\"]\n ? parseNumber(raw[\"Closing Short/Long Ratio\"])\n : undefined,\n openingVix: raw[\"Opening VIX\"] ? parseNumber(raw[\"Opening VIX\"]) : undefined,\n closingVix: raw[\"Closing VIX\"] ? parseNumber(raw[\"Closing VIX\"]) : undefined,\n gap: raw[\"Gap\"] ? parseNumber(raw[\"Gap\"]) : undefined,\n movement: raw[\"Movement\"] ? parseNumber(raw[\"Movement\"]) : undefined,\n maxProfit: raw[\"Max Profit\"] ? parseNumber(raw[\"Max Profit\"]) : undefined,\n maxLoss: raw[\"Max Loss\"] ? parseNumber(raw[\"Max Loss\"]) : undefined,\n };\n\n const customFields: Record<string, number | string> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (!KNOWN_TRADE_COLUMNS.has(key) && value !== undefined && value.trim() !== \"\") {\n const cleaned = value.replace(/[$,%]/g, \"\").trim();\n const parsed = parseFloat(cleaned);\n customFields[key] = !isNaN(parsed) && isFinite(parsed) ? parsed : value.trim();\n }\n }\n if (Object.keys(customFields).length > 0) {\n trade.customFields = customFields;\n }\n\n return trade;\n } catch {\n return null;\n }\n}\n\n/**\n * Convert raw CSV record to DailyLogEntry object\n */\nfunction convertToDailyLogEntry(\n raw: Record<string, string>,\n blockId?: string,\n): DailyLogEntry | null {\n try {\n const date = parseDatePreservingCalendarDay(raw[\"Date\"]);\n if (isNaN(date.getTime())) return null;\n\n return {\n date,\n netLiquidity: parseNumber(raw[\"Net Liquidity\"]),\n currentFunds: parseNumber(raw[\"Current Funds\"]),\n withdrawn: parseNumber(raw[\"Withdrawn\"], 0),\n tradingFunds: parseNumber(raw[\"Trading Funds\"]),\n dailyPl: parseNumber(raw[\"P/L\"]),\n dailyPlPct: parseNumber(raw[\"P/L %\"]),\n drawdownPct: parseNumber(raw[\"Drawdown %\"]),\n blockId,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Load trades from tradelog CSV file\n * @param blockPath - Path to the block directory\n * @param filename - CSV filename (default: \"tradelog.csv\")\n */\nasync function loadTrades(\n blockPath: string,\n filename: string = \"tradelog.csv\",\n blockId?: string,\n): Promise<Trade[]> {\n const tradelogPath = path.join(blockPath, filename);\n const content = await fs.readFile(tradelogPath, \"utf-8\");\n const records = parseCSV(content);\n\n const trades: Trade[] = [];\n for (const record of records) {\n const trade = convertToTrade(record, blockId);\n if (trade) {\n trades.push(trade);\n }\n }\n\n // Sort by date and time\n trades.sort((a, b) => {\n const dateCompare = new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime();\n if (dateCompare !== 0) return dateCompare;\n return a.timeOpened.localeCompare(b.timeOpened);\n });\n\n return trades;\n}\n\n/**\n * Load daily logs from dailylog CSV file (optional)\n * @param blockPath - Path to the block directory\n * @param blockId - Block identifier\n * @param filename - CSV filename (default: \"dailylog.csv\")\n */\nasync function loadDailyLogs(\n blockPath: string,\n blockId: string,\n filename: string = \"dailylog.csv\",\n): Promise<DailyLogEntry[] | undefined> {\n const dailylogPath = path.join(blockPath, filename);\n\n try {\n await fs.access(dailylogPath);\n const content = await fs.readFile(dailylogPath, \"utf-8\");\n const records = parseCSV(content);\n\n const entries: DailyLogEntry[] = [];\n for (const record of records) {\n const entry = convertToDailyLogEntry(record, blockId);\n if (entry) {\n entries.push(entry);\n }\n }\n\n // Sort by date\n entries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());\n\n return entries.length > 0 ? entries : undefined;\n } catch {\n // Daily log doesn't exist - that's fine\n return undefined;\n }\n}\n\n/**\n * Load a complete block (trades + optional daily logs).\n * Uses csv-discovery header sniffing for file resolution.\n */\nexport async function loadBlock(baseDir: string, blockId: string): Promise<LoadedBlock> {\n const blocksDir = resolveBlocksBaseDir(baseDir);\n const blockPath = path.join(blocksDir, blockId);\n\n // Discover CSV files via header sniffing\n const { mappings } = await discoverCsvFiles(blockPath);\n\n // Determine tradelog filename (from discovery or default)\n const tradelogFilename = mappings.tradelog || \"tradelog.csv\";\n const tradelogPath = path.join(blockPath, tradelogFilename);\n\n // Verify tradelog exists\n try {\n await fs.access(tradelogPath);\n } catch {\n throw new Error(`Block not found or missing tradelog: ${blockId}`);\n }\n\n // Determine dailylog filename\n const dailylogFilename = mappings.dailylog || \"dailylog.csv\";\n\n const trades = await loadTrades(blockPath, tradelogFilename, blockId);\n const dailyLogs = await loadDailyLogs(blockPath, blockId, dailylogFilename);\n\n return {\n blockId,\n trades,\n dailyLogs,\n };\n}\n\n/**\n * Helper to convert a DuckDB date value to a JS Date.\n * DuckDB may return Date objects, strings, or numeric day offsets.\n */\nfunction toDuckDbDate(val: unknown): Date | null {\n if (val == null) return null;\n if (val instanceof Date) return val;\n // DuckDB node-api returns DATE as {days: N} object (days since epoch)\n if (typeof val === \"object\" && val !== null && \"days\" in val) {\n return new Date((val as { days: number }).days * 86400000);\n }\n if (typeof val === \"number\") {\n // DuckDB DATE type returns days since epoch as a number\n return new Date(val * 86400000);\n }\n if (typeof val === \"string\") {\n // Try calendar-date parse first (YYYY-MM-DD)\n const match = val.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) {\n return new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]));\n }\n return new Date(val);\n }\n return null;\n}\n\n/**\n * List all valid blocks in the base directory.\n * Stats are computed from DuckDB (data synced by middleware before tool calls).\n * Also scans filesystem to include unsynced block folders.\n */\nexport async function listBlocks(baseDir: string): Promise<BlockInfo[]> {\n const blocks: BlockInfo[] = [];\n\n try {\n const conn = await getConnection(baseDir);\n const blocksDir = resolveBlocksBaseDir(baseDir);\n\n // Query 1: Trade stats per block from DuckDB\n // Safety filter: restrict to rows whose `source` is NULL or 'csv' (i.e., direct CSV\n // imports). Rows populated by any optional private extension live in a separate\n // attached DB and are ignored here (Phase b72). This WHERE clause prevents regression\n // if stale data lingers.\n const tradeStatsReader = await conn.runAndReadAll(`\n SELECT\n t.block_id,\n COUNT(*) as trade_count,\n MIN(t.date_opened) as min_date,\n MAX(t.date_opened) as max_date,\n SUM(t.pl) as total_pl,\n SUM(t.pl) - SUM(COALESCE(t.opening_commissions, 0) + COALESCE(t.closing_commissions, 0)) as net_pl\n FROM trades.trade_data t\n WHERE t.source IS NULL OR t.source = 'csv'\n GROUP BY t.block_id\n `);\n\n // Separate query for strategies (avoids ARRAY_AGG DuckDB node-api serialization issues)\n const strategiesReader = await conn.runAndReadAll(`\n SELECT block_id, strategy\n FROM (SELECT DISTINCT block_id, strategy FROM trades.trade_data WHERE strategy IS NOT NULL AND (source IS NULL OR source = 'csv'))\n ORDER BY block_id, strategy\n `);\n const strategiesByBlock = new Map<string, string[]>();\n for (const row of strategiesReader.getRows()) {\n const bid = row[0] as string;\n if (!strategiesByBlock.has(bid)) strategiesByBlock.set(bid, []);\n strategiesByBlock.get(bid)!.push(row[1] as string);\n }\n\n // Build a map of block_id -> trade stats\n const tradeStats = new Map<\n string,\n {\n tradeCount: number;\n strategies: string[];\n minDate: Date | null;\n maxDate: Date | null;\n totalPl: number;\n netPl: number;\n }\n >();\n\n for (const row of tradeStatsReader.getRows()) {\n const blockId = row[0] as string;\n const tradeCount = Number(row[1]);\n const minDate = toDuckDbDate(row[2]);\n const maxDate = toDuckDbDate(row[3]);\n const totalPl = Number(row[4]) || 0;\n const netPl = Number(row[5]) || 0;\n const strategies = strategiesByBlock.get(blockId) ?? [];\n\n tradeStats.set(blockId, {\n tradeCount,\n strategies,\n minDate,\n maxDate,\n totalPl,\n netPl,\n });\n }\n\n // Query 2: Reporting log summaries from DuckDB\n const reportingReader = await conn.runAndReadAll(`\n SELECT\n r.block_id,\n COUNT(*) as trade_count,\n COUNT(DISTINCT r.strategy) as strategy_count,\n SUM(r.pl) as total_pl,\n MIN(r.date_opened)::VARCHAR as min_date,\n MAX(r.date_opened)::VARCHAR as max_date\n FROM trades.reporting_data r\n GROUP BY r.block_id\n `);\n\n const reportingStats = new Map<\n string,\n {\n tradeCount: number;\n strategyCount: number;\n totalPL: number;\n minDate: string | null;\n maxDate: string | null;\n }\n >();\n\n for (const row of reportingReader.getRows()) {\n const blockId = row[0] as string;\n reportingStats.set(blockId, {\n tradeCount: Number(row[1]),\n strategyCount: Number(row[2]),\n totalPL: Number(row[3]) || 0,\n minDate: row[4] as string | null,\n maxDate: row[5] as string | null,\n });\n }\n\n // Query 3: Sync metadata to determine hasDailyLog/hasReportingLog\n const syncMeta = new Map<\n string,\n {\n hasDailyLog: boolean;\n hasReportingLog: boolean;\n }\n >();\n\n if (isParquetMode()) {\n // In Parquet mode, read .sync-meta.json files from blocksDir\n const metaEntries = await fs.readdir(blocksDir, { withFileTypes: true });\n for (const entry of metaEntries) {\n if (!entry.isDirectory() || entry.name.startsWith(\".\")) continue;\n const meta = await getSyncMetadataJson(entry.name, blocksDir);\n if (meta) {\n syncMeta.set(entry.name, {\n hasDailyLog: meta.dailylog_hash != null,\n hasReportingLog: meta.reportinglog_hash != null,\n });\n }\n }\n } else {\n // DuckDB path (existing code)\n const syncReader = await conn.runAndReadAll(`\n SELECT block_id, dailylog_hash, reportinglog_hash FROM trades._sync_metadata\n `);\n for (const row of syncReader.getRows()) {\n const blockId = row[0] as string;\n syncMeta.set(blockId, {\n hasDailyLog: row[1] != null,\n hasReportingLog: row[2] != null,\n });\n }\n }\n\n // Scan filesystem for block folders (some may not be synced yet)\n const entries = await fs.readdir(blocksDir, { withFileTypes: true });\n const blockFolders = new Set<string>();\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name.startsWith(\".\")) continue;\n blockFolders.add(entry.name);\n }\n\n // Also include blocks that are in DuckDB but might not be on filesystem anymore\n // (middleware handles deletion, but list should be consistent with DuckDB state)\n for (const blockId of tradeStats.keys()) {\n blockFolders.add(blockId);\n }\n\n // Build BlockInfo for each block\n for (const blockId of blockFolders) {\n const stats = tradeStats.get(blockId);\n const sync = syncMeta.get(blockId);\n const reporting = reportingStats.get(blockId);\n\n if (stats && stats.tradeCount > 0) {\n // Block has synced trade data in DuckDB\n const info: BlockInfo = {\n blockId,\n name: blockId,\n tradeCount: stats.tradeCount,\n hasDailyLog: sync?.hasDailyLog ?? false,\n hasReportingLog: sync?.hasReportingLog ?? false,\n dateRange: {\n start: stats.minDate,\n end: stats.maxDate,\n },\n strategies: stats.strategies,\n totalPl: stats.totalPl,\n netPl: stats.netPl,\n };\n\n // Add reporting log summary if available\n if (reporting) {\n info.reportingLog = {\n tradeCount: reporting.tradeCount,\n strategyCount: reporting.strategyCount,\n totalPL: reporting.totalPL,\n dateRange: {\n start: reporting.minDate,\n end: reporting.maxDate,\n },\n stale: false, // Data is synced fresh via middleware\n };\n }\n\n blocks.push(info);\n } else if (!stats) {\n // Block folder exists but has no synced data yet.\n // Check if it has CSVs (it will sync on next tool call via middleware).\n const blockPath = path.join(blocksDir, blockId);\n try {\n const { mappings } = await discoverCsvFiles(blockPath);\n if (mappings.tradelog) {\n // Has a tradelog CSV but not yet synced - include with zero stats\n blocks.push({\n blockId,\n name: blockId,\n tradeCount: 0,\n hasDailyLog: !!mappings.dailylog,\n hasReportingLog: !!mappings.reportinglog,\n dateRange: { start: null, end: null },\n strategies: [],\n totalPl: 0,\n netPl: 0,\n });\n }\n } catch {\n // Can't read folder - skip\n }\n }\n }\n\n // Sort by name\n blocks.sort((a, b) => a.name.localeCompare(b.name));\n\n return blocks;\n } catch (error) {\n throw new Error(`Failed to list blocks: ${(error as Error).message}`);\n }\n}\n\n/**\n * Normalize header names using column aliases\n */\nfunction normalizeRecordHeaders(raw: Record<string, string>): Record<string, string> {\n const normalized: Record<string, string> = { ...raw };\n Object.entries(REPORTING_TRADE_COLUMN_ALIASES).forEach(([alias, canonical]) => {\n if (normalized[alias] !== undefined) {\n normalized[canonical] = normalized[alias];\n delete normalized[alias];\n }\n });\n return normalized;\n}\n\n/**\n * Convert raw CSV record to ReportingTrade object\n */\nexport function convertToReportingTrade(raw: Record<string, string>): ReportingTrade | null {\n // Check if this is a TAT format row\n const keys = Object.keys(raw);\n if (isTatFormat(keys)) {\n return convertTatRowToReportingTrade(raw);\n }\n\n // Existing OO conversion logic below\n try {\n const normalized = normalizeRecordHeaders(raw);\n\n const dateOpened = parseDatePreservingCalendarDay(normalized[\"Date Opened\"]);\n if (isNaN(dateOpened.getTime())) return null;\n\n const dateClosed = normalized[\"Date Closed\"]\n ? parseDatePreservingCalendarDay(normalized[\"Date Closed\"])\n : undefined;\n\n const strategy = (normalized[\"Strategy\"] || \"\").trim() || \"Unknown\";\n\n return {\n strategy,\n dateOpened,\n timeOpened: normalized[\"Time Opened\"] || undefined,\n openingPrice: parseNumber(normalized[\"Opening Price\"]),\n legs: normalized[\"Legs\"] || \"\",\n initialPremium: parseNumber(normalized[\"Initial Premium\"]),\n numContracts: parseNumber(normalized[\"No. of Contracts\"], 1),\n pl: parseNumber(normalized[\"P/L\"]),\n closingPrice: normalized[\"Closing Price\"]\n ? parseNumber(normalized[\"Closing Price\"])\n : undefined,\n dateClosed,\n timeClosed: normalized[\"Time Closed\"] || undefined,\n avgClosingCost: normalized[\"Avg. Closing Cost\"]\n ? parseNumber(normalized[\"Avg. Closing Cost\"])\n : undefined,\n reasonForClose: normalized[\"Reason For Close\"] || undefined,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Load reporting log (actual trades) from reportinglog CSV.\n * Uses csv-discovery header sniffing for file resolution.\n * @throws Error if reportinglog CSV does not exist\n */\nexport async function loadReportingLog(\n baseDir: string,\n blockId: string,\n): Promise<ReportingTrade[]> {\n const blocksDir = resolveBlocksBaseDir(baseDir);\n const blockPath = path.join(blocksDir, blockId);\n\n // Discover CSV files via header sniffing\n const { mappings } = await discoverCsvFiles(blockPath);\n const filename = mappings.reportinglog || \"reportinglog.csv\";\n const reportingLogPath = path.join(blockPath, filename);\n\n // Check if file exists - throw if not\n try {\n await fs.access(reportingLogPath);\n } catch {\n throw new Error(`reportinglog.csv not found in block: ${blockId}`);\n }\n\n const content = await fs.readFile(reportingLogPath, \"utf-8\");\n const records = parseCSV(content);\n\n const trades: ReportingTrade[] = [];\n for (const record of records) {\n const trade = convertToReportingTrade(record);\n if (trade) {\n trades.push(trade);\n }\n }\n\n // Sort by date\n trades.sort((a, b) => new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime());\n\n return trades;\n}\n\n/**\n * Import CSV result\n */\nexport interface ImportCsvResult {\n blockId: string;\n name: string;\n csvType: CsvType;\n recordCount: number;\n dateRange: {\n start: string | null;\n end: string | null;\n };\n strategies: string[];\n blockPath: string;\n}\n\n/**\n * Import CSV options\n */\nexport interface ImportCsvOptions {\n /** Absolute path to the CSV file */\n csvPath: string;\n /** Name for the block */\n blockName: string;\n /** Type of CSV data */\n csvType?: \"tradelog\" | \"dailylog\" | \"reportinglog\";\n}\n\n/**\n * Convert a string to kebab-case for blockId\n */\nfunction toKebabCase(str: string): string {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1-$2\") // camelCase to kebab-case\n .replace(/[\\s_]+/g, \"-\") // spaces and underscores to hyphens\n .replace(/[^a-zA-Z0-9-]/g, \"\") // remove special characters\n .toLowerCase()\n .replace(/-+/g, \"-\") // collapse multiple hyphens\n .replace(/^-|-$/g, \"\"); // trim leading/trailing hyphens\n}\n\n/**\n * Validate CSV has required columns for the specified type\n */\nfunction validateCsvColumns(\n records: Record<string, string>[],\n csvType: \"tradelog\" | \"dailylog\" | \"reportinglog\",\n): { valid: boolean; error?: string } {\n if (records.length === 0) {\n return { valid: false, error: \"CSV file is empty or has no data rows\" };\n }\n\n const headers = Object.keys(records[0]);\n\n switch (csvType) {\n case \"tradelog\": {\n // Required columns for trade log\n const required = [\"Date Opened\", \"P/L\"];\n const missing = required.filter((col) => !headers.includes(col));\n if (missing.length > 0) {\n return {\n valid: false,\n error: `Missing required columns for tradelog: ${missing.join(\", \")}. Expected columns include: Date Opened, P/L, Strategy, Legs, etc.`,\n };\n }\n break;\n }\n case \"dailylog\": {\n // Required columns for daily log\n const required = [\"Date\", \"Net Liquidity\"];\n const missing = required.filter((col) => !headers.includes(col));\n if (missing.length > 0) {\n return {\n valid: false,\n error: `Missing required columns for dailylog: ${missing.join(\", \")}. Expected columns include: Date, Net Liquidity, P/L, etc.`,\n };\n }\n break;\n }\n case \"reportinglog\": {\n // Check for TAT format first (has TradeID, ProfitLoss, BuyingPower)\n if (isTatFormat(headers)) {\n break; // TAT format is valid, skip OO column checks\n }\n // Required columns for OO reporting log (with aliases)\n const dateOpenedAliases = [\"Date Opened\", \"date_opened\"];\n const plAliases = [\"P/L\", \"pl\"];\n const hasDateOpened = dateOpenedAliases.some((col) => headers.includes(col));\n const hasPl = plAliases.some((col) => headers.includes(col));\n const missing: string[] = [];\n if (!hasDateOpened) missing.push(\"Date Opened\");\n if (!hasPl) missing.push(\"P/L\");\n if (missing.length > 0) {\n return {\n valid: false,\n error: `Missing required columns for reportinglog: ${missing.join(\", \")}. Expected columns include: Date Opened (or date_opened), P/L (or pl), Strategy, etc.`,\n };\n }\n break;\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Import a CSV file into the blocks directory\n *\n * Requires local filesystem access. The MCP server must be running locally\n * (via npx tradeblocks-mcp or mcpb desktop extension) to access files.\n *\n * @param baseDir - Base directory for blocks\n * @param options - Import options: csvPath, blockName, csvType\n * @returns Import result with block info\n */\nexport async function importCsv(\n baseDir: string,\n options: ImportCsvOptions,\n): Promise<ImportCsvResult> {\n const { csvPath, blockName } = options;\n let { csvType = \"tradelog\" } = options;\n const blocksDir = resolveBlocksBaseDir(baseDir);\n\n // Validate source file exists\n try {\n await fs.access(csvPath);\n } catch {\n throw new Error(`CSV file not found: ${csvPath}`);\n }\n\n // Read and parse the CSV\n const content = await fs.readFile(csvPath, \"utf-8\");\n const records = parseCSV(content);\n\n // Auto-detect TAT format: if csvType is default \"tradelog\" but headers\n // match TAT signature, reclassify as \"reportinglog\"\n if (csvType === \"tradelog\" && records.length > 0) {\n const headers = Object.keys(records[0]);\n if (isTatFormat(headers)) {\n csvType = \"reportinglog\";\n }\n }\n\n // Validate CSV has required columns\n const validation = validateCsvColumns(records, csvType);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n\n // Convert blockName to kebab-case for blockId\n const name = blockName;\n const blockId = toKebabCase(name);\n\n if (!blockId) {\n throw new Error(\"Could not derive a valid block ID from the filename or provided name\");\n }\n\n // Check if block already exists\n const blockPath = path.join(blocksDir, blockId);\n try {\n await fs.access(blockPath);\n throw new Error(\n `Block \"${blockId}\" already exists. Use a different blockName or delete the existing block first.`,\n );\n } catch (error) {\n // Directory doesn't exist - good, we can create it\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error; // Re-throw if it's not a \"not found\" error\n }\n }\n\n // Create block directory\n await fs.mkdir(blockPath, { recursive: true });\n\n // Determine target filename\n const targetFilename =\n csvType === \"tradelog\"\n ? \"tradelog.csv\"\n : csvType === \"dailylog\"\n ? \"dailylog.csv\"\n : \"reportinglog.csv\";\n\n // Copy CSV to block directory\n const targetPath = path.join(blockPath, targetFilename);\n await fs.copyFile(csvPath, targetPath);\n\n // Extract metadata for return value based on CSV type\n let dateRange: { start: string | null; end: string | null } = {\n start: null,\n end: null,\n };\n let strategies: string[] = [];\n\n if (csvType === \"tradelog\") {\n // Parse trades to extract metadata\n const trades: Trade[] = [];\n for (const record of records) {\n const trade = convertToTrade(record, blockName);\n if (trade) trades.push(trade);\n }\n\n if (trades.length > 0) {\n const dates = trades.map((t) => new Date(t.dateOpened).getTime());\n dateRange = {\n start: new Date(Math.min(...dates)).toISOString(),\n end: new Date(Math.max(...dates)).toISOString(),\n };\n strategies = Array.from(new Set(trades.map((t) => t.strategy))).sort();\n }\n } else if (csvType === \"dailylog\") {\n // Parse daily logs to extract date range\n const entries: DailyLogEntry[] = [];\n for (const record of records) {\n const entry = convertToDailyLogEntry(record, blockId);\n if (entry) entries.push(entry);\n }\n\n if (entries.length > 0) {\n const dates = entries.map((e) => new Date(e.date).getTime());\n dateRange = {\n start: new Date(Math.min(...dates)).toISOString(),\n end: new Date(Math.max(...dates)).toISOString(),\n };\n }\n } else if (csvType === \"reportinglog\") {\n // Parse reporting trades to extract metadata\n const trades: ReportingTrade[] = [];\n for (const record of records) {\n const trade = convertToReportingTrade(record);\n if (trade) trades.push(trade);\n }\n\n if (trades.length > 0) {\n const dates = trades.map((t) => new Date(t.dateOpened).getTime());\n dateRange = {\n start: new Date(Math.min(...dates)).toISOString(),\n end: new Date(Math.max(...dates)).toISOString(),\n };\n strategies = Array.from(new Set(trades.map((t) => t.strategy))).sort();\n }\n }\n\n return {\n blockId,\n name,\n csvType,\n recordCount: records.length,\n dateRange,\n strategies,\n blockPath,\n };\n}\n","/**\n * CSV Discovery & Type Detection\n *\n * Shared module for detecting CSV file types (tradelog, dailylog, reportinglog)\n * by examining column headers. Used by both block-loader.ts and block-sync.ts.\n *\n * This module must NOT import from block-loader.ts to avoid circular dependencies.\n */\n\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport { REPORTING_TRADE_COLUMN_ALIASES } from \"@tradeblocks/lib\";\n\n/**\n * CSV file mappings for flexible discovery\n */\nexport interface CsvMappings {\n tradelog?: string;\n dailylog?: string;\n reportinglog?: string;\n}\n\n/**\n * CSV type detection result\n */\nexport type CsvType = \"tradelog\" | \"dailylog\" | \"reportinglog\" | null;\n\n/**\n * Parse a single CSV line handling quoted fields\n */\nfunction parseCSVLine(line: string): string[] {\n const result: string[] = [];\n let current = \"\";\n let inQuotes = false;\n\n for (let i = 0; i < line.length; i++) {\n const char = line[i];\n\n if (char === '\"') {\n if (inQuotes && line[i + 1] === '\"') {\n current += '\"';\n i++;\n } else {\n inQuotes = !inQuotes;\n }\n } else if (char === \",\" && !inQuotes) {\n result.push(current.trim());\n current = \"\";\n } else {\n current += char;\n }\n }\n\n result.push(current.trim());\n return result;\n}\n\n/**\n * Read just the header line from a CSV file (for detection)\n */\nasync function readCsvHeaders(filePath: string): Promise<string[]> {\n const content = await fs.readFile(filePath, \"utf-8\");\n const firstLine = content.replace(/^\\uFEFF/, \"\").split(\"\\n\")[0] || \"\";\n return parseCSVLine(firstLine).map((h) => h.toLowerCase().trim());\n}\n\n/**\n * Detect CSV type by examining column headers.\n * Returns the detected type or null if unrecognized.\n */\nexport async function detectCsvType(filePath: string): Promise<CsvType> {\n try {\n const headers = await readCsvHeaders(filePath);\n\n // Trade log detection:\n // Required: \"P/L\" or \"P&L\" or \"Profit/Loss\"\n // Plus at least 2 of: \"Date Opened\", \"Date Closed\", \"Symbol\", \"Strategy\", \"Contracts\", \"Premium\"\n const plColumnAliases = [\"p/l\", \"p&l\", \"profit/loss\", \"pl\"];\n const tradeOptionalColumns = [\n \"date opened\",\n \"date closed\",\n \"symbol\",\n \"strategy\",\n \"contracts\",\n \"no. of contracts\",\n \"premium\",\n \"legs\",\n ];\n\n const hasPl = plColumnAliases.some((alias) => headers.includes(alias));\n // Match trade columns - require header to contain the full column pattern\n const matchedTradeColumns = tradeOptionalColumns.filter((col) =>\n headers.some((h) => h.includes(col)),\n );\n\n if (hasPl && matchedTradeColumns.length >= 2) {\n // Before classifying as tradelog, check if this looks like a reporting log.\n // Option Omega reporting exports share P/L + Date Opened + Strategy + Contracts\n // but also have \"Initial Premium\" (or aliases) which tradelogs use \"Premium\" instead.\n const reportingOnlyColumns = [\"initial premium\", \"initial credit\", \"initial premium ($)\"];\n const hasReportingOnly = reportingOnlyColumns.some((col) =>\n headers.some((h) => h.includes(col)),\n );\n if (hasReportingOnly) {\n return \"reportinglog\";\n }\n return \"tradelog\";\n }\n\n // Daily log detection:\n // Required: \"Date\" (but not \"Date Opened\"/\"Date Closed\"), and value column\n const hasSimpleDate = headers.some(\n (h) => h === \"date\" || (h.includes(\"date\") && !h.includes(\"opened\") && !h.includes(\"closed\")),\n );\n const valueColumnAliases = [\n \"portfolio value\",\n \"value\",\n \"equity\",\n \"net liquidity\",\n \"netliquidity\",\n ];\n const hasValue = valueColumnAliases.some((alias) =>\n headers.some((h) => h.includes(alias) || alias.includes(h)),\n );\n\n // Dailylog: has date + value columns but lacks trade-specific columns\n if (hasSimpleDate && hasValue && matchedTradeColumns.length < 2) {\n return \"dailylog\";\n }\n\n // TAT (Trade Automation Toolbox) detection:\n // Has \"TradeID\" AND \"ProfitLoss\" AND \"BuyingPower\"\n const tatSignature = [\"tradeid\", \"profitloss\", \"buyingpower\"];\n const isTat = tatSignature.every((sig) => headers.includes(sig));\n if (isTat) {\n return \"reportinglog\";\n }\n\n // Reporting log detection:\n // Has \"Actual P/L\" or columns from REPORTING_TRADE_COLUMN_ALIASES\n const reportingAliases = Object.keys(REPORTING_TRADE_COLUMN_ALIASES).map((k) =>\n k.toLowerCase(),\n );\n const hasReportingColumns = reportingAliases.some((alias) => headers.includes(alias));\n const hasActualPl = headers.some((h) => h.includes(\"actual\") && h.includes(\"p\"));\n const hasReportedStyle =\n headers.includes(\"trade id\") || headers.some((h) => h.includes(\"reported\"));\n\n if (hasActualPl || hasReportingColumns || hasReportedStyle) {\n // Double-check it's not a regular tradelog\n if (!hasPl || hasActualPl) {\n return \"reportinglog\";\n }\n }\n\n // If we have P/L and trade columns, fallback to tradelog\n if (hasPl && matchedTradeColumns.length >= 1) {\n return \"tradelog\";\n }\n\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Discover CSV files in a folder and detect their types.\n * Returns mapping of detected CSV types to filenames.\n */\nexport async function discoverCsvFiles(\n folderPath: string,\n): Promise<{ mappings: CsvMappings; unrecognized: string[] }> {\n const mappings: CsvMappings = {};\n const unrecognized: string[] = [];\n\n try {\n const entries = await fs.readdir(folderPath, { withFileTypes: true });\n const csvFiles = entries\n .filter((e) => e.isFile() && e.name.toLowerCase().endsWith(\".csv\"))\n .map((e) => e.name);\n\n // First, check for exact standard names\n if (csvFiles.includes(\"tradelog.csv\")) {\n mappings.tradelog = \"tradelog.csv\";\n }\n if (csvFiles.includes(\"dailylog.csv\")) {\n mappings.dailylog = \"dailylog.csv\";\n }\n if (csvFiles.includes(\"reportinglog.csv\")) {\n mappings.reportinglog = \"reportinglog.csv\";\n }\n\n // Second, check for filename patterns (before content detection)\n if (!mappings.reportinglog) {\n const strategyLogFile = csvFiles.find((f) => {\n const lower = f.toLowerCase();\n return (\n lower.includes(\"strategy-trade-log\") ||\n lower.includes(\"strategylog\") ||\n lower.startsWith(\"strategy-log\")\n );\n });\n if (strategyLogFile) {\n mappings.reportinglog = strategyLogFile;\n }\n }\n\n // For any remaining CSVs, detect by content\n for (const csvFile of csvFiles) {\n // Skip if already mapped via exact name or filename pattern\n if (\n csvFile === \"tradelog.csv\" ||\n csvFile === \"dailylog.csv\" ||\n csvFile === \"reportinglog.csv\" ||\n csvFile === mappings.reportinglog\n ) {\n continue;\n }\n\n const csvPath = path.join(folderPath, csvFile);\n const detectedType = await detectCsvType(csvPath);\n\n if (detectedType) {\n // Only assign if we haven't found this type yet\n if (detectedType === \"tradelog\" && !mappings.tradelog) {\n mappings.tradelog = csvFile;\n } else if (detectedType === \"dailylog\" && !mappings.dailylog) {\n mappings.dailylog = csvFile;\n } else if (detectedType === \"reportinglog\" && !mappings.reportinglog) {\n mappings.reportinglog = csvFile;\n } else {\n // Type already found, this is an extra CSV\n unrecognized.push(csvFile);\n }\n } else {\n unrecognized.push(csvFile);\n }\n }\n } catch {\n // Folder read error - return empty\n }\n\n return { mappings, unrecognized };\n}\n\n/**\n * Log warning when folder has CSVs but none match expected patterns\n */\nexport function logCsvDiscoveryWarning(folderName: string, csvFiles: string[]): void {\n console.error(\n `Warning: Folder '${folderName}' has CSV files but none match expected trade log format.`,\n );\n console.error(` Found: ${csvFiles.join(\", \")}`);\n console.error(` Expected columns: P/L, Date Opened, Date Closed, Symbol, Strategy`);\n}\n"],"mappings":"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","names":["existsSync","fs","path","fs","path","path","fs","path","fs","path","profiles","path","hasColumn","path","toDate","stdout","ppid","command","resolve","fs","path","fs","fs","path","fs","path","parseCSVLine","parseCSV","parseCSVLine","existsSync"]}