joycecast commited on
Commit
204120e
·
verified ·
1 Parent(s): be88db8

Upload 2 files

Browse files
Files changed (2) hide show
  1. HTS_list.py +117 -0
  2. hts_validator.py +73 -29
HTS_list.py CHANGED
@@ -203,3 +203,120 @@ Aluminum_primary_HTS_list = [
203
  7616995140, 7616995190,
204
  7612100000, 7612905000, 7613000000, 76141010
205
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  7616995140, 7616995190,
204
  7612100000, 7612905000, 7613000000, 76141010
205
  ]
206
+
207
+ # Computer Parts primary HTS List (4/8 digit = prefix match)
208
+ Computer_parts_HTS_list = [
209
+ # 4-digit prefix matches
210
+ 8471, # 8471 - all subcodes
211
+ 8486, # 8486 - all subcodes
212
+ 8524, # 8524 - all subcodes
213
+ 8542, # 8542 - all subcodes
214
+ # 8-digit codes
215
+ 84733000, # 8473.30
216
+ 85171300, # 8517.13.00
217
+ 85176200, # 8517.62.00
218
+ 85235100, # 8523.51.00
219
+ 85285200, # 8528.52.00
220
+ 85411000, # 8541.10.00
221
+ 85412100, # 8541.21.00
222
+ 85412900, # 8541.29.00
223
+ 85413000, # 8541.30.00
224
+ 85414910, # 8541.49.10
225
+ 85414970, # 8541.49.70
226
+ 85414980, # 8541.49.80
227
+ 85414995, # 8541.49.95
228
+ 85415100, # 8541.51.00
229
+ 85415900, # 8541.59.00
230
+ 85419000, # 8541.90.00
231
+ ]
232
+
233
+ # Auto Parts primary HTS List (4/8 digit = prefix match)
234
+ # Note: 9903.94.05/06 are additional tariff codes, not primary HTS
235
+ Auto_parts_HTS_list = [
236
+ # 4-digit prefix matches
237
+ 8471, # 8471 - all subcodes (shared with computer parts)
238
+ # 8-digit codes
239
+ 40091200, # 4009.12.00 (20 suffix)
240
+ 40092200, # 4009.22.00 (20 suffix)
241
+ 40093200, # 4009.32.00 (20 suffix)
242
+ 40094200, # 4009.42.00 (20 suffix)
243
+ 40111010, # 4011.10.10
244
+ 40111050, # 4011.10.50
245
+ 40112010, # 4011.20.10
246
+ 40121940, # 4012.19.40
247
+ 40121980, # 4012.19.80
248
+ 40122060, # 4012.20.60
249
+ 40131000, # 4013.10.00 (10/20 suffix)
250
+ 40169960, # 4016.99.60 (10 suffix)
251
+ 70072151, # 7007.21.51
252
+ 70091000, # 7009.10.00
253
+ 73201000, # 7320.10 - prefix
254
+ 73202010, # 7320.20.10
255
+ 83012000, # 8301.20.00
256
+ 83021030, # 8302.10.30
257
+ 83023000, # 8302.30 - prefix
258
+ 84073100, # 8407.31.00
259
+ 84073200, # 8407.32 - prefix
260
+ 84073300, # 8407.33 - prefix
261
+ 84073400, # 8407.34 - prefix
262
+ 84082020, # 8408.20.20
263
+ 84099110, # 8409.91.10 (40 suffix)
264
+ 84099910, # 8409.99.10 (40 suffix)
265
+ 84133010, # 8413.30.10
266
+ 84133090, # 8413.30.90
267
+ 84139110, # 8413.91.10
268
+ 84139190, # 8413.91.90 (10 suffix)
269
+ 84143080, # 8414.30.80 (30 suffix)
270
+ 84145930, # 8414.59.30
271
+ 84145965, # 8414.59.65 (40 suffix)
272
+ 84148005, # 8414.80.05
273
+ 84152000, # 8415.20.00
274
+ 84212300, # 8421.23.00
275
+ 84213200, # 8421.32.00
276
+ 84254900, # 8425.49.00
277
+ 84269100, # 8426.91.00
278
+ 84311000, # 8431.10.00 (90 suffix)
279
+ 84821010, # 8482.10.10
280
+ 84821050, # 8482.10.50 (44/48 suffix)
281
+ 84822000, # 8482.20.00 (20/30/40/61/70/81 suffix)
282
+ 84824000, # 8482.40.00
283
+ 84825000, # 8482.50.00
284
+ 84831010, # 8483.10.10 (30 suffix)
285
+ 84831030, # 8483.10.30
286
+ 85013200, # 8501.32 - prefix
287
+ 85013300, # 8501.33 - prefix
288
+ 85013400, # 8501.34 - prefix
289
+ 85014000, # 8501.40 - prefix
290
+ 85015100, # 8501.51 - prefix
291
+ 85015200, # 8501.52 - prefix
292
+ 85071000, # 8507.10 - prefix
293
+ 85076000, # 8507.60 - prefix
294
+ 85079040, # 8507.90.40
295
+ 85079080, # 8507.90.80
296
+ 85111000, # 8511.10.00.00
297
+ 85112000, # 8511.20.00
298
+ 85113000, # 8511.30.00 (40/80 suffix)
299
+ 85114000, # 8511.40.00
300
+ 85115000, # 8511.50.00
301
+ 85118020, # 8511.80.20
302
+ 85118060, # 8511.80.60
303
+ 85119060, # 8511.90.60 (20/40 suffix)
304
+ 85122020, # 8512.20.20
305
+ 85122040, # 8512.20.40
306
+ 85123000, # 8512.30.00
307
+ 85124020, # 8512.40.20
308
+ 85124040, # 8512.40.40
309
+ 85129020, # 8512.90.20
310
+ 85129060, # 8512.90.60
311
+ 85129070, # 8512.90.70
312
+ 85198120, # 8519.81.20
313
+ 85256010, # 8525.60.10 (10 suffix)
314
+ 85272100, # 8527.21 - prefix
315
+ 85272900, # 8527.29 - prefix
316
+ 85364100, # 8536.41.00 (05 suffix)
317
+ 85371000, # 8537.10 - prefix
318
+ 85372000, # 8537.20 - prefix
319
+ ]
320
+
321
+ # Auto Parts additional tariff codes
322
+ AUTO_PARTS_301_CODES = [99039405, 99039406]
hts_validator.py CHANGED
@@ -6,7 +6,8 @@ Validates primary HTS codes against additional HTS and description keywords
6
  import re
7
  from typing import Dict, List, Optional, Tuple, Set
8
  from dataclasses import dataclass
9
- from HTS_list import Steel_primary_HTS_list, Aluminum_primary_HTS_list, Copper_primary_HTS_list
 
10
 
11
 
12
  # Key Additional HTS codes
@@ -32,8 +33,10 @@ SCENARIO_SUMMARIES = {
32
  "S14": "Plastics keyword + metal HTS - override, should ONLY apply 99030125",
33
  "S15": "Steel HTS + aluminum keyword - should apply 99030125, no 99030133 or 232 tariffs",
34
  "S16": "Aluminum HTS + steel keyword - should apply 99030125, no 99030133 or 232 tariffs",
35
- "S17": "Copper HTS but NO copper keyword - should apply copper tariffs + 99030133, no 99030125",
36
  "COPPER_OK": "Copper HTS + copper keyword - verify copper tariffs applied",
 
 
37
  "NONE": "No applicable scenario - entry does not match any validation rules",
38
  }
39
 
@@ -86,6 +89,8 @@ class HTSValidator:
86
  self.steel_hts_set = self._convert_hts_list(Steel_primary_HTS_list)
87
  self.aluminum_hts_set = self._convert_hts_list(Aluminum_primary_HTS_list)
88
  self.copper_hts_set = self._convert_hts_list(Copper_primary_HTS_list)
 
 
89
 
90
  def _convert_hts_list(self, hts_list: List) -> Set[str]:
91
  """Convert HTS list to set of strings"""
@@ -159,6 +164,8 @@ class HTSValidator:
159
  in_steel = self._hts_matches_list(primary_str, self.steel_hts_set)
160
  in_aluminum = self._hts_matches_list(primary_str, self.aluminum_hts_set)
161
  in_copper = self._hts_matches_list(primary_str, self.copper_hts_set)
 
 
162
 
163
  # Check description keywords
164
  has_metal_kw = self._contains_keywords(desc, self.metal_keywords)
@@ -183,6 +190,8 @@ class HTSValidator:
183
  in_steel=in_steel,
184
  in_aluminum=in_aluminum,
185
  in_copper=in_copper,
 
 
186
  has_metal_kw=has_metal_kw,
187
  has_aluminum_kw=has_aluminum_kw,
188
  has_copper_kw=has_copper_kw,
@@ -199,6 +208,7 @@ class HTSValidator:
199
  def _apply_validation_rules(self, entry_number: str, description: str,
200
  primary_hts: str, additional_hts: List[str],
201
  in_steel: bool, in_aluminum: bool, in_copper: bool,
 
202
  has_metal_kw: bool, has_aluminum_kw: bool,
203
  has_copper_kw: bool, has_zinc_kw: bool,
204
  has_plastics_kw: bool, has_steel_232: bool,
@@ -259,7 +269,41 @@ class HTSValidator:
259
  issue="; ".join(issues) if issues else "Correct - plastics with only 99030125"
260
  )
261
 
262
- # Priority 2: Dual list scenarios
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
 
264
  # Dual Aluminum + Copper
265
  if in_aluminum and in_copper:
@@ -636,32 +680,32 @@ class HTSValidator:
636
  unexpected_hts=[],
637
  issue="; ".join(issues) if issues else "Correct - copper HTS + keyword"
638
  )
639
- else:
640
- # S17: Copper HTS but NO copper keyword - apply copper tariffs + 99030133, no 99030125
641
- expected = list(COPPER_CODES) + [GENERAL_301_CODE]
642
- issues = []
643
-
644
- if not has_copper_tariff:
645
- issues.append("Missing copper tariff (99037801/02)")
646
- if not has_301:
647
- issues.append("Missing 99030133 - copper HTS without copper keyword")
648
- if has_mismatch:
649
- issues.append("Should NOT have 99030125 - no mismatch expected for copper HTS")
650
-
651
- status = "PASS" if not issues else "FAIL"
652
- return ValidationResult(
653
- entry_number=entry_number,
654
- description=description,
655
- primary_hts=primary_hts,
656
- additional_hts=additional_hts,
657
- scenario_id="S17",
658
- scenario_summary=SCENARIO_SUMMARIES["S17"],
659
- status=status,
660
- expected_hts=expected,
661
- missing_hts=(list(COPPER_CODES) if not has_copper_tariff else []) + ([GENERAL_301_CODE] if not has_301 else []),
662
- unexpected_hts=[MISMATCH_CODE] if has_mismatch else [],
663
- issue="; ".join(issues) if issues else "Correct - copper HTS without keyword, has copper tariffs + 99030133"
664
- )
665
 
666
  # S9: Copper keyword but NOT in copper list
667
  if has_copper_kw and not in_copper:
 
6
  import re
7
  from typing import Dict, List, Optional, Tuple, Set
8
  from dataclasses import dataclass
9
+ from HTS_list import (Steel_primary_HTS_list, Aluminum_primary_HTS_list, Copper_primary_HTS_list,
10
+ Computer_parts_HTS_list, Auto_parts_HTS_list)
11
 
12
 
13
  # Key Additional HTS codes
 
33
  "S14": "Plastics keyword + metal HTS - override, should ONLY apply 99030125",
34
  "S15": "Steel HTS + aluminum keyword - should apply 99030125, no 99030133 or 232 tariffs",
35
  "S16": "Aluminum HTS + steel keyword - should apply 99030125, no 99030133 or 232 tariffs",
36
+ "S17": "Copper HTS but NO copper keyword - should apply copper tariffs + 99030133, no 99030125",
37
  "COPPER_OK": "Copper HTS + copper keyword - verify copper tariffs applied",
38
+ "S18": "Computer Parts HTS - flag for manual review",
39
+ "S19": "Auto Parts HTS - flag for manual review",
40
  "NONE": "No applicable scenario - entry does not match any validation rules",
41
  }
42
 
 
89
  self.steel_hts_set = self._convert_hts_list(Steel_primary_HTS_list)
90
  self.aluminum_hts_set = self._convert_hts_list(Aluminum_primary_HTS_list)
91
  self.copper_hts_set = self._convert_hts_list(Copper_primary_HTS_list)
92
+ self.computer_parts_hts_set = self._convert_hts_list(Computer_parts_HTS_list)
93
+ self.auto_parts_hts_set = self._convert_hts_list(Auto_parts_HTS_list)
94
 
95
  def _convert_hts_list(self, hts_list: List) -> Set[str]:
96
  """Convert HTS list to set of strings"""
 
164
  in_steel = self._hts_matches_list(primary_str, self.steel_hts_set)
165
  in_aluminum = self._hts_matches_list(primary_str, self.aluminum_hts_set)
166
  in_copper = self._hts_matches_list(primary_str, self.copper_hts_set)
167
+ in_computer_parts = self._hts_matches_list(primary_str, self.computer_parts_hts_set)
168
+ in_auto_parts = self._hts_matches_list(primary_str, self.auto_parts_hts_set)
169
 
170
  # Check description keywords
171
  has_metal_kw = self._contains_keywords(desc, self.metal_keywords)
 
190
  in_steel=in_steel,
191
  in_aluminum=in_aluminum,
192
  in_copper=in_copper,
193
+ in_computer_parts=in_computer_parts,
194
+ in_auto_parts=in_auto_parts,
195
  has_metal_kw=has_metal_kw,
196
  has_aluminum_kw=has_aluminum_kw,
197
  has_copper_kw=has_copper_kw,
 
208
  def _apply_validation_rules(self, entry_number: str, description: str,
209
  primary_hts: str, additional_hts: List[str],
210
  in_steel: bool, in_aluminum: bool, in_copper: bool,
211
+ in_computer_parts: bool, in_auto_parts: bool,
212
  has_metal_kw: bool, has_aluminum_kw: bool,
213
  has_copper_kw: bool, has_zinc_kw: bool,
214
  has_plastics_kw: bool, has_steel_232: bool,
 
269
  issue="; ".join(issues) if issues else "Correct - plastics with only 99030125"
270
  )
271
 
272
+ # Priority 2: Computer Parts and Auto Parts - FLAG for review
273
+
274
+ # S18: Computer Parts HTS - flag for manual review
275
+ if in_computer_parts:
276
+ return ValidationResult(
277
+ entry_number=entry_number,
278
+ description=description,
279
+ primary_hts=primary_hts,
280
+ additional_hts=additional_hts,
281
+ scenario_id="S18",
282
+ scenario_summary=SCENARIO_SUMMARIES["S18"],
283
+ status="FLAG",
284
+ expected_hts=[],
285
+ missing_hts=[],
286
+ unexpected_hts=[],
287
+ issue="Computer parts HTS - manual review required for tariff verification"
288
+ )
289
+
290
+ # S19: Auto Parts HTS - flag for manual review
291
+ if in_auto_parts:
292
+ return ValidationResult(
293
+ entry_number=entry_number,
294
+ description=description,
295
+ primary_hts=primary_hts,
296
+ additional_hts=additional_hts,
297
+ scenario_id="S19",
298
+ scenario_summary=SCENARIO_SUMMARIES["S19"],
299
+ status="FLAG",
300
+ expected_hts=[],
301
+ missing_hts=[],
302
+ unexpected_hts=[],
303
+ issue="Auto parts HTS - manual review required for tariff verification"
304
+ )
305
+
306
+ # Priority 3: Dual list scenarios
307
 
308
  # Dual Aluminum + Copper
309
  if in_aluminum and in_copper:
 
680
  unexpected_hts=[],
681
  issue="; ".join(issues) if issues else "Correct - copper HTS + keyword"
682
  )
683
+ else:
684
+ # S17: Copper HTS but NO copper keyword - apply copper tariffs + 99030133, no 99030125
685
+ expected = list(COPPER_CODES) + [GENERAL_301_CODE]
686
+ issues = []
687
+
688
+ if not has_copper_tariff:
689
+ issues.append("Missing copper tariff (99037801/02)")
690
+ if not has_301:
691
+ issues.append("Missing 99030133 - copper HTS without copper keyword")
692
+ if has_mismatch:
693
+ issues.append("Should NOT have 99030125 - no mismatch expected for copper HTS")
694
+
695
+ status = "PASS" if not issues else "FAIL"
696
+ return ValidationResult(
697
+ entry_number=entry_number,
698
+ description=description,
699
+ primary_hts=primary_hts,
700
+ additional_hts=additional_hts,
701
+ scenario_id="S17",
702
+ scenario_summary=SCENARIO_SUMMARIES["S17"],
703
+ status=status,
704
+ expected_hts=expected,
705
+ missing_hts=(list(COPPER_CODES) if not has_copper_tariff else []) + ([GENERAL_301_CODE] if not has_301 else []),
706
+ unexpected_hts=[MISMATCH_CODE] if has_mismatch else [],
707
+ issue="; ".join(issues) if issues else "Correct - copper HTS without keyword, has copper tariffs + 99030133"
708
+ )
709
 
710
  # S9: Copper keyword but NOT in copper list
711
  if has_copper_kw and not in_copper: