@toolpath/tool-scraper 0.1.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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/conventions.d.ts +124 -0
  4. package/dist/conventions.js +143 -0
  5. package/dist/errors.d.ts +46 -0
  6. package/dist/errors.js +53 -0
  7. package/dist/families/destinytool.d.ts +49 -0
  8. package/dist/families/destinytool.js +55 -0
  9. package/dist/families/index.d.ts +59 -0
  10. package/dist/families/index.js +91 -0
  11. package/dist/families/kennametal.d.ts +757 -0
  12. package/dist/families/kennametal.js +660 -0
  13. package/dist/families/regofix.d.ts +185 -0
  14. package/dist/families/regofix.js +250 -0
  15. package/dist/family.d.ts +130 -0
  16. package/dist/family.js +38 -0
  17. package/dist/fetch.d.ts +98 -0
  18. package/dist/fetch.js +116 -0
  19. package/dist/identity.d.ts +133 -0
  20. package/dist/identity.js +118 -0
  21. package/dist/index.d.ts +31 -0
  22. package/dist/index.js +31 -0
  23. package/dist/node/cad-mirror.d.ts +55 -0
  24. package/dist/node/cad-mirror.js +89 -0
  25. package/dist/node/cli.d.ts +35 -0
  26. package/dist/node/cli.js +340 -0
  27. package/dist/node/csv.d.ts +47 -0
  28. package/dist/node/csv.js +123 -0
  29. package/dist/node/index.d.ts +16 -0
  30. package/dist/node/index.js +16 -0
  31. package/dist/node/main.d.ts +13 -0
  32. package/dist/node/main.js +14 -0
  33. package/dist/node/paths.d.ts +60 -0
  34. package/dist/node/paths.js +80 -0
  35. package/dist/node/receipts.d.ts +100 -0
  36. package/dist/node/receipts.js +107 -0
  37. package/dist/order.d.ts +10 -0
  38. package/dist/order.js +12 -0
  39. package/dist/provenance.d.ts +125 -0
  40. package/dist/provenance.js +133 -0
  41. package/dist/records.d.ts +305 -0
  42. package/dist/records.js +297 -0
  43. package/dist/registry.d.ts +63 -0
  44. package/dist/registry.js +145 -0
  45. package/dist/scrape.d.ts +70 -0
  46. package/dist/scrape.js +37 -0
  47. package/dist/thread.d.ts +48 -0
  48. package/dist/thread.js +98 -0
  49. package/dist/uuid5.d.ts +31 -0
  50. package/dist/uuid5.js +64 -0
  51. package/dist/vendors/destinytool/index.d.ts +11 -0
  52. package/dist/vendors/destinytool/index.js +11 -0
  53. package/dist/vendors/destinytool/records.d.ts +118 -0
  54. package/dist/vendors/destinytool/records.js +266 -0
  55. package/dist/vendors/destinytool/scrape.d.ts +108 -0
  56. package/dist/vendors/destinytool/scrape.js +192 -0
  57. package/dist/vendors/kennametal/cad.d.ts +87 -0
  58. package/dist/vendors/kennametal/cad.js +119 -0
  59. package/dist/vendors/kennametal/index.d.ts +21 -0
  60. package/dist/vendors/kennametal/index.js +21 -0
  61. package/dist/vendors/kennametal/materials.d.ts +143 -0
  62. package/dist/vendors/kennametal/materials.js +200 -0
  63. package/dist/vendors/kennametal/records.d.ts +88 -0
  64. package/dist/vendors/kennametal/records.js +241 -0
  65. package/dist/vendors/kennametal/scrape.d.ts +111 -0
  66. package/dist/vendors/kennametal/scrape.js +226 -0
  67. package/dist/vendors/kennametal/thread-column.d.ts +28 -0
  68. package/dist/vendors/kennametal/thread-column.js +41 -0
  69. package/dist/vendors/regofix/index.d.ts +8 -0
  70. package/dist/vendors/regofix/index.js +8 -0
  71. package/dist/vendors/regofix/scrape.d.ts +237 -0
  72. package/dist/vendors/regofix/scrape.js +521 -0
  73. package/package.json +76 -0
@@ -0,0 +1,237 @@
1
+ /**
2
+ * REGO-FIX powRgrip product index -> toolholding rows.
3
+ *
4
+ * Nothing here is shared with any other adapter beyond `conventions` —
5
+ * REGO-FIX is a Drupal site, not Kennametal's AEM platform, so the transport,
6
+ * the identity fields and the dimension source are all different. See
7
+ * `docs/REGOFIX_PRODUCTFINDER_API.md` for how the endpoint was found.
8
+ *
9
+ * ## Two sources, and both are needed
10
+ *
11
+ * **The roster** comes from the ProductFinder's Elasticsearch proxy: one POST
12
+ * returns every variant of a group with its part number, its designation, its
13
+ * PG series and its CAD links. It carries no geometry beyond the projection
14
+ * length.
15
+ *
16
+ * **The geometry** comes from a per-part DIN 4000 XML on the vendor's CDN,
17
+ * linked from each hit. Three of its codes are pinned to a meaning by
18
+ * REGO-FIX's own published tables and the rest are not:
19
+ *
20
+ * - **`B3`** — projection from the flange face. The `L` column of the BT/PG
21
+ * table in the PG product catalog, row for row.
22
+ * - **`B4`** — gage length. `B4 - B3 == 48.4` on every row, and 48.4 mm is
23
+ * BT 30's gauge-line-to-flange distance in the vendor's own standards table.
24
+ * - **`A1`** — diameter at the collet end. The `D` column of that same BT/PG
25
+ * table, row for row.
26
+ *
27
+ * `A2`, `B1`, `B2` and `B3_WOA` are carried into the CSV verbatim under their
28
+ * raw DIN codes and are **not** promoted onto a record. Nothing available here
29
+ * says what they measure, and the standing rule is to leave a vendor code
30
+ * unlabelled rather than guess at it (JG 2026-08-07). `conventions.DIN_PREFIX`
31
+ * is what keeps them from reading as promoted dimensions: a column named
32
+ * `A2_mm` would sit in the CSV looking exactly like `L1_mm`, which is mapped.
33
+ *
34
+ * There is a lead on pinning them honestly. REGO-FIX publishes
35
+ * `DXF_ISO13399/DXF` and `DXF_ISO13399/PDF` beside the `XML_DIN4000/XML` this
36
+ * module reads, so the standard this package's canonical names come from is
37
+ * already in the vendor's own source material.
38
+ *
39
+ * `A4` is 46 on every BT 30 holder — the flange diameter, a property of the
40
+ * taper and not of the part — so it is checked rather than stored.
41
+ *
42
+ * ## What the vendor gets wrong
43
+ *
44
+ * Three faults found on 2026-08-07, all reported as warnings rather than
45
+ * fixed: two disagreeing vendor cells cannot say which one is wrong, and a
46
+ * scraper that corrects one becomes a place tool data is authored by hand.
47
+ *
48
+ * 1. `4130.70646`'s XML states its own part number as `4130.71646`.
49
+ * 2. `J22`, the XML's own product-line label, says "PG-SG secuRgrip
50
+ * Werkzeughalter" on several plain BT 30 holders that are not secuRgrip
51
+ * parts. It is never read.
52
+ * 3. `o_mm` on a tapping collet repeats the previous row's value twice
53
+ * (`1715.08215` and `1725.08215`). This is why the nominal size is parsed
54
+ * from the vendor's own designation and `o_mm` is only ever a cross-check.
55
+ */
56
+ import { type Fetcher } from '../../fetch.js';
57
+ import { type ScrapeResult, type ScrapedRow, type Warn } from '../../scrape.js';
58
+ export declare const MM_PER_INCH = 25.4;
59
+ /**
60
+ * The Searchkit proxy the ProductFinder posts its Elasticsearch queries to.
61
+ * Discovered by reading the app bundle, which constructs
62
+ * `SearchkitManager(origin + '/' + lang, {searchUrlPath: '/elastic/post'})`.
63
+ */
64
+ export declare const SEARCH_URL = "https://us.rego-fix.com/en/elastic/post";
65
+ /**
66
+ * Where a part's DIN 4000 XML lives. The filename is the part number with its
67
+ * dot removed, which is also `field_sku_ngram`.
68
+ */
69
+ export declare const DIN4000_URL: string;
70
+ /**
71
+ * Gauge line to flange face, JIS B 6339 / MAS 403 size 30, as published in the
72
+ * vendor's own interface table (PG product catalog, "BT MAS 403":
73
+ * `BT 30 | 31.75 | 46 | 2 | 48.4 | 20 | M 12`). Used to *verify* that `B4` is
74
+ * the gage length rather than to compute one — both numbers are scraped, and
75
+ * their difference is what identifies the code.
76
+ */
77
+ export declare const BT30_GAUGE_TO_FLANGE = 48.4;
78
+ /** Flange diameter of a BT 30 taper, from the same row. `A4` on every holder. */
79
+ export declare const BT30_FLANGE_DIAMETER = 46;
80
+ /**
81
+ * DIN 4000 codes carried into the CSV verbatim because nothing here says what
82
+ * they measure. Written out rather than "everything else" so that a code the
83
+ * vendor adds later shows up as an unhandled key instead of silently appearing
84
+ * as a column.
85
+ */
86
+ export declare const UNPINNED_DIN_CODES: readonly ["A2", "B1", "B2", "B3_WOA"];
87
+ /**
88
+ * `form_name`, the vendor's own field, mapped to this catalog's `contact`
89
+ * axis. `Plus +` is REGO-FIX's designation for the dual-contact shank that
90
+ * seats on the spindle face as well as the cone — the same distinction
91
+ * Kennametal sells as BTKV. It is a **scraped fact here**, not family config,
92
+ * because REGO-FIX publishes both forms in one product group:
93
+ * `BT 30 / PG 25 x 080 H` and `BT+ 30 / PG 25 x 080 H` are two rows of one
94
+ * table.
95
+ *
96
+ * There is no default. A third form is a stop-and-ask, and `BT-OM 30` is
97
+ * already sitting in that table undefined — nothing on the vendor's site or in
98
+ * its catalog says what OM designates, so its three parts are deliberately not
99
+ * scraped (JG 2026-08-07).
100
+ */
101
+ export declare const CONTACT_BY_FORM: Record<string, string>;
102
+ /**
103
+ * The taper designations this package scrapes, and what they mean.
104
+ *
105
+ * `BT-OM 30` is published in the same product group and is **not** here: the
106
+ * family page, the product catalog and the ProductFinder all print the token
107
+ * and none of them says what OM designates, so recording a spindle interface
108
+ * for it would be a guess about which machine a holder fits (JG 2026-08-07).
109
+ * Its three parts are a stop-and-ask, not an omission to fix silently.
110
+ */
111
+ export declare const SCRAPED_TAPERS: readonly ["BT 30", "BT+ 30"];
112
+ /** One Elasticsearch `_source`: every value is a list, even a single one. */
113
+ export type Source = Record<string, unknown>;
114
+ /**
115
+ * Every `_source` matching an AND of term filters, newest index first.
116
+ *
117
+ * One request: the index holds 4142 products in total and the largest group
118
+ * asked for here is 321, so there is nothing to page. `size` is an explicit
119
+ * ceiling rather than a page length, and going over it throws — a silently
120
+ * truncated roster is the failure this whole package is built to notice.
121
+ */
122
+ export declare function search(fetcher: Fetcher, filters: Record<string, string>, size?: number): Promise<Source[]>;
123
+ /**
124
+ * A field of an Elasticsearch `_source`, which stores every value as a list
125
+ * even when there is exactly one.
126
+ *
127
+ * Missing and empty are both null: `o_inch` is absent on a metric collet,
128
+ * which is the vendor saying it is metric rather than a gap.
129
+ */
130
+ export declare function one(source: Source, field: string): string | number | null;
131
+ /**
132
+ * DIN 4000 property codes to their values, empty ones dropped.
133
+ *
134
+ * The document repeats a `<PropertyName>`/`<Value>` pair per property and
135
+ * states most of them empty, so dropping blanks is what makes "the vendor
136
+ * published this" and "the vendor published a hole" different states.
137
+ *
138
+ * Throws on a document with no properties at all rather than returning an
139
+ * empty map, for the reason `kennametal.parseVariantTable` throws on an
140
+ * unparseable response: reporting a changed format as no data looks exactly
141
+ * like a discontinued part.
142
+ */
143
+ export declare function parseDin4000(xml: string): Record<string, string>;
144
+ /**
145
+ * One part's DIN 4000 properties, or null when the vendor publishes none.
146
+ *
147
+ * Null is a real state — two of the BT+ 30 holders have DXF and PDF but no XML
148
+ * — and it is distinguished from a failed request, which throws. A holder with
149
+ * no XML has no gage length and cannot be converted, so it is the caller that
150
+ * decides what to do about it.
151
+ */
152
+ export declare function fetchDin4000(fetcher: Fetcher, sku: string): Promise<Record<string, string> | null>;
153
+ /**
154
+ * A number as the vendor would print it: no trailing `.0` on an integer.
155
+ *
156
+ * The CSV is read back as a number, so this only decides what a human and a
157
+ * git diff see — and `10` rather than `10.0` is what the vendor's own
158
+ * designation says.
159
+ */
160
+ export declare function plain(value: string | number | null | undefined): string;
161
+ /** `1/4` or `3.5` — the vendor prints both, and both are exact here. */
162
+ export declare function parseSize(size: string): number;
163
+ /**
164
+ * The part's STEP model, absolute, or empty when none is published.
165
+ *
166
+ * The index gives protocol-relative CDN URLs, and `conventions.CAD_COLUMN`
167
+ * holds a URL a consumer can fetch — so the scheme is added here rather than
168
+ * left for every reader to guess at.
169
+ */
170
+ export declare function cadUrl(source: Source): string;
171
+ /**
172
+ * One search hit plus its DIN 4000 properties -> one row.
173
+ *
174
+ * Column names are the shared toolholding vocabulary rather than REGO-FIX's
175
+ * own, so a consumer reads a REGO-FIX holder exactly as it reads a Kennametal
176
+ * one — `L1` is the gage length whoever published it. The DIN codes those came
177
+ * from are in this module's docstring; the ones that stay unmapped keep their
178
+ * raw code behind `conventions.DIN_PREFIX`.
179
+ *
180
+ * **This is the one place a REGO-FIX label is rewritten, and it is a
181
+ * holder-geometry label rather than an identity or a dimension code.** The
182
+ * identity columns are Kennametal's, adopted here because this vendor came
183
+ * second — see `conventions.IDENTITY_COLUMNS`.
184
+ *
185
+ * `D1` is deliberately absent. A powRgrip holder clamps through a collet, and
186
+ * a collet-clamping holder that also carried a bore would be claiming two ways
187
+ * of gripping one tool.
188
+ */
189
+ export declare function holderRow(source: Source, properties: Record<string, string>, warn?: Warn): ScrapedRow;
190
+ /**
191
+ * One search hit -> one row, with the nominal size read off the vendor's own
192
+ * designation.
193
+ *
194
+ * **The size comes from the title, not from `o_mm`.** `o_mm` is rounded to two
195
+ * decimals, which puts a 1/8 in collet at 3.18 mm where the part is 3.175 —
196
+ * five microns out, against the two-micron tolerance a fit test sizes its
197
+ * equality to, so every inch collet would have failed to match its own shank
198
+ * size. It is also wrong outright on two tapping collets, where it repeats the
199
+ * previous row's value. The title states the vendor's designation exactly
200
+ * (`Ø 1/4"`, `Ø 3.5 mm`) and says which unit system it is in, so it is both
201
+ * more precise and the only source here that carries a unit at all.
202
+ *
203
+ * `o_mm` is kept as a cross-check column rather than dropped, the same way
204
+ * Kennametal's contradictory unit cells are kept: it is what the vendor said.
205
+ */
206
+ export declare function colletRow(source: Source, warn?: Warn): ScrapedRow;
207
+ /**
208
+ * Rows to a result whose header is the union of their keys, in first-seen
209
+ * order.
210
+ *
211
+ * A union rather than the first row's keys: a mixed-unit collet family has
212
+ * `D1_mm` on its metric rows and `D1_in` on its inch ones, and keying off row
213
+ * one would drop whichever came second.
214
+ */
215
+ export declare function unionHeader(rows: readonly ScrapedRow[]): string[];
216
+ /** Options every REGO-FIX scrape accepts. */
217
+ export interface RegofixOptions {
218
+ warn?: Warn;
219
+ }
220
+ /**
221
+ * Every powRgrip holder of `group` whose taper is in {@link SCRAPED_TAPERS}.
222
+ *
223
+ * Two requests' worth of work per part — the roster is one POST, then one DIN
224
+ * 4000 document each. A part the vendor publishes no XML for is dropped with a
225
+ * message rather than written with holes: a gage length is required, and a
226
+ * holder without one fails conversion anyway.
227
+ */
228
+ export declare function scrapeHolders(fetcher: Fetcher, group?: string, category?: string, options?: RegofixOptions): Promise<ScrapeResult>;
229
+ /**
230
+ * Every powRgrip collet of one product group, in the given PG sizes.
231
+ *
232
+ * `sizes` are the vendor's `norm_size` values — the PG series numbers a BT 30
233
+ * holder can take. It is an argument rather than "all of them" because the
234
+ * sizes are what tie a collet family to the holders in this catalog: PG 32 and
235
+ * PG 48 collets exist and no BT 30 holder accepts one.
236
+ */
237
+ export declare function scrapeCollets(fetcher: Fetcher, group: string, sizes: readonly string[], options?: RegofixOptions): Promise<ScrapeResult>;