observability-toolkit 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1285) hide show
  1. package/README.md +166 -398
  2. package/dist/__tests__/find-constant-dedup.test.d.ts +11 -0
  3. package/dist/__tests__/find-constant-dedup.test.d.ts.map +1 -0
  4. package/dist/__tests__/find-constant-dedup.test.js +132 -0
  5. package/dist/__tests__/find-constant-dedup.test.js.map +1 -0
  6. package/dist/backends/backend-schemas.d.ts +309 -0
  7. package/dist/backends/backend-schemas.d.ts.map +1 -0
  8. package/dist/backends/backend-schemas.js +215 -0
  9. package/dist/backends/backend-schemas.js.map +1 -0
  10. package/dist/backends/cloud.d.ts +46 -0
  11. package/dist/backends/cloud.d.ts.map +1 -0
  12. package/dist/backends/cloud.js +520 -0
  13. package/dist/backends/cloud.js.map +1 -0
  14. package/dist/backends/cloud.test.d.ts +2 -0
  15. package/dist/backends/cloud.test.d.ts.map +1 -0
  16. package/dist/backends/cloud.test.js +436 -0
  17. package/dist/backends/cloud.test.js.map +1 -0
  18. package/dist/backends/index.d.ts +659 -386
  19. package/dist/backends/index.d.ts.map +1 -1
  20. package/dist/backends/index.js +318 -41
  21. package/dist/backends/index.js.map +1 -1
  22. package/dist/backends/index.test.js +578 -57
  23. package/dist/backends/index.test.js.map +1 -1
  24. package/dist/backends/local-jsonl-boolean-search.test.js +8 -7
  25. package/dist/backends/local-jsonl-boolean-search.test.js.map +1 -1
  26. package/dist/backends/local-jsonl-cache.test.js +33 -31
  27. package/dist/backends/local-jsonl-cache.test.js.map +1 -1
  28. package/dist/backends/local-jsonl-circuit-breaker.test.js +9 -7
  29. package/dist/backends/local-jsonl-circuit-breaker.test.js.map +1 -1
  30. package/dist/backends/local-jsonl-export.test.js +73 -58
  31. package/dist/backends/local-jsonl-export.test.js.map +1 -1
  32. package/dist/backends/local-jsonl-index.test.js +52 -50
  33. package/dist/backends/local-jsonl-index.test.js.map +1 -1
  34. package/dist/backends/local-jsonl-logs.test.js +47 -31
  35. package/dist/backends/local-jsonl-logs.test.js.map +1 -1
  36. package/dist/backends/local-jsonl-metrics.test.js +85 -82
  37. package/dist/backends/local-jsonl-metrics.test.js.map +1 -1
  38. package/dist/backends/local-jsonl-otlp-unwrap.test.d.ts +2 -0
  39. package/dist/backends/local-jsonl-otlp-unwrap.test.d.ts.map +1 -0
  40. package/dist/backends/local-jsonl-otlp-unwrap.test.js +602 -0
  41. package/dist/backends/local-jsonl-otlp-unwrap.test.js.map +1 -0
  42. package/dist/backends/local-jsonl-traces.test.js +161 -147
  43. package/dist/backends/local-jsonl-traces.test.js.map +1 -1
  44. package/dist/backends/local-jsonl.d.ts +37 -8
  45. package/dist/backends/local-jsonl.d.ts.map +1 -1
  46. package/dist/backends/local-jsonl.js +1088 -241
  47. package/dist/backends/local-jsonl.js.map +1 -1
  48. package/dist/backends/shared.d.ts +9 -0
  49. package/dist/backends/shared.d.ts.map +1 -0
  50. package/dist/backends/shared.js +9 -0
  51. package/dist/backends/shared.js.map +1 -0
  52. package/dist/generated/opentelemetry/proto/collector/logs/v1/logs_service_pb.d.ts +40 -0
  53. package/dist/generated/opentelemetry/proto/collector/logs/v1/logs_service_pb.d.ts.map +1 -0
  54. package/dist/generated/opentelemetry/proto/collector/logs/v1/logs_service_pb.js +27 -0
  55. package/dist/generated/opentelemetry/proto/collector/logs/v1/logs_service_pb.js.map +1 -0
  56. package/dist/generated/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.d.ts +106 -0
  57. package/dist/generated/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.d.ts.map +1 -0
  58. package/dist/generated/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.js +43 -0
  59. package/dist/generated/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.js.map +1 -0
  60. package/dist/generated/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb.d.ts +111 -0
  61. package/dist/generated/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb.d.ts.map +1 -0
  62. package/dist/generated/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb.js +42 -0
  63. package/dist/generated/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb.js.map +1 -0
  64. package/dist/generated/opentelemetry/proto/collector/trace/v1/trace_service_pb.d.ts +106 -0
  65. package/dist/generated/opentelemetry/proto/collector/trace/v1/trace_service_pb.d.ts.map +1 -0
  66. package/dist/generated/opentelemetry/proto/collector/trace/v1/trace_service_pb.js +43 -0
  67. package/dist/generated/opentelemetry/proto/collector/trace/v1/trace_service_pb.js.map +1 -0
  68. package/dist/generated/opentelemetry/proto/common/v1/common_pb.d.ts +243 -0
  69. package/dist/generated/opentelemetry/proto/common/v1/common_pb.d.ts.map +1 -0
  70. package/dist/generated/opentelemetry/proto/common/v1/common_pb.js +49 -0
  71. package/dist/generated/opentelemetry/proto/common/v1/common_pb.js.map +1 -0
  72. package/dist/generated/opentelemetry/proto/logs/v1/logs_pb.d.ts +90 -0
  73. package/dist/generated/opentelemetry/proto/logs/v1/logs_pb.d.ts.map +1 -0
  74. package/dist/generated/opentelemetry/proto/logs/v1/logs_pb.js +66 -0
  75. package/dist/generated/opentelemetry/proto/logs/v1/logs_pb.js.map +1 -0
  76. package/dist/generated/opentelemetry/proto/metrics/v1/metrics_pb.d.ts +1134 -0
  77. package/dist/generated/opentelemetry/proto/metrics/v1/metrics_pb.d.ts.map +1 -0
  78. package/dist/generated/opentelemetry/proto/metrics/v1/metrics_pb.js +223 -0
  79. package/dist/generated/opentelemetry/proto/metrics/v1/metrics_pb.js.map +1 -0
  80. package/dist/generated/opentelemetry/proto/profiles/v1development/profiles_pb.d.ts +678 -0
  81. package/dist/generated/opentelemetry/proto/profiles/v1development/profiles_pb.d.ts.map +1 -0
  82. package/dist/generated/opentelemetry/proto/profiles/v1development/profiles_pb.js +107 -0
  83. package/dist/generated/opentelemetry/proto/profiles/v1development/profiles_pb.js.map +1 -0
  84. package/dist/generated/opentelemetry/proto/resource/v1/resource_pb.d.ts +46 -0
  85. package/dist/generated/opentelemetry/proto/resource/v1/resource_pb.d.ts.map +1 -0
  86. package/dist/generated/opentelemetry/proto/resource/v1/resource_pb.js +25 -0
  87. package/dist/generated/opentelemetry/proto/resource/v1/resource_pb.js.map +1 -0
  88. package/dist/generated/opentelemetry/proto/trace/v1/trace_pb.d.ts +569 -0
  89. package/dist/generated/opentelemetry/proto/trace/v1/trace_pb.d.ts.map +1 -0
  90. package/dist/generated/opentelemetry/proto/trace/v1/trace_pb.js +195 -0
  91. package/dist/generated/opentelemetry/proto/trace/v1/trace_pb.js.map +1 -0
  92. package/dist/lib/agent-judge/agent-as-judge.d.ts +157 -0
  93. package/dist/lib/agent-judge/agent-as-judge.d.ts.map +1 -0
  94. package/dist/lib/agent-judge/agent-as-judge.js +137 -0
  95. package/dist/lib/agent-judge/agent-as-judge.js.map +1 -0
  96. package/dist/lib/agent-judge/agent-as-judge.test.d.ts.map +1 -0
  97. package/dist/lib/agent-judge/agent-as-judge.test.js +839 -0
  98. package/dist/lib/agent-judge/agent-as-judge.test.js.map +1 -0
  99. package/dist/lib/agent-judge/agent-eval-metrics.d.ts +293 -0
  100. package/dist/lib/agent-judge/agent-eval-metrics.d.ts.map +1 -0
  101. package/dist/lib/agent-judge/agent-eval-metrics.js +715 -0
  102. package/dist/lib/agent-judge/agent-eval-metrics.js.map +1 -0
  103. package/dist/lib/agent-judge/agent-eval-metrics.test.d.ts +5 -0
  104. package/dist/lib/agent-judge/agent-eval-metrics.test.d.ts.map +1 -0
  105. package/dist/lib/agent-judge/agent-eval-metrics.test.js +676 -0
  106. package/dist/lib/agent-judge/agent-eval-metrics.test.js.map +1 -0
  107. package/dist/lib/agent-judge/agent-judge-classes.d.ts +95 -0
  108. package/dist/lib/agent-judge/agent-judge-classes.d.ts.map +1 -0
  109. package/dist/lib/agent-judge/agent-judge-classes.js +222 -0
  110. package/dist/lib/agent-judge/agent-judge-classes.js.map +1 -0
  111. package/dist/lib/agent-judge/agent-judge-classes.test.d.ts +6 -0
  112. package/dist/lib/agent-judge/agent-judge-classes.test.d.ts.map +1 -0
  113. package/dist/lib/agent-judge/agent-judge-classes.test.js +271 -0
  114. package/dist/lib/agent-judge/agent-judge-classes.test.js.map +1 -0
  115. package/dist/lib/agent-judge/agent-judge-consensus.d.ts +58 -0
  116. package/dist/lib/agent-judge/agent-judge-consensus.d.ts.map +1 -0
  117. package/dist/lib/agent-judge/agent-judge-consensus.js +149 -0
  118. package/dist/lib/agent-judge/agent-judge-consensus.js.map +1 -0
  119. package/dist/lib/agent-judge/agent-judge-consensus.test.d.ts +2 -0
  120. package/dist/lib/agent-judge/agent-judge-consensus.test.d.ts.map +1 -0
  121. package/dist/lib/agent-judge/agent-judge-consensus.test.js +170 -0
  122. package/dist/lib/agent-judge/agent-judge-consensus.test.js.map +1 -0
  123. package/dist/lib/agent-judge/agent-judge-verification.d.ts +89 -0
  124. package/dist/lib/agent-judge/agent-judge-verification.d.ts.map +1 -0
  125. package/dist/lib/agent-judge/agent-judge-verification.js +235 -0
  126. package/dist/lib/agent-judge/agent-judge-verification.js.map +1 -0
  127. package/dist/lib/agent-judge/agent-judge-verification.test.d.ts +5 -0
  128. package/dist/lib/agent-judge/agent-judge-verification.test.d.ts.map +1 -0
  129. package/dist/lib/agent-judge/agent-judge-verification.test.js +399 -0
  130. package/dist/lib/agent-judge/agent-judge-verification.test.js.map +1 -0
  131. package/dist/lib/audit/agent-auditor-scoring.d.ts +167 -0
  132. package/dist/lib/audit/agent-auditor-scoring.d.ts.map +1 -0
  133. package/dist/lib/audit/agent-auditor-scoring.js +338 -0
  134. package/dist/lib/audit/agent-auditor-scoring.js.map +1 -0
  135. package/dist/lib/audit/agent-auditor-scoring.test.d.ts +2 -0
  136. package/dist/lib/audit/agent-auditor-scoring.test.d.ts.map +1 -0
  137. package/dist/lib/audit/agent-auditor-scoring.test.js +576 -0
  138. package/dist/lib/audit/agent-auditor-scoring.test.js.map +1 -0
  139. package/dist/lib/audit/audit-record.d.ts +139 -0
  140. package/dist/lib/audit/audit-record.d.ts.map +1 -0
  141. package/dist/lib/audit/audit-record.js +288 -0
  142. package/dist/lib/audit/audit-record.js.map +1 -0
  143. package/dist/lib/audit/audit-record.test.d.ts +5 -0
  144. package/dist/lib/audit/audit-record.test.d.ts.map +1 -0
  145. package/dist/lib/audit/audit-record.test.js +258 -0
  146. package/dist/lib/audit/audit-record.test.js.map +1 -0
  147. package/dist/lib/audit/audit-scoring-constants.d.ts +57 -0
  148. package/dist/lib/audit/audit-scoring-constants.d.ts.map +1 -0
  149. package/dist/lib/audit/audit-scoring-constants.js +59 -0
  150. package/dist/lib/audit/audit-scoring-constants.js.map +1 -0
  151. package/dist/lib/audit/compliance-report.d.ts +125 -0
  152. package/dist/lib/audit/compliance-report.d.ts.map +1 -0
  153. package/dist/lib/audit/compliance-report.js +205 -0
  154. package/dist/lib/audit/compliance-report.js.map +1 -0
  155. package/dist/lib/audit/compliance-report.test.d.ts +5 -0
  156. package/dist/lib/audit/compliance-report.test.d.ts.map +1 -0
  157. package/dist/lib/audit/compliance-report.test.js +290 -0
  158. package/dist/lib/audit/compliance-report.test.js.map +1 -0
  159. package/dist/lib/audit/retention-guard.d.ts +41 -0
  160. package/dist/lib/audit/retention-guard.d.ts.map +1 -0
  161. package/dist/lib/audit/retention-guard.js +103 -0
  162. package/dist/lib/audit/retention-guard.js.map +1 -0
  163. package/dist/lib/audit/retention-guard.test.d.ts +5 -0
  164. package/dist/lib/audit/retention-guard.test.d.ts.map +1 -0
  165. package/dist/lib/audit/retention-guard.test.js +109 -0
  166. package/dist/lib/audit/retention-guard.test.js.map +1 -0
  167. package/dist/lib/audit/skill-auditor-scoring.d.ts +69 -0
  168. package/dist/lib/audit/skill-auditor-scoring.d.ts.map +1 -0
  169. package/dist/lib/audit/skill-auditor-scoring.js +149 -0
  170. package/dist/lib/audit/skill-auditor-scoring.js.map +1 -0
  171. package/dist/lib/audit/skill-auditor-scoring.test.d.ts +2 -0
  172. package/dist/lib/audit/skill-auditor-scoring.test.d.ts.map +1 -0
  173. package/dist/lib/audit/skill-auditor-scoring.test.js +369 -0
  174. package/dist/lib/audit/skill-auditor-scoring.test.js.map +1 -0
  175. package/dist/lib/audit/verification-events.d.ts +119 -0
  176. package/dist/lib/audit/verification-events.d.ts.map +1 -0
  177. package/dist/lib/audit/verification-events.js +175 -0
  178. package/dist/lib/audit/verification-events.js.map +1 -0
  179. package/dist/lib/audit/verification-events.test.d.ts.map +1 -0
  180. package/dist/lib/audit/verification-events.test.js +197 -0
  181. package/dist/lib/audit/verification-events.test.js.map +1 -0
  182. package/dist/lib/core/constants-models.d.ts +90 -0
  183. package/dist/lib/core/constants-models.d.ts.map +1 -0
  184. package/dist/lib/core/constants-models.js +208 -0
  185. package/dist/lib/core/constants-models.js.map +1 -0
  186. package/dist/lib/core/constants-otel.d.ts +68 -0
  187. package/dist/lib/core/constants-otel.d.ts.map +1 -0
  188. package/dist/lib/core/constants-otel.js +128 -0
  189. package/dist/lib/core/constants-otel.js.map +1 -0
  190. package/dist/lib/core/constants-symlink.test.d.ts.map +1 -0
  191. package/dist/lib/core/constants-symlink.test.js +358 -0
  192. package/dist/lib/core/constants-symlink.test.js.map +1 -0
  193. package/dist/lib/core/constants-telemetry.d.ts +21 -0
  194. package/dist/lib/core/constants-telemetry.d.ts.map +1 -0
  195. package/dist/lib/core/constants-telemetry.js +162 -0
  196. package/dist/lib/core/constants-telemetry.js.map +1 -0
  197. package/dist/lib/core/constants.d.ts +152 -0
  198. package/dist/lib/core/constants.d.ts.map +1 -0
  199. package/dist/lib/core/constants.js +223 -0
  200. package/dist/lib/core/constants.js.map +1 -0
  201. package/dist/lib/core/constants.test.d.ts.map +1 -0
  202. package/dist/lib/core/constants.test.js +833 -0
  203. package/dist/lib/core/constants.test.js.map +1 -0
  204. package/dist/lib/core/doc-sync.test.d.ts +9 -0
  205. package/dist/lib/core/doc-sync.test.d.ts.map +1 -0
  206. package/dist/lib/core/doc-sync.test.js +159 -0
  207. package/dist/lib/core/doc-sync.test.js.map +1 -0
  208. package/dist/lib/core/edge-cases.test.d.ts.map +1 -0
  209. package/dist/lib/core/edge-cases.test.js +637 -0
  210. package/dist/lib/core/edge-cases.test.js.map +1 -0
  211. package/dist/lib/core/file-utils.d.ts +360 -0
  212. package/dist/lib/core/file-utils.d.ts.map +1 -0
  213. package/dist/lib/core/file-utils.js +890 -0
  214. package/dist/lib/core/file-utils.js.map +1 -0
  215. package/dist/lib/core/file-utils.test-constants.d.ts +38 -0
  216. package/dist/lib/core/file-utils.test-constants.d.ts.map +1 -0
  217. package/dist/lib/core/file-utils.test-constants.js +40 -0
  218. package/dist/lib/core/file-utils.test-constants.js.map +1 -0
  219. package/dist/lib/core/file-utils.test.d.ts.map +1 -0
  220. package/dist/lib/core/file-utils.test.js +1329 -0
  221. package/dist/lib/core/file-utils.test.js.map +1 -0
  222. package/dist/lib/core/input-validator.d.ts +125 -0
  223. package/dist/lib/core/input-validator.d.ts.map +1 -0
  224. package/dist/lib/core/input-validator.fuzz.test.d.ts.map +1 -0
  225. package/dist/lib/core/input-validator.fuzz.test.js +302 -0
  226. package/dist/lib/core/input-validator.fuzz.test.js.map +1 -0
  227. package/dist/lib/core/input-validator.js +348 -0
  228. package/dist/lib/core/input-validator.js.map +1 -0
  229. package/dist/lib/core/input-validator.test.d.ts.map +1 -0
  230. package/dist/lib/core/input-validator.test.js +465 -0
  231. package/dist/lib/core/input-validator.test.js.map +1 -0
  232. package/dist/lib/core/logger.d.ts +32 -0
  233. package/dist/lib/core/logger.d.ts.map +1 -0
  234. package/dist/lib/core/logger.js +104 -0
  235. package/dist/lib/core/logger.js.map +1 -0
  236. package/dist/lib/core/logger.test.d.ts.map +1 -0
  237. package/dist/lib/core/logger.test.js.map +1 -0
  238. package/dist/lib/core/schema-types.d.ts +37 -0
  239. package/dist/lib/core/schema-types.d.ts.map +1 -0
  240. package/dist/lib/core/schema-types.js +29 -0
  241. package/dist/lib/core/schema-types.js.map +1 -0
  242. package/dist/lib/core/server-utils.d.ts +98 -0
  243. package/dist/lib/core/server-utils.d.ts.map +1 -0
  244. package/dist/lib/core/server-utils.js +193 -0
  245. package/dist/lib/core/server-utils.js.map +1 -0
  246. package/dist/lib/core/shared-schemas.d.ts +301 -0
  247. package/dist/lib/core/shared-schemas.d.ts.map +1 -0
  248. package/dist/lib/core/shared-schemas.js +222 -0
  249. package/dist/lib/core/shared-schemas.js.map +1 -0
  250. package/dist/lib/core/shared-schemas.test.d.ts.map +1 -0
  251. package/dist/lib/core/shared-schemas.test.js +136 -0
  252. package/dist/lib/core/shared-schemas.test.js.map +1 -0
  253. package/dist/lib/core/units.d.ts +67 -0
  254. package/dist/lib/core/units.d.ts.map +1 -0
  255. package/dist/lib/core/units.js +88 -0
  256. package/dist/lib/core/units.js.map +1 -0
  257. package/dist/lib/cost/cost-estimation.d.ts +264 -0
  258. package/dist/lib/cost/cost-estimation.d.ts.map +1 -0
  259. package/dist/lib/cost/cost-estimation.js +541 -0
  260. package/dist/lib/cost/cost-estimation.js.map +1 -0
  261. package/dist/lib/cost/cost-estimation.test.d.ts +5 -0
  262. package/dist/lib/cost/cost-estimation.test.d.ts.map +1 -0
  263. package/dist/lib/cost/cost-estimation.test.js +701 -0
  264. package/dist/lib/cost/cost-estimation.test.js.map +1 -0
  265. package/dist/lib/cost/pricing-cache.d.ts +59 -0
  266. package/dist/lib/cost/pricing-cache.d.ts.map +1 -0
  267. package/dist/lib/cost/pricing-cache.js +120 -0
  268. package/dist/lib/cost/pricing-cache.js.map +1 -0
  269. package/dist/lib/cost/pricing-cache.test.d.ts +5 -0
  270. package/dist/lib/cost/pricing-cache.test.d.ts.map +1 -0
  271. package/dist/lib/cost/pricing-cache.test.js +176 -0
  272. package/dist/lib/cost/pricing-cache.test.js.map +1 -0
  273. package/dist/lib/dashboard-file-utils.d.ts +35 -0
  274. package/dist/lib/dashboard-file-utils.d.ts.map +1 -0
  275. package/dist/lib/dashboard-file-utils.js +94 -0
  276. package/dist/lib/dashboard-file-utils.js.map +1 -0
  277. package/dist/lib/errors/error-sanitizer.d.ts +62 -0
  278. package/dist/lib/errors/error-sanitizer.d.ts.map +1 -0
  279. package/dist/lib/errors/error-sanitizer.js +235 -0
  280. package/dist/lib/errors/error-sanitizer.js.map +1 -0
  281. package/dist/lib/errors/error-sanitizer.test.d.ts.map +1 -0
  282. package/dist/lib/errors/error-sanitizer.test.js +534 -0
  283. package/dist/lib/errors/error-sanitizer.test.js.map +1 -0
  284. package/dist/lib/errors/error-types.d.ts +59 -0
  285. package/dist/lib/errors/error-types.d.ts.map +1 -0
  286. package/dist/lib/errors/error-types.js +187 -0
  287. package/dist/lib/errors/error-types.js.map +1 -0
  288. package/dist/lib/errors/error-types.test.d.ts.map +1 -0
  289. package/dist/lib/errors/error-types.test.js +246 -0
  290. package/dist/lib/errors/error-types.test.js.map +1 -0
  291. package/dist/lib/errors/query-sanitizer.d.ts.map +1 -0
  292. package/dist/lib/errors/query-sanitizer.js +269 -0
  293. package/dist/lib/errors/query-sanitizer.js.map +1 -0
  294. package/dist/lib/errors/query-sanitizer.test.d.ts.map +1 -0
  295. package/dist/lib/errors/query-sanitizer.test.js +403 -0
  296. package/dist/lib/errors/query-sanitizer.test.js.map +1 -0
  297. package/dist/lib/exports/confident-export.d.ts +105 -0
  298. package/dist/lib/exports/confident-export.d.ts.map +1 -0
  299. package/dist/lib/exports/confident-export.js +385 -0
  300. package/dist/lib/exports/confident-export.js.map +1 -0
  301. package/dist/lib/exports/confident-export.test.d.ts.map +1 -0
  302. package/dist/lib/exports/confident-export.test.js +848 -0
  303. package/dist/lib/exports/confident-export.test.js.map +1 -0
  304. package/dist/lib/exports/datadog-export.d.ts +200 -0
  305. package/dist/lib/exports/datadog-export.d.ts.map +1 -0
  306. package/dist/lib/exports/datadog-export.js +488 -0
  307. package/dist/lib/exports/datadog-export.js.map +1 -0
  308. package/dist/lib/exports/datadog-export.test.d.ts +2 -0
  309. package/dist/lib/exports/datadog-export.test.d.ts.map +1 -0
  310. package/dist/lib/exports/datadog-export.test.js +890 -0
  311. package/dist/lib/exports/datadog-export.test.js.map +1 -0
  312. package/dist/lib/exports/export-config-schemas.d.ts +67 -0
  313. package/dist/lib/exports/export-config-schemas.d.ts.map +1 -0
  314. package/dist/lib/exports/export-config-schemas.js +120 -0
  315. package/dist/lib/exports/export-config-schemas.js.map +1 -0
  316. package/dist/lib/exports/export-config-schemas.test.d.ts +8 -0
  317. package/dist/lib/exports/export-config-schemas.test.d.ts.map +1 -0
  318. package/dist/lib/exports/export-config-schemas.test.js +503 -0
  319. package/dist/lib/exports/export-config-schemas.test.js.map +1 -0
  320. package/dist/lib/exports/export-utils.d.ts +127 -0
  321. package/dist/lib/exports/export-utils.d.ts.map +1 -0
  322. package/dist/lib/exports/export-utils.js +303 -0
  323. package/dist/lib/exports/export-utils.js.map +1 -0
  324. package/dist/lib/exports/export-utils.test.d.ts.map +1 -0
  325. package/dist/lib/exports/export-utils.test.js +344 -0
  326. package/dist/lib/exports/export-utils.test.js.map +1 -0
  327. package/dist/lib/exports/langfuse-export.d.ts +129 -0
  328. package/dist/lib/exports/langfuse-export.d.ts.map +1 -0
  329. package/dist/lib/exports/langfuse-export.js +370 -0
  330. package/dist/lib/exports/langfuse-export.js.map +1 -0
  331. package/dist/lib/exports/langfuse-export.test.d.ts.map +1 -0
  332. package/dist/lib/exports/langfuse-export.test.js +1020 -0
  333. package/dist/lib/exports/langfuse-export.test.js.map +1 -0
  334. package/dist/lib/exports/otlp-export.d.ts +179 -0
  335. package/dist/lib/exports/otlp-export.d.ts.map +1 -0
  336. package/dist/lib/exports/otlp-export.js +397 -0
  337. package/dist/lib/exports/otlp-export.js.map +1 -0
  338. package/dist/lib/exports/otlp-format-converter.d.ts +70 -0
  339. package/dist/lib/exports/otlp-format-converter.d.ts.map +1 -0
  340. package/dist/lib/exports/otlp-format-converter.js +401 -0
  341. package/dist/lib/exports/otlp-format-converter.js.map +1 -0
  342. package/dist/lib/exports/otlp-proto-encode.d.ts +53 -0
  343. package/dist/lib/exports/otlp-proto-encode.d.ts.map +1 -0
  344. package/dist/lib/exports/otlp-proto-encode.js +165 -0
  345. package/dist/lib/exports/otlp-proto-encode.js.map +1 -0
  346. package/dist/lib/exports/otlp-proto-encode.test.d.ts +7 -0
  347. package/dist/lib/exports/otlp-proto-encode.test.d.ts.map +1 -0
  348. package/dist/lib/exports/otlp-proto-encode.test.js +997 -0
  349. package/dist/lib/exports/otlp-proto-encode.test.js.map +1 -0
  350. package/dist/lib/exports/phoenix-export.d.ts +119 -0
  351. package/dist/lib/exports/phoenix-export.d.ts.map +1 -0
  352. package/dist/lib/exports/phoenix-export.js +448 -0
  353. package/dist/lib/exports/phoenix-export.js.map +1 -0
  354. package/dist/lib/exports/phoenix-export.test.d.ts.map +1 -0
  355. package/dist/lib/exports/phoenix-export.test.js +816 -0
  356. package/dist/lib/exports/phoenix-export.test.js.map +1 -0
  357. package/dist/lib/index.d.ts +16 -0
  358. package/dist/lib/index.d.ts.map +1 -0
  359. package/dist/lib/index.js +31 -0
  360. package/dist/lib/index.js.map +1 -0
  361. package/dist/lib/judge/evaluation-hooks-schemas.d.ts +186 -0
  362. package/dist/lib/judge/evaluation-hooks-schemas.d.ts.map +1 -0
  363. package/dist/lib/judge/evaluation-hooks-schemas.js +125 -0
  364. package/dist/lib/judge/evaluation-hooks-schemas.js.map +1 -0
  365. package/dist/lib/judge/evaluation-hooks.d.ts +88 -0
  366. package/dist/lib/judge/evaluation-hooks.d.ts.map +1 -0
  367. package/dist/lib/judge/evaluation-hooks.js +658 -0
  368. package/dist/lib/judge/evaluation-hooks.js.map +1 -0
  369. package/dist/lib/judge/evaluation-hooks.test.d.ts.map +1 -0
  370. package/dist/lib/judge/evaluation-hooks.test.js +934 -0
  371. package/dist/lib/judge/evaluation-hooks.test.js.map +1 -0
  372. package/dist/lib/judge/llm-as-judge.d.ts +138 -0
  373. package/dist/lib/judge/llm-as-judge.d.ts.map +1 -0
  374. package/dist/lib/judge/llm-as-judge.js +103 -0
  375. package/dist/lib/judge/llm-as-judge.js.map +1 -0
  376. package/dist/lib/judge/llm-as-judge.test.d.ts.map +1 -0
  377. package/dist/lib/judge/llm-as-judge.test.js +2179 -0
  378. package/dist/lib/judge/llm-as-judge.test.js.map +1 -0
  379. package/dist/lib/judge/llm-judge-bias.d.ts +44 -0
  380. package/dist/lib/judge/llm-judge-bias.d.ts.map +1 -0
  381. package/dist/lib/judge/llm-judge-bias.js +130 -0
  382. package/dist/lib/judge/llm-judge-bias.js.map +1 -0
  383. package/dist/lib/judge/llm-judge-bias.test.d.ts +2 -0
  384. package/dist/lib/judge/llm-judge-bias.test.d.ts.map +1 -0
  385. package/dist/lib/judge/llm-judge-bias.test.js +380 -0
  386. package/dist/lib/judge/llm-judge-bias.test.js.map +1 -0
  387. package/dist/lib/judge/llm-judge-code.d.ts +99 -0
  388. package/dist/lib/judge/llm-judge-code.d.ts.map +1 -0
  389. package/dist/lib/judge/llm-judge-code.js +261 -0
  390. package/dist/lib/judge/llm-judge-code.js.map +1 -0
  391. package/dist/lib/judge/llm-judge-code.test.d.ts +2 -0
  392. package/dist/lib/judge/llm-judge-code.test.d.ts.map +1 -0
  393. package/dist/lib/judge/llm-judge-code.test.js +981 -0
  394. package/dist/lib/judge/llm-judge-code.test.js.map +1 -0
  395. package/dist/lib/judge/llm-judge-config.d.ts +241 -0
  396. package/dist/lib/judge/llm-judge-config.d.ts.map +1 -0
  397. package/dist/lib/judge/llm-judge-config.js +390 -0
  398. package/dist/lib/judge/llm-judge-config.js.map +1 -0
  399. package/dist/lib/judge/llm-judge-config.test.d.ts +5 -0
  400. package/dist/lib/judge/llm-judge-config.test.d.ts.map +1 -0
  401. package/dist/lib/judge/llm-judge-config.test.js +392 -0
  402. package/dist/lib/judge/llm-judge-config.test.js.map +1 -0
  403. package/dist/lib/judge/llm-judge-constants.d.ts +111 -0
  404. package/dist/lib/judge/llm-judge-constants.d.ts.map +1 -0
  405. package/dist/lib/judge/llm-judge-constants.js +150 -0
  406. package/dist/lib/judge/llm-judge-constants.js.map +1 -0
  407. package/dist/lib/judge/llm-judge-dag.d.ts +57 -0
  408. package/dist/lib/judge/llm-judge-dag.d.ts.map +1 -0
  409. package/dist/lib/judge/llm-judge-dag.js +217 -0
  410. package/dist/lib/judge/llm-judge-dag.js.map +1 -0
  411. package/dist/lib/judge/llm-judge-dag.test.d.ts +8 -0
  412. package/dist/lib/judge/llm-judge-dag.test.d.ts.map +1 -0
  413. package/dist/lib/judge/llm-judge-dag.test.js +973 -0
  414. package/dist/lib/judge/llm-judge-dag.test.js.map +1 -0
  415. package/dist/lib/judge/llm-judge-domain.d.ts +42 -0
  416. package/dist/lib/judge/llm-judge-domain.d.ts.map +1 -0
  417. package/dist/lib/judge/llm-judge-domain.js +167 -0
  418. package/dist/lib/judge/llm-judge-domain.js.map +1 -0
  419. package/dist/lib/judge/llm-judge-domain.test.d.ts +6 -0
  420. package/dist/lib/judge/llm-judge-domain.test.d.ts.map +1 -0
  421. package/dist/lib/judge/llm-judge-domain.test.js +337 -0
  422. package/dist/lib/judge/llm-judge-domain.test.js.map +1 -0
  423. package/dist/lib/judge/llm-judge-geval.d.ts +42 -0
  424. package/dist/lib/judge/llm-judge-geval.d.ts.map +1 -0
  425. package/dist/lib/judge/llm-judge-geval.js +213 -0
  426. package/dist/lib/judge/llm-judge-geval.js.map +1 -0
  427. package/dist/lib/judge/llm-judge-geval.test.d.ts +2 -0
  428. package/dist/lib/judge/llm-judge-geval.test.d.ts.map +1 -0
  429. package/dist/lib/judge/llm-judge-geval.test.js +556 -0
  430. package/dist/lib/judge/llm-judge-geval.test.js.map +1 -0
  431. package/dist/lib/judge/llm-judge-otel.test.d.ts +9 -0
  432. package/dist/lib/judge/llm-judge-otel.test.d.ts.map +1 -0
  433. package/dist/lib/judge/llm-judge-otel.test.js +91 -0
  434. package/dist/lib/judge/llm-judge-otel.test.js.map +1 -0
  435. package/dist/lib/judge/llm-judge-qag.d.ts +38 -0
  436. package/dist/lib/judge/llm-judge-qag.d.ts.map +1 -0
  437. package/dist/lib/judge/llm-judge-qag.js +205 -0
  438. package/dist/lib/judge/llm-judge-qag.js.map +1 -0
  439. package/dist/lib/judge/llm-judge-qag.test.d.ts +2 -0
  440. package/dist/lib/judge/llm-judge-qag.test.d.ts.map +1 -0
  441. package/dist/lib/judge/llm-judge-qag.test.js +386 -0
  442. package/dist/lib/judge/llm-judge-qag.test.js.map +1 -0
  443. package/dist/lib/judge/llm-judge-resilience.d.ts +74 -0
  444. package/dist/lib/judge/llm-judge-resilience.d.ts.map +1 -0
  445. package/dist/lib/judge/llm-judge-resilience.js +146 -0
  446. package/dist/lib/judge/llm-judge-resilience.js.map +1 -0
  447. package/dist/lib/judge/llm-judge-resilience.test.d.ts +2 -0
  448. package/dist/lib/judge/llm-judge-resilience.test.d.ts.map +1 -0
  449. package/dist/lib/judge/llm-judge-resilience.test.js +353 -0
  450. package/dist/lib/judge/llm-judge-resilience.test.js.map +1 -0
  451. package/dist/lib/judge/llm-judge-security.d.ts +106 -0
  452. package/dist/lib/judge/llm-judge-security.d.ts.map +1 -0
  453. package/dist/lib/judge/llm-judge-security.js +314 -0
  454. package/dist/lib/judge/llm-judge-security.js.map +1 -0
  455. package/dist/lib/judge/llm-judge-security.test.d.ts +2 -0
  456. package/dist/lib/judge/llm-judge-security.test.d.ts.map +1 -0
  457. package/dist/lib/judge/llm-judge-security.test.js +1011 -0
  458. package/dist/lib/judge/llm-judge-security.test.js.map +1 -0
  459. package/dist/lib/observability/context-accumulator.d.ts +32 -0
  460. package/dist/lib/observability/context-accumulator.d.ts.map +1 -0
  461. package/dist/lib/observability/context-accumulator.js +87 -0
  462. package/dist/lib/observability/context-accumulator.js.map +1 -0
  463. package/dist/lib/observability/evaluation-events.d.ts +35 -0
  464. package/dist/lib/observability/evaluation-events.d.ts.map +1 -0
  465. package/dist/lib/observability/evaluation-events.js +90 -0
  466. package/dist/lib/observability/evaluation-events.js.map +1 -0
  467. package/dist/lib/observability/file-span-exporter.d.ts +17 -0
  468. package/dist/lib/observability/file-span-exporter.d.ts.map +1 -0
  469. package/dist/lib/observability/file-span-exporter.js +49 -0
  470. package/dist/lib/observability/file-span-exporter.js.map +1 -0
  471. package/dist/lib/observability/histogram-bucket-constants.d.ts +25 -0
  472. package/dist/lib/observability/histogram-bucket-constants.d.ts.map +1 -0
  473. package/dist/lib/observability/histogram-bucket-constants.js +60 -0
  474. package/dist/lib/observability/histogram-bucket-constants.js.map +1 -0
  475. package/dist/lib/observability/histogram.d.ts +112 -0
  476. package/dist/lib/observability/histogram.d.ts.map +1 -0
  477. package/dist/lib/observability/histogram.js +170 -0
  478. package/dist/lib/observability/histogram.js.map +1 -0
  479. package/dist/lib/observability/histogram.test.d.ts.map +1 -0
  480. package/dist/lib/observability/histogram.test.js +385 -0
  481. package/dist/lib/observability/histogram.test.js.map +1 -0
  482. package/dist/lib/observability/indexer.d.ts +114 -0
  483. package/dist/lib/observability/indexer.d.ts.map +1 -0
  484. package/dist/lib/observability/indexer.js +402 -0
  485. package/dist/lib/observability/indexer.js.map +1 -0
  486. package/dist/lib/observability/indexer.test.d.ts.map +1 -0
  487. package/dist/lib/observability/indexer.test.js +713 -0
  488. package/dist/lib/observability/indexer.test.js.map +1 -0
  489. package/dist/lib/observability/instrumentation-eval.test.d.ts +5 -0
  490. package/dist/lib/observability/instrumentation-eval.test.d.ts.map +1 -0
  491. package/dist/lib/observability/instrumentation-eval.test.js +63 -0
  492. package/dist/lib/observability/instrumentation-eval.test.js.map +1 -0
  493. package/dist/lib/observability/instrumentation-init-errors.test.d.ts +13 -0
  494. package/dist/lib/observability/instrumentation-init-errors.test.d.ts.map +1 -0
  495. package/dist/lib/observability/instrumentation-init-errors.test.js +194 -0
  496. package/dist/lib/observability/instrumentation-init-errors.test.js.map +1 -0
  497. package/dist/lib/observability/instrumentation-retry-timeout.test.d.ts +15 -0
  498. package/dist/lib/observability/instrumentation-retry-timeout.test.d.ts.map +1 -0
  499. package/dist/lib/observability/instrumentation-retry-timeout.test.js +188 -0
  500. package/dist/lib/observability/instrumentation-retry-timeout.test.js.map +1 -0
  501. package/dist/lib/observability/instrumentation-set-otel.test.d.ts +5 -0
  502. package/dist/lib/observability/instrumentation-set-otel.test.d.ts.map +1 -0
  503. package/dist/lib/observability/instrumentation-set-otel.test.js +59 -0
  504. package/dist/lib/observability/instrumentation-set-otel.test.js.map +1 -0
  505. package/dist/lib/observability/instrumentation.d.ts +158 -0
  506. package/dist/lib/observability/instrumentation.d.ts.map +1 -0
  507. package/dist/lib/observability/instrumentation.integration.test.d.ts.map +1 -0
  508. package/dist/lib/observability/instrumentation.integration.test.js +590 -0
  509. package/dist/lib/observability/instrumentation.integration.test.js.map +1 -0
  510. package/dist/lib/observability/instrumentation.js +512 -0
  511. package/dist/lib/observability/instrumentation.js.map +1 -0
  512. package/dist/lib/observability/instrumentation.test.d.ts.map +1 -0
  513. package/dist/lib/observability/instrumentation.test.js +822 -0
  514. package/dist/lib/observability/instrumentation.test.js.map +1 -0
  515. package/dist/lib/observability/mcp-semconv-constants.d.ts +98 -0
  516. package/dist/lib/observability/mcp-semconv-constants.d.ts.map +1 -0
  517. package/dist/lib/observability/mcp-semconv-constants.js +102 -0
  518. package/dist/lib/observability/mcp-semconv-constants.js.map +1 -0
  519. package/dist/lib/observability/mcp-semconv.d.ts +37 -0
  520. package/dist/lib/observability/mcp-semconv.d.ts.map +1 -0
  521. package/dist/lib/observability/mcp-semconv.js +87 -0
  522. package/dist/lib/observability/mcp-semconv.js.map +1 -0
  523. package/dist/lib/observability/mcp-semconv.test.d.ts +2 -0
  524. package/dist/lib/observability/mcp-semconv.test.d.ts.map +1 -0
  525. package/dist/lib/observability/mcp-semconv.test.js +168 -0
  526. package/dist/lib/observability/mcp-semconv.test.js.map +1 -0
  527. package/dist/lib/observability/metrics.d.ts +100 -0
  528. package/dist/lib/observability/metrics.d.ts.map +1 -0
  529. package/dist/lib/observability/metrics.js +429 -0
  530. package/dist/lib/observability/metrics.js.map +1 -0
  531. package/dist/lib/observability/metrics.test.d.ts.map +1 -0
  532. package/dist/lib/observability/metrics.test.js +191 -0
  533. package/dist/lib/observability/metrics.test.js.map +1 -0
  534. package/dist/lib/observability/observability-test-constants.d.ts +34 -0
  535. package/dist/lib/observability/observability-test-constants.d.ts.map +1 -0
  536. package/dist/lib/observability/observability-test-constants.js +55 -0
  537. package/dist/lib/observability/observability-test-constants.js.map +1 -0
  538. package/dist/lib/observability/opentelemetry-resources.test.d.ts +2 -0
  539. package/dist/lib/observability/opentelemetry-resources.test.d.ts.map +1 -0
  540. package/dist/lib/observability/opentelemetry-resources.test.js +19 -0
  541. package/dist/lib/observability/opentelemetry-resources.test.js.map +1 -0
  542. package/dist/lib/observability/parse-stats.d.ts.map +1 -0
  543. package/dist/lib/observability/parse-stats.js +207 -0
  544. package/dist/lib/observability/parse-stats.js.map +1 -0
  545. package/dist/lib/observability/parse-stats.test.d.ts.map +1 -0
  546. package/dist/lib/observability/parse-stats.test.js +287 -0
  547. package/dist/lib/observability/parse-stats.test.js.map +1 -0
  548. package/dist/lib/observability/render-trace-tree.d.ts +31 -0
  549. package/dist/lib/observability/render-trace-tree.d.ts.map +1 -0
  550. package/dist/lib/observability/render-trace-tree.js +95 -0
  551. package/dist/lib/observability/render-trace-tree.js.map +1 -0
  552. package/dist/lib/observability/render-trace-tree.test.d.ts +5 -0
  553. package/dist/lib/observability/render-trace-tree.test.d.ts.map +1 -0
  554. package/dist/lib/observability/render-trace-tree.test.js +97 -0
  555. package/dist/lib/observability/render-trace-tree.test.js.map +1 -0
  556. package/dist/lib/observability/span-attributes.d.ts +27 -0
  557. package/dist/lib/observability/span-attributes.d.ts.map +1 -0
  558. package/dist/lib/observability/span-attributes.js +85 -0
  559. package/dist/lib/observability/span-attributes.js.map +1 -0
  560. package/dist/lib/observability/trace-anomaly-detector.d.ts +23 -0
  561. package/dist/lib/observability/trace-anomaly-detector.d.ts.map +1 -0
  562. package/dist/lib/observability/trace-anomaly-detector.js +211 -0
  563. package/dist/lib/observability/trace-anomaly-detector.js.map +1 -0
  564. package/dist/lib/observability/trace-anomaly-detector.test.d.ts +5 -0
  565. package/dist/lib/observability/trace-anomaly-detector.test.d.ts.map +1 -0
  566. package/dist/lib/observability/trace-anomaly-detector.test.js +224 -0
  567. package/dist/lib/observability/trace-anomaly-detector.test.js.map +1 -0
  568. package/dist/lib/observability/trace-anomaly-schemas.d.ts +189 -0
  569. package/dist/lib/observability/trace-anomaly-schemas.d.ts.map +1 -0
  570. package/dist/lib/observability/trace-anomaly-schemas.js +167 -0
  571. package/dist/lib/observability/trace-anomaly-schemas.js.map +1 -0
  572. package/dist/lib/privacy/content-redaction.d.ts +141 -0
  573. package/dist/lib/privacy/content-redaction.d.ts.map +1 -0
  574. package/dist/lib/privacy/content-redaction.js +210 -0
  575. package/dist/lib/privacy/content-redaction.js.map +1 -0
  576. package/dist/lib/privacy/content-redaction.test.d.ts +2 -0
  577. package/dist/lib/privacy/content-redaction.test.d.ts.map +1 -0
  578. package/dist/lib/privacy/content-redaction.test.js +302 -0
  579. package/dist/lib/privacy/content-redaction.test.js.map +1 -0
  580. package/dist/lib/quality/bucket-utils.d.ts +17 -0
  581. package/dist/lib/quality/bucket-utils.d.ts.map +1 -0
  582. package/dist/lib/quality/bucket-utils.js +31 -0
  583. package/dist/lib/quality/bucket-utils.js.map +1 -0
  584. package/dist/lib/quality/bucket-utils.test.d.ts +2 -0
  585. package/dist/lib/quality/bucket-utils.test.d.ts.map +1 -0
  586. package/dist/lib/quality/bucket-utils.test.js +42 -0
  587. package/dist/lib/quality/bucket-utils.test.js.map +1 -0
  588. package/dist/lib/quality/qfe-backtest-detail.test.d.ts +5 -0
  589. package/dist/lib/quality/qfe-backtest-detail.test.d.ts.map +1 -0
  590. package/dist/lib/quality/qfe-backtest-detail.test.js +179 -0
  591. package/dist/lib/quality/qfe-backtest-detail.test.js.map +1 -0
  592. package/dist/lib/quality/qfe-calibration-paths.test.d.ts +5 -0
  593. package/dist/lib/quality/qfe-calibration-paths.test.d.ts.map +1 -0
  594. package/dist/lib/quality/qfe-calibration-paths.test.js +203 -0
  595. package/dist/lib/quality/qfe-calibration-paths.test.js.map +1 -0
  596. package/dist/lib/quality/qfe-correlation-helpers.test.d.ts +6 -0
  597. package/dist/lib/quality/qfe-correlation-helpers.test.d.ts.map +1 -0
  598. package/dist/lib/quality/qfe-correlation-helpers.test.js +143 -0
  599. package/dist/lib/quality/qfe-correlation-helpers.test.js.map +1 -0
  600. package/dist/lib/quality/qfe-cqi-paths.test.d.ts +6 -0
  601. package/dist/lib/quality/qfe-cqi-paths.test.d.ts.map +1 -0
  602. package/dist/lib/quality/qfe-cqi-paths.test.js +231 -0
  603. package/dist/lib/quality/qfe-cqi-paths.test.js.map +1 -0
  604. package/dist/lib/quality/qfe-critic-internals.test.d.ts +6 -0
  605. package/dist/lib/quality/qfe-critic-internals.test.d.ts.map +1 -0
  606. package/dist/lib/quality/qfe-critic-internals.test.js +191 -0
  607. package/dist/lib/quality/qfe-critic-internals.test.js.map +1 -0
  608. package/dist/lib/quality/qfe-derived-paths.test.d.ts +2 -0
  609. package/dist/lib/quality/qfe-derived-paths.test.d.ts.map +1 -0
  610. package/dist/lib/quality/qfe-derived-paths.test.js +372 -0
  611. package/dist/lib/quality/qfe-derived-paths.test.js.map +1 -0
  612. package/dist/lib/quality/qfe-dynamics-paths.test.d.ts +8 -0
  613. package/dist/lib/quality/qfe-dynamics-paths.test.d.ts.map +1 -0
  614. package/dist/lib/quality/qfe-dynamics-paths.test.js +223 -0
  615. package/dist/lib/quality/qfe-dynamics-paths.test.js.map +1 -0
  616. package/dist/lib/quality/qfe-granger-internals.test.d.ts +6 -0
  617. package/dist/lib/quality/qfe-granger-internals.test.d.ts.map +1 -0
  618. package/dist/lib/quality/qfe-granger-internals.test.js +158 -0
  619. package/dist/lib/quality/qfe-granger-internals.test.js.map +1 -0
  620. package/dist/lib/quality/qfe-label-normalize.test.d.ts +7 -0
  621. package/dist/lib/quality/qfe-label-normalize.test.d.ts.map +1 -0
  622. package/dist/lib/quality/qfe-label-normalize.test.js +332 -0
  623. package/dist/lib/quality/qfe-label-normalize.test.js.map +1 -0
  624. package/dist/lib/quality/qfe-ordinal-edge.test.d.ts +6 -0
  625. package/dist/lib/quality/qfe-ordinal-edge.test.d.ts.map +1 -0
  626. package/dist/lib/quality/qfe-ordinal-edge.test.js +98 -0
  627. package/dist/lib/quality/qfe-ordinal-edge.test.js.map +1 -0
  628. package/dist/lib/quality/qfe-roles-detail.test.d.ts +5 -0
  629. package/dist/lib/quality/qfe-roles-detail.test.d.ts.map +1 -0
  630. package/dist/lib/quality/qfe-roles-detail.test.js +115 -0
  631. package/dist/lib/quality/qfe-roles-detail.test.js.map +1 -0
  632. package/dist/lib/quality/qfe-rolling-detail.test.d.ts +7 -0
  633. package/dist/lib/quality/qfe-rolling-detail.test.d.ts.map +1 -0
  634. package/dist/lib/quality/qfe-rolling-detail.test.js +249 -0
  635. package/dist/lib/quality/qfe-rolling-detail.test.js.map +1 -0
  636. package/dist/lib/quality/qfe-stats-internals.test.d.ts +7 -0
  637. package/dist/lib/quality/qfe-stats-internals.test.d.ts.map +1 -0
  638. package/dist/lib/quality/qfe-stats-internals.test.js +143 -0
  639. package/dist/lib/quality/qfe-stats-internals.test.js.map +1 -0
  640. package/dist/lib/quality/qfe-streaming.test.d.ts +5 -0
  641. package/dist/lib/quality/qfe-streaming.test.d.ts.map +1 -0
  642. package/dist/lib/quality/qfe-streaming.test.js +239 -0
  643. package/dist/lib/quality/qfe-streaming.test.js.map +1 -0
  644. package/dist/lib/quality/qfe-sweep-detail.test.d.ts +6 -0
  645. package/dist/lib/quality/qfe-sweep-detail.test.d.ts.map +1 -0
  646. package/dist/lib/quality/qfe-sweep-detail.test.js +291 -0
  647. package/dist/lib/quality/qfe-sweep-detail.test.js.map +1 -0
  648. package/dist/lib/quality/quality-alerts.d.ts +23 -0
  649. package/dist/lib/quality/quality-alerts.d.ts.map +1 -0
  650. package/dist/lib/quality/quality-alerts.js +89 -0
  651. package/dist/lib/quality/quality-alerts.js.map +1 -0
  652. package/dist/lib/quality/quality-alerts.test.d.ts +2 -0
  653. package/dist/lib/quality/quality-alerts.test.d.ts.map +1 -0
  654. package/dist/lib/quality/quality-alerts.test.js +86 -0
  655. package/dist/lib/quality/quality-alerts.test.js.map +1 -0
  656. package/dist/lib/quality/quality-constants.d.ts +294 -0
  657. package/dist/lib/quality/quality-constants.d.ts.map +1 -0
  658. package/dist/lib/quality/quality-constants.js +335 -0
  659. package/dist/lib/quality/quality-constants.js.map +1 -0
  660. package/dist/lib/quality/quality-feature-engineering.d.ts +1071 -0
  661. package/dist/lib/quality/quality-feature-engineering.d.ts.map +1 -0
  662. package/dist/lib/quality/quality-feature-engineering.js +2076 -0
  663. package/dist/lib/quality/quality-feature-engineering.js.map +1 -0
  664. package/dist/lib/quality/quality-feature-engineering.test.d.ts +5 -0
  665. package/dist/lib/quality/quality-feature-engineering.test.d.ts.map +1 -0
  666. package/dist/lib/quality/quality-feature-engineering.test.js +2908 -0
  667. package/dist/lib/quality/quality-feature-engineering.test.js.map +1 -0
  668. package/dist/lib/quality/quality-metrics.d.ts +943 -0
  669. package/dist/lib/quality/quality-metrics.d.ts.map +1 -0
  670. package/dist/lib/quality/quality-metrics.js +1151 -0
  671. package/dist/lib/quality/quality-metrics.js.map +1 -0
  672. package/dist/lib/quality/quality-metrics.test.d.ts +5 -0
  673. package/dist/lib/quality/quality-metrics.test.d.ts.map +1 -0
  674. package/dist/lib/quality/quality-metrics.test.js +2766 -0
  675. package/dist/lib/quality/quality-metrics.test.js.map +1 -0
  676. package/dist/lib/quality/quality-multi-agent.d.ts +106 -0
  677. package/dist/lib/quality/quality-multi-agent.d.ts.map +1 -0
  678. package/dist/lib/quality/quality-multi-agent.js +124 -0
  679. package/dist/lib/quality/quality-multi-agent.js.map +1 -0
  680. package/dist/lib/quality/quality-multi-agent.test.d.ts +6 -0
  681. package/dist/lib/quality/quality-multi-agent.test.d.ts.map +1 -0
  682. package/dist/lib/quality/quality-multi-agent.test.js +163 -0
  683. package/dist/lib/quality/quality-multi-agent.test.js.map +1 -0
  684. package/dist/lib/quality/quality-sla.d.ts +35 -0
  685. package/dist/lib/quality/quality-sla.d.ts.map +1 -0
  686. package/dist/lib/quality/quality-sla.js +62 -0
  687. package/dist/lib/quality/quality-sla.js.map +1 -0
  688. package/dist/lib/quality/quality-sla.test.d.ts +5 -0
  689. package/dist/lib/quality/quality-sla.test.d.ts.map +1 -0
  690. package/dist/lib/quality/quality-sla.test.js +144 -0
  691. package/dist/lib/quality/quality-sla.test.js.map +1 -0
  692. package/dist/lib/quality/quality-test-constants.d.ts +23 -0
  693. package/dist/lib/quality/quality-test-constants.d.ts.map +1 -0
  694. package/dist/lib/quality/quality-test-constants.js +25 -0
  695. package/dist/lib/quality/quality-test-constants.js.map +1 -0
  696. package/dist/lib/quality/quality-trends.d.ts +101 -0
  697. package/dist/lib/quality/quality-trends.d.ts.map +1 -0
  698. package/dist/lib/quality/quality-trends.js +299 -0
  699. package/dist/lib/quality/quality-trends.js.map +1 -0
  700. package/dist/lib/quality/quality-trends.test.d.ts +6 -0
  701. package/dist/lib/quality/quality-trends.test.d.ts.map +1 -0
  702. package/dist/lib/quality/quality-trends.test.js +377 -0
  703. package/dist/lib/quality/quality-trends.test.js.map +1 -0
  704. package/dist/lib/quality/quality-views.d.ts +966 -0
  705. package/dist/lib/quality/quality-views.d.ts.map +1 -0
  706. package/dist/lib/quality/quality-views.js +367 -0
  707. package/dist/lib/quality/quality-views.js.map +1 -0
  708. package/dist/lib/quality/quality-views.test.d.ts +6 -0
  709. package/dist/lib/quality/quality-views.test.d.ts.map +1 -0
  710. package/dist/lib/quality/quality-views.test.js +262 -0
  711. package/dist/lib/quality/quality-views.test.js.map +1 -0
  712. package/dist/lib/quality/quality-visualization.d.ts +112 -0
  713. package/dist/lib/quality/quality-visualization.d.ts.map +1 -0
  714. package/dist/lib/quality/quality-visualization.js +136 -0
  715. package/dist/lib/quality/quality-visualization.js.map +1 -0
  716. package/dist/lib/quality/quality-visualization.test.d.ts +5 -0
  717. package/dist/lib/quality/quality-visualization.test.d.ts.map +1 -0
  718. package/dist/lib/quality/quality-visualization.test.js +189 -0
  719. package/dist/lib/quality/quality-visualization.test.js.map +1 -0
  720. package/dist/lib/resilience/cache.d.ts +56 -0
  721. package/dist/lib/resilience/cache.d.ts.map +1 -0
  722. package/dist/lib/resilience/cache.js +96 -0
  723. package/dist/lib/resilience/cache.js.map +1 -0
  724. package/dist/lib/resilience/cache.test.d.ts.map +1 -0
  725. package/dist/lib/resilience/cache.test.js +106 -0
  726. package/dist/lib/resilience/cache.test.js.map +1 -0
  727. package/dist/lib/resilience/circuit-breaker.d.ts +147 -0
  728. package/dist/lib/resilience/circuit-breaker.d.ts.map +1 -0
  729. package/dist/lib/resilience/circuit-breaker.js +251 -0
  730. package/dist/lib/resilience/circuit-breaker.js.map +1 -0
  731. package/dist/lib/resilience/circuit-breaker.test.d.ts.map +1 -0
  732. package/dist/lib/resilience/circuit-breaker.test.js +266 -0
  733. package/dist/lib/resilience/circuit-breaker.test.js.map +1 -0
  734. package/dist/lib/resilience/toon-encoder.d.ts +31 -0
  735. package/dist/lib/resilience/toon-encoder.d.ts.map +1 -0
  736. package/dist/lib/resilience/toon-encoder.js +66 -0
  737. package/dist/lib/resilience/toon-encoder.js.map +1 -0
  738. package/dist/lib/resilience/toon-encoder.test.d.ts.map +1 -0
  739. package/dist/lib/resilience/toon-encoder.test.js +86 -0
  740. package/dist/lib/resilience/toon-encoder.test.js.map +1 -0
  741. package/dist/lib/testing/mock-llm-builder.d.ts +139 -0
  742. package/dist/lib/testing/mock-llm-builder.d.ts.map +1 -0
  743. package/dist/lib/testing/mock-llm-builder.js +254 -0
  744. package/dist/lib/testing/mock-llm-builder.js.map +1 -0
  745. package/dist/lib/testing/mock-llm-builder.test.d.ts +5 -0
  746. package/dist/lib/testing/mock-llm-builder.test.d.ts.map +1 -0
  747. package/dist/lib/testing/mock-llm-builder.test.js +304 -0
  748. package/dist/lib/testing/mock-llm-builder.test.js.map +1 -0
  749. package/dist/lib/validation/api-schemas.d.ts +705 -0
  750. package/dist/lib/validation/api-schemas.d.ts.map +1 -0
  751. package/dist/lib/validation/api-schemas.js +351 -0
  752. package/dist/lib/validation/api-schemas.js.map +1 -0
  753. package/dist/lib/validation/api-schemas.test.d.ts +5 -0
  754. package/dist/lib/validation/api-schemas.test.d.ts.map +1 -0
  755. package/dist/lib/validation/api-schemas.test.js +427 -0
  756. package/dist/lib/validation/api-schemas.test.js.map +1 -0
  757. package/dist/lib/validation/dashboard-schemas.d.ts +203 -0
  758. package/dist/lib/validation/dashboard-schemas.d.ts.map +1 -0
  759. package/dist/lib/validation/dashboard-schemas.js +186 -0
  760. package/dist/lib/validation/dashboard-schemas.js.map +1 -0
  761. package/dist/lib/validation/dashboard-schemas.test.d.ts +5 -0
  762. package/dist/lib/validation/dashboard-schemas.test.d.ts.map +1 -0
  763. package/dist/lib/validation/dashboard-schemas.test.js +353 -0
  764. package/dist/lib/validation/dashboard-schemas.test.js.map +1 -0
  765. package/dist/server.d.ts +2 -1
  766. package/dist/server.d.ts.map +1 -1
  767. package/dist/server.js +158 -144
  768. package/dist/server.js.map +1 -1
  769. package/dist/server.test.js +102 -95
  770. package/dist/server.test.js.map +1 -1
  771. package/dist/test-helpers/assertions.d.ts +6 -0
  772. package/dist/test-helpers/assertions.d.ts.map +1 -0
  773. package/dist/test-helpers/assertions.js +11 -0
  774. package/dist/test-helpers/assertions.js.map +1 -0
  775. package/dist/test-helpers/env-utils.d.ts +0 -64
  776. package/dist/test-helpers/env-utils.d.ts.map +1 -1
  777. package/dist/test-helpers/env-utils.js +0 -100
  778. package/dist/test-helpers/env-utils.js.map +1 -1
  779. package/dist/test-helpers/fuzz-generators.d.ts.map +1 -1
  780. package/dist/test-helpers/fuzz-generators.js +62 -22
  781. package/dist/test-helpers/fuzz-generators.js.map +1 -1
  782. package/dist/test-helpers/index.d.ts +3 -2
  783. package/dist/test-helpers/index.d.ts.map +1 -1
  784. package/dist/test-helpers/index.js +4 -2
  785. package/dist/test-helpers/index.js.map +1 -1
  786. package/dist/test-helpers/memfs-utils.test.js +81 -76
  787. package/dist/test-helpers/memfs-utils.test.js.map +1 -1
  788. package/dist/test-helpers/mock-backends.d.ts +19 -17
  789. package/dist/test-helpers/mock-backends.d.ts.map +1 -1
  790. package/dist/test-helpers/mock-backends.js +16 -4
  791. package/dist/test-helpers/mock-backends.js.map +1 -1
  792. package/dist/test-helpers/mock-backends.test.js +43 -112
  793. package/dist/test-helpers/mock-backends.test.js.map +1 -1
  794. package/dist/test-helpers/race-condition-helpers.d.ts.map +1 -1
  795. package/dist/test-helpers/race-condition-helpers.js +3 -2
  796. package/dist/test-helpers/race-condition-helpers.js.map +1 -1
  797. package/dist/test-helpers/schema-validators.d.ts +2 -2
  798. package/dist/test-helpers/schema-validators.d.ts.map +1 -1
  799. package/dist/test-helpers/schema-validators.js +35 -31
  800. package/dist/test-helpers/schema-validators.js.map +1 -1
  801. package/dist/test-helpers/test-constants.d.ts +74 -0
  802. package/dist/test-helpers/test-constants.d.ts.map +1 -0
  803. package/dist/test-helpers/test-constants.js +78 -0
  804. package/dist/test-helpers/test-constants.js.map +1 -0
  805. package/dist/test-helpers/test-data-builders.d.ts +25 -7
  806. package/dist/test-helpers/test-data-builders.d.ts.map +1 -1
  807. package/dist/test-helpers/test-data-builders.js +32 -9
  808. package/dist/test-helpers/test-data-builders.js.map +1 -1
  809. package/dist/test-helpers/test-data-builders.test.js +116 -107
  810. package/dist/test-helpers/test-data-builders.test.js.map +1 -1
  811. package/dist/test-helpers/tool-validators.d.ts +1 -1
  812. package/dist/test-helpers/tool-validators.d.ts.map +1 -1
  813. package/dist/test-helpers/tool-validators.js +10 -10
  814. package/dist/test-helpers/tool-validators.js.map +1 -1
  815. package/dist/tools/audit-trail.d.ts +170 -0
  816. package/dist/tools/audit-trail.d.ts.map +1 -0
  817. package/dist/tools/audit-trail.js +109 -0
  818. package/dist/tools/audit-trail.js.map +1 -0
  819. package/dist/tools/audit-trail.test.d.ts +5 -0
  820. package/dist/tools/audit-trail.test.d.ts.map +1 -0
  821. package/dist/tools/audit-trail.test.js +122 -0
  822. package/dist/tools/audit-trail.test.js.map +1 -0
  823. package/dist/tools/context-stats.d.ts +6 -20
  824. package/dist/tools/context-stats.d.ts.map +1 -1
  825. package/dist/tools/context-stats.js +106 -88
  826. package/dist/tools/context-stats.js.map +1 -1
  827. package/dist/tools/context-stats.test.js +109 -60
  828. package/dist/tools/context-stats.test.js.map +1 -1
  829. package/dist/tools/detect-trace-anomalies.d.ts +123 -0
  830. package/dist/tools/detect-trace-anomalies.d.ts.map +1 -0
  831. package/dist/tools/detect-trace-anomalies.js +66 -0
  832. package/dist/tools/detect-trace-anomalies.js.map +1 -0
  833. package/dist/tools/estimate-cost.d.ts +77 -0
  834. package/dist/tools/estimate-cost.d.ts.map +1 -0
  835. package/dist/tools/estimate-cost.js +104 -0
  836. package/dist/tools/estimate-cost.js.map +1 -0
  837. package/dist/tools/estimate-cost.test.d.ts +5 -0
  838. package/dist/tools/estimate-cost.test.d.ts.map +1 -0
  839. package/dist/tools/estimate-cost.test.js +343 -0
  840. package/dist/tools/estimate-cost.test.js.map +1 -0
  841. package/dist/tools/export-base.d.ts +77 -0
  842. package/dist/tools/export-base.d.ts.map +1 -0
  843. package/dist/tools/export-base.js +150 -0
  844. package/dist/tools/export-base.js.map +1 -0
  845. package/dist/tools/export-base.test.d.ts +18 -0
  846. package/dist/tools/export-base.test.d.ts.map +1 -0
  847. package/dist/tools/export-base.test.js +220 -0
  848. package/dist/tools/export-base.test.js.map +1 -0
  849. package/dist/tools/export-confident.d.ts +94 -90
  850. package/dist/tools/export-confident.d.ts.map +1 -1
  851. package/dist/tools/export-confident.js +17 -115
  852. package/dist/tools/export-confident.js.map +1 -1
  853. package/dist/tools/export-confident.test.js +79 -75
  854. package/dist/tools/export-confident.test.js.map +1 -1
  855. package/dist/tools/export-datadog.d.ts +77 -116
  856. package/dist/tools/export-datadog.d.ts.map +1 -1
  857. package/dist/tools/export-datadog.js +38 -40
  858. package/dist/tools/export-datadog.js.map +1 -1
  859. package/dist/tools/export-datadog.test.js +122 -165
  860. package/dist/tools/export-datadog.test.js.map +1 -1
  861. package/dist/tools/export-jaeger.d.ts +100 -0
  862. package/dist/tools/export-jaeger.d.ts.map +1 -0
  863. package/dist/tools/export-jaeger.js +154 -0
  864. package/dist/tools/export-jaeger.js.map +1 -0
  865. package/dist/tools/export-jaeger.test.d.ts +2 -0
  866. package/dist/tools/export-jaeger.test.d.ts.map +1 -0
  867. package/dist/tools/export-jaeger.test.js +113 -0
  868. package/dist/tools/export-jaeger.test.js.map +1 -0
  869. package/dist/tools/export-langfuse.d.ts +78 -80
  870. package/dist/tools/export-langfuse.d.ts.map +1 -1
  871. package/dist/tools/export-langfuse.js +15 -113
  872. package/dist/tools/export-langfuse.js.map +1 -1
  873. package/dist/tools/export-langfuse.test.js +70 -81
  874. package/dist/tools/export-langfuse.test.js.map +1 -1
  875. package/dist/tools/export-phoenix.d.ts +115 -90
  876. package/dist/tools/export-phoenix.d.ts.map +1 -1
  877. package/dist/tools/export-phoenix.js +29 -117
  878. package/dist/tools/export-phoenix.js.map +1 -1
  879. package/dist/tools/export-phoenix.test.js +95 -94
  880. package/dist/tools/export-phoenix.test.js.map +1 -1
  881. package/dist/tools/get-trace-url.d.ts +2 -10
  882. package/dist/tools/get-trace-url.d.ts.map +1 -1
  883. package/dist/tools/get-trace-url.js +5 -8
  884. package/dist/tools/get-trace-url.js.map +1 -1
  885. package/dist/tools/get-trace-url.test.js +81 -399
  886. package/dist/tools/get-trace-url.test.js.map +1 -1
  887. package/dist/tools/hallucination-detection.d.ts +203 -0
  888. package/dist/tools/hallucination-detection.d.ts.map +1 -0
  889. package/dist/tools/hallucination-detection.js +189 -0
  890. package/dist/tools/hallucination-detection.js.map +1 -0
  891. package/dist/tools/hallucination-detection.test.d.ts +5 -0
  892. package/dist/tools/hallucination-detection.test.d.ts.map +1 -0
  893. package/dist/tools/hallucination-detection.test.js +529 -0
  894. package/dist/tools/hallucination-detection.test.js.map +1 -0
  895. package/dist/tools/health-check.d.ts +9 -16
  896. package/dist/tools/health-check.d.ts.map +1 -1
  897. package/dist/tools/health-check.js +88 -101
  898. package/dist/tools/health-check.js.map +1 -1
  899. package/dist/tools/health-check.test.js +72 -165
  900. package/dist/tools/health-check.test.js.map +1 -1
  901. package/dist/tools/index.d.ts +13 -0
  902. package/dist/tools/index.d.ts.map +1 -1
  903. package/dist/tools/index.js +13 -0
  904. package/dist/tools/index.js.map +1 -1
  905. package/dist/tools/ingest-constants.d.ts +8 -0
  906. package/dist/tools/ingest-constants.d.ts.map +1 -0
  907. package/dist/tools/ingest-constants.js +8 -0
  908. package/dist/tools/ingest-constants.js.map +1 -0
  909. package/dist/tools/ingest-spans.d.ts +45 -0
  910. package/dist/tools/ingest-spans.d.ts.map +1 -0
  911. package/dist/tools/ingest-spans.js +129 -0
  912. package/dist/tools/ingest-spans.js.map +1 -0
  913. package/dist/tools/ingest-spans.test.d.ts +5 -0
  914. package/dist/tools/ingest-spans.test.d.ts.map +1 -0
  915. package/dist/tools/ingest-spans.test.js +250 -0
  916. package/dist/tools/ingest-spans.test.js.map +1 -0
  917. package/dist/tools/ingest-traces.d.ts +76 -0
  918. package/dist/tools/ingest-traces.d.ts.map +1 -0
  919. package/dist/tools/ingest-traces.js +164 -0
  920. package/dist/tools/ingest-traces.js.map +1 -0
  921. package/dist/tools/ingest-traces.test.d.ts +5 -0
  922. package/dist/tools/ingest-traces.test.d.ts.map +1 -0
  923. package/dist/tools/ingest-traces.test.js +483 -0
  924. package/dist/tools/ingest-traces.test.js.map +1 -0
  925. package/dist/tools/inject-evaluations.d.ts +136 -1197
  926. package/dist/tools/inject-evaluations.d.ts.map +1 -1
  927. package/dist/tools/inject-evaluations.js +65 -53
  928. package/dist/tools/inject-evaluations.js.map +1 -1
  929. package/dist/tools/inject-evaluations.test.js +83 -71
  930. package/dist/tools/inject-evaluations.test.js.map +1 -1
  931. package/dist/tools/manage-datasets.d.ts +850 -0
  932. package/dist/tools/manage-datasets.d.ts.map +1 -0
  933. package/dist/tools/manage-datasets.js +139 -0
  934. package/dist/tools/manage-datasets.js.map +1 -0
  935. package/dist/tools/manage-datasets.test.d.ts +5 -0
  936. package/dist/tools/manage-datasets.test.d.ts.map +1 -0
  937. package/dist/tools/manage-datasets.test.js +430 -0
  938. package/dist/tools/manage-datasets.test.js.map +1 -0
  939. package/dist/tools/multi-agent-coordination.d.ts +178 -0
  940. package/dist/tools/multi-agent-coordination.d.ts.map +1 -0
  941. package/dist/tools/multi-agent-coordination.js +270 -0
  942. package/dist/tools/multi-agent-coordination.js.map +1 -0
  943. package/dist/tools/multi-agent-coordination.test.d.ts +5 -0
  944. package/dist/tools/multi-agent-coordination.test.d.ts.map +1 -0
  945. package/dist/tools/multi-agent-coordination.test.js +530 -0
  946. package/dist/tools/multi-agent-coordination.test.js.map +1 -0
  947. package/dist/tools/query-evaluations.d.ts +147 -105
  948. package/dist/tools/query-evaluations.d.ts.map +1 -1
  949. package/dist/tools/query-evaluations.js +205 -178
  950. package/dist/tools/query-evaluations.js.map +1 -1
  951. package/dist/tools/query-evaluations.test.js +386 -391
  952. package/dist/tools/query-evaluations.test.js.map +1 -1
  953. package/dist/tools/query-llm-events.d.ts +100 -75
  954. package/dist/tools/query-llm-events.d.ts.map +1 -1
  955. package/dist/tools/query-llm-events.js +106 -80
  956. package/dist/tools/query-llm-events.js.map +1 -1
  957. package/dist/tools/query-llm-events.test.js +183 -346
  958. package/dist/tools/query-llm-events.test.js.map +1 -1
  959. package/dist/tools/query-logs.d.ts +45 -58
  960. package/dist/tools/query-logs.d.ts.map +1 -1
  961. package/dist/tools/query-logs.js +54 -101
  962. package/dist/tools/query-logs.js.map +1 -1
  963. package/dist/tools/query-logs.test.js +118 -314
  964. package/dist/tools/query-logs.test.js.map +1 -1
  965. package/dist/tools/query-metric-histograms.d.ts +112 -0
  966. package/dist/tools/query-metric-histograms.d.ts.map +1 -0
  967. package/dist/tools/query-metric-histograms.js +69 -0
  968. package/dist/tools/query-metric-histograms.js.map +1 -0
  969. package/dist/tools/query-metric-histograms.test.d.ts +5 -0
  970. package/dist/tools/query-metric-histograms.test.d.ts.map +1 -0
  971. package/dist/tools/query-metric-histograms.test.js +209 -0
  972. package/dist/tools/query-metric-histograms.test.js.map +1 -0
  973. package/dist/tools/query-metrics.d.ts +159 -60
  974. package/dist/tools/query-metrics.d.ts.map +1 -1
  975. package/dist/tools/query-metrics.js +133 -111
  976. package/dist/tools/query-metrics.js.map +1 -1
  977. package/dist/tools/query-metrics.test.js +314 -389
  978. package/dist/tools/query-metrics.test.js.map +1 -1
  979. package/dist/tools/query-regressions.d.ts +76 -0
  980. package/dist/tools/query-regressions.d.ts.map +1 -0
  981. package/dist/tools/query-regressions.js +122 -0
  982. package/dist/tools/query-regressions.js.map +1 -0
  983. package/dist/tools/query-regressions.test.d.ts +8 -0
  984. package/dist/tools/query-regressions.test.d.ts.map +1 -0
  985. package/dist/tools/query-regressions.test.js +129 -0
  986. package/dist/tools/query-regressions.test.js.map +1 -0
  987. package/dist/tools/query-traces.d.ts +103 -71
  988. package/dist/tools/query-traces.d.ts.map +1 -1
  989. package/dist/tools/query-traces.js +75 -106
  990. package/dist/tools/query-traces.js.map +1 -1
  991. package/dist/tools/query-traces.test.js +140 -846
  992. package/dist/tools/query-traces.test.js.map +1 -1
  993. package/dist/tools/query-verifications.d.ts +55 -43
  994. package/dist/tools/query-verifications.d.ts.map +1 -1
  995. package/dist/tools/query-verifications.js +47 -46
  996. package/dist/tools/query-verifications.js.map +1 -1
  997. package/dist/tools/query-verifications.test.js +42 -35
  998. package/dist/tools/query-verifications.test.js.map +1 -1
  999. package/dist/tools/routing-telemetry.d.ts +168 -0
  1000. package/dist/tools/routing-telemetry.d.ts.map +1 -0
  1001. package/dist/tools/routing-telemetry.js +267 -0
  1002. package/dist/tools/routing-telemetry.js.map +1 -0
  1003. package/dist/tools/routing-telemetry.test.d.ts +5 -0
  1004. package/dist/tools/routing-telemetry.test.d.ts.map +1 -0
  1005. package/dist/tools/routing-telemetry.test.js +747 -0
  1006. package/dist/tools/routing-telemetry.test.js.map +1 -0
  1007. package/dist/tools/setup-claudeignore.d.ts +4 -32
  1008. package/dist/tools/setup-claudeignore.d.ts.map +1 -1
  1009. package/dist/tools/setup-claudeignore.js +18 -22
  1010. package/dist/tools/setup-claudeignore.js.map +1 -1
  1011. package/dist/tools/setup-claudeignore.test.js +50 -49
  1012. package/dist/tools/setup-claudeignore.test.js.map +1 -1
  1013. package/dist/tools/token-budget.d.ts +170 -0
  1014. package/dist/tools/token-budget.d.ts.map +1 -0
  1015. package/dist/tools/token-budget.js +219 -0
  1016. package/dist/tools/token-budget.js.map +1 -0
  1017. package/dist/tools/token-budget.test.d.ts +5 -0
  1018. package/dist/tools/token-budget.test.d.ts.map +1 -0
  1019. package/dist/tools/token-budget.test.js +293 -0
  1020. package/dist/tools/token-budget.test.js.map +1 -0
  1021. package/package.json +72 -10
  1022. package/dist/backends/local-jsonl.test.d.ts +0 -2
  1023. package/dist/backends/local-jsonl.test.d.ts.map +0 -1
  1024. package/dist/backends/local-jsonl.test.js +0 -4651
  1025. package/dist/backends/local-jsonl.test.js.map +0 -1
  1026. package/dist/backends/signoz-api-circuit-breaker.test.d.ts +0 -6
  1027. package/dist/backends/signoz-api-circuit-breaker.test.d.ts.map +0 -1
  1028. package/dist/backends/signoz-api-circuit-breaker.test.js +0 -548
  1029. package/dist/backends/signoz-api-circuit-breaker.test.js.map +0 -1
  1030. package/dist/backends/signoz-api-rate-limiter.test.d.ts +0 -6
  1031. package/dist/backends/signoz-api-rate-limiter.test.d.ts.map +0 -1
  1032. package/dist/backends/signoz-api-rate-limiter.test.js +0 -390
  1033. package/dist/backends/signoz-api-rate-limiter.test.js.map +0 -1
  1034. package/dist/backends/signoz-api-ssrf.test.d.ts +0 -6
  1035. package/dist/backends/signoz-api-ssrf.test.d.ts.map +0 -1
  1036. package/dist/backends/signoz-api-ssrf.test.js +0 -216
  1037. package/dist/backends/signoz-api-ssrf.test.js.map +0 -1
  1038. package/dist/backends/signoz-api-test-helpers.d.ts +0 -80
  1039. package/dist/backends/signoz-api-test-helpers.d.ts.map +0 -1
  1040. package/dist/backends/signoz-api-test-helpers.js +0 -79
  1041. package/dist/backends/signoz-api-test-helpers.js.map +0 -1
  1042. package/dist/backends/signoz-api.d.ts +0 -109
  1043. package/dist/backends/signoz-api.d.ts.map +0 -1
  1044. package/dist/backends/signoz-api.integration.test.d.ts +0 -8
  1045. package/dist/backends/signoz-api.integration.test.d.ts.map +0 -1
  1046. package/dist/backends/signoz-api.integration.test.js +0 -137
  1047. package/dist/backends/signoz-api.integration.test.js.map +0 -1
  1048. package/dist/backends/signoz-api.js +0 -1132
  1049. package/dist/backends/signoz-api.js.map +0 -1
  1050. package/dist/backends/signoz-api.test.d.ts +0 -11
  1051. package/dist/backends/signoz-api.test.d.ts.map +0 -1
  1052. package/dist/backends/signoz-api.test.js +0 -832
  1053. package/dist/backends/signoz-api.test.js.map +0 -1
  1054. package/dist/lib/agent-as-judge.d.ts +0 -388
  1055. package/dist/lib/agent-as-judge.d.ts.map +0 -1
  1056. package/dist/lib/agent-as-judge.js +0 -740
  1057. package/dist/lib/agent-as-judge.js.map +0 -1
  1058. package/dist/lib/agent-as-judge.test.d.ts.map +0 -1
  1059. package/dist/lib/agent-as-judge.test.js +0 -816
  1060. package/dist/lib/agent-as-judge.test.js.map +0 -1
  1061. package/dist/lib/cache.d.ts +0 -90
  1062. package/dist/lib/cache.d.ts.map +0 -1
  1063. package/dist/lib/cache.js +0 -133
  1064. package/dist/lib/cache.js.map +0 -1
  1065. package/dist/lib/cache.test.d.ts.map +0 -1
  1066. package/dist/lib/cache.test.js +0 -105
  1067. package/dist/lib/cache.test.js.map +0 -1
  1068. package/dist/lib/circuit-breaker.d.ts +0 -101
  1069. package/dist/lib/circuit-breaker.d.ts.map +0 -1
  1070. package/dist/lib/circuit-breaker.js +0 -158
  1071. package/dist/lib/circuit-breaker.js.map +0 -1
  1072. package/dist/lib/circuit-breaker.test.d.ts.map +0 -1
  1073. package/dist/lib/circuit-breaker.test.js +0 -263
  1074. package/dist/lib/circuit-breaker.test.js.map +0 -1
  1075. package/dist/lib/confident-export.d.ts +0 -101
  1076. package/dist/lib/confident-export.d.ts.map +0 -1
  1077. package/dist/lib/confident-export.js +0 -393
  1078. package/dist/lib/confident-export.js.map +0 -1
  1079. package/dist/lib/confident-export.test.d.ts.map +0 -1
  1080. package/dist/lib/confident-export.test.js +0 -835
  1081. package/dist/lib/confident-export.test.js.map +0 -1
  1082. package/dist/lib/constants-symlink.test.d.ts.map +0 -1
  1083. package/dist/lib/constants-symlink.test.js +0 -357
  1084. package/dist/lib/constants-symlink.test.js.map +0 -1
  1085. package/dist/lib/constants.d.ts +0 -183
  1086. package/dist/lib/constants.d.ts.map +0 -1
  1087. package/dist/lib/constants.js +0 -453
  1088. package/dist/lib/constants.js.map +0 -1
  1089. package/dist/lib/constants.test.d.ts.map +0 -1
  1090. package/dist/lib/constants.test.js +0 -717
  1091. package/dist/lib/constants.test.js.map +0 -1
  1092. package/dist/lib/datadog-export.d.ts +0 -156
  1093. package/dist/lib/datadog-export.d.ts.map +0 -1
  1094. package/dist/lib/datadog-export.js +0 -464
  1095. package/dist/lib/datadog-export.js.map +0 -1
  1096. package/dist/lib/datadog-export.test.d.ts +0 -14
  1097. package/dist/lib/datadog-export.test.d.ts.map +0 -1
  1098. package/dist/lib/datadog-export.test.js +0 -890
  1099. package/dist/lib/datadog-export.test.js.map +0 -1
  1100. package/dist/lib/edge-cases.test.d.ts.map +0 -1
  1101. package/dist/lib/edge-cases.test.js +0 -634
  1102. package/dist/lib/edge-cases.test.js.map +0 -1
  1103. package/dist/lib/error-sanitizer.d.ts +0 -57
  1104. package/dist/lib/error-sanitizer.d.ts.map +0 -1
  1105. package/dist/lib/error-sanitizer.js +0 -233
  1106. package/dist/lib/error-sanitizer.js.map +0 -1
  1107. package/dist/lib/error-sanitizer.test.d.ts.map +0 -1
  1108. package/dist/lib/error-sanitizer.test.js +0 -528
  1109. package/dist/lib/error-sanitizer.test.js.map +0 -1
  1110. package/dist/lib/error-types.d.ts +0 -54
  1111. package/dist/lib/error-types.d.ts.map +0 -1
  1112. package/dist/lib/error-types.js +0 -154
  1113. package/dist/lib/error-types.js.map +0 -1
  1114. package/dist/lib/error-types.test.d.ts.map +0 -1
  1115. package/dist/lib/error-types.test.js +0 -196
  1116. package/dist/lib/error-types.test.js.map +0 -1
  1117. package/dist/lib/evaluation-hooks.d.ts +0 -49
  1118. package/dist/lib/evaluation-hooks.d.ts.map +0 -1
  1119. package/dist/lib/evaluation-hooks.js +0 -488
  1120. package/dist/lib/evaluation-hooks.js.map +0 -1
  1121. package/dist/lib/evaluation-hooks.test.d.ts.map +0 -1
  1122. package/dist/lib/evaluation-hooks.test.js +0 -624
  1123. package/dist/lib/evaluation-hooks.test.js.map +0 -1
  1124. package/dist/lib/export-utils.d.ts +0 -99
  1125. package/dist/lib/export-utils.d.ts.map +0 -1
  1126. package/dist/lib/export-utils.js +0 -238
  1127. package/dist/lib/export-utils.js.map +0 -1
  1128. package/dist/lib/export-utils.test.d.ts.map +0 -1
  1129. package/dist/lib/export-utils.test.js +0 -193
  1130. package/dist/lib/export-utils.test.js.map +0 -1
  1131. package/dist/lib/file-utils.d.ts +0 -320
  1132. package/dist/lib/file-utils.d.ts.map +0 -1
  1133. package/dist/lib/file-utils.js +0 -816
  1134. package/dist/lib/file-utils.js.map +0 -1
  1135. package/dist/lib/file-utils.test.d.ts.map +0 -1
  1136. package/dist/lib/file-utils.test.js +0 -1333
  1137. package/dist/lib/file-utils.test.js.map +0 -1
  1138. package/dist/lib/histogram.d.ts +0 -119
  1139. package/dist/lib/histogram.d.ts.map +0 -1
  1140. package/dist/lib/histogram.js +0 -202
  1141. package/dist/lib/histogram.js.map +0 -1
  1142. package/dist/lib/histogram.test.d.ts.map +0 -1
  1143. package/dist/lib/histogram.test.js +0 -381
  1144. package/dist/lib/histogram.test.js.map +0 -1
  1145. package/dist/lib/indexer.d.ts +0 -96
  1146. package/dist/lib/indexer.d.ts.map +0 -1
  1147. package/dist/lib/indexer.js +0 -353
  1148. package/dist/lib/indexer.js.map +0 -1
  1149. package/dist/lib/indexer.test.d.ts.map +0 -1
  1150. package/dist/lib/indexer.test.js +0 -696
  1151. package/dist/lib/indexer.test.js.map +0 -1
  1152. package/dist/lib/input-validator.d.ts +0 -115
  1153. package/dist/lib/input-validator.d.ts.map +0 -1
  1154. package/dist/lib/input-validator.fuzz.test.d.ts.map +0 -1
  1155. package/dist/lib/input-validator.fuzz.test.js +0 -290
  1156. package/dist/lib/input-validator.fuzz.test.js.map +0 -1
  1157. package/dist/lib/input-validator.js +0 -304
  1158. package/dist/lib/input-validator.js.map +0 -1
  1159. package/dist/lib/input-validator.test.d.ts.map +0 -1
  1160. package/dist/lib/input-validator.test.js +0 -415
  1161. package/dist/lib/input-validator.test.js.map +0 -1
  1162. package/dist/lib/instrumentation.d.ts +0 -153
  1163. package/dist/lib/instrumentation.d.ts.map +0 -1
  1164. package/dist/lib/instrumentation.integration.test.d.ts.map +0 -1
  1165. package/dist/lib/instrumentation.integration.test.js +0 -589
  1166. package/dist/lib/instrumentation.integration.test.js.map +0 -1
  1167. package/dist/lib/instrumentation.js +0 -520
  1168. package/dist/lib/instrumentation.js.map +0 -1
  1169. package/dist/lib/instrumentation.test.d.ts.map +0 -1
  1170. package/dist/lib/instrumentation.test.js +0 -821
  1171. package/dist/lib/instrumentation.test.js.map +0 -1
  1172. package/dist/lib/langfuse-export.d.ts +0 -125
  1173. package/dist/lib/langfuse-export.d.ts.map +0 -1
  1174. package/dist/lib/langfuse-export.js +0 -367
  1175. package/dist/lib/langfuse-export.js.map +0 -1
  1176. package/dist/lib/langfuse-export.test.d.ts.map +0 -1
  1177. package/dist/lib/langfuse-export.test.js +0 -1007
  1178. package/dist/lib/langfuse-export.test.js.map +0 -1
  1179. package/dist/lib/llm-as-judge.d.ts +0 -657
  1180. package/dist/lib/llm-as-judge.d.ts.map +0 -1
  1181. package/dist/lib/llm-as-judge.js +0 -1397
  1182. package/dist/lib/llm-as-judge.js.map +0 -1
  1183. package/dist/lib/llm-as-judge.test.d.ts.map +0 -1
  1184. package/dist/lib/llm-as-judge.test.js +0 -2409
  1185. package/dist/lib/llm-as-judge.test.js.map +0 -1
  1186. package/dist/lib/logger.d.ts +0 -46
  1187. package/dist/lib/logger.d.ts.map +0 -1
  1188. package/dist/lib/logger.js +0 -81
  1189. package/dist/lib/logger.js.map +0 -1
  1190. package/dist/lib/logger.test.d.ts.map +0 -1
  1191. package/dist/lib/logger.test.js.map +0 -1
  1192. package/dist/lib/metrics.d.ts +0 -62
  1193. package/dist/lib/metrics.d.ts.map +0 -1
  1194. package/dist/lib/metrics.js +0 -166
  1195. package/dist/lib/metrics.js.map +0 -1
  1196. package/dist/lib/metrics.test.d.ts.map +0 -1
  1197. package/dist/lib/metrics.test.js +0 -189
  1198. package/dist/lib/metrics.test.js.map +0 -1
  1199. package/dist/lib/otlp-export.d.ts +0 -178
  1200. package/dist/lib/otlp-export.d.ts.map +0 -1
  1201. package/dist/lib/otlp-export.js +0 -382
  1202. package/dist/lib/otlp-export.js.map +0 -1
  1203. package/dist/lib/parse-stats.d.ts.map +0 -1
  1204. package/dist/lib/parse-stats.js +0 -206
  1205. package/dist/lib/parse-stats.js.map +0 -1
  1206. package/dist/lib/parse-stats.test.d.ts.map +0 -1
  1207. package/dist/lib/parse-stats.test.js +0 -283
  1208. package/dist/lib/parse-stats.test.js.map +0 -1
  1209. package/dist/lib/phoenix-export.d.ts +0 -109
  1210. package/dist/lib/phoenix-export.d.ts.map +0 -1
  1211. package/dist/lib/phoenix-export.js +0 -429
  1212. package/dist/lib/phoenix-export.js.map +0 -1
  1213. package/dist/lib/phoenix-export.test.d.ts.map +0 -1
  1214. package/dist/lib/phoenix-export.test.js +0 -725
  1215. package/dist/lib/phoenix-export.test.js.map +0 -1
  1216. package/dist/lib/query-sanitizer.d.ts.map +0 -1
  1217. package/dist/lib/query-sanitizer.js +0 -261
  1218. package/dist/lib/query-sanitizer.js.map +0 -1
  1219. package/dist/lib/query-sanitizer.test.d.ts.map +0 -1
  1220. package/dist/lib/query-sanitizer.test.js +0 -400
  1221. package/dist/lib/query-sanitizer.test.js.map +0 -1
  1222. package/dist/lib/server-utils.d.ts +0 -93
  1223. package/dist/lib/server-utils.d.ts.map +0 -1
  1224. package/dist/lib/server-utils.js +0 -181
  1225. package/dist/lib/server-utils.js.map +0 -1
  1226. package/dist/lib/shared-schemas.d.ts +0 -87
  1227. package/dist/lib/shared-schemas.d.ts.map +0 -1
  1228. package/dist/lib/shared-schemas.js +0 -87
  1229. package/dist/lib/shared-schemas.js.map +0 -1
  1230. package/dist/lib/shared-schemas.test.d.ts.map +0 -1
  1231. package/dist/lib/shared-schemas.test.js +0 -106
  1232. package/dist/lib/shared-schemas.test.js.map +0 -1
  1233. package/dist/lib/toon-encoder.d.ts +0 -26
  1234. package/dist/lib/toon-encoder.d.ts.map +0 -1
  1235. package/dist/lib/toon-encoder.js +0 -61
  1236. package/dist/lib/toon-encoder.js.map +0 -1
  1237. package/dist/lib/toon-encoder.test.d.ts.map +0 -1
  1238. package/dist/lib/toon-encoder.test.js +0 -85
  1239. package/dist/lib/toon-encoder.test.js.map +0 -1
  1240. package/dist/lib/verification-events.d.ts +0 -100
  1241. package/dist/lib/verification-events.d.ts.map +0 -1
  1242. package/dist/lib/verification-events.js +0 -162
  1243. package/dist/lib/verification-events.js.map +0 -1
  1244. package/dist/lib/verification-events.test.d.ts.map +0 -1
  1245. package/dist/lib/verification-events.test.js +0 -193
  1246. package/dist/lib/verification-events.test.js.map +0 -1
  1247. package/dist/tools/signoz.integration.test.d.ts +0 -8
  1248. package/dist/tools/signoz.integration.test.d.ts.map +0 -1
  1249. package/dist/tools/signoz.integration.test.js +0 -141
  1250. package/dist/tools/signoz.integration.test.js.map +0 -1
  1251. package/dist/types/evaluation-hooks.d.ts +0 -176
  1252. package/dist/types/evaluation-hooks.d.ts.map +0 -1
  1253. package/dist/types/evaluation-hooks.js +0 -49
  1254. package/dist/types/evaluation-hooks.js.map +0 -1
  1255. /package/dist/lib/{agent-as-judge.test.d.ts → agent-judge/agent-as-judge.test.d.ts} +0 -0
  1256. /package/dist/lib/{verification-events.test.d.ts → audit/verification-events.test.d.ts} +0 -0
  1257. /package/dist/lib/{constants-symlink.test.d.ts → core/constants-symlink.test.d.ts} +0 -0
  1258. /package/dist/lib/{constants.test.d.ts → core/constants.test.d.ts} +0 -0
  1259. /package/dist/lib/{edge-cases.test.d.ts → core/edge-cases.test.d.ts} +0 -0
  1260. /package/dist/lib/{file-utils.test.d.ts → core/file-utils.test.d.ts} +0 -0
  1261. /package/dist/lib/{input-validator.fuzz.test.d.ts → core/input-validator.fuzz.test.d.ts} +0 -0
  1262. /package/dist/lib/{input-validator.test.d.ts → core/input-validator.test.d.ts} +0 -0
  1263. /package/dist/lib/{logger.test.d.ts → core/logger.test.d.ts} +0 -0
  1264. /package/dist/lib/{logger.test.js → core/logger.test.js} +0 -0
  1265. /package/dist/lib/{shared-schemas.test.d.ts → core/shared-schemas.test.d.ts} +0 -0
  1266. /package/dist/lib/{error-sanitizer.test.d.ts → errors/error-sanitizer.test.d.ts} +0 -0
  1267. /package/dist/lib/{error-types.test.d.ts → errors/error-types.test.d.ts} +0 -0
  1268. /package/dist/lib/{query-sanitizer.d.ts → errors/query-sanitizer.d.ts} +0 -0
  1269. /package/dist/lib/{query-sanitizer.test.d.ts → errors/query-sanitizer.test.d.ts} +0 -0
  1270. /package/dist/lib/{confident-export.test.d.ts → exports/confident-export.test.d.ts} +0 -0
  1271. /package/dist/lib/{export-utils.test.d.ts → exports/export-utils.test.d.ts} +0 -0
  1272. /package/dist/lib/{langfuse-export.test.d.ts → exports/langfuse-export.test.d.ts} +0 -0
  1273. /package/dist/lib/{phoenix-export.test.d.ts → exports/phoenix-export.test.d.ts} +0 -0
  1274. /package/dist/lib/{evaluation-hooks.test.d.ts → judge/evaluation-hooks.test.d.ts} +0 -0
  1275. /package/dist/lib/{llm-as-judge.test.d.ts → judge/llm-as-judge.test.d.ts} +0 -0
  1276. /package/dist/lib/{histogram.test.d.ts → observability/histogram.test.d.ts} +0 -0
  1277. /package/dist/lib/{indexer.test.d.ts → observability/indexer.test.d.ts} +0 -0
  1278. /package/dist/lib/{instrumentation.integration.test.d.ts → observability/instrumentation.integration.test.d.ts} +0 -0
  1279. /package/dist/lib/{instrumentation.test.d.ts → observability/instrumentation.test.d.ts} +0 -0
  1280. /package/dist/lib/{metrics.test.d.ts → observability/metrics.test.d.ts} +0 -0
  1281. /package/dist/lib/{parse-stats.d.ts → observability/parse-stats.d.ts} +0 -0
  1282. /package/dist/lib/{parse-stats.test.d.ts → observability/parse-stats.test.d.ts} +0 -0
  1283. /package/dist/lib/{cache.test.d.ts → resilience/cache.test.d.ts} +0 -0
  1284. /package/dist/lib/{circuit-breaker.test.d.ts → resilience/circuit-breaker.test.d.ts} +0 -0
  1285. /package/dist/lib/{toon-encoder.test.d.ts → resilience/toon-encoder.test.d.ts} +0 -0
@@ -0,0 +1,2076 @@
1
+ /**
2
+ * Feature Engineering for Evaluation Analytics (Design Doc Section 16)
3
+ *
4
+ * Derived features, adaptive score scaling, label encoding, and composite metrics
5
+ * computed from existing evaluation data. Enhances anomaly detection, correlation
6
+ * discovery, and role-appropriate information presentation.
7
+ *
8
+ * @see quality-metrics.ts for core types
9
+ * @see frontend/docs/llm-explainability-design.md Section 16
10
+ */
11
+ import { readFileSync, writeFileSync } from 'fs';
12
+ import { join } from 'path';
13
+ import { z } from 'zod';
14
+ import { normalizedScoreSchema } from '../core/shared-schemas.js';
15
+ import { pipe, map, filter, sort } from 'remeda';
16
+ import { roundTo, SCORE_PRECISION, } from './quality-metrics.js';
17
+ import { AUDITOR_EXPLANATION_TRUNCATION, AUDITOR_MAX_WORST_EVALUATIONS, BINARY_SCALE_EXCELLENT_THRESHOLD, CONFIRMATION_MIN_CONSECUTIVE_BREACHES, COVERAGE_SAMPLE_HIGH_THRESHOLD, COVERAGE_SAMPLE_LOW_THRESHOLD, DYNAMICS_CONFIDENCE_BASE, DYNAMICS_CONFIDENCE_MAX, DYNAMICS_CONFIDENCE_STEP, EWMA_BASELINE_FRACTION, EWMA_BASELINE_MIN_POINTS, EWMA_DRIFT_MIN_VALUES, EWMA_RANGE_FALLBACK_RATIO, EXECUTIVE_EXPLANATION_TRUNCATION, EXECUTIVE_MAX_WORST_EVALUATIONS, GRANGER_FIXED_LAG_ORDER, LATENCY_NORMALIZATION_MAX_SECONDS, LATENCY_SKEW_SIGNAL_THRESHOLD, LOG_SCALE_DIVISOR, MAD_CONSISTENCY_FACTOR, MIN_PERIOD_HOURS, NUMERICAL_MIN_DENOMINATOR, OPERATOR_MAX_WORST_EVALUATIONS, SCORE_BAND_ADEQUATE, SCORE_BAND_EXCELLENT, SCORE_BAND_GOOD, SCORE_BAND_POOR, STEP_SCALE_ADEQUATE_THRESHOLD, STEP_SCALE_EXCELLENT_THRESHOLD, DEGRADATION_BASELINE_RATIO, DEGRADATION_STATE_FILE, MIN_BUCKETS_FOR_SIGNAL, CALIBRATION_WINDOW_DAYS, PSI_RECALIBRATION_THRESHOLD, CALIBRATION_STATE_FILE, DEFAULT_BIN_COUNT, PSI_LOG_EPSILON, AHP_RANDOM_INDEX, AHP_CRITIC_DEFAULT_ALPHA, TUNED_CQI_WEIGHTS_FEATURE_VERSION, } from './quality-constants.js';
18
+ import { TIME_MS, } from '../core/units.js';
19
+ import { TEXT_PREVIEW_LIMIT } from '../core/constants.js';
20
+ import * as ss from 'simple-statistics';
21
+ import tCdf from '@stdlib/stats-base-dists-t-cdf';
22
+ import fCdf from '@stdlib/stats-base-dists-f-cdf';
23
+ import { DDSketch } from '@datadog/sketches-js';
24
+ import { Matrix, solve } from 'ml-matrix';
25
+ const QUANTILE_P10 = 0.1;
26
+ const QUANTILE_P25 = 0.25;
27
+ const QUANTILE_P50 = 0.5;
28
+ const QUANTILE_P75 = 0.75;
29
+ const QUANTILE_P90 = 0.9;
30
+ const DEFAULT_SENSITIVITY_DELTA = 0.05;
31
+ const CORRELATION_MIN_POINTS = 3;
32
+ const FORMAT_JSON_INDENT_SPACES = 2;
33
+ /** Default scale strategy per metric */
34
+ export const METRIC_SCALE_STRATEGY = {
35
+ relevance: 'quantile',
36
+ faithfulness: 'binary',
37
+ coherence: 'uniform',
38
+ hallucination: 'log',
39
+ task_completion: 'step',
40
+ tool_correctness: 'categorical',
41
+ evaluation_latency: 'percentile_rank',
42
+ };
43
+ /**
44
+ * Map a score value to a 5-band color band with direction awareness.
45
+ * For 'minimize' metrics (hallucination, latency), low scores are excellent.
46
+ * For 'maximize' metrics (relevance, faithfulness), high scores are excellent.
47
+ */
48
+ export function scoreColorBand(value, direction = 'maximize') {
49
+ const v = direction === 'minimize' ? 1 - value : value;
50
+ if (v >= SCORE_BAND_EXCELLENT)
51
+ return 'excellent';
52
+ if (v >= SCORE_BAND_GOOD)
53
+ return 'good';
54
+ if (v >= SCORE_BAND_ADEQUATE)
55
+ return 'adequate';
56
+ if (v >= SCORE_BAND_POOR)
57
+ return 'poor';
58
+ return 'failing';
59
+ }
60
+ // ============================================================================
61
+ // Zod Schemas for Feature Engineering Types
62
+ // ============================================================================
63
+ /**
64
+ * Zod schemas for runtime validation of quality feature types.
65
+ * Used for state persistence and API data validation.
66
+ */
67
+ export const percentileDistributionSchema = z.object({
68
+ p10: normalizedScoreSchema,
69
+ p25: normalizedScoreSchema,
70
+ p50: normalizedScoreSchema,
71
+ p75: normalizedScoreSchema,
72
+ p90: normalizedScoreSchema,
73
+ });
74
+ export const scoreDirectionSchema = z.enum(['maximize', 'minimize']);
75
+ export const scaleStrategySchema = z.enum([
76
+ 'quantile',
77
+ 'binary',
78
+ 'uniform',
79
+ 'log',
80
+ 'step',
81
+ 'categorical',
82
+ 'percentile_rank',
83
+ ]);
84
+ export const scoreColorBandSchema = z.enum(['excellent', 'good', 'adequate', 'poor', 'failing']);
85
+ export const degradationStateSchema = z.object({
86
+ lastRun: z.string(),
87
+ breaches: z.record(z.string(), z.int().nonnegative()),
88
+ });
89
+ export const calibrationStateSchema = z.object({
90
+ lastCalibrated: z.string(),
91
+ distributions: z.record(z.string(), z.object({
92
+ distribution: percentileDistributionSchema,
93
+ sampleSize: z.int().nonnegative(),
94
+ windowStart: z.string(),
95
+ windowEnd: z.string(),
96
+ })),
97
+ psiValues: z.record(z.string(), z.number()).optional(),
98
+ rawScores: z.record(z.string(), z.array(z.number())).optional(),
99
+ });
100
+ export const psiResultSchema = z.object({
101
+ psi: z.number().nonnegative(),
102
+ drifted: z.boolean(),
103
+ });
104
+ export const backtestConfigSchema = z.object({
105
+ varianceThreshold: z.number().positive(),
106
+ coverageDropoutThreshold: normalizedScoreSchema,
107
+ latencySkewThreshold: z.number().positive(),
108
+ confirmationWindow: z.int().positive(),
109
+ ewmaLambda: normalizedScoreSchema.optional().describe('EWMA smoothing factor for drift detection (0–1). Default: EWMA_LAMBDA'),
110
+ stabilityThreshold: z.number().positive().optional().describe('Number of MAD multiples above baseline that constitute drift'),
111
+ });
112
+ export const backtestSnapshotSchema = z.object({
113
+ currentStdDev: z.number().nonnegative(),
114
+ baselineStdDev: z.number().nonnegative(),
115
+ coverageGapCount: z.int().nonnegative(),
116
+ totalCoverageCells: z.int().nonnegative(),
117
+ latencyP95: z.number().nonnegative().optional(),
118
+ latencyP50: z.number().nonnegative().optional(),
119
+ });
120
+ export const taprMetricsSchema = z.object({
121
+ precision: normalizedScoreSchema,
122
+ recall: normalizedScoreSchema,
123
+ f1: normalizedScoreSchema,
124
+ detectionDelay: z.number().nonnegative(),
125
+ });
126
+ export const degradationBacktestResultSchema = z.object({
127
+ config: backtestConfigSchema,
128
+ truePositives: z.int().nonnegative(),
129
+ falsePositives: z.int().nonnegative(),
130
+ falseNegatives: z.int().nonnegative(),
131
+ trueNegatives: z.int().nonnegative(),
132
+ tapr: taprMetricsSchema,
133
+ pointPrecision: normalizedScoreSchema,
134
+ pointRecall: normalizedScoreSchema,
135
+ pointF1: normalizedScoreSchema,
136
+ });
137
+ export const backtestSweepResultSchema = z.object({
138
+ results: z.array(degradationBacktestResultSchema),
139
+ bestByF1: degradationBacktestResultSchema,
140
+ bestByRecall: degradationBacktestResultSchema,
141
+ currentConfigResult: degradationBacktestResultSchema,
142
+ });
143
+ export const ahpComparisonSchema = z.object({
144
+ metricA: z.string().min(1),
145
+ metricB: z.string().min(1),
146
+ preference: z.enum(['1', '3', '5', '7', '9']).transform((v) => parseInt(v)),
147
+ preferred: z.enum(['A', 'B']),
148
+ }).refine((data) => data.metricA < data.metricB, {
149
+ error: 'metricA must be less than metricB (alphabetically sorted)'
150
+ });
151
+ export const tunedCQIWeightsSchema = z.object({
152
+ featureVersion: z.string(),
153
+ weights: z.record(z.string(), normalizedScoreSchema),
154
+ ahpWeights: z.record(z.string(), z.number().nonnegative()),
155
+ criticWeights: z.record(z.string(), z.number().nonnegative()),
156
+ alpha: z.number(),
157
+ consistencyRatio: z.number(),
158
+ incidentCorrelations: z.record(z.string(), z.number()),
159
+ });
160
+ /**
161
+ * Compute empirical CDF position for a value within a percentile distribution.
162
+ * Returns 0-1 representing where the value falls in the empirical distribution.
163
+ * Uses linear interpolation between known percentile points.
164
+ */
165
+ export function empiricalCDF(value, dist) {
166
+ if (!Number.isFinite(value))
167
+ return QUANTILE_P50;
168
+ const points = [
169
+ [dist.p10, QUANTILE_P10],
170
+ [dist.p25, QUANTILE_P25],
171
+ [dist.p50, QUANTILE_P50],
172
+ [dist.p75, QUANTILE_P75],
173
+ [dist.p90, QUANTILE_P90],
174
+ ];
175
+ // Below p10
176
+ if (value <= dist.p10)
177
+ return Math.max(0, QUANTILE_P10 * (value / Math.max(dist.p10, NUMERICAL_MIN_DENOMINATOR)));
178
+ // Above p90
179
+ if (value >= dist.p90)
180
+ return Math.min(1, QUANTILE_P90 + QUANTILE_P10 * Math.min(1, (value - dist.p90) / Math.max(1 - dist.p90, NUMERICAL_MIN_DENOMINATOR)));
181
+ // Linear interpolation between adjacent percentile points
182
+ for (let i = 0; i < points.length - 1; i++) {
183
+ const [v0, p0] = points[i];
184
+ const [v1, p1] = points[i + 1];
185
+ if (value >= v0 && value <= v1) {
186
+ const range = v1 - v0;
187
+ if (range === 0)
188
+ return p0;
189
+ return p0 + (p1 - p0) * ((value - v0) / range);
190
+ }
191
+ }
192
+ return QUANTILE_P50; // Fallback
193
+ }
194
+ /**
195
+ * Minimum sample size for quantile-based scaling to be considered valid.
196
+ * Below this threshold, quantile scaling falls back to uniform even when
197
+ * distribution data is provided (insufficient data for reliable percentiles).
198
+ */
199
+ export const MIN_QUANTILE_SAMPLE_SIZE = 100;
200
+ /**
201
+ * Adaptive score color band using per-metric scaling strategies.
202
+ * Falls back to uniform scoreColorBand when distribution data is unavailable.
203
+ *
204
+ * @param value - Raw score value
205
+ * @param metric - Metric name (used to look up ScaleStrategy)
206
+ * @param direction - Score direction (maximize or minimize)
207
+ * @param distribution - Optional empirical percentile distribution for quantile scaling
208
+ * @param sampleSize - Number of samples used to compute distribution; quantile requires >= 100
209
+ * @example
210
+ * ```ts
211
+ * adaptiveScoreColorBand(0.92, 'relevance'); // 'excellent' (uniform, maximize)
212
+ * adaptiveScoreColorBand(0.3, 'hallucination', 'minimize'); // 'excellent' (low hallucination is good)
213
+ * adaptiveScoreColorBand(0.5, 'coherence', 'maximize', dist, 200); // quantile-scaled band
214
+ * ```
215
+ */
216
+ export function adaptiveScoreColorBand(value, metric, direction = 'maximize', distribution, sampleSize) {
217
+ const strategy = METRIC_SCALE_STRATEGY[metric] ?? 'uniform';
218
+ switch (strategy) {
219
+ case 'quantile': {
220
+ if (!distribution || (sampleSize !== undefined && sampleSize < MIN_QUANTILE_SAMPLE_SIZE)) {
221
+ return scoreColorBand(value, direction);
222
+ }
223
+ const rank = empiricalCDF(value, distribution);
224
+ return scoreColorBand(rank, 'maximize');
225
+ }
226
+ case 'log': {
227
+ const clamped = Math.max(value, NUMERICAL_MIN_DENOMINATOR);
228
+ const logNorm = Math.min(1, -Math.log10(clamped) / LOG_SCALE_DIVISOR);
229
+ return scoreColorBand(logNorm, 'maximize');
230
+ }
231
+ case 'binary':
232
+ return value >= BINARY_SCALE_EXCELLENT_THRESHOLD ? 'excellent' : 'failing';
233
+ case 'step':
234
+ // Rule-based: binary pass/fail; LLM-based: standard 5-band
235
+ return value >= STEP_SCALE_EXCELLENT_THRESHOLD ? 'excellent' : value >= STEP_SCALE_ADEQUATE_THRESHOLD ? 'adequate' : 'failing';
236
+ case 'categorical':
237
+ // Display as fraction; color by standard bands
238
+ return scoreColorBand(value, direction);
239
+ case 'percentile_rank':
240
+ // Without distribution data, fall back to uniform
241
+ if (!distribution)
242
+ return scoreColorBand(value, direction);
243
+ return scoreColorBand(empiricalCDF(value, distribution), 'maximize');
244
+ case 'uniform':
245
+ default:
246
+ return scoreColorBand(value, direction);
247
+ }
248
+ }
249
+ /**
250
+ * Metrics where lower scores are better. Used as fallback when alert
251
+ * configuration is unavailable for direction inference.
252
+ */
253
+ export const MINIMIZE_METRICS = new Set([
254
+ 'hallucination',
255
+ 'evaluation_latency',
256
+ 'error_rate',
257
+ 'cost',
258
+ ]);
259
+ /**
260
+ * Infer ScoreDirection from a metric's alert configuration.
261
+ * Metrics whose primary alert fires on 'below' are 'maximize' metrics.
262
+ * Metrics whose primary alert fires on 'above' are 'minimize' metrics.
263
+ */
264
+ export function inferScoreDirection(alertDirection) {
265
+ return alertDirection === 'above' ? 'minimize' : 'maximize';
266
+ }
267
+ /** Known label-to-ordinal lookup table */
268
+ const LABEL_ORDINAL_MAP = {
269
+ // Ordinal 4 - Excellent (Pass)
270
+ excellent: { ordinal: 4, category: 'Pass' },
271
+ highly_relevant: { ordinal: 4, category: 'Pass' },
272
+ fully_faithful: { ordinal: 4, category: 'Pass' },
273
+ perfect: { ordinal: 4, category: 'Pass' },
274
+ // Ordinal 3 - Good (Pass)
275
+ relevant: { ordinal: 3, category: 'Pass' },
276
+ good: { ordinal: 3, category: 'Pass' },
277
+ faithful: { ordinal: 3, category: 'Pass' },
278
+ pass: { ordinal: 3, category: 'Pass' },
279
+ correct: { ordinal: 3, category: 'Pass' },
280
+ coherent: { ordinal: 3, category: 'Pass' },
281
+ // Ordinal 2 - Review
282
+ partial: { ordinal: 2, category: 'Review' },
283
+ borderline: { ordinal: 2, category: 'Review' },
284
+ adequate: { ordinal: 2, category: 'Review' },
285
+ mixed: { ordinal: 2, category: 'Review' },
286
+ // Ordinal 1 - Fail
287
+ off_topic: { ordinal: 1, category: 'Fail' },
288
+ irrelevant: { ordinal: 1, category: 'Fail' },
289
+ unfaithful: { ordinal: 1, category: 'Fail' },
290
+ fail: { ordinal: 1, category: 'Fail' },
291
+ incorrect: { ordinal: 1, category: 'Fail' },
292
+ incoherent: { ordinal: 1, category: 'Fail' },
293
+ // Ordinal 0 - Critical Fail
294
+ hallucinated: { ordinal: 0, category: 'Fail' },
295
+ fabricated: { ordinal: 0, category: 'Fail' },
296
+ toxic: { ordinal: 0, category: 'Fail' },
297
+ dangerous: { ordinal: 0, category: 'Fail' },
298
+ };
299
+ /**
300
+ * Normalize a label for lookup: lowercase, replace hyphens with underscores.
301
+ */
302
+ function normalizeLabel(label) {
303
+ return label.toLowerCase().replace(/-/g, '_').trim();
304
+ }
305
+ /**
306
+ * Convert a free-text score label to an ordinal value for sorting/filtering.
307
+ * Unmapped labels default to ordinal 2 ("Review") with mapped=false.
308
+ *
309
+ * @param label - Free-text score label from EvaluationResult.scoreLabel
310
+ * @returns Ordinal encoding with category and mapping status
311
+ */
312
+ export function labelToOrdinal(label) {
313
+ const normalized = normalizeLabel(label);
314
+ const entry = LABEL_ORDINAL_MAP[normalized];
315
+ if (entry) {
316
+ return { ordinal: entry.ordinal, category: entry.category, mapped: true };
317
+ }
318
+ // Default: unmapped labels are "Review" with ordinal 2
319
+ return { ordinal: 2, category: 'Review', mapped: false };
320
+ }
321
+ /**
322
+ * Get the label filter category for a given ordinal value.
323
+ */
324
+ export function ordinalToCategory(ordinal) {
325
+ if (ordinal >= 3)
326
+ return 'Pass';
327
+ if (ordinal === 2)
328
+ return 'Review';
329
+ return 'Fail';
330
+ }
331
+ // ============================================================================
332
+ // Composite Quality Index (Section 16.3)
333
+ // ============================================================================
334
+ /** CQI per-metric contribution breakdown */
335
+ export const cqiContributionSchema = z.object({
336
+ metric: z.string(),
337
+ rawScore: z.number(),
338
+ normalizedScore: z.number(),
339
+ weight: z.number(),
340
+ contribution: z.number(),
341
+ });
342
+ /** Composite Quality Index result */
343
+ export const compositeQualityIndexSchema = z.object({
344
+ /** Feature version for historical comparison validity */
345
+ featureVersion: z.string(),
346
+ /** Weighted composite value (0-1) */
347
+ value: normalizedScoreSchema,
348
+ /** Weight configuration used */
349
+ weights: z.record(z.string(), z.number()),
350
+ /** Per-metric breakdown */
351
+ contributions: z.array(cqiContributionSchema),
352
+ });
353
+ // ============================================================================
354
+ // CQI Weight Constants
355
+ // ============================================================================
356
+ const CQI_WEIGHT_RELEVANCE = 0.25;
357
+ const CQI_WEIGHT_FAITHFULNESS = 0.20;
358
+ const CQI_WEIGHT_HALLUCINATION = 0.20;
359
+ const CQI_WEIGHT_TASK_COMPLETION = 0.15;
360
+ const CQI_WEIGHT_COHERENCE = 0.10;
361
+ const CQI_WEIGHT_TOOL_CORRECTNESS = 0.05;
362
+ const CQI_WEIGHT_EVALUATION_LATENCY = 0.05;
363
+ /** Default CQI weights per design doc Section 16.3 */
364
+ export const DEFAULT_CQI_WEIGHTS = {
365
+ relevance: CQI_WEIGHT_RELEVANCE,
366
+ faithfulness: CQI_WEIGHT_FAITHFULNESS,
367
+ hallucination: CQI_WEIGHT_HALLUCINATION,
368
+ task_completion: CQI_WEIGHT_TASK_COMPLETION,
369
+ coherence: CQI_WEIGHT_COHERENCE,
370
+ tool_correctness: CQI_WEIGHT_TOOL_CORRECTNESS,
371
+ evaluation_latency: CQI_WEIGHT_EVALUATION_LATENCY,
372
+ };
373
+ /** Current feature version for CQI */
374
+ const CQI_FEATURE_VERSION = '1.0';
375
+ /**
376
+ * Compute Composite Quality Index from metric results.
377
+ * Direction-normalizes scores before weighting (minimize metrics are inverted).
378
+ * Metrics with no data are excluded; weights are renormalized over available metrics.
379
+ *
380
+ * @param metrics - Quality metric results from dashboard summary
381
+ * @param weights - Optional custom weights (default: DEFAULT_CQI_WEIGHTS)
382
+ * @returns CQI result, or undefined if no metrics have data
383
+ * @example
384
+ * ```ts
385
+ * const cqi = computeCQI(dashboardSummary.metrics);
386
+ * // { score: 0.82, band: 'good', contributions: [...], featureVersion: '1.0' }
387
+ *
388
+ * // Custom weights emphasizing relevance
389
+ * const custom = computeCQI(metrics, { relevance: 0.4, faithfulness: 0.3, coherence: 0.3 });
390
+ * ```
391
+ */
392
+ export function computeCQI(metrics, weights) {
393
+ const w = weights ?? DEFAULT_CQI_WEIGHTS;
394
+ const contributions = [];
395
+ let totalWeight = 0;
396
+ for (const metric of metrics) {
397
+ const weight = w[metric.name];
398
+ if (weight === undefined || weight <= 0)
399
+ continue;
400
+ // Use avg as the primary aggregation for CQI
401
+ const rawScore = metric.values.avg;
402
+ if (rawScore === null)
403
+ continue;
404
+ // Direction-normalize: minimize metrics (hallucination, eval_latency) are inverted
405
+ const direction = inferScoreDirection(metric.alerts[0]?.direction ?? (MINIMIZE_METRICS.has(metric.name) ? 'above' : 'below'));
406
+ let normalizedScore;
407
+ if (direction === 'minimize') {
408
+ // For latency (range 0-LATENCY_NORMALIZATION_MAX_SECONDS), normalize to 0-1 first
409
+ if (metric.name === 'evaluation_latency') {
410
+ normalizedScore = Math.max(0, 1 - rawScore / LATENCY_NORMALIZATION_MAX_SECONDS);
411
+ }
412
+ else {
413
+ normalizedScore = 1 - rawScore;
414
+ }
415
+ }
416
+ else {
417
+ normalizedScore = rawScore;
418
+ }
419
+ normalizedScore = Math.max(0, Math.min(1, normalizedScore));
420
+ totalWeight += weight;
421
+ contributions.push({
422
+ metric: metric.name,
423
+ rawScore: roundTo(rawScore, SCORE_PRECISION),
424
+ normalizedScore: roundTo(normalizedScore, SCORE_PRECISION),
425
+ weight,
426
+ contribution: 0, // placeholder, computed after renormalization
427
+ });
428
+ }
429
+ if (contributions.length === 0 || totalWeight === 0)
430
+ return undefined;
431
+ // Renormalize weights and compute contributions so they sum to rawValue
432
+ for (const c of contributions) {
433
+ const normalizedWeight = c.weight / totalWeight;
434
+ c.contribution = roundTo(c.normalizedScore * normalizedWeight, SCORE_PRECISION);
435
+ }
436
+ const rawValue = contributions.reduce((sum, c) => sum + c.contribution, 0);
437
+ return {
438
+ featureVersion: CQI_FEATURE_VERSION,
439
+ value: roundTo(rawValue, SCORE_PRECISION),
440
+ weights: w,
441
+ contributions,
442
+ };
443
+ }
444
+ /**
445
+ * Compute CQI sensitivity by perturbing each weight +/- delta.
446
+ * Reports the CQI range when each weight varies independently.
447
+ *
448
+ * Only metrics with positive base weights are analyzed. Perturbed weights are
449
+ * not renormalized — computeCQI handles renormalization internally, so the
450
+ * sensitivity reflects the effect of shifting one weight's relative importance.
451
+ *
452
+ * @param metrics - Quality metric results
453
+ * @param weights - Base weights (default: DEFAULT_CQI_WEIGHTS)
454
+ * @param delta - Perturbation amount (default: 0.05)
455
+ * @returns Sensitivity analysis, or undefined if CQI cannot be computed
456
+ * @example
457
+ * ```ts
458
+ * const sens = computeCQISensitivity(metrics);
459
+ * // { baseValue: 0.82, delta: 0.05, entries: [
460
+ * // { metric: 'relevance', baseWeight: 0.25, low: 0.80, high: 0.84, range: 0.04 },
461
+ * // ...
462
+ * // ]}
463
+ * ```
464
+ */
465
+ export function computeCQISensitivity(metrics, weights, delta = DEFAULT_SENSITIVITY_DELTA) {
466
+ const baseWeights = weights ?? DEFAULT_CQI_WEIGHTS;
467
+ const baseCQI = computeCQI(metrics, baseWeights);
468
+ if (!baseCQI)
469
+ return undefined;
470
+ const entries = pipe(Object.entries(baseWeights), filter(([, weight]) => weight > 0), map(([key, weight]) => {
471
+ const lowWeights = { ...baseWeights, [key]: Math.max(0, weight - delta) };
472
+ const highWeights = { ...baseWeights, [key]: weight + delta };
473
+ const lowCQI = computeCQI(metrics, lowWeights);
474
+ const highCQI = computeCQI(metrics, highWeights);
475
+ const low = lowCQI?.value ?? baseCQI.value;
476
+ const high = highCQI?.value ?? baseCQI.value;
477
+ return {
478
+ metric: key,
479
+ baseWeight: weight,
480
+ low: roundTo(Math.min(low, high), SCORE_PRECISION),
481
+ high: roundTo(Math.max(low, high), SCORE_PRECISION),
482
+ range: roundTo(Math.abs(high - low), SCORE_PRECISION),
483
+ };
484
+ }), sort((a, b) => b.range - a.range));
485
+ return {
486
+ baseValue: baseCQI.value,
487
+ delta,
488
+ entries,
489
+ };
490
+ }
491
+ const DYNAMICS_FEATURE_VERSION = '1.1';
492
+ /** Default exponential smoothing factor for velocity noise suppression */
493
+ const EMA_ALPHA = 0.3;
494
+ /**
495
+ * Apply exponential moving average to a sequence of values.
496
+ * More recent values receive higher weight (alpha).
497
+ *
498
+ * @param values - Time-ordered values (oldest first)
499
+ * @param alpha - Smoothing factor (0-1). Higher = less smoothing.
500
+ * @returns Smoothed values array of same length
501
+ */
502
+ function exponentialSmooth(values, alpha) {
503
+ if (values.length === 0)
504
+ return [];
505
+ const result = [values[0]];
506
+ for (let i = 1; i < values.length; i++) {
507
+ result.push(alpha * values[i] + (1 - alpha) * result[i - 1]);
508
+ }
509
+ return result;
510
+ }
511
+ /**
512
+ * Compute metric dynamics from consecutive trend snapshots.
513
+ * When trendHistory is provided, applies exponential smoothing (alpha=0.3)
514
+ * to suppress noise amplification from second derivatives.
515
+ *
516
+ * @param currentTrend - Current period trend
517
+ * @param previousTrend - Previous period trend (for acceleration)
518
+ * @param periodHours - Length of each period in hours
519
+ * @param thresholds - Alert thresholds for breach projection
520
+ * @param trendHistory - Optional array of historical trends (oldest first) for EMA smoothing
521
+ * @returns Metric dynamics, or undefined if insufficient data
522
+ * @example
523
+ * ```ts
524
+ * const dynamics = computeMetricDynamics(
525
+ * { currentValue: 0.85, previousValue: 0.80, delta: 0.05 },
526
+ * { currentValue: 0.80, previousValue: 0.78, delta: 0.02 },
527
+ * 1, // 1-hour periods
528
+ * [{ value: 0.7, direction: 'below', severity: 'warning' }]
529
+ * );
530
+ * // { velocity: 0.05, acceleration: 0.03, confidence: 0.5, projectedStatus: 'healthy', ... }
531
+ * ```
532
+ */
533
+ export function computeMetricDynamics(currentTrend, previousTrend, periodHours, thresholds, trendHistory) {
534
+ if (periodHours < MIN_PERIOD_HOURS)
535
+ return undefined;
536
+ let velocity;
537
+ let acceleration = 0;
538
+ let inflectionDetected = false;
539
+ if (trendHistory && trendHistory.length >= 2) {
540
+ // Apply exponential smoothing to the velocity series for noise suppression
541
+ const rawVelocities = trendHistory.map(t => t.delta / periodHours);
542
+ const smoothed = exponentialSmooth(rawVelocities, EMA_ALPHA);
543
+ velocity = smoothed[smoothed.length - 1];
544
+ // Acceleration from last two smoothed velocities
545
+ if (smoothed.length >= 2) {
546
+ const prevSmoothedVel = smoothed[smoothed.length - 2];
547
+ acceleration = (velocity - prevSmoothedVel) / periodHours;
548
+ inflectionDetected = prevSmoothedVel !== 0 && Math.sign(velocity) !== Math.sign(prevSmoothedVel);
549
+ }
550
+ }
551
+ else {
552
+ // Fallback to raw computation (backward-compatible)
553
+ velocity = currentTrend.delta / periodHours;
554
+ if (previousTrend) {
555
+ const previousVelocity = previousTrend.delta / periodHours;
556
+ acceleration = (velocity - previousVelocity) / periodHours;
557
+ inflectionDetected = previousVelocity !== 0 && Math.sign(velocity) !== Math.sign(previousVelocity);
558
+ }
559
+ }
560
+ // Confidence: higher with more data points and smoothing
561
+ // 0.275 (1 trend), 0.35 (2 trends), 0.575 (5 trends), caps at DYNAMICS_CONFIDENCE_MAX (11+ trends)
562
+ const dataPoints = trendHistory ? trendHistory.length : (previousTrend ? 2 : 1);
563
+ const confidence = Math.min(DYNAMICS_CONFIDENCE_MAX, DYNAMICS_CONFIDENCE_BASE + DYNAMICS_CONFIDENCE_STEP * dataPoints);
564
+ // Project breach time
565
+ let projectedStatus = 'healthy';
566
+ let projectedBreachTime;
567
+ if (thresholds && velocity !== 0) {
568
+ const currentValue = currentTrend.currentValue;
569
+ const now = Date.now();
570
+ // Find nearest threshold breach
571
+ let minBreachHours = Infinity;
572
+ for (const t of thresholds) {
573
+ // Determine if velocity is moving toward breach
574
+ const distanceToThreshold = t.direction === 'above'
575
+ ? t.value - currentValue // Need to go up to breach
576
+ : currentValue - t.value; // Need to go down to breach
577
+ if (distanceToThreshold <= 0) {
578
+ // Already breached
579
+ projectedStatus = t.severity === 'critical' ? 'critical' : 'warning';
580
+ continue;
581
+ }
582
+ // Check if velocity is moving toward threshold
583
+ const movingToward = t.direction === 'above' ? velocity > 0 : velocity < 0;
584
+ if (!movingToward)
585
+ continue;
586
+ const hoursToBreak = distanceToThreshold / Math.abs(velocity);
587
+ if (hoursToBreak < minBreachHours) {
588
+ minBreachHours = hoursToBreak;
589
+ projectedStatus = t.severity === 'critical' ? 'critical' : 'warning';
590
+ projectedBreachTime = new Date(now + hoursToBreak * TIME_MS.HOUR).toISOString();
591
+ }
592
+ }
593
+ }
594
+ return {
595
+ featureVersion: DYNAMICS_FEATURE_VERSION,
596
+ velocity: roundTo(velocity, SCORE_PRECISION),
597
+ acceleration: roundTo(acceleration, SCORE_PRECISION),
598
+ inflectionDetected,
599
+ projectedStatus,
600
+ projectedBreachTime,
601
+ confidence: roundTo(confidence, SCORE_PRECISION),
602
+ };
603
+ }
604
+ const COVERAGE_CONFIDENCE_VERSION = '2.0';
605
+ /**
606
+ * Compute Normalized Shannon Entropy (Pielou's J) for distribution uniformity.
607
+ * Returns 1 for perfectly uniform, 0 for maximally concentrated.
608
+ *
609
+ * @param counts - Array of counts per group (e.g., evaluations per metric category)
610
+ * @returns Normalized Shannon entropy J in [0,1] where 1 = perfectly uniform
611
+ */
612
+ export function computeNormalizedEntropy(counts) {
613
+ if (counts.length <= 1)
614
+ return 1;
615
+ const total = counts.reduce((s, c) => s + c, 0);
616
+ if (total === 0)
617
+ return 0;
618
+ const k = counts.length;
619
+ const hMax = Math.log2(k);
620
+ let h = 0;
621
+ for (const c of counts) {
622
+ if (c > 0) {
623
+ const p = c / total;
624
+ h -= p * Math.log2(p);
625
+ }
626
+ }
627
+ return roundTo(Math.max(0, Math.min(1, h / hMax)), SCORE_PRECISION);
628
+ }
629
+ /**
630
+ * Compute coverage-weighted confidence.
631
+ * Adjusts confidence level based on evaluation distribution uniformity across inputs.
632
+ * A metric with 100 evaluations all from one session is less representative
633
+ * than 100 evaluations across 50 sessions.
634
+ *
635
+ * @param baseConfidence - Base confidence indicator from computeConfidence()
636
+ * @param evaluationsPerInput - Count of evaluations per input (session or trace)
637
+ * @returns Extended confidence with coverage weighting
638
+ */
639
+ export function computeCoverageWeightedConfidence(baseConfidence, evaluationsPerInput) {
640
+ const coverageUniformity = computeNormalizedEntropy(evaluationsPerInput);
641
+ const effectiveSampleSize = roundTo(baseConfidence.sampleCount * coverageUniformity, SCORE_PRECISION);
642
+ // Recalculate level using effective sample size
643
+ // Minimum COVERAGE_SAMPLE_HIGH_THRESHOLD effective samples for "high" regardless of uniformity
644
+ let adjustedLevel;
645
+ if (effectiveSampleSize < COVERAGE_SAMPLE_LOW_THRESHOLD) {
646
+ adjustedLevel = 'low';
647
+ }
648
+ else if (effectiveSampleSize >= COVERAGE_SAMPLE_HIGH_THRESHOLD && baseConfidence.level !== 'low') {
649
+ adjustedLevel = 'high';
650
+ }
651
+ else {
652
+ adjustedLevel = 'medium';
653
+ }
654
+ return {
655
+ ...baseConfidence,
656
+ featureVersion: COVERAGE_CONFIDENCE_VERSION,
657
+ coverageUniformity,
658
+ effectiveSampleSize,
659
+ adjustedLevel,
660
+ };
661
+ }
662
+ // ============================================================================
663
+ // Degradation Signal (Section 16.1)
664
+ // ============================================================================
665
+ /**
666
+ * Variance ratio threshold for "increasing" trend detection.
667
+ * Research R5: shifted from 1.5x (~87% coverage, 13% FP) to 2.0 (~95% coverage, 5% FP).
668
+ * Aligns with Datadog 2-3 sigma default anomaly bounds.
669
+ */
670
+ export const VARIANCE_INCREASE_THRESHOLD = 2.0;
671
+ /**
672
+ * Variance decrease threshold. Ratios below this indicate decreasing variance.
673
+ * Mirrors VARIANCE_INCREASE_THRESHOLD: values significantly below 1.0 indicate
674
+ * the metric is converging (less noisy than baseline).
675
+ */
676
+ export const VARIANCE_DECREASE_THRESHOLD = 0.7;
677
+ /**
678
+ * Coverage dropout rate threshold. Rates above this are considered degraded.
679
+ * A 20% gap rate means 1 in 5 coverage cells is missing evaluations.
680
+ */
681
+ export const COVERAGE_DROPOUT_THRESHOLD = 0.2;
682
+ /** EWMA smoothing factor. lambda=0.1 catches slow drift; higher = more reactive. */
683
+ export const EWMA_LAMBDA = 0.1;
684
+ /** Current feature version for degradation signal */
685
+ const DEGRADATION_FEATURE_VERSION = '1.1';
686
+ /**
687
+ * Compute EWMA (Exponentially Weighted Moving Average) from a time series.
688
+ * EWMA with lambda=0.1 catches slow quality drift that period-over-period deltas miss.
689
+ *
690
+ * @param values - Time-ordered score values (oldest first)
691
+ * @param lambda - Smoothing factor (0-1). Default: EWMA_LAMBDA (0.1)
692
+ * @returns Final EWMA value, or undefined if no values
693
+ */
694
+ export function computeEWMA(values, lambda = EWMA_LAMBDA) {
695
+ if (values.length === 0)
696
+ return undefined;
697
+ let ewma = values[0];
698
+ for (let i = 1; i < values.length; i++) {
699
+ ewma = lambda * values[i] + (1 - lambda) * ewma;
700
+ }
701
+ return roundTo(ewma, SCORE_PRECISION);
702
+ }
703
+ /**
704
+ * Compute Median Absolute Deviation (MAD) for robust dispersion estimation.
705
+ * MAD is resistant to outliers unlike standard deviation.
706
+ * Scaled by 1.4826 for normal-equivalent consistency.
707
+ *
708
+ * @param values - Numeric values
709
+ * @returns Scaled MAD value, or 0 for insufficient data
710
+ */
711
+ export function computeMAD(values) {
712
+ if (values.length < 2)
713
+ return 0;
714
+ const mad = ss.medianAbsoluteDeviation(values);
715
+ return roundTo(mad * MAD_CONSISTENCY_FACTOR, SCORE_PRECISION);
716
+ }
717
+ /**
718
+ * Detect EWMA drift by comparing the trailing EWMA to a baseline mean.
719
+ * The baseline is the first 70% of the series; drift is detected when the
720
+ * EWMA (which tracks recent values) deviates from the baseline by > k * MAD.
721
+ * When MAD = 0, falls back to range-based detection (> 10% of range).
722
+ */
723
+ export function detectEWMADrift(values, lambda = EWMA_LAMBDA, k = 2) {
724
+ if (values.length < EWMA_DRIFT_MIN_VALUES)
725
+ return false;
726
+ const ewma = computeEWMA(values, lambda);
727
+ if (ewma === undefined)
728
+ return false;
729
+ // Use first EWMA_BASELINE_FRACTION of series as baseline
730
+ const baselineEnd = Math.max(EWMA_BASELINE_MIN_POINTS, Math.floor(values.length * EWMA_BASELINE_FRACTION));
731
+ const baseline = values.slice(0, baselineEnd);
732
+ const baselineMean = baseline.reduce((s, v) => s + v, 0) / baseline.length;
733
+ const mad = computeMAD(baseline);
734
+ if (mad === 0) {
735
+ const min = Math.min(...values);
736
+ const max = Math.max(...values);
737
+ const range = max - min;
738
+ return range > 0 && Math.abs(ewma - baselineMean) > EWMA_RANGE_FALLBACK_RATIO * range;
739
+ }
740
+ return Math.abs(ewma - baselineMean) > k * mad;
741
+ }
742
+ /**
743
+ * Compute degradation signal from current and baseline metrics.
744
+ *
745
+ * @param currentStdDev - Current period score standard deviation
746
+ * @param baselineStdDev - Baseline period score standard deviation
747
+ * @param coverageGapCount - Number of missing coverage cells
748
+ * @param totalCoverageCells - Total coverage cells expected
749
+ * @param latencyP95 - Evaluation latency p95 (optional)
750
+ * @param latencyP50 - Evaluation latency p50 (optional)
751
+ * @param options - Additional options for EWMA drift and confirmation windows
752
+ */
753
+ export function computeDegradationSignal(currentStdDev, baselineStdDev, coverageGapCount, totalCoverageCells, latencyP95, latencyP50, options) {
754
+ // Variance ratio — when baseline had zero variance but current has variance,
755
+ // use VARIANCE_INCREASE_THRESHOLD + 1 to signal a clear spike rather than
756
+ // defaulting to 1 (stable) which masks the transition from stable to unstable.
757
+ let varianceRatio;
758
+ if (currentStdDev !== null && baselineStdDev !== null) {
759
+ if (baselineStdDev > 0) {
760
+ varianceRatio = roundTo(currentStdDev / baselineStdDev, SCORE_PRECISION);
761
+ }
762
+ else if (currentStdDev > 0) {
763
+ varianceRatio = VARIANCE_INCREASE_THRESHOLD + 1;
764
+ }
765
+ else {
766
+ varianceRatio = 1;
767
+ }
768
+ }
769
+ else {
770
+ varianceRatio = 1;
771
+ }
772
+ // Variance trend (R5: shifted from 1.5x to 2.0 for 5% FP rate)
773
+ let varianceTrend = 'stable';
774
+ if (varianceRatio > VARIANCE_INCREASE_THRESHOLD)
775
+ varianceTrend = 'increasing';
776
+ else if (varianceRatio < VARIANCE_DECREASE_THRESHOLD)
777
+ varianceTrend = 'decreasing';
778
+ // Coverage dropout rate
779
+ const coverageDropoutRate = totalCoverageCells > 0
780
+ ? roundTo(coverageGapCount / totalCoverageCells, SCORE_PRECISION)
781
+ : 0;
782
+ // Latency skew ratio
783
+ const latencySkewRatio = (latencyP95 != null && latencyP50 != null && latencyP50 > 0)
784
+ ? roundTo(latencyP95 / latencyP50, SCORE_PRECISION)
785
+ : 1;
786
+ // EWMA drift detection
787
+ const ewmaDriftDetected = options?.historicalValues
788
+ ? detectEWMADrift(options.historicalValues)
789
+ : false;
790
+ // Predicted status from feature combination
791
+ let predictedStatus = 'healthy';
792
+ const signals = [
793
+ varianceTrend === 'increasing',
794
+ coverageDropoutRate > COVERAGE_DROPOUT_THRESHOLD,
795
+ latencySkewRatio > LATENCY_SKEW_SIGNAL_THRESHOLD,
796
+ ];
797
+ const activeSignals = signals.filter(Boolean).length;
798
+ if (activeSignals >= 2)
799
+ predictedStatus = 'critical';
800
+ else if (activeSignals >= 1)
801
+ predictedStatus = 'warning';
802
+ // EWMA drift escalates healthy -> warning (supplementary signal)
803
+ if (ewmaDriftDetected && predictedStatus === 'healthy') {
804
+ predictedStatus = 'warning';
805
+ }
806
+ // Confirmation window: require CONFIRMATION_MIN_CONSECUTIVE_BREACHES+ for confirmed status
807
+ const priorBreaches = options?.priorConsecutiveBreaches ?? 0;
808
+ const currentlyBreaching = predictedStatus !== 'healthy';
809
+ const consecutiveBreaches = currentlyBreaching ? priorBreaches + 1 : 0;
810
+ const confirmed = consecutiveBreaches >= CONFIRMATION_MIN_CONSECUTIVE_BREACHES;
811
+ return {
812
+ featureVersion: DEGRADATION_FEATURE_VERSION,
813
+ varianceTrend,
814
+ varianceRatio,
815
+ coverageDropoutRate,
816
+ latencySkewRatio,
817
+ predictedStatus,
818
+ ewmaDriftDetected,
819
+ consecutiveBreaches,
820
+ confirmed,
821
+ };
822
+ }
823
+ const CORRELATION_FEATURE_VERSION = '3.1';
824
+ /**
825
+ * Compute Pearson correlation coefficient between two numeric arrays.
826
+ * Returns 0 for insufficient data (< 3 points) or zero variance.
827
+ */
828
+ export function computePearsonR(xs, ys) {
829
+ const n = Math.min(xs.length, ys.length);
830
+ if (n < CORRELATION_MIN_POINTS)
831
+ return 0;
832
+ const xSlice = xs.slice(0, n);
833
+ const ySlice = ys.slice(0, n);
834
+ const r = ss.sampleCorrelation(xSlice, ySlice);
835
+ if (!Number.isFinite(r))
836
+ return 0;
837
+ return roundTo(r, SCORE_PRECISION);
838
+ }
839
+ /**
840
+ * Compute p-value for Pearson R using t-distribution approximation.
841
+ * t = r * sqrt((n-2) / (1-r^2)), two-tailed test.
842
+ * Uses the incomplete beta function approximation for t-distribution CDF.
843
+ */
844
+ export function pearsonPValue(r, n) {
845
+ if (n < CORRELATION_MIN_POINTS)
846
+ return null;
847
+ const absR = Math.abs(r);
848
+ if (absR >= 1)
849
+ return 0;
850
+ if (absR === 0)
851
+ return 1;
852
+ const df = n - 2;
853
+ const t = absR * Math.sqrt(df / (1 - absR * absR));
854
+ // Two-tailed p-value via t-distribution CDF
855
+ const p = 2 * (1 - tCdf(t, df));
856
+ return Math.max(0, Math.min(1, p));
857
+ }
858
+ /**
859
+ * Apply Benjamini-Hochberg FDR correction to a set of p-values.
860
+ * Returns a boolean array indicating which hypotheses are significant.
861
+ *
862
+ * @param pValues - Array of p-values
863
+ * @param q - FDR threshold (default: 0.05)
864
+ * @returns Boolean array: true = significant at given FDR level
865
+ */
866
+ export function benjaminiHochberg(pValues, q = DEFAULT_SENSITIVITY_DELTA) {
867
+ const m = pValues.length;
868
+ if (m === 0)
869
+ return [];
870
+ // Sort indices by p-value
871
+ const indexed = pValues.map((p, i) => ({ p, i }));
872
+ indexed.sort((a, b) => a.p - b.p);
873
+ // Find largest k where p_(k) <= k/m * q
874
+ const significant = new Array(m).fill(false);
875
+ let maxK = -1;
876
+ for (let k = 0; k < m; k++) {
877
+ const threshold = ((k + 1) / m) * q;
878
+ if (indexed[k].p <= threshold) {
879
+ maxK = k;
880
+ }
881
+ }
882
+ // All hypotheses up to maxK are significant
883
+ for (let k = 0; k <= maxK; k++) {
884
+ significant[indexed[k].i] = true;
885
+ }
886
+ return significant;
887
+ }
888
+ /**
889
+ * Compute Spearman rank correlation coefficient.
890
+ * Converts values to fractional ranks (average rank for ties) then computes Pearson R on ranks.
891
+ * Better than Pearson for bounded [0,1] quality scores that may have non-linear relationships.
892
+ */
893
+ export function computeSpearmanR(xs, ys) {
894
+ const n = Math.min(xs.length, ys.length);
895
+ if (n < CORRELATION_MIN_POINTS)
896
+ return 0;
897
+ return computePearsonR(fractionalRanks(xs.slice(0, n)), fractionalRanks(ys.slice(0, n)));
898
+ }
899
+ /** Convert values to fractional ranks (1-based, average rank for ties) */
900
+ function fractionalRanks(values) {
901
+ const indexed = values.map((v, i) => ({ v, i }));
902
+ indexed.sort((a, b) => a.v - b.v);
903
+ const ranks = new Array(values.length);
904
+ let i = 0;
905
+ while (i < indexed.length) {
906
+ let j = i;
907
+ while (j < indexed.length && indexed[j].v === indexed[i].v)
908
+ j++;
909
+ const avgRank = (i + j + 1) / 2; // average of 1-based ranks i+1..j
910
+ for (let k = i; k < j; k++)
911
+ ranks[indexed[k].i] = avgRank;
912
+ i = j;
913
+ }
914
+ return ranks;
915
+ }
916
+ /**
917
+ * Compute Cohen's d effect size from a correlation coefficient.
918
+ * d = 2r / sqrt(1 - r^2). Interpretation: small=0.2, medium=0.5, large=0.8.
919
+ */
920
+ function correlationToCohenD(r) {
921
+ const absR = Math.abs(r);
922
+ if (absR >= 1)
923
+ return Infinity;
924
+ const d = (2 * absR) / Math.sqrt(1 - absR * absR);
925
+ return roundTo(d, SCORE_PRECISION);
926
+ }
927
+ /**
928
+ * Find the best lag (highest |R|) for a pair of time series.
929
+ * Tests lags from 0 to maxLagSteps by shifting seriesB forward.
930
+ */
931
+ function findBestLag(seriesA, seriesB, maxLagSteps) {
932
+ let bestR = computePearsonR(seriesA, seriesB);
933
+ let bestLag = 0;
934
+ for (let lag = 1; lag <= maxLagSteps; lag++) {
935
+ // Shift B forward by lag: compare A[lag:] with B[0:n-lag]
936
+ const n = Math.min(seriesA.length, seriesB.length) - lag;
937
+ if (n < CORRELATION_MIN_POINTS)
938
+ break;
939
+ const shiftedA = seriesA.slice(lag);
940
+ const shiftedB = seriesB.slice(0, n);
941
+ const r = computePearsonR(shiftedA, shiftedB);
942
+ if (Math.abs(r) > Math.abs(bestR)) {
943
+ bestR = r;
944
+ bestLag = lag;
945
+ }
946
+ // Also test negative lag: A[0:n-lag] with B[lag:]
947
+ const revA = seriesA.slice(0, n);
948
+ const revB = seriesB.slice(lag);
949
+ const rRev = computePearsonR(revA, revB);
950
+ if (Math.abs(rRev) > Math.abs(bestR)) {
951
+ bestR = rRev;
952
+ bestLag = -lag;
953
+ }
954
+ }
955
+ return { r: bestR, lagStep: bestLag };
956
+ }
957
+ // ============================================================================
958
+ // R3: Bivariate Granger Causality Test
959
+ // ============================================================================
960
+ const GRANGER_MIN_SAMPLE_SIZE = 30;
961
+ /**
962
+ * Fit an autoregressive (AR) model using OLS and return the sum of squared residuals.
963
+ * y[t] = c + sum(a_i * y[t-i]) + sum(b_j * x[t-j]) + e[t]
964
+ *
965
+ * When xLags is empty, fits a univariate (restricted) model.
966
+ * When xLags is provided, fits a bivariate (unrestricted) model.
967
+ *
968
+ * Uses normal equations: (X'X)^-1 X'y via Gaussian elimination.
969
+ */
970
+ function fitARResidualSSR(y, yLags, xLags = []) {
971
+ const n = y.length;
972
+ const k = 1 + yLags.length + xLags.length; // intercept + yLag cols + xLag cols
973
+ if (n <= k)
974
+ return 0;
975
+ // Build design matrix X (n x k): [intercept, yLag1..., xLag1...]
976
+ const xData = [];
977
+ for (let i = 0; i < n; i++) {
978
+ const row = [1];
979
+ for (const lagCol of yLags)
980
+ row.push(lagCol[i]);
981
+ for (const lagCol of xLags)
982
+ row.push(lagCol[i]);
983
+ xData.push(row);
984
+ }
985
+ const X = new Matrix(xData);
986
+ const Y = Matrix.columnVector(y);
987
+ // Solve normal equations: (X'X)β = X'y via QR decomposition
988
+ const beta = solve(X, Y, false);
989
+ // Compute SSR = sum((y - Xβ)^2)
990
+ const residuals = X.mmul(beta).sub(Y);
991
+ let ssr = 0;
992
+ for (let i = 0; i < n; i++) {
993
+ const r = residuals.get(i, 0);
994
+ ssr += r * r;
995
+ }
996
+ return ssr;
997
+ }
998
+ /**
999
+ * Compute p-value for F-distribution.
1000
+ * P(F > f) = 1 - CDF(f, d1, d2)
1001
+ */
1002
+ function fDistPValue(f, d1, d2) {
1003
+ if (f <= 0 || d1 <= 0 || d2 <= 0)
1004
+ return 1;
1005
+ return 1 - fCdf(f, d1, d2);
1006
+ }
1007
+ /**
1008
+ * Run bivariate Granger causality test: does X Granger-cause Y?
1009
+ *
1010
+ * Compares restricted model (Y on own lags) vs unrestricted model (Y on own lags + X lags).
1011
+ * F = ((SSR_r - SSR_u) / p) / (SSR_u / (n - 2p - 1))
1012
+ *
1013
+ * @returns F-statistic and p-value, or undefined if insufficient data
1014
+ */
1015
+ function grangerFTest(y, x, lags) {
1016
+ const n = Math.min(y.length, x.length);
1017
+ if (n < lags + GRANGER_MIN_SAMPLE_SIZE)
1018
+ return undefined;
1019
+ const effectiveN = n - lags;
1020
+ const yTarget = y.slice(lags, lags + effectiveN);
1021
+ // Build lag matrices
1022
+ const yLagCols = [];
1023
+ const xLagCols = [];
1024
+ for (let lag = 1; lag <= lags; lag++) {
1025
+ yLagCols.push(y.slice(lags - lag, lags - lag + effectiveN));
1026
+ xLagCols.push(x.slice(lags - lag, lags - lag + effectiveN));
1027
+ }
1028
+ const ssrRestricted = fitARResidualSSR(yTarget, yLagCols);
1029
+ const ssrUnrestricted = fitARResidualSSR(yTarget, yLagCols, xLagCols);
1030
+ if (ssrUnrestricted <= 0)
1031
+ return undefined;
1032
+ const p = lags; // number of additional parameters
1033
+ const dfResidual = effectiveN - 2 * p - 1;
1034
+ if (dfResidual <= 0)
1035
+ return undefined;
1036
+ const fStat = ((ssrRestricted - ssrUnrestricted) / p) / (ssrUnrestricted / dfResidual);
1037
+ if (!Number.isFinite(fStat) || fStat < 0)
1038
+ return undefined;
1039
+ const pValue = fDistPValue(fStat, p, dfResidual);
1040
+ return { fStatistic: roundTo(fStat, SCORE_PRECISION), pValue: roundTo(pValue, SCORE_PRECISION) };
1041
+ }
1042
+ /**
1043
+ * Compute Granger causality for a pair of time series.
1044
+ * Tests both directions (A->B and B->A) using the best lag from correlation analysis.
1045
+ *
1046
+ * @param seriesA - Time-ordered scores for metric A
1047
+ * @param seriesB - Time-ordered scores for metric B
1048
+ * @param lags - Number of lags to use (from correlation lag detection, minimum 1)
1049
+ * @param significanceLevel - P-value threshold (default: 0.05)
1050
+ */
1051
+ export function computeGrangerCausality(seriesA, seriesB, lags, significanceLevel = DEFAULT_SENSITIVITY_DELTA) {
1052
+ // Cap lags at n/4 to ensure sufficient residual degrees of freedom (Lütkepohl 2005).
1053
+ // With GRANGER_MIN_SAMPLE_SIZE=30, effectiveLags=1 needs n>=31, effectiveLags=2 needs n>=32.
1054
+ const effectiveLags = Math.max(1, Math.min(lags, Math.floor(Math.min(seriesA.length, seriesB.length) / 4)));
1055
+ // Test A -> B (does A Granger-cause B?)
1056
+ const abTest = grangerFTest(seriesB, seriesA, effectiveLags);
1057
+ // Test B -> A (does B Granger-cause A?)
1058
+ const baTest = grangerFTest(seriesA, seriesB, effectiveLags);
1059
+ if (!abTest && !baTest)
1060
+ return undefined;
1061
+ const abSignificant = abTest != null && abTest.pValue < significanceLevel;
1062
+ const baSignificant = baTest != null && baTest.pValue < significanceLevel;
1063
+ let direction = 'none';
1064
+ let bestF;
1065
+ let bestP;
1066
+ if (abSignificant && baSignificant) {
1067
+ if (!abTest || !baTest) {
1068
+ throw new Error('Expected both Granger tests when both are significant');
1069
+ }
1070
+ direction = 'bidirectional';
1071
+ bestF = Math.max(abTest.fStatistic, baTest.fStatistic);
1072
+ bestP = Math.min(abTest.pValue, baTest.pValue);
1073
+ }
1074
+ else if (abSignificant) {
1075
+ if (!abTest) {
1076
+ throw new Error('Expected A->B Granger test when significant');
1077
+ }
1078
+ direction = 'A->B';
1079
+ bestF = abTest.fStatistic;
1080
+ bestP = abTest.pValue;
1081
+ }
1082
+ else if (baSignificant) {
1083
+ if (!baTest) {
1084
+ throw new Error('Expected B->A Granger test when significant');
1085
+ }
1086
+ direction = 'B->A';
1087
+ bestF = baTest.fStatistic;
1088
+ bestP = baTest.pValue;
1089
+ }
1090
+ else {
1091
+ // Neither significant: report the one with lower p-value
1092
+ if (abTest && baTest) {
1093
+ bestF = abTest.pValue < baTest.pValue ? abTest.fStatistic : baTest.fStatistic;
1094
+ bestP = Math.min(abTest.pValue, baTest.pValue);
1095
+ }
1096
+ else {
1097
+ const test = abTest ?? baTest;
1098
+ if (!test) {
1099
+ throw new Error('Expected at least one Granger test result');
1100
+ }
1101
+ bestF = test.fStatistic;
1102
+ bestP = test.pValue;
1103
+ }
1104
+ }
1105
+ return {
1106
+ fStatistic: bestF,
1107
+ pValue: bestP,
1108
+ lags: effectiveLags,
1109
+ direction,
1110
+ };
1111
+ }
1112
+ /**
1113
+ * Compute correlation matrix across all metric pairs.
1114
+ * Supports time-lag detection and BH-FDR significance testing.
1115
+ *
1116
+ * Empty metric time series arrays are valid input and result in `pearsonR=0`
1117
+ * for any pair involving that metric (insufficient data for correlation).
1118
+ *
1119
+ * @param metricTimeSeries - Map of metric name to time-ordered score arrays
1120
+ * @param knownToxicCombos - Set of "metricA:metricB" pairs from correlation rules
1121
+ * @param degradedPeriods - Map of metric name to boolean arrays (true = degraded in that period)
1122
+ * @param options - Lag detection and significance testing options
1123
+ * @example
1124
+ * ```ts
1125
+ * const series = new Map([
1126
+ * ['relevance', [0.9, 0.85, 0.88, 0.92]],
1127
+ * ['faithfulness', [0.8, 0.82, 0.79, 0.85]],
1128
+ * ]);
1129
+ * const correlations = computeCorrelationMatrix(series);
1130
+ * // [{ metricA: 'faithfulness', metricB: 'relevance', pearsonR: 0.87, ... }]
1131
+ * ```
1132
+ */
1133
+ export function computeCorrelationMatrix(metricTimeSeries, knownToxicCombos, degradedPeriods, options) {
1134
+ const metrics = [...metricTimeSeries.keys()].sort();
1135
+ const maxLagSteps = options?.maxLagSteps ?? 0;
1136
+ const lagStepHours = options?.lagStepHours ?? 1;
1137
+ const fdrQ = options?.fdrQ ?? DEFAULT_SENSITIVITY_DELTA;
1138
+ const results = [];
1139
+ // First pass: compute all correlations and p-values
1140
+ const pairData = [];
1141
+ for (let i = 0; i < metrics.length; i++) {
1142
+ for (let j = i + 1; j < metrics.length; j++) {
1143
+ const metricA = metrics[i];
1144
+ const metricB = metrics[j];
1145
+ const seriesA = metricTimeSeries.get(metricA);
1146
+ const seriesB = metricTimeSeries.get(metricB);
1147
+ if (!seriesA || !seriesB) {
1148
+ console.debug(`[computeCorrelationMatrix] skipping pair (${metricA}, ${metricB}): series not found`);
1149
+ continue;
1150
+ }
1151
+ // Find best lag (or use lag=0 if no lag detection)
1152
+ let pearsonR;
1153
+ let lagStep;
1154
+ if (maxLagSteps > 0) {
1155
+ const best = findBestLag(seriesA, seriesB, maxLagSteps);
1156
+ pearsonR = best.r;
1157
+ lagStep = best.lagStep;
1158
+ }
1159
+ else {
1160
+ pearsonR = computePearsonR(seriesA, seriesB);
1161
+ lagStep = 0;
1162
+ }
1163
+ const effectiveN = Math.min(seriesA.length, seriesB.length) - Math.abs(lagStep);
1164
+ const pValue = pearsonPValue(pearsonR, effectiveN);
1165
+ // Spearman rank correlation (computed at best lag)
1166
+ let spearmanR;
1167
+ if (lagStep === 0) {
1168
+ spearmanR = computeSpearmanR(seriesA, seriesB);
1169
+ }
1170
+ else if (lagStep > 0) {
1171
+ spearmanR = computeSpearmanR(seriesA.slice(lagStep), seriesB.slice(0, effectiveN));
1172
+ }
1173
+ else {
1174
+ spearmanR = computeSpearmanR(seriesA.slice(0, effectiveN), seriesB.slice(-lagStep));
1175
+ }
1176
+ const effectSize = correlationToCohenD(pearsonR);
1177
+ // Co-occurrence rate from degraded periods
1178
+ let coOccurrenceRate = 0;
1179
+ if (degradedPeriods) {
1180
+ const degradedA = degradedPeriods.get(metricA);
1181
+ const degradedB = degradedPeriods.get(metricB);
1182
+ if (degradedA && degradedB) {
1183
+ const n = Math.min(degradedA.length, degradedB.length);
1184
+ if (n > 0) {
1185
+ let coCount = 0;
1186
+ for (let k = 0; k < n; k++) {
1187
+ if (degradedA[k] && degradedB[k])
1188
+ coCount++;
1189
+ }
1190
+ coOccurrenceRate = roundTo(coCount / n, SCORE_PRECISION);
1191
+ }
1192
+ }
1193
+ }
1194
+ const comboKey = `${metricA}:${metricB}`;
1195
+ const reverseKey = `${metricB}:${metricA}`;
1196
+ const isKnownToxicCombo = knownToxicCombos
1197
+ ? (knownToxicCombos.has(comboKey) || knownToxicCombos.has(reverseKey))
1198
+ : false;
1199
+ pairData.push({
1200
+ metricA, metricB, pearsonR, spearmanR, effectSize,
1201
+ lagHours: lagStep * lagStepHours,
1202
+ pValue, coOccurrenceRate, isKnownToxicCombo, effectiveN,
1203
+ });
1204
+ }
1205
+ }
1206
+ // Second pass: apply BH-FDR significance testing
1207
+ // Filter out pairs with null pValues (insufficient data) before BH-FDR
1208
+ const validIndices = pairData.reduce((acc, d, i) => {
1209
+ if (d.pValue !== null)
1210
+ acc.push(i);
1211
+ return acc;
1212
+ }, []);
1213
+ const validPValues = validIndices.map(i => pairData[i].pValue);
1214
+ const validSignificantFlags = benjaminiHochberg(validPValues, fdrQ);
1215
+ const validIndexMap = new Map(validIndices.map((idx, pos) => [idx, pos]));
1216
+ const significantFlags = pairData.map((d, i) => {
1217
+ if (d.pValue === null)
1218
+ return false;
1219
+ const validPos = validIndexMap.get(i);
1220
+ return validPos !== undefined ? validSignificantFlags[validPos] : false;
1221
+ });
1222
+ // Granger causality config
1223
+ const grangerCfg = options?.grangerConfig;
1224
+ const grangerEnabled = grangerCfg?.enabled === true;
1225
+ const grangerMinN = grangerCfg?.minSampleSize ?? GRANGER_MIN_SAMPLE_SIZE;
1226
+ const grangerAlpha = grangerCfg?.significanceLevel ?? DEFAULT_SENSITIVITY_DELTA;
1227
+ for (let idx = 0; idx < pairData.length; idx++) {
1228
+ const d = pairData[idx];
1229
+ const isSignificant = significantFlags[idx];
1230
+ // Run Granger test on significant pairs when enabled
1231
+ let granger;
1232
+ let causalConfidence = 'correlation';
1233
+ if (grangerEnabled && isSignificant && d.effectiveN >= grangerMinN) {
1234
+ const seriesA = metricTimeSeries.get(d.metricA);
1235
+ const seriesB = metricTimeSeries.get(d.metricB);
1236
+ if (!seriesA || !seriesB) {
1237
+ continue;
1238
+ }
1239
+ // Use fixed lag=GRANGER_FIXED_LAG_ORDER (not correlation-detected lag) to avoid data-snooping
1240
+ // inflation of Type I error. The correlation lag maximizes |R| across
1241
+ // tested offsets; reusing it for AR order would bias the F-test.
1242
+ granger = computeGrangerCausality(seriesA, seriesB, GRANGER_FIXED_LAG_ORDER, grangerAlpha);
1243
+ if (granger && granger.direction !== 'none') {
1244
+ causalConfidence = 'granger';
1245
+ }
1246
+ }
1247
+ results.push({
1248
+ featureVersion: CORRELATION_FEATURE_VERSION,
1249
+ metricA: d.metricA,
1250
+ metricB: d.metricB,
1251
+ pearsonR: d.pearsonR,
1252
+ spearmanR: d.spearmanR,
1253
+ effectSize: d.effectSize,
1254
+ lagHours: d.lagHours,
1255
+ significant: isSignificant,
1256
+ pValue: d.pValue,
1257
+ causalConfidence,
1258
+ coOccurrenceRate: d.coOccurrenceRate,
1259
+ isKnownToxicCombo: d.isKnownToxicCombo,
1260
+ granger,
1261
+ });
1262
+ }
1263
+ return results;
1264
+ }
1265
+ // ============================================================================
1266
+ // R4: Percentile Distribution & Streaming Digest (Section 16.2)
1267
+ // ============================================================================
1268
+ /**
1269
+ * Compute percentile distribution from an array of numeric scores.
1270
+ * Uses linear interpolation between adjacent sorted values.
1271
+ *
1272
+ * @param scores - Array of numeric scores (unsorted ok, will be sorted internally)
1273
+ * @returns PercentileDistribution with p10/p25/p50/p75/p90, or undefined if < 3 scores
1274
+ */
1275
+ export function computePercentileDistribution(scores) {
1276
+ if (scores.length < CORRELATION_MIN_POINTS)
1277
+ return undefined;
1278
+ const sorted = [...scores].sort((a, b) => a - b);
1279
+ return {
1280
+ p10: roundTo(ss.quantileSorted(sorted, QUANTILE_P10), SCORE_PRECISION),
1281
+ p25: roundTo(ss.quantileSorted(sorted, QUANTILE_P25), SCORE_PRECISION),
1282
+ p50: roundTo(ss.quantileSorted(sorted, QUANTILE_P50), SCORE_PRECISION),
1283
+ p75: roundTo(ss.quantileSorted(sorted, QUANTILE_P75), SCORE_PRECISION),
1284
+ p90: roundTo(ss.quantileSorted(sorted, QUANTILE_P90), SCORE_PRECISION),
1285
+ };
1286
+ }
1287
+ /**
1288
+ * Compute the percentile rank of a value within a sorted array.
1289
+ * Returns the fraction of values in the array that are <= the given value (0-1).
1290
+ *
1291
+ * @param value - The value to rank
1292
+ * @param sortedScores - Pre-sorted (ascending) array of scores
1293
+ * @returns Percentile rank (0-1)
1294
+ */
1295
+ export function computePercentileRank(value, sortedScores) {
1296
+ if (sortedScores.length === 0)
1297
+ return QUANTILE_P50;
1298
+ const n = sortedScores.length;
1299
+ // Binary search for first index where sortedScores[idx] > value (upper bound)
1300
+ let lo = 0, hi = n;
1301
+ while (lo < hi) {
1302
+ const mid = (lo + hi) >>> 1;
1303
+ if (sortedScores[mid] <= value)
1304
+ lo = mid + 1;
1305
+ else
1306
+ hi = mid;
1307
+ }
1308
+ const upper = lo; // count of values <= value
1309
+ // Binary search for first index where sortedScores[idx] >= value (lower bound)
1310
+ lo = 0;
1311
+ hi = n;
1312
+ while (lo < hi) {
1313
+ const mid = (lo + hi) >>> 1;
1314
+ if (sortedScores[mid] < value)
1315
+ lo = mid + 1;
1316
+ else
1317
+ hi = mid;
1318
+ }
1319
+ const lower = lo; // count of values < value
1320
+ // Fractional rank: midpoint of tied range
1321
+ return roundTo((lower + upper) / (2 * n), SCORE_PRECISION);
1322
+ }
1323
+ /**
1324
+ * Streaming percentile estimator backed by DDSketch.
1325
+ * Wraps `@datadog/sketches-js` with a simplified API for quality pipeline use.
1326
+ */
1327
+ export class StreamingPercentile {
1328
+ sketch;
1329
+ totalCount = 0;
1330
+ constructor() {
1331
+ this.sketch = new DDSketch();
1332
+ }
1333
+ /** Add a single value to the sketch */
1334
+ add(value) {
1335
+ if (!Number.isFinite(value))
1336
+ throw new TypeError(`StreamingPercentile: value must be finite, got ${value}`);
1337
+ this.sketch.accept(value);
1338
+ this.totalCount++;
1339
+ }
1340
+ /** Add multiple values at once */
1341
+ addAll(values) {
1342
+ for (const v of values)
1343
+ this.add(v);
1344
+ }
1345
+ /** Get the current total count of observed values */
1346
+ count() {
1347
+ return this.totalCount;
1348
+ }
1349
+ /** Estimate a percentile (0-100) from the sketch */
1350
+ percentile(p) {
1351
+ if (this.totalCount === 0)
1352
+ return 0;
1353
+ return roundTo(this.sketch.getValueAtQuantile(p / 100), SCORE_PRECISION);
1354
+ }
1355
+ /** Get full percentile distribution */
1356
+ distribution() {
1357
+ if (this.totalCount < CORRELATION_MIN_POINTS)
1358
+ return undefined;
1359
+ return {
1360
+ p10: roundTo(this.sketch.getValueAtQuantile(QUANTILE_P10), SCORE_PRECISION),
1361
+ p25: roundTo(this.sketch.getValueAtQuantile(QUANTILE_P25), SCORE_PRECISION),
1362
+ p50: roundTo(this.sketch.getValueAtQuantile(QUANTILE_P50), SCORE_PRECISION),
1363
+ p75: roundTo(this.sketch.getValueAtQuantile(QUANTILE_P75), SCORE_PRECISION),
1364
+ p90: roundTo(this.sketch.getValueAtQuantile(QUANTILE_P90), SCORE_PRECISION),
1365
+ };
1366
+ }
1367
+ }
1368
+ /**
1369
+ * Compute Population Stability Index (PSI) between two distributions.
1370
+ * PSI measures distribution shift: < 0.1 = stable, 0.1–0.25 = moderate, > 0.25 = significant.
1371
+ *
1372
+ * Uses a `bins`-bin histogram comparison between expected (baseline) and actual (current).
1373
+ * Returns { psi: 0, drifted: false } when either array has fewer than MIN_QUANTILE_SAMPLE_SIZE
1374
+ * values (insufficient data for reliable estimation).
1375
+ */
1376
+ export function computePSI(expected, actual, bins = DEFAULT_BIN_COUNT) {
1377
+ if (expected.length < MIN_QUANTILE_SAMPLE_SIZE || actual.length < MIN_QUANTILE_SAMPLE_SIZE) {
1378
+ return { psi: 0, drifted: false };
1379
+ }
1380
+ const toBinProportions = (values) => {
1381
+ const hist = new Array(bins).fill(0);
1382
+ for (const v of values) {
1383
+ const bin = Math.min(Math.max(Math.floor(v * bins), 0), bins - 1);
1384
+ hist[bin]++;
1385
+ }
1386
+ // PSI_LOG_EPSILON prevents log(0) when a bin is empty
1387
+ return hist.map(c => Math.max(c / values.length, PSI_LOG_EPSILON));
1388
+ };
1389
+ const pExpected = toBinProportions(expected);
1390
+ const pActual = toBinProportions(actual);
1391
+ let psi = 0;
1392
+ for (let i = 0; i < bins; i++) {
1393
+ psi += (pActual[i] - pExpected[i]) * Math.log(pActual[i] / pExpected[i]);
1394
+ }
1395
+ const roundedPsi = roundTo(Math.max(0, psi), SCORE_PRECISION);
1396
+ return {
1397
+ psi: roundedPsi,
1398
+ drifted: roundedPsi > PSI_RECALIBRATION_THRESHOLD,
1399
+ };
1400
+ }
1401
+ /**
1402
+ * Determine whether calibration state should be rewritten based on PSI drift.
1403
+ * Returns shouldWrite: true on first run (null previousState) or if any metric drifts.
1404
+ */
1405
+ export function shouldRecalibrate(previousState, currentScores) {
1406
+ if (previousState === null) {
1407
+ return { shouldWrite: true, psiValues: {} };
1408
+ }
1409
+ if (!previousState.rawScores) {
1410
+ return { shouldWrite: true, psiValues: {} };
1411
+ }
1412
+ const psiValues = {};
1413
+ let shouldWrite = false;
1414
+ for (const metric of Object.keys(currentScores)) {
1415
+ const previous = previousState.rawScores[metric];
1416
+ if (!previous) {
1417
+ shouldWrite = true;
1418
+ continue;
1419
+ }
1420
+ const result = computePSI(previous, currentScores[metric]);
1421
+ psiValues[metric] = result.psi;
1422
+ if (result.drifted) {
1423
+ shouldWrite = true;
1424
+ }
1425
+ }
1426
+ return { shouldWrite, psiValues };
1427
+ }
1428
+ /**
1429
+ * Compute calibration distributions from per-metric evaluation scores.
1430
+ * Skips metrics with fewer than MIN_QUANTILE_SAMPLE_SIZE scores (unreliable percentiles).
1431
+ * Uses StreamingPercentile to compute p10/p25/p50/p75/p90 for each qualifying metric.
1432
+ * windowStart/windowEnd metadata reflects a CALIBRATION_WINDOW_DAYS rolling window,
1433
+ * but the caller is responsible for pre-filtering scores to that window before passing them in.
1434
+ */
1435
+ export function computeCalibrationDistributions(evaluationsByMetric) {
1436
+ const distributions = {};
1437
+ const now = new Date().toISOString();
1438
+ const windowStart = new Date(Date.now() - CALIBRATION_WINDOW_DAYS * TIME_MS.DAY).toISOString();
1439
+ for (const [metric, scores] of Object.entries(evaluationsByMetric)) {
1440
+ if (scores.length < MIN_QUANTILE_SAMPLE_SIZE)
1441
+ continue;
1442
+ const dist = computePercentileDistribution(scores);
1443
+ if (!dist)
1444
+ continue;
1445
+ distributions[metric] = {
1446
+ distribution: dist,
1447
+ sampleSize: scores.length,
1448
+ windowStart,
1449
+ windowEnd: now,
1450
+ };
1451
+ }
1452
+ return distributions;
1453
+ }
1454
+ /**
1455
+ * Load calibration state from persisted JSON file.
1456
+ * Returns null if file does not exist or is malformed.
1457
+ */
1458
+ export function loadCalibrationState(dir) {
1459
+ const filePath = join(dir, CALIBRATION_STATE_FILE);
1460
+ try {
1461
+ const raw = readFileSync(filePath, 'utf8');
1462
+ const parsed = JSON.parse(raw);
1463
+ const result = calibrationStateSchema.safeParse(parsed);
1464
+ if (result.success) {
1465
+ return result.data;
1466
+ }
1467
+ }
1468
+ catch { /* file not found or malformed */ }
1469
+ return null;
1470
+ }
1471
+ /**
1472
+ * Save calibration state to JSON file for cross-run use by the dashboard API.
1473
+ * Validates state before writing to ensure round-trip integrity.
1474
+ */
1475
+ export function saveCalibrationState(dir, state) {
1476
+ const validated = calibrationStateSchema.parse(state);
1477
+ writeFileSync(join(dir, CALIBRATION_STATE_FILE), JSON.stringify(validated, null, FORMAT_JSON_INDENT_SPACES) + '\n');
1478
+ }
1479
+ /**
1480
+ * Compute all derived features in correct dependency order.
1481
+ *
1482
+ * Pipeline ordering (QM2):
1483
+ * Step 2 (aggregate) outputs: metrics, CoverageHeatmap
1484
+ * ↓
1485
+ * Step 3a: CQI (depends on metrics)
1486
+ * Step 3b: Coverage-weighted confidence (depends on metrics + CoverageHeatmap via evaluationsPerInput)
1487
+ * Step 3c: Correlation matrix (depends on metricTimeSeries, independent of 3a/3b)
1488
+ *
1489
+ * CoverageHeatmap must be computed before calling this function.
1490
+ * The evaluationsPerInput parameter is derived from CoverageHeatmap output.
1491
+ *
1492
+ * @param input - Aggregated data from Step 2
1493
+ * @returns All derived features
1494
+ */
1495
+ export function computeDerivedFeatures(input) {
1496
+ // Step 3a: Composite Quality Index (depends only on metrics)
1497
+ const cqi = computeCQI(input.metrics, input.cqiWeights);
1498
+ const cqiSensitivity = cqi ? computeCQISensitivity(input.metrics, input.cqiWeights) : undefined;
1499
+ // Step 3b: Coverage-weighted confidence (depends on metrics + evaluationsPerInput from CoverageHeatmap)
1500
+ const coverageConfidence = new Map();
1501
+ if (input.evaluationsPerInput) {
1502
+ for (const metric of input.metrics) {
1503
+ const perInput = input.evaluationsPerInput.get(metric.name);
1504
+ if (metric.confidence && perInput && perInput.length > 0) {
1505
+ coverageConfidence.set(metric.name, computeCoverageWeightedConfidence(metric.confidence, perInput));
1506
+ }
1507
+ }
1508
+ }
1509
+ // Step 3c: Correlation matrix (depends on metricTimeSeries, independent of 3a/3b)
1510
+ const correlations = input.metricTimeSeries
1511
+ ? computeCorrelationMatrix(input.metricTimeSeries, input.knownToxicCombos, input.degradedPeriods, input.correlationOptions)
1512
+ : [];
1513
+ return { cqi, cqiSensitivity, coverageConfidence, correlations };
1514
+ }
1515
+ /** Parameter grid for sweepDegradationParams — all combinations are evaluated */
1516
+ export const BACKTEST_SWEEP = {
1517
+ varianceThreshold: [1.5, 1.75, 2.0, 2.5, 3.0],
1518
+ coverageDropoutThreshold: [0.1, 0.15, 0.2, 0.3, 0.4],
1519
+ ewmaLambda: [0.05, 0.1, 0.15, 0.2, 0.3],
1520
+ confirmationWindow: [1, 2, 3, 4],
1521
+ stabilityThreshold: [0.005, 0.01, 0.02, 0.03, 0.05],
1522
+ };
1523
+ /** Current production detector configuration used as baseline in sweepDegradationParams */
1524
+ export const CURRENT_PRODUCTION_CONFIG = {
1525
+ varianceThreshold: VARIANCE_INCREASE_THRESHOLD,
1526
+ coverageDropoutThreshold: COVERAGE_DROPOUT_THRESHOLD,
1527
+ latencySkewThreshold: LATENCY_SKEW_SIGNAL_THRESHOLD,
1528
+ ewmaLambda: EWMA_LAMBDA,
1529
+ confirmationWindow: CONFIRMATION_MIN_CONSECUTIVE_BREACHES,
1530
+ };
1531
+ /**
1532
+ * Inject synthetic degradation into baseline snapshots for testing.
1533
+ * Returns a new array with degradation injected at the specified range.
1534
+ *
1535
+ * - variance_spike: sets currentStdDev = baselineStdDev * magnitude
1536
+ * - coverage_gap: sets coverageGapCount = totalCoverageCells * magnitude (fraction)
1537
+ * - latency_jump: sets latencyP95 = latencyP50 * magnitude (creates skew ratio = magnitude)
1538
+ */
1539
+ export function injectDegradationScenario(snapshots, type, magnitude, startIdx, duration) {
1540
+ return snapshots.map((s, i) => {
1541
+ if (i < startIdx || i >= startIdx + duration)
1542
+ return s;
1543
+ switch (type) {
1544
+ case 'variance_spike':
1545
+ return { ...s, currentStdDev: s.baselineStdDev * magnitude };
1546
+ case 'coverage_gap':
1547
+ return { ...s, coverageGapCount: Math.round(s.totalCoverageCells * magnitude) };
1548
+ case 'latency_jump':
1549
+ if (s.latencyP50 === undefined)
1550
+ return s;
1551
+ return { ...s, latencyP95: s.latencyP50 * magnitude };
1552
+ default:
1553
+ return s;
1554
+ }
1555
+ });
1556
+ }
1557
+ // ============================================================================
1558
+ // Role-Aware Feature Config (Section 16.4)
1559
+ // ============================================================================
1560
+ /** Zod schema for feature visibility configuration per role */
1561
+ export const roleFeatureConfigSchema = z.object({
1562
+ showCQI: z.boolean(),
1563
+ showCQIBreakdown: z.boolean(),
1564
+ showVariance: z.boolean(),
1565
+ showAcceleration: z.boolean(),
1566
+ showProjectedBreach: z.boolean(),
1567
+ showCorrelationRemediation: z.boolean(),
1568
+ showCoverageHeatmap: z.boolean(),
1569
+ showPipelineFunnel: z.boolean(),
1570
+ showProvenance: z.boolean(),
1571
+ showRawExport: z.boolean(),
1572
+ /** Chars before "Show more" truncation in explanations */
1573
+ explanationTruncation: z.number().int().nonnegative(),
1574
+ /** How many worst evaluations to display */
1575
+ maxWorstEvaluations: z.number().int().nonnegative(),
1576
+ });
1577
+ /** Feature configuration per role per design doc Section 16.4 */
1578
+ const configs = {
1579
+ executive: {
1580
+ showCQI: true,
1581
+ showCQIBreakdown: true,
1582
+ showVariance: false,
1583
+ showAcceleration: false,
1584
+ showProjectedBreach: true,
1585
+ showCorrelationRemediation: false,
1586
+ showCoverageHeatmap: false,
1587
+ showPipelineFunnel: false,
1588
+ showProvenance: false,
1589
+ showRawExport: false,
1590
+ explanationTruncation: EXECUTIVE_EXPLANATION_TRUNCATION,
1591
+ maxWorstEvaluations: EXECUTIVE_MAX_WORST_EVALUATIONS,
1592
+ },
1593
+ operator: {
1594
+ showCQI: false,
1595
+ showCQIBreakdown: false,
1596
+ showVariance: true,
1597
+ showAcceleration: true,
1598
+ showProjectedBreach: true,
1599
+ showCorrelationRemediation: true,
1600
+ showCoverageHeatmap: true,
1601
+ showPipelineFunnel: true,
1602
+ showProvenance: false,
1603
+ showRawExport: false,
1604
+ explanationTruncation: TEXT_PREVIEW_LIMIT,
1605
+ maxWorstEvaluations: OPERATOR_MAX_WORST_EVALUATIONS,
1606
+ },
1607
+ auditor: {
1608
+ showCQI: true,
1609
+ showCQIBreakdown: true,
1610
+ showVariance: true,
1611
+ showAcceleration: false,
1612
+ showProjectedBreach: false,
1613
+ showCorrelationRemediation: true,
1614
+ showCoverageHeatmap: true,
1615
+ showPipelineFunnel: true,
1616
+ showProvenance: true,
1617
+ showRawExport: true,
1618
+ explanationTruncation: AUDITOR_EXPLANATION_TRUNCATION,
1619
+ maxWorstEvaluations: AUDITOR_MAX_WORST_EVALUATIONS,
1620
+ },
1621
+ };
1622
+ /**
1623
+ * Get the feature configuration for a role.
1624
+ */
1625
+ export function getRoleFeatureConfig(role) {
1626
+ return configs[role];
1627
+ }
1628
+ /**
1629
+ * Compute sample standard deviation from an array of scores.
1630
+ * Returns null for arrays with fewer than 2 values.
1631
+ */
1632
+ export function computeStdDev(values) {
1633
+ if (values.length < 2)
1634
+ return null;
1635
+ return roundTo(ss.sampleStandardDeviation(values), SCORE_PRECISION);
1636
+ }
1637
+ /**
1638
+ * Load degradation state from persisted JSON file.
1639
+ * Returns empty state if file does not exist or is malformed.
1640
+ */
1641
+ export function loadDegradationState(dir) {
1642
+ const filePath = join(dir, DEGRADATION_STATE_FILE);
1643
+ try {
1644
+ const raw = readFileSync(filePath, 'utf8');
1645
+ const parsed = JSON.parse(raw);
1646
+ const result = degradationStateSchema.safeParse(parsed);
1647
+ if (result.success) {
1648
+ return result.data;
1649
+ }
1650
+ }
1651
+ catch { /* file not found or malformed */ }
1652
+ return { lastRun: '', breaches: {} };
1653
+ }
1654
+ /**
1655
+ * Save degradation state to JSON file for cross-run breach tracking.
1656
+ * Validates state before writing to ensure round-trip integrity.
1657
+ */
1658
+ export function saveDegradationState(dir, state) {
1659
+ const validated = degradationStateSchema.parse(state);
1660
+ writeFileSync(join(dir, DEGRADATION_STATE_FILE), JSON.stringify(validated, null, FORMAT_JSON_INDENT_SPACES) + '\n');
1661
+ }
1662
+ /**
1663
+ * Compute degradation signals for all metrics from time-bucketed evaluation data.
1664
+ *
1665
+ * Bridges the gap between sync-to-kv's bucket structure and computeDegradationSignal()'s
1666
+ * input requirements:
1667
+ * - Computes stdDev inline from bucket scores (not available in computeAggregations)
1668
+ * - Defines baseline as first DEGRADATION_BASELINE_RATIO (70%) of non-empty buckets
1669
+ * - Uses bucket sparsity (buckets with 0 evals / total buckets) as coverage proxy
1670
+ * - Passes latencyP95/P50 as null (latencySkewRatio defaults to 1.0)
1671
+ * - Reads priorConsecutiveBreaches from persisted DegradationState
1672
+ *
1673
+ * Skips metrics with fewer than MIN_BUCKETS_FOR_SIGNAL (3) non-empty buckets.
1674
+ */
1675
+ export function computeRollingDegradationSignals(timeBuckets, metricNames, state, window) {
1676
+ const reports = [];
1677
+ for (const metricName of metricNames) {
1678
+ const buckets = timeBuckets[metricName];
1679
+ if (!buckets || buckets.length === 0)
1680
+ continue;
1681
+ // Filter to non-empty buckets for baseline/current split
1682
+ const nonEmptyBuckets = buckets.filter(b => b.scores.length > 0);
1683
+ if (nonEmptyBuckets.length < MIN_BUCKETS_FOR_SIGNAL)
1684
+ continue;
1685
+ // Split into baseline (first 70%) and current (last bucket)
1686
+ const baselineEnd = Math.max(1, Math.floor(nonEmptyBuckets.length * DEGRADATION_BASELINE_RATIO));
1687
+ const baselineBuckets = nonEmptyBuckets.slice(0, baselineEnd);
1688
+ const currentBucket = nonEmptyBuckets[nonEmptyBuckets.length - 1];
1689
+ // Compute stdDev for baseline and current
1690
+ const baselineScores = baselineBuckets.flatMap(b => b.scores);
1691
+ const currentStdDev = computeStdDev(currentBucket.scores);
1692
+ const baselineStdDev = computeStdDev(baselineScores);
1693
+ // Bucket sparsity as coverage proxy
1694
+ const bucketSparsityGaps = buckets.filter(b => b.scores.length === 0).length;
1695
+ // Historical values: per-bucket averages, oldest-first (for EWMA).
1696
+ // Includes the current bucket intentionally — EWMA uses the full series to
1697
+ // track drift including the most recent observation. Contrast with
1698
+ // baselineStdDev (L2349) which uses only baselineBuckets (first 70%),
1699
+ // because stdDev comparison is meant to measure change vs. the stable baseline.
1700
+ const historicalValues = nonEmptyBuckets
1701
+ .map(b => b.scores.reduce((s, v) => s + v, 0) / b.scores.length);
1702
+ const priorBreaches = state.breaches[metricName] ?? 0;
1703
+ const signal = computeDegradationSignal(currentStdDev, baselineStdDev, bucketSparsityGaps, buckets.length, null, // latencyP95 unavailable
1704
+ null, // latencyP50 unavailable
1705
+ { historicalValues, priorConsecutiveBreaches: priorBreaches });
1706
+ // Total evaluation count
1707
+ const evaluationCount = buckets.reduce((sum, b) => sum + b.scores.length, 0);
1708
+ // Baseline period timestamps
1709
+ const baselinePeriod = {
1710
+ startDate: baselineBuckets[0].startTime,
1711
+ endDate: baselineBuckets[baselineBuckets.length - 1].endTime,
1712
+ };
1713
+ reports.push({
1714
+ metricName,
1715
+ signal,
1716
+ trend: null, // QualityMetricConfig unavailable here; caller enriches via computeTrend()
1717
+ window,
1718
+ baselinePeriod,
1719
+ evaluationCount,
1720
+ });
1721
+ }
1722
+ return reports;
1723
+ }
1724
+ export function computeAHPWeights(comparisons, metrics) {
1725
+ const n = metrics.length;
1726
+ const equalWeight = 1 / n;
1727
+ if (comparisons.length === 0) {
1728
+ const weights = {};
1729
+ for (const m of metrics)
1730
+ weights[m] = equalWeight;
1731
+ return { weights, consistencyRatio: 0 };
1732
+ }
1733
+ // Build n×n comparison matrix
1734
+ const matrix = Array.from({ length: n }, () => Array(n).fill(1));
1735
+ const indexMap = {};
1736
+ for (let i = 0; i < n; i++) {
1737
+ const m = metrics[i];
1738
+ if (m === undefined)
1739
+ throw new Error(`AHP: metrics array has hole at index ${i}`);
1740
+ indexMap[m] = i;
1741
+ }
1742
+ for (const c of comparisons) {
1743
+ const i = indexMap[c.metricA];
1744
+ const j = indexMap[c.metricB];
1745
+ if (i === undefined || j === undefined)
1746
+ continue;
1747
+ const val = c.preferred === 'A' ? c.preference : 1 / c.preference;
1748
+ if (matrix[i])
1749
+ matrix[i][j] = val;
1750
+ if (matrix[j])
1751
+ matrix[j][i] = 1 / val;
1752
+ }
1753
+ // Geometric mean of each row → normalize
1754
+ const geoMeans = pipe(matrix, map((row) => Math.pow(row.reduce((acc, v) => acc * v, 1), 1 / n)));
1755
+ const geoSum = geoMeans.reduce((acc, v) => acc + v, 0);
1756
+ const weights = Object.fromEntries(metrics.map((m, i) => [m, (geoMeans[i] ?? 0) / geoSum]));
1757
+ // Consistency check
1758
+ const weightVec = metrics.map(m => weights[m] ?? 0);
1759
+ let lambdaMax = 0;
1760
+ for (let i = 0; i < n; i++) {
1761
+ let rowSum = 0;
1762
+ const matrixRow = matrix[i];
1763
+ const wi = weightVec[i];
1764
+ if (!matrixRow || wi === undefined || wi === 0)
1765
+ throw new Error(`AHP: corrupt weight vector at index ${i}`);
1766
+ for (let j = 0; j < n; j++)
1767
+ rowSum += (matrixRow[j] ?? 0) * (weightVec[j] ?? 0);
1768
+ lambdaMax += rowSum / wi;
1769
+ }
1770
+ lambdaMax /= n;
1771
+ const ci = (lambdaMax - n) / (n - 1);
1772
+ const ri = AHP_RANDOM_INDEX[n] ?? 0;
1773
+ const consistencyRatio = ri === 0 ? 0 : ci / ri;
1774
+ return { weights, consistencyRatio };
1775
+ }
1776
+ export function computeCRITICWeights(metricScoreHistory) {
1777
+ const metrics = Object.keys(metricScoreHistory);
1778
+ if (metrics.length === 1) {
1779
+ const m = metrics[0] ?? '';
1780
+ const sd = computeStdDev(metricScoreHistory[m] ?? []) ?? 0;
1781
+ return {
1782
+ weights: { [m]: 1.0 },
1783
+ informationContent: { [m]: sd },
1784
+ };
1785
+ }
1786
+ // Stddev per metric
1787
+ const stdevs = {};
1788
+ for (const m of metrics)
1789
+ stdevs[m] = computeStdDev(metricScoreHistory[m] ?? []) ?? 0;
1790
+ // Pearson correlation matrix
1791
+ const correlations = {};
1792
+ for (const m of metrics) {
1793
+ correlations[m] = {};
1794
+ for (const other of metrics) {
1795
+ if (m === other) {
1796
+ if (correlations[m])
1797
+ correlations[m][other] = 1;
1798
+ }
1799
+ else {
1800
+ if (correlations[m])
1801
+ correlations[m][other] = computePearsonR(metricScoreHistory[m] ?? [], metricScoreHistory[other] ?? []);
1802
+ }
1803
+ }
1804
+ }
1805
+ // Information content = stddev × (1 - mean(|correlations with other metrics|))
1806
+ const informationContent = {};
1807
+ for (const m of metrics) {
1808
+ const others = metrics.filter(o => o !== m);
1809
+ const meanAbsCorr = others.length === 0
1810
+ ? 0
1811
+ : others.reduce((acc, o) => acc + Math.abs(correlations[m]?.[o] ?? 0), 0) / others.length;
1812
+ const ic = (stdevs[m] ?? 0) * (1 - meanAbsCorr);
1813
+ informationContent[m] = Number.isFinite(ic) ? ic : 0;
1814
+ }
1815
+ // Normalize to get weights
1816
+ const totalIC = Object.values(informationContent).reduce((a, v) => a + v, 0);
1817
+ const weights = {};
1818
+ if (totalIC === 0) {
1819
+ for (const m of metrics)
1820
+ weights[m] = 1 / metrics.length;
1821
+ }
1822
+ else {
1823
+ for (const m of metrics)
1824
+ weights[m] = (informationContent[m] ?? 0) / totalIC;
1825
+ }
1826
+ return { weights, informationContent };
1827
+ }
1828
+ export function computeHybridCQIWeights(ahpWeights, criticWeights, incidents, metrics, alpha = AHP_CRITIC_DEFAULT_ALPHA) {
1829
+ // Blend AHP and CRITIC weights
1830
+ const blended = {};
1831
+ for (const m of metrics) {
1832
+ blended[m] = alpha * (ahpWeights[m] ?? 0) + (1 - alpha) * (criticWeights[m] ?? 0);
1833
+ }
1834
+ // Normalize blended weights
1835
+ const blendedSum = Object.values(blended).reduce((a, v) => a + v, 0);
1836
+ const weights = {};
1837
+ for (const m of metrics) {
1838
+ weights[m] = blendedSum === 0 ? 1 / metrics.length : (blended[m] ?? 0) / blendedSum;
1839
+ }
1840
+ // Incident correlations
1841
+ const incidentCorrelations = {};
1842
+ if (incidents.length === 0) {
1843
+ for (const m of metrics)
1844
+ incidentCorrelations[m] = 0;
1845
+ }
1846
+ else {
1847
+ // For each metric: average score drop at incident times vs overall mean
1848
+ // Use point-biserial: incident signal = 1 at incident timestamps, 0 otherwise
1849
+ // Simplify: compute mean metric score at incident times; higher drop = higher correlation
1850
+ // Actually compute the "drop magnitude" = overallMean - incidentMean (if positive, metric drops at incidents)
1851
+ // Normalize across metrics to get relative correlation values
1852
+ const drops = {};
1853
+ for (const m of metrics) {
1854
+ const incidentScores = incidents
1855
+ .map(inc => inc.metricScores[m])
1856
+ .filter((v) => v !== undefined);
1857
+ const meanAtIncident = incidentScores.length === 0
1858
+ ? 0
1859
+ : incidentScores.reduce((a, v) => a + v, 0) / incidentScores.length;
1860
+ drops[m] = 1 - meanAtIncident; // higher drop value means more correlated with incident
1861
+ }
1862
+ // Normalize drops to sum to 1 if there's signal, else keep raw
1863
+ const maxDrop = Math.max(...Object.values(drops));
1864
+ const minDrop = Math.min(...Object.values(drops));
1865
+ const range = maxDrop - minDrop;
1866
+ for (const m of metrics) {
1867
+ incidentCorrelations[m] = range === 0 ? 0 : ((drops[m] ?? 0) - minDrop) / range;
1868
+ }
1869
+ }
1870
+ return {
1871
+ featureVersion: TUNED_CQI_WEIGHTS_FEATURE_VERSION,
1872
+ weights,
1873
+ ahpWeights,
1874
+ criticWeights,
1875
+ alpha,
1876
+ consistencyRatio: 0,
1877
+ incidentCorrelations,
1878
+ };
1879
+ }
1880
+ const ZERO_TAPR_METRICS = { precision: 0, recall: 0, f1: 0, detectionDelay: 0 };
1881
+ export function computeTaPR(detectedRanges, incidentRanges) {
1882
+ if (detectedRanges.length === 0 || incidentRanges.length === 0)
1883
+ return ZERO_TAPR_METRICS;
1884
+ const overlapMs = (a, b) => Math.max(0, Math.min(a.end, b.end) - Math.max(a.start, b.start));
1885
+ // Precision: average overlap ratio per detected range
1886
+ let precisionSum = 0;
1887
+ for (const d of detectedRanges) {
1888
+ const dDuration = d.end - d.start;
1889
+ if (dDuration <= 0)
1890
+ continue;
1891
+ let overlap = 0;
1892
+ for (const inc of incidentRanges)
1893
+ overlap += overlapMs(d, inc);
1894
+ precisionSum += Math.min(1, overlap / dDuration);
1895
+ }
1896
+ const precision = precisionSum / detectedRanges.length;
1897
+ // Recall: average overlap ratio per incident range
1898
+ let recallSum = 0;
1899
+ for (const inc of incidentRanges) {
1900
+ const incDuration = inc.end - inc.start;
1901
+ if (incDuration <= 0)
1902
+ continue;
1903
+ let overlap = 0;
1904
+ for (const d of detectedRanges)
1905
+ overlap += overlapMs(d, inc);
1906
+ recallSum += Math.min(1, overlap / incDuration);
1907
+ }
1908
+ const recall = recallSum / incidentRanges.length;
1909
+ const f1 = precision + recall > 0 ? (2 * precision * recall) / (precision + recall) : 0;
1910
+ // Detection delay: for each incident with an overlapping detection, delay = max(0, firstOverlappingDetection.start - incident.start)
1911
+ const delays = [];
1912
+ for (const inc of incidentRanges) {
1913
+ const overlapping = detectedRanges.filter(d => overlapMs(d, inc) > 0);
1914
+ if (overlapping.length === 0)
1915
+ continue;
1916
+ const firstStart = Math.min(...overlapping.map(d => d.start));
1917
+ delays.push(Math.max(0, firstStart - inc.start));
1918
+ }
1919
+ const detectionDelay = delays.length > 0 ? delays.reduce((a, b) => a + b, 0) / delays.length : 0;
1920
+ return { precision, recall, f1, detectionDelay };
1921
+ }
1922
+ /** Returns true if any signal for the point exceeds its configured threshold. */
1923
+ function isPointBreaching(pt, config) {
1924
+ const varianceRatio = pt.baselineStdDev > 0 ? pt.currentStdDev / pt.baselineStdDev : 0;
1925
+ const dropoutRate = pt.totalCoverageCells > 0 ? pt.coverageGapCount / pt.totalCoverageCells : 0;
1926
+ const skewRatio = pt.latencyP50 > 0 ? pt.latencyP95 / pt.latencyP50 : 0;
1927
+ const ewmaDrift = config.ewmaLambda !== undefined && pt.historicalValues.length > 0
1928
+ && detectEWMADrift(pt.historicalValues, config.ewmaLambda);
1929
+ const signals = [
1930
+ varianceRatio > config.varianceThreshold,
1931
+ dropoutRate > config.coverageDropoutThreshold,
1932
+ skewRatio > config.latencySkewThreshold,
1933
+ ewmaDrift,
1934
+ ];
1935
+ return signals.some(Boolean);
1936
+ }
1937
+ /**
1938
+ * Runs a degradation signal backtest with a specific parameter configuration.
1939
+ * Replays historical time series data and compares detected degradation periods
1940
+ * against labeled incidents using TaPR and point-based metrics.
1941
+ */
1942
+ export function backtestDegradationConfig(config, timeSeries, incidents) {
1943
+ if (timeSeries.length === 0) {
1944
+ return { config, truePositives: 0, falsePositives: 0, falseNegatives: 0, trueNegatives: 0,
1945
+ tapr: ZERO_TAPR_METRICS, pointPrecision: 0, pointRecall: 0, pointF1: 0 };
1946
+ }
1947
+ const incidentRanges = incidents.map(inc => ({
1948
+ start: new Date(inc.startTime).getTime(),
1949
+ end: new Date(inc.endTime).getTime(),
1950
+ }));
1951
+ // Classify each timestamp as breaching or not
1952
+ const breaching = timeSeries.map(pt => isPointBreaching(pt, config));
1953
+ // Build confirmed degradation state per timestamp (confirmationWindow consecutive breaches)
1954
+ const confirmed = new Array(timeSeries.length).fill(false);
1955
+ let consecutiveBreaches = 0;
1956
+ for (let i = 0; i < timeSeries.length; i++) {
1957
+ consecutiveBreaches = breaching[i] ? consecutiveBreaches + 1 : 0;
1958
+ confirmed[i] = consecutiveBreaches >= config.confirmationWindow;
1959
+ }
1960
+ // Build detected ranges from consecutive confirmed timestamps
1961
+ const detectedRanges = [];
1962
+ let rangeStart = null;
1963
+ for (let i = 0; i < timeSeries.length; i++) {
1964
+ if (confirmed[i]) {
1965
+ if (rangeStart === null)
1966
+ rangeStart = timeSeries[i].timestamp;
1967
+ }
1968
+ else {
1969
+ if (rangeStart !== null) {
1970
+ detectedRanges.push({ start: rangeStart, end: timeSeries[i - 1].timestamp });
1971
+ rangeStart = null;
1972
+ }
1973
+ }
1974
+ }
1975
+ if (rangeStart !== null) {
1976
+ detectedRanges.push({ start: rangeStart, end: timeSeries[timeSeries.length - 1].timestamp });
1977
+ }
1978
+ const tapr = computeTaPR(detectedRanges, incidentRanges);
1979
+ // Point-based classification
1980
+ let tp = 0, fp = 0, fn = 0, tn = 0;
1981
+ for (let i = 0; i < timeSeries.length; i++) {
1982
+ const ts = timeSeries[i].timestamp;
1983
+ const inIncident = incidentRanges.some(r => ts >= r.start && ts <= r.end);
1984
+ if (confirmed[i]) {
1985
+ if (inIncident)
1986
+ tp++;
1987
+ else
1988
+ fp++;
1989
+ }
1990
+ else {
1991
+ if (inIncident)
1992
+ fn++;
1993
+ else
1994
+ tn++;
1995
+ }
1996
+ }
1997
+ const pointPrecision = tp + fp > 0 ? tp / (tp + fp) : 0;
1998
+ const pointRecall = tp + fn > 0 ? tp / (tp + fn) : 0;
1999
+ const pointF1 = pointPrecision + pointRecall > 0
2000
+ ? (2 * pointPrecision * pointRecall) / (pointPrecision + pointRecall)
2001
+ : 0;
2002
+ return { config, truePositives: tp, falsePositives: fp, falseNegatives: fn, trueNegatives: tn,
2003
+ tapr, pointPrecision, pointRecall, pointF1 };
2004
+ }
2005
+ /** All cartesian-product BacktestConfig combinations from BACKTEST_SWEEP */
2006
+ const SWEEP_CONFIGS = (() => {
2007
+ const { varianceThreshold, coverageDropoutThreshold, ewmaLambda, confirmationWindow, stabilityThreshold } = BACKTEST_SWEEP;
2008
+ const configs = [];
2009
+ for (const vt of varianceThreshold) {
2010
+ for (const cdt of coverageDropoutThreshold) {
2011
+ for (const lambda of ewmaLambda) {
2012
+ for (const cw of confirmationWindow) {
2013
+ for (const st of stabilityThreshold) {
2014
+ configs.push({ varianceThreshold: vt, coverageDropoutThreshold: cdt, ewmaLambda: lambda, confirmationWindow: cw, stabilityThreshold: st, latencySkewThreshold: LATENCY_SKEW_SIGNAL_THRESHOLD });
2015
+ }
2016
+ }
2017
+ }
2018
+ }
2019
+ }
2020
+ return configs;
2021
+ })();
2022
+ /**
2023
+ * Runs a full parameter sweep across the BACKTEST_SWEEP grid, ranks results by TaPR F1,
2024
+ * and includes the current production config as a baseline comparison.
2025
+ */
2026
+ export function sweepDegradationParams(timeSeries, incidents) {
2027
+ const configs = SWEEP_CONFIGS;
2028
+ const results = configs.map(config => backtestDegradationConfig(config, timeSeries, incidents));
2029
+ const currentConfigResult = backtestDegradationConfig(CURRENT_PRODUCTION_CONFIG, timeSeries, incidents);
2030
+ const bestByF1 = results.reduce((best, r) => r.tapr.f1 > best.tapr.f1 ? r : best, results[0]);
2031
+ const bestByRecall = results.reduce((best, r) => r.tapr.recall > best.tapr.recall ? r : best, results[0]);
2032
+ return { results, bestByF1, bestByRecall, currentConfigResult };
2033
+ }
2034
+ /**
2035
+ * Leave-one-out cross-validation for sweepDegradationParams.
2036
+ *
2037
+ * With only 5-10 incidents, overfitting risk is high. This function holds out
2038
+ * each incident in turn, runs the full sweep on the remaining incidents, and
2039
+ * reports variance in the optimal config selection across folds.
2040
+ *
2041
+ * High parameterVariance values indicate that the best config is sensitive to
2042
+ * individual incidents and should not be trusted for threshold graduation.
2043
+ */
2044
+ export function sweepWithCrossValidation(timeSeries, incidents) {
2045
+ const fullSweep = sweepDegradationParams(timeSeries, incidents);
2046
+ // LOO folds — each fold holds out one incident
2047
+ const folds = incidents.map((heldOut, i) => {
2048
+ const remaining = incidents.filter((_, j) => j !== i);
2049
+ return { heldOutIncident: heldOut, sweepResult: sweepDegradationParams(timeSeries, remaining) };
2050
+ });
2051
+ // Compute std dev of best-by-F1 config parameters across folds
2052
+ const paramValues = folds.map(f => f.sweepResult.bestByF1.config);
2053
+ const varStd = computeStdDev(paramValues.map(c => c.varianceThreshold)) ?? 0;
2054
+ const dropStd = computeStdDev(paramValues.map(c => c.coverageDropoutThreshold)) ?? 0;
2055
+ const winStd = computeStdDev(paramValues.map(c => c.confirmationWindow)) ?? 0;
2056
+ // Stable when every LOO fold selects the same best-by-F1 config as the full sweep.
2057
+ // Values are from a discrete sweep grid, so exact equality is safe.
2058
+ const fullBest = fullSweep.bestByF1.config;
2059
+ const isStable = folds.every(f => {
2060
+ const fb = f.sweepResult.bestByF1.config;
2061
+ return fb.varianceThreshold === fullBest.varianceThreshold &&
2062
+ fb.coverageDropoutThreshold === fullBest.coverageDropoutThreshold &&
2063
+ fb.confirmationWindow === fullBest.confirmationWindow;
2064
+ });
2065
+ return {
2066
+ fullSweep,
2067
+ folds,
2068
+ parameterStability: {
2069
+ varianceThresholdStdDev: varStd,
2070
+ coverageDropoutThresholdStdDev: dropStd,
2071
+ confirmationWindowStdDev: winStd,
2072
+ },
2073
+ isStable,
2074
+ };
2075
+ }
2076
+ //# sourceMappingURL=quality-feature-engineering.js.map