Spaces:
Sleeping
Sleeping
Download classification/classifier.py from archittmittal/Email-Segregation: direct link, hf CLI and curl.
- Browser
- Download file 3.47 kB
-
https://huggingface.co/spaces/archittmittal/Email-Segregation/resolve/main/classification/classifier.py
- Command line
-
hf download hf://spaces/archittmittal/Email-Segregation/classification/classifier.py
-
curl -L -o classifier.py https://huggingface.co/spaces/archittmittal/Email-Segregation/resolve/main/classification/classifier.py
3.47 kB
| from __future__ import annotations | |
| import re | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| from sklearn.linear_model import LogisticRegression | |
| from sklearn.pipeline import Pipeline | |
| from classification.training_data import TONNAGE_SAMPLES, CARGO_VC_SAMPLES, CARGO_TC_SAMPLES | |
| # ββ Confidence threshold below which we fall back to keyword rules ββββββββββ | |
| CONFIDENCE_THRESHOLD = 0.55 | |
| # ββ Keyword rule sets (based on real shipping email patterns) ββββββββββββββββ | |
| TONNAGE_KEYWORDS = [ | |
| r'\bopen\s+[a-z]', r'\bdwt\b', r'\bbuilt\s*\d{4}', r'\bblt\s*\d{4}', | |
| r'\bloa\b', r'\beeta\b', r'\bscrubber\b', r'\bspeed.{0,20}cons', | |
| r'\bballast\b', r'\bbeam\b', r'\bgrain\s*cap', r'\bflag\b.*\bclass\b', | |
| r'\bopen\s+vessel', r'\bvessel\s+position', r'\bour\s+direct\s+ow', | |
| r'\bopen\s+as\s+follows', r'\bdirect\s+ow', r'o/a\s+\d', | |
| ] | |
| CARGO_VC_KEYWORDS = [ | |
| r'\bload\s*port\b', r'\bdischarge\s*port\b', r'\blaycan\b', | |
| r'\bpol\b', r'\bpod\b', r'\bfios\b', r'\bpwwd\b', r'\bsshex\b', | |
| r'\blp\s*:', r'\bdp\s*:', r'\bload\s*rate\b', r'\bvoyage\s*charter\b', | |
| r'\bmts\b.*\b(?:bulk|coal|grain|ore|cargo)\b', | |
| r'\bcargo.*(?:port|laycan)', r'(?:discharge|load).*laycan', | |
| ] | |
| CARGO_TC_KEYWORDS = [ | |
| r'\btime\s*charter\b', r'\btct\b', r'\bdely\b', r'\bdelivery\b.*\bredelivery\b', | |
| r'\bduration\s+abt\b', r'\bredel\b', r'\bredelivery\b', r'\bhire\b', | |
| r'1\s*tct\s*with', r'\bdelivery\b.*\blaycan\b', r'\bperiod\b.*\bdelivery\b', | |
| r'\b1[-β]\d+\s*years?\b', r'\bflat\s*or\s*index\b', | |
| ] | |
| # ββ Global pipeline instance βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _pipeline: Pipeline | None = None | |
| def _build_and_train(): | |
| global _pipeline | |
| X = TONNAGE_SAMPLES + CARGO_VC_SAMPLES + CARGO_TC_SAMPLES | |
| y = ( | |
| ["tonnage"] * len(TONNAGE_SAMPLES) | |
| + ["cargo_vc"] * len(CARGO_VC_SAMPLES) | |
| + ["cargo_tc"] * len(CARGO_TC_SAMPLES) | |
| ) | |
| _pipeline = Pipeline([ | |
| ("tfidf", TfidfVectorizer( | |
| ngram_range=(1, 3), | |
| max_features=8000, | |
| sublinear_tf=True, | |
| min_df=1, | |
| )), | |
| ("clf", LogisticRegression( | |
| max_iter=1000, | |
| C=2.0, | |
| solver="lbfgs", | |
| )), | |
| ]) | |
| _pipeline.fit(X, y) | |
| def _keyword_classify(text: str) -> str | None: | |
| t = text.lower() | |
| scores = { | |
| "tonnage": sum(1 for p in TONNAGE_KEYWORDS if re.search(p, t)), | |
| "cargo_vc": sum(1 for p in CARGO_VC_KEYWORDS if re.search(p, t)), | |
| "cargo_tc": sum(1 for p in CARGO_TC_KEYWORDS if re.search(p, t)), | |
| } | |
| best = max(scores, key=scores.get) | |
| return best if scores[best] > 0 else "unknown" | |
| def classify(text: str) -> tuple[str, float]: | |
| """Return (category, confidence). Category is one of: | |
| 'tonnage' | 'cargo_vc' | 'cargo_tc' | 'unknown' | |
| """ | |
| global _pipeline | |
| if _pipeline is None: | |
| _build_and_train() | |
| clean = text.upper() | |
| proba = _pipeline.predict_proba([clean])[0] | |
| classes = _pipeline.classes_ | |
| best_idx = proba.argmax() | |
| confidence = float(proba[best_idx]) | |
| label = classes[best_idx] | |
| if confidence < CONFIDENCE_THRESHOLD: | |
| kw_label = _keyword_classify(text) | |
| return kw_label, confidence | |
| return label, confidence | |
| # Initialise on import so first request is fast | |
| _build_and_train() | |