@toolpath/tool-scraper 2.4.0 → 2.5.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 CHANGED
@@ -69,6 +69,7 @@ version.
69
69
  export TOOLPATH_SCRAPE_ROOT=~/toolpath-scrapes # default: ./scrape-out, gitignored
70
70
 
71
71
  toolpath-scrape kennametal 100003658 "$TOOLPATH_SCRAPE_ROOT/kennametal/csv/godrill_3xd_metric.csv"
72
+ toolpath-scrape kennametal --collets # every ER collet family, and which CSV claims it
72
73
  toolpath-scrape materials godrill_3xd_metric.csv
73
74
  toolpath-scrape regofix holders "$TOOLPATH_SCRAPE_ROOT/regofix/csv/regofix_bt30_pg_holders.csv"
74
75
  toolpath-scrape destinytool "$TOOLPATH_SCRAPE_ROOT/destinytool/csv/destinytool_end_mills_inch.csv"
@@ -757,15 +757,169 @@ export declare const HOLDER_FAMILIES: {
757
757
  };
758
758
  };
759
759
  };
760
+ /**
761
+ * ER collets, one entry per vendor family.
762
+ *
763
+ * **One entry per vendor family and not one per style.** Kennametal codes the
764
+ * coolant-through line as twelve families — one per series per unit — and their
765
+ * column shapes genuinely differ: the inch half publishes `LF` and `L`, the
766
+ * metric half publishes `L9` instead, and only `109321468` publishes both.
767
+ * Merging them into two CSVs would also destroy `ToolholdingDefinition.rows`,
768
+ * which is a per-family restatement and the only independent check that a
769
+ * re-scrape did not lose parts.
770
+ *
771
+ * Every `familyCode` here came out of `kennametal --collets`, which is what
772
+ * `vendors/kennametal/catalog.ts` exists for. The row counts are that walk's
773
+ * own per-family totals, and each category's parts sum to them exactly —
774
+ * 117 + 107 = 224 standard, 75 + 74 = 149 coolant-through, 47 + 49 = 96 tap.
775
+ *
776
+ * ## The four kit families are deliberately absent
777
+ *
778
+ * `100000428`, `100000425`, `109433662` and `109433658` — 26 parts — publish
779
+ * `Kit Series`, `Number-Kit Items`, `Dimension Range-Kit Items` and
780
+ * `Incremental Division-Kit Items`, and no `D1`, no capacity and no length. A
781
+ * kit is a purchasing unit whose contents are already the per-part families
782
+ * below, and minting one into a `holding.ColletRecord` would mean deriving a
783
+ * capacity band from a range string — authoring tool data, which this package
784
+ * does not do. `kennametal --collets` lists them every run, so they stay
785
+ * visible rather than forgotten.
786
+ *
787
+ * ## ER8 fits nothing in this catalog
788
+ *
789
+ * Nine of the 110 standard metric collets are ER8, and no BT30 ER8 adapter is
790
+ * configured. They are scraped anyway: `holding.HolderRecord.colletSeries`
791
+ * states the direction — a collet nothing takes costs an option, and a collet
792
+ * offered for a holder it does not fit costs a machinist a purchase.
793
+ */
760
794
  export declare const COLLET_FAMILIES: {
761
795
  readonly 'er_standard_collets_metric.csv': {
762
796
  readonly catalogName: "Kennametal ER Standard Collets Metric";
763
797
  readonly rows: 110;
798
+ readonly familyCode: "100000478";
764
799
  readonly facts: {
765
800
  readonly style: {
766
801
  readonly value: "er-standard";
767
802
  readonly source: "vendor-stated";
768
- readonly cite: "the family is Kennametal's plain ER collet line";
803
+ readonly cite: "the vendor's category is 'ER Collets / ER Standard Collets'; the family publishes a CCCN-CCCX capacity band and no square";
804
+ };
805
+ readonly unit: {
806
+ readonly value: "millimeters";
807
+ readonly source: "vendor-stated";
808
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
809
+ };
810
+ };
811
+ };
812
+ readonly 'er_standard_collets_inch.csv': {
813
+ readonly catalogName: "Kennametal ER Standard Collets Inch";
814
+ readonly rows: 98;
815
+ readonly familyCode: "100000479";
816
+ readonly facts: {
817
+ readonly style: {
818
+ readonly value: "er-standard";
819
+ readonly source: "vendor-stated";
820
+ readonly cite: "the vendor's category is 'ER Collets / ER Standard Collets'; the family publishes a CCCN-CCCX capacity band and no square";
821
+ };
822
+ readonly unit: {
823
+ readonly value: "inches";
824
+ readonly source: "vendor-stated";
825
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
826
+ };
827
+ };
828
+ };
829
+ readonly 'er11_collets_coolant_through_metric.csv': {
830
+ readonly catalogName: "Kennametal ER11 Collets Coolant-Through Metric";
831
+ readonly rows: 5;
832
+ readonly familyCode: "109333979";
833
+ readonly facts: {
834
+ readonly style: {
835
+ readonly value: "er-sealed";
836
+ readonly source: "vendor-stated";
837
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
838
+ };
839
+ readonly unit: {
840
+ readonly value: "millimeters";
841
+ readonly source: "vendor-stated";
842
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
843
+ };
844
+ };
845
+ };
846
+ readonly 'er16_collets_coolant_through_metric.csv': {
847
+ readonly catalogName: "Kennametal ER16 Collets Coolant-Through Metric";
848
+ readonly rows: 8;
849
+ readonly familyCode: "109333976";
850
+ readonly facts: {
851
+ readonly style: {
852
+ readonly value: "er-sealed";
853
+ readonly source: "vendor-stated";
854
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
855
+ };
856
+ readonly unit: {
857
+ readonly value: "millimeters";
858
+ readonly source: "vendor-stated";
859
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
860
+ };
861
+ };
862
+ };
863
+ readonly 'er20_collets_coolant_through_metric.csv': {
864
+ readonly catalogName: "Kennametal ER20 Collets Coolant-Through Metric";
865
+ readonly rows: 11;
866
+ readonly familyCode: "109333973";
867
+ readonly facts: {
868
+ readonly style: {
869
+ readonly value: "er-sealed";
870
+ readonly source: "vendor-stated";
871
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
872
+ };
873
+ readonly unit: {
874
+ readonly value: "millimeters";
875
+ readonly source: "vendor-stated";
876
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
877
+ };
878
+ };
879
+ };
880
+ readonly 'er25_collets_coolant_through_metric.csv': {
881
+ readonly catalogName: "Kennametal ER25 Collets Coolant-Through Metric";
882
+ readonly rows: 11;
883
+ readonly familyCode: "109333627";
884
+ readonly facts: {
885
+ readonly style: {
886
+ readonly value: "er-sealed";
887
+ readonly source: "vendor-stated";
888
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
889
+ };
890
+ readonly unit: {
891
+ readonly value: "millimeters";
892
+ readonly source: "vendor-stated";
893
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
894
+ };
895
+ };
896
+ };
897
+ readonly 'er32_collets_coolant_through_metric.csv': {
898
+ readonly catalogName: "Kennametal ER32 Collets Coolant-Through Metric";
899
+ readonly rows: 15;
900
+ readonly familyCode: "109333626";
901
+ readonly facts: {
902
+ readonly style: {
903
+ readonly value: "er-sealed";
904
+ readonly source: "vendor-stated";
905
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
906
+ };
907
+ readonly unit: {
908
+ readonly value: "millimeters";
909
+ readonly source: "vendor-stated";
910
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
911
+ };
912
+ };
913
+ };
914
+ readonly 'er40_collets_coolant_through_metric.csv': {
915
+ readonly catalogName: "Kennametal ER40 Collets Coolant-Through Metric";
916
+ readonly rows: 19;
917
+ readonly familyCode: "109321468";
918
+ readonly facts: {
919
+ readonly style: {
920
+ readonly value: "er-sealed";
921
+ readonly source: "vendor-stated";
922
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
769
923
  };
770
924
  readonly unit: {
771
925
  readonly value: "millimeters";
@@ -774,14 +928,32 @@ export declare const COLLET_FAMILIES: {
774
928
  };
775
929
  };
776
930
  };
931
+ readonly 'er11_collets_coolant_through_inch.csv': {
932
+ readonly catalogName: "Kennametal ER11 Collets Coolant-Through Inch";
933
+ readonly rows: 4;
934
+ readonly familyCode: "109333978";
935
+ readonly facts: {
936
+ readonly style: {
937
+ readonly value: "er-sealed";
938
+ readonly source: "vendor-stated";
939
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
940
+ };
941
+ readonly unit: {
942
+ readonly value: "inches";
943
+ readonly source: "vendor-stated";
944
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
945
+ };
946
+ };
947
+ };
777
948
  readonly 'er16_collets_coolant_through_inch.csv': {
778
949
  readonly catalogName: "Kennametal ER16 Collets Coolant-Through Inch";
779
950
  readonly rows: 10;
951
+ readonly familyCode: "109333975";
780
952
  readonly facts: {
781
953
  readonly style: {
782
954
  readonly value: "er-sealed";
783
955
  readonly source: "vendor-stated";
784
- readonly cite: "CCCX == CCCN == D1 on every row; Kennametal specs an H6 shank, so it clamps one exact size";
956
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
785
957
  };
786
958
  readonly unit: {
787
959
  readonly value: "inches";
@@ -790,4 +962,106 @@ export declare const COLLET_FAMILIES: {
790
962
  };
791
963
  };
792
964
  };
965
+ readonly 'er20_collets_coolant_through_inch.csv': {
966
+ readonly catalogName: "Kennametal ER20 Collets Coolant-Through Inch";
967
+ readonly rows: 13;
968
+ readonly familyCode: "109333974";
969
+ readonly facts: {
970
+ readonly style: {
971
+ readonly value: "er-sealed";
972
+ readonly source: "vendor-stated";
973
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
974
+ };
975
+ readonly unit: {
976
+ readonly value: "inches";
977
+ readonly source: "vendor-stated";
978
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
979
+ };
980
+ };
981
+ };
982
+ readonly 'er25_collets_coolant_through_inch.csv': {
983
+ readonly catalogName: "Kennametal ER25 Collets Coolant-Through Inch";
984
+ readonly rows: 14;
985
+ readonly familyCode: "109333628";
986
+ readonly facts: {
987
+ readonly style: {
988
+ readonly value: "er-sealed";
989
+ readonly source: "vendor-stated";
990
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
991
+ };
992
+ readonly unit: {
993
+ readonly value: "inches";
994
+ readonly source: "vendor-stated";
995
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
996
+ };
997
+ };
998
+ };
999
+ readonly 'er32_collets_coolant_through_inch.csv': {
1000
+ readonly catalogName: "Kennametal ER32 Collets Coolant-Through Inch";
1001
+ readonly rows: 17;
1002
+ readonly familyCode: "109333625";
1003
+ readonly facts: {
1004
+ readonly style: {
1005
+ readonly value: "er-sealed";
1006
+ readonly source: "vendor-stated";
1007
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
1008
+ };
1009
+ readonly unit: {
1010
+ readonly value: "inches";
1011
+ readonly source: "vendor-stated";
1012
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
1013
+ };
1014
+ };
1015
+ };
1016
+ readonly 'er40_collets_coolant_through_inch.csv': {
1017
+ readonly catalogName: "Kennametal ER40 Collets Coolant-Through Inch";
1018
+ readonly rows: 12;
1019
+ readonly familyCode: "109321469";
1020
+ readonly facts: {
1021
+ readonly style: {
1022
+ readonly value: "er-sealed";
1023
+ readonly source: "vendor-stated";
1024
+ readonly cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size";
1025
+ };
1026
+ readonly unit: {
1027
+ readonly value: "inches";
1028
+ readonly source: "vendor-stated";
1029
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
1030
+ };
1031
+ };
1032
+ };
1033
+ readonly 'er_tap_collets_ansi.csv': {
1034
+ readonly catalogName: "Kennametal ER Standard Tap Collets ANSI";
1035
+ readonly rows: 47;
1036
+ readonly familyCode: "100000434";
1037
+ readonly facts: {
1038
+ readonly style: {
1039
+ readonly value: "er-tap";
1040
+ readonly source: "vendor-stated";
1041
+ readonly cite: "the vendor's category is 'ER Collets / ER Tap Collets'; every row publishes an S10 square size and no capacity band";
1042
+ };
1043
+ readonly unit: {
1044
+ readonly value: "inches";
1045
+ readonly source: "vendor-stated";
1046
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
1047
+ };
1048
+ };
1049
+ };
1050
+ readonly 'er_tap_collets_din_iso.csv': {
1051
+ readonly catalogName: "Kennametal ER Standard Tap Collets DIN and ISO";
1052
+ readonly rows: 49;
1053
+ readonly familyCode: "100000435";
1054
+ readonly facts: {
1055
+ readonly style: {
1056
+ readonly value: "er-tap";
1057
+ readonly source: "vendor-stated";
1058
+ readonly cite: "the vendor's category is 'ER Collets / ER Tap Collets'; every row publishes an S10 square size and no capacity band";
1059
+ };
1060
+ readonly unit: {
1061
+ readonly value: "millimeters";
1062
+ readonly source: "vendor-stated";
1063
+ readonly cite: "the family is titled and catalogued in this system; both unit columns are usually published, so this decides which is displayed";
1064
+ };
1065
+ };
1066
+ };
793
1067
  };
@@ -139,6 +139,51 @@ const ER_COLLET_CHUCK = {
139
139
  source: 'vendor-stated',
140
140
  cite: 'breadcrumb ".../ER Collet Chucks/ER(tm) Collet Adapter -BT30"',
141
141
  };
142
+ /**
143
+ * The three ER collet styles, each stated once for the families that share it.
144
+ *
145
+ * `style` is the finer axis beside a holder's `clamping`, and here it is what
146
+ * tells a consumer which kind of collet it is holding. All three are
147
+ * Kennametal's own product lines, read off the collet category tree — see
148
+ * `vendors/kennametal/catalog.ts`, which is what enumerates them.
149
+ */
150
+ const ER_STANDARD = {
151
+ value: 'er-standard',
152
+ source: 'vendor-stated',
153
+ cite: "the vendor's category is 'ER Collets / ER Standard Collets'; the family publishes a CCCN-CCCX capacity band and no square",
154
+ };
155
+ /**
156
+ * Sealed coolant-through collets. `style` separates them from the standard ones
157
+ * because they behave differently in a way the numbers show but a label should
158
+ * not hide: `CCCX == CCCN` on every row, so each clamps one exact size rather
159
+ * than a 1 mm band.
160
+ *
161
+ * No special case is needed anywhere — a zero-width range is still a range —
162
+ * but a user choosing one deserves to be told which kind it is.
163
+ *
164
+ * **The ER40 inch line is designated larger than it clamps**, and that is the
165
+ * vendor's own statement rather than a rounding: `40ERSS1000` is named for one
166
+ * inch and publishes a capacity of 0.9938 in / 25.243 mm against a `D1` of
167
+ * 1.0 in / 25.4 mm — both figures agree across both unit columns. Ten of its
168
+ * twelve rows do this, up to 0.193 mm on `40ERSS0812`. `holding.NOMINAL_SLACK`
169
+ * is the gate that lets a designation sit outside the size it measures, and
170
+ * those rows are where its bound comes from.
171
+ */
172
+ const ER_SEALED = {
173
+ value: 'er-sealed',
174
+ source: 'vendor-stated',
175
+ cite: "the vendor's category is 'ER Collets / ER Coolant Through Collets'; CCCX == CCCN == D1 on all 139 rows of the twelve families, and Kennametal specs an H6 shank, so each clamps one exact size",
176
+ };
177
+ /**
178
+ * Tap collets. The square drive is the fact that matters and it is scraped
179
+ * rather than declared — `holding.ColletRecord.squareSize` — so this is the
180
+ * label and not the discriminant.
181
+ */
182
+ const ER_TAP = {
183
+ value: 'er-tap',
184
+ source: 'vendor-stated',
185
+ cite: "the vendor's category is 'ER Collets / ER Tap Collets'; every row publishes an S10 square size and no capacity band",
186
+ };
142
187
  const METRIC_CATALOG = {
143
188
  value: 'millimeters',
144
189
  source: 'vendor-stated',
@@ -673,35 +718,152 @@ export const HOLDER_FAMILIES = {
673
718
  },
674
719
  },
675
720
  };
721
+ /**
722
+ * ER collets, one entry per vendor family.
723
+ *
724
+ * **One entry per vendor family and not one per style.** Kennametal codes the
725
+ * coolant-through line as twelve families — one per series per unit — and their
726
+ * column shapes genuinely differ: the inch half publishes `LF` and `L`, the
727
+ * metric half publishes `L9` instead, and only `109321468` publishes both.
728
+ * Merging them into two CSVs would also destroy `ToolholdingDefinition.rows`,
729
+ * which is a per-family restatement and the only independent check that a
730
+ * re-scrape did not lose parts.
731
+ *
732
+ * Every `familyCode` here came out of `kennametal --collets`, which is what
733
+ * `vendors/kennametal/catalog.ts` exists for. The row counts are that walk's
734
+ * own per-family totals, and each category's parts sum to them exactly —
735
+ * 117 + 107 = 224 standard, 75 + 74 = 149 coolant-through, 47 + 49 = 96 tap.
736
+ *
737
+ * ## The four kit families are deliberately absent
738
+ *
739
+ * `100000428`, `100000425`, `109433662` and `109433658` — 26 parts — publish
740
+ * `Kit Series`, `Number-Kit Items`, `Dimension Range-Kit Items` and
741
+ * `Incremental Division-Kit Items`, and no `D1`, no capacity and no length. A
742
+ * kit is a purchasing unit whose contents are already the per-part families
743
+ * below, and minting one into a `holding.ColletRecord` would mean deriving a
744
+ * capacity band from a range string — authoring tool data, which this package
745
+ * does not do. `kennametal --collets` lists them every run, so they stay
746
+ * visible rather than forgotten.
747
+ *
748
+ * ## ER8 fits nothing in this catalog
749
+ *
750
+ * Nine of the 110 standard metric collets are ER8, and no BT30 ER8 adapter is
751
+ * configured. They are scraped anyway: `holding.HolderRecord.colletSeries`
752
+ * states the direction — a collet nothing takes costs an option, and a collet
753
+ * offered for a holder it does not fit costs a machinist a purchase.
754
+ */
676
755
  export const COLLET_FAMILIES = {
756
+ // ── ER standard collets (JG 2026-09-08) ──────────────────────────────────
677
757
  'er_standard_collets_metric.csv': {
678
758
  catalogName: 'Kennametal ER Standard Collets Metric',
679
759
  rows: 110,
680
- facts: {
681
- style: {
682
- value: 'er-standard',
683
- source: 'vendor-stated',
684
- cite: "the family is Kennametal's plain ER collet line",
685
- },
686
- unit: METRIC_CATALOG,
687
- },
760
+ familyCode: '100000478',
761
+ facts: { style: ER_STANDARD, unit: METRIC_CATALOG },
762
+ },
763
+ 'er_standard_collets_inch.csv': {
764
+ catalogName: 'Kennametal ER Standard Collets Inch',
765
+ rows: 98,
766
+ familyCode: '100000479',
767
+ facts: { style: ER_STANDARD, unit: INCH_CATALOG },
768
+ },
769
+ // ── ER coolant-through collets (JG 2026-09-08) ───────────────────────────
770
+ // Twelve families, one per series per unit. `ER8` has no coolant-through
771
+ // line; `ER11` upward do.
772
+ 'er11_collets_coolant_through_metric.csv': {
773
+ catalogName: 'Kennametal ER11 Collets Coolant-Through Metric',
774
+ rows: 5,
775
+ familyCode: '109333979',
776
+ facts: { style: ER_SEALED, unit: METRIC_CATALOG },
777
+ },
778
+ 'er16_collets_coolant_through_metric.csv': {
779
+ catalogName: 'Kennametal ER16 Collets Coolant-Through Metric',
780
+ rows: 8,
781
+ familyCode: '109333976',
782
+ facts: { style: ER_SEALED, unit: METRIC_CATALOG },
783
+ },
784
+ 'er20_collets_coolant_through_metric.csv': {
785
+ catalogName: 'Kennametal ER20 Collets Coolant-Through Metric',
786
+ rows: 11,
787
+ familyCode: '109333973',
788
+ facts: { style: ER_SEALED, unit: METRIC_CATALOG },
789
+ },
790
+ 'er25_collets_coolant_through_metric.csv': {
791
+ catalogName: 'Kennametal ER25 Collets Coolant-Through Metric',
792
+ rows: 11,
793
+ familyCode: '109333627',
794
+ facts: { style: ER_SEALED, unit: METRIC_CATALOG },
795
+ },
796
+ 'er32_collets_coolant_through_metric.csv': {
797
+ catalogName: 'Kennametal ER32 Collets Coolant-Through Metric',
798
+ rows: 15,
799
+ familyCode: '109333626',
800
+ facts: { style: ER_SEALED, unit: METRIC_CATALOG },
801
+ },
802
+ 'er40_collets_coolant_through_metric.csv': {
803
+ catalogName: 'Kennametal ER40 Collets Coolant-Through Metric',
804
+ rows: 19,
805
+ familyCode: '109321468',
806
+ facts: { style: ER_SEALED, unit: METRIC_CATALOG },
807
+ },
808
+ 'er11_collets_coolant_through_inch.csv': {
809
+ catalogName: 'Kennametal ER11 Collets Coolant-Through Inch',
810
+ rows: 4,
811
+ familyCode: '109333978',
812
+ facts: { style: ER_SEALED, unit: INCH_CATALOG },
688
813
  },
689
- // Sealed coolant-through collets. `style` separates them from the standard
690
- // ones because they behave differently in a way the numbers alone show but
691
- // a label should not hide: CCCX == CCCN on every row, so each clamps one
692
- // exact size (Kennametal specs H6 shank tolerance) rather than a 1 mm band.
693
- // No special case is needed anywhere — a zero-width range is still a range
694
- // — but a user choosing one deserves to be told which kind it is.
695
814
  'er16_collets_coolant_through_inch.csv': {
696
815
  catalogName: 'Kennametal ER16 Collets Coolant-Through Inch',
697
816
  rows: 10,
698
- facts: {
699
- style: {
700
- value: 'er-sealed',
701
- source: 'vendor-stated',
702
- cite: 'CCCX == CCCN == D1 on every row; Kennametal specs an H6 shank, so it clamps one exact size',
703
- },
704
- unit: INCH_CATALOG,
705
- },
817
+ familyCode: '109333975',
818
+ facts: { style: ER_SEALED, unit: INCH_CATALOG },
819
+ },
820
+ 'er20_collets_coolant_through_inch.csv': {
821
+ catalogName: 'Kennametal ER20 Collets Coolant-Through Inch',
822
+ rows: 13,
823
+ familyCode: '109333974',
824
+ facts: { style: ER_SEALED, unit: INCH_CATALOG },
825
+ },
826
+ 'er25_collets_coolant_through_inch.csv': {
827
+ catalogName: 'Kennametal ER25 Collets Coolant-Through Inch',
828
+ rows: 14,
829
+ familyCode: '109333628',
830
+ facts: { style: ER_SEALED, unit: INCH_CATALOG },
831
+ },
832
+ 'er32_collets_coolant_through_inch.csv': {
833
+ catalogName: 'Kennametal ER32 Collets Coolant-Through Inch',
834
+ rows: 17,
835
+ familyCode: '109333625',
836
+ facts: { style: ER_SEALED, unit: INCH_CATALOG },
837
+ },
838
+ 'er40_collets_coolant_through_inch.csv': {
839
+ catalogName: 'Kennametal ER40 Collets Coolant-Through Inch',
840
+ rows: 12,
841
+ familyCode: '109321469',
842
+ facts: { style: ER_SEALED, unit: INCH_CATALOG },
843
+ },
844
+ // ── ER tap collets (JG 2026-09-08) ───────────────────────────────────────
845
+ // Two families, split by thread system rather than by series: each holds
846
+ // ER16 through ER40. Neither publishes `CCCN`/`CCCX` at all — see
847
+ // `vendors/kennametal/holding.ts`'s `colletCapacity` for why that is a
848
+ // zero-width band at `D1` rather than an incomplete row.
849
+ //
850
+ // **`unit` is decided by which column carries the exact value**, the same
851
+ // test the TT HPV split above uses. `100000434` publishes `16ERTC10`'s `D1`
852
+ // as 0.194 in and 4.928 mm — 0.194 in is the ANSI shank of a #10 tap, and
853
+ // 4.928 is it rounded — while `100000435` publishes `16ERTC045034M`'s as
854
+ // 4.5 mm and 0.1772 in. So the ANSI family is inch-native and the DIN/ISO
855
+ // one metric-native, which also decides which of the two `Tap Range` columns
856
+ // a record carries: `#14 & 1/4` on one, `M6 & M6.3` on the other.
857
+ 'er_tap_collets_ansi.csv': {
858
+ catalogName: 'Kennametal ER Standard Tap Collets ANSI',
859
+ rows: 47,
860
+ familyCode: '100000434',
861
+ facts: { style: ER_TAP, unit: INCH_CATALOG },
862
+ },
863
+ 'er_tap_collets_din_iso.csv': {
864
+ catalogName: 'Kennametal ER Standard Tap Collets DIN and ISO',
865
+ rows: 49,
866
+ familyCode: '100000435',
867
+ facts: { style: ER_TAP, unit: METRIC_CATALOG },
706
868
  },
707
869
  };
package/dist/family.d.ts CHANGED
@@ -128,6 +128,18 @@ export interface FamilyDefinition extends CommonDefinition {
128
128
  export interface ToolholdingDefinition extends CommonDefinition {
129
129
  /** What a human calls this family. Holders and collets have no `id`. */
130
130
  readonly catalogName: string;
131
+ /**
132
+ * The vendor's own code for the family, where the scrape target is a family
133
+ * page — {@link FamilyDefinition.familyCode}'s counterpart.
134
+ *
135
+ * Absent where the vendor has no such page: REGO-FIX and MariTool families
136
+ * are a set of index filters and name none. It was absent on Kennametal's
137
+ * toolholding too until 2026-09-08, because those families were scraped by
138
+ * hand from a code read off the page at the time — which made a re-scrape a
139
+ * trip back to the browser. Recording it is what makes one re-runnable, and
140
+ * it is what `kennametal --collets` reconciles its walk against.
141
+ */
142
+ readonly familyCode?: string;
131
143
  }
132
144
  /** A cutting-tool family after {@link FamilyDefinition} has been validated. */
133
145
  export type BoundFamily = Omit<FamilyDefinition, 'columns'> & FamilyFacts & {
package/dist/holding.d.ts CHANGED
@@ -211,6 +211,41 @@ export interface ColletRecord extends HoldingIdentity {
211
211
  readonly functionalLength: number | null;
212
212
  /** `L` — overall length. */
213
213
  readonly overallLength: number | null;
214
+ /**
215
+ * `L9` — how deep the clamping bore is, and therefore how much shank the
216
+ * collet actually holds.
217
+ *
218
+ * Twinned in millimetres because it is **compared and not only displayed**:
219
+ * it is `@toolpath/tool-support`'s `Collet.clampLength`, the one input to
220
+ * `maxStickout`, which answers `null` for every collet without it. That is
221
+ * the rule this module states for `bore`, `gaugeLength` and the capacity
222
+ * pair, and this is the fourth dimension to earn it.
223
+ *
224
+ * Not a second overall length. Kennametal's `109321468` publishes both: `L`
225
+ * is 46 mm down all nineteen ER40 rows while `L9` runs 22 / 28 / 46 by size.
226
+ */
227
+ readonly clampingLength: number | null;
228
+ readonly clampingLengthMm: number | null;
229
+ /**
230
+ * The vendor's own designation of the taps this collet is for, verbatim —
231
+ * `"M6 & M6.3"`, `"#14 & 1/4"`.
232
+ *
233
+ * **Text, and deliberately not parsed.** It names two thread designations in
234
+ * the vendor's own words and this package does not author tool data; what a
235
+ * fit rule compares is {@link ColletRecord.clampMin}/{@link ColletRecord.clampMax},
236
+ * which are numbers. `null` on every collet whose vendor publishes none.
237
+ */
238
+ readonly tapRange: string | null;
239
+ /**
240
+ * `S10` — the square drive the bore carries, across flats.
241
+ *
242
+ * The fact that makes a tap collet a tap collet: its bore is not round, so
243
+ * the part it takes is a tap and nothing else. A shank test alone would say
244
+ * yes to an end mill of the same diameter, held by nothing —
245
+ * `@toolpath/tool-support`'s `holderTakesTool` reads this to refuse that.
246
+ * `null` where the vendor publishes no square, which is every round collet.
247
+ */
248
+ readonly squareSize: number | null;
214
249
  }
215
250
  /** Either toolholding record. Narrow on {@link HoldingIdentity.kind}. */
216
251
  export type HoldingRecord = HolderRecord | ColletRecord;
@@ -295,6 +330,29 @@ export declare function millimeters(value: number | null, unit: UnitSystem): num
295
330
  * for a machinist's number and refuses a range rather than summing it.
296
331
  */
297
332
  export declare function dim(row: ScrapedRow, label: string, unit: UnitSystem): number | null;
333
+ /**
334
+ * How far a collet's *designation* may sit from its measured capacity, as a
335
+ * fraction of that capacity.
336
+ *
337
+ * {@link ColletRecord.nominal} is the size the vendor **designates** the collet
338
+ * by; {@link ColletRecord.clampMin}/{@link ColletRecord.clampMax} is what it
339
+ * measures. They are not two readings of one number, and on a sealed collet
340
+ * they are routinely different: Kennametal's `40ERSS1000` is designated 1 inch
341
+ * and clamps 0.9938, `40ERSS0500` is designated 1/2 and clamps 0.4943. Both
342
+ * unit columns agree on both figures — 25.4 against 25.243 mm, 12.7 against
343
+ * 12.556 — so this is the vendor stating an undersized capacity for a collet it
344
+ * names by the fraction, not a cell in the wrong column.
345
+ *
346
+ * **Relative rather than absolute, because the gap scales with the collet.**
347
+ * Across the 443-part collet corpus fourteen rows sit outside their own band.
348
+ * The widest is `40ERSS0812` at 0.193 mm, which is 0.94 % of its 20.4 mm
349
+ * capacity; the widest by fraction is `40ERSS0500` at 1.14 %. The error this
350
+ * must still refuse is a cell in the wrong unit system, which is 96 % out — the
351
+ * shape `16ERSS0312` has, where `D1`'s metric cell holds the inch value. This
352
+ * sits 4.4x above the first and 19x below the second. A row that lands in
353
+ * between is a finding to investigate rather than a number to widen.
354
+ */
355
+ export declare const NOMINAL_SLACK = 0.05;
298
356
  /**
299
357
  * Report where a vendor's own millimetre and inch cells disagree.
300
358
  *
@@ -387,10 +445,10 @@ type HolderFields = Omit<HolderRecord, 'kind' | 'guid' | 'vendor' | 'productLink
387
445
  * mutated one would be reaching back across the seam this type exists to draw.
388
446
  */
389
447
  export declare function holderRecord(fields: HolderFields): HolderRecord;
390
- /** The same, for a collet. REGO-FIX publishes none of these four. */
391
- type OptionalColletFields = 'nominal' | 'bodyDiameter' | 'functionalLength' | 'overallLength';
448
+ /** The same, for a collet. REGO-FIX publishes none of these seven. */
449
+ type OptionalColletFields = 'nominal' | 'bodyDiameter' | 'functionalLength' | 'overallLength' | 'clampingLength' | 'tapRange' | 'squareSize';
392
450
  /** What a mapper supplies to build a collet. */
393
- type ColletFields = Omit<ColletRecord, 'kind' | 'guid' | 'vendor' | 'productLink' | 'clampMinMm' | 'clampMaxMm' | OptionalColletFields> & Partial<Pick<ColletRecord, OptionalColletFields>>;
451
+ type ColletFields = Omit<ColletRecord, 'kind' | 'guid' | 'vendor' | 'productLink' | 'clampMinMm' | 'clampMaxMm' | 'clampingLengthMm' | OptionalColletFields> & Partial<Pick<ColletRecord, OptionalColletFields>>;
394
452
  /** Build a {@link ColletRecord}, on the same terms as {@link holderRecord}. */
395
453
  export declare function colletRecord(fields: ColletFields): ColletRecord;
396
454
  export {};