gem-bid-risk / features.py
HarshilDaGoat's picture
Upload risk model
3fb2f8d verified
Raw History Blame Contribute Delete
9.7 kB
"""Bid facts -> the risk model's feature vector.
Input is what the pipeline observes, as plain dicts (no app imports), so the same code runs
in training, inside the app, and standalone from the Hugging Face repo:
featurize(tender, observed)
tender: {"msme_reserved", "mii_threshold", "epfo_applicable", "mandatory_docs"}
observed: {"portal": {source: payload}, "ocr": {doc_type: ocr_extracted_json},
"declarations": {criterion_code: value}, "missing_docs": [doc_type],
"employees": int | None}
Comparisons normalise values the way the rule engine does (name token sets, +/-2 points on
local content, compacted identifiers) but are reimplemented here to keep this module standalone.
"""
import math
import re
from datetime import date
NAN = math.nan
_LEGAL_SUFFIXES = {"private", "pvt", "limited", "ltd", "llp", "m/s", "ms", "the"}
# Categorical encodings. "missing" = the source returned nothing (outage / not verified).
STATUS_CODES = {
"gstn": ["missing", "active", "cancelled", "suspended", "other"],
"pan": ["missing", "valid", "invalid", "other"],
"debarment": ["missing", "clear", "debarred", "other"],
"udyam": ["missing", "valid", "not_found", "other"],
"startup_india": ["missing", "valid", "expired", "other"],
"epfo_esic": ["missing", "active", "other"],
}
CATEGORY_CODES = ["none", "Micro", "Small", "Medium", "Large"]
FACTS = [
"enterprise_category", "local_content_pct", "startup_recognized", "manufacturer_or_trader",
"gstin", "gst_trade_name", "gst_legal_name", "pan", "udyam_number", "epfo_establishment_code",
"dpiit_number",
]
_UDYAM_DOCS = ("UDYAM_CERTIFICATE", "EMD_EXEMPTION_PROOF")
FEATURE_NAMES = (
["tender_msme_reserved", "tender_mii_threshold", "tender_epfo_applicable", "tender_n_mandatory_docs"]
+ [f"{source}_status" for source in STATUS_CODES]
+ ["udyam_category", "mii_verified_pct", "mii_gap_to_threshold", "declared_pct", "document_pct",
"declared_minus_verified_pct", "document_minus_verified_pct", "employees_declared",
"epfo_registered", "employee_ratio", "n_missing_mandatory_docs", "missing_exemption_proof",
"n_uploaded_docs", "n_unreadable_docs", "n_declarations"]
+ [f"match_{fact}" for fact in FACTS]
+ ["n_mismatches", "n_comparisons"]
)
CATEGORICAL_FEATURES = [f"{source}_status" for source in STATUS_CODES] + ["udyam_category"]
def _num(value) -> float:
try:
return float(value)
except (TypeError, ValueError):
return NAN
def _code(options: list[str], value) -> int:
if value is None:
return 0
return options.index(value) if value in options else len(options) - 1
def _fields(observed: dict, *doc_types: str) -> dict:
for doc_type in doc_types:
doc = observed["ocr"].get(doc_type) or {}
if doc.get("status") == "extracted":
return doc.get("fields") or {}
return {}
def _tokens(value) -> frozenset | None:
toks = frozenset(re.findall(r"[a-z0-9]+", str(value).casefold().replace("&", " and "))) - _LEGAL_SUFFIXES
return toks or None
def _compact(value) -> str | None:
return re.sub(r"\s", "", str(value)).upper() or None
def _roles(value) -> frozenset | None:
text = str(value).casefold()
if not text.strip():
return None
roles = {r for r, stem in (("manufacturer", "manufactur"), ("trader", "trad")) if stem in text}
return frozenset(roles or {"other"})
def _recognized(value) -> bool | None:
return None if value is None else str(value).casefold() in ("valid", "active", "recognized")
def _still_valid(value) -> bool | None:
try:
return date.fromisoformat(str(value)) >= date.today()
except ValueError:
return None
def _agree(values: list, equal) -> int:
"""1 all available sources agree, 0 some pair disagrees, -1 fewer than two sources."""
present = [v for v in values if v is not None]
if len(present) < 2:
return -1
return int(all(equal(a, b) for i, a in enumerate(present) for b in present[i + 1:]))
def fact_matches(observed: dict) -> dict[str, int]:
p, d = observed["portal"], observed["declarations"]
udyam_doc = _fields(observed, *_UDYAM_DOCS)
gst_doc = _fields(observed, "GST_CERTIFICATE")
mii_doc = _fields(observed, "MII_LOCAL_CONTENT_SELF_CERTIFICATE")
startup_doc = _fields(observed, "STARTUP_EMD_EXEMPTION_PROOF")
get = lambda source, key: (p.get(source) or {}).get(key) # noqa: E731
casefold = lambda v: None if v is None else str(v).casefold() # noqa: E731
pct = lambda v: None if math.isnan(_num(v)) else _num(v) # noqa: E731
declared_role = {"manufacturer": frozenset({"manufacturer"}), "trader": frozenset({"trader"}),
"trading": frozenset({"trader"})}.get(str(d.get("manufacturer_or_trader_self_declared", "")).casefold())
same = lambda a, b: a == b # noqa: E731
return {
"enterprise_category": _agree([casefold(d.get("enterprise_category_self_declared")),
casefold(udyam_doc.get("enterprise_category")),
casefold(get("udyam", "category"))], same),
"local_content_pct": _agree([pct(d.get("local_content_pct_self_declared")), pct(mii_doc.get("local_content_pct")),
pct(get("mii_local_content", "verified_pct_estimate"))],
lambda a, b: abs(a - b) <= 2.0),
"startup_recognized": _agree([None if "startup_status_self_declared" not in d
else "recognized" in str(d["startup_status_self_declared"]).casefold(),
_still_valid(startup_doc.get("valid_until")) if startup_doc.get("valid_until") else None,
_recognized(get("startup_india", "status"))], same),
"manufacturer_or_trader": _agree([declared_role, _roles(udyam_doc["activity"]) if udyam_doc.get("activity") else None,
_roles(get("udyam", "activity")) if get("udyam", "activity") else None],
lambda a, b: bool(a & b)),
"gstin": _agree([_compact(gst_doc["gstin"]) if gst_doc.get("gstin") else None,
_compact(get("gstn", "gstin")) if get("gstn", "gstin") else None], same),
"gst_trade_name": _agree([_tokens(gst_doc["trade_name"]) if gst_doc.get("trade_name") else None,
_tokens(get("gstn", "trade_name")) if get("gstn", "trade_name") else None], same),
"gst_legal_name": _agree([_tokens(gst_doc["legal_name"]) if gst_doc.get("legal_name") else None,
_tokens(get("gstn", "legal_name")) if get("gstn", "legal_name") else None], same),
"pan": _agree([_compact(_fields(observed, "PAN_CARD")["pan"]) if _fields(observed, "PAN_CARD").get("pan") else None,
_compact(get("pan", "pan")) if get("pan", "pan") else None], same),
"udyam_number": _agree([_compact(udyam_doc["udyam_number"]) if udyam_doc.get("udyam_number") else None,
_compact(get("udyam", "registration_number")) if get("udyam", "registration_number") else None], same),
"epfo_establishment_code": _agree(
[_compact(_fields(observed, "EPFO_ESIC_COMPLIANCE_CERTIFICATE").get("establishment_code") or "") ,
_compact(get("epfo_esic", "establishment_code") or "")], same),
"dpiit_number": _agree([_compact(startup_doc["dpiit_number"]) if startup_doc.get("dpiit_number") else None,
_compact(get("startup_india", "recognition_number")) if get("startup_india", "recognition_number") else None], same),
}
def featurize(tender: dict, observed: dict) -> list[float]:
portal, declared = observed["portal"], observed["declarations"]
threshold = _num(tender.get("mii_threshold")) or 0.0
verified = _num((portal.get("mii_local_content") or {}).get("verified_pct_estimate"))
declared_pct = _num(declared.get("local_content_pct_self_declared"))
document_pct = _num(_fields(observed, "MII_LOCAL_CONTENT_SELF_CERTIFICATE").get("local_content_pct"))
employees = _num(observed.get("employees"))
registered = _num((portal.get("epfo_esic") or {}).get("registered_employee_count"))
udyam = portal.get("udyam") or {}
matches = fact_matches(observed)
row = [
float(bool(tender.get("msme_reserved"))),
threshold,
float(bool(tender.get("epfo_applicable"))),
float(len(tender.get("mandatory_docs", []))),
*[float(_code(options, (portal.get(source) or {}).get("status"))) for source, options in STATUS_CODES.items()],
float(_code(CATEGORY_CODES, udyam.get("category") or ("none" if udyam else None))),
verified,
verified - threshold if threshold else NAN,
declared_pct,
document_pct,
declared_pct - verified,
document_pct - verified,
employees,
registered,
registered / employees if employees and not math.isnan(registered) else NAN,
float(len(observed.get("missing_docs", []))),
float("EMD_EXEMPTION_PROOF" in observed.get("missing_docs", [])),
float(len(observed["ocr"])),
float(sum(1 for doc in observed["ocr"].values() if doc.get("status") not in (None, "extracted"))),
float(len(declared)),
*[float(matches[fact]) for fact in FACTS],
float(sum(1 for v in matches.values() if v == 0)),
float(sum(1 for v in matches.values() if v >= 0)),
]
assert len(row) == len(FEATURE_NAMES)
return row