Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- HTS_list.py +117 -0
- 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:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|