import torch
import faiss
import numpy as np

class StatsTracker():
    def __init__(self,  frame_w, frame_h, fps=30):
        self.shot_try_done = False
        self.shot_made_done = False
        self.deep_shot_mode = False
        self.exit_frame_num = -1
        self.shot_exit = -1
        self.fps = fps
        self.frame_w = frame_w
        self.frame_h = frame_h
        self.frame_num = 0
        self.shot_count = 0
        self.made_count = 0
        self.no_ball = 0
        self.pre_ball_data = []

    def track(self, img, results, re_id, id_dict):

        results = results.numpy()

        ball_data = results[results[:,-1]==1]
        made_data = results[results[:,-1]==2]
        person_data = results[results[:,-1]==3]
        rim_data = results[results[:,-1]==4]
        shoot_data = results[results[:,-1]==5]

        ball_num = len(ball_data)
        made_num = len(made_data)
        person_num = len(person_data)
        rim_num = len(rim_data)
        shoot_num = len(shoot_data)

        #림 결정하기
        if rim_num > 0:
            #사람 x 좌표 정렬
            temp = (person_data[:,0] + person_data[:,2])/2
            temp.sort()

            #x좌표 기준 중위값 사람 선별
            if person_num%2 == 1:
                person_x = temp[int(person_num/2)]
            else:
                person_x = (temp[int(person_num/2)]+temp[int(person_num/2)-1])/2

            #중위값에 위치한 사람과 가까운 림 찾기
            temp = abs(person_x - (rim_data[:,0] + rim_data[:,2]/2))
            rim_data = rim_data[[np.argmin(temp)]]
            results = np.concatenate((rim_data, results[results[:,-1]!=4]))

        #볼 결정하기
        if len(rim_data) != 0 and len(ball_data) != 0:
            ball_data = self.ball_rim_closest(ball_data, rim_data[0])
            results = np.concatenate((ball_data, results[results[:,-1]!=1]))

        outs = results

        ball_state = True
        if len(ball_data) == 0:
            ball_state = False

        rim_state = True
        if len(rim_data) == 0:
            rim_state = False

        #### 슛시도 및 득점 인식 알고리즘 시작 ####

        #공과 림이 존재하면
        if ball_state and rim_state:

            #### shoot class ID 인식 #####
            if not self.deep_shot_mode and shoot_data.any():
                max_sh_idx = np.argmax(shoot_data[:,4])
                sh_data = shoot_data[max_sh_idx]
                if sh_data[4] > 0.5:
                    re_id.shot_id = self.shoot_id_check_reid(re_id, img, person_data, sh_data)
                    # re_id.shot_id = self.shoot_id_check_iou(id_dict, sh_data[0])
                    self.deep_shot_mode = True

                    self.shot_try_done = False
                    self.shot_made_done = False
                    self.shot_count = 0
                    self.made_count=0

                    self.exit_frame_num = self.frame_num + self.fps*1
                    self.shot_count = 0
            #### shoot class ID 인식 종료 #####

            #슛 시도 포인트 체크
            ball_x_state, ball_y_state = self.shot_try_check(ball_data, rim_data[0])

            #조건 만족하면 슛시도 인정
            if not self.shot_try_done and re_id.shot_id != -1:

                # shoot_class 2개 이하일 땐 x,y 좌표 모두 고려
                if self.shot_count < 2:
                    if (ball_x_state & ball_y_state):
                        self.shot_count+=1

                # shoot_class 2개 이상일 땐 x좌표만 고려
                else:
                    if ball_x_state:
                        self.shot_count+=1

                if self.shot_count >=4: #4 frame 이상 조건 만족할 시
                    re_id.player_dict[re_id.shot_id].shot_try_plus()
                    self.shot_try_done = True
                    self.shot_count = 0
                    self.shot_exit = self.frame_num + int(self.fps*(2/3))

        # 특정 thr 넘는 made class 추적 기간 동안 3번 이상 등장하면 득점 인정
        if not self.shot_made_done and self.shot_try_done:
            if self.shot_made_check(made_data, 0.65):
                self.made_count+=1
                if self.made_count >= 3: #3 frame 이상 조건 만족할 시
                    self.shot_made_done=True
                    re_id.player_dict[re_id.shot_id].shot_made_plus()
                    self.made_count=0

         # id 최대 1초 동안만 강제 유지
        if self.frame_num == self.exit_frame_num:
            self.deep_shot_mode=False

        # 슛 시도 flag 최대 0.7초 동안만 강제 유지
        # if self.frame_num == self.shot_exit:

        return outs

    def shot_try_check(self, ball_data, rim_data):
        ball_xposition = False
        ball_yposition = False
        b = ball_data[0]

        #공 중점의 y 좌표가 림의 상단 y 좌표값을 넘어야 함
        if (b[1]+b[3])/2 <= rim_data[1]:
            ball_yposition = True

        ball_center = ((b[0] + b[2])/2)
        rim_length = rim_data[2] - rim_data[0]
        left_limit = rim_data[0] - rim_length
        right_limit = rim_data[2]+ rim_length

        if left_limit <= ball_center <= right_limit:
            ball_xposition = True

        return ball_xposition, ball_yposition

    def shoot_id_check_iou(self, id_dict, sh_data):
        iou_lst = []
        iou_id = []
        for img_id in id_dict:
            iou = self.cal_iou(id_dict[img_id], sh_data)
            iou_lst.append(iou)
            iou_id.append(img_id)
        max_ps_idx = np.argmax(iou_lst)
        return iou_id[max_ps_idx]

    def shoot_id_check_reid(self, re_id, img, person_data, sh_data):
        #shooting 동작과 가장 가까운 person_data 찾기
        iou_lst = []
        for p in person_data:
            iou = self.cal_iou(p, sh_data)
            iou_lst.append(iou)
        max_ps_idx = np.argmax(iou_lst)

        #reid process
        person_idx_lst, person_img_lst1 = re_id.person_query_lst(img, person_data, thr=0.5)
        detected_query1 = person_img_lst1
        detected_query_stack1 = torch.stack(detected_query1, dim=0).to('cuda')

        with torch.no_grad():
            detected_query_vector1 = re_id.model(detected_query_stack1).detach().cpu().numpy()
        faiss.normalize_L2(detected_query_vector1)

        C, I = re_id.faiss_index.search(detected_query_vector1, 5)

        matched_list1 = re_id.hard_voting(I,C)
        # print(matched_list1[max_ps_idx])
        return matched_list1[max_ps_idx]

    def shot_made_check(self, made_data, thr):
        flag = False
        if made_data.any():
            #made classs는 하나라고 가정
            if made_data[0][4] > thr:
                flag = True
        return flag

    def close_pre_rim(self, pre_rim_data, rim_data):
        pr_x, pr_y, r_x, r_y = (pre_rim_data[0]+pre_rim_data[2])/2, (pre_rim_data[1]+pre_rim_data[3])/2, (rim_data[0]+rim_data[2])/2, (rim_data[1]+rim_data[3])/2
        x, y = abs(pr_x-r_x), abs(pr_y-r_y)

        return (x**2+y**2)**(1/2)

    def cal_iou(self, box1, box2):
        # box = (x1, y1, x2, y2)
        box1_area = (box1[2] - box1[0] + 1) * (box1[3] - box1[1] + 1)
        box2_area = (box2[2] - box2[0] + 1) * (box2[3] - box2[1] + 1)

        # obtain x1, y1, x2, y2 of the intersection
        x1 = max(box1[0], box2[0])
        y1 = max(box1[1], box2[1])
        x2 = min(box1[2], box2[2])
        y2 = min(box1[3], box2[3])

        # compute the width and height of the intersection
        w = max(0, x2 - x1 + 1)
        h = max(0, y2 - y1 + 1)

        inter = w * h
        iou = inter / (box1_area + box2_area - inter)
        return iou

    def ball_rim_closest(self, ball_data, rim_data):

        rx1, ry1, rx2, ry2 = rim_data[0], rim_data[1], rim_data[2], rim_data[3]
        c_rx = (rx2 + rx1)/2
        c_ry = (ry2 + ry1)/2

        d_lst = []
        for b in ball_data:
            bx1, by1, bx2, by2 = b[0], b[1], b[2], b[3]
            c_bx = (bx1 + bx2)/2
            c_by = (by1 + by2)/2
            dist = np.sqrt((c_rx-c_bx)**2 + (c_ry-c_by)**2)
            d_lst.append(dist)

        close_idx = np.argmin(d_lst)
        return ball_data[[close_idx]]

    # def ball_slope_cal(self, ball_data, pre_ball_data, eps=1e-9):
    #     x1, y1, x2, y2, c, l = ball_data[0]
    #     px1, py1, px2, py2, c, l = pre_ball_data[0]
    #     bc = np.array([(x1+x2)//2, (y1+y2)//2])
    #     pbc = np.array([(px1+px2)//2, (py1+py2)//2])
    #     x, y = bc - pbc
    #     slope = abs(y/(x+eps))
    #     return slope


import numpy as np
import pandas as pd
import codecademylib3
import matplotlib.pyplot as plt
import seaborn as sns

# ---------------
# Task 1: Drop nan values.
# ---------------

# Read the csv data as a DataFrame
df = pd.read_csv('./telescope_data.csv', index_col=0)

# Remove null and na values
df.dropna()

# Print the DataFrame head
print('Task 1:')
print(df.head())

# ---------------
# Task 2: Extract class column.
# ---------------
# Extract the class classes
classes = df['class']
data_matrix = df.drop(columns='class')

print('Task 2:')
print(data_matrix)

# ---------------
# Task 3: Create a correlation matrix.
# ---------------
# Use the `.corr()` method on `data_matrix` to get the correlation matrix
correlation_matrix = data_matrix.corr()

ax = plt.axes()
sns.heatmap(correlation_matrix, cmap='Greens', ax=ax)
ax.set_title('Task 3:')
plt.show()

# ---------------
# Task 4: Perform eigendecomposition.
# ---------------
print('Task 4:')

# Perform eigendecomposition using `np.linalg.eig`
eigenvalues, eigenvectors = np.linalg.eig(correlation_matrix)
print(f'Eigenvalues length: {eigenvalues.size}, Original Number of Features: {data_matrix.shape[1]}')

# Order the eigenvalues by ordering the indices of the eigenvalues using `argsort`, and use [::-1] to order them from greatest to smallest
indices = eigenvalues.argsort()[::-1]
eigenvalues = eigenvalues[indices]
eigenvectors = eigenvectors[:, indices]

print(eigenvalues.shape, eigenvectors.shape)

# ---------------
# Task 5: Find the variance/information percentages for each eigenvalue.
# ---------------
# Find the percentages of information for each eigenvector, which is generated by the proportion of its eigenvalues to the sum of all eigenvalues
information_proportions = eigenvalues / eigenvalues.sum()
information_percents = information_proportions * 100

# Plot the principal axes vs the information proportions for each principal axis
plt.figure()
plt.plot(information_percents, 'ro-', linewidth=2)
plt.title('Task 5: Scree Plot')
plt.xlabel('Principal Axes')
plt.ylabel('Percent of Information Explained')
plt.show()

# ---------------
# Task 6: Find the cumulative variance/information percentages for each eigenvalue.
# ---------------
# Find the cumulative sum of the percentages
cumulative_information_percents = np.cumsum(information_percents)

# Plot the cumulative percentages array
plt.figure()
plt.plot(cumulative_information_percents, 'ro-', linewidth=2)

# Also plot a horizontal line indicating the 95% mark, and a vertical line for the third principal axis
plt.hlines(y=95, xmin=0, xmax=15)
plt.vlines(x=6, ymin=0, ymax=100)
plt.title('Task 6: Cumulative Information percentages')
plt.xlabel('Principal Axes')
plt.ylabel('Cumulative Proportion of Variance Explained')
plt.show()


"""
This Python takes the dataframe with Core-US data, Bond price data and Ratings and
fetches the 12 month forward rating corresponding to quarterly date in question

It shift the 12 months future ratings to current quarterly record. it also calculate change in rating rank and insert as new column - rank_diff.
This will create the data frame all bonds in scope for model building.

"""

import numpy as np
import pandas as pd
from pathlib import Path
import datetime
import time
import asyncio
import os

pd.set_option('display.max_columns', 20)
pd.set_option('display.width', 2000)

ROOT_PATH = Path("../stock_data/project2/data")

def dateDiff(d1, d2):
       tdelta = (d1 - d2).days
       return tdelta

def getCusip(isin):
       if isin == "":
              return isin

       cusip = isin[2:len(isin)-1]
       return cusip

async def get_shfit_rating(core_df):
       start_time = time.time()
       shifted_df = pd.DataFrame()
       cusip_nums = core_df['cusip'].unique().tolist()
       count = 0

       await asyncio.sleep(0.1)

       # test_cols = ['cusip', 'date_fundamentals', 'date_fundamentals_shifted', 'rating', 'rating_shifted' 'rank','rank_shifted']
       print('Strated processing Core US cusips to shift ratings... ')

       for cusip in cusip_nums:
              try:
                     cusip_df = core_df.loc[core_df['cusip'] == cusip].sort_values(by='date_fundamentals')
                     cusip_df['date_fundamentals_shifted'] = cusip_df['date_fundamentals'].shift(-2)
                     cusip_df['rating_shifted'] = cusip_df['rating'].shift(-2)
                     cusip_df['rank_shifted'] = cusip_df['rank'].shift(-2)
                     cusip_df['rank_diff'] = cusip_df['rank'] - cusip_df['rank_shifted']

                     if not shifted_df.empty:
                            shifted_df = shifted_df.append(cusip_df)
                     else:
                            shifted_df = cusip_df

              except Exception as e:
                     print(e)

              count += 1

       if count % 10 == 0:
              print(f'{count} cusips processed... ')

       end_time = time.time()
       print("--- %s seconds ---" % (end_time - start_time))

       return shifted_df

async def main():
       data_files = ['nt3_cusips_core_rating_df.csv', 'nt4_cusips_core_df_rating.csv']
       dfs = []

       for file in data_files:
              file_path = Path.joinpath(ROOT_PATH, file)
              core_bond_df = pd.read_csv(file_path, infer_datetime_format=True, parse_dates=True)

              date_cols = ['date_fundamentals', 'mktclosedate', 'maturity_date', 'date']
              for date_col in date_cols:
                     core_bond_df[date_col] = pd.to_datetime(core_bond_df[date_col], errors='coerce').dt.date

              dfs.append(core_bond_df)

       core_us_df = pd.concat(dfs)

       core_cusips = sorted(core_us_df['cusip'].unique().tolist())
       # all_uscode = comp_cusips[0:100]

       chunk_size = 20
       chunks = [core_cusips[x: x + chunk_size] for x in range(0, len(core_cusips), chunk_size)]

       tasks = []
       final_core_df = []
       for chunk in chunks:
              # print(chunk)
              chunk_core_df = core_us_df.loc[core_us_df['cusip'].isin(chunk)]
              tasks.append(asyncio.create_task(get_shfit_rating(chunk_core_df)))

       await asyncio.gather(*tasks)

       for task in tasks:
              if len(task.result()) == 0:
                     continue
              final_core_df.append(task.result())

       return final_core_df

if __name__ == '__main__':

       start_time = time.time()
       res = asyncio.run(main())

       final_df = pd.concat(res)

       end_time = time.time()
       print("--- %s seconds ---" % (end_time - start_time))


import os
from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api_file_upload.settings")
app = Celery("api_file_upload")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

app.conf.beat_schedule = {
    'Testing new files': {
        'task': 'api.tasks.check_files',
        'schedule': 10
    },
}
app.conf.timezone = 'UTC'


# -*- coding: utf-8 -*-
"""
Created on Sat Jun 18 19:10:31 2022

@author: bbisht
"""

import argparse
import openpyxl
from openpyxl.styles import Alignment, PatternFill, Border, Side, Font
from openpyxl.styles.numbers import FORMAT_PERCENTAGE

parser = argparse.ArgumentParser()
parser.add_argument('--in_path', type = str, default = './', help = 'path to the stats file')
parser.add_argument('--in_file', type = str, default = 'stats', help = 'name of the stats file')
parser.add_argument('--sheet_name', type = str, default = 'Summary', help = 'name of this spreadsheet')

args = parser.parse_args()

in_path = str(args.in_path)
in_file = str(args.in_file)
sheet_name = str(args.sheet_name)
thin = Side(border_style="thin", color="000000")
head_font = Font(size=13, bold=True)
subhead_font = Font(size=13)
benchmarks = ['astar1', 'astarHMPKI', 'graph500', 'omnetpp', 'stream', 'xsbench']
sectors = ['512']

#convert column number to alphabetical letters
def colnum_string(n):
    string = ""
    while n > 0:
        n, remainder = divmod(n - 1, 26)
        string = chr(65 + remainder) + string
    return string

#fill the colors into specified portion of excel sheet
def fillCellColor(row_start, col_start, row_end, col_end, color):
	for row in range(row_start, row_end+1):
		for col in range(col_start, col_end+1):
			worksheet[colnum_string(col)+str(row)].fill = PatternFill(start_color=color, end_color=color, fill_type="solid")

#print the Heading
def printHeading(name, col, row):
    worksheet[colnum_string(col)+str(row)] = name
    worksheet[colnum_string(col)+str(row)].font = head_font

def printSubHeading(name, col, row):
    worksheet[colnum_string(col)+str(row)] = name
    worksheet[colnum_string(col)+str(row)].font = subhead_font

# open results excel-file
workbook = openpyxl.load_workbook(in_path+'/'+in_file+'.xlsx')
worksheet = workbook.create_sheet(title = sheet_name)

#print left side labels in excel sheet
worksheet.column_dimensions['A'].width = 30
worksheet.row_dimensions[1].height = 30
printHeading('Baseline with Trace command injections', 1, 1)
printHeading('HSF Size', 1, 2)
printSubHeading('MLC Miss %', 1, 3)
worksheet['A4'] = 'LLC Hit %'
worksheet['A5'] = 'Core-to-core transfer %'
worksheet['A6'] = 'Data from DRAM %'
printSubHeading('SF Snoops breakdown %', 1, 7)
worksheet['A8'] = 'shared data %'
worksheet['A9'] = 'remaining (migrate) %'
worksheet['A10'] = 'SF BackInv % (eviction%)'
worksheet['A11'] = 'SF data BackInv %'
printSubHeading('HSF miss (adds to DRAM%)', 1, 12)
worksheet['A13'] = 'HSF sector miss %'
worksheet['A14'] = 'HSF False hit %'
worksheet['A15'] = 'HSF BackInv %'
worksheet['A16'] = 'HSF BackInv to SF %'
worksheet['A17'] = 'HSF BackInv to LLC %'
worksheet['A18'] = 'avg CL\'s BackInv per sector'

col_no = 2
for bench in benchmarks:
    printHeading(bench, col_no, 1)
    worksheet.merge_cells(colnum_string(col_no)+'1:'+colnum_string(col_no+len(sectors)-1)+'1')
    worksheet[colnum_string(col_no)+'1'].alignment = Alignment(horizontal='center')

    worksheet[colnum_string(col_no)+'3'] = '='+bench+'_32MC_'+sectors[0]+'!D50/'+bench+'_32MC_'+sectors[0]+'!B50'
    worksheet[colnum_string(col_no)+'4'] = '='+bench+'_32MC_'+sectors[0]+'!AD50/'+bench+'_32MC_'+sectors[0]+'!B50'
    worksheet[colnum_string(col_no)+'5'] = '='+bench+'_32MC_'+sectors[0]+'!N50/'+bench+'_32MC_'+sectors[0]+'!B50'
    worksheet[colnum_string(col_no)+'6'] = '='+bench+'_32MC_'+sectors[0]+'!AK50/'+bench+'_32MC_'+sectors[0]+'!B50'
    worksheet[colnum_string(col_no)+'7'] = '='+colnum_string(col_no)+'5'
    worksheet[colnum_string(col_no)+'8'] = '='+bench+'_32MC_'+sectors[0]+'!S50/'+bench+'_32MC_'+sectors[0]+'!B50'
    worksheet[colnum_string(col_no)+'9'] = '=('+bench+'_32MC_'+sectors[0]+'!N50-'+bench+'_32MC_'+sectors[0]+'!S50)/'+bench+'_32MC_'+sectors[0]+'!B50'
    worksheet[colnum_string(col_no)+'10'] = '='+bench+'_32MC_'+sectors[0]+'!P50/'+bench+'_32MC_'+sectors[0]+'!B50'
    worksheet[colnum_string(col_no)+'11'] = '='+bench+'_32MC_'+sectors[0]+'!F50/'+bench+'_32MC_'+sectors[0]+'!B50'
    worksheet[colnum_string(col_no)+'12'] = '='+colnum_string(col_no)+'6'

    for row in range(3,13):
        worksheet[colnum_string(col_no)+str(row)].number_format = FORMAT_PERCENTAGE
        worksheet[colnum_string(col_no)+str(row)].alignment = Alignment(horizontal='center')
        worksheet.merge_cells(colnum_string(col_no)+str(row)+':'+colnum_string(col_no+len(sectors)-1)+str(row))

    for sector in sectors:
        worksheet.column_dimensions[colnum_string(col_no)].width = 13
        printSubHeading(sector+'B', col_no, 2)
        worksheet[colnum_string(col_no)+'13'] = '=('+bench+'_32MC_'+sector+'!AK50-'+bench+'_32MC_'+sector+'!AP50)/'+bench+'_32MC_'+sector+'!B50'
        worksheet[colnum_string(col_no)+'14'] = '='+bench+'_32MC_'+sector+'!AP50/'+bench+'_32MC_'+sector+'!B50'
        worksheet[colnum_string(col_no)+'15'] = '='+bench+'_32MC_'+sector+'!AM50/'+bench+'_32MC_'+sector+'!B50'
        worksheet[colnum_string(col_no)+'16'] = '='+bench+'_32MC_'+sector+'!H50/'+bench+'_32MC_'+sector+'!B50'
        worksheet[colnum_string(col_no)+'17'] = '='+bench+'_32MC_'+sector+'!AG50/'+bench+'_32MC_'+sector+'!B50'

        worksheet[colnum_string(col_no)+'18'] = '='+bench+'_32MC_'+sector+'!AM50/'+bench+'_32MC_'+sector+'!AN50/100'
        for row in range(13,19):
            worksheet[colnum_string(col_no)+str(row)].number_format = FORMAT_PERCENTAGE
            worksheet[colnum_string(col_no)+str(row)].alignment = Alignment(horizontal='center')
        col_no += 1

printHeading('Commnets', col_no, 1)
worksheet.column_dimensions[colnum_string(col_no)].width = 20
worksheet.merge_cells(colnum_string(col_no)+'1:'+colnum_string(col_no)+'2')
fillCellColor(1, 1, 2, col_no, '00CCCCFF')

#save and close the workbook
workbook.save(in_path+'/'+in_file+'.xlsx')
workbook.close()


'''
You can modify the parameters, return values and data structures used in every function if it conflicts with your
coding style or you want to accelerate your code.

You can also import packages you want.

But please do not change the basic structure of this file including the function names. It is not recommended to merge
functions, otherwise it will be hard for TAs to grade your code. However, you can add helper function if necessary.
'''

from flask import Flask, request
from flask import render_template
import time
import json
from scipy.interpolate import interp1d
import numpy as np
import math
from sklearn.metrics.pairwise import euclidean_distances
import sys

app = Flask(__name__)

centroids_X = [50, 205, 135, 120, 100, 155, 190, 225, 275, 260, 295, 330, 275, 240, 310, 345, 30, 135, 85, 170, 240, 170, 65, 100, 205, 65]
centroids_Y = [85, 120, 120, 85, 50, 85, 85, 85, 50, 85, 85, 85, 120, 120, 50, 50, 50, 50, 85, 50, 50, 120, 50, 120, 50, 120]

words, probabilities = [], {}
template_points_X, template_points_Y = [], []
file = open('words_10000.txt')
content = file.read()
file.close()
content = content.split('\n')
for line in content:
    line = line.split('\t')
    words.append(line[0])
    probabilities[line[0]] = float(line[2])
    template_points_X.append([])
    template_points_Y.append([])
    for c in line[0]:
        template_points_X[-1].append(centroids_X[ord(c) - 97])
        template_points_Y[-1].append(centroids_Y[ord(c) - 97])

alphaList = np.zeros((100))

template_sample_points_X = []
template_sample_points_Y = []

def generate_sample_points(pointsX, pointsY):
    '''Generate 100 sampled points for a gesture.

    In this function, we should convert every gesture or template to a set of 100 points, such that we can compare
    the input gesture and a template.

    :param points_X: A list of X-axis values of a gesture.
    :param points_Y: A list of Y-axis values of a gesture.

    :return:
        sample_points_X: A list of X-axis values of a gesture after sampling, containing 100 elements.
        sample_points_Y: A list of Y-axis values of a gesture after sampling, containing 100 elements.
    '''

    sample_points_X, sample_points_Y = [], []
    euclideanDistances = np.sqrt(np.ediff1d(pointsX, to_begin= 0)**2 + np.ediff1d(pointsY, to_begin= 0)**2)
    prefixSums  = np.cumsum(euclideanDistances)

    prefixSums = prefixSums / prefixSums[-1]

    transFactorX = interp1d(prefixSums, pointsX)
    transFactorY = interp1d(prefixSums, pointsY)

    alphaSpace = np.linspace(0, 1, 100)

    sampleX = transFactorX(alphaSpace).tolist()
    sampleY = transFactorY(alphaSpace).tolist()

    return sampleX, sampleY

def normalizePoints(x, y):
    scaledCentroidX = np.mean(x, axis=1)
    scaledCentroidY = np.mean(y, axis=1)

    xTrans = -scaledCentroidX
    yTrans = -scaledCentroidY

    translation_matrix_X = np.reshape(xTrans, (-1, 1))
    translation_matrix_Y = np.reshape(yTrans, (-1, 1))

    return translation_matrix_X + x, translation_matrix_Y + y

def sampleTemplates(templatePointsX, templatePointsY):
    global template_sample_points_X
    global template_sample_points_Y
    for i in range(10000):
        sampleX, sampleY = generate_sample_points(templatePointsX[i], templatePointsY[i])
        template_sample_points_X.append(sampleX)
        template_sample_points_Y.append(sampleY)

normalized_template_sample_points_X  = []
normalized_template_sample_points_Y = []
def getDifference(li):
    max = np.max(li, axis = 1)
    min = np.min(li, axis = 1)
    return max - min

def preProcess(template_sample_points_X, template_sample_points_Y):
    global normalized_template_sample_points_X
    global normalized_template_sample_points_Y
    L = 250
    W = getDifference(template_sample_points_X)
    H = getDifference(template_sample_points_Y)

    # S = L / Max(W, H)
    s = L / np.max(np.array([W, H]), axis=0)

    scale = np.diag(s)
    scaled_template_points_X = np.matmul(scale, template_sample_points_X)
    scaled_template_points_Y = np.matmul(scale, template_sample_points_Y)

    normalized_template_sample_points_X, normalized_template_sample_points_Y = normalizePoints(scaled_template_points_X, scaled_template_points_Y)

def do_pruning(gesture_points_X, gesture_points_Y, template_sample_points_X, template_sample_points_Y):
    '''Do pruning on the dictionary of 10000 words.

    In this function, we use the pruning method described in the paper (or any other method you consider reasonable)
    to narrow down the number of valid words so that ambiguity can be avoided.

    :param gesture_points_X: A list of X-axis values of input gesture points, which has 100 values since we have
        sampled 100 points.
    :param gesture_points_Y: A list of Y-axis values of input gesture points, which has 100 values since we have
        sampled 100 points.
    :param template_sample_points_X: 2D list, containing X-axis values of every template (10000 templates in total).
        Each of the elements is a 1D list and has the length of 100.
    :param template_sample_points_Y: 2D list, containing Y-axis values of every template (10000 templates in total).
        Each of the elements is a 1D list and has the length of 100.

    :return:
        valid_words: A list of valid words after pruning.
        valid_probabilities: The corresponding probabilities of valid_words.
        valid_template_sample_points_X: 2D list, the corresponding X-axis values of valid_words. Each of the elements
            is a 1D list and has the length of 100.
        valid_template_sample_points_Y: 2D list, the corresponding Y-axis values of valid_words. Each of the elements
            is a 1D list and has the length of 100.
    '''
    valid_words, valid_template_sample_points_X, valid_template_sample_points_Y = [], [], []

    threshold = 20

    startIdx = gesture_points_X[0]
    startJdx = gesture_points_Y[0]

    endIdx = gesture_points_X[len(gesture_points_X) - 1]
    endJdx = gesture_points_Y[len(gesture_points_Y) - 1]

    start = np.array([startIdx, startJdx])
    end = np.array([endIdx, endJdx])

    startPoints = []
    endPoints = []

    for idx in range(len(template_sample_points_X)):
        startPoints.append([template_sample_points_X[idx][0], template_sample_points_Y[idx][0]])
    startPoints = np.array(startPoints)

    for idx in range(len(template_sample_points_X)):
        endPoints.append([template_sample_points_X[idx][-1], template_sample_points_Y[idx][-1]])
    endPoints = np.array(endPoints)

    start = np.reshape(start, (1, -1))
    end = np.reshape(end, (1, -1))
    startDiff = euclidean_distances(start, startPoints)[0]
    endDiff = euclidean_distances(end, endPoints)[0]

    refinedIdxes = []

    for idx in range(len(startDiff)):
        if startDiff[idx] + endDiff[idx] < threshold:
            refinedIdxes.append(idx)

    refinedXes = []
    refinedYes = []
    refinedXes = np.array(template_sample_points_X)[refinedIdxes]
    refinedYes = np.array(template_sample_points_Y)[refinedIdxes]
    refinedWords = [words[idx] for idx in refinedIdxes]

    return refinedIdxes, refinedWords, refinedXes, refinedYes

def get_scaled_points(sample_points_X, sample_points_Y, L):
    x_maximum = max(sample_points_X)
    x_minimum = min(sample_points_X)
    W = x_maximum - x_minimum
    y_maximum = max(sample_points_Y)
    y_minimum = min(sample_points_Y)
    H = y_maximum - y_minimum
    r = L/max(H, W)

    gesture_X, gesture_Y = [], []
    for point_x, point_y in zip(sample_points_X, sample_points_Y):
        gesture_X.append(r * point_x)
        gesture_Y.append(r * point_y)

    centroid_x = (max(gesture_X) - min(gesture_X))/2
    centroid_y = (max(gesture_Y) - min(gesture_Y))/2
    scaled_X, scaled_Y = [], []
    for point_x, point_y in zip(gesture_X, gesture_Y):
        scaled_X.append(point_x - centroid_x)
        scaled_Y.append(point_y - centroid_y)
    return scaled_X, scaled_Y

def getNormalizedPoints(scaledIdxes):
    centroidDashX = np.mean(scaledIdxes[0])
    xDash = 0 - centroidDashX

    centroidDashY = np.mean(scaledIdxes[1])
    yDash = 0 - centroidDashY

    changeFactor = np.array([[xDash],
                                   [yDash]])

    normalizedPoints = changeFactor + scaledIdxes

    return normalizedPoints

def populateAlphas():
    global alphaList

    for num in range(50):

        alphaList[50 - num - 1], alphaList[50 + num] = num/2450, num/2450

def get_shape_scores(refinedIdxes, gesture_sample_points_X, gesture_sample_points_Y, valid_template_sample_points_X, valid_template_sample_points_Y):
    '''Get the shape score for every valid word after pruning.

    In this function, we should compare the sampled input gesture (containing 100 points) with every single valid
    template (containing 100 points) and give each of them a shape score.

    :param gesture_sample_points_X: A list of X-axis values of input gesture points, which has 100 values since we
        have sampled 100 points.
    :param gesture_sample_points_Y: A list of Y-axis values of input gesture points, which has 100 values since we
        have sampled 100 points.
    :param valid_template_sample_points_X: 2D list, containing X-axis values of every valid template. Each of the
        elements is a 1D list and has the length of 100.
    :param valid_template_sample_points_Y: 2D list, containing Y-axis values of every valid template. Each of the
        elements is a 1D list and has the length of 100.

    :return:
        A list of shape scores.
    '''
    # TODO: Set your own L

    # TODO: Calculate shape scores (10 points)
    shape_scores = []
    L = 200

    maxX = np.max(gesture_sample_points_X)
    minX = np.min(gesture_sample_points_X)

    width = maxX - minX

    maxY = np.max(gesture_sample_points_Y)
    minY = np.min(gesture_sample_points_Y)

    height = maxY - minY

    s = L / max(width, height, 1)

    originalIdxes = np.array([gesture_sample_points_X,
                                   gesture_sample_points_Y])

    scale = np.array([[s, 0],[0, s]])

    scaledIdxes = np.matmul(scale, originalIdxes)

    normalizedPoints = getNormalizedPoints(scaledIdxes)

    valid_normalized_template_sample_points_X = normalized_template_sample_points_X[refinedIdxes]
    valid_normalized_template_sample_points_Y = normalized_template_sample_points_Y[refinedIdxes]

    return getScore(normalizedPoints, valid_normalized_template_sample_points_X, valid_normalized_template_sample_points_Y)

def getScore(normalizedPoints, valid_normalized_template_sample_points_X, valid_normalized_template_sample_points_Y):
    xi = valid_normalized_template_sample_points_X
    xj = np.reshape(normalizedPoints[0], (1, -1))
    xi_j = (xi - xj) ** 2

    yi = valid_normalized_template_sample_points_Y
    yj = np.reshape(normalizedPoints[1], (1, -1))
    yi_j = (yi - yj) ** 2

    score = np.sum((xi_j + yi_j) ** 0.5, axis=1) / 100

    return score

def get_small_d(p_X, p_Y, q_X, q_Y):
    min_distance = []
    for n in range(0, 100):
        distance = math.sqrt((p_X - q_X[n])**2 + (p_Y - q_Y[n])**2)
        min_distance.append(distance)
    return (sorted(min_distance)[0])

def get_big_d(p_X, p_Y, q_X, q_Y, r):
    final_max = 0
    for n in range(0, 100):
        local_max = 0
        distance = get_small_d(p_X[n], p_Y[n], q_X, q_Y)
        local_max = max(distance-r , 0)
        final_max += local_max
    return final_max

def get_delta(u_X, u_Y, t_X, t_Y, r, i):
    D1 = get_big_d(u_X, u_Y, t_X, t_Y, r)
    D2 = get_big_d(t_X, t_Y, u_X, u_Y, r)
    if D1 == 0 and D2 == 0:
        return 0
    else:
        return math.sqrt((u_X[i] - t_X[i])**2 + (u_Y[i] - t_Y[i])**2)

def get_location_scores(gesture_sample_points_X, gesture_sample_points_Y, valid_template_sample_points_X, valid_template_sample_points_Y):
    '''Get the location score for every valid word after pruning.

    In this function, we should compare the sampled user gesture (containing 100 points) with every single valid
    template (containing 100 points) and give each of them a location score.

    :param gesture_sample_points_X: A list of X-axis values of input gesture points, which has 100 values since we
        have sampled 100 points.
    :param gesture_sample_points_Y: A list of Y-axis values of input gesture points, which has 100 values since we
        have sampled 100 points.
    :param template_sample_points_X: 2D list, containing X-axis values of every valid template. Each of the
        elements is a 1D list and has the length of 100.
    :param template_sample_points_Y: 2D list, containing Y-axis values of every valid template. Each of the
        elements is a 1D list and has the length of 100.

    :return:
        A list of location scores.
    '''

    location_scores = [0 for _ in range(len(valid_template_sample_points_X))]
    location_scores = np.array(location_scores)

    radius = 15
    # TODO: Calculate location scores (10 points)
    gestureIdxes = []
    gestureIdxesX = []
    gestureIdxesY = []
    for idx in range(100):
        gestureIdxesX.append(gesture_sample_points_X[idx])
    for idx in range(100):
        gestureIdxesY.append(gesture_sample_points_Y[idx])
    print(len(gestureIdxesX), len(gestureIdxesY))
    for idx in range(100):
        gestureIdxes.append([gestureIdxesX[idx], gestureIdxesY[idx]])

    for i in range(len(valid_template_sample_points_X)):

        templateIdxes = []
        templateIdxesX = []
        templateIdxesY = []
        # for jdx in range(num_sample_points):
        #     templateIdxesX.append(valid_template_sample_points_X[i][jdx])
        # for jdx in range(num_sample_points):
        #     templateIdxesY.append(valid_template_sample_points_Y[i][jdx])
        # print(len(gestureIdxesX), len(gestureIdxesY))
        # for jdx in range(num_sample_points):
        #     templateIdxes.append([templateIdxesX[jdx], templateIdxesY[jdx]])
        for jdx in range(100):
            xCoord = valid_template_sample_points_X[i][jdx]
            yCoord = valid_template_sample_points_Y[i][jdx]
            templateIdxes.append([xCoord, yCoord])

        currentDistances = euclidean_distances(gestureIdxes, templateIdxes)

        closestGesturePoint = np.min(currentDistances, axis=0)

        closestTemplatePoint = np.min(currentDistances, axis=1)
        flag = True if np.any(closestTemplatePoint > radius) or np.any(closestGesturePoint > radius) else False
        if flag:
            deltaList = np.diagonal(currentDistances)
            location_scores[i] = np.sum(np.multiply(alphaList, deltaList))

    return location_scores

def get_integration_scores(shape_scores, location_scores):
    integration_scores = []

    a = 0.4
    b = 0.6
    for i in range(len(shape_scores)):
        integration_scores.append(a * shape_scores[i] + b * location_scores[i])
    return integration_scores

def get_best_word(valid_words, integration_scores):
    '''Get the best word.

    In this function, you should select top-n words with the highest integration scores and then use their corresponding
    probability (stored in variable "probabilities") as weight. The word with the highest weighted integration score is
    exactly the word we want.

    :param valid_words: A list of valid words.
    :param integration_scores: A list of corresponding integration scores of valid_words.
    :return: The most probable word suggested to the user.
    '''

    n = 3

    if n >= len(integration_scores):
        n = len(integration_scores) - 1
    # Ref: https://stackoverflow.com/questions/34226400/find-the-index-of-the-k-smallest-values-of-a-numpy-array
    nIdxes = np.argpartition(integration_scores, n)

    topWords = [ valid_words[idx] for idx in nIdxes ]

    bestScore = integration_scores[nIdxes[0]] * (1 - probabilities[topWords[0]])
    bestWord = topWords[0]

    for word, idx in zip(topWords[1::], nIdxes[1::]):
        currentScore = integration_scores[idx] * (1 - probabilities[word])
        if currentScore < bestScore:
            bestScore = currentScore
            bestWord = word
        # print(bestScore, bestWord)
    return bestWord

@app.route("/")
def init():
    # sampleTemplates(template_points_X, template_points_Y)
    # preProcess(template_sample_points_X, template_sample_points_Y)
    return render_template('index.html')

@app.route('/shark2', methods=['POST'])
def shark2():
    global template_sample_points_X
    global template_sample_points_Y
    global normalized_template_sample_points_X
    global normalized_template_sample_points_Y
    start_time = time.time()
    data = json.loads(request.get_data())

    gesture_points_X = []
    gesture_points_Y = []
    for i in range(len(data)):
        gesture_points_X.append(data[i]['x'])
        gesture_points_Y.append(data[i]['y'])
    template_sample_points_X = []
    template_sample_points_Y = []
    normalized_template_sample_points_X = []
    normalized_template_sample_points_Y = []

    sampleTemplates(template_points_X, template_points_Y)
    preProcess(template_sample_points_X, template_sample_points_Y)
    gesture_sample_points_X, gesture_sample_points_Y = generate_sample_points(gesture_points_X, gesture_points_Y)

    refinedIndexes, valid_words, valid_template_sample_points_X, valid_template_sample_points_Y = do_pruning(gesture_points_X, gesture_points_Y, template_sample_points_X, template_sample_points_Y)
    if len(valid_words) == 0:
        end_time = time.time()
        return '{"Word not found", "elapsed_time":"' + str(round((end_time - start_time) * 1000, 5)) + 'ms"}'

    best_word = "Word not found"

    shape_scores = get_shape_scores(refinedIndexes, gesture_sample_points_X, gesture_sample_points_Y, valid_template_sample_points_X, valid_template_sample_points_Y)

    location_scores = get_location_scores(gesture_sample_points_X, gesture_sample_points_Y, valid_template_sample_points_X, valid_template_sample_points_Y)

    integration_scores = get_integration_scores(shape_scores, location_scores)

    best_word = get_best_word(valid_words, integration_scores)

    end_time = time.time()

    return '{"best_word":"' + best_word + '", "elapsed_time":"' + str(round((end_time - start_time) * 1000, 5)) + 'ms"}'

if __name__ == "__main__":
    app.run()


import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

###########################################
### Calculate dimension in pixel way ######
###########################################
df_tobii = pd.read_csv(r'C:\Users\lguo8\PycharmProjects\EyeTracking_baseline\Tobii\luyao_12_13_3_light_on\20221213T071219Z\cv2_pixels\pixel_coordinates.csv',header=None )
df_tobii.columns = ['tobii_pixel_x', 'tobii_pixel_y', 'baseline_x', 'baseline_y']

df_world_camera = pd.read_csv(r'C:\Users\lguo8\PycharmProjects\EyeTracking_baseline\Tobii\luyao_12_13_3_light_on\20221213T071219Z\cv2_pixels\pixel_world_camera.csv',header=None)
tobii_pixel_x = df_tobii.iloc[:]['tobii_pixel_x'].tolist()
tobii_pixel_y = df_tobii.iloc[:]['tobii_pixel_y'].tolist()
baseline_x = df_tobii.iloc[:]['baseline_x'].tolist()
baseline_y = df_tobii.iloc[:]['baseline_y'].tolist()
# plt.scatter(tobii_pixel_x, tobii_pixel_y)
# plt.show()

# define screen outlines pixels
# assume as rectangle

for i in range(len(baseline_x)):
    if baseline_x[i]==0 and baseline_y[i]== 8:
        index_left_top = i
    elif baseline_x[i]==0 and baseline_y[i]== 0:
        index_left_bottom = i
    elif baseline_x[i]==8 and baseline_y[i]== 0:
        index_right_bottom = i
    elif baseline_x[i]==8 and baseline_y[i]== 8:
        index_right_top = i
# print(index_left_top)
# due to the fact that pixel is defined by dot, a few space is saved for dot size
value_given = 6
# pixel in y axis is reverse to baseline to y axis

pixel_left_bottom = (tobii_pixel_x[index_left_bottom]-value_given, tobii_pixel_y[index_left_bottom]-value_given)
pixel_left_top = (tobii_pixel_x[index_left_top]-value_given, tobii_pixel_y[index_left_top]+value_given)
pixel_right_bottom = (tobii_pixel_x[index_right_bottom]+value_given, tobii_pixel_y[index_right_bottom]-value_given)
pixel_right_top = (tobii_pixel_x[index_right_top]+value_given, tobii_pixel_y[index_right_top]+value_given)

# use left_bottom, right_bottom and righttop to define screen outlines
x=[pixel_left_bottom[0],pixel_right_top[0],pixel_right_top[0],pixel_right_bottom[0]]
y=[pixel_left_bottom[1],pixel_right_top[1],pixel_right_top[1],pixel_right_bottom[1]]

# plt.plot(x,y, 'ro')

def connectpoints(x,y,p1,p2):
    x1, x2 = x[p1], x[p2]
    y1, y2 = y[p1], y[p2]
    plt.plot([x1,x2],[y1,y2],'k-')

# treat these 2 lines as screen outlines
plt.plot([x[0],x[3]],[y[0],y[3]],'k-')
plt.plot([x[2],x[3]],[y[2],y[3]],'k-')

# calculate slopes and intercepts
slope_bottom = (y[3]-y[0])/(x[3]-x[0])
intercept_bottom = y[3]-slope_bottom*x[3]

screen_left_top_x = x[0]+x[2]-x[3]
screen_left_top_y = y[0]+y[2]-y[3]
plt.plot([x[0],screen_left_top_x],[y[0],screen_left_top_y],'k-')
plt.plot([x[2],screen_left_top_x],[y[2],screen_left_top_y],'k-')

# define xlim and ylim by world_camera_pixels
world_x_min = df_world_camera.iloc[0][0]
world_x_max = df_world_camera.iloc[2][0]
world_y_min = df_world_camera.iloc[0][1]
world_y_max = df_world_camera.iloc[2][1]
plt.xlim([world_x_min, world_x_max])
plt.ylim([world_y_min, world_y_max])
# plt.axis('equal')
plt.show()

screen_x=[screen_left_top_x,pixel_left_bottom[0],pixel_right_top[0],pixel_right_bottom[0]]
screen_y=[screen_left_top_y-value_given,pixel_left_bottom[1],pixel_right_top[1],pixel_right_bottom[1]]

dot_gap_x = (screen_x[3]-screen_x[1])/8
dot_gap_y = (screen_y[2]-screen_y[3])/8
dot_scatter_x = []
dot_scatter_y = []
baseline_x_temp = []
baseline_y_temp = []

screen_bottom_line_y_segment = np.linspace(screen_y[1],screen_y[3],9)

for i in range(9):
    for j in range(8,-1,-2):
        dot_scatter_x.append(dot_gap_x*i+screen_x[1])
        dot_scatter_y.append(dot_gap_y * abs(j-8)+ screen_bottom_line_y_segment[i])
        plt.scatter(dot_scatter_x,dot_scatter_y, color='black')
        baseline_x_temp.append(i)
        baseline_y_temp.append(abs(j-8))
        # print(j)
print(dot_scatter_y)
print(baseline_y_temp)
# plt.show()
# print(dot_scatter_y)
# print(baseline_x)
# change order
order = [4,9,14,19,24,29,34,39,44]
order2 = []
for i in range(5):
    for j in order:
        order2.append((j-i))

# in real dimension cm
screen_width_cm = 154
screen_height_cm = 87
screen_width_pixel = x[2]-x[0]
screen_height_pixel = y[2]-y[3]
pixel_per_cm = 154/screen_width_pixel
with open('pixel_per_cm.txt','w') as f:
    f.write(str(pixel_per_cm))
dot_cm_x = []
dot_cm_y = []

for i in range(len(dot_scatter_x)):
    # dot_cm_x.append((dot_scatter_x[i]+x[1])*pixel_per_cm)
    # dot_cm_y.append((dot_scatter_y[i] + y[1]) * pixel_per_cm)
    dot_cm_x.append((dot_scatter_x[i]) * pixel_per_cm)
    dot_cm_y.append((dot_scatter_y[i]) * pixel_per_cm)
print(dot_cm_y)
# #change order
dot_cm_x = [dot_cm_x[i] for i in order2]
dot_cm_y = [dot_cm_y[i] for i in order2]
baseline_x_temp = [baseline_x_temp[i] for i in order2]
baseline_y_temp = [baseline_y_temp[i] for i in order2]
print(baseline_y_temp)
print(baseline_x_temp)

# Get world camera scene dimensions in cm
world_x_range_pixel = world_x_max-world_x_min
world_y_range_pixel = world_y_max-world_y_min
world_x_range_cm = world_x_range_pixel*pixel_per_cm
world_y_range_cm = world_y_range_pixel*pixel_per_cm

f = open('world_cm.txt','w')
f.write(str(world_x_range_cm)+"\n")
f.write(str(world_y_range_cm))
f.close()

# change coordinate: origin from left-top to left_bottom
# dot_cm_y = [(world_y_range_cm-dot_cm_y[i]) for i in range(len(dot_cm_y))]

dot_cm_x = np.tile(dot_cm_x,3)
dot_cm_y = np.tile(dot_cm_y,3)
baseline_x_temp = np.tile(baseline_x_temp,3)
baseline_y_temp = np.tile(baseline_y_temp,3)
# print(baseline_y_temp)

df_cm = pd.DataFrame(dot_cm_x,columns=['dot_x_cm'])
df_cm['dot_y_cm'] = pd.DataFrame(dot_cm_y)
df_cm['baseline_x_temp'] = pd.DataFrame(baseline_x_temp)
df_cm['baseline_y_temp'] = pd.DataFrame(baseline_y_temp)

df_dot = pd.read_csv('dot_data_0_8_8_8_2022_12_13_18_12_27.csv')
df_cm = pd.concat([df_dot,df_cm],axis=1)
df_cm.to_csv('cm.csv',index=False)

# Get world camera scene dimensions in cm
world_x_range_pixel = world_x_max-world_x_min
world_y_range_pixel = world_y_max-world_y_min
world_x_range_cm = world_x_range_pixel*pixel_per_cm
world_y_range_cm = world_y_range_pixel*pixel_per_cm
f = open('world_cm.txt','w')
f.write(str(world_x_range_cm)+"\n")
f.write(str(world_y_range_cm))
f.close()


import logging

from spider.base_spider import Spider
from bs4 import BeautifulSoup
from collections import defaultdict

class SellInfoSpider(Spider):
    def __init__(self, district_dao, date):
        self.logger = logging.getLogger(self.get_name())
        handler = logging.StreamHandler()
        self.logger.addHandler(handler)
        self.logger.setLevel(logging.INFO)
        super().__init__()
        self.district_dao = district_dao
        self.date = date

    # def __init__(self):
    #     self.logger = logging.getLogger(spider.get_name())
    #     handler = logging.StreamHandler()
    #     self.logger.addHandler(handler)
    #     self.logger.setLevel(logging.INFO)
    #     pass

    def get_name(self):
        return "sell_info"

    def start_urls(self):
        self.district_info = self.district_dao.select_with_date(self.date)
        self.cur_district_idx = 0
        if len(self.district_info) > 0:
            return [self.district_info[0]['url']]
        return []

    def parse_price(self, price_str):
        tmp = ''
        for ch in price_str:
            if ch.isdigit() or ch == '.':
                tmp += ch
        try:
            rt = float(tmp)
        except:
            rt = 0.0
        return rt

    def parse_area(self, area_str):
        tmp = ''
        for ch in area_str:
            if ch.isdigit() or ch == '.':
                tmp += ch
        try:
            rt = float(tmp)
        except:
            rt = 0.0
        return rt

    def parse_one_item(self, li, date, district, superior):
        lianjia_id = ""
        link = ""
        img_link = ""
        title = ""
        goodhouse = False
        position = ""
        detail = ""
        follow = ""
        tags = []
        area = 0
        total_price_str = ""
        total_price = 0
        unit_price_str = ""
        unit_price = 0

        img_tag = li.select('a.img')
        if len(img_tag) > 0:
            img_tag = img_tag[0]
            link = img_tag.attrs['href']
            inner_img_tag = img_tag.select('img.lj-lazy')
            if len(inner_img_tag) > 0:
                inner_img_tag = inner_img_tag[0]
                img_link = inner_img_tag.attrs['src']

        title_tag = li.select('div.title')
        if len(title_tag) > 0:
            title_tag = title_tag[0]
            inner_title_tag = title_tag.select('a')
            if len(inner_title_tag) > 0:
                inner_title_tag = inner_title_tag[0]
                title = inner_title_tag.text
                link_ = inner_title_tag.attrs['href']
                if link == "":
                    link = link_
            goodhouse_tag = title_tag.select('span.goodhouse_tag')
            if len(goodhouse_tag) > 0:
                goodhouse = True
        if len(link) > 0:
            link_last_part = link.split('/')[-1]
            if link_last_part.endswith('.html'):
                lianjia_id = link_last_part[:-5]
            elif link_last_part.endswith('.htm'):
                lianjia_id = link_last_part[:-4]
        position_tag = li.select('div.positionInfo')
        if len(position_tag) > 0:
            position = position_tag[0].text.replace(' ', '')
        detail_tag = li.select('div.houseInfo')
        if len(detail_tag) > 0:
            detail = detail_tag[0].text
        detail_splits = detail.split('|')
        if len(detail_splits) >= 2:
            area = self.parse_area(detail_splits[1])
        follow_tag = li.select('div.followInfo')
        if len(follow_tag) > 0:
            follow = follow_tag[0].text
        tag_tag = li.select('div.tag')
        if len(tag_tag) > 0:
            tag_tag = tag_tag[0]
            for tag_child in tag_tag.children:
                tags.append(tag_child.text)
        total_price_tag = li.select('div.totalPrice')
        if len(total_price_tag) > 0:
            total_price_tag = total_price_tag[0]
            total_price_str = total_price_tag.text
            total_price = self.parse_price(total_price_str)
        unit_price_tag = li.select('div.unitPrice')
        if len(unit_price_tag) > 0:
            unit_price_tag = unit_price_tag[0]
            unit_price_str = unit_price_tag.text
            unit_price = self.parse_price(unit_price_str)
        return {
            'lianjia_id': lianjia_id,
            'link': link,
            'title': title,
            'goodhouse': goodhouse,
            'position': position,
            'detail': detail,
            'follow': follow,
            'tags': tags,
            'area': area,
            'total_price_str': total_price_str,
            'total_price': total_price,
            'unit_price_str': unit_price_str,
            'unit_price': unit_price,
            'district': district,
            'superior': superior,
            'date': date
        }

    def parse(self, url, resp, date=None, **kwargs):
        district = self.district_info[self.cur_district_idx]['name']
        superior = self.district_info[self.cur_district_idx]['superior']
        date = self.date
        statistic = defaultdict(int)

        page = 1
        url_splits = url.rstrip('/\r\n').split('/')
        url_last_part = url_splits[-1]
        if url_last_part.startswith('pg'):
            try:
                page = int(url_last_part[2:])
            except:
                page = 0

        parse_items = []
        content = resp.content.decode()
        soup = BeautifulSoup(content)
        content_list = soup.select('ul.sellListContent')
        # 没有项目，说明这个地区到头了
        # 要是到100了，可能是哪里有问题死循环了，也跳转到下一个地区。
        if len(content_list) == 0 or page == 100:
            if self.cur_district_idx == len(self.district_info) - 1:
                return parse_items, [], {}
            else:
                self.cur_district_idx += 1
                next_district_url = self.district_info[self.cur_district_idx]['url']
                return parse_items, [next_district_url], {}
        content_list = content_list[0]
        lis = content_list.find_all("li")
        for li in lis:
            try:
                item = self.parse_one_item(li, date, district, superior)
            except:
                item = None
            if item is not None:
                parse_items.append(item)
            else:
                statistic['sell_info_parse_fail'] += 1
        new_urls = []

        # page解析成功，跳转到下一页
        if page > 0:
            page += 1
            if url_last_part.startswith('pg'):
                url_splits[-1] = 'pg' + str(page)
            else:
                url_splits.append('pg' + str(page))
            new_urls.append('/'.join(url_splits))
            return parse_items, new_urls, statistic
        # page == 0: 解析错了，跳到下一个地区
        else:
            if self.cur_district_idx == len(self.district_info) - 1:
                return parse_items, [], {}
            else:
                self.cur_district_idx += 1
                next_district_url = self.district_info[self.cur_district_idx]['url']
                return parse_items, [next_district_url], {}

#
# spider = SellInfoSpider()
# url = 'https://bj.lianjia.com/ershoufang/andingmen/pg8'
# resp = requests.get(url)
# spider.parse(url, resp)


import asyncio
import functools
import inspect
import jsons
import logging
import os
import signal
from enum import Enum
from typing import Optional
from azure.servicebus.aio import ServiceBusClient, AutoLockRenewer, ServiceBusReceiver
from azure.servicebus import ServiceBusReceivedMessage
from azure.identity.aio import WorkloadIdentityCredential
from pydantic import BaseModel, parse_obj_as
from timeit import default_timer as timer

from . import case
from dotenv import load_dotenv

# TODO - refactor config storage/handling
load_dotenv()

CONNECTION_STR = os.environ.get("SERVICE_BUS_CONNECTION_STRING")
AZURE_CLIENT_ID = os.getenv("AZURE_CLIENT_ID", "")
AZURE_TENANT_ID = os.getenv("AZURE_TENANT_ID", "")
AZURE_AUTHORITY_HOST = os.getenv("AZURE_AUTHORITY_HOST", "")
AZURE_FEDERATED_TOKEN_FILE = os.getenv("AZURE_FEDERATED_TOKEN_FILE", "")
SERVICE_BUS_NAMESPACE = os.getenv("SERVICE_BUS_NAMESPACE", "")

MAX_MESSAGE_COUNT = int(os.getenv("MAX_MESSAGE_COUNT", "10"))
MAX_WAIT_TIME = int(os.getenv("MAX_WAIT_TIME", "30"))
MAX_LOCK_RENEWAL_DURATION = int(os.getenv("MAX_LOCK_RENEWAL_DURATION", "300"))

SUBSCRIBER_FILTER = os.getenv("SUBSCRIBER_FILTER", None)

class ConsumerResult(Enum):
    """ConsumerResult is used to indicate the result when a consumer processes a message"""

    SUCCESS = 0
    """The message was processed successfully and should be marked as completed"""

    RETRY = 1
    """The message was not processed successfully and should be marked as abandoned and retried"""

    DROP = 2
    """The message was not processed successfully but is invalid and should be sent to the dead-letter queue"""

class StateChangeEventBase(BaseModel):
    """StateChangeEventBase is the base type for state change events"""

    entity_id: str

    def get_event_classes():
        event_classes = []
        StateChangeEventBase._append_event_classes_for_type(event_classes, StateChangeEventBase)
        return event_classes

    def _append_event_classes_for_type(event_classes, type):
        for event_class in type.__subclasses__():
            if not event_class.__name__.endswith("Base"):
                event_classes.append(event_class)
            StateChangeEventBase._append_event_classes_for_type(event_classes, event_class)

class Subscription:
    topic: str
    subscription_name: str
    handler: callable
    max_message_count: Optional[int]
    max_wait_time: Optional[int]
    func_name: str

    def __init__(
        self,
        topic: str,
        subscription_name,
        handler: callable,
        func_name: str,
        max_message_count: Optional[int] = None,
        max_wait_time: Optional[int] = None,
        max_lock_renewal_duration: Optional[int] = None,
    ):
        self.topic = topic
        self.subscription_name = subscription_name
        self.handler = handler
        self.func_name = func_name
        self.max_message_count = max_message_count
        self.max_wait_time = max_wait_time
        self.max_lock_renewal_duration = max_lock_renewal_duration

def get_topic_name_from_method(func):
    function_name = func.__name__
    if not function_name.startswith("on_"):
        raise Exception(f"Function name must be in the form on_<entity-name>_<event-name>")
    topic_name = case.snake_to_kebab_case(function_name[3:])
    return topic_name

def get_topic_name_from_event_class(event_class):
    event_class_name = event_class.__name__
    if not event_class_name.endswith("StateChangeEvent"):
        raise Exception(f"Event class name must end with StateChangeEvent")

    topic_name = event_class_name.replace("StateChangeEvent", "")

    return case.pascal_to_kebab_case(topic_name)

class ConsumerApp:
    """ConsumerApp is a helper for simplifying the consumption of messages from a Service Bus topic/subscription"""

    _subscriptions: list[Subscription]
    _is_cancelled: bool = False
    _default_subscription_name: str
    _logger: logging.Logger
    _payload_type_converters: dict
    _topic_to_event_class_map: dict
    _default_max_message_count: int
    _default_max_wait_time: int
    _default_max_lock_renewal_duration: int

    def __init__(
        self,
        default_subscription_name: str = None,
        max_message_count: int = None,
        max_wait_time: int = None,
        max_lock_renewal_duration: int = None,
    ):
        self._logger = logging.getLogger(__name__)
        self._logger.info("SubscriberApp initialized")
        self._subscriptions = []
        if not default_subscription_name:
            default_subscription_name = os.environ.get("DEFAULT_SUBSCRIPTION_NAME")
        if not default_subscription_name:
            raise Exception("default_subscription_name must be provided or set in env var DEFAULT_SUBSCRIPTION_NAME")
        self._default_subscription_name = default_subscription_name

        self._default_max_message_count = max_message_count or MAX_MESSAGE_COUNT
        self._default_max_wait_time = max_wait_time or MAX_WAIT_TIME
        self._default_max_lock_renewal_duration = max_lock_renewal_duration or MAX_LOCK_RENEWAL_DURATION

        self._init_event_classes()

    def _init_event_classes(self):
        event_classes = StateChangeEventBase.get_event_classes()

        self._topic_to_event_class_map = {
            get_topic_name_from_event_class(event_class): event_class for event_class in event_classes
        }

        for event_class in event_classes:
            self._logger.info(f"ðŸ”Ž Found state event class: {event_class}")

    def _get_event_class_from_method(self, func):
        topic_name = get_topic_name_from_method(func)
        return self._topic_to_event_class_map[topic_name]

    def _get_payload_type_from_method(self, func):
        argspec = inspect.getfullargspec(func)

        # For simplicity currently, limit to a single argument that is the notification payload
        if len(argspec.args) != 1:
            raise Exception("Function must have exactly one argument (the notification)")

        event_class = argspec.annotations.get(argspec.args[0], None)
        return event_class

    def consume(
        self,
        func=None,
        *,
        topic_name: Optional[str] = None,
        subscription_name: Optional[str] = None,
        max_message_count: Optional[int] = None,
        max_wait_time: Optional[int] = None,
        max_lock_renewal_duration: Optional[int] = None,
    ):
        """Decorator for consuming messages from a Service Bus topic/subscription

        By default, the topic and subscription names are derived from the function name.
        For this, the function name should be in the for on_<entity-name>_<event-name>, e.g. on_task_created.

        Alternatively, the topic and subscription names can be provided as arguments to the decorator.
        """

        @functools.wraps(func)
        def decorator(func):
            nonlocal subscription_name
            nonlocal topic_name

            # Generate Subscription to capture func ready for use in run() later
            subscription = self._get_subscription_from_method(
                func, topic_name, subscription_name, max_message_count, max_wait_time, max_lock_renewal_duration
            )
            self._subscriptions.append(subscription)
            return func

        if func is None:
            # We are called with keyword arguments
            return decorator
        else:
            # We are called as a simple decorator
            return decorator(func)

    def _get_subscription_from_method(
        self,
        func,
        topic_name: Optional[str] = None,
        subscription_name: Optional[str] = None,
        max_message_count: Optional[int] = None,
        max_wait_time: Optional[int] = None,
        max_lock_renewal_duration: Optional[int] = None,
    ):
        notification_type = get_topic_name_from_method(func)

        if subscription_name is None:
            subscription_name = self._default_subscription_name

        if topic_name is None:
            topic_name = notification_type
            self._logger.debug(f"topic_name not set, using topic_name from function name: {topic_name}")

        event_class = self._topic_to_event_class_map.get(topic_name, None)
        if event_class is None:
            raise Exception(f"No event class found to match topic name '{topic_name}'")

        self._logger.info(
            f"ðŸ”Ž Found consumer {func.__qualname__} (topic={topic_name}, subscription={subscription_name}"
        )

        async def wrap_handler(receiver: ServiceBusReceiver, msg: ServiceBusReceivedMessage):
            # Convert message to correct payload type
            parsed_message = jsons.loads(str(msg), dict)
            payload_type = self._get_payload_type_from_method(func)
            if payload_type is dict:
                payload = parsed_message
            elif payload_type is None or payload_type is event_class:
                payload = parse_obj_as(event_class, parsed_message)
            else:
                raise Exception(f"Unsupported payload type: {payload_type}")

            try:
                # Call the decorated function
                result = await func(payload)

                # Handle the response
                if result == ConsumerResult.RETRY:
                    self._logger.info(f"Handler returned RETRY ({msg.message_id}) - abandoning")
                    await receiver.abandon_message(msg)
                elif result == ConsumerResult.DROP:
                    self._logger.info(f"Handler returned DROP ({msg.message_id}) - deadlettering")
                    await receiver.dead_letter_message(msg, reason="dropped by subscriber")
                else:
                    # Other return values are treated as success
                    self._logger.info(f"Handler returned successfully ({msg.message_id}) - completing")
                    await receiver.complete_message(msg)
            except Exception as e:
                self._logger.info(f"Error processing message ({msg.message_id}) - abandoning: {e}")
                await receiver.abandon_message(msg)

        func_name = func.__qualname__
        subscription = Subscription(
            topic=topic_name,
            subscription_name=subscription_name,
            handler=wrap_handler,
            func_name=func_name,
            max_message_count=max_message_count,
            max_wait_time=max_wait_time,
            max_lock_renewal_duration=max_lock_renewal_duration,
        )
        return subscription

    async def _process_subscription(self, servicebus_client: ServiceBusClient, subscription: Subscription):
        # AutoLockRenewer performs message lock renewal (for long message processing)
        max_message_count = subscription.max_message_count or self._default_max_message_count
        max_wait_time = subscription.max_wait_time or self._default_max_wait_time
        max_lock_renewal_duration = subscription.max_lock_renewal_duration or self._default_max_lock_renewal_duration

        renewer = AutoLockRenewer(max_lock_renewal_duration=max_lock_renewal_duration)
        receiver = servicebus_client.get_subscription_receiver(
            topic_name=subscription.topic,
            subscription_name=subscription.subscription_name,
            auto_lock_renewer=renewer,
        )
        # TODO - set up a logger for the subscription that includes the topic and subscription with log output

        async with receiver:
            self._logger.info(
                f"ðŸ‘‚ Starting message receiver for {subscription.func_name} (topic={subscription.topic}, subscription={subscription.subscription_name}..."
            )
            while not self._is_cancelled:
                # TODO: Add back-off logic when no messages?
                #       This could allow longer wait times with more efficient termination
                #       But it would increase the time to process new messages after a period of inactivity

                self._logger.debug("Receiving messages...")
                received_msgs = await receiver.receive_messages(
                    max_message_count=max_message_count, max_wait_time=max_wait_time
                )

                if len(received_msgs) == 0:
                    self._logger.debug(f"No messages received(topic={subscription.topic})")
                    continue

                self._logger.info(f"ðŸ“¦ Batch received, size =  {len(received_msgs)}")
                start = timer()

                # process messages in parallel
                await asyncio.gather(*[subscription.handler(receiver, msg) for msg in received_msgs])
                end = timer()
                duration = end - start
                self._logger.info(f"ðŸ“¦ Batch done, size={len(received_msgs)}, duration={duration}s")

            self._logger.info(
                f"Finished processing messages for {subscription.func_name} (topic={subscription.topic}, subscription={subscription.subscription_name})"
            )

    def _sigterm_handler(self, sig: int, frame):
        """Handle a SIGTERM by cancelling the consumer app"""
        self._logger.info(f"Received SIGTERM, calling cancel")
        self.cancel()

    async def run(self, filter: Optional[list[str]] = None):
        """Run the consumer app, i.e. begin processing messages from the Service Bus subscriptions

        Args:
            filter (Optional[list[str]]): A list of topic+subscription filters. Filters should be in the form "<topic-name>|<subscription-name>", e.g. "task-created|my-subscription". If not specified, defaults to the value from the SUBSCRIBER_FILTER environment variable (or None if not set).
        """

        # TODO - ensure only a single runner, check not cancelled, ...

        if len(self._subscriptions) == 0:
            raise Exception("No consumers registered - ensure you have added @consumer decorators to your handlers")

        workload_identity_credential = None
        servicebus_client = None

        self._logger.info("Connecting to service bus...")
        if AZURE_CLIENT_ID and AZURE_TENANT_ID and AZURE_AUTHORITY_HOST and AZURE_FEDERATED_TOKEN_FILE:
            self._logger.info("Using workload identity credentials")
            workload_identity_credential = WorkloadIdentityCredential(
                client_id=AZURE_CLIENT_ID,
                tenant_id=AZURE_TENANT_ID,
                token_file_path=AZURE_FEDERATED_TOKEN_FILE,
            )
            servicebus_client = ServiceBusClient(
                fully_qualified_namespace=SERVICE_BUS_NAMESPACE,
                credential=workload_identity_credential,
            )
        else:
            self._logger.info("No workload identity credentials found, using connection string")
            servicebus_client = ServiceBusClient.from_connection_string(conn_str=CONNECTION_STR)

        signal.signal(signal.SIGTERM, self._sigterm_handler)

        try:
            async with servicebus_client:
                self._logger.info("Starting subscription processors...")
                if filter is None:
                    if not SUBSCRIBER_FILTER is None:
                        self._logger.info(
                            f"Using filter from SUBSCRIBER_FILTER environment variable: {SUBSCRIBER_FILTER}"
                        )
                        filter = SUBSCRIBER_FILTER.split(",")
                else:
                    self._logger.info(f"Using filter from argument: {filter}")

                await asyncio.gather(
                    *[
                        self._process_subscription(servicebus_client, subscription)
                        for subscription in self._subscriptions
                        if filter is None or f"{subscription.topic}|{subscription.subscription_name}" in filter
                    ]
                )
                self._logger.info("Subscription processors completed")

        finally:
            if workload_identity_credential:
                await workload_identity_credential.close()

    def cancel(self):
        """Mark the consumer app as cancelled to shut down processing loops"""
        self._is_cancelled = True


# -*- coding: utf-8 -*-
"""
参数列表：
    参数1         -->    参数2
    files         -->    文件全路径
    dirs          -->    目录全路径
参数描述:
    False         -->    未变更
    True          -->    被修改
标准输出: 布尔值
作者: zz
版本: v4.0.0
"""

## 导入python模块
import os, sys, time, socket
import logging

## 获取根目录并添加到全局路径变量中
nowPath  = os.path.dirname(os.path.abspath(__file__))
homePath = os.path.join(nowPath, '../../')
sys.path.append(homePath)

## 从全局变量文件中引用相应变量
from monitor import cfg, osCfg, socketTimeout

## 从全局函数文件中引用相应函数
from function import load_json_file, get_index, get_sec_index, get_initdata

## 设置socket执行超时
socket.setdefaulttimeout(socketTimeout)

## 指定日志格式
cfg.logFormat

## 主程序
def main(**kwargs):
    ## 获取项目标签
    label = cfg.fileLabel

    ## 获取缓存文件路径
    cachePath   = os.path.join(cfg.cachePath, label)
    curFileName = cfg.curCache.format(label)
    preFileName = cfg.preCache.format(label)
    curFile     = os.path.join(cachePath, curFileName)
    preFile     = os.path.join(cachePath, preFileName)

    ## 获取索引
    keyList = get_index(jsonFile = curFile)

    ## 获取结果数据
    try:
        arg1 = kwargs.get('arg1') or sys.argv[1].lower()
        arg2 = kwargs.get('arg2') or sys.argv[2].lower()
    except IndexError:
        logging.error(
            '参数错误, 需要两个参数. '
        )
    else:
        secKeyList = get_sec_index( jsonFile = curFile, arg1 = arg1)

        if keyList and secKeyList:
           if arg1 in keyList and arg2 in secKeyList:
               curValue = get_initdata(
                   arg1     = arg1,
                   arg2     = arg2,
                   jsonFile = curFile
               )
               preValue = get_initdata(
                   arg1     = arg1,
                   arg2     = arg2,
                   jsonFile = preFile
               )
               if curValue != None and preValue != None :
                   if curValue == preValue:
                       result = False
                   else:
                       result = True
               else:
                   logging.warning(
                       '当前值或之前值不存在: preValue={}, curValue={}, preFile={}, curFile'.format(
                       preValue, curValue, preFile, curFile)
                   )
                   result = None
           else:
               logging.error(
                   '参数错误: arg1={}, arg2={}, curFile={}, preFile={}'.format(
                   arg1, arg2, curFile, preFile)
               )
               result = None
           return result

## 调试
if __name__ == '__main__':
    if main() is None:
        print(__doc__)
    else:
        print(main())


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models
import django.contrib.postgres.fields

class Migration(migrations.Migration):

    dependencies = [
        ('patients', '0027_auto_20160517_1559'),
    ]

    operations = [
        migrations.AlterField(
            model_name='profilesuser',
            name='thresholdHr_min_max',
            field=django.contrib.postgres.fields.ArrayField(null=True, size=2, base_field=models.PositiveIntegerField(), verbose_name='Soglie Pulsazioni', blank=True, default=list),
        ),
    ]


 # noqa
import importlib
import logging

from pipeline.conf import settings
from django.http import JsonResponse

from blueapps.utils.esbclient import get_client_by_request
from pipeline.components.utils import cc_get_inner_ip_by_module_id

atoms_cc = importlib.import_module('pipeline.components.collections.sites.%s.cc' % settings.RUN_VER)

logger = logging.getLogger('root')

def cc_search_object_attribute(request, obj_id, biz_cc_id):
    """
    @summary: 获取对象自定义属性
    @param request:
    @param biz_cc_id:
    @return:
    """
    client = get_client_by_request(request)
    client.set_bk_api_ver('v2')
    kwargs = {
        'bk_obj_id': obj_id,
    }
    cc_result = client.cc.search_object_attribute(kwargs)
    if not cc_result['result']:
        message = atoms_cc.cc_handle_api_error('cc.search_object_attribute', kwargs, cc_result['message'])
        logger.error(message)
        result = {
            'result': False,
            'data': [],
            'message': message
        }
        return JsonResponse(result)

    obj_property = []
    for item in cc_result['data']:
        if item['editable']:
            obj_property.append({
                'value': item['bk_property_id'],
                'text': item['bk_property_name']
            })

    return JsonResponse({'result': True, 'data': obj_property})

def cc_search_create_object_attribute(request, obj_id, biz_cc_id):
    client = get_client_by_request(request)
    client.set_bk_api_ver('v2')
    kwargs = {
        'bk_obj_id': obj_id,
    }
    cc_result = client.cc.search_object_attribute(kwargs)
    if not cc_result['result']:
        message = atoms_cc.cc_handle_api_error('cc.search_object_attribute', kwargs, cc_result['message'])
        logger.error(message)
        result = {
            'result': False,
            'data': [],
            'message': message
        }
        return JsonResponse(result)

    obj_property = []
    for item in cc_result['data']:
        if item['editable']:
            prop_dict = {
                'tag_code': item['bk_property_id'],
                'type': "input",
                'attrs': {
                    'name': item['bk_property_name'],
                    'editable': 'true',
                },
            }
            if item['bk_property_id'] in ['bk_set_name']:
                prop_dict["attrs"]["validation"] = [
                    {
                        "type": "required"
                    }
                ]
            obj_property.append(prop_dict)

    return JsonResponse({'result': True, 'data': obj_property})

def cc_format_topo_data(data, obj_id):
    tree_data = []
    data_flag = False
    for item in data:
        tree_item = {
            'id': item['bk_inst_id'],
            'label': item['bk_inst_name']
        }

        if item['bk_obj_id'] == obj_id:
            tree_data.append(tree_item)
            data_flag = True

        if item['bk_obj_id'] != obj_id and item.get('child'):
            tree_item['children'], data_flag = cc_format_topo_data(item['child'], obj_id)
            if data_flag:
                tree_data.append(tree_item)

    return tree_data, data_flag

def cc_format_picker_topo_data(data, obj_id):
    tree_data = []
    data_flag = False
    for item in data:
        tree_item = {
            'id': '%s_%s' % (item['bk_obj_id'], item['bk_inst_id']),
            'label': item['bk_inst_name']
        }

        if item['bk_obj_id'] == obj_id:
            tree_data.append(tree_item)
            data_flag = True

        if item['bk_obj_id'] != obj_id and item.get('child'):
            tree_item['children'], data_flag = cc_format_picker_topo_data(item['child'], obj_id)
            if data_flag:
                tree_data.append(tree_item)

    return tree_data, data_flag

def cc_format_prev_topo_data(data, obj_id):
    tree_data = []
    for item in data:
        if item['bk_obj_id'] != obj_id:
            tree_item = {
                'id': item['bk_inst_id'],
                'label': item['bk_inst_name']
            }

            if item.get('child'):
                tree_item['children'] = cc_format_prev_topo_data(item['child'], obj_id)

            tree_data.append(tree_item)
    return tree_data

def cc_format_module_hosts(username, biz_cc_id, module_id_list):
    module_host_list = cc_get_inner_ip_by_module_id(username, biz_cc_id, module_id_list)
    module_host_dict = {}
    for item in module_host_list:
        for module in item['module']:
            if module_host_dict.get('module_%s' % module['bk_module_id']):
                module_host_dict['module_%s' % module['bk_module_id']].append({
                    'id': '%s_%s' % (module['bk_module_id'], item['host']['bk_host_innerip']),
                    'label': item['host']['bk_host_innerip']
                })
            else:
                module_host_dict['module_%s' % module['bk_module_id']] = [{
                    'id': '%s_%s' % (module['bk_module_id'], item['host']['bk_host_innerip']),
                    'label': item['host']['bk_host_innerip']
                }]
    return module_host_dict

def cc_search_topo(request, obj_id, category, biz_cc_id):
    """
    @summary: 查询对象拓扑
    @param request:
    @param biz_cc_id:
    @return:
    """
    client = get_client_by_request(request)
    client.set_bk_api_ver('v2')
    kwargs = {
        'bk_biz_id': biz_cc_id
    }
    cc_result = client.cc.search_biz_inst_topo(kwargs)
    if not cc_result['result']:
        message = atoms_cc.cc_handle_api_error('cc.search_biz_inst_topo', kwargs, cc_result['message'])
        logger.error(message)
        result = {
            'result': False,
            'data': [],
            'message': message
        }
        return JsonResponse(result)

    if category == "normal":
        cc_topo, _ = cc_format_topo_data(cc_result['data'], obj_id)
    elif category == "prev":
        cc_topo = cc_format_prev_topo_data(cc_result['data'], obj_id)
    elif category == "picker":
        cc_topo, _ = cc_format_picker_topo_data(cc_result['data'], obj_id)
    else:
        cc_topo = []

    return JsonResponse({'result': True, 'data': cc_topo})

def cc_get_host_by_module_id(request, biz_cc_id):
    """
    查询模块对应主机
    :param request:
    :param biz_cc_id:
    :return:
    """
    select_module_id = request.GET.getlist('query', [])
    # 查询module对应的主机
    module_hosts = cc_format_module_hosts(request.user.username, biz_cc_id, map(lambda x: int(x), select_module_id))

    for del_id in (set(module_hosts.keys()) - set(map(lambda x: 'module_%s' % x, select_module_id))):
        del module_hosts[del_id]

    return JsonResponse({'result': True if module_hosts else False, 'data': module_hosts})


import logging
from typing import Dict, Set, Tuple
import numpy as np
import pandas as pd
from pyranges import PyRanges, read_gtf
from ..utils import get_frame_complement, i8_0, i8_2, validate_strand

GFF_FIELDS = [
    'Chromosome',
    'Strand',
    'Start',
    'End',
    'Frame',
    'Feature',
    'gene_id',
    'transcript_id'
]

GFF_FEATURES = {
    'CDS',
    'stop_codon',
    'UTR'
}

def get_exon_indices(g: pd.core.groupby.generic.DataFrameGroupBy) -> pd.Series:

    # Validate strand information
    strand_counts: pd.Series = g.Strand.value_counts()

    n = strand_counts.shape[0]
    if n == 0:
        raise ValueError("No strand information available!")
    if n > 1:
        raise ValueError("Inconsistent strand information for transcript!")

    strand: str = strand_counts.index.get_level_values(1).to_numpy()[0]
    validate_strand(strand)

    # Assign indices based on strand
    is_plus_strand: bool = strand == '+'
    return g.cumcount(ascending=is_plus_strand).astype(np.int32)

def _get_last_cds_indices(cds_ranges: pd.DataFrame, strand: str) -> np.ndarray:
    return cds_ranges[cds_ranges.Strand == strand].groupby('transcript_id', sort=False).exon_index.idxmax()

def _get_frames(frame: pd.Series) -> pd.Series:
    # Convert frame
    try:
        frame = frame.astype(np.int8)
    except ValueError:
        raise ValueError("Invalid frame in GTF/GFF2 file: not an integer!")

    # Validate frame range
    if not frame.between(i8_0, i8_2, inclusive=True).all():
        raise ValueError("Invalid frame in GTF/GFF2 file: out of range!")

    # Transform frame to its complement
    return get_frame_complement(frame)

def load_gff_cds(fp: str) -> Tuple[PyRanges, PyRanges]:

    # Load necessary fields from GTF/GFF2 file
    ranges: pd.DataFrame = read_gtf(fp, as_df=True)[GFF_FIELDS].rename(columns={
        'Frame': 'frame'
    })

    logging.debug("GTF/GFF2 file: %d features found." % ranges.shape[0])

    # Drop unnecessary features
    ranges = ranges[ranges.Feature.isin(GFF_FEATURES)]

    logging.debug("GTF/GFF2 file: %d CDS features found." % ranges.shape[0])

    # Compress identifiers
    ranges.transcript_id = ranges.transcript_id.astype('category')
    ranges.gene_id = ranges.gene_id.astype('category')

    # Extract UTR features
    utr_mask: pd.Series = ranges.Feature == 'UTR'
    utr_ranges: PyRanges = PyRanges(df=ranges[utr_mask].drop('Feature', axis=1))
    ranges = ranges[~utr_mask]
    del utr_mask

    # Transform frames
    ranges.frame = _get_frames(ranges.frame)
    assert ranges.frame.dtype == 'int8'

    # Extract stop codon features
    stop_mask: pd.Series = ranges.Feature == 'stop_codon'
    stop_codons: Dict[str, int] = {
        r.transcript_id: r.End if r.Strand == '+' else r.Start
        for r in ranges[stop_mask].itertuples()
    }
    ranges = ranges[~stop_mask]
    del stop_mask

    # Sort CDS features by genomic coordinates
    ranges = ranges.drop('Feature', axis=1).sort_values(by=[
        'gene_id', 'transcript_id', 'Chromosome', 'Strand', 'Start', 'End'
    ], ignore_index=True)

    # Validate number of gene and transcript identifiers
    gene_n: int = ranges.gene_id.cat.categories.size
    transcript_n: int = ranges.transcript_id.cat.categories.size

    logging.debug("GTF/GFF2 file: %d genes found." % gene_n)
    logging.debug("GTF/GFF2 file: %d transcripts found." % transcript_n)

    if gene_n == 0 or transcript_n == 0:
        raise ValueError("No gene or transcript ID found in GTF/GFF file!")

    if transcript_n > gene_n:
        raise ValueError("Multiple transcripts per gene in GTF/GFF file are not supported!")

    # Check for missing identifiers
    if ranges.gene_id.isnull().values.any():
        raise ValueError("Missing gene ID in GTF/GFF2 file!")
    if ranges.transcript_id.isnull().values.any():
        raise ValueError("Missing transcript ID in GTF/GFF2 file!")

    # Check only one transcript per gene is present
    gene_transcript_counts: pd.DataFrame = ranges.groupby([
        'gene_id',
        'transcript_id'
    ], sort=False).size().reset_index(name='counts')
    gene_transcript_counts = gene_transcript_counts[gene_transcript_counts.counts > 0]
    if gene_transcript_counts.shape[0] > gene_n:
        raise ValueError("Multiple transcripts per gene in GTF/GFF file are not supported!")

    # Assign a sequential index to each CDS feature (5' to 3')
    ranges['exon_index'] = ranges.groupby(
        ['transcript_id'], sort=False).pipe(get_exon_indices)

    # Append the stop codons to the last CDS features
    strands: Set[str] = set(ranges.Strand.cat.categories.values)

    if '+' in strands:
        last_cds_plus: np.ndarray = _get_last_cds_indices(ranges, '+')
        ranges.loc[last_cds_plus, 'End'] += 3
        del last_cds_plus

    if '-' in strands:
        last_cds_minus: np.ndarray = _get_last_cds_indices(ranges, '-')
        ranges.loc[last_cds_minus, 'Start'] -= 3
        del last_cds_minus

    # TODO: validate with stop codon features
    del stop_codons

    # Convert to PyRanges
    cds_ranges: PyRanges = PyRanges(df=ranges)

    return cds_ranges, utr_ranges


import requests
from bs4 import BeautifulSoup

LIMIT = 50
URL = f"https://www.indeed.com/jobs?q=python&limit={LIMIT}"

#indeed 페이지를 추출하는 함수
def extract_indeed_pages():
    result = requests.get(URL)
    soup = BeautifulSoup(result.text, "html.parser")
    pagination = soup.find("div", {"class" : "pagination"})

    links = pagination.find_all("a")
    pages = []

    for link in links[:-1] :
        pages.append(int(link.string))

    max_page = pages[-1]
    return max_page

#2번째 request : indeed 페이지의 마지막 페이지까지 낱개로 배열
def extract_indeed_jobs(last_page):
    jobs = []
    #for page in range(last_page):  이렇게 하니까 모든 페이지의 h2 class="title"이 나와서 주석달고 다른 for문 만들거임
    result = requests.get(f"{URL}&start={0*LIMIT}") #for문 없앴으니까 page*LIMIT -> 0*LIMIT으로 변경
    soup = BeautifulSoup(result.text, "html.parser") #soup 이용해서 html 데이터 중 제목 부분을 추출할거임
    results = soup.find_all("div", {"class" : "jobsearch-SerpJobCard"}) # soup의 find_all 기능이 div class="jobsearch-SerpJobCard" 요소를 찾음
    #print(results)
    #for문을 사용해서 div class="title 안의 a title=""만 가져와서 제목만 추출할거임
    for result in results:
        title = result.find("h2",{"class":"title"}).find("a")["title"] #[]하면 attribute 써주기
        print(title)
    # print(result.status_code) / 작동이 잘 되는지만 확인
    return jobs


import pandas as pd
import numpy as np
import pulp

def optimize_team(expected_scores, prices, positions, clubs, penalty, total_budget=100, sub_factor=0):
    num_players = len(expected_scores)
    model = pulp.LpProblem("FPL Points Optimization", pulp.LpMaximize)
    decisions = [
        pulp.LpVariable("x{}".format(i), lowBound=0, upBound=1, cat='Integer')
        for i in range(num_players)
    ]
    captain_decisions = [
        pulp.LpVariable("y{}".format(i), lowBound=0, upBound=1, cat='Integer')
        for i in range(num_players)
    ]
    sub_decisions = [
        pulp.LpVariable("z{}".format(i), lowBound=0, upBound=1, cat='Integer')
        for i in range(num_players)
    ]

    # objective function:
    model += sum(((captain_decisions[i] + decisions[i] + sub_decisions[i]*sub_factor) * expected_scores[i]) - ((decisions[i] + sub_decisions[i]) * penalty[i])
                 for i in range(num_players)), "Objective"

    # cost constraint
    model += sum((decisions[i] + sub_decisions[i]) * prices[i] for i in range(num_players)) <= total_budget  # total cost

    # position constraints
    # 1 starting goalkeeper
    model += sum(decisions[i] for i in range(num_players) if positions[i] == 1) == 1
    # 2 total goalkeepers
    model += sum(decisions[i] + sub_decisions[i] for i in range(num_players) if positions[i] == 1) == 2

    # 3-5 starting defenders
    model += sum(decisions[i] for i in range(num_players) if positions[i] == 2) >= 3
    model += sum(decisions[i] for i in range(num_players) if positions[i] == 2) <= 5
    # 5 total defenders
    model += sum(decisions[i] + sub_decisions[i] for i in range(num_players) if positions[i] == 2) == 5

    # 3-5 starting midfielders
    model += sum(decisions[i] for i in range(num_players) if positions[i] == 3) >= 3
    model += sum(decisions[i] for i in range(num_players) if positions[i] == 3) <= 5
    # 5 total midfielders
    model += sum(decisions[i] + sub_decisions[i] for i in range(num_players) if positions[i] == 3) == 5

    # 1-3 starting attackers
    model += sum(decisions[i] for i in range(num_players) if positions[i] == 4) >= 1
    model += sum(decisions[i] for i in range(num_players) if positions[i] == 4) <= 3
    # 3 total attackers
    model += sum(decisions[i] + sub_decisions[i] for i in range(num_players) if positions[i] == 4) == 3

    # club constraint
    for club_id in np.unique(clubs):
        model += sum(decisions[i] for i in range(num_players) if clubs[i] == club_id) <= 2  # max 2 players from one team in starting 11
        model += sum(decisions[i] + sub_decisions[i] for i in range(num_players) if clubs[i] == club_id) <= 3  # max 3 players overall

    model += sum(decisions) == 11  # total team size
    model += sum(captain_decisions) == 1  # 1 captain

    for i in range(num_players):
        model += (decisions[i] - captain_decisions[i]) >= 0  # captain must also be on team
        model += (decisions[i] + sub_decisions[i]) <= 1  # subs must not be on team

    model.solve()
    print("Total expected score = {}".format(model.objective.value()))

    return decisions, captain_decisions, sub_decisions

def SelectTeam(current_team, playerData_df, penal, balance,subFactor=0):
    expectedScores = playerData_df['points']
    prices = playerData_df['value']
    positions = playerData_df['element_type']
    clubs = playerData_df['player_team']
    names = playerData_df['player_name']
    element = playerData_df['element']

    print(playerData_df[playerData_df.element.isin(current_team)])
    budget = playerData_df[playerData_df.element.isin(current_team)].value.sum() + balance
    playerData_df['penalty'] = penal
    for sel in current_team:
        indexs = playerData_df[playerData_df['element'] == sel].index
        playerData_df.loc[indexs, 'penalty'] = 0
    penalty = playerData_df['penalty']

    decisions, captain, subs = optimize_team(expectedScores.values, prices.values, positions.values, clubs.values, penalty.values, budget, subFactor)
    new_team=[]
    print("Players in Starting X1\n")
    for i in range(playerData_df.shape[0]):
        if decisions[i].value()!=0:
            new_team.append(element[i])
            print("**{}** {} Points = {}, Price = {}".format(names[i],element[i],expectedScores[i], prices[i]))

    print("Captain\n")
    for i in range(playerData_df.shape[0]):
        if captain[i].value()==1:
            print("**Captain {}** {} Points = {}, Price = {}".format(names[i],element[i],expectedScores[i], prices[i]))

    print("Substitute Players\n")
    for i in range(playerData_df.shape[0]):
        if subs[i].value()!=0:
            new_team.append(element[i])
            print("**Subs {}** {} Points = {}, Price = {}".format(names[i],element[i],expectedScores[i], prices[i]))

    print("\n\nTotal Budget: ",budget)
    balance = budget - playerData_df[playerData_df.element.isin(new_team)].value.sum()
    print("New Team Value: ",playerData_df[playerData_df.element.isin(new_team)].value.sum())
    print("New Balance: ", balance)
    playerData_df = playerData_df.drop(columns=['penalty','points'])
    removed = [x for x in current_team if x not in new_team]
    added = [x for x in new_team if x not in current_team]
    removed_df = playerData_df[playerData_df['element'].isin(removed)]
    added_df = playerData_df[playerData_df['element'].isin(added)]
    newTeam_df = playerData_df[playerData_df['element'].isin(new_team)]
    newTeam_df = pd.concat([newTeam_df,removed_df])
    newTeam_df.loc[playerData_df['element'].isin(added),"transfers"] = "IN"
    newTeam_df.loc[playerData_df['element'].isin(removed),"transfers"] = "OUT"
    newTeam_df.loc[playerData_df['element'].isin(new_team[-4:]),"Substitute"] = "YES"
    return new_team, newTeam_df

method = 'RandomForest/'
round = 29
balance = 0.8
playerData_df = pd.read_csv('./prediction/Gameweeks/'+str(round)+'/prediction/'+ method +'PredictRF.csv')
my_team = pd.read_csv('./prediction/Gameweeks/'+str(round-1)+'/prediction/'+method+'PredictedTeam.csv')
indexNames = my_team[ my_team['transfers'] == 'OUT' ].index
my_team.drop(indexNames , inplace=True)
playerData_df['value'] = playerData_df.value.astype(float)
my_team['value'] = my_team.value.astype(float)
my_team['value'] = np.round(my_team['value'],1)
playerData_df['value'] = np.round(playerData_df['value'],1)
playerData_df['selling_value'] = playerData_df['value']
playerData_df['selling_value'] = ((playerData_df['element'].map(my_team.set_index('element')['value']) + playerData_df['selling_value'])/2).fillna(playerData_df['selling_value'])
playerData_df['selling_value'] = (playerData_df['selling_value']*10).astype(int)/10

#comment for only this round
playerData_df['value'] = np.where(playerData_df['value']<playerData_df['selling_value'],playerData_df['value'],playerData_df['selling_value'])

my_team = my_team['element'].tolist()
playerRaw_df = pd.read_csv('./current year/2020-21/players_raw.csv')
playerData_df['next_match'] = playerData_df.element.map(playerRaw_df.set_index('id').chance_of_playing_next_round)
playerData_df['ep_next'] = playerData_df.element.map(playerRaw_df.set_index('id').ep_next)
playerData_df['penalty'] = playerData_df.element.map(playerRaw_df.set_index('id').penalties_order)
playerData_df['freekick'] = playerData_df.element.map(playerRaw_df.set_index('id').direct_freekicks_order)
playerData_df['corner'] = playerData_df.element.map(playerRaw_df.set_index('id').corners_and_indirect_freekicks_order)

playerData_df.loc[(playerData_df['round']==round) & (playerData_df['next_match']<=50),"points"] = 0
#For Free Hit, to get all players
#playerData_df.loc[(playerData_df['round']==round+1),"points"] = 0
playerData_df.loc[(playerData_df['round']==round) & (playerData_df['ep_next']<1),"points"] = \
    playerData_df.loc[(playerData_df['round']==round) & (playerData_df['ep_next']<1),"points"]\
   * playerData_df.loc[(playerData_df['round']==round) & (playerData_df['ep_next']<1),"ep_next"]\
   * (2 - playerData_df.loc[(playerData_df['round']==round) & (playerData_df['ep_next']<1),"ep_next"])
playerData_df.loc[(playerData_df['penalty']==1),"points"] = playerData_df.loc[(playerData_df['penalty']==1),"points"] * 1.02
playerData_df.loc[(playerData_df['freekick']==1),"points"] = playerData_df.loc[(playerData_df['freekick']==1),"points"] * 1.02
playerData_df.loc[(playerData_df['corner']==1),"points"] = playerData_df.loc[(playerData_df['corner']==1),"points"] * 1.02

playerData_df = playerData_df.drop(columns=['next_match','penalty','freekick','corner','ep_next','selling_value'])

data1 = playerData_df[playerData_df['round'] == round].reset_index(drop=True)
data2 = playerData_df[playerData_df['round'] == (round+1)].reset_index(drop=True)
data3 = playerData_df[playerData_df['round'] == (round+2)].reset_index(drop=True)
data4 = playerData_df[playerData_df['round'] == (round+3)].reset_index(drop=True)

playerData_df = pd.pivot_table(playerData_df, values=['points'], index=['player_name', 'player_team','element_type','element','value'], aggfunc=np.sum).reset_index()

rone = pd.pivot_table(pd.concat([data1,data2]), values=['points'], index=['player_name', 'player_team','element_type','element','value'], aggfunc=np.sum).reset_index()
rtwo = pd.pivot_table(pd.concat([data2,data3]), values=['points'], index=['player_name', 'player_team','element_type','element','value'], aggfunc=np.sum).reset_index()
rthree = pd.pivot_table(pd.concat([data3,data4]), values=['points'], index=['player_name', 'player_team','element_type','element','value'], aggfunc=np.sum).reset_index()

my_team, saveTeam_df = SelectTeam(my_team,rone,0,balance,0.2)

#my_team, saveTeam_df = SelectTeam(my_team,data1,100,0)
#my_team = SelectTeam(my_team,data3,1)
#my_team = SelectTeam(my_team,data4,1)

my_team = pd.read_csv('./prediction/Gameweeks/'+str(round-1)+'/prediction/'+method+'PredictedTeam.csv')
indexNames = my_team[ my_team['transfers'] == 'OUT' ].index
my_team.drop(indexNames , inplace=True)

#comment for only this round
saveTeam_df['value'] = saveTeam_df['element'].map(my_team.set_index('element')['value']).fillna(saveTeam_df['value'])
saveTeam_df.to_csv('./prediction/Gameweeks/'+str(round)+'/prediction/'+method+'PredictedTeam.csv', index=False)

#saveTeam_df.to_csv('./prediction/Gameweeks/'+str(round)+'/prediction/'+method+'PredictedTeamOnlyThisRound.csv', index=False)

my_team = pd.read_csv('./prediction/Gameweeks/'+str(round)+'/prediction/'+method+'PredictedTeam.csv')
indexNames = my_team[ my_team['transfers'] == 'OUT' ].index
my_team_list = my_team.drop(indexNames)
my_team_list = my_team_list['element'].tolist()
my_team_list, saveTeam_df = SelectTeam(my_team_list,data1,100,0)
my_team['Substitute'] = my_team.element.map(saveTeam_df.set_index('element').Substitute)
my_team.to_csv('./prediction/Gameweeks/'+str(round)+'/prediction/'+method+'PredictedTeam.csv', index=False)


import uuid
from django.db import models
from resorts.models import Resort

class Forecast(models.Model):
	id = models.UUIDField(
		primary_key=True,
		default=uuid.uuid4,
		editable=False
	)
	created_at = models.DateTimeField(auto_now=True)
	resort = models.ForeignKey(
		Resort,
		related_name='resort_forecast',
		on_delete=models.CASCADE
	)
	weather = models.JSONField(blank=True, null=True)

	def __str__(self):
		return '{0} ({1})'.format(str(self.resort), str(self.created_at))

class ForecastDetail(models.Model):
	id = models.UUIDField(
		primary_key=True,
		default=uuid.uuid4,
		editable=False
	)
	created_at = models.DateTimeField(auto_now=True)
	forecast = models.ForeignKey(
		Forecast,
		related_name='forecast_details',
		on_delete=models.CASCADE
	)
	forecasted_at = models.DateTimeField(auto_now=False)
	temp = models.DecimalField(
		max_digits=7,
		decimal_places=3
	)
	feels_like = models.DecimalField(
		max_digits=7,
		decimal_places=3
	)
	temp_min = models.DecimalField(
		max_digits=7,
		decimal_places=3
	)
	temp_max = models.DecimalField(
		max_digits=7,
		decimal_places=3
	)
	pressure = models.IntegerField()
	sea_level = models.IntegerField()
	grnd_level = models.IntegerField()
	humidity = models.IntegerField()
	temp_kf = models.IntegerField()
	weather = models.CharField(max_length=50)
	weather_id = models.IntegerField()
	weather_description = models.CharField(max_length=200)
	weather_icon_name = models.CharField(max_length=10)
	clouds = models.IntegerField()
	wind_speed = models.DecimalField(
		max_digits=7,
		decimal_places=3
	)
	wind_deg = models.IntegerField()
	visibility = models.IntegerField()
	pop = models.DecimalField(
		max_digits=7,
		decimal_places=3
	)
	rain = models.DecimalField(
		blank=True,
		null=True,
		max_digits=7,
		decimal_places=3
	)
	snow = models.DecimalField(
		blank=True,
		null=True,
		max_digits=7,
		decimal_places=3
	)
	sys = models.CharField(max_length=1)

	def __str__(self):
		return str(self.forecast)