@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/scrape.js
CHANGED
|
@@ -13,6 +13,30 @@
|
|
|
13
13
|
export const consoleWarn = (message) => {
|
|
14
14
|
console.warn(message);
|
|
15
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Rows to the header they imply: the union of their keys, in first-seen order.
|
|
18
|
+
*
|
|
19
|
+
* A union rather than the first row's keys, and the reason is the same for both
|
|
20
|
+
* vendors that build a `ScrapeResult` this way. A REGO-FIX collet family is
|
|
21
|
+
* mixed-unit — `D1_mm` on its metric rows and `D1_in` on its inch ones — and a
|
|
22
|
+
* MariTool CSV mixes three holder styles, so which spec keys a part publishes is
|
|
23
|
+
* a function of its style. Keying off row one drops whichever came second.
|
|
24
|
+
*
|
|
25
|
+
* It lived in both adapters, byte for byte, until 2026-08-29. Building the
|
|
26
|
+
* header of a {@link ScrapeResult} is this module's business rather than any
|
|
27
|
+
* manufacturer's — the same call `pause` and `conventions.CAD_COLUMN` already
|
|
28
|
+
* got, and the one `tests/vendor-boundary.test.ts` now makes automatically.
|
|
29
|
+
*/
|
|
30
|
+
export function unionHeader(rows) {
|
|
31
|
+
const header = [];
|
|
32
|
+
for (const row of rows) {
|
|
33
|
+
for (const key of Object.keys(row)) {
|
|
34
|
+
if (!header.includes(key))
|
|
35
|
+
header.push(key);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return header;
|
|
39
|
+
}
|
|
16
40
|
/**
|
|
17
41
|
* Milliseconds between requests, wherever a scrape or a mirror loops.
|
|
18
42
|
*
|
|
@@ -94,6 +94,13 @@ export declare function shoulderDiameter(description: string, dc: number): numbe
|
|
|
94
94
|
* column when populated, or a fallback keyed on flute count when it is not
|
|
95
95
|
* (blank on 423 of 3,898 rows, 2026-08-19).
|
|
96
96
|
*
|
|
97
|
+
* **The two answers are labelled, not blended.** A populated cell is
|
|
98
|
+
* `vendor-stated` and the flute-count fallback is `derived`, so a consumer that
|
|
99
|
+
* will not route a cut off this package's arithmetic can filter on the source
|
|
100
|
+
* rather than having to know which rows Destiny Tool left blank. Neither is
|
|
101
|
+
* ever `null`: the fallback covers every blank cell, so this vendor always has
|
|
102
|
+
* an answer.
|
|
103
|
+
*
|
|
97
104
|
* The fallback is not a new rule invented for this vendor — it is the split
|
|
98
105
|
* cutting-data presets are routed by downstream (≤3 flutes non-ferrous, >3
|
|
99
106
|
* ferrous), applied here to the material-groups facet instead. Real vendor
|
|
@@ -109,10 +116,13 @@ export declare function shoulderDiameter(description: string, dc: number): numbe
|
|
|
109
116
|
* array and a tool's own list from another has no way to notice the two
|
|
110
117
|
* disagree.
|
|
111
118
|
*/
|
|
112
|
-
export declare function materialGroups(row: ScrapedRow, flutes: number):
|
|
119
|
+
export declare function materialGroups(row: ScrapedRow, flutes: number): Pick<ToolRecord, 'materialGroups' | 'materialGroupsSource'>;
|
|
113
120
|
/**
|
|
114
|
-
* A solid end mill,
|
|
115
|
-
*
|
|
121
|
+
* A solid end mill, in the family's declared unit.
|
|
122
|
+
*
|
|
123
|
+
* Which is inches on the one family there is — Destiny Tool publishes no metric
|
|
124
|
+
* line — but read from the `unit` fact rather than hardcoded, so the fact stays
|
|
125
|
+
* the single authored copy the way it is for every other vendor here.
|
|
116
126
|
*/
|
|
117
127
|
export declare function endmillRecord(row: ScrapedRow, family: BoundFamily, columns: ColumnMap, options?: MapperOptions): ToolRecord;
|
|
118
128
|
export declare const RECORD_MAPPERS: RecordMappers;
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* row.
|
|
24
24
|
*/
|
|
25
25
|
import { VendorResponseError } from '../../errors.js';
|
|
26
|
-
import {
|
|
26
|
+
import { fractionValue } from '../../measure.js';
|
|
27
|
+
import { fact, familyBrand } from '../../family.js';
|
|
27
28
|
import { BRANDS } from '../../identity.js';
|
|
28
29
|
import { ISO_MATERIAL_GROUPS, toolRecord, } from '../../records.js';
|
|
29
30
|
import { consoleWarn } from '../../scrape.js';
|
|
@@ -86,31 +87,18 @@ export function parseFractionInches(text) {
|
|
|
86
87
|
const s = text.trim().replace(/"+$/, '');
|
|
87
88
|
if (!s)
|
|
88
89
|
throw new RangeError(`empty dimension: ${JSON.stringify(text)}`);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
? fraction(s)
|
|
95
|
-
: Number(s);
|
|
96
|
-
if (!Number.isFinite(value)) {
|
|
90
|
+
// The trailing quote is the only thing Destiny Tool puts around a dimension
|
|
91
|
+
// that `measure.fractionValue` does not read; the grammar itself is the one
|
|
92
|
+
// every vendor here publishes, and it is read in one place.
|
|
93
|
+
const value = fractionValue(s);
|
|
94
|
+
if (value === null) {
|
|
97
95
|
throw new RangeError(`unrecognized dimension: ${JSON.stringify(text)}`);
|
|
98
96
|
}
|
|
99
97
|
return value;
|
|
100
98
|
}
|
|
101
|
-
/** `1-1/2` — a whole number and a simple fraction. */
|
|
102
|
-
function mixed(s) {
|
|
103
|
-
const cut = s.indexOf('-');
|
|
104
|
-
return Number(s.slice(0, cut)) + fraction(s.slice(cut + 1));
|
|
105
|
-
}
|
|
106
|
-
/** `3/4`. */
|
|
107
|
-
function fraction(s) {
|
|
108
|
-
const [num, den] = s.split('/');
|
|
109
|
-
return Number(num) / Number(den);
|
|
110
|
-
}
|
|
111
99
|
/** A dimension the kind requires, parsed as an inch fraction. */
|
|
112
|
-
function required(row, columns, canonical, what) {
|
|
113
|
-
const column = columns.column(canonical,
|
|
100
|
+
function required(row, columns, canonical, unit, what) {
|
|
101
|
+
const column = columns.column(canonical, unit);
|
|
114
102
|
const raw = column === null ? undefined : row[column];
|
|
115
103
|
if (raw === undefined || raw.trim() === '') {
|
|
116
104
|
throw new VendorResponseError(what, `no value for ${canonical} in column ${JSON.stringify(column)}`);
|
|
@@ -190,6 +178,13 @@ export function shoulderDiameter(description, dc) {
|
|
|
190
178
|
* column when populated, or a fallback keyed on flute count when it is not
|
|
191
179
|
* (blank on 423 of 3,898 rows, 2026-08-19).
|
|
192
180
|
*
|
|
181
|
+
* **The two answers are labelled, not blended.** A populated cell is
|
|
182
|
+
* `vendor-stated` and the flute-count fallback is `derived`, so a consumer that
|
|
183
|
+
* will not route a cut off this package's arithmetic can filter on the source
|
|
184
|
+
* rather than having to know which rows Destiny Tool left blank. Neither is
|
|
185
|
+
* ever `null`: the fallback covers every blank cell, so this vendor always has
|
|
186
|
+
* an answer.
|
|
187
|
+
*
|
|
193
188
|
* The fallback is not a new rule invented for this vendor — it is the split
|
|
194
189
|
* cutting-data presets are routed by downstream (≤3 flutes non-ferrous, >3
|
|
195
190
|
* ferrous), applied here to the material-groups facet instead. Real vendor
|
|
@@ -209,23 +204,31 @@ export function materialGroups(row, flutes) {
|
|
|
209
204
|
const cell = row['isoMaterialGroups'] ?? '';
|
|
210
205
|
if (cell.trim()) {
|
|
211
206
|
const present = new Set(cell.split(/\s+/).filter(Boolean));
|
|
212
|
-
return
|
|
207
|
+
return {
|
|
208
|
+
materialGroups: ISO_MATERIAL_GROUPS.filter((group) => present.has(group)),
|
|
209
|
+
materialGroupsSource: 'vendor-stated',
|
|
210
|
+
};
|
|
213
211
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
return {
|
|
213
|
+
materialGroups: flutes <= NON_FERROUS_MAX_FLUTES ? ['N'] : ['P', 'M', 'K', 'S', 'H'],
|
|
214
|
+
materialGroupsSource: 'derived',
|
|
215
|
+
};
|
|
217
216
|
}
|
|
218
217
|
/**
|
|
219
|
-
* A solid end mill,
|
|
220
|
-
*
|
|
218
|
+
* A solid end mill, in the family's declared unit.
|
|
219
|
+
*
|
|
220
|
+
* Which is inches on the one family there is — Destiny Tool publishes no metric
|
|
221
|
+
* line — but read from the `unit` fact rather than hardcoded, so the fact stays
|
|
222
|
+
* the single authored copy the way it is for every other vendor here.
|
|
221
223
|
*/
|
|
222
224
|
export function endmillRecord(row, family, columns, options = {}) {
|
|
225
|
+
const unit = fact(family, 'unit', family.unit);
|
|
223
226
|
const what = row[ITEM_NUMBER] ?? '';
|
|
224
227
|
const description = row['description'] ?? '';
|
|
225
|
-
const dc = required(row, columns, 'DC', what);
|
|
226
|
-
const fluteLength = required(row, columns, 'LCF', what);
|
|
227
|
-
const oal = required(row, columns, 'OAL', what);
|
|
228
|
-
const radColumn = columns.column('RE',
|
|
228
|
+
const dc = required(row, columns, 'DC', unit, what);
|
|
229
|
+
const fluteLength = required(row, columns, 'LCF', unit, what);
|
|
230
|
+
const oal = required(row, columns, 'OAL', unit, what);
|
|
231
|
+
const radColumn = columns.column('RE', unit);
|
|
229
232
|
const radRaw = radColumn === null ? undefined : row[radColumn];
|
|
230
233
|
const radCell = radRaw && radRaw.trim() ? parseFractionInches(radRaw) : null;
|
|
231
234
|
// Refused rather than allowed through as NaN: `materialGroups` reads it, and
|
|
@@ -237,17 +240,26 @@ export function endmillRecord(row, family, columns, options = {}) {
|
|
|
237
240
|
throw new VendorResponseError(what, `no integer in column "flutes"`);
|
|
238
241
|
}
|
|
239
242
|
return toolRecord({
|
|
243
|
+
brand: familyBrand(family),
|
|
240
244
|
vendor: BRANDS[familyBrand(family)].vendor,
|
|
241
245
|
materialNumber: what,
|
|
242
246
|
catalogNumber: what,
|
|
243
247
|
description,
|
|
244
248
|
kind: 'endmill',
|
|
245
|
-
unit
|
|
246
|
-
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
249
|
+
unit,
|
|
250
|
+
// `series` is the vendor's own product-line name and the one column here
|
|
251
|
+
// that names one — `Viper`, `Raptor`, `DiamondBack`, and `viper-mini` and
|
|
252
|
+
// `python` in the vendor's own lower case, which stays exactly as
|
|
253
|
+
// published for the reason `records.ToolRecord.productLine` gives. It has
|
|
254
|
+
// been scraped since the adapter was written and read by nothing until
|
|
255
|
+
// now; the description cannot stand in for it, because Raptor calls itself
|
|
256
|
+
// `DVH` there and DiamondBack calls itself `DBACK RGHR`.
|
|
257
|
+
productLine: row['series']?.trim() || null,
|
|
258
|
+
substrate: (row['material'] || fact(family, 'bmc', family.bmc)).toLowerCase(),
|
|
259
|
+
// No carbide grade is published; the coating id is what there is.
|
|
260
|
+
coating: row['coatingId'] ?? '',
|
|
261
|
+
...materialGroups(row, flutes),
|
|
262
|
+
coolantThrough: fact(family, 'coolantThrough', family.coolantThrough),
|
|
251
263
|
geometry: {
|
|
252
264
|
DC: dc,
|
|
253
265
|
RE: cornerRadius(description, row['endStyle'] ?? '', what, dc, radCell,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EMUGE-FRANKEN — end mills, twist drills and taps, from a SAP Commerce API.
|
|
3
|
+
*
|
|
4
|
+
* Not a catalog to parse: the storefront renders its listings in the browser,
|
|
5
|
+
* so the "scrape" is a JSON client against the same unauthenticated API the
|
|
6
|
+
* site's own Vue front end reads. See `scrape.ts` for the three calls it takes
|
|
7
|
+
* and why, `value.ts` for the grammar of a vendor value string, and
|
|
8
|
+
* `records.ts` for what the vendor states that no other vendor here does — a
|
|
9
|
+
* point angle per drill, an ISO 513 rating per part — and the one thing it
|
|
10
|
+
* states nowhere at all, which is a tap's flute count.
|
|
11
|
+
*
|
|
12
|
+
* `value.ts` is pure and holds the parsing risk; `scrape.ts` is the only module
|
|
13
|
+
* that reads through a `Fetcher`.
|
|
14
|
+
*/
|
|
15
|
+
export * from './records.js';
|
|
16
|
+
export * from './scrape.js';
|
|
17
|
+
export * from './value.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EMUGE-FRANKEN — end mills, twist drills and taps, from a SAP Commerce API.
|
|
3
|
+
*
|
|
4
|
+
* Not a catalog to parse: the storefront renders its listings in the browser,
|
|
5
|
+
* so the "scrape" is a JSON client against the same unauthenticated API the
|
|
6
|
+
* site's own Vue front end reads. See `scrape.ts` for the three calls it takes
|
|
7
|
+
* and why, `value.ts` for the grammar of a vendor value string, and
|
|
8
|
+
* `records.ts` for what the vendor states that no other vendor here does — a
|
|
9
|
+
* point angle per drill, an ISO 513 rating per part — and the one thing it
|
|
10
|
+
* states nowhere at all, which is a tap's flute count.
|
|
11
|
+
*
|
|
12
|
+
* `value.ts` is pure and holds the parsing risk; `scrape.ts` is the only module
|
|
13
|
+
* that reads through a `Fetcher`.
|
|
14
|
+
*/
|
|
15
|
+
export * from './records.js';
|
|
16
|
+
export * from './scrape.js';
|
|
17
|
+
export * from './value.js';
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EMUGE-FRANKEN rows -> {@link ToolRecord}.
|
|
3
|
+
*
|
|
4
|
+
* The CSV a scrape writes holds the vendor's own value strings — `1 1/2 "`,
|
|
5
|
+
* `3 mm`, `140 deg`, `4` — rather than parsed numbers, because the file is the
|
|
6
|
+
* receipt and EMUGE's fractional inches and stated units are part of what it
|
|
7
|
+
* published. So this module is where a cell becomes a number, through
|
|
8
|
+
* `value.ts`. The closest precedent is Harvey Tool's, not Kennametal's, whose
|
|
9
|
+
* columns are already decimals.
|
|
10
|
+
*
|
|
11
|
+
* ## What EMUGE publishes that nobody else here does
|
|
12
|
+
*
|
|
13
|
+
* - **Both identity columns.** The 18-digit SAP material number and the catalog
|
|
14
|
+
* article code, per part, so there is no `conventions.IDENTITY_DEVIATIONS`
|
|
15
|
+
* entry — the first vendor since Kennametal that needs none.
|
|
16
|
+
* - **A point angle, per drill.** Kennametal's drill families assume theirs; the
|
|
17
|
+
* detail record states it, so `SIG` is a mapped column here and no fact. On
|
|
18
|
+
* all but one part: 2,669 of 2,670 drill variants state one, and part
|
|
19
|
+
* `000000000010727800` publishes a single classification feature and no
|
|
20
|
+
* dimensional properties at all — so its row carries no `SIG` **key**, not
|
|
21
|
+
* an empty one. That is why `records.RECORD_GEOMETRY.drill` lists `SIG`
|
|
22
|
+
* under `sometimes`, and why {@link angle} reads the row rather than `cell`.
|
|
23
|
+
* - **A per-part ISO 513 index.** `applicationMaterials` returns the vendor's
|
|
24
|
+
* own P/M/K/N/S/H rating for each part, which fills `materialGroups` as
|
|
25
|
+
* `vendor-stated`.
|
|
26
|
+
* - **A description for every part.** The grouped product's `productListInfo`
|
|
27
|
+
* is one sentence of the vendor's own prose per product line, so every kind
|
|
28
|
+
* here fills `description` from `conventions.DESCRIPTION_COLUMN` — including
|
|
29
|
+
* the tap, where Kennametal has to fall back to the thread designation
|
|
30
|
+
* because it publishes no such text. EMUGE's designation is on the CSV as
|
|
31
|
+
* `dimensionFeatureValue` and its thread is on the record as `TP`.
|
|
32
|
+
*
|
|
33
|
+
* ## What it does not publish
|
|
34
|
+
*
|
|
35
|
+
* **No flute count on a drill or a tap.** Not on the grouped product, the
|
|
36
|
+
* variant listing, the per-part detail or any facet. A drill's comes from the
|
|
37
|
+
* family's `flutes` fact — all seventeen drill groups state
|
|
38
|
+
* `Specification: Twist drill` — and a tap's is simply absent, which is why
|
|
39
|
+
* `records.RECORD_GEOMETRY.tap` lists `NOF` under `sometimes`. EMUGE's own tap
|
|
40
|
+
* families run 2, 3 and 4 flutes across a size range, so no per-family constant
|
|
41
|
+
* could be true of every row, and 0 is not a substitute for a number nobody
|
|
42
|
+
* stated.
|
|
43
|
+
*
|
|
44
|
+
* **No CAD a scrape can reach.** The STEP, DXF and DIN 4000 documents are
|
|
45
|
+
* published behind a login — `anonymousAccess: false`, with both URL fields
|
|
46
|
+
* null — so neither `conventions.CAD_COLUMN` nor `CAD_DXF_COLUMN` is written.
|
|
47
|
+
* See `docs/EMUGE_FRANKEN_COMMERCE_API.md`.
|
|
48
|
+
*/
|
|
49
|
+
import { type BoundFamily, type RecordMappers } from '../../family.js';
|
|
50
|
+
import { type ColumnMap, type ToolRecord } from '../../records.js';
|
|
51
|
+
import { type MapperOptions, type ScrapedRow } from '../../scrape.js';
|
|
52
|
+
/** The cutting-material property, spelled the same way in all three categories. */
|
|
53
|
+
export declare const SUBSTRATE_COLUMN = "Cutting material";
|
|
54
|
+
/**
|
|
55
|
+
* The coating property, which is **not** spelled the same way.
|
|
56
|
+
*
|
|
57
|
+
* `coating` on a milling part and `Coating` on a drill or a tap, in the same
|
|
58
|
+
* API, on the same day. Both are read and the first non-empty one wins, because
|
|
59
|
+
* relabelling one onto the other in the CSV would be this adapter deciding what
|
|
60
|
+
* the vendor meant — see `scrape.ts`.
|
|
61
|
+
*/
|
|
62
|
+
export declare const COATING_COLUMNS: readonly ["coating", "Coating"];
|
|
63
|
+
/**
|
|
64
|
+
* Whether a part is through-coolant, by the vendor's own word for it.
|
|
65
|
+
*
|
|
66
|
+
* Three columns and three vocabularies, one per category, each a closed facet
|
|
67
|
+
* this package read off the vendor's own index on 2026-09-01 — so a value
|
|
68
|
+
* absent from here is a vocabulary that changed rather than a part that is
|
|
69
|
+
* odd, and {@link coolantThrough} refuses it by naming the table to add to.
|
|
70
|
+
*
|
|
71
|
+
* ## The facet does not cover the whole of milling
|
|
72
|
+
*
|
|
73
|
+
* The values below account for 6,862 milling variants, 2,670 drilling and
|
|
74
|
+
* 11,566 tapping. Drilling and tapping are the whole category — the same two
|
|
75
|
+
* numbers `docs/EMUGE_FRANKEN_COMMERCE_API.md` §4 gives — and milling is
|
|
76
|
+
* **159 short** of `FF01`'s 7,021, which is also `families/emuge.ts`'s two row
|
|
77
|
+
* counts added up. So 159 milling variants carry no `internal coolant supply`
|
|
78
|
+
* value at all.
|
|
79
|
+
*
|
|
80
|
+
* That is a gap in the vendor's index rather than a vocabulary that moved, and
|
|
81
|
+
* it is why {@link coolantThrough} separates the two cases: an unrecognised
|
|
82
|
+
* *value* still refuses, and a column nobody filled warns and records `false`.
|
|
83
|
+
* They were one case until 2026-09-01, and because `registry.toRecords` maps
|
|
84
|
+
* its rows, a single such part threw and took the whole family's conversion
|
|
85
|
+
* with it — 159 unindexed variants for two dead end mill CSVs.
|
|
86
|
+
*/
|
|
87
|
+
export declare const COOLANT_COLUMNS: Readonly<Record<string, Readonly<Record<string, boolean>>>>;
|
|
88
|
+
/**
|
|
89
|
+
* The vendor's cutting materials, onto the package's own vocabulary.
|
|
90
|
+
*
|
|
91
|
+
* `FamilyFacts.bmc` names a material class — `carbide`, `hss`, `diamond` — and
|
|
92
|
+
* EMUGE's index names an alloy and a production route: HSS, HSSE and HSSE-PM
|
|
93
|
+
* are three grades of high-speed steel and all three are `hss`. `PCD` is
|
|
94
|
+
* `diamond`, which is the call `families/kennametal.ts` already made for its
|
|
95
|
+
* PCD drills: the word names the cutting material rather than the body it is
|
|
96
|
+
* brazed to.
|
|
97
|
+
*
|
|
98
|
+
* `cbn` and `ceramic` keep the vendor's own name, lowercased, because the
|
|
99
|
+
* package's three words have no counterpart for either and inventing one would
|
|
100
|
+
* be worse than recording what is standard. Closed, and it throws on anything
|
|
101
|
+
* else — the seven values below are the whole of the vendor's `HYB_AAM_MAT`
|
|
102
|
+
* facet across all three categories (2026-09-01).
|
|
103
|
+
*/
|
|
104
|
+
export declare const SUBSTRATES: Readonly<Record<string, string>>;
|
|
105
|
+
/**
|
|
106
|
+
* The column each EMUGE-FRANKEN category states its product line in, keyed by
|
|
107
|
+
* the vendor's own category code.
|
|
108
|
+
*
|
|
109
|
+
* **One column per category, and each is a facet that partitions its category
|
|
110
|
+
* exactly.** Checked against the vendor's own index on 2026-09-01, at group
|
|
111
|
+
* level rather than by summing counts:
|
|
112
|
+
*
|
|
113
|
+
* | Category | Facet | Values | Groups it covers | Groups in two values |
|
|
114
|
+
* | -------- | -------------------- | ------ | ---------------- | -------------------- |
|
|
115
|
+
* | `FF01` | `AMM_PROG_LINIE` | 15 | 554 of 554 | 2 |
|
|
116
|
+
* | `FB01` | `HYB_BAM_SB_GT` | 4 | 17 of 17 | 0 |
|
|
117
|
+
* | `FG01` | `HYB_BAM_SB_GT` | 17 | 414 of 414 | 0 |
|
|
118
|
+
*
|
|
119
|
+
* That is what makes a product line here a **read and not an arbitration**.
|
|
120
|
+
* EMUGE's marketing publishes 43 overlapping product-family pages — a tap is
|
|
121
|
+
* simultaneously "Rekord B-Z Taps", "Enorm Z Taps" and "Left Hand Taps" — and
|
|
122
|
+
* choosing between those would be this package inventing a rule the vendor
|
|
123
|
+
* never stated. The facets above are the vendor's own partition of the same
|
|
124
|
+
* catalog, so there is nothing to choose.
|
|
125
|
+
*
|
|
126
|
+
* **The two milling groups that fall in two values cost nothing**, because the
|
|
127
|
+
* milling column is read per part: `H300024` and `H300025` each hold both
|
|
128
|
+
* `FRANKEN TiNox-Cut` and `FRANKEN TiNox-Cut VAR` variants, and the per-part
|
|
129
|
+
* detail record states which is which. A rule that tagged a whole group would
|
|
130
|
+
* have had to pick.
|
|
131
|
+
*
|
|
132
|
+
* **No request is added for any of this.** `Geometry` is on the grouped product
|
|
133
|
+
* and `product line` on the per-part detail, so `scrape.ts` already writes both
|
|
134
|
+
* into every row — see its three-call note.
|
|
135
|
+
*/
|
|
136
|
+
export declare const PRODUCT_LINE_COLUMNS: Readonly<Record<string, string>>;
|
|
137
|
+
/**
|
|
138
|
+
* A category's product-line codes onto the vendor's own name for each.
|
|
139
|
+
*
|
|
140
|
+
* **Both sides are EMUGE's.** The key is the value its `HYB_BAM_SB_GT` facet
|
|
141
|
+
* publishes and the name is the title of the vendor's own product-family
|
|
142
|
+
* article page for it, read on 2026-09-01 and cited per entry. Nothing here is
|
|
143
|
+
* this package's wording, which is the whole condition on a table like this —
|
|
144
|
+
* the same rule {@link SUBSTRATES} keeps, one level less strict because that
|
|
145
|
+
* one maps onto a vocabulary this package owns and this one does not.
|
|
146
|
+
*
|
|
147
|
+
* **A category whose facet already names the line has no entry.** `FF01`'s
|
|
148
|
+
* values are `FRANKEN TOP-Cut`, `FRANKEN Hard-Cut`, `FRANKEN Alu-Cut` — the
|
|
149
|
+
* vendor's marketing names already — so milling passes through verbatim and
|
|
150
|
+
* appears nowhere below. Drilling and tapping index by a geometry code
|
|
151
|
+
* (`MULTI`, `Z`, `VA`) that matches nothing EMUGE sells under that name, which
|
|
152
|
+
* is the only reason this table exists.
|
|
153
|
+
*
|
|
154
|
+
* **A code with no article page keeps the code**, and that is a gap in the
|
|
155
|
+
* vendor's marketing rather than a hole here: `SPEED`, `FK`, `GAL`, `GG` and
|
|
156
|
+
* `TILEG` are real lines with no `/a/` page on the US storefront, so the
|
|
157
|
+
* honest answer is the vendor's own code until one appears.
|
|
158
|
+
*/
|
|
159
|
+
export declare const PRODUCT_LINES: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
160
|
+
/**
|
|
161
|
+
* The product line this row states, or null where the vendor states none.
|
|
162
|
+
*
|
|
163
|
+
* Three answers and they are different things. A family whose category has no
|
|
164
|
+
* entry in {@link PRODUCT_LINE_COLUMNS} is a category this adapter has not
|
|
165
|
+
* looked at, and it answers null rather than guessing at which of the row's
|
|
166
|
+
* columns is the line. A column the vendor left empty is null too — the same
|
|
167
|
+
* silence `angle` treats as an omission rather than a fault. A *value* is
|
|
168
|
+
* mapped through {@link PRODUCT_LINES} where its category has a table and
|
|
169
|
+
* passed through verbatim where it does not, which is the milling case.
|
|
170
|
+
*
|
|
171
|
+
* It does not throw on an unknown code, and that is the difference between
|
|
172
|
+
* this and `substrate`: a cutting material this package cannot map would put a
|
|
173
|
+
* wrong word in `ToolRecord.substrate`, where an unmapped product-line code is
|
|
174
|
+
* the vendor's own code and readable as one. A new EMUGE geometry showing up
|
|
175
|
+
* as `SPEED` is a name to improve, not a record to refuse.
|
|
176
|
+
*/
|
|
177
|
+
export declare function productLine(row: ScrapedRow, family: BoundFamily): string | null;
|
|
178
|
+
/**
|
|
179
|
+
* The flute count EMUGE publishes where it has none: 64 end mill variants, on
|
|
180
|
+
* 2026-09-01. A sentinel and not a number, so it is refused rather than read.
|
|
181
|
+
*/
|
|
182
|
+
export declare const NO_FLUTE_COUNT = 999;
|
|
183
|
+
/**
|
|
184
|
+
* A solid end mill, in the family's declared unit.
|
|
185
|
+
*
|
|
186
|
+
* The two shoulder fields fall back the way every end mill mapper here does: a
|
|
187
|
+
* family with no neck column is a plain tool whose usable length below the
|
|
188
|
+
* shank is its flute length and whose shoulder is its cutting diameter. EMUGE
|
|
189
|
+
* publishes `neck length l₃` and `neck diameter Ød₃` on the necked lines and
|
|
190
|
+
* neither on the plain ones, in the same family, so the fallback is per row
|
|
191
|
+
* rather than per family.
|
|
192
|
+
*
|
|
193
|
+
* A missing `radius r₁` is a square end and 0 is the right answer — the reason
|
|
194
|
+
* `RE` is optional on the end mill contract.
|
|
195
|
+
*/
|
|
196
|
+
export declare function endmillRecord(row: ScrapedRow, family: BoundFamily, columns: ColumnMap, options?: MapperOptions): ToolRecord;
|
|
197
|
+
/**
|
|
198
|
+
* A twist drill, in millimetres — the only system EMUGE publishes its drill
|
|
199
|
+
* lengths in.
|
|
200
|
+
*
|
|
201
|
+
* `SIG` is a **mapped column**, which no other drill family in this package
|
|
202
|
+
* manages: the per-part detail record states the point angle outright, so
|
|
203
|
+
* nothing here is derived from a point length or assumed from a product line.
|
|
204
|
+
* It is also the one geometry key this record may not carry — one variant's
|
|
205
|
+
* cell is empty, and {@link angle} says what that costs. `NOF` is the one
|
|
206
|
+
* thing that is a fact, and `nonFerrous` with it — neither has a default
|
|
207
|
+
* anywhere, by design.
|
|
208
|
+
*/
|
|
209
|
+
export declare function drillRecord(row: ScrapedRow, family: BoundFamily, columns: ColumnMap, options?: MapperOptions): ToolRecord;
|
|
210
|
+
/**
|
|
211
|
+
* A tap, in millimetres — including an inch-thread one.
|
|
212
|
+
*
|
|
213
|
+
* That is what the vendor published rather than a conversion this package made:
|
|
214
|
+
* a `#4-40 UNC` tap's major diameter is stated as `2.845 mm` and its shank,
|
|
215
|
+
* overall length and cutting-edge length are millimetres too. So the family
|
|
216
|
+
* declares `unit: millimeters` and there is no per-row thread system to read —
|
|
217
|
+
* the shape Kennametal needs, where one family holds both and `Thread System`
|
|
218
|
+
* is a column the scrape tags on.
|
|
219
|
+
*
|
|
220
|
+
* `DC` is **read, not derived**. Kennametal parses a major diameter out of the
|
|
221
|
+
* thread designation because its tap tables publish no diameter column; EMUGE
|
|
222
|
+
* publishes one, and reading the vendor's own number is always the better of
|
|
223
|
+
* the two. The designation is still on the CSV as `dimensionFeatureValue`, and
|
|
224
|
+
* `thread symbol` and `threads per inch` beside it.
|
|
225
|
+
*
|
|
226
|
+
* `NOF` is absent, and its absence is the vendor's silence — see the module
|
|
227
|
+
* docstring and `records.RECORD_GEOMETRY`.
|
|
228
|
+
*/
|
|
229
|
+
export declare function tapRecord(row: ScrapedRow, family: BoundFamily, columns: ColumnMap, options?: MapperOptions): ToolRecord;
|
|
230
|
+
export declare const RECORD_MAPPERS: RecordMappers;
|