@toolpath/tool-scraper 2.1.0 → 2.3.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/dist/conventions.d.ts +48 -2
- package/dist/conventions.js +41 -0
- package/dist/family.d.ts +16 -2
- package/dist/holding.d.ts +396 -0
- package/dist/holding.js +360 -0
- package/dist/index.d.ts +23 -13
- package/dist/index.js +23 -13
- package/dist/measure.d.ts +14 -10
- package/dist/measure.js +14 -13
- package/dist/node/cad-mirror.d.ts +58 -1
- package/dist/node/cad-mirror.js +56 -8
- package/dist/node/cli.d.ts +4 -1
- package/dist/node/cli.js +192 -18
- package/dist/node/holder-import.d.ts +223 -0
- package/dist/node/holder-import.js +379 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +1 -0
- package/dist/node/paths.d.ts +16 -0
- package/dist/node/paths.js +20 -0
- package/dist/profiles.d.ts +275 -0
- package/dist/profiles.js +295 -0
- package/dist/provenance.d.ts +9 -1
- package/dist/provenance.js +9 -1
- package/dist/records.d.ts +60 -20
- package/dist/records.js +31 -19
- package/dist/registry.d.ts +61 -5
- package/dist/registry.js +109 -6
- package/dist/vendors/kennametal/holding.d.ts +35 -0
- package/dist/vendors/kennametal/holding.js +112 -0
- package/dist/vendors/kennametal/index.d.ts +1 -0
- package/dist/vendors/kennametal/index.js +1 -0
- package/dist/vendors/maritool/holding.d.ts +79 -0
- package/dist/vendors/maritool/holding.js +164 -0
- package/dist/vendors/maritool/index.d.ts +1 -0
- package/dist/vendors/maritool/index.js +1 -0
- package/dist/vendors/maritool/scrape.d.ts +37 -13
- package/dist/vendors/maritool/scrape.js +53 -14
- package/dist/vendors/regofix/holding.d.ts +35 -0
- package/dist/vendors/regofix/holding.js +108 -0
- package/dist/vendors/regofix/index.d.ts +1 -0
- package/dist/vendors/regofix/index.js +1 -0
- package/dist/vendors/regofix/scrape.js +2 -2
- package/package.json +3 -2
package/dist/records.d.ts
CHANGED
|
@@ -42,9 +42,33 @@
|
|
|
42
42
|
* always a copy is not a second measurement. An adapter that could supply them
|
|
43
43
|
* separately could supply a tool that claims a holder it does not have.
|
|
44
44
|
*/
|
|
45
|
-
import { type UnitSystem } from '
|
|
45
|
+
import { type UnitSystem } from '@toolpath/tool-support';
|
|
46
46
|
import { type BrandName } from './identity.js';
|
|
47
47
|
import type { FactSource } from './provenance.js';
|
|
48
|
+
/**
|
|
49
|
+
* One canonical geometry name: what it measures, and whose name it is.
|
|
50
|
+
*
|
|
51
|
+
* **Declared here rather than re-exported from `@toolpath/tool-support`**, even
|
|
52
|
+
* though the dictionary's entries are the shared ones. The shared
|
|
53
|
+
* `GeometryField` carries a required `unit` and is `readonly` throughout, and
|
|
54
|
+
* this type has been published since 1.0: adopting it outright would stop a
|
|
55
|
+
* consumer that builds one of these — `{ definition, iso }` — from compiling,
|
|
56
|
+
* for no gain the values below do not already give. The entries *satisfy* the
|
|
57
|
+
* shared shape, so a field renamed upstream is still a compile error here.
|
|
58
|
+
*/
|
|
59
|
+
export interface GeometryField {
|
|
60
|
+
/**
|
|
61
|
+
* What the field measures, phrased so it can be quoted back at whoever
|
|
62
|
+
* mapped a column to the wrong one.
|
|
63
|
+
*/
|
|
64
|
+
definition: string;
|
|
65
|
+
/**
|
|
66
|
+
* The ISO 13399 code for this measurement, or `null` where the standard's
|
|
67
|
+
* counterpart has not been pinned against the dictionary. Equal to the
|
|
68
|
+
* canonical name itself on every field that *is* the standard's code.
|
|
69
|
+
*/
|
|
70
|
+
iso: string | null;
|
|
71
|
+
}
|
|
48
72
|
/** The kinds of cutting tool this package maps. */
|
|
49
73
|
export type ToolKind = 'drill' | 'tap' | 'endmill';
|
|
50
74
|
/**
|
|
@@ -87,20 +111,6 @@ export declare const UNSPECIFIED = "unspecified";
|
|
|
87
111
|
* type so that the vocabularies cannot drift apart.
|
|
88
112
|
*/
|
|
89
113
|
export type MaterialGroupsSource = FactSource | typeof UNSPECIFIED;
|
|
90
|
-
/** One canonical geometry name: what it measures, and whose name it is. */
|
|
91
|
-
export interface GeometryField {
|
|
92
|
-
/**
|
|
93
|
-
* What the field measures, phrased so it can be quoted back at whoever
|
|
94
|
-
* mapped a column to the wrong one.
|
|
95
|
-
*/
|
|
96
|
-
definition: string;
|
|
97
|
-
/**
|
|
98
|
-
* The ISO 13399 code for this measurement, or `null` where the standard's
|
|
99
|
-
* counterpart has not been pinned against the dictionary. Equal to the
|
|
100
|
-
* canonical name itself on every field that *is* the standard's code.
|
|
101
|
-
*/
|
|
102
|
-
iso: string | null;
|
|
103
|
-
}
|
|
104
114
|
/**
|
|
105
115
|
* Canonical geometry fields an adapter may supply, and what each means.
|
|
106
116
|
*
|
|
@@ -109,6 +119,26 @@ export interface GeometryField {
|
|
|
109
119
|
* so the vocabulary's source is readable from the code rather than from a plan
|
|
110
120
|
* document.
|
|
111
121
|
*
|
|
122
|
+
* ## What each entry says, and what this table says
|
|
123
|
+
*
|
|
124
|
+
* The definitions and ISO codes are `@toolpath/tool-support`'s — that is the
|
|
125
|
+
* dictionary, and it is shared because a code has to mean one thing in every
|
|
126
|
+
* package that reads one. **This is not that dictionary.** It is the narrower
|
|
127
|
+
* question only a scraper asks: which of those names an *adapter may map a
|
|
128
|
+
* vendor column to*.
|
|
129
|
+
*
|
|
130
|
+
* The two are not the same list and must not become one. The dictionary knows
|
|
131
|
+
* `LBH` and `LD`, which are derived from a tool and a holder downstream and
|
|
132
|
+
* which no vendor publishes; an adapter permitted to map a column to `LBH`
|
|
133
|
+
* could supply a tool that claims a stickout nobody set. It also knows `LSCN`,
|
|
134
|
+
* ISO's clamping-length minimum, which is a real vendor column the day a vendor
|
|
135
|
+
* prints one — and which is deliberately absent below until that day, because
|
|
136
|
+
* the load-time check's job is to refuse a name no scrape can fill.
|
|
137
|
+
*
|
|
138
|
+
* So each entry is an explicit pick out of the shared table rather than a
|
|
139
|
+
* spread of it: a name dropped from the dictionary is a compile error here, and
|
|
140
|
+
* a name added to the dictionary does not silently become mappable.
|
|
141
|
+
*
|
|
112
142
|
* **Seven of the ten are the standard's codes with the standard's meanings.**
|
|
113
143
|
* The three that are not are Autodesk's, and each has an ISO counterpart
|
|
114
144
|
* Autodesk did not use:
|
|
@@ -127,43 +157,53 @@ export interface GeometryField {
|
|
|
127
157
|
*/
|
|
128
158
|
export declare const GEOMETRY_FIELDS: {
|
|
129
159
|
readonly DC: {
|
|
160
|
+
readonly unit: "mm";
|
|
130
161
|
readonly definition: "cutting diameter";
|
|
131
162
|
readonly iso: "DC";
|
|
132
163
|
};
|
|
133
164
|
readonly SFDM: {
|
|
134
|
-
readonly
|
|
165
|
+
readonly unit: "mm";
|
|
166
|
+
readonly definition: "shank diameter \u2014 what the holder grips";
|
|
135
167
|
readonly iso: "DMM";
|
|
136
168
|
};
|
|
137
169
|
readonly OAL: {
|
|
138
|
-
readonly
|
|
170
|
+
readonly unit: "mm";
|
|
171
|
+
readonly definition: "overall length, tip to the end of the shank";
|
|
139
172
|
readonly iso: "OAL";
|
|
140
173
|
};
|
|
141
174
|
readonly LCF: {
|
|
142
|
-
readonly
|
|
175
|
+
readonly unit: "mm";
|
|
176
|
+
readonly definition: "flute length \u2014 the length of the cutting edge";
|
|
143
177
|
readonly iso: "LCF";
|
|
144
178
|
};
|
|
145
179
|
readonly RE: {
|
|
180
|
+
readonly unit: "mm";
|
|
146
181
|
readonly definition: "corner radius; 0 on a square-end tool";
|
|
147
182
|
readonly iso: "RE";
|
|
148
183
|
};
|
|
149
184
|
readonly TP: {
|
|
150
|
-
readonly
|
|
185
|
+
readonly unit: "mm";
|
|
186
|
+
readonly definition: "thread pitch, in the tool\u2019s own unit system";
|
|
151
187
|
readonly iso: "TP";
|
|
152
188
|
};
|
|
153
189
|
readonly NOF: {
|
|
190
|
+
readonly unit: "count";
|
|
154
191
|
readonly definition: "number of flutes";
|
|
155
192
|
readonly iso: "NOF";
|
|
156
193
|
};
|
|
157
194
|
readonly SIG: {
|
|
195
|
+
readonly unit: "deg";
|
|
158
196
|
readonly definition: "point angle, degrees included";
|
|
159
197
|
readonly iso: "SIG";
|
|
160
198
|
};
|
|
161
199
|
readonly 'shoulder-length': {
|
|
200
|
+
readonly unit: "mm";
|
|
162
201
|
readonly definition: "usable length below the full shank";
|
|
163
202
|
readonly iso: null;
|
|
164
203
|
};
|
|
165
204
|
readonly 'shoulder-diameter': {
|
|
166
|
-
readonly
|
|
205
|
+
readonly unit: "mm";
|
|
206
|
+
readonly definition: "diameter at the shoulder \u2014 the neck, where necked";
|
|
167
207
|
readonly iso: null;
|
|
168
208
|
};
|
|
169
209
|
};
|
package/dist/records.js
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* always a copy is not a second measurement. An adapter that could supply them
|
|
43
43
|
* separately could supply a tool that claims a holder it does not have.
|
|
44
44
|
*/
|
|
45
|
+
import { GEOMETRY_FIELDS as DICTIONARY } from '@toolpath/tool-support';
|
|
45
46
|
import { dimensionalColumn } from './conventions.js';
|
|
46
47
|
import { ScraperConfigError } from './errors.js';
|
|
47
48
|
import { recordGuid } from './identity.js';
|
|
@@ -81,6 +82,26 @@ export const UNSPECIFIED = 'unspecified';
|
|
|
81
82
|
* so the vocabulary's source is readable from the code rather than from a plan
|
|
82
83
|
* document.
|
|
83
84
|
*
|
|
85
|
+
* ## What each entry says, and what this table says
|
|
86
|
+
*
|
|
87
|
+
* The definitions and ISO codes are `@toolpath/tool-support`'s — that is the
|
|
88
|
+
* dictionary, and it is shared because a code has to mean one thing in every
|
|
89
|
+
* package that reads one. **This is not that dictionary.** It is the narrower
|
|
90
|
+
* question only a scraper asks: which of those names an *adapter may map a
|
|
91
|
+
* vendor column to*.
|
|
92
|
+
*
|
|
93
|
+
* The two are not the same list and must not become one. The dictionary knows
|
|
94
|
+
* `LBH` and `LD`, which are derived from a tool and a holder downstream and
|
|
95
|
+
* which no vendor publishes; an adapter permitted to map a column to `LBH`
|
|
96
|
+
* could supply a tool that claims a stickout nobody set. It also knows `LSCN`,
|
|
97
|
+
* ISO's clamping-length minimum, which is a real vendor column the day a vendor
|
|
98
|
+
* prints one — and which is deliberately absent below until that day, because
|
|
99
|
+
* the load-time check's job is to refuse a name no scrape can fill.
|
|
100
|
+
*
|
|
101
|
+
* So each entry is an explicit pick out of the shared table rather than a
|
|
102
|
+
* spread of it: a name dropped from the dictionary is a compile error here, and
|
|
103
|
+
* a name added to the dictionary does not silently become mappable.
|
|
104
|
+
*
|
|
84
105
|
* **Seven of the ten are the standard's codes with the standard's meanings.**
|
|
85
106
|
* The three that are not are Autodesk's, and each has an ISO counterpart
|
|
86
107
|
* Autodesk did not use:
|
|
@@ -98,25 +119,16 @@ export const UNSPECIFIED = 'unspecified';
|
|
|
98
119
|
* recognises it.
|
|
99
120
|
*/
|
|
100
121
|
export const GEOMETRY_FIELDS = {
|
|
101
|
-
DC:
|
|
102
|
-
SFDM:
|
|
103
|
-
OAL:
|
|
104
|
-
LCF:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
SIG: { definition: 'point angle, degrees included', iso: 'SIG' },
|
|
112
|
-
'shoulder-length': {
|
|
113
|
-
definition: 'usable length below the full shank',
|
|
114
|
-
iso: null,
|
|
115
|
-
},
|
|
116
|
-
'shoulder-diameter': {
|
|
117
|
-
definition: 'diameter at the shoulder — the neck, where necked',
|
|
118
|
-
iso: null,
|
|
119
|
-
},
|
|
122
|
+
DC: DICTIONARY.DC,
|
|
123
|
+
SFDM: DICTIONARY.SFDM,
|
|
124
|
+
OAL: DICTIONARY.OAL,
|
|
125
|
+
LCF: DICTIONARY.LCF,
|
|
126
|
+
RE: DICTIONARY.RE,
|
|
127
|
+
TP: DICTIONARY.TP,
|
|
128
|
+
NOF: DICTIONARY.NOF,
|
|
129
|
+
SIG: DICTIONARY.SIG,
|
|
130
|
+
'shoulder-length': DICTIONARY['shoulder-length'],
|
|
131
|
+
'shoulder-diameter': DICTIONARY['shoulder-diameter'],
|
|
120
132
|
};
|
|
121
133
|
/**
|
|
122
134
|
* The canonical names that are **not** ISO 13399's own codes, derived rather
|
package/dist/registry.d.ts
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* entry point into this package goes through here.
|
|
28
28
|
*/
|
|
29
29
|
import { type BoundFamily, type BoundToolholding, type RecordMappers } from './family.js';
|
|
30
|
+
import type { HoldingMappers, HoldingRecord } from './holding.js';
|
|
30
31
|
import { type ToolRecord } from './records.js';
|
|
31
32
|
import { type MapperOptions, type ScrapeResult } from './scrape.js';
|
|
32
33
|
/**
|
|
@@ -39,6 +40,26 @@ import { type MapperOptions, type ScrapeResult } from './scrape.js';
|
|
|
39
40
|
* map.
|
|
40
41
|
*/
|
|
41
42
|
export declare const ADAPTERS: Record<string, RecordMappers>;
|
|
43
|
+
/**
|
|
44
|
+
* Brand -> its toolholding mappers, by the kind of thing they build.
|
|
45
|
+
*
|
|
46
|
+
* The toolholding counterpart of {@link ADAPTERS}, and **partial in both
|
|
47
|
+
* directions on purpose**. A brand absent from here can still be scraped: its
|
|
48
|
+
* families bind, its CSVs are written, and its receipt is checked, exactly as
|
|
49
|
+
* before — what it cannot do is mint records. A brand present with a mapper for
|
|
50
|
+
* only one kind is the same statement one level down; MariTool publishes ER
|
|
51
|
+
* collets that this package does not scrape, so it maps holders and nothing
|
|
52
|
+
* else.
|
|
53
|
+
*
|
|
54
|
+
* That is what makes minting records additive rather than a break. Nothing here
|
|
55
|
+
* changes what a vendor with no entry does today, and the refusal only happens
|
|
56
|
+
* where a caller explicitly asks for records from a family whose brand maps
|
|
57
|
+
* none — {@link toHolding}, which names the brand and what it does map.
|
|
58
|
+
*
|
|
59
|
+
* One entry serves two brands for the reason {@link ADAPTERS} states: Kennametal
|
|
60
|
+
* and WIDIA are the same platform and the same table vocabulary.
|
|
61
|
+
*/
|
|
62
|
+
export declare const HOLDING_ADAPTERS: Record<string, HoldingMappers>;
|
|
42
63
|
/**
|
|
43
64
|
* Every cutting-tool family, validated and bound to its record mapper.
|
|
44
65
|
*
|
|
@@ -46,13 +67,25 @@ export declare const ADAPTERS: Record<string, RecordMappers>;
|
|
|
46
67
|
*/
|
|
47
68
|
export declare function boundFamilies(): Map<string, BoundFamily>;
|
|
48
69
|
/**
|
|
49
|
-
* Every holder and collet family, with its facts checked and projected
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* constant no variant table states, exactly like
|
|
70
|
+
* Every holder and collet family, with its facts checked and projected, and
|
|
71
|
+
* bound to the mapper its brand supplies for its kind.
|
|
72
|
+
*
|
|
73
|
+
* Their facts pass the same gate cutting-tool families' do: a taper or a
|
|
74
|
+
* clamping mode is a per-family constant no variant table states, exactly like
|
|
75
|
+
* a drill's flute count.
|
|
76
|
+
*
|
|
77
|
+
* **A family whose brand maps nothing binds `undefined` rather than throwing**,
|
|
78
|
+
* which is where this differs from {@link boundFamilies}. A cutting-tool family
|
|
79
|
+
* with no mapper is a catalog fault — nothing can be done with it — but a
|
|
80
|
+
* toolholding family with no mapper is the state every one of them was in until
|
|
81
|
+
* records existed, and it still scrapes, writes a CSV and checks a receipt.
|
|
82
|
+
* Refusing at bind time would take that away from every consumer that never
|
|
83
|
+
* asked for a record. {@link toHolding} is where the absence is reported, at
|
|
84
|
+
* the one call that cannot proceed without it.
|
|
54
85
|
*/
|
|
55
86
|
export declare function boundToolholding(): Map<string, BoundToolholding>;
|
|
87
|
+
/** One bound toolholding family by CSV name. */
|
|
88
|
+
export declare function boundHolding(name: string): BoundToolholding;
|
|
56
89
|
/** One bound cutting-tool family by CSV name. */
|
|
57
90
|
export declare function boundFamily(name: string): BoundFamily;
|
|
58
91
|
/**
|
|
@@ -108,6 +141,29 @@ export declare function boundFamily(name: string): BoundFamily;
|
|
|
108
141
|
* every part by number.
|
|
109
142
|
*/
|
|
110
143
|
export declare function toRecords(familyName: string, scrape: ScrapeResult, options?: MapperOptions): ToolRecord[];
|
|
144
|
+
/**
|
|
145
|
+
* One toolholding family's scrape, as {@link HoldingRecord}s.
|
|
146
|
+
*
|
|
147
|
+
* {@link toRecords}'s counterpart, and deliberately the same shape: the two
|
|
148
|
+
* checks run before the first row, one incomplete part does not end the family,
|
|
149
|
+
* and the count of what was dropped is not returned because the caller has the
|
|
150
|
+
* row count it passed in and the length it got back.
|
|
151
|
+
*
|
|
152
|
+
* **It refuses only where a caller asked for something this package cannot
|
|
153
|
+
* give.** A toolholding family whose brand maps no mapper binds one anyway
|
|
154
|
+
* (see {@link boundToolholding}) and scrapes exactly as it did before; this is
|
|
155
|
+
* the one call that cannot proceed without one, so this is where the absence is
|
|
156
|
+
* named — with the brand and with what that brand does map, the way
|
|
157
|
+
* {@link boundFamilies} names a missing tool mapper.
|
|
158
|
+
*
|
|
159
|
+
* `checkColumnsExist` has no counterpart here: a holder family carries no
|
|
160
|
+
* `ColumnMap`, because the columns a holder publishes are the vendor's own and
|
|
161
|
+
* are read by that vendor's mapper rather than through a canonical name. What
|
|
162
|
+
* does still run is {@link checkIdentityColumns}, which catches the failure that
|
|
163
|
+
* matters most — a re-scrape whose part-number column was renamed still parses,
|
|
164
|
+
* still has the right row count, and mints every guid off an empty string.
|
|
165
|
+
*/
|
|
166
|
+
export declare function toHolding(familyName: string, scrape: ScrapeResult, options?: MapperOptions): HoldingRecord[];
|
|
111
167
|
/**
|
|
112
168
|
* Forget what has been bound.
|
|
113
169
|
*
|
package/dist/registry.js
CHANGED
|
@@ -36,7 +36,10 @@ import { consoleWarn } from './scrape.js';
|
|
|
36
36
|
import { RECORD_MAPPERS as DESTINYTOOL } from './vendors/destinytool/records.js';
|
|
37
37
|
import { RECORD_MAPPERS as EMUGE } from './vendors/emuge/records.js';
|
|
38
38
|
import { RECORD_MAPPERS as HARVEY } from './vendors/harvey/records.js';
|
|
39
|
+
import { HOLDING_MAPPERS as KM_HOLDING } from './vendors/kennametal/holding.js';
|
|
39
40
|
import { RECORD_MAPPERS as KENNAMETAL } from './vendors/kennametal/records.js';
|
|
41
|
+
import { HOLDING_MAPPERS as MARITOOL_HOLDING } from './vendors/maritool/holding.js';
|
|
42
|
+
import { HOLDING_MAPPERS as REGOFIX_HOLDING } from './vendors/regofix/holding.js';
|
|
40
43
|
/**
|
|
41
44
|
* Brand -> its row-to-record mappers, by tool kind.
|
|
42
45
|
*
|
|
@@ -53,6 +56,31 @@ export const ADAPTERS = {
|
|
|
53
56
|
harvey: HARVEY,
|
|
54
57
|
emuge: EMUGE,
|
|
55
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Brand -> its toolholding mappers, by the kind of thing they build.
|
|
61
|
+
*
|
|
62
|
+
* The toolholding counterpart of {@link ADAPTERS}, and **partial in both
|
|
63
|
+
* directions on purpose**. A brand absent from here can still be scraped: its
|
|
64
|
+
* families bind, its CSVs are written, and its receipt is checked, exactly as
|
|
65
|
+
* before — what it cannot do is mint records. A brand present with a mapper for
|
|
66
|
+
* only one kind is the same statement one level down; MariTool publishes ER
|
|
67
|
+
* collets that this package does not scrape, so it maps holders and nothing
|
|
68
|
+
* else.
|
|
69
|
+
*
|
|
70
|
+
* That is what makes minting records additive rather than a break. Nothing here
|
|
71
|
+
* changes what a vendor with no entry does today, and the refusal only happens
|
|
72
|
+
* where a caller explicitly asks for records from a family whose brand maps
|
|
73
|
+
* none — {@link toHolding}, which names the brand and what it does map.
|
|
74
|
+
*
|
|
75
|
+
* One entry serves two brands for the reason {@link ADAPTERS} states: Kennametal
|
|
76
|
+
* and WIDIA are the same platform and the same table vocabulary.
|
|
77
|
+
*/
|
|
78
|
+
export const HOLDING_ADAPTERS = {
|
|
79
|
+
kennametal: KM_HOLDING,
|
|
80
|
+
widia: KM_HOLDING,
|
|
81
|
+
regofix: REGOFIX_HOLDING,
|
|
82
|
+
maritool: MARITOOL_HOLDING,
|
|
83
|
+
};
|
|
56
84
|
/**
|
|
57
85
|
* Check every fact of one family and project its value onto the config under
|
|
58
86
|
* its own key.
|
|
@@ -110,27 +138,50 @@ export function boundFamilies() {
|
|
|
110
138
|
return bound;
|
|
111
139
|
}
|
|
112
140
|
/**
|
|
113
|
-
* Every holder and collet family, with its facts checked and projected
|
|
141
|
+
* Every holder and collet family, with its facts checked and projected, and
|
|
142
|
+
* bound to the mapper its brand supplies for its kind.
|
|
114
143
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
144
|
+
* Their facts pass the same gate cutting-tool families' do: a taper or a
|
|
145
|
+
* clamping mode is a per-family constant no variant table states, exactly like
|
|
146
|
+
* a drill's flute count.
|
|
147
|
+
*
|
|
148
|
+
* **A family whose brand maps nothing binds `undefined` rather than throwing**,
|
|
149
|
+
* which is where this differs from {@link boundFamilies}. A cutting-tool family
|
|
150
|
+
* with no mapper is a catalog fault — nothing can be done with it — but a
|
|
151
|
+
* toolholding family with no mapper is the state every one of them was in until
|
|
152
|
+
* records existed, and it still scrapes, writes a CSV and checks a receipt.
|
|
153
|
+
* Refusing at bind time would take that away from every consumer that never
|
|
154
|
+
* asked for a record. {@link toHolding} is where the absence is reported, at
|
|
155
|
+
* the one call that cannot proceed without it.
|
|
118
156
|
*/
|
|
119
157
|
export function boundToolholding() {
|
|
120
158
|
if (toolholding !== null)
|
|
121
159
|
return toolholding;
|
|
122
160
|
const bound = new Map();
|
|
123
|
-
for (const [
|
|
161
|
+
for (const [kind, families_] of [
|
|
124
162
|
['holder', HOLDER_FAMILIES],
|
|
125
163
|
['collet', COLLET_FAMILIES],
|
|
126
164
|
]) {
|
|
127
165
|
for (const [name, cfg] of Object.entries(families_)) {
|
|
128
|
-
|
|
166
|
+
const mappers = HOLDING_ADAPTERS[familyBrand(cfg)];
|
|
167
|
+
bound.set(name, {
|
|
168
|
+
...project(kind, name, cfg),
|
|
169
|
+
kind,
|
|
170
|
+
records: mappers?.[kind],
|
|
171
|
+
});
|
|
129
172
|
}
|
|
130
173
|
}
|
|
131
174
|
toolholding = bound;
|
|
132
175
|
return bound;
|
|
133
176
|
}
|
|
177
|
+
/** One bound toolholding family by CSV name. */
|
|
178
|
+
export function boundHolding(name) {
|
|
179
|
+
const cfg = boundToolholding().get(name);
|
|
180
|
+
if (cfg === undefined) {
|
|
181
|
+
throw new ScraperConfigError(name, `unknown toolholding family (known: ${[...boundToolholding().keys()].sort().join(', ')})`);
|
|
182
|
+
}
|
|
183
|
+
return cfg;
|
|
184
|
+
}
|
|
134
185
|
/** One bound cutting-tool family by CSV name. */
|
|
135
186
|
export function boundFamily(name) {
|
|
136
187
|
const cfg = boundFamilies().get(name);
|
|
@@ -209,6 +260,58 @@ export function toRecords(familyName, scrape, options) {
|
|
|
209
260
|
}
|
|
210
261
|
return records;
|
|
211
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* One toolholding family's scrape, as {@link HoldingRecord}s.
|
|
265
|
+
*
|
|
266
|
+
* {@link toRecords}'s counterpart, and deliberately the same shape: the two
|
|
267
|
+
* checks run before the first row, one incomplete part does not end the family,
|
|
268
|
+
* and the count of what was dropped is not returned because the caller has the
|
|
269
|
+
* row count it passed in and the length it got back.
|
|
270
|
+
*
|
|
271
|
+
* **It refuses only where a caller asked for something this package cannot
|
|
272
|
+
* give.** A toolholding family whose brand maps no mapper binds one anyway
|
|
273
|
+
* (see {@link boundToolholding}) and scrapes exactly as it did before; this is
|
|
274
|
+
* the one call that cannot proceed without one, so this is where the absence is
|
|
275
|
+
* named — with the brand and with what that brand does map, the way
|
|
276
|
+
* {@link boundFamilies} names a missing tool mapper.
|
|
277
|
+
*
|
|
278
|
+
* `checkColumnsExist` has no counterpart here: a holder family carries no
|
|
279
|
+
* `ColumnMap`, because the columns a holder publishes are the vendor's own and
|
|
280
|
+
* are read by that vendor's mapper rather than through a canonical name. What
|
|
281
|
+
* does still run is {@link checkIdentityColumns}, which catches the failure that
|
|
282
|
+
* matters most — a re-scrape whose part-number column was renamed still parses,
|
|
283
|
+
* still has the right row count, and mints every guid off an empty string.
|
|
284
|
+
*/
|
|
285
|
+
export function toHolding(familyName, scrape, options) {
|
|
286
|
+
const cfg = boundHolding(familyName);
|
|
287
|
+
const warn = options?.warn ?? consoleWarn;
|
|
288
|
+
const brand = familyBrand(cfg);
|
|
289
|
+
const mapper = cfg.records;
|
|
290
|
+
if (mapper === undefined) {
|
|
291
|
+
const mappers = HOLDING_ADAPTERS[brand];
|
|
292
|
+
throw new ScraperConfigError(familyName, `brand ${JSON.stringify(brand)} has no ${cfg.kind} mapper ` +
|
|
293
|
+
(mappers === undefined
|
|
294
|
+
? `— it maps no toolholding at all, so this family ends at rows and ` +
|
|
295
|
+
`a receipt (brands that map some: ` +
|
|
296
|
+
`${Object.keys(HOLDING_ADAPTERS).sort().join(', ')})`
|
|
297
|
+
: `(it maps: ${Object.keys(mappers).sort().join(', ')})`));
|
|
298
|
+
}
|
|
299
|
+
checkIdentityColumns(brand, scrape.header);
|
|
300
|
+
const records = [];
|
|
301
|
+
for (const row of scrape.rows) {
|
|
302
|
+
try {
|
|
303
|
+
records.push(cfg.kind === 'holder'
|
|
304
|
+
? mapper(row, cfg, options)
|
|
305
|
+
: mapper(row, cfg, options));
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (!(error instanceof IncompletePartError))
|
|
309
|
+
throw error;
|
|
310
|
+
warn(` WARNING: ${error.message} — no record written for it`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return records;
|
|
314
|
+
}
|
|
212
315
|
/**
|
|
213
316
|
* Forget what has been bound.
|
|
214
317
|
*
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kennametal's and WIDIA's toolholding column vocabulary, and nothing else.
|
|
3
|
+
*
|
|
4
|
+
* The counterpart of `records.ts` for a holder and a collet: every decision
|
|
5
|
+
* about *what a record is* lives in `holding.ts`, and what lives here is which
|
|
6
|
+
* of this vendor's columns answers each question. That is the same line
|
|
7
|
+
* `vendors/kennametal/records.ts` draws for cutting tools.
|
|
8
|
+
*
|
|
9
|
+
* ## What this platform states as a fact rather than a column
|
|
10
|
+
*
|
|
11
|
+
* Almost everything. `taper`, `contact`, `clamping`, `style` and `unit` are all
|
|
12
|
+
* per-family constants here, declared in `families/kennametal.ts` with a
|
|
13
|
+
* citation each, because Kennametal sells one interface and one clamping mode
|
|
14
|
+
* per family — its dual-contact BT30 is a separate line (BTKV\*) with its own
|
|
15
|
+
* family code. The two vendors whose holders vary row by row are the ones whose
|
|
16
|
+
* mappers read a column instead.
|
|
17
|
+
*
|
|
18
|
+
* ## Two published columns this deliberately does not carry
|
|
19
|
+
*
|
|
20
|
+
* - **`L1FC`**, the BTKV30 line's "Gage Length Face Contact", 0.998 mm shorter
|
|
21
|
+
* than `L1` on every row. It is not a second measurement of one thing: it is
|
|
22
|
+
* the gage length *in a face-contact spindle*, so which of the two is true is
|
|
23
|
+
* a fact about the machine rather than about the holder. Carrying both would
|
|
24
|
+
* put two numbers named "gage" on one record with nothing to say which one
|
|
25
|
+
* stickout arithmetic should use. `contact` records that the holder has the
|
|
26
|
+
* geometry; `gaugeLength` stays `L1`. Promote it the day something reads a
|
|
27
|
+
* spindle's contact mode, and change `gaugeLength` with it rather than
|
|
28
|
+
* showing both.
|
|
29
|
+
* - **Torque figures, actuation-screw drive size, weight and `D21`.** Published,
|
|
30
|
+
* and dropped under `records.ToolRecord`'s standing rule: add a field when
|
|
31
|
+
* something displays it, not before.
|
|
32
|
+
*/
|
|
33
|
+
import { type HoldingMappers } from '../../holding.js';
|
|
34
|
+
/** The toolholding half of the adapter contract `registry` looks up by brand. */
|
|
35
|
+
export declare const HOLDING_MAPPERS: HoldingMappers;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kennametal's and WIDIA's toolholding column vocabulary, and nothing else.
|
|
3
|
+
*
|
|
4
|
+
* The counterpart of `records.ts` for a holder and a collet: every decision
|
|
5
|
+
* about *what a record is* lives in `holding.ts`, and what lives here is which
|
|
6
|
+
* of this vendor's columns answers each question. That is the same line
|
|
7
|
+
* `vendors/kennametal/records.ts` draws for cutting tools.
|
|
8
|
+
*
|
|
9
|
+
* ## What this platform states as a fact rather than a column
|
|
10
|
+
*
|
|
11
|
+
* Almost everything. `taper`, `contact`, `clamping`, `style` and `unit` are all
|
|
12
|
+
* per-family constants here, declared in `families/kennametal.ts` with a
|
|
13
|
+
* citation each, because Kennametal sells one interface and one clamping mode
|
|
14
|
+
* per family — its dual-contact BT30 is a separate line (BTKV\*) with its own
|
|
15
|
+
* family code. The two vendors whose holders vary row by row are the ones whose
|
|
16
|
+
* mappers read a column instead.
|
|
17
|
+
*
|
|
18
|
+
* ## Two published columns this deliberately does not carry
|
|
19
|
+
*
|
|
20
|
+
* - **`L1FC`**, the BTKV30 line's "Gage Length Face Contact", 0.998 mm shorter
|
|
21
|
+
* than `L1` on every row. It is not a second measurement of one thing: it is
|
|
22
|
+
* the gage length *in a face-contact spindle*, so which of the two is true is
|
|
23
|
+
* a fact about the machine rather than about the holder. Carrying both would
|
|
24
|
+
* put two numbers named "gage" on one record with nothing to say which one
|
|
25
|
+
* stickout arithmetic should use. `contact` records that the holder has the
|
|
26
|
+
* geometry; `gaugeLength` stays `L1`. Promote it the day something reads a
|
|
27
|
+
* spindle's contact mode, and change `gaugeLength` with it rather than
|
|
28
|
+
* showing both.
|
|
29
|
+
* - **Torque figures, actuation-screw drive size, weight and `D21`.** Published,
|
|
30
|
+
* and dropped under `records.ToolRecord`'s standing rule: add a field when
|
|
31
|
+
* something displays it, not before.
|
|
32
|
+
*/
|
|
33
|
+
import { CAD_COLUMN, COLLET_DESIGNATION_COLUMN, COLLET_SERIES_COLUMN } from '../../conventions.js';
|
|
34
|
+
import { familyBrand } from '../../family.js';
|
|
35
|
+
import { checkUnitAgreement, clampingMode, colletRecord, contactMode, dim, holderRecord, holdingFact, published, } from '../../holding.js';
|
|
36
|
+
import { consoleWarn } from '../../scrape.js';
|
|
37
|
+
import { CATALOG_NUMBER, MATERIAL_NUMBER } from './records.js';
|
|
38
|
+
/**
|
|
39
|
+
* The dimensional labels whose two unit columns are worth cross-checking.
|
|
40
|
+
*
|
|
41
|
+
* Every dimension either record carries, and no more: a label nothing reads
|
|
42
|
+
* cannot produce a wrong number, so warning about it is noise. `holding.dim`
|
|
43
|
+
* reads the native column and never the other one, which is what makes this a
|
|
44
|
+
* report rather than a gate.
|
|
45
|
+
*/
|
|
46
|
+
const HOLDER_LABELS = ['D1', 'L1', 'L2', 'L9', 'V', 'D2', 'D11'];
|
|
47
|
+
const COLLET_LABELS = ['CCCN', 'CCCX', 'D1', 'BDX', 'LF', 'L'];
|
|
48
|
+
/** How a part names itself in a warning or a refusal. */
|
|
49
|
+
function subject(row) {
|
|
50
|
+
return `${row[CATALOG_NUMBER] ?? ''} (${row[MATERIAL_NUMBER] ?? ''})`;
|
|
51
|
+
}
|
|
52
|
+
/** One Kennametal or WIDIA holder row -> one {@link HolderRecord}. */
|
|
53
|
+
function holder(row, family, options = {}) {
|
|
54
|
+
const warn = options.warn ?? consoleWarn;
|
|
55
|
+
const what = subject(row);
|
|
56
|
+
const unit = holdingFact(family, 'unit', family.unit);
|
|
57
|
+
for (const label of HOLDER_LABELS)
|
|
58
|
+
checkUnitAgreement(row, label, what, warn);
|
|
59
|
+
// Kennametal publishes no description column for toolholding, and `''` is the
|
|
60
|
+
// honest answer where a vendor publishes none — `records.ToolRecord.description`
|
|
61
|
+
// states the rule and the reason: a description that restates the catalog
|
|
62
|
+
// number puts one string in two fields.
|
|
63
|
+
return holderRecord({
|
|
64
|
+
brand: familyBrand(family),
|
|
65
|
+
materialNumber: published(row[MATERIAL_NUMBER], what, 'material number'),
|
|
66
|
+
catalogNumber: published(row[CATALOG_NUMBER], what, 'catalog number'),
|
|
67
|
+
description: '',
|
|
68
|
+
unit,
|
|
69
|
+
taper: holdingFact(family, 'taper', family.taper),
|
|
70
|
+
contact: contactMode(holdingFact(family, 'contact', family.contact), what),
|
|
71
|
+
clamping: clampingMode(holdingFact(family, 'clamping', family.clamping), what),
|
|
72
|
+
style: holdingFact(family, 'style', family.style),
|
|
73
|
+
colletSeries: row[COLLET_SERIES_COLUMN] || null,
|
|
74
|
+
bore: dim(row, 'D1', unit),
|
|
75
|
+
gaugeLength: published(dim(row, 'L1', unit), what, 'L1 gage length'),
|
|
76
|
+
usableLength: dim(row, 'L2', unit),
|
|
77
|
+
clampingLength: dim(row, 'L9', unit),
|
|
78
|
+
adjustmentRange: dim(row, 'V', unit),
|
|
79
|
+
bodyDiameter: dim(row, 'D2', unit),
|
|
80
|
+
lockNutDiameter: dim(row, 'D11', unit),
|
|
81
|
+
cadModelUrl: row[CAD_COLUMN] || null,
|
|
82
|
+
// This platform publishes a STEP model and no 2D profile. A DXF column would
|
|
83
|
+
// be a claim about the data, and false — the call `conventions.CAD_DXF_COLUMN`
|
|
84
|
+
// records.
|
|
85
|
+
cadDxfUrl: null,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** One Kennametal or WIDIA collet row -> one {@link ColletRecord}. */
|
|
89
|
+
function collet(row, family, options = {}) {
|
|
90
|
+
const warn = options.warn ?? consoleWarn;
|
|
91
|
+
const what = subject(row);
|
|
92
|
+
const unit = holdingFact(family, 'unit', family.unit);
|
|
93
|
+
for (const label of COLLET_LABELS)
|
|
94
|
+
checkUnitAgreement(row, label, what, warn);
|
|
95
|
+
return colletRecord({
|
|
96
|
+
brand: familyBrand(family),
|
|
97
|
+
materialNumber: published(row[MATERIAL_NUMBER], what, 'material number'),
|
|
98
|
+
catalogNumber: published(row[CATALOG_NUMBER], what, 'catalog number'),
|
|
99
|
+
description: '',
|
|
100
|
+
unit,
|
|
101
|
+
series: published(row[COLLET_DESIGNATION_COLUMN], what, 'collet series'),
|
|
102
|
+
style: holdingFact(family, 'style', family.style),
|
|
103
|
+
nominal: dim(row, 'D1', unit),
|
|
104
|
+
clampMin: published(dim(row, 'CCCN', unit), what, 'CCCN clamping minimum'),
|
|
105
|
+
clampMax: published(dim(row, 'CCCX', unit), what, 'CCCX clamping maximum'),
|
|
106
|
+
bodyDiameter: dim(row, 'BDX', unit),
|
|
107
|
+
functionalLength: dim(row, 'LF', unit),
|
|
108
|
+
overallLength: dim(row, 'L', unit),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/** The toolholding half of the adapter contract `registry` looks up by brand. */
|
|
112
|
+
export const HOLDING_MAPPERS = { holder, collet };
|