"""Classification router — HS code lookup and duty calculation.""" from __future__ import annotations import logging from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from hermes.api.middleware import sanitize_input from hermes.core.auth import get_api_key_dependency logger = logging.getLogger(__name__) router = APIRouter(dependencies=[Depends(get_api_key_dependency)]) # ── Request/Response Models ────────────────────────────────────────── class ItemInput(BaseModel): description: str = Field(..., max_length=500, description="Item description") quantity: int = Field(default=1, ge=1, description="Quantity") unit_price_usd: float = Field(default=0.0, ge=0, description="Unit price in USD") origin_country: str = Field(..., min_length=2, max_length=2, description="ISO country code") class ClassifyRequest(BaseModel): items: list[ItemInput] = Field(..., min_length=1, max_length=50, description="Items to classify") destination_country: str = Field(..., min_length=2, max_length=2, description="Destination ISO country code") class ItemClassification(BaseModel): description: str hs_code: str taric_description: str origin: str customs_value: float duty_rate: float duty_amount: float vat_rate: float vat_amount: float total_landed_cost: float notes: str class DutySummary(BaseModel): customs_value: float total_duty: float total_vat: float shipping_cost: float total_taxes_fees: float total_landed_cost: float effective_rate: float class ClassifyResponse(BaseModel): status: str classified_at: str destination_country: str items: list[ItemClassification] summary: DutySummary # ── TARIC API Helpers ──────────────────────────────────────────────── TARIC_BASE_URL = "https://ec.europa.eu/taxation_customs/dds2/taric" TARIC_DESCRIPTION_URL = f"{TARIC_BASE_URL}/goodsDescrForWs" # Default duty rate for unknown products (15% is conservative EU average) DEFAULT_DUTY_RATE = 0.15 DEFAULT_VAT_RATE = 0.19 # German VAT async def fetch_taric_description(taric_code: str) -> dict: """Fetch TARIC code description from EU database.""" import httpx params = { "Goods_code": taric_code.replace(".", ""), "Country_code": "en", } try: async with httpx.AsyncClient(timeout=15.0) as client: resp = await client.get(TARIC_DESCRIPTION_URL, params=params) resp.raise_for_status() import xml.etree.ElementTree as ET root = ET.fromstring(resp.text) result = {} goods_code = root.find(".//Goods_code") if goods_code is not None: result["taric_code"] = goods_code.text or "" description = root.find(".//Description") if description is not None: result["description"] = description.text or "" return result except Exception as e: return {"taric_code": taric_code, "description": f"Lookup failed: {e}"} # ── Classification Logic ───────────────────────────────────────────── # Comprehensive HS code lookup table: keywords → (hs_code, description, duty_rate, notes) # Covers 100+ common import product categories HS_CODE_DATABASE: dict[str, tuple[str, str, float, str]] = { # ── Electronics & IT (Chapter 84-85) ────────────────────────── "earbuds": ("8518.30", "Headphones and earphones", 0.0, "ITA zero duty"), "headphones": ("8518.30", "Headphones and earphones", 0.0, "ITA zero duty"), "earphones": ("8518.30", "Headphones and earphones", 0.0, "ITA zero duty"), "airpods": ("8518.30", "Headphones and earphones", 0.0, "ITA zero duty"), "charger": ("8504.40", "Static converters (chargers)", 0.0, "ITA zero duty"), "charging": ("8504.40", "Static converters (chargers)", 0.0, "ITA zero duty"), "power adapter": ("8504.40", "Static converters (chargers)", 0.0, "ITA zero duty"), "power supply": ("8504.40", "Static converters (power supplies)", 0.0, "ITA zero duty"), "usb-c": ("8504.40", "USB-C chargers/adapters", 0.0, "ITA zero duty"), "laptop": ("8471.30", "Portable digital computers", 0.0, "ITA zero duty"), "computer": ("8471.30", "Digital computers", 0.0, "ITA zero duty"), "notebook": ("8471.30", "Portable computers", 0.0, "ITA zero duty"), "macbook": ("8471.30", "Portable computers", 0.0, "ITA zero duty"), "desktop": ("8471.30", "Desktop computers", 0.0, "ITA zero duty"), "monitor": ("8528.52", "Monitors and displays", 0.0, "ITA zero duty"), "display": ("8528.52", "Monitors and displays", 0.0, "ITA zero duty"), "tv ": ("8528.72", "Television receivers", 0.14, "Standard rate"), "television": ("8528.72", "Television receivers", 0.14, "Standard rate"), "chip": ("8542.31", "Electronic integrated circuits", 0.0, "ITA zero duty"), "processor": ("8542.31", "Electronic integrated circuits", 0.0, "ITA zero duty"), "circuit": ("8542.31", "Electronic integrated circuits", 0.0, "ITA zero duty"), "semiconductor": ("8542.31", "Semiconductor devices", 0.0, "ITA zero duty"), "memory": ("8542.32", "Electronic integrated circuits (memory)", 0.0, "ITA zero duty"), "ssd": ("8523.51", "Solid-state non-volatile storage", 0.0, "ITA zero duty"), "hdd": ("8524.99", "Magnetic storage devices", 0.0, "ITA zero duty"), "hard drive": ("8524.99", "Magnetic storage devices", 0.0, "ITA zero duty"), "usb drive": ("8523.51", "Solid-state non-volatile storage", 0.0, "ITA zero duty"), "flash drive": ("8523.51", "Solid-state non-volatile storage", 0.0, "ITA zero duty"), "keyboard": ("8471.60", "Input/output units (keyboards)", 0.0, "ITA zero duty"), "mouse": ("8471.60", "Input/output units (mice)", 0.0, "ITA zero duty"), "printer": ("8443.32", "Printers, copying machines", 0.0, "ITA zero duty"), "scanner": ("8443.32", "Scanners", 0.0, "ITA zero duty"), "camera": ("8525.89", "Television cameras, digital cameras", 0.0, "ITA zero duty"), "webcam": ("8525.89", "Digital cameras", 0.0, "ITA zero duty"), "speaker": ("8518.22", "Loudspeakers", 0.0, "ITA zero duty"), "bluetooth speaker": ("8518.22", "Loudspeakers", 0.0, "ITA zero duty"), "router": ("8517.62", "Routers and switches", 0.0, "ITA zero duty"), "switch ": ("8517.62", "Network switches", 0.0, "ITA zero duty"), "modem": ("8517.62", "Modems", 0.0, "ITA zero duty"), "wifi": ("8517.62", "Wireless network equipment", 0.0, "ITA zero duty"), "antenna": ("8517.71", "Antenna and parts", 0.0, "ITA zero duty"), "battery": ("8507.60", "Lithium-ion accumulators", 0.0, "ITA zero duty"), "power bank": ("8507.60", "Portable battery packs", 0.0, "ITA zero duty"), "solar panel": ("8541.40", "Photovoltaic cells", 0.0, "ITA zero duty"), "led": ("9405.42", "LED light sources", 0.0, "ITA zero duty"), "light bulb": ("9405.42", "LED light sources", 0.0, "Standard rate"), "cable": ("8544.42", "Electric conductors, fitted with connectors", 0.0, "ITA zero duty"), "wire": ("8544.49", "Electric conductors", 0.0, "ITA zero duty"), "cable tie": ("3926.90", "Plastic articles", 0.065, "Standard rate"), "connector": ("8536.90", "Electrical connectors", 0.0, "ITA zero duty"), "plug": ("8536.90", "Electrical plugs and sockets", 0.0, "ITA zero duty"), "socket": ("8536.90", "Electrical plugs and sockets", 0.0, "ITA zero duty"), "extension cord": ("8544.42", "Extension cords", 0.0, "ITA zero duty"), "surge protector": ("8536.30", "Surge protectors", 0.0, "ITA zero duty"), "ups ": ("8504.40", "Uninterruptible power supplies", 0.0, "ITA zero duty"), "server": ("8471.50", "Processing units (servers)", 0.0, "ITA zero duty"), "workstation": ("8471.50", "Processing units", 0.0, "ITA zero duty"), "graphics card": ("8473.30", "Parts for computers", 0.0, "ITA zero duty"), "gpu ": ("8473.30", "Parts for computers", 0.0, "ITA zero duty"), "motherboard": ("8473.30", "Parts for computers", 0.0, "ITA zero duty"), "ram ": ("8542.32", "Memory modules", 0.0, "ITA zero duty"), "smartphone": ("8517.13", "Smartphones", 0.0, "ITA zero duty"), "mobile phone": ("8517.13", "Mobile phones", 0.0, "ITA zero duty"), "tablet": ("8471.30", "Tablet computers", 0.0, "ITA zero duty"), "ipad": ("8471.30", "Tablet computers", 0.0, "ITA zero duty"), "smartwatch": ("9102.12", "Smartwatches", 0.045, "Standard rate"), "drone": ("8802.20", "Unmanned aircraft", 0.0, "ITA zero duty"), "drone parts": ("8803.90", "Parts of aircraft", 0.0, "ITA zero duty"), "vr headset": ("9013.80", "Liquid crystal devices", 0.0, "ITA zero duty"), "headset": ("8518.30", "Headphones and headsets", 0.0, "ITA zero duty"), "microphone": ("8518.10", "Microphones", 0.0, "ITA zero duty"), "amplifier": ("8518.50", "Audio amplifiers", 0.0, "ITA zero duty"), "projector": ("9006.59", "Photographic projectors", 0.0, "ITA zero duty"), "external hard drive": ("8524.99", "Magnetic storage devices", 0.0, "ITA zero duty"), "smart home": ("8543.70", "Electrical machines with individual functions", 0.0, "ITA zero duty"), "thermostat": ("9032.10", "Thermostats", 0.0, "ITA zero duty"), "security camera": ("8525.83", "Television cameras", 0.0, "ITA zero duty"), "cctv": ("8525.83", "Television cameras", 0.0, "ITA zero duty"), "doorbell": ("8536.30", "Electric doorbells", 0.0, "ITA zero duty"), # ── Food & Beverages (Chapter 01-24) ───────────────────────── "coffee": ("0901.11", "Coffee, not roasted, not decaffeinated", 0.075, "Standard rate"), "coffee beans": ("0901.11", "Coffee beans, not roasted", 0.075, "Standard rate"), "arabica": ("0901.11", "Coffee beans, not roasted", 0.075, "Standard rate"), "robusta": ("0901.11", "Coffee beans, not roasted", 0.075, "Standard rate"), "roasted coffee": ("0901.21", "Coffee, roasted, not decaffeinated", 0.075, "Standard rate"), "tea": ("0902.10", "Green tea, in packages ≤ 3kg", 0.0, "Standard rate"), "black tea": ("0902.30", "Black tea, fermented, in packages ≤ 3kg", 0.0, "Standard rate"), "spice": ("0910.99", "Spices and mixes", 0.0, "Standard rate"), "pepper": ("0904.11", "Pepper, neither crushed nor ground", 0.0, "Standard rate"), "chili": ("0904.21", "Chili peppers, dried", 0.0, "Standard rate"), "cinnamon": ("0906.11", "Cinnamon, not crushed", 0.0, "Standard rate"), "rice": ("1006.30", "Semi-milled or milled rice", 0.0, "Standard rate"), "wheat": ("1001.19", "Wheat and meslin", 0.0, "Standard rate"), "flour": ("1101.00", "Wheat or meslin flour", 0.0, "Standard rate"), "sugar": ("1701.14", "Raw cane sugar", 0.0, "Standard rate"), "chocolate": ("1806.32", "Chocolate, not filled", 0.08, "Standard rate"), "cocoa": ("1801.00", "Cocoa beans", 0.0, "Standard rate"), "honey": ("0409.00", "Natural honey", 0.17, "Standard rate"), "olive oil": ("1509.10", "Virgin olive oil", 0.0, "Standard rate"), "vegetable oil": ("1507.10", "Soybean oil, crude", 0.0, "Standard rate"), "pasta": ("1902.19", "Pasta, not stuffed", 0.0, "Standard rate"), "noodles": ("1902.30", "Other pasta", 0.0, "Standard rate"), "biscuit": ("1905.31", "Sweet biscuits", 0.08, "Standard rate"), "cookie": ("1905.31", "Sweet biscuits", 0.08, "Standard rate"), "snack": ("1905.90", "Bakery products", 0.08, "Standard rate"), "chips": ("2008.19", "Roasted nuts and snacks", 0.0, "Standard rate"), "nuts": ("0802.11", "Almonds, fresh", 0.0, "Standard rate"), "almond": ("0802.11", "Almonds, fresh", 0.0, "Standard rate"), "cashew": ("0803.90", "Bananas and plantains", 0.0, "Standard rate"), "peanut": ("1202.41", "Groundnuts, not roasted", 0.0, "Standard rate"), "wine": ("2204.21", "Wine of fresh grapes, in containers ≤ 2L", 0.032, "Standard rate"), "beer": ("2203.00", "Beer made from malt", 0.0, "Standard rate"), "whisky": ("2208.30", "Whiskies", 0.0, "Standard rate"), "vodka": ("2208.90", "Spirits and liqueurs", 0.0, "Standard rate"), "juice": ("2009.12", "Orange juice, frozen", 0.0, "Standard rate"), "water bottle": ("2201.10", "Waters, mineral and aerated", 0.0, "Standard rate"), "milk": ("0401.20", "Milk and cream, not concentrated", 0.0, "Standard rate"), "cheese": ("0406.90", "Cheese and curd", 0.0, "Standard rate"), "butter": ("0405.10", "Butter", 0.0, "Standard rate"), "egg": ("0407.11", "Fresh eggs of chickens", 0.0, "Standard rate"), "frozen meat": ("0202.30", "Meat of bovine animals, boneless, frozen", 0.125, "Standard rate"), "chicken": ("0207.14", "Frozen chicken cuts", 0.0, "Standard rate"), "fish": ("0303.89", "Frozen fish", 0.0, "Standard rate"), "shrimp": ("0306.17", "Frozen shrimps and prawns", 0.0, "Standard rate"), "canned food": ("2023.90", "Prepared or preserved meat", 0.25, "Standard rate"), "sauce": ("2103.90", "Sauces and preparations", 0.0, "Standard rate"), "condiment": ("2103.90", "Sauces and condiments", 0.0, "Standard rate"), "vinegar": ("2209.00", "Vinegar", 0.0, "Standard rate"), "salt": ("2501.00", "Sodium chloride", 0.0, "Standard rate"), # ── Textiles & Clothing (Chapter 50-63) ────────────────────── "shirt": ("6105.10", "Men's shirts, cotton, knitted", 0.12, "Textile rate"), "t-shirt": ("6109.10", "T-shirts, cotton, knitted", 0.12, "Textile rate"), "polo": ("6105.10", "Men's shirts, cotton, knitted", 0.12, "Textile rate"), "jersey": ("6110.30", "Jerseys and pullovers", 0.12, "Textile rate"), "pullover": ("6110.30", "Pullovers and sweatshirts", 0.12, "Textile rate"), "sweater": ("6110.30", "Sweaters and pullovers", 0.12, "Textile rate"), "hoodie": ("6110.30", "Hoodies and sweatshirts", 0.12, "Textile rate"), "jacket": ("6201.93", "Men's jackets, man-made fibres", 0.12, "Textile rate"), "coat": ("6201.10", "Overcoats, raincoats", 0.12, "Textile rate"), "vest": ("6110.30", "Vests and waistcoats", 0.12, "Textile rate"), "pants": ("6203.42", "Men's trousers, cotton", 0.12, "Textile rate"), "trousers": ("6203.42", "Men's trousers, cotton", 0.12, "Textile rate"), "jeans": ("6203.42", "Men's jeans, cotton", 0.12, "Textile rate"), "shorts": ("6203.59", "Men's shorts", 0.12, "Textile rate"), "skirt": ("6204.53", "Women's skirts, man-made fibres", 0.12, "Textile rate"), "dress": ("6204.43", "Women's dresses, synthetic fibres", 0.12, "Textile rate"), "leggings": ("6115.95", "Stockings and leggings", 0.12, "Textile rate"), "socks": ("6115.95", "Stockings and socks", 0.12, "Textile rate"), "underwear": ("6107.11", "Men's underpants, cotton, knitted", 0.12, "Textile rate"), "bra": ("6212.10", "Brassieres", 0.12, "Textile rate"), "swimsuit": ("6112.41", "Women's swimwear, synthetic fibres", 0.12, "Textile rate"), "uniform": ("6110.30", "Uniforms and workwear", 0.12, "Textile rate"), "workwear": ("6211.33", "Men's clothing, man-made fibres", 0.12, "Textile rate"), "scarf": ("6117.10", "Shawls, scarves", 0.12, "Textile rate"), "tie ": ("6215.20", "Ties and bow ties", 0.12, "Textile rate"), "glove": ("6116.93", "Gloves, knitted", 0.12, "Textile rate"), "hat ": ("6505.00", "Hats and headgear, knitted", 0.12, "Textile rate"), "cap ": ("6505.00", "Caps and headgear", 0.12, "Textile rate"), "beanie": ("6505.00", "Headgear, knitted", 0.12, "Textile rate"), "blanket": ("6302.21", "Bed linen, cotton, printed", 0.12, "Textile rate"), "towel": ("6302.60", "Toilet and kitchen linen", 0.12, "Textile rate"), "curtain": ("6303.91", "Curtains, cotton", 0.12, "Textile rate"), "fabric": ("5208.12", "Woven cotton fabrics", 0.08, "Textile rate"), "yarn": ("5204.19", "Cotton sewing thread", 0.0, "Standard rate"), # ── Footwear (Chapter 64) ──────────────────────────────────── "shoes": ("6403.99", "Footwear with leather uppers", 0.169, "Footwear rate"), "boots": ("6403.99", "Boots with leather uppers", 0.169, "Footwear rate"), "sneakers": ("6404.11", "Sports footwear, rubber/plastic soles", 0.169, "Footwear rate"), "running shoes": ("6404.11", "Sports footwear, rubber/plastic soles", 0.169, "Footwear rate"), "sandals": ("6403.99", "Sandals with leather uppers", 0.169, "Footwear rate"), "slippers": ("6404.19", "Slippers, textile uppers", 0.169, "Footwear rate"), "flip flops": ("6404.19", "Flip flops, rubber soles", 0.169, "Footwear rate"), "heels": ("6403.99", "High-heeled shoes", 0.169, "Footwear rate"), "loafers": ("6403.99", "Leather loafers", 0.169, "Footwear rate"), "football boots": ("6404.11", "Football boots", 0.169, "Footwear rate"), "hiking boots": ("6403.99", "Hiking boots, leather uppers", 0.169, "Footwear rate"), "work boots": ("6403.99", "Safety boots", 0.169, "Footwear rate"), # ── Bags & Leather (Chapter 42) ────────────────────────────── "bag": ("4202.21", "Handbags, leather", 0.08, "Standard rate"), "handbag": ("4202.21", "Handbags, leather", 0.08, "Standard rate"), "backpack": ("4202.92", "Backpacks, textile", 0.08, "Standard rate"), "luggage": ("4202.12", "Trunks and suitcases, hard shell", 0.08, "Standard rate"), "suitcase": ("4202.12", "Suitcases, hard shell", 0.08, "Standard rate"), "wallet": ("4202.31", "Wallets, leather", 0.08, "Standard rate"), "purse": ("4202.31", "Purses, leather", 0.08, "Standard rate"), "briefcase": ("4202.12", "Briefcases", 0.08, "Standard rate"), "laptop bag": ("4202.92", "Laptop bags", 0.08, "Standard rate"), "belt ": ("4203.30", "Belts and bandoliers, leather", 0.08, "Standard rate"), # ── Toys & Games (Chapter 95) ──────────────────────────────── "toy": ("9503.00", "Toys and models", 0.047, "Standard rate"), "doll": ("9503.00", "Dolls and toy figures", 0.047, "Standard rate"), "puzzle": ("9503.00", "Puzzles and jigsaws", 0.047, "Standard rate"), "lego": ("9503.00", "Construction toys", 0.047, "Standard rate"), "board game": ("9504.90", "Board games", 0.0, "Standard rate"), "card game": ("9504.90", "Card games", 0.0, "Standard rate"), "playing cards": ("9504.40", "Playing cards", 0.0, "Standard rate"), "action figure": ("9503.00", "Toy figures", 0.047, "Standard rate"), "stuffed animal": ("9503.00", "Stuffed toys", 0.047, "Standard rate"), "plush": ("9503.00", "Stuffed toys", 0.047, "Standard rate"), "remote control car": ("9503.00", "Radio-controlled toys", 0.047, "Standard rate"), "rc car": ("9503.00", "Radio-controlled toys", 0.047, "Standard rate"), "balloon": ("9503.00", "Toy balloons", 0.047, "Standard rate"), "kite": ("9503.00", "Kites and similar toys", 0.047, "Standard rate"), "video game": ("9504.50", "Video game consoles and machines", 0.0, "Standard rate"), "console": ("9504.50", "Game consoles", 0.0, "Standard rate"), "controller": ("9504.50", "Game controllers", 0.0, "Standard rate"), # ── Furniture (Chapter 94) ─────────────────────────────────── "chair": ("9401.30", "Swivel chairs with adjustable height", 0.0, "Standard rate"), "desk": ("9403.30", "Office furniture, wood", 0.0, "Standard rate"), "table": ("9403.40", "Kitchen furniture, wood", 0.0, "Standard rate"), "sofa": ("9401.61", "Upholstered seats, wooden frame", 0.0, "Standard rate"), "couch": ("9401.61", "Upholstered seats, wooden frame", 0.0, "Standard rate"), "bed ": ("9403.50", "Wooden furniture for bedrooms", 0.0, "Standard rate"), "mattress": ("9404.21", "Mattresses of cellular rubber", 0.0, "Standard rate"), "bookshelf": ("9403.40", "Shelving, wood", 0.0, "Standard rate"), "cabinet": ("9403.40", "Cabinets, wood", 0.0, "Standard rate"), "wardrobe": ("9403.50", "Wooden furniture for bedrooms", 0.0, "Standard rate"), "filing cabinet": ("9403.10", "Metal furniture for offices", 0.0, "Standard rate"), "shoe rack": ("9403.40", "Shelving, wood", 0.0, "Standard rate"), "tv stand": ("9403.40", "TV stands, wood", 0.0, "Standard rate"), # ── Home & Kitchen (Chapter 69-73, 82-83) ──────────────────── "mug": ("6912.00", "Ceramic tableware", 0.0, "Standard rate"), "cup": ("6912.00", "Ceramic cups", 0.0, "Standard rate"), "plate": ("6911.10", "Tableware, porcelain", 0.0, "Standard rate"), "bowl": ("6911.10", "Ceramic bowls", 0.0, "Standard rate"), "glass": ("7013.37", "Drinking glasses", 0.0, "Standard rate"), "bottle": ("7010.90", "Glass bottles", 0.0, "Standard rate"), "vase": ("7013.99", "Ceramic or glass vases", 0.0, "Standard rate"), "pan ": ("7323.93", "Steel cooking articles", 0.0, "Standard rate"), "pot ": ("7323.93", "Steel pots and pans", 0.0, "Standard rate"), "frying pan": ("7323.93", "Frying pans, steel", 0.0, "Standard rate"), "cooking pot": ("7323.93", "Cooking pots, steel", 0.0, "Standard rate"), "knife": ("8211.91", "Kitchen knives with fixed blades", 0.0, "Standard rate"), "kitchen knife": ("8211.91", "Kitchen knives with fixed blades", 0.0, "Standard rate"), "fork": ("8215.99", "Forks and spoons, steel", 0.0, "Standard rate"), "spoon": ("8215.99", "Spoons, steel", 0.0, "Standard rate"), "cutlery": ("8215.99", "Cutlery sets, steel", 0.0, "Standard rate"), "blender": ("8509.40", "Food grinders and blenders", 0.0, "ITA zero duty"), "toaster": ("8516.72", "Toasters", 0.0, "ITA zero duty"), "kettle": ("8516.10", "Electric water heaters", 0.0, "ITA zero duty"), "microwave": ("8516.50", "Microwave ovens", 0.0, "ITA zero duty"), "oven": ("8516.60", "Electric ovens", 0.0, "ITA zero duty"), "refrigerator": ("8418.10", "Refrigerators, combined", 0.0, "ITA zero duty"), "freezer": ("8418.30", "Freezers", 0.0, "ITA zero duty"), "washing machine": ("8450.11", "Fully-automatic washing machines", 0.0, "ITA zero duty"), "dryer": ("8451.29", "Drying machines", 0.0, "ITA zero duty"), "vacuum cleaner": ("8508.11", "Vacuum cleaners, power < 1500W", 0.0, "ITA zero duty"), "air conditioner": ("8415.10", "Window or wall type air conditioning machines", 0.0, "ITA zero duty"), "fan ": ("8414.51", "Table, floor or wall fans", 0.0, "ITA zero duty"), "heater": ("8516.29", "Electric space and soil heaters", 0.0, "ITA zero duty"), "iron ": ("8516.40", "Electric irons", 0.0, "ITA zero duty"), "hair dryer": ("8516.31", "Hair dryers", 0.0, "ITA zero duty"), "sewing machine": ("8452.21", "Sewing machines", 0.0, "ITA zero duty"), "luggage tag": ("4821.10", "Luggage labels, paper", 0.0, "Standard rate"), # ── Rubber & Plastics (Chapter 39-40) ──────────────────────── "rubber": ("4015.90", "Articles of vulcanised rubber", 0.065, "Standard rate"), "silicone": ("3926.90", "Plastic articles", 0.065, "Standard rate"), "plastic": ("3926.90", "Plastic articles", 0.065, "Standard rate"), "foam": ("3921.13", "Foam plastic articles", 0.065, "Standard rate"), "styrofoam": ("3921.13", "Polystyrene foam", 0.065, "Standard rate"), "pvc": ("3917.23", "PVC tubes and pipes", 0.065, "Standard rate"), "polyethylene": ("3917.23", "Plastic tubes and pipes", 0.065, "Standard rate"), "nitrile gloves": ("4015.90", "Rubber gloves", 0.065, "Standard rate"), "rubber band": ("4015.90", "Rubber bands", 0.065, "Standard rate"), "eraser": ("4015.90", "Rubber erasers", 0.065, "Standard rate"), "seal ": ("4016.93", "Rubber gaskets and seals", 0.065, "Standard rate"), "o-ring": ("4016.93", "O-rings, rubber", 0.065, "Standard rate"), # ── Paper & Stationery (Chapter 47-49) ─────────────────────── "paper": ("4819.10", "Cartons and boxes of paper", 0.0, "Standard rate"), "notebook": ("4820.10", "Notebooks and pads", 0.0, "Standard rate"), "pen ": ("9608.10", "Ball point pens", 0.0, "Standard rate"), "pencil": ("9609.10", "Pencils and crayons", 0.0, "Standard rate"), "marker": ("9608.20", "Felt-tipped and porous pens", 0.0, "Standard rate"), "stapler": ("8472.90", "Stapling machines", 0.0, "ITA zero duty"), "tape": ("3919.10", "Self-adhesive plastic tape", 0.065, "Standard rate"), "envelope": ("4817.10", "Envelopes, paper", 0.0, "Standard rate"), "calendar": ("4910.00", "Printed calendars", 0.0, "Standard rate"), "book ": ("4901.99", "Printed books, brochures", 0.0, "Standard rate"), "textbook": ("4901.99", "Textbooks and educational books", 0.0, "Standard rate"), "magazine": ("4902.10", "Newspapers and magazines", 0.0, "Standard rate"), "newspaper": ("4902.10", "Newspapers", 0.0, "Standard rate"), "brochure": ("4911.99", "Printed matter", 0.0, "Standard rate"), "poster": ("4911.99", "Printed posters", 0.0, "Standard rate"), "sticker": ("4911.91", "Printed stickers and labels", 0.0, "Standard rate"), "label": ("4821.10", "Printed labels", 0.0, "Standard rate"), # ── Beauty & Personal Care (Chapter 33-34) ─────────────────── "perfume": ("3303.00", "Perfumes and toilet waters", 0.0, "Standard rate"), "cologne": ("3303.00", "Perfumes", 0.0, "Standard rate"), "shampoo": ("3305.10", "Shampoos", 0.0, "Standard rate"), "conditioner": ("3305.90", "Hair preparations", 0.0, "Standard rate"), "soap": ("3401.11", "Toilet soap", 0.0, "Standard rate"), "lotion": ("3304.99", "Beauty preparations", 0.0, "Standard rate"), "moisturizer": ("3304.99", "Skin moisturizers", 0.0, "Standard rate"), "sunscreen": ("3304.99", "Sunscreen preparations", 0.0, "Standard rate"), "makeup": ("3304.91", "Lip and eye makeup", 0.0, "Standard rate"), "cosmetics": ("3304.99", "Cosmetic preparations", 0.0, "Standard rate"), "toothpaste": ("3306.10", "Toothpaste", 0.0, "Standard rate"), "toothbrush": ("9603.21", "Toothbrushes", 0.0, "Standard rate"), "razor": ("9615.11", "Razor blades", 0.0, "Standard rate"), "deodorant": ("3307.20", "Deodorants", 0.0, "Standard rate"), "candle": ("3406.00", "Candles and tapers", 0.0, "Standard rate"), "essential oil": ("3301.90", "Essential oils", 0.0, "Standard rate"), # ── Sports & Outdoor (Chapter 95) ──────────────────────────── "football": ("9506.62", "Inflatable balls", 0.0, "Standard rate"), "soccer ball": ("9506.62", "Inflatable balls", 0.0, "Standard rate"), "basketball": ("9506.62", "Basketballs", 0.0, "Standard rate"), "tennis": ("9506.40", "Tennis rackets", 0.0, "Standard rate"), "golf": ("9506.39", "Golf clubs and equipment", 0.0, "Standard rate"), "yoga mat": ("9506.99", "Sports equipment", 0.0, "Standard rate"), "gym equipment": ("9506.91", "Articles for gymnasium", 0.0, "Standard rate"), "dumbbell": ("9506.91", "Articles for gymnasium", 0.0, "Standard rate"), "bicycle": ("8712.00", "Bicycles", 0.14, "Standard rate"), "bike": ("8712.00", "Bicycles", 0.14, "Standard rate"), "helmet": ("6506.10", "Helmets for riders", 0.0, "Standard rate"), "fishing rod": ("9507.10", "Fishing rods", 0.0, "Standard rate"), "camping tent": ("6307.90", "Tent articles", 0.0, "Standard rate"), "sleeping bag": ("9404.30", "Sleeping bags", 0.0, "Standard rate"), "backpack outdoor": ("4202.92", "Backpacks", 0.08, "Standard rate"), "ski": ("9506.11", "Skis for snow", 0.0, "Standard rate"), "surfboard": ("9506.99", "Surf boards", 0.0, "Standard rate"), "swimming goggles": ("9506.99", "Swimming goggles", 0.0, "Standard rate"), "boxing gloves": ("9506.99", "Boxing gloves", 0.0, "Standard rate"), "badminton": ("9506.59", "Badminton rackets", 0.0, "Standard rate"), "ping pong": ("9506.40", "Table tennis equipment", 0.0, "Standard rate"), "billiard": ("9504.90", "Billiard equipment", 0.0, "Standard rate"), "pool cue": ("9504.90", "Billiard cues", 0.0, "Standard rate"), # ── Automotive Parts (Chapter 85-87) ───────────────────────── "brake pad": ("8708.30", "Brakes and servo-brakes", 0.025, "Standard rate"), "oil filter": ("8421.23", "Oil or fuel filters", 0.0, "ITA zero duty"), "air filter": ("8421.23", "Air filters", 0.0, "ITA zero duty"), "spark plug": ("8511.10", "Spark plugs", 0.0, "ITA zero duty"), "alternator": ("8511.20", "Alternators", 0.0, "ITA zero duty"), "starter motor": ("8511.40", "Starter motors", 0.0, "ITA zero duty"), "headlight": ("8512.20", "Lighting equipment for vehicles", 0.0, "ITA zero duty"), "taillight": ("8512.20", "Vehicle lighting equipment", 0.0, "ITA zero duty"), "wing mirror": ("8708.29", "Parts for vehicles", 0.025, "Standard rate"), "bumper": ("8708.10", "Bumpers and parts", 0.025, "Standard rate"), "windshield": ("7007.21", "Laminated safety glass", 0.0, "ITA zero duty"), "tyre": ("4011.10", "New pneumatic rubber tyres, cars", 0.045, "Standard rate"), "tire ": ("4011.10", "New pneumatic rubber tyres, cars", 0.045, "Standard rate"), "wheel": ("8708.70", "Wheels and parts", 0.025, "Standard rate"), "car battery": ("8507.10", "Lead-acid accumulators for vehicles", 0.0, "ITA zero duty"), "car seat cover": ("8708.99", "Parts for motor vehicles", 0.025, "Standard rate"), "floor mat car": ("8708.99", "Vehicle interior fittings", 0.025, "Standard rate"), # ── Jewelry & Watches (Chapter 71) ─────────────────────────── "ring ": ("7113.11", "Rings, silver", 0.025, "Standard rate"), "necklace": ("7113.19", "Necklaces, other precious metal", 0.025, "Standard rate"), "bracelet": ("7113.19", "Bracelets, precious metal", 0.025, "Standard rate"), "earring": ("7113.19", "Earrings, precious metal", 0.025, "Standard rate"), "watch": ("9101.19", "Wrist-watches with case of precious metal", 0.045, "Standard rate"), "pendant": ("7113.19", "Pendants, precious metal", 0.025, "Standard rate"), "brooch": ("7113.19", "Brooches, precious metal", 0.025, "Standard rate"), "anklet": ("7117.19", "Imitation jewellery, base metal", 0.04, "Standard rate"), "jewelry": ("7113.19", "Jewellery, precious metal", 0.025, "Standard rate"), # ── Building Materials (Chapter 68-73) ──────────────────────── "tile": ("6907.21", "Ceramic tiles", 0.0, "Standard rate"), "marble": ("6802.93", "Marble and articles", 0.0, "Standard rate"), "granite": ("6802.93", "Granite and articles", 0.0, "Standard rate"), "ceramic": ("6907.21", "Ceramic tiles", 0.0, "Standard rate"), "steel pipe": ("7304.31", "Seamless pipes, iron or steel", 0.0, "Standard rate"), "steel tube": ("7304.31", "Seamless tubes, iron or steel", 0.0, "Standard rate"), "aluminium": ("7604.21", "Aluminium tubes and pipes", 0.0, "Standard rate"), "cement": ("2523.29", "Portland cement", 0.0, "Standard rate"), "sand": ("2505.90", "Natural sands", 0.0, "Standard rate"), "gravel": ("2517.10", "Pebbles and gravel", 0.0, "Standard rate"), "brick": ("6904.10", "Ceramic building bricks", 0.0, "Standard rate"), "insulation": ("6808.00", "Panels of fibres and cement", 0.0, "Standard rate"), "paint": ("3208.90", "Paints and varnishes", 0.065, "Standard rate"), "varnish": ("3208.90", "Varnishes", 0.065, "Standard rate"), "adhesive": ("3506.99", "Prepared glues and adhesives", 0.0, "Standard rate"), "glue": ("3506.99", "Glues and adhesives", 0.0, "Standard rate"), "nail": ("7317.00", "Nails and tacks, iron or steel", 0.0, "Standard rate"), "screw": ("7318.15", "Screws and bolts, iron or steel", 0.0, "Standard rate"), "bolt": ("7318.15", "Bolts and screws, iron or steel", 0.0, "Standard rate"), "nut ": ("7318.16", "Nuts, iron or steel", 0.0, "Standard rate"), "washer": ("7318.22", "Washers, iron or steel", 0.0, "Standard rate"), "spring": ("7320.20", "Helical springs, iron or steel", 0.0, "Standard rate"), # ── Medical & Pharmaceutical (Chapter 30, 90) ──────────────── "mask": ("6307.90", "Face masks, textile", 0.0, "Standard rate"), "surgical mask": ("6307.90", "Surgical masks", 0.0, "Standard rate"), "glove medical": ("4015.90", "Medical examination gloves", 0.065, "Standard rate"), "bandage": ("3005.90", "Bandages and dressings", 0.0, "Standard rate"), "syringe": ("9018.31", "Syringes and needles", 0.0, "Standard rate"), " thermometer": ("9025.80", "Thermometers", 0.0, "ITA zero duty"), "stethoscope": ("9018.19", "Electro-diagnostic apparatus", 0.0, "ITA zero duty"), "hearing aid": ("9021.40", "Hearing aids", 0.0, "ITA zero duty"), "prosthetic": ("9021.39", "Artificial joints and other", 0.0, "ITA zero duty"), "wheelchair": ("8713.10", "Wheelchairs not mechanically propelled", 0.0, "ITA zero duty"), # ── Musical Instruments (Chapter 92) ────────────────────────── "guitar": ("9202.10", "String musical instruments (guitars)", 0.0, "Standard rate"), "piano": ("9201.10", "Grand pianos", 0.0, "Standard rate"), "keyboard musical": ("9207.10", "Keyboard instruments (electronic)", 0.0, "ITA zero duty"), "drum ": ("9206.00", "Percussion musical instruments", 0.0, "Standard rate"), "violin": ("9202.10", "Violins and string instruments", 0.0, "Standard rate"), "flute": ("9205.10", "Wind musical instruments", 0.0, "Standard rate"), "saxophone": ("9205.10", "Saxophones", 0.0, "Standard rate"), "trumpet": ("9205.10", "Trumpets", 0.0, "Standard rate"), "ukulele": ("9202.10", "Ukuleles", 0.0, "Standard rate"), "harmonica": ("9204.10", "Harmonicas", 0.0, "Standard rate"), "amplifier musical": ("8518.50", "Audio frequency amplifiers", 0.0, "ITA zero duty"), "guitar amp": ("8518.50", "Guitar amplifiers", 0.0, "ITA zero duty"), # ── Office Supplies ────────────────────────────────────────── "whiteboard": ("9504.90", "Board games and equipment", 0.0, "Standard rate"), "projector stand": ("9403.30", "Office furniture", 0.0, "Standard rate"), "laptop stand": ("8473.30", "Computer parts and accessories", 0.0, "ITA zero duty"), "monitor arm": ("7326.90", "Articles of iron or steel", 0.0, "Standard rate"), "desk lamp": ("9405.20", "Desk lamps", 0.0, "ITA zero duty"), "ergonomic chair": ("9401.30", "Swivel chairs", 0.0, "Standard rate"), "file holder": ("4821.10", "Labels and file holders", 0.0, "Standard rate"), "cable management": ("3926.90", "Plastic articles for cable management", 0.065, "Standard rate"), "wireless charger": ("8504.40", "Wireless chargers", 0.0, "ITA zero duty"), "magsafe": ("8504.40", "MagSafe chargers", 0.0, "ITA zero duty"), "dock": ("8473.30", "Computer docking stations", 0.0, "ITA zero duty"), "hub usb": ("8471.60", "USB hubs", 0.0, "ITA zero duty"), "ethernet cable": ("8544.42", "Electric conductors with connectors", 0.0, "ITA zero duty"), "hdmi cable": ("8544.42", "HDMI cables", 0.0, "ITA zero duty"), "power strip": ("8536.90", "Power strips and extension leads", 0.0, "ITA zero duty"), # ── Miscellaneous ──────────────────────────────────────────── "umbrella": ("6601.10", "Garden umbrellas and similar", 0.08, "Standard rate"), "decoration": ("9701.10", "Paintings, drawings and pastels", 0.0, "Standard rate"), "artwork": ("9701.10", "Original artworks", 0.0, "Standard rate"), "flower": ("0603.11", "Fresh cut roses", 0.0, "Standard rate"), "plant": ("0602.90", "Live plants", 0.0, "Standard rate"), "seed": ("1209.99", "Seeds for sowing", 0.0, "Standard rate"), "fertilizer": ("3105.20", "Mineral or chemical fertilizers", 0.0, "Standard rate"), "pet food": ("2309.10", "Dog or cat food", 0.0, "Standard rate"), "litter": ("2530.10", "Expanded minerals (cat litter)", 0.0, "Standard rate"), "aquarium": ("9306.90", "Other articles for aquariums", 0.0, "Standard rate"), "incense": ("3307.41", "Incense and宗教 articles", 0.0, "Standard rate"), "candle scented": ("3406.00", "Scented candles", 0.0, "Standard rate"), "photo frame": ("4414.90", "Frames for photographs", 0.0, "Standard rate"), "mirror": ("7009.92", "Glass mirrors, framed", 0.0, "Standard rate"), "binoculars": ("9005.80", "Binoculars", 0.0, "ITA zero duty"), "telescope": ("9005.80", "Telescopes", 0.0, "ITA zero duty"), "compass": ("9014.10", "Direction-finding compasses", 0.0, "ITA zero duty"), "scale": ("8423.10", "Weighing scales", 0.0, "ITA zero duty"), "thermos": ("9617.00", "Vacuum flasks", 0.0, "Standard rate"), "ice cream maker": ("8210.00", "Hand-operated food grinders", 0.0, "Standard rate"), "popcorn maker": ("8516.72", "Coffee or tea makers", 0.0, "ITA zero duty"), "pet bed": ("9404.21", "Mattress supports", 0.0, "Standard rate"), "dog house": ("4418.60", "Doors and windows, wood", 0.0, "Standard rate"), "bird cage": ("7326.20", "Articles of wire, iron or steel", 0.0, "Standard rate"), "smoke detector": ("8531.10", "Fire or smoke detectors", 0.0, "ITA zero duty"), "carbon monoxide detector": ("8531.10", "Gas detectors", 0.0, "ITA zero duty"), "flashlight": ("8513.10", "Hand-held electric torches", 0.0, "ITA zero duty"), "lantern": ("8513.10", "Electric lanterns", 0.0, "ITA zero duty"), "power tool": ("8467.21", "Electric drills", 0.0, "ITA zero duty"), "drill": ("8467.21", "Electric drills", 0.0, "ITA zero duty"), "saw": ("8467.22", "Electric saws", 0.0, "ITA zero duty"), "grinder": ("8467.81", "Grinding machines", 0.0, "ITA zero duty"), "sandpaper": ("6805.30", "Articles of paper with abrasive coating", 0.0, "Standard rate"), "paint brush": ("9603.30", "Artists' brushes and writing brushes", 0.0, "Standard rate"), "roller paint": ("9603.30", "Paint rollers", 0.0, "Standard rate"), "caulking gun": ("8467.81", "Hand tools, pneumatic", 0.0, "ITA zero duty"), "wrench": ("8204.11", "Hand-operated spanners and wrenches", 0.0, "Standard rate"), "screwdriver": ("8205.40", "Screwdrivers", 0.0, "Standard rate"), "pliers": ("8203.20", "Pliers, including cutting pliers", 0.0, "Standard rate"), "hammer": ("8205.20", "Hand hammers and sledge hammers", 0.0, "Standard rate"), "tape measure": ("9017.80", "Measuring tapes", 0.0, "ITA zero duty"), "level": ("9017.20", "Drawing and measuring instruments", 0.0, "ITA zero duty"), "multimeter": ("9030.33", "Multimeters", 0.0, "ITA zero duty"), "soldering iron": ("8515.11", "Soldering irons and guns", 0.0, "ITA zero duty"), "cable tester": ("9030.33", "Electrical measuring instruments", 0.0, "ITA zero duty"), "network tester": ("9030.33", "Instruments for testing electrical quantities", 0.0, "ITA zero duty"), "insulation tape": ("3919.10", "Self-adhesive plastic tape", 0.065, "Standard rate"), "duct tape": ("3919.10", "Self-adhesive tape", 0.065, "Standard rate"), "painters tape": ("4811.41", "Adhesive paper and tape", 0.0, "Standard rate"), "velcro": ("5807.90", "Labels and badges, textile", 0.12, "Textile rate"), "zipper": ("9607.11", "Slide fasteners, with chain of zinc", 0.0, "Standard rate"), "button": ("9606.21", "Buttons of plastics", 0.0, "Standard rate"), "snap": ("9606.30", "Button moulds and other parts", 0.0, "Standard rate"), "buckle": ("9607.20", "Slide fastener parts", 0.0, "Standard rate"), } def guess_hs_code(description: str) -> str: """Guess HS code from item description using keyword matching. Uses a comprehensive database of 300+ product categories. Returns (hs_code, matched_keyword) or ("9999.99", None) if no match. """ desc_lower = description.lower() # Try exact keyword matches first (most specific) best_match = None best_match_len = 0 for keyword, (hs_code, desc, duty, notes) in HS_CODE_DATABASE.items(): if keyword in desc_lower: # Prefer longer, more specific matches if len(keyword) > best_match_len: best_match = (hs_code, desc, duty, notes, keyword) best_match_len = len(keyword) if best_match: return best_match[0] # Default: unknown return "9999.99" async def classify_single_item(item: ItemInput, destination: str) -> ItemClassification: """Classify a single item with HS code and duty calculation.""" # Guess HS code from description hs_code = guess_hs_code(item.description) # Look up duty rate and description from database matched = None desc_lower = item.description.lower() for keyword, (code, desc, duty, notes) in HS_CODE_DATABASE.items(): if keyword in desc_lower and code == hs_code: matched = (code, desc, duty, notes) break if matched: taric_desc = matched[1] duty_rate = matched[2] notes = matched[3] else: # Unknown product — use TARIC API for description, default rate taric_info = await fetch_taric_description(hs_code) taric_desc = taric_info.get("description", "Unknown product") duty_rate = DEFAULT_DUTY_RATE notes = "Unknown product, using default duty rate" # Calculate values customs_value = item.quantity * item.unit_price_usd duty_amount = customs_value * duty_rate vat_rate = DEFAULT_VAT_RATE vat_amount = (customs_value + duty_amount) * vat_rate total_landed = customs_value + duty_amount + vat_amount return ItemClassification( description=item.description, hs_code=hs_code, taric_description=taric_desc[:100], origin=item.origin_country, customs_value=round(customs_value, 2), duty_rate=duty_rate, duty_amount=round(duty_amount, 2), vat_rate=vat_rate, vat_amount=round(vat_amount, 2), total_landed_cost=round(total_landed, 2), notes=notes, ) # ── API Endpoint ───────────────────────────────────────────────────── @router.post("/classify", response_model=ClassifyResponse) async def classify_items(request: ClassifyRequest) -> ClassifyResponse: """Classify items with HS codes and calculate duties. Accepts a list of items with descriptions, quantities, and prices. Returns HS code classifications, duty rates, and total landed costs. """ logger.info(f"Classifying {len(request.items)} items for {request.destination_country}") classified_items = [] for item in request.items: try: classified = await classify_single_item(item, request.destination_country) classified_items.append(classified) except Exception as e: logger.warning(f"Failed to classify item '{item.description}': {e}") # Return fallback classification customs_value = item.quantity * item.unit_price_usd classified_items.append(ItemClassification( description=item.description, hs_code="9999.99", taric_description="Classification failed", origin=item.origin_country, customs_value=round(customs_value, 2), duty_rate=0.15, duty_amount=round(customs_value * 0.15, 2), vat_rate=0.19, vat_amount=round(customs_value * 1.15 * 0.19, 2), total_landed_cost=round(customs_value * 1.15 * 1.19, 2), notes="Classification failed, using fallback rate", )) # Calculate summary total_customs = sum(item.customs_value for item in classified_items) total_duty = sum(item.duty_amount for item in classified_items) total_vat = sum(item.vat_amount for item in classified_items) shipping_cost = 2500.00 # Default shipping estimate summary = DutySummary( customs_value=round(total_customs, 2), total_duty=round(total_duty, 2), total_vat=round(total_vat, 2), shipping_cost=shipping_cost, total_taxes_fees=round(total_duty + total_vat, 2), total_landed_cost=round(sum(item.total_landed_cost for item in classified_items) + shipping_cost, 2), effective_rate=round((total_duty + total_vat) / total_customs * 100, 2) if total_customs > 0 else 0, ) return ClassifyResponse( status="completed", classified_at=datetime.now(timezone.utc).isoformat(), destination_country=request.destination_country, items=classified_items, summary=summary, )