Загрузка данных
from pathlib import Path
from openpyxl import load_workbook
# Файлы лежат рядом с notebook.ipynb
SOURCE_FILE = Path("Потребность в диагностике (1).xlsx")
TARGET_FILE = Path("База 2026 корректировка 3.xlsx")
OUTPUT_FILE = Path("result_base.xlsx")
SOURCE_SHEET = "Лист1"
TARGET_SHEET = "Вся База"
def normalize_identifier_from_need(col_m_value):
"""Преобразует идентификатор к виду short_id."""
if col_m_value is None:
return None
m_str = str(col_m_value).strip()
if not m_str:
return None
dr_pos = m_str.lower().find("dr")
if dr_pos != -1:
short_id = m_str[:dr_pos].strip()
else:
short_id = m_str
if short_id.endswith("_s"):
return short_id
if short_id.endswith("_"):
return short_id.rstrip("_")
return short_id
def as_text(value):
"""Преобразует значение в текст, не меняя номера заявок и заданий."""
if value is None:
return ""
return str(value).strip()
def normalize_for_match(value):
"""
Приводит ID к единому виду для поиска:
убирает внешние пробелы и приводит к нижнему регистру.
"""
if value is None:
return None
value = str(value).strip()
if not value:
return None
return value.lower()
# Проверка наличия файлов
if not SOURCE_FILE.exists():
raise FileNotFoundError(f"Не найден первый файл: {SOURCE_FILE.resolve()}")
if not TARGET_FILE.exists():
raise FileNotFoundError(f"Не найден файл базы: {TARGET_FILE.resolve()}")
# Первая книга: потоковое чтение, быстро и с меньшим использованием памяти.
wb_source = load_workbook(
SOURCE_FILE,
read_only=True,
data_only=False
)
# Вторая книга: обычный режим, потому что в неё нужно записывать данные.
wb_target = load_workbook(
TARGET_FILE,
read_only=False,
data_only=False
)
ws_source = wb_source[SOURCE_SHEET]
ws_target = wb_target[TARGET_SHEET]
print(f"Первая таблица: {ws_source.max_row} строк")
print(f"Вторая таблица: {ws_target.max_row} строк")
# ---------------------------------------------------------
# 1. Индексируем AE второй таблицы.
# AE = 31. Будем читать только этот один столбец.
# ---------------------------------------------------------
index_ae = {}
for row_number, row_values in enumerate(
ws_target.iter_rows(
min_row=2,
max_row=ws_target.max_row,
min_col=31,
max_col=31,
values_only=True
),
start=2
):
value_ae = row_values[0]
# Если AE уже содержит короткий ID, не обрезаем его по dr.
# Только приводим его к единому виду для сравнения.
id_ae = normalize_for_match(value_ae)
if id_ae:
index_ae.setdefault(id_ae, []).append(row_number)
print(f"В индекс добавлено уникальных ID из AE: {len(index_ae)}")
# ---------------------------------------------------------
# 2. Читаем из первой таблицы только A, B и M.
# Диапазон A:M нужен, потому что M — 13-й столбец.
# iter_rows идёт последовательно и работает значительно быстрее.
# ---------------------------------------------------------
not_found = []
duplicate_ids = []
updated_rows = 0
processed_rows = 0
empty_m_rows = 0
for source_row, values in enumerate(
ws_source.iter_rows(
min_row=2,
max_row=ws_source.max_row,
min_col=1,
max_col=13,
values_only=True
),
start=2
):
# values — кортеж из A:M:
# values[0] = A, values[1] = B, values[12] = M
request_number = as_text(values[0])
task_number = as_text(values[1])
value_m = values[12]
short_id = normalize_identifier_from_need(value_m)
if not short_id:
empty_m_rows += 1
continue
# Сравниваем с ключами из AE в одинаковом виде.
search_id = normalize_for_match(short_id)
found_rows = index_ae.get(search_id)
if not found_rows:
not_found.append({
"строка_Лист1": source_row,
"идентификатор_M": short_id
})
continue
if len(found_rows) > 1:
duplicate_ids.append({
"строка_Лист1": source_row,
"идентификатор": short_id,
"строки_Вся_База": found_rows
})
for target_row in found_rows:
# AF = 32, AG = 33
cell_af = ws_target.cell(row=target_row, column=32)
cell_ag = ws_target.cell(row=target_row, column=33)
# Сохраняем как текст, в том числе для номеров с ведущими нулями.
cell_af.number_format = "@"
cell_ag.number_format = "@"
cell_af.value = request_number
cell_ag.value = task_number
updated_rows += 1
processed_rows += 1
# Индикатор прогресса: появится внизу ячейки Jupyter.
if processed_rows % 1000 == 0:
print(
f"Обработано совпавших строк: {processed_rows}; "
f"заполнено строк базы: {updated_rows}"
)
# ---------------------------------------------------------
# 3. Сохраняем новую копию базы.
# ---------------------------------------------------------
wb_target.save(OUTPUT_FILE)
wb_source.close()
wb_target.close()
print("\nГОТОВО")
print(f"Файл результата: {OUTPUT_FILE.resolve()}")
print(f"Заполнено строк в базе: {updated_rows}")
print(f"Строк с пустым M: {empty_m_rows}")
print(f"ID из M, не найдено в AE: {len(not_found)}")
print(f"ID с дублями в AE: {len(duplicate_ids)}")
if not_found:
print("\nПервые 10 ID, которые не нашлись в AE:")
for item in not_found[:10]:
print(item)
if duplicate_ids:
print("\nПервые 10 повторяющихся ID в AE:")
for item in duplicate_ids[:10]:
print(item)