feat(pos): parsear nombres de modelos — solo nombre primario visible

Remueve codigos de generacion, numeros romanos, tipos de carroceria.
Deduplica por display_name. Toyota: 236 → 73 modelos limpios.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 08:23:31 +00:00
parent d1093ab9a2
commit 0f979b7912
2 changed files with 44 additions and 9 deletions

View File

@@ -15,6 +15,28 @@ import re
from services.na_models import is_na_model
def _clean_model_name(name):
"""Parse TecDoc model name to show only the primary name.
'4 RUNNER V (_N28_)''4 RUNNER'
'Corolla Hatchback (_E21_)''COROLLA'
'CAMRY Saloon (_V4_)''CAMRY'
'RAV 4 III (_A3_)''RAV 4'
"""
s = name.strip()
# Remove generation codes in parentheses: (_N28_), (B1_), (_E21_), etc.
s = re.sub(r'\s*\([^)]*\)\s*', '', s)
# Remove Roman numeral generation suffixes: I, II, III, IV, V, VI, VII, VIII, IX, X
s = re.sub(r'\s+(?:VIII|VII|VI|IV|IX|III|II|V|X|I)(?:\s|$)', ' ', s)
# Remove body type suffixes
s = re.sub(r'\s+(?:Estate|Saloon|Hatchback|Van|Coupe|Coupé|Convertible|Wagon|Pickup|Cab|Sedan|SUV|MPV|Kombi|Kasten|Bus|Box|Platform|Chassis)\b', '', s, flags=re.IGNORECASE)
# Remove "Hatchback Van", "Box Body" compound types
s = re.sub(r'\s+(?:Hatchback|Box)\s+(?:Van|Body)\b', '', s, flags=re.IGNORECASE)
# Clean up extra spaces
s = re.sub(r'\s+', ' ', s).strip()
return s.upper() if s else name.upper()
# ─────────────────────────────────────────────────────────────────────────────
# VEHICLE HIERARCHY NAVIGATION
# ─────────────────────────────────────────────────────────────────────────────
@@ -87,12 +109,25 @@ def get_models(master_conn, brand_id, year_id=None, brand_name=None):
rows = cur.fetchall()
cur.close()
# Filter to North America models only
return [
{'id_model': r[0], 'name_model': r[1]}
for r in rows
if is_na_model(brand_name, r[1])
]
# Filter to North America models only, add clean display name, deduplicate
filtered = [r for r in rows if is_na_model(brand_name, r[1])]
# Group by clean name — keep all id_models but show one display name
seen = {} # display_name → first row
results = []
for r in filtered:
display = _clean_model_name(r[1])
if display not in seen:
seen[display] = True
results.append({
'id_model': r[0],
'name_model': r[1],
'display_name': display,
})
# Sort by display_name
results.sort(key=lambda x: x['display_name'])
return results
def get_years(master_conn, model_id):

View File

@@ -219,8 +219,8 @@
if (!data || !data.data || !data.data.length) { showEmpty('Sin modelos', 'No hay modelos con partes para ' + nav.brand.name); return; }
navGrid.className = 'nav-grid';
navGrid.innerHTML = data.data.map(function (m) {
return '<div class="nav-card" role="listitem" data-model-id="' + m.id_model + '" data-name="' + esc(m.name_model) + '">' +
'<div class="nav-card__name">' + esc(m.name_model) + '</div>' +
return '<div class="nav-card" role="listitem" data-model-id="' + m.id_model + '" data-name="' + esc(m.display_name || m.name_model) + '">' +
'<div class="nav-card__name">' + esc(m.display_name || m.name_model) + '</div>' +
'</div>';
}).join('');
@@ -887,7 +887,7 @@
if (!models) return;
vsModel.innerHTML = '<option value="">Modelo...</option>';
models.forEach(function (m) {
vsModel.innerHTML += '<option value="' + m.id_model + '">' + esc(m.name_model) + '</option>';
vsModel.innerHTML += '<option value="' + m.id_model + '">' + esc(m.display_name || m.name_model) + '</option>';
});
});
}