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.99ASSISTANT
Ваш код на 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`.