Respan Dataset Explorer

Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.

5,167,182physical rows
86shards
0.00%qualified row coverage
0.00%qualified cell coverage
Random row JSON API

turns-00054.parquet:741

b56c1e3f4a84cb034f8902c4
turn 1/7gpt-4o-mini-2024-07-18RussianRussia4148 words
degenerate_repetitionAbsentFinal dense release
USER
bad recovery показывает везде true, это не правильно. True показывает если есть дубликаты по колонке "номер", "Serial", "Графа" по какой то одной из колонок в базе данных. False если вообще у записи нету дубликатов в базе данных.
from flask import Flask, render_template, request, redirect, make_response, jsonify, send_file, Response
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.sql import func
from datetime import datetime, timedelta, date, timezone
import pandas as pd
from io import BytesIO
import json
import queue
import logging
from sqlalchemy import cast, Integer, or_
import os
from collections import deque
import random



app = Flask(__name__)



app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///table_data.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

ACTIVITY_HISTORY_LENGTH = 400  # Количество последних значений для графика
activity_data = {}

class TableRow(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), nullable=False)
    row_number = db.Column(db.String(10))
    timestamp = db.Column(db.String(50))
    number = db.Column(db.String(50))
    serial = db.Column(db.String(50))
    dl_number = db.Column(db.String(50))
    worker_name = db.Column(db.String(100))
    pack_time = db.Column(db.String(50))
    arma_type = db.Column(db.String(50))
    dateshipment = db.Column(db.String(50))
    aktshipment = db.Column(db.String(50))
    bad_recovery = db.Column(db.Boolean, default=False)


class DLCounter(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), unique=True, nullable=False)
    counter = db.Column(db.Integer, nullable=False)

    def __repr__(self):
        return f'<DLCounter {self.date}: {self.counter}>'


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    login_history = db.relationship('LoginHistory', backref='user', lazy=True)


class LoginHistory(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    login_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

class Statistics(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), unique=True, nullable=False)
    generate_clicks = db.Column(db.Integer, default=0)
    pack_clicks = db.Column(db.Integer, default=0)

    def __repr__(self):
        return f'<Statistics {self.date}: Generate={self.generate_clicks}, Pack={self.pack_clicks}>'

class Prefix(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    value = db.Column(db.String(10), nullable=False, unique=True)

# Добавьте это в раздел моделей вашего Flask-приложения

class Device(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(10), unique=True, nullable=False)  # Имена: ARM1, ARM2 и т.д.
    arma_type = db.Column(db.String(10), nullable=False, default='Other')  # Тип АРМА
    last_active = db.Column(db.DateTime, nullable=True)  # Время последней активности
    internet_up = db.Column(db.Boolean, nullable=False, default=True)  # Статус интернет-соединения
    copying = db.Column(db.Boolean, nullable=False, default=False)  # Статус копирования
    previous_copying = db.Column(db.Boolean, nullable=False, default=False)  # Новое поле
    sending_number = db.Column(db.Boolean, nullable=False, default=False)  # Статус отправки номера
    processing = db.Column(db.Boolean, nullable=False, default=False)  # Новое поле для активности activationGlaz.py

    def to_dict(self, current_time=None):
        if current_time is None:
            current_time = datetime.utcnow()
        if self.copying:
            status = 'copying'
        elif self.sending_number:
            status = 'sending_number'
        else:
            status = 'offline'
            if self.last_active:
                delta = current_time - self.last_active
                if delta <= timedelta(seconds=60):
                    if self.internet_up:
                        status = 'online'
                    else:
                        status = 'no_internet'
        return {
            "id": self.id,
            "name": self.name,
            "arma_type": self.arma_type,
            "last_active": self.last_active.isoformat() if self.last_active else None,
            "internet_up": self.internet_up,
            "copying": self.copying,
            "sending_number": self.sending_number,
            "status": status
        }
    
    def get_status(self, current_time=None):
        if current_time is None:
            current_time = datetime.utcnow()

        if self.copying:
            return 'copying'
        elif self.sending_number:
            return 'sending_number'
        elif self.name == 'ARM3':
            # Для ARM3 игнорируем статус 'processing'
            if self.last_active:
                delta = current_time - self.last_active
                threshold = timedelta(seconds=5)
                if delta <= threshold:
                    if self.internet_up:
                        return 'online'
                    else:
                        return 'no_internet'
            return 'offline'
        elif self.processing:
            return 'processing'
        else:
            if self.last_active:
                delta = current_time - self.last_active
                threshold = timedelta(seconds=5)
                if delta <= threshold:
                    if self.internet_up:
                        return 'online'
                    else:
                        return 'no_internet'
            return 'offline'




tables = {}
table = [['№', 'Время отправки', 'Номер ', 'Serial', 'Графа', 'Имя отправителя','Время упаковки' ,'Тип АРМА',  'Дата отгрузки',  'Номер Акта',  'Действие']]
# Создаем очередь для событий
update_queue = queue.Queue()
previous_processing_states = {}

@app.route('/get_device_status', methods=['GET'])
def get_device_status():
    devices = Device.query.all()
    device_statuses = {}
    threshold = timedelta(seconds=5)  # Reduced time threshold for quick offline detection
    now = datetime.utcnow()

    for device in devices:
        name = device.name

        # Retrieve the previous processing state
        prev_processing = previous_processing_states.get(name, False)

        # Check if last_active is within the threshold
        is_recent = device.last_active and now - device.last_active <= threshold

        if device.sending_number:
            status = 'sending_number'
            activity_value = random.choice([2, 3, 2, 3, 4, 3, 2])
        elif device.copying:
            status = 'copying'
            activity_value = random.choice([0, 1, 0, 1, 2, 1, 0])
        elif is_recent:
            if device.internet_up:
                status = 'online'
                activity_value = 1
            else:
                status = 'no_internet'
                activity_value = 0

            # Handle processing status
            if device.processing:
                if device.name == 'ARM3':
                    if not prev_processing:
                        # Processing started on ARM3, send a single impulse
                        activity_value = 5  # Single impulse
                        status = 'online'  # Keep status as online for ARM3
                else:
                    status = 'processing'
                    activity_value = random.choice([2, 3, 2, 3, 4, 3, 2])
        else:
            status = 'offline'
            activity_value = 0

        device_statuses[name] = {
            'status': status,
            'processing': device.processing
        }
        activity_data[name].append(activity_value)
        logging.debug(f"Device {name}: status {status}, activity {activity_value}")

        # Update previous_processing_states
        previous_processing_states[name] = device.processing

    # Convert activity_data to serializable format
    activity_data_serializable = {name: list(data) for name, data in activity_data.items()}

    return jsonify({
        'status': 'success',
        'devices': device_statuses,
        'activity': activity_data_serializable
    })



def initialize_prefix():
    prefix = Prefix.query.first()
    if prefix:
        app.config['DL_PREFIX'] = prefix.value
    else:
        app.config['DL_PREFIX'] = 'DL'  # Устанавливаем значение по умолчанию, если префикс не найден


@app.route('/set_dl_prefix', methods=['POST'])
def set_dl_prefix():
    data = request.json
    new_prefix = data.get('prefix')

    if not new_prefix:
        return jsonify({"status": "error", "message": "Prefix is required"}), 400

    prefix = Prefix.query.first()
    if prefix:
        prefix.value = new_prefix
    else:
        prefix = Prefix(value=new_prefix)
        db.session.add(prefix)

    db.session.commit()
    app.config['DL_PREFIX'] = new_prefix
    return jsonify({"status": "success", "new_prefix": new_prefix})


def initialize_activity_data():
    device_names = ['ARM1', 'ARM2', 'ARM3', 'ARM4', 'ARM5', 'ARM6']
    for name in device_names:
        activity_data[name] = deque([0]*ACTIVITY_HISTORY_LENGTH, maxlen=ACTIVITY_HISTORY_LENGTH)

def create_devices():
    device_types = {
        'ARM1': 'CFA',
        'ARM2': 'VND',
        'ARM3': 'SRC',
        'ARM4': 'SRC',
        'ARM5': 'RPA',
        'ARM6': 'RPA'  # Убедитесь, что ARM6 тоже имеет тип 'RPA'
    }
    for name, arma_type in device_types.items():
        device = Device.query.filter_by(name=name).first()
        if not device:
            device = Device(name=name, arma_type=arma_type, sending_number=False)
            db.session.add(device)
    db.session.commit()
    initialize_activity_data()

@app.route('/register_user', methods=['POST'])
def register_user():
    data = request.json
    worker_name = data.get('worker_name')
    internet_up = data.get('internet_up', False)
    copying = data.get('copying', False)
    sending_number = data.get('sending_number', False)
    processing = data.get('processing', False)  # Новое поле

    if not worker_name:
        return jsonify({"status": "error", "message": "worker_name is required"}), 400

    device = Device.query.filter_by(name=worker_name).first()
    if not device:
        return jsonify({"status": "error", "message": "Device not found"}), 404

    device.last_active = datetime.utcnow()
    device.internet_up = internet_up
    device.copying = copying
    device.sending_number = sending_number
    device.processing = processing  # Сохраняем новое поле
    db.session.commit()

    return jsonify({"status": "success"})

@app.route('/get_unpacked_days', methods=['GET'])
def get_unpacked_days():
    year = request.args.get('year')
    month = request.args.get('month')
    
    if not year or not month:
        return jsonify({"status": "error", "message": "Необходимо указать год и месяц"}), 400
    
    try:
        year = int(year)
        month = int(month)
    except ValueError:
        return jsonify({"status": "error", "message": "Год и месяц должны быть числами"}), 400

    # Формируем формат даты для фильтрации
    month_str = f"{month:02d}"
    
    # Запрашиваем уникальные дни, где pack_time отсутствует и arma_type равен 'RPA'
    unpacked_dates = TableRow.query.filter(
        TableRow.date.like(f"{year}-{month_str}-%"),
        TableRow.arma_type == 'RPA',
        or_(TableRow.pack_time == None, TableRow.pack_time == '')
    ).with_entities(TableRow.date).distinct().all()
    
    # Извлекаем дни из дат
    unpacked_days = set()
    for date_str, in unpacked_dates:
        try:
            day = int(date_str.split('-')[2])
            unpacked_days.add(day)
        except (IndexError, ValueError):
            continue  # Пропускаем некорректные форматы дат

    return jsonify({'status': 'success', 'unpacked_days': list(unpacked_days)})

@app.route('/stream')
def stream():
    def event_stream():
        while True:
            try:
                data = update_queue.get(timeout=20)
                yield f'data: {data}\n\n'
            except queue.Empty:
                yield ': keep-alive\n\n'
    return Response(event_stream(), mimetype='text/event-stream')


# Удален маршрут /ping, так как он использовал ActiveUser
@app.route('/get_table_data')
def get_table_data():
    date = request.args.get('date')
    filter_arma = request.args.get('filter_arma')

    # Расширенное логирование
    app.logger.debug(f"Получен запрос: date={date}, filter_arma={filter_arma}")

    query = TableRow.query.filter_by(date=date)

    if filter_arma == 'RPA':
        query = query.filter_by(arma_type='RPA')

    rows = query.all()
    app.logger.debug(f"Найдено записей: {len(rows)}")

    # Словари для подсчета
    number_count = {}
    serial_count = {}
    dl_number_count = {}

    # Детальный подсчет
    for row in rows:
        # Номер
        if row.number:
            clean_number = ''.join(filter(str.isdigit, row.number)).strip()
            if clean_number:
                number_count[clean_number] = number_count.get(clean_number, 0) + 1
                app.logger.debug(f"Номер: {clean_number}, Count: {number_count[clean_number]}")

        # Serial
        if row.serial:
            clean_serial = ''.join(filter(str.isdigit, row.serial)).strip()
            if clean_serial:
                serial_count[clean_serial] = serial_count.get(clean_serial, 0) + 1
                app.logger.debug(f"Serial: {clean_serial}, Count: {serial_count[clean_serial]}")

        # DL Number
        if row.dl_number:
            clean_dl_number = ''.join(filter(str.isdigit, row.dl_number)).strip()
            if clean_dl_number:
                dl_number_count[clean_dl_number] = dl_number_count.get(clean_dl_number, 0) + 1
                app.logger.debug(f"DL Number: {clean_dl_number}, Count: {dl_number_count[clean_dl_number]}")

    # Установка bad_recovery с подробным логированием
    for row in rows:
        number_duplicate = False
        serial_duplicate = False
        dl_number_duplicate = False

        if row.number:
            clean_number = ''.join(filter(str.isdigit, row.number)).strip()
            number_duplicate = number_count.get(clean_number, 0) > 1

        if row.serial:
            clean_serial = ''.join(filter(str.isdigit, row.serial)).strip()
            serial_duplicate = serial_count.get(clean_serial, 0) > 1

        if row.dl_number:
            clean_dl_number = ''.join(filter(str.isdigit, row.dl_number)).strip()
            dl_number_duplicate = dl_number_count.get(clean_dl_number, 0) > 1

        row.bad_recovery = number_duplicate or serial_duplicate or dl_number_duplicate

        # Расширенное логирование для каждой строки
        app.logger.debug(f"Row ID: {row.id}")
        app.logger.debug(f"Number: {row.number}, Duplicate: {number_duplicate}")
        app.logger.debug(f"Serial: {row.serial}, Duplicate: {serial_duplicate}")
        app.logger.debug(f"DL Number: {row.dl_number}, Duplicate: {dl_number_duplicate}")
        app.logger.debug(f"Bad Recovery: {row.bad_recovery}")

    # Сохраняем изменения
    db.session.commit()

    # Подготовка данных для ответа
    data = []
    for row in rows:
        data.append({
            'id': row.id,
            'row_number': row.row_number,
            'timestamp': row.timestamp,
            'number': row.number,
            'serial': row.serial,
            'dl_number': row.dl_number,
            'worker_name': row.worker_name,
            'pack_time': row.pack_time,
            'arma_type': row.arma_type,
            'dateshipment': row.dateshipment,
            'aktshipment': row.aktshipment,
            'bad_recovery': row.bad_recovery,
        })

    return jsonify({"data": data})



@app.route('/update_table', methods=['POST'])
def update_table():
    data = request.json
    print(data)
    date = data['date']
    worker_name = data['worker_name']
    arma_type = data['arma_type']

    # Получаем количество существующих записей для этой даты
    row_count = TableRow.query.filter_by(date=date).count() + 1

    # Создаём новую строку в таблице
    if not data.get('number') and not data.get('serial'):
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number='',  # Пустое значение для номера
            serial='',  # Пустое значение для серийного номера
            dl_number='',  # Пустое значение для графы
            worker_name=worker_name,
            arma_type=arma_type,
            dateshipment='',
            aktshipment='',
            bad_recovery=data.get('bad_recovery', False)
        )
    elif not data.get('number'):
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number='',  # Пустое значение для номера
            serial=data['serial'],
            dl_number='',  # Пустое значение для графы
            worker_name=worker_name,
            arma_type=arma_type,
            dateshipment='',
            aktshipment='',
            bad_recovery=data.get('bad_recovery', False)
        )
    else:
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number=data['number'],
            serial=data['serial'],
            dl_number=data.get('dl_number', ''),
            worker_name=worker_name,
            arma_type=arma_type,
            dateshipment=data.get('dateshipment', ' '),
            aktshipment=data.get('aktshipment', ' '),
            bad_recovery=data.get('bad_recovery', False)
        )

    # Сохраняем запись в базе данных
    db.session.add(new_row)

    device = Device.query.filter_by(name=worker_name).first()
    if device:
        device.last_active = datetime.utcnow()
    db.session.commit()

    # Отправляем сообщение в очередь для обновления клиентов
    update_queue.put(json.dumps({"type": "update", "date": date}))

    # Добавляем импульсивное значение для sparkline, если запись отправлена ARM5 или ARM6
    if worker_name in ['ARM5', 'ARM6']:
        activity_data[worker_name].append(5)  # Значение импульса (можно настроить по необходимости)

    return jsonify({"status": "success"})


@app.route('/update_graf', methods=['POST'])
def update_graf():
    try:
        data = request.json
        row_id = int(data.get('id'))
        new_value = data.get('new_value')
        date = data.get('date')

        if not row_id or not date:
            return jsonify({"status": "error", "message": "Missing 'id' or 'date' in request"}), 400

        # Находим запись по id и дате
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.dl_number = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        else:
            return jsonify({"status": "error", "message": "Row not found"}), 404
    except (ValueError, TypeError) as e:
        return jsonify({"status": "error", "message": f"Invalid input: {str(e)}"}), 400
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500


@app.route('/update_arma_type', methods=['POST'])
def update_arma_type():
    try:
        data = request.json
        date = data['date']
        serial = data['serial']
        arma_type = data['arma_type']

        row = TableRow.query.filter_by(date=date, serial=serial).first()
        if row:
            row.arma_type = arma_type
            db.session.commit()
            return jsonify({"status": "success"})
        else:
            return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)})


@app.route('/update_number', methods=['POST'])
def update_number():
    try:
        data = request.json
        row_id = int(data['id'])  # Используем 'id' вместо 'row_index'
        new_value = data['new_value']
        date = data['date']

        # Находим запись по id и дате
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.number = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)})

@app.route('/pack_item', methods=['POST'])
def pack_item():
    try:
        data = request.json
        row_id = data['id']
        date = data['date']

        rows = TableRow.query.filter_by(id=row_id, date=date).all()
        if rows:
            row = rows[0]
            pack_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            row.pack_time = pack_time
            db.session.commit()
            
            # Обновляем статистику упаковки
            statistics = Statistics.query.filter_by(date=date).first()
            if statistics:
                statistics.pack_clicks += 1
            else:
                statistics = Statistics(date=date, generate_clicks=0, pack_clicks=1)  # pack_clicks=1
                db.session.add(statistics)
            db.session.commit()
            
            # Отправляем сообщение в очередь для обновления клиентов
            update_queue.put(json.dumps({"type": "update", "date": date}))
            
            return jsonify({"status": "success", "pack_time": pack_time})

        return jsonify({"status": "error", "message": "Строка не найдена"}), 404
    except Exception as e:
        print(f"Ошибка в pack_item: {str(e)}")
        return jsonify({"status": "error", "message": str(e)}), 500



@app.route('/set_initial_dl', methods=['POST'])
def set_initial_dl():
    try:
        data = request.json
        if not data:
            return jsonify({"status": "error", "message": "No data received"})

        date = data.get('date')
        initial_number = data.get('initial_number')

        if not date or not initial_number:
            return jsonify({
                "status": "error", 
                "message": "Missing required fields",
                "received": {"date": date, "initial_number": initial_number}
            })

        try:
            initial_number = int(initial_number)
        except ValueError:
            return jsonify({
                "status": "error",
                "message": "Initial number must be an integer"
            })

        print(f"Setting initial DL for date {date}: {initial_number}")  # Отладочный вывод

        dl_counter = DLCounter.query.filter_by(date=date).first()
        if dl_counter:
            dl_counter.counter = initial_number
            print(f"Updated existing counter for {date}")  # Отладочный вывод
        else:
            dl_counter = DLCounter(date=date, counter=initial_number)
            db.session.add(dl_counter)
            print(f"Created new counter for {date}")  # Отладочный вывод

        db.session.commit()
        return jsonify({
            "status": "success",
            "message": f"DL counter set to {initial_number} for date {date}"
        })

    except Exception as e:
        print(f"Error in set_initial_dl: {str(e)}")  # Отладочный вывод
        db.session.rollback()
        return jsonify({
            "status": "error",
            "message": str(e)
        })


@app.route('/generate_dl', methods=['POST'])
def generate_dl():
    try:
        data = request.json
        row_id = data['id']
        date = data['date']

        # Получаем или создаем счетчик DL для данной даты
        dl_counter = DLCounter.query.filter_by(date=date).first()
        if not dl_counter:
            return jsonify({"status": "error", "message": "DL counter не инициализирован"}), 400

        # Генерируем новый DL номер с использованием префикса
        dl_prefix = app.config['DL_PREFIX']  # Получаем префикс из конфигурации
        dl_number = f"{dl_prefix}{dl_counter.counter:06d}"

        # Находим и обновляем нужную запись по id
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.dl_number = dl_number
            dl_counter.counter += 1  # Увеличиваем счетчик

            # Обновляем статистику генерации
            statistics = Statistics.query.filter_by(date=date).first()
            if statistics:
                statistics.generate_clicks += 1
            else:
                statistics = Statistics(date=date, generate_clicks=1, pack_clicks=0)
                db.session.add(statistics)

            db.session.commit()
            return jsonify({"status": "success", "dl_number": dl_number, "id": row_id})

        return jsonify({"status": "error", "message": "Строка не найдена"}), 404
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 500


@app.route('/export_excel')
def export_excel():
    date = request.args.get('date')
    export_all_rpa = request.args.get('export_all_rpa')

    if export_all_rpa == 'true':
        # Экспортируем все записи RPA
        query = TableRow.query.filter_by(arma_type='RPA')
    else:
        # Экспортируем записи за выбранный день
        query = TableRow.query.filter_by(date=date, arma_type='RPA')

    rows = query.all()

    # Создаем DataFrame с данными
    data = []
    for row in rows:
        data.append({
            '№': row.row_number,
            'Время отправки': row.timestamp,
            'Номер': row.number,
            'Serial': row.serial,
            'Графа': row.dl_number,
            'Имя отправителя': row.worker_name,
            'Время упаковки': row.pack_time,
            'Тип АРМА': row.arma_type,
            'Дата отгрузки': row.dateshipment,
            'Номер акта': row.aktshipment,
            'Bad Recovery': row.bad_recovery,
        })

    if not data:
        return jsonify({"error": "Нет записей для экспорта с заданными критериями."}), 400

    df = pd.DataFrame(data)

    # Создаем Excel файл
    output = BytesIO()
    with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
        df.to_excel(writer, index=False, sheet_name='Sheet1')

        # Настраиваем форматирование
        workbook = writer.book
        worksheet = writer.sheets['Sheet1']

        # Автоматическая ширина столбцов
        for idx, col in enumerate(df.columns):
            series = df[col]
            max_len = max(
                series.astype(str).map(len).max(),
                len(str(col))
            ) + 2
            worksheet.set_column(idx, idx, max_len)

    output.seek(0)

    filename = 'RPA_export.xlsx' if export_all_rpa == 'true' else f'export_{date}.xlsx'
    return send_file(
        output,
        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        as_attachment=True,
        download_name=filename
    )

@app.route('/update_serial', methods=['POST'])
def update_serial():
    try:
        data = request.json
        row_index = int(data['row_index'])
        new_value = data['new_value']
        date = data['date']

        rows = TableRow.query.filter_by(date=date).all()
        if 0 <= row_index < len(rows):
            row = rows[row_index]
            row.serial = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        return jsonify({"status": "error", "message": "Invalid row index"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)})


def initialize_table_for_date(date):
    # Проверяем, существуют ли уже записи для этой даты
    existing = TableRow.query.filter_by(date=date).first()
    if not existing:
        # Создаем новую запись только в случае отсутствия данных
        new_row = TableRow(
            date=date,
            row_number=str(1),
            timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            number='',
            serial='',
            dl_number='',
            worker_name='',
            arma_type='',
            pack_time='',
            dateshipment='',
            aktshipment='',
            bad_recovery='',
        )
        db.session.add(new_row)
        
        # Инициализируем статистику для новой даты
        statistics = Statistics(date=date, generate_clicks=0, pack_clicks=0)
        db.session.add(statistics)
        
        db.session.commit()


@app.route('/update_serial_arma', methods=['POST'])
def update_serial_arma():
    try:
        data = request.json
        date = data['date']
        serial = data['serial']
        arma_type = data['arma_type']

        # Создаем новую запись в таблице
        new_row = TableRow(
            date=date,
            serial=serial,
            arma_type=arma_type,
            timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        )

        db.session.add(new_row)
        db.session.commit()

        return jsonify({"status": "success"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500

@app.route('/get_statistics')
def get_statistics():
    try:
        date = request.args.get('date')
        app.logger.debug(f"Получен запрос статистики для даты: {date}")
        
        # Получаем статистику генерации и упаковки
        statistics = Statistics.query.filter_by(date=date).first()
        if not statistics:
            generate_clicks = 0
            pack_clicks = 0
            app.logger.debug(f"Статистика для даты {date} не найдена. Установлено значение 0.")
        else:
            generate_clicks = statistics.generate_clicks
            pack_clicks = statistics.pack_clicks
            app.logger.debug(f"Статистика для даты {date}: Генераций = {generate_clicks}, Упаковок = {pack_clicks}")
        
        # Получаем количество записей типа "RPA"
        rpa_records = TableRow.query.filter_by(date=date, arma_type='RPA').count()
        app.logger.debug(f"Количество записей типа RPA для даты {date}: {rpa_records}")
        
        # Считаем количество записей "RPA" без значения в колонке "Графа"
        unpacked_clicks = TableRow.query.filter(
            TableRow.date == date,
            TableRow.arma_type == 'RPA',
            TableRow.dl_number == None
        ).count()
        
        # Получаем количество упакованных записей за текущий час
        current_hour = datetime.now().replace(minute=0, second=0, microsecond=0)
        next_hour = current_hour + timedelta(hours=1)
        done_per_hour = TableRow.query.filter(
            TableRow.date == date,
            TableRow.arma_type == 'RPA',
            TableRow.pack_time >= current_hour,
            TableRow.pack_time < next_hour
        ).count()
        
        return jsonify({
            "generate_clicks": generate_clicks,
            "pack_clicks": pack_clicks,
            "rpa_records": rpa_records,
            "unpacked_clicks": unpacked_clicks,
            "done_per_hour": done_per_hour
        })
    except Exception as e:
        app.logger.error(f"Ошибка в get_statistics: {str(e)}")
        return jsonify({
            "generate_clicks": 0,
            "pack_clicks": 0,
            "rpa_records": 0,
            "unpacked_clicks": 0,
            "done_per_hour": 0,
            "error": str(e)
        })
@app.route('/delete_row', methods=['POST'])
def delete_row():
    try:
        data = request.json
        row_id = data.get('id')

        if not row_id:
            return jsonify({'status': 'error', 'message': 'ID записи не предоставлен'}), 400

        row = TableRow.query.get(row_id)
        if not row:
            return jsonify({'status': 'error', 'message': 'Запись не найдена'}), 404

        date = row.date  # Получаем дату записи для обновления статистики

        # Получаем запись статистики для этой даты
        statistics = Statistics.query.filter_by(date=date).first()

        if statistics:
            # Если запись имела сгенерированный DL номер, уменьшаем generate_clicks
            if row.dl_number:
                if statistics.generate_clicks > 0:
                    statistics.generate_clicks -= 1
                else:
                    app.logger.warning(f"Attempting to decrement generate_clicks below zero for date {date}")

            # Если запись была упакована, уменьшаем pack_clicks
            if row.pack_time:
                if statistics.pack_clicks > 0:
                    statistics.pack_clicks -= 1
                else:
                    app.logger.warning(f"Attempting to decrement pack_clicks below zero for date {date}")

            db.session.commit()
        else:
            app.logger.warning(f"No Statistics entry found for date {date} when deleting row {row_id}")

        # Удаляем запись из таблицы
        db.session.delete(row)
        db.session.commit()

        # Уменьшаем счетчик DL, если запись содержала DL-номер
        if row.dl_number:
            dl_counter = DLCounter.query.filter_by(date=date).first()
            if dl_counter and dl_counter.counter > 0:
                dl_counter.counter -= 1
                db.session.commit()

        # Отправляем сообщение в очередь для обновления клиентов
        update_queue.put(json.dumps({"type": "delete", "date": date, "id": row.id}))

        return jsonify({'status': 'success'})
    except Exception as e:
        db.session.rollback()
        app.logger.error(f"Ошибка в delete_row: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/', methods=['GET', 'POST'])
def main():
    if request.method == 'POST':
        worker = request.form.get('worker')
        name = request.form.get('name')

        # Сохраняем информацию о входе
        user = User.query.filter_by(username=name).first()
        if not user:
            user = User(username=name)
            db.session.add(user)

        login_history = LoginHistory(user=user)
        db.session.add(login_history)
        db.session.commit()

        resp = make_response(redirect('/welcome'))
        resp.set_cookie('worker', worker)
        resp.set_cookie('name', name)
        return resp

    return render_template('main.html')


@app.route('/get_current_dl', methods=['GET'])
def get_current_dl():
    date = request.args.get('date')
    if not date:
        return jsonify({"status": "error", "message": "Дата обязательна"}), 400

    dl_counter = DLCounter.query.filter_by(date=date).first()
    if dl_counter:
        return jsonify({"status": "success", "current_dl": dl_counter.counter})
    else:
        return jsonify({"status": "error", "message": "Счетчик DL не инициализирован для этой даты"}), 404


@app.route('/welcome')
def welcome():
    worker = request.cookies.get('worker')
    name = request.cookies.get('name')

    if not worker or not name:
        return redirect('/')

    current_time = datetime.utcnow()
    devices = [device.to_dict(current_time=current_time) for device in Device.query.order_by(Device.name).all()]
    current_time_str = current_time.isoformat()

    # Получаем текущий префикс
    prefix = Prefix.query.first()
    dl_prefix = prefix.value if prefix else 'DL'

    return render_template('welcome.html', name=name, worker=worker, table=table, devices=devices, current_time=current_time_str, dl_prefix=dl_prefix)


@app.route('/logout')
def logout():
    resp = make_response(redirect('/'))
    resp.delete_cookie('worker')
    resp.delete_cookie('name')
    return resp

@app.route('/get_unpacked_rpa_counts', methods=['GET'])
def get_unpacked_rpa_counts():
    year = request.args.get('year')
    month = request.args.get('month')
    
    if not year or not month:
        return jsonify({"status": "error", "message": "Необходимо указать год и месяц"}), 400
    
    try:
        year = int(year)
        month = int(month)
    except ValueError:
        return jsonify({"status": "error", "message": "Год и месяц должны быть числами"}), 400

    # Формируем формат даты для фильтрации
    month_str = f"{month:02d}"
    
    # Запрашиваем количество записей RPA с пустым значением в колонке "Время упаковки" для каждого дня
    unpacked_counts = TableRow.query.filter(
        TableRow.date.like(f"{year}-{month_str}-%"),
        TableRow.arma_type == 'RPA',
        or_(TableRow.pack_time == None, TableRow.pack_time == '')
    ).group_by(TableRow.date).with_entities(TableRow.date, func.count(TableRow.id)).all()
    
    # Преобразуем результат в словарь
    unpacked_counts_dict = {date: count for date, count in unpacked_counts}
    
    return jsonify({'status': 'success', 'unpacked_counts': unpacked_counts_dict})

@app.route('/get_last_dl_number_all_time', methods=['GET'])
def get_last_dl_number_all_time():
    # Получаем последнюю запись с максимальным значением DL за всё время
    last_dl_row = TableRow.query.order_by(TableRow.dl_number.desc()).first()
    if last_dl_row:
        last_dl = last_dl_row.dl_number
        if last_dl and last_dl.startswith('DL'):
            last_dl_number = int(last_dl[2:])
            next_dl_number = last_dl_number + 1
        else:
            next_dl_number = 1
    else:
        next_dl_number = 1

    return jsonify({"status": "success", "next_dl_number": next_dl_number})


@app.route('/import_excel', methods=['POST'])
def import_excel():
    try:
        # Check if a file is provided
        if 'file' not in request.files:
            return jsonify({"status": "error", "message": "No file provided"}), 400

        # Get the uploaded file
        file = request.files['file']

        # Check the file extension for security reasons
        if not file.filename.endswith('.xlsx') and not file.filename.endswith('.xls'):
            return jsonify({"status": "error", "message": "File type not allowed"}), 400

        # Read the Excel file into a DataFrame
        df = pd.read_excel(file)

        # Define the fields expected in the Excel file
        required_columns = ['Время отправки', 'Номер', 'Serial', 'Графа', 'Имя отправителя']

        # Check if all required columns are in the DataFrame
        if not all(col in df.columns for col in required_columns):
            return jsonify({"status": "error", "message": f"Missing required columns. Required columns are: {', '.join(required_columns)}"}), 400

        # Get current date
        current_date = datetime.now().date().strftime("%Y-%m-%d")

        # Iterate over DataFrame rows and insert data into the database
        for _, row in df.iterrows():
            new_row = TableRow(
                date=current_date,
                timestamp=row['Время отправки'],
                number=row['Номер'],
                serial=row['Serial'],
                dl_number=row['Графа'],
                worker_name=row['Имя отправителя'],
                row_number='',  # Will be calculated later
                pack_time='0',  # Default value
                arma_type='0',  # Default value
                dateshipment='0', 
                aktshipment='0',   # Default value
                bad_recovery=row.get('bad_recovery', False) ,
            )
            db.session.add(new_row)
        db.session.commit()

        # Update row numbers
        rows_of_day = TableRow.query.filter_by(date=current_date).order_by(TableRow.id).all()
        for idx, row in enumerate(rows_of_day, 1):
            row.row_number = str(idx)
        db.session.commit()

        return jsonify({"status": "success", "message": "Data imported successfully"})

    except Exception as e:
        db.session.rollback()
        app.logger.error(f"Error during Excel import: {str(e)}")  # Логируем ошибку
        return jsonify({"status": "error", "message": str(e)}), 500
    
@app.route('/delete_imported_data', methods=['POST'])
def delete_imported_data():
    try:
        current_date = datetime.now().date().strftime("%Y-%m-%d")
        rows_deleted = TableRow.query.filter_by(date=current_date).delete()
        db.session.commit()

        return jsonify({"status": "success", "message": f"{rows_deleted} rows deleted for date {current_date}"})

    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500
    
@app.route('/get_rpa_records', methods=['GET'])
def get_rpa_records():
    try:
        # Замените фильтр по имени 'worker_name' на фильтр по 'arma_type'
        rows = TableRow.query.filter_by(arma_type='RPA').all()

        data = []
        for row in rows:
            data.append({
                'id': row.id,
                'row_number': row.row_number,
                'timestamp': row.timestamp,
                'number': row.number,
                'serial': row.serial,
                'dl_number': row.dl_number,
                'worker_name': row.worker_name,
                'pack_time': row.pack_time,
                'arma_type': row.arma_type,
                'dateshipment': row.dateshipment,
                'aktshipment': row.aktshipment,
                'bad_recovery': row.bad_recovery,
            })

        return jsonify({"status": "success", "data": data})

    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 500

@app.route('/export_rpa_excel')
def export_rpa_excel():
    try:
        # Начинаем построение запроса с фильтрации по типу АРМА "RPA"
        query = TableRow.query.filter_by(arma_type='RPA')

        # Дополнительные условия фильтрации: 
        # - dl_number не пустой
        # - pack_time не пустой
        query = query.filter(
            TableRow.dl_number.isnot(None),
            TableRow.dl_number != '',
            TableRow.pack_time.isnot(None),
            TableRow.pack_time != ''
        )

        rows = query.all()

        # Создаем DataFrame только с отфильтрованными данными
        data = []
        for row in rows:
            data.append({
                '№': row.row_number,
                'Время отправки': row.timestamp,
                'Номер': row.number,
                'Serial': row.serial,
                'Графа': row.dl_number,
                'Имя отправителя': row.worker_name,
                'Время упаковки': row.pack_time,
                'Тип АРМА': row.arma_type,
                'Дата отгрузки': row.dateshipment,
                'Номер акта': row.aktshipment,
                'bad_recovery': row.bad_recovery,
            })

        if not data:
            return jsonify({"error": "Нет записей для экспорта с заданными критериями."}), 400

        df = pd.DataFrame(data)

        # Создаем Excel файл
        output = BytesIO()
        with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
            df.to_excel(writer, index=False, sheet_name='Sheet1')

            # Настраиваем форматирование
            workbook = writer.book
            worksheet = writer.sheets['Sheet1']

            # Автоматическая ширина столбцов
            for idx, col in enumerate(df.columns):
                series = df[col]
                max_len = max(
                    series.astype(str).map(len).max(),
                    len(str(col))
                ) + 2
                worksheet.set_column(idx, idx, max_len)

                # Форматирование числовых столбцов
                if col in ['Номер', 'Графа']:
                    num_format = workbook.add_format({'num_format': '0'})
                    worksheet.set_column(idx, idx, max_len, num_format)

        output.seek(0)

        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name='RPA_export.xlsx'
        )

    except Exception as e:
        print(f"Export error: {str(e)}")
        return jsonify({"error": str(e)}), 500
    

@app.route('/get_duplicates')
def get_duplicates():
    try:
        # Получим все строки из таблицы
        rows = TableRow.query.all()

        # Хранилища для множества значений
        numbers = {}
        serials = {}
        dl_numbers = {}

        # Собираем все значения и их индексы
        data = []
        for row in rows:
            row_data = {
                'id': row.id,
                'row_number': row.row_number,
                'timestamp': row.timestamp,
                'number': row.number,
                'serial': row.serial,
                'dl_number': row.dl_number,
                'worker_name': row.worker_name,
                'pack_time': row.pack_time,
                'arma_type': row.arma_type,
                'dateshipment': row.dateshipment,
                'aktshipment': row.aktshipment,
                'bad_recovery': row.bad_recovery,
            }
            data.append(row_data)
            if row.number:
                clean_number = ''.join(filter(str.isdigit, row.number))
                if clean_number in numbers:
                    numbers[clean_number].append(len(data) - 1)
                else:
                    numbers[clean_number] = [len(data) - 1]

            if row.serial:
                clean_serial = ''.join(filter(str.isdigit, row.serial))
                if clean_serial in serials:
                    serials[clean_serial].append(len(data) - 1)
                else:
                    serials[clean_serial] = [len(data) - 1]

            if row.dl_number:
                clean_dl_number = ''.join(filter(str.isdigit, row.dl_number))
                if clean_dl_number in dl_numbers:
                    dl_numbers[clean_dl_number].append(len(data) - 1)
                else:
                    dl_numbers[clean_dl_number] = [len(data) - 1]

        # Фильтруем только реальные дубликаты
        duplicate_numbers = {k: v for k, v in numbers.items() if len(v) > 1}
        duplicate_serials = {k: v for k, v in serials.items() if len(v) > 1}
        duplicate_dl_numbers = {k: v for k, v in dl_numbers.items() if len(v) > 1}

        duplicates = {
            "numbers": duplicate_numbers,
            "serials": duplicate_serials,
            "dl_numbers": duplicate_dl_numbers
        }

        # Сохраняем индексы всех уникальных дубликатов
        duplicate_indices = set()
        for indices in duplicate_numbers.values():
            duplicate_indices.update(indices)
        for indices in duplicate_serials.values():
            duplicate_indices.update(indices)
        for indices in duplicate_dl_numbers.values():
            duplicate_indices.update(indices)

        # Отбираем только дубликаты
        duplicates_data = [data[idx] for idx in duplicate_indices]

        return jsonify({
            "status": "success",
            "data": duplicates_data,
            "duplicates": duplicates
        })

    except Exception as e:
        return jsonify({"status": "error", "message": str(e)})

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
        create_devices()



    logging.disable(logging.CRITICAL)
    os.system('title СЕРВЕР')
    app.run(host='192.168.50.110', port=80, debug=True)  #192.168.50.99
ASSISTANT
Ваш код на Python (Flask) для управления базой данных записей содержит модель `TableRow`, в которой есть поле `bad_recovery`, определяющее, есть ли дублирующиеся записи. Чтобы правильно устанавливать значение `bad_recovery`, вам необходимо убедиться, что оно зависит от наличия дубликатов по `number`, `serial` и `dl_number`.

Находясь в функции `get_table_data`, после того как мы определим количество дубликатов для каждого поля, мы установим значение `bad_recovery` на `True`, только если существует хотя бы один дубликат для ветки проверок:

1. Убедитесь, что вы правильно считаете количество записей для числа, серийного номера и графы (DL number).
2. Установите значение `bad_recovery` для каждой строки на основе наличия дубликатов.

Вот как можно внести соответствующие изменения в вашу функцию `get_table_data`:

```python
@app.route('/get_table_data')
def get_table_data():
    date = request.args.get('date')
    filter_arma = request.args.get('filter_arma')

    app.logger.debug(f"Получен запрос: date={date}, filter_arma={filter_arma}")

    query = TableRow.query.filter_by(date=date)

    if filter_arma == 'RPA':
        query = query.filter_by(arma_type='RPA')

    rows = query.all()
    app.logger.debug(f"Найдено записей: {len(rows)}")

    # Словари для подсчета
    number_count = {}
    serial_count = {}
    dl_number_count = {}

    # Подсчет дубликатов
    for row in rows:
        # Номер
        if row.number:
            clean_number = ''.join(filter(str.isdigit, row.number)).strip()
            if clean_number:
                number_count[clean_number] = number_count.get(clean_number, 0) + 1

        # Serial
        if row.serial:
            clean_serial = ''.join(filter(str.isdigit, row.serial)).strip()
            if clean_serial:
                serial_count[clean_serial] = serial_count.get(clean_serial, 0) + 1

        # DL Number
        if row.dl_number:
            clean_dl_number = ''.join(filter(str.isdigit, row.dl_number)).strip()
            if clean_dl_number:
                dl_number_count[clean_dl_number] = dl_number_count.get(clean_dl_number, 0) + 1

    # Установка bad_recovery 
    for row in rows:
        number_duplicate = False
        serial_duplicate = False
        dl_number_duplicate = False

        if row.number:
            clean_number = ''.join(filter(str.isdigit, row.number)).strip()
            number_duplicate = number_count.get(clean_number, 0) > 1

        if row.serial:
            clean_serial = ''.join(filter(str.isdigit, row.serial)).strip()
            serial_duplicate = serial_count.get(clean_serial, 0) > 1

        if row.dl_number:
            clean_dl_number = ''.join(filter(str.isdigit, row.dl_number)).strip()
            dl_number_duplicate = dl_number_count.get(clean_dl_number, 0) > 1

        # Устанавливаем значение bad_recovery на основе наличия дубликатов в одном из полей
        row.bad_recovery = number_duplicate or serial_duplicate or dl_number_duplicate

        # Дополнительное логирование
        app.logger.debug(f"Row ID: {row.id}, Bad Recovery: {row.bad_recovery}")

    # Сохраняем изменения
    db.session.commit()

    # Подготовка данных для ответа
    data = []
    for row in rows:
        data.append({
            'id': row.id,
            'row_number': row.row_number,
            'timestamp': row.timestamp,
            'number': row.number,
            'serial': row.serial,
            'dl_number': row.dl_number,
            'worker_name': row.worker_name,
            'pack_time': row.pack_time,
            'arma_type': row.arma_type,
            'dateshipment': row.dateshipment,
            'aktshipment': row.aktshipment,
            'bad_recovery': row.bad_recovery,
        })

    return jsonify({"data": data})
```

Изменения включают:

- Использование специальных переменных для отслеживания наличия дубликатов каждого поля.
- Проверка и установка состояния `bad_recovery` на основе значения наличия дубликатов. 

После этой переработки, если хотя бы одно из полей записи имеет дубликат, `bad_recovery` будет установлено в `True`. Если дубликатов нет, `bad_recovery` будет `False`.

turns-00054.parquet:742

511188054affb9f90cc03749
turn 2/7gpt-4o-mini-2024-07-18RussianRussia340 words
degenerate_repetitionAbsentFinal dense release
USER
почему у меня все записи true не понимаю
ASSISTANT
Если все записи в поле `bad_recovery` показывают `True`, это может указывать на несколько возможных причин. Давайте разберёмся по шагам:

1. **Подсчёт дубликатов**: Убедитесь, что ваша логика подсчёта дубликатов работает корректно. В частности, проверьте, правильно ли вы очищаете информацию в полях `number`, `serial` и `dl_number` и добавить условие, при котором вы считаете количество дубликатов только если есть фактические значения.

2. **Прогон данных**: Убедитесь, что в базе данных на самом деле нет дубликатов вводимых значений. Возможно, вы случайно заполнили таблицу идентичными данными.

3. **Логика установки `bad_recovery`**: Мы проверяем, если количество дубликатов для значения в каждом из полей больше 1. Если это условие выполняется, переменная `bad_recovery` устанавливается в `True`.

Давайте ещё раз проконтролируем участок кода, responsible for setting `bad_recovery`, и добавим дополнительное логирование:

```python
# Установка bad_recovery
for row in rows:
    number_duplicate = False
    serial_duplicate = False
    dl_number_duplicate = False

    # Проверяем номер
    if row.number:
        clean_number = ''.join(filter(str.isdigit, row.number)).strip()
        number_duplicate = number_count.get(clean_number, 0) > 1

    # Проверяем Serial
    if row.serial:
        clean_serial = ''.join(filter(str.isdigit, row.serial)).strip()
        serial_duplicate = serial_count.get(clean_serial, 0) > 1

    # Проверяем DL Number
    if row.dl_number:
        clean_dl_number = ''.join(filter(str.isdigit, row.dl_number)).strip()
        dl_number_duplicate = dl_number_count.get(clean_dl_number, 0) > 1

    # Устанавливаем значение bad_recovery на основе наличия дубликатов
    row.bad_recovery = number_duplicate or serial_duplicate or dl_number_duplicate

    # Логируем результаты
    app.logger.debug(f"Row ID: {row.id}, Number: {row.number}, Serial: {row.serial}, DL Number: {row.dl_number}")
    app.logger.debug(f"Duplicates - Number: {number_duplicate}, Serial: {serial_duplicate}, DL Number: {dl_number_duplicate}")
    app.logger.debug(f"Bad Recovery for Row ID {row.id}: {row.bad_recovery}")

# Сохраняем изменения
db.session.commit()
```

### Рекомендации по отладке:

1. **Логирование**: Внимательно просматривайте логи, чтобы удостовериться, какие значения проверяются для `number`, `serial` и `dl_number`, и какое количество дубликатов устанавливается.
  
2. **Проверка данных в БД**: Используйте инструмент для работы с SQL, чтобы вручную проверить, есть ли дубликаты в значениях, которые должны проверяться. Запустите соответствующие SQL запросы для поиска дубликатов по вашим критериям.

3. **Использование встроенных инструментов для предоставления данных**: Попробуйте добавить отладочную логику, чтобы просмотреть в какой момент и какие данные выходят в `bad_recovery`.

Если после всех проверок проблема остаётся, вы можете протестировать с простым набором тестовых данных, чтобы убедиться, что логика работает так, как ожидается.

turns-00054.parquet:743

dd88c2605cf15ac17ed68c4b
turn 3/7gpt-4o-mini-2024-07-18RussianRussia477 words
degenerate_repetitionAbsentFinal dense release
USER
что здесь происходит?
ASSISTANT
Давайте разберем ключевые моменты кода, который вы предоставили, и поймем, что именно происходит на каждом этапе.

### 1. Подсчет дубликатов

Сначала происходит подсчет дубликатов по каждому из полей: `number`, `serial`, и `dl_number`:

```python
for row in rows:
    # Номер
    if row.number:
        clean_number = ''.join(filter(str.isdigit, row.number)).strip()
        if clean_number:
            number_count[clean_number] = number_count.get(clean_number, 0) + 1

    # Serial
    if row.serial:
        clean_serial = ''.join(filter(str.isdigit, row.serial)).strip()
        if clean_serial:
            serial_count[clean_serial] = serial_count.get(clean_serial, 0) + 1

    # DL Number
    if row.dl_number:
        clean_dl_number = ''.join(filter(str.isdigit, row.dl_number)).strip()
        if clean_dl_number:
            dl_number_count[clean_dl_number] = dl_number_count.get(clean_dl_number, 0) + 1
```

**Что здесь происходит?**
- Для каждой строки данных (переменная `row`) происходит проверка на наличие значений в полях `number`, `serial` и `dl_number`.
- Если значение существует, оно очищается от нецифровых символов с помощью `filter` и `str.isdigit`.
- Затем происходит подсчет количества вхождений каждого уникального значения в словарях `number_count`, `serial_count`, и `dl_number_count`.


### 2. Установка флага `bad_recovery`

Затем мы переходим к установке значения флага `bad_recovery` для каждой строки:

```python
for row in rows:
    number_duplicate = False
    serial_duplicate = False
    dl_number_duplicate = False

    if row.number:
        clean_number = ''.join(filter(str.isdigit, row.number)).strip()
        number_duplicate = number_count.get(clean_number, 0) > 1

    if row.serial:
        clean_serial = ''.join(filter(str.isdigit, row.serial)).strip()
        serial_duplicate = serial_count.get(clean_serial, 0) > 1

    if row.dl_number:
        clean_dl_number = ''.join(filter(str.isdigit, row.dl_number)).strip()
        dl_number_duplicate = dl_number_count.get(clean_dl_number, 0) > 1

    # Устанавливаем значение bad_recovery на основе наличия дубликатов
    row.bad_recovery = number_duplicate or serial_duplicate or dl_number_duplicate
```

**Что здесь происходит?**
- В начале каждой итерации создаются переменные, которые фиксируют наличие дубликатов (`number_duplicate`, `serial_duplicate`, и `dl_number_duplicate`).
- Если значение поля существует, оно снова очищается от нецифровых символов.
- Затем для каждого из полей проверяется, есть ли его количество в словаре более 1. Если да, переменная дубликата устанавливается в `True`.
- Наконец, поле `bad_recovery` устанавливается в `True`, если хотя бы одно из полей (`number`, `serial`, или `dl_number`) имеет дубликаты.

### 3. Проблемы, которые могут возникнуть

Если все записи в `bad_recovery` становятся `True`, это может происходить по следующим причинам:

1. **Все значения одинаковые**: Если в вашей базе данных действительно присутствуют записи с одинаковыми значениями во всех полях, например, для `number`, `serial`, и `dl_number`, то проверка будет возвращать `True` для всех записей.

2. **Ошибка в очищении значений**: Если по какой-то причине значения в полях `number`, `serial`, или `dl_number` очищаются неправильно, то это может привести к тому, что записи будут выглядеть идентично, что также будет показывать наличие дубликатов.

3. **Логика подсчета количества дубликатов**: Убедитесь, что подсчет осуществляется правильно на всех записях и что вы проверяете каждое поле на наличие дубликатов в контексте всей базы данных.

4. **Тестовые данные**: Проверьте вашу базу данных на наличие тестовых наборов данных для поиска возможных дубликатов.

### Рекомендации по отладке

- Логирование значений: Добавьте больше логов, чтобы увидеть, какие значения проходят через динамические проверки, и фиксируйте результат подсчета дубликатов перед установкой `bad_recovery`.

- Фильтрация данных: Проверьте, нет ли несоответствий в фильтрации данных. Возможно, поля не очищаются, как предполагалось, или обрабатываются неправильно.

Проверив эти моменты, вы сможете определить причину проблемы и устранить ее.

turns-00054.parquet:744

8818bd23cd4b75509a3f0890
turn 4/7gpt-4o-mini-2024-07-18RussianRussia644 words
degenerate_repetitionAbsentFinal dense release
USER
C:\Users\Lenovo\Desktop\myprojectV11\main.py:971: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
  current_time = datetime.utcnow()
C:\Users\Lenovo\Desktop\myprojectV11\main.py:163: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
  now = datetime.utcnow()
Traceback (most recent call last):
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1536, in __call__
    return self.wsgi_app(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1514, in wsgi_app
    response = self.handle_exception(e)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1511, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Lenovo\Desktop\myprojectV11\main.py", line 389, in get_table_data
    app.logger.debug(f"Row ID: {row.id}, Number: {row.number}, Serial: {row.serial}, DL Number: {row.dl_number}")
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnboundLocalError: cannot access local variable 'row' where it is not associated with a value
Traceback (most recent call last):
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1536, in __call__
    return self.wsgi_app(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1514, in wsgi_app
    response = self.handle_exception(e)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1511, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Lenovo\Desktop\myprojectV11\main.py", line 389, in get_table_data
    app.logger.debug(f"Row ID: {row.id}, Number: {row.number}, Serial: {row.serial}, DL Number: {row.dl_number}")
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnboundLocalError: cannot access local variable 'row' where it is not associated with a value
ASSISTANT
Ошибка `UnboundLocalError: cannot access local variable 'row' where it is not associated with a value` указывает на то, что в вашем коде вы пытаетесь получить доступ к переменной `row`, которая не была правильно инициализирована или определена в том контексте, где вы ее используете.

### Разбор ошибок

1. **DeprecationWarning**:
   - **Причина**: `datetime.datetime.utcnow()` считается устаревшим (deprecated) в новых версиях Python. Вместо нее рекомендуется использовать `datetime.datetime.now(datetime.timezone.utc)`.
   - **Решение**:
     Измените все случаи `datetime.utcnow()` на `datetime.now(datetime.timezone.utc)`. Например:
     ```python
     now = datetime.now(datetime.timezone.utc)
     current_time = datetime.now(datetime.timezone.utc)
     ```

2. **UnboundLocalError**:
   - **Причина**: Эта ошибка может возникнуть, если вы пытаетесь использовать `row` вне его контекста. Это обычно происходит, если код внутри цикла не выполняется, либо если нет данных для обработки.
   - **Решение**: Убедитесь, что переменная `row` правильно определена в пределах области видимости. Например, если вы обращаетесь к переменной в блоке кода, где она не была объявлена, это вызовет эту ошибку.

### Как исправить UnboundLocalError

1. **Проверьте, что `rows` не пустой**: Перед тем как проходить по `rows`, добавьте проверку на наличие записей.

```python
rows = query.all()
if not rows:
    app.logger.debug("Нет записей для обработки.")
    return jsonify({"data": []})  # Или любое другое значение, подходящее для вашего ответа
```

2. **Отладочные сообщения**: Добавьте отладочные сообщения перед использованием переменной `row`, чтобы удостовериться, что она доступна.

3. **Правильный контекст цикла**: Убедитесь, что все операции с переменной `row` происходят внутри коррелирующего цикла (например, внутри `for`).

### Полный пример, учитывающий исправление

Вот как может выглядеть фрагмент кода с учетом всех предложенных изменений:

```python
@app.route('/get_table_data')
def get_table_data():
    date = request.args.get('date')
    filter_arma = request.args.get('filter_arma')

    app.logger.debug(f"Получен запрос: date={date}, filter_arma={filter_arma}")

    query = TableRow.query.filter_by(date=date)
    if filter_arma == 'RPA':
        query = query.filter_by(arma_type='RPA')

    # Получаем все строки, связанные с указанной датой
    rows = query.all()
    if not rows:
        app.logger.debug("Нет записей для обработки.")
        return jsonify({"data": []})  # Можно вернуть пустой массив как ответ

    app.logger.debug(f"Найдено записей: {len(rows)}")

    # ... ваш код для подсчета дубликатов ...

    # Установка значений для bad_recovery
    for row in rows:
        # Ваш код для проверки дубликатов и установки row.bad_recovery
        app.logger.debug(f"Row ID: {row.id}, Number: {row.number}, Serial: {row.serial}, DL Number: {row.dl_number}")

    # Сохраняем изменения и подготовим данные для ответа
    db.session.commit()
    data = [{'id': row.id, 'number': row.number, 'serial': row.serial, 'bad_recovery': row.bad_recovery} for row in rows]

    return jsonify({"data": data})
```

### Заключение

После внесения изменений, включая переход на использование `datetime.now(datetime.timezone.utc)`, ваша программа должна работать без предупреждений и ошибок. Убедитесь также, что данные в вашем запросе обрабатываются корректно для предотвращения возникновения ситуаций с `UnboundLocalError`.

turns-00054.parquet:745

dc7fd9d1c7156c54abcb033f
turn 5/7gpt-4o-mini-2024-07-18RussianRussia592 words
degenerate_repetitionAbsentFinal dense release
USER
C:\Users\Lenovo\Desktop\myprojectV11\main.py:163: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
  now = datetime.utcnow()
C:\Users\Lenovo\Desktop\myprojectV11\main.py:975: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
  current_time = datetime.utcnow()
Traceback (most recent call last):
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1536, in __call__
    return self.wsgi_app(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1514, in wsgi_app
    response = self.handle_exception(e)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1511, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Lenovo\Desktop\myprojectV11\main.py", line 389, in get_table_data
    app.logger.debug(f"Row ID: {row.id}, Number: {row.number}, Serial: {row.serial}, DL Number: {row.dl_number}")
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnboundLocalError: cannot access local variable 'row' where it is not associated with a value
Traceback (most recent call last):
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1536, in __call__
    return self.wsgi_app(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1514, in wsgi_app
    response = self.handle_exception(e)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 1511, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Program Files\Python312\Lib\site-packages\flask\app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\Lenovo\Desktop\myprojectV11\main.py", line 389, in get_table_data
    app.logger.debug(f"Row ID: {row.id}, Number: {row.number}, Serial: {row.serial}, DL Number: {row.dl_number}")
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnboundLocalError: cannot access local variable 'row' where it is not associated with a value
ASSISTANT
Ваши ошибки указывают на две основные проблемы в коде:

1. **DeprecationWarning** (предупреждение о устаревании): использование функции `datetime.utcnow()` считается устаревшим, и, как упоминалось ранее, следует заменить его на `datetime.now(datetime.timezone.utc)`.

2. **UnboundLocalError**: ошибка "cannot access local variable 'row' where it is not associated with a value" возникает из-за того, что переменная `row` пытается быть использована вне области видимости, где она была объявлена или инициализирована.

### Реализация исправлений

#### 1. Измените все вызовы `datetime.utcnow()`

Замените все вызовы `datetime.utcnow()` на `datetime.now(datetime.timezone.utc)`. Например:

```python
from datetime import datetime

# Пример замены
now = datetime.now(datetime.timezone.utc)
current_time = datetime.now(datetime.timezone.utc)
```

Убедитесь, что исправления внесены во все места вашего кода, где используется `datetime.utcnow()`.

#### 2. Проверка на наличие записей

Чтобы избежать `UnboundLocalError`, перед тем как использовать переменную `row`, убедитесь, что вы заранее проверили, что у вас есть записи для обработки. Сначала убедитесь, что вы правильно получаете данные:

```python
@app.route('/get_table_data')
def get_table_data():
    date = request.args.get('date')
    filter_arma = request.args.get('filter_arma')

    app.logger.debug(f"Получен запрос: date={date}, filter_arma={filter_arma}")

    query = TableRow.query.filter_by(date=date)
    if filter_arma == 'RPA':
        query = query.filter_by(arma_type='RPA')

    # Получаем все строки, связанные с указанной датой
    rows = query.all()
    if not rows:
        app.logger.debug("Нет записей для обработки.")
        return jsonify({"data": []})  # Возвращаем пустой массив, если нет записей

    app.logger.debug(f"Найдено записей: {len(rows)}")

    # Ваш код для подсчета дубликатов
    for row in rows:
        # Установка значения для bad_recovery и другие вычисления
        app.logger.debug(f"Row ID: {row.id}, Number: {row.number}, Serial: {row.serial}, DL Number: {row.dl_number}")

    # Сохраняем изменения и подготавливаем данные для ответа
    db.session.commit()
    data = [{'id': row.id, 'number': row.number, 'serial': row.serial, 'bad_recovery': row.bad_recovery} for row in rows]

    return jsonify({"data": data})
```

### Обратите внимание на следующее:

- **Проверка на пустой результат**: Если `rows` пуст, уведомляем об этом в логах и возвращаем пустой ответ. Это предотвращает дальнейшую обработку и обращение к переменной `row`, если данных нет.
  
- **Использование переменной `row` в правильном контексте**: Убедитесь, что код, который использует переменную `row`, находится внутри цикла, который ее инициализирует.

### Итог

После внесения указанных изменений ваше приложение не должно больше вызывать `DeprecationWarning` и `UnboundLocalError`. Убедитесь, что вы провели полные проверки кода и протестировали его, чтобы подтвердить, что все работает корректно.

turns-00054.parquet:746

53c0fb82301462474eee23d1
turn 6/7gpt-4o-mini-2024-07-18RussianRussia432 words
degenerate_repetitionAbsentFinal dense release
USER
Найдено записей: 0
Найдено записей: 0
Найдено записей: 0
ASSISTANT
Если ваше приложение показывает `Найдено записей: 0` для всех запросов, существует несколько возможных причин. Давайте рассмотрим, как диагностировать и решить эту проблему.

### Возможные причины и решения

1. **Неправильный ввод параметров**:
   - Убедитесь, что `date` и `filter_arma` передаются в запросе правильно. Например, если значение `date` не совпадает с форматом, который используется в базе данных, запрос не вернёт записей. Используйте формат даты, который соответствует формату в базе, например: `YYYY-MM-DD`.

2. **Проверка данных в базе данных**:
   - Убедитесь, что записи действительно есть в базе данных для заданной даты. Вы можете сделать выборку всех записей из таблицы `TableRow` и посмотреть, есть ли они.
     ```python
     rows = TableRow.query.all()
     for row in rows:
         print(row)
     ```

3. **Проблемы с фильтрацией`filter_arma`**:
   - Убедитесь, что критерий фильтрации `arma_type` соответствует доступным записям. Это можно проверить с помощью запроса, который извлекает все уникальные значения `arma_type`.
     ```python
     unique_arma_types = db.session.query(TableRow.arma_type).distinct().all()
     print(unique_arma_types)
     ```

4. **Инициализация базы данных**:
   - Если вы только что создали базу данных, возможно, данные не были инициализированы. Убедитесь, что у вас есть записи в вашей базе.
   - Проверьте, где вы выполняете вставку данных (например, в `update_table`, `update_serial_arma`, и других маршрутах). Вы можете временно добавить некоторые операторы для вставки тестовых данных в базу данных, чтобы проверить свою логику:

   ```python
   # Пример добавления тестовой записи (возможно, в отдельном маршруте)
   @app.route('/add_test_record', methods=['POST'])
   def add_test_record():
       new_row = TableRow(
           date="2023-10-01",   # Убедитесь, что эта дата соответствует вашему формату
           number="12345",
           serial="A1B2C3",
           dl_number="DL123456",
           worker_name="Test Worker",
           row_number="1",
           pack_time="2023-10-01 10:00:00",
           arma_type="RPA",
           dateshipment="2023-10-02",
           aktshipment="Акт 1",
           bad_recovery=False
       )
       db.session.add(new_row)
       db.session.commit()
       return jsonify({"status": "success", "message": "Test record added"})
   ```

5. **Отладка запросов**:
   - Добавьте дополнительные журналы (логирование) перед выполнением вашего запроса, чтобы проверить, какие значения были переданы в фильтры:
   ```python
   app.logger.debug(f"Фильтрация по дате: {date}, фильтр ARMA: {filter_arma}")
   ```

6. **Проблемы с транзакциями**:
   - Если ваша база данных использует транзакции и вы ожидаете увидеть записи, убедитесь, что все транзакции были зафиксированы (`committed`). Проверьте, что у вас нет незавершённых транзакций в коде.

### Проверка всей логики запроса

Пример финальной логики проверки всех упомянутых моментов:

```python
@app.route('/get_table_data')
def get_table_data():
    date = request.args.get('date')
    filter_arma = request.args.get('filter_arma')

    app.logger.debug(f"Получен запрос: date={date}, filter_arma={filter_arma}")

    # Проверяем на правильность параметров
    if not date:
        return jsonify({"status": "error", "message": "Не указана дата"}), 400

    query = TableRow.query.filter_by(date=date)
    
    if filter_arma == 'RPA':
        query = query.filter_by(arma_type='RPA')

    rows = query.all()
    app.logger.debug(f"Найдено записей: {len(rows)}")

    if not rows:
        app.logger.debug("Нет записей для обработки.")
        return jsonify({"data": []})

    # Дальше ваша логика подсчета дубликатов и т.д.
    ...
```

### Заключение

Если вы выполнили все указанные действия, но все еще видите, что записи возвращаются как 0, пожалуйста, проверьте структуру вашей базы данных и хранимые данные. Это поможет вам диагностировать и найти, где может быть проблема.

turns-00054.parquet:747

4601480905e138df3d0e1583
turn 7/7gpt-4o-mini-2024-07-18RussianRussia8950 words
degenerate_repetitionAbsentFinal dense release
USER
Сделай чтобы отобразилось общее количество RPA значение
<div id="rpa-total-container" style="display: none;">
                    <div class="stat-box">
                        <span class="stat-label">Общее количество RPA:</span>
                        <span id="total-rpa-records" class="stat-value">0</span>
                </div>
from flask import Flask, render_template, request, redirect, make_response, jsonify, send_file, Response
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.sql import func
from datetime import datetime, timedelta, date, timezone
import pandas as pd
from io import BytesIO
import json
import queue
import logging
from sqlalchemy import cast, Integer, or_
import os
from collections import deque
import random



app = Flask(__name__)



app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///table_data.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

ACTIVITY_HISTORY_LENGTH = 400  # Количество последних значений для графика
activity_data = {}

class TableRow(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), nullable=False)
    row_number = db.Column(db.String(10))
    timestamp = db.Column(db.String(50))
    number = db.Column(db.String(50))
    serial = db.Column(db.String(50))
    dl_number = db.Column(db.String(50))
    worker_name = db.Column(db.String(100))
    pack_time = db.Column(db.String(50))
    arma_type = db.Column(db.String(50))
    dateshipment = db.Column(db.String(50))
    aktshipment = db.Column(db.String(50))
    bad_recovery = db.Column(db.Boolean, default=False)


class DLCounter(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), unique=True, nullable=False)
    counter = db.Column(db.Integer, nullable=False)

    def __repr__(self):
        return f'<DLCounter {self.date}: {self.counter}>'


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    login_history = db.relationship('LoginHistory', backref='user', lazy=True)


class LoginHistory(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    login_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

class Statistics(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), unique=True, nullable=False)
    generate_clicks = db.Column(db.Integer, default=0)
    pack_clicks = db.Column(db.Integer, default=0)

    def __repr__(self):
        return f'<Statistics {self.date}: Generate={self.generate_clicks}, Pack={self.pack_clicks}>'

class Prefix(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    value = db.Column(db.String(10), nullable=False, unique=True)

# Добавьте это в раздел моделей вашего Flask-приложения

class Device(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(10), unique=True, nullable=False)  # Имена: ARM1, ARM2 и т.д.
    arma_type = db.Column(db.String(10), nullable=False, default='Other')  # Тип АРМА
    last_active = db.Column(db.DateTime, nullable=True)  # Время последней активности
    internet_up = db.Column(db.Boolean, nullable=False, default=True)  # Статус интернет-соединения
    copying = db.Column(db.Boolean, nullable=False, default=False)  # Статус копирования
    previous_copying = db.Column(db.Boolean, nullable=False, default=False)  # Новое поле
    sending_number = db.Column(db.Boolean, nullable=False, default=False)  # Статус отправки номера
    processing = db.Column(db.Boolean, nullable=False, default=False)  # Новое поле для активности activationGlaz.py

    def to_dict(self, current_time=None):
        if current_time is None:
            current_time = datetime.utcnow()
        if self.copying:
            status = 'copying'
        elif self.sending_number:
            status = 'sending_number'
        else:
            status = 'offline'
            if self.last_active:
                delta = current_time - self.last_active
                if delta <= timedelta(seconds=60):
                    if self.internet_up:
                        status = 'online'
                    else:
                        status = 'no_internet'
        return {
            "id": self.id,
            "name": self.name,
            "arma_type": self.arma_type,
            "last_active": self.last_active.isoformat() if self.last_active else None,
            "internet_up": self.internet_up,
            "copying": self.copying,
            "sending_number": self.sending_number,
            "status": status
        }
    
    def get_status(self, current_time=None):
        if current_time is None:
            current_time = datetime.utcnow()

        if self.copying:
            return 'copying'
        elif self.sending_number:
            return 'sending_number'
        elif self.name == 'ARM3':
            # Для ARM3 игнорируем статус 'processing'
            if self.last_active:
                delta = current_time - self.last_active
                threshold = timedelta(seconds=5)
                if delta <= threshold:
                    if self.internet_up:
                        return 'online'
                    else:
                        return 'no_internet'
            return 'offline'
        elif self.processing:
            return 'processing'
        else:
            if self.last_active:
                delta = current_time - self.last_active
                threshold = timedelta(seconds=5)
                if delta <= threshold:
                    if self.internet_up:
                        return 'online'
                    else:
                        return 'no_internet'
            return 'offline'




tables = {}
table = [['№', 'Время отправки', 'Номер ', 'Serial', 'Графа', 'Имя отправителя','Время упаковки' ,'Тип АРМА',  'Дата отгрузки',  'Номер Акта',  'Действие']]
# Создаем очередь для событий
update_queue = queue.Queue()
previous_processing_states = {}

@app.route('/get_device_status', methods=['GET'])
def get_device_status():
    devices = Device.query.all()
    device_statuses = {}
    threshold = timedelta(seconds=5)  # Reduced time threshold for quick offline detection
    now = datetime.utcnow()

    for device in devices:
        name = device.name

        # Retrieve the previous processing state
        prev_processing = previous_processing_states.get(name, False)

        # Check if last_active is within the threshold
        is_recent = device.last_active and now - device.last_active <= threshold

        if device.sending_number:
            status = 'sending_number'
            activity_value = random.choice([2, 3, 2, 3, 4, 3, 2])
        elif device.copying:
            status = 'copying'
            activity_value = random.choice([0, 1, 0, 1, 2, 1, 0])
        elif is_recent:
            if device.internet_up:
                status = 'online'
                activity_value = 1
            else:
                status = 'no_internet'
                activity_value = 0

            # Handle processing status
            if device.processing:
                if device.name == 'ARM3':
                    if not prev_processing:
                        # Processing started on ARM3, send a single impulse
                        activity_value = 5  # Single impulse
                        status = 'online'  # Keep status as online for ARM3
                else:
                    status = 'processing'
                    activity_value = random.choice([2, 3, 2, 3, 4, 3, 2])
        else:
            status = 'offline'
            activity_value = 0

        device_statuses[name] = {
            'status': status,
            'processing': device.processing
        }
        activity_data[name].append(activity_value)
        logging.debug(f"Device {name}: status {status}, activity {activity_value}")

        # Update previous_processing_states
        previous_processing_states[name] = device.processing

    # Convert activity_data to serializable format
    activity_data_serializable = {name: list(data) for name, data in activity_data.items()}

    return jsonify({
        'status': 'success',
        'devices': device_statuses,
        'activity': activity_data_serializable
    })



def initialize_prefix():
    prefix = Prefix.query.first()
    if prefix:
        app.config['DL_PREFIX'] = prefix.value
    else:
        app.config['DL_PREFIX'] = 'DL'  # Устанавливаем значение по умолчанию, если префикс не найден


@app.route('/set_dl_prefix', methods=['POST'])
def set_dl_prefix():
    data = request.json
    new_prefix = data.get('prefix')

    if not new_prefix:
        return jsonify({"status": "error", "message": "Prefix is required"}), 400

    prefix = Prefix.query.first()
    if prefix:
        prefix.value = new_prefix
    else:
        prefix = Prefix(value=new_prefix)
        db.session.add(prefix)

    db.session.commit()
    app.config['DL_PREFIX'] = new_prefix
    return jsonify({"status": "success", "new_prefix": new_prefix})


def initialize_activity_data():
    device_names = ['ARM1', 'ARM2', 'ARM3', 'ARM4', 'ARM5', 'ARM6']
    for name in device_names:
        activity_data[name] = deque([0]*ACTIVITY_HISTORY_LENGTH, maxlen=ACTIVITY_HISTORY_LENGTH)

def create_devices():
    device_types = {
        'ARM1': 'CFA',
        'ARM2': 'VND',
        'ARM3': 'SRC',
        'ARM4': 'SRC',
        'ARM5': 'RPA',
        'ARM6': 'RPA'  # Убедитесь, что ARM6 тоже имеет тип 'RPA'
    }
    for name, arma_type in device_types.items():
        device = Device.query.filter_by(name=name).first()
        if not device:
            device = Device(name=name, arma_type=arma_type, sending_number=False)
            db.session.add(device)
    db.session.commit()
    initialize_activity_data()

@app.route('/register_user', methods=['POST'])
def register_user():
    data = request.json
    worker_name = data.get('worker_name')
    internet_up = data.get('internet_up', False)
    copying = data.get('copying', False)
    sending_number = data.get('sending_number', False)
    processing = data.get('processing', False)  # Новое поле

    if not worker_name:
        return jsonify({"status": "error", "message": "worker_name is required"}), 400

    device = Device.query.filter_by(name=worker_name).first()
    if not device:
        return jsonify({"status": "error", "message": "Device not found"}), 404

    device.last_active = datetime.utcnow()
    device.internet_up = internet_up
    device.copying = copying
    device.sending_number = sending_number
    device.processing = processing  # Сохраняем новое поле
    db.session.commit()

    return jsonify({"status": "success"})

@app.route('/get_unpacked_days', methods=['GET'])
def get_unpacked_days():
    year = request.args.get('year')
    month = request.args.get('month')
    
    if not year or not month:
        return jsonify({"status": "error", "message": "Необходимо указать год и месяц"}), 400
    
    try:
        year = int(year)
        month = int(month)
    except ValueError:
        return jsonify({"status": "error", "message": "Год и месяц должны быть числами"}), 400

    # Формируем формат даты для фильтрации
    month_str = f"{month:02d}"
    
    # Запрашиваем уникальные дни, где pack_time отсутствует и arma_type равен 'RPA'
    unpacked_dates = TableRow.query.filter(
        TableRow.date.like(f"{year}-{month_str}-%"),
        TableRow.arma_type == 'RPA',
        or_(TableRow.pack_time == None, TableRow.pack_time == '')
    ).with_entities(TableRow.date).distinct().all()
    
    # Извлекаем дни из дат
    unpacked_days = set()
    for date_str, in unpacked_dates:
        try:
            day = int(date_str.split('-')[2])
            unpacked_days.add(day)
        except (IndexError, ValueError):
            continue  # Пропускаем некорректные форматы дат

    return jsonify({'status': 'success', 'unpacked_days': list(unpacked_days)})

@app.route('/stream')
def stream():
    def event_stream():
        while True:
            try:
                data = update_queue.get(timeout=20)
                yield f'data: {data}\n\n'
            except queue.Empty:
                yield ': keep-alive\n\n'
    return Response(event_stream(), mimetype='text/event-stream')


@app.route('/get_table_data')
def get_table_data():
    date = request.args.get('date')
    filter_arma = request.args.get('filter_arma')
   
    app.logger.debug(f"Получен запрос: date={date}, filter_arma={filter_arma}")

    query = TableRow.query.filter_by(date=date)

    if filter_arma == 'RPA':
        query = query.filter_by(arma_type='RPA')

    rows = query.all()
    print(f"Найдено записей: {len(rows)}")
    
    # Словари для подсчета
    number_count = {}
    serial_count = {}
    dl_number_count = {}

    # Подсчет количества вхождений для каждого значения
    for row in rows:
        if row.number:
            clean_number = ''.join(filter(str.isdigit, row.number)).strip()
            number_count[clean_number] = number_count.get(clean_number, 0) + 1

        if row.serial:
            clean_serial = ''.join(filter(str.isdigit, row.serial)).strip()
            serial_count[clean_serial] = serial_count.get(clean_serial, 0) + 1

        if row.dl_number:
            clean_dl_number = ''.join(filter(str.isdigit, row.dl_number)).strip()
            dl_number_count[clean_dl_number] = dl_number_count.get(clean_dl_number, 0) + 1

    # Логирование подсчитанных значений
    app.logger.debug(f"Number count: {number_count}")
    app.logger.debug(f"Serial count: {serial_count}")
    app.logger.debug(f"DL number count: {dl_number_count}")

    # Установка bad_recovery
    for row in rows:
        number_duplicate = False
        serial_duplicate = False
        dl_number_duplicate = False

        if row.number:
            clean_number = ''.join(filter(str.isdigit, row.number)).strip()
            number_duplicate = number_count.get(clean_number, 1) > 1

        if row.serial:
            clean_serial = ''.join(filter(str.isdigit, row.serial)).strip()
            serial_duplicate = serial_count.get(clean_serial, 1) > 1

        if row.dl_number:
            clean_dl_number = ''.join(filter(str.isdigit, row.dl_number)).strip()
            dl_number_duplicate = dl_number_count.get(clean_dl_number, 1) > 1

        # Устанавливаем значение bad_recovery на основе наличия дубликатов
        row.bad_recovery = number_duplicate or serial_duplicate or dl_number_duplicate

        # Логируем результаты
        app.logger.debug(f"Row ID: {row.id}, Number: {row.number}, Serial: {row.serial}, DL Number: {row.dl_number}")
        app.logger.debug(f"Duplicates - Number: {number_duplicate}, Serial: {serial_duplicate}, DL Number: {dl_number_duplicate}")
        app.logger.debug(f"Bad Recovery for Row ID {row.id}: {row.bad_recovery}")

    if not rows:
        app.logger.debug("Нет записей для обработки.")
        return jsonify({"data": []})  # Или любое другое значение, подходящее для вашего ответа

    # Сохраняем изменения
    db.session.commit()

    # Подготовка данных для ответа
    data = []
    for row in rows:
        data.append({
            'id': row.id,
            'row_number': row.row_number,
            'timestamp': row.timestamp,
            'number': row.number,
            'serial': row.serial,
            'dl_number': row.dl_number,
            'worker_name': row.worker_name,
            'pack_time': row.pack_time,
            'arma_type': row.arma_type,
            'dateshipment': row.dateshipment,
            'aktshipment': row.aktshipment,
            'bad_recovery': row.bad_recovery,
        })

    return jsonify({"data": data})



@app.route('/update_table', methods=['POST'])
def update_table():
    data = request.json
    print(data)
    date = data['date']
    worker_name = data['worker_name']
    arma_type = data['arma_type']

    # Получаем количество существующих записей для этой даты
    row_count = TableRow.query.filter_by(date=date).count() + 1

    # Создаём новую строку в таблице
    if not data.get('number') and not data.get('serial'):
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number='',  # Пустое значение для номера
            serial='',  # Пустое значение для серийного номера
            dl_number='',  # Пустое значение для графы
            worker_name=worker_name,
            arma_type=arma_type,
            dateshipment='',
            aktshipment='',
            bad_recovery=data.get('bad_recovery', False)
        )
    elif not data.get('number'):
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number='',  # Пустое значение для номера
            serial=data['serial'],
            dl_number='',  # Пустое значение для графы
            worker_name=worker_name,
            arma_type=arma_type,
            dateshipment='',
            aktshipment='',
            bad_recovery=data.get('bad_recovery', False)
        )
    else:
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number=data['number'],
            serial=data['serial'],
            dl_number=data.get('dl_number', ''),
            worker_name=worker_name,
            arma_type=arma_type,
            dateshipment=data.get('dateshipment', ' '),
            aktshipment=data.get('aktshipment', ' '),
            bad_recovery=data.get('bad_recovery', False)
        )

    # Сохраняем запись в базе данных
    db.session.add(new_row)

    device = Device.query.filter_by(name=worker_name).first()
    if device:
        device.last_active = datetime.utcnow()
    db.session.commit()

    # Отправляем сообщение в очередь для обновления клиентов
    update_queue.put(json.dumps({"type": "update", "date": date}))

    # Добавляем импульсивное значение для sparkline, если запись отправлена ARM5 или ARM6
    if worker_name in ['ARM5', 'ARM6']:
        activity_data[worker_name].append(5)  # Значение импульса (можно настроить по необходимости)

    return jsonify({"status": "success"})


@app.route('/update_graf', methods=['POST'])
def update_graf():
    try:
        data = request.json
        row_id = int(data.get('id'))
        new_value = data.get('new_value')
        date = data.get('date')

        if not row_id or not date:
            return jsonify({"status": "error", "message": "Missing 'id' or 'date' in request"}), 400

        # Находим запись по id и дате
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.dl_number = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        else:
            return jsonify({"status": "error", "message": "Row not found"}), 404
    except (ValueError, TypeError) as e:
        return jsonify({"status": "error", "message": f"Invalid input: {str(e)}"}), 400
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500


@app.route('/update_arma_type', methods=['POST'])
def update_arma_type():
    try:
        data = request.json
        date = data['date']
        serial = data['serial']
        arma_type = data['arma_type']

        row = TableRow.query.filter_by(date=date, serial=serial).first()
        if row:
            row.arma_type = arma_type
            db.session.commit()
            return jsonify({"status": "success"})
        else:
            return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)})


@app.route('/update_number', methods=['POST'])
def update_number():
    try:
        data = request.json
        row_id = int(data['id'])  # Используем 'id' вместо 'row_index'
        new_value = data['new_value']
        date = data['date']

        # Находим запись по id и дате
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.number = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)})

@app.route('/pack_item', methods=['POST'])
def pack_item():
    try:
        data = request.json
        row_id = data['id']
        date = data['date']

        rows = TableRow.query.filter_by(id=row_id, date=date).all()
        if rows:
            row = rows[0]
            pack_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            row.pack_time = pack_time
            db.session.commit()
            
            # Обновляем статистику упаковки
            statistics = Statistics.query.filter_by(date=date).first()
            if statistics:
                statistics.pack_clicks += 1
            else:
                statistics = Statistics(date=date, generate_clicks=0, pack_clicks=1)  # pack_clicks=1
                db.session.add(statistics)
            db.session.commit()
            
            # Отправляем сообщение в очередь для обновления клиентов
            update_queue.put(json.dumps({"type": "update", "date": date}))
            
            return jsonify({"status": "success", "pack_time": pack_time})

        return jsonify({"status": "error", "message": "Строка не найдена"}), 404
    except Exception as e:
        print(f"Ошибка в pack_item: {str(e)}")
        return jsonify({"status": "error", "message": str(e)}), 500



@app.route('/set_initial_dl', methods=['POST'])
def set_initial_dl():
    try:
        data = request.json
        if not data:
            return jsonify({"status": "error", "message": "No data received"})

        date = data.get('date')
        initial_number = data.get('initial_number')

        if not date or not initial_number:
            return jsonify({
                "status": "error", 
                "message": "Missing required fields",
                "received": {"date": date, "initial_number": initial_number}
            })

        try:
            initial_number = int(initial_number)
        except ValueError:
            return jsonify({
                "status": "error",
                "message": "Initial number must be an integer"
            })

        print(f"Setting initial DL for date {date}: {initial_number}")  # Отладочный вывод

        dl_counter = DLCounter.query.filter_by(date=date).first()
        if dl_counter:
            dl_counter.counter = initial_number
            print(f"Updated existing counter for {date}")  # Отладочный вывод
        else:
            dl_counter = DLCounter(date=date, counter=initial_number)
            db.session.add(dl_counter)
            print(f"Created new counter for {date}")  # Отладочный вывод

        db.session.commit()
        return jsonify({
            "status": "success",
            "message": f"DL counter set to {initial_number} for date {date}"
        })

    except Exception as e:
        print(f"Error in set_initial_dl: {str(e)}")  # Отладочный вывод
        db.session.rollback()
        return jsonify({
            "status": "error",
            "message": str(e)
        })


@app.route('/generate_dl', methods=['POST'])
def generate_dl():
    try:
        data = request.json
        row_id = data['id']
        date = data['date']

        # Получаем или создаем счетчик DL для данной даты
        dl_counter = DLCounter.query.filter_by(date=date).first()
        if not dl_counter:
            return jsonify({"status": "error", "message": "DL counter не инициализирован"}), 400

        # Генерируем новый DL номер с использованием префикса
        dl_prefix = app.config['DL_PREFIX']  # Получаем префикс из конфигурации
        dl_number = f"{dl_prefix}{dl_counter.counter:06d}"

        # Находим и обновляем нужную запись по id
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.dl_number = dl_number
            dl_counter.counter += 1  # Увеличиваем счетчик

            # Обновляем статистику генерации
            statistics = Statistics.query.filter_by(date=date).first()
            if statistics:
                statistics.generate_clicks += 1
            else:
                statistics = Statistics(date=date, generate_clicks=1, pack_clicks=0)
                db.session.add(statistics)

            db.session.commit()
            return jsonify({"status": "success", "dl_number": dl_number, "id": row_id})

        return jsonify({"status": "error", "message": "Строка не найдена"}), 404
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 500


@app.route('/export_excel')
def export_excel():
    date = request.args.get('date')
    export_all_rpa = request.args.get('export_all_rpa')

    if export_all_rpa == 'true':
        # Экспортируем все записи RPA
        query = TableRow.query.filter_by(arma_type='RPA')
    else:
        # Экспортируем записи за выбранный день
        query = TableRow.query.filter_by(date=date, arma_type='RPA')

    rows = query.all()

    # Создаем DataFrame с данными
    data = []
    for row in rows:
        data.append({
            '№': row.row_number,
            'Время отправки': row.timestamp,
            'Номер': row.number,
            'Serial': row.serial,
            'Графа': row.dl_number,
            'Имя отправителя': row.worker_name,
            'Время упаковки': row.pack_time,
            'Тип АРМА': row.arma_type,
            'Дата отгрузки': row.dateshipment,
            'Номер акта': row.aktshipment,
            'Bad Recovery': row.bad_recovery,
        })

    if not data:
        return jsonify({"error": "Нет записей для экспорта с заданными критериями."}), 400

    df = pd.DataFrame(data)

    # Создаем Excel файл
    output = BytesIO()
    with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
        df.to_excel(writer, index=False, sheet_name='Sheet1')

        # Настраиваем форматирование
        workbook = writer.book
        worksheet = writer.sheets['Sheet1']

        # Автоматическая ширина столбцов
        for idx, col in enumerate(df.columns):
            series = df[col]
            max_len = max(
                series.astype(str).map(len).max(),
                len(str(col))
            ) + 2
            worksheet.set_column(idx, idx, max_len)

    output.seek(0)

    filename = 'RPA_export.xlsx' if export_all_rpa == 'true' else f'export_{date}.xlsx'
    return send_file(
        output,
        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        as_attachment=True,
        download_name=filename
    )

@app.route('/update_serial', methods=['POST'])
def update_serial():
    try:
        data = request.json
        row_index = int(data['row_index'])
        new_value = data['new_value']
        date = data['date']

        rows = TableRow.query.filter_by(date=date).all()
        if 0 <= row_index < len(rows):
            row = rows[row_index]
            row.serial = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        return jsonify({"status": "error", "message": "Invalid row index"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)})


def initialize_table_for_date(date):
    # Проверяем, существуют ли уже записи для этой даты
    existing = TableRow.query.filter_by(date=date).first()
    if not existing:
        # Создаем новую запись только в случае отсутствия данных
        new_row = TableRow(
            date=date,
            row_number=str(1),
            timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            number='',
            serial='',
            dl_number='',
            worker_name='',
            arma_type='',
            pack_time='',
            dateshipment='',
            aktshipment='',
            bad_recovery='',
        )
        db.session.add(new_row)
        
        # Инициализируем статистику для новой даты
        statistics = Statistics(date=date, generate_clicks=0, pack_clicks=0)
        db.session.add(statistics)
        
        db.session.commit()


@app.route('/update_serial_arma', methods=['POST'])
def update_serial_arma():
    try:
        data = request.json
        date = data['date']
        serial = data['serial']
        arma_type = data['arma_type']

        # Создаем новую запись в таблице
        new_row = TableRow(
            date=date,
            serial=serial,
            arma_type=arma_type,
            timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        )

        db.session.add(new_row)
        db.session.commit()

        return jsonify({"status": "success"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500

@app.route('/get_statistics')
def get_statistics():
    try:
        date = request.args.get('date')
        app.logger.debug(f"Получен запрос статистики для даты: {date}")
        
        # Получаем статистику генерации и упаковки
        statistics = Statistics.query.filter_by(date=date).first()
        if not statistics:
            generate_clicks = 0
            pack_clicks = 0
            app.logger.debug(f"Статистика для даты {date} не найдена. Установлено значение 0.")
        else:
            generate_clicks = statistics.generate_clicks
            pack_clicks = statistics.pack_clicks
            app.logger.debug(f"Статистика для даты {date}: Генераций = {generate_clicks}, Упаковок = {pack_clicks}")
        
        # Получаем количество записей типа "RPA"
        rpa_records = TableRow.query.filter_by(date=date, arma_type='RPA').count()
        app.logger.debug(f"Количество записей типа RPA для даты {date}: {rpa_records}")
        
        # Считаем количество записей "RPA" без значения в колонке "Графа"
        unpacked_clicks = TableRow.query.filter(
            TableRow.date == date,
            TableRow.arma_type == 'RPA',
            TableRow.dl_number == None
        ).count()
        
        # Получаем количество упакованных записей за текущий час
        current_hour = datetime.now().replace(minute=0, second=0, microsecond=0)
        next_hour = current_hour + timedelta(hours=1)
        done_per_hour = TableRow.query.filter(
            TableRow.date == date,
            TableRow.arma_type == 'RPA',
            TableRow.pack_time >= current_hour,
            TableRow.pack_time < next_hour
        ).count()
        
        return jsonify({
            "generate_clicks": generate_clicks,
            "pack_clicks": pack_clicks,
            "rpa_records": rpa_records,
            "unpacked_clicks": unpacked_clicks,
            "done_per_hour": done_per_hour
        })
    except Exception as e:
        app.logger.error(f"Ошибка в get_statistics: {str(e)}")
        return jsonify({
            "generate_clicks": 0,
            "pack_clicks": 0,
            "rpa_records": 0,
            "unpacked_clicks": 0,
            "done_per_hour": 0,
            "error": str(e)
        })
@app.route('/delete_row', methods=['POST'])
def delete_row():
    try:
        data = request.json
        row_id = data.get('id')

        if not row_id:
            return jsonify({'status': 'error', 'message': 'ID записи не предоставлен'}), 400

        row = TableRow.query.get(row_id)
        if not row:
            return jsonify({'status': 'error', 'message': 'Запись не найдена'}), 404

        date = row.date  # Получаем дату записи для обновления статистики

        # Получаем запись статистики для этой даты
        statistics = Statistics.query.filter_by(date=date).first()

        if statistics:
            # Если запись имела сгенерированный DL номер, уменьшаем generate_clicks
            if row.dl_number:
                if statistics.generate_clicks > 0:
                    statistics.generate_clicks -= 1
                else:
                    app.logger.warning(f"Attempting to decrement generate_clicks below zero for date {date}")

            # Если запись была упакована, уменьшаем pack_clicks
            if row.pack_time:
                if statistics.pack_clicks > 0:
                    statistics.pack_clicks -= 1
                else:
                    app.logger.warning(f"Attempting to decrement pack_clicks below zero for date {date}")

            db.session.commit()
        else:
            app.logger.warning(f"No Statistics entry found for date {date} when deleting row {row_id}")

        # Удаляем запись из таблицы
        db.session.delete(row)
        db.session.commit()

        # Уменьшаем счетчик DL, если запись содержала DL-номер
        if row.dl_number:
            dl_counter = DLCounter.query.filter_by(date=date).first()
            if dl_counter and dl_counter.counter > 0:
                dl_counter.counter -= 1
                db.session.commit()

        # Отправляем сообщение в очередь для обновления клиентов
        update_queue.put(json.dumps({"type": "delete", "date": date, "id": row.id}))

        return jsonify({'status': 'success'})
    except Exception as e:
        db.session.rollback()
        app.logger.error(f"Ошибка в delete_row: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/', methods=['GET', 'POST'])
def main():
    if request.method == 'POST':
        worker = request.form.get('worker')
        name = request.form.get('name')

        # Сохраняем информацию о входе
        user = User.query.filter_by(username=name).first()
        if not user:
            user = User(username=name)
            db.session.add(user)

        login_history = LoginHistory(user=user)
        db.session.add(login_history)
        db.session.commit()

        resp = make_response(redirect('/welcome'))
        resp.set_cookie('worker', worker)
        resp.set_cookie('name', name)
        return resp

    return render_template('main.html')


@app.route('/get_current_dl', methods=['GET'])
def get_current_dl():
    date = request.args.get('date')
    if not date:
        return jsonify({"status": "error", "message": "Дата обязательна"}), 400

    dl_counter = DLCounter.query.filter_by(date=date).first()
    if dl_counter:
        return jsonify({"status": "success", "current_dl": dl_counter.counter})
    else:
        return jsonify({"status": "error", "message": "Счетчик DL не инициализирован для этой даты"}), 404


@app.route('/welcome')
def welcome():
    worker = request.cookies.get('worker')
    name = request.cookies.get('name')

    if not worker or not name:
        return redirect('/')

    current_time = datetime.utcnow()
    devices = [device.to_dict(current_time=current_time) for device in Device.query.order_by(Device.name).all()]
    current_time_str = current_time.isoformat()

    # Получаем текущий префикс
    prefix = Prefix.query.first()
    dl_prefix = prefix.value if prefix else 'DL'

    return render_template('welcome.html', name=name, worker=worker, table=table, devices=devices, current_time=current_time_str, dl_prefix=dl_prefix)


@app.route('/logout')
def logout():
    resp = make_response(redirect('/'))
    resp.delete_cookie('worker')
    resp.delete_cookie('name')
    return resp

@app.route('/get_unpacked_rpa_counts', methods=['GET'])
def get_unpacked_rpa_counts():
    year = request.args.get('year')
    month = request.args.get('month')
    
    if not year or not month:
        return jsonify({"status": "error", "message": "Необходимо указать год и месяц"}), 400
    
    try:
        year = int(year)
        month = int(month)
    except ValueError:
        return jsonify({"status": "error", "message": "Год и месяц должны быть числами"}), 400

    # Формируем формат даты для фильтрации
    month_str = f"{month:02d}"
    
    # Запрашиваем количество записей RPA с пустым значением в колонке "Время упаковки" для каждого дня
    unpacked_counts = TableRow.query.filter(
        TableRow.date.like(f"{year}-{month_str}-%"),
        TableRow.arma_type == 'RPA',
        or_(TableRow.pack_time == None, TableRow.pack_time == '')
    ).group_by(TableRow.date).with_entities(TableRow.date, func.count(TableRow.id)).all()
    
    # Преобразуем результат в словарь
    unpacked_counts_dict = {date: count for date, count in unpacked_counts}
    
    return jsonify({'status': 'success', 'unpacked_counts': unpacked_counts_dict})

@app.route('/get_last_dl_number_all_time', methods=['GET'])
def get_last_dl_number_all_time():
    # Получаем последнюю запись с максимальным значением DL за всё время
    last_dl_row = TableRow.query.order_by(TableRow.dl_number.desc()).first()
    if last_dl_row:
        last_dl = last_dl_row.dl_number
        if last_dl and last_dl.startswith('DL'):
            last_dl_number = int(last_dl[2:])
            next_dl_number = last_dl_number + 1
        else:
            next_dl_number = 1
    else:
        next_dl_number = 1

    return jsonify({"status": "success", "next_dl_number": next_dl_number})


@app.route('/import_excel', methods=['POST'])
def import_excel():
    try:
        # Check if a file is provided
        if 'file' not in request.files:
            return jsonify({"status": "error", "message": "No file provided"}), 400

        # Get the uploaded file
        file = request.files['file']

        # Check the file extension for security reasons
        if not file.filename.endswith('.xlsx') and not file.filename.endswith('.xls'):
            return jsonify({"status": "error", "message": "File type not allowed"}), 400

        # Read the Excel file into a DataFrame
        df = pd.read_excel(file)

        # Define the fields expected in the Excel file
        required_columns = ['Время отправки', 'Номер', 'Serial', 'Графа', 'Имя отправителя']

        # Check if all required columns are in the DataFrame
        if not all(col in df.columns for col in required_columns):
            return jsonify({"status": "error", "message": f"Missing required columns. Required columns are: {', '.join(required_columns)}"}), 400

        # Get current date
        current_date = datetime.now().date().strftime("%Y-%m-%d")

        # Iterate over DataFrame rows and insert data into the database
        for _, row in df.iterrows():
            new_row = TableRow(
                date=current_date,
                timestamp=row['Время отправки'],
                number=row['Номер'],
                serial=row['Serial'],
                dl_number=row['Графа'],
                worker_name=row['Имя отправителя'],
                row_number='',  # Will be calculated later
                pack_time='0',  # Default value
                arma_type='0',  # Default value
                dateshipment='0', 
                aktshipment='0',   # Default value
                bad_recovery=row.get('bad_recovery', False) ,
            )
            db.session.add(new_row)
        db.session.commit()

        # Update row numbers
        rows_of_day = TableRow.query.filter_by(date=current_date).order_by(TableRow.id).all()
        for idx, row in enumerate(rows_of_day, 1):
            row.row_number = str(idx)
        db.session.commit()

        return jsonify({"status": "success", "message": "Data imported successfully"})

    except Exception as e:
        db.session.rollback()
        app.logger.error(f"Error during Excel import: {str(e)}")  # Логируем ошибку
        return jsonify({"status": "error", "message": str(e)}), 500
    
@app.route('/delete_imported_data', methods=['POST'])
def delete_imported_data():
    try:
        current_date = datetime.now().date().strftime("%Y-%m-%d")
        rows_deleted = TableRow.query.filter_by(date=current_date).delete()
        db.session.commit()

        return jsonify({"status": "success", "message": f"{rows_deleted} rows deleted for date {current_date}"})

    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500
    
@app.route('/get_rpa_records', methods=['GET'])
def get_rpa_records():
    try:
        # Замените фильтр по имени 'worker_name' на фильтр по 'arma_type'
        rows = TableRow.query.filter_by(arma_type='RPA').all()

        data = []
        for row in rows:
            data.append({
                'id': row.id,
                'row_number': row.row_number,
                'timestamp': row.timestamp,
                'number': row.number,
                'serial': row.serial,
                'dl_number': row.dl_number,
                'worker_name': row.worker_name,
                'pack_time': row.pack_time,
                'arma_type': row.arma_type,
                'dateshipment': row.dateshipment,
                'aktshipment': row.aktshipment,
                'bad_recovery': row.bad_recovery,
            })

        return jsonify({"status": "success", "data": data})

    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 500

@app.route('/export_rpa_excel')
def export_rpa_excel():
    try:
        # Начинаем построение запроса с фильтрации по типу АРМА "RPA"
        query = TableRow.query.filter_by(arma_type='RPA')

        # Дополнительные условия фильтрации: 
        # - dl_number не пустой
        # - pack_time не пустой
        query = query.filter(
            TableRow.dl_number.isnot(None),
            TableRow.dl_number != '',
            TableRow.pack_time.isnot(None),
            TableRow.pack_time != ''
        )

        rows = query.all()

        # Создаем DataFrame только с отфильтрованными данными
        data = []
        for row in rows:
            data.append({
                '№': row.row_number,
                'Время отправки': row.timestamp,
                'Номер': row.number,
                'Serial': row.serial,
                'Графа': row.dl_number,
                'Имя отправителя': row.worker_name,
                'Время упаковки': row.pack_time,
                'Тип АРМА': row.arma_type,
                'Дата отгрузки': row.dateshipment,
                'Номер акта': row.aktshipment,
                'bad_recovery': row.bad_recovery,
            })

        if not data:
            return jsonify({"error": "Нет записей для экспорта с заданными критериями."}), 400

        df = pd.DataFrame(data)

        # Создаем Excel файл
        output = BytesIO()
        with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
            df.to_excel(writer, index=False, sheet_name='Sheet1')

            # Настраиваем форматирование
            workbook = writer.book
            worksheet = writer.sheets['Sheet1']

            # Автоматическая ширина столбцов
            for idx, col in enumerate(df.columns):
                series = df[col]
                max_len = max(
                    series.astype(str).map(len).max(),
                    len(str(col))
                ) + 2
                worksheet.set_column(idx, idx, max_len)

                # Форматирование числовых столбцов
                if col in ['Номер', 'Графа']:
                    num_format = workbook.add_format({'num_format': '0'})
                    worksheet.set_column(idx, idx, max_len, num_format)

        output.seek(0)

        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name='RPA_export.xlsx'
        )

    except Exception as e:
        print(f"Export error: {str(e)}")
        return jsonify({"error": str(e)}), 500
    

@app.route('/get_duplicates')
def get_duplicates():
    try:
        # Получим все строки из таблицы
        rows = TableRow.query.all()

        # Хранилища для множества значений
        numbers = {}
        serials = {}
        dl_numbers = {}

        # Собираем все значения и их индексы
        data = []
        for row in rows:
            row_data = {
                'id': row.id,
                'row_number': row.row_number,
                'timestamp': row.timestamp,
                'number': row.number,
                'serial': row.serial,
                'dl_number': row.dl_number,
                'worker_name': row.worker_name,
                'pack_time': row.pack_time,
                'arma_type': row.arma_type,
                'dateshipment': row.dateshipment,
                'aktshipment': row.aktshipment,
                'bad_recovery': row.bad_recovery,
            }
            data.append(row_data)
            if row.number:
                clean_number = ''.join(filter(str.isdigit, row.number))
                if clean_number in numbers:
                    numbers[clean_number].append(len(data) - 1)
                else:
                    numbers[clean_number] = [len(data) - 1]

            if row.serial:
                clean_serial = ''.join(filter(str.isdigit, row.serial))
                if clean_serial in serials:
                    serials[clean_serial].append(len(data) - 1)
                else:
                    serials[clean_serial] = [len(data) - 1]

            if row.dl_number:
                clean_dl_number = ''.join(filter(str.isdigit, row.dl_number))
                if clean_dl_number in dl_numbers:
                    dl_numbers[clean_dl_number].append(len(data) - 1)
                else:
                    dl_numbers[clean_dl_number] = [len(data) - 1]

        # Фильтруем только реальные дубликаты
        duplicate_numbers = {k: v for k, v in numbers.items() if len(v) > 1}
        duplicate_serials = {k: v for k, v in serials.items() if len(v) > 1}
        duplicate_dl_numbers = {k: v for k, v in dl_numbers.items() if len(v) > 1}

        duplicates = {
            "numbers": duplicate_numbers,
            "serials": duplicate_serials,
            "dl_numbers": duplicate_dl_numbers
        }

        # Сохраняем индексы всех уникальных дубликатов
        duplicate_indices = set()
        for indices in duplicate_numbers.values():
            duplicate_indices.update(indices)
        for indices in duplicate_serials.values():
            duplicate_indices.update(indices)
        for indices in duplicate_dl_numbers.values():
            duplicate_indices.update(indices)

        # Отбираем только дубликаты
        duplicates_data = [data[idx] for idx in duplicate_indices]

        return jsonify({
            "status": "success",
            "data": duplicates_data,
            "duplicates": duplicates
        })

    except Exception as e:
        return jsonify({"status": "error", "message": str(e)})

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
        create_devices()



    logging.disable(logging.CRITICAL)
    os.system('title СЕРВЕР')
    app.run(host='192.168.50.110', port=80, debug=True)  #192.168.50.99
let isEditing = false;
let isRPAFilterActive = false;
let isShowingDuplicates = false;
let searchTimeout;
let lastUpdateTime = 0;
const updateInterval = 1000;
let eventSource;
let lastData = null;
let isDeleting = false;
let fpInstance;
let unpackedDaysCache = {}; // Ключи будут в формате 'YYYY-MM', значение - Set дней
const deviceSparklines = {};

// Глобальные переменные для отслеживания сортировки
let globalSortColumn = null;
let globalSortOrder = 'asc';

function getSelectedDate() {
    return $('#datepicker').val();
}

// Функция для получения состояния фильтра
function getArmaFilter() {
    return $('#armaFilter').is(':checked') ? 'RPA' : '';
}

function updateTable(force = false, highlightNew = false) {
    return new Promise((resolve, reject) => {
        const now = Date.now();
        if (!force && now - lastUpdateTime < updateInterval) {
            resolve();
            return;
        }
        if (isEditing) {
            resolve();
            return;
        }

        if (isEditing || isDeleting) { // Добавляем проверку на isDeleting
            resolve();
            return;
        }

        // Проверьте, если фильтр RPA активен, не выполняйте обновление автоматически
        if (isRPAFilterActive) {
            resolve();
            return;
        }
       
        if (isShowingDuplicates) {
            resolve();
            return;
        }

        $.ajax({
            url: '/get_table_data',
            type: 'GET',
            data: { 
                date: getSelectedDate(),
                filter_arma: getArmaFilter()
            },
            success: function(response) {
                var tableBody = $('#tableBody');

                // Обновляем содержимое таблицы
                updateTableContent(response.data, tableBody, highlightNew);

                // Применяем текущую сортировку
                if (globalSortColumn !== null) {
                    sortTable(globalSortColumn, globalSortOrder);
                }

                updateStatistics();
                lastUpdateTime = now;

                // Проверяем и подсвечиваем дубликаты
                highlightDuplicates(response.duplicates);

                // Скрываем окно с общим количеством RPA и восстанавливаем статистические окна
                hideRPATotal();

                resolve();
            },
            error: function(xhr, status, error) {
                console.error('Error updating table:', error);
                reject(error);
            }
        });
    });
}
function initializeSparklines(activity) {
    for (const [name, data] of Object.entries(activity)) {
        const sparklineSpan = $(`#${name}-sparkline`);
        sparklineSpan.sparkline(data, {
            type: 'line',
            width: '300px',  // Увеличенная ширина
            height: '50px',  // Также можно увеличить высоту для лучшей видимости
            lineColor: 'green',
            fillColor: 'transparent',
            spotColor: 'red',          // Отображение пиков
            minSpotColor: 'blue',      // Минимальные пики
            maxSpotColor: 'orange',    // Максимальные пики
            highlightSpotColor: 'blue',
            highlightLineColor: 'blue'
        });
        sparklineSpan.data('sparkline-initialized', true);
    }
}

function updateSparklines(devices, activity) {
    for (const [name, status] of Object.entries(devices)) {
        const sparklineSpan = $(`#${name}-sparkline`);
        const data = activity[name];

        // Determine line color based on device status
        let lineColor = 'green';  // Default color

        if (status === 'processing') {
            lineColor = 'green';  // Color for processing state
        } else if (status === 'copying') {
            lineColor = 'orange';
        } else if (status === 'sending_number') {
            lineColor = 'blue';
        } else if (status === 'no_internet') {
            lineColor = 'red';
        }

        // Check for impulse (e.g., value > 4)
        const lastValue = data[data.length - 1];
        if (lastValue > 4) {
            lineColor = 'purple';  // Color for impulse
        }

        if (sparklineSpan.data('sparkline-initialized')) {
            // Update existing Sparkline
            sparklineSpan.sparkline(data, {
                type: 'line',
                width: '100%', 
                height: '20px', 
                lineColor: lineColor,
                fillColor: 'transparent',
                spotColor: 'red',
                minSpotColor: 'blue',
                maxSpotColor: 'orange',
                highlightSpotColor: 'blue',
                highlightLineColor: 'blue'
            });
        } else {
            // Initialize Sparkline if not already
            sparklineSpan.sparkline(data, {
                type: 'line',
                width: '100%', 
                height: '50px', 
                lineColor: lineColor,
                fillColor: 'transparent',
                spotColor: 'red',
                minSpotColor: 'blue',
                maxSpotColor: 'orange',
                highlightSpotColor: 'blue',
                highlightLineColor: 'blue'
            });
            sparklineSpan.data('sparkline-initialized', true);
        }
    }
}

function fetchDeviceStatuses() {
    $.ajax({
        url: '/get_device_status',
        type: 'GET',
        success: function(response) {
            if (response.status === 'success') {
                const devices = response.devices;
                const activity = response.activity;

                for (const [name, deviceInfo] of Object.entries(devices)) {
                    const status = deviceInfo.status;

                    // Обновление статуса устройства
                    const statusCircle = $(`#${name}-status`);
                    const statusText = $(`#${name}-status-text`);
                    statusCircle.removeClass('online offline no_internet copying sending_number processing');

                    if (status === 'offline') {
                        statusCircle.addClass('offline');
                        statusText.text('Нет соединения');
                    } else if (status === 'no_internet') {
                        statusCircle.addClass('no_internet');
                        statusText.text('Нет интернета');
                    } else if (status === 'copying') {
                        statusCircle.addClass('copying');
                        statusText.text('Копирование');
                    } else if (status === 'sending_number') {
                        statusCircle.addClass('sending_number');
                        statusText.text('Отправка номера');
                    } else if (status === 'online') {
                        statusCircle.addClass('online');
                        statusText.text('Онлайн');
                    } else if (status === 'processing') {
                        // Если все еще есть устройства, для которых нужен статус 'processing'
                        statusCircle.addClass('processing');
                        statusText.text('Онлайн');
                    }
                }

                // Обновление спарклайнов активности
                updateSparklines(devices, activity);
            }
        },
        error: function(error) {
            console.error('Ошибка при получении статусов устройств:', error);
        }
    });
}


function highlightDuplicates(duplicates) {
    // Очищаем предыдущие выделения
    $('#tableBody td').removeClass('duplicate-highlight');

    // Подсвечиваем дубликаты номеров
    for (let number in duplicates.numbers) {
        duplicates.numbers[number].forEach(index => {
            $(`#tableBody tr:eq(${index}) td:nth-child(3)`).addClass('duplicate-highlight');
        });
    }

    // Подсвечиваем дубликаты серийных номеров
    for (let serial in duplicates.serials) {
        duplicates.serials[serial].forEach(index => {
            $(`#tableBody tr:eq(${index}) td:nth-child(4)`).addClass('duplicate-highlight');
        });
    }

    // Подсвечиваем дубликаты "Графа" (dl_number)
    for (let dl_number in duplicates.dl_numbers) {
        duplicates.dl_numbers[dl_number].forEach(index => {
            $(`#tableBody tr:eq(${index}) td:nth-child(5)`).addClass('duplicate-highlight');
        });
    }
}

function updateCell(cell, newValue, rowId, cellIndex, rowData) {
    console.log(`Updating cell: index=${cellIndex}, value=${newValue}, rowId=${rowId}`);

    const excludedArmaTypes = ["CFA", "VND", "SRC"];

    if (cellIndex === 4) { // Столбец "Графа"
        if (rowData.arma_type === 'RPA' && !excludedArmaTypes.includes(rowData.arma_type)) {
            if (newValue && newValue.startsWith('DL')) {
                cell.text(newValue)
                    .addClass('clickable')
                    .css('cursor', 'pointer')
                    .off('click') // Удаляем предыдущие обработчики
                    .on('click', function() {
                        copyToClipboard(newValue);
                        $(this).addClass('copied');
                        setTimeout(() => {
                            $(this).removeClass('copied');
                        }, 200);
                    });

                // Обновляем data-value
                let numericValue = parseDL(newValue) || 0;
                cell.attr('data-value', numericValue);
            } else {
                cell.html('<button class="generate-button">Генерировать</button>')
                    .find('button')
                    .click(function() {
                        generateDL(rowId);
                    });

                // Обновляем data-value
                cell.attr('data-value', 0);
            }
        } else if (!excludedArmaTypes.includes(rowData.arma_type)) {
            if (newValue && newValue.startsWith('DL')) {
                cell.text(newValue)
                    .addClass('clickable')
                    .css('cursor', 'pointer')
                    .off('click')
                    .on('click', function() {
                        copyToClipboard(newValue);
                        $(this).addClass('copied');
                        setTimeout(() => {
                            $(this).removeClass('copied');
                        }, 200);
                    });

                // Обновляем data-value
                let numericValue = parseDL(newValue) || 0;
                cell.attr('data-value', numericValue);
            } else {
                cell.text(newValue || '');
                // Обновляем data-value
                let numericValue = parseDL(newValue) || 0;
                cell.attr('data-value', numericValue);
            }
        } else {
            cell.text(newValue || '');
            // Обновляем data-value
            let numericValue = parseDL(newValue) || 0;
            cell.attr('data-value', numericValue);
        }
    } else if (cellIndex === 6) { // Столбец "Время упаковки"
        const dlCell = cell.parent().find('td').eq(4);
        const dlValue = dlCell.text().trim();
        const armaType = rowData.arma_type;

        if (dlValue.startsWith('DL') && !newValue && !excludedArmaTypes.includes(armaType)) {
            cell.html('<button class="pack-button">Упаковать</button>')
                .find('button')
                .click(function() {
                    packItem(rowId);
                });
        } else {
            cell.text(newValue);
        }
    } else if (cellIndex === 7) { // Столбец "Тип АРМА"
        if (!newValue || newValue.trim() === '') {
            newValue = 'RPA';
        }
        cell.text(newValue)
            .toggleClass('editable', true)
            .attr('data-column', cellIndex);
    } else {
        cell.text(newValue)
            .toggleClass('editable', cellIndex === 2 || cellIndex === 3)
            .attr('data-column', cellIndex);
    }
}

function sortTable(column, order) {
    const rows = $('#tableBody > tr').get();

    rows.sort(function(a, b) {
        let A = $(a).children('td').eq(column).text().trim();
        let B = $(b).children('td').eq(column).text().trim();

        // Обработка пустых значений
        if (A === '' && B === '') return 0;
        if (A === '') return order === 'asc' ? 1 : -1;
        if (B === '') return order === 'asc' ? -1 : 1;

        // Специальная обработка для столбца "Графа" (dl_number) с использованием data-value
        if (column === 4) { // Индекс столбца "Графа"
            let numA = parseInt($(a).children('td').eq(column).attr('data-value')) || 0;
            let numB = parseInt($(b).children('td').eq(column).attr('data-value')) || 0;

            // Отладочные сообщения
            console.log(`Сортировка Графа: numA = ${numA}; numB = ${numB}`);

            return order === 'asc' ? numA - numB : numB - numA;
        }

        // Обработка дат и времени для соответствующих столбцов
        if (column === 1 || column === 6) {  // Столбцы "Время отправки" и "Время упаковки"
            let dateA = Date.parse(A);
            let dateB = Date.parse(B);
            if (isNaN(dateA)) dateA = 0;
            if (isNaN(dateB)) dateB = 0;
            return order === 'asc' ? dateA - dateB : dateB - dateA;
        }

        // Обработка числовых столбцов
        if (column === 0 || column === 2) {  // Столбцы с числами (№ и Номер)
            let numA = parseFloat(A) || 0;
            let numB = parseFloat(B) || 0;
            return order === 'asc' ? numA - numB : numB - numA;
        }

        // Обработка строковых столбцов
        if (column === 5) {  // Столбец с именем отправителя
            A = A.toLowerCase();
            B = B.toLowerCase();
        }

        // Строковое сравнение для остальных столбцов
        A = A.toUpperCase();
        B = B.toUpperCase();
        if (A < B) return order === 'asc' ? -1 : 1;
        if (A > B) return order === 'asc' ? 1 : -1;
        return 0;
    });

    // Очистка тела таблицы
    $('#tableBody').empty();

    // Добавление отсортированных строк
    $.each(rows, function(index, row) {
        $('#tableBody').append(row);
    });

    checkDuplicates();
    bindEventHandlers();
}

function parseDL(dl_number) {
    if (typeof dl_number !== 'string') return null;
    dl_number = dl_number.trim();
    let match = dl_number.match(/^DL[-\s]?(\d+)$/i);
    if (match && match[1]) {
        return parseInt(match[1], 10);
    }
    return null;
}

function generateDL(rowId) {
    console.log(`Generating DL for row ID ${rowId}`);
    const selectedDate = getSelectedDate();
    console.log("Selected date:", selectedDate);

    $.ajax({
        url: '/generate_dl',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({
            id: rowId,
            date: selectedDate
        }),
        success: function(response) {
            if (response.status === 'success') {
                console.log('DL успешно сгенерирован:', response.dl_number);
                var row = $(`#tableBody tr[data-id='${rowId}']`);
                var dlCell = row.find('td:eq(4)');
                var packCell = row.find('td:eq(6)'); // Столбец "Время упаковки"
                var armaType = row.find('td:eq(7)').text().trim();

                dlCell.text(response.dl_number)
                    .addClass('clickable')
                    .css('cursor', 'pointer')
                    .off('click') // Удаляем предыдущие обработчики
                    .on('click', function() {
                        copyToClipboard(response.dl_number);
                        $(this).addClass('copied');
                        setTimeout(() => {
                            $(this).removeClass('copied');
                        }, 200);
                    });

                // Добавляем кнопку упаковки только если arma_type не исключен
                const excludedArmaTypes = ["CFA", "VND", "SRC"];
                if (!excludedArmaTypes.includes(armaType)) {
                    packCell.html('<button class="pack-button">Упаковать</button>')
                        .find('button')
                        .click(function() {
                            packItem(rowId);
                        });
                }

                // Обновляем поле initial-dl с текущим значением DL
                loadCurrentDL();
            } else {
                alert('Ошибка при генерации DL: ' + response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error('Ошибка при генерации DL:', status, error);
            alert('Ошибка при генерации DL: ' + error);
        }
    });
}

function packItem(rowId) {
    console.log(`Упаковка элемента для строки ID ${rowId}`);
    const selectedDate = getSelectedDate();
    console.log("Selected date:", selectedDate);

    $.ajax({
        url: '/pack_item',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({
            id: rowId,
            date: selectedDate
        }),
        success: function(response) {
            console.log("Ответ сервера:", response);
            if (response.status === 'success') {
                var row = $(`#tableBody tr[data-id='${rowId}']`);
                row.addClass('packed-row');
                var cell = row.find('td:eq(6)'); // Столбец "Время упаковки"
                cell.text(response.pack_time);
                updateStatistics();

                // Удаляем кнопку "Упаковать", так как запись уже упакована
                cell.find('button.pack-button').remove();

                // Определяем год и месяц из `selectedDate`
                const [year, month, day] = selectedDate.split('-').map(Number);

                // Принудительно обновляем неупакованные дни для этого месяца
                markUnpackedDays(year, month, true);
            } else {
                alert('Ошибка при упаковке: ' + response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error("Ошибка AJAX:", xhr, status, error);
            alert('Ошибка при упаковке: ' + error);
        }
    });
}

function areSimilarNumbers(num1, num2) {
    // Очищаем строки от нецифровых символов и пробелов
    const clean1 = num1.toString().replace(/\D/g, '').trim();
    const clean2 = num2.toString().replace(/\D/g, '').trim();
    
    // Если строки пустые или разной длины - не дубликаты
    if (!clean1 || !clean2 || clean1.length !== clean2.length) {
        return false;
    }
    
    // Проверяем на точное совпадение
    return clean1 === clean2;
}

function checkDuplicates() {
    console.log("Checking for duplicates...");
    const rows = $('#tableBody tr');
    
    // Очищаем предыдущие выделения
    rows.find('td').removeClass('duplicate-highlight');
    
    // Создаем массивы для хранения номеров и их индексов
    const numbers = new Map();
    const dl_numbers = new Map();
    const serials = new Map();
    
    // Собираем все номера и серийные номера
    rows.each(function(index) {
        const number = $(this).find('td:nth-child(3)').text().trim();
        const serial = $(this).find('td:nth-child(4)').text().trim();
        const dl_number = $(this).find('td:nth-child(5)').text().trim();
        
        if (number) {
            if (!numbers.has(number)) {
                numbers.set(number, []);
            }
            numbers.get(number).push(index);
        }

        if (dl_number) {
            if (!dl_numbers.has(dl_number)) {
                dl_numbers.set(dl_number, []);
            }
            dl_numbers.get(dl_number).push(index);
        }
        
        if (serial) {
            if (!serials.has(serial)) {
                serials.set(serial, []);
            }
            serials.get(serial).push(index);
        }
    });
    
    // Подсвечиваем дубликаты
    let duplicatesFound = false;
    


    dl_numbers.forEach((indices, dl_number) => {
        if (dl_number.startsWith("DL")) {
            if (indices.length > 1) {
                indices.forEach(index => {
                    rows.eq(index).find('td:nth-child(5)').addClass('duplicate-highlight');
                });
                duplicatesFound = true;
            }
        }
    });

    // Проверяем номера
    numbers.forEach((indices, number) => {
        if (indices.length > 1) {
            indices.forEach(index => {
                rows.eq(index).find('td:nth-child(3)').addClass('duplicate-highlight');
            });
            duplicatesFound = true;
        }
    });

    // // Проверяем грайвера
    // dl_numbers.forEach((indices, dl_number) => {
    //     if (indices.length > 1) {
    //         indices.forEach(index => {
    //             rows.eq(index).find('td:nth-child(5)').addClass('duplicate-highlight');
    //         });
    //         duplicatesFound = true;
    //     }
    // });
    
    // Проверяем серийные номера
    serials.forEach((indices, serial) => {
        if (indices.length > 1) {
            indices.forEach(index => {
                rows.eq(index).find('td:nth-child(4)').addClass('duplicate-highlight');
            });
            duplicatesFound = true;
        }
    });

    return duplicatesFound;
}

function appendNewRow(tableBody, rowData) {
    console.log("Добавление новой строки:", rowData);

    // Создаем элемент строки и присваиваем уникальный data-id
    var tr = $('<tr>').attr('data-id', rowData.id);

    // Если есть время упаковки и это не заголовок, добавляем класс 'packed-row'
    if (rowData.pack_time && rowData.pack_time !== "Время упаковки") {
        tr.addClass('packed-row');
    }

    // Добавляем ячейки в строку
    tr.append($('<td>').text(rowData.row_number));
    tr.append($('<td>').text(rowData.timestamp));

    // Столбец "Номер" (Третий столбец, индекс 2)
    var numberCell = $('<td>')
        .text(rowData.number)
        .addClass('editable') // Добавляем класс для редактирования
        .attr('data-column', '2'); // Указываем индекс столбца
    tr.append(numberCell);

    tr.append($('<td>').text(rowData.serial));

    // Столбец "Графа" (Пятый столбец, индекс 4)
    var dlCell = $('<td>')
        .text(rowData.dl_number || '')
        .addClass('editable') // Добавляем класс для редактирования
        .attr('data-column', '4'); // Указываем индекс столбца "Графа"
    tr.append(dlCell);
    
    // Добавляем data-value
    var numericValue = parseDL(rowData.dl_number) || 0;
    dlCell.attr('data-value', numericValue);

    // Проверка и добавление кнопок или кликабельного текста для "Графа"
    const excludedArmaTypes = ["CFA", "VND", "SRC"];

    if (rowData.arma_type === 'RPA' && !excludedArmaTypes.includes(rowData.arma_type)) {
        // Для типа АРМА 'RPA', проверяем наличие и формат dl_number
        if (!rowData.dl_number || !rowData.dl_number.startsWith('DL')) {
            // Если dl_number отсутствует или не начинается с 'DL', отображаем кнопку "Генерировать"
            dlCell.html('<button class="generate-button">Генерировать</button>')
                .find('button')
                .click(function() {
                    generateDL(rowData.id); // Передаем уникальный ID строки
                });
        } else {
            // Если dl_number присутствует и начинается с 'DL', делаем его кликабельным для копирования
            dlCell.text(rowData.dl_number)
                .addClass('clickable')
                .css('cursor', 'pointer')
                .on('click', function() {
                    copyToClipboard(rowData.dl_number);
                    $(this).addClass('copied');
                    setTimeout(() => {
                        $(this).removeClass('copied');
                    }, 200);
                });
        }
    } else if (!excludedArmaTypes.includes(rowData.arma_type)) {
        // Для других типов АРМА, которые не исключены, отображаем или скрываем кнопку
        if (rowData.dl_number && rowData.dl_number.startsWith('DL')) {
            dlCell.text(rowData.dl_number)
                .addClass('clickable')
                .css('cursor', 'pointer')
                .on('click', function() {
                    copyToClipboard(rowData.dl_number);
                    $(this).addClass('copied');
                    setTimeout(() => {
                        $(this).removeClass('copied');
                    }, 200);
                });
        } else {
            dlCell.text(rowData.dl_number || '');
        }
    } else {
        // Для типов АРМА "CFA", "VND", "SRC" оставляем ячейку пустой или с текущим значением
        dlCell.text(rowData.dl_number || '');
    }

    // Добавляем ячейку с именем отправителя
    tr.append($('<td>').text(rowData.worker_name));

    /**
     * Обработка ячейки "Время упаковки" (pack_time)
     */
    var packCell = $('<td>');

    if (rowData.dl_number && rowData.dl_number.startsWith('DL') && !rowData.pack_time && !excludedArmaTypes.includes(rowData.arma_type)) {
        // Если dl_number с 'DL', pack_time отсутствует и arma_type не исключен, показываем кнопку "Упаковать"
        packCell.html('<button class="pack-button">Упаковать</button>')
            .find('button')
            .click(function() {
                packItem(rowData.id); // Передаем уникальный ID строки
            });
    } else {
        // Иначе отображаем время упаковки или оставляем пустым
        packCell.text(rowData.pack_time || '');
    }

    tr.append(packCell);

    // Добавляем ячейку с типом АРМА
    tr.append($('<td>').text(rowData.arma_type || ''));

    tr.append($('<td>').text(rowData.dateshipment));  
    tr.append($('<td>').text(rowData.aktshipment)); 
    tr.append($('<td>').text(rowData.bad_recovery)); 
   
    /**
     * Добавление ячейки "Действие" с кнопкой "Удалить"
     */
    var actionTd = $('<td>')
    var deleteButton = $('<button>').text('Удалить').addClass('delete-button btn btn-danger btn-sm')
    deleteButton.click(function() {
        initiateDeleteConfirmation(rowData.id); // Используем новую функцию
    });
    actionTd.append(deleteButton);
    tr.append(actionTd);

    // Добавляем строку в тело таблицы
    tableBody.append(tr);

    /**
     * Дополнительная обработка после добавления строки
     */
    setTimeout(() => {
        const dlValue = dlCell.text().trim();
        const packValue = packCell.text().trim();

        if (dlValue.startsWith('DL') && !packValue && !excludedArmaTypes.includes(rowData.arma_type)) {
            console.log('Добавляем кнопку упаковки для DL:', dlValue);
            packCell.html('<button class="pack-button">Упаковать</button>')
                .find('button')
                .click(function() {
                    packItem(rowData.id); // Передаем уникальный ID строки
                });
        }

        // Проверяем дубликаты после добавления новой строки
        checkDuplicates();
    }, 100);
}


function bindEventHandlers() {
    // Привязка обработчика для кнопки "Генерировать"
    $('.generate-button').off('click').on('click', function() {
        var rowId = $(this).closest('tr').data('id');
        generateDL(rowId);
    });

    // Привязка обработчика для кнопки "Упаковать"
    $('.pack-button').off('click').on('click', function() {
        var rowId = $(this).closest('tr').data('id');
        packItem(rowId);
    });

    // Привязка обработчика для кнопки "Удалить"
    $('.delete-button').off('click').on('click', function() {
        var rowId = $(this).closest('tr').data('id');
        initiateDeleteConfirmation(rowId);
    });
}

function updateTableContent(data, tableBody, highlightNew = false) {
    tableBody.empty();  // Очищаем текущее содержимое таблицы
    data.forEach(function(rowData) {
        appendNewRow(tableBody, rowData);
    });
    checkDuplicates();
    bindEventHandlers();  // Привязываем обработчики после обновления таблицы
}

function scheduleNextUpdate() {
    setTimeout(() => {
        if (!isEditing) {
            updateTable(false, false).then(() => {
                scheduleNextUpdate();
            });
        } else {
            scheduleNextUpdate();
        }
    }, 3000);
}

function initializeEventSource() {
    if (eventSource) {
        eventSource.close();
    }

    eventSource = new EventSource('/stream');

    eventSource.onmessage = function(event) {
        const data = JSON.parse(event.data);
        if (data.type === 'update' && data.date === getSelectedDate()) {
            // Обновляем таблицу
            updateTable(true).then(() => {
                // После обновления таблицы, обновляем подсветку дней
                const currentDate = new Date(fpInstance.selectedDates[0]);
                markUnpackedDays(currentDate.getFullYear(), currentDate.getMonth() + 1);
                fetchDeviceStatuses();
            });
        }
    };

    eventSource.onerror = function(error) {
        console.error('EventSource failed:', error);
        eventSource.close();
        setTimeout(initializeEventSource, 5000);  // Попытка переподключения через 5 секунд
    };
}

// Обработчики событий для таблицы
$(document).ready(function() {
    updateTable();
    updateStatistics(); // Убедитесь, что эта функция определена
    initializeEventSource();
    scheduleNextUpdate();
    fetchDeviceStatuses();
    loadCurrentDL();
    // Обработчик сортировки
    $('.sortable').click(function() {
        const column = $(this).data('column');
        const order = $(this).hasClass('asc') ? 'desc' : 'asc';
        
        // Удаляем классы сортировки со всех заголовков
        $('.sortable').removeClass('asc desc');
        // Добавляем класс текущей сортировки
        $(this).addClass(order);
        
        // Сохраняем текущие параметры сортировки
        globalSortColumn = column;
        globalSortOrder = order;
        
        // Сортируем таблицу
        sortTable(column, order);
    });

    // Обработчик поиска
    $('#searchButton').click(function() {
        searchNumber();
    });
    $('#searchInput').keypress(function(e) {
        if (e.which === 13) {
            searchNumber();
        }
    });
    $('#armaFilter').on('change', function() {
        updateTable();
    });
});

// Обработчик изменения даты
$('#datepicker').on('change', function() {
    initializeEventSource();
    updateTable();
});

$(document).ready(function() {
    $('.export-button').on('click', function(e) {
        e.preventDefault();

        const selectedDate = getSelectedDate();
        const exportAllRPA = $('#showRPAButton').is(':checked');

        const url = `/export_excel?date=${selectedDate}&export_all_rpa=${exportAllRPA}`;

        fetch(url)
            .then(response => {
                if (response.ok) {
                    return response.blob();
                }
                throw new Error('Network response was not ok.');
            })
            .then(blob => {
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = exportAllRPA ? 'RPA_export.xlsx' : `export_${selectedDate}.xlsx`;
                document.body.appendChild(a);
                a.click();
                window.URL.revokeObjectURL(url);
                a.remove();
            })
            .catch(error => {
                console.error('Error:', error);
                alert('Ошибка при экспорте файла');
            });
    });
});
$(document).ready(function() {
    fpInstance = flatpickr("#datepicker", {
        locale: "ru",
        dateFormat: "Y-m-d",
        defaultDate: "today",
        onChange: function(selectedDates, dateStr, instance) {
            updateTable();
            const selected = selectedDates[0];
            if (selected) {
                markUnpackedDays(selected.getFullYear(), selected.getMonth() + 1);
            }
        },
        onMonthChange: function(selectedDates, dateStr, instance) {
            const selected = instance.currentYear && instance.currentMonth !== undefined
                ? new Date(instance.currentYear, instance.currentMonth, 1)
                : new Date();
            markUnpackedDays(selected.getFullYear(), selected.getMonth() + 1);
        },
        onDayCreate: function(dObj, dStr, fp, dayElem) {
            const year = dayElem.dateObj.getFullYear();
            const month = dayElem.dateObj.getMonth() + 1; // getMonth() возвращает 0-11
            const day = dayElem.dateObj.getDate();
            const key = `${year}-${String(month).padStart(2, '0')}`;
            
            if (unpackedDaysCache[key] && unpackedDaysCache[key].has(day)) {
                dayElem.style.backgroundColor = 'lightcoral';
                dayElem.style.color = 'white';
            } else {
                // Сброс стилей для других дней
                dayElem.style.backgroundColor = '';
                dayElem.style.color = '';
            }
        }
    });

    // Инициализируем для текущего месяца
    const today = new Date();
    markUnpackedDays(today.getFullYear(), today.getMonth() + 1);

    initializeEventSource();
    updateTable();
    updateStatistics();
    fetchDeviceStatuses();
    setInterval(fetchDeviceStatuses, 1000); // Интервал 1 секунду (скорректируйте при необходимости)
});


// Обработчик двойного клика для редактирования ячеек
$(document).on('dblclick', '.editable', function() {
    if (isEditing) return; // Предотвращаем многократное редактирование
    isEditing = true;

    var td = $(this);
    var currentValue = td.text();
    var input = $('<input type="text">')
        .val(currentValue)
        .addClass('edit-input');

    td.html(input);
    input.focus();

    input.on('blur', function() {
        var newValue = $(this).val().trim();
        var rowId = td.closest('tr').data('id');
        var columnIndex = td.attr('data-column');

        // Определяем URL для обновления в зависимости от столбца
        let url;
        let updateData = {
            id: rowId,
            new_value: newValue,
            date: getSelectedDate()
        };

        if (columnIndex === '2') { // Столбец "Номер"
            url = '/update_number';
        } else if (columnIndex === '4') { // Столбец "Графа"
            url = '/update_graf';
        } else {
            alert('Неподдерживаемый столбец для редактирования');
            td.text(currentValue);
            isEditing = false;
            return;
        }

        $.ajax({
            url: url,
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify(updateData),
            success: function(response) {
                if (response.status === 'success') {
                    td.text(newValue);
                    checkDuplicates();
                } else {
                    alert('Ошибка при обновлении значения: ' + response.message);
                    td.text(currentValue);
                }
                isEditing = false;
            },
            error: function(xhr, status, error) {
                alert('Ошибка при отправке данных: ' + error);
                td.text(currentValue);
                isEditing = false;
            }
        });
    });

    input.on('keypress', function(e) {
        if (e.which === 13) { // Нажатие Enter
            $(this).blur();
        }
    });

    input.on('keydown', function(e) {
        if (e.key === 'Escape') { // Нажатие Esc
            td.text(currentValue);
            isEditing = false;
        }
    });
});

function searchNumber() {
    const searchValue = $('#searchInput').val().trim();
    if (!searchValue) return;

    // Удаляем предыдущую подсветку
    $('.highlight-search').removeClass('highlight-search');

    const rows = $('#tableBody tr');
    let found = false;

    rows.each(function() {
        const numberCell = $(this).find('td:nth-child(3)');
        const cellText = numberCell.text().trim();
        if (cellText === searchValue) {
            numberCell.addClass('highlight-search');
            
            // Прокрутка к найденной строке
            $('.table-container').animate({
                scrollTop: $(this).offset().top - $('.table-container').offset().top + $('.table-container').scrollTop() - 100
            }, 500);

            found = true;

            // Устанавливаем таймер на удаление подсветки через 3 секунды
            setTimeout(() => {
                numberCell.removeClass('highlight-search');
            }, 3000);

            // Выводим сообщение в консоль для отладки
            console.log('Найдено совпадение:', cellText);

            return false; // Прерываем цикл, так как нашли нужную строку
        }
    });

    if (!found) {
        alert('Точное совпадение номера не найдено');
    }
}

function initiateDeleteConfirmation(rowId) {
    isDeleting = true; // Устанавливаем флаг подтверждения удаления
    var deleteCell = $(`tr[data-id='${rowId}'] td:last-child`);

    // Проверяем, не находится ли уже в состоянии подтверждения
    if (deleteCell.data('confirming')) {
        return; // Если подтверждение уже запрошено, ничего не делаем
    }

    // Устанавливаем флаг подтверждения
    deleteCell.data('confirming', true);

    // Создаем контейнер для кнопок "Да" и "Нет"
    var buttonsContainer = $('<div>').addClass('confirm-buttons');

    var yesButton = $('<button>')
        .text('Да')
        .addClass('confirm-yes-button btn btn-success btn-sm me-2')
        .click(function() {
            deleteConfirmed(rowId);
        });

    var noButton = $('<button>')
        .text('Нет')
        .addClass('confirm-no-button btn btn-secondary btn-sm')
        .click(function() {
            cancelDeleteConfirmation(rowId);
        });

    buttonsContainer.append(yesButton);
    buttonsContainer.append(noButton);

    // Заменяем содержимое ячейки на контейнер с кнопками
    deleteCell.empty();
    deleteCell.append(buttonsContainer);
}

function deleteConfirmed(rowId) {
    $.ajax({
        url: '/delete_row',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({ id: rowId }),
        success: function(response) {
            if (response.status === 'success') {
                // Удаляем строку из таблицы
                $(`tr[data-id='${rowId}']`).remove();
                console.log(`Row with ID ${rowId} deleted successfully.`);

                // Обновляем статистику после удаления записи
                updateStatistics();

                // Обновляем значение DL на клиенте
                loadCurrentDL();
            } else {
                console.error('Ошибка при удалении записи:', response.message);
                // Можно добавить уведомление пользователю о неудаче
            }
            // Восстанавливаем кнопку "Удалить" и сбрасываем флаг
            cancelDeleteConfirmation(rowId);
        },
        error: function(xhr, status, error) {
            console.error('Произошла ошибка при удалении записи:', error);
            // Можно добавить уведомление пользователю о неудаче
            // Восстанавливаем кнопку "Удалить" и сбрасываем флаг
            cancelDeleteConfirmation(rowId);
        }
    });
}

function cancelDeleteConfirmation(rowId) {
    var deleteCell = $(`tr[data-id='${rowId}'] td:last-child`);
    // Сбрасываем флаг подтверждения
    deleteCell.data('confirming', false);
    // Восстанавливаем кнопку "Удалить"
    deleteCell.empty();
    var deleteButton = $('<button>')
        .text('Удалить')
        .addClass('delete-button btn btn-danger btn-sm')
        .click(function() {
            initiateDeleteConfirmation(rowId);
        });
    deleteCell.append(deleteButton);

    isDeleting = false; // Сбрасываем глобальный флаг
}

function loadCurrentDL() {
    const selectedDate = getSelectedDate();
    $.ajax({
        url: '/get_current_dl',
        type: 'GET',
        data: { date: selectedDate },
        success: function(response) {
            if (response.status === 'success') {
                $('#initial-dl').val(response.current_dl);
            } else {
                $('#initial-dl').val(''); // Можно установить значение по умолчанию или показать сообщение
                console.warn(response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error('Ошибка при получении текущего DL:', error);
            $('#initial-dl').val('');
        }
    });
}
function updateInitialDLInput(nextDLNumber) {
    $('#initial-dl').val(nextDLNumber);
}

// Вызываем функцию при загрузке страницы
$(document).ready(function() {
    loadCurrentDL();
});

function markUnpackedDays(year, month, force = false) {
    const key = `${year}-${String(month).padStart(2, '0')}`;

    if (!force && unpackedDaysCache[key] !== undefined) {
        fpInstance.redraw(); // Перерисовываем календарь для применения стилей
        return;
    }

    // Запрашиваем неупакованные дни для выбранного месяца и года
    fetch(`/get_unpacked_days?year=${year}&month=${month}`)
        .then(response => response.json())
        .then(data => {
            if (data.status === 'success') {
                unpackedDaysCache[key] = new Set(data.unpacked_days);
                console.log(`Неупакованные дни для ${key}:`, unpackedDaysCache[key]);
            } else {
                console.error('Ошибка при получении неупакованных дней:', data.message);
                unpackedDaysCache[key] = new Set(); // Инициализируем пустым множеством
            }
            fpInstance.redraw(); // Перерисовываем календарь после обновления данных
        })
        .catch(error => {
            console.error('Ошибка при запросе неупакованных дней:', error);
            unpackedDaysCache[key] = new Set(); // Инициализируем пустым множеством
            fpInstance.redraw();
        });
}
document.addEventListener("DOMContentLoaded", function() {
    flatpickr("#datepicker", {
        dateFormat: "Y-m-d",
        onMonthChange: function(selectedDates, dateStr, instance) {
            // Получаем дни, которые нужно выделить
            fetch(`/get_unpacked_days?year=${instance.currentYear}&month=${instance.currentMonth + 1}`)
                .then(response => response.json())
                .then(data => {
                    instance.set("disable", []);
                    instance.set("enable", []);
                    instance.set("marked", data.unpacked_days.map(day => {
                        return `${instance.currentYear}-${instance.currentMonth + 1}-${day}`;
                    }));
                });
        }
    });
});
function loadRpaCounts(year, month) {
    fetch(`/get_rpa_counts?year=${year}&month=${month}`)
        .then(response => response.json())
        .then(data => {
            if (data.status === 'success') {
                const rpaCounts = data.rpa_counts;
                fpInstance.redraw(); // Перерисовываем календарь

                // Обновляем количество записей RPA для каждого дня
                fpInstance.days.childNodes.forEach(dayElem => {
                    const dateStr = dayElem.dateObj.toISOString().split('T')[0];
                    if (rpaCounts[dateStr]) {
                        dayElem.classList.add('has-rpa-records');
                        dayElem.setAttribute('data-rpa-count', rpaCounts[dateStr]);
                    } else {
                        dayElem.classList.remove('has-rpa-records');
                        dayElem.removeAttribute('data-rpa-count');
                    }
                });

                // Сохраняем количество записей RPA в экземпляре flatpickr
                fpInstance.rpaCounts = rpaCounts;
            } else {
                console.error('Ошибка при получении количества записей RPA:', data.message);
            }
        })
        .catch(error => {
            console.error('Ошибка при запросе количества записей RPA:', error);
        });
}

// Обновляем количество записей RPA при изменении месяца
$('#datepicker').on('change', function() {
    const selectedDate = new Date(fpInstance.selectedDates[0]);
    loadRpaCounts(selectedDate.getFullYear(), selectedDate.getMonth() + 1);
});

// Обновляем количество записей RPA при загрузке страницы
$(document).ready(function() {
    const today = new Date();
    loadRpaCounts(today.getFullYear(), today.getMonth() + 1);
});

// Обновляем количество записей RPA при переходе на другой день
fpInstance.config.onDayCreate = function(dObj, dStr, fp, dayElem) {
    const dateStr = dayElem.dateObj.toISOString().split('T')[0];
    if (fpInstance.rpaCounts && fpInstance.rpaCounts[dateStr]) {
        dayElem.classList.add('has-rpa-records');
        dayElem.setAttribute('data-rpa-count', fpInstance.rpaCounts[dateStr]);
    } else {
        dayElem.classList.remove('has-rpa-records');
        dayElem.removeAttribute('data-rpa-count');
    }
};
$(document).ready(function() {
    // Инициализация календаря
    fpInstance = flatpickr("#datepicker", {
        locale: "ru",
        dateFormat: "Y-m-d",
        defaultDate: "today",
        onChange: function(selectedDates, dateStr, instance) {
            const selectedDate = selectedDates[0];
            if (selectedDate) {
                loadRpaCounts(selectedDate.getFullYear(), selectedDate.getMonth() + 1);
            }
        },
        onMonthChange: function(selectedDates, dateStr, instance) {
            const selectedDate = new Date(dateStr);
            loadRpaCounts(selectedDate.getFullYear(), selectedDate.getMonth() + 1);
        },
        onDayCreate: function(dObj, dStr, fp, dayElem) {
            const dateStr = dayElem.dateObj.toISOString().split('T')[0];
            if (fpInstance.rpaCounts && fpInstance.rpaCounts[dateStr]) {
                dayElem.classList.add('has-rpa-records');
                dayElem.setAttribute('data-rpa-count', fpInstance.rpaCounts[dateStr]);
            } else {
                dayElem.classList.remove('has-rpa-records');
                dayElem.removeAttribute('data-rpa-count');
            }
        }
    });

    // Перетаскивание календаря
    let isDragging = false;
    let dragOffsetX = 0;
    let dragOffsetY = 0;

    $('.flatpickr-calendar').on('mousedown', function(e) {
        isDragging = true;
        dragOffsetX = e.clientX - $(this).offset().left;
        dragOffsetY = e.clientY - $(this).offset().top;
        $(this).css('cursor', 'grabbing');
    });

    $(document).on('mousemove', function(e) {
        if (isDragging) {
            $('.flatpickr-calendar').offset({
                top: e.clientY - dragOffsetY,
                left: e.clientX - dragOffsetX
            });
        }
    });

    $(document).on('mouseup', function() {
        isDragging = false;
        $('.flatpickr-calendar').css('cursor', 'grab');
    });

    // Загрузка данных при загрузке страницы
    const today = new Date();
    loadRpaCounts(today.getFullYear(), today.getMonth() + 1);
});
document.addEventListener('DOMContentLoaded', function() {
    // Инициализация календаря
    const fpInstance = flatpickr("#datepicker", {
        locale: "ru",
        dateFormat: "Y-m-d",
        defaultDate: "today",
        onChange: function(selectedDates, dateStr, instance) {
            updateDLValue(dateStr);
        }
    });

    // Функция для обновления значения DL
    function updateDLValue(dateStr) {
        console.log('Updating DL value for date:', dateStr);
        fetch(`/get_table_data?date=${dateStr}`)
            .then(response => response.json())
            .then(data => {
                if (data.status === 'success') {
                    const rows = data.data;
                    let maxDL = 0;

                    // Ищем максимальное значение DL в таблице
                    rows.forEach(row => {
                        const dlNumber = parseInt(row.dl_number.replace('DL', ''), 10);
                        if (!isNaN(dlNumber) && dlNumber > maxDL) {
                            maxDL = dlNumber;
                        }
                    });

                    // Увеличиваем максимальное значение на единицу
                    const nextDL = maxDL + 1;
                    console.log('Next DL value:', nextDL);
                    document.getElementById('initial-dl').value = `DL${nextDL}`;
                } else {
                    console.warn(data.message);
                }
            })
            .catch(error => {
                console.error('Ошибка при получении данных таблицы:', error);
            });
    }

    // Инициализация значения DL для текущей даты
    const currentDate = new Date().toISOString().split('T')[0];
    updateDLValue(currentDate);
});
document.addEventListener('DOMContentLoaded', function() {
    // Инициализация календаря
    const fpInstance = flatpickr("#datepicker", {
        locale: "ru",
        dateFormat: "Y-m-d",
        defaultDate: "today",
        onChange: function(selectedDates, dateStr, instance) {
            updateStatistics(dateStr);
        }
    });

    // Функция для обновления статистики
    function updateStatistics(dateStr) {
        console.log('Updating statistics for date:', dateStr);
        fetch(`/get_statistics?date=${dateStr}`)
            .then(response => {
                if (!response.ok) {
                    throw new Error('Network response was not ok ' + response.statusText);
                }
                return response.json();
            })
            .then(data => {
                if (data.status === 'success') {
                    console.log('Received statistics:', data);
                    document.getElementById('generateClicks').textContent = data.generate_clicks;
                    document.getElementById('packClicks').textContent = data.pack_clicks;
                    const unpackedClicks = data.rpa_records - data.pack_clicks;
                    console.log('Unpacked clicks:', unpackedClicks);
                    document.getElementById('unpackedClicks').textContent = unpackedClicks;
                } else {
                    console.warn(data.message);
                }
            })
            .catch(error => {
                console.error('Ошибка при получении статистики:', error);
            });
    }

    // Инициализация статистики для текущей даты
    const currentDate = new Date().toISOString().split('T')[0];
    updateStatistics(currentDate);
});

const toggle = document.getElementById("toggle");
const refresh = document.getElementById("refresh");
const theme = window.localStorage.getItem("theme");

/* checks if the theme stored in localStorage is dark
if yes apply the dark theme to the body */
if (theme === "dark") document.body.classList.add("dark");

// event listener stops when the change theme button is clicked
toggle.addEventListener("click", () => {
   document.body.classList.toggle("dark");
   if (theme === "dark") {
     window.localStorage.setItem("theme", "light");
   } else window.localStorage.setItem("theme", "dark");
});

refresh.addEventListener("click", () => {
   window.location.reload();
});
function toggleDeviceStatusWindow() {
    const deviceStatusWindow = $('#deviceStatusWindow');
    if (deviceStatusWindow.hasClass('show')) {
        deviceStatusWindow.removeClass('show');
    } else {
        deviceStatusWindow.addClass('show');
    }
}

$(document).ready(function() {
    // Обработчик для кнопки "Статусы Устройств"
    $('.status-button').on('click', function() {
        toggleDeviceStatusWindow();
    });

    // Обработчик для крестика
    $('.close-button').on('click', function() {
        $('#deviceStatusWindow').removeClass('show');
    });
});
$(document).ready(function() {
    // Обработчик для кнопки "Импорт из Excel"
    $('.import-button').on('click', function() {
        $('#fileInput').click(); // Вызываем клик на скрытом input[type="file"]
    });

    // Обработчик для выбора файла
    $('#fileInput').on('change', function(event) {
        const file = event.target.files[0];
        if (file) {
            const formData = new FormData();
            formData.append('file', file);

            // Отправка файла на сервер
            $.ajax({
                url: '/import_excel', // URL для обработки файла на сервере
                type: 'POST',
                data: formData,
                processData: false,
                contentType: false,
                success: function(response) {
                    if (response.status === 'success') {
                        alert('Файл успешно импортирован');
                        // Обновляем таблицу или выполняем другие действия после импорта
                        updateTable();
                    } else {
                        alert('Ошибка при импорте файла: ' + response.message);
                    }
                },
                error: function(xhr, status, error) {
                    alert('Ошибка при отправке файла: ' + error);
                }
            });
        }
    });
});
$(document).ready(function() {
    // Устанавливаем значение префикса при загрузке страницы
    $('#dl-prefix-input').val('{{ dl_prefix }}');

    // Обработчик события для установки префикса
    $('#set-prefix-button').on('click', function() {
        const newPrefix = $('#dl-prefix-input').val().trim();

        if (newPrefix) {
            $.ajax({
                url: '/set_dl_prefix',
                type: 'POST',
                contentType: 'application/json',
                data: JSON.stringify({ prefix: newPrefix }),
                success: function(response) {
                    if (response.status === 'success') {
                        $('#prefix-notification').text(`Префикс успешно установлен: ${response.new_prefix}`).css('color', 'green');
                    } else {
                        $('#prefix-notification').text(`Ошибка: ${response.message}`).css('color', 'red');
                    }
                },
                error: function(xhr, status, error) {
                    $('#prefix-notification').text(`Ошибка при установке префикса: ${error}`).css('color', 'red');
                }
            });
        } else {
            $('#prefix-notification').text('Пожалуйста, введите префикс.').css('color', 'red');
        }
    });
});
// Функция для отображения общего количества RPA и их количества за всё время
function showRPATotal() {
    $.ajax({
        url: '/get_rpa_records',
        type: 'GET',
        success: function(response) {
            if (response.status === 'success') {
                const totalRPA = response.data.length;
                $('#total-rpa-records').text(totalRPA);
                $('#total-rpa-all-time').text(totalRPA);
                $('#rpa-total-container').show();
                $('.statistics-container').hide();
            } else {
                alert('Ошибка при получении записей RPA: ' + response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error('Ошибка при получении записей RPA:', error);
            alert('Ошибка запроса при получении записей RPA: ' + error);
        }
    });
}

// Функция для скрытия окна с общим количеством RPA и восстановления статистических окон
function hideRPATotal() {
    $('#rpa-total-container').hide();
    $('.statistics-container').show();
}

function showDuplicates() {
    isShowingDuplicates = true;
    const showOnlyRPA = $('#armaFilter').is(':checked');

    $.ajax({
        url: '/get_duplicates',
        type: 'GET',
        success: function(response) {
            if (response.status === 'success') {
                var tableBody = $('#tableBody');
                tableBody.empty();

                // Фильтруем дубликаты, если выбрано "Показать только RPA"
                let filteredData = response.data;
                if (showOnlyRPA) {
                    filteredData = response.data.filter(row => row.arma_type === 'RPA');
                }

                updateTableContent(filteredData, tableBody, false);

                if (globalSortColumn !== null) {
                    sortTable(globalSortColumn, globalSortOrder);
                }

                highlightDuplicates(response.duplicates);
            } else {
                alert('Ошибка при получении дубликатов: ' + response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error('Ошибка при получении дубликатов:', error);
            alert('Ошибка запроса при получении дубликатов: ' + error);
        }
    });
}

// Обработчик события для галочки "Показать только RPA"
$('#armaFilter').on('change', function() {
    if (isShowingDuplicates) {
        showDuplicates();
    }
});

// Обработчик события для кнопки "Показать все RPA"
$('#showRPAButton').on('click', function() {
    isRPAFilterActive = true;
    $.ajax({
        url: '/get_rpa_records',
        type: 'GET',
        success: function(response) {
            if (response.status === 'success') {
                var tableBody = $('#tableBody');
                tableBody.empty();
                updateTableContent(response.data, tableBody, false);

                if (globalSortColumn !== null) {
                    sortTable(globalSortColumn, globalSortOrder);
                }

                checkDuplicates();

                // Показываем общее количество RPA и их количество за всё время
                showRPATotal();
            } else {
                alert('Ошибка при получении записей RPA: ' + response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error('Ошибка при получении записей RPA:', error);
            alert('Ошибка запроса при получении записей RPA: ' + error);
        }
    });
});

// Обработчик события для кнопки "Показать дубликаты"
$('#showDuplicatesButton').on('click', function() {
    showDuplicates();
});

// Обработчик события для галочки "Показать только RPA"
$('#armaFilter').on('change', function() {
    if (isShowingDuplicates) {
        showDuplicates();
    }
});

// Обновление функции updateTable
function updateTable(force = false, highlightNew = false) {
    return new Promise((resolve, reject) => {
        const now = Date.now();
        if (!force && now - lastUpdateTime < updateInterval) {
            resolve();
            return;
        }
        if (isEditing) {
            resolve();
            return;
        }

        if (isEditing || isDeleting) { // Добавляем проверку на isDeleting
            resolve();
            return;
        }

        // Проверьте, если фильтр RPA активен, не выполняйте обновление автоматически
        if (isRPAFilterActive) {
            resolve();
            return;
        }
       
        if (isShowingDuplicates) {
            resolve();
            return;
        }

        $.ajax({
            url: '/get_table_data',
            type: 'GET',
            data: { 
                date: getSelectedDate(),
                filter_arma: getArmaFilter()
            },
            success: function(response) {
                var tableBody = $('#tableBody');

                // Обновляем содержимое таблицы
                updateTableContent(response.data, tableBody, highlightNew);

                // Применяем текущую сортировку
                if (globalSortColumn !== null) {
                    sortTable(globalSortColumn, globalSortOrder);
                }

                updateStatistics();
                lastUpdateTime = now;

                // Проверяем и подсвечиваем дубликаты
                highlightDuplicates(response.duplicates);

                // Скрываем окно с общим количеством RPA и восстанавливаем статистические окна
                hideRPATotal();

                resolve();
            },
            error: function(xhr, status, error) {
                console.error('Error updating table:', error);
                reject(error);
            }
        });
    });
}
document.getElementById('show-all-rpa-button').addEventListener('click', function() {
    var statBoxes = document.querySelectorAll('.stat-box');
    statBoxes.forEach(function(box) {
        box.classList.toggle('hidden');
    });
});
document.getElementById('show-all-rpa-button').addEventListener('click', function() {
    var statBoxes = document.querySelectorAll('.stat-box');
    statBoxes.forEach(function(box) {
        box.classList.toggle('hidden');
    });
});

// Функция для вычисления общего количества RPA
function calculateTotalRPA() {
    var totalRPA = rpaData.length;
    document.getElementById('total-rpa-value').textContent = totalRPA;
}

// Вызываем функцию для вычисления и отображения общего количества RPA
calculateTotalRPA();
ASSISTANT
Чтобы отобразить общее количество записей типа 'RPA' в вашем HTML, необходимо немного изменить как серверный, так и клиентский код. В частности, мы добавим новый маршрут на стороне Flask, который будет возвращать общее количество записей типа 'RPA', а затем обновим JavaScript для вызова этого маршрута и изменения значения в соответствующем элементе интерфейса.

### Шаг 1: Добавить маршрут на сервере

Добавим новый маршрут в ваш Flask-приложение, который будет возвращать общее количество записей типа 'RPA':

```python
@app.route('/get_total_rpa_count', methods=['GET'])
def get_total_rpa_count():
    total_rpa_count = TableRow.query.filter_by(arma_type='RPA').count()
    return jsonify({"status": "success", "total_rpa": total_rpa_count})
```

### Шаг 2: Обновить JavaScript

Теперь мы добавим JavaScript-код, чтобы получить общее количество записей типа 'RPA' после загрузки страницы и обновить соответствующий HTML элемент.

```javascript
function loadTotalRPA() {
    $.ajax({
        url: '/get_total_rpa_count',
        type: 'GET',
        success: function(response) {
            if (response.status === 'success') {
                $('#total-rpa-records').text(response.total_rpa);
                $('#rpa-total-container').show(); // Показываем контейнер
            } else {
                console.error('Ошибка при получении общего количества RPA:', response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error('Ошибка при получение общего количества RPA:', error);
        }
    });
}

// Вызов функции при загрузке страницы
$(document).ready(function() {
    loadTotalRPA();
});
```

### Шаг 3: HTML Разметка

Убедитесь, что у вас есть соответствующая секция в вашем HTML для отображения общего количества записей RPA:

```html
<div id="rpa-total-container" style="display: none;">
    <div class="stat-box">
        <span class="stat-label">Общее количество RPA:</span>
        <span id="total-rpa-records" class="stat-value">0</span>
    </div>
</div>
```

### Итог

Теперь, когда вы загружаете страницу, JavaScript вызовет новый маршрут `/get_total_rpa_count`, который вернет общее количество записей типа 'RPA'. Этот счет будет отображаться в контейнере общего количества RPA, и вы сможете видеть, сколько записей у вас есть в базе данных.

При этом:

- **loadTotalRPA**: Эта функция будет вызываться на событии загрузки страницы и обновит элемент с id `total-rpa-records` значением общего количества RPA.
- **Обновленный маршрут**: Создает новый маршрут на стороне сервера, чтобы получить количество RPA.

Убедитесь, что вы проверяете консоль разработчика в браузере на наличие возможных ошибок или предупреждений, чтобы убедиться, что все работает корректно.

turns-00054.parquet:748

4dde1612fe6be074dc10848e
turn 1/13gpt-4o-mini-2024-07-18EnglishIndia80 words
degenerate_repetitionAbsentFinal dense release
USER
Dear Team, 

The defect ID has been raised for this specific case with the number  PM11051  and this issue has been resloved in UAT and we are doint testing. 


please reframe it
ASSISTANT
Subject: Update on Defect ID PM11051

Dear Team,

I would like to inform you that a defect ID has been raised for this specific case: PM11051. I’m pleased to report that the issue has been resolved in UAT, and we are currently conducting testing.

Best regards,  
[Your Name]  

turns-00054.parquet:749

73abbcfb29bb18c98f0145e3
turn 2/13gpt-4o-mini-2024-07-18EnglishIndia98 words
degenerate_repetitionAbsentFinal dense release
USER
Dear Sir, 

Based on our analysis, we found that the name on the Aadhar document is has low visual quality where the text present and the text appear blur , resucing the sharpness of character edges due to which the orc has provided the inaccurate details.  

Regards
Manish.

please reframe it
ASSISTANT
Subject: Aadhar Document Analysis Findings

Dear Sir,

Upon our analysis, we discovered that the name on the Aadhar document has low visual quality. The text appears blurred, which reduces the sharpness of the character edges. As a result, the OCR may have provided inaccurate details.

Regards,  
Manish

turns-00054.parquet:750

63cb905fb79d96f3fbdd8943
turn 3/13gpt-4o-mini-2024-07-18EnglishIndia270 words
degenerate_repetitionAbsentFinal dense release
USER
[appuser@ip-172-20-16-97 ~]$ aws s3 ls s3://ocr-aadhaar-masking-application-data-uat/

Unable to locate credentials. You can configure credentials by running "aws configure".
ASSISTANT
The error message you're seeing indicates that the AWS CLI (Command Line Interface) on your system cannot find the necessary credentials to access your AWS resources, specifically the S3 bucket you are trying to list. 

To resolve this issue, you'll need to configure your AWS credentials. Here’s how you can do that:

1. **Run AWS Configure Command:**
   Open your terminal and execute the following command:

   ```bash
   aws configure
   ```

2. **Enter Your AWS Credentials:**
   You will be prompted to enter the following information:
   - **AWS Access Key ID:** Your AWS access key ID.
   - **AWS Secret Access Key:** Your AWS secret access key.
   - **Default region name:** The region where your bucket is located (e.g., `us-east-1`).
   - **Default output format:** The output format (e.g., `json`, `text`, or `table`). You may leave this blank for JSON.

3. **Verify Credentials:**
   After configuring, you can verify that the credentials are set correctly by running:

   ```bash
   aws s3 ls
   ```

   This command should now work without throwing an error, and it should list the S3 buckets associated with your credentials.

4. **Check IAM Permissions:**
   Ensure that the IAM user or role associated with the provided AWS credentials has the necessary permissions to access the S3 bucket.

5. **Environment Variables (Optional):**
   As an alternative, you can also set the AWS credentials through environment variables:

   ```bash
   export AWS_ACCESS_KEY_ID=your_access_key_id
   export AWS_SECRET_ACCESS_KEY=your_secret_access_key
   export AWS_REGION=your_region
   ```

By following these steps, you should be able to resolve the "Unable to locate credentials" error and successfully list the contents of your S3 bucket.