@toolpath/tool-scraper 0.1.0 → 2.0.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 +62 -0
- package/dist/columns.js +62 -0
- package/dist/conventions.d.ts +89 -15
- package/dist/conventions.js +108 -15
- package/dist/families/emuge.d.ts +183 -0
- package/dist/families/emuge.js +159 -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 +152 -2
- package/dist/node/main.js +0 -0
- package/dist/records.d.ts +169 -12
- package/dist/records.js +116 -5
- package/dist/registry.d.ts +31 -1
- package/dist/registry.js +40 -1
- 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 +41 -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 +150 -0
- package/dist/vendors/emuge/records.js +375 -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/records.d.ts +13 -4
- package/dist/vendors/kennametal/records.js +61 -26
- 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/README.md
CHANGED
|
@@ -17,26 +17,41 @@ pnpm add @toolpath/tool-scraper
|
|
|
17
17
|
|
|
18
18
|
## Vendors
|
|
19
19
|
|
|
20
|
-
| Vendor | Transport
|
|
21
|
-
| ------------------ |
|
|
22
|
-
| Kennametal / WIDIA | AEM variant-table GET, parsed with `htmlparser2`
|
|
23
|
-
| REGO-FIX | Elasticsearch proxy POST + per-part DIN 4000 XML
|
|
24
|
-
| Destiny Tool | Firestore REST, paginated
|
|
20
|
+
| Vendor | Transport | What it publishes |
|
|
21
|
+
| ------------------ | ------------------------------------------------------- | ------------------------------------ |
|
|
22
|
+
| Kennametal / WIDIA | AEM variant-table GET, parsed with `htmlparser2` | tools and toolholding |
|
|
23
|
+
| REGO-FIX | Elasticsearch proxy POST + per-part DIN 4000 XML | toolholding |
|
|
24
|
+
| Destiny Tool | Firestore REST, paginated | solid end mills |
|
|
25
|
+
| Harvey Tool | inline JS literal on a product page, plus its `<thead>` | miniature end mills, keyseat cutters |
|
|
26
|
+
| MariTool | osCommerce category listings, then one page per part | toolholding |
|
|
27
|
+
| EMUGE-FRANKEN | SAP Commerce JSON API: grouped, variant, batched detail | end mills, twist drills, taps |
|
|
25
28
|
|
|
26
29
|
## Two entry points
|
|
27
30
|
|
|
28
|
-
**`@toolpath/tool-scraper` returns records.** Every scrape hands back
|
|
29
|
-
say where they came from;
|
|
30
|
-
|
|
31
|
+
**`@toolpath/tool-scraper` returns records.** Every scrape hands back the vendor's own rows and
|
|
32
|
+
enough provenance to say where they came from; `toRecords` turns one family's scrape into
|
|
33
|
+
`ToolRecord[]` — canonical ISO 13399 geometry, one shape whatever the vendor. Nothing in either
|
|
34
|
+
touches the filesystem, so a backend can embed it and do what it likes with the result.
|
|
31
35
|
|
|
32
36
|
```ts
|
|
33
|
-
import { createFetcher } from '@toolpath/tool-scraper'
|
|
37
|
+
import { createFetcher, type ToolRecord } from '@toolpath/tool-scraper'
|
|
38
|
+
import { toRecords } from '@toolpath/tool-scraper/registry'
|
|
34
39
|
import { scrapeFamily } from '@toolpath/tool-scraper/vendors/kennametal'
|
|
35
40
|
|
|
36
41
|
const fetcher = createFetcher() // or your own: retries, proxy, rate limits
|
|
37
|
-
const
|
|
42
|
+
const scrape = await scrapeFamily(fetcher, '100003658')
|
|
43
|
+
|
|
44
|
+
const records: ToolRecord[] = toRecords('godrill_3xd_metric.csv', scrape)
|
|
45
|
+
// { brand: 'kennametal', guid: '…', geometry: { DC: 10, OAL: 89, … },
|
|
46
|
+
// materialGroups: ['P', 'N'], materialGroupsSource: 'vendor-stated', … }
|
|
38
47
|
```
|
|
39
48
|
|
|
49
|
+
`toRecords` is on the `./registry` subpath because it is the one place that knows both the family
|
|
50
|
+
table and the vendor adapters; the main entry point deliberately imports no vendor. It checks the
|
|
51
|
+
scrape's header for the identity and mapped columns before it maps a single row, so a re-scrape
|
|
52
|
+
whose part-number column was renamed fails by name instead of minting every guid off an empty
|
|
53
|
+
string.
|
|
54
|
+
|
|
40
55
|
The transport is a parameter, not a module global. Supply your own `Fetcher` and the vendor
|
|
41
56
|
adapters read through it — which is also how every test in this package runs without a network.
|
|
42
57
|
|
|
@@ -57,6 +72,11 @@ toolpath-scrape kennametal 100003658 "$TOOLPATH_SCRAPE_ROOT/kennametal/csv/godri
|
|
|
57
72
|
toolpath-scrape materials godrill_3xd_metric.csv
|
|
58
73
|
toolpath-scrape regofix holders "$TOOLPATH_SCRAPE_ROOT/regofix/csv/regofix_bt30_pg_holders.csv"
|
|
59
74
|
toolpath-scrape destinytool "$TOOLPATH_SCRAPE_ROOT/destinytool/csv/destinytool_end_mills_inch.csv"
|
|
75
|
+
toolpath-scrape harvey harvey_endmill_008.csv # the page and the unit come from its config
|
|
76
|
+
toolpath-scrape harvey --catalog # what the four category trees link to today
|
|
77
|
+
toolpath-scrape maritool maritool_cat40_holders.csv # its leaf categories come from its config
|
|
78
|
+
toolpath-scrape maritool --catalog # what the five taper trees hold today
|
|
79
|
+
toolpath-scrape emuge emuge_drills.csv # its category and unit come from its config
|
|
60
80
|
```
|
|
61
81
|
|
|
62
82
|
`toolpath-scrape --help` lists the rest.
|
|
@@ -71,6 +91,14 @@ definition and names the three that are Autodesk's rather than the standard's.
|
|
|
71
91
|
Vendor CSVs keep the **vendor's** own column labels. Nothing reads a vendor's CSV but that vendor's
|
|
72
92
|
adapter, and `conventions.ts` holds the short list of rules that do hold across all of them.
|
|
73
93
|
|
|
94
|
+
`materialGroups` has three states, and they are different claims: `null` labelled `unspecified` is
|
|
95
|
+
"we do not know what this tool is for" — not indexed, not published, or not swept — `[]` is a vendor
|
|
96
|
+
index that rates the part for nothing, and a non-empty list is a rating. `materialGroupsSource` is
|
|
97
|
+
never absent: it is `unspecified`, or it says whether the rating was `vendor-stated` or `derived`
|
|
98
|
+
here. Every Harvey record is `unspecified`: Harvey's material index is published per part rather
|
|
99
|
+
than in a variant table, and a scrape cannot reach it — see
|
|
100
|
+
[`docs/HARVEY_PRODUCT_TABLE.md`](docs/HARVEY_PRODUCT_TABLE.md) §1.5.1.
|
|
101
|
+
|
|
74
102
|
Every per-family constant no vendor table states carries its provenance — whether it was
|
|
75
103
|
vendor-stated, derived or assumed, and by whom on what date. The types enforce it: an assumed fact
|
|
76
104
|
without a note, a date and initials does not compile.
|
|
@@ -80,7 +108,9 @@ without a note, a date and initials does not compile.
|
|
|
80
108
|
- [`docs/ADDING-A-VENDOR.md`](docs/ADDING-A-VENDOR.md) — the runbook.
|
|
81
109
|
- [`docs/KENNAMETAL_CAD_API.md`](docs/KENNAMETAL_CAD_API.md),
|
|
82
110
|
[`docs/KENNAMETAL_SPEEDFEED_API.md`](docs/KENNAMETAL_SPEEDFEED_API.md),
|
|
83
|
-
[`docs/REGOFIX_PRODUCTFINDER_API.md`](docs/REGOFIX_PRODUCTFINDER_API.md)
|
|
111
|
+
[`docs/REGOFIX_PRODUCTFINDER_API.md`](docs/REGOFIX_PRODUCTFINDER_API.md),
|
|
112
|
+
[`docs/HARVEY_PRODUCT_TABLE.md`](docs/HARVEY_PRODUCT_TABLE.md),
|
|
113
|
+
[`docs/MARITOOL_CATALOG.md`](docs/MARITOOL_CATALOG.md) — how each endpoint or table was
|
|
84
114
|
found, and the dead ends tried first.
|
|
85
115
|
- [`../../docs/TOOL-SCRAPER-PLAN.md`](../../docs/TOOL-SCRAPER-PLAN.md) — the structure, the evidence
|
|
86
116
|
behind it, and what has landed.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading one canonical field out of a scraped row, for every vendor whose CSV
|
|
3
|
+
* holds display strings rather than numbers.
|
|
4
|
+
*
|
|
5
|
+
* Two vendors publish a receipt of the vendor's own text — Harvey Tool and
|
|
6
|
+
* EMUGE-FRANKEN — and a mapper for either one needs the same three steps
|
|
7
|
+
* between a `GeometryName` and a number: ask the family's `ColumnMap` which
|
|
8
|
+
* column that field is in, read the cell, and decide what an unreadable one
|
|
9
|
+
* costs. Those three were a verbatim copy in both mappers, down to the wording
|
|
10
|
+
* of the refusal, which is the same argument `measure.ts` makes one level down:
|
|
11
|
+
* two copies of four lines is cheap and two copies of the **decision** is not.
|
|
12
|
+
*
|
|
13
|
+
* ## What is shared and what is not
|
|
14
|
+
*
|
|
15
|
+
* The vendor supplies the grammar, as a {@link LengthReader} — Harvey's
|
|
16
|
+
* `dimension` reads `.250 (1/4)` and a `-` that means "does not apply", EMUGE's
|
|
17
|
+
* `measureIn` reads `1 1/2 "` and refuses a range. What this module owns is
|
|
18
|
+
* everything either side of that call:
|
|
19
|
+
*
|
|
20
|
+
* - a column the family maps to nothing is `undefined`, not an error — a family
|
|
21
|
+
* with no neck column is a plain tool, and that is the mapper's fallback to
|
|
22
|
+
* make;
|
|
23
|
+
* - a **required** field with no reading refuses the row, naming the canonical
|
|
24
|
+
* field and quoting the cell, because a tool with no cutting diameter is not
|
|
25
|
+
* a part;
|
|
26
|
+
* - an **optional** one answers null and lets the mapper decide.
|
|
27
|
+
*
|
|
28
|
+
* `vendors/destinytool/records.ts` keeps its own `required` and is not wired
|
|
29
|
+
* through here: it reads a dimension out of free text rather than out of a
|
|
30
|
+
* cell, throws `RangeError` from its own parser rather than answering null, and
|
|
31
|
+
* has no `optional` at all. Forcing it into this shape would change how it
|
|
32
|
+
* refuses, which is the one thing `measure.ts` says is a vendor's own call.
|
|
33
|
+
*/
|
|
34
|
+
import type { UnitSystem } from './conventions.js';
|
|
35
|
+
import type { ColumnMap, GeometryName } from './records.js';
|
|
36
|
+
import type { MapperOptions, ScrapedRow, Warn } from './scrape.js';
|
|
37
|
+
/**
|
|
38
|
+
* How one vendor turns its own display cell into a length in `unit`.
|
|
39
|
+
*
|
|
40
|
+
* The signature `vendors/harvey/value.ts`'s `dimension` and
|
|
41
|
+
* `vendors/emuge/value.ts`'s `measureIn` already have.
|
|
42
|
+
*/
|
|
43
|
+
export type LengthReader = (display: string, unit: UnitSystem, what: string, warn?: Warn) => number | null;
|
|
44
|
+
/** The three readers a display-string mapper needs, bound to one grammar. */
|
|
45
|
+
export interface ColumnReaders {
|
|
46
|
+
/** One canonical field's cell, or undefined where the family maps none. */
|
|
47
|
+
cell(row: ScrapedRow, columns: ColumnMap, canonical: GeometryName, unit: UnitSystem): string | undefined;
|
|
48
|
+
/** A dimension the kind requires, refusing a row the vendor left blank. */
|
|
49
|
+
required(row: ScrapedRow, columns: ColumnMap, canonical: GeometryName, unit: UnitSystem, what: string, options: MapperOptions): number;
|
|
50
|
+
/** A dimension the contract does not require. Null where there is none. */
|
|
51
|
+
optional(row: ScrapedRow, columns: ColumnMap, canonical: GeometryName, unit: UnitSystem, what: string, options: MapperOptions): number | null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The three readers, over one vendor's `read`.
|
|
55
|
+
*
|
|
56
|
+
* `columns` is the caller's map and not `family.columns`. They are the same
|
|
57
|
+
* object through `registry.toRecords` — but `RecordMapper` passes one as an
|
|
58
|
+
* argument, `registry` validates *that* one with `checkColumnsExist`, and a
|
|
59
|
+
* mapper reading a different reference is validating one map and reading
|
|
60
|
+
* another.
|
|
61
|
+
*/
|
|
62
|
+
export declare function columnReaders(read: LengthReader): ColumnReaders;
|
package/dist/columns.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading one canonical field out of a scraped row, for every vendor whose CSV
|
|
3
|
+
* holds display strings rather than numbers.
|
|
4
|
+
*
|
|
5
|
+
* Two vendors publish a receipt of the vendor's own text — Harvey Tool and
|
|
6
|
+
* EMUGE-FRANKEN — and a mapper for either one needs the same three steps
|
|
7
|
+
* between a `GeometryName` and a number: ask the family's `ColumnMap` which
|
|
8
|
+
* column that field is in, read the cell, and decide what an unreadable one
|
|
9
|
+
* costs. Those three were a verbatim copy in both mappers, down to the wording
|
|
10
|
+
* of the refusal, which is the same argument `measure.ts` makes one level down:
|
|
11
|
+
* two copies of four lines is cheap and two copies of the **decision** is not.
|
|
12
|
+
*
|
|
13
|
+
* ## What is shared and what is not
|
|
14
|
+
*
|
|
15
|
+
* The vendor supplies the grammar, as a {@link LengthReader} — Harvey's
|
|
16
|
+
* `dimension` reads `.250 (1/4)` and a `-` that means "does not apply", EMUGE's
|
|
17
|
+
* `measureIn` reads `1 1/2 "` and refuses a range. What this module owns is
|
|
18
|
+
* everything either side of that call:
|
|
19
|
+
*
|
|
20
|
+
* - a column the family maps to nothing is `undefined`, not an error — a family
|
|
21
|
+
* with no neck column is a plain tool, and that is the mapper's fallback to
|
|
22
|
+
* make;
|
|
23
|
+
* - a **required** field with no reading refuses the row, naming the canonical
|
|
24
|
+
* field and quoting the cell, because a tool with no cutting diameter is not
|
|
25
|
+
* a part;
|
|
26
|
+
* - an **optional** one answers null and lets the mapper decide.
|
|
27
|
+
*
|
|
28
|
+
* `vendors/destinytool/records.ts` keeps its own `required` and is not wired
|
|
29
|
+
* through here: it reads a dimension out of free text rather than out of a
|
|
30
|
+
* cell, throws `RangeError` from its own parser rather than answering null, and
|
|
31
|
+
* has no `optional` at all. Forcing it into this shape would change how it
|
|
32
|
+
* refuses, which is the one thing `measure.ts` says is a vendor's own call.
|
|
33
|
+
*/
|
|
34
|
+
import { VendorResponseError } from './errors.js';
|
|
35
|
+
/**
|
|
36
|
+
* The three readers, over one vendor's `read`.
|
|
37
|
+
*
|
|
38
|
+
* `columns` is the caller's map and not `family.columns`. They are the same
|
|
39
|
+
* object through `registry.toRecords` — but `RecordMapper` passes one as an
|
|
40
|
+
* argument, `registry` validates *that* one with `checkColumnsExist`, and a
|
|
41
|
+
* mapper reading a different reference is validating one map and reading
|
|
42
|
+
* another.
|
|
43
|
+
*/
|
|
44
|
+
export function columnReaders(read) {
|
|
45
|
+
const cell = (row, columns, canonical, unit) => {
|
|
46
|
+
const column = columns.column(canonical, unit);
|
|
47
|
+
return column === null ? undefined : row[column];
|
|
48
|
+
};
|
|
49
|
+
const optional = (row, columns, canonical, unit, what, options) => {
|
|
50
|
+
const raw = cell(row, columns, canonical, unit);
|
|
51
|
+
return raw === undefined ? null : read(raw, unit, what, options.warn);
|
|
52
|
+
};
|
|
53
|
+
const required = (row, columns, canonical, unit, what, options) => {
|
|
54
|
+
const raw = cell(row, columns, canonical, unit);
|
|
55
|
+
const value = raw === undefined ? null : read(raw, unit, what, options.warn);
|
|
56
|
+
if (value === null) {
|
|
57
|
+
throw new VendorResponseError(what, `publishes no ${canonical} — its cell is ${JSON.stringify(raw ?? '')}`);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
};
|
|
61
|
+
return { cell, required, optional };
|
|
62
|
+
}
|
package/dist/conventions.d.ts
CHANGED
|
@@ -12,14 +12,19 @@
|
|
|
12
12
|
* *across* the CSVs anyway, and the reason to make them explicit is that
|
|
13
13
|
* vendor #3 already drifted from one:
|
|
14
14
|
*
|
|
15
|
-
* | Convention
|
|
16
|
-
* |
|
|
17
|
-
* | `_mm`/`_in` carries the unit on a dimension
|
|
18
|
-
* | Multi-value cells are space-separated
|
|
19
|
-
* | One row per orderable part
|
|
20
|
-
* | `CAD_STEP_URL` names a CAD model where one exists
|
|
21
|
-
* |
|
|
22
|
-
* |
|
|
15
|
+
* | Convention | Held by |
|
|
16
|
+
* | -------------------------------------------------- | ------------------------- |
|
|
17
|
+
* | `_mm`/`_in` carries the unit on a dimension | all five |
|
|
18
|
+
* | Multi-value cells are space-separated | all five |
|
|
19
|
+
* | One row per orderable part | all five |
|
|
20
|
+
* | `CAD_STEP_URL` names a CAD model where one exists | Kennametal, REGO-FIX |
|
|
21
|
+
* | `CAD_DXF_URL` names a 2D profile where one exists | Harvey, MariTool |
|
|
22
|
+
* | `Description` carries the vendor's own free text | Harvey, MariTool |
|
|
23
|
+
* | `contact` says how a holder seats | REGO-FIX, MariTool |
|
|
24
|
+
* | `CST` names the collet series a holder takes | REGO-FIX, MariTool |
|
|
25
|
+
* | `L1_in`/`L1_mm` carry a holder's gage length | REGO-FIX, MariTool |
|
|
26
|
+
* | Unmapped vendor codes keep a `DIN_` prefix | REGO-FIX; rule is general |
|
|
27
|
+
* | The identity columns | **broken** — see below |
|
|
23
28
|
*
|
|
24
29
|
* Identity and units are the two worth enforcing; the rest are advisory, and
|
|
25
30
|
* are here so that "advisory" is a decision on the page rather than an
|
|
@@ -63,6 +68,69 @@ export declare const UNIT_SUFFIX: Record<UnitSystem, string>;
|
|
|
63
68
|
* neither owns it — the leak the vendor-boundary test exists to catch.
|
|
64
69
|
*/
|
|
65
70
|
export declare const CAD_COLUMN = "CAD_STEP_URL";
|
|
71
|
+
/**
|
|
72
|
+
* The CSV column holding a part's downloadable 2D DXF profile.
|
|
73
|
+
*
|
|
74
|
+
* A second column rather than a second thing written into {@link CAD_COLUMN},
|
|
75
|
+
* because a DXF is not a STEP model: one is a flat profile a machinist prints
|
|
76
|
+
* or traces, the other is the solid a CAM system imports. Harvey Tool publishes
|
|
77
|
+
* a DXF for 12,773 of its 12,799 parts and a STEP for none of them, so writing
|
|
78
|
+
* its link into `CAD_STEP_URL` would repeat exactly the mistake the
|
|
79
|
+
* `CAD_STP_LWM` -> `CAD_STEP_URL` rename fixed on 2026-08-08 — a column name
|
|
80
|
+
* that is a claim about the data, and false.
|
|
81
|
+
*
|
|
82
|
+
* Vendor-neutral and beside `CAD_COLUMN` for the same reason that one is: a
|
|
83
|
+
* format is not one manufacturer's fact, and the second vendor to publish a DXF
|
|
84
|
+
* must write it into this column rather than inventing another.
|
|
85
|
+
*/
|
|
86
|
+
export declare const CAD_DXF_COLUMN = "CAD_DXF_URL";
|
|
87
|
+
/**
|
|
88
|
+
* The CSV column holding the vendor's own free text about one part.
|
|
89
|
+
*
|
|
90
|
+
* Harvey Tool writes a product title here and MariTool a product name; both are
|
|
91
|
+
* the vendor's own prose rather than a designation this package composed. It is
|
|
92
|
+
* here and not in either adapter for the reason {@link CAD_COLUMN} is: two of
|
|
93
|
+
* them write it and neither owns it.
|
|
94
|
+
*
|
|
95
|
+
* **Not every vendor publishes one, and `''` is the answer where none does** —
|
|
96
|
+
* a description that restates the catalog number is not a description. See
|
|
97
|
+
* `records.ToolRecord.description`, which states the same rule for the record.
|
|
98
|
+
*/
|
|
99
|
+
export declare const DESCRIPTION_COLUMN = "Description";
|
|
100
|
+
/**
|
|
101
|
+
* The CSV column saying how a holder seats in the spindle: `taper` or `face`.
|
|
102
|
+
*
|
|
103
|
+
* `face` is dual contact — the flange face seats at the same time as the cone.
|
|
104
|
+
* REGO-FIX resolves it from its own `form_name` and MariTool from its `Taper`
|
|
105
|
+
* cell, which is the right shape: **how** a vendor states it is that vendor's
|
|
106
|
+
* business, and what the column is called is not.
|
|
107
|
+
*/
|
|
108
|
+
export declare const CONTACT_COLUMN = "contact";
|
|
109
|
+
/**
|
|
110
|
+
* The CSV column naming the collet series a holder accepts — `ER16`, `PG25`.
|
|
111
|
+
*
|
|
112
|
+
* The join key between a holder family and a collet family, which is exactly
|
|
113
|
+
* why it cannot be spelled twice: `families/kennametal.ts` states the join
|
|
114
|
+
* against this column, and two spellings of it join to nothing. Both vendors
|
|
115
|
+
* that publish it close the vendor's own spacing before writing it here, for
|
|
116
|
+
* the same reason.
|
|
117
|
+
*/
|
|
118
|
+
export declare const COLLET_SERIES_COLUMN = "CST";
|
|
119
|
+
/**
|
|
120
|
+
* The CSV columns carrying a holder's gage length, one per unit system.
|
|
121
|
+
*
|
|
122
|
+
* A **pair** with exactly one cell filled, rather than one column and a unit
|
|
123
|
+
* tag, because a single catalog page can publish both: MariTool gages
|
|
124
|
+
* `HSK40E-ER11-40` in millimetres and `HSK40E-ER16-3.0M` in inches on one
|
|
125
|
+
* listing. Nothing is converted between them — the vendor's own imperial
|
|
126
|
+
* conversion is unusable and computing one here would put a number in the file
|
|
127
|
+
* the vendor never published.
|
|
128
|
+
*
|
|
129
|
+
* REGO-FIX fills only the millimetre cell, because its DIN 4000 documents are
|
|
130
|
+
* metric throughout; the pair is still the shape, so the two vendors' holder
|
|
131
|
+
* CSVs answer the same question with the same columns.
|
|
132
|
+
*/
|
|
133
|
+
export declare const GAGE_COLUMNS: Record<UnitSystem, string>;
|
|
66
134
|
/**
|
|
67
135
|
* The prefix an unmapped vendor code keeps, so it cannot read as a dimension.
|
|
68
136
|
*
|
|
@@ -85,13 +153,19 @@ export declare const IDENTITY_COLUMNS: readonly ["Material Number", "ISO Catalog
|
|
|
85
153
|
* Where a vendor's CSV does not use {@link IDENTITY_COLUMNS}, and what it uses
|
|
86
154
|
* instead.
|
|
87
155
|
*
|
|
88
|
-
* **
|
|
89
|
-
* adopted Kennametal's identity labels; Destiny Tool passes
|
|
90
|
-
* `itemNumber` straight through and publishes no catalog
|
|
91
|
-
* the convention was real but informal, and it eroded the
|
|
92
|
-
* did not resemble the first two. Writing the deviation
|
|
93
|
-
*
|
|
94
|
-
* happened.
|
|
156
|
+
* **Three entries, and they are a record of drift rather than a licence.**
|
|
157
|
+
* REGO-FIX adopted Kennametal's identity labels; Destiny Tool passes
|
|
158
|
+
* Firestore's own `itemNumber` straight through and publishes no catalog
|
|
159
|
+
* designation at all — the convention was real but informal, and it eroded the
|
|
160
|
+
* first time a vendor did not resemble the first two. Writing the deviation
|
|
161
|
+
* down is what makes the next vendor's drift a decision somebody made rather
|
|
162
|
+
* than a thing that happened.
|
|
163
|
+
*
|
|
164
|
+
* Harvey Tool and MariTool are the honest kind, and they are now the majority:
|
|
165
|
+
* both genuinely publish one identifier per part, so their entries record a
|
|
166
|
+
* fact about the vendor rather than a shortcut taken here. Two of the two
|
|
167
|
+
* vendors added since the convention was written have needed one, which says
|
|
168
|
+
* the two-column shape is Kennametal's rather than the industry's.
|
|
95
169
|
*/
|
|
96
170
|
export declare const IDENTITY_DEVIATIONS: Partial<Record<BrandName, readonly string[]>>;
|
|
97
171
|
/**
|
package/dist/conventions.js
CHANGED
|
@@ -12,14 +12,19 @@
|
|
|
12
12
|
* *across* the CSVs anyway, and the reason to make them explicit is that
|
|
13
13
|
* vendor #3 already drifted from one:
|
|
14
14
|
*
|
|
15
|
-
* | Convention
|
|
16
|
-
* |
|
|
17
|
-
* | `_mm`/`_in` carries the unit on a dimension
|
|
18
|
-
* | Multi-value cells are space-separated
|
|
19
|
-
* | One row per orderable part
|
|
20
|
-
* | `CAD_STEP_URL` names a CAD model where one exists
|
|
21
|
-
* |
|
|
22
|
-
* |
|
|
15
|
+
* | Convention | Held by |
|
|
16
|
+
* | -------------------------------------------------- | ------------------------- |
|
|
17
|
+
* | `_mm`/`_in` carries the unit on a dimension | all five |
|
|
18
|
+
* | Multi-value cells are space-separated | all five |
|
|
19
|
+
* | One row per orderable part | all five |
|
|
20
|
+
* | `CAD_STEP_URL` names a CAD model where one exists | Kennametal, REGO-FIX |
|
|
21
|
+
* | `CAD_DXF_URL` names a 2D profile where one exists | Harvey, MariTool |
|
|
22
|
+
* | `Description` carries the vendor's own free text | Harvey, MariTool |
|
|
23
|
+
* | `contact` says how a holder seats | REGO-FIX, MariTool |
|
|
24
|
+
* | `CST` names the collet series a holder takes | REGO-FIX, MariTool |
|
|
25
|
+
* | `L1_in`/`L1_mm` carry a holder's gage length | REGO-FIX, MariTool |
|
|
26
|
+
* | Unmapped vendor codes keep a `DIN_` prefix | REGO-FIX; rule is general |
|
|
27
|
+
* | The identity columns | **broken** — see below |
|
|
23
28
|
*
|
|
24
29
|
* Identity and units are the two worth enforcing; the rest are advisory, and
|
|
25
30
|
* are here so that "advisory" is a decision on the page rather than an
|
|
@@ -64,6 +69,72 @@ export const UNIT_SUFFIX = {
|
|
|
64
69
|
* neither owns it — the leak the vendor-boundary test exists to catch.
|
|
65
70
|
*/
|
|
66
71
|
export const CAD_COLUMN = 'CAD_STEP_URL';
|
|
72
|
+
/**
|
|
73
|
+
* The CSV column holding a part's downloadable 2D DXF profile.
|
|
74
|
+
*
|
|
75
|
+
* A second column rather than a second thing written into {@link CAD_COLUMN},
|
|
76
|
+
* because a DXF is not a STEP model: one is a flat profile a machinist prints
|
|
77
|
+
* or traces, the other is the solid a CAM system imports. Harvey Tool publishes
|
|
78
|
+
* a DXF for 12,773 of its 12,799 parts and a STEP for none of them, so writing
|
|
79
|
+
* its link into `CAD_STEP_URL` would repeat exactly the mistake the
|
|
80
|
+
* `CAD_STP_LWM` -> `CAD_STEP_URL` rename fixed on 2026-08-08 — a column name
|
|
81
|
+
* that is a claim about the data, and false.
|
|
82
|
+
*
|
|
83
|
+
* Vendor-neutral and beside `CAD_COLUMN` for the same reason that one is: a
|
|
84
|
+
* format is not one manufacturer's fact, and the second vendor to publish a DXF
|
|
85
|
+
* must write it into this column rather than inventing another.
|
|
86
|
+
*/
|
|
87
|
+
export const CAD_DXF_COLUMN = 'CAD_DXF_URL';
|
|
88
|
+
/**
|
|
89
|
+
* The CSV column holding the vendor's own free text about one part.
|
|
90
|
+
*
|
|
91
|
+
* Harvey Tool writes a product title here and MariTool a product name; both are
|
|
92
|
+
* the vendor's own prose rather than a designation this package composed. It is
|
|
93
|
+
* here and not in either adapter for the reason {@link CAD_COLUMN} is: two of
|
|
94
|
+
* them write it and neither owns it.
|
|
95
|
+
*
|
|
96
|
+
* **Not every vendor publishes one, and `''` is the answer where none does** —
|
|
97
|
+
* a description that restates the catalog number is not a description. See
|
|
98
|
+
* `records.ToolRecord.description`, which states the same rule for the record.
|
|
99
|
+
*/
|
|
100
|
+
export const DESCRIPTION_COLUMN = 'Description';
|
|
101
|
+
/**
|
|
102
|
+
* The CSV column saying how a holder seats in the spindle: `taper` or `face`.
|
|
103
|
+
*
|
|
104
|
+
* `face` is dual contact — the flange face seats at the same time as the cone.
|
|
105
|
+
* REGO-FIX resolves it from its own `form_name` and MariTool from its `Taper`
|
|
106
|
+
* cell, which is the right shape: **how** a vendor states it is that vendor's
|
|
107
|
+
* business, and what the column is called is not.
|
|
108
|
+
*/
|
|
109
|
+
export const CONTACT_COLUMN = 'contact';
|
|
110
|
+
/**
|
|
111
|
+
* The CSV column naming the collet series a holder accepts — `ER16`, `PG25`.
|
|
112
|
+
*
|
|
113
|
+
* The join key between a holder family and a collet family, which is exactly
|
|
114
|
+
* why it cannot be spelled twice: `families/kennametal.ts` states the join
|
|
115
|
+
* against this column, and two spellings of it join to nothing. Both vendors
|
|
116
|
+
* that publish it close the vendor's own spacing before writing it here, for
|
|
117
|
+
* the same reason.
|
|
118
|
+
*/
|
|
119
|
+
export const COLLET_SERIES_COLUMN = 'CST';
|
|
120
|
+
/**
|
|
121
|
+
* The CSV columns carrying a holder's gage length, one per unit system.
|
|
122
|
+
*
|
|
123
|
+
* A **pair** with exactly one cell filled, rather than one column and a unit
|
|
124
|
+
* tag, because a single catalog page can publish both: MariTool gages
|
|
125
|
+
* `HSK40E-ER11-40` in millimetres and `HSK40E-ER16-3.0M` in inches on one
|
|
126
|
+
* listing. Nothing is converted between them — the vendor's own imperial
|
|
127
|
+
* conversion is unusable and computing one here would put a number in the file
|
|
128
|
+
* the vendor never published.
|
|
129
|
+
*
|
|
130
|
+
* REGO-FIX fills only the millimetre cell, because its DIN 4000 documents are
|
|
131
|
+
* metric throughout; the pair is still the shape, so the two vendors' holder
|
|
132
|
+
* CSVs answer the same question with the same columns.
|
|
133
|
+
*/
|
|
134
|
+
export const GAGE_COLUMNS = {
|
|
135
|
+
inches: 'L1_in',
|
|
136
|
+
millimeters: 'L1_mm',
|
|
137
|
+
};
|
|
67
138
|
/**
|
|
68
139
|
* The prefix an unmapped vendor code keeps, so it cannot read as a dimension.
|
|
69
140
|
*
|
|
@@ -86,16 +157,38 @@ export const IDENTITY_COLUMNS = ['Material Number', 'ISO Catalog Number'];
|
|
|
86
157
|
* Where a vendor's CSV does not use {@link IDENTITY_COLUMNS}, and what it uses
|
|
87
158
|
* instead.
|
|
88
159
|
*
|
|
89
|
-
* **
|
|
90
|
-
* adopted Kennametal's identity labels; Destiny Tool passes
|
|
91
|
-
* `itemNumber` straight through and publishes no catalog
|
|
92
|
-
* the convention was real but informal, and it eroded the
|
|
93
|
-
* did not resemble the first two. Writing the deviation
|
|
94
|
-
*
|
|
95
|
-
* happened.
|
|
160
|
+
* **Three entries, and they are a record of drift rather than a licence.**
|
|
161
|
+
* REGO-FIX adopted Kennametal's identity labels; Destiny Tool passes
|
|
162
|
+
* Firestore's own `itemNumber` straight through and publishes no catalog
|
|
163
|
+
* designation at all — the convention was real but informal, and it eroded the
|
|
164
|
+
* first time a vendor did not resemble the first two. Writing the deviation
|
|
165
|
+
* down is what makes the next vendor's drift a decision somebody made rather
|
|
166
|
+
* than a thing that happened.
|
|
167
|
+
*
|
|
168
|
+
* Harvey Tool and MariTool are the honest kind, and they are now the majority:
|
|
169
|
+
* both genuinely publish one identifier per part, so their entries record a
|
|
170
|
+
* fact about the vendor rather than a shortcut taken here. Two of the two
|
|
171
|
+
* vendors added since the convention was written have needed one, which says
|
|
172
|
+
* the two-column shape is Kennametal's rather than the industry's.
|
|
96
173
|
*/
|
|
97
174
|
export const IDENTITY_DEVIATIONS = {
|
|
98
175
|
destinytool: ['itemNumber'],
|
|
176
|
+
// Harvey Tool publishes exactly one number per part — the `Tool #` its own
|
|
177
|
+
// table column is headed with, which is also the segment of its per-part URL
|
|
178
|
+
// — and no second catalog designation anywhere on a product page or a part
|
|
179
|
+
// page. Inventing an `ISO Catalog Number` to satisfy the convention would put
|
|
180
|
+
// a column in the CSV that the vendor does not publish, which is the one
|
|
181
|
+
// thing a receipt must not do.
|
|
182
|
+
harvey: ['Tool #'],
|
|
183
|
+
// MariTool publishes one number per part — the `Part#` line its own listing
|
|
184
|
+
// rows are headed with, which is also what its `Available Downloads for …`
|
|
185
|
+
// header restates and what its search endpoint matches on — and no second
|
|
186
|
+
// catalog designation anywhere on a category page or a product page. The
|
|
187
|
+
// store's `products_id` is not that second number: it is an internal id a
|
|
188
|
+
// re-created product would change, and every guid minted off it with it.
|
|
189
|
+
// Same call as Harvey's, and the honest kind: the entry records a fact about
|
|
190
|
+
// the vendor rather than a shortcut taken here.
|
|
191
|
+
maritool: ['Material Number'],
|
|
99
192
|
};
|
|
100
193
|
/**
|
|
101
194
|
* A vendor's bare column label, suffixed for `unit`.
|