у меня есть приложение, давай разнесём его по модулям
import math
import os
import re
import csv
import json
from flask import Flask, render_template, redirect, url_for, request
import pyodbc
from datetime import datetime
import pandas as pd


app = Flask(__name__)

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

CSV_PATH = os.path.join(BASE_DIR, 'data', 'data.csv')
SCAN_FOLDER = os.path.join(BASE_DIR, 'static', 'scans')
DB_CONFIG_PATH = os.path.join(BASE_DIR, 'db_config.json')

#ACT_REGEX = re.compile(r"Акт № (\S+) от (\d+)\.(\d+)\.(\d{4})", re.IGNORECASE)
ACT_REGEX = re.compile(r"№\s*(\S+)\s*от\s*(\d+)\.(\d+)\.(\d{4})", re.IGNORECASE)

def load_db_config(path):
    with open(path, 'r', encoding='utf-8') as f:
        return json.load(f)

def parse_acts_from_description(description_text):
    acts = []
    del_space = str(description_text).replace('  ', ' ').replace('   ', ' ')
    if pd.isna(description_text):
        return acts
    for match in ACT_REGEX.finditer(del_space):
        
        number_zakl = match.group(1)
        day, month, year = match.group(2), match.group(3), match.group(4)
        date_str = f"{year}-{month}-{day}"  # YYYY-MM-DD
        
        acts.append((number_zakl, date_str))
        #print(acts)
    return acts

def load_csv_data():
    df = pd.read_csv(CSV_PATH, encoding='utf-8',sep=',')    
    if 'check' not in df.columns:
        df['check'] = ''  # пустые значения
    # Создаём колонку 'acts' с разобранными актами
    df['acts'] = df['description_text'].apply(parse_acts_from_description)
    return df
global csv_data
csv_data = load_csv_data()
#print(csv_data)
remote_config = load_db_config(DB_CONFIG_PATH)
connection_string = (
    f"DRIVER={{MySQL ODBC 5.1 Driver}};"
    f"SERVER={remote_config['host']};"
    f"DATABASE={remote_config['database']};"
    f"UID={remote_config['user']};"
    f"PWD={remote_config['password']};"
)

def get_spisanie_by_number_and_date(number_zakl, date_iso):
    conn = pyodbc.connect(connection_string)
    cursor = conn.cursor()
    query = "SELECT * FROM spisanie WHERE number_zakl = ? AND DATE(date) = ?"
    cursor.execute(query, (number_zakl, date_iso))
    row = cursor.fetchone()
    conn.close()
    if not row:
        return None
    return {
        'spisanie_id': row.id,
        'number_zakl': row.number_zakl,
        'date': row.date.strftime('%Y-%m-%d'),
        'region': row.region,
        'type': row.type,
        'serial_number': row.serial_number,
        'inventory_number': row.inventory_number,
        'problem': row.problem,
        'TNPA': row.TNPA,
        'fio': row.fio,
        'status_zakl': row.status_zakl,
        'zalit': row.zalit,
        'description': row.description,
        'mac': row.mac,
        'factory_num': row.factory_num
    }

def find_csv_rows_for_act(number_zakl, date_iso):
    mask = csv_data ['acts'].apply(lambda acts: (number_zakl, date_iso) in acts)
    rows_df = csv_data [mask]
    return rows_df.to_dict(orient='records')


def highlight_match(text, pattern):
    import html
    text_esc = html.escape(text or '')
    pattern_esc = re.escape(pattern)
    regex = re.compile(pattern_esc, re.IGNORECASE)
    return regex.sub(r'<span style="background-color:#b6fcb6;">\g<0></span>', text_esc)

@app.route('/')
def index():
    
    unique_acts = []
    seen = set()

    for idx, row in csv_data.iterrows():
        acts_list = row['acts']
        # Если строка, распарсим
        if not isinstance(acts_list, list):
            import ast
            try:
                acts_list = ast.literal_eval(acts_list)
            except Exception:
                acts_list = []

        for act in acts_list:
            if act not in seen:
                seen.add(act)
                unique_acts.append(act)

    if unique_acts:
        number, date = unique_acts[0]
        return redirect(url_for('show_act', number_zakl=number, date=date))
    else:
        return "No acts found in CSV"


@app.route('/act/<number_zakl>/<date>')
def show_act(number_zakl, date):
    spisanie = get_spisanie_by_number_and_date(number_zakl, date)
    if not spisanie:
        #return f"Act {number_zakl} from {date} not found in DB"
        spisanie = {
        'spisanie_id': '___',
        'number_zakl': '___',
        'date': '0000-00-00',
        'region': '___',
        'type': '___',
        'serial_number': '___',
        'inventory_number': '___',
        'problem': '___',
        'TNPA': '___',
        'fio': '___',
        'status_zakl': '___',
        'zalit': '___',
        'description': '___',
        'mac': '___',
        'factory_num': '___',
    }

    csv_rows = find_csv_rows_for_act(number_zakl, date)
    for row in csv_rows:
        sn = spisanie['serial_number']
        inv = spisanie['inventory_number']
        row['name_txt_highlight'] = highlight_match(row['name'], sn) if sn else row['name']
        row['inv_highlight'] = highlight_match(row['inv'], inv) if inv else row['inv']

    y,m,d = date.split('-')
    scan_file_name = f"akt_{number_zakl}_{d}_{m}_{y}.png"
    scan_file_path = os.path.join(SCAN_FOLDER, scan_file_name)
    scan_exists = os.path.isfile(scan_file_path)

    unique_acts = []
    seen = set()
    for idx, row in csv_data.iterrows():
        for act in row['acts']:
            if act not in seen:
                seen.add(act)
                unique_acts.append(act)

    current_index = unique_acts.index((number_zakl, date)) if (number_zakl, date) in unique_acts else -1

    prev_act = unique_acts[current_index - 1] if current_index > 0 else None
    next_act = unique_acts[current_index + 1] if current_index != -1 and current_index < len(unique_acts) - 1 else None
    del spisanie['spisanie_id']
    del spisanie['number_zakl']
    del spisanie['date']
    del spisanie['fio']
    del spisanie['status_zakl']
    del spisanie['zalit']
    del spisanie['problem']
    return render_template('act.html',
                           number_zakl=number_zakl,
                           date=date,
                           spisanie=spisanie,
                           csv_rows=csv_rows,
                           scan_file_name=scan_file_name if scan_exists else None,
                           prev_act=prev_act,
                           next_act=next_act)
@app.route('/update_check/<number_zakl>/<date>', methods=['POST'])
def update_check(number_zakl, date):
    global csv_data

    # Получаем, какую отметку ставим (OK или ИСПР)
    status = request.form.get('status')
    if status not in ['OK', 'ИСПР']:
        return "Неверный статус", 400

    # Находим строки CSV, где есть текущий акт
    mask = csv_data['acts'].apply(lambda acts: (number_zakl, date) in acts if isinstance(acts, list) else False)

    # Записываем в колонку check для этих строк нужный статус
    csv_data.loc[mask, 'check'] = status

    # Сохраняем CSV на диск
    csv_data.to_csv(CSV_PATH, index=False, encoding='utf-8')

    # Определяем следующий акт (для редиректа)
    unique_acts = []
    seen = set()
    for acts_list in csv_data['acts']:
        if not isinstance(acts_list, list):
            continue
        for act in acts_list:
            if act not in seen:
                seen.add(act)
                unique_acts.append(act)

    try:
        current_index = unique_acts.index((number_zakl, date))
    except ValueError:
        current_index = -1

    next_act = None
    if 0 <= current_index < len(unique_acts) - 1:
        next_act = unique_acts[current_index + 1]

    if next_act:
        return redirect(url_for('show_act', number_zakl=next_act[0], date=next_act[1]))
    else:
        return "Это последний акт. Нет следующего."
    
if __name__ == '__main__':
    app.run(debug=True)