@startsimpli/integrations 0.2.2 → 0.2.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/integrations",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Provider-agnostic types, API clients, hooks, and UI primitives for email + calendar integrations across StartSimpli apps.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -431,6 +431,28 @@ describe('extractCommitments', () => {
|
|
|
431
431
|
expect(result.created).toBeGreaterThan(0);
|
|
432
432
|
});
|
|
433
433
|
|
|
434
|
+
it('slices a sweep so one call fits the gateway, and says where to resume', async () => {
|
|
435
|
+
// A deployed tenant closes a request at 60s and one call over the whole
|
|
436
|
+
// corpus does not finish inside it (measured on tandem — 504). The sweep is
|
|
437
|
+
// therefore BATCHED INTO CALLS THAT FIT, never one call per document.
|
|
438
|
+
const corpus = createFixtureCorpusApi({ scope: TANDEM, documents: FIXTURE_TRACKED_DOCUMENTS });
|
|
439
|
+
const extractor = createFixtureExtractor();
|
|
440
|
+
const spy = vi.spyOn(extractor, 'extract');
|
|
441
|
+
|
|
442
|
+
const first = await extractCommitments({ scope: TANDEM, limit: 2 }, { corpus, extractor });
|
|
443
|
+
expect(first.documentsProcessed).toBe(2);
|
|
444
|
+
expect(first.totalDocuments).toBe(FIXTURE_TRACKED_DOCUMENTS.length);
|
|
445
|
+
expect(first.nextOffset).toBe(2);
|
|
446
|
+
expect(spy.mock.calls[0][0].documents).toHaveLength(2); // still N per call
|
|
447
|
+
|
|
448
|
+
const second = await extractCommitments(
|
|
449
|
+
{ scope: TANDEM, offset: first.nextOffset!, limit: 2 },
|
|
450
|
+
{ corpus, extractor },
|
|
451
|
+
);
|
|
452
|
+
expect(second.documentsProcessed).toBe(1);
|
|
453
|
+
expect(second.nextOffset).toBeNull();
|
|
454
|
+
});
|
|
455
|
+
|
|
434
456
|
it('refuses outright when the session carries no workspace claim', async () => {
|
|
435
457
|
const corpus = createFixtureCorpusApi({ leakScope: true, documents: FIXTURE_TRACKED_DOCUMENTS });
|
|
436
458
|
const listDocuments = vi.spyOn(corpus, 'listDocuments');
|
|
@@ -196,6 +196,8 @@ describe('TrackerPage — what is outstanding', () => {
|
|
|
196
196
|
describe('TrackerPage — running the extraction', () => {
|
|
197
197
|
const RESULT = {
|
|
198
198
|
documentsProcessed: 16,
|
|
199
|
+
totalDocuments: 16,
|
|
200
|
+
nextOffset: null,
|
|
199
201
|
fromRules: 24,
|
|
200
202
|
fromModel: 11,
|
|
201
203
|
created: 30,
|
package/src/corpus/extract.ts
CHANGED
|
@@ -235,6 +235,17 @@ export interface ExtractRequest {
|
|
|
235
235
|
scope: CorpusScope;
|
|
236
236
|
/** Narrow the run to specific records. Omit to sweep the corpus. */
|
|
237
237
|
documentIds?: string[];
|
|
238
|
+
/**
|
|
239
|
+
* Where this call starts in the in-scope corpus, and how many documents it
|
|
240
|
+
* may take. Omit both to do the whole thing in one model call.
|
|
241
|
+
*
|
|
242
|
+
* This exists for a deployment fact, not a design preference: the tandem ALB
|
|
243
|
+
* closes a request at 60 seconds, and one batched call over all sixteen real
|
|
244
|
+
* documents does not finish inside it (measured — 504). Slicing keeps every
|
|
245
|
+
* call batched over N documents while letting the caller walk the corpus.
|
|
246
|
+
*/
|
|
247
|
+
offset?: number;
|
|
248
|
+
limit?: number;
|
|
238
249
|
}
|
|
239
250
|
|
|
240
251
|
export interface ExtractDeps {
|
|
@@ -258,12 +269,24 @@ export async function extractCommitments(
|
|
|
258
269
|
|
|
259
270
|
const { documents: candidates, matched } = await readDocuments(corpus, documentLimit, maxPages);
|
|
260
271
|
const { kept, dropped } = enforceScope(candidates, request.scope);
|
|
261
|
-
const
|
|
272
|
+
const inScope = request.documentIds?.length
|
|
262
273
|
? kept.filter((document) => request.documentIds!.includes(document.id))
|
|
263
274
|
: kept;
|
|
264
275
|
|
|
276
|
+
// The slice this call handles. `listDocuments` orders stably (the tenant's
|
|
277
|
+
// (org_id, entity_type, -created_at) index), so an offset means the same thing
|
|
278
|
+
// between calls of one sweep.
|
|
279
|
+
const offset = Math.max(0, request.offset ?? 0);
|
|
280
|
+
const documents = request.limit ? inScope.slice(offset, offset + request.limit) : inScope.slice(offset);
|
|
281
|
+
const consumed = offset + documents.length;
|
|
282
|
+
const nextOffset = consumed < inScope.length ? consumed : null;
|
|
283
|
+
|
|
265
284
|
if (documents.length === 0) {
|
|
266
|
-
return {
|
|
285
|
+
return {
|
|
286
|
+
...empty(extractor, 'nothing in scope'),
|
|
287
|
+
totalDocuments: inScope.length,
|
|
288
|
+
droppedOutOfScope: dropped.length,
|
|
289
|
+
};
|
|
267
290
|
}
|
|
268
291
|
|
|
269
292
|
// 3. rules first, so the model can be told what not to repeat.
|
|
@@ -317,6 +340,8 @@ export async function extractCommitments(
|
|
|
317
340
|
|
|
318
341
|
return {
|
|
319
342
|
documentsProcessed: documents.length,
|
|
343
|
+
totalDocuments: inScope.length,
|
|
344
|
+
nextOffset,
|
|
320
345
|
fromRules: ruleDrafts.length,
|
|
321
346
|
fromModel: modelDrafts.length,
|
|
322
347
|
created: reconciled.created,
|
|
@@ -325,8 +350,8 @@ export async function extractCommitments(
|
|
|
325
350
|
needsReview: resolutions.needsReview,
|
|
326
351
|
droppedOutOfScope: dropped.length,
|
|
327
352
|
provider: extractor.describe(),
|
|
328
|
-
...(matched > candidates.length ? { } : {}),
|
|
329
353
|
};
|
|
354
|
+
void matched;
|
|
330
355
|
}
|
|
331
356
|
|
|
332
357
|
/**
|
|
@@ -424,6 +449,8 @@ function empty(extractor: CorpusExtractorLlm, _why: string): ExtractionResult {
|
|
|
424
449
|
void _why;
|
|
425
450
|
return {
|
|
426
451
|
documentsProcessed: 0,
|
|
452
|
+
totalDocuments: 0,
|
|
453
|
+
nextOffset: null,
|
|
427
454
|
fromRules: 0,
|
|
428
455
|
fromModel: 0,
|
|
429
456
|
created: 0,
|
package/src/corpus/types.ts
CHANGED
|
@@ -565,12 +565,30 @@ export interface CommitmentTrackerApi extends CommitmentApi {
|
|
|
565
565
|
* Run extraction over documents that have not been processed. Optional: a
|
|
566
566
|
* read-only deployment omits it and the control does not render.
|
|
567
567
|
*/
|
|
568
|
-
extract?(input: {
|
|
568
|
+
extract?(input: {
|
|
569
|
+
scope: CorpusScope;
|
|
570
|
+
documentIds?: string[];
|
|
571
|
+
/** Resume point for a batched sweep. See `ExtractionResult.nextOffset`. */
|
|
572
|
+
offset?: number;
|
|
573
|
+
limit?: number;
|
|
574
|
+
}): Promise<ExtractionResult>;
|
|
569
575
|
}
|
|
570
576
|
|
|
571
577
|
export interface ExtractionResult {
|
|
572
|
-
/** Documents read and sent to the extractor. */
|
|
578
|
+
/** Documents read and sent to the extractor IN THIS CALL. */
|
|
573
579
|
documentsProcessed: number;
|
|
580
|
+
/** Documents in scope altogether, so a partial sweep can say what it is of. */
|
|
581
|
+
totalDocuments: number;
|
|
582
|
+
/**
|
|
583
|
+
* Where a resumed sweep should start, or `null` when the corpus is done.
|
|
584
|
+
*
|
|
585
|
+
* A run is RESUMABLE because it has to be: a deployed tenant sits behind a
|
|
586
|
+
* gateway with a request timeout (60s on the tandem ALB, measured — a single
|
|
587
|
+
* call over sixteen documents 504s), and one model call per document is
|
|
588
|
+
* forbidden. So the sweep is batched into calls that fit, and the caller walks
|
|
589
|
+
* the offsets. Still N documents per model call; just not always all of them.
|
|
590
|
+
*/
|
|
591
|
+
nextOffset: number | null;
|
|
574
592
|
/** Items the vendor's own structure produced, with no model involved. */
|
|
575
593
|
fromRules: number;
|
|
576
594
|
/** Items only the model found. */
|