Версия: 1.0
Дата: 2026-02-27
Проект: new.lideravto.ru — Drupal 11.3.3 + Commerce 3.x
Модуль: dru_lider_importer
Аудитория: разработчики Drupal
Система импорта предназначена для загрузки, обновления и управления каталогом запчастей для грузовых автомобилей на платформе new.lideravto.ru. Система обеспечивает:
lider_part)lider_oem) с нормализованными формамиlider_compatibility);| № | Поле | Тип | Обязательное | Описание |
|---|---|---|---|---|
| 1 | артикул |
string | Нет | Внутренний артикул поставщика Bazon. Не является OEM. Может отсутствовать. |
| 2 | наименование |
string | Да | Наименование детали на русском языке. Максимум 50 символов (ограничение по SEO-заголовку: title = наименование + OEM + марка + модель ≤ 70 символов). |
| 3 | марка |
string | Да | Марка грузового автомобиля (Scania, Volvo, Mercedes, MAN, DAF, Renault, Iveco, Liebherr). |
| 4 | модель |
string | Да | Модель грузового автомобиля в пределах марки (R5, FH, Actros, TGX и т.д.). |
| 5 | oem |
string | Да | Каноничный OEM-номер — чистый, без префиксов/суффиксов. Mercedes без A-prefix, Renault/Volvo без 742-prefix. Если OEM начинается с 0 — ноль сохранять. |
| 6 | кросс_номера |
string | Нет | Варианты написания и аналоги подходящие для всех моделей данного OEM: с A-буквой, с пробелами, с дефисами, OEM других производителей. Разделитель: запятая. Если аналог подходит только части моделей — заводить отдельной строкой (см. раздел 3.4). |
| 7 | производитель |
string | Нет | Производитель детали (Bosch, ZF, Knorr-Bremse, SKF и т.д.). Не марка грузовика. |
| 8 | состояние |
string | Нет | Состояние товара: "новый", "оригинал", "аналог". |
| 9 | цена |
decimal | Да | Цена в рублях. Формат: число с точкой или запятой как десятичным разделителем. |
| 10 | группа |
string | Нет | Группа деталей первого уровня (Двигатель, Трансмиссия, Тормозная система и т.д.). |
| 11 | система |
string | Нет | Система в пределах группы (Топливная система, Система охлаждения и т.д.). |
| 12 | узел |
string | Нет | Конкретный узел или агрегат (Форсунка, Насос ГУР, Балансир и т.д.). |
LDR-7821;Форсунка DC13 XPI;Scania;R5;9604621423;A9604621423,F00RJ01285,0445120229,96046-21423;Bosch;новый;18500;Двигатель;Топливная система;Форсунка;Подходит для: Scania R5, Scania G5, Scania P5, Scania F5
Разбор:
- артикул: LDR-7821
- наименование: Форсунка DC13 XPI ← до 50 символов
- марка: Scania
- модель: R5 ← основная модель (для карточки)
- OEM: 9604621423 ← каноничный, чистый (без A-prefix, без пробелов)
- кросс_номера: A9604621423,F00RJ01285,0445120229,96046-21423 ← ВСЕ варианты как ищут люди
- производитель: Bosch
- состояние: новый
- цена: 18500
- группа: Двигатель
- система: Топливная система
- узел: Форсунка
- комментарий: Подходит для: Scania R5, Scania G5, Scania P5, Scania F5 ← совместимость
Критические ошибки (прекращают импорт строки):
- Пустое поле наименование
- Пустое поле марка
- Пустое поле модель
- Пустое поле oem И пустое поле артикул одновременно
- Некорректная цена (не число, отрицательное, ноль)
Предупреждения (строка импортируется с флагом):
- Наименование состоит только из стоп-слова (см. раздел 3.2)
- OEM содержит известные ошибки (нормализуется автоматически, пишется предупреждение)
- Поле кросс_номера содержит нечитаемые символы
Допустимые ситуации (нет предупреждения):
- Пустые поля: артикул, кросс_номера, производитель, состояние, группа, система, узел
- Различные форматы цены: 18500, 18500.00, 18 500, 18500,00
Нормализация применяется к полю oem каждой строки перед любой другой обработкой.
Алгоритм normalize_oem() — полная реализация на Python:
import re
def normalize_oem(oem: str) -> str:
"""
Нормализует OEM-номер детали:
- Заменяет кириллические буквы на латинские (визуально идентичные)
- Удаляет разделители (пробелы, дефисы, точки, запятые)
- Приводит к верхнему регистру
- Снимает A-prefix у Mercedes (A + ровно 10 цифр)
- Снимает PE-suffix у DAF
- Снимает 742-prefix у Renault/Volvo
Returns: нормализованный OEM или пустую строку если входные данные некорректны
"""
if not oem or not isinstance(oem, str):
return ''
oem = oem.strip()
# Шаг 1: Замена кириллических букв на латинские (визуально идентичные)
cyrillic_to_latin = {
'А': 'A', # кириллическая А → латинская A
'В': 'B', # кириллическая В → латинская B
'Е': 'E', # кириллическая Е → латинская E
'О': 'O', # кириллическая О → латинская O
'Р': 'R', # кириллическая Р → латинская R
'С': 'C', # кириллическая С → латинская C
'Х': 'X', # кириллическая Х → латинская X
'а': 'a', # строчные тоже
'в': 'b',
'е': 'e',
'о': 'o',
'р': 'r',
'с': 'c',
'х': 'x',
}
for cyr, lat in cyrillic_to_latin.items():
oem = oem.replace(cyr, lat)
# Шаг 2: Удаление разделителей (пробел, дефис, точка, запятая, слэш)
oem = re.sub(r'[\s\-\.\,\/]', '', oem)
# Шаг 3: Приведение к верхнему регистру
oem = oem.upper()
# Шаг 4: Снятие A-prefix у Mercedes
# Паттерн: буква A + ровно 10 цифр (и больше ничего)
if re.match(r'^A\d{10}$', oem):
oem = oem[1:]
# Шаг 5: Снятие PE-suffix у DAF
# Паттерн: строка заканчивается на PE, длина > 4 символа
if oem.endswith('PE') and len(oem) > 4:
oem = oem[:-2]
# Шаг 6: Снятие 742-prefix у Renault/Volvo
# Паттерн: начинается с 742, итоговый номер (без 742) длиннее 6 символов
if re.match(r'^742', oem) and len(oem) > 10:
candidate = oem[3:]
if len(candidate) >= 7:
oem = candidate
return oem
def normalize_oem_list(oem_string: str) -> list[str]:
"""
Нормализует строку с несколькими OEM (поле кросс_номера).
Разделители: запятая, пробел, точка с запятой.
Returns: список нормализованных OEM, пустые строки исключены.
"""
if not oem_string:
return []
# Разделяем по запятой, пробелу или точке с запятой
raw_list = re.split(r'[,\s;]+', oem_string.strip())
result = []
for raw in raw_list:
normalized = normalize_oem(raw)
if normalized:
result.append(normalized)
return result
Хранение в базе данных:
Для каждого OEM хранятся три значения:
| Поле | Пример | Описание |
|---|---|---|
oem_original |
A9604621423 |
Как пришло от поставщика (не изменяется) |
oem_normalized |
9604621423 |
После применения normalize_oem() |
oem_canonical |
9604621423 |
Главный OEM (при конфликтах нормализаций) |
Список стоп-слов — слова, при которых наименование из одного слова считается неполным и требует уточнения:
// dru_lider_importer/src/Service/NamingValidator.php
const STOP_WORDS = [
'Кронштейн',
'Болт',
'Гайка',
'Шпилька',
'Кнопка',
'Накладка',
'Прокладка',
'Датчик',
'Клапан',
'Шланг',
'Фильтр',
'Трубка',
'Хомут',
'Пружина',
'Втулка',
'Подшипник',
'Кольцо',
'Заглушка',
'Крышка',
'Патрубок',
'Уплотнение',
'Манжета',
'Сальник',
];
Правило применения стоп-слов:
Алгоритм автофикса:
def auto_fix_name(name: str, node_field: str, system_field: str) -> tuple[str, bool]:
"""
Пытается автоматически улучшить неполное наименование.
Returns:
(new_name, needs_review)
needs_review=True если автофикс не дал достаточного результата
"""
# Если наименование уже нормальное — возвращаем как есть
if not is_stop_word_only(name):
return name, False
# Попытка 1: добавить поле "узел" если оно информативно
if node_field and len(node_field) > 3 and node_field.lower() not in [name.lower()]:
candidate = f"{name} {node_field.lower()}"
if len(candidate) <= 80:
return candidate, False # автофикс успешен
# Попытка 2: добавить поле "система" если узел пустой
if system_field and len(system_field) > 3:
candidate = f"{name} {system_field.lower()}"
if len(candidate) <= 80:
return candidate, True # частичный автофикс, всё равно на проверку
# Автофикс не удался — оставляем как есть, ставим на проверку
return name, True
Поведение:
| Ситуация | Действие |
|---|---|
Наименование = одно стоп-слово, узел заполнен |
Автофикс: {наименование} {узел}, флаг не ставится |
Наименование = одно стоп-слово, узел пустой, система заполнена |
Автофикс частичный: {наименование} {система}, флаг needs_review = true |
| Наименование = одно стоп-слово, оба поля пустые | Наименование без изменений, флаг needs_review = true |
| Наименование нормальное (больше одного значимого слова) | Без изменений, без флага |
Дельта-импорт сравнивает новый прайс с текущим состоянием базы данных. Сравнение ведётся по полю oem_normalized.
Статусы строк:
| Статус | Условие | Действие |
|---|---|---|
NEW |
oem_normalized не найден в БД |
Создать новый node, Commerce Product, OEM entity |
UPDATED_PRICE |
OEM найден, цена изменилась | Обновить цену в Commerce Product Variation |
UPDATED_COMPAT |
OEM найден, марка/модель — новая совместимость | Добавить запись в lider_compatibility |
DISAPPEARED |
OEM был в БД, в новом прайсе отсутствует | Установить status=out_of_stock в Commerce Product, НЕ удалять |
UNCHANGED |
OEM найден, цена и совместимость не изменились | Пропустить (skip), не записывать в БД |
Алгоритм дельты:
def calculate_delta(new_rows: list[dict], existing_catalog: dict) -> list[dict]:
"""
existing_catalog: словарь {oem_normalized: {price, compatibilities: set}}
Возвращает: список строк со статусом
"""
delta = []
new_oems = set()
for row in new_rows:
oem_norm = normalize_oem(row['oem'])
new_oems.add(oem_norm)
if oem_norm not in existing_catalog:
delta.append({**row, 'status': 'NEW'})
continue
existing = existing_catalog[oem_norm]
changes = []
# Проверка цены
if abs(float(row['цена']) - existing['price']) > 0.01:
changes.append('PRICE')
# Проверка совместимости
compat_key = f"{row['марка']}:{row['модель']}"
if compat_key not in existing['compatibilities']:
changes.append('COMPAT')
if not changes:
delta.append({**row, 'status': 'UNCHANGED'})
elif changes == ['PRICE']:
delta.append({**row, 'status': 'UPDATED_PRICE'})
elif 'COMPAT' in changes:
delta.append({**row, 'status': 'UPDATED_COMPAT', 'price_changed': 'PRICE' in changes})
# Найти исчезнувшие OEM
disappeared_oems = set(existing_catalog.keys()) - new_oems
for oem in disappeared_oems:
delta.append({'oem': oem, 'status': 'DISAPPEARED'})
return delta
Из поля кросс_номера:
Поле содержит OEM-номера других производителей для той же детали. Каждый кросс-номер нормализуется и сохраняется как отдельная OEM entity со связью cross_reference на основной OEM.
Входные данные: "F00RJ01285,0445120229, 1 418 522 006"
После split: ["F00RJ01285", "0445120229", "1418522006"]
Нормализация: ["F00RJ01285", "0445120229", "1418522006"]
Результат: 3 кросс-записи связанных с основным OEM
Правило совместимости кросс-номеров:
Кросс-номера из поля кросс_номера автоматически наследуют совместимость основного OEM со всеми моделями. Если аналог подходит только части моделей — его нужно заводить отдельной строкой в CSV:
| Случай | Метод |
|---|---|
F00RJ01285 подходит всем (R5, G5, P5) |
В поле кросс_номера основного OEM |
F00RJ01285 подходит только R5 |
Отдельная строка: OEM=F00RJ01285, марка=Scania, модель=R5 |
Вариант написания 96046-21423 |
Всегда в кросс_номера — это форма написания, не деталь |
Отображение аналогов на листинге модели:
При рендеринге листинга модели блок "Аналоги" для каждой детали фильтрует кросс-OEM по совместимости с этой моделью:
-- Аналоги при просмотре списка через /scania/g5/.../
SELECT cross_oem.*
FROM lider_oem cross_oem
JOIN lider_compatibility compat ON compat.part_id = cross_oem.part_id
WHERE cross_oem.cross_ref_of = :main_oem_id -- кросс основного OEM
AND compat.model_id = :current_model_id -- только G5
Результат: в разделе /scania/r5/ и /scania/g5/ блок "Аналоги" может показывать разные наборы кросс-OEM для одной и той же детали.
Из поля комментарий (основной источник совместимости):
Совместимость пишется в поле комментарий в строгом формате:
Подходит для: Scania R5, Scania G5, Scania P5
или
Совместимые модели: Volvo FH4, Volvo FM4, Renault T
Парсинг строгий — по ключевым словам с двоеточием:
COMPAT_PATTERNS = [
# "Подходит для: Scania R5, Scania G5" ← основной формат
r'[Пп]одходит\s+для\s*:\s*([^\n]+)',
# "Совместимые модели: Volvo FH4, Volvo FM4"
r'[Сс]овместимые\s+модели\s*:\s*([^\n]+)',
]
def parse_compatibility(comment: str) -> list[dict]:
"""
Парсит поле комментарий → список совместимостей.
Формат: "Подходит для: Марка Модель, Марка Модель"
Модели как в Bazon (те же названия что в полях марка и модель).
Returns: [{'brand': 'Scania', 'model': 'R5'}, ...]
"""
result = []
for pattern in COMPAT_PATTERNS:
match = re.search(pattern, comment)
if not match:
continue
models_str = match.group(1).strip()
# Разбиваем по запятой
model_tokens = [m.strip() for m in models_str.split(',') if m.strip()]
for token in model_tokens:
# Ожидаем формат "Марка Модель" (два слова)
parts = token.split(None, 1)
if len(parts) == 2:
result.append({'brand': parts[0], 'model': parts[1]})
elif len(parts) == 1:
# Только марка без модели — логируем как предупреждение
result.append({'brand': parts[0], 'model': None, 'warning': 'no_model'})
break # нашли один паттерн — достаточно
return result
Пример разбора:
Комментарий: "Подходит для: Scania R5, Scania G5, Scania P5, Mercedes Actros MP4"
→ [
{'brand': 'Scania', 'model': 'R5'},
{'brand': 'Scania', 'model': 'G5'},
{'brand': 'Scania', 'model': 'P5'},
{'brand': 'Mercedes', 'model': 'Actros MP4'},
]
→ Создаётся 4 записи lider_compatibility
Важно: Модели в комментарии должны совпадать с taxonomy terms lider_brands и lider_models. Если совпадения нет — запись логируется с предупреждением brand_not_found или model_not_found, но импорт не останавливается.
Результат парсинга сохраняется с флагом source=comment_parsed для аудита.
vocabulary: lider_brands
- name: Scania (значение из поля марка)
- field_brand_slug: scania (slug для URL)
- field_canonical_priority: порядок приоритета моделей этого бренда
vocabulary: lider_models
- name: R5
- field_model_slug: r5
- field_brand: reference → lider_brands term
- field_generation: 5th Generation
- field_years: 2004–2016
vocabulary: lider_systems
- name: Топливная система
- field_system_slug: toplivnaya-sistema
- field_group: Двигатель (родительская группа)
vocabulary: lider_nodes (узлы)
- name: Форсунка
- field_node_slug: forsunka
content type: lider_part
| Поле | Тип | Описание |
|---|---|---|
title |
string | Наименование детали |
field_oem_canonical |
string | Главный нормализованный OEM |
field_manufacturer |
string | Производитель компонента |
field_condition |
list (string) | новый / аналог / оригинал |
field_brand |
reference → lider_brands | Марка (основная) |
field_system |
reference → lider_systems | Система |
field_node_ref |
reference → lider_nodes | Узел |
field_needs_review |
boolean | Флаг: требует проверки наименования |
field_import_source |
string | Источник: bazon_csv |
field_bazon_article |
string | Артикул Bazon |
status |
boolean | Опубликовано (true если есть цена) |
product type: lider_spare_part
- title: наименование (дублирует node.title)
- stores: default store
- variations: одна или несколько вариаций (по цене/состоянию)
product variation type: lider_spare_part_variation
- sku: {oem_normalized} или {oem_normalized}-{состояние}
- price: Amount объект с currency RUB
- field_condition: аналог / оригинал / новый
- status: active (если деталь доступна) или inactive (DISAPPEARED)
entity type: lider_oem
| Поле | Описание |
|---|---|
oem_original |
OEM как пришёл от поставщика |
oem_normalized |
После normalize_oem() |
oem_canonical |
Главный (для поиска, URL) |
oem_type |
main / cross_reference |
part_reference |
reference → lider_part node |
brand_hint |
Марка-производитель (Mercedes, DAF и т.д.) — помогает нормализации |
entity type: lider_compatibility
| Поле | Описание |
|---|---|
part_id |
reference → lider_part node |
brand_id |
reference → lider_brands taxonomy term |
model_id |
reference → lider_models taxonomy term |
source |
catalog_csv / donor_expansion / text_parsed / manual |
confidence |
high / medium / low |
Формат: /zapchasti/{brand_slug}/{model_slug}/{system_slug}/{name_slug}-{oem_canonical}/
Правило ANY для {model_slug}:
| Совместимость детали | {model_slug} |
Пример URL |
|---|---|---|
| 1 бренд, 1 модель | slug модели (r5) |
/zapchasti/scania/r5/toplivnaya-sistema/forsunka-9604621423/ |
| 1 бренд, 2+ модели | any |
/zapchasti/scania/any/toplivnaya-sistema/forsunka-9604621423/ |
| 2+ бренда | any + приоритетный бренд |
/zapchasti/volvo/any/toplivnaya-sistema/forsunka-1765026/ |
Приоритет бренда при 2+ брендах: Scania → Volvo → Mercedes → MAN → DAF → Renault → Iveco → Liebherr
Правила slug-ификации:
- Кириллица → транслитерация (ГОСТ 7.79-2000)
- Пробелы → дефис
- Множественные дефисы → один дефис
- Точки, запятые, скобки → удаляются
- Максимальная длина slug наименования: 50 символов
Правило: Один OEM = один URL. Model-alias страниц нет.
Format-error (HTTP 301 redirect)
Ошибочные написания OEM в URL (A-prefix, дефисы, 742-prefix) → 301 на canonical URL. Обрабатывается OemNormalizerSubscriber. Яндекс и Google эти страницы не индексируют.
/zapchasti/scania/r5/toplivnaya-sistema/forsunka-a9604621423/
→ 301 →
/zapchasti/scania/r5/toplivnaya-sistema/forsunka-9604621423/
/zapchasti/scania/r5/toplivnaya-sistema/forsunka-96046-21423/
→ 301 →
/zapchasti/scania/r5/toplivnaya-sistema/forsunka-9604621423/
Canonical URL возвращает 200 OK. <link rel="canonical"> = self. Страница в sitemap.
# Импорт всего каталога из файла
drush lider:import:catalog \
[--mode=upsert|insert|dry-run] \
[--brand=scania] \
[--limit=100] \
[--file=path/to/catalog.csv] \
[--batch=100]
# Импорт совместимости
drush lider:import:compatibility \
[--source=catalog|donors|comments|all] \
[--brand=scania]
# Перестройка редиректов
drush lider:import:redirects \
[--rebuild] \
[--brand=scania]
# Пересчёт canonical URL
drush lider:import:canonical \
[--recalculate] \
[--brand=scania] \
[--dry-run]
# Только валидация без импорта
drush lider:import:validate \
--file=path/to/catalog.csv \
[--verbose]
# Показать статус последнего импорта
drush lider:import:status
# Показать очередь на проверку наименований
drush lider:import:review-queue \
[--brand=scania] \
[--limit=50]
| Режим | Описание |
|---|---|
upsert |
Создать новые + обновить существующие (рекомендуемый для обновлений) |
insert |
Только создать новые, существующие игнорировать |
dry-run |
Симуляция: показать что будет сделано, ничего не записывать |
# Полная симуляция нового прайса
drush lider:import:validate --file=/tmp/bazon_new.csv --verbose
# Тестовый импорт первых 100 записей Scania
drush lider:import:catalog --brand=scania --limit=100 --mode=dry-run --file=/tmp/bazon.csv
# Реальный импорт Scania
drush lider:import:catalog --brand=scania --file=/tmp/bazon.csv --mode=upsert
# Полный импорт всех брендов
drush lider:import:catalog --file=/tmp/bazon.csv --mode=upsert
# Расширение совместимости через доноров
drush lider:import:compatibility --source=donors
# Пересчитать canonical только для DAF
drush lider:import:canonical --recalculate --brand=daf
╔═══════════════════════════════════════════════════════════════════╗
║ ОТЧЁТ ИМПОРТА: lideravto catalog ║
║ 2026-02-27 14:35:22 UTC ║
╠═══════════════════════════════════════════════════════════════════╣
║ Файл: bazon_prays_2026-02.csv ║
║ Режим: upsert ║
║ Бренд: all ║
╠═══════════════════════════════════════════════════════════════════╣
║ СТРОКИ CSV: ║
║ Всего строк в файле: 13 621 ║
║ Успешно обработано: 13 598 ║
║ Пропущено (ошибки): 23 ║
╠═══════════════════════════════════════════════════════════════════╣
║ РЕЗУЛЬТАТЫ: ║
║ Новых деталей создано: 124 ║
║ Обновлено цен: 891 ║
║ Добавлено связей совместимости: 45 ║
║ Без изменений (skip): 12 538 ║
║ Исчезнувших деталей: 12 → out_of_stock ║
╠═══════════════════════════════════════════════════════════════════╣
║ КАЧЕСТВО ДАННЫХ: ║
║ Исправлено OEM (A-prefix): 31 ║
║ Исправлено OEM (PE-suffix): 8 ║
║ Исправлено OEM (742-prefix): 15 ║
║ Исправлено OEM (кириллица): 4 ║
║ Требуют проверки наименований: 38 → /admin/lider/review ║
╠═══════════════════════════════════════════════════════════════════╣
║ CANONICAL: ║
║ Пересчитан canonical: 89 деталей ║
║ Новых редиректов создано: 89 ║
║ Sitemap перестроен ✓ ║
╠═══════════════════════════════════════════════════════════════════╣
║ ВРЕМЯ: ║
║ Нормализация OEM: 0:00:45 ║
║ Загрузка в БД: 0:08:30 ║
║ Пересчёт canonical: 0:02:15 ║
║ Итого: 0:11:30 ║
╠═══════════════════════════════════════════════════════════════════╣
║ ОШИБКИ (23): ║
║ [строка 1247] пустое наименование: пропущена ║
║ [строка 3891] некорректная цена '18 500,00 руб': исправлено ║
║ ... (21 ещё) → /admin/reports/lider-import-log/latest ║
╚═══════════════════════════════════════════════════════════════════╝
| Уровень | Что пишется |
|---|---|
| ERROR | Строки пропущенные из-за критических ошибок |
| WARNING | Исправленные OEM, частичные автофиксы наименований |
| INFO | Сводная статистика (создано / обновлено / пропущено) |
| DEBUG | Каждая обработанная строка (включать только при отладке) |
/var/log/drupal/lider-import/YYYY-MM-DD.loglider_import_log (хранить 90 дней)/admin/reports/lider-import-log/admin/reports/lider-import-log/latest| Ситуация | Тип | Действие |
|---|---|---|
| Файл не найден | CRITICAL | Остановить импорт, уведомить |
| Файл не UTF-8 | ERROR | Попытка конвертации из cp1251/win1251, если не удалось — остановить |
| Неправильный разделитель | ERROR | Попытка автоопределения разделителя |
| Нет заголовка (первая строка — данные) | WARNING | Продолжить, предупредить |
Пустое поле наименование |
ERROR | Пропустить строку |
Пустое поле oem |
ERROR | Пропустить строку если нет артикула; если артикул есть — создать с артикулом как временным ID |
Пустые поля марка или модель |
ERROR | Пропустить строку |
| Некорректная цена | WARNING | Попытка парсинга (18 500,00 руб → 18500); если не удалось — пропустить |
| Очень длинное наименование (>255 символов) | WARNING | Усечь до 255, залогировать |
| OEM с ошибкой (A-prefix, PE-suffix и т.д.) | WARNING | Нормализовать, залогировать исправление |
| Дубликат OEM в пределах одного файла | WARNING | Обработать оба как UPDATED_COMPAT (добавить совместимость) |
| Таймаут БД при записи | ERROR | Повторить 3 раза с паузой 5с; если не удалось — записать в retry-очередь |
| 3 ошибки подряд в одном батче | CRITICAL | Остановить батч, сообщить администратору, продолжить со следующего батча |
Записи не прошедшие из-за временных ошибок (таймаут, блокировка БД) помещаются в таблицу lider_import_retry и повторно обрабатываются по cron через 15 минут.
| Параметр | Значение | Описание |
|---|---|---|
| Batch size | 100 строк | Размер одного батча |
| Memory limit | 512 MB | Минимум для нормальной работы |
| Max execution time | 300 сек | Для PHP CLI (drush) — без ограничения |
| DB connection timeout | 30 сек | Таймаут подключения к MySQL |
| Операция | Строк | Ожидаемое время |
|---|---|---|
| Валидация CSV | 13 621 | 30 сек |
| Нормализация OEM | 13 621 | 45 сек |
| Дельта-расчёт | 13 621 | 1 мин |
| Загрузка NEW в БД | ~124 | 20 сек |
| Обновление цен | ~891 | 40 сек |
| Добавление совместимости | ~45 | 10 сек |
| Пересчёт canonical | 9 212 | 3 мин |
| Генерация редиректов | ~89 новых | 30 сек |
| Итого | ~7–12 мин |
Первоначальный полный импорт (9 212 новых записей): ~45–60 минут.
Перед импортом проверить наличие индексов:
-- Критически важны для производительности дельты:
CREATE INDEX IF NOT EXISTS idx_lider_oem_normalized ON lider_oem (oem_normalized);
CREATE INDEX IF NOT EXISTS idx_lider_compat_part ON lider_compatibility (part_id, brand_id, model_id);
Функциональность:
- [ ] drush lider:import:validate выявляет все 6 типов ошибок OEM и сообщает о них
- [ ] drush lider:import:catalog --mode=dry-run не вносит изменений в БД
- [ ] Первоначальный импорт 9 212 OEM завершается без ошибок
- [ ] После импорта 9 212 страниц /zapchasti/{brand}/{model}/... возвращают 200 OK
- [ ] После импорта OEM с ошибкой (A-prefix) — canonical URL без буквы A
- [ ] Дельта-импорт: цена изменилась в CSV → изменилась на сайте
- [ ] Дельта-импорт: OEM исчез из CSV → статус out_of_stock, страница осталась (200 OK)
- [ ] Дельта-импорт: новый OEM → новая страница создана
SEO:
- [ ] Каждая страница детали возвращает 200 и имеет тег <link rel="canonical"> с самим собой (canonical = self)
- [ ] Страница с 1 моделью имеет URL вида /brand/model/..., с 2+ моделями — /brand/any/...
- [ ] Format-error URL (ошибочный OEM — A-prefix, дефисы, 742-prefix) возвращают 301 на правильный URL
Качество данных:
- [ ] Детали с наименованием из стоп-листа получают флаг needs_review
- [ ] Очередь проверки доступна в /admin/lider/review
- [ ] После снятия флага деталь не возвращается в очередь при следующем импорте (если наименование не изменилось в CSV)
Производительность:
- [ ] Дельта-импорт 13 621 строк завершается за ≤ 15 минут
- [ ] Потребление памяти PHP не превышает 512 MB
- [ ] Нет N+1 запросов к БД (проверить через drush devel:sql или EXPLAIN)
Логирование:
- [ ] Отчёт содержит все метрики из раздела 6.1
- [ ] Ошибки с номерами строк CSV записываются в лог
- [ ] Лог доступен через /admin/reports/lider-import-log/latest
Документ подготовлен для проекта new.lideravto.ru
Дата: 2026-02-27