mirror of
https://github.com/koloideal/DutyLog.git
synced 2026-06-10 10:25:29 +03:00
103 lines
3.8 KiB
Python
103 lines
3.8 KiB
Python
import argparse
|
||
import asyncio
|
||
import re
|
||
from docx import Document
|
||
import asyncpg
|
||
|
||
|
||
def parse_residents(docx_path: str) -> list[dict]:
|
||
doc = Document(docx_path)
|
||
results = []
|
||
seen = set()
|
||
|
||
for table in doc.tables:
|
||
current_room = None
|
||
for row in table.rows:
|
||
cells = row.cells
|
||
if len(cells) < 3:
|
||
continue
|
||
|
||
cell0_text = cells[0].text.strip()
|
||
room_match = re.search(r'\b(\d{3,4})\b', cell0_text)
|
||
if room_match:
|
||
current_room = int(room_match.group(1))
|
||
|
||
name_text = cells[2].text.strip()
|
||
skip_keywords = ('Дата', 'Фамилия', 'Осталось', 'Совершеннолетних')
|
||
|
||
if name_text and current_room and not any(kw in name_text for kw in skip_keywords):
|
||
clean_name = re.sub(r'\s+[СC]$', '', name_text).strip()
|
||
if clean_name and len(clean_name) > 2:
|
||
key = (current_room, clean_name)
|
||
if key not in seen:
|
||
seen.add(key)
|
||
results.append({"room_number": current_room, "real_name": clean_name})
|
||
|
||
return results
|
||
|
||
|
||
async def insert_data(database_url: str, residents: list[dict]) -> None:
|
||
async with asyncpg.create_pool(database_url) as pool:
|
||
async with pool.acquire() as conn:
|
||
async with conn.transaction():
|
||
floor_rows = await conn.fetch("SELECT id, number FROM floors;")
|
||
floor_map = {row['number']: row['id'] for row in floor_rows}
|
||
|
||
if not floor_map:
|
||
raise RuntimeError("Таблица floors пуста, невозможно привязать комнаты.")
|
||
|
||
unique_rooms = set(r["room_number"] for r in residents)
|
||
room_insert_params = []
|
||
|
||
for room_num in unique_rooms:
|
||
floor_num = room_num // 100
|
||
if floor_num not in floor_map:
|
||
raise ValueError(f"Для комнаты {room_num} не найден этаж {floor_num} в БД.")
|
||
room_insert_params.append((room_num, floor_map[floor_num]))
|
||
|
||
await conn.executemany(
|
||
"""
|
||
INSERT INTO rooms (number, on_floor)
|
||
VALUES ($1, $2)
|
||
ON CONFLICT (number) DO NOTHING;
|
||
""",
|
||
room_insert_params
|
||
)
|
||
|
||
room_rows = await conn.fetch(
|
||
"""
|
||
SELECT id, number
|
||
FROM rooms
|
||
WHERE number = ANY($1::int[]);
|
||
""",
|
||
list(unique_rooms)
|
||
)
|
||
room_map = {row['number']: row['id'] for row in room_rows}
|
||
|
||
resident_params = [
|
||
(r["real_name"], room_map[r["room_number"]])
|
||
for r in residents
|
||
]
|
||
|
||
await conn.executemany(
|
||
"""
|
||
INSERT INTO residents (real_name, room, created_at, updated_at)
|
||
VALUES ($1, $2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
|
||
""",
|
||
resident_params
|
||
)
|
||
|
||
|
||
async def main() -> None:
|
||
parser = argparse.ArgumentParser(description="Импорт жильцов из docx в PostgreSQL")
|
||
parser.add_argument("--db-url", required=True, help="URL базы данных (напр. postgresql://user:pass@localhost/db)")
|
||
parser.add_argument("--file", required=True, help="Путь к docx файлу")
|
||
args = parser.parse_args()
|
||
|
||
residents = parse_residents(args.file)
|
||
await insert_data(args.db_url, residents)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(main())
|