@frihet/mcp-server 1.17.0 → 2.0.0
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/README.md +84 -59
- package/dist/agent-onboarding.d.ts +1 -1
- package/dist/agent-onboarding.js +3 -3
- package/dist/canary/mcp-harness.d.ts +1 -1
- package/dist/canary/mcp-harness.js +4 -1
- package/dist/capability-truth.js +20 -12
- package/dist/claude-candidate-contract.d.ts +482 -0
- package/dist/claude-candidate-contract.js +87 -0
- package/dist/claude-output-privacy.d.ts +17 -0
- package/dist/claude-output-privacy.js +240 -0
- package/dist/claude-profile.d.ts +18 -0
- package/dist/claude-profile.js +260 -0
- package/dist/client-interface.d.ts +26 -20
- package/dist/client.d.ts +65 -29
- package/dist/client.js +278 -167
- package/dist/demo-client.d.ts +27 -21
- package/dist/demo-client.js +31 -14
- package/dist/deposit-contract.d.ts +29 -0
- package/dist/deposit-contract.js +76 -0
- package/dist/fetch-no-redirect.d.ts +3 -0
- package/dist/fetch-no-redirect.js +16 -0
- package/dist/fiscal-aliases.d.ts +2 -2
- package/dist/fiscal-aliases.js +2 -2
- package/dist/fiscal-period.d.ts +26 -0
- package/dist/fiscal-period.js +31 -0
- package/dist/google-profile.d.ts +19 -0
- package/dist/google-profile.js +122 -0
- package/dist/index.js +31 -7
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +2 -2
- package/dist/muse-profile.d.ts +19 -0
- package/dist/muse-profile.js +122 -0
- package/dist/observability.js +3 -2
- package/dist/openai-profile.d.ts +2 -2
- package/dist/openai-profile.js +37 -11
- package/dist/openai-review-contract.d.ts +1 -1
- package/dist/openai-review-contract.js +4 -5
- package/dist/openai-review-oauth.d.ts +107 -7
- package/dist/openai-review-oauth.js +161 -17
- package/dist/prompts/register-all.js +32 -78
- package/dist/redaction.d.ts +5 -0
- package/dist/redaction.js +9 -0
- package/dist/resources/register-all.js +150 -54
- package/dist/retry-after.d.ts +19 -0
- package/dist/retry-after.js +84 -0
- package/dist/server-composition.d.ts +5 -2
- package/dist/server-composition.js +35 -5
- package/dist/tool-exposure.d.ts +6 -5
- package/dist/tool-exposure.js +15 -13
- package/dist/tools/audit_gl.js +8 -8
- package/dist/tools/backend-availability.d.ts +8 -29
- package/dist/tools/backend-availability.js +31 -30
- package/dist/tools/bank_rules.js +66 -15
- package/dist/tools/banking.js +5 -13
- package/dist/tools/clients.js +252 -6
- package/dist/tools/deposits.js +107 -97
- package/dist/tools/einvoice.d.ts +17 -3
- package/dist/tools/einvoice.js +59 -12
- package/dist/tools/fiscal.d.ts +3 -4
- package/dist/tools/fiscal.js +93 -60
- package/dist/tools/gestoria.js +15 -7
- package/dist/tools/hr.js +41 -11
- package/dist/tools/igic.d.ts +9 -8
- package/dist/tools/igic.js +34 -64
- package/dist/tools/impuesto_sociedades.d.ts +4 -5
- package/dist/tools/impuesto_sociedades.js +16 -37
- package/dist/tools/intelligence.js +164 -10
- package/dist/tools/invoices.js +109 -42
- package/dist/tools/kitchen.js +19 -9
- package/dist/tools/quotes.js +86 -25
- package/dist/tools/recurring.js +26 -25
- package/dist/tools/register-all.d.ts +2 -2
- package/dist/tools/register-all.js +9 -6
- package/dist/tools/search.d.ts +19 -0
- package/dist/tools/search.js +84 -0
- package/dist/tools/shared.d.ts +17 -37
- package/dist/tools/shared.js +96 -30
- package/dist/tools/team.js +8 -16
- package/dist/tools/time.js +4 -12
- package/dist/tools/vendors.js +240 -6
- package/dist/types.d.ts +3 -1
- package/dist/workflows/daily-operational-brief.d.ts +163 -0
- package/dist/workflows/daily-operational-brief.js +881 -0
- package/docs/agent-onboarding.json +15 -5
- package/package.json +26 -12
- package/dist/tools/onboarding.d.ts +0 -19
- package/dist/tools/onboarding.js +0 -64
package/dist/tools/vendors.js
CHANGED
|
@@ -13,6 +13,168 @@ const addressSchema = z
|
|
|
13
13
|
})
|
|
14
14
|
.optional()
|
|
15
15
|
.describe("Vendor address / Direccion del proveedor");
|
|
16
|
+
/**
|
|
17
|
+
* Splits a concatenated / camelCase / snake_case / kebab-case search query into
|
|
18
|
+
* space-separated tokens (e.g. "CanariasBlue" -> "Canarias Blue",
|
|
19
|
+
* "SLCanarias" -> "SL Canarias", "Canarias_Blue" -> "Canarias Blue").
|
|
20
|
+
*/
|
|
21
|
+
function splitCompoundQuery(q) {
|
|
22
|
+
return q
|
|
23
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
24
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
25
|
+
.replace(/[_-]+/g, " ")
|
|
26
|
+
.replace(/\s+/g, " ")
|
|
27
|
+
.trim();
|
|
28
|
+
}
|
|
29
|
+
const OPTIONAL_VENDOR_STRING_FIELDS = [
|
|
30
|
+
"email",
|
|
31
|
+
"phone",
|
|
32
|
+
"taxId",
|
|
33
|
+
"createdAt",
|
|
34
|
+
"updatedAt",
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Normalizes a vendor record returned by the Frihet API so it always satisfies
|
|
38
|
+
* `vendorItemOutput` without losing ERP-canonical data:
|
|
39
|
+
* - Strips `null` on optional string fields (`email`, `phone`, `taxId`,
|
|
40
|
+
* `createdAt`, `updatedAt`) so `z.string().optional()` never rejects legacy docs.
|
|
41
|
+
* - Backfills `taxId` from legacy `nif` / `cif` / `vatNumber` / `tax_id` when
|
|
42
|
+
* `taxId` is absent or null, preserving all original fields.
|
|
43
|
+
* - Converts legacy string `address` into `{ street }`.
|
|
44
|
+
* - Omits `null` or empty `address`.
|
|
45
|
+
* - Maps ERP canonical address keys (`province` -> `state`, `zip` -> `postalCode`,
|
|
46
|
+
* `countryCode` -> `country`).
|
|
47
|
+
*/
|
|
48
|
+
function normalizeVendorRecord(vendorRecord) {
|
|
49
|
+
if (!vendorRecord || typeof vendorRecord !== "object" || Array.isArray(vendorRecord)) {
|
|
50
|
+
return vendorRecord;
|
|
51
|
+
}
|
|
52
|
+
const out = { ...vendorRecord };
|
|
53
|
+
for (const field of OPTIONAL_VENDOR_STRING_FIELDS) {
|
|
54
|
+
if (out[field] === null) {
|
|
55
|
+
delete out[field];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (typeof out.taxId !== "string" || !out.taxId.trim()) {
|
|
59
|
+
for (const alias of ["nif", "cif", "vatNumber", "tax_id"]) {
|
|
60
|
+
const candidate = out[alias];
|
|
61
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
62
|
+
out.taxId = candidate.trim();
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const rawAddress = out.address;
|
|
68
|
+
const rootStreet = typeof out.street === "string" && out.street.trim() ? out.street.trim() : undefined;
|
|
69
|
+
const rootCity = typeof out.city === "string" && out.city.trim() ? out.city.trim() : undefined;
|
|
70
|
+
const rootState = typeof out.state === "string" && out.state.trim()
|
|
71
|
+
? out.state.trim()
|
|
72
|
+
: typeof out.province === "string" && out.province.trim()
|
|
73
|
+
? out.province.trim()
|
|
74
|
+
: undefined;
|
|
75
|
+
const rootPostalCode = typeof out.postalCode === "string" && out.postalCode.trim()
|
|
76
|
+
? out.postalCode.trim()
|
|
77
|
+
: typeof out.zip === "string" && out.zip.trim()
|
|
78
|
+
? out.zip.trim()
|
|
79
|
+
: undefined;
|
|
80
|
+
const rootCountry = typeof out.country === "string" && out.country.trim()
|
|
81
|
+
? out.country.trim()
|
|
82
|
+
: typeof out.countryCode === "string" && out.countryCode.trim()
|
|
83
|
+
? out.countryCode.trim()
|
|
84
|
+
: undefined;
|
|
85
|
+
const buildFlatAddress = (streetOverride) => {
|
|
86
|
+
const built = {};
|
|
87
|
+
const st = streetOverride ?? rootStreet;
|
|
88
|
+
if (st !== undefined)
|
|
89
|
+
built.street = st;
|
|
90
|
+
if (rootCity !== undefined)
|
|
91
|
+
built.city = rootCity;
|
|
92
|
+
if (rootState !== undefined)
|
|
93
|
+
built.state = rootState;
|
|
94
|
+
if (rootPostalCode !== undefined)
|
|
95
|
+
built.postalCode = rootPostalCode;
|
|
96
|
+
if (rootCountry !== undefined)
|
|
97
|
+
built.country = rootCountry;
|
|
98
|
+
return Object.keys(built).length > 0 ? built : undefined;
|
|
99
|
+
};
|
|
100
|
+
if (rawAddress === undefined || rawAddress === null) {
|
|
101
|
+
if (rawAddress === null) {
|
|
102
|
+
delete out.address;
|
|
103
|
+
}
|
|
104
|
+
const flatAddr = buildFlatAddress();
|
|
105
|
+
if (flatAddr) {
|
|
106
|
+
out.address = flatAddr;
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
if (typeof rawAddress === "string") {
|
|
111
|
+
const trimmed = rawAddress.trim();
|
|
112
|
+
const flatAddr = buildFlatAddress(trimmed || undefined);
|
|
113
|
+
if (!flatAddr) {
|
|
114
|
+
delete out.address;
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
out.address = flatAddr;
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
if (typeof rawAddress === "object" && !Array.isArray(rawAddress)) {
|
|
121
|
+
const addrObj = rawAddress;
|
|
122
|
+
const normalizedAddress = {};
|
|
123
|
+
for (const [k, v] of Object.entries(addrObj)) {
|
|
124
|
+
if (v !== null && v !== undefined) {
|
|
125
|
+
normalizedAddress[k] = v;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const street = typeof addrObj.street === "string" && addrObj.street.trim()
|
|
129
|
+
? addrObj.street.trim()
|
|
130
|
+
: rootStreet;
|
|
131
|
+
const city = typeof addrObj.city === "string" && addrObj.city.trim()
|
|
132
|
+
? addrObj.city.trim()
|
|
133
|
+
: rootCity;
|
|
134
|
+
const state = typeof addrObj.state === "string" && addrObj.state.trim()
|
|
135
|
+
? addrObj.state.trim()
|
|
136
|
+
: typeof addrObj.province === "string" && addrObj.province.trim()
|
|
137
|
+
? addrObj.province.trim()
|
|
138
|
+
: rootState;
|
|
139
|
+
const postalCode = typeof addrObj.postalCode === "string" && addrObj.postalCode.trim()
|
|
140
|
+
? addrObj.postalCode.trim()
|
|
141
|
+
: typeof addrObj.zip === "string" && addrObj.zip.trim()
|
|
142
|
+
? addrObj.zip.trim()
|
|
143
|
+
: rootPostalCode;
|
|
144
|
+
const country = typeof addrObj.country === "string" && addrObj.country.trim()
|
|
145
|
+
? addrObj.country.trim()
|
|
146
|
+
: typeof addrObj.countryCode === "string" && addrObj.countryCode.trim()
|
|
147
|
+
? addrObj.countryCode.trim()
|
|
148
|
+
: rootCountry;
|
|
149
|
+
if (street !== undefined)
|
|
150
|
+
normalizedAddress.street = street;
|
|
151
|
+
else
|
|
152
|
+
delete normalizedAddress.street;
|
|
153
|
+
if (city !== undefined)
|
|
154
|
+
normalizedAddress.city = city;
|
|
155
|
+
else
|
|
156
|
+
delete normalizedAddress.city;
|
|
157
|
+
if (state !== undefined)
|
|
158
|
+
normalizedAddress.state = state;
|
|
159
|
+
else
|
|
160
|
+
delete normalizedAddress.state;
|
|
161
|
+
if (postalCode !== undefined)
|
|
162
|
+
normalizedAddress.postalCode = postalCode;
|
|
163
|
+
else
|
|
164
|
+
delete normalizedAddress.postalCode;
|
|
165
|
+
if (country !== undefined)
|
|
166
|
+
normalizedAddress.country = country;
|
|
167
|
+
else
|
|
168
|
+
delete normalizedAddress.country;
|
|
169
|
+
if (Object.keys(normalizedAddress).length === 0) {
|
|
170
|
+
delete out.address;
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
out.address = normalizedAddress;
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
16
178
|
export function registerVendorTools(server, client) {
|
|
17
179
|
// -- list_vendors --
|
|
18
180
|
server.registerTool("list_vendors", {
|
|
@@ -31,10 +193,82 @@ export function registerVendorTools(server, client) {
|
|
|
31
193
|
},
|
|
32
194
|
outputSchema: paginatedOutput(vendorItemOutput, { projectable: true }),
|
|
33
195
|
}, async ({ q, limit, offset, after, fields }) => withToolLogging("list_vendors", async () => {
|
|
34
|
-
const
|
|
196
|
+
const trimmedQ = q?.trim();
|
|
197
|
+
const trimmedAfter = after?.trim();
|
|
198
|
+
// Limitation in Frihet-ERP `listResources` (publicApi.ts L8158-8268):
|
|
199
|
+
// When `search` (`q`) is provided, the backend slices results by `offset`/`limit`
|
|
200
|
+
// and returns before evaluating `cursorParam` (`after`). Protected profiles
|
|
201
|
+
// (Claude `claudeToolResult` and OpenAI `applyOpenAIProfile`) rebuild `content`
|
|
202
|
+
// from `structuredContent` while `paginatedOutput` is frozen, so fail closed
|
|
203
|
+
// (`isError: true` without `structuredContent`).
|
|
204
|
+
if (trimmedQ && trimmedAfter) {
|
|
205
|
+
return {
|
|
206
|
+
content: [{
|
|
207
|
+
type: "text",
|
|
208
|
+
text: "Error: text search ('q') cannot be combined with cursor pagination ('after') because the ERP backend slices search results by 'offset'/'limit' and ignores 'after'. Use 'offset' and 'limit' when searching with 'q', or omit 'q' when paginating with 'after'. / " +
|
|
209
|
+
"Error: la busqueda textual ('q') no se puede combinar con el cursor 'after' porque el backend pagina las busquedas mediante 'offset'/'limit' e ignora 'after'. Usa 'offset' y 'limit' con 'q', u omite 'q' al paginar con 'after'.",
|
|
210
|
+
}],
|
|
211
|
+
isError: true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
let result = await client.listVendors({ q, limit, offset, after, fields });
|
|
215
|
+
let usedFallback = false;
|
|
216
|
+
const splitQ = trimmedQ ? splitCompoundQuery(trimmedQ) : undefined;
|
|
217
|
+
const isSubsequentPage = (offset !== undefined && offset > 0) || Boolean(after);
|
|
218
|
+
const initialTruncated = result?.truncated === true;
|
|
219
|
+
const canFallback = splitQ !== undefined && splitQ !== trimmedQ && !after && !initialTruncated;
|
|
220
|
+
const hasZeroTotalMatches = Array.isArray(result?.data) &&
|
|
221
|
+
result.data.length === 0 &&
|
|
222
|
+
(isSubsequentPage ? result.total === 0 : result.total === 0 || result.total === undefined);
|
|
223
|
+
if (canFallback && hasZeroTotalMatches) {
|
|
224
|
+
try {
|
|
225
|
+
const fallbackResult = await client.listVendors({ q: splitQ, limit, offset, after, fields });
|
|
226
|
+
const fallbackTruncated = fallbackResult?.truncated === true;
|
|
227
|
+
if (fallbackTruncated ||
|
|
228
|
+
(fallbackResult?.data && (fallbackResult.data.length > 0 || (fallbackResult.total !== undefined && fallbackResult.total > 0)))) {
|
|
229
|
+
result = fallbackResult;
|
|
230
|
+
usedFallback = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Preserve original response if fallback fails
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const rawEnvelope = (result ?? {});
|
|
238
|
+
const isTruncated = rawEnvelope.truncated === true;
|
|
239
|
+
const normalizedData = Array.isArray(result?.data)
|
|
240
|
+
? result.data.map((v) => (v && typeof v === "object" ? normalizeVendorRecord(v) : v))
|
|
241
|
+
: [];
|
|
242
|
+
// Conservative refusal when the ERP 500-document scan cap was saturated (`truncated: true`).
|
|
243
|
+
// Because `paginatedOutput` is a frozen schema contract and both Claude (`claudeToolResult`)
|
|
244
|
+
// and OpenAI (`applyOpenAIProfile`) overwrite `content` whenever `structuredContent` is
|
|
245
|
+
// present, returning `structuredContent` here would hide the truncation warning and present
|
|
246
|
+
// an incomplete 500-doc scan as exhaustive.
|
|
247
|
+
if (isTruncated) {
|
|
248
|
+
const partialCount = typeof result?.total === "number" ? result.total : normalizedData.length;
|
|
249
|
+
return {
|
|
250
|
+
content: [{
|
|
251
|
+
type: "text",
|
|
252
|
+
text: `Error: vendor search hit the ERP 500-document scan cap (truncated=true; ${partialCount} match(es) within the first 500 scanned records) and cannot be returned as an exhaustive result across protected profiles. Narrow 'q' or paginate without 'q' using 'after'. / ` +
|
|
253
|
+
`Error: la busqueda de proveedores alcanzo el limite de escaneo de 500 documentos del ERP (truncated=true; ${partialCount} coincidencia(s) en los primeros 500 registros) y no puede devolverse como resultado exhaustivo. Acota 'q' o pagina sin 'q' usando 'after'.`,
|
|
254
|
+
}],
|
|
255
|
+
isError: true,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const cleanEnvelope = {
|
|
259
|
+
data: normalizedData,
|
|
260
|
+
total: typeof result?.total === "number" ? result.total : normalizedData.length,
|
|
261
|
+
limit: typeof result?.limit === "number" ? result.limit : (limit ?? 20),
|
|
262
|
+
offset: typeof result?.offset === "number" ? result.offset : (offset ?? 0),
|
|
263
|
+
...(typeof result?.nextCursor === "string" ? { nextCursor: result.nextCursor } : {}),
|
|
264
|
+
};
|
|
265
|
+
const notes = [];
|
|
266
|
+
if (usedFallback) {
|
|
267
|
+
notes.push(` (criterio aplicado: "${splitQ}")`);
|
|
268
|
+
}
|
|
35
269
|
return {
|
|
36
|
-
content: [listContent(formatPaginatedResponse("vendors",
|
|
37
|
-
structuredContent:
|
|
270
|
+
content: [listContent(formatPaginatedResponse("vendors", cleanEnvelope) + notes.join(""))],
|
|
271
|
+
structuredContent: cleanEnvelope,
|
|
38
272
|
};
|
|
39
273
|
}));
|
|
40
274
|
// -- get_vendor --
|
|
@@ -48,7 +282,7 @@ export function registerVendorTools(server, client) {
|
|
|
48
282
|
},
|
|
49
283
|
outputSchema: vendorItemOutput,
|
|
50
284
|
}, async ({ id }) => withToolLogging("get_vendor", async () => {
|
|
51
|
-
const result = await client.getVendor(id);
|
|
285
|
+
const result = normalizeVendorRecord(await client.getVendor(id));
|
|
52
286
|
return {
|
|
53
287
|
content: [getContent(formatRecord("Vendor", result))],
|
|
54
288
|
structuredContent: result,
|
|
@@ -72,7 +306,7 @@ export function registerVendorTools(server, client) {
|
|
|
72
306
|
},
|
|
73
307
|
outputSchema: vendorItemOutput,
|
|
74
308
|
}, async (input) => withToolLogging("create_vendor", async () => {
|
|
75
|
-
const result = await client.createVendor(input);
|
|
309
|
+
const result = normalizeVendorRecord(await client.createVendor(input));
|
|
76
310
|
const hints = enrichResponse("vendors", "create", result);
|
|
77
311
|
return {
|
|
78
312
|
content: [mutateContent(formatRecord("Vendor created", result) + hints)],
|
|
@@ -96,7 +330,7 @@ export function registerVendorTools(server, client) {
|
|
|
96
330
|
},
|
|
97
331
|
outputSchema: vendorItemOutput,
|
|
98
332
|
}, async ({ id, ...data }) => withToolLogging("update_vendor", async () => {
|
|
99
|
-
const result = await client.updateVendor(id, data);
|
|
333
|
+
const result = normalizeVendorRecord(await client.updateVendor(id, data));
|
|
100
334
|
return {
|
|
101
335
|
content: [mutateContent(formatRecord("Vendor updated", result))],
|
|
102
336
|
structuredContent: result,
|
package/dist/types.d.ts
CHANGED
|
@@ -38,11 +38,13 @@ export interface Invoice {
|
|
|
38
38
|
status?: string;
|
|
39
39
|
notes?: string;
|
|
40
40
|
taxRate?: number;
|
|
41
|
+
operationType?: "service" | "goods";
|
|
42
|
+
fiscalTreatment?: "not_subject_location" | "export_exempt";
|
|
41
43
|
total?: number;
|
|
42
44
|
createdAt?: string;
|
|
43
45
|
updatedAt?: string;
|
|
44
46
|
}
|
|
45
|
-
export type CreateInvoiceInput = Pick<Invoice, "clientName" | "items"> & Partial<Pick<Invoice, "issueDate" | "dueDate" | "status" | "notes" | "taxRate">>;
|
|
47
|
+
export type CreateInvoiceInput = Pick<Invoice, "clientName" | "items"> & Partial<Pick<Invoice, "issueDate" | "dueDate" | "status" | "notes" | "taxRate" | "operationType" | "fiscalTreatment">>;
|
|
46
48
|
export type UpdateInvoiceInput = Partial<CreateInvoiceInput>;
|
|
47
49
|
export interface Expense {
|
|
48
50
|
id: string;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daily Operational Brief workflow ("¿Qué debería revisar hoy en mi negocio y por qué?").
|
|
3
|
+
*
|
|
4
|
+
* Grounded strictly in read-only Frihet workspace tools:
|
|
5
|
+
* - get_business_context
|
|
6
|
+
* - list_invoices
|
|
7
|
+
* - list_quotes
|
|
8
|
+
* - list_expenses
|
|
9
|
+
* - list_clients
|
|
10
|
+
* - list_client_activities
|
|
11
|
+
*
|
|
12
|
+
* Enforces:
|
|
13
|
+
* 1. Explicit temporal scope (`unfiltered_operational_sample_as_of_date` vs `currentMonthActivity`)
|
|
14
|
+
* and per-resource coverage (inspected vs total records).
|
|
15
|
+
* 2. Strict separation between draft documents (non-issued) and real/issued activity.
|
|
16
|
+
* 3. Explicit amount provenance (`stored_field`, `derived_from_items`, `business_context_summary`,
|
|
17
|
+
* `unavailable`, or `mixed_currency_unconverted`).
|
|
18
|
+
* - Absent/corrupt amounts NEVER coerce to 0 (`amount: null`, `provenance: "unavailable"`).
|
|
19
|
+
* - Multi-currency totals NEVER sum across currencies without conversion (`totalsByCurrency`, `currency: "MIXED"`).
|
|
20
|
+
* - Derived line-item totals apply canonical per-line `discount`, document `discountRate`,
|
|
21
|
+
* `taxRate`, `equivalenceSurchargeRate`, and `irpfRate` over the discounted tax-exclusive base.
|
|
22
|
+
* 4. Honest unpaid invoice semantics: full nominal totals of unpaid invoices are labeled
|
|
23
|
+
* "importe nominal de facturas no marcadas como pagadas; saldo pendiente real no determinable
|
|
24
|
+
* sin desglose de cobros parciales ni conciliación bancaria" (`realOutstandingBalanceDeterminable: false`).
|
|
25
|
+
* 5. Record-backed prioritized review items and suggested next actions without auto-executing writes.
|
|
26
|
+
* 6. Explicit declaration of what cannot be determined ("knownUnknowns").
|
|
27
|
+
*/
|
|
28
|
+
import type { IFrihetClient } from "../client-interface.js";
|
|
29
|
+
export type AmountProvenanceKind = "stored_field" | "derived_from_items" | "business_context_summary" | "unavailable" | "mixed_currency_unconverted";
|
|
30
|
+
export interface CurrencyBucketAmount {
|
|
31
|
+
amount: number;
|
|
32
|
+
currency: string;
|
|
33
|
+
provenance: "stored_field" | "derived_from_items";
|
|
34
|
+
documentCount: number;
|
|
35
|
+
}
|
|
36
|
+
export interface ProvenancedAmount {
|
|
37
|
+
amount: number | null;
|
|
38
|
+
currency: string;
|
|
39
|
+
provenance: AmountProvenanceKind;
|
|
40
|
+
fieldOrFormula: string;
|
|
41
|
+
totalsByCurrency?: Record<string, CurrencyBucketAmount>;
|
|
42
|
+
unavailableCount?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface ResourceCoverage {
|
|
45
|
+
collection: "invoices" | "quotes" | "expenses" | "clients" | "client_activities";
|
|
46
|
+
sourceTool: string;
|
|
47
|
+
inspected: number;
|
|
48
|
+
total: number;
|
|
49
|
+
complete: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface BriefCoverageSummary {
|
|
52
|
+
asOfDate: string;
|
|
53
|
+
periodMonth: string;
|
|
54
|
+
referenceMonth: string;
|
|
55
|
+
temporalScope: "unfiltered_operational_sample_as_of_date";
|
|
56
|
+
temporalScopeNote: string;
|
|
57
|
+
isPartial: boolean;
|
|
58
|
+
resources: Record<"invoices" | "quotes" | "expenses" | "clients" | "client_activities", ResourceCoverage>;
|
|
59
|
+
}
|
|
60
|
+
export interface BriefDocumentSummary {
|
|
61
|
+
id: string;
|
|
62
|
+
documentNumber: string | null;
|
|
63
|
+
status: string;
|
|
64
|
+
counterpartyName: string;
|
|
65
|
+
issueDate: string | null;
|
|
66
|
+
dueDate: string | null;
|
|
67
|
+
amount: ProvenancedAmount;
|
|
68
|
+
}
|
|
69
|
+
export interface BriefPriorityItem {
|
|
70
|
+
priority: "high" | "medium" | "low";
|
|
71
|
+
category: "overdue_invoice" | "draft_invoice" | "draft_or_expiring_quote" | "expense_review" | "client_followup" | "plan_quota";
|
|
72
|
+
title: string;
|
|
73
|
+
why: string;
|
|
74
|
+
recordRefs: Array<{
|
|
75
|
+
type: "invoice" | "quote" | "expense" | "client" | "client_activity" | "plan_quota";
|
|
76
|
+
id: string;
|
|
77
|
+
label: string;
|
|
78
|
+
}>;
|
|
79
|
+
amount?: ProvenancedAmount;
|
|
80
|
+
suggestedNextAction: {
|
|
81
|
+
description: string;
|
|
82
|
+
recommendedTool: string;
|
|
83
|
+
requiresConfirmation: boolean;
|
|
84
|
+
autoExecuted: false;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export interface DailyOperationalBrief {
|
|
88
|
+
question: string;
|
|
89
|
+
coverage: BriefCoverageSummary;
|
|
90
|
+
activitySeparation: {
|
|
91
|
+
draftActivity: {
|
|
92
|
+
note: string;
|
|
93
|
+
invoiceDraftsCount: number;
|
|
94
|
+
invoiceDraftsTotal: ProvenancedAmount;
|
|
95
|
+
invoiceDrafts: BriefDocumentSummary[];
|
|
96
|
+
quoteDraftsCount: number;
|
|
97
|
+
quoteDraftsTotal: ProvenancedAmount;
|
|
98
|
+
quoteDrafts: BriefDocumentSummary[];
|
|
99
|
+
};
|
|
100
|
+
issuedActivity: {
|
|
101
|
+
note: string;
|
|
102
|
+
realOutstandingBalanceDeterminable: false;
|
|
103
|
+
unpaidNominalSemantics: "nominal_unpaid_invoice_totals_without_partial_payments";
|
|
104
|
+
issuedInvoicesCount: number;
|
|
105
|
+
issuedInvoicesByStatus: Record<string, number>;
|
|
106
|
+
issuedInvoicesTotal: ProvenancedAmount;
|
|
107
|
+
issuedInvoices: BriefDocumentSummary[];
|
|
108
|
+
paidInvoicesCount: number;
|
|
109
|
+
paidInvoicesTotal: ProvenancedAmount;
|
|
110
|
+
paidInvoices: BriefDocumentSummary[];
|
|
111
|
+
unpaidNominalInvoicesCount: number;
|
|
112
|
+
unpaidNominalTotal: ProvenancedAmount;
|
|
113
|
+
unpaidNominalTotalsByCurrency: Record<string, CurrencyBucketAmount>;
|
|
114
|
+
overdueNominalInvoicesCount: number;
|
|
115
|
+
overdueNominalTotal: ProvenancedAmount;
|
|
116
|
+
onTimeNominalPendingCount: number;
|
|
117
|
+
onTimeNominalPendingTotal: ProvenancedAmount;
|
|
118
|
+
outstandingInvoicesCount: number;
|
|
119
|
+
outstandingInvoicesTotal: ProvenancedAmount;
|
|
120
|
+
overdueInvoicesCount: number;
|
|
121
|
+
overdueInvoicesTotal: ProvenancedAmount;
|
|
122
|
+
onTimePendingCount: number;
|
|
123
|
+
onTimePendingTotal: ProvenancedAmount;
|
|
124
|
+
outstandingInvoices: BriefDocumentSummary[];
|
|
125
|
+
};
|
|
126
|
+
recordedExpenses: {
|
|
127
|
+
note: string;
|
|
128
|
+
expensesCount: number;
|
|
129
|
+
expensesTotal: ProvenancedAmount;
|
|
130
|
+
deductibleCount: number;
|
|
131
|
+
deductibleTotal: ProvenancedAmount;
|
|
132
|
+
nonDeductibleCount: number;
|
|
133
|
+
nonDeductibleTotal: ProvenancedAmount;
|
|
134
|
+
pendingReviewCount: number;
|
|
135
|
+
};
|
|
136
|
+
currentMonthActivity: {
|
|
137
|
+
referenceMonth: string;
|
|
138
|
+
note: string;
|
|
139
|
+
issuedInvoicesInMonthCount: number;
|
|
140
|
+
issuedInvoicesInMonthTotal: ProvenancedAmount;
|
|
141
|
+
invoiceDraftsInMonthCount: number;
|
|
142
|
+
invoiceDraftsInMonthTotal: ProvenancedAmount;
|
|
143
|
+
expensesInMonthCount: number;
|
|
144
|
+
expensesInMonthTotal: ProvenancedAmount;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
priorities: BriefPriorityItem[];
|
|
148
|
+
knownUnknowns: string[];
|
|
149
|
+
mutationsExecuted: 0;
|
|
150
|
+
markdown: string;
|
|
151
|
+
}
|
|
152
|
+
export interface GenerateDailyBriefOptions {
|
|
153
|
+
asOfDate?: string;
|
|
154
|
+
pageLimit?: number;
|
|
155
|
+
maxActivityClients?: number;
|
|
156
|
+
}
|
|
157
|
+
export declare function formatProvenancedAmount(p: ProvenancedAmount): string;
|
|
158
|
+
export declare function resolveDocumentAmount(doc: Record<string, unknown>, defaultCurrency?: string): ProvenancedAmount;
|
|
159
|
+
export declare function sumProvenancedAmounts(amounts: ProvenancedAmount[], defaultCurrency?: string): ProvenancedAmount;
|
|
160
|
+
export declare function generateDailyOperationalBrief(client: Pick<IFrihetClient, "getBusinessContext" | "listInvoices" | "listQuotes" | "listExpenses" | "listClients" | "listClientActivities">, options?: GenerateDailyBriefOptions): Promise<DailyOperationalBrief>;
|
|
161
|
+
export declare function buildScenario1CompleteWorkspaceClient(callLog: string[]): Pick<IFrihetClient, "getBusinessContext" | "listInvoices" | "listQuotes" | "listExpenses" | "listClients" | "listClientActivities">;
|
|
162
|
+
export declare function buildScenario2PartialCoverageClient(callLog: string[]): Pick<IFrihetClient, "getBusinessContext" | "listInvoices" | "listQuotes" | "listExpenses" | "listClients" | "listClientActivities">;
|
|
163
|
+
//# sourceMappingURL=daily-operational-brief.d.ts.map
|