@toolpath/tool-scraper 0.1.0 → 2.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.
- package/README.md +41 -11
- package/dist/columns.d.ts +64 -0
- package/dist/columns.js +68 -0
- package/dist/conventions.d.ts +107 -15
- package/dist/conventions.js +126 -15
- package/dist/errors.d.ts +30 -0
- package/dist/errors.js +30 -0
- package/dist/families/emuge.d.ts +185 -0
- package/dist/families/emuge.js +163 -0
- package/dist/families/harvey.d.ts +1782 -0
- package/dist/families/harvey.js +1328 -0
- package/dist/families/index.js +5 -2
- package/dist/families/kennametal.d.ts +21 -0
- package/dist/families/kennametal.js +10 -0
- package/dist/families/maritool.d.ts +120 -0
- package/dist/families/maritool.js +175 -0
- package/dist/family.d.ts +35 -1
- package/dist/family.js +28 -0
- package/dist/identity.d.ts +18 -0
- package/dist/identity.js +46 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +16 -0
- package/dist/measure.d.ts +112 -0
- package/dist/measure.js +130 -0
- package/dist/node/cli.d.ts +3 -0
- package/dist/node/cli.js +158 -3
- package/dist/node/main.js +0 -0
- package/dist/records.d.ts +214 -12
- package/dist/records.js +140 -5
- package/dist/registry.d.ts +56 -1
- package/dist/registry.js +79 -2
- package/dist/scrape.d.ts +15 -0
- package/dist/scrape.js +24 -0
- package/dist/vendors/destinytool/records.d.ts +13 -3
- package/dist/vendors/destinytool/records.js +49 -37
- package/dist/vendors/emuge/index.d.ts +17 -0
- package/dist/vendors/emuge/index.js +17 -0
- package/dist/vendors/emuge/records.d.ts +230 -0
- package/dist/vendors/emuge/records.js +543 -0
- package/dist/vendors/emuge/scrape.d.ts +227 -0
- package/dist/vendors/emuge/scrape.js +358 -0
- package/dist/vendors/emuge/value.d.ts +75 -0
- package/dist/vendors/emuge/value.js +116 -0
- package/dist/vendors/harvey/catalog.d.ts +53 -0
- package/dist/vendors/harvey/catalog.js +120 -0
- package/dist/vendors/harvey/header.d.ts +89 -0
- package/dist/vendors/harvey/header.js +185 -0
- package/dist/vendors/harvey/index.d.ts +21 -0
- package/dist/vendors/harvey/index.js +21 -0
- package/dist/vendors/harvey/lexicon.d.ts +73 -0
- package/dist/vendors/harvey/lexicon.js +126 -0
- package/dist/vendors/harvey/literal.d.ts +68 -0
- package/dist/vendors/harvey/literal.js +214 -0
- package/dist/vendors/harvey/records.d.ts +79 -0
- package/dist/vendors/harvey/records.js +163 -0
- package/dist/vendors/harvey/scrape.d.ts +187 -0
- package/dist/vendors/harvey/scrape.js +483 -0
- package/dist/vendors/harvey/value.d.ts +74 -0
- package/dist/vendors/harvey/value.js +119 -0
- package/dist/vendors/kennametal/family.d.ts +119 -0
- package/dist/vendors/kennametal/family.js +155 -0
- package/dist/vendors/kennametal/index.d.ts +1 -0
- package/dist/vendors/kennametal/index.js +1 -0
- package/dist/vendors/kennametal/records.d.ts +29 -4
- package/dist/vendors/kennametal/records.js +93 -26
- package/dist/vendors/kennametal/scrape.d.ts +25 -2
- package/dist/vendors/kennametal/scrape.js +28 -3
- package/dist/vendors/maritool/catalog.d.ts +81 -0
- package/dist/vendors/maritool/catalog.js +132 -0
- package/dist/vendors/maritool/index.d.ts +10 -0
- package/dist/vendors/maritool/index.js +10 -0
- package/dist/vendors/maritool/scrape.d.ts +297 -0
- package/dist/vendors/maritool/scrape.js +593 -0
- package/dist/vendors/regofix/scrape.d.ts +8 -11
- package/dist/vendors/regofix/scrape.js +21 -36
- package/package.json +31 -7
package/dist/families/index.js
CHANGED
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import { ScraperConfigError } from '../errors.js';
|
|
33
33
|
import { FAMILIES as DESTINYTOOL } from './destinytool.js';
|
|
34
|
+
import { FAMILIES as EMUGE } from './emuge.js';
|
|
35
|
+
import { FAMILIES as HARVEY } from './harvey.js';
|
|
36
|
+
import { HOLDER_FAMILIES as MARITOOL_HOLDERS } from './maritool.js';
|
|
34
37
|
import { COLLET_FAMILIES as KM_COLLETS, FAMILIES as KENNAMETAL, HOLDER_FAMILIES as KM_HOLDERS, } from './kennametal.js';
|
|
35
38
|
import { COLLET_FAMILIES as RF_COLLETS, HOLDER_FAMILIES as RF_HOLDERS } from './regofix.js';
|
|
36
39
|
/**
|
|
@@ -57,7 +60,7 @@ function merge(...tables) {
|
|
|
57
60
|
return merged;
|
|
58
61
|
}
|
|
59
62
|
/** Every cutting-tool family, keyed by the CSV it is scraped into. */
|
|
60
|
-
export const FAMILIES = merge(KENNAMETAL, DESTINYTOOL);
|
|
63
|
+
export const FAMILIES = merge(KENNAMETAL, DESTINYTOOL, HARVEY, EMUGE);
|
|
61
64
|
/**
|
|
62
65
|
* Every toolholding family — holders, and the collets that go in them.
|
|
63
66
|
*
|
|
@@ -66,7 +69,7 @@ export const FAMILIES = merge(KENNAMETAL, DESTINYTOOL);
|
|
|
66
69
|
* states a taper and a clamping mode; a collet states a series and a capacity
|
|
67
70
|
* band) and a scrape of one is not a scrape of the other.
|
|
68
71
|
*/
|
|
69
|
-
export const HOLDER_FAMILIES = merge(KM_HOLDERS, RF_HOLDERS);
|
|
72
|
+
export const HOLDER_FAMILIES = merge(KM_HOLDERS, RF_HOLDERS, MARITOOL_HOLDERS);
|
|
70
73
|
export const COLLET_FAMILIES = merge(KM_COLLETS, RF_COLLETS);
|
|
71
74
|
/**
|
|
72
75
|
* Every family this package knows, by CSV name — tools and toolholding alike.
|
|
@@ -230,6 +230,13 @@ export declare const FAMILIES: {
|
|
|
230
230
|
readonly checked: "2026-08-08";
|
|
231
231
|
readonly by: "JG";
|
|
232
232
|
};
|
|
233
|
+
readonly coolantThrough: {
|
|
234
|
+
readonly value: false;
|
|
235
|
+
readonly source: "assumed";
|
|
236
|
+
readonly note: "HSS taps; no coolant-through variant appears in the family's variant table, and the table publishes no coolant column";
|
|
237
|
+
readonly checked: "2026-08-29";
|
|
238
|
+
readonly by: "JG";
|
|
239
|
+
};
|
|
233
240
|
};
|
|
234
241
|
};
|
|
235
242
|
readonly 'khsst_hand_metric_plug.csv': {
|
|
@@ -251,6 +258,13 @@ export declare const FAMILIES: {
|
|
|
251
258
|
readonly checked: "2026-08-08";
|
|
252
259
|
readonly by: "JG";
|
|
253
260
|
};
|
|
261
|
+
readonly coolantThrough: {
|
|
262
|
+
readonly value: false;
|
|
263
|
+
readonly source: "assumed";
|
|
264
|
+
readonly note: "HSS taps; no coolant-through variant appears in the family's variant table, and the table publishes no coolant column";
|
|
265
|
+
readonly checked: "2026-08-29";
|
|
266
|
+
readonly by: "JG";
|
|
267
|
+
};
|
|
254
268
|
};
|
|
255
269
|
};
|
|
256
270
|
readonly 'spiral_point_metric_plug.csv': {
|
|
@@ -272,6 +286,13 @@ export declare const FAMILIES: {
|
|
|
272
286
|
readonly checked: "2026-08-08";
|
|
273
287
|
readonly by: "JG";
|
|
274
288
|
};
|
|
289
|
+
readonly coolantThrough: {
|
|
290
|
+
readonly value: false;
|
|
291
|
+
readonly source: "assumed";
|
|
292
|
+
readonly note: "HSS taps; no coolant-through variant appears in the family's variant table, and the table publishes no coolant column";
|
|
293
|
+
readonly checked: "2026-08-29";
|
|
294
|
+
readonly by: "JG";
|
|
295
|
+
};
|
|
275
296
|
};
|
|
276
297
|
};
|
|
277
298
|
readonly 'gomill_pro_radiused_4fl_necked_metric.csv': {
|
|
@@ -66,6 +66,13 @@ const PCD_SUBSTRATE = {
|
|
|
66
66
|
source: 'vendor-stated',
|
|
67
67
|
cite: 'grade KD1415 is PCD — the cutting material, not the carbide body it is brazed to',
|
|
68
68
|
};
|
|
69
|
+
const NO_COOLANT_THROUGH_TAP = {
|
|
70
|
+
value: false,
|
|
71
|
+
source: 'assumed',
|
|
72
|
+
note: "HSS taps; no coolant-through variant appears in the family's variant table, and the table publishes no coolant column",
|
|
73
|
+
checked: '2026-08-29',
|
|
74
|
+
by: 'JG',
|
|
75
|
+
};
|
|
69
76
|
const HSS_ASSUMED = {
|
|
70
77
|
value: 'hss',
|
|
71
78
|
source: 'assumed',
|
|
@@ -299,6 +306,7 @@ export const FAMILIES = {
|
|
|
299
306
|
columns: { SFDM: 'D', OAL: 'L', LCF: 'L3', TP: 'Thread Pitch' },
|
|
300
307
|
facts: {
|
|
301
308
|
bmc: HSS_ASSUMED,
|
|
309
|
+
coolantThrough: NO_COOLANT_THROUGH_TAP,
|
|
302
310
|
},
|
|
303
311
|
},
|
|
304
312
|
'khsst_hand_metric_plug.csv': {
|
|
@@ -309,6 +317,7 @@ export const FAMILIES = {
|
|
|
309
317
|
columns: { SFDM: 'D', OAL: 'L', LCF: 'L3', TP: 'Thread Pitch' },
|
|
310
318
|
facts: {
|
|
311
319
|
bmc: HSS_ASSUMED,
|
|
320
|
+
coolantThrough: NO_COOLANT_THROUGH_TAP,
|
|
312
321
|
},
|
|
313
322
|
},
|
|
314
323
|
'spiral_point_metric_plug.csv': {
|
|
@@ -319,6 +328,7 @@ export const FAMILIES = {
|
|
|
319
328
|
columns: { SFDM: 'D', OAL: 'L', LCF: 'L3', TP: 'Thread Pitch' },
|
|
320
329
|
facts: {
|
|
321
330
|
bmc: HSS_ASSUMED,
|
|
331
|
+
coolantThrough: NO_COOLANT_THROUGH_TAP,
|
|
322
332
|
},
|
|
323
333
|
},
|
|
324
334
|
'gomill_pro_radiused_4fl_necked_metric.csv': {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MariTool's families — toolholding only, and one CSV per spindle taper.
|
|
3
|
+
*
|
|
4
|
+
* There is no `FAMILIES` table here: MariTool publishes no cutting tools, so
|
|
5
|
+
* this vendor never goes through a column map and binds no record mapper —
|
|
6
|
+
* the REGO-FIX case. What it publishes is ER collet chucks, shrink-fit holders
|
|
7
|
+
* and hydraulic chucks in five tapers.
|
|
8
|
+
*
|
|
9
|
+
* ## The scrape target is a list of leaf categories
|
|
10
|
+
*
|
|
11
|
+
* MariTool's tree is two to four levels deep and **no root carries products
|
|
12
|
+
* itself**, so there is no single page to name. Each family below states the
|
|
13
|
+
* leaf cPaths it is scraped from, in {@link LEAVES}, and that list is config
|
|
14
|
+
* rather than something a scrape re-derives: a walk of the five roots is 199
|
|
15
|
+
* requests to rediscover 41 paths that are already written down. The walk is
|
|
16
|
+
* kept — `vendors/maritool/catalog.ts`, reachable as `maritool --catalog` —
|
|
17
|
+
* because it is how this list was built and how it gets rechecked.
|
|
18
|
+
*
|
|
19
|
+
* **A leaf carries the clamping mode and the style with it.** Each CSV is one
|
|
20
|
+
* taper and mixes all three holder styles, so neither can be a family fact;
|
|
21
|
+
* and MariTool's own name for the leaf is what classifies the part, so the
|
|
22
|
+
* column is vendor-stated rather than a coinage here. `ER Collet Chucks` is
|
|
23
|
+
* `collet` / `er-collet-chuck`, `Shrink Fit Holders` is `shrink` /
|
|
24
|
+
* `shrink-fit`, `Hydraulic Chucks` is `hydraulic` / `hydraulic-chuck`.
|
|
25
|
+
*
|
|
26
|
+
* **Three of the leaves are named `Collet Chucks` rather than `ER Collet
|
|
27
|
+
* Chucks`, and all three are ER** — `c23_24_45` (CAT50 ER32), `c23_24_429_430`
|
|
28
|
+
* (dual-contact CAT50 ER32) and `c23_46_1811_1812` (HSK63F ER20), verified
|
|
29
|
+
* from the part numbers on each page. That is the reason this is a list and
|
|
30
|
+
* not a name filter: a rule matching `ER Collet Chucks` drops all three
|
|
31
|
+
* silently.
|
|
32
|
+
*
|
|
33
|
+
* ## Not a fact between them
|
|
34
|
+
*
|
|
35
|
+
* No family below declares one, and every candidate is a column instead:
|
|
36
|
+
*
|
|
37
|
+
* - **`taper` and `contact`** — MariTool states the interface per part in its
|
|
38
|
+
* `Taper` cell, in both plain and dual-contact forms, and the HSK family
|
|
39
|
+
* holds nine sizes in one CSV. A family constant would mask a scrape that
|
|
40
|
+
* lost the column.
|
|
41
|
+
* - **`clamping` and `style`** — three styles per CSV, from the leaf.
|
|
42
|
+
* - **`unit`** — `Gage Length` is metric on some parts and imperial on others
|
|
43
|
+
* inside one file *and* inside one category page, so there is no
|
|
44
|
+
* family-level answer to declare. The scraper promotes an `L1_in`/`L1_mm`
|
|
45
|
+
* pair with one cell filled instead.
|
|
46
|
+
*
|
|
47
|
+
* ## What is not scraped
|
|
48
|
+
*
|
|
49
|
+
* **BT50 and ISO30 produce no CSV.** MariTool's only BT50 category is end mill
|
|
50
|
+
* holders, which is out of scope; ISO30 has three ER parts and none of them
|
|
51
|
+
* publishes a spec table, so the family would be a receipt of nothing.
|
|
52
|
+
*
|
|
53
|
+
* Mega Grip, SK, TG, end mill and shell mill holders, slitting-saw and hob
|
|
54
|
+
* arbors, threaded-body and DSF/MCS modulars, drill chucks, floating tap
|
|
55
|
+
* holders, boring-head adapters, calibration tooling and coolant tubes are all
|
|
56
|
+
* out of scope as well — 730 further parts under `c23`. Adding one is a
|
|
57
|
+
* decision, not a widening of this table.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* One leaf to scrape, and how MariTool classifies what is in it.
|
|
61
|
+
*
|
|
62
|
+
* Declared here rather than imported from `vendors/maritool/scrape.ts`,
|
|
63
|
+
* which declares a structurally identical `LeafTarget`: `families/` is config
|
|
64
|
+
* and must not import an adapter — `tests/vendor-boundary.test.ts` refuses it,
|
|
65
|
+
* and the table is read by every test, none of which should drag a vendor's
|
|
66
|
+
* scraper in behind it.
|
|
67
|
+
*/
|
|
68
|
+
export interface Leaf {
|
|
69
|
+
/** MariTool's own category path, e.g. `23_25_42`. */
|
|
70
|
+
readonly cPath: string;
|
|
71
|
+
/** How a holder in this leaf grips: `collet`, `shrink`, `hydraulic`. */
|
|
72
|
+
readonly clamping: string;
|
|
73
|
+
/** The product style, as MariTool names the leaf. */
|
|
74
|
+
readonly style: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Which leaf categories each family is scraped from, by CSV name.
|
|
78
|
+
*
|
|
79
|
+
* Keyed the same as {@link HOLDER_FAMILIES}, which
|
|
80
|
+
* `tests/maritool.test.ts` holds the two tables to — a family with no leaves
|
|
81
|
+
* would scrape nothing and report it as an empty catalog.
|
|
82
|
+
*
|
|
83
|
+
* The plain and dual-contact leaves of one style sit next to each other on
|
|
84
|
+
* purpose: they share no `products_id` at all, so a dual-contact holder is a
|
|
85
|
+
* distinct part rather than a re-listing, and both belong in the taper's CSV.
|
|
86
|
+
*/
|
|
87
|
+
export declare const LEAVES: {
|
|
88
|
+
readonly 'maritool_cat40_holders.csv': readonly [Leaf, Leaf, Leaf, Leaf, Leaf, Leaf];
|
|
89
|
+
readonly 'maritool_cat50_holders.csv': readonly [Leaf, Leaf, Leaf, Leaf, Leaf, Leaf];
|
|
90
|
+
readonly 'maritool_bt30_holders.csv': readonly [Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf];
|
|
91
|
+
readonly 'maritool_bt40_holders.csv': readonly [Leaf, Leaf, Leaf, Leaf, Leaf, Leaf];
|
|
92
|
+
readonly 'maritool_hsk_holders.csv': readonly [Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf, Leaf];
|
|
93
|
+
};
|
|
94
|
+
export declare const HOLDER_FAMILIES: {
|
|
95
|
+
readonly 'maritool_cat40_holders.csv': {
|
|
96
|
+
readonly catalogName: "MariTool CAT40 Toolholders";
|
|
97
|
+
readonly rows: 217;
|
|
98
|
+
readonly brand: "maritool";
|
|
99
|
+
};
|
|
100
|
+
readonly 'maritool_cat50_holders.csv': {
|
|
101
|
+
readonly catalogName: "MariTool CAT50 Toolholders";
|
|
102
|
+
readonly rows: 9;
|
|
103
|
+
readonly brand: "maritool";
|
|
104
|
+
};
|
|
105
|
+
readonly 'maritool_bt30_holders.csv': {
|
|
106
|
+
readonly catalogName: "MariTool BT30 Toolholders";
|
|
107
|
+
readonly rows: 164;
|
|
108
|
+
readonly brand: "maritool";
|
|
109
|
+
};
|
|
110
|
+
readonly 'maritool_bt40_holders.csv': {
|
|
111
|
+
readonly catalogName: "MariTool BT40 Toolholders";
|
|
112
|
+
readonly rows: 67;
|
|
113
|
+
readonly brand: "maritool";
|
|
114
|
+
};
|
|
115
|
+
readonly 'maritool_hsk_holders.csv': {
|
|
116
|
+
readonly catalogName: "MariTool HSK Toolholders";
|
|
117
|
+
readonly rows: 70;
|
|
118
|
+
readonly brand: "maritool";
|
|
119
|
+
};
|
|
120
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MariTool's families — toolholding only, and one CSV per spindle taper.
|
|
3
|
+
*
|
|
4
|
+
* There is no `FAMILIES` table here: MariTool publishes no cutting tools, so
|
|
5
|
+
* this vendor never goes through a column map and binds no record mapper —
|
|
6
|
+
* the REGO-FIX case. What it publishes is ER collet chucks, shrink-fit holders
|
|
7
|
+
* and hydraulic chucks in five tapers.
|
|
8
|
+
*
|
|
9
|
+
* ## The scrape target is a list of leaf categories
|
|
10
|
+
*
|
|
11
|
+
* MariTool's tree is two to four levels deep and **no root carries products
|
|
12
|
+
* itself**, so there is no single page to name. Each family below states the
|
|
13
|
+
* leaf cPaths it is scraped from, in {@link LEAVES}, and that list is config
|
|
14
|
+
* rather than something a scrape re-derives: a walk of the five roots is 199
|
|
15
|
+
* requests to rediscover 41 paths that are already written down. The walk is
|
|
16
|
+
* kept — `vendors/maritool/catalog.ts`, reachable as `maritool --catalog` —
|
|
17
|
+
* because it is how this list was built and how it gets rechecked.
|
|
18
|
+
*
|
|
19
|
+
* **A leaf carries the clamping mode and the style with it.** Each CSV is one
|
|
20
|
+
* taper and mixes all three holder styles, so neither can be a family fact;
|
|
21
|
+
* and MariTool's own name for the leaf is what classifies the part, so the
|
|
22
|
+
* column is vendor-stated rather than a coinage here. `ER Collet Chucks` is
|
|
23
|
+
* `collet` / `er-collet-chuck`, `Shrink Fit Holders` is `shrink` /
|
|
24
|
+
* `shrink-fit`, `Hydraulic Chucks` is `hydraulic` / `hydraulic-chuck`.
|
|
25
|
+
*
|
|
26
|
+
* **Three of the leaves are named `Collet Chucks` rather than `ER Collet
|
|
27
|
+
* Chucks`, and all three are ER** — `c23_24_45` (CAT50 ER32), `c23_24_429_430`
|
|
28
|
+
* (dual-contact CAT50 ER32) and `c23_46_1811_1812` (HSK63F ER20), verified
|
|
29
|
+
* from the part numbers on each page. That is the reason this is a list and
|
|
30
|
+
* not a name filter: a rule matching `ER Collet Chucks` drops all three
|
|
31
|
+
* silently.
|
|
32
|
+
*
|
|
33
|
+
* ## Not a fact between them
|
|
34
|
+
*
|
|
35
|
+
* No family below declares one, and every candidate is a column instead:
|
|
36
|
+
*
|
|
37
|
+
* - **`taper` and `contact`** — MariTool states the interface per part in its
|
|
38
|
+
* `Taper` cell, in both plain and dual-contact forms, and the HSK family
|
|
39
|
+
* holds nine sizes in one CSV. A family constant would mask a scrape that
|
|
40
|
+
* lost the column.
|
|
41
|
+
* - **`clamping` and `style`** — three styles per CSV, from the leaf.
|
|
42
|
+
* - **`unit`** — `Gage Length` is metric on some parts and imperial on others
|
|
43
|
+
* inside one file *and* inside one category page, so there is no
|
|
44
|
+
* family-level answer to declare. The scraper promotes an `L1_in`/`L1_mm`
|
|
45
|
+
* pair with one cell filled instead.
|
|
46
|
+
*
|
|
47
|
+
* ## What is not scraped
|
|
48
|
+
*
|
|
49
|
+
* **BT50 and ISO30 produce no CSV.** MariTool's only BT50 category is end mill
|
|
50
|
+
* holders, which is out of scope; ISO30 has three ER parts and none of them
|
|
51
|
+
* publishes a spec table, so the family would be a receipt of nothing.
|
|
52
|
+
*
|
|
53
|
+
* Mega Grip, SK, TG, end mill and shell mill holders, slitting-saw and hob
|
|
54
|
+
* arbors, threaded-body and DSF/MCS modulars, drill chucks, floating tap
|
|
55
|
+
* holders, boring-head adapters, calibration tooling and coolant tubes are all
|
|
56
|
+
* out of scope as well — 730 further parts under `c23`. Adding one is a
|
|
57
|
+
* decision, not a widening of this table.
|
|
58
|
+
*/
|
|
59
|
+
const collet = (cPath) => ({ cPath, clamping: 'collet', style: 'er-collet-chuck' });
|
|
60
|
+
const shrink = (cPath) => ({ cPath, clamping: 'shrink', style: 'shrink-fit' });
|
|
61
|
+
const hydraulic = (cPath) => ({
|
|
62
|
+
cPath,
|
|
63
|
+
clamping: 'hydraulic',
|
|
64
|
+
style: 'hydraulic-chuck',
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Which leaf categories each family is scraped from, by CSV name.
|
|
68
|
+
*
|
|
69
|
+
* Keyed the same as {@link HOLDER_FAMILIES}, which
|
|
70
|
+
* `tests/maritool.test.ts` holds the two tables to — a family with no leaves
|
|
71
|
+
* would scrape nothing and report it as an empty catalog.
|
|
72
|
+
*
|
|
73
|
+
* The plain and dual-contact leaves of one style sit next to each other on
|
|
74
|
+
* purpose: they share no `products_id` at all, so a dual-contact holder is a
|
|
75
|
+
* distinct part rather than a re-listing, and both belong in the taper's CSV.
|
|
76
|
+
*/
|
|
77
|
+
export const LEAVES = {
|
|
78
|
+
'maritool_cat40_holders.csv': [
|
|
79
|
+
collet('23_25_42'),
|
|
80
|
+
collet('23_25_432_433'),
|
|
81
|
+
shrink('23_25_503'),
|
|
82
|
+
shrink('23_25_432_524'),
|
|
83
|
+
hydraulic('23_25_929'),
|
|
84
|
+
hydraulic('23_25_432_930'),
|
|
85
|
+
],
|
|
86
|
+
'maritool_cat50_holders.csv': [
|
|
87
|
+
collet('23_24_45'),
|
|
88
|
+
collet('23_24_429_430'),
|
|
89
|
+
shrink('23_24_1978'),
|
|
90
|
+
shrink('23_24_429_1979'),
|
|
91
|
+
hydraulic('23_24_957'),
|
|
92
|
+
hydraulic('23_24_429_1512'),
|
|
93
|
+
],
|
|
94
|
+
'maritool_bt30_holders.csv': [
|
|
95
|
+
collet('23_33_35'),
|
|
96
|
+
collet('23_33_444_445'),
|
|
97
|
+
collet('23_33_269_270'),
|
|
98
|
+
shrink('23_33_515'),
|
|
99
|
+
shrink('23_33_444_559'),
|
|
100
|
+
shrink('23_33_269_1575'),
|
|
101
|
+
hydraulic('23_33_911'),
|
|
102
|
+
hydraulic('23_33_444_912'),
|
|
103
|
+
],
|
|
104
|
+
'maritool_bt40_holders.csv': [
|
|
105
|
+
collet('23_26_36'),
|
|
106
|
+
collet('23_26_480_481'),
|
|
107
|
+
shrink('23_26_1640'),
|
|
108
|
+
shrink('23_26_480_1641'),
|
|
109
|
+
hydraulic('23_26_1199'),
|
|
110
|
+
hydraulic('23_26_480_1409'),
|
|
111
|
+
],
|
|
112
|
+
// Fifteen leaves for seventy parts, because HSK is nine sizes rather than
|
|
113
|
+
// one taper and MariTool files each size's styles under it separately. The
|
|
114
|
+
// sizes in scope are HSK25E, HSK40E, HSK50A, HSK50E, HSK63A, HSK63F, HSK80F
|
|
115
|
+
// and HSK100A; the `Taper` column is what tells them apart in the file.
|
|
116
|
+
'maritool_hsk_holders.csv': [
|
|
117
|
+
collet('23_46_943_1494'), // HSK63A
|
|
118
|
+
collet('23_46_1523_1543'), // HSK50A
|
|
119
|
+
collet('23_46_1552_1558'), // HSK40E
|
|
120
|
+
collet('23_46_1591_1592'), // HSK25E
|
|
121
|
+
collet('23_46_972_1605'), // HSK50E
|
|
122
|
+
collet('23_46_1811_1812'), // HSK63F — the leaf named "Collet Chuck Holders"
|
|
123
|
+
shrink('23_46_943_947'), // HSK63A
|
|
124
|
+
shrink('23_46_972_1495'), // HSK50E
|
|
125
|
+
shrink('23_46_1523_1542'), // HSK50A
|
|
126
|
+
shrink('23_46_1552_1553'), // HSK40E
|
|
127
|
+
shrink('23_46_1513_1903'), // HSK100A
|
|
128
|
+
shrink('23_46_1841_1844'), // HSK80F
|
|
129
|
+
hydraulic('23_46_943_946'), // HSK63A
|
|
130
|
+
hydraulic('23_46_1513_1534'), // HSK100A
|
|
131
|
+
hydraulic('23_46_972_973'), // HSK50E
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
export const HOLDER_FAMILIES = {
|
|
135
|
+
// ── MariTool toolholding (JG 2026-08-29) ───────────────────────────────
|
|
136
|
+
// Five families, 529 parts across 41 leaves, and **no facts on any of
|
|
137
|
+
// them**. Every per-family constant the other toolholding vendors declare is
|
|
138
|
+
// a column here, for the reasons in this module's docstring; a fact stating
|
|
139
|
+
// one would be a constant the vendor's own table contradicts row by row.
|
|
140
|
+
//
|
|
141
|
+
// `rows` is the sum of what each leaf's own `(of N products)` line says,
|
|
142
|
+
// which is the independent restatement `receipts.checkRows` needs: every
|
|
143
|
+
// other count is computed from the same file it is checking, so a scrape
|
|
144
|
+
// that silently lost rows agrees with itself.
|
|
145
|
+
'maritool_cat40_holders.csv': {
|
|
146
|
+
catalogName: 'MariTool CAT40 Toolholders',
|
|
147
|
+
rows: 217,
|
|
148
|
+
brand: 'maritool',
|
|
149
|
+
},
|
|
150
|
+
// 11 parts in the vendor's six leaves and 9 rows here: `CAT50-ER32-3.0` and
|
|
151
|
+
// `CAT50-ER32-4.0` publish no `Product Specifications` table at all — they
|
|
152
|
+
// state their geometry as prose in a `Product Info` bullet list — so they
|
|
153
|
+
// have no gage length and are skipped by the scraper with a message. They
|
|
154
|
+
// are the only two such parts in scope.
|
|
155
|
+
'maritool_cat50_holders.csv': {
|
|
156
|
+
catalogName: 'MariTool CAT50 Toolholders',
|
|
157
|
+
rows: 9,
|
|
158
|
+
brand: 'maritool',
|
|
159
|
+
},
|
|
160
|
+
'maritool_bt30_holders.csv': {
|
|
161
|
+
catalogName: 'MariTool BT30 Toolholders',
|
|
162
|
+
rows: 164,
|
|
163
|
+
brand: 'maritool',
|
|
164
|
+
},
|
|
165
|
+
'maritool_bt40_holders.csv': {
|
|
166
|
+
catalogName: 'MariTool BT40 Toolholders',
|
|
167
|
+
rows: 67,
|
|
168
|
+
brand: 'maritool',
|
|
169
|
+
},
|
|
170
|
+
'maritool_hsk_holders.csv': {
|
|
171
|
+
catalogName: 'MariTool HSK Toolholders',
|
|
172
|
+
rows: 70,
|
|
173
|
+
brand: 'maritool',
|
|
174
|
+
},
|
|
175
|
+
};
|
package/dist/family.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { MapperOptions, ScrapedRow } from './scrape.js';
|
|
|
24
24
|
/**
|
|
25
25
|
* The per-family constants a fact can carry, and their types.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
27
|
+
* Eleven keys, which is the whole vocabulary the catalog uses. Naming them rather
|
|
28
28
|
* than accepting any string is what lets a mapper read `family.coolantThrough`
|
|
29
29
|
* as a `boolean` instead of casting an `unknown` out of a bag — and what makes
|
|
30
30
|
* a fact whose value is the wrong type a compile error where the family is
|
|
@@ -35,6 +35,18 @@ export interface FamilyFacts {
|
|
|
35
35
|
unit?: UnitSystem;
|
|
36
36
|
/** Cutting-material code — `carbide`, `hss`, `diamond`. */
|
|
37
37
|
bmc?: string;
|
|
38
|
+
/**
|
|
39
|
+
* The end profile, as the vendor names it — `Ball`, `Square`,
|
|
40
|
+
* `Corner Radius`.
|
|
41
|
+
*
|
|
42
|
+
* A per-family constant here rather than a per-row derivation because some
|
|
43
|
+
* vendors state it once for a whole product line and never in the variant
|
|
44
|
+
* table: Harvey Tool's 52 product pages each publish one profile in the page
|
|
45
|
+
* title and no profile column, where Destiny Tool publishes an `endStyle`
|
|
46
|
+
* per row and needs none of this. It is what tells a mapper that a family
|
|
47
|
+
* with no corner-radius column has `RE = DC / 2` rather than `RE = 0`.
|
|
48
|
+
*/
|
|
49
|
+
profile?: string;
|
|
38
50
|
coolantThrough?: boolean;
|
|
39
51
|
flutes?: number;
|
|
40
52
|
/** Degrees included. */
|
|
@@ -125,6 +137,28 @@ export type BoundToolholding = ToolholdingDefinition & FamilyFacts;
|
|
|
125
137
|
* stays one route parameter.
|
|
126
138
|
*/
|
|
127
139
|
export declare function familyId(cfg: FamilyDefinition | BoundFamily): string;
|
|
140
|
+
/**
|
|
141
|
+
* A per-family constant a mapper cannot proceed without.
|
|
142
|
+
*
|
|
143
|
+
* The projection is what makes this readable — a mapper says `family.unit` and
|
|
144
|
+
* never learns about provenance — but a projected key is still optional on the
|
|
145
|
+
* type, because {@link FamilyFacts} is one vocabulary shared by four vendors
|
|
146
|
+
* and a tap states no `unit`. So the check is per read, and it belongs here
|
|
147
|
+
* rather than in one adapter: it lived in `vendors/kennametal/records.ts` until
|
|
148
|
+
* 2026-08-29, which is why the other two adapters each invented their own
|
|
149
|
+
* weaker answer instead — `family.unit!`, `family.bmc ?? ''`,
|
|
150
|
+
* `family.coolantThrough ?? false`, a hardcoded `'inches'`.
|
|
151
|
+
*
|
|
152
|
+
* **Every one of those defaults is a claim the family never made.** A missing
|
|
153
|
+
* `coolantThrough` becoming `false` ships a through-coolant drill with its
|
|
154
|
+
* coolant presets dropped; a missing `bmc` becoming `''` ships a tool with no
|
|
155
|
+
* cutting material at all. Refusing names the family and the key, at the one
|
|
156
|
+
* place that can fix it — the config table.
|
|
157
|
+
*
|
|
158
|
+
* Same move `scrape.pause` and `conventions.CAD_COLUMN` already made, and for
|
|
159
|
+
* the same reason: a core concern does not live in one manufacturer's folder.
|
|
160
|
+
*/
|
|
161
|
+
export declare function fact<T>(family: BoundFamily, key: string, value: T | undefined): T;
|
|
128
162
|
/** The brand that published a family, defaulting as the catalog does. */
|
|
129
163
|
export declare function familyBrand(cfg: CommonDefinition | BoundFamily | BoundToolholding): BrandName;
|
|
130
164
|
export {};
|
package/dist/family.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* says `family.pointAngle` and never learns about provenance — but there is
|
|
17
17
|
* only ever one authored copy to drift from.
|
|
18
18
|
*/
|
|
19
|
+
import { ScraperConfigError } from './errors.js';
|
|
19
20
|
/**
|
|
20
21
|
* A family's id: `<brand>:<vendor-local id>`.
|
|
21
22
|
*
|
|
@@ -32,6 +33,33 @@
|
|
|
32
33
|
export function familyId(cfg) {
|
|
33
34
|
return `${cfg.brand ?? 'kennametal'}:${cfg.id}`;
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* A per-family constant a mapper cannot proceed without.
|
|
38
|
+
*
|
|
39
|
+
* The projection is what makes this readable — a mapper says `family.unit` and
|
|
40
|
+
* never learns about provenance — but a projected key is still optional on the
|
|
41
|
+
* type, because {@link FamilyFacts} is one vocabulary shared by four vendors
|
|
42
|
+
* and a tap states no `unit`. So the check is per read, and it belongs here
|
|
43
|
+
* rather than in one adapter: it lived in `vendors/kennametal/records.ts` until
|
|
44
|
+
* 2026-08-29, which is why the other two adapters each invented their own
|
|
45
|
+
* weaker answer instead — `family.unit!`, `family.bmc ?? ''`,
|
|
46
|
+
* `family.coolantThrough ?? false`, a hardcoded `'inches'`.
|
|
47
|
+
*
|
|
48
|
+
* **Every one of those defaults is a claim the family never made.** A missing
|
|
49
|
+
* `coolantThrough` becoming `false` ships a through-coolant drill with its
|
|
50
|
+
* coolant presets dropped; a missing `bmc` becoming `''` ships a tool with no
|
|
51
|
+
* cutting material at all. Refusing names the family and the key, at the one
|
|
52
|
+
* place that can fix it — the config table.
|
|
53
|
+
*
|
|
54
|
+
* Same move `scrape.pause` and `conventions.CAD_COLUMN` already made, and for
|
|
55
|
+
* the same reason: a core concern does not live in one manufacturer's folder.
|
|
56
|
+
*/
|
|
57
|
+
export function fact(family, key, value) {
|
|
58
|
+
if (value === undefined) {
|
|
59
|
+
throw new ScraperConfigError(family.id, `a ${family.kind} family must state ${key} as a fact`);
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
35
63
|
/** The brand that published a family, defaulting as the catalog does. */
|
|
36
64
|
export function familyBrand(cfg) {
|
|
37
65
|
return cfg.brand ?? 'kennametal';
|
package/dist/identity.d.ts
CHANGED
|
@@ -77,6 +77,24 @@ export declare const BRANDS: {
|
|
|
77
77
|
readonly vendor: "Destiny Tool";
|
|
78
78
|
readonly productLink: "https://destinytool.com/products?search={material}";
|
|
79
79
|
};
|
|
80
|
+
readonly harvey: {
|
|
81
|
+
readonly host: "harveytool.com";
|
|
82
|
+
readonly home: "https://www.harveytool.com";
|
|
83
|
+
readonly vendor: "Harvey Tool";
|
|
84
|
+
readonly productLink: "https://www.harveytool.com/products/tool-details-{material}";
|
|
85
|
+
};
|
|
86
|
+
readonly maritool: {
|
|
87
|
+
readonly host: "maritool.com";
|
|
88
|
+
readonly home: "https://www.maritool.com";
|
|
89
|
+
readonly vendor: "MariTool";
|
|
90
|
+
readonly productLink: "https://www.maritool.com/advanced_search_result.php?keywords={material}";
|
|
91
|
+
};
|
|
92
|
+
readonly emuge: {
|
|
93
|
+
readonly host: "www.emuge-franken-group.com";
|
|
94
|
+
readonly home: "https://www.emuge-franken-group.com";
|
|
95
|
+
readonly vendor: "EMUGE-FRANKEN";
|
|
96
|
+
readonly productLink: "https://www.emuge-franken-group.com/us/en/p/{material}";
|
|
97
|
+
};
|
|
80
98
|
};
|
|
81
99
|
/**
|
|
82
100
|
* The brands this package knows, as a type.
|
package/dist/identity.js
CHANGED
|
@@ -65,6 +65,52 @@ export const BRANDS = {
|
|
|
65
65
|
vendor: 'Destiny Tool',
|
|
66
66
|
productLink: 'https://destinytool.com/products?search={material}',
|
|
67
67
|
},
|
|
68
|
+
// Harvey Tool is the first brand here whose per-part link is a real page the
|
|
69
|
+
// vendor already publishes: every tool number in a product table is rendered
|
|
70
|
+
// as `<a href="/products/tool-details-14916">`, so the template below is the
|
|
71
|
+
// vendor's own URL rather than a search standing in for one. The 26 parts
|
|
72
|
+
// whose table cell carries no link are the exception, and the link is still
|
|
73
|
+
// the right thing to offer for them — see `docs/HARVEY_PRODUCT_TABLE.md` §3.
|
|
74
|
+
harvey: {
|
|
75
|
+
host: 'harveytool.com',
|
|
76
|
+
home: 'https://www.harveytool.com',
|
|
77
|
+
vendor: 'Harvey Tool',
|
|
78
|
+
productLink: 'https://www.harveytool.com/products/tool-details-{material}',
|
|
79
|
+
},
|
|
80
|
+
// MariTool is an osCommerce-family storefront, and its per-part page is
|
|
81
|
+
// addressable three ways: `product_info.php?products_id=100`, the
|
|
82
|
+
// SEO-rewritten `/p100/<slug>/product_info.html`, and a keyword search. The
|
|
83
|
+
// first two are keyed on an internal store id rather than on the part
|
|
84
|
+
// number, and that id is the one thing about a MariTool part that is not
|
|
85
|
+
// stable — a re-created product changes it. So the link is a search for the
|
|
86
|
+
// part number, the same call REGO-FIX and Destiny Tool got, and here it is a
|
|
87
|
+
// search the vendor really answers: `advanced_search_result.php?keywords=`
|
|
88
|
+
// is the storefront's own search endpoint and a part number matches exactly
|
|
89
|
+
// one product (JG 2026-08-29).
|
|
90
|
+
//
|
|
91
|
+
// No `node`: that key is Kennametal's AEM platform and nothing else.
|
|
92
|
+
maritool: {
|
|
93
|
+
host: 'maritool.com',
|
|
94
|
+
home: 'https://www.maritool.com',
|
|
95
|
+
vendor: 'MariTool',
|
|
96
|
+
productLink: 'https://www.maritool.com/advanced_search_result.php?keywords={material}',
|
|
97
|
+
},
|
|
98
|
+
// EMUGE-FRANKEN is a SAP Commerce storefront whose pages carry no product
|
|
99
|
+
// data at all — the Vue front end reads a JSON API, and that API is what the
|
|
100
|
+
// adapter talks to. See `docs/EMUGE_FRANKEN_COMMERCE_API.md`.
|
|
101
|
+
//
|
|
102
|
+
// The per-part link is the vendor's own: every variant record answers with
|
|
103
|
+
// `url: "/us/en/p/<18-digit material number>"`, so this is that page rather
|
|
104
|
+
// than a search standing in for one. `www.emuge-franken-group.com` is the
|
|
105
|
+
// group site serving every region; `/us/en/` is the US storefront the
|
|
106
|
+
// `emugefrankenUSA` base site the scrape reads corresponds to, so the link
|
|
107
|
+
// and the data are the same catalog (JG 2026-09-01).
|
|
108
|
+
emuge: {
|
|
109
|
+
host: 'www.emuge-franken-group.com',
|
|
110
|
+
home: 'https://www.emuge-franken-group.com',
|
|
111
|
+
vendor: 'EMUGE-FRANKEN',
|
|
112
|
+
productLink: 'https://www.emuge-franken-group.com/us/en/p/{material}',
|
|
113
|
+
},
|
|
68
114
|
};
|
|
69
115
|
/**
|
|
70
116
|
* The two brands on Kennametal's AEM platform.
|
package/dist/index.d.ts
CHANGED
|
@@ -19,12 +19,28 @@
|
|
|
19
19
|
* that goes beside one, is `@toolpath/tool-scraper/node` — a separate entry
|
|
20
20
|
* point, because a backend embedding this wants the data and a maintainer
|
|
21
21
|
* running the CLI wants the file, and only one of those two needs `fs`.
|
|
22
|
+
*
|
|
23
|
+
* ## Cutting tools become records; toolholding is rows
|
|
24
|
+
*
|
|
25
|
+
* **`ToolRecord` is the uniform output for cutting tools, and only for those.**
|
|
26
|
+
* `registry.toRecords` maps one cutting-tool family's scrape onto it, through
|
|
27
|
+
* the adapter its brand binds. Holders and collets have no record type and no
|
|
28
|
+
* mapper: REGO-FIX and MariTool ship toolholding and nothing else, so a scrape
|
|
29
|
+
* of either ends at rows and a receipt, and a consumer that wants a holder
|
|
30
|
+
* reads the CSV's own columns — the shared ones are named in `conventions`.
|
|
31
|
+
*
|
|
32
|
+
* That is a real gap rather than a design: `identity.recordGuid` already exists
|
|
33
|
+
* so that a holder and a tool can be minted into one guid space, and nothing
|
|
34
|
+
* mints a holder yet. Until something does, `toRecords` is the tool half and
|
|
35
|
+
* the receipt is the whole of the toolholding half.
|
|
22
36
|
*/
|
|
37
|
+
export * from './columns.js';
|
|
23
38
|
export * from './conventions.js';
|
|
24
39
|
export * from './errors.js';
|
|
25
40
|
export * from './family.js';
|
|
26
41
|
export * from './fetch.js';
|
|
27
42
|
export * from './identity.js';
|
|
43
|
+
export * from './measure.js';
|
|
28
44
|
export * from './provenance.js';
|
|
29
45
|
export * from './records.js';
|
|
30
46
|
export * from './scrape.js';
|
package/dist/index.js
CHANGED
|
@@ -19,12 +19,28 @@
|
|
|
19
19
|
* that goes beside one, is `@toolpath/tool-scraper/node` — a separate entry
|
|
20
20
|
* point, because a backend embedding this wants the data and a maintainer
|
|
21
21
|
* running the CLI wants the file, and only one of those two needs `fs`.
|
|
22
|
+
*
|
|
23
|
+
* ## Cutting tools become records; toolholding is rows
|
|
24
|
+
*
|
|
25
|
+
* **`ToolRecord` is the uniform output for cutting tools, and only for those.**
|
|
26
|
+
* `registry.toRecords` maps one cutting-tool family's scrape onto it, through
|
|
27
|
+
* the adapter its brand binds. Holders and collets have no record type and no
|
|
28
|
+
* mapper: REGO-FIX and MariTool ship toolholding and nothing else, so a scrape
|
|
29
|
+
* of either ends at rows and a receipt, and a consumer that wants a holder
|
|
30
|
+
* reads the CSV's own columns — the shared ones are named in `conventions`.
|
|
31
|
+
*
|
|
32
|
+
* That is a real gap rather than a design: `identity.recordGuid` already exists
|
|
33
|
+
* so that a holder and a tool can be minted into one guid space, and nothing
|
|
34
|
+
* mints a holder yet. Until something does, `toRecords` is the tool half and
|
|
35
|
+
* the receipt is the whole of the toolholding half.
|
|
22
36
|
*/
|
|
37
|
+
export * from './columns.js';
|
|
23
38
|
export * from './conventions.js';
|
|
24
39
|
export * from './errors.js';
|
|
25
40
|
export * from './family.js';
|
|
26
41
|
export * from './fetch.js';
|
|
27
42
|
export * from './identity.js';
|
|
43
|
+
export * from './measure.js';
|
|
28
44
|
export * from './provenance.js';
|
|
29
45
|
export * from './records.js';
|
|
30
46
|
export * from './scrape.js';
|