@toolpath/tool-scraper 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/columns.d.ts +5 -3
  2. package/dist/columns.js +11 -5
  3. package/dist/conventions.d.ts +54 -0
  4. package/dist/conventions.js +59 -0
  5. package/dist/errors.d.ts +30 -0
  6. package/dist/errors.js +30 -0
  7. package/dist/families/emuge.d.ts +3 -1
  8. package/dist/families/emuge.js +8 -4
  9. package/dist/family.d.ts +16 -2
  10. package/dist/holding.d.ts +396 -0
  11. package/dist/holding.js +360 -0
  12. package/dist/index.d.ts +23 -13
  13. package/dist/index.js +23 -13
  14. package/dist/node/cad-mirror.d.ts +58 -1
  15. package/dist/node/cad-mirror.js +56 -8
  16. package/dist/node/cli.d.ts +4 -1
  17. package/dist/node/cli.js +198 -19
  18. package/dist/node/holder-import.d.ts +223 -0
  19. package/dist/node/holder-import.js +379 -0
  20. package/dist/node/index.d.ts +1 -0
  21. package/dist/node/index.js +1 -0
  22. package/dist/node/paths.d.ts +16 -0
  23. package/dist/node/paths.js +20 -0
  24. package/dist/profiles.d.ts +265 -0
  25. package/dist/profiles.js +295 -0
  26. package/dist/records.d.ts +57 -12
  27. package/dist/records.js +37 -13
  28. package/dist/registry.d.ts +86 -5
  29. package/dist/registry.js +149 -8
  30. package/dist/vendors/destinytool/records.js +8 -0
  31. package/dist/vendors/emuge/records.d.ts +83 -3
  32. package/dist/vendors/emuge/records.js +184 -16
  33. package/dist/vendors/kennametal/family.d.ts +119 -0
  34. package/dist/vendors/kennametal/family.js +155 -0
  35. package/dist/vendors/kennametal/holding.d.ts +35 -0
  36. package/dist/vendors/kennametal/holding.js +112 -0
  37. package/dist/vendors/kennametal/index.d.ts +2 -0
  38. package/dist/vendors/kennametal/index.js +2 -0
  39. package/dist/vendors/kennametal/records.d.ts +16 -0
  40. package/dist/vendors/kennametal/records.js +32 -0
  41. package/dist/vendors/kennametal/scrape.d.ts +25 -2
  42. package/dist/vendors/kennametal/scrape.js +28 -3
  43. package/dist/vendors/maritool/holding.d.ts +79 -0
  44. package/dist/vendors/maritool/holding.js +164 -0
  45. package/dist/vendors/maritool/index.d.ts +1 -0
  46. package/dist/vendors/maritool/index.js +1 -0
  47. package/dist/vendors/maritool/scrape.d.ts +37 -13
  48. package/dist/vendors/maritool/scrape.js +53 -14
  49. package/dist/vendors/regofix/holding.d.ts +35 -0
  50. package/dist/vendors/regofix/holding.js +108 -0
  51. package/dist/vendors/regofix/index.d.ts +1 -0
  52. package/dist/vendors/regofix/index.js +1 -0
  53. package/dist/vendors/regofix/scrape.js +2 -2
  54. package/package.json +1 -1
package/dist/columns.d.ts CHANGED
@@ -20,9 +20,11 @@
20
20
  * - a column the family maps to nothing is `undefined`, not an error — a family
21
21
  * with no neck column is a plain tool, and that is the mapper's fallback to
22
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;
23
+ * - a **required** field with no reading refuses the row with an
24
+ * `errors.IncompletePartError`, naming the canonical field and quoting the
25
+ * cell, because a tool with no cutting diameter is not a part — and that is
26
+ * the one refusal `registry.toRecords` skips past rather than failing the
27
+ * whole family on;
26
28
  * - an **optional** one answers null and lets the mapper decide.
27
29
  *
28
30
  * `vendors/destinytool/records.ts` keeps its own `required` and is not wired
package/dist/columns.js CHANGED
@@ -20,9 +20,11 @@
20
20
  * - a column the family maps to nothing is `undefined`, not an error — a family
21
21
  * with no neck column is a plain tool, and that is the mapper's fallback to
22
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;
23
+ * - a **required** field with no reading refuses the row with an
24
+ * `errors.IncompletePartError`, naming the canonical field and quoting the
25
+ * cell, because a tool with no cutting diameter is not a part — and that is
26
+ * the one refusal `registry.toRecords` skips past rather than failing the
27
+ * whole family on;
26
28
  * - an **optional** one answers null and lets the mapper decide.
27
29
  *
28
30
  * `vendors/destinytool/records.ts` keeps its own `required` and is not wired
@@ -31,7 +33,7 @@
31
33
  * has no `optional` at all. Forcing it into this shape would change how it
32
34
  * refuses, which is the one thing `measure.ts` says is a vendor's own call.
33
35
  */
34
- import { VendorResponseError } from './errors.js';
36
+ import { IncompletePartError } from './errors.js';
35
37
  /**
36
38
  * The three readers, over one vendor's `read`.
37
39
  *
@@ -54,7 +56,11 @@ export function columnReaders(read) {
54
56
  const raw = cell(row, columns, canonical, unit);
55
57
  const value = raw === undefined ? null : read(raw, unit, what, options.warn);
56
58
  if (value === null) {
57
- throw new VendorResponseError(what, `publishes no ${canonical} its cell is ${JSON.stringify(raw ?? '')}`);
59
+ // `IncompletePartError` and not the general vendor fault: this is the
60
+ // one refusal `registry.toRecords` skips past, because a single part the
61
+ // vendor left a cell blank on must not end a family's conversion. See
62
+ // that type for why the others still must not be skipped.
63
+ throw new IncompletePartError(what, `publishes no ${canonical} — its cell is ${JSON.stringify(raw ?? '')}`);
58
64
  }
59
65
  return value;
60
66
  };
@@ -22,6 +22,7 @@
22
22
  * | `Description` carries the vendor's own free text | Harvey, MariTool |
23
23
  * | `contact` says how a holder seats | REGO-FIX, MariTool |
24
24
  * | `CST` names the collet series a holder takes | REGO-FIX, MariTool |
25
+ * | `Collet Series` names the series a collet is | Kennametal, REGO-FIX |
25
26
  * | `L1_in`/`L1_mm` carry a holder's gage length | REGO-FIX, MariTool |
26
27
  * | Unmapped vendor codes keep a `DIN_` prefix | REGO-FIX; rule is general |
27
28
  * | The identity columns | **broken** — see below |
@@ -97,6 +98,24 @@ export declare const CAD_DXF_COLUMN = "CAD_DXF_URL";
97
98
  * `records.ToolRecord.description`, which states the same rule for the record.
98
99
  */
99
100
  export declare const DESCRIPTION_COLUMN = "Description";
101
+ /**
102
+ * The CSV column holding the vendor's own full name for the family a part is
103
+ * in — `KenCut™ FF • HPFT • Square End • 6 Flutes • Plain Shank • Inch`.
104
+ *
105
+ * Constant down a family's whole table, and that is what makes it a *family*
106
+ * title rather than a {@link DESCRIPTION_COLUMN}: it names the group, not the
107
+ * part. Kennametal and WIDIA publish one as the `h1` of a family page and it
108
+ * reaches the CSV whole, while `records.ToolRecord.productLine` keeps only its
109
+ * leading segment — the rest is the vendor's own wording for the shape, the
110
+ * flute count, the shank and the unit, and a receipt that dropped it would be
111
+ * throwing away published text to save a column.
112
+ *
113
+ * Vendor-neutral and here rather than in the Kennametal adapter for the reason
114
+ * {@link CAD_COLUMN} is: a second vendor that publishes a family title writes
115
+ * this column rather than inventing another. Nothing forces one to — a vendor
116
+ * whose parts carry no family title simply has no such column.
117
+ */
118
+ export declare const FAMILY_TITLE_COLUMN = "Family Title";
100
119
  /**
101
120
  * The CSV column saying how a holder seats in the spindle: `taper` or `face`.
102
121
  *
@@ -116,6 +135,21 @@ export declare const CONTACT_COLUMN = "contact";
116
135
  * the same reason.
117
136
  */
118
137
  export declare const COLLET_SERIES_COLUMN = "CST";
138
+ /**
139
+ * The CSV column naming the series a collet **is** — `ER16`, `PG25`, `PGST15`.
140
+ *
141
+ * The other half of {@link COLLET_SERIES_COLUMN}, and deliberately a second
142
+ * column rather than the same one: `CST` is the series a *holder takes* and
143
+ * this is the series a *collet belongs to*, so the join between a holder family
144
+ * and a collet family is a comparison of the two. One column carrying both
145
+ * would make that join a row's comparison with itself.
146
+ *
147
+ * Vendor-neutral and here rather than in either adapter for the reason
148
+ * {@link CAD_COLUMN} is: Kennametal's ER collet tables and REGO-FIX's PG collet
149
+ * index both write it, and neither owns it. Both close the vendor's own spacing
150
+ * before writing, exactly as they do for `CST` — two spellings join to nothing.
151
+ */
152
+ export declare const COLLET_DESIGNATION_COLUMN = "Collet Series";
119
153
  /**
120
154
  * The CSV columns carrying a holder's gage length, one per unit system.
121
155
  *
@@ -188,6 +222,26 @@ export declare function dimensionalColumn(label: string, unit: string): string;
188
222
  * indistinguishable from a scrape that lost it.
189
223
  */
190
224
  export declare function identityColumns(brand: BrandName): readonly string[];
225
+ /**
226
+ * The identity column a human reads one part by.
227
+ *
228
+ * The catalog designation where the vendor publishes one, and the sole
229
+ * identifier where it does not — the **last** of {@link identityColumns},
230
+ * because that is the order the pair is written in and a deviation lists the
231
+ * one column its vendor has.
232
+ *
233
+ * A lookup rather than a read of the row, for the reason {@link identityColumns}
234
+ * gives: a header that is missing `ISO Catalog Number` is indistinguishable
235
+ * from a scrape that lost it, so a caller that fell back column by column would
236
+ * name half a family's files after part numbers and never say why.
237
+ *
238
+ * It exists because `node/cad-mirror.ts` names a mirrored file after the part
239
+ * it holds, and hardcoded `'ISO Catalog Number'` to do it. That is Kennametal's
240
+ * pair; MariTool publishes one number per part under `Material Number` and no
241
+ * catalog designation at all, so every one of its 357 STEP models was skipped
242
+ * with a warning that the row had no catalog number to name it.
243
+ */
244
+ export declare function catalogColumn(brand: BrandName): string;
191
245
  /**
192
246
  * Every identity column `brand` claims is really in the CSV header.
193
247
  *
@@ -22,6 +22,7 @@
22
22
  * | `Description` carries the vendor's own free text | Harvey, MariTool |
23
23
  * | `contact` says how a holder seats | REGO-FIX, MariTool |
24
24
  * | `CST` names the collet series a holder takes | REGO-FIX, MariTool |
25
+ * | `Collet Series` names the series a collet is | Kennametal, REGO-FIX |
25
26
  * | `L1_in`/`L1_mm` carry a holder's gage length | REGO-FIX, MariTool |
26
27
  * | Unmapped vendor codes keep a `DIN_` prefix | REGO-FIX; rule is general |
27
28
  * | The identity columns | **broken** — see below |
@@ -98,6 +99,24 @@ export const CAD_DXF_COLUMN = 'CAD_DXF_URL';
98
99
  * `records.ToolRecord.description`, which states the same rule for the record.
99
100
  */
100
101
  export const DESCRIPTION_COLUMN = 'Description';
102
+ /**
103
+ * The CSV column holding the vendor's own full name for the family a part is
104
+ * in — `KenCut™ FF • HPFT • Square End • 6 Flutes • Plain Shank • Inch`.
105
+ *
106
+ * Constant down a family's whole table, and that is what makes it a *family*
107
+ * title rather than a {@link DESCRIPTION_COLUMN}: it names the group, not the
108
+ * part. Kennametal and WIDIA publish one as the `h1` of a family page and it
109
+ * reaches the CSV whole, while `records.ToolRecord.productLine` keeps only its
110
+ * leading segment — the rest is the vendor's own wording for the shape, the
111
+ * flute count, the shank and the unit, and a receipt that dropped it would be
112
+ * throwing away published text to save a column.
113
+ *
114
+ * Vendor-neutral and here rather than in the Kennametal adapter for the reason
115
+ * {@link CAD_COLUMN} is: a second vendor that publishes a family title writes
116
+ * this column rather than inventing another. Nothing forces one to — a vendor
117
+ * whose parts carry no family title simply has no such column.
118
+ */
119
+ export const FAMILY_TITLE_COLUMN = 'Family Title';
101
120
  /**
102
121
  * The CSV column saying how a holder seats in the spindle: `taper` or `face`.
103
122
  *
@@ -117,6 +136,21 @@ export const CONTACT_COLUMN = 'contact';
117
136
  * the same reason.
118
137
  */
119
138
  export const COLLET_SERIES_COLUMN = 'CST';
139
+ /**
140
+ * The CSV column naming the series a collet **is** — `ER16`, `PG25`, `PGST15`.
141
+ *
142
+ * The other half of {@link COLLET_SERIES_COLUMN}, and deliberately a second
143
+ * column rather than the same one: `CST` is the series a *holder takes* and
144
+ * this is the series a *collet belongs to*, so the join between a holder family
145
+ * and a collet family is a comparison of the two. One column carrying both
146
+ * would make that join a row's comparison with itself.
147
+ *
148
+ * Vendor-neutral and here rather than in either adapter for the reason
149
+ * {@link CAD_COLUMN} is: Kennametal's ER collet tables and REGO-FIX's PG collet
150
+ * index both write it, and neither owns it. Both close the vendor's own spacing
151
+ * before writing, exactly as they do for `CST` — two spellings join to nothing.
152
+ */
153
+ export const COLLET_DESIGNATION_COLUMN = 'Collet Series';
120
154
  /**
121
155
  * The CSV columns carrying a holder's gage length, one per unit system.
122
156
  *
@@ -218,6 +252,31 @@ export function dimensionalColumn(label, unit) {
218
252
  export function identityColumns(brand) {
219
253
  return IDENTITY_DEVIATIONS[brand] ?? IDENTITY_COLUMNS;
220
254
  }
255
+ /**
256
+ * The identity column a human reads one part by.
257
+ *
258
+ * The catalog designation where the vendor publishes one, and the sole
259
+ * identifier where it does not — the **last** of {@link identityColumns},
260
+ * because that is the order the pair is written in and a deviation lists the
261
+ * one column its vendor has.
262
+ *
263
+ * A lookup rather than a read of the row, for the reason {@link identityColumns}
264
+ * gives: a header that is missing `ISO Catalog Number` is indistinguishable
265
+ * from a scrape that lost it, so a caller that fell back column by column would
266
+ * name half a family's files after part numbers and never say why.
267
+ *
268
+ * It exists because `node/cad-mirror.ts` names a mirrored file after the part
269
+ * it holds, and hardcoded `'ISO Catalog Number'` to do it. That is Kennametal's
270
+ * pair; MariTool publishes one number per part under `Material Number` and no
271
+ * catalog designation at all, so every one of its 357 STEP models was skipped
272
+ * with a warning that the row had no catalog number to name it.
273
+ */
274
+ export function catalogColumn(brand) {
275
+ const columns = identityColumns(brand);
276
+ // Non-null: `IDENTITY_COLUMNS` has two entries and every deviation has at
277
+ // least one, which `tests/conventions.test.ts` holds the table to.
278
+ return columns[columns.length - 1];
279
+ }
221
280
  /**
222
281
  * Every identity column `brand` claims is really in the CSV header.
223
282
  *
package/dist/errors.d.ts CHANGED
@@ -10,6 +10,9 @@
10
10
  * cannot read.** The variants endpoint changed shape, a scrape's row count
11
11
  * disagrees with the declared one, a DIN 4000 code that should be pinned is
12
12
  * absent. The catalog is fine; the world moved.
13
+ * - {@link IncompletePartError} — **one part is missing a measurement it
14
+ * cannot be a part without.** A narrower case of the second, and the only
15
+ * one `registry.toRecords` may skip past: see below.
13
16
  *
14
17
  * They throw rather than exiting the process: a Node backend imports this, and
15
18
  * a mapped column that moved must not take down somebody's request handler.
@@ -43,4 +46,31 @@ export declare class ScraperConfigError extends ScraperError {
43
46
  */
44
47
  export declare class VendorResponseError extends ScraperError {
45
48
  }
49
+ /**
50
+ * One part does not publish a dimension its kind requires.
51
+ *
52
+ * **The only failure a whole family survives.** `registry.toRecords` maps a
53
+ * family's rows together, so before this type existed every refusal was
54
+ * equally fatal: one part with an unpublished cell ended the conversion and
55
+ * took every other row with it. EMUGE-FRANKEN omits `overall length l₁` on
56
+ * roughly 175 of its 7,021 end mill variants, and both end mill families
57
+ * produced nothing at all because of them.
58
+ *
59
+ * It is a distinct type rather than a flag on {@link VendorResponseError}
60
+ * because the two must not be skipped alike. A cutting material this package
61
+ * has no word for, a point-angle column a family stopped mapping, a variants
62
+ * table that changed shape — those are the vendor's vocabulary or this
63
+ * package's map having moved, and skipping past them quietly is how a scraper
64
+ * starts publishing a catalog nobody checked. Only `columns.required` raises
65
+ * this one, and only for a cell the vendor left unpublished.
66
+ *
67
+ * **It is not a licence to relax a kind's contract.** `records.RECORD_GEOMETRY`
68
+ * still says an end mill always has an `OAL`, and that stays true of every
69
+ * record this package emits: a part without one does not become a record with
70
+ * a hole in it, it becomes no record and a warning. Where a *vendor* genuinely
71
+ * never publishes a field, the answer is still `sometimes` — that is what a
72
+ * drill's `SIG` is, and why one is a contract decision and the other is not.
73
+ */
74
+ export declare class IncompletePartError extends VendorResponseError {
75
+ }
46
76
  export {};
package/dist/errors.js CHANGED
@@ -10,6 +10,9 @@
10
10
  * cannot read.** The variants endpoint changed shape, a scrape's row count
11
11
  * disagrees with the declared one, a DIN 4000 code that should be pinned is
12
12
  * absent. The catalog is fine; the world moved.
13
+ * - {@link IncompletePartError} — **one part is missing a measurement it
14
+ * cannot be a part without.** A narrower case of the second, and the only
15
+ * one `registry.toRecords` may skip past: see below.
13
16
  *
14
17
  * They throw rather than exiting the process: a Node backend imports this, and
15
18
  * a mapped column that moved must not take down somebody's request handler.
@@ -51,3 +54,30 @@ export class ScraperConfigError extends ScraperError {
51
54
  */
52
55
  export class VendorResponseError extends ScraperError {
53
56
  }
57
+ /**
58
+ * One part does not publish a dimension its kind requires.
59
+ *
60
+ * **The only failure a whole family survives.** `registry.toRecords` maps a
61
+ * family's rows together, so before this type existed every refusal was
62
+ * equally fatal: one part with an unpublished cell ended the conversion and
63
+ * took every other row with it. EMUGE-FRANKEN omits `overall length l₁` on
64
+ * roughly 175 of its 7,021 end mill variants, and both end mill families
65
+ * produced nothing at all because of them.
66
+ *
67
+ * It is a distinct type rather than a flag on {@link VendorResponseError}
68
+ * because the two must not be skipped alike. A cutting material this package
69
+ * has no word for, a point-angle column a family stopped mapping, a variants
70
+ * table that changed shape — those are the vendor's vocabulary or this
71
+ * package's map having moved, and skipping past them quietly is how a scraper
72
+ * starts publishing a catalog nobody checked. Only `columns.required` raises
73
+ * this one, and only for a cell the vendor left unpublished.
74
+ *
75
+ * **It is not a licence to relax a kind's contract.** `records.RECORD_GEOMETRY`
76
+ * still says an end mill always has an `OAL`, and that stays true of every
77
+ * record this package emits: a part without one does not become a record with
78
+ * a hole in it, it becomes no record and a warning. Where a *vendor* genuinely
79
+ * never publishes a field, the answer is still `sometimes` — that is what a
80
+ * drill's `SIG` is, and why one is a contract decision and the other is not.
81
+ */
82
+ export class IncompletePartError extends VendorResponseError {
83
+ }
@@ -8,7 +8,9 @@
8
8
  * coating and the coolant supply **per part**, in columns, and a scraped column
9
9
  * beats a family constant. Splitting by product line would turn four counted
10
10
  * row totals into thirty and buy nothing a `product line` column does not
11
- * already carry.
11
+ * already carry — and it is carried: `vendors/emuge/records.ts`'s
12
+ * `PRODUCT_LINE_COLUMNS` reads one per part onto `ToolRecord.productLine`, from
13
+ * a column every scrape already writes.
12
14
  *
13
15
  * So the only fact three of these four state is `unit`, and the fourth adds the
14
16
  * two a drill record cannot be built without.
@@ -8,7 +8,9 @@
8
8
  * coating and the coolant supply **per part**, in columns, and a scraped column
9
9
  * beats a family constant. Splitting by product line would turn four counted
10
10
  * row totals into thirty and buy nothing a `product line` column does not
11
- * already carry.
11
+ * already carry — and it is carried: `vendors/emuge/records.ts`'s
12
+ * `PRODUCT_LINE_COLUMNS` reads one per part onto `ToolRecord.productLine`, from
13
+ * a column every scrape already writes.
12
14
  *
13
15
  * So the only fact three of these four state is `unit`, and the fourth adds the
14
16
  * two a drill record cannot be built without.
@@ -100,9 +102,11 @@ export const FAMILIES = {
100
102
  kind: 'drill',
101
103
  familyCode: 'FB01',
102
104
  // `SIG` is a mapped column and not a fact, which no other drill family in
103
- // this package manages: EMUGE states a point angle on every part's detail
104
- // record. Kennametal's two drill lines assume theirs or derive them from a
105
- // point length, and both say so at length in `families/kennametal.ts`.
105
+ // this package manages: EMUGE states a point angle on the detail record of
106
+ // 2,669 of these 2,670 parts, and leaves the cell empty on the last, so the
107
+ // record may carry no `SIG` see `vendors/emuge/records.ts`'s `angle`.
108
+ // Kennametal's two drill lines assume theirs or derive them from a point
109
+ // length, and both say so at length in `families/kennametal.ts`.
106
110
  columns: {
107
111
  DC: 'nominal diameter d₁',
108
112
  SFDM: 'Shank diameter d₂',
package/dist/family.d.ts CHANGED
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import type { UnitSystem } from './conventions.js';
20
20
  import type { BrandName } from './identity.js';
21
+ import type { HoldingMapper, ToolholdingKind } from './holding.js';
21
22
  import type { Fact } from './provenance.js';
22
23
  import type { ColumnMap, ToolKind, ToolRecord } from './records.js';
23
24
  import type { MapperOptions, ScrapedRow } from './scrape.js';
@@ -121,8 +122,21 @@ export type BoundFamily = Omit<FamilyDefinition, 'columns'> & FamilyFacts & {
121
122
  /** The adapter that turns this family's rows into records. */
122
123
  readonly records: RecordMapper;
123
124
  };
124
- /** A toolholding family after its facts have been checked and projected. */
125
- export type BoundToolholding = ToolholdingDefinition & FamilyFacts;
125
+ /**
126
+ * A toolholding family after its facts have been checked and projected.
127
+ *
128
+ * `kind` is projected by the registry from **which table declared the family**,
129
+ * rather than being a key on {@link ToolholdingDefinition}: the table is the
130
+ * fact, and a `kind` beside it would be a second copy free to disagree with it.
131
+ * `records` is the mapper that brand binds for that kind, and it is optional
132
+ * because a vendor's holders may be scraped long before anybody has read its
133
+ * columns — see `holding.HoldingMappers`.
134
+ */
135
+ export type BoundToolholding = ToolholdingDefinition & FamilyFacts & {
136
+ readonly kind: ToolholdingKind;
137
+ /** The adapter that turns this family's rows into records, where there is one. */
138
+ readonly records?: HoldingMapper;
139
+ };
126
140
  /**
127
141
  * A family's id: `<brand>:<vendor-local id>`.
128
142
  *