@marinoscar/kvox-eval 0.0.0-bootstrap → 0.1.0-next.0

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 (245) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/dist/ask/index.d.ts +6 -0
  5. package/dist/ask/index.js +28 -0
  6. package/dist/ask/question-schema.d.ts +121 -0
  7. package/dist/ask/question-schema.js +167 -0
  8. package/dist/ask/report.d.ts +88 -0
  9. package/dist/ask/report.js +170 -0
  10. package/dist/ask/scorer.d.ts +104 -0
  11. package/dist/ask/scorer.js +164 -0
  12. package/dist/ask-run/ask-command.d.ts +62 -0
  13. package/dist/ask-run/ask-command.js +254 -0
  14. package/dist/ask-run/host/app.d.ts +65 -0
  15. package/dist/ask-run/host/app.js +146 -0
  16. package/dist/ask-run/host/db.d.ts +38 -0
  17. package/dist/ask-run/host/db.js +126 -0
  18. package/dist/ask-run/host/ports.d.ts +35 -0
  19. package/dist/ask-run/host/ports.js +113 -0
  20. package/dist/ask-run/index.d.ts +13 -0
  21. package/dist/ask-run/index.js +35 -0
  22. package/dist/ask-run/replay.d.ts +128 -0
  23. package/dist/ask-run/replay.js +212 -0
  24. package/dist/ask-run/run-ask-eval.d.ts +125 -0
  25. package/dist/ask-run/run-ask-eval.js +193 -0
  26. package/dist/ask-run/seed-golden-graph.d.ts +64 -0
  27. package/dist/ask-run/seed-golden-graph.js +495 -0
  28. package/dist/cli/index.d.ts +3 -0
  29. package/dist/cli/index.js +12 -0
  30. package/dist/cli/kg-command.d.ts +66 -0
  31. package/dist/cli/kg-command.js +238 -0
  32. package/dist/cli/main.d.ts +18 -0
  33. package/dist/cli/main.js +69 -0
  34. package/dist/fixtures/domains.d.ts +16 -0
  35. package/dist/fixtures/domains.js +33 -0
  36. package/dist/fixtures/fixture-schema.d.ts +323 -0
  37. package/dist/fixtures/fixture-schema.js +199 -0
  38. package/dist/fixtures/index.d.ts +16 -0
  39. package/dist/fixtures/index.js +40 -0
  40. package/dist/fixtures/load.d.ts +73 -0
  41. package/dist/fixtures/load.js +137 -0
  42. package/dist/fixtures/questions.d.ts +15 -0
  43. package/dist/fixtures/questions.js +26 -0
  44. package/dist/index.d.ts +20 -0
  45. package/dist/index.js +36 -0
  46. package/dist/kg/gold-runner.d.ts +13 -0
  47. package/dist/kg/gold-runner.js +57 -0
  48. package/dist/kg/index.d.ts +17 -0
  49. package/dist/kg/index.js +65 -0
  50. package/dist/kg/internals.d.ts +7 -0
  51. package/dist/kg/internals.js +17 -0
  52. package/dist/kg/prediction-schema.d.ts +245 -0
  53. package/dist/kg/prediction-schema.js +102 -0
  54. package/dist/kg/predictions.d.ts +11 -0
  55. package/dist/kg/predictions.js +49 -0
  56. package/dist/kg/report.d.ts +84 -0
  57. package/dist/kg/report.js +125 -0
  58. package/dist/kg/run-kg-eval.d.ts +77 -0
  59. package/dist/kg/run-kg-eval.js +137 -0
  60. package/dist/kg/runner.d.ts +72 -0
  61. package/dist/kg/runner.js +51 -0
  62. package/dist/kg/runners/extract-resolve-runner.d.ts +40 -0
  63. package/dist/kg/runners/extract-resolve-runner.js +199 -0
  64. package/dist/kg/runners/extract-runner.d.ts +65 -0
  65. package/dist/kg/runners/extract-runner.js +243 -0
  66. package/dist/kg/scorer.d.ts +226 -0
  67. package/dist/kg/scorer.js +463 -0
  68. package/dist/kg/targets.d.ts +17 -0
  69. package/dist/kg/targets.js +16 -0
  70. package/dist/kg/text.d.ts +45 -0
  71. package/dist/kg/text.js +100 -0
  72. package/dist/live/adapters.d.ts +16 -0
  73. package/dist/live/adapters.js +58 -0
  74. package/dist/live/env.d.ts +39 -0
  75. package/dist/live/env.js +56 -0
  76. package/dist/live/index.d.ts +6 -0
  77. package/dist/live/index.js +16 -0
  78. package/dist/live/live-ai.d.ts +104 -0
  79. package/dist/live/live-ai.js +157 -0
  80. package/dist/parity/constants.d.ts +79 -0
  81. package/dist/parity/constants.js +104 -0
  82. package/dist/parity/index.d.ts +20 -0
  83. package/dist/parity/index.js +38 -0
  84. package/dist/parity/internals.d.ts +8 -0
  85. package/dist/parity/internals.js +16 -0
  86. package/dist/parity/scenario.d.ts +45 -0
  87. package/dist/parity/scenario.js +245 -0
  88. package/dist/parity/script.d.ts +44 -0
  89. package/dist/parity/script.js +136 -0
  90. package/dist/parity/snapshot.d.ts +43 -0
  91. package/dist/parity/snapshot.js +71 -0
  92. package/dist/real-data/index.d.ts +2 -0
  93. package/dist/real-data/index.js +12 -0
  94. package/dist/real-data/real-data.d.ts +76 -0
  95. package/dist/real-data/real-data.js +156 -0
  96. package/fixtures/kg-golden/README.md +136 -0
  97. package/fixtures/kg-golden/ask-questions.json +458 -0
  98. package/fixtures/kg-golden/meetings/m01-northwind-pilot-kickoff.json +792 -0
  99. package/fixtures/kg-golden/meetings/m02-halden-migration-planning.json +814 -0
  100. package/fixtures/kg-golden/meetings/m03-larkspur-weekly-sync.json +601 -0
  101. package/fixtures/kg-golden/meetings/m04-corvid-intake-discovery.json +690 -0
  102. package/fixtures/kg-golden/meetings/m05-hosting-vendor-selection.json +631 -0
  103. package/fixtures/kg-golden/meetings/m06-northwind-floor-data-review.json +649 -0
  104. package/fixtures/kg-golden/meetings/m07-gullwing-intro-notes.json +428 -0
  105. package/fixtures/kg-golden/meetings/m08-halden-migration-checkpoint.json +672 -0
  106. package/fixtures/kg-golden/meetings/m09-northwind-platform-org-update.json +711 -0
  107. package/fixtures/kg-golden/meetings/m10-corvid-prototype-review.json +623 -0
  108. package/fixtures/kg-golden/meetings/m11-solvane-grid-dashboard-scoping.json +689 -0
  109. package/fixtures/kg-golden/meetings/m12-northwind-pilot-replan.json +681 -0
  110. package/fixtures/kg-golden/meetings/m13-halden-cutover-go-no-go.json +721 -0
  111. package/fixtures/kg-golden/meetings/m14-gullwing-discovery-readout.json +719 -0
  112. package/fixtures/kg-golden/meetings/m15-halden-handover-ravi-leaving.json +716 -0
  113. package/fixtures/kg-golden/meetings/m16-varden-claims-portal-intro.json +704 -0
  114. package/fixtures/kg-golden/meetings/m17-corvid-usability-debrief.json +572 -0
  115. package/fixtures/kg-golden/meetings/m18-hosting-decision-revisited.json +655 -0
  116. package/fixtures/kg-golden/meetings/m19-solvane-integration-kickoff.json +726 -0
  117. package/fixtures/kg-golden/meetings/m20-northwind-go-live-retro.json +673 -0
  118. package/fixtures/kg-golden/meetings/m21-quarterly-account-review.json +449 -0
  119. package/fixtures/kg-golden/meetings/m22-halden-intro-call-2020.json +558 -0
  120. package/fixtures/kg-golden/meetings/m23-gullwing-model-review.json +702 -0
  121. package/fixtures/kg-golden/meetings/m24-varden-discovery-midpoint.json +643 -0
  122. package/fixtures/kg-golden/meetings/m25-corvid-rollout-planning.json +642 -0
  123. package/fixtures/kg-golden/meetings/m26-larkspur-engineering-prep.json +695 -0
  124. package/fixtures/kg-golden/meetings/m27-solvane-phase-one-demo.json +649 -0
  125. package/fixtures/kg-golden/meetings/m28-northwind-building-5-scoping.json +715 -0
  126. package/fixtures/kg-golden/meetings/m29-halden-southern-cutover-review.json +752 -0
  127. package/fixtures/kg-golden/meetings/m30-varden-portal-build-kickoff.json +697 -0
  128. package/fixtures/kg-golden/meetings/m31-gullwing-rollout-renewal.json +681 -0
  129. package/fixtures/kg-golden/meetings/m32-brambleway-weekly-sync.json +472 -0
  130. package/fixtures/kg-golden/meetings/m33-nina-oliver-one-on-one.json +342 -0
  131. package/fixtures/kg-golden/meetings/m34-coffee-with-dana.json +249 -0
  132. package/fixtures/kg-golden/meetings/m35-brambleway-june-sync.json +553 -0
  133. package/fixtures/kg-golden/meetings/m36-copenhagen-conference-planning.json +296 -0
  134. package/host/generated/client.d.ts +1 -0
  135. package/host/generated/client.js +5 -0
  136. package/host/generated/default.d.ts +1 -0
  137. package/host/generated/default.js +5 -0
  138. package/host/generated/edge.d.ts +1 -0
  139. package/host/generated/edge.js +1658 -0
  140. package/host/generated/index-browser.js +1684 -0
  141. package/host/generated/index.d.ts +192335 -0
  142. package/host/generated/index.js +1658 -0
  143. package/host/generated/package.json +139 -0
  144. package/host/generated/query_compiler_fast_bg.js +2 -0
  145. package/host/generated/query_compiler_fast_bg.wasm +0 -0
  146. package/host/generated/query_compiler_fast_bg.wasm-base64.js +2 -0
  147. package/host/generated/runtime/client.d.ts +3433 -0
  148. package/host/generated/runtime/client.js +110 -0
  149. package/host/generated/runtime/index-browser.d.ts +90 -0
  150. package/host/generated/runtime/index-browser.js +6 -0
  151. package/host/generated/runtime/wasm-compiler-edge.js +100 -0
  152. package/host/generated/schema.prisma +6312 -0
  153. package/host/generated/wasm-edge-light-loader.mjs +5 -0
  154. package/host/generated/wasm-worker-loader.mjs +5 -0
  155. package/host/prisma/fragments/kvox.ask.prisma +126 -0
  156. package/host/prisma/fragments/kvox.graph.prisma +1073 -0
  157. package/host/prisma/fragments/kvox.notes.prisma +787 -0
  158. package/host/prisma/fragments/kvox.search.prisma +211 -0
  159. package/host/prisma/fragments/kvox.transcripts.prisma +790 -0
  160. package/host/prisma/kvox.lock +101 -0
  161. package/host/prisma/migrations/20240101000000_initial/migration.sql +297 -0
  162. package/host/prisma/migrations/20240101000001_add_personal_access_tokens/migration.sql +31 -0
  163. package/host/prisma/migrations/20240101000002_add_credentials/migration.sql +20 -0
  164. package/host/prisma/migrations/20240101000003_add_notification_deliveries/migration.sql +27 -0
  165. package/host/prisma/migrations/20240101000004_drop_stale_uuid_defaults/migration.sql +35 -0
  166. package/host/prisma/migrations/20240101000005_add_notifications/migration.sql +22 -0
  167. package/host/prisma/migrations/20240101000006_add_push_subscriptions/migration.sql +25 -0
  168. package/host/prisma/migrations/20240101000007_add_jobs/migration.sql +110 -0
  169. package/host/prisma/migrations/20240101000008_add_worker_nodes/migration.sql +90 -0
  170. package/host/prisma/migrations/20240101000009_add_database_backup_runs/migration.sql +104 -0
  171. package/host/prisma/migrations/20240101000010_add_notification_broadcasts/migration.sql +57 -0
  172. package/host/prisma/migrations/20240101000011_add_backup_run_job_link/migration.sql +87 -0
  173. package/host/prisma/migrations/20240101000012_add_job_node_secrets/migration.sql +61 -0
  174. package/host/prisma/migrations/20240101000013_add_backup_run_pg_dump_version/migration.sql +26 -0
  175. package/host/prisma/migrations/20240101000014_add_job_claim_token/migration.sql +92 -0
  176. package/host/prisma/migrations/20240101000015_add_ai_platform/migration.sql +108 -0
  177. package/host/prisma/migrations/20240101000016_revoke_viewer_ai_use/migration.sql +32 -0
  178. package/host/prisma/migrations/20240101000017_add_user_credentials/migration.sql +29 -0
  179. package/host/prisma/migrations/20240101000018_add_device_session_credential_link/migration.sql +52 -0
  180. package/host/prisma/migrations/20240101000019_add_worker_node_vitals/migration.sql +3 -0
  181. package/host/prisma/migrations/20240101000020_add_job_trace_context/migration.sql +6 -0
  182. package/host/prisma/migrations/20240101000021_add_retention_created_at_indexes/migration.sql +13 -0
  183. package/host/prisma/migrations/20240101000022_add_organizations/migration.sql +142 -0
  184. package/host/prisma/migrations/20240101000023_split_system_org_roles/migration.sql +131 -0
  185. package/host/prisma/migrations/20240101000024_org_scoped_rls/migration.sql +215 -0
  186. package/host/prisma/migrations/20240101000025_add_groups/migration.sql +165 -0
  187. package/host/prisma/migrations/20240101000026_add_grants/migration.sql +117 -0
  188. package/host/prisma/migrations/20240101000027_add_org_settings/migration.sql +49 -0
  189. package/host/prisma/migrations/20240101000028_add_org_credentials/migration.sql +54 -0
  190. package/host/prisma/migrations/20240101000029_add_jobs_org_id/migration.sql +18 -0
  191. package/host/prisma/migrations/20240101000030_add_jobs_org_id_status_index/migration.sql +6 -0
  192. package/host/prisma/migrations/20240101000031_add_broadcast_target_org/migration.sql +21 -0
  193. package/host/prisma/migrations/20240101000032_add_android_app/migration.sql +60 -0
  194. package/host/prisma/migrations/20240101000033_open_job_reason/migration.sql +16 -0
  195. package/host/prisma/migrations/20240101000034_ai_usage_feature/migration.sql +37 -0
  196. package/host/prisma/migrations/20240101000035_add_app_metadata/migration.sql +49 -0
  197. package/host/prisma/migrations/20240101000036_open_sharing_vocabularies/migration.sql +97 -0
  198. package/host/prisma/migrations/20260914130000_kvox_add_transcripts/migration.sql +322 -0
  199. package/host/prisma/migrations/20260914140000_kvox_add_notes/migration.sql +311 -0
  200. package/host/prisma/migrations/20260915120000_kvox_add_search_vectors/migration.sql +150 -0
  201. package/host/prisma/migrations/20260915130000_kvox_add_search_embeddings/migration.sql +362 -0
  202. package/host/prisma/migrations/20260915210000_kvox_add_note_title_source/migration.sql +30 -0
  203. package/host/prisma/migrations/20260923120000_kvox_add_note_generation_context_snapshot/migration.sql +24 -0
  204. package/host/prisma/migrations/20260923130000_kvox_add_user_hidden_note_templates/migration.sql +35 -0
  205. package/host/prisma/migrations/20260923140000_kvox_add_transcript_speaker_identities/migration.sql +6 -0
  206. package/host/prisma/migrations/20260923150000_kvox_transcript_name_checks/migration.sql +95 -0
  207. package/host/prisma/migrations/20260923160000_kvox_add_note_body_format/migration.sql +27 -0
  208. package/host/prisma/migrations/20260923170000_kvox_add_note_version_body_format/migration.sql +23 -0
  209. package/host/prisma/migrations/20260926012548_kvox_add_knowledge_graph/migration.sql +680 -0
  210. package/host/prisma/migrations/20260926030000_kvox_fix_kg_relations_valid_precision_chk/migration.sql +29 -0
  211. package/host/prisma/migrations/20260926030100_kvox_add_transcript_recorded_at/migration.sql +9 -0
  212. package/host/prisma/migrations/20260926030200_kvox_kg_evidence_invariant/migration.sql +60 -0
  213. package/host/prisma/migrations/20260926040000_kvox_add_kg_graph_layouts/migration.sql +30 -0
  214. package/host/prisma/migrations/20260926050000_kvox_add_ask_conversations/migration.sql +99 -0
  215. package/host/prisma/migrations/20260926231512_kvox_add_kg_exports/migration.sql +52 -0
  216. package/host/prisma/migrations/20261016120000_kvox_transcripts_org_id/migration.sql +49 -0
  217. package/host/prisma/platform.lock +319 -0
  218. package/host/prisma/schema/app.kvox.ask.prisma +110 -0
  219. package/host/prisma/schema/app.kvox.graph.prisma +1030 -0
  220. package/host/prisma/schema/app.kvox.notes.prisma +746 -0
  221. package/host/prisma/schema/app.kvox.search.prisma +208 -0
  222. package/host/prisma/schema/app.kvox.transcripts.prisma +726 -0
  223. package/host/prisma/schema/platform.ai.prisma +419 -0
  224. package/host/prisma/schema/platform.android-app.prisma +46 -0
  225. package/host/prisma/schema/platform.base.prisma +15 -0
  226. package/host/prisma/schema/platform.credentials.prisma +156 -0
  227. package/host/prisma/schema/platform.db-backup.prisma +359 -0
  228. package/host/prisma/schema/platform.identity.prisma +638 -0
  229. package/host/prisma/schema/platform.jobs.prisma +824 -0
  230. package/host/prisma/schema/platform.notifications.prisma +641 -0
  231. package/host/prisma/schema/platform.settings.prisma +53 -0
  232. package/host/prisma/schema/platform.sharing.prisma +208 -0
  233. package/host/prisma/schema/platform.storage.prisma +118 -0
  234. package/package.json +112 -1
  235. package/parity/requests.ask.json +2681 -0
  236. package/parity/requests.m01.json +1554 -0
  237. package/parity/requests.m02.json +1554 -0
  238. package/parity/requests.m03.json +1554 -0
  239. package/parity/requests.m04.json +1554 -0
  240. package/parity/requests.m05.json +1554 -0
  241. package/parity/requests.m06.json +1554 -0
  242. package/parity/requests.m07.json +1554 -0
  243. package/parity/requests.m08.json +1554 -0
  244. package/parity/requests.m09.json +1554 -0
  245. package/parity/requests.m10.json +1554 -0
@@ -0,0 +1,495 @@
1
+ "use strict";
2
+ // =============================================================================
3
+ // The headless Ask evaluation's golden-graph seeder (issue #608; port of the web
4
+ // app's `apps/api/scripts/ask-eval/seed-graph.ts`, #382)
5
+ // =============================================================================
6
+ //
7
+ // Loads EVERY meeting of the golden set and writes it as the evaluation user's
8
+ // data: a `transcripts`/`transcript_speakers`/`transcript_segments` row per
9
+ // meeting with a transcript, a `notes`/`note_versions` row for every meeting,
10
+ // and the meeting's HAND LABELS (never a model's extraction) as `accepted`
11
+ // entities, relations and items with evidence pointing at the segments and the
12
+ // note it just created. That is what makes the evaluation ISOLATE the agent:
13
+ // extraction quality is `kvox-eval kg`'s question, not this one's.
14
+ //
15
+ // EVERY GRAPH ROW GOES THROUGH `GraphWriteService` (the one sanctioned write
16
+ // path): entities, their aliases, relations, items, evidence and supersession,
17
+ // inside one `prisma.$transaction` per fixture with the user's effective
18
+ // schema. So the type, endpoint, temporal, status and evidence-readability
19
+ // checks run, and the deferred `kg_assert_has_evidence` trigger checks every
20
+ // accepted row at COMMIT. Transcripts, segments, speakers, notes, note versions
21
+ // and storage objects are not graph rows; they are plain inserts.
22
+ //
23
+ // AN ENTITY THAT RECURS ACROSS MEETINGS BECOMES ONE ROW. A fixture's
24
+ // `knownEntities[].id` (`"g-person-sarah-chen"`) is the stable cross-fixture
25
+ // identity a later meeting's `existingId` links back to. Fixtures are seeded in
26
+ // id order, so a person's first mention creates the row; a later `existingId`,
27
+ // or a label/alias that matches what any fixture declares for that global id,
28
+ // resolves to it (and adds its evidence with `addEvidence`). A label no
29
+ // `knownEntities` list mentions stays a one-off row of its own meeting.
30
+ //
31
+ // A REFERENCE-ONLY ENTITY (a `knownEntities` id a relation or item names, with
32
+ // no label of its own anywhere yet) still needs evidence to be `accepted`. The
33
+ // web app's seeder wrote a quote-only filler row (no anchor); the write service
34
+ // refuses an unanchored citation, so the filler cites the referring meeting's
35
+ // note with the label as its quote, the shape of a note-Context citation. It is
36
+ // left OUT of `evidenceSource`, exactly as the unanchored filler was.
37
+ //
38
+ // WHAT THE WRITE SERVICE REFUSES IS REPORTED, NEVER DROPPED SILENTLY: each
39
+ // refusal lands in `refusals` (fixture, row, the service's message; type keys
40
+ // and paths only, never a label, statement or quote) and the row is skipped.
41
+ // The refusals the service raises happen before its insert, so the
42
+ // transaction stays usable.
43
+ // =============================================================================
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.seedGoldenGraph = seedGoldenGraph;
46
+ const node_crypto_1 = require("node:crypto");
47
+ const graph_1 = require("@marinoscar/kvox-api/graph");
48
+ const index_js_1 = require("../fixtures/index.js");
49
+ const normalize = (s) => s.trim().toLowerCase();
50
+ function buildGlobalCatalog(fixtures) {
51
+ const catalog = new Map();
52
+ for (const fixture of fixtures) {
53
+ for (const known of fixture.knownEntities) {
54
+ const existing = catalog.get(known.id);
55
+ if (existing) {
56
+ for (const alias of known.aliases)
57
+ existing.aliases.add(alias);
58
+ }
59
+ else {
60
+ catalog.set(known.id, { type: known.type, label: known.label, aliases: new Set(known.aliases) });
61
+ }
62
+ }
63
+ }
64
+ return catalog;
65
+ }
66
+ function buildLabelIndex(catalog) {
67
+ const index = new Map();
68
+ for (const [globalId, entry] of catalog) {
69
+ for (const name of [entry.label, ...entry.aliases]) {
70
+ index.set(`${entry.type}\u0000${normalize(name)}`, globalId);
71
+ }
72
+ }
73
+ return index;
74
+ }
75
+ /** The global identity this label resolves to, if any: `existingId`, else a label/alias match against the catalog. */
76
+ function globalIdFor(ctx, entity) {
77
+ if (entity.existingId)
78
+ return entity.existingId;
79
+ const byLabel = ctx.labelToGlobalId.get(`${entity.type}\u0000${normalize(entity.label)}`);
80
+ if (byLabel)
81
+ return byLabel;
82
+ for (const alias of entity.aliases) {
83
+ const hit = ctx.labelToGlobalId.get(`${entity.type}\u0000${normalize(alias)}`);
84
+ if (hit)
85
+ return hit;
86
+ }
87
+ return null;
88
+ }
89
+ /** The refusal's message: the error's own text plus its issue paths (the service never puts data in either). */
90
+ function reasonOf(error) {
91
+ const e = error;
92
+ const issues = e.details?.issues?.map((i) => `${String(i.path)}: ${String(i.message)}`) ?? [];
93
+ const response = e.response;
94
+ const invalidEvidence = response?.details?.invalidEvidence;
95
+ const extra = [...issues, ...(invalidEvidence ? [`invalid evidence at ${invalidEvidence.join(', ')}`] : [])];
96
+ return `${typeof e.message === 'string' ? e.message : String(error)}${extra.length ? ` [${extra.join('; ')}]` : ''}`;
97
+ }
98
+ /** Runs one write; a refusal is recorded and answers `null`. */
99
+ async function attempt(ctx, row, write) {
100
+ try {
101
+ return await write();
102
+ }
103
+ catch (error) {
104
+ const name = error.name;
105
+ const status = error.getStatus?.();
106
+ const refusal = name === 'GraphValidationError' ||
107
+ name === 'GraphInvariantError' ||
108
+ name === 'GraphDuplicateError' ||
109
+ status === 400 ||
110
+ status === 404;
111
+ if (!refusal)
112
+ throw error;
113
+ ctx.refusals.push({ fixtureId: ctx.fixture.id, row, reason: reasonOf(error) });
114
+ return null;
115
+ }
116
+ }
117
+ /** A label's evidence as write-service input, anchored to the rows this run created. */
118
+ function toEvidence(ctx, labels) {
119
+ const out = [];
120
+ for (const ev of labels) {
121
+ const quote = ev.quote.slice(0, 400);
122
+ if (ev.source === 'segment') {
123
+ const segment = ctx.segmentRows.get(ev.segmentId);
124
+ if (!segment) {
125
+ ctx.warnings.push(`${ctx.fixture.id}: evidence cites unknown segment "${ev.segmentId}"`);
126
+ continue;
127
+ }
128
+ out.push(evidenceInput({ transcriptId: segment.transcriptId, segmentId: segment.id, startMs: segment.startMs, endMs: segment.endMs, quote }));
129
+ }
130
+ else {
131
+ const noteId = ctx.idMap.notes.get(ctx.fixture.id);
132
+ if (!noteId) {
133
+ ctx.warnings.push(`${ctx.fixture.id}: evidence cites the meeting's note, which it has none of`);
134
+ continue;
135
+ }
136
+ out.push(evidenceInput({ noteId, noteVersion: ctx.fixture.note.version, quote }));
137
+ }
138
+ }
139
+ return out;
140
+ }
141
+ /** A full {@link EvidenceInput} (every anchor field `null` unless given). */
142
+ function evidenceInput(fields) {
143
+ return {
144
+ transcriptId: null,
145
+ segmentId: null,
146
+ segmentRev: null,
147
+ startMs: null,
148
+ endMs: null,
149
+ noteId: null,
150
+ noteVersion: null,
151
+ charStart: null,
152
+ charEnd: null,
153
+ importObjectId: null,
154
+ sourceIri: null,
155
+ ...fields,
156
+ };
157
+ }
158
+ /**
159
+ * Maps the subject's evidence rows that are not mapped yet to their fixture
160
+ * source (`"m01-s009"`, `"m01#note"`). The write service's creates return the
161
+ * row, not its evidence, so the ids are read back by subject.
162
+ */
163
+ async function mapEvidence(ctx, subjectKind, subjectId) {
164
+ const rows = await ctx.tx.kgEvidence.findMany({
165
+ where: { subjectKind, subjectId },
166
+ select: { id: true, segmentId: true, noteId: true },
167
+ });
168
+ for (const row of rows) {
169
+ if (ctx.idMap.evidenceSource.has(row.id))
170
+ continue;
171
+ const source = row.segmentId
172
+ ? ctx.segmentSource.get(row.segmentId)
173
+ : row.noteId
174
+ ? `${ctx.noteSource.get(row.noteId)}#note`
175
+ : undefined;
176
+ if (source)
177
+ ctx.idMap.evidenceSource.set(row.id, source);
178
+ }
179
+ }
180
+ /** Creates one accepted entity with its aliases and evidence; maps the evidence. */
181
+ async function createEntity(ctx, row, entity, evidence, mapIt = true) {
182
+ const created = await attempt(ctx, row, () => ctx.writer.createEntity(ctx.tx, {
183
+ ownerId: ctx.ownerId,
184
+ type: entity.type,
185
+ label: entity.label,
186
+ props: {},
187
+ reviewStatus: 'accepted',
188
+ labelSource: 'extraction',
189
+ aliases: [...entity.aliases].map((alias) => ({ alias, source: 'extraction' })),
190
+ evidence,
191
+ }, ctx.schema));
192
+ if (!created)
193
+ return null;
194
+ ctx.counts.entities += 1;
195
+ ctx.counts.evidence += evidence.length;
196
+ if (mapIt)
197
+ await mapEvidence(ctx, 'entity', created.id);
198
+ return created.id;
199
+ }
200
+ /** Adds evidence to an entity an earlier label already created; maps the new rows. */
201
+ async function addEntityEvidence(ctx, row, entityId, evidence) {
202
+ if (evidence.length === 0)
203
+ return;
204
+ const added = await attempt(ctx, row, () => ctx.writer.addEvidence(ctx.tx, ctx.ownerId, 'entity', entityId, evidence));
205
+ if (!added)
206
+ return;
207
+ ctx.counts.evidence += added.length;
208
+ await mapEvidence(ctx, 'entity', entityId);
209
+ }
210
+ /** One entity label of one fixture → its `kg_entities.id`, creating it (or its global row) exactly once. */
211
+ async function resolveEntity(ctx, entity, row) {
212
+ const evidence = toEvidence(ctx, entity.evidence);
213
+ const globalId = globalIdFor(ctx, entity);
214
+ if (globalId) {
215
+ const already = ctx.globalEntityId.get(globalId);
216
+ if (already) {
217
+ ctx.localEntityId.set(entity.key, already);
218
+ await addEntityEvidence(ctx, row, already, evidence);
219
+ return;
220
+ }
221
+ const catalogEntry = ctx.globalCatalog.get(globalId);
222
+ const id = await createEntity(ctx, row, {
223
+ type: catalogEntry?.type ?? entity.type,
224
+ label: catalogEntry?.label ?? entity.label,
225
+ aliases: catalogEntry ? catalogEntry.aliases : entity.aliases,
226
+ }, evidence);
227
+ if (!id)
228
+ return;
229
+ ctx.globalEntityId.set(globalId, id);
230
+ ctx.idMap.entities.set(globalId, id);
231
+ ctx.localEntityId.set(entity.key, id);
232
+ return;
233
+ }
234
+ const id = await createEntity(ctx, row, entity, evidence);
235
+ if (id)
236
+ ctx.localEntityId.set(entity.key, id);
237
+ }
238
+ /** A `knownEntities` id this fixture names without restating it as one of its labels: created once, from the catalog. */
239
+ async function resolveKnownEntityRef(ctx, globalId) {
240
+ const already = ctx.globalEntityId.get(globalId);
241
+ if (already)
242
+ return already;
243
+ const entry = ctx.globalCatalog.get(globalId);
244
+ if (!entry)
245
+ return null;
246
+ const noteId = ctx.idMap.notes.get(ctx.fixture.id);
247
+ if (!noteId) {
248
+ ctx.warnings.push(`${ctx.fixture.id}: reference-only entity "${globalId}" has no note to cite`);
249
+ return null;
250
+ }
251
+ // The filler citation (see the header): the referring meeting's note, the label as the quote.
252
+ const filler = [evidenceInput({ noteId, noteVersion: ctx.fixture.note.version, quote: entry.label.slice(0, 400) })];
253
+ const id = await createEntity(ctx, `reference-only entity ${globalId} (${entry.type})`, entry, filler, false);
254
+ if (!id)
255
+ return null;
256
+ ctx.globalEntityId.set(globalId, id);
257
+ ctx.idMap.entities.set(globalId, id);
258
+ return id;
259
+ }
260
+ /** A relation's `from`/`to`, an item's `subject`/`owner`/`counterparty`: a local key, or a global (`knownEntities`) id. */
261
+ async function resolveRef(ctx, ref) {
262
+ if (ref === null)
263
+ return null;
264
+ const local = ctx.localEntityId.get(ref);
265
+ if (local)
266
+ return local;
267
+ if (ctx.globalCatalog.has(ref))
268
+ return resolveKnownEntityRef(ctx, ref);
269
+ ctx.warnings.push(`${ctx.fixture.id}: unresolved entity reference "${ref}"`);
270
+ return null;
271
+ }
272
+ /** `YYYY-MM-DD` bounds → a range, or `null` when both are open. */
273
+ function toValidRange(validFrom, validTo) {
274
+ if (validFrom === null && validTo === null)
275
+ return null;
276
+ return { from: validFrom ? new Date(`${validFrom}T00:00:00Z`) : null, to: validTo ? new Date(`${validTo}T00:00:00Z`) : null };
277
+ }
278
+ async function seedTranscriptAndNote(ctx) {
279
+ const { tx, fixture } = ctx;
280
+ let transcriptId = null;
281
+ if (fixture.hasTranscript) {
282
+ const source = await tx.storageObject.create({
283
+ data: {
284
+ name: `${fixture.id}.m4a`,
285
+ size: BigInt(1),
286
+ mimeType: 'audio/mp4',
287
+ storageKey: `ask-eval/${(0, node_crypto_1.randomUUID)()}`,
288
+ orgId: ctx.orgId,
289
+ uploadedById: ctx.ownerId,
290
+ },
291
+ });
292
+ const transcript = await tx.transcript.create({
293
+ data: {
294
+ ownerId: ctx.ownerId,
295
+ orgId: ctx.orgId,
296
+ title: fixture.title,
297
+ sourceObjectId: source.id,
298
+ provider: 'assemblyai',
299
+ status: 'ready',
300
+ transcriptionStatus: 'completed',
301
+ playbackStatus: 'ready',
302
+ recordedAt: new Date(fixture.recordedAt),
303
+ },
304
+ });
305
+ transcriptId = transcript.id;
306
+ const speakerId = new Map();
307
+ for (const [i, speaker] of fixture.speakers.entries()) {
308
+ const row = await tx.transcriptSpeaker.create({
309
+ data: { transcriptId: transcript.id, label: speaker.label, displayName: speaker.displayName ?? speaker.label, colorIndex: i % 8 },
310
+ });
311
+ speakerId.set(speaker.id, row.id);
312
+ }
313
+ for (const segment of fixture.segments) {
314
+ const row = await tx.transcriptSegment.create({
315
+ data: {
316
+ transcriptId: transcript.id,
317
+ speakerId: speakerId.get(segment.speakerId) ?? [...speakerId.values()][0],
318
+ startMs: segment.startMs,
319
+ endMs: segment.endMs,
320
+ ordinal: segment.startMs,
321
+ text: segment.text,
322
+ words: [],
323
+ },
324
+ });
325
+ ctx.idMap.segments.set(segment.id, row.id);
326
+ ctx.segmentSource.set(row.id, segment.id);
327
+ ctx.segmentRows.set(segment.id, { id: row.id, transcriptId: transcript.id, startMs: segment.startMs, endMs: segment.endMs });
328
+ }
329
+ }
330
+ let sourceObjectId = null;
331
+ if (!fixture.hasTranscript) {
332
+ const doc = await tx.storageObject.create({
333
+ data: {
334
+ name: `${fixture.id}.md`,
335
+ size: BigInt(fixture.note.body.length),
336
+ mimeType: 'text/markdown',
337
+ storageKey: `ask-eval/${(0, node_crypto_1.randomUUID)()}`,
338
+ orgId: ctx.orgId,
339
+ uploadedById: ctx.ownerId,
340
+ },
341
+ });
342
+ sourceObjectId = doc.id;
343
+ }
344
+ const note = await tx.note.create({
345
+ data: {
346
+ ownerId: ctx.ownerId,
347
+ title: fixture.title,
348
+ body: fixture.note.body,
349
+ status: 'ready',
350
+ currentVersion: fixture.note.version,
351
+ sourceType: transcriptId ? 'transcript' : 'document',
352
+ sourceTranscriptId: transcriptId,
353
+ sourceObjectId,
354
+ createdAt: new Date(fixture.recordedAt),
355
+ },
356
+ });
357
+ await tx.noteVersion.create({ data: { noteId: note.id, version: fixture.note.version, kind: 'ai_generated', body: fixture.note.body } });
358
+ ctx.idMap.notes.set(fixture.id, note.id);
359
+ ctx.noteSource.set(note.id, fixture.id);
360
+ }
361
+ async function seedRelations(ctx) {
362
+ for (const [index, relation] of ctx.fixture.labels.relations.entries()) {
363
+ const row = `relation #${index + 1} (${relation.type})`;
364
+ const fromId = await resolveRef(ctx, relation.from);
365
+ const toId = await resolveRef(ctx, relation.to);
366
+ if (!fromId || !toId) {
367
+ ctx.refusals.push({ fixtureId: ctx.fixture.id, row, reason: `an endpoint did not resolve (${!fromId ? 'from' : 'to'})` });
368
+ continue;
369
+ }
370
+ const temporal = ctx.schema.relationType(relation.type)?.temporal ?? false;
371
+ const valid = toValidRange(relation.validFrom, relation.validTo);
372
+ // The label's precision travels with its range; a temporal relation with
373
+ // no range keeps `unknown` (the write service's "no range" spelling).
374
+ const validPrecision = valid ? relation.precision : temporal && relation.precision === 'unknown' ? 'unknown' : null;
375
+ const evidence = toEvidence(ctx, relation.evidence);
376
+ const created = await attempt(ctx, row, () => ctx.writer.createRelation(ctx.tx, { ownerId: ctx.ownerId, type: relation.type, fromId, toId, props: {}, valid, validPrecision, reviewStatus: 'accepted', evidence }, ctx.schema));
377
+ if (!created)
378
+ continue;
379
+ ctx.counts.relations += 1;
380
+ ctx.counts.evidence += evidence.length;
381
+ await mapEvidence(ctx, 'relation', created.id);
382
+ }
383
+ }
384
+ /** The ontology type that stores items of `kind`. */
385
+ function itemTypeKey(schema, kind) {
386
+ return schema.entityTypes.find((t) => t.storage === 'item' && t.itemKind === kind)?.key ?? null;
387
+ }
388
+ async function seedItems(ctx, meetingId) {
389
+ const countByKind = new Map();
390
+ for (const item of ctx.fixture.labels.items) {
391
+ const n = countByKind.get(item.kind) ?? 0;
392
+ countByKind.set(item.kind, n + 1);
393
+ const address = (0, index_js_1.itemAddress)(ctx.fixture.id, item.kind, n);
394
+ const row = `item ${address}`;
395
+ const typeKey = itemTypeKey(ctx.schema, item.kind);
396
+ if (!typeKey) {
397
+ ctx.refusals.push({ fixtureId: ctx.fixture.id, row, reason: `no item type stores '${item.kind}'` });
398
+ continue;
399
+ }
400
+ const subjectId = await resolveRef(ctx, item.subject);
401
+ const ownerPersonId = await resolveRef(ctx, item.owner);
402
+ const counterpartyId = await resolveRef(ctx, item.counterparty);
403
+ let supersedesId = null;
404
+ if (item.supersedesLabel) {
405
+ supersedesId = ctx.itemByAddress.get(item.supersedesLabel) ?? null;
406
+ if (!supersedesId)
407
+ ctx.warnings.push(`${ctx.fixture.id}: ${address} supersedes unknown item address "${item.supersedesLabel}"`);
408
+ }
409
+ // The meeting column only where the ontology has one for this item type (a
410
+ // Commitment or Decision has `meeting_id`; a Claim or PersonFact does not):
411
+ // the web app's seeder stamped it on every item, which the write service refuses.
412
+ const meetingAllowed = ctx.schema.relationTypes.some((r) => r.representation.kind === 'item_column' && r.representation.column === 'meeting_id' && r.from.includes(typeKey));
413
+ const evidence = toEvidence(ctx, item.evidence);
414
+ const created = await attempt(ctx, row, () => ctx.writer.createItem(ctx.tx, {
415
+ ownerId: ctx.ownerId,
416
+ kind: item.kind,
417
+ typeKey,
418
+ subjectId,
419
+ meetingId: meetingAllowed ? meetingId : null,
420
+ ownerPersonId,
421
+ counterpartyId,
422
+ title: item.title,
423
+ statement: item.statement,
424
+ status: item.status ?? (item.kind === 'commitment' ? 'open' : 'active'),
425
+ occurredAt: item.occurredAt ? new Date(item.occurredAt) : null,
426
+ dueAt: item.dueAt ? new Date(item.dueAt) : null,
427
+ // The web app's seeder default (`business`), kept so the two seed the same graph;
428
+ // the write service's own default for an unlabelled person fact is `personal`.
429
+ sensitivity: item.kind === 'person_fact' ? (item.sensitivity ?? 'business') : null,
430
+ reviewStatus: 'accepted',
431
+ supersedesId,
432
+ evidence,
433
+ }, ctx.schema));
434
+ if (!created)
435
+ continue;
436
+ ctx.counts.items += 1;
437
+ ctx.counts.evidence += evidence.length;
438
+ ctx.itemByAddress.set(address, created.id);
439
+ await mapEvidence(ctx, 'item', created.id);
440
+ }
441
+ }
442
+ /**
443
+ * Seeds the evaluation user's entire gold graph from EVERY fixture in
444
+ * `fixtures` (pass the full loaded set: `loadGoldenSet()`), one transaction per
445
+ * fixture, in fixture id order, through `GraphWriteService`.
446
+ *
447
+ * @param host - the running host (its client, user and organization, and the write service).
448
+ * @param fixtures - the golden meetings.
449
+ * @returns the id map the scorer needs, the refusals and the warnings.
450
+ * @stability experimental
451
+ */
452
+ async function seedGoldenGraph(host, fixtures) {
453
+ for (const f of fixtures)
454
+ index_js_1.goldenFixtureSchema.parse(f); // fail loudly on a malformed fixture before writing anything
455
+ const sorted = [...fixtures].sort((a, b) => a.id.localeCompare(b.id, 'en', { numeric: true }));
456
+ // The personal domain (Trip, Milestone, SPOUSE_OF, ...) is off by default; the
457
+ // golden set's personal meetings need it on, as `kvox-eval kg` extracts them
458
+ // (`fixtureDomains`). The Ask tools hide personal types whatever this says.
459
+ if (sorted.some((f) => (0, index_js_1.fixtureDomains)(f).includes('personal'))) {
460
+ await host.settings.patchUser(host.userId, 'graph', { domains: { work: true, personal: true } });
461
+ }
462
+ const writer = host.resolve(graph_1.GraphWriteService);
463
+ const schema = await host.resolve(graph_1.GraphOntologyService).effectiveSchemaFor(host.userId);
464
+ const idMap = { segments: new Map(), notes: new Map(), entities: new Map(), evidenceSource: new Map() };
465
+ const globalCatalog = buildGlobalCatalog(sorted);
466
+ const shared = {
467
+ writer,
468
+ schema,
469
+ ownerId: host.userId,
470
+ orgId: host.orgId,
471
+ idMap,
472
+ segmentSource: new Map(),
473
+ noteSource: new Map(),
474
+ globalEntityId: new Map(),
475
+ globalCatalog,
476
+ labelToGlobalId: buildLabelIndex(globalCatalog),
477
+ itemByAddress: new Map(),
478
+ refusals: [],
479
+ warnings: [],
480
+ counts: { entities: 0, relations: 0, items: 0, evidence: 0 },
481
+ };
482
+ for (const fixture of sorted) {
483
+ await host.prisma.$transaction(async (tx) => {
484
+ const ctx = { ...shared, tx, fixture, localEntityId: new Map(), segmentRows: new Map() };
485
+ await seedTranscriptAndNote(ctx);
486
+ for (const [index, entity] of fixture.labels.entities.entries()) {
487
+ await resolveEntity(ctx, entity, `entity #${index + 1} (${entity.type})`);
488
+ }
489
+ const meetingEntityId = ctx.localEntityId.get('meeting') ?? null;
490
+ await seedRelations(ctx);
491
+ await seedItems(ctx, meetingEntityId);
492
+ }, { timeout: 120_000, maxWait: 120_000 });
493
+ }
494
+ return { userId: host.userId, idMap, refusals: shared.refusals, warnings: shared.warnings, counts: shared.counts };
495
+ }
@@ -0,0 +1,3 @@
1
+ export { KG_COMMAND_USAGE, KG_EVAL_EXIT, runKgCommand } from './kg-command.js';
2
+ export type { EvalCommandIo, KgCommandOptions } from './kg-command.js';
3
+ export { KVOX_EVAL_USAGE, main } from './main.js';
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = exports.KVOX_EVAL_USAGE = exports.runKgCommand = exports.KG_EVAL_EXIT = exports.KG_COMMAND_USAGE = void 0;
4
+ // The cli slice's public surface (re-exported by the package root). The `bin`
5
+ // is `dist/cli/main.js`.
6
+ var kg_command_js_1 = require("./kg-command.js");
7
+ Object.defineProperty(exports, "KG_COMMAND_USAGE", { enumerable: true, get: function () { return kg_command_js_1.KG_COMMAND_USAGE; } });
8
+ Object.defineProperty(exports, "KG_EVAL_EXIT", { enumerable: true, get: function () { return kg_command_js_1.KG_EVAL_EXIT; } });
9
+ Object.defineProperty(exports, "runKgCommand", { enumerable: true, get: function () { return kg_command_js_1.runKgCommand; } });
10
+ var main_js_1 = require("./main.js");
11
+ Object.defineProperty(exports, "KVOX_EVAL_USAGE", { enumerable: true, get: function () { return main_js_1.KVOX_EVAL_USAGE; } });
12
+ Object.defineProperty(exports, "main", { enumerable: true, get: function () { return main_js_1.main; } });
@@ -0,0 +1,66 @@
1
+ import type { AiProviderAdapter } from '@marinoscar/platform-api/ai';
2
+ /**
3
+ * The exit codes of `kvox-eval kg` (and of the web app's `npm run kg:eval`).
4
+ *
5
+ * @stability experimental
6
+ */
7
+ export declare const KG_EVAL_EXIT: Readonly<{
8
+ readonly ok: 0;
9
+ readonly targetsFailed: 1;
10
+ readonly usage: 2;
11
+ readonly pathRefused: 3;
12
+ }>;
13
+ /**
14
+ * Where a command writes, and the environment it reads.
15
+ *
16
+ * @stability experimental
17
+ */
18
+ export interface EvalCommandIo {
19
+ /** One line to standard output. */
20
+ out: (line: string) => void;
21
+ /** One line to standard error. */
22
+ err: (line: string) => void;
23
+ /** The environment (`process.env`, or a test's own). */
24
+ env: Readonly<Record<string, string | undefined>>;
25
+ }
26
+ /**
27
+ * How a host runs the kg command under its own name.
28
+ *
29
+ * @stability experimental
30
+ */
31
+ export interface KgCommandOptions {
32
+ /** The prefix of every message (`kvox-eval kg`, `kg:eval`). */
33
+ name?: string;
34
+ /** The usage text printed with a usage error and for `--help`. */
35
+ usage?: string;
36
+ /**
37
+ * A git-ignored directory inside the repository that output of a run over the
38
+ * SYNTHETIC set may be written to, and the default `--out` root (the web app's
39
+ * `apps/api/.kg-eval/`). Default: none, and the output defaults to the system
40
+ * temporary directory.
41
+ */
42
+ syntheticRunsDir?: string | null;
43
+ /** The provider adapters by id. Default: the platform's. A test passes a fake. */
44
+ adapters?: Readonly<Record<string, AiProviderAdapter>>;
45
+ }
46
+ /**
47
+ * The usage text of `kvox-eval kg`.
48
+ *
49
+ * @stability experimental
50
+ */
51
+ export declare const KG_COMMAND_USAGE = "usage: kvox-eval kg (--predictions <dir|gold> | --runner extract|extract-resolve --model <id>)\n [--provider openai|openai-compatible|copilot] [--base-url <url>] [--out <dir>]\n [--only m01,m07] [--tag <tag>] [--json <file>] [--enforce] [--real-dir <dir>]\n reads a live runner's API key from KVOX_EVAL_OPENAI_API_KEY (never a flag)";
52
+ /**
53
+ * Runs `kvox-eval kg` with the given arguments (everything after `kg`).
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * const code = await runKgCommand(['--predictions', 'gold'], { out: console.log, err: console.error, env: process.env });
58
+ * ```
59
+ *
60
+ * @param argv - the arguments.
61
+ * @param io - output streams and environment.
62
+ * @param options - the host's name, usage text, scratch directory and adapters.
63
+ * @returns the exit code ({@link KG_EVAL_EXIT}).
64
+ * @stability experimental
65
+ */
66
+ export declare function runKgCommand(argv: string[], io: EvalCommandIo, options?: KgCommandOptions): Promise<number>;