Spaces:
Sleeping
Sleeping
feat: country tag on catalogue items for locale-based grounding
Browse filesCatalogue items and tool matches carry a 2-letter country code (default
ZW). The client filters matches to the viewer country, so a supplier only
shows in its own market.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
main.py
CHANGED
|
@@ -185,6 +185,11 @@ _TOOL_STOPWORDS = {'a', 'an', 'the', 'of', 'for', 'and', 'or', 'with', 'to', 'in
|
|
| 185 |
|
| 186 |
SUPPORTED_CITIES = ['Harare', 'Bulawayo', 'Gweru', 'Mutare', 'Other']
|
| 187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
def _normalize_tool_name(name):
|
| 189 |
"""Lowercase, strip punctuation/stopwords, singularize trailing 's' for catalogue matching."""
|
| 190 |
words = re.sub(r"[^a-z0-9\s]", " ", (name or "").lower()).split()
|
|
@@ -229,6 +234,7 @@ def match_tools_to_catalogue(tools_list):
|
|
| 229 |
'currency': item.get('currency', 'USD'),
|
| 230 |
'supplier': item.get('supplier', 'Electrosales'),
|
| 231 |
'city': item.get('city', 'Harare'),
|
|
|
|
| 232 |
'imageUrl': item.get('imageUrl', ''),
|
| 233 |
})
|
| 234 |
return matches
|
|
@@ -1079,6 +1085,9 @@ def _build_catalogue_item(data, admin_uid):
|
|
| 1079 |
if isinstance(extra, str):
|
| 1080 |
tokens |= set(_normalize_tool_name(extra).split())
|
| 1081 |
|
|
|
|
|
|
|
|
|
|
| 1082 |
return {
|
| 1083 |
'name': name,
|
| 1084 |
'normalizedName': normalized,
|
|
@@ -1087,6 +1096,7 @@ def _build_catalogue_item(data, admin_uid):
|
|
| 1087 |
'currency': (data.get('currency') or 'USD').upper(),
|
| 1088 |
'supplier': (data.get('supplier') or 'Electrosales').strip(),
|
| 1089 |
'city': data.get('city') if data.get('city') in SUPPORTED_CITIES else 'Harare',
|
|
|
|
| 1090 |
'category': (data.get('category') or 'General').strip(),
|
| 1091 |
'imageUrl': data.get('imageUrl') or '',
|
| 1092 |
'inStock': bool(data.get('inStock', True)),
|
|
@@ -1239,6 +1249,11 @@ def admin_update_catalogue_item(item_id):
|
|
| 1239 |
if data['city'] not in SUPPORTED_CITIES:
|
| 1240 |
return jsonify({'error': f'city must be one of: {", ".join(SUPPORTED_CITIES)}'}), 400
|
| 1241 |
updates['city'] = data['city']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1242 |
if 'inStock' in data:
|
| 1243 |
updates['inStock'] = bool(data['inStock'])
|
| 1244 |
if not updates:
|
|
|
|
| 185 |
|
| 186 |
SUPPORTED_CITIES = ['Harare', 'Bulawayo', 'Gweru', 'Mutare', 'Other']
|
| 187 |
|
| 188 |
+
# Country a supplier's catalogue serves. The client detects the viewer's country
|
| 189 |
+
# (timezone/IP) and only surfaces catalogues for their country; everyone else
|
| 190 |
+
# gets the generic tools list. Add a code here when onboarding a new market.
|
| 191 |
+
DEFAULT_CATALOGUE_COUNTRY = 'ZW'
|
| 192 |
+
|
| 193 |
def _normalize_tool_name(name):
|
| 194 |
"""Lowercase, strip punctuation/stopwords, singularize trailing 's' for catalogue matching."""
|
| 195 |
words = re.sub(r"[^a-z0-9\s]", " ", (name or "").lower()).split()
|
|
|
|
| 234 |
'currency': item.get('currency', 'USD'),
|
| 235 |
'supplier': item.get('supplier', 'Electrosales'),
|
| 236 |
'city': item.get('city', 'Harare'),
|
| 237 |
+
'country': item.get('country', DEFAULT_CATALOGUE_COUNTRY),
|
| 238 |
'imageUrl': item.get('imageUrl', ''),
|
| 239 |
})
|
| 240 |
return matches
|
|
|
|
| 1085 |
if isinstance(extra, str):
|
| 1086 |
tokens |= set(_normalize_tool_name(extra).split())
|
| 1087 |
|
| 1088 |
+
raw_country = (data.get('country') or DEFAULT_CATALOGUE_COUNTRY).strip().upper()
|
| 1089 |
+
country = raw_country if len(raw_country) == 2 else DEFAULT_CATALOGUE_COUNTRY
|
| 1090 |
+
|
| 1091 |
return {
|
| 1092 |
'name': name,
|
| 1093 |
'normalizedName': normalized,
|
|
|
|
| 1096 |
'currency': (data.get('currency') or 'USD').upper(),
|
| 1097 |
'supplier': (data.get('supplier') or 'Electrosales').strip(),
|
| 1098 |
'city': data.get('city') if data.get('city') in SUPPORTED_CITIES else 'Harare',
|
| 1099 |
+
'country': country,
|
| 1100 |
'category': (data.get('category') or 'General').strip(),
|
| 1101 |
'imageUrl': data.get('imageUrl') or '',
|
| 1102 |
'inStock': bool(data.get('inStock', True)),
|
|
|
|
| 1249 |
if data['city'] not in SUPPORTED_CITIES:
|
| 1250 |
return jsonify({'error': f'city must be one of: {", ".join(SUPPORTED_CITIES)}'}), 400
|
| 1251 |
updates['city'] = data['city']
|
| 1252 |
+
if 'country' in data:
|
| 1253 |
+
country = (data.get('country') or '').strip().upper()
|
| 1254 |
+
if len(country) != 2:
|
| 1255 |
+
return jsonify({'error': 'country must be a 2-letter ISO code'}), 400
|
| 1256 |
+
updates['country'] = country
|
| 1257 |
if 'inStock' in data:
|
| 1258 |
updates['inStock'] = bool(data['inStock'])
|
| 1259 |
if not updates:
|