@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.
Files changed (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +288 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/analyze.d.ts +91 -0
  5. package/dist/analyze.d.ts.map +1 -0
  6. package/dist/analyze.js +274 -0
  7. package/dist/analyze.js.map +1 -0
  8. package/dist/annotate/client.d.ts +64 -0
  9. package/dist/annotate/client.d.ts.map +1 -0
  10. package/dist/annotate/client.js +139 -0
  11. package/dist/annotate/client.js.map +1 -0
  12. package/dist/annotate/llm.d.ts +46 -0
  13. package/dist/annotate/llm.d.ts.map +1 -0
  14. package/dist/annotate/llm.js +107 -0
  15. package/dist/annotate/llm.js.map +1 -0
  16. package/dist/annotate/prompt.d.ts +38 -0
  17. package/dist/annotate/prompt.d.ts.map +1 -0
  18. package/dist/annotate/prompt.js +165 -0
  19. package/dist/annotate/prompt.js.map +1 -0
  20. package/dist/annotate/store.d.ts +43 -0
  21. package/dist/annotate/store.d.ts.map +1 -0
  22. package/dist/annotate/store.js +46 -0
  23. package/dist/annotate/store.js.map +1 -0
  24. package/dist/canonical/html.d.ts +33 -0
  25. package/dist/canonical/html.d.ts.map +1 -0
  26. package/dist/canonical/html.js +156 -0
  27. package/dist/canonical/html.js.map +1 -0
  28. package/dist/canonical/text.d.ts +75 -0
  29. package/dist/canonical/text.d.ts.map +1 -0
  30. package/dist/canonical/text.js +217 -0
  31. package/dist/canonical/text.js.map +1 -0
  32. package/dist/classify/confidence.d.ts +38 -0
  33. package/dist/classify/confidence.d.ts.map +1 -0
  34. package/dist/classify/confidence.js +59 -0
  35. package/dist/classify/confidence.js.map +1 -0
  36. package/dist/classify/reconcile.d.ts +52 -0
  37. package/dist/classify/reconcile.d.ts.map +1 -0
  38. package/dist/classify/reconcile.js +100 -0
  39. package/dist/classify/reconcile.js.map +1 -0
  40. package/dist/classify/rules.d.ts +51 -0
  41. package/dist/classify/rules.d.ts.map +1 -0
  42. package/dist/classify/rules.js +648 -0
  43. package/dist/classify/rules.js.map +1 -0
  44. package/dist/cli/annotate.d.ts +23 -0
  45. package/dist/cli/annotate.d.ts.map +1 -0
  46. package/dist/cli/annotate.js +328 -0
  47. package/dist/cli/annotate.js.map +1 -0
  48. package/dist/cli/args.d.ts +17 -0
  49. package/dist/cli/args.d.ts.map +1 -0
  50. package/dist/cli/args.js +72 -0
  51. package/dist/cli/args.js.map +1 -0
  52. package/dist/cli/benchmark.d.ts +13 -0
  53. package/dist/cli/benchmark.d.ts.map +1 -0
  54. package/dist/cli/benchmark.js +172 -0
  55. package/dist/cli/benchmark.js.map +1 -0
  56. package/dist/cli/corpus.d.ts +9 -0
  57. package/dist/cli/corpus.d.ts.map +1 -0
  58. package/dist/cli/corpus.js +152 -0
  59. package/dist/cli/corpus.js.map +1 -0
  60. package/dist/cli/disagreements.d.ts +14 -0
  61. package/dist/cli/disagreements.d.ts.map +1 -0
  62. package/dist/cli/disagreements.js +133 -0
  63. package/dist/cli/disagreements.js.map +1 -0
  64. package/dist/cli/evaluate.d.ts +15 -0
  65. package/dist/cli/evaluate.d.ts.map +1 -0
  66. package/dist/cli/evaluate.js +215 -0
  67. package/dist/cli/evaluate.js.map +1 -0
  68. package/dist/cli/extract.d.ts +10 -0
  69. package/dist/cli/extract.d.ts.map +1 -0
  70. package/dist/cli/extract.js +139 -0
  71. package/dist/cli/extract.js.map +1 -0
  72. package/dist/cli/main.d.ts +9 -0
  73. package/dist/cli/main.d.ts.map +1 -0
  74. package/dist/cli/main.js +147 -0
  75. package/dist/cli/main.js.map +1 -0
  76. package/dist/corpus/analyze.d.ts +66 -0
  77. package/dist/corpus/analyze.d.ts.map +1 -0
  78. package/dist/corpus/analyze.js +214 -0
  79. package/dist/corpus/analyze.js.map +1 -0
  80. package/dist/corpus/migrations/index.d.ts +14 -0
  81. package/dist/corpus/migrations/index.d.ts.map +1 -0
  82. package/dist/corpus/migrations/index.js +158 -0
  83. package/dist/corpus/migrations/index.js.map +1 -0
  84. package/dist/corpus/sample.d.ts +66 -0
  85. package/dist/corpus/sample.d.ts.map +1 -0
  86. package/dist/corpus/sample.js +98 -0
  87. package/dist/corpus/sample.js.map +1 -0
  88. package/dist/corpus/scanner.d.ts +79 -0
  89. package/dist/corpus/scanner.d.ts.map +1 -0
  90. package/dist/corpus/scanner.js +256 -0
  91. package/dist/corpus/scanner.js.map +1 -0
  92. package/dist/corpus/stats.d.ts +76 -0
  93. package/dist/corpus/stats.d.ts.map +1 -0
  94. package/dist/corpus/stats.js +240 -0
  95. package/dist/corpus/stats.js.map +1 -0
  96. package/dist/corpus/store.d.ts +67 -0
  97. package/dist/corpus/store.d.ts.map +1 -0
  98. package/dist/corpus/store.js +196 -0
  99. package/dist/corpus/store.js.map +1 -0
  100. package/dist/corpus/thread-index.d.ts +31 -0
  101. package/dist/corpus/thread-index.d.ts.map +1 -0
  102. package/dist/corpus/thread-index.js +182 -0
  103. package/dist/corpus/thread-index.js.map +1 -0
  104. package/dist/corpus/worker.d.ts +21 -0
  105. package/dist/corpus/worker.d.ts.map +1 -0
  106. package/dist/corpus/worker.js +74 -0
  107. package/dist/corpus/worker.js.map +1 -0
  108. package/dist/detect/client.d.ts +28 -0
  109. package/dist/detect/client.d.ts.map +1 -0
  110. package/dist/detect/client.js +121 -0
  111. package/dist/detect/client.js.map +1 -0
  112. package/dist/detect/disclaimer.d.ts +12 -0
  113. package/dist/detect/disclaimer.d.ts.map +1 -0
  114. package/dist/detect/disclaimer.js +28 -0
  115. package/dist/detect/disclaimer.js.map +1 -0
  116. package/dist/detect/forward-header.d.ts +10 -0
  117. package/dist/detect/forward-header.d.ts.map +1 -0
  118. package/dist/detect/forward-header.js +35 -0
  119. package/dist/detect/forward-header.js.map +1 -0
  120. package/dist/detect/patterns/disclaimers.json +113 -0
  121. package/dist/detect/patterns/forward-headers.json +40 -0
  122. package/dist/detect/patterns/html-clients.json +157 -0
  123. package/dist/detect/patterns/quoted-headers.json +41 -0
  124. package/dist/detect/patterns/reply-headers.json +139 -0
  125. package/dist/detect/patterns/signatures.json +58 -0
  126. package/dist/detect/patterns.d.ts +43 -0
  127. package/dist/detect/patterns.d.ts.map +1 -0
  128. package/dist/detect/patterns.js +125 -0
  129. package/dist/detect/patterns.js.map +1 -0
  130. package/dist/detect/reply-header.d.ts +44 -0
  131. package/dist/detect/reply-header.d.ts.map +1 -0
  132. package/dist/detect/reply-header.js +134 -0
  133. package/dist/detect/reply-header.js.map +1 -0
  134. package/dist/detect/signature.d.ts +28 -0
  135. package/dist/detect/signature.d.ts.map +1 -0
  136. package/dist/detect/signature.js +77 -0
  137. package/dist/detect/signature.js.map +1 -0
  138. package/dist/detect/traffic.d.ts +26 -0
  139. package/dist/detect/traffic.d.ts.map +1 -0
  140. package/dist/detect/traffic.js +98 -0
  141. package/dist/detect/traffic.js.map +1 -0
  142. package/dist/eval/metrics.d.ts +87 -0
  143. package/dist/eval/metrics.d.ts.map +1 -0
  144. package/dist/eval/metrics.js +185 -0
  145. package/dist/eval/metrics.js.map +1 -0
  146. package/dist/index.d.ts +54 -0
  147. package/dist/index.d.ts.map +1 -0
  148. package/dist/index.js +163 -0
  149. package/dist/index.js.map +1 -0
  150. package/dist/match/history.d.ts +62 -0
  151. package/dist/match/history.d.ts.map +1 -0
  152. package/dist/match/history.js +128 -0
  153. package/dist/match/history.js.map +1 -0
  154. package/dist/match/normalize.d.ts +33 -0
  155. package/dist/match/normalize.d.ts.map +1 -0
  156. package/dist/match/normalize.js +63 -0
  157. package/dist/match/normalize.js.map +1 -0
  158. package/dist/match/shingle.d.ts +44 -0
  159. package/dist/match/shingle.d.ts.map +1 -0
  160. package/dist/match/shingle.js +78 -0
  161. package/dist/match/shingle.js.map +1 -0
  162. package/dist/mime/charset.d.ts +33 -0
  163. package/dist/mime/charset.d.ts.map +1 -0
  164. package/dist/mime/charset.js +118 -0
  165. package/dist/mime/charset.js.map +1 -0
  166. package/dist/mime/headers.d.ts +32 -0
  167. package/dist/mime/headers.d.ts.map +1 -0
  168. package/dist/mime/headers.js +127 -0
  169. package/dist/mime/headers.js.map +1 -0
  170. package/dist/mime/model.d.ts +111 -0
  171. package/dist/mime/model.d.ts.map +1 -0
  172. package/dist/mime/model.js +17 -0
  173. package/dist/mime/model.js.map +1 -0
  174. package/dist/mime/parse.d.ts +27 -0
  175. package/dist/mime/parse.d.ts.map +1 -0
  176. package/dist/mime/parse.js +343 -0
  177. package/dist/mime/parse.js.map +1 -0
  178. package/dist/segment/features.d.ts +29 -0
  179. package/dist/segment/features.d.ts.map +1 -0
  180. package/dist/segment/features.js +102 -0
  181. package/dist/segment/features.js.map +1 -0
  182. package/dist/segment/html-blocks.d.ts +28 -0
  183. package/dist/segment/html-blocks.d.ts.map +1 -0
  184. package/dist/segment/html-blocks.js +596 -0
  185. package/dist/segment/html-blocks.js.map +1 -0
  186. package/dist/segment/text-blocks.d.ts +30 -0
  187. package/dist/segment/text-blocks.d.ts.map +1 -0
  188. package/dist/segment/text-blocks.js +204 -0
  189. package/dist/segment/text-blocks.js.map +1 -0
  190. package/dist/segment/types.d.ts +88 -0
  191. package/dist/segment/types.d.ts.map +1 -0
  192. package/dist/segment/types.js +11 -0
  193. package/dist/segment/types.js.map +1 -0
  194. package/dist/types.d.ts +85 -0
  195. package/dist/types.d.ts.map +1 -0
  196. package/dist/types.js +17 -0
  197. package/dist/types.js.map +1 -0
  198. package/dist/util/hash.d.ts +49 -0
  199. package/dist/util/hash.d.ts.map +1 -0
  200. package/dist/util/hash.js +0 -0
  201. package/dist/util/hash.js.map +1 -0
  202. package/dist/util/lang.d.ts +25 -0
  203. package/dist/util/lang.d.ts.map +1 -0
  204. package/dist/util/lang.js +135 -0
  205. package/dist/util/lang.js.map +1 -0
  206. package/dist/util/worker-pool.d.ts +51 -0
  207. package/dist/util/worker-pool.d.ts.map +1 -0
  208. package/dist/util/worker-pool.js +155 -0
  209. package/dist/util/worker-pool.js.map +1 -0
  210. package/package.json +74 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Evaluation metrics.
3
+ *
4
+ * Two rules shape this module.
5
+ *
6
+ * First, aggregate accuracy is close to meaningless here. The label distribution is skewed, so a
7
+ * classifier that guessed NEW_CONTENT for everything would score respectably while being useless.
8
+ * Per label precision and recall, and the catastrophic counts, are what matter.
9
+ *
10
+ * Second, the cost of errors is asymmetric by roughly ten to one. So the headline number is recall
11
+ * of genuine new content, measured in characters rather than blocks, because losing one long
12
+ * paragraph is worse than losing one short line.
13
+ */
14
+ import { type Label } from '../types';
15
+ export interface LabelledBlock {
16
+ path: string;
17
+ blockIndex: number;
18
+ charCount: number;
19
+ reference: Label;
20
+ predicted: Label;
21
+ /** Whether the prediction would actually be hidden from the reader. */
22
+ hidden: boolean;
23
+ /** Grouping key for per segment breakdowns, for example the client or the language. */
24
+ group?: string;
25
+ }
26
+ export interface LabelMetrics {
27
+ label: Label;
28
+ support: number;
29
+ predicted: number;
30
+ truePositives: number;
31
+ precision: number;
32
+ recall: number;
33
+ f1: number;
34
+ }
35
+ export interface RetentionMetrics {
36
+ /** Characters the reference calls authored by the sender. */
37
+ newContentChars: number;
38
+ /** How many of those remain visible. This is the headline number. */
39
+ newContentCharsRetained: number;
40
+ newContentCharRecall: number;
41
+ /** Blocks the reference calls authored, and how many remain visible. */
42
+ newContentBlocks: number;
43
+ newContentBlocksRetained: number;
44
+ newContentBlockRecall: number;
45
+ /** Of what was hidden, how much the reference agrees was history. */
46
+ hiddenChars: number;
47
+ hiddenCharsGenuinelyHistory: number;
48
+ hiddenPrecision: number;
49
+ /** History the reference identified that is still on screen. */
50
+ historyChars: number;
51
+ historyCharsStillVisible: number;
52
+ historyStillVisibleShare: number;
53
+ }
54
+ export interface CatastrophicCounts {
55
+ /** Messages where every authored block was hidden. */
56
+ allNewContentRemoved: number;
57
+ /** Messages where an authored block inside an interleaved reply was hidden. */
58
+ inlineAnswerRemoved: number;
59
+ /** Messages where quoted context was hidden while its answer stayed. */
60
+ inlineContextRemoved: number;
61
+ /** Messages where forwarded content was hidden. */
62
+ forwardedDiscarded: number;
63
+ /** Messages where the only visible content is a footer. */
64
+ onlyFooterRetained: number;
65
+ /** Messages producing nothing visible from a non empty body. */
66
+ emptyExtraction: number;
67
+ /** Messages where more than half the hidden characters were UNKNOWN in the reference. */
68
+ unknownHidden: number;
69
+ messages: number;
70
+ }
71
+ export interface EvaluationReport {
72
+ blocks: number;
73
+ messages: number;
74
+ exactAgreement: number;
75
+ perLabel: LabelMetrics[];
76
+ confusion: Map<string, number>;
77
+ retention: RetentionMetrics;
78
+ catastrophic: CatastrophicCounts;
79
+ byGroup: {
80
+ group: string;
81
+ blocks: number;
82
+ agreement: number;
83
+ newContentCharRecall: number;
84
+ }[];
85
+ }
86
+ export declare function evaluate(rows: readonly LabelledBlock[]): EvaluationReport;
87
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/eval/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAU,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,KAAK,CAAC;IACjB,SAAS,EAAE,KAAK,CAAC;IACjB,uEAAuE;IACvE,MAAM,EAAE,OAAO,CAAC;IAChB,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC7B,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,gBAAgB,EAAE,MAAM,CAAC;IACzB,wBAAwB,EAAE,MAAM,CAAC;IACjC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,2BAA2B,EAAE,MAAM,CAAC;IACpC,eAAe,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB,EAAE,MAAM,CAAC;IACjC,wBAAwB,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IAC/B,sDAAsD;IACtD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,+EAA+E;IAC/E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mDAAmD;IACnD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2DAA2D;IAC3D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,eAAe,EAAE,MAAM,CAAC;IACxB,yFAAyF;IACzF,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,YAAY,EAAE,kBAAkB,CAAC;IACjC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,oBAAoB,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjG;AA2ID,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,GAAG,gBAAgB,CAuCzE"}
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ /**
3
+ * Evaluation metrics.
4
+ *
5
+ * Two rules shape this module.
6
+ *
7
+ * First, aggregate accuracy is close to meaningless here. The label distribution is skewed, so a
8
+ * classifier that guessed NEW_CONTENT for everything would score respectably while being useless.
9
+ * Per label precision and recall, and the catastrophic counts, are what matter.
10
+ *
11
+ * Second, the cost of errors is asymmetric by roughly ten to one. So the headline number is recall
12
+ * of genuine new content, measured in characters rather than blocks, because losing one long
13
+ * paragraph is worse than losing one short line.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.evaluate = evaluate;
17
+ const types_1 = require("../types");
18
+ function labelMetrics(rows, label) {
19
+ let truePositives = 0;
20
+ let support = 0;
21
+ let predicted = 0;
22
+ for (const row of rows) {
23
+ if (row.reference === label) {
24
+ support++;
25
+ }
26
+ if (row.predicted === label) {
27
+ predicted++;
28
+ }
29
+ if (row.reference === label && row.predicted === label) {
30
+ truePositives++;
31
+ }
32
+ }
33
+ const precision = predicted ? truePositives / predicted : 0;
34
+ const recall = support ? truePositives / support : 0;
35
+ return {
36
+ label,
37
+ support,
38
+ predicted,
39
+ truePositives,
40
+ precision,
41
+ recall,
42
+ f1: precision + recall > 0 ? (2 * precision * recall) / (precision + recall) : 0
43
+ };
44
+ }
45
+ /** Labels the reference considers authored by the sender of this message. */
46
+ const AUTHORED = new Set(['NEW_CONTENT', 'SIGNATURE']);
47
+ function retention(rows) {
48
+ let newContentChars = 0;
49
+ let newContentCharsRetained = 0;
50
+ let newContentBlocks = 0;
51
+ let newContentBlocksRetained = 0;
52
+ let hiddenChars = 0;
53
+ let hiddenCharsGenuinelyHistory = 0;
54
+ let historyChars = 0;
55
+ let historyCharsStillVisible = 0;
56
+ for (const row of rows) {
57
+ if (row.reference === 'NEW_CONTENT') {
58
+ newContentChars += row.charCount;
59
+ newContentBlocks++;
60
+ if (!row.hidden) {
61
+ newContentCharsRetained += row.charCount;
62
+ newContentBlocksRetained++;
63
+ }
64
+ }
65
+ if (row.hidden) {
66
+ hiddenChars += row.charCount;
67
+ if (row.reference === 'REPLY_HISTORY') {
68
+ hiddenCharsGenuinelyHistory += row.charCount;
69
+ }
70
+ }
71
+ if (row.reference === 'REPLY_HISTORY') {
72
+ historyChars += row.charCount;
73
+ if (!row.hidden) {
74
+ historyCharsStillVisible += row.charCount;
75
+ }
76
+ }
77
+ }
78
+ return {
79
+ newContentChars,
80
+ newContentCharsRetained,
81
+ newContentCharRecall: newContentChars ? newContentCharsRetained / newContentChars : 1,
82
+ newContentBlocks,
83
+ newContentBlocksRetained,
84
+ newContentBlockRecall: newContentBlocks ? newContentBlocksRetained / newContentBlocks : 1,
85
+ hiddenChars,
86
+ hiddenCharsGenuinelyHistory,
87
+ hiddenPrecision: hiddenChars ? hiddenCharsGenuinelyHistory / hiddenChars : 1,
88
+ historyChars,
89
+ historyCharsStillVisible,
90
+ historyStillVisibleShare: historyChars ? historyCharsStillVisible / historyChars : 0
91
+ };
92
+ }
93
+ function catastrophic(rows) {
94
+ const byMessage = new Map();
95
+ for (const row of rows) {
96
+ const list = byMessage.get(row.path);
97
+ if (list) {
98
+ list.push(row);
99
+ }
100
+ else {
101
+ byMessage.set(row.path, [row]);
102
+ }
103
+ }
104
+ const counts = {
105
+ allNewContentRemoved: 0,
106
+ inlineAnswerRemoved: 0,
107
+ inlineContextRemoved: 0,
108
+ forwardedDiscarded: 0,
109
+ onlyFooterRetained: 0,
110
+ emptyExtraction: 0,
111
+ unknownHidden: 0,
112
+ messages: byMessage.size
113
+ };
114
+ for (const blocks of byMessage.values()) {
115
+ const authored = blocks.filter(block => block.reference === 'NEW_CONTENT');
116
+ const visible = blocks.filter(block => !block.hidden);
117
+ if (authored.length && authored.every(block => block.hidden)) {
118
+ counts.allNewContentRemoved++;
119
+ }
120
+ if (!visible.length && blocks.some(block => block.charCount > 0)) {
121
+ counts.emptyExtraction++;
122
+ }
123
+ if (visible.length && visible.every(block => AUTHORED.has(block.reference) && block.reference !== 'NEW_CONTENT')) {
124
+ counts.onlyFooterRetained++;
125
+ }
126
+ if (blocks.some(block => block.reference === 'FORWARDED_CONTENT' && block.hidden)) {
127
+ counts.forwardedDiscarded++;
128
+ }
129
+ if (blocks.some(block => block.reference === 'INLINE_QUOTE' && block.hidden)) {
130
+ // Hiding an inline quote leaves its answer without the question it answers.
131
+ counts.inlineContextRemoved++;
132
+ }
133
+ // An interleaved message is one with quoted context and authored replies alternating.
134
+ const interleaved = blocks.some(block => block.reference === 'INLINE_QUOTE') && blocks.some(block => block.reference === 'NEW_CONTENT');
135
+ if (interleaved && authored.some(block => block.hidden)) {
136
+ counts.inlineAnswerRemoved++;
137
+ }
138
+ const hiddenChars = blocks.filter(block => block.hidden).reduce((sum, block) => sum + block.charCount, 0);
139
+ const unknownHiddenChars = blocks.filter(block => block.hidden && block.reference === 'UNKNOWN').reduce((sum, block) => sum + block.charCount, 0);
140
+ if (hiddenChars > 0 && unknownHiddenChars / hiddenChars > 0.5) {
141
+ counts.unknownHidden++;
142
+ }
143
+ }
144
+ return counts;
145
+ }
146
+ function evaluate(rows) {
147
+ const confusion = new Map();
148
+ let agreed = 0;
149
+ for (const row of rows) {
150
+ const key = `${row.reference}|${row.predicted}`;
151
+ confusion.set(key, (confusion.get(key) ?? 0) + 1);
152
+ if (row.reference === row.predicted) {
153
+ agreed++;
154
+ }
155
+ }
156
+ const groups = new Map();
157
+ for (const row of rows) {
158
+ const key = row.group ?? 'all';
159
+ const list = groups.get(key);
160
+ if (list) {
161
+ list.push(row);
162
+ }
163
+ else {
164
+ groups.set(key, [row]);
165
+ }
166
+ }
167
+ return {
168
+ blocks: rows.length,
169
+ messages: new Set(rows.map(row => row.path)).size,
170
+ exactAgreement: rows.length ? agreed / rows.length : 0,
171
+ perLabel: types_1.LABELS.map(label => labelMetrics(rows, label)),
172
+ confusion,
173
+ retention: retention(rows),
174
+ catastrophic: catastrophic(rows),
175
+ byGroup: [...groups]
176
+ .map(([group, list]) => ({
177
+ group,
178
+ blocks: list.length,
179
+ agreement: list.filter(row => row.reference === row.predicted).length / list.length,
180
+ newContentCharRecall: retention(list).newContentCharRecall
181
+ }))
182
+ .sort((a, b) => b.blocks - a.blocks)
183
+ };
184
+ }
185
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/eval/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Public library API.
3
+ *
4
+ * `extract()` is the whole product surface: raw message in, classified segments out, with every
5
+ * segment traceable to a MIME part and a source range, and nothing ever discarded.
6
+ */
7
+ import { type ParseOptions } from './mime/model';
8
+ import { type ConfidenceConfig } from './classify/confidence';
9
+ import { type HistoryDocument } from './match/history';
10
+ import { type ExtractionResult, type Reason } from './types';
11
+ export * from './types';
12
+ export { parseMessage } from './mime/parse';
13
+ export type { ParsedMessage, MimePart } from './mime/model';
14
+ export type { Block, BlockFeatures, BlockKind, Evidence } from './segment/types';
15
+ export { DEFAULT_CONFIDENCE } from './classify/confidence';
16
+ export { buildHistoryDocument, matchBlocks, type HistoryDocument, type BlockMatch } from './match/history';
17
+ export { corpusThreadResolver, folderThreadResolver, type ThreadResolver } from './corpus/thread-index';
18
+ export { analyze, resolveHistory, senderTokensFrom, type MessageContent, type AnalyzeOptions, type AnalyzeResult } from './analyze';
19
+ export interface ExtractOptions {
20
+ parse?: ParseOptions;
21
+ confidence?: ConfidenceConfig;
22
+ /**
23
+ * Earlier messages in the thread, most recent first. Optional: extraction works without any
24
+ * history, and history only ever adds evidence.
25
+ */
26
+ history?: readonly string[];
27
+ /** Pre-built history documents, for callers that resolve threads themselves. */
28
+ historyDocuments?: readonly HistoryDocument[];
29
+ /** Keep block level diagnostics on the result. Used by `extract --explain` and the review app. */
30
+ explain?: boolean;
31
+ }
32
+ export interface ExplainedResult extends ExtractionResult {
33
+ /** Present when `explain` is set. */
34
+ diagnostics?: {
35
+ structure: string;
36
+ primaryPart: string | null;
37
+ blocks: {
38
+ index: number;
39
+ kind: string;
40
+ label: string;
41
+ confidence: number;
42
+ reasons: Reason[];
43
+ evidence: {
44
+ id: string;
45
+ kind: string;
46
+ strength: number;
47
+ }[];
48
+ features: Record<string, unknown>;
49
+ preview: string;
50
+ }[];
51
+ };
52
+ }
53
+ export declare function extract(raw: Buffer | string, options?: ExtractOptions): Promise<ExplainedResult>;
54
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAiB,KAAK,YAAY,EAAsB,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAqB,KAAK,gBAAgB,EAAE,KAAK,MAAM,EAAgB,MAAM,SAAS,CAAC;AAE9F,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC5D,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACxG,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAEpI,MAAM,WAAW,cAAc;IAC3B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,kGAAkG;IAClG,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACrD,qCAAqC;IACrC,WAAW,CAAC,EAAE;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,MAAM,EAAE;YACJ,KAAK,EAAE,MAAM,CAAC;YACd,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;YACd,UAAU,EAAE,MAAM,CAAC;YACnB,OAAO,EAAE,MAAM,EAAE,CAAC;YAClB,QAAQ,EAAE;gBAAE,EAAE,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAC;gBAAC,QAAQ,EAAE,MAAM,CAAA;aAAE,EAAE,CAAC;YAC3D,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAClC,OAAO,EAAE,MAAM,CAAC;SACnB,EAAE,CAAC;KACP,CAAC;CACL;AAkFD,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,eAAe,CAAC,CA+C1G"}
package/dist/index.js ADDED
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ /**
3
+ * Public library API.
4
+ *
5
+ * `extract()` is the whole product surface: raw message in, classified segments out, with every
6
+ * segment traceable to a MIME part and a source range, and nothing ever discarded.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
20
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
21
+ };
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.senderTokensFrom = exports.resolveHistory = exports.analyze = exports.folderThreadResolver = exports.corpusThreadResolver = exports.matchBlocks = exports.buildHistoryDocument = exports.DEFAULT_CONFIDENCE = exports.parseMessage = void 0;
24
+ exports.extract = extract;
25
+ const parse_1 = require("./mime/parse");
26
+ const analyze_1 = require("./analyze");
27
+ const types_1 = require("./types");
28
+ __exportStar(require("./types"), exports);
29
+ var parse_2 = require("./mime/parse");
30
+ Object.defineProperty(exports, "parseMessage", { enumerable: true, get: function () { return parse_2.parseMessage; } });
31
+ var confidence_1 = require("./classify/confidence");
32
+ Object.defineProperty(exports, "DEFAULT_CONFIDENCE", { enumerable: true, get: function () { return confidence_1.DEFAULT_CONFIDENCE; } });
33
+ var history_1 = require("./match/history");
34
+ Object.defineProperty(exports, "buildHistoryDocument", { enumerable: true, get: function () { return history_1.buildHistoryDocument; } });
35
+ Object.defineProperty(exports, "matchBlocks", { enumerable: true, get: function () { return history_1.matchBlocks; } });
36
+ var thread_index_1 = require("./corpus/thread-index");
37
+ Object.defineProperty(exports, "corpusThreadResolver", { enumerable: true, get: function () { return thread_index_1.corpusThreadResolver; } });
38
+ Object.defineProperty(exports, "folderThreadResolver", { enumerable: true, get: function () { return thread_index_1.folderThreadResolver; } });
39
+ var analyze_2 = require("./analyze");
40
+ Object.defineProperty(exports, "analyze", { enumerable: true, get: function () { return analyze_2.analyze; } });
41
+ Object.defineProperty(exports, "resolveHistory", { enumerable: true, get: function () { return analyze_2.resolveHistory; } });
42
+ Object.defineProperty(exports, "senderTokensFrom", { enumerable: true, get: function () { return analyze_2.senderTokensFrom; } });
43
+ /**
44
+ * Choose the text and HTML parts that hold the message body.
45
+ *
46
+ * The largest candidate of each type wins. A message can carry several text parts, for example a
47
+ * gateway appended disclaimer in `multipart/mixed(alternative(P,H), P)`, and the body is the large
48
+ * one. Attachments and parts inside a forwarded `message/rfc822` are excluded: an embedded message
49
+ * is content to be classified, not the container's own body.
50
+ */
51
+ function selectBodyParts(message) {
52
+ const insideEmbedded = new Set();
53
+ for (const part of message.parts) {
54
+ if (!part.rfc822 && !part.embeddedRoot) {
55
+ continue;
56
+ }
57
+ for (const other of message.parts) {
58
+ let cursor = other;
59
+ while (cursor?.parentId) {
60
+ if (cursor.parentId === part.partId) {
61
+ insideEmbedded.add(other.partId);
62
+ break;
63
+ }
64
+ cursor = message.partsById.get(cursor.parentId);
65
+ }
66
+ }
67
+ }
68
+ let text = null;
69
+ let html = null;
70
+ for (const part of message.parts) {
71
+ if (part.disposition === 'attachment' || !part.decoded || insideEmbedded.has(part.partId)) {
72
+ continue;
73
+ }
74
+ if (part.contentType === 'text/plain') {
75
+ if (!text || part.decoded.text.length > (text.decoded?.text.length ?? 0)) {
76
+ text = part;
77
+ }
78
+ }
79
+ else if (part.contentType === 'text/html') {
80
+ if (!html || part.decoded.text.length > (html.decoded?.text.length ?? 0)) {
81
+ html = part;
82
+ }
83
+ }
84
+ }
85
+ return { text, html };
86
+ }
87
+ /**
88
+ * Extract the conversationally relevant content of a raw RFC 5322 message.
89
+ *
90
+ * Parses the MIME tree, picks the body parts, then hands off to analyze(). Delegating rather than
91
+ * duplicating the pipeline is deliberate: the two entry points must not be able to disagree about
92
+ * the same message, and a rule added to one must reach the other for free.
93
+ *
94
+ * Never throws for malformed input. A failure produces a result in which the whole body is a single
95
+ * visible UNKNOWN segment, plus a warning, because showing too much is always preferable to
96
+ * showing nothing.
97
+ */
98
+ function degraded(text, warnings) {
99
+ return {
100
+ segments: text.trim()
101
+ ? [
102
+ {
103
+ type: 'UNKNOWN',
104
+ text,
105
+ confidence: 0.5,
106
+ visibility: 'show',
107
+ reasons: ['degraded_to_unknown'],
108
+ source: { mimePart: 'TEXT', representation: 'text', blockIndex: 0, start: 0, end: text.length }
109
+ }
110
+ ]
111
+ : [],
112
+ hiddenSegments: [],
113
+ overallConfidence: 0.5,
114
+ warnings,
115
+ originalAvailable: true,
116
+ primaryRepresentation: 'none',
117
+ extractorVersion: types_1.EXTRACTOR_VERSION,
118
+ historyUsed: false
119
+ };
120
+ }
121
+ async function extract(raw, options = {}) {
122
+ const buffer = typeof raw === 'string' ? Buffer.from(raw) : raw;
123
+ let message;
124
+ try {
125
+ message = await (0, parse_1.parseMessage)(buffer, options.parse);
126
+ }
127
+ catch (err) {
128
+ return degraded(buffer.toString('utf8'), [
129
+ { code: 'mime_parse_error', detail: err instanceof Error ? err.message.slice(0, 200) : 'unknown' },
130
+ { code: 'extraction_degraded' }
131
+ ]);
132
+ }
133
+ const parts = selectBodyParts(message);
134
+ const result = await (0, analyze_1.analyze)({
135
+ text: parts.text?.decoded?.text ?? null,
136
+ html: parts.html?.decoded?.text ?? null,
137
+ subject: message.headers.subject,
138
+ from: message.headers.from,
139
+ inReplyTo: message.headers.inReplyTo,
140
+ references: message.headers.references,
141
+ ...(parts.text ? { textPartId: parts.text.partId } : {}),
142
+ ...(parts.html ? { htmlPartId: parts.html.partId } : {})
143
+ }, {
144
+ ...(options.confidence ? { confidence: options.confidence } : {}),
145
+ ...(options.history ? { history: options.history } : {}),
146
+ ...(options.historyDocuments ? { historyDocuments: options.historyDocuments } : {}),
147
+ ...(options.explain ? { explain: true } : {})
148
+ });
149
+ // MIME level warnings belong to the message, not to the body analysis.
150
+ const warnings = [...message.warnings, ...result.warnings];
151
+ return {
152
+ segments: result.segments,
153
+ hiddenSegments: result.hiddenSegments,
154
+ overallConfidence: result.overallConfidence,
155
+ warnings,
156
+ originalAvailable: true,
157
+ primaryRepresentation: result.primaryRepresentation,
158
+ extractorVersion: result.extractorVersion,
159
+ historyUsed: result.historyUsed,
160
+ ...(result.diagnostics ? { diagnostics: { ...result.diagnostics, structure: message.structure } } : {})
161
+ };
162
+ }
163
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Stage 5: matching blocks against messages that already existed.
3
+ *
4
+ * This is the strongest evidence the pipeline can have. Every other signal is an inference from
5
+ * markup or wording: an attribution line suggests a quote follows, a blockquote suggests its
6
+ * contents are old. A match against the actual parent message is not an inference. The text
7
+ * demonstrably existed before this message was written, so it cannot be something the sender just
8
+ * wrote.
9
+ *
10
+ * In the working corpus 90.9 percent of replies resolve their In-Reply-To to a message that is also
11
+ * present, which makes this unusually available here. It remains strictly optional: extraction
12
+ * works with no history at all, and history only ever adds evidence.
13
+ *
14
+ * The matcher is deliberately conservative in one direction. A match can promote a block to
15
+ * REPLY_HISTORY, which hides it, so short blocks are never matched, and a high containment
16
+ * threshold is required. A non match never demotes anything.
17
+ */
18
+ import { type Block } from '../segment/types';
19
+ import { type ShingleIndex } from './shingle';
20
+ export interface HistoryDocument {
21
+ /** Identifier for diagnostics, typically a path or Message-ID. */
22
+ id: string;
23
+ /** Normalised text of the whole message, all representations concatenated. */
24
+ normalised: string;
25
+ index: ShingleIndex;
26
+ /** True when this is the direct parent rather than an older ancestor. */
27
+ isParent: boolean;
28
+ }
29
+ export interface BlockMatch {
30
+ blockIndex: number;
31
+ documentId: string;
32
+ isParent: boolean;
33
+ containment: number;
34
+ /** True when the normalised text appeared verbatim. */
35
+ exact: boolean;
36
+ }
37
+ /**
38
+ * Build a searchable document from a raw earlier message.
39
+ *
40
+ * Both representations are folded into one text projection. Which representation the earlier
41
+ * message used has nothing to do with which one the reply quoted from, and clients routinely quote
42
+ * the HTML of a message into the plain text of a reply.
43
+ */
44
+ export declare function buildHistoryDocument(raw: Buffer | string, id: string, isParent: boolean): Promise<HistoryDocument>;
45
+ /**
46
+ * Build a searchable document from already parsed content.
47
+ *
48
+ * The counterpart to buildHistoryDocument for callers that never had a raw message. Both
49
+ * representations are folded together for the same reason: which one the earlier message used has
50
+ * nothing to do with which one the reply quoted from.
51
+ */
52
+ export declare function buildHistoryDocumentFromContent(text: string, html: string, id: string, isParent: boolean): HistoryDocument;
53
+ /**
54
+ * Match blocks against known history.
55
+ *
56
+ * Returns one match per block at most, preferring the direct parent over older ancestors, and
57
+ * higher containment over lower.
58
+ */
59
+ export declare function matchBlocks(blocks: readonly Block[], history: readonly HistoryDocument[]): BlockMatch[];
60
+ /** Convenience view for the classifier, which only needs to know which indices matched. */
61
+ export declare function matchedIndices(matches: readonly BlockMatch[]): Set<number>;
62
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/match/history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAsE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAGlH,MAAM,WAAW,eAAe;IAC5B,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,YAAY,CAAC;IACpB,yEAAyE;IACzE,QAAQ,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,KAAK,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAkBxH;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,eAAe,CAU1H;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,UAAU,EAAE,CA4CvG;AASD,2FAA2F;AAC3F,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAE1E"}