"""Reglas v2 (sin modelos) de A1 «precio frente a comparables»: correcciones de PROPUESTA_PREREG_A1 §3.1 sobre la v1 (`reglas.py`, que no se toca). Python puro (re), sobre el objeto normalizado (minúsculas, sin acentos: reglas.norm). 1. Coma de millares inglesa: «1,500 litres» → 1.500 si el valor leído con coma decimal es < 10 y la parte tras la coma tiene exactamente 3 cifras; «2,000,000 l» → 2.000.000 (varios grupos de 3 tras coma). 2. Capacidad, no cantidad: se descarta la cantidad si en los 40 caracteres anteriores al número aparece carrillo|deposito|diposit|bidon|petaca|garrafa|lata|capacidad|maletero|congelador, o si tras la unidad va «/100 km». 3. Producto (carburantes): C, B, gasolina, A, o «sin_producto» (sin letra no hay A por defecto); «mixto» si el objeto nombra gasóleo y gasolina sin destino de calefacción ni letra única. 4. Mercado carburantes: además de la exclusión v1, fuera limpiador|aditivo|hidrolimpiadora|congelador. 5. Plausibilidad del dato: cociente fuera de [0,5; 2] → «dudoso» (nunca entra en un hecho). """ import re from reglas import norm, UNIDADES, FIN, MERCADOS VERSION = 'v2' NUM2 = r"(\d{1,3}(?:[.\s]\d{3})+(?:,\d+)?|\d{1,3}(?:,\d{3}){2,}|\d+(?:[.,]\d+)?)" CAPACIDAD_ANTES = re.compile(r"(carrillo|deposito|diposit|bidon|petaca|garrafa|\blata|capacidad|maletero|congelador)") CAPACIDAD_DESPUES = re.compile(r"^\s*/\s*100\s*km") EXCL_CARB_V2 = r"(limpiador|aditivo|hidrolimpiadora|congelador)" _RX2 = [(c, re.compile(NUM2 + r"\s*" + u + FIN)) for c, u in UNIDADES] def numero2(s): """Como reglas.numero, más la coma de millares inglesa (regla 1).""" s = s.strip() if re.fullmatch(r"\d{1,3}(?:[.\s]\d{3})+(?:,\d+)?", s): return float(re.sub(r"[.\s]", "", s).replace(',', '.')) if re.fullmatch(r"\d{1,3}(?:,\d{3}){2,}", s): return float(s.replace(',', '')) m = re.fullmatch(r"(\d{1,3}),(\d{3})", s) if m and float(f"{m.group(1)}.{m.group(2)}") < 10: return float(m.group(1) + m.group(2)) return float(s.replace(',', '.')) def cantidades2(objeto, clases=None): """Lista de (clase, valor, literal, capacidad: bool). Las de capacidad se devuelven marcadas (no se usan).""" o = norm(objeto); out = [] for c, rx in _RX2: if clases and c not in clases: continue for m in rx.finditer(o): lit = m.group(0).rstrip(' .,;:)(-/') pre = o[max(0, m.start() - 1):m.start()] if pre and (pre.isalnum() or pre in '.,'): continue try: v = numero2(m.group(1)) except ValueError: continue if c == 'litros' and re.search(r"\d\s*l\.?$", lit) and v < 1: continue cap = bool(CAPACIDAD_ANTES.search(o[max(0, m.start() - 40):m.start()])) or \ bool(CAPACIDAD_DESPUES.search(o[m.start() + len(lit):])) out.append((c, v, lit, cap)) return out def cantidad_principal(objeto, clase): """(valor, literal, n_distintas) de la clase pedida, sin capacidades; None si no hay.""" l = [x for x in cantidades2(objeto, {clase}) if not x[3]] if not l: return None return l[0][1], l[0][2], len({x[1] for x in l}) _C = re.compile(r"gasoleo (tipo )?[\"“]?c\b|gasoil (tipo )?[\"“]?c\b|gas-oil (tipo )?c\b|gasoleo de calefacci|calefacc|calefaccio|caldera|colegio|\bceip\b|pabellon|poliesportiu") _B = re.compile(r"gasoleo (tipo )?[\"“]?b\b|gasoil (tipo )?[\"“]?b\b|gas-oil (tipo )?b\b|agricol|bonificad|tipo b\b") _GASOLINA = re.compile(r"gasolina|sin plomo|\bs/?plomo|\beuro ?super\b") _A = re.compile(r"gasoleo (tipo )?[\"“]?a\b|gasoil (tipo )?[\"“]?a\b|gas-oil (tipo )?a\b|automocion|\bb7\b|vehicul|flota|parque movil|parc mobil") _GASOLEO = re.compile(r"gasoleo|gasoil|gas-oil|diesel") _NO_C = re.compile(r"vehicul|automocion") def producto2(objeto): o = norm(objeto) if _C.search(o) and not _NO_C.search(o): return 'gasoleo_c' if _B.search(o): return 'gasoleo_b' gl, go = bool(_GASOLINA.search(o)), bool(_GASOLEO.search(o)) if gl and go: return 'mixto' if gl: return 'gasolina95' if _A.search(o): return 'gasoleo_a' return 'sin_producto' def excluido_v2(objeto, mercado='carburantes'): return mercado == 'carburantes' and bool(re.search(EXCL_CARB_V2, norm(objeto))) def plausible(cociente): return cociente is not None and 0.5 <= cociente <= 2.0