@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,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `email-content benchmark` subcommand.
|
|
4
|
+
*
|
|
5
|
+
* Two jobs in one pass over a sample of the corpus: measure throughput and memory, and count the
|
|
6
|
+
* catastrophic outcomes that matter more than any average. A benchmark that only reported
|
|
7
|
+
* milliseconds per message would miss the failure this project exists to prevent.
|
|
8
|
+
*
|
|
9
|
+
* The catastrophic checks here are structural and need no labels, so they can run over hundreds of
|
|
10
|
+
* thousands of messages. They are a floor, not a substitute for the labelled evaluation in Phase 4.
|
|
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.benchmarkCommand = benchmarkCommand;
|
|
17
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
18
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
19
|
+
const index_1 = require("../index");
|
|
20
|
+
const store_1 = require("../corpus/store");
|
|
21
|
+
const args_1 = require("./args");
|
|
22
|
+
const ANCHOR_REASONS = new Set([
|
|
23
|
+
'preceded_by_reply_header',
|
|
24
|
+
'preceded_by_forward_header',
|
|
25
|
+
'inside_quote_container',
|
|
26
|
+
'outlook_reply_separator',
|
|
27
|
+
'matched_parent_message',
|
|
28
|
+
'matched_thread_ancestor'
|
|
29
|
+
]);
|
|
30
|
+
function percentile(values, fraction) {
|
|
31
|
+
if (!values.length) {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
const index = Math.min(values.length - 1, Math.max(0, Math.floor(values.length * fraction)));
|
|
35
|
+
return values[index] ?? 0;
|
|
36
|
+
}
|
|
37
|
+
async function benchmarkCommand(args) {
|
|
38
|
+
const dbPath = (0, args_1.flagString)(args, 'db', 'data/corpus.sqlite');
|
|
39
|
+
if (!node_fs_1.default.existsSync(dbPath)) {
|
|
40
|
+
process.stderr.write(`no corpus database at ${dbPath}. Run "corpus scan" first.\n`);
|
|
41
|
+
return 2;
|
|
42
|
+
}
|
|
43
|
+
const limit = (0, args_1.flagNumber)(args, 'sample', 2000);
|
|
44
|
+
const traffic = (0, args_1.flagString)(args, 'traffic', 'human');
|
|
45
|
+
const onlyReplies = (0, args_1.flagString)(args, 'replies', 'true') !== 'false';
|
|
46
|
+
const store = new store_1.CorpusStore(dbPath);
|
|
47
|
+
const root = store.queryOne('SELECT root FROM runs ORDER BY id DESC LIMIT 1')?.root;
|
|
48
|
+
if (!root) {
|
|
49
|
+
process.stderr.write('no completed scan run found\n');
|
|
50
|
+
store.close();
|
|
51
|
+
return 2;
|
|
52
|
+
}
|
|
53
|
+
// Ordering by path keeps the selection deterministic for a given database and filter.
|
|
54
|
+
const where = ["f.status = 'ok'"];
|
|
55
|
+
if (traffic !== 'all') {
|
|
56
|
+
where.push(`m.traffic = '${traffic.replace(/'/g, '')}'`);
|
|
57
|
+
}
|
|
58
|
+
if (onlyReplies) {
|
|
59
|
+
where.push('m.is_reply = 1');
|
|
60
|
+
}
|
|
61
|
+
const rows = store.query(`SELECT m.path AS path FROM messages m JOIN files f ON f.path = m.path WHERE ${where.join(' AND ')} ORDER BY m.path LIMIT ?`, limit);
|
|
62
|
+
store.close();
|
|
63
|
+
if (!rows.length) {
|
|
64
|
+
process.stderr.write('no matching messages\n');
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
const counters = {
|
|
68
|
+
messages: 0,
|
|
69
|
+
errors: 0,
|
|
70
|
+
emptyFromNonEmpty: 0,
|
|
71
|
+
everythingHidden: 0,
|
|
72
|
+
onlySignatureVisible: 0,
|
|
73
|
+
hiddenWithoutAnchor: 0,
|
|
74
|
+
forwardedHidden: 0,
|
|
75
|
+
hiddenChars: 0,
|
|
76
|
+
visibleChars: 0,
|
|
77
|
+
labels: new Map(),
|
|
78
|
+
durations: []
|
|
79
|
+
};
|
|
80
|
+
const rssStart = process.memoryUsage.rss();
|
|
81
|
+
let rssPeak = rssStart;
|
|
82
|
+
const started = Date.now();
|
|
83
|
+
for (const row of rows) {
|
|
84
|
+
let raw;
|
|
85
|
+
try {
|
|
86
|
+
raw = node_fs_1.default.readFileSync(node_path_1.default.join(root, row.path));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
counters.errors++;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const t0 = process.hrtime.bigint();
|
|
93
|
+
let result;
|
|
94
|
+
try {
|
|
95
|
+
result = await (0, index_1.extract)(raw);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
counters.errors++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
counters.durations.push(Number(process.hrtime.bigint() - t0) / 1e6);
|
|
102
|
+
counters.messages++;
|
|
103
|
+
const visible = result.segments;
|
|
104
|
+
const hidden = result.hiddenSegments;
|
|
105
|
+
const visibleChars = visible.reduce((sum, segment) => sum + segment.text.length, 0);
|
|
106
|
+
const hiddenChars = hidden.reduce((sum, segment) => sum + segment.text.length, 0);
|
|
107
|
+
counters.visibleChars += visibleChars;
|
|
108
|
+
counters.hiddenChars += hiddenChars;
|
|
109
|
+
for (const segment of [...visible, ...hidden]) {
|
|
110
|
+
counters.labels.set(segment.type, (counters.labels.get(segment.type) ?? 0) + 1);
|
|
111
|
+
}
|
|
112
|
+
const hadContent = visibleChars + hiddenChars > 0;
|
|
113
|
+
if (hadContent && visibleChars === 0) {
|
|
114
|
+
counters.everythingHidden++;
|
|
115
|
+
}
|
|
116
|
+
if (hadContent && !visible.length) {
|
|
117
|
+
counters.emptyFromNonEmpty++;
|
|
118
|
+
}
|
|
119
|
+
if (visible.length > 0 && visible.every(segment => segment.type === 'SIGNATURE' || segment.type === 'DISCLAIMER')) {
|
|
120
|
+
counters.onlySignatureVisible++;
|
|
121
|
+
}
|
|
122
|
+
for (const segment of hidden) {
|
|
123
|
+
if (!segment.reasons.some(reason => ANCHOR_REASONS.has(reason))) {
|
|
124
|
+
counters.hiddenWithoutAnchor++;
|
|
125
|
+
}
|
|
126
|
+
if (segment.type === 'FORWARDED_CONTENT') {
|
|
127
|
+
counters.forwardedHidden++;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (counters.messages % 250 === 0) {
|
|
131
|
+
rssPeak = Math.max(rssPeak, process.memoryUsage.rss());
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
rssPeak = Math.max(rssPeak, process.memoryUsage.rss());
|
|
135
|
+
const elapsed = Date.now() - started;
|
|
136
|
+
counters.durations.sort((a, b) => a - b);
|
|
137
|
+
const out = [];
|
|
138
|
+
const pct = (value) => `${((100 * value) / Math.max(1, counters.messages)).toFixed(2)}%`;
|
|
139
|
+
out.push(`sample: ${counters.messages} messages (traffic=${traffic}, replies-only=${onlyReplies})`);
|
|
140
|
+
out.push(`errors: ${counters.errors}`);
|
|
141
|
+
out.push('');
|
|
142
|
+
out.push('THROUGHPUT');
|
|
143
|
+
out.push(` total ${(elapsed / 1000).toFixed(1)}s`);
|
|
144
|
+
out.push(` rate ${((counters.messages / elapsed) * 1000).toFixed(0)} messages/s (single threaded)`);
|
|
145
|
+
out.push(` per message p50 ${percentile(counters.durations, 0.5).toFixed(2)} ms`);
|
|
146
|
+
out.push(` per message p90 ${percentile(counters.durations, 0.9).toFixed(2)} ms`);
|
|
147
|
+
out.push(` per message p99 ${percentile(counters.durations, 0.99).toFixed(2)} ms`);
|
|
148
|
+
out.push(` per message max ${percentile(counters.durations, 1).toFixed(2)} ms`);
|
|
149
|
+
out.push(` rss delta ${((rssPeak - rssStart) / (1024 * 1024)).toFixed(0)} MB`);
|
|
150
|
+
out.push('');
|
|
151
|
+
out.push('CONTENT');
|
|
152
|
+
const totalChars = counters.visibleChars + counters.hiddenChars;
|
|
153
|
+
out.push(` visible characters ${counters.visibleChars.toLocaleString('en-US')} (${((100 * counters.visibleChars) / Math.max(1, totalChars)).toFixed(1)}%)`);
|
|
154
|
+
out.push(` hidden characters ${counters.hiddenChars.toLocaleString('en-US')} (${((100 * counters.hiddenChars) / Math.max(1, totalChars)).toFixed(1)}%)`);
|
|
155
|
+
out.push('');
|
|
156
|
+
out.push('SEGMENT LABELS');
|
|
157
|
+
for (const [label, count] of [...counters.labels].sort((a, b) => b[1] - a[1])) {
|
|
158
|
+
out.push(` ${label.padEnd(18)} ${String(count).padStart(7)}`);
|
|
159
|
+
}
|
|
160
|
+
out.push('');
|
|
161
|
+
out.push('CATASTROPHIC CHECKS (structural, no labels required)');
|
|
162
|
+
out.push(` empty extraction from non empty message ${counters.emptyFromNonEmpty} (${pct(counters.emptyFromNonEmpty)})`);
|
|
163
|
+
out.push(` all content hidden ${counters.everythingHidden} (${pct(counters.everythingHidden)})`);
|
|
164
|
+
out.push(` only a footer left visible ${counters.onlySignatureVisible} (${pct(counters.onlySignatureVisible)})`);
|
|
165
|
+
out.push(` hidden without a structural anchor ${counters.hiddenWithoutAnchor} (${pct(counters.hiddenWithoutAnchor)})`);
|
|
166
|
+
out.push(` forwarded content hidden ${counters.forwardedHidden} (${pct(counters.forwardedHidden)})`);
|
|
167
|
+
process.stdout.write(`${out.join('\n')}\n`);
|
|
168
|
+
// A non zero exit makes this usable as a regression gate.
|
|
169
|
+
const failed = counters.emptyFromNonEmpty + counters.everythingHidden + counters.hiddenWithoutAnchor + counters.forwardedHidden;
|
|
170
|
+
return failed === 0 ? 0 : 1;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=benchmark.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"benchmark.js","sourceRoot":"","sources":["../../src/cli/benchmark.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `email-content corpus ...` subcommands.
|
|
3
|
+
*
|
|
4
|
+
* Progress goes to stderr so that stdout stays clean for piping. No line printed here can contain
|
|
5
|
+
* message content: only counts, rates and file paths relative to the corpus root.
|
|
6
|
+
*/
|
|
7
|
+
import { type ParsedArgs } from './args';
|
|
8
|
+
export declare function corpusCommand(sub: string | undefined, args: ParsedArgs): Promise<number>;
|
|
9
|
+
//# sourceMappingURL=corpus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corpus.d.ts","sourceRoot":"","sources":["../../src/cli/corpus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAAwD,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAoI/F,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAY9F"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `email-content corpus ...` subcommands.
|
|
4
|
+
*
|
|
5
|
+
* Progress goes to stderr so that stdout stays clean for piping. No line printed here can contain
|
|
6
|
+
* message content: only counts, rates and file paths relative to the corpus root.
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.corpusCommand = corpusCommand;
|
|
13
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
const scanner_1 = require("../corpus/scanner");
|
|
16
|
+
const stats_1 = require("../corpus/stats");
|
|
17
|
+
const store_1 = require("../corpus/store");
|
|
18
|
+
const args_1 = require("./args");
|
|
19
|
+
const DEFAULT_DB = 'data/corpus.sqlite';
|
|
20
|
+
function formatDuration(ms) {
|
|
21
|
+
const seconds = Math.round(ms / 1000);
|
|
22
|
+
if (seconds < 60) {
|
|
23
|
+
return `${seconds}s`;
|
|
24
|
+
}
|
|
25
|
+
const minutes = Math.floor(seconds / 60);
|
|
26
|
+
if (minutes < 60) {
|
|
27
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
28
|
+
}
|
|
29
|
+
return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;
|
|
30
|
+
}
|
|
31
|
+
function renderProgress(progress) {
|
|
32
|
+
const pct = progress.total ? ((100 * progress.seen) / progress.total).toFixed(1) : '0.0';
|
|
33
|
+
const eta = progress.etaMs === null ? 'unknown' : formatDuration(progress.etaMs);
|
|
34
|
+
const rss = (progress.rssBytes / (1024 * 1024)).toFixed(0);
|
|
35
|
+
return (` ${progress.seen.toLocaleString('en-US')}/${progress.total.toLocaleString('en-US')} (${pct}%) ` +
|
|
36
|
+
`ok=${progress.processed.toLocaleString('en-US')} failed=${progress.failed} skipped=${progress.skipped} ` +
|
|
37
|
+
`${progress.filesPerSecond.toFixed(0)}/s eta ${eta} rss ${rss}MB`);
|
|
38
|
+
}
|
|
39
|
+
async function scan(args) {
|
|
40
|
+
const root = args.positional[0];
|
|
41
|
+
if (!root) {
|
|
42
|
+
process.stderr.write('usage: email-content corpus scan <folder> [--db path] [--sample N] [--seed S] [--concurrency N] [--no-resume]\n');
|
|
43
|
+
return 2;
|
|
44
|
+
}
|
|
45
|
+
if (!node_fs_1.default.existsSync(root)) {
|
|
46
|
+
process.stderr.write(`corpus root not found: ${root}\n`);
|
|
47
|
+
return 2;
|
|
48
|
+
}
|
|
49
|
+
const dbPath = (0, args_1.flagString)(args, 'db', DEFAULT_DB);
|
|
50
|
+
const isTty = process.stderr.isTTY;
|
|
51
|
+
let lastLength = 0;
|
|
52
|
+
process.stderr.write(`Scanning ${node_path_1.default.resolve(root)}\n`);
|
|
53
|
+
process.stderr.write(`Database ${node_path_1.default.resolve(dbPath)}\n`);
|
|
54
|
+
const summary = await (0, scanner_1.scanCorpus)({
|
|
55
|
+
root,
|
|
56
|
+
dbPath,
|
|
57
|
+
sample: (0, args_1.flagNumber)(args, 'sample', 0),
|
|
58
|
+
seed: (0, args_1.flagString)(args, 'seed', 'default'),
|
|
59
|
+
concurrency: (0, args_1.flagNumber)(args, 'concurrency', 0) || undefined,
|
|
60
|
+
resume: (0, args_1.flagBool)(args, 'resume', true),
|
|
61
|
+
maxFileBytes: (0, args_1.flagNumber)(args, 'max-file-mb', 32) * 1024 * 1024,
|
|
62
|
+
timeoutMs: (0, args_1.flagNumber)(args, 'timeout', 30000),
|
|
63
|
+
batchSize: (0, args_1.flagNumber)(args, 'batch', 500),
|
|
64
|
+
onProgress: progress => {
|
|
65
|
+
const line = renderProgress(progress);
|
|
66
|
+
if (isTty) {
|
|
67
|
+
process.stderr.write(`\r${line.padEnd(lastLength)}`);
|
|
68
|
+
lastLength = line.length;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
process.stderr.write(`${line}\n`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
if (isTty) {
|
|
76
|
+
process.stderr.write('\n');
|
|
77
|
+
}
|
|
78
|
+
process.stderr.write(`${summary.status}: ${summary.processed.toLocaleString('en-US')} parsed, ${summary.failed} failed, ${summary.skipped} skipped in ${formatDuration(summary.elapsedMs)} ` +
|
|
79
|
+
`(${summary.filesPerSecond.toFixed(0)} files/s)\n`);
|
|
80
|
+
return summary.status === 'completed' ? 0 : 1;
|
|
81
|
+
}
|
|
82
|
+
function stats(args) {
|
|
83
|
+
const dbPath = (0, args_1.flagString)(args, 'db', DEFAULT_DB);
|
|
84
|
+
if (!node_fs_1.default.existsSync(dbPath)) {
|
|
85
|
+
process.stderr.write(`no corpus database at ${dbPath}. Run "corpus scan" first.\n`);
|
|
86
|
+
return 2;
|
|
87
|
+
}
|
|
88
|
+
const store = new store_1.CorpusStore(dbPath);
|
|
89
|
+
try {
|
|
90
|
+
const computed = (0, stats_1.computeStats)(store);
|
|
91
|
+
if ((0, args_1.flagBool)(args, 'json', false)) {
|
|
92
|
+
process.stdout.write(`${JSON.stringify(computed, null, 2)}\n`);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
const root = store.queryOne('SELECT root FROM runs ORDER BY id DESC LIMIT 1')?.root ?? 'unknown';
|
|
96
|
+
const report = (0, stats_1.renderReport)(computed, root);
|
|
97
|
+
const out = (0, args_1.flagOptionalString)(args, 'out');
|
|
98
|
+
if (out) {
|
|
99
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(node_path_1.default.resolve(out)), { recursive: true });
|
|
100
|
+
node_fs_1.default.writeFileSync(out, report);
|
|
101
|
+
process.stderr.write(`wrote ${out}\n`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
process.stdout.write(report);
|
|
105
|
+
}
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
store.close();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function threads(args) {
|
|
113
|
+
const dbPath = (0, args_1.flagString)(args, 'db', DEFAULT_DB);
|
|
114
|
+
if (!node_fs_1.default.existsSync(dbPath)) {
|
|
115
|
+
process.stderr.write(`no corpus database at ${dbPath}. Run "corpus scan" first.\n`);
|
|
116
|
+
return 2;
|
|
117
|
+
}
|
|
118
|
+
const store = new store_1.CorpusStore(dbPath);
|
|
119
|
+
try {
|
|
120
|
+
const rows = store.query(`
|
|
121
|
+
SELECT CASE
|
|
122
|
+
WHEN m.in_reply_to IS NULL THEN 'no-in-reply-to'
|
|
123
|
+
WHEN EXISTS (SELECT 1 FROM messages p WHERE p.message_id = m.in_reply_to) THEN 'parent-in-corpus'
|
|
124
|
+
ELSE 'parent-missing'
|
|
125
|
+
END AS label, COUNT(*) AS count
|
|
126
|
+
FROM messages m GROUP BY label
|
|
127
|
+
`);
|
|
128
|
+
const total = rows.reduce((sum, row) => sum + row.count, 0);
|
|
129
|
+
for (const row of rows.sort((a, b) => b.count - a.count)) {
|
|
130
|
+
const share = total ? ((100 * row.count) / total).toFixed(1) : '0.0';
|
|
131
|
+
process.stdout.write(`${row.label.padEnd(20)} ${String(row.count).padStart(8)} ${share.padStart(5)}%\n`);
|
|
132
|
+
}
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
store.close();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function corpusCommand(sub, args) {
|
|
140
|
+
switch (sub) {
|
|
141
|
+
case 'scan':
|
|
142
|
+
return scan(args);
|
|
143
|
+
case 'stats':
|
|
144
|
+
return stats(args);
|
|
145
|
+
case 'threads':
|
|
146
|
+
return threads(args);
|
|
147
|
+
default:
|
|
148
|
+
process.stderr.write('usage: email-content corpus <scan|stats|threads> [options]\n');
|
|
149
|
+
return 2;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=corpus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corpus.js","sourceRoot":"","sources":["../../src/cli/corpus.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `email-content disagreements` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* The point of annotating with a model is not the agreement percentage, it is this list. Blocks
|
|
5
|
+
* where the deterministic baseline and the annotator disagree are where a human's attention is
|
|
6
|
+
* worth spending, and they sort naturally by cost: a block the baseline hid but the annotator calls
|
|
7
|
+
* authored is a candidate content loss bug, and everything else is a candidate tidiness bug.
|
|
8
|
+
*
|
|
9
|
+
* This command prints message content by design, because a reviewer has to read the block to judge
|
|
10
|
+
* it. It is the only command that does so besides `extract`.
|
|
11
|
+
*/
|
|
12
|
+
import { type ParsedArgs } from './args';
|
|
13
|
+
export declare function disagreementsCommand(args: ParsedArgs): Promise<number>;
|
|
14
|
+
//# sourceMappingURL=disagreements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disagreements.d.ts","sourceRoot":"","sources":["../../src/cli/disagreements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AA2CjE,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA0F5E"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `email-content disagreements` subcommand.
|
|
4
|
+
*
|
|
5
|
+
* The point of annotating with a model is not the agreement percentage, it is this list. Blocks
|
|
6
|
+
* where the deterministic baseline and the annotator disagree are where a human's attention is
|
|
7
|
+
* worth spending, and they sort naturally by cost: a block the baseline hid but the annotator calls
|
|
8
|
+
* authored is a candidate content loss bug, and everything else is a candidate tidiness bug.
|
|
9
|
+
*
|
|
10
|
+
* This command prints message content by design, because a reviewer has to read the block to judge
|
|
11
|
+
* it. It is the only command that does so besides `extract`.
|
|
12
|
+
*/
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.disagreementsCommand = disagreementsCommand;
|
|
18
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
19
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
+
const rules_1 = require("../classify/rules");
|
|
21
|
+
const store_1 = require("../corpus/store");
|
|
22
|
+
const parse_1 = require("../mime/parse");
|
|
23
|
+
const html_blocks_1 = require("../segment/html-blocks");
|
|
24
|
+
const text_blocks_1 = require("../segment/text-blocks");
|
|
25
|
+
const args_1 = require("./args");
|
|
26
|
+
const DIRECTIONS = {
|
|
27
|
+
expensive: "baseline_label = 'REPLY_HISTORY' AND label IN ('NEW_CONTENT', 'INLINE_QUOTE')",
|
|
28
|
+
cheap: "label = 'REPLY_HISTORY' AND baseline_label IN ('NEW_CONTENT', 'INLINE_QUOTE')",
|
|
29
|
+
footers: "baseline_label = 'NEW_CONTENT' AND label IN ('SIGNATURE', 'DISCLAIMER')",
|
|
30
|
+
all: 'agrees = 0'
|
|
31
|
+
};
|
|
32
|
+
/** Rebuild the blocks of a message so the stored offsets can be resolved back to text. */
|
|
33
|
+
async function blocksFor(root, relPath) {
|
|
34
|
+
const message = await (0, parse_1.parseMessage)(node_fs_1.default.readFileSync(node_path_1.default.join(root, relPath)));
|
|
35
|
+
let best = null;
|
|
36
|
+
for (const part of message.parts) {
|
|
37
|
+
if (!part.decoded || part.disposition === 'attachment') {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (part.contentType === 'text/html') {
|
|
41
|
+
if (!best || best.representation === 'text' || part.decoded.text.length > best.text.length) {
|
|
42
|
+
best = { part: part.partId, text: part.decoded.text, representation: 'html' };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else if (part.contentType === 'text/plain' && (!best || (best.representation === 'text' && part.decoded.text.length > best.text.length))) {
|
|
46
|
+
best = { part: part.partId, text: part.decoded.text, representation: 'text' };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!best) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
return best.representation === 'html' ? (0, html_blocks_1.segmentHtmlBlocks)(best.text, best.part).blocks : (0, text_blocks_1.segmentTextBlocks)(best.text, best.part).blocks;
|
|
53
|
+
}
|
|
54
|
+
async function disagreementsCommand(args) {
|
|
55
|
+
const dbPath = (0, args_1.flagString)(args, 'db', 'data/corpus.sqlite');
|
|
56
|
+
if (!node_fs_1.default.existsSync(dbPath)) {
|
|
57
|
+
process.stderr.write(`no corpus database at ${dbPath}\n`);
|
|
58
|
+
return 2;
|
|
59
|
+
}
|
|
60
|
+
const direction = (0, args_1.flagString)(args, 'direction', 'expensive');
|
|
61
|
+
const clause = DIRECTIONS[direction];
|
|
62
|
+
if (!clause) {
|
|
63
|
+
process.stderr.write(`unknown direction: ${direction}. Use one of ${Object.keys(DIRECTIONS).join(', ')}\n`);
|
|
64
|
+
return 2;
|
|
65
|
+
}
|
|
66
|
+
const limit = (0, args_1.flagNumber)(args, 'limit', 20);
|
|
67
|
+
const context = (0, args_1.flagNumber)(args, 'context', 2);
|
|
68
|
+
const store = new store_1.CorpusStore(dbPath);
|
|
69
|
+
const root = store.queryOne('SELECT root FROM runs ORDER BY id DESC LIMIT 1')?.root;
|
|
70
|
+
if (!root) {
|
|
71
|
+
process.stderr.write('no completed scan run found\n');
|
|
72
|
+
store.close();
|
|
73
|
+
return 2;
|
|
74
|
+
}
|
|
75
|
+
const runFilter = (0, args_1.flagNumber)(args, 'run', 0);
|
|
76
|
+
const rows = store.query(`SELECT path, block_index, representation, label, confidence, baseline_label, baseline_conf, baseline_reasons, char_count
|
|
77
|
+
FROM annotations
|
|
78
|
+
WHERE ${clause} ${runFilter ? 'AND run_id = ' + runFilter : ''}
|
|
79
|
+
ORDER BY char_count DESC
|
|
80
|
+
LIMIT ?`, limit);
|
|
81
|
+
if (!rows.length) {
|
|
82
|
+
process.stdout.write(`no ${direction} disagreements recorded\n`);
|
|
83
|
+
store.close();
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
const out = [];
|
|
87
|
+
out.push(`${rows.length} ${direction} disagreement(s), largest first`);
|
|
88
|
+
if (direction === 'expensive') {
|
|
89
|
+
out.push('The baseline treated these as reply history. The annotator thought the sender wrote them.');
|
|
90
|
+
out.push('Each one is a candidate content loss bug and should be judged by a human.');
|
|
91
|
+
}
|
|
92
|
+
out.push('');
|
|
93
|
+
const cache = new Map();
|
|
94
|
+
for (const row of rows) {
|
|
95
|
+
let blocks = cache.get(row.path);
|
|
96
|
+
if (!blocks) {
|
|
97
|
+
try {
|
|
98
|
+
blocks = await blocksFor(root, row.path);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
blocks = [];
|
|
102
|
+
}
|
|
103
|
+
cache.set(row.path, blocks);
|
|
104
|
+
}
|
|
105
|
+
const block = blocks.find(candidate => candidate.index === row.block_index);
|
|
106
|
+
const decisions = blocks.length ? (0, rules_1.classifyBlocks)(blocks) : [];
|
|
107
|
+
out.push(`${row.path} block #${row.block_index} (${row.representation})`);
|
|
108
|
+
out.push(` baseline: ${row.baseline_label} ${row.baseline_conf?.toFixed(2) ?? '?'} annotator: ${row.label} ${row.confidence.toFixed(2)}`);
|
|
109
|
+
out.push(` reasons: ${row.baseline_reasons || 'none'}`);
|
|
110
|
+
if (block) {
|
|
111
|
+
out.push(` depth: quote=${block.features.quoteDepth} blockquote=${block.features.blockquoteDepth} kind=${block.kind}`);
|
|
112
|
+
}
|
|
113
|
+
// Neighbouring blocks are what make a decision judgeable: the same text means different
|
|
114
|
+
// things above and below an attribution line.
|
|
115
|
+
const from = Math.max(0, row.block_index - context);
|
|
116
|
+
const to = Math.min(blocks.length - 1, row.block_index + context);
|
|
117
|
+
for (let i = from; i <= to; i++) {
|
|
118
|
+
const neighbour = blocks[i];
|
|
119
|
+
if (!neighbour || neighbour.features.isAllBlank) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const decision = decisions.find(candidate => candidate.block.index === i);
|
|
123
|
+
const marker = i === row.block_index ? '>>' : ' ';
|
|
124
|
+
const text = neighbour.content.replace(/\s+/g, ' ').slice(0, 110);
|
|
125
|
+
out.push(` ${marker} #${String(i).padStart(2)} d${neighbour.features.quoteDepth} ${(decision?.label ?? '?').padEnd(17)} ${text}`);
|
|
126
|
+
}
|
|
127
|
+
out.push('');
|
|
128
|
+
}
|
|
129
|
+
process.stdout.write(`${out.join('\n')}\n`);
|
|
130
|
+
store.close();
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=disagreements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disagreements.js","sourceRoot":"","sources":["../../src/cli/disagreements.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `email-content evaluate` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Scores the current extractor against a stored reference set. The reference is re-scored from
|
|
5
|
+
* scratch every run, rather than reusing the baseline verdict captured at annotation time, so that
|
|
6
|
+
* a change to the rules is actually measured instead of being compared against its own past
|
|
7
|
+
* output.
|
|
8
|
+
*
|
|
9
|
+
* The reference here is LLM produced. It is treated as a reference, not as truth: two capable
|
|
10
|
+
* models agree with each other on about 80 percent of blocks, so a number in the high nineties
|
|
11
|
+
* would indicate a measurement problem, not a solved task.
|
|
12
|
+
*/
|
|
13
|
+
import { type ParsedArgs } from './args';
|
|
14
|
+
export declare function evaluateCommand(args: ParsedArgs): Promise<number>;
|
|
15
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/cli/evaluate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,OAAO,EAAoC,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AA8E3E,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAuJvE"}
|