almeira-web/scripts/migrate_clean_appwrite_titles.py

161 lines
6.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import urllib.request
import json
import ssl
import sys
import re
import io
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
endpoint = 'https://appwrite.almeira.com.co/v1'
project_id = '69b84df5003bd8957c5c'
api_key = 'standard_cce3cdb866ea6433888d40cedd24dc45987372308fdc3dd0697de08648c0e5c7e04b02c3bcee271403fd656b00a5820f04c08b51631284a6e8a3582d8aed2aa33234f3c29cc777e9b162eb54810b789251b0d006a9a3b081ebc2da53b9baa94614b124b3508ad849fd9a4187cc6094f506d4b57f409b69d9562a74df594f3ec3'
db_id = 'sneakers_multistore_db'
collection_id = 'catalog_products'
headers = {
'X-Appwrite-Project': project_id,
'X-Appwrite-Key': api_key,
'Content-Type': 'application/json'
}
def clean_title(title, brand='Sneakers'):
c = title or ''
# 0. Normalizar fuentes estilizadas (Unicode Mathematical / Bold fonts)
import unicodedata
c = unicodedata.normalize('NFKD', c).strip()
# 1. Quitar emojis y pictogramas de todo tipo
c = re.sub(r'[\U00010000-\U0010ffff]|[\u2600-\u27bf]|[\u2300-\u23ff]|[\u200d\ufe0f]|\*|⭐|🌹|⚡|💥|👖|👩🏻|👫|👻|☀️|🔝|🙋🏻‍♀️|😍|🎩|💕|⏰|🌎', ' ', c)
# 2. Quitar códigos de proveedor/bodega: Cod: 734-732 Bbo, Cod: 2236, Cod: 668/833 Bbo, etc.
c = re.sub(r'\bcod(?:igo)?\s*[:\-–—]?\s*[\d\s\-\/]+(?:\s*bbo)?\b', ' ', c, flags=re.I)
# 3. Quitar paréntesis o corchetes con tallas o números
c = re.sub(r'\(\s*(?:tallas?|euro|eur|talla|disponibles?|del)?[^\)]*\b(?:3[2-9]|4[0-9]|5[0-6])\b[^\)]*\)', ' ', c, flags=re.I)
c = re.sub(r'\([^\)]*\b(?:euro|eur|talla|tallas|mil)\b[^\)]*\)', ' ', c, flags=re.I)
c = re.sub(r'\(\s*\)|\[\s*\]', ' ', c)
# 4. Quitar 'del 36 al 39 eur', 'del 40 al 44 eur'
c = re.sub(r'\bdel\s+(?:3[0-9]|4[0-9])\s+al\s+(?:3[0-9]|4[0-9])(?:\s*(?:eur|eu|euro))?\b', ' ', c, flags=re.I)
# 5. Quitar 'euro 36 - 39', 'eur 36/37/38', 'eu 36 al 39', '36 al 39 eu'
c = re.sub(r'\b(?:euro|eur|eu)\s*[:\-–—\/\s]+(?:3[0-9]|4[0-9])(?:\s*(?:-|–|—|a|al|\/)\s*(?:3[0-9]|4[0-9]))*', ' ', c, flags=re.I)
c = re.sub(r'\b(?:3[0-9]|4[0-9])\s+al\s+(?:3[0-9]|4[0-9])(?:\s*(?:eur|eu|euro))\b', ' ', c, flags=re.I)
# 6. Quitar secuencias de tallas: 32-34-36-38-40, 36/37/38, 36 - 38 - 44, 36 & Hombre 43
c = re.sub(r'\b(?:3[2-9]|4[0-9]|5[0-6])\s*(?:&|y|\/|\-)\s*(?:hombre\s+)?(?:3[2-9]|4[0-9]|5[0-6])(?:\s*(?:eur|eu|euro))?\b', ' ', c, flags=re.I)
c = re.sub(r'\b(?:3[2-9]|4[0-9]|5[0-6])(?:\s*[-–—\/]\s*(?:3[2-9]|4[0-9]|5[0-6]))+\b', ' ', c, flags=re.I)
# 7. Quitar rangos de tallas: 36 - 39, 40 - 41, 36 a 39, 36 al 39
c = re.sub(r'\b(?:3[4-9]|4[0-8])\s*(?:-|–|—|a|al|\/)\s*(?:3[4-9]|4[0-8])\b', ' ', c, flags=re.I)
# 8. Quitar tallas individuales con palabra clave
c = re.sub(r'\b(?:talla|tallas|eur|euro|eu)\s+(?:3[2-9]|4[0-8])\b', ' ', c, flags=re.I)
c = re.sub(r'\b(?:3[2-9]|4[0-8])\s*(?:eur|euro|eu)\b', ' ', c, flags=re.I)
c = re.sub(r'\btallas?\s+(?:de\s+)?(?:hombre|dama|disponibles?)?(?:\s+mil)?\b', ' ', c, flags=re.I)
# 9. Quitar .000 y residuos de precios
c = re.sub(r'(?:^|[\s\(\[\.,\-_])(?:[1-9]\d{0,2})?\.000\b', ' ', c, flags=re.I)
c = re.sub(r'\b\d{2,3}[\.,]\d{3}\b|\b\d{2,3}k\b|\b\d{2,3}\s*mil\b|\$\s*\d+', ' ', c, flags=re.I)
c = re.sub(r'\.000\b', ' ', c, flags=re.I)
# 10. Quitar palabras residuales
c = re.sub(r'dama\s*y\s*caballero|recarga|nueva\s*mercanc[ií]a|[uú]ltima\s*unidad', ' ', c, flags=re.I)
c = re.sub(r'\b(costo|precio|valor|al\s*por\s*mayor|mayorista)\b', ' ', c, flags=re.I)
c = re.sub(r'\b(hombre|dama|caballero)\b', ' ', c, flags=re.I)
c = re.sub(r'\b(eur|euro|eu|mil)\b', ' ', c, flags=re.I)
# 11. Limpiar símbolos residuales y ampersands aislados
c = re.sub(r'\s+&\s*$', '', c)
c = re.sub(r'^\s*&\s+', '', c)
c = re.sub(r'^[#*_\-•—–~:;,\s\.\/&]+|[#*_\-•—–~:;,\s\.\/&]+$', '', c)
c = re.sub(r'\s+', ' ', c).strip()
# 12. Fallback si quedó genérico o vacío
clean_lower = c.lower()
is_generic = (
len(c) < 3 or
re.match(r'^[\d\s\-.,/&]+$', c) or
clean_lower in ('euro', 'eur', 'zapatillas', 'sneakers', 'calzado', 'estilo', 'moda', 'solo', 'sin producto') or
clean_lower.startswith('solo ') or
clean_lower.startswith('euro ') or
clean_lower.startswith('sin producto')
)
if is_generic:
if brand and brand not in ('Urbano / Variado', 'Desconocida', 'Generica'):
c = f'{brand} Urban Edition'
else:
c = 'Sneakers Urban Classic'
# 13. Capital Case limpio conservando acrónimos
words = c.split()
formatted_words = []
for w in words:
if re.match(r'^(OG|SE|SB|TN|CL|GT|RS-X|BB|LV|NB|XT-6|CR7|V2K|V5K|9060|550|530|1906R|2002R)$', w, re.I):
formatted_words.append(w.upper())
elif re.match(r'^[A-Z0-9]+$', w) and len(w) <= 4 and not re.search(r'[aeiou]', w, re.I):
formatted_words.append(w.upper())
else:
formatted_words.append(w.capitalize())
return ' '.join(formatted_words)
def appwrite_get(path):
req = urllib.request.Request(f'{endpoint}{path}', headers=headers, method='GET')
with urllib.request.urlopen(req, context=ctx, timeout=30) as resp:
return json.loads(resp.read().decode())
def appwrite_patch(path, data):
body = json.dumps(data).encode('utf-8')
req = urllib.request.Request(f'{endpoint}{path}', data=body, headers=headers, method='PATCH')
with urllib.request.urlopen(req, context=ctx, timeout=30) as resp:
return json.loads(resp.read().decode())
print("📦 Consultando productos desde Appwrite...")
docs = []
cursor = None
while True:
q = '?limit=100'
if cursor:
q += f'&queries[]=cursorAfter("{cursor}")'
path = f'/databases/{db_id}/collections/{collection_id}/documents{q}'
res = appwrite_get(path)
cur_docs = res.get('documents', [])
if not cur_docs:
break
docs.extend(cur_docs)
cursor = cur_docs[-1]['$id']
if len(cur_docs) < 100:
break
print(f"Total documentos encontrados en Appwrite: {len(docs)}")
updated_count = 0
for doc in docs:
doc_id = doc['$id']
old_name = doc.get('name', '')
brand = doc.get('brand', 'Sneakers')
new_name = clean_title(old_name, brand)
if old_name != new_name:
try:
appwrite_patch(f'/databases/{db_id}/collections/{collection_id}/documents/{doc_id}', {
'data': {
'name': new_name,
'modelName': new_name
}
})
print(f" [FIXED] {doc_id}:\n OLD: {old_name}\n NEW: {new_name}")
updated_count += 1
except Exception as e:
print(f" [ERROR] {doc_id}: {e}")
print(f"🎉 Migración completada en Appwrite: {updated_count} productos actualizados.")