@postalsys/email-content 0.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/LICENSE +21 -0
- package/README.md +288 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/analyze.d.ts +91 -0
- package/dist/analyze.d.ts.map +1 -0
- package/dist/analyze.js +274 -0
- package/dist/analyze.js.map +1 -0
- package/dist/annotate/client.d.ts +64 -0
- package/dist/annotate/client.d.ts.map +1 -0
- package/dist/annotate/client.js +139 -0
- package/dist/annotate/client.js.map +1 -0
- package/dist/annotate/llm.d.ts +46 -0
- package/dist/annotate/llm.d.ts.map +1 -0
- package/dist/annotate/llm.js +107 -0
- package/dist/annotate/llm.js.map +1 -0
- package/dist/annotate/prompt.d.ts +38 -0
- package/dist/annotate/prompt.d.ts.map +1 -0
- package/dist/annotate/prompt.js +165 -0
- package/dist/annotate/prompt.js.map +1 -0
- package/dist/annotate/store.d.ts +43 -0
- package/dist/annotate/store.d.ts.map +1 -0
- package/dist/annotate/store.js +46 -0
- package/dist/annotate/store.js.map +1 -0
- package/dist/canonical/html.d.ts +33 -0
- package/dist/canonical/html.d.ts.map +1 -0
- package/dist/canonical/html.js +156 -0
- package/dist/canonical/html.js.map +1 -0
- package/dist/canonical/text.d.ts +75 -0
- package/dist/canonical/text.d.ts.map +1 -0
- package/dist/canonical/text.js +217 -0
- package/dist/canonical/text.js.map +1 -0
- package/dist/classify/confidence.d.ts +38 -0
- package/dist/classify/confidence.d.ts.map +1 -0
- package/dist/classify/confidence.js +59 -0
- package/dist/classify/confidence.js.map +1 -0
- package/dist/classify/reconcile.d.ts +52 -0
- package/dist/classify/reconcile.d.ts.map +1 -0
- package/dist/classify/reconcile.js +100 -0
- package/dist/classify/reconcile.js.map +1 -0
- package/dist/classify/rules.d.ts +51 -0
- package/dist/classify/rules.d.ts.map +1 -0
- package/dist/classify/rules.js +648 -0
- package/dist/classify/rules.js.map +1 -0
- package/dist/cli/annotate.d.ts +23 -0
- package/dist/cli/annotate.d.ts.map +1 -0
- package/dist/cli/annotate.js +328 -0
- package/dist/cli/annotate.js.map +1 -0
- package/dist/cli/args.d.ts +17 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +72 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/benchmark.d.ts +13 -0
- package/dist/cli/benchmark.d.ts.map +1 -0
- package/dist/cli/benchmark.js +172 -0
- package/dist/cli/benchmark.js.map +1 -0
- package/dist/cli/corpus.d.ts +9 -0
- package/dist/cli/corpus.d.ts.map +1 -0
- package/dist/cli/corpus.js +152 -0
- package/dist/cli/corpus.js.map +1 -0
- package/dist/cli/disagreements.d.ts +14 -0
- package/dist/cli/disagreements.d.ts.map +1 -0
- package/dist/cli/disagreements.js +133 -0
- package/dist/cli/disagreements.js.map +1 -0
- package/dist/cli/evaluate.d.ts +15 -0
- package/dist/cli/evaluate.d.ts.map +1 -0
- package/dist/cli/evaluate.js +215 -0
- package/dist/cli/evaluate.js.map +1 -0
- package/dist/cli/extract.d.ts +10 -0
- package/dist/cli/extract.d.ts.map +1 -0
- package/dist/cli/extract.js +139 -0
- package/dist/cli/extract.js.map +1 -0
- package/dist/cli/main.d.ts +9 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +147 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/corpus/analyze.d.ts +66 -0
- package/dist/corpus/analyze.d.ts.map +1 -0
- package/dist/corpus/analyze.js +214 -0
- package/dist/corpus/analyze.js.map +1 -0
- package/dist/corpus/migrations/index.d.ts +14 -0
- package/dist/corpus/migrations/index.d.ts.map +1 -0
- package/dist/corpus/migrations/index.js +158 -0
- package/dist/corpus/migrations/index.js.map +1 -0
- package/dist/corpus/sample.d.ts +66 -0
- package/dist/corpus/sample.d.ts.map +1 -0
- package/dist/corpus/sample.js +98 -0
- package/dist/corpus/sample.js.map +1 -0
- package/dist/corpus/scanner.d.ts +79 -0
- package/dist/corpus/scanner.d.ts.map +1 -0
- package/dist/corpus/scanner.js +256 -0
- package/dist/corpus/scanner.js.map +1 -0
- package/dist/corpus/stats.d.ts +76 -0
- package/dist/corpus/stats.d.ts.map +1 -0
- package/dist/corpus/stats.js +240 -0
- package/dist/corpus/stats.js.map +1 -0
- package/dist/corpus/store.d.ts +67 -0
- package/dist/corpus/store.d.ts.map +1 -0
- package/dist/corpus/store.js +196 -0
- package/dist/corpus/store.js.map +1 -0
- package/dist/corpus/thread-index.d.ts +31 -0
- package/dist/corpus/thread-index.d.ts.map +1 -0
- package/dist/corpus/thread-index.js +182 -0
- package/dist/corpus/thread-index.js.map +1 -0
- package/dist/corpus/worker.d.ts +21 -0
- package/dist/corpus/worker.d.ts.map +1 -0
- package/dist/corpus/worker.js +74 -0
- package/dist/corpus/worker.js.map +1 -0
- package/dist/detect/client.d.ts +28 -0
- package/dist/detect/client.d.ts.map +1 -0
- package/dist/detect/client.js +121 -0
- package/dist/detect/client.js.map +1 -0
- package/dist/detect/disclaimer.d.ts +12 -0
- package/dist/detect/disclaimer.d.ts.map +1 -0
- package/dist/detect/disclaimer.js +28 -0
- package/dist/detect/disclaimer.js.map +1 -0
- package/dist/detect/forward-header.d.ts +10 -0
- package/dist/detect/forward-header.d.ts.map +1 -0
- package/dist/detect/forward-header.js +35 -0
- package/dist/detect/forward-header.js.map +1 -0
- package/dist/detect/patterns/disclaimers.json +113 -0
- package/dist/detect/patterns/forward-headers.json +40 -0
- package/dist/detect/patterns/html-clients.json +157 -0
- package/dist/detect/patterns/quoted-headers.json +41 -0
- package/dist/detect/patterns/reply-headers.json +139 -0
- package/dist/detect/patterns/signatures.json +58 -0
- package/dist/detect/patterns.d.ts +43 -0
- package/dist/detect/patterns.d.ts.map +1 -0
- package/dist/detect/patterns.js +125 -0
- package/dist/detect/patterns.js.map +1 -0
- package/dist/detect/reply-header.d.ts +44 -0
- package/dist/detect/reply-header.d.ts.map +1 -0
- package/dist/detect/reply-header.js +134 -0
- package/dist/detect/reply-header.js.map +1 -0
- package/dist/detect/signature.d.ts +28 -0
- package/dist/detect/signature.d.ts.map +1 -0
- package/dist/detect/signature.js +77 -0
- package/dist/detect/signature.js.map +1 -0
- package/dist/detect/traffic.d.ts +26 -0
- package/dist/detect/traffic.d.ts.map +1 -0
- package/dist/detect/traffic.js +98 -0
- package/dist/detect/traffic.js.map +1 -0
- package/dist/eval/metrics.d.ts +87 -0
- package/dist/eval/metrics.d.ts.map +1 -0
- package/dist/eval/metrics.js +185 -0
- package/dist/eval/metrics.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +163 -0
- package/dist/index.js.map +1 -0
- package/dist/match/history.d.ts +62 -0
- package/dist/match/history.d.ts.map +1 -0
- package/dist/match/history.js +128 -0
- package/dist/match/history.js.map +1 -0
- package/dist/match/normalize.d.ts +33 -0
- package/dist/match/normalize.d.ts.map +1 -0
- package/dist/match/normalize.js +63 -0
- package/dist/match/normalize.js.map +1 -0
- package/dist/match/shingle.d.ts +44 -0
- package/dist/match/shingle.d.ts.map +1 -0
- package/dist/match/shingle.js +78 -0
- package/dist/match/shingle.js.map +1 -0
- package/dist/mime/charset.d.ts +33 -0
- package/dist/mime/charset.d.ts.map +1 -0
- package/dist/mime/charset.js +118 -0
- package/dist/mime/charset.js.map +1 -0
- package/dist/mime/headers.d.ts +32 -0
- package/dist/mime/headers.d.ts.map +1 -0
- package/dist/mime/headers.js +127 -0
- package/dist/mime/headers.js.map +1 -0
- package/dist/mime/model.d.ts +111 -0
- package/dist/mime/model.d.ts.map +1 -0
- package/dist/mime/model.js +17 -0
- package/dist/mime/model.js.map +1 -0
- package/dist/mime/parse.d.ts +27 -0
- package/dist/mime/parse.d.ts.map +1 -0
- package/dist/mime/parse.js +343 -0
- package/dist/mime/parse.js.map +1 -0
- package/dist/segment/features.d.ts +29 -0
- package/dist/segment/features.d.ts.map +1 -0
- package/dist/segment/features.js +102 -0
- package/dist/segment/features.js.map +1 -0
- package/dist/segment/html-blocks.d.ts +28 -0
- package/dist/segment/html-blocks.d.ts.map +1 -0
- package/dist/segment/html-blocks.js +596 -0
- package/dist/segment/html-blocks.js.map +1 -0
- package/dist/segment/text-blocks.d.ts +30 -0
- package/dist/segment/text-blocks.d.ts.map +1 -0
- package/dist/segment/text-blocks.js +204 -0
- package/dist/segment/text-blocks.js.map +1 -0
- package/dist/segment/types.d.ts +88 -0
- package/dist/segment/types.d.ts.map +1 -0
- package/dist/segment/types.js +11 -0
- package/dist/segment/types.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/util/hash.d.ts +49 -0
- package/dist/util/hash.d.ts.map +1 -0
- package/dist/util/hash.js +0 -0
- package/dist/util/hash.js.map +1 -0
- package/dist/util/lang.d.ts +25 -0
- package/dist/util/lang.d.ts.map +1 -0
- package/dist/util/lang.js +135 -0
- package/dist/util/lang.js.map +1 -0
- package/dist/util/worker-pool.d.ts +51 -0
- package/dist/util/worker-pool.d.ts.map +1 -0
- package/dist/util/worker-pool.js +155 -0
- package/dist/util/worker-pool.js.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Aggregate corpus statistics.
|
|
4
|
+
*
|
|
5
|
+
* Everything here is a count, a ratio or a percentile. No subject, address or body text is read,
|
|
6
|
+
* and the only free text that reaches the report is a MIME structure signature and detector ids,
|
|
7
|
+
* both of which are drawn from a closed vocabulary. That is what makes the report safe to paste
|
|
8
|
+
* into a document or an issue.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.computeStats = computeStats;
|
|
12
|
+
exports.renderReport = renderReport;
|
|
13
|
+
function toDistribution(rows, total, limit = 25) {
|
|
14
|
+
return rows
|
|
15
|
+
.filter(row => row.count > 0)
|
|
16
|
+
.sort((a, b) => b.count - a.count)
|
|
17
|
+
.slice(0, limit)
|
|
18
|
+
.map(row => ({
|
|
19
|
+
label: row.label === null || row.label === '' ? '(none)' : String(row.label),
|
|
20
|
+
count: row.count,
|
|
21
|
+
share: total ? row.count / total : 0
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Expand a comma separated column into per token counts.
|
|
26
|
+
*
|
|
27
|
+
* Grouping in SQL first keeps this cheap: a few thousand distinct combinations are fetched instead
|
|
28
|
+
* of one row per message.
|
|
29
|
+
*/
|
|
30
|
+
function expandMultiValue(store, column, total, limit = 30) {
|
|
31
|
+
const rows = store.query(`SELECT ${column} AS value, COUNT(*) AS count FROM messages GROUP BY ${column}`);
|
|
32
|
+
const counts = new Map();
|
|
33
|
+
for (const row of rows) {
|
|
34
|
+
const raw = row.value || '';
|
|
35
|
+
if (!raw) {
|
|
36
|
+
counts.set('(none)', (counts.get('(none)') || 0) + row.count);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
for (const token of raw.split(',')) {
|
|
40
|
+
const key = token.trim();
|
|
41
|
+
if (key) {
|
|
42
|
+
counts.set(key, (counts.get(key) || 0) + row.count);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return toDistribution([...counts].map(([label, count]) => ({ label, count })), total, limit);
|
|
47
|
+
}
|
|
48
|
+
function groupCounts(store, column, table = 'messages') {
|
|
49
|
+
return store.query(`SELECT ${column} AS label, COUNT(*) AS count FROM ${table} GROUP BY ${column}`);
|
|
50
|
+
}
|
|
51
|
+
function percentile(store, fraction, total) {
|
|
52
|
+
if (!total) {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
const offset = Math.min(total - 1, Math.max(0, Math.floor(total * fraction)));
|
|
56
|
+
const row = store.queryOne('SELECT size FROM files ORDER BY size LIMIT 1 OFFSET ?', offset);
|
|
57
|
+
return row?.size ?? 0;
|
|
58
|
+
}
|
|
59
|
+
function computeStats(store) {
|
|
60
|
+
const fileTotal = store.queryOne('SELECT COUNT(*) AS count FROM files')?.count ?? 0;
|
|
61
|
+
const statusRows = groupCounts(store, 'status', 'files');
|
|
62
|
+
const statusOf = (status) => statusRows.find(row => row.label === status)?.count ?? 0;
|
|
63
|
+
const messageTotal = store.queryOne('SELECT COUNT(*) AS count FROM messages')?.count ?? 0;
|
|
64
|
+
const withMessageId = store.queryOne("SELECT COUNT(*) AS count FROM messages WHERE message_id IS NOT NULL AND message_id != ''")?.count ?? 0;
|
|
65
|
+
const withInReplyTo = store.queryOne('SELECT COUNT(*) AS count FROM messages WHERE in_reply_to IS NOT NULL')?.count ?? 0;
|
|
66
|
+
const parentResolved = store.queryOne('SELECT COUNT(*) AS count FROM messages m WHERE m.in_reply_to IS NOT NULL AND EXISTS (SELECT 1 FROM messages p WHERE p.message_id = m.in_reply_to)')?.count ?? 0;
|
|
67
|
+
const withReferences = store.queryOne('SELECT COUNT(*) AS count FROM messages WHERE references_count > 0')?.count ?? 0;
|
|
68
|
+
const ancestorResolved = store.queryOne('SELECT COUNT(*) AS count FROM messages m WHERE m.references_root IS NOT NULL AND EXISTS (SELECT 1 FROM messages p WHERE p.message_id = m.references_root)')?.count ?? 0;
|
|
69
|
+
const humanTotal = store.queryOne("SELECT COUNT(*) AS count FROM messages WHERE traffic = 'human'")?.count ?? 0;
|
|
70
|
+
const humanReplies = store.queryOne("SELECT COUNT(*) AS count FROM messages WHERE traffic = 'human' AND is_reply = 1")?.count ?? 0;
|
|
71
|
+
const humanRepliesResolved = store.queryOne("SELECT COUNT(*) AS count FROM messages m WHERE m.traffic = 'human' AND m.in_reply_to IS NOT NULL AND EXISTS (SELECT 1 FROM messages p WHERE p.message_id = m.in_reply_to)")?.count ?? 0;
|
|
72
|
+
const humanBoth = store.queryOne("SELECT COUNT(*) AS count FROM messages WHERE traffic = 'human' AND body_kind = 'both'")?.count ?? 0;
|
|
73
|
+
const humanDisagree = store.queryOne("SELECT COUNT(*) AS count FROM messages WHERE traffic = 'human' AND quote_marking_disagrees = 1")?.count ?? 0;
|
|
74
|
+
const distinctSha = store.queryOne('SELECT COUNT(DISTINCT sha256) AS count FROM files WHERE sha256 IS NOT NULL')?.count ?? 0;
|
|
75
|
+
const inDupGroups = store.queryOne('SELECT COALESCE(SUM(c), 0) AS count FROM (SELECT COUNT(*) AS c FROM files WHERE sha256 IS NOT NULL GROUP BY sha256 HAVING c > 1)')?.count ?? 0;
|
|
76
|
+
const distinctSimhash = store.queryOne("SELECT COUNT(DISTINCT body_simhash) AS count FROM messages WHERE body_simhash IS NOT NULL AND body_simhash != '0000000000000000'")?.count ?? 0;
|
|
77
|
+
const inNearDupGroups = store.queryOne("SELECT COALESCE(SUM(c), 0) AS count FROM (SELECT COUNT(*) AS c FROM messages WHERE body_simhash IS NOT NULL AND body_simhash != '0000000000000000' GROUP BY body_simhash HAVING c > 1)")?.count ?? 0;
|
|
78
|
+
const bothRepresentations = store.queryOne("SELECT COUNT(*) AS count FROM messages WHERE body_kind = 'both'")?.count ?? 0;
|
|
79
|
+
const disagree = store.queryOne('SELECT COUNT(*) AS count FROM messages WHERE quote_marking_disagrees = 1')?.count ?? 0;
|
|
80
|
+
const totalBytes = store.queryOne('SELECT COALESCE(SUM(size), 0) AS total FROM files')?.total ?? 0;
|
|
81
|
+
return {
|
|
82
|
+
generatedAt: new Date().toISOString(),
|
|
83
|
+
files: {
|
|
84
|
+
total: fileTotal,
|
|
85
|
+
ok: statusOf('ok'),
|
|
86
|
+
failed: statusOf('failed'),
|
|
87
|
+
oversize: statusOf('oversize'),
|
|
88
|
+
timeout: statusOf('timeout'),
|
|
89
|
+
skipped: statusOf('skipped')
|
|
90
|
+
},
|
|
91
|
+
errors: toDistribution(groupCounts(store, 'error_code', 'files').filter(row => row.label), fileTotal),
|
|
92
|
+
sizeBytes: {
|
|
93
|
+
p10: percentile(store, 0.1, fileTotal),
|
|
94
|
+
p50: percentile(store, 0.5, fileTotal),
|
|
95
|
+
p90: percentile(store, 0.9, fileTotal),
|
|
96
|
+
p99: percentile(store, 0.99, fileTotal),
|
|
97
|
+
max: store.queryOne('SELECT MAX(size) AS size FROM files')?.size ?? 0,
|
|
98
|
+
total: totalBytes
|
|
99
|
+
},
|
|
100
|
+
parseWarnings: expandMultiValue(store, 'warnings', messageTotal),
|
|
101
|
+
bodyKind: toDistribution(groupCounts(store, 'body_kind'), messageTotal),
|
|
102
|
+
structures: toDistribution(groupCounts(store, 'structure'), messageTotal, 25),
|
|
103
|
+
charsets: expandMultiValue(store, 'charsets', messageTotal, 20),
|
|
104
|
+
encodings: expandMultiValue(store, 'encodings', messageTotal, 12),
|
|
105
|
+
traffic: toDistribution(groupCounts(store, 'traffic'), messageTotal),
|
|
106
|
+
clients: toDistribution(groupCounts(store, 'client'), messageTotal, 25),
|
|
107
|
+
clientSources: toDistribution(groupCounts(store, 'client_source'), messageTotal),
|
|
108
|
+
subjectPrefix: toDistribution(groupCounts(store, 'subject_prefix'), messageTotal),
|
|
109
|
+
replyStyle: toDistribution(groupCounts(store, 'reply_style'), messageTotal),
|
|
110
|
+
quoteDepth: toDistribution(groupCounts(store, 'quote_depth_max'), messageTotal, 10),
|
|
111
|
+
markers: expandMultiValue(store, 'markers', messageTotal, 30),
|
|
112
|
+
htmlHints: expandMultiValue(store, 'html_hints', messageTotal, 25),
|
|
113
|
+
languages: toDistribution(groupCounts(store, 'lang'), messageTotal, 15),
|
|
114
|
+
duplicates: {
|
|
115
|
+
distinctSha256: distinctSha,
|
|
116
|
+
filesInDuplicateGroups: inDupGroups,
|
|
117
|
+
distinctSimhash,
|
|
118
|
+
filesInNearDuplicateGroups: inNearDupGroups
|
|
119
|
+
},
|
|
120
|
+
threading: {
|
|
121
|
+
withMessageId,
|
|
122
|
+
withInReplyTo,
|
|
123
|
+
parentResolvedInCorpus: parentResolved,
|
|
124
|
+
withReferences,
|
|
125
|
+
ancestorResolvedInCorpus: ancestorResolved
|
|
126
|
+
},
|
|
127
|
+
humanSlice: {
|
|
128
|
+
total: humanTotal,
|
|
129
|
+
replies: humanReplies,
|
|
130
|
+
repliesWithParentInCorpus: humanRepliesResolved,
|
|
131
|
+
bothRepresentations: humanBoth,
|
|
132
|
+
quoteMarkingDisagrees: humanDisagree
|
|
133
|
+
},
|
|
134
|
+
contentAgreement: {
|
|
135
|
+
messagesWithBoth: bothRepresentations,
|
|
136
|
+
quoteMarkingDisagrees: disagree
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function pct(value, total) {
|
|
141
|
+
if (!total) {
|
|
142
|
+
return '0.0%';
|
|
143
|
+
}
|
|
144
|
+
return `${((100 * value) / total).toFixed(1)}%`;
|
|
145
|
+
}
|
|
146
|
+
function bytes(value) {
|
|
147
|
+
if (value >= 1024 * 1024 * 1024) {
|
|
148
|
+
return `${(value / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
149
|
+
}
|
|
150
|
+
if (value >= 1024 * 1024) {
|
|
151
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
152
|
+
}
|
|
153
|
+
if (value >= 1024) {
|
|
154
|
+
return `${(value / 1024).toFixed(1)} KB`;
|
|
155
|
+
}
|
|
156
|
+
return `${value} B`;
|
|
157
|
+
}
|
|
158
|
+
function table(title, rows) {
|
|
159
|
+
if (!rows.length) {
|
|
160
|
+
return `### ${title}\n\nNo data.\n`;
|
|
161
|
+
}
|
|
162
|
+
const lines = ['### ' + title, '', '| value | count | share |', '| --- | ---: | ---: |'];
|
|
163
|
+
for (const row of rows) {
|
|
164
|
+
lines.push(`| \`${row.label.replace(/\|/g, '\\|')}\` | ${row.count.toLocaleString('en-US')} | ${(row.share * 100).toFixed(1)}% |`);
|
|
165
|
+
}
|
|
166
|
+
lines.push('');
|
|
167
|
+
return lines.join('\n');
|
|
168
|
+
}
|
|
169
|
+
/** Render the statistics as a Markdown report. Safe to share: contains no message content. */
|
|
170
|
+
function renderReport(stats, root) {
|
|
171
|
+
const files = stats.files;
|
|
172
|
+
const messages = stats.files.ok;
|
|
173
|
+
const out = [];
|
|
174
|
+
out.push('# Corpus report', '');
|
|
175
|
+
out.push(`Generated ${stats.generatedAt} from \`${root}\`.`, '');
|
|
176
|
+
out.push('This report contains aggregate statistics only. No subject lines, addresses or body text are', 'stored or displayed. Detector names come from a closed vocabulary defined in', '`src/detect/patterns/`.', '');
|
|
177
|
+
out.push('## Files', '');
|
|
178
|
+
out.push('| metric | value |', '| --- | ---: |');
|
|
179
|
+
out.push(`| files scanned | ${files.total.toLocaleString('en-US')} |`);
|
|
180
|
+
out.push(`| parsed successfully | ${files.ok.toLocaleString('en-US')} (${pct(files.ok, files.total)}) |`);
|
|
181
|
+
out.push(`| parse failures | ${files.failed.toLocaleString('en-US')} (${pct(files.failed, files.total)}) |`);
|
|
182
|
+
out.push(`| oversize, not parsed | ${files.oversize.toLocaleString('en-US')} |`);
|
|
183
|
+
out.push(`| timed out | ${files.timeout.toLocaleString('en-US')} |`);
|
|
184
|
+
out.push(`| total bytes | ${bytes(stats.sizeBytes.total)} |`);
|
|
185
|
+
out.push(`| size p10 / p50 / p90 / p99 / max | ${bytes(stats.sizeBytes.p10)} / ${bytes(stats.sizeBytes.p50)} / ${bytes(stats.sizeBytes.p90)} / ${bytes(stats.sizeBytes.p99)} / ${bytes(stats.sizeBytes.max)} |`);
|
|
186
|
+
out.push('');
|
|
187
|
+
if (stats.errors.length) {
|
|
188
|
+
out.push(table('Failure reasons', stats.errors));
|
|
189
|
+
}
|
|
190
|
+
out.push('## Threading', '');
|
|
191
|
+
out.push('| metric | value | share |', '| --- | ---: | ---: |');
|
|
192
|
+
out.push(`| messages with Message-ID | ${stats.threading.withMessageId.toLocaleString('en-US')} | ${pct(stats.threading.withMessageId, messages)} |`);
|
|
193
|
+
out.push(`| messages with In-Reply-To | ${stats.threading.withInReplyTo.toLocaleString('en-US')} | ${pct(stats.threading.withInReplyTo, messages)} |`);
|
|
194
|
+
out.push(`| **parent present in corpus** | ${stats.threading.parentResolvedInCorpus.toLocaleString('en-US')} | ${pct(stats.threading.parentResolvedInCorpus, stats.threading.withInReplyTo)} of replies |`);
|
|
195
|
+
out.push(`| messages with References | ${stats.threading.withReferences.toLocaleString('en-US')} | ${pct(stats.threading.withReferences, messages)} |`);
|
|
196
|
+
out.push(`| thread root present in corpus | ${stats.threading.ancestorResolvedInCorpus.toLocaleString('en-US')} | ${pct(stats.threading.ancestorResolvedInCorpus, stats.threading.withReferences)} of those |`);
|
|
197
|
+
out.push('');
|
|
198
|
+
out.push('Parent resolution is what makes distant supervision possible: when the parent message is in', 'the corpus, any text the extractor hides can be checked against text that demonstrably', 'existed before, without a human labelling it.', '');
|
|
199
|
+
out.push('## Human conversation slice', '');
|
|
200
|
+
out.push('| metric | value | share |', '| --- | ---: | ---: |');
|
|
201
|
+
out.push(`| classified human | ${stats.humanSlice.total.toLocaleString('en-US')} | ${pct(stats.humanSlice.total, messages)} of all |`);
|
|
202
|
+
out.push(`| human replies | ${stats.humanSlice.replies.toLocaleString('en-US')} | ${pct(stats.humanSlice.replies, stats.humanSlice.total)} of human |`);
|
|
203
|
+
out.push(`| human replies with parent in corpus | ${stats.humanSlice.repliesWithParentInCorpus.toLocaleString('en-US')} | ${pct(stats.humanSlice.repliesWithParentInCorpus, stats.humanSlice.replies)} of human replies |`);
|
|
204
|
+
out.push(`| human, both plain and HTML | ${stats.humanSlice.bothRepresentations.toLocaleString('en-US')} | ${pct(stats.humanSlice.bothRepresentations, stats.humanSlice.total)} |`);
|
|
205
|
+
out.push(`| human, plain and HTML disagree on quoting | ${stats.humanSlice.quoteMarkingDisagrees.toLocaleString('en-US')} | ${pct(stats.humanSlice.quoteMarkingDisagrees, stats.humanSlice.bothRepresentations)} of those |`);
|
|
206
|
+
out.push('');
|
|
207
|
+
out.push('## Duplicates', '');
|
|
208
|
+
out.push('| metric | value |', '| --- | ---: |');
|
|
209
|
+
out.push(`| distinct file hashes | ${stats.duplicates.distinctSha256.toLocaleString('en-US')} of ${files.total.toLocaleString('en-US')} |`);
|
|
210
|
+
out.push(`| files in exact duplicate groups | ${stats.duplicates.filesInDuplicateGroups.toLocaleString('en-US')} |`);
|
|
211
|
+
out.push(`| distinct body SimHashes | ${stats.duplicates.distinctSimhash.toLocaleString('en-US')} |`);
|
|
212
|
+
out.push(`| files sharing a body SimHash | ${stats.duplicates.filesInNearDuplicateGroups.toLocaleString('en-US')} |`);
|
|
213
|
+
out.push('');
|
|
214
|
+
out.push('SimHash collisions are a lower bound on near duplicates: only identical hashes are counted here, not small Hamming distances.', '');
|
|
215
|
+
out.push('## Structure', '');
|
|
216
|
+
out.push(table('Body composition', stats.bodyKind));
|
|
217
|
+
out.push(table('MIME structures', stats.structures));
|
|
218
|
+
out.push(table('Charsets', stats.charsets));
|
|
219
|
+
out.push(table('Transfer encodings', stats.encodings));
|
|
220
|
+
out.push(table('Parse warnings', stats.parseWarnings));
|
|
221
|
+
out.push('## Traffic and clients', '');
|
|
222
|
+
out.push(table('Traffic class', stats.traffic));
|
|
223
|
+
out.push(table('Composing client', stats.clients));
|
|
224
|
+
out.push(table('Client evidence source', stats.clientSources));
|
|
225
|
+
out.push('## Reply structure', '');
|
|
226
|
+
out.push(table('Subject prefix', stats.subjectPrefix));
|
|
227
|
+
out.push(table('Plain text reply style', stats.replyStyle));
|
|
228
|
+
out.push(table('Maximum quote depth', stats.quoteDepth));
|
|
229
|
+
out.push(table('Line level markers detected', stats.markers));
|
|
230
|
+
out.push(table('HTML structure hints', stats.htmlHints));
|
|
231
|
+
out.push('## Language', '');
|
|
232
|
+
out.push(table('Estimated language', stats.languages));
|
|
233
|
+
out.push('## Plain text and HTML agreement', '');
|
|
234
|
+
out.push(`Messages carrying both representations: ${stats.contentAgreement.messagesWithBoth.toLocaleString('en-US')}.`, '');
|
|
235
|
+
out.push(`Quote marking disagrees between the two in ${stats.contentAgreement.quoteMarkingDisagrees.toLocaleString('en-US')} of them ` +
|
|
236
|
+
`(${pct(stats.contentAgreement.quoteMarkingDisagrees, stats.contentAgreement.messagesWithBoth)}).`, '');
|
|
237
|
+
out.push('Disagreement is the norm rather than the exception, and it is almost always the plain part', 'that has lost the quote markers. That is why HTML is the primary representation when both', 'are present.', '');
|
|
238
|
+
return out.join('\n');
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/corpus/stats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus metadata store.
|
|
3
|
+
*
|
|
4
|
+
* node:sqlite is used rather than better-sqlite3 or DuckDB: it is built into Node 24, so there is
|
|
5
|
+
* no native build step and no lockfile that can stop working in two years. The corpus is a few
|
|
6
|
+
* hundred thousand rows of small metadata, which is well within its comfortable range.
|
|
7
|
+
*
|
|
8
|
+
* The store holds derived metadata only. Raw messages stay on disk, referenced by path, and are
|
|
9
|
+
* never copied. See docs/threat-model.md for what the derived data can still reveal.
|
|
10
|
+
*/
|
|
11
|
+
import { type MessageRecord } from './analyze';
|
|
12
|
+
export type FileStatus = 'ok' | 'failed' | 'skipped' | 'oversize' | 'timeout';
|
|
13
|
+
export interface FileRecord {
|
|
14
|
+
path: string;
|
|
15
|
+
size: number;
|
|
16
|
+
mtime: number;
|
|
17
|
+
sha256: string | null;
|
|
18
|
+
status: FileStatus;
|
|
19
|
+
errorCode: string | null;
|
|
20
|
+
errorDetail: string | null;
|
|
21
|
+
durationMs: number | null;
|
|
22
|
+
}
|
|
23
|
+
export interface ScanRow {
|
|
24
|
+
file: FileRecord;
|
|
25
|
+
message: MessageRecord | null;
|
|
26
|
+
}
|
|
27
|
+
export interface RunOptions {
|
|
28
|
+
root: string;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
export declare class CorpusStore {
|
|
32
|
+
private readonly db;
|
|
33
|
+
private readonly insertFileStmt;
|
|
34
|
+
private readonly insertMessageStmt;
|
|
35
|
+
private readonly seenStmt;
|
|
36
|
+
/** Per database salt for privacy preserving hashes. Created on first open. */
|
|
37
|
+
readonly salt: string;
|
|
38
|
+
constructor(file: string);
|
|
39
|
+
private migrate;
|
|
40
|
+
private readOrCreateSalt;
|
|
41
|
+
getMeta(key: string): string | null;
|
|
42
|
+
setMeta(key: string, value: string): void;
|
|
43
|
+
startRun(options: RunOptions): number;
|
|
44
|
+
finishRun(runId: number, status: 'completed' | 'interrupted' | 'failed', counts: {
|
|
45
|
+
seen: number;
|
|
46
|
+
processed: number;
|
|
47
|
+
failed: number;
|
|
48
|
+
skipped: number;
|
|
49
|
+
}): void;
|
|
50
|
+
/**
|
|
51
|
+
* True when this exact file has already been processed by the current processing version.
|
|
52
|
+
* Resume relies on size and mtime rather than a hash, because hashing every file would mean
|
|
53
|
+
* reading the whole corpus again just to decide what to skip.
|
|
54
|
+
*/
|
|
55
|
+
isProcessed(relPath: string, size: number, mtime: number): boolean;
|
|
56
|
+
/** Write a batch inside a single transaction. */
|
|
57
|
+
writeBatch(rows: readonly ScanRow[], runId: number): void;
|
|
58
|
+
/** Execute a statement. Returns the driver result, including lastInsertRowid. */
|
|
59
|
+
run(sql: string, ...params: (string | number | null)[]): import("node:sqlite").StatementResultingChanges;
|
|
60
|
+
/** Run a function inside a transaction, rolling back on any throw. */
|
|
61
|
+
transaction(fn: () => void): void;
|
|
62
|
+
/** Run an arbitrary read only query. Used by the stats and sampling commands. */
|
|
63
|
+
query<T = Record<string, unknown>>(sql: string, ...params: (string | number | null)[]): T[];
|
|
64
|
+
queryOne<T = Record<string, unknown>>(sql: string, ...params: (string | number | null)[]): T | null;
|
|
65
|
+
close(): void;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/corpus/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,OAAO;IACpB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAE1B,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM;IAuDxB,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAKnC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAIzC,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM;IASrC,SAAS,CACL,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,GAAG,aAAa,GAAG,QAAQ,EAC9C,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAC7E,IAAI;IAMP;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAQlE,iDAAiD;IACjD,UAAU,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IA6EzD,iFAAiF;IACjF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE;IAItD,sEAAsE;IACtE,WAAW,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAWjC,iFAAiF;IACjF,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE;IAI3F,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI;IAInG,KAAK,IAAI,IAAI;CAQhB"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Corpus metadata store.
|
|
4
|
+
*
|
|
5
|
+
* node:sqlite is used rather than better-sqlite3 or DuckDB: it is built into Node 24, so there is
|
|
6
|
+
* no native build step and no lockfile that can stop working in two years. The corpus is a few
|
|
7
|
+
* hundred thousand rows of small metadata, which is well within its comfortable range.
|
|
8
|
+
*
|
|
9
|
+
* The store holds derived metadata only. Raw messages stay on disk, referenced by path, and are
|
|
10
|
+
* never copied. See docs/threat-model.md for what the derived data can still reveal.
|
|
11
|
+
*/
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.CorpusStore = void 0;
|
|
17
|
+
const node_sqlite_1 = require("node:sqlite");
|
|
18
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
19
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
+
const types_1 = require("../types");
|
|
21
|
+
const hash_1 = require("../util/hash");
|
|
22
|
+
const migrations_1 = require("./migrations");
|
|
23
|
+
class CorpusStore {
|
|
24
|
+
db;
|
|
25
|
+
insertFileStmt;
|
|
26
|
+
insertMessageStmt;
|
|
27
|
+
seenStmt;
|
|
28
|
+
/** Per database salt for privacy preserving hashes. Created on first open. */
|
|
29
|
+
salt;
|
|
30
|
+
constructor(file) {
|
|
31
|
+
const dir = node_path_1.default.dirname(node_path_1.default.resolve(file));
|
|
32
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
33
|
+
this.db = new node_sqlite_1.DatabaseSync(file);
|
|
34
|
+
this.db.exec('PRAGMA journal_mode = WAL');
|
|
35
|
+
this.db.exec('PRAGMA synchronous = NORMAL');
|
|
36
|
+
this.db.exec('PRAGMA foreign_keys = ON');
|
|
37
|
+
this.migrate();
|
|
38
|
+
this.salt = this.readOrCreateSalt();
|
|
39
|
+
this.insertFileStmt = this.db.prepare(`
|
|
40
|
+
INSERT INTO files (path, size, mtime, sha256, status, error_code, error_detail, duration_ms, scanned_at, run_id, processing_version)
|
|
41
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
42
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
43
|
+
size = excluded.size, mtime = excluded.mtime, sha256 = excluded.sha256,
|
|
44
|
+
status = excluded.status, error_code = excluded.error_code, error_detail = excluded.error_detail,
|
|
45
|
+
duration_ms = excluded.duration_ms, scanned_at = excluded.scanned_at, run_id = excluded.run_id,
|
|
46
|
+
processing_version = excluded.processing_version
|
|
47
|
+
`);
|
|
48
|
+
this.insertMessageStmt = this.db.prepare(`
|
|
49
|
+
INSERT INTO messages (
|
|
50
|
+
path, message_id, in_reply_to, references_count, references_root, date_ms,
|
|
51
|
+
from_domain, from_hash, subject_hash, subject_prefix, subject_length,
|
|
52
|
+
structure, body_kind, charsets, encodings, part_count, attachment_count, has_rfc822,
|
|
53
|
+
text_chars, html_chars, flowed, client, client_source, traffic, traffic_signals,
|
|
54
|
+
is_reply, is_forward, quote_depth_max, quoted_ratio, reply_style, markers, html_hints,
|
|
55
|
+
blockquote_count, table_count, has_image, suspect_malformed_html, lang, lang_score,
|
|
56
|
+
body_simhash, warnings, quote_marking_disagrees
|
|
57
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
58
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
59
|
+
message_id = excluded.message_id, in_reply_to = excluded.in_reply_to,
|
|
60
|
+
references_count = excluded.references_count, references_root = excluded.references_root,
|
|
61
|
+
date_ms = excluded.date_ms, from_domain = excluded.from_domain, from_hash = excluded.from_hash,
|
|
62
|
+
subject_hash = excluded.subject_hash, subject_prefix = excluded.subject_prefix,
|
|
63
|
+
subject_length = excluded.subject_length, structure = excluded.structure,
|
|
64
|
+
body_kind = excluded.body_kind, charsets = excluded.charsets, encodings = excluded.encodings,
|
|
65
|
+
part_count = excluded.part_count, attachment_count = excluded.attachment_count,
|
|
66
|
+
has_rfc822 = excluded.has_rfc822, text_chars = excluded.text_chars, html_chars = excluded.html_chars,
|
|
67
|
+
flowed = excluded.flowed, client = excluded.client, client_source = excluded.client_source,
|
|
68
|
+
traffic = excluded.traffic, traffic_signals = excluded.traffic_signals, is_reply = excluded.is_reply,
|
|
69
|
+
is_forward = excluded.is_forward, quote_depth_max = excluded.quote_depth_max,
|
|
70
|
+
quoted_ratio = excluded.quoted_ratio, reply_style = excluded.reply_style, markers = excluded.markers,
|
|
71
|
+
html_hints = excluded.html_hints, blockquote_count = excluded.blockquote_count,
|
|
72
|
+
table_count = excluded.table_count, has_image = excluded.has_image,
|
|
73
|
+
suspect_malformed_html = excluded.suspect_malformed_html, lang = excluded.lang,
|
|
74
|
+
lang_score = excluded.lang_score, body_simhash = excluded.body_simhash,
|
|
75
|
+
warnings = excluded.warnings, quote_marking_disagrees = excluded.quote_marking_disagrees
|
|
76
|
+
`);
|
|
77
|
+
this.seenStmt = this.db.prepare('SELECT size, mtime, processing_version FROM files WHERE path = ?');
|
|
78
|
+
}
|
|
79
|
+
migrate() {
|
|
80
|
+
const current = Number(this.db.prepare('PRAGMA user_version').get()?.user_version ?? 0);
|
|
81
|
+
for (const migration of migrations_1.MIGRATIONS) {
|
|
82
|
+
if (migration.version <= current) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
this.db.exec('BEGIN');
|
|
86
|
+
try {
|
|
87
|
+
this.db.exec(migration.sql);
|
|
88
|
+
this.db.exec(`PRAGMA user_version = ${migration.version}`);
|
|
89
|
+
this.db.exec('COMMIT');
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
this.db.exec('ROLLBACK');
|
|
93
|
+
throw new Error(`migration ${migration.version} (${migration.name}) failed`, { cause: err });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
readOrCreateSalt() {
|
|
98
|
+
const row = this.db.prepare('SELECT value FROM meta WHERE key = ?').get('salt');
|
|
99
|
+
if (row?.value) {
|
|
100
|
+
return row.value;
|
|
101
|
+
}
|
|
102
|
+
const salt = (0, hash_1.newSalt)();
|
|
103
|
+
this.db.prepare('INSERT INTO meta (key, value) VALUES (?, ?)').run('salt', salt);
|
|
104
|
+
return salt;
|
|
105
|
+
}
|
|
106
|
+
getMeta(key) {
|
|
107
|
+
const row = this.db.prepare('SELECT value FROM meta WHERE key = ?').get(key);
|
|
108
|
+
return row?.value ?? null;
|
|
109
|
+
}
|
|
110
|
+
setMeta(key, value) {
|
|
111
|
+
this.db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);
|
|
112
|
+
}
|
|
113
|
+
startRun(options) {
|
|
114
|
+
const stmt = this.db.prepare(`
|
|
115
|
+
INSERT INTO runs (root, started_at, status, processing_version, extractor_version, options_json)
|
|
116
|
+
VALUES (?, ?, 'running', ?, ?, ?)
|
|
117
|
+
`);
|
|
118
|
+
const result = stmt.run(options.root, Date.now(), types_1.PROCESSING_VERSION, types_1.EXTRACTOR_VERSION, JSON.stringify(options));
|
|
119
|
+
return Number(result.lastInsertRowid);
|
|
120
|
+
}
|
|
121
|
+
finishRun(runId, status, counts) {
|
|
122
|
+
this.db
|
|
123
|
+
.prepare('UPDATE runs SET finished_at = ?, status = ?, files_seen = ?, files_processed = ?, files_failed = ?, files_skipped = ? WHERE id = ?')
|
|
124
|
+
.run(Date.now(), status, counts.seen, counts.processed, counts.failed, counts.skipped, runId);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* True when this exact file has already been processed by the current processing version.
|
|
128
|
+
* Resume relies on size and mtime rather than a hash, because hashing every file would mean
|
|
129
|
+
* reading the whole corpus again just to decide what to skip.
|
|
130
|
+
*/
|
|
131
|
+
isProcessed(relPath, size, mtime) {
|
|
132
|
+
const row = this.seenStmt.get(relPath);
|
|
133
|
+
if (!row) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return row.size === size && row.mtime === mtime && row.processing_version === types_1.PROCESSING_VERSION;
|
|
137
|
+
}
|
|
138
|
+
/** Write a batch inside a single transaction. */
|
|
139
|
+
writeBatch(rows, runId) {
|
|
140
|
+
if (!rows.length) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.db.exec('BEGIN');
|
|
144
|
+
try {
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
for (const row of rows) {
|
|
147
|
+
const file = row.file;
|
|
148
|
+
this.insertFileStmt.run(file.path, file.size, file.mtime, file.sha256, file.status, file.errorCode, file.errorDetail, file.durationMs, now, runId, types_1.PROCESSING_VERSION);
|
|
149
|
+
const record = row.message;
|
|
150
|
+
if (!record) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
this.insertMessageStmt.run(file.path, record.messageId, record.inReplyTo, record.referencesCount, record.referencesRoot, record.dateMs, record.fromDomain, record.fromHash, record.subjectHash, record.subjectPrefix, record.subjectLength, record.structure, record.bodyKind, record.charsets, record.encodings, record.partCount, record.attachmentCount, record.hasRfc822 ? 1 : 0, record.textChars, record.htmlChars, record.flowed ? 1 : 0, record.client, record.clientSource, record.traffic, record.trafficSignals, record.isReply ? 1 : 0, record.isForward ? 1 : 0, record.quoteDepthMax, record.quotedRatio, record.replyStyle, record.markers, record.htmlHints, record.blockquoteCount, record.tableCount, record.hasImage ? 1 : 0, record.suspectMalformedHtml ? 1 : 0, record.lang, record.langScore, record.bodySimhash, record.warnings, record.quoteMarkingDisagrees ? 1 : 0);
|
|
154
|
+
}
|
|
155
|
+
this.db.exec('COMMIT');
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
this.db.exec('ROLLBACK');
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Execute a statement. Returns the driver result, including lastInsertRowid. */
|
|
163
|
+
run(sql, ...params) {
|
|
164
|
+
return this.db.prepare(sql).run(...params);
|
|
165
|
+
}
|
|
166
|
+
/** Run a function inside a transaction, rolling back on any throw. */
|
|
167
|
+
transaction(fn) {
|
|
168
|
+
this.db.exec('BEGIN');
|
|
169
|
+
try {
|
|
170
|
+
fn();
|
|
171
|
+
this.db.exec('COMMIT');
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
this.db.exec('ROLLBACK');
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Run an arbitrary read only query. Used by the stats and sampling commands. */
|
|
179
|
+
query(sql, ...params) {
|
|
180
|
+
return this.db.prepare(sql).all(...params);
|
|
181
|
+
}
|
|
182
|
+
queryOne(sql, ...params) {
|
|
183
|
+
return this.db.prepare(sql).get(...params) ?? null;
|
|
184
|
+
}
|
|
185
|
+
close() {
|
|
186
|
+
try {
|
|
187
|
+
this.db.exec('PRAGMA optimize');
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// Best effort only. A failure here must not mask a successful scan.
|
|
191
|
+
}
|
|
192
|
+
this.db.close();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
exports.CorpusStore = CorpusStore;
|
|
196
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/corpus/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread reconstruction over a scanned corpus.
|
|
3
|
+
*
|
|
4
|
+
* Resolves a message's In-Reply-To and References to files that are actually present, so that
|
|
5
|
+
* Stage 5 has something to match against. Measured over the working corpus, 90.9 percent of replies
|
|
6
|
+
* resolve their direct parent this way.
|
|
7
|
+
*
|
|
8
|
+
* Ancestors are returned nearest first. The direct parent carries most of the value: it is what the
|
|
9
|
+
* sender's client quoted from. Older ancestors matter for deeply nested threads, where the reply
|
|
10
|
+
* contains several generations of history at once.
|
|
11
|
+
*/
|
|
12
|
+
import { type HistoryDocument } from '../match/history';
|
|
13
|
+
import { type MessageHeaders } from '../mime/model';
|
|
14
|
+
import { type CorpusStore } from './store';
|
|
15
|
+
export interface ThreadResolver {
|
|
16
|
+
/** Load history documents for a message, nearest ancestor first. */
|
|
17
|
+
resolve(headers: MessageHeaders, maxAncestors?: number): Promise<HistoryDocument[]>;
|
|
18
|
+
}
|
|
19
|
+
/** How many ancestors to load by default. Each one costs a file read and a parse. */
|
|
20
|
+
export declare const DEFAULT_MAX_ANCESTORS = 3;
|
|
21
|
+
/**
|
|
22
|
+
* Build a resolver backed by a scanned corpus database.
|
|
23
|
+
*
|
|
24
|
+
* The lookup is a single indexed query per candidate Message-ID, so no in memory index of the whole
|
|
25
|
+
* corpus is needed. That matters: a Message-ID map over 320k messages is tens of megabytes that
|
|
26
|
+
* would otherwise be paid for on every process start.
|
|
27
|
+
*/
|
|
28
|
+
export declare function corpusThreadResolver(store: CorpusStore, root: string): ThreadResolver;
|
|
29
|
+
/** Build a resolver from a folder of .eml files, for callers with no corpus database. */
|
|
30
|
+
export declare function folderThreadResolver(folder: string): ThreadResolver;
|
|
31
|
+
//# sourceMappingURL=thread-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-index.d.ts","sourceRoot":"","sources":["../../src/corpus/thread-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC3B,oEAAoE;IACpE,OAAO,CAAC,OAAO,EAAE,cAAc,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACvF;AAED,qFAAqF;AACrF,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,CAqDrF;AAED,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAiEnE"}
|