@flowselections/mailbox-orders 1.0.30 → 1.0.33
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/dist-lib/components/MailboxProcessedPage.js +1 -1
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +202 -122
- package/dist-lib/components/MailboxSkippedPage.d.ts +2 -0
- package/dist-lib/components/MailboxSkippedPage.d.ts.map +1 -0
- package/dist-lib/components/MailboxSkippedPage.js +71 -0
- package/dist-lib/components/settings/MailboxFilterSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/MailboxFilterSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MailboxFilterSettingsCard.js +78 -0
- package/dist-lib/components/settings/MailboxTestSetCard.d.ts +2 -0
- package/dist-lib/components/settings/MailboxTestSetCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MailboxTestSetCard.js +69 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +8 -2
- package/dist-lib/integrations/supabase/types.d.ts +1442 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ai.server.d.ts +33 -12
- package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ai.server.js +47 -17
- package/dist-lib/lib/mailbox-filter-settings.functions.d.ts +143 -0
- package/dist-lib/lib/mailbox-filter-settings.functions.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-filter-settings.functions.js +241 -0
- package/dist-lib/lib/mailbox-filter.d.ts +34 -0
- package/dist-lib/lib/mailbox-filter.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-filter.js +121 -0
- package/dist-lib/lib/mailbox-ingest.server.d.ts +114 -17
- package/dist-lib/lib/mailbox-ingest.server.d.ts.map +1 -1
- package/dist-lib/lib/mailbox-ingest.server.js +594 -266
- package/dist-lib/lib/mailbox-match.d.ts +28 -0
- package/dist-lib/lib/mailbox-match.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-match.js +74 -0
- package/dist-lib/lib/mailbox-units.d.ts +18 -0
- package/dist-lib/lib/mailbox-units.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-units.js +80 -0
- package/dist-lib/lib/mailbox.functions.d.ts +74 -0
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +170 -25
- package/package.json +6 -1
|
@@ -20,16 +20,30 @@ export interface ParsedFieldValue {
|
|
|
20
20
|
needs_review: boolean;
|
|
21
21
|
reason?: string;
|
|
22
22
|
}
|
|
23
|
+
export interface ParsedLine {
|
|
24
|
+
raw_product_text: string;
|
|
25
|
+
code: string | null;
|
|
26
|
+
name_in_mail: string | null;
|
|
27
|
+
product_id: string | null;
|
|
28
|
+
plant_type: string | null;
|
|
29
|
+
quantity: number | null;
|
|
30
|
+
unit_text: string | null;
|
|
31
|
+
unit: "stuk" | "fust" | "laag" | "kar" | "onbekend" | null;
|
|
32
|
+
pieces_per_unit: number | null;
|
|
33
|
+
price: number | null;
|
|
34
|
+
currency: string | null;
|
|
35
|
+
price_per: "stuk" | "eenheid" | null;
|
|
36
|
+
match_confidence: number;
|
|
37
|
+
confidence: {
|
|
38
|
+
product: number;
|
|
39
|
+
quantity: number;
|
|
40
|
+
unit: number;
|
|
41
|
+
price: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
23
44
|
export interface ParsedOrder {
|
|
24
45
|
fields: Record<string, ParsedFieldValue>;
|
|
25
|
-
lines:
|
|
26
|
-
raw_product_text: string;
|
|
27
|
-
product_id: string | null;
|
|
28
|
-
plant_type: string | null;
|
|
29
|
-
quantity: number | null;
|
|
30
|
-
unit: string | null;
|
|
31
|
-
match_confidence: number;
|
|
32
|
-
}>;
|
|
46
|
+
lines: ParsedLine[];
|
|
33
47
|
validation: {
|
|
34
48
|
missing: string[];
|
|
35
49
|
uncertain: string[];
|
|
@@ -37,12 +51,20 @@ export interface ParsedOrder {
|
|
|
37
51
|
};
|
|
38
52
|
overall_confidence: number;
|
|
39
53
|
}
|
|
54
|
+
export interface AiCandidate {
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
codes: string[];
|
|
58
|
+
belading: string | null;
|
|
59
|
+
}
|
|
40
60
|
export interface AiParseMeta {
|
|
41
61
|
providerSlug: string;
|
|
42
62
|
modelSlug: string;
|
|
43
63
|
modelName: string;
|
|
44
64
|
durationMs: number;
|
|
45
65
|
fingerprint: string;
|
|
66
|
+
inputTokens: number | null;
|
|
67
|
+
outputTokens: number | null;
|
|
46
68
|
}
|
|
47
69
|
/** Huidige vingerafdruk van de gekoppelde AI-agent, of null als AI niet beschikbaar is. */
|
|
48
70
|
export declare function currentAiFingerprint(admin: AdminClient): Promise<string | null>;
|
|
@@ -66,10 +88,7 @@ export declare function parseOrderEmail(admin: AdminClient, args: {
|
|
|
66
88
|
bodyText: string;
|
|
67
89
|
subject: string;
|
|
68
90
|
templateFields: TemplateFieldSpec[];
|
|
69
|
-
productCatalog:
|
|
70
|
-
id: string;
|
|
71
|
-
name: string;
|
|
72
|
-
}>;
|
|
91
|
+
productCatalog: AiCandidate[];
|
|
73
92
|
deliveryLocations?: Array<{
|
|
74
93
|
id: string;
|
|
75
94
|
name: string;
|
|
@@ -77,6 +96,8 @@ export declare function parseOrderEmail(admin: AdminClient, args: {
|
|
|
77
96
|
plantTypes?: string[];
|
|
78
97
|
requiredFields?: string[];
|
|
79
98
|
systemPrompt?: string | null;
|
|
99
|
+
now?: Date;
|
|
100
|
+
maxAttemptsPerModel?: number;
|
|
80
101
|
}): Promise<{
|
|
81
102
|
parsed: ParsedOrder;
|
|
82
103
|
meta: AiParseMeta;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mailbox-ai.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ai.server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAE9D,OAAO,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,EAAE,6BAA6B,EAAE,CAAC;AAEzC,KAAK,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;AAE5C,eAAO,MAAM,qBAAqB,yBAAyB,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"mailbox-ai.server.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-ai.server.ts"],"names":[],"mappings":"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"}
|
|
@@ -131,9 +131,19 @@ const OUTPUT_CONTRACT = [
|
|
|
131
131
|
"Antwoord uitsluitend met JSON, zonder uitleg en zonder markdown.",
|
|
132
132
|
"Structuur:",
|
|
133
133
|
'{"fields":{"<key>":{"value":<any|null>,"confidence":0..1,"needs_review":true|false}},',
|
|
134
|
-
'"lines":[{"
|
|
134
|
+
'"lines":[{"raw_text":string,"code":string|null,"name_in_mail":string|null,"product_id":string|null,"plant_type":string|null,',
|
|
135
|
+
'"quantity":number|null,"unit_text":string|null,"unit":"stuk"|"fust"|"laag"|"kar"|"onbekend","pieces_per_unit":number|null,',
|
|
136
|
+
'"price":number|null,"currency":string|null,"price_per":"stuk"|"eenheid"|null,',
|
|
137
|
+
'"confidence":{"product":0..1,"quantity":0..1,"unit":0..1,"price":0..1}}],',
|
|
135
138
|
'"validation":{"missing":[string],"uncertain":[string],"notes":string|null},',
|
|
136
139
|
'"overall_confidence":0..1}',
|
|
140
|
+
"Regels per bestelregel:",
|
|
141
|
+
"- raw_text: de letterlijke regel uit de mail. code: artikelcode/EAN/VBN precies zoals in de mail. name_in_mail: productnaam zoals in de mail.",
|
|
142
|
+
"- quantity en unit_text precies zoals in de mail. Reken NIET om naar planten. unit: tray/doos/kist/fust = fust; laag/plank = laag; kar/container = kar; stuks/planten = stuk; anders onbekend.",
|
|
143
|
+
"- pieces_per_unit alleen als de mail dat noemt (bijv. '20 stuks per fust').",
|
|
144
|
+
"- price, currency en price_per ('stuk' bij 'per stuk', 'eenheid' bij 'per fust/tray/kar'). Geen prijs in de mail: null.",
|
|
145
|
+
"- product_id alleen uit product_candidates; twijfel je, dan null.",
|
|
146
|
+
"- Het veld 'reference' is het order-, referentie- of PO-nummer van de klant; letterlijk overnemen.",
|
|
137
147
|
"Datums altijd als YYYY-MM-DD.",
|
|
138
148
|
].join("\n");
|
|
139
149
|
const DAY_NAMES = ["zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"];
|
|
@@ -181,12 +191,13 @@ function buildPrompt(bodyText, subject, templateFields, productCatalog, extra) {
|
|
|
181
191
|
const system = `${base}\n\n${OUTPUT_CONTRACT}`;
|
|
182
192
|
const user = JSON.stringify({
|
|
183
193
|
email: { subject, body: (bodyText ?? "").slice(0, 20000) },
|
|
194
|
+
// Relatieve datums ('morgen', 'woensdag') rekenen vanaf de ontvangstdatum van de mail.
|
|
184
195
|
date_context: buildDateContext(extra.now ?? new Date()),
|
|
185
196
|
required_fields: extra.requiredFields ?? [],
|
|
186
197
|
fields: fieldSpec,
|
|
187
198
|
delivery_locations: extra.deliveryLocations?.slice(0, 100) ?? [],
|
|
188
199
|
plant_types: (extra.plantTypes ?? []).slice(0, 400),
|
|
189
|
-
|
|
200
|
+
product_candidates: productCatalog,
|
|
190
201
|
});
|
|
191
202
|
return { system, user };
|
|
192
203
|
}
|
|
@@ -195,6 +206,8 @@ function strList(v) {
|
|
|
195
206
|
? v.map((x) => (typeof x === "string" ? x.slice(0, 120) : "")).filter(Boolean).slice(0, 50)
|
|
196
207
|
: [];
|
|
197
208
|
}
|
|
209
|
+
const str = (v, max = 200) => typeof v === "string" && v.trim() ? v.trim().slice(0, max) : null;
|
|
210
|
+
const UNIT_KINDS = new Set(["stuk", "fust", "laag", "kar", "onbekend"]);
|
|
198
211
|
function normalize(raw, productCatalog) {
|
|
199
212
|
const allowedProductIds = new Set(productCatalog.map((product) => product.id));
|
|
200
213
|
const fields = {};
|
|
@@ -212,17 +225,32 @@ function normalize(raw, productCatalog) {
|
|
|
212
225
|
}
|
|
213
226
|
const rawLines = Array.isArray(raw?.lines) ? raw.lines : [];
|
|
214
227
|
const lines = rawLines
|
|
215
|
-
.map((l) =>
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
228
|
+
.map((l) => {
|
|
229
|
+
const c = l?.confidence && typeof l.confidence === "object" ? l.confidence : {};
|
|
230
|
+
const unitRaw = typeof l?.unit === "string" ? l.unit.toLowerCase() : null;
|
|
231
|
+
return {
|
|
232
|
+
raw_product_text: str(l?.raw_text ?? l?.raw_product_text, 500) ?? "",
|
|
233
|
+
code: str(l?.code, 60),
|
|
234
|
+
name_in_mail: str(l?.name_in_mail, 200),
|
|
235
|
+
product_id: typeof l?.product_id === "string" && allowedProductIds.has(l.product_id) ? l.product_id : null,
|
|
236
|
+
plant_type: str(l?.plant_type),
|
|
237
|
+
quantity: num(l?.quantity),
|
|
238
|
+
unit_text: str(l?.unit_text, 60),
|
|
239
|
+
unit: unitRaw && UNIT_KINDS.has(unitRaw) ? unitRaw : null,
|
|
240
|
+
pieces_per_unit: num(l?.pieces_per_unit),
|
|
241
|
+
price: num(l?.price),
|
|
242
|
+
currency: str(l?.currency, 8),
|
|
243
|
+
price_per: l?.price_per === "stuk" || l?.price_per === "eenheid" ? l.price_per : null,
|
|
244
|
+
match_confidence: clamp01(c?.product ?? l?.match_confidence),
|
|
245
|
+
confidence: {
|
|
246
|
+
product: clamp01(c?.product ?? l?.match_confidence),
|
|
247
|
+
quantity: clamp01(c?.quantity),
|
|
248
|
+
unit: clamp01(c?.unit),
|
|
249
|
+
price: clamp01(c?.price),
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
})
|
|
253
|
+
.filter((l) => l.raw_product_text || l.product_id || l.code);
|
|
226
254
|
const rv = raw?.validation ?? {};
|
|
227
255
|
return {
|
|
228
256
|
fields,
|
|
@@ -272,11 +300,11 @@ export async function parseOrderEmail(admin, args) {
|
|
|
272
300
|
plantTypes: args.plantTypes,
|
|
273
301
|
requiredFields: args.requiredFields,
|
|
274
302
|
systemPrompt: args.systemPrompt,
|
|
303
|
+
now: args.now,
|
|
275
304
|
});
|
|
276
|
-
// Per model
|
|
277
|
-
// naar het volgende model in de keten.
|
|
278
|
-
|
|
279
|
-
const MAX_ATTEMPTS_PER_MODEL = 3;
|
|
305
|
+
// Per model een begrensd aantal pogingen (instelling) met exponentiële
|
|
306
|
+
// backoff; daarna uitwijken naar het volgende model in de keten.
|
|
307
|
+
const MAX_ATTEMPTS_PER_MODEL = Math.max(1, Math.min(5, Number(args.maxAttemptsPerModel ?? 3)));
|
|
280
308
|
let attemptNo = 0;
|
|
281
309
|
let lastMessage = "AI niet beschikbaar.";
|
|
282
310
|
let lastTransient = false;
|
|
@@ -306,6 +334,8 @@ export async function parseOrderEmail(admin, args) {
|
|
|
306
334
|
modelName: cfg.modelName,
|
|
307
335
|
durationMs,
|
|
308
336
|
fingerprint: primary.fingerprint,
|
|
337
|
+
inputTokens: completion.inputTokens,
|
|
338
|
+
outputTokens: completion.outputTokens,
|
|
309
339
|
},
|
|
310
340
|
};
|
|
311
341
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export declare const getMailboxFilterSettings: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
2
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
3
|
+
userId: string;
|
|
4
|
+
claims: import("@supabase/auth-js").User;
|
|
5
|
+
}, undefined, undefined, undefined>], undefined, Promise<{
|
|
6
|
+
settings: any;
|
|
7
|
+
auto_analyze: boolean;
|
|
8
|
+
usage: {
|
|
9
|
+
month_analyses: number;
|
|
10
|
+
today_analyses: number;
|
|
11
|
+
test_analyses: number;
|
|
12
|
+
input_tokens: number;
|
|
13
|
+
output_tokens: number;
|
|
14
|
+
ai_calls_month: any;
|
|
15
|
+
waiting: any;
|
|
16
|
+
};
|
|
17
|
+
}>>;
|
|
18
|
+
export declare const saveMailboxFilterSettings: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
19
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
20
|
+
userId: string;
|
|
21
|
+
claims: import("@supabase/auth-js").User;
|
|
22
|
+
}, undefined, undefined, undefined>], (input: unknown) => {
|
|
23
|
+
domain_match: boolean;
|
|
24
|
+
analyze_unknown_with_signals: boolean;
|
|
25
|
+
min_signals_unknown: number;
|
|
26
|
+
order_keywords: string[];
|
|
27
|
+
order_patterns: string[];
|
|
28
|
+
negative_keywords: string[];
|
|
29
|
+
skip_senders: string[];
|
|
30
|
+
skip_domains: string[];
|
|
31
|
+
max_ai_per_day: number;
|
|
32
|
+
max_ai_per_month: number;
|
|
33
|
+
max_candidates: number;
|
|
34
|
+
max_retry_attempts: number;
|
|
35
|
+
max_retries_per_run: number;
|
|
36
|
+
max_agent_reparse: number;
|
|
37
|
+
conf_green: number;
|
|
38
|
+
conf_orange: number;
|
|
39
|
+
duplicate_days: number;
|
|
40
|
+
auto_analyze: boolean;
|
|
41
|
+
}, Promise<{
|
|
42
|
+
ok: true;
|
|
43
|
+
}>>;
|
|
44
|
+
/** Telling van openstaande voorstellen die het nieuwe filter als "geen bestelling" zou zien. Wijzigt niets. */
|
|
45
|
+
export declare const countLikelyFakeProposals: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
46
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
47
|
+
userId: string;
|
|
48
|
+
claims: import("@supabase/auth-js").User;
|
|
49
|
+
}, undefined, undefined, undefined>], undefined, Promise<{
|
|
50
|
+
total: number;
|
|
51
|
+
known: number;
|
|
52
|
+
wouldSkip: number;
|
|
53
|
+
withSignals: number;
|
|
54
|
+
}>>;
|
|
55
|
+
export declare const listTestCases: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
56
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
57
|
+
userId: string;
|
|
58
|
+
claims: import("@supabase/auth-js").User;
|
|
59
|
+
}, undefined, undefined, undefined>], undefined, Promise<{
|
|
60
|
+
items: any[];
|
|
61
|
+
}>>;
|
|
62
|
+
export declare const saveTestCase: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
63
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
64
|
+
userId: string;
|
|
65
|
+
claims: import("@supabase/auth-js").User;
|
|
66
|
+
}, undefined, undefined, undefined>], (input: unknown) => {
|
|
67
|
+
name: string;
|
|
68
|
+
subject: string;
|
|
69
|
+
from_email: string;
|
|
70
|
+
body_text: string;
|
|
71
|
+
has_attachment: boolean;
|
|
72
|
+
expected: Record<string, any>;
|
|
73
|
+
id?: string | undefined;
|
|
74
|
+
}, Promise<{
|
|
75
|
+
ok: true;
|
|
76
|
+
}>>;
|
|
77
|
+
export declare const deleteTestCase: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
78
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
79
|
+
userId: string;
|
|
80
|
+
claims: import("@supabase/auth-js").User;
|
|
81
|
+
}, undefined, undefined, undefined>], (input: unknown) => {
|
|
82
|
+
id: string;
|
|
83
|
+
}, Promise<{
|
|
84
|
+
ok: true;
|
|
85
|
+
}>>;
|
|
86
|
+
type Check = {
|
|
87
|
+
field: string;
|
|
88
|
+
expected: any;
|
|
89
|
+
actual: any;
|
|
90
|
+
ok: boolean;
|
|
91
|
+
};
|
|
92
|
+
/** Draait één voorbeeld: filter + extractie, maakt geen voorstel of order aan. */
|
|
93
|
+
export declare const runTestCase: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
|
|
94
|
+
supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
|
|
95
|
+
userId: string;
|
|
96
|
+
claims: import("@supabase/auth-js").User;
|
|
97
|
+
}, undefined, undefined, undefined>], (input: unknown) => {
|
|
98
|
+
id: string;
|
|
99
|
+
}, Promise<{
|
|
100
|
+
checks: Check[];
|
|
101
|
+
passed: boolean;
|
|
102
|
+
filter: import("./mailbox-filter.js").FilterResult;
|
|
103
|
+
sender: string;
|
|
104
|
+
forwarded: boolean;
|
|
105
|
+
analyzed: boolean;
|
|
106
|
+
tokens: {
|
|
107
|
+
input: number;
|
|
108
|
+
output: number;
|
|
109
|
+
};
|
|
110
|
+
result: null;
|
|
111
|
+
} | {
|
|
112
|
+
checks: Check[];
|
|
113
|
+
passed: boolean;
|
|
114
|
+
filter: import("./mailbox-filter.js").FilterResult;
|
|
115
|
+
sender: string;
|
|
116
|
+
forwarded: boolean;
|
|
117
|
+
analyzed: boolean;
|
|
118
|
+
tokens: {
|
|
119
|
+
input: number;
|
|
120
|
+
output: number;
|
|
121
|
+
};
|
|
122
|
+
result: {
|
|
123
|
+
error: string | null;
|
|
124
|
+
customer_known: boolean;
|
|
125
|
+
delivery_date: any;
|
|
126
|
+
reference: any;
|
|
127
|
+
attachment_unread: boolean;
|
|
128
|
+
lines: {
|
|
129
|
+
text: string;
|
|
130
|
+
code: string | null;
|
|
131
|
+
product_id: string | null;
|
|
132
|
+
product_known: boolean;
|
|
133
|
+
match_source: "code" | "naam" | "ai" | null;
|
|
134
|
+
quantity: number | null;
|
|
135
|
+
unit: import("./mailbox-units.js").UnitKind;
|
|
136
|
+
plants: number | null;
|
|
137
|
+
unit_price: number | null;
|
|
138
|
+
warnings: string[];
|
|
139
|
+
}[];
|
|
140
|
+
};
|
|
141
|
+
}>>;
|
|
142
|
+
export {};
|
|
143
|
+
//# sourceMappingURL=mailbox-filter-settings.functions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailbox-filter-settings.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-filter-settings.functions.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;GA0CjC,CAAC;AAEL,eAAO,MAAM,yBAAyB;;;;8CAEZ,OAAO;;;;;;;;;;;;;;;;;;;;;GAuB7B,CAAC;AAEL,+GAA+G;AAC/G,eAAO,MAAM,wBAAwB;;;;;;;;;GA+BjC,CAAC;AAIL,eAAO,MAAM,aAAa;;;;;;GAUtB,CAAC;AAYL,eAAO,MAAM,YAAY;;;;8CAEC,OAAO;;;;;;;;;;GAW7B,CAAC;AAEL,eAAO,MAAM,cAAc;;;;8CAED,OAAO;;;;GAM7B,CAAC;AAEL,KAAK,KAAK,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;IAAC,EAAE,EAAE,OAAO,CAAA;CAAE,CAAC;AA4BxE,kFAAkF;AAClF,eAAO,MAAM,WAAW;;;;8CAEE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqB7B,CAAC"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Instellingen voor het voorfilter, de kostengrenzen en de testset.
|
|
2
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
3
|
+
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
const list = z.array(z.string().trim().min(1).max(200)).max(500);
|
|
6
|
+
const SettingsInput = z.object({
|
|
7
|
+
domain_match: z.boolean(),
|
|
8
|
+
analyze_unknown_with_signals: z.boolean(),
|
|
9
|
+
min_signals_unknown: z.number().int().min(1).max(20),
|
|
10
|
+
order_keywords: list,
|
|
11
|
+
order_patterns: list,
|
|
12
|
+
negative_keywords: list,
|
|
13
|
+
skip_senders: list,
|
|
14
|
+
skip_domains: list,
|
|
15
|
+
max_ai_per_day: z.number().int().min(0).max(100000),
|
|
16
|
+
max_ai_per_month: z.number().int().min(0).max(1000000),
|
|
17
|
+
max_candidates: z.number().int().min(1).max(250),
|
|
18
|
+
max_retry_attempts: z.number().int().min(1).max(5),
|
|
19
|
+
max_retries_per_run: z.number().int().min(0).max(50),
|
|
20
|
+
max_agent_reparse: z.number().int().min(0).max(200),
|
|
21
|
+
conf_green: z.number().min(0).max(1),
|
|
22
|
+
conf_orange: z.number().min(0).max(1),
|
|
23
|
+
duplicate_days: z.number().int().min(1).max(365),
|
|
24
|
+
auto_analyze: z.boolean(),
|
|
25
|
+
});
|
|
26
|
+
export const getMailboxFilterSettings = createServerFn({ method: "GET" })
|
|
27
|
+
.middleware([requireSupabaseAuth])
|
|
28
|
+
.handler(async () => {
|
|
29
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
30
|
+
const admin = supabaseAdmin;
|
|
31
|
+
const { data: s, error } = await admin.from("mailbox_filter_settings").select("*").eq("singleton", true).maybeSingle();
|
|
32
|
+
if (error)
|
|
33
|
+
throw new Error(error.message);
|
|
34
|
+
const { data: ai } = await admin.from("mailbox_ai_settings").select("enabled").limit(1).maybeSingle();
|
|
35
|
+
const now = new Date();
|
|
36
|
+
const monthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)).toISOString();
|
|
37
|
+
const dayStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())).toISOString();
|
|
38
|
+
const { data: runs } = await admin
|
|
39
|
+
.from("mailbox_ai_runs")
|
|
40
|
+
.select("started_at, input_tokens, output_tokens, is_test")
|
|
41
|
+
.gte("started_at", monthStart)
|
|
42
|
+
.range(0, 9999);
|
|
43
|
+
const rs = Array.isArray(runs) ? runs : [];
|
|
44
|
+
const real = rs.filter((r) => !r.is_test);
|
|
45
|
+
const { count: calls } = await admin
|
|
46
|
+
.from("ai_call_log")
|
|
47
|
+
.select("id", { count: "exact", head: true })
|
|
48
|
+
.eq("consumer_module_id", "mailbox-bestellingen")
|
|
49
|
+
.gte("created_at", monthStart);
|
|
50
|
+
const { count: waitingCount } = await admin
|
|
51
|
+
.from("mailbox_messages")
|
|
52
|
+
.select("id", { count: "exact", head: true })
|
|
53
|
+
.eq("analysis_state", "wacht");
|
|
54
|
+
return {
|
|
55
|
+
settings: s ?? null,
|
|
56
|
+
auto_analyze: ai?.enabled !== false,
|
|
57
|
+
usage: {
|
|
58
|
+
month_analyses: real.length,
|
|
59
|
+
today_analyses: real.filter((r) => r.started_at >= dayStart).length,
|
|
60
|
+
test_analyses: rs.length - real.length,
|
|
61
|
+
input_tokens: rs.reduce((a, r) => a + Number(r.input_tokens ?? 0), 0),
|
|
62
|
+
output_tokens: rs.reduce((a, r) => a + Number(r.output_tokens ?? 0), 0),
|
|
63
|
+
ai_calls_month: calls ?? 0,
|
|
64
|
+
waiting: waitingCount ?? 0,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
export const saveMailboxFilterSettings = createServerFn({ method: "POST" })
|
|
69
|
+
.middleware([requireSupabaseAuth])
|
|
70
|
+
.inputValidator((input) => SettingsInput.parse(input))
|
|
71
|
+
.handler(async ({ data }) => {
|
|
72
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
73
|
+
const admin = supabaseAdmin;
|
|
74
|
+
for (const p of data.order_patterns) {
|
|
75
|
+
try {
|
|
76
|
+
new RegExp(p.replace(/\\m|\\M/g, "\\b"));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new Error(`Ongeldig patroon: ${p}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const { auto_analyze, ...rest } = data;
|
|
83
|
+
const { error } = await admin
|
|
84
|
+
.from("mailbox_filter_settings")
|
|
85
|
+
.update({ ...rest, updated_at: new Date().toISOString() })
|
|
86
|
+
.eq("singleton", true);
|
|
87
|
+
if (error)
|
|
88
|
+
throw new Error(error.message);
|
|
89
|
+
const { data: ai } = await admin.from("mailbox_ai_settings").select("id").limit(1).maybeSingle();
|
|
90
|
+
if (ai?.id) {
|
|
91
|
+
const { error: e2 } = await admin.from("mailbox_ai_settings").update({ enabled: auto_analyze }).eq("id", ai.id);
|
|
92
|
+
if (e2)
|
|
93
|
+
throw new Error(e2.message);
|
|
94
|
+
}
|
|
95
|
+
return { ok: true };
|
|
96
|
+
});
|
|
97
|
+
/** Telling van openstaande voorstellen die het nieuwe filter als "geen bestelling" zou zien. Wijzigt niets. */
|
|
98
|
+
export const countLikelyFakeProposals = createServerFn({ method: "GET" })
|
|
99
|
+
.middleware([requireSupabaseAuth])
|
|
100
|
+
.handler(async () => {
|
|
101
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
102
|
+
const admin = supabaseAdmin;
|
|
103
|
+
const { loadFilterSettings } = await import("./mailbox-ingest.server.js");
|
|
104
|
+
const { classifyMessage } = await import("./mailbox-filter.js");
|
|
105
|
+
const s = await loadFilterSettings(admin);
|
|
106
|
+
let total = 0, known = 0, wouldSkip = 0, withSignals = 0;
|
|
107
|
+
for (let from = 0; from < 20000; from += 1000) {
|
|
108
|
+
const { data } = await admin
|
|
109
|
+
.from("mailbox_order_proposals")
|
|
110
|
+
.select("matched_customer_id, mailbox_messages(from_email, subject, preview)")
|
|
111
|
+
.eq("status", "pending")
|
|
112
|
+
.range(from, from + 999);
|
|
113
|
+
const rows = Array.isArray(data) ? data : [];
|
|
114
|
+
for (const r of rows) {
|
|
115
|
+
total++;
|
|
116
|
+
const m = r.mailbox_messages ?? {};
|
|
117
|
+
const isKnown = !!r.matched_customer_id;
|
|
118
|
+
if (isKnown)
|
|
119
|
+
known++;
|
|
120
|
+
const c = classifyMessage({ fromEmail: String(m.from_email ?? ""), subject: String(m.subject ?? ""), body: String(m.preview ?? ""), knownSender: isKnown }, s);
|
|
121
|
+
if (c.outcome === "geen_bestelling")
|
|
122
|
+
wouldSkip++;
|
|
123
|
+
else if (c.outcome === "onbekend_met_signalen")
|
|
124
|
+
withSignals++;
|
|
125
|
+
}
|
|
126
|
+
if (rows.length < 1000)
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
return { total, known, wouldSkip, withSignals };
|
|
130
|
+
});
|
|
131
|
+
// ─── Testset ────────────────────────────────────────────────────────────────
|
|
132
|
+
export const listTestCases = createServerFn({ method: "GET" })
|
|
133
|
+
.middleware([requireSupabaseAuth])
|
|
134
|
+
.handler(async () => {
|
|
135
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
136
|
+
const { data, error } = await supabaseAdmin
|
|
137
|
+
.from("mailbox_test_cases")
|
|
138
|
+
.select("*")
|
|
139
|
+
.order("sort_order", { ascending: true });
|
|
140
|
+
if (error)
|
|
141
|
+
throw new Error(error.message);
|
|
142
|
+
return { items: Array.isArray(data) ? data : [] };
|
|
143
|
+
});
|
|
144
|
+
const CaseInput = z.object({
|
|
145
|
+
id: z.string().uuid().optional(),
|
|
146
|
+
name: z.string().trim().min(1).max(120),
|
|
147
|
+
subject: z.string().max(500),
|
|
148
|
+
from_email: z.string().max(320),
|
|
149
|
+
body_text: z.string().max(20000),
|
|
150
|
+
has_attachment: z.boolean(),
|
|
151
|
+
expected: z.record(z.string(), z.any()),
|
|
152
|
+
});
|
|
153
|
+
export const saveTestCase = createServerFn({ method: "POST" })
|
|
154
|
+
.middleware([requireSupabaseAuth])
|
|
155
|
+
.inputValidator((input) => CaseInput.parse(input))
|
|
156
|
+
.handler(async ({ data }) => {
|
|
157
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
158
|
+
const admin = supabaseAdmin;
|
|
159
|
+
const row = { ...data, updated_at: new Date().toISOString() };
|
|
160
|
+
const q = data.id
|
|
161
|
+
? admin.from("mailbox_test_cases").update(row).eq("id", data.id)
|
|
162
|
+
: admin.from("mailbox_test_cases").insert(row);
|
|
163
|
+
const { error } = await q;
|
|
164
|
+
if (error)
|
|
165
|
+
throw new Error(error.message);
|
|
166
|
+
return { ok: true };
|
|
167
|
+
});
|
|
168
|
+
export const deleteTestCase = createServerFn({ method: "POST" })
|
|
169
|
+
.middleware([requireSupabaseAuth])
|
|
170
|
+
.inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
|
|
171
|
+
.handler(async ({ data }) => {
|
|
172
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
173
|
+
const { error } = await supabaseAdmin.from("mailbox_test_cases").delete().eq("id", data.id);
|
|
174
|
+
if (error)
|
|
175
|
+
throw new Error(error.message);
|
|
176
|
+
return { ok: true };
|
|
177
|
+
});
|
|
178
|
+
function compare(expected, run) {
|
|
179
|
+
const checks = [];
|
|
180
|
+
const add = (field, exp, act, ok = exp === act) => checks.push({ field, expected: exp, actual: act, ok });
|
|
181
|
+
const isOrder = run.analyzed && run.result && !run.result.error;
|
|
182
|
+
if (typeof expected.is_order === "boolean")
|
|
183
|
+
add("Bestelling", expected.is_order ? "ja" : "nee", run.analyzed ? "ja" : "nee", expected.is_order === run.analyzed);
|
|
184
|
+
if (!isOrder)
|
|
185
|
+
return checks;
|
|
186
|
+
const r = run.result;
|
|
187
|
+
if (expected.delivery_date)
|
|
188
|
+
add("Leverdatum", expected.delivery_date, r.delivery_date);
|
|
189
|
+
if (expected.reference)
|
|
190
|
+
add("Referentie", expected.reference, r.reference);
|
|
191
|
+
if (expected.forwarded_from)
|
|
192
|
+
add("Doorgestuurd van", expected.forwarded_from, run.sender);
|
|
193
|
+
if (expected.attachment_warning)
|
|
194
|
+
add("Bijlage-melding", true, r.attachment_unread);
|
|
195
|
+
const el = Array.isArray(expected.lines) ? expected.lines : [];
|
|
196
|
+
if (el.length)
|
|
197
|
+
add("Aantal regels", el.length, r.lines.length);
|
|
198
|
+
el.forEach((e, i) => {
|
|
199
|
+
const a = r.lines[i] ?? {};
|
|
200
|
+
const n = `Regel ${i + 1}`;
|
|
201
|
+
if (e.quantity !== undefined)
|
|
202
|
+
add(`${n} aantal`, e.quantity, a.quantity ?? null);
|
|
203
|
+
if (e.unit !== undefined)
|
|
204
|
+
add(`${n} eenheid`, e.unit, a.unit ?? null);
|
|
205
|
+
if (e.plants !== undefined)
|
|
206
|
+
add(`${n} planten`, e.plants, a.plants ?? null);
|
|
207
|
+
if (e.unit_price !== undefined)
|
|
208
|
+
add(`${n} prijs`, e.unit_price, a.unit_price ?? null);
|
|
209
|
+
if (e.product_known !== undefined)
|
|
210
|
+
add(`${n} artikel bekend`, e.product_known, !!a.product_known);
|
|
211
|
+
if (e.code !== undefined)
|
|
212
|
+
add(`${n} gekoppeld op code`, true, a.match_source === "code");
|
|
213
|
+
});
|
|
214
|
+
return checks;
|
|
215
|
+
}
|
|
216
|
+
/** Draait één voorbeeld: filter + extractie, maakt geen voorstel of order aan. */
|
|
217
|
+
export const runTestCase = createServerFn({ method: "POST" })
|
|
218
|
+
.middleware([requireSupabaseAuth])
|
|
219
|
+
.inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
|
|
220
|
+
.handler(async ({ data }) => {
|
|
221
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
|
|
222
|
+
const admin = supabaseAdmin;
|
|
223
|
+
const { data: tc, error } = await admin.from("mailbox_test_cases").select("*").eq("id", data.id).maybeSingle();
|
|
224
|
+
if (error || !tc)
|
|
225
|
+
throw new Error("Voorbeeld niet gevonden.");
|
|
226
|
+
const { dryRunAnalysis } = await import("./mailbox-ingest.server.js");
|
|
227
|
+
const run = await dryRunAnalysis(admin, {
|
|
228
|
+
subject: tc.subject ?? "",
|
|
229
|
+
body: tc.body_text ?? "",
|
|
230
|
+
fromEmail: tc.from_email ?? "",
|
|
231
|
+
hasAttachment: tc.has_attachment === true,
|
|
232
|
+
simulateKnownSender: tc.expected?.known_sender === true,
|
|
233
|
+
});
|
|
234
|
+
const checks = compare(tc.expected ?? {}, run);
|
|
235
|
+
const result = { ...run, checks, passed: checks.every((c) => c.ok) };
|
|
236
|
+
await admin
|
|
237
|
+
.from("mailbox_test_cases")
|
|
238
|
+
.update({ last_result: result, last_run_at: new Date().toISOString() })
|
|
239
|
+
.eq("id", tc.id);
|
|
240
|
+
return result;
|
|
241
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface FilterSettings {
|
|
2
|
+
domain_match: boolean;
|
|
3
|
+
analyze_unknown_with_signals: boolean;
|
|
4
|
+
min_signals_unknown: number;
|
|
5
|
+
order_keywords: string[];
|
|
6
|
+
order_patterns: string[];
|
|
7
|
+
negative_keywords: string[];
|
|
8
|
+
skip_senders: string[];
|
|
9
|
+
skip_domains: string[];
|
|
10
|
+
}
|
|
11
|
+
export type FilterOutcome = "analyseren" | "onbekend_met_signalen" | "geen_bestelling";
|
|
12
|
+
export interface FilterResult {
|
|
13
|
+
outcome: FilterOutcome;
|
|
14
|
+
reason: string;
|
|
15
|
+
signals: string[];
|
|
16
|
+
}
|
|
17
|
+
/** Postgres-woordgrenzen (\m, \M) omzetten naar JS (\b). */
|
|
18
|
+
export declare function toJsRegex(pattern: string): RegExp | null;
|
|
19
|
+
export declare function classifyMessage(input: {
|
|
20
|
+
fromEmail: string;
|
|
21
|
+
subject: string;
|
|
22
|
+
body: string;
|
|
23
|
+
knownSender: boolean;
|
|
24
|
+
}, s: FilterSettings): FilterResult;
|
|
25
|
+
/**
|
|
26
|
+
* Alleen het nieuwste deel van de mail: geciteerde tekst en handtekening weg.
|
|
27
|
+
* Bij een doorgestuurde mail is het doorgestuurde bericht leidend en wordt de
|
|
28
|
+
* oorspronkelijke afzender teruggegeven.
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractLatestPart(body: string): {
|
|
31
|
+
text: string;
|
|
32
|
+
forwardedFrom: string | null;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=mailbox-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailbox-filter.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox-filter.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,OAAO,CAAC;IACtB,4BAA4B,EAAE,OAAO,CAAC;IACtC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,uBAAuB,GAAG,iBAAiB,CAAC;AAEvF,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAID,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMxD;AAoBD,wBAAgB,eAAe,CAC7B,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,EACjF,CAAC,EAAE,cAAc,GAChB,YAAY,CAoDd;AAqBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAgB9F"}
|