dsh-data-domain-knowledge 0.1.0-alpha.13 → 0.1.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -56
- package/cordis.patch.yml +36 -129
- package/eval/benchmark-extraction-format.mjs +20 -0
- package/lib/client.js +3262 -4142
- package/lib/doc-page.js +1 -1
- package/lib/pages.js +1147 -364
- package/lib/types/axis-registry.d.ts +8 -1
- package/lib/types/axis-registry.d.ts.map +1 -1
- package/lib/types/axis-registry.js +62 -7
- package/lib/types/axis-registry.js.map +1 -1
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +3 -0
- package/lib/types/convert/host.js.map +1 -1
- package/lib/types/convert/ingest.d.ts +9 -0
- package/lib/types/convert/ingest.d.ts.map +1 -1
- package/lib/types/convert/ingest.js +68 -9
- package/lib/types/convert/ingest.js.map +1 -1
- package/lib/types/diagnosis/cards.d.ts +7 -3
- package/lib/types/diagnosis/cards.d.ts.map +1 -1
- package/lib/types/diagnosis/cards.js +13 -8
- package/lib/types/diagnosis/cards.js.map +1 -1
- package/lib/types/diagnosis/run.d.ts +4 -13
- package/lib/types/diagnosis/run.d.ts.map +1 -1
- package/lib/types/diagnosis/run.js +26 -16
- package/lib/types/diagnosis/run.js.map +1 -1
- package/lib/types/diagnosis/schema.js +2 -2
- package/lib/types/diagnosis/schema.js.map +1 -1
- package/lib/types/diagnosis/seed-source.d.ts +1 -1
- package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
- package/lib/types/diagnosis/seed-source.js +5 -4
- package/lib/types/diagnosis/seed-source.js.map +1 -1
- package/lib/types/diagnosis/seed.d.ts +4 -4
- package/lib/types/diagnosis/seed.d.ts.map +1 -1
- package/lib/types/diagnosis/seed.js +6 -8
- package/lib/types/diagnosis/seed.js.map +1 -1
- package/lib/types/diagnosis/store.d.ts +18 -17
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +94 -66
- package/lib/types/diagnosis/store.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +10 -16
- package/lib/types/diagnosis/types.d.ts.map +1 -1
- package/lib/types/diagnosis/types.js.map +1 -1
- package/lib/types/environment/dsh-python.d.ts +77 -0
- package/lib/types/environment/dsh-python.d.ts.map +1 -0
- package/lib/types/environment/dsh-python.js +139 -0
- package/lib/types/environment/dsh-python.js.map +1 -0
- package/lib/types/environment/install.d.ts +9 -1
- package/lib/types/environment/install.d.ts.map +1 -1
- package/lib/types/environment/install.js +149 -24
- package/lib/types/environment/install.js.map +1 -1
- package/lib/types/environment/model-catalog.d.ts +34 -2
- package/lib/types/environment/model-catalog.d.ts.map +1 -1
- package/lib/types/environment/model-catalog.js +40 -2
- package/lib/types/environment/model-catalog.js.map +1 -1
- package/lib/types/environment/service.d.ts +13 -3
- package/lib/types/environment/service.d.ts.map +1 -1
- package/lib/types/environment/service.js +127 -23
- package/lib/types/environment/service.js.map +1 -1
- package/lib/types/environment/types.d.ts +9 -0
- package/lib/types/environment/types.d.ts.map +1 -1
- package/lib/types/events.d.ts +2 -0
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/events.js +13 -0
- package/lib/types/events.js.map +1 -1
- package/lib/types/extract-contract.d.ts +1 -1
- package/lib/types/extract-contract.d.ts.map +1 -1
- package/lib/types/extract-contract.js +34 -31
- package/lib/types/extract-contract.js.map +1 -1
- package/lib/types/extract-model.d.ts +2 -0
- package/lib/types/extract-model.d.ts.map +1 -1
- package/lib/types/extract-model.js.map +1 -1
- package/lib/types/extract-table-contract.d.ts +3 -0
- package/lib/types/extract-table-contract.d.ts.map +1 -0
- package/lib/types/extract-table-contract.js +30 -0
- package/lib/types/extract-table-contract.js.map +1 -0
- package/lib/types/extract-table.d.ts +8 -0
- package/lib/types/extract-table.d.ts.map +1 -0
- package/lib/types/extract-table.js +151 -0
- package/lib/types/extract-table.js.map +1 -0
- package/lib/types/extraction-runs.d.ts +30 -6
- package/lib/types/extraction-runs.d.ts.map +1 -1
- package/lib/types/extraction-runs.js +69 -8
- package/lib/types/extraction-runs.js.map +1 -1
- package/lib/types/fs-atomic.d.ts +0 -7
- package/lib/types/fs-atomic.d.ts.map +1 -1
- package/lib/types/fs-atomic.js +17 -2
- package/lib/types/fs-atomic.js.map +1 -1
- package/lib/types/images.d.ts +1 -1
- package/lib/types/images.d.ts.map +1 -1
- package/lib/types/images.js +2 -2
- package/lib/types/images.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +232 -50
- package/lib/types/index.js.map +1 -1
- package/lib/types/intent/scope.d.ts.map +1 -1
- package/lib/types/intent/scope.js +1 -0
- package/lib/types/intent/scope.js.map +1 -1
- package/lib/types/intent/tools.d.ts +19 -0
- package/lib/types/intent/tools.d.ts.map +1 -1
- package/lib/types/intent/tools.js +37 -1
- package/lib/types/intent/tools.js.map +1 -1
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +5 -16
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/knowledge.d.ts +3 -0
- package/lib/types/knowledge.d.ts.map +1 -1
- package/lib/types/knowledge.js +21 -5
- package/lib/types/knowledge.js.map +1 -1
- package/lib/types/materialize-snapshot.d.ts +49 -0
- package/lib/types/materialize-snapshot.d.ts.map +1 -0
- package/lib/types/materialize-snapshot.js +2 -0
- package/lib/types/materialize-snapshot.js.map +1 -0
- package/lib/types/model-text.d.ts +1 -1
- package/lib/types/model-text.d.ts.map +1 -1
- package/lib/types/model-text.js +5 -4
- package/lib/types/model-text.js.map +1 -1
- package/lib/types/oplog.d.ts.map +1 -1
- package/lib/types/oplog.js +7 -0
- package/lib/types/oplog.js.map +1 -1
- package/lib/types/pipeline/engine.d.ts +60 -0
- package/lib/types/pipeline/engine.d.ts.map +1 -0
- package/lib/types/pipeline/engine.js +338 -0
- package/lib/types/pipeline/engine.js.map +1 -0
- package/lib/types/pipeline/store.d.ts +73 -0
- package/lib/types/pipeline/store.d.ts.map +1 -0
- package/lib/types/pipeline/store.js +143 -0
- package/lib/types/pipeline/store.js.map +1 -0
- package/lib/types/pipeline/types.d.ts +101 -0
- package/lib/types/pipeline/types.d.ts.map +1 -0
- package/lib/types/pipeline/types.js +5 -0
- package/lib/types/pipeline/types.js.map +1 -0
- package/lib/types/present.d.ts +2 -0
- package/lib/types/present.d.ts.map +1 -1
- package/lib/types/present.js +9 -4
- package/lib/types/present.js.map +1 -1
- package/lib/types/retention.d.ts +39 -0
- package/lib/types/retention.d.ts.map +1 -0
- package/lib/types/retention.js +42 -0
- package/lib/types/retention.js.map +1 -0
- package/lib/types/runtime.d.ts +115 -5
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +862 -132
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/source-evidence.d.ts +9 -1
- package/lib/types/source-evidence.d.ts.map +1 -1
- package/lib/types/source-evidence.js +13 -5
- package/lib/types/source-evidence.js.map +1 -1
- package/lib/types/store/axis-references.js +3 -3
- package/lib/types/store/axis-references.js.map +1 -1
- package/lib/types/store/derived.d.ts +1 -1
- package/lib/types/store/derived.d.ts.map +1 -1
- package/lib/types/store/derived.js +41 -7
- package/lib/types/store/derived.js.map +1 -1
- package/lib/types/store/knowledge-db.d.ts +18 -0
- package/lib/types/store/knowledge-db.d.ts.map +1 -1
- package/lib/types/store/knowledge-db.js +111 -6
- package/lib/types/store/knowledge-db.js.map +1 -1
- package/lib/types/store/migrate.js +1 -1
- package/lib/types/store/migrate.js.map +1 -1
- package/lib/types/trajectory-types.d.ts +172 -0
- package/lib/types/trajectory-types.d.ts.map +1 -0
- package/lib/types/trajectory-types.js +36 -0
- package/lib/types/trajectory-types.js.map +1 -0
- package/lib/types/types.d.ts +3 -4
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-finalize.d.ts +16 -0
- package/lib/types/wiki-finalize.d.ts.map +1 -1
- package/lib/types/wiki-finalize.js +26 -0
- package/lib/types/wiki-finalize.js.map +1 -1
- package/lib/types/wiki-materialize.d.ts.map +1 -1
- package/lib/types/wiki-materialize.js +3 -15
- package/lib/types/wiki-materialize.js.map +1 -1
- package/lib/types/wiki-trial-types.d.ts +3 -0
- package/lib/types/wiki-trial-types.d.ts.map +1 -1
- package/lib/types/wiki-trial.d.ts +2 -0
- package/lib/types/wiki-trial.d.ts.map +1 -1
- package/lib/types/wiki-trial.js +31 -7
- package/lib/types/wiki-trial.js.map +1 -1
- package/package.json +6 -3
- package/preset/tita-data-agent/agent.cordis.yml +36 -129
- package/src/axis-registry.ts +57 -9
- package/src/client/AttributionEventGuide.tsx +10 -9
- package/src/client/CompileDialogs.tsx +10 -2
- package/src/client/EnvironmentSetup.tsx +76 -16
- package/src/client/ExpansionGraph.tsx +13 -7
- package/src/client/ExtractionTasksPage.tsx +543 -156
- package/src/client/ImportFilesDialog.tsx +2 -13
- package/src/client/IntentTab.tsx +3 -1
- package/src/client/KnowledgeBaseTab.tsx +139 -122
- package/src/client/KnowledgeLayerSection.tsx +19 -2
- package/src/client/KnowledgePanel.tsx +17 -3
- package/src/client/ListPager.tsx +15 -5
- package/src/client/MaterializeTrajectoryView.tsx +124 -0
- package/src/client/NodeSettings.tsx +90 -25
- package/src/client/NormalizeTrajectoryView.tsx +136 -0
- package/src/client/PipelineNotice.tsx +26 -0
- package/src/client/RecallPanel.tsx +55 -25
- package/src/client/TabGuide.tsx +4 -4
- package/src/client/TrialReportView.tsx +61 -16
- package/src/client/TypeDefinitionDrawer.tsx +33 -9
- package/src/client/WikiCardPreview.tsx +43 -10
- package/src/client/WikiFlowPanel.tsx +4 -4
- package/src/client/architecture.tsx +1 -1
- package/src/client/attribution/AttributionTab.tsx +37 -36
- package/src/client/attribution/CaseView.tsx +3 -1
- package/src/client/attribution/EventView.tsx +14 -4
- package/src/client/attribution/RunView.tsx +2 -2
- package/src/client/attribution/ViewSwitch.tsx +20 -5
- package/src/client/attribution/graph-options.ts +38 -31
- package/src/client/attribution/model.ts +16 -9
- package/src/client/graph/palette.ts +165 -0
- package/src/client/pages.css +224 -28
- package/src/client/pages.tsx +33 -11
- package/src/client/styles.css +61 -6
- package/src/client/wiki-trial.ts +30 -1
- package/src/convert/host.ts +3 -0
- package/src/convert/ingest.ts +73 -9
- package/src/diagnosis/cards.ts +13 -8
- package/src/diagnosis/run.ts +27 -17
- package/src/diagnosis/schema.ts +2 -2
- package/src/diagnosis/seed-source.ts +5 -4
- package/src/diagnosis/seed.ts +6 -8
- package/src/diagnosis/store.ts +99 -69
- package/src/diagnosis/types.ts +10 -16
- package/src/environment/dsh-python.ts +156 -0
- package/src/environment/install.ts +134 -19
- package/src/environment/model-catalog.ts +45 -3
- package/src/environment/service.ts +111 -23
- package/src/environment/types.ts +6 -0
- package/src/events.ts +12 -0
- package/src/extract-contract.ts +6 -3
- package/src/extract-model.ts +2 -0
- package/src/extract-table-contract.ts +29 -0
- package/src/extract-table.ts +126 -0
- package/src/extraction-runs.ts +70 -11
- package/src/fs-atomic.ts +19 -2
- package/src/images.ts +2 -2
- package/src/index.ts +207 -72
- package/src/intent/scope.ts +1 -0
- package/src/intent/tools.ts +38 -1
- package/src/knowledge-bases.ts +4 -12
- package/src/knowledge.ts +24 -4
- package/src/materialize-snapshot.ts +46 -0
- package/src/model-text.ts +5 -3
- package/src/oplog.ts +7 -0
- package/src/pipeline/engine.ts +312 -0
- package/src/pipeline/store.ts +147 -0
- package/src/pipeline/types.ts +92 -0
- package/src/present.ts +10 -4
- package/src/retention.ts +76 -0
- package/src/runtime.ts +853 -131
- package/src/source-evidence.ts +12 -5
- package/src/store/axis-references.ts +3 -3
- package/src/store/derived.ts +40 -7
- package/src/store/knowledge-db.ts +111 -5
- package/src/store/migrate.ts +1 -1
- package/src/trajectory-types.ts +142 -0
- package/src/types.ts +3 -4
- package/src/wiki-finalize.ts +28 -0
- package/src/wiki-materialize.ts +3 -14
- package/src/wiki-trial-types.ts +3 -0
- package/src/wiki-trial.ts +26 -7
- package/src/client/convert/ConvertPanel.tsx +0 -583
- package/src/client/convert/styles.css +0 -138
- package/src/client/convert/styles.ts +0 -49
package/src/source-evidence.ts
CHANGED
|
@@ -2,17 +2,24 @@ import type { Knowledge, KnowledgeSource } from './knowledge.js'
|
|
|
2
2
|
import type { KnowledgeDb } from './store/knowledge-db.js'
|
|
3
3
|
|
|
4
4
|
/** Checking an evidence anchor does not establish whether a claim is true. */
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* `bodies` 是可选的**来源正文预读**(documentId → 当前版本与正文)。批量路径(物化快照)
|
|
7
|
+
* 一次读完全部来源再传进来,于是每条知识不再各自 join 一次 blob、各做一次整份文档的
|
|
8
|
+
* flatten 与子串匹配 —— 那是 O(知识条数 × 文档大小)。不传时退回按需读取(单条判定)。
|
|
9
|
+
*/
|
|
10
|
+
export function assessKnowledgeEvidence(knowledge: Knowledge, db: KnowledgeDb, bodies?: ReadonlyMap<string, { version: number; content: string }>): Knowledge {
|
|
6
11
|
return {
|
|
7
12
|
...knowledge,
|
|
8
|
-
sources: knowledge.sources.map(source => assessSource(source, db)),
|
|
13
|
+
sources: knowledge.sources.map(source => assessSource(source, db, bodies)),
|
|
9
14
|
}
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
function assessSource(source: KnowledgeSource, db: KnowledgeDb): KnowledgeSource {
|
|
17
|
+
function assessSource(source: KnowledgeSource, db: KnowledgeDb, bodies?: ReadonlyMap<string, { version: number; content: string }>): KnowledgeSource {
|
|
18
|
+
if (source.withdrawn) return { ...source, evidenceStatus: 'historical', quoteInCurrentVersion: false }
|
|
13
19
|
if (!source.documentId) return { ...source, evidenceStatus: 'human' }
|
|
14
|
-
const head = db.source(source.documentId)
|
|
20
|
+
const head = bodies?.get(source.documentId) ?? db.source(source.documentId)
|
|
15
21
|
if (!head) return { ...source, evidenceStatus: 'missing', quoteInCurrentVersion: false }
|
|
22
|
+
const content = 'record' in head ? head.record.content : head.content
|
|
16
23
|
const flatten = (text: string): string => text.replace(/\s+/g, '')
|
|
17
24
|
const quote = flatten(source.quote)
|
|
18
25
|
return {
|
|
@@ -20,7 +27,7 @@ function assessSource(source: KnowledgeSource, db: KnowledgeDb): KnowledgeSource
|
|
|
20
27
|
currentVersion: head.version,
|
|
21
28
|
evidenceStatus: source.toVersion === undefined ? 'unversioned'
|
|
22
29
|
: source.toVersion === head.version ? 'current' : 'historical',
|
|
23
|
-
quoteInCurrentVersion: quote !== '' && flatten(
|
|
30
|
+
quoteInCurrentVersion: quote !== '' && flatten(content).includes(quote),
|
|
24
31
|
}
|
|
25
32
|
}
|
|
26
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
2
|
import type { AxisKind } from '../axis-registry.js'
|
|
3
|
-
import { compositeEventIdentity, eventEntityFromRecords, type EventEntity } from '../events.js'
|
|
3
|
+
import { compositeEventIdentity, eventEntityFromRecords, stripEventReferenceSection, type EventEntity } from '../events.js'
|
|
4
4
|
import { cardCoordinateKey, type CardCoordinate } from '../types.js'
|
|
5
5
|
import type { KnowledgeDb, CardKnowledgeRow } from './knowledge-db.js'
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ export function redirectAxisReferences(db: KnowledgeDb, kind: AxisKind, fromId:
|
|
|
28
28
|
const eventRefs = [...new Set([...db.cardEventIds(base.card.entityId), ...db.cardEventIds(row.card.entityId)])]
|
|
29
29
|
const card = {
|
|
30
30
|
...base.card, coordinate: next,
|
|
31
|
-
content: merged ? `${base.card.content}\n\n${row.card.content}` : row.card.content,
|
|
31
|
+
content: stripEventReferenceSection(merged ? `${base.card.content}\n\n${row.card.content}` : row.card.content),
|
|
32
32
|
sourceRefs: [...new Set([...base.card.sourceRefs, ...row.card.sourceRefs])],
|
|
33
33
|
annotations: [...new Map([...(base.card.annotations ?? []), ...(row.card.annotations ?? [])].map(annotation => [annotation.id, annotation])).values()],
|
|
34
34
|
metadata: { ...base.card.metadata, layout: undefined, citations: undefined,
|
|
@@ -84,7 +84,7 @@ export function redirectAxisReferences(db: KnowledgeDb, kind: AxisKind, fromId:
|
|
|
84
84
|
}
|
|
85
85
|
if (redirects.size) {
|
|
86
86
|
for (const row of db.cards()) {
|
|
87
|
-
let content = row.card.content
|
|
87
|
+
let content = stripEventReferenceSection(row.card.content)
|
|
88
88
|
for (const [from, into] of redirects) content = content.replaceAll(from, into)
|
|
89
89
|
if (content === row.card.content && !affectedCards.has(row.card.entityId)) continue
|
|
90
90
|
const metadata = { ...row.card.metadata, event_refs: JSON.stringify(db.cardEventIds(row.card.entityId)) }
|
package/src/store/derived.ts
CHANGED
|
@@ -158,12 +158,29 @@ export class DerivedDb {
|
|
|
158
158
|
try {
|
|
159
159
|
const owner = readPragma(db, 'application_id')
|
|
160
160
|
const version = readPragma(db, 'user_version')
|
|
161
|
-
|
|
161
|
+
/**
|
|
162
|
+
* 属主标记为 0 = 这份文件从来没有被我们的初始化**写完过**。两种来源都要按「无主」处理:
|
|
163
|
+
*
|
|
164
|
+
* 1. 全新/空的文件(一张表都没有);
|
|
165
|
+
* 2. `schemaSql()` 已经建好表、`PRAGMA application_id` 还没写就被杀掉的那一次
|
|
166
|
+
* (崩溃、kill、磁盘满)。第二种原来会落到下面的「属主不对」分支,返回
|
|
167
|
+
* `NOT_A_DERIVED_DB` —— 而 `derivedHandle()` 会把这个结论缓存住,于是编译与召回
|
|
168
|
+
* **永久**失败,尽管这个文件按定义是可重建的。
|
|
169
|
+
*
|
|
170
|
+
* 但不能因此误伤别人的库:只有「文件里所有表都是我们自己建的」才认领重建,
|
|
171
|
+
* 出现陌生表就仍然拒绝打开(认错库比打不开糟)。
|
|
172
|
+
*/
|
|
173
|
+
const tables = userTables(db)
|
|
174
|
+
const foreign = tables.filter(name => !['artifact', 'vector_index', 'fts'].includes(name))
|
|
175
|
+
if (owner === 0 && foreign.length === 0) {
|
|
176
|
+
db.exec('DROP TABLE IF EXISTS fts')
|
|
177
|
+
db.exec('DROP TABLE IF EXISTS artifact')
|
|
178
|
+
db.exec('DROP TABLE IF EXISTS vector_index')
|
|
162
179
|
db.exec('PRAGMA journal_mode = WAL')
|
|
163
180
|
db.exec(schemaSql())
|
|
164
181
|
db.exec(`PRAGMA application_id = ${DERIVED_DB_APPLICATION_ID}`)
|
|
165
182
|
db.exec(`PRAGMA user_version = ${DERIVED_DB_SCHEMA}`)
|
|
166
|
-
return { ok: true, db: new DerivedDb(db, file), rebuilt:
|
|
183
|
+
return { ok: true, db: new DerivedDb(db, file), rebuilt: tables.length > 0 }
|
|
167
184
|
}
|
|
168
185
|
if (owner !== DERIVED_DB_APPLICATION_ID) {
|
|
169
186
|
db.close()
|
|
@@ -302,11 +319,19 @@ export class DerivedDb {
|
|
|
302
319
|
* 查询语法错误或索引损坏时**返回空数组而不抛**:关键词路是三条召回路线之一,
|
|
303
320
|
* 它挂了不该把整次召回带走 —— 调用方在诊断里如实说明这一路没结果。
|
|
304
321
|
*/
|
|
305
|
-
searchFts(query: string, limit: number): FtsHit[] {
|
|
322
|
+
searchFts(query: string, limit: number, eligible?: ReadonlySet<string>): FtsHit[] {
|
|
306
323
|
const expression = matchExpression(query)
|
|
307
324
|
if (expression === null || !this.hasFts()) return []
|
|
308
325
|
try {
|
|
309
|
-
|
|
326
|
+
/*
|
|
327
|
+
* `eligible` 必须进 SQL。先 LIMIT 再在 JS 里过滤,会让已删除/停用卡片的死
|
|
328
|
+
* unitId 占满候选窗口(索引只在编译时重建,删除不会立刻反映到 fts 表)。
|
|
329
|
+
* 用 `json_each` 传一个 JSON 数组而不是拼参数列表:一个绑定参数,不受
|
|
330
|
+
* SQLITE_MAX_VARIABLE_NUMBER 限制。
|
|
331
|
+
*/
|
|
332
|
+
const rows = (eligible === undefined
|
|
333
|
+
? this.raw('SELECT unit_id, bm25(fts) AS rank FROM fts WHERE fts MATCH ? ORDER BY rank LIMIT ?').all(expression, limit)
|
|
334
|
+
: this.raw('SELECT unit_id, bm25(fts) AS rank FROM fts WHERE fts MATCH ? AND unit_id IN (SELECT value FROM json_each(?)) ORDER BY rank LIMIT ?').all(expression, JSON.stringify([...eligible]), limit)) as Array<{ unit_id?: unknown; rank?: unknown }>
|
|
310
335
|
const parsed = rows
|
|
311
336
|
.map(row => ({ unitId: String(row.unit_id ?? ''), raw: Number(row.rank ?? 0) }))
|
|
312
337
|
.filter(row => row.unitId !== '' && Number.isFinite(row.raw))
|
|
@@ -316,6 +341,14 @@ export class DerivedDb {
|
|
|
316
341
|
const scale = best > 0 ? best : 1
|
|
317
342
|
return parsed.map(row => ({ ...row, score: Math.max(0, Math.min(1, -row.raw / scale)) }))
|
|
318
343
|
} catch {
|
|
344
|
+
/**
|
|
345
|
+
* `json_each` 不可用(SQLite 没编 JSON)或索引损坏时,**不能直接返回空**。
|
|
346
|
+
*
|
|
347
|
+
* 空数组会被上游读成「这一路没命中」,而实际是「这一路坏了」—— 正是这次要消灭的
|
|
348
|
+
* 那种静默降级。退回不带资格过滤的两参数查询:结果仍然正确(调用方还会过滤一遍),
|
|
349
|
+
* 只是候选窗口可能被已删除的条目占掉。真的连那条也失败,才认「没有结果」。
|
|
350
|
+
*/
|
|
351
|
+
if (eligible !== undefined) return this.searchFts(query, limit)
|
|
319
352
|
return []
|
|
320
353
|
}
|
|
321
354
|
}
|
|
@@ -335,7 +368,7 @@ function readPragma(db: SqliteDatabase, name: string): number {
|
|
|
335
368
|
return typeof first === 'number' ? first : Number(first ?? 0)
|
|
336
369
|
}
|
|
337
370
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
return
|
|
371
|
+
/** 文件里现有的表名。用来判断「这份文件是不是我们建的」。 */
|
|
372
|
+
function userTables(db: SqliteDatabase): string[] {
|
|
373
|
+
return (db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").all() as Array<{ name?: unknown }>).map(row => String(row.name ?? ''))
|
|
341
374
|
}
|
|
@@ -31,6 +31,7 @@ import { createHash } from 'node:crypto'
|
|
|
31
31
|
import { mkdir } from 'node:fs/promises'
|
|
32
32
|
import { dirname } from 'node:path'
|
|
33
33
|
import type { SqliteDatabase, SqliteStatement } from '../fts.js'
|
|
34
|
+
import { applyRetention } from '../retention.js'
|
|
34
35
|
import { KNOWLEDGE_DB_FILE } from '../layout.js'
|
|
35
36
|
import { cardCoordinateKey, legacyCardStorageKey, type CardCoordinate, type KnowledgeCard, type SourceRaw } from '../types.js'
|
|
36
37
|
import type { EventEntity } from '../events.js'
|
|
@@ -445,15 +446,25 @@ function rebuildCardTableForSchemaMigration(db: SqliteDatabase): void {
|
|
|
445
446
|
head_version, created_at, updated_at, object_id, action_id, scene_id)
|
|
446
447
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
447
448
|
`)
|
|
449
|
+
/** 已经用掉的落位键。同坐标多行时后面那些要退回身份键(见下面的说明)。 */
|
|
450
|
+
const used = new Set<string>()
|
|
448
451
|
for (const row of rows) {
|
|
449
452
|
const entityId = String(row.entity_id ?? '')
|
|
450
453
|
const objectId = String(row.object_id ?? '')
|
|
451
454
|
const actionId = String(row.action_id ?? '')
|
|
452
455
|
const sceneId = String(row.scene_id ?? '')
|
|
453
456
|
const coordinate = coordinateOfRow({ object_id: objectId, action_id: actionId, scene_id: sceneId })
|
|
454
|
-
|
|
457
|
+
let key = coordinate === undefined
|
|
455
458
|
? legacyCardStorageKey(entityId)
|
|
456
459
|
: cardCoordinateKey(coordinate)
|
|
460
|
+
/**
|
|
461
|
+
* 同一坐标在旧表里可能有多行(那时还没有坐标唯一约束)。`coordinate_key` 是主键,
|
|
462
|
+
* 一旦冲突整次迁移就回滚 —— 而迁移在打开库时跑,于是**每次启动都打不开这个库**,
|
|
463
|
+
* 也没有修复路径。落在后面的那一行退回按 `entityId` 生成的身份键:两张卡都留住,
|
|
464
|
+
* 坐标冲突交给既有的审计路径去报。`entity_id` 在旧表里是唯一的,所以这个键不会重。
|
|
465
|
+
*/
|
|
466
|
+
if (used.has(key)) key = legacyCardStorageKey(entityId)
|
|
467
|
+
used.add(key)
|
|
457
468
|
insert.run(
|
|
458
469
|
key,
|
|
459
470
|
entityId,
|
|
@@ -615,6 +626,8 @@ interface ColumnInfo {
|
|
|
615
626
|
name: string
|
|
616
627
|
notNull: boolean
|
|
617
628
|
hasDefault: boolean
|
|
629
|
+
/** 主键里的序号(1 起);0 = 不是主键的一部分。判「是不是同一行」要用它。 */
|
|
630
|
+
pk: number
|
|
618
631
|
}
|
|
619
632
|
|
|
620
633
|
/**
|
|
@@ -851,6 +864,8 @@ export class KnowledgeTx {
|
|
|
851
864
|
*/
|
|
852
865
|
export class KnowledgeDb {
|
|
853
866
|
private transactionSequence = 0
|
|
867
|
+
/** 距离上一次保留裁剪又提交了几次写事务。 */
|
|
868
|
+
private writesSincePrune = 0
|
|
854
869
|
private constructor(private readonly db: SqliteDatabase, readonly file: string) {}
|
|
855
870
|
|
|
856
871
|
/**
|
|
@@ -935,6 +950,8 @@ export class KnowledgeDb {
|
|
|
935
950
|
}
|
|
936
951
|
|
|
937
952
|
close(): void {
|
|
953
|
+
// 关库前收一次尾:否则「最后一次裁剪之后新增的行」会一直留到下次打开。
|
|
954
|
+
this.pruneRetention()
|
|
938
955
|
try { this.db.close() } catch { /* already closed */ }
|
|
939
956
|
}
|
|
940
957
|
|
|
@@ -971,6 +988,17 @@ export class KnowledgeDb {
|
|
|
971
988
|
const result = fn(tx)
|
|
972
989
|
this.raw('UPDATE tree SET manifest = ? WHERE op_id = ?').run(tx.manifestJson(), entry.opId)
|
|
973
990
|
this.db.exec(`RELEASE ${savepoint}`)
|
|
991
|
+
/**
|
|
992
|
+
* 裁剪**每 50 次写入**做一次,不是每次。
|
|
993
|
+
*
|
|
994
|
+
* 每次都做的话,一次卡片写入要额外跑一遍 `ROW_NUMBER()` 按实体分区的窗口(水位以上
|
|
995
|
+
* 收敛到约 20 版/实体,5000 个实体就是十万行排序)外加一次 blob 引用集比对 ——
|
|
996
|
+
* 那是把维护成本摊到每一次交互上。每 50 次一次,界面上感觉不到,库也照样有界。
|
|
997
|
+
*/
|
|
998
|
+
if (++this.writesSincePrune >= 50) {
|
|
999
|
+
this.writesSincePrune = 0
|
|
1000
|
+
this.pruneRetention()
|
|
1001
|
+
}
|
|
974
1002
|
return result
|
|
975
1003
|
} catch (cause) {
|
|
976
1004
|
try { this.db.exec(`ROLLBACK TO ${savepoint}; RELEASE ${savepoint}`) } catch { /* rollback of a dead tx */ }
|
|
@@ -978,6 +1006,47 @@ export class KnowledgeDb {
|
|
|
978
1006
|
}
|
|
979
1007
|
}
|
|
980
1008
|
|
|
1009
|
+
/**
|
|
1010
|
+
* 保留上限(0070)。
|
|
1011
|
+
*
|
|
1012
|
+
* 文档里写了保留策略,代码里一条 `DELETE` 都没有。这里补上**新数据**的上限,
|
|
1013
|
+
* 且只裁「水位以上」的行 —— 升级那一刻已有的历史一行不动(见 `retention.ts`)。
|
|
1014
|
+
*
|
|
1015
|
+
* - `revision`:机器版本(`auto`)每个实体留最近 20 版;**命名版本永不裁**
|
|
1016
|
+
* (`named` 是人对内容做的决定,保留策略里它是永久的)。
|
|
1017
|
+
* - `pipeline_extractions`:抽取检查点同时是复用缓存,每份来源留最近 5 份;
|
|
1018
|
+
* `pipeline_sources` 指着的那两份(当前抽取 / 当前归一)绝不删 ——
|
|
1019
|
+
* 删了会让那份来源显示成「还没抽取」。
|
|
1020
|
+
* - `blob`:水位以上、已经没有任何 revision 引用的才删。水位以下的 blob 一律留着,
|
|
1021
|
+
* 因为老版本可能还在引用它们。
|
|
1022
|
+
* - `tree`:操作台账,与处理日志同一量级。
|
|
1023
|
+
*/
|
|
1024
|
+
private pruneRetention(): void {
|
|
1025
|
+
/*
|
|
1026
|
+
* 每一张都要先问在不在:`pipeline_extractions` 与 `pipeline_sources` 是**管线模块**
|
|
1027
|
+
* 懒建的(`PipelineStore` 的构造函数),而 knowledge.db 可能被别的模块先打开、
|
|
1028
|
+
* 也可能根本没跑过管线。少了这一步,这里会以「no such table」把**每一次写入**都打挂。
|
|
1029
|
+
*
|
|
1030
|
+
* 整个方法包在 try 里还有一层理由:裁剪是**尽力而为**的维护动作,它绝不能成为
|
|
1031
|
+
* 一次已提交写入的失败原因。真出错时宁可不裁(库继续变大),也不让用户写不进去。
|
|
1032
|
+
*/
|
|
1033
|
+
try {
|
|
1034
|
+
if (tableExists(this.db, 'revision')) {
|
|
1035
|
+
applyRetention(this.db, 'revision', { table: 'revision', key: 'rowid', keep: 20, where: "kind = 'auto'", groupBy: "entity_kind || '/' || entity_id" })
|
|
1036
|
+
}
|
|
1037
|
+
if (tableExists(this.db, 'pipeline_extractions') && tableExists(this.db, 'pipeline_sources')) {
|
|
1038
|
+
applyRetention(this.db, 'pipeline_extractions', {
|
|
1039
|
+
table: 'pipeline_extractions', key: 'rowid', keep: 5, groupBy: 'source_id',
|
|
1040
|
+
where: "id NOT IN (SELECT extracted_id FROM pipeline_sources WHERE extracted_id IS NOT NULL) AND id NOT IN (SELECT normalized_id FROM pipeline_sources WHERE normalized_id IS NOT NULL)",
|
|
1041
|
+
})
|
|
1042
|
+
}
|
|
1043
|
+
if (tableExists(this.db, 'blob') && tableExists(this.db, 'revision')) {
|
|
1044
|
+
applyRetention(this.db, 'blob', { table: 'blob', key: 'rowid', keep: 0, where: 'hash NOT IN (SELECT blob_hash FROM revision)' })
|
|
1045
|
+
}
|
|
1046
|
+
if (tableExists(this.db, 'tree')) applyRetention(this.db, 'tree', { table: 'tree', key: 'rowid', keep: 2000 })
|
|
1047
|
+
} catch { /* 裁剪是尽力而为:不让它成为写入失败的原因 */ }
|
|
1048
|
+
}
|
|
1049
|
+
|
|
981
1050
|
/** 记一次**没有内容变更**的操作(迁移收尾、审核结论这一类)。 */
|
|
982
1051
|
recordOperation(entry: TreeEntry, manifest: Record<string, number> = {}): void {
|
|
983
1052
|
this.raw('INSERT OR REPLACE INTO tree(op_id, at, actor, summary, manifest) VALUES (?, ?, ?, ?, ?)')
|
|
@@ -1284,12 +1353,13 @@ export class KnowledgeDb {
|
|
|
1284
1353
|
* 名字拼成语法错误。
|
|
1285
1354
|
*/
|
|
1286
1355
|
private columnsOf(schema: 'main' | 'legacy', table: string): ColumnInfo[] {
|
|
1287
|
-
const rows = this.raw('SELECT name, "notnull" AS not_null, dflt_value AS dflt FROM pragma_table_info(?, ?)')
|
|
1356
|
+
const rows = this.raw('SELECT name, "notnull" AS not_null, dflt_value AS dflt, pk FROM pragma_table_info(?, ?)')
|
|
1288
1357
|
.all(table, schema) as Array<Record<string, unknown>>
|
|
1289
1358
|
return rows.map(row => ({
|
|
1290
1359
|
name: String(row.name ?? ''),
|
|
1291
1360
|
notNull: Number(row.not_null ?? 0) === 1,
|
|
1292
1361
|
hasDefault: row.dflt !== null && row.dflt !== undefined,
|
|
1362
|
+
pk: Number(row.pk ?? 0),
|
|
1293
1363
|
}))
|
|
1294
1364
|
}
|
|
1295
1365
|
|
|
@@ -1377,11 +1447,47 @@ export class KnowledgeDb {
|
|
|
1377
1447
|
if (shared.length === 0) { problems.push(`${name} 与目标表没有同名的列,行搬不动`); continue }
|
|
1378
1448
|
const columns = shared.map(quoteIdentifier).join(', ')
|
|
1379
1449
|
const expected = Number((this.db.prepare(`SELECT count(*) AS c FROM legacy.${quoteIdentifier(name)}`).get() as { c?: unknown } | undefined)?.c ?? 0)
|
|
1380
|
-
|
|
1450
|
+
/**
|
|
1451
|
+
* 「有没有丢行」要按**三个账**算(0070):`already`(插入**之前**主键就已存在)、
|
|
1452
|
+
* `inserted`(本次真的插进去的)、以及两者之外的 `unaccounted`
|
|
1453
|
+
* —— 被唯一索引 / 非空 / 检查约束挡掉的那些才是真的丢。
|
|
1454
|
+
*
|
|
1455
|
+
* **顺序很要紧**:`already` 必须在 `INSERT` 之前数。插完再数会把本次插入的行
|
|
1456
|
+
* 也算进去,重复计数恰好把丢行掩盖成 0(试过,就是这样漏掉一行的)。
|
|
1457
|
+
*
|
|
1458
|
+
* 原来那条 `copied < expected`(拿目标**总行数**比旧文件行数)则**永远不成立** ——
|
|
1459
|
+
* 每一次主键冲突都对应一行早就存在 —— 于是丢行之后调用方还会把旧库删掉。
|
|
1460
|
+
*/
|
|
1461
|
+
const keyColumns = this.columnsOf('main', name).filter(column => column.pk > 0).map(column => column.name)
|
|
1462
|
+
const comparable = shared.filter(column => !keyColumns.includes(column))
|
|
1463
|
+
const already = keyColumns.length === 0 ? 0 : Number((this.db.prepare(
|
|
1464
|
+
`SELECT COUNT(*) AS c FROM legacy.${quoteIdentifier(name)} l WHERE EXISTS (
|
|
1465
|
+
SELECT 1 FROM main.${quoteIdentifier(name)} m WHERE ${keyColumns.map(column => `m.${quoteIdentifier(column)} IS l.${quoteIdentifier(column)}`).join(' AND ')}
|
|
1466
|
+
)`,
|
|
1467
|
+
).get() as { c?: unknown } | undefined)?.c ?? 0)
|
|
1468
|
+
const run = this.db.prepare(`INSERT OR IGNORE INTO main.${quoteIdentifier(name)} (${columns}) SELECT ${columns} FROM legacy.${quoteIdentifier(name)}`).run() as { changes?: unknown } | undefined
|
|
1469
|
+
const inserted = Number(run?.changes ?? 0)
|
|
1381
1470
|
const copied = Number((this.db.prepare(`SELECT count(*) AS c FROM main.${quoteIdentifier(name)}`).get() as { c?: unknown } | undefined)?.c ?? 0)
|
|
1382
1471
|
tables[name] = copied
|
|
1383
|
-
|
|
1384
|
-
|
|
1472
|
+
if (keyColumns.length === 0) {
|
|
1473
|
+
if (inserted < expected) problems.push(`${name} 只拷进 ${inserted} 行(旧文件 ${expected} 行):这张表没有主键,无法逐行核对,旧文件不删`)
|
|
1474
|
+
} else {
|
|
1475
|
+
const unaccounted = expected - already - inserted
|
|
1476
|
+
if (unaccounted > 0) problems.push(`${name} 有 ${unaccounted} 行没能进来(主键之外还有唯一索引 / 非空 / 检查约束挡住的行):旧文件不删`)
|
|
1477
|
+
/**
|
|
1478
|
+
* 纯主键表(例如 `entity_asset(entity_kind, entity_id, ref)`)没有可比的非键列:
|
|
1479
|
+
* 主键相同就是同一行。重跑时报「无法比对内容」是误报,会让旧文件永久留在磁盘上。
|
|
1480
|
+
*/
|
|
1481
|
+
if (comparable.length > 0) {
|
|
1482
|
+
const onEq = keyColumns.map(column => `m.${quoteIdentifier(column)} = l.${quoteIdentifier(column)}`).join(' AND ')
|
|
1483
|
+
// `IS NOT` 而不是 `<>`:NULL 与任何值都不相等,用 `<>` 会把「一边是 NULL」漏判。
|
|
1484
|
+
const differs = Number((this.db.prepare(
|
|
1485
|
+
`SELECT COUNT(*) AS c FROM legacy.${quoteIdentifier(name)} l JOIN main.${quoteIdentifier(name)} m ON ${onEq}
|
|
1486
|
+
WHERE ${comparable.map(column => `m.${quoteIdentifier(column)} IS NOT l.${quoteIdentifier(column)}`).join(' OR ')}`,
|
|
1487
|
+
).get() as { c?: unknown } | undefined)?.c ?? 0)
|
|
1488
|
+
if (differs > 0) problems.push(`${name} 有 ${differs} 行与目标库主键相同但内容不同:旧文件不删`)
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1385
1491
|
} catch (cause) {
|
|
1386
1492
|
problems.push(`${name} 的行没拷成:${cause instanceof Error ? cause.message : String(cause)}`)
|
|
1387
1493
|
}
|
package/src/store/migrate.ts
CHANGED
|
@@ -525,7 +525,7 @@ export async function exportKnowledgeTree(input: {
|
|
|
525
525
|
for (const item of db.sources()) {
|
|
526
526
|
if (only !== undefined && item.record.sourceId !== only) continue
|
|
527
527
|
const path = join(targetRoot, LEGACY_SOURCE, sourceName(item.record.title, item.record.sourceId))
|
|
528
|
-
if (await write(path, serialize(item.frontmatter, item.record.content))) sources += 1
|
|
528
|
+
if (await write(path, serialize({ ...item.frontmatter, source_id: item.record.sourceId }, item.record.content))) sources += 1
|
|
529
529
|
}
|
|
530
530
|
for (const item of db.cards()) {
|
|
531
531
|
const path = join(targetRoot, LEGACY_WIKI, item.card.entityType, cardName(item.card.entityId))
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 三段轨迹的**传输形状**(抽取 / 归一 / 物化)。
|
|
3
|
+
*
|
|
4
|
+
* 抽取那段本来就有报告(`TrialReport`,见 `wiki-trial-types.ts`),形状没动。这一份只补
|
|
5
|
+
* 新分开的两段 —— 它们此前**没有轨迹**:只有任务里一段流式输出和一张结果表,跑完就查不到
|
|
6
|
+
* 「这条知识是怎么从模型给的名字变成库里的坐标的」「这张卡是按什么排出来的」。
|
|
7
|
+
*
|
|
8
|
+
* ## 为什么这些类型住在浏览器安全的一层
|
|
9
|
+
*
|
|
10
|
+
* 服务端按这个形状写,客户端按同一个形状渲染。写两份就会漂。它们不 import 任何
|
|
11
|
+
* `node:*`,所以可以直接被两边引用 —— 这与 `pipeline/types.ts` 是同一条规矩。
|
|
12
|
+
* (`client/wiki-trial.ts` 里那份手写镜像之所以必须手写,是因为它需要引用的服务端模块
|
|
13
|
+
* import 了 `node:fs`;这里没有那个问题,所以不重复。)
|
|
14
|
+
*
|
|
15
|
+
* ## 一份轨迹对应「一次工作」,不是一次任务
|
|
16
|
+
*
|
|
17
|
+
* - 抽取:一份来源 × 一次运行。
|
|
18
|
+
* - 归一:一份来源 × 一次运行(复用的那一次不落轨迹 —— 那一次什么都没做)。
|
|
19
|
+
* - 物化:一个卡片分组 × 一次运行。
|
|
20
|
+
*
|
|
21
|
+
* 粒度定在这里,是因为这三件事的失败与复用手忙脚乱的程度不同:一份文档抽取失败不影响
|
|
22
|
+
* 另一份;一张卡物化失败也不该让同批其它卡的轨迹看起来像没跑。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** 轨迹属于哪一段。与 `PipelineStage` 的前三段同形(`all` 不是轨迹)。 */
|
|
26
|
+
export type TrajectoryStage = 'extract' | 'normalize' | 'materialize'
|
|
27
|
+
|
|
28
|
+
/** 三个名字只在这里写一次:页标题、列表徽标、日志都用它。 */
|
|
29
|
+
export const TRAJECTORY_STAGE_LABEL: Record<TrajectoryStage, string> = {
|
|
30
|
+
extract: '抽取轨迹',
|
|
31
|
+
normalize: '归一轨迹',
|
|
32
|
+
materialize: '物化轨迹',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 挤在行内时用的短名(列表徽标、筛选器)。 */
|
|
36
|
+
export const TRAJECTORY_STAGE_SHORT: Record<TrajectoryStage, string> = {
|
|
37
|
+
extract: '抽取',
|
|
38
|
+
normalize: '归一',
|
|
39
|
+
materialize: '物化',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 每条轨迹都要有的那段公共头。 */
|
|
43
|
+
export interface TrajectoryHead {
|
|
44
|
+
at: string
|
|
45
|
+
/** 模型接缝当时通不通。抽取以外的两段也可能根本没有模型调用。 */
|
|
46
|
+
model: { ok: boolean; why?: string; provider?: string; model?: string; reasoningEffort?: string }
|
|
47
|
+
warnings: string[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 坐标三个分量在轨迹里只作为**展示文本**出现(真源是轴注册表的 id)。 */
|
|
51
|
+
export type TrajectoryAxisKind = 'object' | 'action' | 'scene'
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 归一的坐标解析结论。
|
|
55
|
+
*
|
|
56
|
+
* `outcome` 的取值与 `TrialAxis.outcome` 同源:命中已有实体 / 新建候选 / 不唯一没猜 /
|
|
57
|
+
* 只做了文档内归一(`no-registry`)。
|
|
58
|
+
*/
|
|
59
|
+
export interface NormalizeAxisRow {
|
|
60
|
+
kind: TrajectoryAxisKind
|
|
61
|
+
/** 文档里的写法。 */
|
|
62
|
+
name: string
|
|
63
|
+
outcome: 'matched' | 'new' | 'ambiguous' | 'no-registry' | string
|
|
64
|
+
/** 落到的实体 id(新建的是候选 id)。歧义时没有。 */
|
|
65
|
+
id?: string
|
|
66
|
+
/** 判成这个结果的理由 / 途径,模型或规则给的。 */
|
|
67
|
+
how?: string
|
|
68
|
+
aliases: string[]
|
|
69
|
+
parentMention?: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 一条写进库的 Knowledge 在轨迹里的投影。 */
|
|
73
|
+
export interface NormalizeFactRow {
|
|
74
|
+
text: string
|
|
75
|
+
/** 「对象 · 动作 · 场景」的展示文本。 */
|
|
76
|
+
coordinate: string
|
|
77
|
+
/** 几条出处。 */
|
|
78
|
+
sources: number
|
|
79
|
+
/** 坐标仍有歧义:事实与出处都留着,但暂不进任何卡片分组。 */
|
|
80
|
+
unresolved: boolean
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 归一轨迹:一份来源的 mention 名字 → 库内坐标,以及随之写入的事实。 */
|
|
84
|
+
export interface NormalizeTrajectoryReport extends TrajectoryHead {
|
|
85
|
+
stage: 'normalize'
|
|
86
|
+
source: { id: string; title: string; version: number }
|
|
87
|
+
/** 依据哪一条抽取记录做的归一。 */
|
|
88
|
+
extractionId: string
|
|
89
|
+
/** 归一之后存下的指纹(下一份文档归一能不能复用,看它)。 */
|
|
90
|
+
fingerprint: string
|
|
91
|
+
/** 模型这次给了多少个坐标提及。 */
|
|
92
|
+
mentions: number
|
|
93
|
+
/**
|
|
94
|
+
* 解析结论的分布。`mechanical` = 这一轮没接注册表,只做了文档内归一
|
|
95
|
+
* (三个分量各自计数,所以它是「多少条提及只做了机械归一」而不是一个布尔)。
|
|
96
|
+
*/
|
|
97
|
+
counts: { matched: number; created: number; ambiguous: number; mechanical: number }
|
|
98
|
+
axes: NormalizeAxisRow[]
|
|
99
|
+
knowledge: { saved: number; merged: number; skipped: number; failures: number; unresolved: number }
|
|
100
|
+
events: { saved: number; merged: number; skipped: number }
|
|
101
|
+
rows: NormalizeFactRow[]
|
|
102
|
+
/** 已有坐标有没有下发进提示词,以及发了多少(0068)。 */
|
|
103
|
+
axesContext?: { injected: number; totals: number; truncated: number; selection?: string }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** 物化轨迹里的一节。 */
|
|
107
|
+
export interface MaterializeSectionRow {
|
|
108
|
+
label: string
|
|
109
|
+
count: number
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 物化轨迹:一个卡片分组 → 一张卡,以及它是怎么排出来的。 */
|
|
113
|
+
export interface MaterializeTrajectoryReport extends TrajectoryHead {
|
|
114
|
+
stage: 'materialize'
|
|
115
|
+
group: { key: string; title: string; coordinate: string; state: string; fingerprint: string }
|
|
116
|
+
action: 'created' | 'updated' | 'withdrawn'
|
|
117
|
+
card: {
|
|
118
|
+
id: string
|
|
119
|
+
title: string
|
|
120
|
+
status: string
|
|
121
|
+
/** 正文规模,字符数。 */
|
|
122
|
+
characters: number
|
|
123
|
+
/** 进卡的知识条数 / 事件数 / 图数。 */
|
|
124
|
+
knowledge: number
|
|
125
|
+
events: number
|
|
126
|
+
images: number
|
|
127
|
+
sections: MaterializeSectionRow[]
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 卡片组织用了没有。
|
|
131
|
+
*
|
|
132
|
+
* `used: false` = 这一库没开卡片组织(`cardOrganize` 关着),正文是按坐标机械拼的;
|
|
133
|
+
* 关着不是失败,但两件事的正文长得不一样,报告必须分得清。
|
|
134
|
+
*/
|
|
135
|
+
organize: { used: boolean; origin?: 'model' | 'fallback'; layoutHash?: string }
|
|
136
|
+
/** 进卡的每一条知识(带出处数),让人对着原文核。 */
|
|
137
|
+
rows: Array<{ id: string; text: string; sources: number }>
|
|
138
|
+
/** 这一组的来源文档。 */
|
|
139
|
+
sources: string[]
|
|
140
|
+
/** 正文里可以指向的其它卡片,以及为什么(`coordinate` / `relation` / `source`)。 */
|
|
141
|
+
related: Array<{ title: string; reason: string }>
|
|
142
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -7,10 +7,8 @@ export type KnowledgeStatus = CardStatus
|
|
|
7
7
|
/**
|
|
8
8
|
* 知识卡片的 entity_type 白名单。
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 返回 `SCHEMA_TYPE_INVALID`,`readCards` 也会跳过 —— 于是排查思路**永远进不了 wikiNotes,
|
|
13
|
-
* 也就永远召不回来**。列表本身在 `diagnosis/schema.ts` 里定义一次,这里只做联合。
|
|
10
|
+
* 诊断卡片类型留在联合里供历史数据兼容。当前排查记录落 SourceRaw,Case 是只读投影;
|
|
11
|
+
* 新运行不会创建 `diagnosis/case` WikiCard。列表本身在 `diagnosis/schema.ts` 里定义一次。
|
|
14
12
|
*/
|
|
15
13
|
export type SchemaType =
|
|
16
14
|
| 'concept' | 'metric' | 'table' | 'event' | 'playbook' | 'case'
|
|
@@ -28,6 +26,7 @@ export type RerankProvider = 'bert-python' | 'none'
|
|
|
28
26
|
*/
|
|
29
27
|
export type CompileModelMode = 'default' | 'fixed'
|
|
30
28
|
export interface CompileModelSelection {
|
|
29
|
+
reasoningEffort?: string
|
|
31
30
|
mode: CompileModelMode
|
|
32
31
|
provider?: string
|
|
33
32
|
model?: string
|
package/src/wiki-finalize.ts
CHANGED
|
@@ -167,6 +167,34 @@ export function cardKey(card: { entityType: string; entityId: string }): string
|
|
|
167
167
|
* 弱的那一类是有用的:知识库刚建起来时,关系一条都没有,而「这几张卡来自同一篇文章」
|
|
168
168
|
* 恰好是人最需要的导航。
|
|
169
169
|
*/
|
|
170
|
+
/**
|
|
171
|
+
* 把「按**卡片身份**」的相关推荐计划,映射到「按**分组键**」(0070)。
|
|
172
|
+
*
|
|
173
|
+
* 两个键空间不同:`planRelated` 的键是 `type:entityId`,而物化阶段的分组键是坐标
|
|
174
|
+
* (`objectId\u0001actionId\u0001sceneId`)。不映射会出现一个**静默的**退化:
|
|
175
|
+
* `organizeCard` 拿不到「可引用的邻居」,而模型一旦照样写出 `[[名字]]`,校验会判
|
|
176
|
+
* `ORGANIZE_UNKNOWN_REFERENCE`,整组物化失败、卡片永远写不出来。
|
|
177
|
+
*
|
|
178
|
+
* 只映射**已经有卡片**的分组:新建的卡片在相关计划里本来就不存在(与旧的
|
|
179
|
+
* `relatedForCard(existing)` 行为一致)。键用卡片自己的类型,也与旧路径一致。
|
|
180
|
+
*/
|
|
181
|
+
/** `T` 是相关推荐条目的形状:调用方按自己的投影传进来,这里只管键的映射。 */
|
|
182
|
+
export function relatedByGroupKey<T>(
|
|
183
|
+
groups: ReadonlyArray<{ key: string; cardId?: string }>,
|
|
184
|
+
cardKeyOf: (groupKey: string) => string | undefined,
|
|
185
|
+
plan: ReadonlyMap<string, T[]>,
|
|
186
|
+
): Map<string, T[]> {
|
|
187
|
+
const mapped = new Map<string, T[]>()
|
|
188
|
+
for (const group of groups) {
|
|
189
|
+
if (group.cardId === undefined) continue
|
|
190
|
+
const key = cardKeyOf(group.key)
|
|
191
|
+
if (key === undefined) continue
|
|
192
|
+
const entries = plan.get(key)
|
|
193
|
+
if (entries !== undefined) mapped.set(group.key, entries)
|
|
194
|
+
}
|
|
195
|
+
return mapped
|
|
196
|
+
}
|
|
197
|
+
|
|
170
198
|
export function planRelated(
|
|
171
199
|
cards: Array<Pick<KnowledgeCard, 'entityType' | 'entityId' | 'title' | 'sourceRefs'>>,
|
|
172
200
|
relations: Array<{ fromKey: string; toKey: string }>,
|
package/src/wiki-materialize.ts
CHANGED
|
@@ -153,21 +153,10 @@ export function candidatesFromReport(input: {
|
|
|
153
153
|
? input.eventIdsByCoordinate?.[coordinateKey]
|
|
154
154
|
?? card.events.map(id => input.eventIds?.[id]).filter((id): id is string => Boolean(id))
|
|
155
155
|
: []
|
|
156
|
-
if (items.length === 0
|
|
157
|
-
const localEvents = completeCoordinate
|
|
158
|
-
? report.events.filter(event => card.events.includes(event.id))
|
|
159
|
-
: []
|
|
160
|
-
const eventSection = localEvents.length > 0
|
|
161
|
-
? `## 关联事件\n\n${localEvents.map(event => `- ${event.title}${event.summary ? `:${event.summary}` : ''}`).join('\n')}`
|
|
162
|
-
: ''
|
|
156
|
+
if (items.length === 0) continue
|
|
163
157
|
const knowledgeBody = renderFallbackBody(card.placements, byId)
|
|
164
|
-
const body =
|
|
165
|
-
const eventEvidence = localEvents.flatMap(event => [
|
|
166
|
-
...event.sources,
|
|
167
|
-
...event.timeline.flatMap(step => step.sources),
|
|
168
|
-
]).find(source => source.found)
|
|
158
|
+
const body = knowledgeBody
|
|
169
159
|
const evidence = evidenceOf(items, sourceId)
|
|
170
|
-
if (evidence.quote === undefined && eventEvidence) evidence.quote = eventEvidence.quote
|
|
171
160
|
|
|
172
161
|
cards.push({
|
|
173
162
|
candidate: {
|
|
@@ -178,7 +167,7 @@ export function candidatesFromReport(input: {
|
|
|
178
167
|
aliases: [...new Set([legacyCardNameFor(card.object, card.action, card.scene)])]
|
|
179
168
|
.filter(alias => alias !== card.name),
|
|
180
169
|
body,
|
|
181
|
-
...(items[0] ? { summary: items[0].text } :
|
|
170
|
+
...(items[0] ? { summary: items[0].text } : {}),
|
|
182
171
|
/**
|
|
183
172
|
* 证据取这条卡里**第一条核对得上的引文**。
|
|
184
173
|
*
|
package/src/wiki-trial-types.ts
CHANGED
|
@@ -338,6 +338,9 @@ export interface TrialReport {
|
|
|
338
338
|
*/
|
|
339
339
|
model: {
|
|
340
340
|
ok: boolean
|
|
341
|
+
/** Structural decoding succeeded, including a valid empty extraction. */
|
|
342
|
+
parsed?: boolean
|
|
343
|
+
outputFormat?: 'json' | 'table'
|
|
341
344
|
why?: string
|
|
342
345
|
/** 解析失败时的短摘录,保留旧界面的错误提示。 */
|
|
343
346
|
raw?: string
|