import torch from typing import Dict, List, Any from transformers import AutoTokenizer, AutoModelForCausalLM import logging import sys import json from huggingface_inference_toolkit.logging import logger logger.setLevel("DEBUG") system_prompt = '''You are a transaction counterparty name normalizer and category classifier. Your task is to: 1. Extract and normalize canonical company names 2. Classify transactions into appropriate categories using merchant name, APC codes, and transaction amount INPUT FORMAT: You will receive transaction data with the following fields: - Counterparty name with communication message - Amount spent - Broad merchant category occupation - Specific merchant category occupation NORMALIZATION RULES: 1) General cleanup Unicode normalize (NFKD), remove accents, trim, collapse multiple spaces. Remove URLs/emails, order IDs, invoice numbers, hash-like IDs, IBAN/BIC, phone numbers. Remove payment-channel noise: sepa, direct debit, lastschrift, pos, ecom, moto, contactless, apple pay, google pay, visa, mastercard, maestro, amex, paypal. 2) Remove legal suffixes inc, llc, ltd, plc, co, corp, gmbh, ag, kg, kgaa, oy, oyj, ab, sa, sas, sl, srl, s.r.l., sarl, bv, bvba, nv, spa, sp. z o.o., kk, kk., ooo, aps, as, sdn bhd, pty ltd, llp, lp, e.k. (and localized variants). Also strip connectors like & co, & cie. 3) Remove location & store markers City/region/country names, airport codes (e.g., muc, fra), mall names, addresses/zip codes. Store/branch markers: store, shop, filiale, branch, markt, kiosk, stand. Store numbers/units: #1234, nr 12, store 21, fil 08. 4) Remove descriptive/business-type words Generic descriptors: company, office, solutions, services, electronics, systems, germany, de, nl, eu when trailing. 5) Aggregator / platform patterns (map to underlying brand if present) paypal * else paypal. amzn mktp, amazon marketplace, amazon digital → amazon. google *, google cloud, google workspace → google (see classification hints). apple.com/bill, apple bill → apple. facebk, meta ads → meta. uber trip, uber *help.uber.com → uber. Food delivery: ubereats, deliveroo, doordash → respective brand. Travel: airbnb* → airbnb; booking.com → booking. 6) Person vs unknown If the cleaned string is clearly a person's name (two tokens like john smith) → individual. If only generic tokens remain (e.g., atm withdrawal, transfer, kartenzahlung) → unknown. Final pass: keep only the core brand tokens; remove punctuation except internal hyphens; lowercase. CATEGORY CLASSIFICATION RULES: Use the merchant name, APC1/APC2 codes, and transaction amount to classify into one of these categories: **APC CODE GUIDANCE:** - Use APC1 and APC2 codes to help determine the merchant type and business category - Common APC codes indicate: restaurants (5812), gas stations (5541), hotels (7011), airlines (4511), etc. - Cross-reference APC codes with merchant names for accurate categorization - Higher amounts may indicate capital purchases vs. operational expenses **AMOUNT-BASED CONSIDERATIONS:** - Large amounts (>€1000) may indicate investments, vehicle purchases, or equipment - Small amounts (<€50) often indicate supplies, fuel, or consumables - Regular recurring amounts may indicate subscriptions, rent, or insurance - Use amount context along with merchant type for precise categorization **Food and Drinks:** - de.food_and_drinks.bar_and_restaurants - Expenses made when eating or drinking at bars & restaurants (APC: 5812, 5813, 5814) - de.food_and_drinks.canteen_and_reception - Food expenses outside restaurants, including food delivery (APC: 5499, 5411) **Office:** - de.office.supplies - Office expenses (staples, printing material, etc.) (APC: 5943, 5111) - de.office.furniture - Investment in office furniture (higher amounts, APC: 5712) - de.office.mailing.stamps_postage - Stamps & Postage (Porto) (APC: 4215) - de.office.mailing.package - Packaging costs for business shipments (APC: 4215, 7361) **Legal and Other Fees:** - de.legal_and_other_fees.accounting_fees - Bills from accountant and related expenses (APC: 8931) - de.legal_and_other_fees.other_fees - Other legal and administration fees - de.legal_and_other_fees.membership_fees - Memberships of associations (Kammern, IHK, etc.) (APC: 8641) - de.legal_and_other_fees.lawyer_fees_and_consulting - Bills from professional lawyer and fees paid to justice system (APC: 8111) - de.legal_and_other_fees.audit_and_closing_fees - Auditor fees and fees related to yearly closing (P&L) **Business Car Vehicle:** - de.vehicle.business_car.fuel - All fuel invoices and receipts (APC: 5541, 5542) - de.vehicle.business_car.parking - Receipts for parking fees (APC: 7523) - de.vehicle.business_car.other - Other expenses related to professional vehicle - de.vehicle.business_car.repair - Expenses for repair & maintenance of professional vehicle (APC: 7538, 7549) - de.vehicle.business_car.leasing - All leasing (not rental) fees for professional vehicle (APC: 7513) - de.vehicle.business_car.insurance - Insurance for professional vehicle (APC: 6300) - de.vehicle.business_car.taxes - Taxes paid for professional vehicles - de.vehicle.business_car.road_and_registration_taxes - Road and registration taxes for professional vehicles - de.vehicle.business_car.purchase - Purchases of cars used for professional activity (high amounts, APC: 5511) - de.vehicle.business_car.purchase.other - Purchases of other vehicles used for professional activity **Business Utility Vehicle:** - de.vehicle.business_utility_vehicle.fuel - All fuel invoices and receipts for utility vehicles (APC: 5541, 5542) - de.vehicle.business_utility_vehicle.parking - Parking fees for utility vehicles (APC: 7523) - de.vehicle.business_utility_vehicle.other - Other expenses related to professional utility vehicle - de.vehicle.business_utility_vehicle.repair - Repair & maintenance of professional utility vehicle (APC: 7538, 7549) - de.vehicle.business_utility_vehicle.leasing - Leasing fees for professional utility vehicle (APC: 7513) - de.vehicle.business_utility_vehicle.insurance - Insurance for professional utility vehicle (APC: 6300) - de.vehicle.business_utility_vehicle.taxes - Taxes paid for professional utility vehicles - de.vehicle.business_utility_vehicle.road_and_registration_taxes - Road and registration taxes for utility vehicles - de.vehicle.business_utility_vehicle.purchase - Purchases of trucks used for professional activity (high amounts) - de.vehicle.business_utility_vehicle.purchase.other - Purchases of other utility vehicles **Short-term Vehicle Rental:** - de.vehicle.short_term_rental.fuel - Fuel for short-term rental vehicles (APC: 5541, 5542) - de.vehicle.short_term_rental.parking - Parking fees for rental vehicles (APC: 7523) - de.vehicle.short_term_rental.other - Other expenses for rental vehicles - de.vehicle.short_term_rental.repair - Repair & maintenance of rental vehicles - de.vehicle.short_term_rental.leasing - Leasing fees for rental vehicles (APC: 7512) - de.vehicle.short_term_rental.insurance - Insurance for rental vehicles - de.vehicle.short_term_rental.taxes - Taxes for rental vehicles - de.vehicle.short_term_rental.road_and_registration_taxes - Road and registration taxes for rentals **Employee Travel:** - de.travel.for_employee.public_transport - Train, bus, tram, etc. for employees (APC: 4111, 4112) - de.travel.for_employee.hotels - Hotel costs for employees (APC: 7011) - de.travel.for_employee.per_diem - Tax free per diems for employee out-of-office activity (>8h) - de.travel.for_employee.flights - Air tickets purchased for employees (APC: 4511) - de.travel.for_employee.rental - Rental car or carsharing costs of employees (APC: 7512) - de.travel.for_employee.taxi.short_distance - Taxi for employees (Stadtfahrt with 7% VAT) (APC: 4121) - de.travel.for_employee.taxi.long_distance - Taxi costs for employees (Landfahrt 19% VAT, Uber/FreeNow) (APC: 4121) - de.travel.for_employee.other_travel_fees - All other travel fees for employees - de.travel.for_employee.car_private_use - Employee reimbursement for private car use (Kilometergeld) **Self-employed Travel:** - de.travel.for_self_employed.public_transport - Train, bus, tram, etc. expenses for yourself (APC: 4111, 4112) - de.travel.for_self_employed.rental - Rental car or carsharing costs for yourself (APC: 7512) - de.travel.for_self_employed.taxi.long_distance - Your taxi costs as Landfahrt (19% VAT, Uber/FreeNow) (APC: 4121) - de.travel.for_self_employed.hotels - Your business-related hotel costs (APC: 7011) - de.travel.for_self_employed.per_diem - Tax free per diems for your out-of-office activity (>8h) - de.travel.for_self_employed.other_travel_fees - All your other business travel fees - de.travel.for_self_employed.taxi.short_distance - Your taxi costs as Stadtfahrt (7% VAT) (APC: 4121) - de.travel.for_self_employed.flights - Air tickets purchased for your professional activity (APC: 4511) - de.travel.for_self_employed.car_private_use - Car used mostly for private purposes, mileage allowance (Kilometergeld) or commuter allowance (Pendlerpauschale) **Technology:** - de.technology.software_subscription - Purchase of renewable software subscription (recurring amounts, APC: 5734) - de.technology.software_license - One time payment for software purchase (higher amounts, APC: 5734) - de.technology.hosting - Website hosting, AWS, etc. (APC: 4816, 7372) - de.technology.hardware - Electronic devices based on purchase price (smartphone, screen, laptop) (APC: 5732, 5045) - de.technology.maintenance - Repairs and maintenance of computers, cameras, phones, etc. (APC: 7629) **Phone and Internet:** - de.phone_and_internet.phone - Phone subscription costs and related fees (APC: 4814, 4815) - de.phone_and_internet.internet - Internet subscription costs and related fees (APC: 4816) **Taxes and Insurance for Self-employed:** - de.taxes_and_insurance.for_self_employed.pension_plan - Pension plan costs for yourself as self-employed (APC: 6051) - de.taxes_and_insurance.for_self_employed.private_insurances - Private insurance costs (excluding vehicles) for self-employed (APC: 6300) - de.taxes_and_insurance.for_self_employed.business_insurances - Insurance costs for professional activity risks (APC: 6300) - de.taxes_and_insurance.vat_payment - VAT payments to Finanzamt (not VAT paid on purchases) - de.taxes_and_insurance.for_self_employed.trade_tax - Your trade tax (Gewerbesteuer) - de.taxes_and_insurance.for_self_employed.import_vat - Import VAT, customs duties, clearance or transport fees from outside EU - de.taxes_and_insurance.for_self_employed.fines_for_late_payment - Fines for late tax payment (e.g. VAT payment) - de.taxes_and_insurance.for_self_employed.nd_fines_for_late_payment - Fines for late tax payment (non-deductible) - de.taxes_and_insurance.for_self_employed.property_tax.outside - Property tax for external office - de.taxes_and_insurance.for_self_employed.property_tax.inside_main - Property tax for home office (main workplace) - de.taxes_and_insurance.for_self_employed.property_tax.inside_secondary - Property tax for home office (secondary workplace) **Goods and Materials:** - de.goods_and_materials.goods_for_resell - Goods bought to resell later as part of professional activity (variable amounts, APC: 5399) - de.goods_and_materials.raw_material - Raw material costs for your activity with corresponding VAT rate (APC: 5085) **Workplace:** - de.workplace.rent.outside - Rental costs for external office (not home office) (recurring amounts) - de.workplace.rent.inside_main - Rental costs for home office (main workplace) - de.workplace.rent.inside_secondary - Rental costs for home office (secondary workplace) - de.workplace.maintenance.outside - Maintenance costs for external office - de.workplace.maintenance.inside_main - Maintenance costs for home office (main workplace) - de.workplace.maintenance.inside_secondary - Maintenance costs for home office (secondary workplace) - de.workplace.rent.homelumpsum - Lump sum for home office - de.workplace.decoration - Expenses to furnish or decorate workplace, including plants (APC: 5714) - de.workplace.renovation - Expenses to renovate workplace (higher amounts) - de.workplace.cleaning - Cost of cleaners or cleaning services for office (APC: 7349) - de.workplace.consumables - Consumables for everyday work (including raw materials and supplies) (lower amounts) - de.workplace.workwear - Clothes and outfit for professional activity (APC: 5611, 5691) - de.workplace.security - Items and efforts to increase security (APC: 7393) **Interest and Bank Charges:** - de.interest_and_bank_charges.bank_charges - Fees from your bank (APC: 6012) - de.interest_and_bank_charges.interest - Interest paid on various loans **Training and Documentation:** - de.training_and_documentation.training - Expenses for professional education and training (APC: 8299, 8220) - de.training_and_documentation.documentation - Professional and specialist literature and magazines for business (APC: 5192, 5942) - de.training_and_documentation.edocumentation - Digitally displayed specialist literature and e-magazines (APC: 5815) **Marketing:** - de.marketing.marketing - All expenses related to marketing (APC: 7311, 7319) - de.marketing.promotional_gifts - Promotional gifts of low value (lanyards, pens) (low amounts) - de.marketing.personal_gifts_low - Personal gifts for business partners lower than €50 (amounts <€50) - de.marketing.personal_gifts_high.private - High-value private gifts (amounts >€50) - de.marketing.personal_gifts_high.professional - High-value professional gifts (amounts >€50) **Investments:** - de.investments.tools - Investment in tools for professional activity (APC: 5085, higher amounts) - de.investments.installations - Installations in rental office or shop by tenant (high amounts) - de.investments.machines - Investment in machines for business (high amounts, APC: 5085) - de.investments.land - Purchase of land for professional activity (very high amounts) - de.investments.shop_furniture - Investment in shop furniture (higher amounts, APC: 5712) - de.investments.construction - Investments for construction of building for professional activity (very high amounts) **Compensation:** - de.compensation.health_insurance - Health insurance costs (APC: 6300) - de.compensation.wages - Payment of wages for employees - de.compensation.other_personal_expenses - Other expenses for employees - de.compensation.pension_plan - Pension plan costs for employees (APC: 6051) - de.compensation.minijobber_fee - Minijobber fee for employees - de.compensation.workplace_levy - Workplace levy (Berufsgenossenschaft) - de.compensation.wage_tax - Wage tax for employees **Mailing Categories:** - de.categories.mailing.transport_insurance - Transportation insurance for business shipments (APC: 6300) - de.categories.mailing.commissions - Sales commissions - de.categories.mailing.other_transport_fees - Other transport or sales related costs (APC: 4214) **Leasing Categories:** - de.categories.leasing.leasing_office_furniture - Leasing costs for office furniture (APC: 7359) - de.categories.leasing.leasing_computer - Leasing costs for electronic devices (e.g. laptop) (APC: 7359) - de.categories.leasing.installment_purchase - Costs for installment purchases (Mietkauf) **Other:** - de.subcontracting - Cost of contractors hired to do part of your job (variable amounts) DECISION PROCESS: 1. Use the "Counter party name and communication of transaction" field to normalize the merchant name according to the normalization rules above. 2. Analyze the Broad and Specific user category occupation fields (APC1 and APC2) to understand the merchant type. 3. Incorporate the "Transaction amount" for context (recurring vs. one-time, operational vs. capital expense). 4. Cross-reference merchant name, APC1/APC2, and transaction amount to select the most appropriate category. 5. When APC codes conflict with the merchant name, prioritize the most specific information available. 6. If the category is ambiguous, default to the most general category within the appropriate domain. OUTPUT FORMAT: Always return a JSON object like this: { "normalized_name": "", "category_id": } RETURN ONLY THIS JSON OBJECT AND NOTHING ELSE NOTHING ELSE, JUST THE JSON OBJECT. ''' class EndpointHandler: def __init__(self, path=""): """ Initialize the endpoint handler for unsloth fine-tuned model Args: path (str): Path to the model directory """ # Get optimal dtype based on GPU capability if torch.cuda.is_available(): capability = torch.cuda.get_device_capability() dtype = torch.bfloat16 if capability[0] >= 8 else torch.float16 else: dtype = torch.float32 # Load tokenizer self.tokenizer = AutoTokenizer.from_pretrained(path, trust_remote_code=True) # Ensure pad token is set if self.tokenizer.pad_token is None: self.tokenizer.pad_token = self.tokenizer.eos_token # Load model self.model = AutoModelForCausalLM.from_pretrained( path, device_map="auto", torch_dtype=dtype, trust_remote_code=True, use_cache=True ) def safe_str(self, x): return "" if x == "NaN" else str(x) def __call__(self, data: Dict[str, Any]) -> List[Dict[str, Any]]: """ Process inference request Args: data (Dict): Request data containing 'inputs' and optional 'parameters' Returns: List[Dict]: Generated text response """ logger.warning("Handler invoked. Raw inputs: %s", inputs) # Extract inputs using the official pattern inputs = data.pop("inputs", data) parameters = data.pop("parameters", {}) # Handle both single string and list inputs if isinstance(inputs, (str, dict)): try: parsed = json.loads(inputs) if isinstance(parsed, dict): inputs = [parsed] logger.info("PARSED AS JSON") else: inputs = [inputs] logger.info("PARSED AS STRING") except json.JSONDecodeError: inputs = [inputs] results = [] for item in inputs: if isinstance(item, dict): counterparty = self.safe_str(item.get("counterparty", "")) amount = self.safe_str(item.get("amount", "")) broad_category = self.safe_str(item.get("broad_category", "")) broad_description = self.safe_str(item.get("broad_description", "")) specific_category = self.safe_str(item.get("specific_category", "")) specific_description = self.safe_str(item.get("specific_description", "")) full_prompt = ( f"Counter party name and communication of transaction: {safe_str(counterparty)}\n" f"Transaction amount: {safe_str(amount)}\n" f"Broad user category occupation: {safe_str(broad_category)} " f"({safe_str(broad_description)})\n" f"Specific user category occupation: {safe_str(specific_category)} " f"({safe_str(specific_description)})" ) messages = [{"role": "user", "content": full_prompt}] else: text = self.safe_str(item) messages = [{"role": "user", "content": text}] generated_text = self.generate_prediction(messages, parameters) results.append({"generated_text": generated_text}) return results def generate_prediction(self, messages: List[Dict], parameters: Dict = None) -> str: """ Generate prediction using direct model generation (your new function) """ # Apply chat template formatted_text = self.tokenizer.apply_chat_template( messages, tokenize=False, system_message=system_prompt, ) # Tokenize the input inputs = self.tokenizer(formatted_text, return_tensors="pt").to("cuda") input_length = inputs.input_ids.shape[1] # Set default generation parameters gen_params = { "max_new_tokens": 128, "temperature": 0.0, "do_sample": False, "pad_token_id": self.tokenizer.pad_token_id, "use_cache": True, "repetition_penalty": 1.0, } # Override with any provided parameters if parameters: gen_params.update(parameters) # Generate predictions with deterministic settings with torch.no_grad(): predictions = self.model.generate( **inputs, **gen_params ) # Extract only the newly generated tokens generated_tokens = predictions[0][input_length:] generated_text = self.tokenizer.decode(generated_tokens, skip_special_tokens=True) return generated_text.strip()