from datetime import datetime
from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from supply import models
from supply.views import set_copy_message

# 查看库存
def inventory_display(request):
    a = models.Gongchangkucun.objects.values('facid_id', 'maid_id').distinct()
    q = []
    for i in a:
        q.append(
            models.Gongchangkucun.objects.filter(facid_id=i['facid_id']
                                                 , maid_id=i['maid_id']).order_by('-updatetime').first()
        )
        # 查询展示每个工厂物料对应的最新的库存信息
    return render(request, 'inventory_display.html', {"queryset": q, "title": "库存展示"})

def inventory_detail(request, uid):  # 路径传参要写进函数参数中
    m = models.Gongchangkucun.objects.filter(id=uid).first()
    nowdata = []
    nowdata.append(m.inventoryonroad)
    nowdata.append(m.inventorytemp)
    nowdata.append(m.inventoryunrest)
    nowdata.append(m.inventoryfreeze)
    fid = m.facid
    mid = m.maid
    qu = models.Gongchangkucun.objects.filter(facid_id=fid, maid_id=mid).order_by("-updatetime")[:6]

    q = qu[::-1]
    datelist = []
    l1 = []
    l2 = []
    l3 = []
    l4 = []
    for i in q:
        datelist.append(str(i.updatetime.strftime("%Y-%m-%d %H:%M:%S")))
        l1.append(i.inventoryonroad)
        l2.append(i.inventorytemp)
        l3.append(i.inventoryunrest)
        l4.append(i.inventoryfreeze)
    result = {
        "history": q, "now": m, "nowdata": nowdata,
        "datelist": datelist,
        "l1": l1,
        "l2": l2,
        "l3": l3,
        "l4": l4,
        "title": "库存详情",
    }
    return render(request, 'inventory_detail.html', result)

# 请购单列表
def inventory_demand(request, did):
    """请购单管理（请购单）"""
    material = models.Wuliao.objects.filter().all()
    factory = models.Gongchang.objects.filter().all()
    caigou = models.Caigouxuqiu.objects.filter(demandid=did)
    if did == "n":
        caigou = models.Caigouxuqiu.objects.filter(isdelete=0).all()
    result = {
        "material": material,
        "factory": factory,
        "caigou": caigou
        , "title": "请购单管理"
    }
    return render(request, 'inventory_demand.html', result)

""" 表单数据验证+报错信息 """

def form_item_check(context, type="nan"):
    if context.strip() == "":
        if type[:2] == "id":
            return "请设置" + type[2:]
        return "必填字段，内容不能为空"
    need = {"int": "整数", "int+": "正整数", "float": "数字", "float+": "正数"}
    if not type == "nan" and not type[:2] == "id":
        ctx = context
        if context[0] == "-":
            ctx = context[1:]
        if ctx.count(".") > 1 or not ctx[0].isdigit() or not ctx.replace(".", "").isdigit():
            return "输入错误,请输入" + need[type]
        if type[-1] == "+" and float(context) <= 0:
            return "输入错误,请输入" + need[type]
    return True

# 添加请购单
def demand_add(request):
    """添加请购单"""
    n = 10000000
    if models.Caigouxuqiu.objects.all().first():
        n = models.Caigouxuqiu.objects.all().order_by('-demandid').first().demandid[2:]
    did = "de" + str(int(n) + 1)  # 编号递增，这样计算避免删除后出现错误
    time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    r = request.POST
    id = request.session["info"]['id']
    # 设置表单数据校验
    toCheck = [r['facid_id'], r['maid_id'], r['price'], r['tcount']]  # 校验字段
    types = ["id工厂编号", "id物料编号", "float+", "int+"]  # 校验类型
    errors = []  # 校验结果
    returnStatus = True  # 是否通过所有校验
    for idx, (cck, typ) in enumerate(zip(toCheck, types)):
        result = form_item_check(cck, typ)
        errors.append(result)
        if not result == True:
            returnStatus = False
    if returnStatus:  # 校验成功才执行插入
        models.Caigouxuqiu.objects.create(demandid=did, price=r["price"], tcount=r['tcount']
                                          , status=0, createtime=time, createuserid_id=id,
                                          facid_id=r['facid_id'], maid_id=r['maid_id'])
        # 自动发信功能
        # 1，确认员工身份，是管理员或库存经理则不发信  否则 向本公司的库存经理发信
        me = models.Yuangong.objects.filter(id=id).first()
        message = []
        notify = []
        notify.append(dict(id=0, tittle="提示", context="请购单创建成功！", type="success", position="top-center"))
        wl = models.Wuliao.objects.filter(id=r['maid_id']).first()
        if me.office != "5" and me.office != "0" and me.isactive == 1:
            message.append("【系统消息】申请审核")
            message.append(
                '待审核请购单：{}
采购物料：{}({})
采购数量：{}
预期采购价：{}元/{}
请前往审核>>'
                .format(set_copy_message(did), wl.desc, wl.id, r['tcount'], r['price'], wl.calcutype))
            to = models.Yuangong.objects.filter(businessid=me.businessid, office="5").first()
            for m in message:
                models.Xiaoxi.objects.create(fromId_id=me.id, toId_id=to.id, time=datetime.now(), context=m, read=0)
            notify.append(
                dict(id=1, tittle="系统消息", context="已向【{}】{}发送请购单审核申请".format(to.get_office_display(), to.username),
                     type="info", position="top-center"))
        else:
            message.append("【系统消息】操作历史记录")
            message.append(
                '待审核请购单：{}
采购物料：{}({})
采购数量：{}
预期采购价：{}元/{}
下一步骤前往审核>>'
                    .format(set_copy_message(did), wl.desc, wl.id, r['tcount'], r['price'], wl.calcutype))
            fromid = models.Yuangong.objects.filter(businessid=me.businessid, office="7").first()
            for m in message:
                models.Xiaoxi.objects.create(fromId_id=fromid.id, toId_id=me.id, time=datetime.now(), context=m, read=0)
            notify.append(dict(id=1, tittle="系统消息", context="操作历史已更新", type="info", position="top-center"))
        if request.session["produceActive"]:
            message = []
            message.append("【系统消息】操作历史记录")
            next = me
            next = models.Yuangong.objects.filter(businessid=me.businessid, office="5").first()
            message.append(
                '【{}】{}
创建请购单：{}
采购物料：{}({})
采购数量：{}
预期采购价：{}元/{}
'
                '下一步操作人:【{}】{}
'
                '下一步骤:审核请购单>>
'
                    .format(me.get_office_display(), me.username, set_copy_message(did), wl.desc, wl.id, r['tcount'],
                            r['price'], wl.calcutype, next.get_office_display(), next.username))
            fromid = models.Yuangong.objects.filter(businessid=me.businessid, office="7").first()
            toid = models.Yuangong.objects.filter(businessid=me.businessid, office="6").first()
            for m in message:
                models.Xiaoxi.objects.create(fromId_id=fromid.id, toId_id=toid.id, time=datetime.now(), context=m,
                                             read=0)
            notify.append(dict(id=len(notify), tittle="系统消息",
                               context="操作历史已抄送至 【{}】{}".format(toid.get_office_display(), toid.username),
                               type="info", position="top-center"))
        request.session["notify"] = notify
    return JsonResponse({"status": returnStatus, "error": errors})

@csrf_exempt
def demand_create(request):
    r = request.POST
    # 设置表单数据校验
    toCheck = [r['volume'], r['price']]  # 校验字段
    types = ["int+", "int+"]  # 校验类型
    errors = []  # 校验结果
    returnStatus = True  # 是否通过所有校验
    for idx, (cck, typ) in enumerate(zip(toCheck, types)):
        result = form_item_check(cck, typ)
        errors.append(result)
        if not result == True:
            returnStatus = False
    if returnStatus:
        message, notify = [], []
        notify.append(dict(id=0, tittle="提示", context="采购需求创建成功！", type="success", position="top-center"))
        wl = models.Wuliao.objects.filter(id=r['maid']).first()
        gc = models.Gongchang.objects.filter(id=r["facid"]).first()
        me = models.Yuangong.objects.filter(id=r['meid']).first()
        yg = models.Yuangong.objects.filter(businessid=me.businessid, office="3").first()
        message.append("【系统消息】物料请购需求")
        message.append(
            '需求工厂：{} | {} | {}
需求物料：{}({})
需求数量：{}{}
预期采购价：{}元/{}'
            '
请前往创建请购单>>'
                .format(gc.id, gc.type, gc.address,
                        wl.desc, wl.id, r['volume'], wl.calcutype, r['price'], wl.calcutype))
        for m in message:
            models.Xiaoxi.objects.create(fromId_id=me.id, toId_id=yg.id, time=datetime.now(), context=m, read=0)
        notify.append(dict(id=1, tittle="系统消息", context="已向【{}】{}发送物料采购申请".format(yg.get_office_display(), yg.username),
                           type="info", position="top-center"))
        request.session["notify"] = notify
    return JsonResponse({"status": returnStatus, "error": errors})

@csrf_exempt
def demand_verify(request):
    """审核请购单"""
    did = request.POST.get("did")
    time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    id = request.session["info"]['id']
    cgd = models.Caigouxuqiu.objects.filter(demandid=did)
    cgd.update(verifytime=time, verifyuserid_id=id, status=1)
    # 自动发信功能
    # 1，确认员工身份，是管理员则不发信 否则 向请购单对应的员工发信
    me = models.Yuangong.objects.filter(id=id).first()
    message = []
    notify = []
    notify.append(dict(id=0, tittle="提示", context="请购单审核成功！", type="success", position="top-center"))
    cgxq = cgd.first()
    wl = models.Wuliao.objects.filter(id=cgxq.maid_id).first()
    if me.office != "0" and me.isactive == 1:
        message.append("【反馈消息】请购单审核反馈")
        message.append("请购单{}
已审核通过"
                       .format(set_copy_message(did)))
        to = models.Yuangong.objects.filter(id=cgxq.createuserid_id).first()
        if me.id != to.id:
            for m in message:
                models.Xiaoxi.objects.create(fromId_id=me.id, toId_id=to.id, time=datetime.now(), context=m, read=0)
        message[1] = "新增已审核请购单：
请购单{}
采购物料：{}({})
采购数量：{}
预期采购价：{}元/{}
已审核通过" \
            .format(set_copy_message(did), wl.desc, wl.id, cgxq.tcount, cgxq.price, wl.calcutype)
        message.append('请尽快前往创建询价单>>')
        pur_yg = models.Yuangong.objects.filter(businessid=me.businessid, isactive=1, office="2").first()
        for m in message:
            models.Xiaoxi.objects.create(fromId_id=me.id, toId_id=pur_yg.id, time=datetime.now(), context=m, read=0)
        notify.append(dict(id=1, tittle="系统消息", context="已向 【{}】{} 发送请购单审核回执"
                           .format(to.get_office_display(), to.username),
                           type="info", position="top-center"))
        notify.append(dict(id=2, tittle="系统消息", context="已向 【{}】{} 发送询价单创建提示"
                           .format(pur_yg.get_office_display(), pur_yg.username),
                           type="info", position="top-center"))
    else:
        message.append("【系统消息】操作历史记录")
        message.append("请购单{}
采购物料：{}({})
采购数量：{}
预期采购价：{}元/{}
已审核通过"
                       .format(set_copy_message(did), wl.desc, wl.id, cgxq.tcount, cgxq.price, wl.calcutype))
        message.append('请尽快前往创建询价单>>')
        fromid = models.Yuangong.objects.filter(businessid=me.businessid, office="7").first()
        for m in message:
            models.Xiaoxi.objects.create(fromId_id=fromid.id, toId_id=me.id, time=datetime.now(), context=m, read=0)
        notify.append(dict(id=1, tittle="系统消息", context="操作历史已更新", type="info", position="top-center"))
    if request.session["produceActive"]:
        message = []
        message.append("【系统消息】操作历史记录")
        next = me
        next = models.Yuangong.objects.filter(businessid=me.businessid, office="2").first()
        message.append(
            '【{}】{}
审核请购单:{}
采购物料：{}({})
采购数量：{}
预期采购价：{}元/{}
'
            '下一步操作人:【{}】{}
'
            '下一步骤:引用请购单，创建询价单>>
'
                .format(me.get_office_display(), me.username, set_copy_message(cgxq.demandid), wl.desc, wl.id,
                        cgxq.tcount, cgxq.price, wl.calcutype,
                        next.get_office_display(), next.username))
        fromid = models.Yuangong.objects.filter(businessid=me.businessid, office="7").first()
        toid = models.Yuangong.objects.filter(businessid=me.businessid, office="6").first()
        for m in message:
            models.Xiaoxi.objects.create(fromId_id=fromid.id, toId_id=toid.id, time=datetime.now(), context=m, read=0)
        notify.append(dict(id=len(notify), tittle="系统消息",
                           context="操作历史已抄送至 【{}】{}".format(toid.get_office_display(), toid.username),
                           type="info", position="top-center"))
    request.session["notify"] = notify
    return JsonResponse({"status": True})

# 进行暂存检查
def inventory_temp(request):
    """暂存检查"""
    # 首先查到所有待暂存的采购订单

    q = models.Zanshoudan.objects.filter(isdelete=0).all()
    return render(request, 'temp_list.html', {"queryset": q, "title": "暂存管理"})

def ischeck(request, pid, notify):
    """判断是否检查完成"""
    z = models.Zanshoudan.objects.filter(purchaseid_id=pid)
    flag = False
    # 先判断是否有检查不通过
    if z.first().qualitycheckinfo == 0 or z.first().quantitycheckinfo == 0:
        n = 10000000
        createtime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        if models.Zanshoudan.objects.all().first():
            n = models.Zanshoudan.objects.all().order_by('-temid').first().temid[2:]
        zid = "te" + str(int(n) + 1)
        z.update(temid=zid, isreceived=0, createtime=createtime)
        # 更新库存信息,此情况下，库存为冻结库存
        fid = z.first().purchaseid.quoteid.inquiryid.demandid.facid_id
        mid = z.first().purchaseid.quoteid.inquiryid.demandid.maid_id
        tcount = z.first().purchaseid.quoteid.inquiryid.demandid.tcount
        # 更新采购单和暂存单的状态为冻结状态
        models.Zanshoudan.objects.filter(temid=zid).update(isreceived=-1)
        models.Caigoudan.objects.filter(purchaseid=pid).update(iscomplete=-1)

        if z.first().qualitycheckinfo:
            notify.append(
                dict(id=1, tittle="提示", context="暂存单 {} 量检不通过，转为冻结库存".format(zid), type="error", position="top-center"))
        if not z.first().qualitycheckinfo:
            notify.append(
                dict(id=1, tittle="提示", context="暂存单 {} 质检不通过，转为冻结库存".format(zid), type="error", position="top-center"))
        w = models.Gongchangkucun.objects.filter(facid_id=fid, maid_id=mid).order_by("-updatetime").first()
        if w:
            models.Gongchangkucun.objects.create(facid_id=fid, maid_id=mid, inventorytemp=w.inventorytemp
                                                 , inventoryonroad=w.inventoryonroad - tcount,
                                                 inventoryfreeze=w.inventoryfreeze + tcount,
                                                 inventoryunrest=w.inventoryunrest
                                                 , updatetime=createtime)
        else:
            models.Gongchangkucun.objects.create(facid_id=fid, maid_id=mid, inventorytemp=0
                                                 , inventoryonroad=0,
                                                 inventoryfreeze=tcount, inventoryunrest=0
                                                 , updatetime=createtime)
        return notify
    # 质检量检均完成后，才算完成
    if z.first().quantitycheckinfo != -1 and z.first().qualitycheckinfo != -1:
        n = 10000000
        createtime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        if models.Zanshoudan.objects.all().first():
            n = models.Zanshoudan.objects.all().order_by('-temid').first().temid[2:]
        zid = "te" + str(int(n) + 1)  # 编号递增，这样计算避免删除后出现错误
        z.update(temid=zid, isreceived=0, createtime=createtime)
        # 更新库存信息
        fid = z.first().purchaseid.quoteid.inquiryid.demandid.facid_id
        mid = z.first().purchaseid.quoteid.inquiryid.demandid.maid_id
        tcount = z.first().purchaseid.quoteid.inquiryid.demandid.tcount
        w = models.Gongchangkucun.objects.filter(facid_id=fid, maid_id=mid).order_by("-updatetime").first()
        if w:
            models.Gongchangkucun.objects.create(facid_id=fid, maid_id=mid, inventorytemp=tcount + w.inventorytemp
                                                 , inventoryonroad=w.inventoryonroad - tcount,
                                                 inventoryfreeze=w.inventoryfreeze, inventoryunrest=w.inventoryunrest
                                                 , updatetime=createtime)
        else:
            models.Gongchangkucun.objects.create(facid_id=fid, maid_id=mid, inventorytemp=tcount
                                                 , inventoryonroad=0,
                                                 inventoryfreeze=0, inventoryunrest=0
                                                 , updatetime=createtime)
        # 暂时生成入库单,单号等于暂存单号
        id = request.session["info"]['id']
        models.Rukudan.objects.create(id=zid, createusersid_id=id,
                                      temid_id=zid)
        flag = True
        # 系统自动发信
        me = models.Yuangong.objects.filter(id=id).first()
        zcd = z.first()
        cgd = zcd.purchaseid
        yg = cgd.createuserid
        gys = cgd.quoteid.inquiryid.supplyid
        fac = cgd.quoteid.inquiryid.demandid.facid
        wl = cgd.quoteid.inquiryid.demandid.maid
        jl = models.Yuangong.objects.filter(businessid=me.businessid, isactive=1, office="5").first()
        notify.append(dict(id=1, tittle="提示", context="暂存单 {} 通过【质检】及【量检】".format(zcd.temid), type="success",
                           position="top-center"))
        notify.append(
            dict(id=2, tittle="系统消息", context="向 【{}】{} 发信反馈暂存单检查完成".format(yg.get_office_display(), yg.username),
                 type="info", position="top-center"))
        notify.append(
            dict(id=3, tittle="系统消息", context="已提示 【{}】{} 前往创建入库单".format(jl.get_office_display(), jl.username),
                 type="info", position="top-center"))
        message = []
        message.append("【反馈消息】暂存单反馈信息")
        message.append("供应商:{}({})
收货工厂:{}({})
采购订单:{}
采购物料:{}({})
采购数量:{}{}
采购价格:{}元/{}"
                       .format(gys.name, gys.id, fac.type, fac.address, set_copy_message(cgd.purchaseid), wl.desc,
                               wl.id, cgd.quoteid.inquiryid.demandid.tcount, wl.calcutype, cgd.quoteid.quote,
                               wl.calcutype))
        mes = "
" + zcd.moreinfo
        if mes.strip() == "
":
            mes = " 无备注"
        message.append("暂存单号:{}
暂存检查情况：
量检通过-质检通过
备注：{}".format(set_copy_message(zcd.temid), mes))
        if me.office != "0" and not me.office == "5":
            for m in message:
                models.Xiaoxi.objects.create(fromId_id=me.id, toId_id=yg.id, time=datetime.now(), context=m, read=0)
            message[0] = "【系统消息】暂存单通过检查，等待入库"
            message.append('请核验物料入库数量并创建入库单>>'.format())
            for m in message:
                models.Xiaoxi.objects.create(fromId_id=me.id, toId_id=jl.id, time=datetime.now(), context=m, read=0)
        else:
            message[0] = "【系统消息】操作历史记录"
            message.append('请核验物料入库数量并创建入库单>>')
            fromid = models.Yuangong.objects.filter(businessid=me.businessid, office="7").first()
            for m in message:
                models.Xiaoxi.objects.create(fromId_id=fromid.id, toId_id=me.id, time=datetime.now(), context=m, read=0)
            notify = notify[:2]
            notify.append(dict(id=len(notify), tittle="系统消息", context="操作历史已更新", type="info", position="top-center"))
        if request.session["produceActive"]:
            message[1] = '【{}】{}
完成暂存物料检查
'.format(me.get_office_display(), me.username) + message[1]
            next = me
            if me.office != "0":
                next = models.Yuangong.objects.filter(businessid=me.businessid, office="5").first()
            message = message[:-1]
            message.append('下一步操作人:【{}】{}
'
                           '下一步骤:核验物料入库数量并创建入库单>>'
                           .format(next.get_office_display(), next.username))
            fromid = models.Yuangong.objects.filter(businessid=me.businessid, office="7").first()
            toid = models.Yuangong.objects.filter(businessid=me.businessid, office="6").first()
            for m in message:
                models.Xiaoxi.objects.create(fromId_id=fromid.id, toId_id=toid.id, time=datetime.now(), context=m,
                                             read=0)
            notify.append(dict(id=len(notify), tittle="系统消息",
                               context="操作历史已抄送至 【{}】{}".format(toid.get_office_display(), toid.username),
                               type="info", position="top-center"))
    return notify

def quality_check(request):
    """质检"""
    puid = request.GET.get("puid")
    q = request.POST.get("check")
    info = request.POST.get("moreinfo")
    state_dict = {"0": "不通过", "1": "通过"}
    z = models.Zanshoudan.objects.filter(purchaseid_id=puid)
    if z.first().moreinfo:
        info = info + z.first().moreinfo
    z.update(qualitycheckinfo=q, moreinfo=info)
    notify = [dict(id=0, tittle="系统消息", type="success", position="top-center",
                   context="暂存单质检成功，质检状态【{}】".format(state_dict[q]))]
    request.session["notify"] = ischeck(request, puid, notify)
    return JsonResponse({"status": True})

def quantity_check(request):
    """量检"""
    puid = request.GET.get("puid")
    q = request.POST.get("check")
    info = request.POST.get("moreinfo")
    state_dict = {"0": "不通过", "1": "通过"}
    z = models.Zanshoudan.objects.filter(purchaseid_id=puid)
    if z.first().moreinfo:
        info = info + z.first().moreinfo
    z.update(quantitycheckinfo=q, moreinfo=info)
    notify = [dict(id=0, tittle="系统消息", type="success", position="top-center",
                   context="暂存单量检成功，量检状态【{}】".format(state_dict[q]))]
    request.session["notify"] = ischeck(request, puid, notify)
    return JsonResponse({"status": True})

# 展示入库单
def inventory_receive(request):
    """入库管理"""
    q = models.Rukudan.objects.filter(isdelete=0).all()

    return render(request, 'inventory_receive.html', {"queryset": q, "title": "入库管理"})

# 添加入库
def receive_add(request):
    """添加入库"""
    tid = request.GET.get("tid")
    t = models.Rukudan.objects.filter(temid_id=tid)
    fid = t.first().temid.purchaseid.quoteid.inquiryid.demandid.facid_id
    mid = t.first().temid.purchaseid.quoteid.inquiryid.demandid.maid_id
    pid = t.first().temid.purchaseid_id
    receivecount = request.POST.get("receivecount")
    if not receivecount:
        return JsonResponse({"status": False, "error": "实际入库数量不能为空"})
    if float(receivecount) < 0:
        return JsonResponse({"status": False, "error": "实际入库数量不能为负数"})
    pcount = t.first().temid.purchaseid.quoteid.inquiryid.demandid.tcount
    moreinfo = request.POST.get("moreinfo")
    id = request.session["info"]['id']
    time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    # 生成入库单
    n = 10000000
    if models.Rukudan.objects.all().first():
        n = models.Rukudan.objects.all().order_by('-id').first().id[2:]
    wid = "wa" + str(int(n) + 1)  # 编号递增，这样计算避免删除后出现错误
    t.update(id=wid, createtime=time, receivecount=receivecount,
             moreinfo=moreinfo, createusersid_id=id)

    # 库存状态更新
    w = models.Gongchangkucun.objects.filter(facid_id=fid, maid_id=mid).order_by("-updatetime").first()
    if w:
        models.Gongchangkucun.objects.create(facid_id=fid, maid_id=mid, inventorytemp=w.inventorytemp - pcount
                                             , inventoryonroad=w.inventoryonroad,
                                             inventoryfreeze=w.inventoryfreeze,
                                             inventoryunrest=w.inventoryunrest + float(receivecount)
                                             , updatetime=time)
    else:
        models.Gongchangkucun.objects.create(facid_id=fid, maid_id=mid, inventorytemp=0
                                             , inventoryonroad=0,
                                             inventoryfreeze=0, inventoryunrest=receivecount
                                             , updatetime=time)

    # 暂存单，采购单收货状态更新

    models.Zanshoudan.objects.filter(temid=tid).update(isreceived=1)
    models.Caigoudan.objects.filter(purchaseid=pid).update(iscomplete=1)

    notify, message = [], []
    me = models.Yuangong.objects.filter(id=request.session["info"]["id"]).first()
    yg = models.Yuangong.objects.filter(isactive=1, businessid_id=me.businessid_id, office="3").first()
    jl = models.Yuangong.objects.filter(isactive=1, businessid_id=me.businessid_id, office="6").first()
    zcd = models.Zanshoudan.objects.filter(temid=tid).first()
    cgd = zcd.purchaseid
    gys = cgd.quoteid.inquiryid.supplyid
    fac = cgd.quoteid.inquiryid.demandid.facid
    wl = cgd.quoteid.inquiryid.demandid.maid
    notify.append(
        dict(id=0, tittle="提示", context="入库单 {} 创建成功".format(zcd.temid), type="success", position="top-center"))
    notify.append(dict(id=1, tittle="系统消息", context="向 【{}】{} 发信反馈入库完成".format(yg.get_office_display(), yg.username),
                       type="info", position="top-center"))
    notify.append(
        dict(id=2, tittle="系统消息", context="向 【{}】{} 发信反馈物料需求入库完成".format(jl.get_office_display(), jl.username),
             type="info", position="top-center"))
    message = []
    message.append("【系统消息】物料需求成功入库")
    message.append("供应商:{}({})
收货工厂:{}({})
采购物料:{}({})
采购数量:{}{}
采购价格:{}元/{}"
                   .format(gys.name, gys.id, fac.type, fac.address, wl.desc, wl.id,
                           cgd.quoteid.inquiryid.demandid.tcount, wl.calcutype, cgd.quoteid.quote, wl.calcutype))
    msg1, msg2 = "
" + zcd.moreinfo, "
" + moreinfo
    if msg1.strip() == "
":
        msg1 = "无备注"
    if msg2.strip() == "
":
        msg2 = "无备注"
    message.append("物料检查情况：
量检通过-质检通过
备注：{}".format(msg1))
    message.append("物料入库情况：
入库数量:{}{}
备注：{}".format(receivecount, wl.calcutype, msg2))
    message.append('查看订单单据流>>'.format(cgd.purchaseid))
    for m in message:
        models.Xiaoxi.objects.create(fromId_id=me.id, toId_id=jl.id, time=datetime.now(), context=m, read=0)
    if me.office != "0":
        message[0] = "【反馈消息】物料需求入库反馈"
        for m in message[:-1]:
            models.Xiaoxi.objects.create(fromId_id=me.id, toId_id=yg.id, time=datetime.now(), context=m, read=0)
    else:
        message[0] = "【系统消息】操作历史记录"
        next = me
        fromid = models.Yuangong.objects.filter(businessid=me.businessid, office="7").first()
        for m in message:
            models.Xiaoxi.objects.create(fromId_id=fromid.id, toId_id=me.id, time=datetime.now(), context=m, read=0)
        notify = notify[:1]
        notify.append(dict(id=1, tittle="系统消息", context="操作历史已更新", type="info", position="top-center"))
    if request.session['produceActive']:
        message[0] = "【系统消息】采购订单入库"
        message[1] = '【{}】{}
'.format(me.get_office_display(), me.username) + message[1]
        next = me
        if me.office != "0":
            next = models.Yuangong.objects.filter(businessid=me.businessid, office="5").first()
        message.append('下一步操作人:【{}】{}
'
                       '下一步骤:开具发票>>
'
                       .format(next.get_office_display(), next.username))
        fromid = models.Yuangong.objects.filter(businessid=me.businessid, office="7").first()
        toid = models.Yuangong.objects.filter(businessid=me.businessid, office="6").first()
        for m in message:
            models.Xiaoxi.objects.create(fromId_id=fromid.id, toId_id=toid.id, time=datetime.now(), context=m,
                                         read=0)
        notify.append(dict(id=len(notify), tittle="系统消息",
                           context="操作历史已抄送至 【{}】{}".format(toid.get_office_display(), toid.username),
                           type="info", position="top-center"))
    request.session["notify"] = notify

    return JsonResponse({"status": True})

# 展示发票
def inventory_invoice(request):
    q = models.Fapiao.objects.all()

    return render(request, 'inventory_invoice.html', {"queryset": q, "title": "发票管理"})

# 添加发票
def invoice_add(request):
    tid = request.GET.get("tid")
    t = models.Rukudan.objects.filter(temid_id=tid)
    pid = t.first().temid.purchaseid_id
    sid = t.first().temid.purchaseid.quoteid.inquiryid.supplyid_id
    fee = request.POST.get("receivecount")
    if not fee:
        return JsonResponse({"status": False, "error": "运费不能为空"})
    if float(fee) < 0:
        return JsonResponse({"status": False, "error": "运费不能为负数"})
    pcount = t.first().temid.purchaseid.quoteid.inquiryid.demandid.tcount
    price = t.first().temid.purchaseid.quoteid.quote
    id = request.session["info"]['id']
    time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    totalcount = pcount
    fee = round(float(fee), 2)
    money = round(float(totalcount) * float(price), 2)
    moreinfo = request.POST.get("moreinfo")

    n = 10000000
    if models.Fapiao.objects.all().first():
        n = models.Fapiao.objects.all().order_by('-invoiceid').first().invoiceid[2:]
    ivid = "iv" + str(int(n) + 1)  # 编号递增，
    models.Fapiao.objects.create(invoiceid=ivid
                                 , fee=fee, createtime=time,
                                 createuserid_id=id, purchaseid_id=pid, money=money, moreinfo=moreinfo
                                 )
    # 更新暂收单状态
    notify = []
    notify.append(dict(id=0, tittle="提示", context="发票{}创建成功".format(ivid), type="success", position="top-center"))
    request.session["notify"] = notify
    models.Zanshoudan.objects.filter(temid=tid).update(isreceived=2)
    return JsonResponse({"status": True, "id": ivid})

def invoice_display(request, ivid):
    """展示发票详情"""
    invoice = models.Fapiao.objects.filter(invoiceid=ivid).first()
    return render(request, 'invoice_display.html', {"invoice": invoice, "title": "发票展示"})

def demand_delete(request):
    """删除已完成的请购单"""
    id = request.GET.get("uid")
    models.Caigouxuqiu.objects.filter(demandid=id).update(isdelete=1)
    notify = []
    notify.append(dict(id=0, tittle="提示", context="请购单删除成功！", type="success", position="top-center"))
    request.session["notify"] = notify
    return JsonResponse({"status": True})

def inventory_delete(request):
    """删除已完成的暂存单"""
    id = request.GET.get("uid")
    models.Zanshoudan.objects.filter(purchaseid_id=id).update(isdelete=1)
    notify = [dict(id=0, tittle="系统消息", type="success", position="top-center",
                   context="暂存单删除成功")]
    request.session["notify"] = notify
    return JsonResponse({"status": True})

def receive_delete(request):
    """删除已完成的入库单"""
    id = request.GET.get("uid")
    models.Rukudan.objects.filter(temid_id=id).update(isdelete=1)
    notify = [dict(id=0, tittle="系统消息", type="success", position="top-center",
                   context="入库单删除成功")]
    request.session["notify"] = notify
    return JsonResponse({"status": True})


from configobj import ConfigObj

# Points to parent directory #
# basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))

config = ConfigObj()

config.filename = 'config.py'

# If set to True, Flask-SQLAlchemy will track modifications of objects and emit signals.
# The default is None, which enables tracking but issues a warning that it will be disabled by default in the future.
# This requires extra memory and should be disabled if not needed. #
# SQLALCHEMY_TRACK_MODIFICATIONS = True

# the URI is required by flask-sqlalchemy extension. This is the path of the database file #
# SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'database.db')

# the REPO is where the migration files are stored #
# SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')

""" Initialize Flask Configurations """

config['WTF_CSRF_ENABLED'] = True
config['SECRET_KEY'] = 'abc123'


import pika, os

def publish_data(data, url_path):
# Access the CLODUAMQP_URL environment variable and parse it (fallback to localhost)
    # url = os.environ.get('CLOUDAMQP_URL', url_path)
    # params = pika.URLParameters(url)
    # connection = pika.BlockingConnection(params)
    # channel = connection.channel() # start a channel
    # channel.queue_declare(queue='ccass1') # Declare a queue
    # channel.basic_publish(exchange='',
    #                     routing_key='ccass1',
    #                     body=data)

    # print(f" [x] Sent {data}")
    # connection.close()

    connection_parameters = pika.ConnectionParameters(
    host='services.irn1.chabokan.net',
    port=60560,  # Default AMQPS port
    virtual_host='/',
    credentials=pika.PlainCredentials('patricia', 'M5oDa0NsJZQ5zAkJ'),
    )

    # Establish the connection
    connection = pika.BlockingConnection(connection_parameters)

    # Create a channel and perform your publishing operations
    channel = connection.channel()
    channel.queue_declare(queue='ccass1')
    channel.basic_publish(exchange='',
                        routing_key='ccass1',
                         body=data)
    # ...

    # Don't forget to close the connection when you're done
    connection.close()


from base64 import b64decode
from io import BytesIO

import cv2
import numpy as np
import torch
import torch.nn as nn
from PIL import Image
from onnxruntime import InferenceSession
from torch.nn.functional import log_softmax
from torchvision import transforms
from werkzeug.datastructures import FileStorage

from utils import TARGET_SIZE, TORCH_MODEL, PADDING, BLACK

class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=10, kernel_size=5),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(10, 20, 3),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )
        self.dense = nn.Sequential(
            nn.Linear(20 * 5 * 5, 100),
            nn.ReLU(),
            nn.Linear(100, 10)
        )

    def forward(self, x):
        in_size = x.size(0)
        conv1 = self.conv1(x)
        conv2 = self.conv2(conv1)
        res = conv2.view(in_size, -1)
        out = self.dense(res)
        out = log_softmax(out, dim=1)
        return out

def load_model():
    """
    load model from saved model
    :return: MNIST model
    """
    model = Net()
    model.load_state_dict(torch.load(TORCH_MODEL))
    model.eval()
    return model

def predict(model, tensor: torch.Tensor):
    """
    use model to predict the input value
    :param model: MNIST model
    :param tensor: input tensor
    :return: predict result
    """
    tensor = tensor.view(-1, 1, 28, 28)
    with torch.no_grad():
        output = model(tensor)
        pred = torch.max(output, 1)[1]
    return pred[0].item()

def read_image(image: FileStorage) -> np.ndarray:
    """
    preprocessed the image into designated size tensor
    :param image: origin image (FileStorage type)
    :return: preprocessed ndarray of image
    """
    stream = image.read()
    image = cv2.imdecode(np.asarray(bytearray(stream)), 0)
    resized = cv2.resize(image, TARGET_SIZE, interpolation=cv2.INTER_AREA)
    return resized

def convert_base64(string: str) -> torch.Tensor:
    """
    Load base64 string from canvas and convert to tensor
    :param string: base64 string
    :return: preprocessed tensor of image
    """
    x = b64decode(string)
    image = Image.open(BytesIO(x))
    image = image.getchannel(3)
    image = transforms.Resize(TARGET_SIZE)(image)
    tensor = transforms.ToTensor()(image)
    return tensor

def convert_b64(string: str) -> np.ndarray:
    """
    Load base64 string form canvas and convert to ndarray
    :param string: base64 string
    :return: preprocessed ndarray of image
    """
    x = b64decode(string)
    image = cv2.imdecode(np.asarray(bytearray(x)), -1)
    gray = np.copy(image[:, :, -1])
    cropped = crop(gray)
    resized = cv2.resize(cropped, TARGET_SIZE, interpolation=cv2.INTER_AREA)
    return resized

def onnx_predict(sess: InferenceSession, x: np.ndarray):
    """
    use ONNX runtime session to predict result
    :param sess: ONNX runtime session
    :param x: input ndarray
    :return: predicted result
    """
    x = x.reshape((-1, 1, 28, 28))
    input_name = sess.get_inputs()[0].name
    label_name = sess.get_outputs()[0].name
    pred = sess.run([label_name], {input_name: x.astype("float32")})[0]
    return np.argmax(pred, axis=1)[0]

def crop(img: np.ndarray):
    """
    Crop the image to centralise stroke and add some padding
    :param img: image array
    :return: cropped image
    """
    bimg, contours, hier = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    (x, y, w, h) = cv2.boundingRect(contours[0])
    cropped = img.copy()[y: y + h, x: x + w]
    if h > w:
        length = h + PADDING
    else:
        length = w + PADDING
    v_pad = (length-h) // 2
    h_pad = (length-w) // 2
    enlarged = cv2.copyMakeBorder(cropped, v_pad, v_pad, h_pad, h_pad, cv2.BORDER_CONSTANT, value=BLACK)
    return enlarged


from datetime import datetime
from ..utils.constants import DATE_FORMAT, TIME_FORMAT

def checkTime(timeMin=None, timeMax=None, time=None):
    if timeMin is not None and timeMax is not None:
        return datetime.strptime(timeMin, TIME_FORMAT) <= datetime.strptime(time, TIME_FORMAT) \
               <= datetime.strptime(timeMax, TIME_FORMAT)

    if timeMin is not None:
        return datetime.strptime(timeMin, TIME_FORMAT) <= datetime.strptime(time, TIME_FORMAT)

    if timeMax is not None:
        return datetime.strptime(timeMax, TIME_FORMAT) >= datetime.strptime(time, TIME_FORMAT)

    return True

def checkValue(value=None, valueMin=None, valueMax=None):
    if valueMin is None and valueMax is None:
        return True
    if valueMin is not None and valueMax is not None:
        return float(valueMin) <= value <= float(valueMax)
    if valueMin is not None:
        return value >= float(valueMin)
    if valueMax is not None:
        return value <= float(valueMax)


from __future__ import annotations
from datetime import datetime
from typing import List, Union

from rdflib import Graph, URIRef, Literal
from rdflib.namespace import PROV, RDF, XSD

# from franz.openrdf.connect import ag_connect
# from franz.openrdf.rio.rdfformat import RDFFormat
from .prov_reporter import ProvReporter, PROVWF
from .entity import Entity
from .agent import Agent

class Activity(ProvReporter):
    """prov:Activity

    :param uri: A URI you assign to the Activity instance. If None, a UUID-based URI will be created,
    defaults to None
    :type uri: Union[URIRef, str], optional

    :param label: A text label you assign, defaults to None
    :type label: str, optional

    :param named_graph_uri: A Named Graph URI you assign
    :type named_graph_uri: Union[URIRef, str], optional

    :param used: A list of Entities used (prov:used) by this Block
    :type used: List[Block], optional

    :param generated: A list of Entities used (prov:generated) by this Block
    :type generated: List[Block], optional

    :param was_associated_with: An Agent that ran this Block (prov:wasAssociatedWith), may or may not be the same as
        the one associated with the Workflow
    :type was_associated_with: Agent, optional

    :param informed: Another Activity that this Activity triggered the creation of
    :type informed: Activity, optional

    :param was_informed_by: Another Activity that triggered the creation of this Activity
    :type was_informed_by: Activity, optional
    """

    def __init__(
        self,
        uri: URIRef = None,
        label: str = None,
        named_graph_uri: URIRef = None,
        used: List[Entity] = None,
        generated: List[Entity] = None,
        was_associated_with: Agent = None,
        informed: List[Activity] = None,
        class_uri: Union[URIRef, str] = None,
    ):
        super().__init__(
            uri=uri, label=label, named_graph_uri=named_graph_uri, class_uri=class_uri
        )

        self.started_at_time = datetime.now()
        self.ended_at_time = None

        self.used = used if used is not None else []
        self.generated = generated if generated is not None else []
        self.was_associated_with = was_associated_with
        self.informed = informed if informed is not None else []

    def prov_to_graph(self, g: Graph = None) -> Graph:
        g = super().prov_to_graph(g)

        # add in type
        g.add((self.uri, RDF.type, PROV.Activity))
        g.remove((self.uri, RDF.type, PROVWF.ProvReporter))

        # all Activities have a startedAtTime
        # made at __init__() time
        g.add(
            (
                self.uri,
                PROV.startedAtTime,
                Literal(
                    datetime.now().astimezone().strftime("%Y-%m-%dT%H:%M:%S%z"),
                    datatype=XSD.dateTimeStamp,
                ),
            )
        )

        if self.used is not None:
            for e in self.used:
                e.prov_to_graph(g)
                g.add((self.uri, PROV.used, e.uri))

        if self.generated is not None:
            for e in self.generated:
                e.prov_to_graph(g)
                g.add((self.uri, PROV.generated, e.uri))

        if self.was_associated_with is not None:
            self.was_associated_with.prov_to_graph(g)
            g.add((self.uri, PROV.wasAssociatedWith, self.was_associated_with.uri))

        if self.informed is not None:
            for i in self.informed:
                i.prov_to_graph(g)
                # g.add((self.uri, PROV.informed, i.uri))
                g.add((i.uri, PROV.wasInformedBy, self.uri))

        # if we don't yet have an endedAtTime recorded, make it now
        if self.ended_at_time is None:
            self.ended_at_time = datetime.now()

        # all Activities have a endedAtTime
        g.add(
            (
                self.uri,
                PROV.endedAtTime,
                Literal(
                    datetime.now().astimezone().strftime("%Y-%m-%dT%H:%M:%S%z"),
                    datatype=XSD.dateTimeStamp,
                ),
            )
        )

        return g


from provworkflow import Block, PROVWF
from rdflib import Literal
from rdflib.namespace import OWL, RDF, XSD

def test_prov_to_graph():
    """A basic Block should prov_to_graph an Activity which is specialised as provwf:Block

    :return: None
    """

    b = Block()
    g = b.prov_to_graph()

    # check typing
    assert (b.uri, RDF.type, PROVWF.Block) in g, "g must contain a provwf:Block"

    assert (
        b.uri,
        OWL.versionIRI,
        Literal(b.version_uri, datatype=XSD.anyURI),
    ) in g, "g must contain an owl:versionIRI property for a provwf:Block instance"

if __name__ == "__main__":
    test_prov_to_graph()


from matplotlib.colors import ListedColormap
from sklearn.svm import SVC
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm

# dataset and targets
X = np.c_[(0, 0), (1, 1), (0, 1), (1, 0)].T
Y = [0] * 2 + [1] * 2
Y = np.array(Y)
X = np.array(X)

# create xor dataset
np.random.seed(0)
X_xor = np.random.randn(200, 2)
y_xor = np.logical_xor(X_xor[:, 0] > 0, X_xor[:, 1] > 0)
y_xor = np.where(y_xor, 1, -1)

# SVM

def plot_decision_regions(X, y, classifier, axs, test_idx=None, resolution=0.02):
    # setup marker generator and color map
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('green', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])

    # plot the decision surface
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                           np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    axs.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
    axs.set_xlim(xx1.min(), xx1.max())
    axs.set_ylim(xx2.min(), xx2.max())
    axs.set_xticks(())
    axs.set_yticks(())

    # plot all samples
    X_test, y_test = X[test_idx, :], y[test_idx]
    for idx, cl in enumerate(np.unique(y)):
        axs.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
                    alpha=0.8, c=colors[idx],
                    marker=markers[idx], label=cl)
    # highlight test samples
    if test_idx:
        X_test, y_test = X[test_idx, :], y[test_idx]
        axs.scatter(X_test[:, 0], X_test[:, 1], c='',
                    alpha=1.0, linewidth=1, marker='o',
                    s=55, label='test set')

fig, axs = plt.subplots(3, 3, figsize=(15, 6), facecolor='w', edgecolor='k')
C = [0.1, 1, 100]
gamma = 2

j = 0
for kernel in ('sigmoid', 'poly', 'rbf'):
    i = 0
    for c in C:
        if(kernel == "poly"):
            clf = SVC(kernel=kernel, gamma=gamma, C=c, degree=2)
        else:
            clf = SVC(kernel=kernel, gamma=gamma, C=c)
        clf.fit(X_xor, y_xor)
        plot_decision_regions(X_xor, y_xor, clf, axs[i, j])
        axs[i, j].set_title(
            'Kernel : {} , C : {}, Gamma : {}'.format(kernel, c, gamma))
        i += 1
    j += 1
plt.legend(loc='upper left')
plt.show()

# axs[i,j].scatter(X_xor[:, 0], X_xor[:, 1], c=Y, zorder=10, cmap="winter",
#             edgecolors='k')
# # plt.axis('tight')
# x_min = -3
# x_max = 3
# y_min = -3
# y_max = 3
# XX, YY = np.mgrid[x_min:x_max:200j, y_min:y_max:200j]
# Z = clf.decision_function(np.c_[XX.ravel(), YY.ravel()])
# # Put the result into a color plot
# Z = Z.reshape(XX.shape)
# axs[i,j].pcolormesh(XX, YY, Z > 0, cmap="winter")
# axs[i,j].contour(XX, YY, Z, colors=['k', 'k', 'k'], linestyles=['--', '-', '--'],
#             levels=[-.5, 0, .5])


from flask import Flask
from flask_pymongo import PyMongo
from config import Config
from dotenv import load_dotenv
import os

app = Flask(__name__)

load_dotenv()

#select environment variables and configure db

if os.getenv('ENV') == 'dev':
    print('Running in development Mode')
    app.config["MONGO_URI"] = Config.MONGO_URI_DEV
elif os.getenv('ENV') == 'prod':
    print('Running in production Mode')
    app.config["MONGO_URI"] = Config.MONGO_URI_PROD

elif os.getenv('ENV') == 'test':
    print('Running in test Mode')
    app.config["MONGO_URI"] = Config.MONGO_URI_TEST
else:
    print("No environment variable set. Please set ENV to 'dev' or 'prod'")
    exit()

mongo = PyMongo(app)

from backend_api import routes


import pandas as pd
import numpy as np
import numpy as geek

import statsmodels.api as sm
import statsmodels.formula.api as smf
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from functools import partial

from sklearn.model_selection import cross_val_score
from sklearn.model_selection import LeaveOneOut
from stargazer.stargazer import Stargazer, LineLocation
from IPython.core.display import HTML

import seaborn as sns
import matplotlib.pyplot as plt

from statsmodels.discrete.discrete_model import Probit

import warnings;
warnings.filterwarnings('ignore');

#from aux_functions import *

from auxiliary.aux_plots import *
from auxiliary.aux_predictions import *
from auxiliary.aux_tables import *

def get_data(sample_level, second_condition_school_info, add_controls, selected_y_var, selected_var):

    data = pd.read_stata('data/BRIGHT_Formatted.dta')
    data.fillna(np.nan, inplace = True)

    if second_condition_school_info == 0:

        if sample_level == 0:
            rslt_df = data.loc[data["child_level"] == 1]

        elif sample_level == 1:
            rslt_df = data.loc[data["village_level"] == 1]

        elif sample_level == 2:
            rslt_df = data.loc[data["school_level"] == 1]

    if second_condition_school_info == 1:

        if sample_level == 0:
            rslt_df = data.loc[data["child_level"] == 1]

        elif sample_level == 1:
            rslt_df = data.loc[data["village_level"] == 1]

        elif sample_level == 2:
            rslt_df = data.loc[data["school_level"] == 1]

        rslt_df = rslt_df.loc[rslt_df["Vill_HasSchoolInfo"] == 1]

    rslt_df = pd.get_dummies(rslt_df, columns = ["department"])
    filter_col = [col for col in rslt_df if col.startswith('department')]
    dummies = rslt_df[filter_col].drop(columns=['department_close', 'department_Bani'])
    controls = rslt_df[["Ch_Girl", "Ch_HeadChild", "Ch_HeadGrandChild", "Ch_HeadNephew", "age7_dummy", "age8_dummy",                             "age9_dummy", "age10_dummy", "age11_dummy", "age12_dummy", "Hh_NumMembers", "Hh_NumKids",
                        "Hh_HeadMale", "Hh_HeadAge", "Hh_HeadSchool", "Hh_ReligionMuslin", "Hh_Animist",
                        "Hh_Christian", "Hh_Lang_Fulfude", "Hh_Lang_Gulmachema", "Hh_Lang_Moore",
                        "Hh_Ethnicity_Gourmanche", "Hh_Ethnicity_Mossi", "Hh_Ethnicity_Peul", "Hh_FloorBasic",
                        "Hh_RoofBasic", "Hh_Radio", "Hh_Telmob", "Hh_Watch", "Hh_Bike", "Hh_Cows", "Hh_Motorbike",
                        "Hh_Cart"]]

    y = rslt_df[selected_y_var]
    y.reset_index(drop= True, inplace = True)
    X = rslt_df[selected_var]

    if add_controls == 0:

        X = pd.concat([X, dummies], axis = 1)
        X.reset_index(drop= True, inplace = True)
        X = sm.add_constant(X)

    if add_controls == 1:

        X = pd.concat([X, dummies, controls], axis = 1)
        X.reset_index(drop= True, inplace = True)
        X = sm.add_constant(X)

    return y,X

#################

def calculate_polynomial_interaction(cluster_se, y_var, initial_data, interact_var1, interact_var2, degree):

    rel_score = np.array(initial_data["rel_score"])
    running_var = rel_score[:, np.newaxis]

    interaction_term = np.array(initial_data[interact_var1] * initial_data[interact_var2])
    interaction_term = interaction_term[:, np.newaxis]

    polynomial_features = PolynomialFeatures(degree=degree)
    rel_score_poly = polynomial_features.fit_transform(running_var)
    interaction_term_poly = polynomial_features.fit_transform(interaction_term)

    column_names_rel_score = ["rel_score" + str(x) for x in range(degree + 1)]
    column_names_interaction = ["interaction_term" + str(x) for x in range(degree + 1)]

    drop_columns = ["interaction_term0", "rel_score0", "rel_score", "clustercode"]

    rel_score_poly_df = pd.DataFrame(rel_score_poly, columns = column_names_rel_score)
    interaction_term_poly_df = pd.DataFrame(interaction_term_poly, columns = column_names_interaction)

    concated_df = pd.concat([rel_score_poly_df, interaction_term_poly_df,
                             initial_data], axis = 1).drop(drop_columns,axis = 1)

    if cluster_se == 0:

        mod_interaction_poly = sm.OLS(y_var,concated_df)
        result_interaction_poly = mod_interaction_poly.fit()

    elif cluster_se == 1:

        mod_interaction_poly = sm.OLS(y_var,concated_df)
        result_interaction_poly = mod_interaction_poly.fit(cov_type = 'cluster',
                                                           cov_kwds={'groups': initial_data["clustercode"]})
    #stargazer = Stargazer([result_interaction_poly])
    return result_interaction_poly

#################

def calculate_probit(cluster_se, y_var, initial_data, degree):

    rel_score = np.array(initial_data["rel_score"])
    running_var = rel_score[:, np.newaxis]

    polynomial_features= PolynomialFeatures(degree=2)
    rel_score_poly = polynomial_features.fit_transform(running_var)

    column_names = []
    for poly_degree in range(rel_score_poly.shape[1]): #can leave this for loop in, or can replace w/ list comprehension
        column_names.append("rel_score" + str(poly_degree))

    drop_columns = ["rel_score", "rel_score0", "const", "clustercode"]

    rel_score_poly_df = pd.DataFrame(rel_score_poly, columns = column_names)
    concated_df = pd.concat([rel_score_poly_df, initial_data], axis = 1).drop(drop_columns, axis = 1)

    if cluster_se == 0:

        mod_probit = sm.Probit(y_var,concated_df)
        result_probit = mod_probit.fit()

    elif cluster_se == 1:

        mod_probit = sm.Probit(y_var,concated_df)
        result_probit = mod_probit.fit(cov_type = 'cluster', cov_kwds={'groups': initial_data["clustercode"]})

    #stargazer = Stargazer([result_probit])
    return result_probit

#################

def calculate_reduced_range_40(cluster_se, y_var, initial_data):

    if cluster_se == 0:

        drop_columns = ["rel_score", "clustercode"]

        new_y_var = y_var.loc[abs(initial_data["rel_score"]) < .0040].reset_index(drop = True)
        new_X_matrix = initial_data.loc[abs(initial_data["rel_score"]) < .0040].drop(drop_columns,
                                                                                axis = 1).reset_index(drop = True)

        mod_reduced_range_40 = sm.OLS(new_y_var, new_X_matrix)
        result_reduced_range_40 = mod_reduced_range_40.fit()

    elif cluster_se == 1:

        drop_columns = ["rel_score"]

        new_y_var = y_var.loc[abs(initial_data["rel_score"]) < .0040].reset_index(drop = True)
        new_X_matrix = initial_data.loc[abs(initial_data["rel_score"]) < .0040].drop(drop_columns,
                                                                                axis = 1).reset_index(drop = True)
        clusters = new_X_matrix["clustercode"]
        new_X_matrix_dropped_clcode = new_X_matrix.drop("clustercode",axis = 1)

        mod_reduced_range_40 = sm.OLS(new_y_var, new_X_matrix_dropped_clcode)
        result_reduced_range_40 = mod_reduced_range_40.fit(cov_type = 'cluster', cov_kwds={'groups': clusters})

    #stargazer = Stargazer([result_reduced_range_40])
    return result_reduced_range_40

#################

def regress_cluster_se_corrected(y_var, initial_data, degree):

    rel_score = np.array(initial_data["rel_score"])

    running_var = rel_score[:, np.newaxis]

    polynomial_features= PolynomialFeatures(degree=degree)
    rel_score_poly = polynomial_features.fit_transform(running_var)

    column_names = ["rel_score" + str(x) for x in range(degree + 1)]
    drop_columns = ["rel_score", "rel_score0"]

    rel_score_poly_df = pd.DataFrame(rel_score_poly, columns = column_names)
    concated_df = pd.concat([rel_score_poly_df, y_var, initial_data], axis = 1).drop(drop_columns ,axis = 1)

    dataset = concated_df.dropna()

    clusters = dataset["clustercode"]

    drop =  ["clustercode","Ch_Highest_Grade"]

    dataset_dropped_clcode = dataset.drop(drop, axis = 1)
    y_dropped_clcode = dataset["Ch_Highest_Grade"]

    mod_poly = sm.OLS(y_dropped_clcode, dataset_dropped_clcode)
    result_poly = mod_poly.fit(cov_type = 'cluster', cov_kwds={'groups': clusters})

    return result_poly

####################

def regress_over_output(cluster_se, y, initial_data):

    if cluster_se == 0:

        model = sm.OLS(y, initial_data, missing='drop')
        res = model.fit()

    elif cluster_se == 1:
        drop_columns = "clustercode"
        X_matrix = initial_data.drop(drop_columns, axis = 1)

        model = sm.OLS(y, X_matrix, missing='drop')
        res = model.fit(cov_type = 'cluster', cov_kwds={'groups': initial_data["clustercode"]})

    #stargazer = Stargazer([res])
    return res

#################

def regress_for_diff_outcomes_poly_features(y_df,dep_variables,X_df,degree,cluster_se):

    varying_depend_var = []

    for dep_var in dep_variables:
        models = calculate_polynomial_features(cluster_se, y_df[dep_var], X_df, degree)
        varying_depend_var.append(models)

    return varying_depend_var

###################

def fit_poly_models_iterate(y_df, X_df, degree, cluster_se):

    fitted_poly_models = []

    for x in range(1, degree + 1):
        models = calculate_polynomial_features(cluster_se, y_df, X_df, x)
        fitted_poly_models.append(models)

    return fitted_poly_models

####################

def compute_locally_weighted_reg(y_var,x_var,X,h,data):

    z_array = abs((data[x_var] - X)/h)

    kernels = []

    for z in z_array:

        if z <= 1:
            kz = (3/4)*(1 - z**2)/h
        else:
            kz = 0
        kernels.append(kz)

    kernels_array = np.array(kernels)

    weights = kernels_array**0.5

    y_predicted = (data[y_var])*weights
    const_predicted = weights
    x_predicted = (data[x_var] - X)*weights

    data["weights"] = weights
    data['y_predicted'] = y_predicted
    data['const_predicted'] = const_predicted

    data['x_predicted'] = x_predicted

    ols_output = smf.ols(formula = 'y_predicted ~ const_predicted + x_predicted - 1', data = data).fit()
    param = ols_output.params["const_predicted"]

    return param

####################

def compute_binned_averages(treated, binned_variable,binsize,bins,data):

    store_xgrid = []
    store_averages =[]

    table = pd.DataFrame({'Xgrid': [],'Binned Averages': []})
    table['Index'] = list(range(bins))
    table = table.set_index('Index')

    if treated == 0:

        set_range = -1*np.array(range(0,250,binsize))

        for value in set_range:
            xgrid = value - binsize/2
            store_xgrid.append(xgrid)

        for value in set_range:
            subset = data[(data["rel_score_redefined"] < value) & (data["rel_score_redefined"] >= value - binsize)]
            means = subset[binned_variable].mean()
            store_averages.append(means)

    if treated == 1:

        set_range = list(range(0,250,binsize))

        for value in set_range:
            xgrid = value + binsize/2
            store_xgrid.append(xgrid)

        for value in set_range:
            subset = data[(data["rel_score_redefined"] > value) & (data["rel_score_redefined"] <= value + binsize)]
            means = subset[binned_variable].mean()
            store_averages.append(means)

    for x in range(bins):
            averages = store_averages
            xgrid = store_xgrid
            results = [xgrid[x],averages[x]]
            table.loc[x] = results
            table.reset_index(drop = True, inplace = True)

    return table

#################################

def test_placebo_discontinuity(y_placebo, X_placebo, treated_side, placebo_cutoff):

    if treated_side == 0:

        y_placebo_untreated = y_placebo.loc[X_placebo["rel_score"] < 0].reset_index(drop = True)
        X_placebo_untreated = X_placebo.loc[X_placebo["rel_score"] < 0].reset_index(drop = True)

        X_placebo_untreated["placebo_treatment_dummy"] = (X_placebo_untreated["rel_score"] >= placebo_cutoff).astype(int)
        clustercodes = X_placebo_untreated["clustercode"]

        X_placebo_untreated.drop(["proj_selected", "clustercode"], inplace = True, axis = 1)

        mod = sm.OLS(y_placebo_untreated,X_placebo_untreated, missing='drop')
        result = mod.fit(cov_type = 'cluster', cov_kwds={'groups': clustercodes})

    if treated_side == 1:
        y_placebo_treated = y_placebo.loc[X_placebo["rel_score"] >= 0].reset_index(drop = True)
        X_placebo_treated = X_placebo.loc[X_placebo["rel_score"] >= 0].reset_index(drop = True)

        X_placebo_treated["placebo_treatment_dummy"] = (X_placebo_treated["rel_score"] >= placebo_cutoff).astype(int)
        clustercodes = X_placebo_treated["clustercode"]

        X_placebo_treated.drop(["proj_selected", "clustercode"], inplace = True, axis = 1)

        mod = sm.OLS(y_placebo_treated,X_placebo_treated, missing='drop')
        result = mod.fit(cov_type = 'cluster', cov_kwds={'groups': clustercodes})

    return result

##################################

def iterate_placebo_test(y_var, X_matrix, treated_side):

    treatment_coeffs = []
    pvalues = []
    CI_lower = []
    CI_upper = []

    table = pd.DataFrame({'Placebo Cutoff': [],'Coefficient Estimate': [],'Coefficient Estimate': [],'P-value': [], 'Conf. Int. Lower': [], 'Conf. Int. Upper': []})
    table['Index'] = list(range(4))
    table = table.set_index('Index')

    if treated_side == 0:

        placebo_cutoffs = [-0.0125, -0.00625, -0.003125, -0.0015625]

        for cutoff in placebo_cutoffs:

            summary = test_placebo_discontinuity(y_var, X_matrix, 0, cutoff)

            treatment_coeffs.append(summary.params["placebo_treatment_dummy"])
            pvalues.append(summary.pvalues["placebo_treatment_dummy"])
            CI_lower.append(summary.conf_int(alpha=0.05, cols=None).loc["placebo_treatment_dummy",:][0])
            CI_upper.append(summary.conf_int(alpha=0.05, cols=None).loc["placebo_treatment_dummy",:][1])

    if treated_side == 1:

        placebo_cutoffs = [0.0125, 0.00625, 0.003125, 0.0015625]

        for cutoff in placebo_cutoffs:

            summary = test_placebo_discontinuity(y_var, X_matrix, 1, cutoff)

            treatment_coeffs.append(summary.params["placebo_treatment_dummy"])
            pvalues.append(summary.pvalues["placebo_treatment_dummy"])
            CI_lower.append(summary.conf_int(alpha=0.05, cols=None).loc["placebo_treatment_dummy",:][0])
            CI_upper.append(summary.conf_int(alpha=0.05, cols=None).loc["placebo_treatment_dummy",:][1])

    for x in range(4):
        results = [placebo_cutoffs[x]*10000, treatment_coeffs[x], pvalues[x], CI_lower[x], CI_upper[x]]
        table.loc[x] = results

    return table

####################################

def check_effect_bandwidth(y, X, attending):

    if attending == 0:

        rslts = pd.DataFrame(columns=["Treatment Effect, std", "P-value"])
        rslts.index.set_names("Bandwidth", inplace=True)

        for h in [1, 0.0400,0.0300, 0.0250, 0.01875, 0.0125,
                  0.003125, 0.0015625, 0.0010, 0.0005]:

            y_limited = y[X["rel_score"].between(-h, h)].reset_index(drop = True)
            X_limited = X[X["rel_score"].between(-h, h)].reset_index(drop = True)

            output = calculate_polynomial_interaction(1,y_limited, X_limited, "proj_selected","rel_score",1)

            info = [output.params[3], round(output.pvalues[3],3)]

            rslts.loc[h*10000] = info

    elif attending == 1:

        rslts = pd.DataFrame(columns=["Treatment Effect, %", "P-value"])
        rslts.index.set_names("Bandwidth", inplace=True)

        for h in [1, 0.0400,0.0300, 0.0250, 0.01875, 0.0125,
                  0.003125, 0.0015625, 0.0010, 0.0005]:

            y_limited = y[X["rel_score"].between(-h, h)].reset_index(drop = True)
            X_limited = X[X["rel_score"].between(-h, h)].reset_index(drop = True)

            output = calculate_polynomial_interaction(1,y_limited, X_limited, "proj_selected","rel_score",1)

            info = [output.params[3] * 100, round(output.pvalues[3],3)]

            rslts.loc[h*10000] = info

    return rslts

################################

def create_bandwidth_check(y_bndwd, X_bndwd, bandwidth):

    scoring = "neg_mean_squared_error"
    model = LinearRegression()
    cv = LeaveOneOut()

    cross_val_score_p = partial(cross_val_score, scoring=scoring, cv=cv)

    rslts = pd.DataFrame(columns=["below", "above", "joint"])
    rslts.index.set_names("Bandwidth", inplace=True)

    for label in ["below", "above"]:
        for h in bandwidth:

            if label == "below":
                df_subset_y = y_bndwd.loc[X_bndwd["rel_score"].between(-h, +0.00)].reset_index(drop = True)
                df_subset_X = X_bndwd.loc[X_bndwd["rel_score"].between(-h, +0.00)].reset_index(drop = True)
            else:
                df_subset_y = y_bndwd.loc[X_bndwd["rel_score"].between(+0.00, +h)].reset_index(drop = True)
                df_subset_X = X_bndwd.loc[X_bndwd["rel_score"].between(+0.00, +h)].reset_index(drop = True)

            y = df_subset_y
            X = df_subset_X.drop(["const"], axis = 1)

            rslts.loc[h*10000, label] = -cross_val_score_p(model, X, y).mean()
            rslts["joint"] = rslts[["below", "above"]].mean(axis=1)

    return rslts

################################

def compute_est_effect_separate(y_df, X_df, dep_var,attending):

    y_untreated = y_df.loc[X_df["rel_score"] < 0].reset_index(drop = True)
    X_untreated = X_df.loc[X_df["rel_score"] < 0].reset_index(drop = True).drop("proj_selected", axis = 1)
    y_treated = y_df.loc[X_df["rel_score"] >= 0].reset_index(drop = True)
    X_treated = X_df.loc[X_df["rel_score"] >= 0].reset_index(drop = True).drop("proj_selected", axis = 1)

    model_untreated = calculate_polynomial_features(1, y_untreated[dep_var], X_untreated, 2)
    model_treated = calculate_polynomial_features(1, y_treated[dep_var], X_treated, 2)

    dep_proportion = X_df.iloc[:,4:50].mean()
    dep_coeff_untreated = model_untreated.params[3:49]
    dep_coeff_treated = model_treated.params[3:49]

    const_untreated = model_untreated.params["const"] + np.dot(dep_proportion,dep_coeff_untreated)
    const_treated = model_treated.params["const"] + np.dot(dep_proportion,dep_coeff_treated)

    if attending == 0:

        est_effect = (const_treated - const_untreated)

        print(f'The measured effect on normalized total test scores for separately plotted control and treatment regressions is {est_effect:.4f} standard deviations.')

    if attending == 1:

        est_effect = (const_treated - const_untreated) * 100

        print(f'The measured effect on attendance for separately plotted control and treatment regressions is {est_effect:.4f}%.')


import frappe
from frappe import (throw, _, msgprint)
from frappe.exceptions import DuplicateEntryError

import json
from typing import TYPE_CHECKING, Dict, List

from rq import Queue, Worker

from frappe.utils.data import convert_utc_to_system_timezone, format_datetime
from frappe.utils.background_jobs import get_redis_conn
from frappe.utils.scheduler import is_scheduler_inactive

if TYPE_CHECKING:
	from rq.job import Job

JOB_COLORS = {
	'queued': 'orange',
	'failed': 'red',
	'started': 'blue',
	'finished': 'green'
}

@frappe.whitelist()
def get_info(show_failed=False) -> List[Dict]:
	if isinstance(show_failed, str):
		show_failed = json.loads(show_failed)

	conn = get_redis_conn()
	queues = Queue.all(conn)
	workers = Worker.all(conn)
	jobs = []

	def add_job(job: 'Job', name: str) -> None:
		if job.kwargs.get('site') == frappe.local.site:
			job_info = {
				'job_name': job.kwargs.get('kwargs', {}).get('playbook_method')
					or job.kwargs.get('kwargs', {}).get('job_type')
					or str(job.kwargs.get('job_name')),
				'status': job.get_status(),
				'queue': name,
				'creation': format_datetime(convert_utc_to_system_timezone(job.created_at)),
				'color': JOB_COLORS[job.get_status()]
			}

			if job.exc_info:
				job_info['exc_info'] = job.exc_info

			jobs.append(job_info)

	# show worker jobs
	for worker in workers:
		job = worker.get_current_job()
		if job:
			add_job(job, worker.name)

	for queue in queues:
		# show active queued jobs
		if queue.name != 'failed':
			for job in queue.jobs:
				add_job(job, queue.name)

		# show failed jobs, if requested
		if show_failed:
			fail_registry = queue.failed_job_registry
			for job_id in fail_registry.get_job_ids():
				job = queue.fetch_job(job_id)
				add_job(job, queue.name)

	return jobs

@frappe.whitelist()
def remove_failed_jobs():
	conn = get_redis_conn()
	queues = Queue.all(conn)
	for queue in queues:
		fail_registry = queue.failed_job_registry
		for job_id in fail_registry.get_job_ids():
			job = queue.fetch_job(job_id)
			fail_registry.remove(job, delete_job=True)

@frappe.whitelist()
def get_scheduler_status():
	if is_scheduler_inactive():
		return [_("Inactive"), "red"]
	return [_("Active"), "green"]

__version__ = '0.0.1'

def publish_progress(docname, id, achieved, total, description, field_name=None, done_message="Done", title="Loading", reload=False):
    frappe.publish_realtime(id,
                            {
                                "title": title,
                                "count": achieved,
                                "total": total,
                                "percentage": achieved / total * 100,
                                "description": f"Progress: {achieved}/{total} {description}",
                                "reload": reload,
                                "fieldname": field_name,
                                "done_message": done_message
                            },
                            user=frappe.session.user,
                            after_commit=False,
                            docname=docname)

def backgroud_jobs_check(function_path):
    list_of_jobs = get_info()
    if list_of_jobs:
        for job in list_of_jobs:
            if job['job_name'] == function_path:
                throw(_([f"Current Job Status: {job['status']}",
                        f"Job has been created at: {job['creation']}"]),
                    DuplicateEntryError,
                    "This job already in progress",
                    as_list=True)


"""
sideinfo.py : get the side information for an MP3 frame
"""

import json

from collections import defaultdict
from .util.bits import Bits
from .header import ChannelEncodings, MP3Header

class ChannelSideInfo(object):
    """
    ChannelSideInfo : container for all that pesky side information per channel

    Attributes :
    - part2_3_length : 1 value
    - big_values : 1 value
    - global_gain : 1 value
    - scalefac_compress : 1 value
    - window_switching_flag : 1 value
    - block_type : 1 value
    - mixed_block_flag : 1 value
    - table_select : 2 values
    - region0_count : 1 value
    - region0_count : 1 value
    - sub_block_gain : 3 values
    - preflag : 1 value
    - scalefac_scale : 1 value
    - count1_table_select : 1 value
    """

    def __init__(self, index: int, bits: Bits):
        self.index = index
        # self.bits = bits # do we actually want to keep a copy of the bitstring here?

        self.part2_3_length = bits.read_bits_as_int(12)
        self.big_values = bits.read_bits_as_int(9)
        self.global_gain = bits.read_bits_as_int(8)
        self.scalefac_compress = bits.read_bits_as_int(4)
        self.window_switch_flag = bits.read_bits_as_int(1)
        self.table_select = [0] * 3
        self.sub_block_gain = [0] * 3
        if self.window_switch_flag == 1:
            self.block_type = bits.read_bits_as_int(2)
            self.mixed_block_flag = bits.read_bits_as_int(1)
            for i in range(0, 2):
                self.table_select[i] = bits.read_bits_as_int(5)
            for i in range(0, 3):
                self.sub_block_gain[i] = bits.read_bits_as_int(3)
            # the below code is present in
            # https://github.com/hajimehoshi/go-mp3/blob/master/internal/sideinfo/sideinfo.go
            # but it's making my tests fail so (shrug)

            # UPDATE: I think it's time to bring this back
            if (self.block_type == 2 and self.mixed_block_flag == 0):
                self.region0_count = 8
            else:
                self.region0_count = 7
            # TODO: come back and look at this later if stuff starts failing
            self.region1_count = 20 - self.region0_count
        else:
            for i in range(0, 3):
                self.table_select[i] = bits.read_bits_as_int(5)
            self.region0_count = bits.read_bits_as_int(4)
            self.region1_count = bits.read_bits_as_int(3)
            # I don't think we need to set block_type or mixed_blog_flag
            # if window_switch_flag isn't set
            self.block_type = 0
            self.mixed_block_flag = 0
            for _ in range(0, 3):
                self.sub_block_gain[i] = 0
        self.preflag = bits.read_bits_as_int(1)
        self.scalefac_scale = bits.read_bits_as_int(1)
        self.count1_table_select = bits.read_bits_as_int(1)

    def __str__(self):
        """
        __str__ : string representation of a channel
        """
        return json.dumps({
            'part2_3_length' : self.part2_3_length,
            'big_values' : self.big_values,
            'global_gain' : self.global_gain,
            'scalefac_compress' : self.scalefac_compress,
            'window_switch_flag' : self.window_switch_flag,
            'block_type' : self.block_type,
            'mixed_block_flag' : self.mixed_block_flag,
            'table_select' : self.table_select,
            'region0_count' : self.region0_count,
            'region0_count' : self.region0_count,
            'sub_block_gain' : self.sub_block_gain,
            'preflag' : self.preflag,
            'scalefac_scale' : self.scalefac_scale,
            'count1_table_select' : self.count1_table_select,
        })

class Granule(object):
    """
    Granule : basically just a container for a two-tuple of Channels
    """

    def __init__(self, index, bits):
        self.index = index
        self.bits = bits
        self.channels = []
        self.channels.append(ChannelSideInfo(0, self.bits))
        self.channels.append(ChannelSideInfo(1, self.bits))

    def collect_channel_values(self, key):
        """
        collect_channel_values : collect all the channel values associated with the given key
        """
        result = []
        for chan in self.channels:
            result.append(chan.__getattribute__(key))
        return result

    def __str__(self):
        """
        __str__ : string representation of a granule
        """
        return json.dumps({
            'index' : self.index,
            'channels' : [str(c) for c in self.channels],
        })

class SideInfo(object):
    """
    SideInfo : parse the side information for an MP3 frame

    Attributes:
        main_data_begin - 9 bits
        private_bits - 5 bites for mono; 3 bits for stereo
        scfsi
        granule_one:
        - part2_3_length
        - big_values
        - global_gain
        - scalefac_compress
        - window_switching_flag
        - block_type
        side_info for gr2 (granule 2)
    """

    def __init__(self, header: MP3Header, raw_bytes):
        """
        __init__ : read the given mp3_file and parse the side information
        this expected the mp3_file file object to already be pointing to the side information
        this will modify the mp3_file file object to point to the first byte
            after the side information
        """
        channels = 1
        if header.channel != ChannelEncodings.MONO:
            channels = 2
        bits = Bits()
        for i in raw_bytes:
            bits.add_bits(i)
        self._bitstring = bits
        # each entry into the two granules object will be an array with each index
        # corresponding to the channel - 1
        self.granules = {
            0 : defaultdict(list),
            1 : defaultdict(list),
        }
        self.main_data_begin = self._bitstring.read_bits_as_int(9)
        if header.channel == ChannelEncodings.MONO:
            self.private_bits = self._bitstring.read_bits_as_int(5)
        else:
            self.private_bits = self._bitstring.read_bits_as_int(3)
        self.scfsi_band = [0] * channels
        for i in range(0, channels):
            self.scfsi_band[i] = [0] * 4
            for j in range(0, 4):
                self.scfsi_band[i][j] = self._bitstring.read_bits_as_int(1)
        for i in range(0, 2):
            self.granules[i] = Granule(i, self._bitstring)

    def __str__(self):
        """
        __str__ : string representation of each thing
        """
        return json.dumps({
            'granules' : [str(self.granules[g]) for g in self.granules],
            'main_data_begin' : self.main_data_begin,
            'private_bits' : self.private_bits,
            'scfsi_band' : self.scfsi_band,
        }).replace('"', '')


import dash_bootstrap_components as dbc
import requests as requests
from dash import html, dcc
from dash.dash_table import DataTable
from dashapp import dash_app

def layout_rows():
    loading = dcc.Loading(
        id="loading-1",
        type="default",
        # fullscreen=True,
        children=html.Div(id="loading-output-1")
    )

    validate_locations_btn = dbc.Button('Validate Locations', id='validate_locations_btn')
    validate_things_btn = dbc.Button('Validate Things', id='validate_things_btn')
    validate_datastreams_btn = dbc.Button('Validate Datastreams', id='validate_datastreams_btn')
    validate_all_btn = dbc.Button('Validate All', id='validate_all_btn')
    validate_btn_grp = dbc.ButtonGroup([validate_all_btn,
                                        validate_locations_btn,
                                        validate_things_btn,
                                        validate_datastreams_btn
                                        ])
    cols = [{"name": "Name", "id": "name"},
            {"name": "@iot.id", "id": "@iot.id"},
            {"name": "ValidationError", "id": "validation_error"},
            {"name": "Instance", "id": "instance", 'presentation': 'markdown'},
            ]
    dt = DataTable(
        id='validation_results',
        style_cell={"textAlign": "left"},
        columns=cols,
        style_as_list_view=True,
        page_size=10,

        style_header={
            "font-family": "verdana",
            "font-weight": "bold",
            "fontSize": "10px",
            # "height": "50px"
        },
        style_data={"fontSize": "10px", "font-family": "verdana",
                    # 'whiteSpace': 'pre-line'
                    },
        style_table={"height": "600px", "overflowY": "auto"},
    )

    return [dbc.Row([dbc.Col([dbc.Input(id='url_to_validate',
                                       value='https://st2.newmexicowaterdata.org/FROST-Server/v1.1')]),
                    dbc.Col([dbc.Input(id='n', value=10)])
                    ]
                    ),
            dbc.Row(dbc.Col([validate_btn_grp])),
            dbc.Row(dbc.Col([dt, loading]))
            ]

def do_layout():
    dash_app.layout = dbc.Container(layout_rows())
# ============= EOF =============================================


from torch import nn

from alpha_network.policy_head import PolicyHead
from alpha_network.feature_extractor import FeatureExtractor

from alpha_network.value_head import ValueHead

import torch
import numpy as np
import pickle

from utils import logger, CUDA, CHECKPOINTS_DIR

class AlphaNetwork(nn.Module):
    def __init__(self, residual_depth, single_channel_size, num_input_channels,
                 num_possible_actions):
        super(AlphaNetwork, self).__init__()

        INTERMIDATE_RESIDUAL_NUM_CHANNELS = 256

        self.feature_extractor = FeatureExtractor(residual_depth,
                                                  num_input_channels,
                                                  INTERMIDATE_RESIDUAL_NUM_CHANNELS)
        self.value_head = ValueHead(INTERMIDATE_RESIDUAL_NUM_CHANNELS,
                                    single_channel_size)
        self.policy_extractor = PolicyHead(INTERMIDATE_RESIDUAL_NUM_CHANNELS,
                                           single_channel_size,
                                           num_possible_actions)

    def _forward(self, x):
        x = self.feature_extractor.forward(x)
        return self.policy_extractor.forward(x), self.value_head.forward(x)

    def predict(self, x):
        assert x.size()[0] == 1, 'Predict on only one state at a time'

        with torch.no_grad():
            policy, value = self._forward(x)

            # last layer is a log max, for prediction, undo log
            # remove batch dimension as-well.
            return policy.exp()[0], value[0]

    @staticmethod
    def _policy_loss(outputs, targets):
        """
        Cross-entropy loss as defined in the paper.
        :note: The outputs are expected to be log-softmax probabilities
        :return: The cross-entropy losses between the output probabilities and expected probabilities
                 averaged over the number of samples in the batch
        """
        return -torch.sum(outputs * targets) / outputs.size()[0]

    @staticmethod
    def _value_loss(outputs, targets):
        """
        :return: The square error between the outputs and target values, averaged over the sample size.
        """
        return torch.sum((targets - outputs) ** 2) / outputs.size()[0]

    def forward_batch(self, states, target_policies, target_values):
        """
        :param states: the batch of states
        :param target_policies: the target policy for each state in the batch
        :param target_values: the target value for each state in the batch
        :return: the total loss for the network as defined in the paper (averaged cross entropy losses for all
                 policies summed with the averaged squared error for each value)
        """
        policies, values = self._forward(states)
        return self._policy_loss(policies, target_policies) + self._value_loss(
            values, target_values)

    def train(self, train_examples, epochs, batch_size=32):
        """
        :param train_examples: train examples, a list of tuples of form:
                <state, expected policy, value>
        :param epochs: number of epochs to train
        :param batch_size: batch size to use

        :return: the loss at the end of each epoch
        """

        # copy the list, since we shuffle it in-place
        train_examples = list(train_examples)

        optimizer = torch.optim.SGD(self.parameters(), lr=0.01, momentum=0.9)
        losses = list()

        num_batches = np.floor(len(train_examples) / batch_size)
        logger.info("beginning training")
        for epoch in range(epochs):
            logger.debug(f'Epoch {epoch}')
            np.random.shuffle(train_examples)
            batch_index = 0
            epoch_loss = 0.0
            while batch_index < num_batches:
                optimizer.zero_grad()

                states, policies, values = list(zip(*train_examples[
                                                     batch_index * batch_size: (
                                                                                batch_index + 1) * batch_size]))
                states = torch.cat(states)
                policies = torch.cat(policies)
                values = torch.cat(values)

                loss = self.forward_batch(states, policies, values)

                # compute gradient and do SGD step
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()

                batch_index += 1
                epoch_loss += loss.item()

            losses += [epoch_loss / num_batches]

        return losses

    def save_checkpoint(self, game_name, examples_history):
        import datetime
        file_name = game_name + datetime.datetime.now().isoformat()\
            .replace(':', '_').replace('.', '_')
        file_path = CHECKPOINTS_DIR / file_name
        history_path = CHECKPOINTS_DIR / (file_name + "_history")

        logger.info(f"Storing checkpoint at {file_path}")
        with open(file_path, 'wb') as f:
            torch.save({
                'state_dict': self.state_dict(),
            }, f)

        with open(history_path, 'wb') as f:
            f.write(pickle.dumps(examples_history))

    def load_checkpoint(self, file_name, load_history=True):
        checkpoint_path = CHECKPOINTS_DIR / file_name
        history_path = CHECKPOINTS_DIR / (file_name + "_history")
        logger.info(f"Loading CPU checkpoint from {checkpoint_path}")
        with open(checkpoint_path, 'rb') as f:
            checkpoint = torch.load(f, map_location='cpu')
            self.load_state_dict(checkpoint['state_dict'])

        if not load_history:
            return

        with open(history_path, 'rb') as f:
            result = pickle.loads(f.read())
        if CUDA:
            result = [(item[0].cuda(), item[1].cuda(), item[2].cuda()) for item
                      in result]
        return result


import functools
import itertools
import json
import pathlib

from ._escape_utils import conditional_escape, mark_safe

class Octicon():

    def __init__(self, data):
        self.width = data['width']
        self.height = data['height']
        self.name = data['name']
        self.keywords = data['keywords']
        self.path = data['path']
        self.viewBox = data.get('viewBox', None)

    @staticmethod
    def key_exists(opts, key):
        return opts.get(key, None) is not None

    @staticmethod
    def escape_attr(key, value):
        if value is True:
            return key
        elif value is False or value is None:
            return ''
        elif key == 'class':
            if not isinstance(value, str):
                value = ' '.join(map(str.strip, value))
        return '%s="%s"' % (key, conditional_escape(value))

    def as_html(self, **opts):
        raw_attrs = self.build_attributes(opts)
        attrs = ' '.join(filter(None, itertools.starmap(
            self.escape_attr, raw_attrs.items())))
        return mark_safe("<svg %s>%s</svg>" % (attrs, self.path))

    def build_attributes(self, opts):
        opts = {k.replace('_', '-'): v for k, v in opts.items()}
        key_exists = functools.partial(self.key_exists, opts)

        defaults = self.default_attrs
        overrides = {}
        if key_exists('width') and not key_exists('height'):
            # Generate the correct height from the aspect ratio
            overrides['height'] = opts["width"] / self.size_ratio
        elif key_exists('height') and not key_exists('width'):
            # Generate the correct width from the aspect ratio
            overrides['width'] = opts["height"] * self.size_ratio
        for class_key in ['class', 'classes']:
            if key_exists(class_key):
                if isinstance(opts[class_key], str):
                    # A string.
                    overrides["class"] = [
                        *defaults['class'],
                        *opts[class_key].split(),
                    ]
                else:
                    # An iterable of some sort.
                    overrides["class"] = [
                        *defaults['class'],
                        *opts[class_key],
                    ]
                # Remove it so that `classes` doesn't get output into the html
                del opts[class_key]
        if key_exists('aria-label'):
            overrides["aria-label"] = opts["aria-label"]
            overrides["role"] = "img"
            # Un-hide the icon
            del defaults["aria-hidden"]
        out_attrs = dict(defaults)
        out_attrs.update(opts)
        out_attrs.update(overrides)
        return {
            key: value
            for key, value in out_attrs.items()
            # If we've defined custom rules on what is considered 'present'
            # Apply them here as well so that they're applied to all values
            if self.key_exists(out_attrs, key)
        }

    @property
    def size_ratio(self):
        return self.width / self.height

    @property
    def default_attrs(self):
        if self.viewBox:
            return {
                "version": "1.1",
                "width": self.width,
                "height": self.height,
                "viewBox": "%s" % (self.viewBox),
                "class": ["octicon", "octicon-" + self.name],
                "aria-hidden": "true"
            }
        return {
            "version": "1.1",
            "width": self.width,
            "height": self.height,
            "viewBox": "0 0 %s %s" % (self.width, self.height),
            "class": ["octicon", "octicon-" + self.name],
            "aria-hidden": "true"
        }

class OcticonStore():

    @classmethod
    def from_file(cls, file=None, additional_file=None):
        if file is None:
            file = pathlib.Path(__file__).parent / 'data.json'
        else:
            file = pathlib.Path(file)
        if additional_file:
            additional_file = pathlib.Path(additional_file)
            with file.open() as f, additional_file.open() as g:
                data = json.load(f)
                data.update(json.load(g))
                return cls(data)
        with file.open() as f:
                return cls(json.load(f))

    def __init__(self, data_dict, *, Octicon=Octicon):
        raw_icons = [
            Octicon(data)
            for data in data_dict.values()
        ]
        self.icons = dict([
            (icon.name, icon)
            for icon in raw_icons
        ] + [
            (keyword, icon)
            for icon in raw_icons
            for keyword in icon.keywords
        ])

    def get_icon(self, name):
        return self.icons[name]

    def __getitem__(self, name):
        return self.get_icon(name)

    def get_as_html(self, name, **opts):
        return self[name].as_html(**opts)


# Line tracking - By: chenxiao - 周二 6月 7 2022

# In atio 1, task 1, use openmv to do line tracking

import sensor, image, time, math
import ustruct
from pid import PID
from pyb import UART

theta_pid = PID(p=0.45, i=0.2)
# initializes the PI controller and envalues the parameters

GRAYSCALE_THRESHOLD = [(128, 255)]
sensor.reset() # resets and initializes the sensor.
sensor.set_pixformat(sensor.RGB565) # sets pixel format to RGB565 (or GRAYSCALE)
sensor.set_framesize(sensor.QVGA) # sets frame size to QVGA (320x240)
sensor.skip_frames(time = 2000) # waits for settings take effect.
clock = time.clock() # creates a clock object to track the FPS.

# set uart
uart = UART(3, 9600)
uart.init(9600,bits=8,parity=None,stop=1)

ROIS = [
(10, 50, 300, 30 ,0.15),
(10, 100, 300, 30, 0.15),
(10, 150, 300, 30, 0.3),
(10, 200, 300, 30, 0.4)
]
# defines four regions of interest

weight_sum = 0 # initializes weight values
for r in ROIS: weight_sum += r[4] # defines calculation for weigted

thresh=[(30, 79, -7, 30, 6, 23)]
clock.tick() # initiates timer

while(True):
    # updates the FPS clock.
    img = sensor.snapshot() # takes a picture and return the image
   # img.rotation_corr(z_rotation=180) # rectify the image because it is shot upside down
    img = img.to_grayscale() # converts to grayscale image
    img = img.find_edges(image.EDGE_CANNY, threshold=(140, 140))
    # one−step realization of Canny detection
    img = img.dilate(1)
    # makes white spots more present

    centroid_sum = 0
    # initializes the weighted distance from centroids to the central line
    for r in ROIS: # for each centroid of ROI
        blobs = img.find_blobs(GRAYSCALE_THRESHOLD , roi=r[0:4], merge=True)
        # r[0:4] is roi tuple.
        # finds all white spots and merge as one (connecting each other with lines)

        # searching for lines in ROI
        if blobs:
            # indentifies the bolb index with the most lines
            largest_blob = 0
            most_pixels = 0
            for i in range(len(blobs)):
            # there might be several line clusters in ROI, but identify the larges as the target line
                if blobs[i].pixels() > most_pixels:
                    most_pixels = blobs[i].pixels()
                    # update the value if there is any larger pixel clusters
                    largest_blob = i

            img.draw_rectangle(blobs[largest_blob].rect(),color = 0)
            # draws a rectangle around the largest pixel cluster
            img.draw_cross(blobs[largest_blob].cx(), blobs[largest_blob].cy(),color = 0)
            # marks the largest pixel cluster with a white cross

            centroid_sum += blobs[largest_blob].cx() *r[4]
            # accumulates the weighted distances to obtain the final result

    center_pos = (centroid_sum / weight_sum) # determines the center of line

    # converts the result above to an angle, using unlinear operations
    deflection_angle = 0
    # initializes the angle to turn for the robot
    deflection_angle = - math.atan((center_pos -160)/60)
    # obtains the result (in radian)
    deflection_angle = math.degrees(deflection_angle)
    # converts result to degrees

    output = theta_pid.get_pid(deflection_angle ,1)

    # generate instructions in different level
    if output<-4: # turn right
        if output>-10:
            sign = '4'
        elif output>-15:
            sign = '5'
        else:
            sign = '6'

    elif output>4: # turn left
        if output<10:
            sign = '3'
        elif output<15:
            sign = '2'
        else:
            sign = '1'
    else: # go straight
        sign = '0'
    #sign = str(sign)

    sign='1'
    print(sign)
    # write data and send
    uart.write(sign)