File size: 3,932 Bytes
b5cb408
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
131
132
133
# policy_engine.py
from misc.rules import RULES

PRIORITY_ATTRS = ["soft_bag", "foam", "paper_cup", "carton", "greasy_or_wet", "hazard"]
ATTR_LABELS = {
    "soft_bag": "Soft bag / plastic wrap",
    "foam": "Foam / Styrofoam",
    "paper_cup": "Paper cup",
    "carton": "Carton",
    "greasy_or_wet": "Greasy or wet",
    "hazard": "Hazardous item",
}


def _normalize_city(city: str | None) -> str:
    if not city:
        return "default"
    city = city.strip().lower()
    if "," in city:
        city = city.split(",", 1)[0].strip()
    return city or "default"


def _title_city(city_key: str) -> str:
    if city_key in ("default", "", None):
        return "Default"
    return " ".join(w.capitalize() for w in city_key.split())


def _normalize_bool(val) -> bool:
    if isinstance(val, bool):
        return val
    if val is None:
        return False
    if isinstance(val, (int, float)):
        return val != 0
    s = str(val).strip().lower()
    if s in {"1", "true", "yes", "y", "on"}:
        return True
    if s in {"0", "false", "no", "n", "off", ""}:
        return False
    return True  # any other non-empty string


def _normalize_attrs(attrs: dict | None) -> dict:
    attrs = attrs or {}
    return {k: _normalize_bool(v) for k, v in attrs.items()}


def _material_key_case_insensitive(table: dict, material: str | None) -> str | None:
    """

    Return the correct material key from `table` using robust case handling.

    Tries:

      1) exact

      2) Title Case (e.g., "plastic bottle" -> "Plastic Bottle")

      3) case-insensitive scan over existing keys

    """
    if not material:
        return None
    m = material.strip()
    if not m:
        return None

    # 1) exact
    if m in table:
        return m

    # 2) title-case
    m_title = " ".join(w.capitalize() for w in m.split())
    if m_title in table:
        return m_title

    # 3) case-insensitive scan
    m_lower = m.lower()
    for k in table.keys():
        if k.lower() == m_lower:
            return k

    return None


def _lookup_material_rules(city_key: str, material: str | None) -> tuple[dict, str]:
    """

    Resolve the material rules for a city with case-insensitive matching.

    Fallback order:

      1) City table match

      2) City's "Trash"

      3) Default table match

      4) Default "Trash"

      5) {"default": "Landfill"}

    Returns (rules_dict, resolved_material_name)

    """
    city_table = RULES.get(city_key, RULES["default"])

    key = _material_key_case_insensitive(city_table, material)
    if key:
        return city_table[key], key

    if "Trash" in city_table:
        return city_table["Trash"], "Trash"

    default_table = RULES["default"]
    key = _material_key_case_insensitive(default_table, material)
    if key:
        return default_table[key], key

    if "Trash" in default_table:
        return default_table["Trash"], "Trash"

    return {"default": "Landfill"}, material or "Unknown"


def decide_action(material: str, attrs: dict | None, city: str | None):
    """

    Decide action using case-insensitive material matching and boolean-normalized attrs.

    """
    city_key = _normalize_city(city)
    city_disp = _title_city(city_key)
    norm_attrs = _normalize_attrs(attrs)

    mat_rules, resolved_mat = _lookup_material_rules(city_key, material)

    # attribute-specific overrides (priority order)
    for a in PRIORITY_ATTRS:
        if norm_attrs.get(a) and a in mat_rules:
            attr_label = ATTR_LABELS.get(a, a.replace("_", " "))
            action = mat_rules[a]
            return action, f"{resolved_mat} marked as '{attr_label}' → {action} ({city_disp})"

    # fallback to material default
    action = mat_rules.get("default", "Landfill")
    return action, f"{resolved_mat}{action} ({city_disp})"