#!/usr/bin/env python # encoding: utf-8 """ @author: tx @file: warning.py @time: 2023/6/15 17:11 @desc: 报警 """ import base64 import io import os import time from datetime import datetime, timedelta from random import random from starlette.responses import FileResponse from PIL import Image import simpleaudio as sa from fastapi import APIRouter, Depends, Request from tortoise import Tortoise from tortoise.functions import Count from tortoise.queryset import Q from tortoise.queryset import QuerySet from conf import setting from helper import login_required, respond_to from helper.tool import parse_datetime from helper.drug import drug_near_already_expired from models import User, Log from models.archive import Archive from pydantic import BaseModel from models.dictionary import Dictionary from models.drug import Drug, DrugStateEnum from models.drug_use_log import DrugUseLog, DrugUseStateEnum from models.template import Template from models.terminal import Terminal from models.cabinet import Cabinet router = APIRouter(prefix='/warning', dependencies=[Depends(login_required)]) async def get_near_expired(archive_id:str = None, page_no: int = 1, page_size: int = 10): query = QuerySet(Drug).filter() if archive_id: query = query.filter(dictionary__archive_id=archive_id) drug_objs = await query.filter(state__in=[DrugStateEnum.IN, DrugStateEnum.OUT]).prefetch_related( 'dictionary', 'template').all() result = await drug_near_already_expired(drug_objs, "near") count = len(result) offset = (page_no - 1) * page_size drug_result = result[offset: offset + page_size] return count, drug_result @router.get('/near_expired', summary="药剂临期") async def index(request: Request,page_no: int = 1, page_size: int = 20): """ 药剂临期信息 - 只查询在库与出库状态药剂 :param page_no: :param page_size: :return: """ archive_id = request.state.archive_id count, drug_result = await get_near_expired(archive_id,page_no, page_size) return respond_to(200, data=dict(count=count, data=drug_result)) async def get_already_expired(archive_id:str = None): query = QuerySet(Drug).filter() if archive_id: query = query.filter(dictionary__archive_id=archive_id) drug_objs = await query.filter(state__in=[DrugStateEnum.IN, DrugStateEnum.OUT]).prefetch_related( 'dictionary', 'template').all() result = await drug_near_already_expired(drug_objs, "already") return result @router.get('/already_expired', summary="药剂过期") async def index(request: Request,page_no: int = 1, page_size: int = 10): """ 药剂过期 - 只查询在库与出库状态药剂 :param page_no: :param page_size: :return: """ archive_id = request.state.archive_id result = await get_already_expired(archive_id) count = len(result) offset = (page_no - 1) * page_size drug_result = result[offset: offset + page_size] return respond_to(200, data=dict(count=count, data=drug_result)) async def get_lack_stock(page_no: int = 1, page_size: int = 10, **kwargs): query = QuerySet(Dictionary).filter() archive_id = kwargs.get("archive_id") if archive_id: query = query.filter(archive_id=archive_id) dictionary_ids = await query.filter().all().values_list("id", flat=True) drug_counts = await Drug.filter(~Q(state=DrugStateEnum.EMPTY), dictionary_id__in=dictionary_ids ).prefetch_related('dictionary').annotate(count=Count('id')).group_by( "dictionary_id").order_by("count").values("dictionary_id", "count", "template_id") data = list() for i in drug_counts: template_obj = await Template.get(id=i.get("template_id")) dictionary_obj = await Dictionary.get(id=i.get("dictionary_id")) lack_stock_count = dictionary_obj.params.get("lack_stock_count") if dictionary_obj.params.get( "lack_stock_count") else 10 if i.get("count") < lack_stock_count: drug_info = await dictionary_obj.attribute_dictionary_info(template_obj) data.append({ "drug_value": drug_info, "drug_info": ",".join(list(map(lambda x:str(x), drug_info.values()))), "count": i.get("count"), "dictionary_id": i.get("dictionary_id") }) count = len(data) offset = (page_no - 1) * page_size result = data[offset: offset + page_size] return count, result @router.get('/lack_stock', summary="试剂库存不足") async def index(request: Request, page_no: int = 1, page_size: int = 10): """ 试剂库存不足 :param page_no: :param page_size: :return: """ # 库存不足阈值 dictionary_obj.params.get("lack_stock_count") archive_id = request.state.archive_id count, result = await get_lack_stock(page_no, page_size, **{"archive_id": archive_id}) return respond_to(data=dict(count=count, data=result)) @router.get('/overdue_return', summary="逾期未归还") async def index(request: Request, page_no: int = 1, page_size: int = 10): """ 逾期未归还 使用期限在终端管理中配置,按照当日固定归还时间或每试剂使用时长 状态为出库状态药剂 最后领用时间与逾期时间比较,超过的为逾期未归还药剂 :param page_no: :param page_size: :return: """ count, result = await overdue_return(request.state.archive_id,page_no, page_size ) return respond_to(200, data=dict(count=count, data=result)) async def overdue_return(archive_id:str = None, page_no: int = 1, page_size: int = 10): # 逾期未归还需要根据大类来获取值 if setting.TERMINAL_ID: cabinet_obj = await Cabinet.filter(terminal_id=setting.TERMINAL_ID).prefetch_related("archive").first() archive_obj = cabinet_obj.archive else: archive_obj = await Archive.get(id=archive_id) return_fixed_at = archive_obj.params.get("return_fixed_at") # 每日几点归还 receive_use_duration = archive_obj.params.get("receive_use_duration") # 领用几小时后归还 query = QuerySet(Drug).filter(state=DrugStateEnum.OUT) archive_id = archive_id if archive_id: query = query.filter(dictionary__archive_id=archive_id) if return_fixed_at: today_begin_time = datetime.now().strftime("%Y-%m-%d 00:00:00") today_end_time = datetime.now().strftime(f"%Y-%m-%d {return_fixed_at}") now_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if now_time < today_end_time: query = query.filter(Q(last_receive_at__lt=today_begin_time)) else: query = query.filter() elif receive_use_duration: sub_time = datetime.now() - timedelta(hours=int(receive_use_duration)) query = query.filter(last_receive_at__lt=sub_time) offset = (page_no - 1) * page_size count = await query.count() drug_objs = await query.limit(page_size).offset(offset) result = list() for drug_obj in drug_objs: drug_info = await drug_obj.attribute_drug_info() result.append({ "drug_value": drug_info, "drug_info": ",".join(list(map(lambda x: str(x), drug_info.values()))), "last_receive_at": parse_datetime(str(drug_obj.last_receive_at), "%Y-%m-%d %H:%M:%S"), "user_name": await drug_obj.attribute_last_user() }) return count, result @router.get('/return_weight', summary="归还未称重") async def index(request: Request,page_no: int = 1, page_size: int = 10): """ 归还未称重 :param page_no: :param page_size: :return: """ offset = (page_no - 1) * page_size query = QuerySet(DrugUseLog).filter(state=DrugUseStateEnum.PUT, alarm_state=1) archive_id = request.state.archive_id if archive_id: query = query.filter(drug__dictionary__archive_id=archive_id) count = await query.count() drug_use_log_objs = await query.prefetch_related("drug").limit(page_size).offset(offset).order_by("-created_at") result = list() for drug_use_log_obj in drug_use_log_objs: # 领用人 领用时间 receive_drug_user_log_obj = await DrugUseLog.filter(drug_id=drug_use_log_obj.drug_id, state=DrugUseStateEnum.TAKE, created_at__lt=drug_use_log_obj.created_at ).order_by("-created_at").first() drug_info = await drug_use_log_obj.drug.attribute_drug_info() result.append({ "drug_value": drug_info, "drug_id": drug_use_log_obj.drug_id, "drug_use_log_id": drug_use_log_obj.id, "drug_info": ",".join(list(map(lambda x:str(x), drug_info.values()))), "return_user": drug_use_log_obj.users, "return_created_at": parse_datetime(str(drug_use_log_obj.created_at), "%Y-%m-%d %H:%M:%S"), "receive_user": receive_drug_user_log_obj.users, "receive_created_at": parse_datetime(str(receive_drug_user_log_obj.created_at), "%Y-%m-%d %H:%M:%S"), "drawer_id": drug_use_log_obj.drawer_id, }) return respond_to(200, data=dict(count=count, data=result)) @router.get('/temperature_overrun', summary="温度超限") async def temperature_overrun(page_no: int = 1, page_size: int = 10): """ 温度超限 :param page_no: :param page_size: :return: """ count_query = """select count(*) num from (SELECT env.id, CONCAT('左温区:', env.left_temperature) as temperature , env.created_at, env.cabinet_id FROM environment_logs as env LEFT JOIN cabinets ON env.cabinet_id = cabinets.id WHERE (env.left_temperature >= JSON_EXTRACT(cabinets.params, '$.temperature[0]')+JSON_EXTRACT(cabinets.params, '$.temp_out') or env.left_temperature <= JSON_EXTRACT(cabinets.params, '$.temperature[0]')-JSON_EXTRACT(cabinets.params, '$.temp_out') ) UNION ALL SELECT env.id, CONCAT(IF(env.temperature_type = 1, '', '右温区:'), env.right_temperature) as temperature , env.created_at, env.cabinet_id FROM environment_logs as env LEFT JOIN cabinets ON env.cabinet_id = cabinets.id WHERE (env.right_temperature >= JSON_EXTRACT(cabinets.params, '$.temperature[1]')+JSON_EXTRACT(cabinets.params, '$.temp_out') or env.right_temperature <= JSON_EXTRACT(cabinets.params, '$.temperature[1]')-JSON_EXTRACT(cabinets.params, '$.temp_out') )) temp""" query = """select id, temperature, created_at, cabinet_id from (SELECT env.id, CONCAT('左温区:', env.left_temperature) as temperature , env.created_at, env.cabinet_id FROM environment_logs as env LEFT JOIN cabinets ON env.cabinet_id = cabinets.id WHERE (env.left_temperature >= JSON_EXTRACT(cabinets.params, '$.temperature[0]')+JSON_EXTRACT(cabinets.params, '$.temp_out') or env.left_temperature <= JSON_EXTRACT(cabinets.params, '$.temperature[0]')-JSON_EXTRACT(cabinets.params, '$.temp_out') ) UNION ALL SELECT env.id, CONCAT(IF(env.temperature_type = 1, '', '右温区:'), env.right_temperature) as temperature , env.created_at, env.cabinet_id FROM environment_logs as env LEFT JOIN cabinets ON env.cabinet_id = cabinets.id WHERE (env.right_temperature >= JSON_EXTRACT(cabinets.params, '$.temperature[1]')+JSON_EXTRACT(cabinets.params, '$.temp_out') or env.right_temperature <= JSON_EXTRACT(cabinets.params, '$.temperature[1]')-JSON_EXTRACT(cabinets.params, '$.temp_out') )) temp ORDER BY created_at desc""" offset = (page_no - 1) * page_size page_method = f" limit {page_size} offset {offset}" query = query + page_method conn = Tortoise.get_connection(connection_name='default') count_result = await conn.execute_query(count_query) page_list = await conn.execute_query(query) conn.close() data = [] for item in page_list[1]: cabinet_obj = await Cabinet.get(id=item.get("cabinet_id")) item.setdefault("cabinet_name", cabinet_obj.label) data.append(item) return respond_to(200, data=dict(count=count_result[1][0].get("num"), data=data)) class LogModel(BaseModel): kind: str comment: str cabinet_id: str user_id: str raw: str # base64的人脸图片 @router.post('/take_out_err_record', summary='非法领用报警记录') async def take_out_err_record(request: Request, model: LogModel): print("非法领用报警记录...") # 针对 抽屉未关闭/试剂放错位置 新增接口 user_id = model.user_id if user_id: user = await User.get(id=user_id).only('name') username = user.name else: username = None bin = base64.b64decode(model.raw) image = Image.open(io.BytesIO(bin)) img_name = str(int(time.time()*10000)) + '.jpg' save_path = os.path.join(os.getcwd(), "static", "take_out_err_record_img") if not os.path.exists(save_path): os.umask(0) os.makedirs(save_path) image.save(os.path.join(save_path, img_name), 'JPEG') await Log.create(**model.dict(), users=username,img_name=img_name) print("非法领用报警播报...") # TODO: 警报声音 wave_obj = sa.WaveObject.from_wave_file("static/warning_message.wav") play_obj = wave_obj.play() # play_obj.wait_done() # 等待直到声音播放完毕 return respond_to() # 获取 非法领用试剂 拍摄图片 @router.get("/get_take_out_err_record_img/{img_name}") def get_take_out_err_record_img(img_name): print( "get_take_out_err_record_img::::::") save_path = os.path.join(os.getcwd(), "static", "take_out_err_record_img") if not os.path.exists(save_path): os.umask(0) os.makedirs(save_path) file_path = os.path.join(save_path, img_name) return FileResponse(file_path)