@toolpath/tool-scraper 0.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -11
- package/dist/columns.d.ts +64 -0
- package/dist/columns.js +68 -0
- package/dist/conventions.d.ts +107 -15
- package/dist/conventions.js +126 -15
- package/dist/errors.d.ts +30 -0
- package/dist/errors.js +30 -0
- package/dist/families/emuge.d.ts +185 -0
- package/dist/families/emuge.js +163 -0
- package/dist/families/harvey.d.ts +1782 -0
- package/dist/families/harvey.js +1328 -0
- package/dist/families/index.js +5 -2
- package/dist/families/kennametal.d.ts +21 -0
- package/dist/families/kennametal.js +10 -0
- package/dist/families/maritool.d.ts +120 -0
- package/dist/families/maritool.js +175 -0
- package/dist/family.d.ts +35 -1
- package/dist/family.js +28 -0
- package/dist/identity.d.ts +18 -0
- package/dist/identity.js +46 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +16 -0
- package/dist/measure.d.ts +112 -0
- package/dist/measure.js +130 -0
- package/dist/node/cli.d.ts +3 -0
- package/dist/node/cli.js +158 -3
- package/dist/node/main.js +0 -0
- package/dist/records.d.ts +214 -12
- package/dist/records.js +140 -5
- package/dist/registry.d.ts +56 -1
- package/dist/registry.js +79 -2
- package/dist/scrape.d.ts +15 -0
- package/dist/scrape.js +24 -0
- package/dist/vendors/destinytool/records.d.ts +13 -3
- package/dist/vendors/destinytool/records.js +49 -37
- package/dist/vendors/emuge/index.d.ts +17 -0
- package/dist/vendors/emuge/index.js +17 -0
- package/dist/vendors/emuge/records.d.ts +230 -0
- package/dist/vendors/emuge/records.js +543 -0
- package/dist/vendors/emuge/scrape.d.ts +227 -0
- package/dist/vendors/emuge/scrape.js +358 -0
- package/dist/vendors/emuge/value.d.ts +75 -0
- package/dist/vendors/emuge/value.js +116 -0
- package/dist/vendors/harvey/catalog.d.ts +53 -0
- package/dist/vendors/harvey/catalog.js +120 -0
- package/dist/vendors/harvey/header.d.ts +89 -0
- package/dist/vendors/harvey/header.js +185 -0
- package/dist/vendors/harvey/index.d.ts +21 -0
- package/dist/vendors/harvey/index.js +21 -0
- package/dist/vendors/harvey/lexicon.d.ts +73 -0
- package/dist/vendors/harvey/lexicon.js +126 -0
- package/dist/vendors/harvey/literal.d.ts +68 -0
- package/dist/vendors/harvey/literal.js +214 -0
- package/dist/vendors/harvey/records.d.ts +79 -0
- package/dist/vendors/harvey/records.js +163 -0
- package/dist/vendors/harvey/scrape.d.ts +187 -0
- package/dist/vendors/harvey/scrape.js +483 -0
- package/dist/vendors/harvey/value.d.ts +74 -0
- package/dist/vendors/harvey/value.js +119 -0
- package/dist/vendors/kennametal/family.d.ts +119 -0
- package/dist/vendors/kennametal/family.js +155 -0
- package/dist/vendors/kennametal/index.d.ts +1 -0
- package/dist/vendors/kennametal/index.js +1 -0
- package/dist/vendors/kennametal/records.d.ts +29 -4
- package/dist/vendors/kennametal/records.js +93 -26
- package/dist/vendors/kennametal/scrape.d.ts +25 -2
- package/dist/vendors/kennametal/scrape.js +28 -3
- package/dist/vendors/maritool/catalog.d.ts +81 -0
- package/dist/vendors/maritool/catalog.js +132 -0
- package/dist/vendors/maritool/index.d.ts +10 -0
- package/dist/vendors/maritool/index.js +10 -0
- package/dist/vendors/maritool/scrape.d.ts +297 -0
- package/dist/vendors/maritool/scrape.js +593 -0
- package/dist/vendors/regofix/scrape.d.ts +8 -11
- package/dist/vendors/regofix/scrape.js +21 -36
- package/package.json +31 -7
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harvey rows -> {@link ToolRecord}.
|
|
3
|
+
*
|
|
4
|
+
* The CSV a Harvey scrape writes holds the vendor's own **display strings** —
|
|
5
|
+
* `.250 (1/4)`, `1-1/2`, `3 mm`, `-` — rather than parsed numbers, because the
|
|
6
|
+
* file is the receipt and Harvey's own fractional and metric annotations are
|
|
7
|
+
* part of what it published. So this module is where a cell becomes a number,
|
|
8
|
+
* through `value.ts`; the closest precedent in this package is Destiny Tool's
|
|
9
|
+
* fractional-inch reader, not Kennametal's, whose columns are already decimals.
|
|
10
|
+
*
|
|
11
|
+
* ## What Harvey does not publish
|
|
12
|
+
*
|
|
13
|
+
* **No second identifier.** One `Tool #` per part, which fills both the material
|
|
14
|
+
* number and the catalog number on a record — see
|
|
15
|
+
* `conventions.IDENTITY_DEVIATIONS.harvey`.
|
|
16
|
+
*
|
|
17
|
+
* **No carbide grade.** `substrate` comes from the family's `bmc` fact and the
|
|
18
|
+
* `COATING` column fills `coating`, exactly as Destiny Tool's coating id does.
|
|
19
|
+
*
|
|
20
|
+
* **No workpiece-material index a scrape can reach.** Nothing in a variant
|
|
21
|
+
* table rates a tool to ISO 513 groups, so every Harvey record is labelled
|
|
22
|
+
* `records.UNSPECIFIED` — *we do not know what this tool is for*, which is a
|
|
23
|
+
* different claim from Kennametal's swept taps being rated for nothing.
|
|
24
|
+
*
|
|
25
|
+
* The per-part page does publish one, and it is **per part and not per family**:
|
|
26
|
+
* a 192-request probe on 2026-08-29 found `harvey_endmill_005.csv` splitting by
|
|
27
|
+
* coating column, uncoated tools rated for steel, stainless, cast iron and
|
|
28
|
+
* titanium where the amorphous-diamond-coated tools of the same geometry are
|
|
29
|
+
* rated for aluminium, wood and composites alone. That is correct metallurgy —
|
|
30
|
+
* diamond cannot cut ferrous — and flattening it to one answer per family would
|
|
31
|
+
* put a diamond-coated end mill under steel. So there is no family fact to
|
|
32
|
+
* write, and the two keyseat pages titled for a material class do not get one
|
|
33
|
+
* either: the "For Non - Ferrous Materials" line's own part pages rate it for
|
|
34
|
+
* steel and stainless steel, so the title does not predict the index.
|
|
35
|
+
* `docs/HARVEY_PRODUCT_TABLE.md` §1.5 has the measurement and what reaching it
|
|
36
|
+
* would cost.
|
|
37
|
+
*
|
|
38
|
+
* **No corner radius on a ball nose.** A ball family publishes no radius column
|
|
39
|
+
* at all, so `RE` comes from the family's `profile` fact — see
|
|
40
|
+
* {@link cornerRadius}. That is a per-family constant with provenance rather
|
|
41
|
+
* than a string match on a description, because Harvey states the profile once,
|
|
42
|
+
* in the page title, for the whole product line.
|
|
43
|
+
*/
|
|
44
|
+
import { columnReaders } from '../../columns.js';
|
|
45
|
+
import { DESCRIPTION_COLUMN } from '../../conventions.js';
|
|
46
|
+
import { VendorResponseError } from '../../errors.js';
|
|
47
|
+
import { fact, familyBrand } from '../../family.js';
|
|
48
|
+
import { BRANDS } from '../../identity.js';
|
|
49
|
+
import { toolRecord } from '../../records.js';
|
|
50
|
+
import { consoleWarn } from '../../scrape.js';
|
|
51
|
+
import { COATING_COLUMN, TOOL_NUMBER_COLUMN } from './scrape.js';
|
|
52
|
+
import { count, dimension } from './value.js';
|
|
53
|
+
/** The `profile` fact value that means a ball nose. Harvey's own word. */
|
|
54
|
+
export const BALL_PROFILE = 'Ball';
|
|
55
|
+
/**
|
|
56
|
+
* The family's declared unit, refused rather than asserted when it is absent.
|
|
57
|
+
*
|
|
58
|
+
* Every Harvey family declares one — the `harvey` CLI command will not scrape a
|
|
59
|
+
* family without it — so `familyUnits` would return exactly this one and a
|
|
60
|
+
* tap's two-system case cannot arise here. It was `family.unit!` until
|
|
61
|
+
* 2026-08-29, which is the same claim with no check behind it: a family added
|
|
62
|
+
* without the fact would have read `undefined` straight into
|
|
63
|
+
* `dimensionalColumn` and asked the CSV for a column called `CUTTER DIA._`.
|
|
64
|
+
*/
|
|
65
|
+
function unitOf(family) {
|
|
66
|
+
return fact(family, 'unit', family.unit);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The three column readers, over this vendor's grammar.
|
|
70
|
+
*
|
|
71
|
+
* `dimension` is the only Harvey-specific half; everything either side of it —
|
|
72
|
+
* an unmapped column answering undefined, a required field refusing the row and
|
|
73
|
+
* quoting the cell — is `columns.columnReaders`, shared with EMUGE-FRANKEN's
|
|
74
|
+
* mapper. All three take the caller's `columns` rather than `family.columns`:
|
|
75
|
+
* they are the same object through `registry.toRecords`, but `registry`
|
|
76
|
+
* validates the argument, and a mapper reading a different reference is
|
|
77
|
+
* validating one map and reading another. This mapper asserted the parameter
|
|
78
|
+
* was unused with an underscore until 2026-08-29.
|
|
79
|
+
*/
|
|
80
|
+
const { cell, required, optional } = columnReaders(dimension);
|
|
81
|
+
/**
|
|
82
|
+
* The corner radius, in priority order.
|
|
83
|
+
*
|
|
84
|
+
* 1. The family's own radius column where it has one — `CORNER RADIUS` on the
|
|
85
|
+
* corner-radius lines, `RADIUS` on the full-radius keyseat cutters.
|
|
86
|
+
* 2. `DC / 2` on a ball nose. Harvey publishes no radius column on any of its
|
|
87
|
+
* twelve ball families, and the radius of a ball end *is* half the diameter,
|
|
88
|
+
* so this is arithmetic rather than a guess — the `profile` fact is what says
|
|
89
|
+
* the family is one.
|
|
90
|
+
* 3. `0` — a real square end — otherwise.
|
|
91
|
+
*
|
|
92
|
+
* A mapped column whose cell is blank falls through to 2 or 3 rather than
|
|
93
|
+
* refusing the row: `RE` is optional on the endmill contract precisely because
|
|
94
|
+
* a square-end row's blank radius is an answer.
|
|
95
|
+
*/
|
|
96
|
+
export function cornerRadius(row, family, columns, what, dc, options) {
|
|
97
|
+
const stated = optional(row, columns, 'RE', unitOf(family), what, options);
|
|
98
|
+
if (stated !== null)
|
|
99
|
+
return stated;
|
|
100
|
+
return family.profile === BALL_PROFILE ? dc / 2 : 0;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The flute count.
|
|
104
|
+
*
|
|
105
|
+
* One column whichever way the table encoded it: `vendors/harvey/scrape.ts`
|
|
106
|
+
* writes `FLUTES` from the row's own column on a `TOOL #` table and from the
|
|
107
|
+
* coating group's sub-label on a matrix one, so nothing downstream has to know
|
|
108
|
+
* which shape the page used.
|
|
109
|
+
*
|
|
110
|
+
* Null is a real answer on the two deburring families, which publish
|
|
111
|
+
* right- and left-hand tooth counts and no flute count at all.
|
|
112
|
+
*/
|
|
113
|
+
export function flutes(row, family, columns) {
|
|
114
|
+
const raw = cell(row, columns, 'NOF', unitOf(family));
|
|
115
|
+
return raw === undefined ? null : count(raw);
|
|
116
|
+
}
|
|
117
|
+
/** One orderable Harvey tool. */
|
|
118
|
+
export function endmillRecord(row, family, columns, options = {}) {
|
|
119
|
+
const warn = options.warn ?? consoleWarn;
|
|
120
|
+
const what = row[TOOL_NUMBER_COLUMN] ?? '';
|
|
121
|
+
if (what === '') {
|
|
122
|
+
throw new VendorResponseError(family.id, `has a row with no ${TOOL_NUMBER_COLUMN}`);
|
|
123
|
+
}
|
|
124
|
+
const opts = { warn };
|
|
125
|
+
const unit = unitOf(family);
|
|
126
|
+
const dc = required(row, columns, 'DC', unit, what, opts);
|
|
127
|
+
const fluteLength = required(row, columns, 'LCF', unit, what, opts);
|
|
128
|
+
// Harvey's reach columns are the distance from the tip to the full shank,
|
|
129
|
+
// which is what `shoulder-length` names. A family with no reach column is a
|
|
130
|
+
// plain tool whose usable length below the shank is its flute length — the
|
|
131
|
+
// same convention Destiny Tool's mapper uses.
|
|
132
|
+
const reach = optional(row, columns, 'shoulder-length', unit, what, opts);
|
|
133
|
+
const neck = optional(row, columns, 'shoulder-diameter', unit, what, opts);
|
|
134
|
+
const geometry = {
|
|
135
|
+
DC: dc,
|
|
136
|
+
RE: cornerRadius(row, family, columns, what, dc, opts),
|
|
137
|
+
SFDM: required(row, columns, 'SFDM', unit, what, opts),
|
|
138
|
+
OAL: required(row, columns, 'OAL', unit, what, opts),
|
|
139
|
+
LCF: fluteLength,
|
|
140
|
+
'shoulder-length': reach ?? fluteLength,
|
|
141
|
+
'shoulder-diameter': neck ?? dc,
|
|
142
|
+
};
|
|
143
|
+
const nof = flutes(row, family, columns);
|
|
144
|
+
if (nof !== null)
|
|
145
|
+
geometry.NOF = nof;
|
|
146
|
+
return toolRecord({
|
|
147
|
+
brand: familyBrand(family),
|
|
148
|
+
vendor: BRANDS[familyBrand(family)].vendor,
|
|
149
|
+
materialNumber: what,
|
|
150
|
+
catalogNumber: what,
|
|
151
|
+
description: row[DESCRIPTION_COLUMN] ?? '',
|
|
152
|
+
kind: 'endmill',
|
|
153
|
+
unit,
|
|
154
|
+
substrate: fact(family, 'bmc', family.bmc),
|
|
155
|
+
coating: row[COATING_COLUMN] ?? '',
|
|
156
|
+
// `materialGroups` and its source are left to the factory's `null`: see the
|
|
157
|
+
// module docstring for what a Harvey part page publishes and why none of it
|
|
158
|
+
// can be stated per family.
|
|
159
|
+
coolantThrough: fact(family, 'coolantThrough', family.coolantThrough),
|
|
160
|
+
geometry,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
export const RECORD_MAPPERS = { endmill: endmillRecord };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One Harvey Tool product page -> one row per orderable part.
|
|
3
|
+
*
|
|
4
|
+
* The page is server-rendered HTML with its variant data inlined as a
|
|
5
|
+
* JavaScript literal; `docs/HARVEY_PRODUCT_TABLE.md` records how that was found
|
|
6
|
+
* and what was tried first. This module is the join between the two halves — a
|
|
7
|
+
* `<thead>` in the DOM and a `tableData<N>` in a `<script>` — plus the one
|
|
8
|
+
* structural thing Harvey's table does that no other vendor here does.
|
|
9
|
+
*
|
|
10
|
+
* ## One HTML row is up to nine orderable parts
|
|
11
|
+
*
|
|
12
|
+
* The table is a matrix. The `a*` cells are the geometry, shared; after them
|
|
13
|
+
* comes a coating × flute grid where each coating is a `colspan`-ed header and
|
|
14
|
+
* every non-empty cell under it is a different tool number at the same geometry.
|
|
15
|
+
* **5,033 HTML rows become 12,799 CSV rows**, and that explosion is the whole
|
|
16
|
+
* risk in this adapter: a coating or flute count read off the wrong column
|
|
17
|
+
* produces a CSV that is the right length and wrong throughout.
|
|
18
|
+
*
|
|
19
|
+
* ## Harvey checks it for us, 5,033 times per scrape
|
|
20
|
+
*
|
|
21
|
+
* Every row carries an `atc` cell whose `j` is the add-to-cart payload, and that
|
|
22
|
+
* payload independently lists every tool number on the row **in the same order
|
|
23
|
+
* as the non-empty tool-number cells**:
|
|
24
|
+
*
|
|
25
|
+
* ```json
|
|
26
|
+
* [{"T":"690508","C":"690508","Q":"1"},{"T":"679608","C":"679608","Q":"1"}]
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* So the explosion is checked against the vendor's own list on every row rather
|
|
30
|
+
* than once against a hand count, and a grid that has shifted by one column
|
|
31
|
+
* cannot pass. That check is {@link checkCartPayload}, and it is the reason the
|
|
32
|
+
* declared `rows` counts in `families/harvey.ts` being seeded rather than
|
|
33
|
+
* hand-counted is survivable.
|
|
34
|
+
*
|
|
35
|
+
* ## Two columns are synthesised, and neither has a cell to copy
|
|
36
|
+
*
|
|
37
|
+
* `Coating` is a fact about which *header* a tool number sits under, and
|
|
38
|
+
* `FLUTES` is too on the matrix tables. Both are encoded in column position and
|
|
39
|
+
* nowhere else, which is why they are built here rather than lifted, and why the
|
|
40
|
+
* coating vocabulary is a closed list in `lexicon.ts`.
|
|
41
|
+
*
|
|
42
|
+
* `FLUTES` is one column either way — synthesised from the sub-label on a matrix
|
|
43
|
+
* table and lifted from the row's own column on a `TOOL #` one — so the vendor's
|
|
44
|
+
* `FLUTES` column is taken out of the geometry list rather than written twice.
|
|
45
|
+
*
|
|
46
|
+
* Everything else in the CSV is the vendor's own display string, verbatim —
|
|
47
|
+
* `.250 (1/4)` reaches the file as `.250 (1/4)`. `value.ts` is what resolves one
|
|
48
|
+
* when a record is built.
|
|
49
|
+
*/
|
|
50
|
+
import { type UnitSystem } from '../../conventions.js';
|
|
51
|
+
import type { Fetcher } from '../../fetch.js';
|
|
52
|
+
import { type ScrapeResult, type Warn } from '../../scrape.js';
|
|
53
|
+
import { type HeaderColumn } from './header.js';
|
|
54
|
+
export declare const BASE = "https://www.harveytool.com";
|
|
55
|
+
/** `cols1`..`cols10` and `tableData1`..`tableData10` exist on every page. */
|
|
56
|
+
export declare const MAX_TABLES = 10;
|
|
57
|
+
/** The CSV columns this adapter synthesises or lifts from the page itself. */
|
|
58
|
+
export declare const TOOL_NUMBER_COLUMN = "Tool #";
|
|
59
|
+
export declare const COATING_COLUMN = "Coating";
|
|
60
|
+
export declare const FLUTES_COLUMN = "FLUTES";
|
|
61
|
+
export declare const PRICE_COLUMN = "PRICE_USD";
|
|
62
|
+
/**
|
|
63
|
+
* The suffix an unlabelled annotation column takes, after the column it
|
|
64
|
+
* annotates.
|
|
65
|
+
*
|
|
66
|
+
* Harvey gives these no header at all — see `docs/HARVEY_PRODUCT_TABLE.md` §5.3
|
|
67
|
+
* — so a name has to be supplied, and naming one for what it annotates is the
|
|
68
|
+
* only honest option available. Dropping them instead would lose a published
|
|
69
|
+
* column; leaving them unnamed would collide with the column they follow.
|
|
70
|
+
*/
|
|
71
|
+
export declare const RATIO_SUFFIX = "RATIO";
|
|
72
|
+
/**
|
|
73
|
+
* The suffix an unlabelled column takes when it is *not* a ratio.
|
|
74
|
+
*
|
|
75
|
+
* Four such columns exist, on two families, and Harvey renders them in white
|
|
76
|
+
* text: two carry the badge `LONG` on a handful of rows and two are entirely
|
|
77
|
+
* empty. Naming them `RATIO` would be a claim about their contents that is
|
|
78
|
+
* false, so which suffix a column gets is decided from the whole page's data
|
|
79
|
+
* rather than from its header — see {@link ratioColumnKeys}.
|
|
80
|
+
*/
|
|
81
|
+
export declare const NOTE_SUFFIX = "NOTE";
|
|
82
|
+
/** One cell of a `tableData<N>` row. `d` is the display HTML — the value. */
|
|
83
|
+
interface Cell {
|
|
84
|
+
d?: string | null;
|
|
85
|
+
j?: string | null;
|
|
86
|
+
}
|
|
87
|
+
type DataRow = Record<string, Cell | undefined>;
|
|
88
|
+
/** One geometry column: which key holds it, and what the CSV calls it. */
|
|
89
|
+
interface GeometryColumn {
|
|
90
|
+
key: string;
|
|
91
|
+
/** The CSV column, unit suffix already applied where the label carries one. */
|
|
92
|
+
column: string;
|
|
93
|
+
/** Harvey's own top label, before disambiguation. */
|
|
94
|
+
label: string;
|
|
95
|
+
}
|
|
96
|
+
/** One coating group: its tool-number columns, and the price they share. */
|
|
97
|
+
interface CoatingGroup {
|
|
98
|
+
coating: string;
|
|
99
|
+
parts: {
|
|
100
|
+
key: string;
|
|
101
|
+
flutes: number | null;
|
|
102
|
+
}[];
|
|
103
|
+
/** Every group on all 80 tables has exactly one, and it is last. */
|
|
104
|
+
priceKey: string;
|
|
105
|
+
}
|
|
106
|
+
/** One table's shape, worked out once and reused for all of its rows. */
|
|
107
|
+
interface TablePlan {
|
|
108
|
+
geometry: GeometryColumn[];
|
|
109
|
+
/** The `FLUTES` column's key, where the table publishes one. */
|
|
110
|
+
flutesKey: string | null;
|
|
111
|
+
/** True where any group states a flute count in its own sub-label. */
|
|
112
|
+
matrix: boolean;
|
|
113
|
+
groups: CoatingGroup[];
|
|
114
|
+
}
|
|
115
|
+
/** `<a href="...">14916</a>` -> `14916`. These cells carry no entities. */
|
|
116
|
+
export declare function cellText(html: string | null | undefined): string;
|
|
117
|
+
/** `$148.40 ` -> `148.40`. The column already says which currency. */
|
|
118
|
+
export declare function priceOf(html: string | null | undefined): string;
|
|
119
|
+
/** Read one table's plan off its `cols<N>` keys and its flattened header. */
|
|
120
|
+
export declare function planTable(family: string, keys: string[], header: HeaderColumn[], unit: UnitSystem, options?: {
|
|
121
|
+
warn?: Warn;
|
|
122
|
+
ratioKeys?: ReadonlySet<string>;
|
|
123
|
+
}): TablePlan;
|
|
124
|
+
/** One entry of a row's add-to-cart payload. */
|
|
125
|
+
export interface CartEntry {
|
|
126
|
+
/** The part number with no footnote marker — what the CSV records. */
|
|
127
|
+
number: string;
|
|
128
|
+
/** The same string as printed in the cell, footnote marker and all. */
|
|
129
|
+
printed: string;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Every part the row's own add-to-cart payload lists, in cell order.
|
|
133
|
+
*
|
|
134
|
+
* `T` and `C` differ on the 36 parts whose printed number carries a footnote
|
|
135
|
+
* marker: `C` keeps it, `T` does not. Both are read, and each does a different
|
|
136
|
+
* job — `C` is what the cell says and is therefore what the cell can be checked
|
|
137
|
+
* against, `T` is Harvey's own clean part number and is therefore what the CSV
|
|
138
|
+
* records. Deriving the second from the first with a regex would work today and
|
|
139
|
+
* be this package's guess rather than the vendor's statement.
|
|
140
|
+
*
|
|
141
|
+
* `Q` is `"1"` on all 12,799 entries and is not read.
|
|
142
|
+
*/
|
|
143
|
+
export declare function cartEntries(family: string, row: DataRow): CartEntry[];
|
|
144
|
+
/** Refuse an explosion the vendor's own payload does not agree with. */
|
|
145
|
+
export declare function checkCartPayload(family: string, printed: string[], cart: CartEntry[]): void;
|
|
146
|
+
/**
|
|
147
|
+
* The unlabelled columns of a page whose cells are all ratio annotations.
|
|
148
|
+
*
|
|
149
|
+
* Decided across the whole page rather than per table, because the tables merge
|
|
150
|
+
* into one CSV and a column cannot be named two things. `EndMill-006` is why: a
|
|
151
|
+
* column is `LONG` on four rows of its first table and empty throughout its
|
|
152
|
+
* second.
|
|
153
|
+
*/
|
|
154
|
+
export declare function ratioColumnKeys(tables: {
|
|
155
|
+
keys: string[];
|
|
156
|
+
rows: DataRow[];
|
|
157
|
+
header: HeaderColumn[];
|
|
158
|
+
}[]): Set<string>;
|
|
159
|
+
/** Options a page scrape accepts. */
|
|
160
|
+
export interface ScrapeOptions {
|
|
161
|
+
/** The unit system the family is published in. Decides every `_mm`/`_in`. */
|
|
162
|
+
unit: UnitSystem;
|
|
163
|
+
warn?: Warn;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* One product page's HTML -> one row per orderable part.
|
|
167
|
+
*
|
|
168
|
+
* Pure, and separate from {@link scrapeProduct} so the whole matrix explosion is
|
|
169
|
+
* testable from a fixture string with no fetcher anywhere near it.
|
|
170
|
+
*
|
|
171
|
+
* A page's tables differ only in published tolerance — which is dropped — so
|
|
172
|
+
* they merge into one CSV. Their column labels are required to agree, because
|
|
173
|
+
* two tables that no longer publish the same columns is a page that has been
|
|
174
|
+
* restructured, and merging them would interleave two different geometries under
|
|
175
|
+
* one header.
|
|
176
|
+
*/
|
|
177
|
+
export declare function parseProductPage(html: string, source: string, options: ScrapeOptions): ScrapeResult;
|
|
178
|
+
/** One product page's URL. */
|
|
179
|
+
export declare function productUrl(path: string): string;
|
|
180
|
+
/**
|
|
181
|
+
* Scrape one product page.
|
|
182
|
+
*
|
|
183
|
+
* The one network call in this module, and the seam every test replaces —
|
|
184
|
+
* everything below it parses a string.
|
|
185
|
+
*/
|
|
186
|
+
export declare function scrapeProduct(fetcher: Fetcher, path: string, options: ScrapeOptions): Promise<ScrapeResult>;
|
|
187
|
+
export {};
|