tablefacts 0.1.0 → 0.3.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.
Files changed (51) hide show
  1. package/.env.example +3 -1
  2. package/CHANGELOG.md +61 -0
  3. package/README.md +67 -19
  4. package/package.json +14 -3
  5. package/src/lib/types.mjs +16 -4
  6. package/src/menu/README.md +63 -15
  7. package/src/menu/cluvi/config.mjs +6 -0
  8. package/src/menu/cluvi/import.mjs +6 -2
  9. package/src/menu/lib/db.mjs +81 -17
  10. package/src/menu/lib/import.mjs +20 -3
  11. package/src/menu/lib/run.mjs +18 -0
  12. package/src/menu/lib/tables.mjs +44 -0
  13. package/src/menu/raw/config.mjs +12 -2
  14. package/src/menu/raw/extract.mjs +36 -15
  15. package/src/menu/raw/images.mjs +109 -0
  16. package/src/menu/raw/import.mjs +330 -51
  17. package/src/menu/raw/normalize.mjs +15 -4
  18. package/src/menu/raw/pdf.mjs +330 -0
  19. package/src/menu/raw/pdfjs.mjs +57 -0
  20. package/src/menu/raw/source.mjs +9 -2
  21. package/src/menu/raw/vision.mjs +124 -65
  22. package/src/research/README.md +23 -13
  23. package/src/research/index.mjs +52 -17
  24. package/src/research/lib/merge.mjs +11 -10
  25. package/src/research/lib/report.mjs +72 -19
  26. package/src/research/lib/search.mjs +127 -0
  27. package/src/research/lib/social.mjs +137 -26
  28. package/src/research/lib/util.mjs +22 -0
  29. package/src/research/lib/website.mjs +3 -14
  30. package/src/research/research.mjs +12 -8
  31. package/types/lib/types.d.mts +70 -6
  32. package/types/menu/cluvi/config.d.mts +1 -0
  33. package/types/menu/cluvi/import.d.mts +2 -0
  34. package/types/menu/lib/db.d.mts +31 -3
  35. package/types/menu/lib/import.d.mts +1 -1
  36. package/types/menu/lib/run.d.mts +6 -0
  37. package/types/menu/lib/tables.d.mts +18 -0
  38. package/types/menu/raw/config.d.mts +2 -0
  39. package/types/menu/raw/images.d.mts +27 -0
  40. package/types/menu/raw/import.d.mts +36 -7
  41. package/types/menu/raw/normalize.d.mts +7 -1
  42. package/types/menu/raw/pdf.d.mts +98 -0
  43. package/types/menu/raw/pdfjs.d.mts +12 -0
  44. package/types/menu/raw/source.d.mts +2 -0
  45. package/types/menu/raw/vision.d.mts +11 -1
  46. package/types/research/lib/merge.d.mts +4 -1
  47. package/types/research/lib/report.d.mts +14 -1
  48. package/types/research/lib/search.d.mts +58 -0
  49. package/types/research/lib/social.d.mts +32 -31
  50. package/types/research/lib/util.d.mts +5 -0
  51. package/types/research/lib/website.d.mts +20 -20
@@ -253,7 +253,19 @@ export type ImportOptions = {
253
253
  */
254
254
  json?: string;
255
255
  /**
256
- * Replace the whole menu, not only the categories in this import.
256
+ * This restaurant's table prefix (e.g. "makibar_"); empty for the unprefixed menu_* tables. Default "": importCluvi/importImageMenu fall back to the source config's.
257
+ */
258
+ tablePrefix?: string;
259
+ /**
260
+ * Write the unprefixed menu_* tables even when other restaurants' prefixed tables exist. Only for a single-restaurant database.
261
+ */
262
+ allowUnprefixed?: boolean;
263
+ /**
264
+ * Confirm a destructive `replaceAll`, which empties the target tables.
265
+ */
266
+ yes?: boolean;
267
+ /**
268
+ * Replace the whole menu, not only the categories in this import. Needs `yes`.
257
269
  */
258
270
  replaceAll?: boolean;
259
271
  /**
@@ -284,6 +296,7 @@ export type ImportResult = {
284
296
  */
285
297
  database: {
286
298
  label: string;
299
+ tables: string[];
287
300
  current: {
288
301
  categories: number;
289
302
  products: number;
@@ -297,6 +310,10 @@ export type ImportMenuOptions = ImportOptions & {
297
310
  title?: string;
298
311
  };
299
312
  export type CluviConfig = {
313
+ /**
314
+ * This restaurant's table prefix in a shared database (e.g. "cannario_"); empty for the unprefixed menu_* tables.
315
+ */
316
+ tablePrefix?: string;
300
317
  /**
301
318
  * Any page of the restaurant's Cluvi menu.
302
319
  */
@@ -315,6 +332,10 @@ export type CluviConfig = {
315
332
  sections?: Record<string, string>;
316
333
  };
317
334
  export type RawConfig = {
335
+ /**
336
+ * This restaurant's table prefix in a shared database (e.g. "mombasa_"); empty for the unprefixed menu_* tables.
337
+ */
338
+ tablePrefix?: string;
318
339
  /**
319
340
  * Page with the menu pictures, or a direct image URL.
320
341
  */
@@ -335,6 +356,10 @@ export type RawConfig = {
335
356
  * Multiplies every price. Default 1.
336
357
  */
337
358
  scale?: number;
359
+ /**
360
+ * Resolution a PDF page is rendered at before its product photos are screenshot. Default 2.
361
+ */
362
+ imageScale?: number;
338
363
  /**
339
364
  * Category that lists each group.
340
365
  */
@@ -372,7 +397,7 @@ export type CluviMenuOptions = {
372
397
  export type ImportCluviOptions = ImportOptions & CluviMenuOptions;
373
398
  export type ImageMenuReadOptions = {
374
399
  /**
375
- * Pages or image URLs. Default: the config's url.
400
+ * Pages or image URLs, or PDF file paths/URLs. Default: the config's url.
376
401
  */
377
402
  urls?: string[];
378
403
  /**
@@ -396,6 +421,18 @@ export type ImageMenuReadOptions = {
396
421
  * Read the pages again instead of using the saved transcriptions.
397
422
  */
398
423
  refresh?: boolean;
424
+ /**
425
+ * Also save the dish photos printed on a PDF page here (resolved against projectDir). Enables photo extraction.
426
+ */
427
+ imageDir?: string;
428
+ /**
429
+ * https folder the saved photos will be published at; fills each product's image_url with it plus the file name.
430
+ */
431
+ imageBaseUrl?: string;
432
+ /**
433
+ * How photos are found: 'auto' (default) reads a PDF page from its text and matches placed photos by position, using the model's boxes only when the page has none; 'always' reads every PDF page as a picture so the model boxes every dish's photo. Default 'auto'.
434
+ */
435
+ imageBoxes?: 'auto' | 'always';
399
436
  /**
400
437
  * Default: the raw config.mjs.
401
438
  */
@@ -406,6 +443,10 @@ export type ListMenuImagesOptions = {
406
443
  urls?: string[];
407
444
  only?: string | number[];
408
445
  minWidth?: number;
446
+ /**
447
+ * Project folder. Default: TABLEFACTS_PROJECT or the current folder.
448
+ */
449
+ projectDir?: string;
409
450
  config?: RawConfig;
410
451
  };
411
452
  export type MenuImage = {
@@ -413,8 +454,19 @@ export type MenuImage = {
413
454
  * Position as the CLI's --list numbers it.
414
455
  */
415
456
  number: number;
457
+ /**
458
+ * The image URL, or "<pdf path or URL>#<page number>" for a PDF page.
459
+ */
416
460
  url: string;
417
461
  alt: string;
462
+ /**
463
+ * Where the page came from. Default: 'image'.
464
+ */
465
+ kind?: 'image' | 'pdf';
466
+ /**
467
+ * Characters of text a PDF page has (0 means it is a scan).
468
+ */
469
+ chars?: number;
418
470
  };
419
471
  export type PriceFormat = {
420
472
  /**
@@ -574,7 +626,10 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
574
626
  * @typedef {object} ImportOptions
575
627
  * @property {boolean} [dryRun] Check and report, write nothing.
576
628
  * @property {string} [json] Also save the extracted menu as JSON at this path (resolved against projectDir).
577
- * @property {boolean} [replaceAll] Replace the whole menu, not only the categories in this import.
629
+ * @property {string} [tablePrefix] This restaurant's table prefix (e.g. "makibar_"); empty for the unprefixed menu_* tables. Default "": importCluvi/importImageMenu fall back to the source config's.
630
+ * @property {boolean} [allowUnprefixed] Write the unprefixed menu_* tables even when other restaurants' prefixed tables exist. Only for a single-restaurant database.
631
+ * @property {boolean} [yes] Confirm a destructive `replaceAll`, which empties the target tables.
632
+ * @property {boolean} [replaceAll] Replace the whole menu, not only the categories in this import. Needs `yes`.
578
633
  * @property {boolean} [force] Write even if the import has far fewer products than it replaces.
579
634
  * @property {string} [databaseUrl] Default: env.SUPABASE_DB_URL.
580
635
  * @property {Env} [env] Environment the database URL and keys are read from. Default process.env.
@@ -587,7 +642,7 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
587
642
  * @property {string[]} notes
588
643
  * @property {boolean} written
589
644
  * @property {boolean} dryRun
590
- * @property {{ label: string, current: { categories: number, products: number, kept: string[] } } | null} database Null when the database was not reached.
645
+ * @property {{ label: string, tables: string[], current: { categories: number, products: number, kept: string[] } } | null} database Null when the database was not reached.
591
646
  */
592
647
  /**
593
648
  * @typedef {ImportOptions & { menu: Menu, notes?: string[], title?: string }} ImportMenuOptions
@@ -595,6 +650,7 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
595
650
  /**
596
651
  * Restaurant-specific part of the Cluvi source (src/menu/cluvi/config.mjs).
597
652
  * @typedef {object} CluviConfig
653
+ * @property {string} [tablePrefix] This restaurant's table prefix in a shared database (e.g. "cannario_"); empty for the unprefixed menu_* tables.
598
654
  * @property {string} [url] Any page of the restaurant's Cluvi menu.
599
655
  * @property {{ slug: string, name: string, from: string[] }[]} [categories] Cluvi main categories folded into each site category.
600
656
  * @property {Record<string, string>} [sections] Cluvi subcategory to section name.
@@ -602,11 +658,13 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
602
658
  /**
603
659
  * Restaurant-specific part of the picture-menu source (src/menu/raw/config.mjs).
604
660
  * @typedef {object} RawConfig
661
+ * @property {string} [tablePrefix] This restaurant's table prefix in a shared database (e.g. "mombasa_"); empty for the unprefixed menu_* tables.
605
662
  * @property {string} [url] Page with the menu pictures, or a direct image URL.
606
663
  * @property {string} currency ISO code of the prices.
607
664
  * @property {string} [thousands] Thousands separator the menu prints. Default ".".
608
665
  * @property {string} [decimal] Decimal separator the menu prints. Default ",".
609
666
  * @property {number} [scale] Multiplies every price. Default 1.
667
+ * @property {number} [imageScale] Resolution a PDF page is rendered at before its product photos are screenshot. Default 2.
610
668
  * @property {{ slug: string, name: string, groups?: ('food' | 'drink')[] }[]} [categories] Category that lists each group.
611
669
  * @property {Record<string, string>} [placeIn] Section title to category slug.
612
670
  * @property {Record<string, string>} [sections] Section title to the name stored.
@@ -623,13 +681,16 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
623
681
  /** @typedef {ImportOptions & CluviMenuOptions} ImportCluviOptions */
624
682
  /**
625
683
  * @typedef {object} ImageMenuReadOptions
626
- * @property {string[]} [urls] Pages or image URLs. Default: the config's url.
684
+ * @property {string[]} [urls] Pages or image URLs, or PDF file paths/URLs. Default: the config's url.
627
685
  * @property {string | number[]} [only] Pages to read, e.g. '1,3-5' or [1, 3, 4, 5].
628
686
  * @property {string} [provider] Vision provider, see `providers`. Default MENU_VISION_PROVIDER or `defaultProvider`.
629
687
  * @property {string} [model]
630
688
  * @property {number} [minWidth] Ignore images declaring a smaller width. Default 500.
631
689
  * @property {string} [apiKey] Default: the provider's key in env.
632
690
  * @property {boolean} [refresh] Read the pages again instead of using the saved transcriptions.
691
+ * @property {string} [imageDir] Also save the dish photos printed on a PDF page here (resolved against projectDir). Enables photo extraction.
692
+ * @property {string} [imageBaseUrl] https folder the saved photos will be published at; fills each product's image_url with it plus the file name.
693
+ * @property {'auto' | 'always'} [imageBoxes] How photos are found: 'auto' (default) reads a PDF page from its text and matches placed photos by position, using the model's boxes only when the page has none; 'always' reads every PDF page as a picture so the model boxes every dish's photo. Default 'auto'.
633
694
  * @property {RawConfig} [config] Default: the raw config.mjs.
634
695
  */
635
696
  /** @typedef {ImportOptions & ImageMenuReadOptions} ImportImageMenuOptions */
@@ -638,13 +699,16 @@ export type TablefactsErrorCode = 'EUSAGE' | 'ECONFIG' | 'EDEPENDENCY' | 'EFAILE
638
699
  * @property {string[]} [urls]
639
700
  * @property {string | number[]} [only]
640
701
  * @property {number} [minWidth]
702
+ * @property {string} [projectDir] Project folder. Default: TABLEFACTS_PROJECT or the current folder.
641
703
  * @property {RawConfig} [config]
642
704
  */
643
705
  /**
644
706
  * @typedef {object} MenuImage
645
707
  * @property {number} number Position as the CLI's --list numbers it.
646
- * @property {string} url
708
+ * @property {string} url The image URL, or "<pdf path or URL>#<page number>" for a PDF page.
647
709
  * @property {string} alt
710
+ * @property {'image' | 'pdf'} [kind] Where the page came from. Default: 'image'.
711
+ * @property {number} [chars] Characters of text a PDF page has (0 means it is a scan).
648
712
  */
649
713
  /**
650
714
  * @typedef {object} PriceFormat
@@ -1,4 +1,5 @@
1
1
  declare const _default: {
2
+ tablePrefix: string;
2
3
  url: string;
3
4
  categories: {
4
5
  slug: string;
@@ -4,6 +4,7 @@
4
4
  */
5
5
  export declare function fetchCluviMenu({ url, service, lang, config }?: {
6
6
  config?: {
7
+ tablePrefix: string;
7
8
  url: string;
8
9
  categories: {
9
10
  slug: string;
@@ -26,6 +27,7 @@ export declare function fetchCluviMenu({ url, service, lang, config }?: {
26
27
  }[];
27
28
  notes: string[];
28
29
  title: string;
30
+ tablePrefix: string;
29
31
  }>;
30
32
  /**
31
33
  * Reads the menu from Cluvi and imports it. Takes importMenu's options too.
@@ -3,19 +3,47 @@ export declare function connect(url: any, { env }?: {}): Promise<{
3
3
  client: any;
4
4
  label: string;
5
5
  }>;
6
+ /**
7
+ * Refuses to touch another site's tables before anything is written.
8
+ *
9
+ * Checks the three target tables exist (pointing at the migration when they do not). Then, when
10
+ * no prefix is set, lists the other restaurants' `<prefix>_menu_categories` tables: any of them
11
+ * means this database is shared, so the import stops unless the caller confirmed it targets the
12
+ * one unprefixed set with `allowUnprefixed`. A whole-menu `replaceAll` is refused in that case
13
+ * whatever the flags say, because deleting every unprefixed row could destroy another site.
14
+ * Only reads; returns `{ prefix, tables, others }`.
15
+ * @param {any} client
16
+ * @param {{ tablePrefix?: string, allowUnprefixed?: boolean, replaceAll?: boolean }} [options]
17
+ */
18
+ export declare function assertTarget(client: any, { tablePrefix, allowUnprefixed, replaceAll }?: {
19
+ tablePrefix?: string;
20
+ allowUnprefixed?: boolean;
21
+ replaceAll?: boolean;
22
+ }): Promise<{
23
+ prefix: string;
24
+ tables: {
25
+ categories: string;
26
+ sections: string;
27
+ products: string;
28
+ };
29
+ others: any;
30
+ }>;
6
31
  /** What an import would replace: the categories it writes, or the whole menu. */
7
- export declare function inspect(client: any, menu: any, { replaceAll }?: {
32
+ export declare function inspect(client: any, menu: any, { replaceAll, tablePrefix }?: {
8
33
  replaceAll?: boolean | undefined;
34
+ tablePrefix?: string | undefined;
9
35
  }): Promise<any>;
10
36
  /**
11
37
  * Replaces the menu in one transaction, so readers see the old menu or the new
12
38
  * one and a failure changes nothing. By default only the categories in `menu`
13
39
  * are replaced (deleting a category cascades to its sections and products);
14
40
  * `replaceAll` empties the menu first. Ids are generated here so the rows can
15
- * be inserted in bulk, a column at a time.
41
+ * be inserted in bulk, a column at a time. `tablePrefix` is the restaurant's
42
+ * own table set; the delete is scoped to it, never to another site's tables.
16
43
  */
17
- export declare function replaceMenu(client: any, menu: any, { replaceAll }?: {
44
+ export declare function replaceMenu(client: any, menu: any, { replaceAll, tablePrefix }?: {
18
45
  replaceAll?: boolean | undefined;
46
+ tablePrefix?: string | undefined;
19
47
  }): Promise<{
20
48
  categories: any;
21
49
  sections: any;
@@ -8,4 +8,4 @@ export declare function templateHints(menu: any, { projectDir }?: {}): Promise<s
8
8
  * @param {import('../../lib/types.mjs').ImportMenuOptions} options
9
9
  * @returns {Promise<import('../../lib/types.mjs').ImportResult>}
10
10
  */
11
- export declare function importMenu({ menu, notes, title, dryRun, json, replaceAll, force, databaseUrl, env, projectDir, log: logOption, }?: import('../../lib/types.mjs').ImportMenuOptions): Promise<import('../../lib/types.mjs').ImportResult>;
11
+ export declare function importMenu({ menu, notes, title, dryRun, json, replaceAll, force, tablePrefix, allowUnprefixed, yes, databaseUrl, env, projectDir, log: logOption, }?: import('../../lib/types.mjs').ImportMenuOptions): Promise<import('../../lib/types.mjs').ImportResult>;
@@ -4,6 +4,12 @@ export declare const menuFlags: {
4
4
  provider: string;
5
5
  model: string;
6
6
  minWidth: string;
7
+ imageDir: string;
8
+ imageBaseUrl: string;
9
+ imageBoxes: string;
10
+ tablePrefix: string;
11
+ allowUnprefixed: string;
12
+ yes: string;
7
13
  replaceAll: string;
8
14
  force: string;
9
15
  dryRun: string;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `tablePrefix` when it is empty or a safe `<name>_` prefix (an ECONFIG error otherwise).
3
+ * @param {string} [tablePrefix]
4
+ * @returns {string}
5
+ */
6
+ export declare function validateTablePrefix(tablePrefix?: string): string;
7
+ /**
8
+ * The three `public` menu tables for a prefix, e.g.
9
+ * `{ categories: "public.makibar_menu_categories", sections: "public.makibar_menu_sections", products: "public.makibar_menu_products" }`.
10
+ * The default (empty prefix) is the unprefixed `public.menu_*` set.
11
+ * @param {string} [tablePrefix]
12
+ * @returns {{ categories: string, sections: string, products: string }}
13
+ */
14
+ export declare function menuTables(tablePrefix?: string): {
15
+ categories: string;
16
+ sections: string;
17
+ products: string;
18
+ };
@@ -1,9 +1,11 @@
1
1
  declare const _default: {
2
+ tablePrefix: string;
2
3
  url: string;
3
4
  currency: string;
4
5
  thousands: string;
5
6
  decimal: string;
6
7
  scale: number;
8
+ imageScale: number;
7
9
  categories: {
8
10
  slug: string;
9
11
  name: string;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The page's text item that best matches `name`: an exact match first, then a
3
+ * line that contains it (preferring the shortest such line). null when no line
4
+ * holds the name, which happens when a heading was drawn as an outline.
5
+ */
6
+ export declare function findNameItem(items: any, name: any): any;
7
+ /**
8
+ * One crop per product, in page order. `pages` maps a page number to
9
+ * `{ width, items, placed, direct }` (see the file comment). A crop is used
10
+ * once, so a single photo between two items goes to the closer one. Returns
11
+ * `{ matches, notes }`; matches carry the placement and the crop it won.
12
+ * @param {{ page: number, name: string, box: number[] | null, products: any[] }[]} placements
13
+ * @param {Map<number, any>} pages
14
+ * @returns {{ matches: { placement: any, crop: any }[], notes: string[] }}
15
+ */
16
+ export declare function matchPlacements(placements: {
17
+ page: number;
18
+ name: string;
19
+ box: number[] | null;
20
+ products: any[];
21
+ }[], pages: Map<number, any>): {
22
+ matches: {
23
+ placement: any;
24
+ crop: any;
25
+ }[];
26
+ notes: string[];
27
+ };
@@ -1,11 +1,17 @@
1
- /** Every image found, and the numbered ones `only` selects. `config` defaults to config.mjs. */
2
- export declare function findPages({ urls, only, minWidth, config }?: {
1
+ /**
2
+ * Every page found, and the numbered ones `only` selects. A PDF argument
3
+ * contributes one page per PDF page; anything else is scanned for menu pictures.
4
+ * `close()` releases the open PDFs.
5
+ */
6
+ export declare function findPages({ urls, only, minWidth, projectDir, config }?: {
3
7
  config?: {
8
+ tablePrefix: string;
4
9
  url: string;
5
10
  currency: string;
6
11
  thousands: string;
7
12
  decimal: string;
8
13
  scale: number;
14
+ imageScale: number;
9
15
  categories: {
10
16
  slug: string;
11
17
  name: string;
@@ -20,23 +26,44 @@ export declare function findPages({ urls, only, minWidth, config }?: {
20
26
  minWidth?: number | undefined;
21
27
  urls?: never[] | undefined;
22
28
  }): Promise<{
23
- pages: any[];
24
- chosen: any[];
29
+ pages: {
30
+ kind: string;
31
+ source: any;
32
+ host: string;
33
+ label: string;
34
+ pageNumber: number;
35
+ url: string;
36
+ chars: number;
37
+ number: number;
38
+ }[];
39
+ chosen: {
40
+ kind: string;
41
+ source: any;
42
+ host: string;
43
+ label: string;
44
+ pageNumber: number;
45
+ url: string;
46
+ chars: number;
47
+ number: number;
48
+ }[];
49
+ close: () => Promise<void>;
25
50
  }>;
26
51
  /**
27
- * The pictures the CLI's `--list` prints, numbered. `only` narrows them.
52
+ * The pages `--list` prints, numbered, including PDF pages with their text size.
28
53
  * @param {import('../../lib/types.mjs').ListMenuImagesOptions} [options]
29
54
  * @returns {Promise<import('../../lib/types.mjs').MenuImage[]>}
30
55
  */
31
56
  export declare function listMenuImages(options?: import('../../lib/types.mjs').ListMenuImagesOptions): Promise<import('../../lib/types.mjs').MenuImage[]>;
32
57
  /** Reads the pages and normalizes them: `{ menu, notes, title }`, ready for importMenu. */
33
- export declare function fetchImageMenu({ urls, only, provider, model, minWidth, refresh, apiKey, env, projectDir, config, log: logOption }?: {
58
+ export declare function fetchImageMenu({ urls, only, provider, model, minWidth, refresh, apiKey, env, projectDir, config, imageDir, imageBaseUrl, imageBoxes, log: logOption }?: {
34
59
  config?: {
60
+ tablePrefix: string;
35
61
  url: string;
36
62
  currency: string;
37
63
  thousands: string;
38
64
  decimal: string;
39
65
  scale: number;
66
+ imageScale: number;
40
67
  categories: {
41
68
  slug: string;
42
69
  name: string;
@@ -48,6 +75,7 @@ export declare function fetchImageMenu({ urls, only, provider, model, minWidth,
48
75
  };
49
76
  skipSections: never[];
50
77
  } | undefined;
78
+ imageBoxes?: string | undefined;
51
79
  minWidth?: number | undefined;
52
80
  refresh?: boolean | undefined;
53
81
  urls?: never[] | undefined;
@@ -55,9 +83,10 @@ export declare function fetchImageMenu({ urls, only, provider, model, minWidth,
55
83
  menu: import("../../lib/types.mjs").Menu;
56
84
  notes: string[];
57
85
  title: string;
86
+ tablePrefix: string;
58
87
  }>;
59
88
  /**
60
- * Reads the menu from pictures with a vision model and imports it. Takes importMenu's options too.
89
+ * Reads the menu from pictures or a PDF and imports it. Takes importMenu's options too.
61
90
  * @param {import('../../lib/types.mjs').ImportImageMenuOptions} [options]
62
91
  * @returns {Promise<import('../../lib/types.mjs').ImportResult>}
63
92
  */
@@ -16,7 +16,7 @@ export declare function parsePrice(text: string, { thousands, decimal, scale }?:
16
16
  * product per column, "Name (Botella)", because a product has one price.
17
17
  * @param {{ number?: number, notes?: string[], sections?: any[] }[]} pages transcriptions, one per page
18
18
  * @param {import('../../lib/types.mjs').RawConfig} config
19
- * @returns {{ menu: import('../../lib/types.mjs').Menu, notes: string[], currency: string }}
19
+ * @returns {{ menu: import('../../lib/types.mjs').Menu, notes: string[], currency: string, placements: { page: number, name: string, box: number[] | null, products: import('../../lib/types.mjs').MenuProduct[] }[] }}
20
20
  */
21
21
  export declare function normalizePages(pages: {
22
22
  number?: number;
@@ -26,4 +26,10 @@ export declare function normalizePages(pages: {
26
26
  menu: import('../../lib/types.mjs').Menu;
27
27
  notes: string[];
28
28
  currency: string;
29
+ placements: {
30
+ page: number;
31
+ name: string;
32
+ box: number[] | null;
33
+ products: import('../../lib/types.mjs').MenuProduct[];
34
+ }[];
29
35
  };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * True when an argument names a PDF: a local file ending in .pdf, or a URL whose
3
+ * path ends in .pdf. A URL that hides the extension comes back from the fetch as
4
+ * an HTML page with no pictures, and the reader says to pass the file instead.
5
+ * @param {string} input
6
+ * @returns {boolean}
7
+ */
8
+ export declare function isPdfInput(input: string): boolean;
9
+ /**
10
+ * The PDF's bytes, from a local file (`projectDir`-relative) or an http(s) URL.
11
+ * `host` names the cache folder, `label` is what a note calls the file.
12
+ * @param {string} input
13
+ * @param {{ projectDir?: string }} [options]
14
+ * @returns {Promise<{ bytes: Uint8Array, host: string, label: string }>}
15
+ */
16
+ export declare function readPdfSource(input: string, { projectDir }?: {
17
+ projectDir?: string;
18
+ }): Promise<{
19
+ bytes: Uint8Array;
20
+ host: string;
21
+ label: string;
22
+ }>;
23
+ /**
24
+ * Opens a PDF for reading. `close()` releases pdfjs's worker; call it when the
25
+ * run is done with the file. `textCache` keeps a page's text from being read twice.
26
+ * @param {Uint8Array} bytes
27
+ * @returns {Promise<any>} the open document handle
28
+ */
29
+ export declare function openPdf(bytes: Uint8Array): Promise<any>;
30
+ /**
31
+ * One page's text and its pieces. `items` are pdfjs's text items, kept for
32
+ * matching a product name to a spot on the page when placing product photos.
33
+ * @returns {Promise<{ page: any, text: string, items: any[] }>}
34
+ */
35
+ export declare function readPdfText(source: any, number: any): Promise<{
36
+ page: any;
37
+ text: string;
38
+ items: any[];
39
+ }>;
40
+ /** Every page that has text worth transcribing, and how much. Used by `--list`. */
41
+ export declare function inspectPdf(source: any): Promise<{
42
+ number: number;
43
+ hasText: boolean;
44
+ chars: number;
45
+ }[]>;
46
+ /**
47
+ * The text items flattened to `{ str, x, y, width, height }`, which both the
48
+ * text builder and the product-photo matcher understand.
49
+ */
50
+ export declare const positionedItems: (items: any) => any;
51
+ /**
52
+ * Rebuilds readable text from pdfjs text items: pieces on the same line are
53
+ * joined in reading order, a wide vertical gap becomes a blank line so the model
54
+ * still sees section breaks. A page with two clear columns is read column by
55
+ * column, so the two columns' rows are not merged. Pure, so it is tested without
56
+ * a PDF.
57
+ * @param {{ str?: string, transform?: number[], width?: number, height?: number }[]} items
58
+ * @returns {string}
59
+ */
60
+ export declare function textFromItems(items: {
61
+ str?: string;
62
+ transform?: number[];
63
+ width?: number;
64
+ height?: number;
65
+ }[]): string;
66
+ /** A page's rendered pixels, the context, and the viewport they were rendered with. */
67
+ export declare function renderPdfPage(source: any, page: any, { scale, recordImages }?: {
68
+ recordImages?: boolean | undefined;
69
+ scale?: number | undefined;
70
+ }): Promise<{
71
+ canvas: any;
72
+ context: any;
73
+ viewport: any;
74
+ scale: number;
75
+ }>;
76
+ /** Releases a rendered canvas's native memory. */
77
+ export declare function destroyPage(source: any, { canvas, context }?: {}): void;
78
+ /**
79
+ * The rectangle each image occupies on a page render. pdfjs records this itself
80
+ * when rendering with `recordImages` (three points per image, fractions of the
81
+ * canvas, top-left origin): it is clip- and group-aware, unlike walking the
82
+ * operator list by hand.
83
+ */
84
+ export declare function imageRects(canvas: any, coordinates: any): {
85
+ x: number;
86
+ y: number;
87
+ width: number;
88
+ height: number;
89
+ }[];
90
+ /** Crops a pixel rectangle out of a page render, as a PNG buffer. */
91
+ export declare function cropPdfPixels(source: any, canvas: any, { x, y, width, height }: {
92
+ height: any;
93
+ width: any;
94
+ x: any;
95
+ y: any;
96
+ }, { padding }?: {
97
+ padding?: number | undefined;
98
+ }): any;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The pdfjs module plus the asset URLs it needs (standard fonts and CMaps) so
3
+ * text is extracted and pages render without the warnings Node otherwise logs.
4
+ * @returns {Promise<{ pdfjs: any, standardFontDataUrl: string, cMapUrl: string }>}
5
+ */
6
+ export declare function loadPdfjs(): Promise<{
7
+ pdfjs: any;
8
+ standardFontDataUrl: string;
9
+ cMapUrl: string;
10
+ }>;
11
+ /** A rendering failure caused by the canvas package being absent becomes EDEPENDENCY with the install hint. */
12
+ export declare function renderError(error: any): any;
@@ -1,3 +1,5 @@
1
+ /** A fetch with retries on server errors; shared with the PDF reader. */
2
+ export declare function get(url: any, what: any): Promise<Response>;
1
3
  /**
2
4
  * The picture URLs of a page, in document order. Lazy-loaders keep the real
3
5
  * address in data-orig-src / data-src and put a placeholder in src, so those
@@ -1,10 +1,20 @@
1
1
  /** @type {Record<string, import('../../lib/types.mjs').VisionProvider>} */
2
2
  export declare const providers: Record<string, import('../../lib/types.mjs').VisionProvider>;
3
3
  export declare const defaultProvider = "anthropic";
4
- /** `file` is a downloaded picture; returns `{ sections, notes }` as the schema above describes. */
4
+ /** `file` is a downloaded picture; returns `{ sections, notes }` as the schema above describes. `boxes` also asks for each item's printed photo rectangle. */
5
5
  export declare function readPage({ file, mediaType }: {
6
6
  file: any;
7
7
  mediaType: any;
8
+ }, { provider, model, apiKey, env, boxes }?: {
9
+ boxes?: boolean | undefined;
10
+ provider?: string | undefined;
11
+ }): Promise<{
12
+ sections: any;
13
+ notes: any;
14
+ }>;
15
+ /** `text` is one page's extracted text; returns the same `{ sections, notes }` the picture path does. */
16
+ export declare function readText({ text }: {
17
+ text: any;
8
18
  }, { provider, model, apiKey, env }?: {
9
19
  provider?: string | undefined;
10
20
  }): Promise<{
@@ -1,9 +1,10 @@
1
- export declare function buildProfile({ query, google, osm, site, hub, instagram, tripadvisor }: {
1
+ export declare function buildProfile({ query, google, osm, site, hub, instagram, tripadvisor, search }: {
2
2
  google: any;
3
3
  hub: any;
4
4
  instagram: any;
5
5
  osm: any;
6
6
  query: any;
7
+ search: any;
7
8
  site: any;
8
9
  tripadvisor: any;
9
10
  }): {
@@ -82,6 +83,8 @@ export declare function buildProfile({ query, google, osm, site, hub, instagram,
82
83
  followers: any;
83
84
  posts: any;
84
85
  blocked: any;
86
+ partial: any;
87
+ surface: any;
85
88
  } | null;
86
89
  };
87
90
  };
@@ -1,4 +1,17 @@
1
- export declare function renderReport(p: any, { notes, photos }: {
1
+ /**
2
+ * Splits the fields into what was discovered from a source, what was only echoed
3
+ * back from the user's own input, and low-confidence guesses, so a summary can
4
+ * say "N facts, M from your input" instead of counting all of them as found.
5
+ */
6
+ export declare function fieldSummary(p: any): {
7
+ discovered: string[];
8
+ echoed: string[];
9
+ guessed: string[];
10
+ };
11
+ /** The CLI summary: what came from a source, what was only echoed, what was a low guess. */
12
+ export declare function summaryLines(p: any): string[];
13
+ export declare function renderReport(p: any, { notes, photos, kept }: {
14
+ kept?: never[] | undefined;
2
15
  notes: any;
3
16
  photos: any;
4
17
  }): string;