Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- HTS_list.py +8 -0
- app.py +17 -6
- hts_validator.py +24 -3
HTS_list.py
CHANGED
|
@@ -320,3 +320,11 @@ Auto_parts_HTS_list = [
|
|
| 320 |
|
| 321 |
# Auto Parts additional tariff codes
|
| 322 |
AUTO_PARTS_301_CODES = [99039405, 99039406]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
|
| 321 |
# Auto Parts additional tariff codes
|
| 322 |
AUTO_PARTS_301_CODES = [99039405, 99039406]
|
| 323 |
+
|
| 324 |
+
# Semiconductor primary HTS List (6-digit prefix match)
|
| 325 |
+
# Note: These overlap with Computer Parts and Aluminum HTS - requires manual review
|
| 326 |
+
Semiconductor_HTS_list = [
|
| 327 |
+
847150, # 8471.50 - Processing units (may contain semiconductors)
|
| 328 |
+
847180, # 8471.80 - Other units of ADP machines
|
| 329 |
+
847330, # 8473.30 - Parts and accessories of machines of heading 84.71
|
| 330 |
+
]
|
app.py
CHANGED
|
@@ -9,7 +9,7 @@ from io import BytesIO
|
|
| 9 |
import hashlib
|
| 10 |
import os
|
| 11 |
from hts_validator import HTSValidator, validate_dataframe, SCENARIO_SUMMARIES
|
| 12 |
-
from HTS_list import Steel_primary_HTS_list, Aluminum_primary_HTS_list, Copper_primary_HTS_list
|
| 13 |
|
| 14 |
|
| 15 |
# Page configuration
|
|
@@ -194,7 +194,7 @@ def color_indicator(val):
|
|
| 194 |
|
| 195 |
# Indicator columns for styling
|
| 196 |
INDICATOR_COLUMNS = [
|
| 197 |
-
"Steel HTS", "Alum HTS", "Copper HTS", "Computer HTS", "Auto HTS",
|
| 198 |
"Metal KW", "Alum KW", "Copper KW", "Zinc KW", "Plastics KW"
|
| 199 |
]
|
| 200 |
|
|
@@ -221,6 +221,7 @@ def results_to_dataframe(results):
|
|
| 221 |
"Copper HTS": bool_to_symbol(r.in_copper_hts),
|
| 222 |
"Computer HTS": bool_to_symbol(r.in_computer_hts),
|
| 223 |
"Auto HTS": bool_to_symbol(r.in_auto_hts),
|
|
|
|
| 224 |
# Keyword indicators
|
| 225 |
"Metal KW": bool_to_symbol(r.has_metal_keyword),
|
| 226 |
"Alum KW": bool_to_symbol(r.has_aluminum_keyword),
|
|
@@ -468,7 +469,7 @@ with tab2:
|
|
| 468 |
"Entry Number", "Description", "Primary HTS",
|
| 469 |
"Additional HTS",
|
| 470 |
# HTS indicators
|
| 471 |
-
"Steel HTS", "Alum HTS", "Copper HTS", "Computer HTS", "Auto HTS",
|
| 472 |
# Keyword indicators
|
| 473 |
"Metal KW", "Alum KW", "Copper KW", "Zinc KW", "Plastics KW",
|
| 474 |
# Validation
|
|
@@ -670,6 +671,7 @@ with tab2b:
|
|
| 670 |
"Copper HTS": "first",
|
| 671 |
"Computer HTS": "first",
|
| 672 |
"Auto HTS": "first",
|
|
|
|
| 673 |
# Keyword indicators
|
| 674 |
"Metal KW": "first",
|
| 675 |
"Alum KW": "first",
|
|
@@ -739,7 +741,7 @@ with tab2b:
|
|
| 739 |
display_cols = [
|
| 740 |
"Primary HTS", "Description", "Additional HTS",
|
| 741 |
# HTS indicators
|
| 742 |
-
"Steel HTS", "Alum HTS", "Copper HTS", "Computer HTS", "Auto HTS",
|
| 743 |
# Keyword indicators
|
| 744 |
"Metal KW", "Alum KW", "Copper KW", "Zinc KW", "Plastics KW",
|
| 745 |
# Validation
|
|
@@ -958,7 +960,7 @@ with tab4:
|
|
| 958 |
# Tab 5: HTS Reference
|
| 959 |
with tab5:
|
| 960 |
st.header("HTS Reference Lists")
|
| 961 |
-
st.markdown("Reference lists of Steel, Aluminum, and
|
| 962 |
|
| 963 |
# Search filter
|
| 964 |
hts_search = st.text_input(
|
|
@@ -967,7 +969,7 @@ with tab5:
|
|
| 967 |
key="hts_reference_search"
|
| 968 |
)
|
| 969 |
|
| 970 |
-
col1, col2, col3 = st.columns(
|
| 971 |
|
| 972 |
with col1:
|
| 973 |
st.subheader(f"Steel HTS ({len(Steel_primary_HTS_list)})")
|
|
@@ -993,6 +995,15 @@ with tab5:
|
|
| 993 |
copper_df = pd.DataFrame({"Copper HTS": copper_list})
|
| 994 |
st.dataframe(copper_df, use_container_width=True, height=400)
|
| 995 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 996 |
# Show overlap info
|
| 997 |
st.subheader("HTS Overlap Analysis")
|
| 998 |
steel_set = set(str(h) for h in Steel_primary_HTS_list)
|
|
|
|
| 9 |
import hashlib
|
| 10 |
import os
|
| 11 |
from hts_validator import HTSValidator, validate_dataframe, SCENARIO_SUMMARIES
|
| 12 |
+
from HTS_list import Steel_primary_HTS_list, Aluminum_primary_HTS_list, Copper_primary_HTS_list, Semiconductor_HTS_list
|
| 13 |
|
| 14 |
|
| 15 |
# Page configuration
|
|
|
|
| 194 |
|
| 195 |
# Indicator columns for styling
|
| 196 |
INDICATOR_COLUMNS = [
|
| 197 |
+
"Steel HTS", "Alum HTS", "Copper HTS", "Computer HTS", "Auto HTS", "Semi HTS",
|
| 198 |
"Metal KW", "Alum KW", "Copper KW", "Zinc KW", "Plastics KW"
|
| 199 |
]
|
| 200 |
|
|
|
|
| 221 |
"Copper HTS": bool_to_symbol(r.in_copper_hts),
|
| 222 |
"Computer HTS": bool_to_symbol(r.in_computer_hts),
|
| 223 |
"Auto HTS": bool_to_symbol(r.in_auto_hts),
|
| 224 |
+
"Semi HTS": bool_to_symbol(r.in_semiconductor_hts),
|
| 225 |
# Keyword indicators
|
| 226 |
"Metal KW": bool_to_symbol(r.has_metal_keyword),
|
| 227 |
"Alum KW": bool_to_symbol(r.has_aluminum_keyword),
|
|
|
|
| 469 |
"Entry Number", "Description", "Primary HTS",
|
| 470 |
"Additional HTS",
|
| 471 |
# HTS indicators
|
| 472 |
+
"Steel HTS", "Alum HTS", "Copper HTS", "Computer HTS", "Auto HTS", "Semi HTS",
|
| 473 |
# Keyword indicators
|
| 474 |
"Metal KW", "Alum KW", "Copper KW", "Zinc KW", "Plastics KW",
|
| 475 |
# Validation
|
|
|
|
| 671 |
"Copper HTS": "first",
|
| 672 |
"Computer HTS": "first",
|
| 673 |
"Auto HTS": "first",
|
| 674 |
+
"Semi HTS": "first",
|
| 675 |
# Keyword indicators
|
| 676 |
"Metal KW": "first",
|
| 677 |
"Alum KW": "first",
|
|
|
|
| 741 |
display_cols = [
|
| 742 |
"Primary HTS", "Description", "Additional HTS",
|
| 743 |
# HTS indicators
|
| 744 |
+
"Steel HTS", "Alum HTS", "Copper HTS", "Computer HTS", "Auto HTS", "Semi HTS",
|
| 745 |
# Keyword indicators
|
| 746 |
"Metal KW", "Alum KW", "Copper KW", "Zinc KW", "Plastics KW",
|
| 747 |
# Validation
|
|
|
|
| 960 |
# Tab 5: HTS Reference
|
| 961 |
with tab5:
|
| 962 |
st.header("HTS Reference Lists")
|
| 963 |
+
st.markdown("Reference lists of Steel, Aluminum, Copper, and Semiconductor HTS codes used for validation")
|
| 964 |
|
| 965 |
# Search filter
|
| 966 |
hts_search = st.text_input(
|
|
|
|
| 969 |
key="hts_reference_search"
|
| 970 |
)
|
| 971 |
|
| 972 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 973 |
|
| 974 |
with col1:
|
| 975 |
st.subheader(f"Steel HTS ({len(Steel_primary_HTS_list)})")
|
|
|
|
| 995 |
copper_df = pd.DataFrame({"Copper HTS": copper_list})
|
| 996 |
st.dataframe(copper_df, use_container_width=True, height=400)
|
| 997 |
|
| 998 |
+
with col4:
|
| 999 |
+
st.subheader(f"Semiconductor HTS ({len(Semiconductor_HTS_list)})")
|
| 1000 |
+
semi_list = [str(h) for h in Semiconductor_HTS_list]
|
| 1001 |
+
if hts_search:
|
| 1002 |
+
semi_list = [h for h in semi_list if hts_search in h]
|
| 1003 |
+
semi_df = pd.DataFrame({"Semiconductor HTS": semi_list})
|
| 1004 |
+
st.dataframe(semi_df, use_container_width=True, height=400)
|
| 1005 |
+
st.caption("Note: Overlaps with Computer Parts and Aluminum HTS")
|
| 1006 |
+
|
| 1007 |
# Show overlap info
|
| 1008 |
st.subheader("HTS Overlap Analysis")
|
| 1009 |
steel_set = set(str(h) for h in Steel_primary_HTS_list)
|
hts_validator.py
CHANGED
|
@@ -16,7 +16,7 @@ import re
|
|
| 16 |
from typing import Dict, List, Optional, Tuple, Set
|
| 17 |
from dataclasses import dataclass
|
| 18 |
from HTS_list import (Steel_primary_HTS_list, Aluminum_primary_HTS_list, Copper_primary_HTS_list,
|
| 19 |
-
Computer_parts_HTS_list, Auto_parts_HTS_list)
|
| 20 |
|
| 21 |
|
| 22 |
# Key Additional HTS codes
|
|
@@ -42,6 +42,7 @@ SCENARIO_SUMMARIES = {
|
|
| 42 |
# Level 1: Special HTS
|
| 43 |
"C1": "Computer Parts HTS - FLAG for manual review",
|
| 44 |
"A1": "Auto Parts HTS - FLAG for manual review",
|
|
|
|
| 45 |
# Level 2: Dual HTS - Steel + Aluminum
|
| 46 |
"D1": "Steel+Alum HTS, no keyword - FLAG",
|
| 47 |
"D2": "Steel+Alum HTS + metal keyword - Steel 232 + 99030133",
|
|
@@ -130,6 +131,7 @@ class ValidationResult:
|
|
| 130 |
in_copper_hts: bool = False
|
| 131 |
in_computer_hts: bool = False
|
| 132 |
in_auto_hts: bool = False
|
|
|
|
| 133 |
# Keyword indicators
|
| 134 |
has_metal_keyword: bool = False
|
| 135 |
has_aluminum_keyword: bool = False
|
|
@@ -172,6 +174,7 @@ class HTSValidator:
|
|
| 172 |
self.copper_hts_set = self._convert_hts_list(Copper_primary_HTS_list)
|
| 173 |
self.computer_parts_hts_set = self._convert_hts_list(Computer_parts_HTS_list)
|
| 174 |
self.auto_parts_hts_set = self._convert_hts_list(Auto_parts_HTS_list)
|
|
|
|
| 175 |
|
| 176 |
def _convert_hts_list(self, hts_list: List) -> Set[str]:
|
| 177 |
"""Convert HTS list to set of strings"""
|
|
@@ -266,6 +269,7 @@ class HTSValidator:
|
|
| 266 |
in_copper = self._hts_matches_list(primary_str, self.copper_hts_set)
|
| 267 |
in_computer_parts = self._hts_matches_list(primary_str, self.computer_parts_hts_set)
|
| 268 |
in_auto_parts = self._hts_matches_list(primary_str, self.auto_parts_hts_set)
|
|
|
|
| 269 |
|
| 270 |
# Check description keywords
|
| 271 |
has_metal_kw = self._contains_keywords(desc, self.metal_keywords)
|
|
@@ -295,6 +299,7 @@ class HTSValidator:
|
|
| 295 |
in_copper=in_copper,
|
| 296 |
in_computer_parts=in_computer_parts,
|
| 297 |
in_auto_parts=in_auto_parts,
|
|
|
|
| 298 |
has_metal_kw=has_metal_kw,
|
| 299 |
has_aluminum_kw=has_aluminum_kw,
|
| 300 |
has_copper_kw=has_copper_kw,
|
|
@@ -316,7 +321,7 @@ class HTSValidator:
|
|
| 316 |
flag_reason: str = "",
|
| 317 |
# Indicators
|
| 318 |
in_steel: bool = False, in_aluminum: bool = False, in_copper: bool = False,
|
| 319 |
-
in_computer: bool = False, in_auto: bool = False,
|
| 320 |
has_metal_kw: bool = False, has_aluminum_kw: bool = False,
|
| 321 |
has_copper_kw: bool = False, has_zinc_kw: bool = False,
|
| 322 |
has_plastics_kw: bool = False) -> ValidationResult:
|
|
@@ -344,6 +349,7 @@ class HTSValidator:
|
|
| 344 |
in_copper_hts=in_copper,
|
| 345 |
in_computer_hts=in_computer,
|
| 346 |
in_auto_hts=in_auto,
|
|
|
|
| 347 |
has_metal_keyword=has_metal_kw,
|
| 348 |
has_aluminum_keyword=has_aluminum_kw,
|
| 349 |
has_copper_keyword=has_copper_kw,
|
|
@@ -417,6 +423,7 @@ class HTSValidator:
|
|
| 417 |
in_copper_hts=in_copper,
|
| 418 |
in_computer_hts=in_computer,
|
| 419 |
in_auto_hts=in_auto,
|
|
|
|
| 420 |
has_metal_keyword=has_metal_kw,
|
| 421 |
has_aluminum_keyword=has_aluminum_kw,
|
| 422 |
has_copper_keyword=has_copper_kw,
|
|
@@ -427,7 +434,7 @@ class HTSValidator:
|
|
| 427 |
def _apply_validation_rules(self, entry_number: str, description: str,
|
| 428 |
primary_hts: str, additional_hts: List[str],
|
| 429 |
in_steel: bool, in_aluminum: bool, in_copper: bool,
|
| 430 |
-
in_computer_parts: bool, in_auto_parts: bool,
|
| 431 |
has_metal_kw: bool, has_aluminum_kw: bool,
|
| 432 |
has_copper_kw: bool, has_zinc_kw: bool,
|
| 433 |
has_plastics_kw: bool, has_steel_232: bool,
|
|
@@ -443,6 +450,7 @@ class HTSValidator:
|
|
| 443 |
"in_copper": in_copper,
|
| 444 |
"in_computer": in_computer_parts,
|
| 445 |
"in_auto": in_auto_parts,
|
|
|
|
| 446 |
"has_metal_kw": has_metal_kw,
|
| 447 |
"has_aluminum_kw": has_aluminum_kw,
|
| 448 |
"has_copper_kw": has_copper_kw,
|
|
@@ -556,6 +564,18 @@ class HTSValidator:
|
|
| 556 |
flag_reason="Auto parts HTS - manual review required"
|
| 557 |
)
|
| 558 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 559 |
# =====================================================================
|
| 560 |
# LEVEL 2: Dual HTS Categories
|
| 561 |
# =====================================================================
|
|
@@ -1122,6 +1142,7 @@ class HTSValidator:
|
|
| 1122 |
in_copper_hts=in_copper,
|
| 1123 |
in_computer_hts=in_computer_parts,
|
| 1124 |
in_auto_hts=in_auto_parts,
|
|
|
|
| 1125 |
has_metal_keyword=has_metal_kw,
|
| 1126 |
has_aluminum_keyword=has_aluminum_kw,
|
| 1127 |
has_copper_keyword=has_copper_kw,
|
|
|
|
| 16 |
from typing import Dict, List, Optional, Tuple, Set
|
| 17 |
from dataclasses import dataclass
|
| 18 |
from HTS_list import (Steel_primary_HTS_list, Aluminum_primary_HTS_list, Copper_primary_HTS_list,
|
| 19 |
+
Computer_parts_HTS_list, Auto_parts_HTS_list, Semiconductor_HTS_list)
|
| 20 |
|
| 21 |
|
| 22 |
# Key Additional HTS codes
|
|
|
|
| 42 |
# Level 1: Special HTS
|
| 43 |
"C1": "Computer Parts HTS - FLAG for manual review",
|
| 44 |
"A1": "Auto Parts HTS - FLAG for manual review",
|
| 45 |
+
"SC1": "Semiconductor HTS - FLAG for manual review (overlaps Computer/Aluminum)",
|
| 46 |
# Level 2: Dual HTS - Steel + Aluminum
|
| 47 |
"D1": "Steel+Alum HTS, no keyword - FLAG",
|
| 48 |
"D2": "Steel+Alum HTS + metal keyword - Steel 232 + 99030133",
|
|
|
|
| 131 |
in_copper_hts: bool = False
|
| 132 |
in_computer_hts: bool = False
|
| 133 |
in_auto_hts: bool = False
|
| 134 |
+
in_semiconductor_hts: bool = False
|
| 135 |
# Keyword indicators
|
| 136 |
has_metal_keyword: bool = False
|
| 137 |
has_aluminum_keyword: bool = False
|
|
|
|
| 174 |
self.copper_hts_set = self._convert_hts_list(Copper_primary_HTS_list)
|
| 175 |
self.computer_parts_hts_set = self._convert_hts_list(Computer_parts_HTS_list)
|
| 176 |
self.auto_parts_hts_set = self._convert_hts_list(Auto_parts_HTS_list)
|
| 177 |
+
self.semiconductor_hts_set = self._convert_hts_list(Semiconductor_HTS_list)
|
| 178 |
|
| 179 |
def _convert_hts_list(self, hts_list: List) -> Set[str]:
|
| 180 |
"""Convert HTS list to set of strings"""
|
|
|
|
| 269 |
in_copper = self._hts_matches_list(primary_str, self.copper_hts_set)
|
| 270 |
in_computer_parts = self._hts_matches_list(primary_str, self.computer_parts_hts_set)
|
| 271 |
in_auto_parts = self._hts_matches_list(primary_str, self.auto_parts_hts_set)
|
| 272 |
+
in_semiconductor = self._hts_matches_list(primary_str, self.semiconductor_hts_set)
|
| 273 |
|
| 274 |
# Check description keywords
|
| 275 |
has_metal_kw = self._contains_keywords(desc, self.metal_keywords)
|
|
|
|
| 299 |
in_copper=in_copper,
|
| 300 |
in_computer_parts=in_computer_parts,
|
| 301 |
in_auto_parts=in_auto_parts,
|
| 302 |
+
in_semiconductor=in_semiconductor,
|
| 303 |
has_metal_kw=has_metal_kw,
|
| 304 |
has_aluminum_kw=has_aluminum_kw,
|
| 305 |
has_copper_kw=has_copper_kw,
|
|
|
|
| 321 |
flag_reason: str = "",
|
| 322 |
# Indicators
|
| 323 |
in_steel: bool = False, in_aluminum: bool = False, in_copper: bool = False,
|
| 324 |
+
in_computer: bool = False, in_auto: bool = False, in_semiconductor: bool = False,
|
| 325 |
has_metal_kw: bool = False, has_aluminum_kw: bool = False,
|
| 326 |
has_copper_kw: bool = False, has_zinc_kw: bool = False,
|
| 327 |
has_plastics_kw: bool = False) -> ValidationResult:
|
|
|
|
| 349 |
in_copper_hts=in_copper,
|
| 350 |
in_computer_hts=in_computer,
|
| 351 |
in_auto_hts=in_auto,
|
| 352 |
+
in_semiconductor_hts=in_semiconductor,
|
| 353 |
has_metal_keyword=has_metal_kw,
|
| 354 |
has_aluminum_keyword=has_aluminum_kw,
|
| 355 |
has_copper_keyword=has_copper_kw,
|
|
|
|
| 423 |
in_copper_hts=in_copper,
|
| 424 |
in_computer_hts=in_computer,
|
| 425 |
in_auto_hts=in_auto,
|
| 426 |
+
in_semiconductor_hts=in_semiconductor,
|
| 427 |
has_metal_keyword=has_metal_kw,
|
| 428 |
has_aluminum_keyword=has_aluminum_kw,
|
| 429 |
has_copper_keyword=has_copper_kw,
|
|
|
|
| 434 |
def _apply_validation_rules(self, entry_number: str, description: str,
|
| 435 |
primary_hts: str, additional_hts: List[str],
|
| 436 |
in_steel: bool, in_aluminum: bool, in_copper: bool,
|
| 437 |
+
in_computer_parts: bool, in_auto_parts: bool, in_semiconductor: bool,
|
| 438 |
has_metal_kw: bool, has_aluminum_kw: bool,
|
| 439 |
has_copper_kw: bool, has_zinc_kw: bool,
|
| 440 |
has_plastics_kw: bool, has_steel_232: bool,
|
|
|
|
| 450 |
"in_copper": in_copper,
|
| 451 |
"in_computer": in_computer_parts,
|
| 452 |
"in_auto": in_auto_parts,
|
| 453 |
+
"in_semiconductor": in_semiconductor,
|
| 454 |
"has_metal_kw": has_metal_kw,
|
| 455 |
"has_aluminum_kw": has_aluminum_kw,
|
| 456 |
"has_copper_kw": has_copper_kw,
|
|
|
|
| 564 |
flag_reason="Auto parts HTS - manual review required"
|
| 565 |
)
|
| 566 |
|
| 567 |
+
# SC1: Semiconductor HTS - always FLAG (overlaps with Computer Parts and Aluminum)
|
| 568 |
+
if in_semiconductor:
|
| 569 |
+
return self._create_result(
|
| 570 |
+
entry_number, description, primary_hts, additional_hts,
|
| 571 |
+
scenario_id="SC1",
|
| 572 |
+
expected_codes=[], forbidden_codes=set(),
|
| 573 |
+
additional_set=additional_set,
|
| 574 |
+
**indicators,
|
| 575 |
+
always_flag=True,
|
| 576 |
+
flag_reason="Semiconductor HTS - manual review required (overlaps Computer/Aluminum)"
|
| 577 |
+
)
|
| 578 |
+
|
| 579 |
# =====================================================================
|
| 580 |
# LEVEL 2: Dual HTS Categories
|
| 581 |
# =====================================================================
|
|
|
|
| 1142 |
in_copper_hts=in_copper,
|
| 1143 |
in_computer_hts=in_computer_parts,
|
| 1144 |
in_auto_hts=in_auto_parts,
|
| 1145 |
+
in_semiconductor_hts=in_semiconductor,
|
| 1146 |
has_metal_keyword=has_metal_kw,
|
| 1147 |
has_aluminum_keyword=has_aluminum_kw,
|
| 1148 |
has_copper_keyword=has_copper_kw,
|