File size: 4,703 Bytes
63f0c94 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | from __future__ import annotations
import re
from typing import Any
AMOUNT_PATTERN = r"(?P<amount>(?:K|ZMW|kwacha)?\s*\d[\d,]*(?:\.\d+)?)"
CONNECTOR_PATTERN = r"[^\dK\.\n!?]{0,20}"
BUSINESS_PATTERNS = {
"grocery shop": [r"grocery shop", r"small shop", r"shop owner", r"mini mart", r"market shop"],
"salon": [r"salon", r"barbershop", r"barber shop", r"barber"],
"farmer": [r"farmer", r"farm", r"agri", r"produce seller"],
"freelancer": [r"freelancer", r"consultant", r"designer", r"developer", r"writer"],
}
def _parse_amount(raw_value: str | None) -> float:
if not raw_value:
return 0.0
cleaned = re.sub(r"[^0-9.]", "", raw_value.replace(",", ""))
return float(cleaned) if cleaned else 0.0
def _extract_first_amount(text: str, patterns: list[str]) -> float:
for pattern in patterns:
match = re.search(pattern, text, flags=re.IGNORECASE)
if match:
return _parse_amount(match.group("amount"))
return 0.0
def _detect_business_type(text: str) -> str | None:
for business_type, patterns in BUSINESS_PATTERNS.items():
if any(re.search(pattern, text, flags=re.IGNORECASE) for pattern in patterns):
return business_type
return None
def _detect_location(text: str) -> str | None:
match = re.search(r"\b(?:in|at|from)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)", text)
if match:
return match.group(1).strip()
return None
def _extract_category_amounts(text: str) -> dict[str, float]:
category_patterns = {
"stock": [
rf"{AMOUNT_PATTERN}\s+(?:on|for)\s+(?:stock|inventory|restocking|products|supplies|inputs)",
rf"(?:stock|inventory|restocking|products|supplies|inputs)(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}",
],
"rent": [
rf"{AMOUNT_PATTERN}\s+(?:on|for)\s+rent",
rf"rent(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}",
],
"transport": [
rf"{AMOUNT_PATTERN}\s+(?:on|for)\s+transport",
rf"transport(?:ation)?(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}",
],
"other_expenses": [
rf"{AMOUNT_PATTERN}\s+(?:on|for)\s+other expenses",
rf"other expenses(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}",
],
"sales": [
rf"(?:made|earned|generated|received)(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}(?:\s+(?:in|from)\s+sales)?",
rf"{AMOUNT_PATTERN}\s+(?:in|from)\s+sales",
rf"(?:sales|revenue|income)(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}",
],
"debt": [
rf"(?:owe|owing|debt|supplier debt)(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}",
rf"{AMOUNT_PATTERN}\s+(?:to|in)\s+(?:my\s+)?supplier",
],
"loan_amount": [
rf"{AMOUNT_PATTERN}\s+loan",
rf"(?:loan|borrow)(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}",
],
}
return {
category: _extract_first_amount(text, patterns)
for category, patterns in category_patterns.items()
}
def _extract_general_expenses(text: str) -> float:
patterns = [
rf"(?:total expenses|overall expenses|expenses were)(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}",
rf"(?:spent)(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}",
]
return _extract_first_amount(text, patterns)
def parse_business_input(text: str) -> dict[str, Any]:
lowered = text.lower()
extracted = _extract_category_amounts(text)
expenses_breakdown = {
"stock": extracted.get("stock", 0.0),
"rent": extracted.get("rent", 0.0),
"transport": extracted.get("transport", 0.0),
"other_expenses": extracted.get("other_expenses", 0.0),
}
detailed_expenses = round(sum(expenses_breakdown.values()), 2)
total_expenses = detailed_expenses or _extract_general_expenses(text)
revenue = extracted.get("sales", 0.0)
if revenue == 0:
revenue = _extract_first_amount(
text,
[
rf"(?:made|earned|revenue|income)(?:{CONNECTOR_PATTERN}){AMOUNT_PATTERN}",
rf"{AMOUNT_PATTERN}\s+(?:in|from)\s+(?:sales|revenue|income)",
],
)
debt = extracted.get("debt", 0.0)
loan_amount = extracted.get("loan_amount", 0.0)
return {
"raw_text": text,
"revenue": round(revenue, 2),
"expenses": round(total_expenses, 2),
"expenses_breakdown": expenses_breakdown,
"debt": round(debt, 2),
"loan_amount": round(loan_amount, 2),
"business_type": _detect_business_type(lowered),
"location": _detect_location(text),
"categories_detected": [name for name, value in expenses_breakdown.items() if value > 0],
}
|