indian-txn-classifier / code /company_inference.py
SahilGoel's picture
Upload code/company_inference.py with huggingface_hub
19cd60d verified
Raw
History Blame Contribute Delete
3.06 kB
"""Conservative company-name labels for transaction model training."""
from __future__ import annotations
import re
from typing import Optional
from pipeline.merchant_classifier import (
CURATED_TRANSACTION_MARKERS,
HANDLE_CATEGORY_MAP,
MERCHANT_ALIASES,
classify_upi_merchant,
extract_upi_handle,
get_merchant,
)
_PERSONAL_CATEGORIES = {
"personal_transfer",
"friends",
"family",
"staff_salary",
"rental",
"transfer",
"cash_withdrawal",
}
_GENERIC_NAMES = {
"",
"unknown upi counterparty",
"upi transfer",
"payment",
"transfer",
"unknown",
}
def _name_key(value: object) -> str:
return " ".join(re.sub(r"[^a-z0-9]+", " ", str(value or "").lower()).split())
_CANONICAL_COMPANIES = {
_name_key(company[0]): company[0]
for company in (
*MERCHANT_ALIASES.values(),
*CURATED_TRANSACTION_MARKERS.values(),
*HANDLE_CATEGORY_MAP.values(),
)
}
_CANONICAL_ALIASES = {
"indian cle": "Indian Clearing Corporation",
"iccl zerodha credit": "Indian Clearing Corporation",
"iccl zerod": "Indian Clearing Corporation",
"zerodha br": "Zerodha",
"zerodha deposit": "Zerodha",
}
def _clean_company_name(value: object) -> Optional[str]:
name = " ".join(str(value or "").split()).strip(" -/|")[:100]
if name.lower() in _GENERIC_NAMES:
return None
return name or None
def _canonical_company_name(value: object) -> Optional[str]:
cleaned = _clean_company_name(value)
if not cleaned:
return None
key = _name_key(cleaned)
if key.startswith("cred ") or key == "cred":
return "CRED"
return _CANONICAL_ALIASES.get(key) or _CANONICAL_COMPANIES.get(key)
def infer_company_name(
description: str,
*,
category: str = "",
explicit_name: object = None,
) -> Optional[str]:
"""Return a company only when merchant evidence is strong enough to label."""
if category in _PERSONAL_CATEGORIES:
return None
explicit = _canonical_company_name(explicit_name)
if explicit:
return explicit
handle = extract_upi_handle(description or "")
if handle:
try:
merchant = get_merchant(handle)
except Exception:
merchant = None
if (
merchant
and merchant.get("category") not in _PERSONAL_CATEGORIES | {"unclassified", "upi_spend"}
and float(merchant.get("confidence", 0.0)) >= 0.70
):
company = _canonical_company_name(merchant.get("display_name"))
if company:
return company
try:
evidence_description = "" if handle else (description or "")
inferred = classify_upi_merchant(handle or "", evidence_description, learn=False)
except Exception:
return None
if (
inferred.get("category") in _PERSONAL_CATEGORIES | {"unclassified"}
or float(inferred.get("confidence", 0.0)) < 0.70
):
return None
return _canonical_company_name(inferred.get("display_name"))