elementary-assertions 1.0.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.
- package/CHANGELOG.md +353 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/bin/elementary-assertions.js +8 -0
- package/docs/DEV_TOOLING.md +98 -0
- package/docs/NPM_RELEASE.md +177 -0
- package/docs/OPERATIONAL.md +159 -0
- package/docs/RELEASE_NOTES_TEMPLATE.md +37 -0
- package/docs/REPO_WORKFLOWS.md +48 -0
- package/package.json +46 -0
- package/src/core/accepted-annotations.js +44 -0
- package/src/core/assertions.js +2304 -0
- package/src/core/determinism.js +95 -0
- package/src/core/diagnostics.js +496 -0
- package/src/core/ids.js +9 -0
- package/src/core/mention-builder.js +272 -0
- package/src/core/mention-evidence.js +52 -0
- package/src/core/mention-head-resolution.js +108 -0
- package/src/core/mention-materialization.js +31 -0
- package/src/core/mentions.js +149 -0
- package/src/core/output.js +296 -0
- package/src/core/projection.js +192 -0
- package/src/core/roles.js +164 -0
- package/src/core/strings.js +7 -0
- package/src/core/tokens.js +53 -0
- package/src/core/upstream.js +31 -0
- package/src/index.js +6 -0
- package/src/render/index.js +5 -0
- package/src/render/layouts/compact.js +10 -0
- package/src/render/layouts/meaning.js +7 -0
- package/src/render/layouts/readable.js +7 -0
- package/src/render/layouts/table.js +7 -0
- package/src/render/render.js +931 -0
- package/src/run.js +278 -0
- package/src/schema/seed.elementary-assertions.schema.json +1751 -0
- package/src/tools/cli.js +158 -0
- package/src/tools/index.js +6 -0
- package/src/tools/io.js +55 -0
- package/src/validate/ajv.js +20 -0
- package/src/validate/coverage.js +215 -0
- package/src/validate/determinism.js +115 -0
- package/src/validate/diagnostics-strict.js +392 -0
- package/src/validate/errors.js +19 -0
- package/src/validate/index.js +20 -0
- package/src/validate/integrity.js +41 -0
- package/src/validate/invariants.js +157 -0
- package/src/validate/references.js +110 -0
- package/src/validate/schema.js +50 -0
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
const { validateElementaryAssertions } = require("../validate");
|
|
2
|
+
const { rejectLegacySlots } = require("../validate/schema");
|
|
3
|
+
const { normalizeIds } = require("../core/ids");
|
|
4
|
+
|
|
5
|
+
function normalizeRenderText(text) {
|
|
6
|
+
if (typeof text !== 'string' || text.length === 0) return text;
|
|
7
|
+
return text;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalizePossessiveDisplay(text) {
|
|
11
|
+
if (typeof text !== 'string' || text.length === 0) return text;
|
|
12
|
+
return text.replace(/([A-Za-z0-9]) (['\u2019]s)\b/g, '$1$2');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeSegmentDisplay(text, options) {
|
|
16
|
+
if (typeof text !== 'string') return text;
|
|
17
|
+
if (!options || options.layout === 'compact') return text;
|
|
18
|
+
return text.replace(/^\n+/, '').replace(/\n+$/, '');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function emptyWikiEvidence() {
|
|
22
|
+
return { exact_titles: [], prefix_titles: [] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createWikiEvidenceResolver(data) {
|
|
26
|
+
const mentionMap = new Map((((data || {}).wiki_title_evidence || {}).mention_matches || []).map((x) => [x.mention_id, x]));
|
|
27
|
+
const predByAssertion = new Map((((data || {}).wiki_title_evidence || {}).assertion_predicate_matches || []).map((x) => [x.assertion_id, x]));
|
|
28
|
+
const predByMention = new Map((((data || {}).wiki_title_evidence || {}).assertion_predicate_matches || []).map((x) => [x.predicate_mention_id, x]));
|
|
29
|
+
return function getWikiEvidenceForSurface(kind, idOrKey) {
|
|
30
|
+
if (typeof idOrKey !== 'string' || idOrKey.length === 0) return emptyWikiEvidence();
|
|
31
|
+
if (kind === 'mention' || kind === 'slot') return mentionMap.get(idOrKey) || emptyWikiEvidence();
|
|
32
|
+
if (kind === 'predicate') return predByAssertion.get(idOrKey) || predByMention.get(idOrKey) || emptyWikiEvidence();
|
|
33
|
+
return emptyWikiEvidence();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderSurfaceWithWiki(surfaceText, evidence) {
|
|
38
|
+
const text = String(surfaceText || '');
|
|
39
|
+
const exact = evidence && Array.isArray(evidence.exact_titles) ? evidence.exact_titles.length : 0;
|
|
40
|
+
const prefix = evidence && Array.isArray(evidence.prefix_titles) ? evidence.prefix_titles.length : 0;
|
|
41
|
+
if (exact > 0) return `⟦${text}|wiki:exact⟧`;
|
|
42
|
+
if (prefix > 0) return `⟦${text}|wiki:prefix⟧`;
|
|
43
|
+
return text;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function joinTokenText(tokens) {
|
|
47
|
+
const sorted = tokens.slice().sort((a, b) => a.i - b.i);
|
|
48
|
+
return normalizeRenderText(sorted.map((t) => t.surface).join(' '));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeDeterminerDisplay(tokens) {
|
|
52
|
+
const sorted = tokens.slice().sort((a, b) => a.i - b.i);
|
|
53
|
+
if (sorted.length < 2) return null;
|
|
54
|
+
const first = sorted[0] || {};
|
|
55
|
+
const firstCoarse = String((((first || {}).pos) || {}).coarse || '').toUpperCase();
|
|
56
|
+
const firstTag = String((((first || {}).pos) || {}).tag || '').toUpperCase();
|
|
57
|
+
const isDeterminer = firstCoarse === 'DT' || firstTag === 'DT';
|
|
58
|
+
if (!isDeterminer) return null;
|
|
59
|
+
const det = String(first.surface || '').trim();
|
|
60
|
+
if (!det) return null;
|
|
61
|
+
const rest = normalizeRenderText(sorted.slice(1).map((t) => t.surface).join(' '));
|
|
62
|
+
if (!rest) return null;
|
|
63
|
+
return `(${det.toLowerCase()}) ${rest}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function assertionSortKey(a, mentionById, tokenById) {
|
|
67
|
+
const m = mentionById.get(a.predicate.mention_id);
|
|
68
|
+
const t = m ? tokenById.get(m.head_token_id) : null;
|
|
69
|
+
return [
|
|
70
|
+
a.segment_id || '',
|
|
71
|
+
t && typeof t.i === 'number' ? String(t.i).padStart(8, '0') : '99999999',
|
|
72
|
+
a.id || '',
|
|
73
|
+
].join('|');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function compareSegmentIdsNatural(a, b) {
|
|
77
|
+
const left = String(a || "");
|
|
78
|
+
const right = String(b || "");
|
|
79
|
+
const leftMatch = /^([^\d]*)(\d+)$/.exec(left);
|
|
80
|
+
const rightMatch = /^([^\d]*)(\d+)$/.exec(right);
|
|
81
|
+
if (leftMatch && rightMatch && leftMatch[1] === rightMatch[1]) {
|
|
82
|
+
const leftNum = Number(leftMatch[2]);
|
|
83
|
+
const rightNum = Number(rightMatch[2]);
|
|
84
|
+
if (leftNum !== rightNum) return leftNum - rightNum;
|
|
85
|
+
}
|
|
86
|
+
return left.localeCompare(right);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const ACTOR_ROLES = new Set(['actor', 'subject', 'agent', 'nsubj', 'nsubjpass', 'csubj', 'csubjpass']);
|
|
90
|
+
const ROLE_TO_DISPLAY_COLUMN = new Map([
|
|
91
|
+
['theme', 'theme'],
|
|
92
|
+
['attribute', 'attr'],
|
|
93
|
+
['topic', 'topic'],
|
|
94
|
+
['location', 'location'],
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
function toViewSlotsFromRoles(assertion) {
|
|
98
|
+
const slots = { actor: [], theme: [], attr: [], topic: [], location: [], other: [] };
|
|
99
|
+
const argumentEntries = Array.isArray(assertion && assertion.arguments) ? assertion.arguments : [];
|
|
100
|
+
const modifierEntries = Array.isArray(assertion && assertion.modifiers) ? assertion.modifiers : [];
|
|
101
|
+
|
|
102
|
+
for (const entry of argumentEntries) {
|
|
103
|
+
const role = String((entry && entry.role) || '');
|
|
104
|
+
const mentionIds = normalizeIds((entry && entry.mention_ids) || []);
|
|
105
|
+
if (!role || mentionIds.length === 0) continue;
|
|
106
|
+
if (ACTOR_ROLES.has(role)) {
|
|
107
|
+
slots.actor = normalizeIds(slots.actor.concat(mentionIds));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const mappedColumn = ROLE_TO_DISPLAY_COLUMN.get(role);
|
|
111
|
+
if (mappedColumn) {
|
|
112
|
+
slots[mappedColumn] = normalizeIds(slots[mappedColumn].concat(mentionIds));
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
slots.other.push({ role, mention_ids: mentionIds });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const entry of modifierEntries) {
|
|
119
|
+
const role = String((entry && entry.role) || '');
|
|
120
|
+
const mentionIds = normalizeIds((entry && entry.mention_ids) || []);
|
|
121
|
+
if (!role || mentionIds.length === 0) continue;
|
|
122
|
+
slots.other.push({ role, mention_ids: mentionIds });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
slots.other = slots.other
|
|
126
|
+
.map((entry) => ({ role: entry.role, mention_ids: normalizeIds(entry.mention_ids || []) }))
|
|
127
|
+
.filter((entry) => entry.mention_ids.length > 0)
|
|
128
|
+
.sort((a, b) => {
|
|
129
|
+
if (a.role !== b.role) return a.role.localeCompare(b.role);
|
|
130
|
+
return JSON.stringify(a.mention_ids).localeCompare(JSON.stringify(b.mention_ids));
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return slots;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function countEntryEvidence(entry) {
|
|
137
|
+
const evidence = (entry && typeof entry === 'object') ? (entry.evidence || {}) : {};
|
|
138
|
+
const relationIds = normalizeIds(Array.isArray(evidence.relation_ids) ? evidence.relation_ids : []);
|
|
139
|
+
const tokenIds = normalizeIds(Array.isArray(evidence.token_ids) ? evidence.token_ids : []);
|
|
140
|
+
return { relationCount: relationIds.length, tokenCount: tokenIds.length };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function buildAssertionEvidenceFootnote(assertion) {
|
|
144
|
+
const argumentEntries = Array.isArray(assertion && assertion.arguments) ? assertion.arguments : [];
|
|
145
|
+
const modifierEntries = Array.isArray(assertion && assertion.modifiers) ? assertion.modifiers : [];
|
|
146
|
+
const operators = Array.isArray(assertion && assertion.operators) ? assertion.operators : [];
|
|
147
|
+
const roleEvidence = new Map();
|
|
148
|
+
|
|
149
|
+
for (const entry of argumentEntries.concat(modifierEntries)) {
|
|
150
|
+
const role = String((entry && entry.role) || '');
|
|
151
|
+
if (!role) continue;
|
|
152
|
+
const counts = countEntryEvidence(entry);
|
|
153
|
+
const prev = roleEvidence.get(role) || { relationCount: 0, tokenCount: 0 };
|
|
154
|
+
roleEvidence.set(role, {
|
|
155
|
+
relationCount: prev.relationCount + counts.relationCount,
|
|
156
|
+
tokenCount: prev.tokenCount + counts.tokenCount,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const roleParts = Array.from(roleEvidence.entries())
|
|
161
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
162
|
+
.map(([role, counts]) => `${role}(r=${counts.relationCount},t=${counts.tokenCount})`);
|
|
163
|
+
|
|
164
|
+
let opRelationCount = 0;
|
|
165
|
+
let opTokenCount = 0;
|
|
166
|
+
for (const op of operators) {
|
|
167
|
+
const evidence = (op && typeof op === 'object') ? (op.evidence || []) : [];
|
|
168
|
+
const relationIds = new Set();
|
|
169
|
+
const tokenIds = new Set();
|
|
170
|
+
for (const ev of evidence) {
|
|
171
|
+
if (!ev || typeof ev !== 'object') continue;
|
|
172
|
+
const upstream = Array.isArray(ev.upstream_relation_ids) ? ev.upstream_relation_ids : [];
|
|
173
|
+
const tids = Array.isArray(ev.token_ids) ? ev.token_ids : [];
|
|
174
|
+
for (const id of upstream) relationIds.add(String(id));
|
|
175
|
+
for (const tid of tids) tokenIds.add(String(tid));
|
|
176
|
+
}
|
|
177
|
+
opRelationCount += relationIds.size;
|
|
178
|
+
opTokenCount += tokenIds.size;
|
|
179
|
+
}
|
|
180
|
+
const operatorPart = `operators(r=${opRelationCount},t=${opTokenCount})`;
|
|
181
|
+
return roleParts.length > 0
|
|
182
|
+
? `${roleParts.join('; ')}; ${operatorPart}`
|
|
183
|
+
: operatorPart;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function mentionSort(mentions) {
|
|
187
|
+
mentions.sort((a, b) => {
|
|
188
|
+
if (a.segment_id !== b.segment_id) return a.segment_id.localeCompare(b.segment_id);
|
|
189
|
+
if (a.span.start !== b.span.start) return a.span.start - b.span.start;
|
|
190
|
+
if (a.span.end !== b.span.end) return a.span.end - b.span.end;
|
|
191
|
+
if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
|
|
192
|
+
return a.id.localeCompare(b.id);
|
|
193
|
+
});
|
|
194
|
+
return mentions;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function buildIntegrity(data) {
|
|
198
|
+
if (!data || typeof data !== 'object') throw new Error('Input must be a mapping');
|
|
199
|
+
if (!Array.isArray(data.tokens)) throw new Error('Input missing tokens');
|
|
200
|
+
if (!Array.isArray(data.mentions)) throw new Error('Input missing mentions');
|
|
201
|
+
if (!Array.isArray(data.assertions)) throw new Error('Input missing assertions');
|
|
202
|
+
if (!data.coverage || typeof data.coverage !== 'object') throw new Error('Input missing coverage');
|
|
203
|
+
|
|
204
|
+
const tokenById = new Map();
|
|
205
|
+
for (const t of data.tokens) {
|
|
206
|
+
if (!t || typeof t.id !== 'string') throw new Error('Token missing id');
|
|
207
|
+
if (typeof t.i !== 'number') throw new Error(`Token ${t.id} missing i`);
|
|
208
|
+
tokenById.set(t.id, t);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const mentionById = new Map();
|
|
212
|
+
for (const m of data.mentions) {
|
|
213
|
+
if (!m || typeof m.id !== 'string') throw new Error('Mention missing id');
|
|
214
|
+
if (!Array.isArray(m.token_ids) || m.token_ids.length === 0) throw new Error(`Mention ${m.id} missing token_ids`);
|
|
215
|
+
for (const tid of m.token_ids) {
|
|
216
|
+
if (!tokenById.has(tid)) throw new Error(`Mention ${m.id} references unknown token ${tid}`);
|
|
217
|
+
}
|
|
218
|
+
if (typeof m.head_token_id !== 'string') throw new Error(`Mention ${m.id} missing head_token_id`);
|
|
219
|
+
if (!m.token_ids.includes(m.head_token_id)) throw new Error(`Mention ${m.id} head_token_id must be contained in token_ids`);
|
|
220
|
+
mentionById.set(m.id, m);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const a of data.assertions) {
|
|
224
|
+
if (!a || typeof a.id !== 'string') throw new Error('Assertion missing id');
|
|
225
|
+
if (!a.predicate || typeof a.predicate.mention_id !== 'string') throw new Error(`Assertion ${a.id} missing predicate.mention_id`);
|
|
226
|
+
if (!mentionById.has(a.predicate.mention_id)) throw new Error(`Assertion ${a.id} references unknown predicate mention ${a.predicate.mention_id}`);
|
|
227
|
+
const slots = toViewSlotsFromRoles(a);
|
|
228
|
+
const lists = []
|
|
229
|
+
.concat(slots.actor || [])
|
|
230
|
+
.concat(slots.theme || [])
|
|
231
|
+
.concat(slots.attr || [])
|
|
232
|
+
.concat(slots.topic || [])
|
|
233
|
+
.concat(slots.location || [])
|
|
234
|
+
.concat((slots.other || []).flatMap((o) => o.mention_ids || []));
|
|
235
|
+
for (const mid of lists) {
|
|
236
|
+
if (!mentionById.has(mid)) throw new Error(`Assertion ${a.id} references unknown assertion role mention ${mid}`);
|
|
237
|
+
}
|
|
238
|
+
const evTokenIds = (a.evidence && Array.isArray(a.evidence.token_ids)) ? a.evidence.token_ids : [];
|
|
239
|
+
for (const tid of evTokenIds) {
|
|
240
|
+
if (!tokenById.has(tid)) throw new Error(`Assertion ${a.id} evidence.token_ids references unknown token ${tid}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const unresolved = Array.isArray(data.coverage.unresolved) ? data.coverage.unresolved : [];
|
|
245
|
+
for (const u of unresolved) {
|
|
246
|
+
if (!mentionById.has(u.mention_id)) throw new Error(`Unresolved item references unknown mention ${u.mention_id}`);
|
|
247
|
+
const tids = (((u || {}).evidence || {}).token_ids) || [];
|
|
248
|
+
for (const tid of tids) {
|
|
249
|
+
if (!tokenById.has(tid)) throw new Error(`Unresolved item references unknown token ${tid}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return { tokenById, mentionById };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function mentionText(mention, tokenById, options) {
|
|
257
|
+
const tokens = mention.token_ids.map((id) => tokenById.get(id)).filter(Boolean);
|
|
258
|
+
if (options && options.normalizeDeterminers) {
|
|
259
|
+
const normalized = normalizeDeterminerDisplay(tokens);
|
|
260
|
+
if (normalized) {
|
|
261
|
+
if (options.layout !== 'compact') return normalizePossessiveDisplay(normalized);
|
|
262
|
+
return normalized;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const raw = joinTokenText(tokens);
|
|
266
|
+
if (options && options.layout !== 'compact') return normalizePossessiveDisplay(raw);
|
|
267
|
+
return raw;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function mentionHeadI(mention, tokenById) {
|
|
271
|
+
const t = tokenById.get(mention.head_token_id);
|
|
272
|
+
return t ? t.i : Number.MAX_SAFE_INTEGER;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function mentionCoverageSortKey(mention) {
|
|
276
|
+
const start = Number((mention && mention.span && mention.span.start) || Number.MAX_SAFE_INTEGER);
|
|
277
|
+
const id = String((mention && mention.id) || '');
|
|
278
|
+
return `${String(start).padStart(12, '0')}|${id}`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function sortMentionIdsForCoverage(ids, mentionById) {
|
|
282
|
+
return (ids || []).slice().sort((x, y) => {
|
|
283
|
+
const mx = mentionById.get(x);
|
|
284
|
+
const my = mentionById.get(y);
|
|
285
|
+
if (!mx || !my) return String(x).localeCompare(String(y));
|
|
286
|
+
const kx = mentionCoverageSortKey(mx);
|
|
287
|
+
const ky = mentionCoverageSortKey(my);
|
|
288
|
+
return kx.localeCompare(ky);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function collectUsedMentionIdsFromAssertions(assertions) {
|
|
293
|
+
const used = new Set();
|
|
294
|
+
const directMentionKeys = new Set(['mention_id', 'actor', 'theme', 'attr', 'topic', 'location']);
|
|
295
|
+
const visit = (value, keyHint) => {
|
|
296
|
+
if (value === null || value === undefined) return;
|
|
297
|
+
if (typeof value === 'string') {
|
|
298
|
+
if (directMentionKeys.has(String(keyHint || ''))) used.add(value);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (Array.isArray(value)) {
|
|
302
|
+
for (const item of value) visit(item, keyHint);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (typeof value !== 'object') return;
|
|
306
|
+
for (const [k, v] of Object.entries(value)) {
|
|
307
|
+
if (k === 'mention_id') {
|
|
308
|
+
if (typeof v === 'string' && v.length > 0) used.add(v);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (k === 'mention_ids' || k === 'transferred_mention_ids') {
|
|
312
|
+
if (Array.isArray(v)) {
|
|
313
|
+
for (const id of v) {
|
|
314
|
+
if (typeof id === 'string' && id.length > 0) used.add(id);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
visit(v, k);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
for (const a of assertions || []) {
|
|
324
|
+
if (!a || typeof a !== 'object') continue;
|
|
325
|
+
visit(a, null);
|
|
326
|
+
}
|
|
327
|
+
return used;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function formatOperators(ops) {
|
|
331
|
+
if (!Array.isArray(ops) || ops.length === 0) return '';
|
|
332
|
+
const sorted = ops
|
|
333
|
+
.slice()
|
|
334
|
+
.sort((a, b) => {
|
|
335
|
+
const ak = [a.kind || '', a.value || '', a.group_id || '', a.token_id || ''].join('|');
|
|
336
|
+
const bk = [b.kind || '', b.value || '', b.group_id || '', b.token_id || ''].join('|');
|
|
337
|
+
return ak.localeCompare(bk);
|
|
338
|
+
})
|
|
339
|
+
.map((o) => {
|
|
340
|
+
if (o.kind === 'modality') return `modality(${o.value || ''})`;
|
|
341
|
+
if (o.kind === 'negation') return `negation(${o.token_id || ''})`;
|
|
342
|
+
if (o.kind === 'coordination_group') {
|
|
343
|
+
const suffix = o.value ? `:${o.value}` : '';
|
|
344
|
+
return `coordination_group(${o.group_id || ''}${suffix})`;
|
|
345
|
+
}
|
|
346
|
+
if (o.kind === 'compare' || o.kind === 'compare_gt' || o.kind === 'compare_lt') {
|
|
347
|
+
return `${o.kind}(${o.token_id || ''})`;
|
|
348
|
+
}
|
|
349
|
+
if (o.kind === 'quantifier') return `quantifier(${o.value || ''}|${o.token_id || ''})`;
|
|
350
|
+
if (o.kind === 'control_inherit_subject') return 'control_inherit_subject';
|
|
351
|
+
if (o.kind === 'control_propagation') return 'control_propagation';
|
|
352
|
+
return o.kind || 'operator';
|
|
353
|
+
});
|
|
354
|
+
return sorted.join(', ');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function sortedSegments(data) {
|
|
358
|
+
return (data.segments || []).slice().sort((a, b) => {
|
|
359
|
+
if (a.span.start !== b.span.start) return a.span.start - b.span.start;
|
|
360
|
+
return a.id.localeCompare(b.id);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function buildAssertionRows(data, refs, options) {
|
|
365
|
+
const { tokenById, mentionById } = refs;
|
|
366
|
+
const assertions = (data.assertions || [])
|
|
367
|
+
.slice()
|
|
368
|
+
.sort((a, b) => assertionSortKey(a, mentionById, tokenById).localeCompare(assertionSortKey(b, mentionById, tokenById)));
|
|
369
|
+
const viewSlotOrder = ['actor', 'theme', 'attr', 'topic', 'location', 'other'];
|
|
370
|
+
|
|
371
|
+
return assertions.map((a, index) => {
|
|
372
|
+
const predMention = mentionById.get(a.predicate.mention_id);
|
|
373
|
+
const predText = mentionText(predMention, tokenById, options);
|
|
374
|
+
const viewSlots = toViewSlotsFromRoles(a);
|
|
375
|
+
const slotValues = {};
|
|
376
|
+
for (const slot of viewSlotOrder) {
|
|
377
|
+
if (slot !== 'other') {
|
|
378
|
+
const mids = (viewSlots && Array.isArray(viewSlots[slot]) ? viewSlots[slot] : []).slice();
|
|
379
|
+
mids.sort((x, y) => {
|
|
380
|
+
const mx = mentionById.get(x);
|
|
381
|
+
const my = mentionById.get(y);
|
|
382
|
+
const dx = mentionHeadI(mx, tokenById);
|
|
383
|
+
const dy = mentionHeadI(my, tokenById);
|
|
384
|
+
if (dx !== dy) return dx - dy;
|
|
385
|
+
return x.localeCompare(y);
|
|
386
|
+
});
|
|
387
|
+
slotValues[slot] = mids.map((mid) => ({
|
|
388
|
+
mention_id: mid,
|
|
389
|
+
text: mentionText(mentionById.get(mid), tokenById, options),
|
|
390
|
+
}));
|
|
391
|
+
} else {
|
|
392
|
+
const other = (viewSlots && Array.isArray(viewSlots.other) ? viewSlots.other : [])
|
|
393
|
+
.map((o) => ({
|
|
394
|
+
role: o.role,
|
|
395
|
+
mention_ids: (o.mention_ids || []).slice().sort((x, y) => {
|
|
396
|
+
const mx = mentionById.get(x);
|
|
397
|
+
const my = mentionById.get(y);
|
|
398
|
+
const dx = mentionHeadI(mx, tokenById);
|
|
399
|
+
const dy = mentionHeadI(my, tokenById);
|
|
400
|
+
if (dx !== dy) return dx - dy;
|
|
401
|
+
return x.localeCompare(y);
|
|
402
|
+
}),
|
|
403
|
+
}))
|
|
404
|
+
.sort((x, y) => x.role.localeCompare(y.role));
|
|
405
|
+
slotValues.other = other.map((o) => ({
|
|
406
|
+
role: o.role,
|
|
407
|
+
values: o.mention_ids.map((mid) => ({
|
|
408
|
+
mention_id: mid,
|
|
409
|
+
text: mentionText(mentionById.get(mid), tokenById, options),
|
|
410
|
+
})),
|
|
411
|
+
}));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
ordinal: index + 1,
|
|
417
|
+
assertion: a,
|
|
418
|
+
predText,
|
|
419
|
+
slotValues,
|
|
420
|
+
opsText: formatOperators(a.operators),
|
|
421
|
+
evidenceFootnote: buildAssertionEvidenceFootnote(a),
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function renderMentions(data, options, refs, lines, md, getWikiEvidenceForSurface) {
|
|
427
|
+
const { tokenById } = refs;
|
|
428
|
+
const mentions = mentionSort((data.mentions || []).slice());
|
|
429
|
+
if (md) lines.push('\n## Mentions');
|
|
430
|
+
else lines.push('\nMentions');
|
|
431
|
+
|
|
432
|
+
if (options.layout === 'compact') {
|
|
433
|
+
for (const m of mentions) {
|
|
434
|
+
const text = renderSurfaceWithWiki(
|
|
435
|
+
mentionText(m, tokenById, options),
|
|
436
|
+
getWikiEvidenceForSurface('mention', m.id)
|
|
437
|
+
);
|
|
438
|
+
const headSurface = tokenById.get(m.head_token_id).surface;
|
|
439
|
+
let line = `mention="${text}" kind=${m.kind} is_primary=${m.is_primary} head="${headSurface}"`;
|
|
440
|
+
if (options.debugIds) {
|
|
441
|
+
line += ` id=${m.id} head_token_id=${m.head_token_id} token_ids=[${m.token_ids.join(',')}] span=${m.span.start}-${m.span.end}`;
|
|
442
|
+
}
|
|
443
|
+
lines.push(md ? `- ${line}` : line);
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const grouped = new Map();
|
|
449
|
+
for (const m of mentions) {
|
|
450
|
+
if (!grouped.has(m.segment_id)) grouped.set(m.segment_id, []);
|
|
451
|
+
grouped.get(m.segment_id).push(m);
|
|
452
|
+
}
|
|
453
|
+
const segmentOrder = Array.from(grouped.keys()).sort();
|
|
454
|
+
for (const segmentId of segmentOrder) {
|
|
455
|
+
const group = grouped.get(segmentId);
|
|
456
|
+
if (md) lines.push(`- Segment ${segmentId}`);
|
|
457
|
+
else lines.push(`Segment ${segmentId}`);
|
|
458
|
+
for (const m of group) {
|
|
459
|
+
const text = renderSurfaceWithWiki(
|
|
460
|
+
mentionText(m, tokenById, options),
|
|
461
|
+
getWikiEvidenceForSurface('mention', m.id)
|
|
462
|
+
);
|
|
463
|
+
const headSurface = tokenById.get(m.head_token_id).surface;
|
|
464
|
+
let line = `${m.span.start}-${m.span.end} ${m.kind} ${text} (head=${headSurface})`;
|
|
465
|
+
if (options.debugIds) {
|
|
466
|
+
line += ` id=${m.id} head_token_id=${m.head_token_id} token_ids=[${m.token_ids.join(',')}]`;
|
|
467
|
+
}
|
|
468
|
+
lines.push(md ? ` - ${line}` : line);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function renderAssertionsCompact(rows, options, lines, md, getWikiEvidenceForSurface) {
|
|
474
|
+
const slotOrder = ['actor', 'theme', 'attr', 'topic', 'location', 'other'];
|
|
475
|
+
for (const row of rows) {
|
|
476
|
+
const slotValues = {};
|
|
477
|
+
for (const slot of slotOrder) {
|
|
478
|
+
if (slot !== 'other') {
|
|
479
|
+
slotValues[slot] = row.slotValues[slot].map((v) =>
|
|
480
|
+
renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))
|
|
481
|
+
);
|
|
482
|
+
} else {
|
|
483
|
+
slotValues.other = row.slotValues.other.map(
|
|
484
|
+
(o) =>
|
|
485
|
+
`${o.role}:${o.values
|
|
486
|
+
.map((v) => renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id)))
|
|
487
|
+
.join('|')}`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const pred = renderSurfaceWithWiki(row.predText, getWikiEvidenceForSurface('predicate', row.assertion.id));
|
|
492
|
+
let line = `pred=${pred} actor=${slotValues.actor.join('|')} theme=${slotValues.theme.join('|')} attr=${slotValues.attr.join('|')} topic=${slotValues.topic.join('|')} location=${slotValues.location.join('|')} other=${slotValues.other.join(';')} ops=${row.opsText}`;
|
|
493
|
+
if (options.debugIds) {
|
|
494
|
+
line = `id=${row.assertion.id} predicate_mention_id=${row.assertion.predicate.mention_id} ` + line;
|
|
495
|
+
}
|
|
496
|
+
lines.push(md ? `- ${line}` : line);
|
|
497
|
+
const evidenceLine = `evidence: ${row.evidenceFootnote}`;
|
|
498
|
+
lines.push(md ? ` - ${evidenceLine}` : ` ${evidenceLine}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function renderAssertionsReadable(rows, options, lines, md, getWikiEvidenceForSurface) {
|
|
503
|
+
for (const row of rows) {
|
|
504
|
+
const predBase = renderSurfaceWithWiki(row.predText, getWikiEvidenceForSurface('predicate', row.assertion.id));
|
|
505
|
+
const predicateQuality = (((row || {}).assertion || {}).diagnostics || {}).predicate_quality;
|
|
506
|
+
const pred = predicateQuality === 'low' ? `${predBase} (predicate_quality=low)` : predBase;
|
|
507
|
+
const suffix = options.debugIds ? ` [id=${row.assertion.id} predicate_mention_id=${row.assertion.predicate.mention_id}]` : '';
|
|
508
|
+
if (md) {
|
|
509
|
+
lines.push(`- Assertion ${row.ordinal}: ${pred}${suffix}`);
|
|
510
|
+
lines.push(` - pred: ${pred}`);
|
|
511
|
+
const orderedSlots = ['actor', 'theme', 'attr', 'topic', 'location'];
|
|
512
|
+
for (const slot of orderedSlots) {
|
|
513
|
+
const values = row.slotValues[slot].map((v) =>
|
|
514
|
+
renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))
|
|
515
|
+
);
|
|
516
|
+
if (values.length > 0) lines.push(` - ${slot}: ${values.join(', ')}`);
|
|
517
|
+
}
|
|
518
|
+
if (row.slotValues.other.length > 0) {
|
|
519
|
+
lines.push(' - other:');
|
|
520
|
+
for (const other of row.slotValues.other) {
|
|
521
|
+
const values = other.values.map((v) =>
|
|
522
|
+
renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))
|
|
523
|
+
);
|
|
524
|
+
lines.push(` - ${other.role}: ${values.join(', ')}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (row.opsText) lines.push(` - ops: ${row.opsText}`);
|
|
528
|
+
lines.push(` - evidence: ${row.evidenceFootnote}`);
|
|
529
|
+
} else {
|
|
530
|
+
lines.push(`Assertion ${row.ordinal}: ${pred}${suffix}`);
|
|
531
|
+
lines.push(`pred: ${pred}`);
|
|
532
|
+
const orderedSlots = ['actor', 'theme', 'attr', 'topic', 'location'];
|
|
533
|
+
for (const slot of orderedSlots) {
|
|
534
|
+
const values = row.slotValues[slot].map((v) =>
|
|
535
|
+
renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))
|
|
536
|
+
);
|
|
537
|
+
if (values.length > 0) lines.push(`${slot}: ${values.join(', ')}`);
|
|
538
|
+
}
|
|
539
|
+
if (row.slotValues.other.length > 0) {
|
|
540
|
+
lines.push('other:');
|
|
541
|
+
for (const other of row.slotValues.other) {
|
|
542
|
+
const values = other.values.map((v) =>
|
|
543
|
+
renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))
|
|
544
|
+
);
|
|
545
|
+
lines.push(` - ${other.role}: ${values.join(', ')}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (row.opsText) lines.push(`ops: ${row.opsText}`);
|
|
549
|
+
lines.push(`evidence: ${row.evidenceFootnote}`);
|
|
550
|
+
lines.push('');
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (!md && lines[lines.length - 1] === '') lines.pop();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function renderAssertionsTable(rows, options, lines, getWikiEvidenceForSurface) {
|
|
557
|
+
const initialColumns = [];
|
|
558
|
+
if (options.debugIds) initialColumns.push({ key: 'assertion_id', title: 'assertion_id', always: true });
|
|
559
|
+
initialColumns.push({ key: 'segment_id', title: 'segment_id' });
|
|
560
|
+
initialColumns.push({ key: 'actor', title: 'actor' });
|
|
561
|
+
initialColumns.push({ key: 'predicate', title: 'predicate', always: true });
|
|
562
|
+
initialColumns.push({ key: 'theme', title: 'theme' });
|
|
563
|
+
initialColumns.push({ key: 'attr', title: 'attr' });
|
|
564
|
+
initialColumns.push({ key: 'topic', title: 'topic' });
|
|
565
|
+
initialColumns.push({ key: 'location', title: 'location' });
|
|
566
|
+
initialColumns.push({ key: 'other', title: 'other' });
|
|
567
|
+
initialColumns.push({ key: 'ops', title: 'ops' });
|
|
568
|
+
initialColumns.push({ key: 'evidence', title: 'evidence', always: true });
|
|
569
|
+
|
|
570
|
+
const tableRows = rows
|
|
571
|
+
.slice()
|
|
572
|
+
.sort((a, b) => {
|
|
573
|
+
const segCmp = compareSegmentIdsNatural(a && a.assertion && a.assertion.segment_id, b && b.assertion && b.assertion.segment_id);
|
|
574
|
+
if (segCmp !== 0) return segCmp;
|
|
575
|
+
return Number((a && a.ordinal) || 0) - Number((b && b.ordinal) || 0);
|
|
576
|
+
})
|
|
577
|
+
.map((row) => ({
|
|
578
|
+
assertion_id: row.assertion.id,
|
|
579
|
+
segment_id: row.assertion.segment_id || '',
|
|
580
|
+
predicate: (() => {
|
|
581
|
+
const base = renderSurfaceWithWiki(row.predText, getWikiEvidenceForSurface('predicate', row.assertion.id));
|
|
582
|
+
const predicateQuality = (((row || {}).assertion || {}).diagnostics || {}).predicate_quality;
|
|
583
|
+
return predicateQuality === 'low' ? `${base} (predicate_quality=low)` : base;
|
|
584
|
+
})(),
|
|
585
|
+
actor: row.slotValues.actor.map((v) => renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))).join(', '),
|
|
586
|
+
theme: row.slotValues.theme.map((v) => renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))).join(', '),
|
|
587
|
+
attr: row.slotValues.attr.map((v) => renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))).join(', '),
|
|
588
|
+
topic: row.slotValues.topic.map((v) => renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))).join(', '),
|
|
589
|
+
location: row.slotValues.location.map((v) => renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))).join(', '),
|
|
590
|
+
other: row.slotValues.other.map((o) => `${o.role}:${o.values.map((v) => renderSurfaceWithWiki(v.text, getWikiEvidenceForSurface('slot', v.mention_id))).join(', ')}`).join(' ; '),
|
|
591
|
+
ops: row.opsText,
|
|
592
|
+
evidence: row.evidenceFootnote,
|
|
593
|
+
}));
|
|
594
|
+
|
|
595
|
+
const columns = initialColumns.filter((col) => col.always || tableRows.some((r) => (r[col.key] || '') !== ''));
|
|
596
|
+
const header = '| ' + columns.map((c) => c.title).join(' | ') + ' |';
|
|
597
|
+
const sep = '| ' + columns.map(() => '---').join(' | ') + ' |';
|
|
598
|
+
lines.push(header);
|
|
599
|
+
lines.push(sep);
|
|
600
|
+
for (const row of tableRows) {
|
|
601
|
+
lines.push('| ' + columns.map((c) => String(row[c.key] || '').replace(/\|/g, '\\|')).join(' | ') + ' |');
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function joinMeaningMentions(mentionIds, refs, options, getWikiEvidenceForSurface) {
|
|
605
|
+
const { mentionById, tokenById } = refs;
|
|
606
|
+
const ids = (mentionIds || []).slice().sort((a, b) => a.localeCompare(b));
|
|
607
|
+
return ids
|
|
608
|
+
.map((mid) => {
|
|
609
|
+
const m = mentionById.get(mid);
|
|
610
|
+
if (!m) return '';
|
|
611
|
+
const base = mentionText(m, tokenById, options);
|
|
612
|
+
return renderSurfaceWithWiki(base, getWikiEvidenceForSurface('slot', mid));
|
|
613
|
+
})
|
|
614
|
+
.filter(Boolean);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function joinMeaningMentionsWithWiki(mentionIds, refs, options, getWikiEvidenceForSurface) {
|
|
618
|
+
const values = joinMeaningMentions(mentionIds, refs, options, getWikiEvidenceForSurface);
|
|
619
|
+
const hasWiki = values.some((x) => x.includes('|wiki:exact⟧') || x.includes('|wiki:prefix⟧'));
|
|
620
|
+
return {
|
|
621
|
+
text: values.join(', '),
|
|
622
|
+
hasWiki,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function meaningGroup(assertion, predText) {
|
|
627
|
+
const ops = Array.isArray(assertion && assertion.operators) ? assertion.operators : [];
|
|
628
|
+
if (ops.some((o) => o && o.kind === 'coordination_group')) return 'Coordinated Actions';
|
|
629
|
+
const slots = toViewSlotsFromRoles(assertion);
|
|
630
|
+
const hasAttr = Array.isArray(slots?.attr) && slots.attr.length > 0;
|
|
631
|
+
const p = String(predText || '').toLowerCase();
|
|
632
|
+
if (hasAttr || p === 'is' || p === 'are' || p === 'was' || p === 'were') return 'Definitions';
|
|
633
|
+
if (ops.some((o) => o && o.kind === 'modality' && String(o.value || '').toLowerCase() === 'can') || p.includes('want')) return 'Capabilities';
|
|
634
|
+
if (ops.some((o) => o && (o.kind === 'control_inherit_subject' || o.kind === 'control_propagation')) || p === 'need' || p === 'needs' || p === 'must') return 'Requirements';
|
|
635
|
+
return 'Actions';
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function renderAssertionsMeaning(data, options, refs, lines, md, getWikiEvidenceForSurface) {
|
|
639
|
+
const { mentionById, tokenById } = refs;
|
|
640
|
+
const assertions = (data.assertions || [])
|
|
641
|
+
.slice()
|
|
642
|
+
.sort((a, b) => assertionSortKey(a, mentionById, tokenById).localeCompare(assertionSortKey(b, mentionById, tokenById)));
|
|
643
|
+
const grouped = new Map();
|
|
644
|
+
for (const a of assertions) {
|
|
645
|
+
const predMention = mentionById.get(a.predicate.mention_id);
|
|
646
|
+
const predText = predMention ? mentionText(predMention, tokenById, options) : '';
|
|
647
|
+
const predEvidence = getWikiEvidenceForSurface('predicate', a.id);
|
|
648
|
+
const predRendered = renderSurfaceWithWiki(predText, predEvidence);
|
|
649
|
+
const predHasWiki = predRendered.includes('|wiki:');
|
|
650
|
+
const actor = {
|
|
651
|
+
text: (toViewSlotsFromRoles(a)?.actor || [])
|
|
652
|
+
.slice()
|
|
653
|
+
.sort((x, y) => x.localeCompare(y))
|
|
654
|
+
.map((mid) => {
|
|
655
|
+
const m = mentionById.get(mid);
|
|
656
|
+
return m ? renderSurfaceWithWiki(mentionText(m, tokenById, options), getWikiEvidenceForSurface('slot', mid)) : '';
|
|
657
|
+
})
|
|
658
|
+
.filter(Boolean)
|
|
659
|
+
.join(', '),
|
|
660
|
+
};
|
|
661
|
+
const slots = toViewSlotsFromRoles(a);
|
|
662
|
+
const theme = joinMeaningMentionsWithWiki(slots?.theme || [], refs, options, getWikiEvidenceForSurface);
|
|
663
|
+
const attr = joinMeaningMentionsWithWiki(slots?.attr || [], refs, options, getWikiEvidenceForSurface);
|
|
664
|
+
const location = joinMeaningMentionsWithWiki(slots?.location || [], refs, options, getWikiEvidenceForSurface);
|
|
665
|
+
const item = {
|
|
666
|
+
assertionId: a.id,
|
|
667
|
+
actor: actor.text,
|
|
668
|
+
predicate: predRendered,
|
|
669
|
+
theme: theme.text,
|
|
670
|
+
attr: attr.text,
|
|
671
|
+
location: location.text,
|
|
672
|
+
hasWiki: Boolean(predHasWiki || theme.hasWiki || attr.hasWiki || location.hasWiki),
|
|
673
|
+
deemphasizeCopula: (predText === 'is' || predText === 'are' || predText === 'was' || predText === 'were') && Array.isArray(slots?.attr) && slots.attr.length > 0,
|
|
674
|
+
evidenceFootnote: buildAssertionEvidenceFootnote(a),
|
|
675
|
+
};
|
|
676
|
+
const g = meaningGroup(a, predText);
|
|
677
|
+
if (!grouped.has(g)) grouped.set(g, []);
|
|
678
|
+
grouped.get(g).push(item);
|
|
679
|
+
}
|
|
680
|
+
const order = ['Definitions', 'Capabilities', 'Requirements', 'Coordinated Actions', 'Actions'];
|
|
681
|
+
for (const g of order) {
|
|
682
|
+
const items = grouped.get(g) || [];
|
|
683
|
+
if (items.length === 0) continue;
|
|
684
|
+
if (md) lines.push(`\n### ${g}`);
|
|
685
|
+
else lines.push(`\n${g}`);
|
|
686
|
+
lines.push(md ? '- Actor | Predicate | Theme | Attr | Location | wiki⁺' : 'Actor | Predicate | Theme | Attr | Location | wiki⁺');
|
|
687
|
+
for (const it of items) {
|
|
688
|
+
const pred = it.deemphasizeCopula ? `(copula:${it.predicate})` : it.predicate;
|
|
689
|
+
const line = `${it.actor || '-'} | ${pred || '-'} | ${it.theme || '-'} | ${it.attr || '-'} | ${it.location || '-'} | ${it.hasWiki ? 'wiki✓' : '-'}`;
|
|
690
|
+
lines.push(md ? `- ${line}${options.debugIds ? ` (${it.assertionId})` : ''}` : `${line}${options.debugIds ? ` (${it.assertionId})` : ''}`);
|
|
691
|
+
lines.push(md ? ` - evidence: ${it.evidenceFootnote}` : ` evidence: ${it.evidenceFootnote}`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function renderAssertions(data, options, refs, lines, md) {
|
|
697
|
+
const getWikiEvidenceForSurface = createWikiEvidenceResolver(data);
|
|
698
|
+
if (options.layout === 'meaning') {
|
|
699
|
+
renderAssertionsMeaning(data, options, refs, lines, md, getWikiEvidenceForSurface);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const rows = buildAssertionRows(data, refs, options);
|
|
703
|
+
if (options.layout === 'compact') {
|
|
704
|
+
renderAssertionsCompact(rows, options, lines, md, getWikiEvidenceForSurface);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (options.layout === 'readable') {
|
|
708
|
+
renderAssertionsReadable(rows, options, lines, md, getWikiEvidenceForSurface);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
renderAssertionsTable(rows, options, lines, getWikiEvidenceForSurface);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function renderSuppressedAssertions(data, options, lines, md) {
|
|
715
|
+
if (!options.debugIds) return;
|
|
716
|
+
const suppressed = Array.isArray(((data || {}).diagnostics || {}).suppressed_assertions)
|
|
717
|
+
? data.diagnostics.suppressed_assertions
|
|
718
|
+
: [];
|
|
719
|
+
if (suppressed.length === 0) return;
|
|
720
|
+
const sorted = suppressed.slice().sort((a, b) => String(a.id || '').localeCompare(String(b.id || '')));
|
|
721
|
+
if (md) lines.push('\n### Suppressed Assertions');
|
|
722
|
+
else lines.push('\nSuppressed Assertions');
|
|
723
|
+
for (const s of sorted) {
|
|
724
|
+
const sb = (((s || {}).diagnostics || {}).suppressed_by) || {};
|
|
725
|
+
const upstreamIds = Array.isArray((((sb || {}).evidence || {}).upstream_relation_ids))
|
|
726
|
+
? sb.evidence.upstream_relation_ids
|
|
727
|
+
: [];
|
|
728
|
+
const line =
|
|
729
|
+
`id=${s.id} kind=${sb.kind || ''} target_assertion_id=${sb.target_assertion_id || ''} ` +
|
|
730
|
+
`reason=${sb.reason || ''} upstream_relation_ids_len=${upstreamIds.length}`;
|
|
731
|
+
lines.push(md ? `- ${line}` : line);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function render(data, options, refs) {
|
|
736
|
+
const { tokenById, mentionById } = refs;
|
|
737
|
+
const getWikiEvidenceForSurface = createWikiEvidenceResolver(data);
|
|
738
|
+
const lines = [];
|
|
739
|
+
const md = options.format === 'md';
|
|
740
|
+
|
|
741
|
+
if (md) lines.push('# Elementary Assertions');
|
|
742
|
+
else lines.push('Elementary Assertions');
|
|
743
|
+
|
|
744
|
+
if (options.segments) {
|
|
745
|
+
const segments = sortedSegments(data);
|
|
746
|
+
if (md) lines.push('\n## Segments');
|
|
747
|
+
else lines.push('\nSegments');
|
|
748
|
+
for (const s of segments) {
|
|
749
|
+
const rawSlice = normalizeRenderText(data.canonical_text.slice(s.span.start, s.span.end));
|
|
750
|
+
const slice = normalizeSegmentDisplay(rawSlice, options);
|
|
751
|
+
if (md) {
|
|
752
|
+
lines.push(`- Segment ${s.id}`);
|
|
753
|
+
lines.push(` - SegmentText: "${slice}"`);
|
|
754
|
+
} else {
|
|
755
|
+
lines.push(`Segment ${s.id}`);
|
|
756
|
+
lines.push(`SegmentText: "${slice}"`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (options.mentions) {
|
|
762
|
+
renderMentions(data, options, refs, lines, md, getWikiEvidenceForSurface);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (md) lines.push('\n## Assertions');
|
|
766
|
+
else lines.push('\nAssertions');
|
|
767
|
+
renderAssertions(data, options, refs, lines, md);
|
|
768
|
+
renderSuppressedAssertions(data, options, lines, md);
|
|
769
|
+
|
|
770
|
+
if (options.coverage) {
|
|
771
|
+
if (md) lines.push('\n## Coverage');
|
|
772
|
+
else lines.push('\nCoverage');
|
|
773
|
+
const c = data.coverage || {};
|
|
774
|
+
const p = Array.isArray(c.primary_mention_ids) ? c.primary_mention_ids : [];
|
|
775
|
+
const covered = Array.isArray(c.covered_primary_mention_ids) ? c.covered_primary_mention_ids : [];
|
|
776
|
+
const uncovered = Array.isArray(c.uncovered_primary_mention_ids) ? c.uncovered_primary_mention_ids : [];
|
|
777
|
+
lines.push(md ? `- primary_mention_ids count: ${p.length}` : `primary_mention_ids count: ${p.length}`);
|
|
778
|
+
lines.push(md ? `- covered_primary_mention_ids count: ${covered.length}` : `covered_primary_mention_ids count: ${covered.length}`);
|
|
779
|
+
lines.push(md ? `- uncovered_primary_mention_ids count: ${uncovered.length}` : `uncovered_primary_mention_ids count: ${uncovered.length}`);
|
|
780
|
+
|
|
781
|
+
const unresolved = Array.isArray(c.unresolved) ? c.unresolved : [];
|
|
782
|
+
const unresolvedByMention = new Map();
|
|
783
|
+
for (const u of unresolved) {
|
|
784
|
+
if (!u || typeof u.mention_id !== 'string') continue;
|
|
785
|
+
const prev = unresolvedByMention.get(u.mention_id);
|
|
786
|
+
const prevKey = prev ? `${prev.kind || ''}|${prev.reason || ''}` : '';
|
|
787
|
+
const curKey = `${u.kind || ''}|${u.reason || ''}`;
|
|
788
|
+
if (!prev || curKey.localeCompare(prevKey) < 0) {
|
|
789
|
+
unresolvedByMention.set(u.mention_id, u);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const usedMentionIds = collectUsedMentionIdsFromAssertions(data.assertions || []);
|
|
793
|
+
const usedMentions = sortMentionIdsForCoverage(Array.from(usedMentionIds), mentionById)
|
|
794
|
+
.map((id) => mentionById.get(id))
|
|
795
|
+
.filter(Boolean);
|
|
796
|
+
const strictlyUncovered = [];
|
|
797
|
+
const containedUncovered = [];
|
|
798
|
+
const uncoveredSorted = sortMentionIdsForCoverage(uncovered, mentionById);
|
|
799
|
+
for (const mid of uncoveredSorted) {
|
|
800
|
+
const m = mentionById.get(mid);
|
|
801
|
+
if (!m) continue;
|
|
802
|
+
const mTokenSet = new Set((m.token_ids || []).map((t) => String(t)));
|
|
803
|
+
const containerIds = [];
|
|
804
|
+
for (const cm of usedMentions) {
|
|
805
|
+
if (!cm || cm.id === mid) continue;
|
|
806
|
+
const cTokenSet = new Set((cm.token_ids || []).map((t) => String(t)));
|
|
807
|
+
let contained = true;
|
|
808
|
+
for (const tid of mTokenSet) {
|
|
809
|
+
if (!cTokenSet.has(tid)) {
|
|
810
|
+
contained = false;
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
if (contained) containerIds.push(cm.id);
|
|
815
|
+
}
|
|
816
|
+
containerIds.sort((a, b) => a.localeCompare(b));
|
|
817
|
+
if (containerIds.length > 0) {
|
|
818
|
+
containedUncovered.push({ mention_id: mid, mention: m, contained_in: containerIds });
|
|
819
|
+
} else {
|
|
820
|
+
strictlyUncovered.push({ mention_id: mid, mention: m, contained_in: [] });
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (md) lines.push('\n### Strictly Uncovered Primary Mentions');
|
|
825
|
+
else lines.push('\nStrictly Uncovered Primary Mentions');
|
|
826
|
+
for (const item of strictlyUncovered) {
|
|
827
|
+
const m = item.mention;
|
|
828
|
+
const u = unresolvedByMention.get(item.mention_id);
|
|
829
|
+
const reason = String((u && u.reason) || 'unknown');
|
|
830
|
+
const text = renderSurfaceWithWiki(
|
|
831
|
+
mentionText(m, tokenById, options),
|
|
832
|
+
getWikiEvidenceForSurface('mention', m.id)
|
|
833
|
+
);
|
|
834
|
+
const line = `${text} (mention_id=${m.id}, reason=${reason})`;
|
|
835
|
+
lines.push(md ? `- ${line}` : line);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (md) lines.push('\n### Contained Uncovered Primary Mentions');
|
|
839
|
+
else lines.push('\nContained Uncovered Primary Mentions');
|
|
840
|
+
for (const item of containedUncovered) {
|
|
841
|
+
const m = item.mention;
|
|
842
|
+
const u = unresolvedByMention.get(item.mention_id);
|
|
843
|
+
const reason = String((u && u.reason) || 'unknown');
|
|
844
|
+
const text = renderSurfaceWithWiki(
|
|
845
|
+
mentionText(m, tokenById, options),
|
|
846
|
+
getWikiEvidenceForSurface('mention', m.id)
|
|
847
|
+
);
|
|
848
|
+
const line = `${text} (mention_id=${m.id}, contained_in=[${item.contained_in.join(',')}], reason=${reason})`;
|
|
849
|
+
lines.push(md ? `- ${line}` : line);
|
|
850
|
+
}
|
|
851
|
+
if (options.renderUncoveredDelta) {
|
|
852
|
+
if (md) lines.push('\n### Uncovered Primary Mentions Summary');
|
|
853
|
+
else lines.push('\nUncovered Primary Mentions Summary');
|
|
854
|
+
lines.push(md ? `- strictly_uncovered_count: ${strictlyUncovered.length}` : `strictly_uncovered_count: ${strictlyUncovered.length}`);
|
|
855
|
+
lines.push(md ? `- contained_uncovered_count: ${containedUncovered.length}` : `contained_uncovered_count: ${containedUncovered.length}`);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (md) lines.push('\n### Unresolved');
|
|
859
|
+
else lines.push('\nUnresolved');
|
|
860
|
+
const groups = new Map();
|
|
861
|
+
for (const u of unresolved) {
|
|
862
|
+
const key = `${u.kind}|${u.reason}`;
|
|
863
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
864
|
+
groups.get(key).push(u);
|
|
865
|
+
}
|
|
866
|
+
const gkeys = Array.from(groups.keys()).sort();
|
|
867
|
+
for (const key of gkeys) {
|
|
868
|
+
const [kind, reason] = key.split('|');
|
|
869
|
+
lines.push(md ? `- ${kind} / ${reason}` : `${kind} / ${reason}`);
|
|
870
|
+
const entries = groups.get(key).slice().sort((a, b) => {
|
|
871
|
+
if (a.segment_id !== b.segment_id) return a.segment_id.localeCompare(b.segment_id);
|
|
872
|
+
return a.mention_id.localeCompare(b.mention_id);
|
|
873
|
+
});
|
|
874
|
+
for (const u of entries) {
|
|
875
|
+
const m = mentionById.get(u.mention_id);
|
|
876
|
+
if (!m) continue;
|
|
877
|
+
const unresolvedText = renderSurfaceWithWiki(
|
|
878
|
+
mentionText(m, tokenById, options),
|
|
879
|
+
getWikiEvidenceForSurface('mention', m.id)
|
|
880
|
+
);
|
|
881
|
+
let line = `${unresolvedText} reason=${u.reason}`;
|
|
882
|
+
if (options.debugIds) {
|
|
883
|
+
const tokenIds = Array.isArray((u.evidence || {}).token_ids) ? u.evidence.token_ids : [];
|
|
884
|
+
const tids = tokenIds.join(',');
|
|
885
|
+
const span = (u.evidence || {}).span ? `${u.evidence.span.start}-${u.evidence.span.end}` : '';
|
|
886
|
+
const mentionIds = Array.isArray(u.mention_ids) ? u.mention_ids : [u.mention_id];
|
|
887
|
+
const upstreamIds = Array.isArray((u.evidence || {}).upstream_relation_ids) ? u.evidence.upstream_relation_ids : [];
|
|
888
|
+
const upstreamPart =
|
|
889
|
+
options.layout === 'meaning'
|
|
890
|
+
? ` upstream_relation_ids=[${upstreamIds.join(',')}]`
|
|
891
|
+
: ` upstream_relation_ids_len=${upstreamIds.length}`;
|
|
892
|
+
const tokenPart =
|
|
893
|
+
options.layout === 'meaning'
|
|
894
|
+
? ` token_ids=[${tids}] token_ids_len=${tokenIds.length}`
|
|
895
|
+
: ` token_ids_len=${tokenIds.length}`;
|
|
896
|
+
line += ` segment_id=${u.segment_id} mention_id=${u.mention_id} mention_ids=[${mentionIds.join(',')}]${tokenPart} span=${span}${upstreamPart}`;
|
|
897
|
+
}
|
|
898
|
+
lines.push(md ? ` - ${line}` : ` ${line}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return lines.join('\n') + '\n';
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
function renderElementaryAssertions(data, options = {}) {
|
|
908
|
+
rejectLegacySlots(data);
|
|
909
|
+
validateElementaryAssertions(data);
|
|
910
|
+
const format = options.format || "txt";
|
|
911
|
+
const layout = options.layout || "compact";
|
|
912
|
+
if (format !== "txt" && format !== "md") throw new Error("Invalid value for format: expected txt|md");
|
|
913
|
+
if (!["compact", "readable", "table", "meaning"].includes(layout)) throw new Error("Invalid value for layout: expected compact|readable|table|meaning");
|
|
914
|
+
const normalizedOptions = {
|
|
915
|
+
format,
|
|
916
|
+
layout,
|
|
917
|
+
segments: options.segments === undefined ? true : Boolean(options.segments),
|
|
918
|
+
mentions: options.mentions === undefined ? true : Boolean(options.mentions),
|
|
919
|
+
coverage: options.coverage === undefined ? true : Boolean(options.coverage),
|
|
920
|
+
debugIds: options.debugIds === undefined ? false : Boolean(options.debugIds),
|
|
921
|
+
normalizeDeterminers: options.normalizeDeterminers === undefined ? true : Boolean(options.normalizeDeterminers),
|
|
922
|
+
renderUncoveredDelta: options.renderUncoveredDelta === undefined ? false : Boolean(options.renderUncoveredDelta),
|
|
923
|
+
};
|
|
924
|
+
const refs = buildIntegrity(data);
|
|
925
|
+
return render(data, normalizedOptions, refs);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
module.exports = {
|
|
929
|
+
renderElementaryAssertions,
|
|
930
|
+
};
|
|
931
|
+
|