'''
## Program: sanity.py
## Authors: Arnav Sharma
##
## Date: 19-01-2019
'''

import sys

from scommands import run_cmd
from sbrowser import stage1, stage3
from serror import *

''' Declare global variables here'''
S1 = False
S2 = False
S3 = False
RID = ""
URL = ""
VERBOSE = False
HELP = False

def shelp():
    """Do sanity tests of a device and update on SCM portal.

  python sanity.py [-s1|-S1] [-s3|-S3] [-rid=RID] [-url=URL] [-v|-V|--verbose]
      [-h|-H|--help|/?]

  -s1 | -S1      Do stage 1 update on SCM Portal.
  -s3 | -S3      Do stage 3 update on SCM Portal.
  -rid=RID       Specify a numeric RID for stage 1 and stage 3 updates. This
                 value must be specified for doing stage 1 and stage 2 updates
                 on SCM portal (i.e. when -s1 or -s2 is used).
  -url=URL       Specify the URL of SCM portal. RID will be concatenated at the
                 end by the program. Default value is (RID obtained from user):
                 http://107.109.51.176:8081/scmportal/
                                        editDeltaMergeUpstreamRequest?id=RID
                 This field will be simply ignored if -s1 or -s2 is not used.
  -v | -V        Show output in detail.
  -h | -H | /?   Display this help.

Options specified multiple times will be ignored."""
    pass

def set_options():
    """ Set global variables based on user input."""

    global S1
    global S2
    global S3
    global RID
    global URL
    global VERBOSE
    global HELP

    error = False
    args = sys.argv[1:]

    for arg in args:
        if arg == "-s1" or arg == "-S1":
            S1 = True
        elif arg == "-s2" or arg == "-S2":
            S2 = True
        elif arg == "-s3" or arg == "-S3":
            S3 = True
        elif arg[:5] == "-rid=":
            RID = arg[5:]
            if not RID.isdigit():
                set_error("ERR_RID")
                error = True
        elif arg[:5] == "-url=":
            URL = arg[5:]
        elif (arg == "-v") or (arg == "-V") or (arg == "--verbose"):
            VERBOSE = True
        elif arg == "-h" or arg == "-H" or arg == "--help" or arg == "/?":
            HELP = True
        else:
            set_error("ERR_WRONG_OPTION")
            error = True

    if (S1 or S3) and (not RID):
        set_error("ERR_RID")
        error = True

    return error

def main():
    """ Start execution here."""

    error = set_options()
    if error:
        print_error()
        return

    if HELP:
        print(shelp.__doc__)
        return

    if not (S1 or S2):
        if VERBOSE:
            print("--- Running sanity tests...")
        flag_error, flag_issue = run_cmd(VERBOSE)

        if flag_error:
            print("--- Program terminated due to errors.")
            if VERBOSE:
                raw_input("Press Enter to exit...")
            return

        if flag_issue:
            print("--- Issues were found. Results not posted.")
            if VERBOSE:
                raw_input("Press Enter to exit...")
            return

        if VERBOSE:
            print("--- Tests were successful.")

    if S1:
        if VERBOSE:
            print("--- Updating Stage 1 results on portal...")
        flag_error, flag_warn = stage1(RID, URL)
        if VERBOSE:
            if flag_warn:
                print_error()
        if flag_error:
            print("--- Program will terminate due to errors.")
            if VERBOSE:
                raw_input("Press Enter to exit...")
            return

    if S3:
        if VERBOSE:
            print("--- Updating Stage 3 results on portal...")
        flag_error, flag_warn = stage3(RID, URL)
        if VERBOSE:
            if flag_warn:
                print_error()
        if flag_error:
            print("--- Program will terminate due to errors.")
            if VERBOSE:
                raw_input("Press Enter to exit...")
            return

main()


# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2022 NV Access Limited

from . import (
	Role,
	State,
)
from typing import (
	Tuple,
	Set,
)

def transformRoleStates(role: Role, states: Set[State]) -> Tuple[Role, Set[State]]:
	""" Map NVDA Role-State combinations to adjusted NVDA Role-State combinations.
	Some combinations of roles and states may be better represented with some alternative combination.
	As an example:
	Role.PROGRESSBAR with State.INDETERMINATE should be represented by only the Role.BUSY_INDICATOR, with
	no State.INDETERMINATE, or State.HALFCHECKED.
	@param role: NVDA Role to consider
	@param states: NVDA States to consider
	@return: A tuple with the new Role and modified States set.
	"""
	if(
		role in [Role.PROGRESSBAR, Role.BUSY_INDICATOR]
		and states.intersection({State.INDETERMINATE, State.HALFCHECKED})
	):
		# Don't report indeterminate progress bars as "half-checked"
		# L{State.HALFCHECKED} maps from oleacc.STATE_SYSTEM_MIXED,
		# which has the same value as oleacc.STATE_SYSTEM_INDETERMINATE.
		# L{State.INDETERMINATE} is not mapped directly from any IA or IA2 state.
		# A progress bar that can not convey progress, only activity, is a busy indicator.
		states.discard(State.HALFCHECKED)
		states.add(State.INDETERMINATE)
		return Role.BUSY_INDICATOR, states
	return role, states


import os

import sum_check

cur_dir = os.path.dirname(os.path.abspath(__file__))

with open(f"{cur_dir}/input") as f:
    values = [int(x.strip()) for x in f]

for i in range(25, len(values)):
    val = values[i]
    sum_vals = sum_check.get_all_sum_vals(values[i-25:i])
    if val not in sum_vals:
        break

print(f"The first value that does not fulfill condition is {val}")


# requires curl, and grib_copy for navgem (ECC grib_tools)

import re
import os
import subprocess
import time
import re
import requests

from datetime import datetime, timedelta
import sqlite3
import json
import shutil
# generalize to ecmwf (json), navgem(asc), and cmc
# todo need a space margin (1 gb at least, as 500 mb for one run)
# todo somehow need to be able to optionally download missing runs

import threading
import sys
import signal

# print based on level (lower is more important):
#  0 is None (for no messages printed)
#  1 is important (i.e. disk space, database error)
#  2 is notification level: messages for when download runs complete
#  3 is potentially not important (could not download files (as in an incomplete model run))
#  4 is for verbose debugging (print curl/grib_copy statements)
print_level_importance = 2

# how often to check (seconds)
backoff_time = 600
# wait between downloads (seconds)
sleep_time = 1
# number of last n runs to get (might get one more than this)
num_latest_runs = 4

# only download runs not already calculated
disturbances_db_file_path = '/home/db/Documents/JRPdata/cyclone-genesis/disturbances.db'

# GFS and NAV are in the same thread as they use the same server
model_names_to_download_thread_groupings = [['GFS', 'NAV'], ['CMC'], ['ECM']]

# use this to download individual grib files for NAVGEM so we can split them with grib_copy
tmp_download_dir = '/tmp'

# min space default is 100 MB (to make sure to not cause other programs to crash)
# tmp refers to tmp_download_dir, the rest refer to model_data_folders_by_model_name
min_space_bytes = {
    'tmp': '100000000',
    'GFS': '100000000',
    'ECM': '100000000',
    'CMC': '100000000',
    'NAV': '100000000',
}

model_data_folders_by_model_name = {
    'GFS': '/home/db/metview/JRPdata/globalmodeldata/gfs',
    'ECM': '/home/db/metview/JRPdata/globalmodeldata/ecm',
    'CMC': '/home/db/metview/JRPdata/globalmodeldata/cmc',
    'NAV': '/home/db/metview/JRPdata/globalmodeldata/nav'
}

url_folder_by_model = {
    'GFS': 'https://nomads.ncep.noaa.gov/pub/data/nccf/com/gfs/prod',
    'ECM': 'https://data.ecmwf.int/forecasts',
    'CMC': 'http://hpfx.collab.science.gc.ca',
    'NAV': 'https://nomads.ncep.noaa.gov/pub/data/nccf/com/fnmoc/prod'
}

# the time steps in the file name are not all ### (ECM just uses a single digit)
model_time_step_str_format = {
    'GFS': '03',
    'ECM': '00',
    'CMC': '03',
    'NAV': '03'
}

# not including GFS analysis files (.anl)
total_model_time_steps = {
    'GFS': {
        '00': 65,
        '06': 65,
        '12': 65,
        '18': 65
    },
    'ECM': {
        '00': 41,
        '12': 41
    },
    'CMC': {
        '00': 41,
        '12': 41
    },
    'NAV': {
        '00': 31,
        '06': 31,
        '12': 31,
        '18': 31
    }
}
# only interested in 6 hour time steps
all_time_steps_by_model = {
    'GFS': {
        '00': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 186, 192, 198, 204, 210, 216, 222, 228, 234, 240, 246, 252, 258, 264, 270, 276, 282, 288, 294, 300, 306, 312, 318, 324, 330, 336, 342, 348, 354, 360, 366, 372, 378, 384],
        '06': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 186, 192, 198, 204, 210, 216, 222, 228, 234, 240, 246, 252, 258, 264, 270, 276, 282, 288, 294, 300, 306, 312, 318, 324, 330, 336, 342, 348, 354, 360, 366, 372, 378, 384],
        '12': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 186, 192, 198, 204, 210, 216, 222, 228, 234, 240, 246, 252, 258, 264, 270, 276, 282, 288, 294, 300, 306, 312, 318, 324, 330, 336, 342, 348, 354, 360, 366, 372, 378, 384],
        '18': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 186, 192, 198, 204, 210, 216, 222, 228, 234, 240, 246, 252, 258, 264, 270, 276, 282, 288, 294, 300, 306, 312, 318, 324, 330, 336, 342, 348, 354, 360, 366, 372, 378, 384]
        },
    'ECM': {
        '00': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 186, 192, 198, 204, 210, 216, 222, 228, 234, 240],
        '12': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 186, 192, 198, 204, 210, 216, 222, 228, 234, 240]
    },
    'CMC': {
        '00': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 186, 192, 198, 204, 210, 216, 222, 228, 234, 240],
        '12': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180, 186, 192, 198, 204, 210, 216, 222, 228, 234, 240]
    },
    'NAV': {
        '00': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180],
        '06': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180],
        '12': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180],
        '18': [0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 114, 120, 126, 132, 138, 144, 150, 156, 162, 168, 174, 180]
    }
}

expected_num_grib_files_by_model_name = {
    'GFS': 9,
    'CMC': 9,
    'ECM': 9,
    'NAV': 9,
    'UKM': 9
}

# in order to select grib files from a folder we need to ignore these file name extensions
ignored_file_extensions = [
    'asc',
    'idx',
    'json',
    'index'
]

# hours between model runs (not the time steps)
model_interval_hours = {
    'GFS': 6,
    'ECM': 12,
    'CMC': 12,
    'NAV': 6
}

# this is used to check for already downloaded files
time_step_re_str_by_model_name = {
    'GFS': r'.*?\.f(?P<time_step>\d\d\d)_',
    'ECM': r'.*?-(?P<time_step>\d+)h-oper-fc',
    'CMC': r'.*?\d+_P(?P<time_step>\d+)',
    'NAV': r'.*?navgem_\d{10}f(?P<time_step>\d\d\d)'
}

# Specifies what parameters to gather (and also used in how to name it for split gribs)
# GFS is what is used in the .idx file
# ECM is what is in the .index file (list of jsons)
# CMC is the portion of the file name
# NAV are the keys used for grib_copy (download the entire grib and split it)
params_by_model_name = {
    'GFS': [
        ':PRMSL:mean sea level:',
        ':HGT:250 mb:',
        ':HGT:850 mb:',
        ':UGRD:850 mb:',
        ':VGRD:850 mb:',
        ':UGRD:925 mb:',
        ':VGRD:925 mb:',
        ':UGRD:10 m above ground:',
        ':VGRD:10 m above ground:'
        ],
    'ECM': [
        {"levtype": 'sfc', "param": 'msl'},
        {"levtype": 'sfc', "param": '10u'},
        {"levtype": 'sfc', "param": '10v'},
        {"levtype": 'pl', "levlist": '250', "param": 'gh'},
        {"levtype": 'pl', "levlist": '850', "param": 'gh'},
        {"levtype": 'pl', "levlist": '850', "param": 'u'},
        {"levtype": 'pl', "levlist": '850', "param": 'v'},
        {"levtype": 'pl', "levlist": '925', "param": 'u'},
        {"levtype": 'pl', "levlist": '925', "param": 'v'},
    ],
    'CMC': [
        'PRMSL_MSL_0',
        'HGT_ISBL_250',
        'HGT_ISBL_850',
        'UGRD_ISBL_850',
        'UGRD_ISBL_925',
        'VGRD_ISBL_850',
        'VGRD_ISBL_925',
        'UGRD_TGL_10',
        'VGRD_TGL_10'
    ],
    'NAV': [
        {"levtype": 'sfc', "level": 0, "shortName": 'prmsl'},
        {"levtype": 'sfc', "level": 10, "shortName": '10u'},
        {"levtype": 'sfc', "level": 10, "shortName": '10v'},
        {"levtype": 'pl', "level": 250, "shortName": 'gh'},
        {"levtype": 'pl', "level": 850, "shortName": 'gh'},
        {"levtype": 'pl', "level": 850, "shortName": 'u'},
        {"levtype": 'pl', "level": 850, "shortName": 'v'},
        {"levtype": 'pl', "level": 925, "shortName": 'u'},
        {"levtype": 'pl', "level": 925, "shortName": 'v'}
    ]
}

# handle thread errors
exit_event = threading.Event()

# print lines at or below print_level_importance
def print_level(level, warning_str):
    if level <= print_level_importance:
        print(warning_str)

def have_enough_disk_space():
    paths_to_check = {}
    if tmp_download_dir:
        paths_to_check['tmp'] = tmp_download_dir

    for model_name, path in model_data_folders_by_model_name.items():
        paths_to_check[model_name] = path

    enough_space_for_all_paths = True
    for name, path in paths_to_check.items():
        try:
            dustats = shutil.disk_usage(path)
            if dustats:
                free_bytes = dustats.free
                if free_bytes < int(min_space_bytes[name]):
                    enough_space_for_all_paths = False
                    break

        except Exception as e:
            print_level(1, e)
            print_level(1, "Warning could not get disk space for {path}")

    return enough_space_for_all_paths

def get_calculated_and_missing_time_steps(model_name, model_timestamp):
    model_hour = f'{model_timestamp.hour:02}'
    # remove from this the calculated timesteps
    model_time_steps_missing = set(all_time_steps_by_model[model_name][model_hour])

    disturbance_candidates = get_disturbances_from_db(model_name, model_timestamp)
    time_steps_calculated = set()
    if disturbance_candidates:
        for time_step_str, candidates in disturbance_candidates.items():
            time_step_int = int(time_step_str)
            time_steps_calculated.add(time_step_int)
            # discard will not throw an error even if it is not there
            model_time_steps_missing.discard(time_step_int)
    return sorted(list(time_steps_calculated)), sorted(list(model_time_steps_missing))

def get_disturbances_from_db(model_name, model_timestamp):
    conn = None
    retrieved_data = {}
    try:
        # store disturbance candidates in database
        conn = sqlite3.connect(disturbances_db_file_path)
        cursor = conn.cursor()

        ds = model_timestamp.isoformat()

        cursor.execute('SELECT data FROM disturbances WHERE model_name = ? AND date = ?', (model_name, ds))
        result = cursor.fetchone()
        if result:
            retrieved_data = json.loads(result[0])

    except sqlite3.Error as e:
        print_level(1, f"SQLite error: {e}")
    finally:
        if conn:
            conn.close()

    return retrieved_data

# for interval_hours = 6, find 6 hour model timestamp prior to 00,06,12,18Z
def get_latest_possible_model_time(model_name):
    interval_hours = model_interval_hours[model_name]
    utc_now = datetime.utcnow()
    model_hour = utc_now.hour
    latest_hour = (model_hour // interval_hours) * interval_hours
    latest_time = datetime.replace(utc_now, hour = latest_hour, minute = 0, second = 0, microsecond = 0)
    return latest_time

# for interval_hours = 6, find 6 hour model timestamp prior to 00,06,12,18Z
def get_latest_n_model_times(model_name, n=num_latest_runs):
    model_times = []
    interval_hours = model_interval_hours[model_name]
    utc_now = datetime.utcnow()
    model_hour = utc_now.hour
    latest_hour = (model_hour // interval_hours) * interval_hours
    latest_time = datetime.replace(utc_now, hour = latest_hour, minute = 0, second = 0, microsecond = 0)

    model_times.append(latest_time)
    for n in range(n):
        latest_time = get_prior_model_time(model_name, latest_time)
        model_times.append(latest_time)
    return model_times

def get_prior_model_time(model_name, dt):
    interval_hours = model_interval_hours[model_name]
    prior_time = dt - timedelta(hours = interval_hours)
    return prior_time

def load_index_file_ecm(idx_file_path):
    with open(idx_file_path, 'r') as file:
        lines = file.readlines()
    return [json.loads(line) for line in lines]

def find_matching_lines_gfs(idx_file_path, params):
    matching_lines = []
    all_lines = []
    with open(idx_file_path, 'r') as file:
        lines = file.readlines()
        for i, line in enumerate(lines):
            if any(param in line for param in params):
                matching_lines.append((i, line))
            if ':' in line:
                all_lines.append((i, line))
    return all_lines, matching_lines

def generate_curl_grib_copy_commands_nav(timestamp_prefix, url_folder, url_base_file_name, output_dir, params):
    output_dir_timestamp = os.path.join(output_dir, timestamp_prefix)

    # have no index file so use requests.head first
    # this allows us to to check if the directory exists before trying to get all the split grib files
    try:
        res = requests.head(url_folder)
        if not res:
            print_level(3, f"Warning: Could not get requests.head() from {url_folder}")
            return -1

        if res.status_code != 200:
            print_level(3, f"Warning: Could not get requests.head() from {url_folder}")
            return -1
    except:
        print_level(3, f"Warning: Could not get requests.head() from {url_folder}")
        return -1

    # download to tmp folder first
    output_file = os.path.join(tmp_download_dir, f"{timestamp_prefix}_{url_base_file_name}.grib2")

    # always overwrite temp file in case of corruption, since all the split gribs should be able to be produced if it is OK
    url = f'{url_folder}{url_base_file_name}.grib2'
    curl_command = f"curl --create-dirs -y 30 -Y 30 -f -v -s {url} -o {output_file}"
    print_level(4, curl_command)

    # Run the subprocess and capture its output
    result = subprocess.run(curl_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    time.sleep(sleep_time)
    if result.returncode != 0:
        print_level(3, f"Warning: Command failed with exit code {result.returncode}")
        print_level(3, f"Command: {curl_command}")
        print_level(3, f"Error output: {result.stderr.decode('utf-8')}")
        return -2

    grib_file_path = output_file

    try:
        os.makedirs(output_dir_timestamp, exist_ok=True)
    except:
        print(1, f"Could not make folder: {output_dir_timestamp}")
        return -1

    for param in params:
        levtype = param["levtype"]
        level = param["level"]
        shortName = param["shortName"]

        output_file = os.path.join(output_dir_timestamp, f"{timestamp_prefix}_{url_base_file_name}_{shortName}_{levtype}_{level}.grib2")

        grib_copy_command = f"grib_copy -w levtype='{levtype}',level={level},shortName='{shortName}' '{grib_file_path}' '{output_file}'"
        print_level(4, grib_copy_command)

        # Run the subprocess and capture its output
        result = subprocess.run(grib_copy_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        if result.returncode != 0:
            print_level(3, f"Warning: Command failed with exit code {result.returncode}")
            print_level(3, f"Command: {curl_command}")
            print_level(3, f"Error output: {result.stderr.decode('utf-8')}")
            # delete the tmp file now on an error
            try:
                os.remove(grib_file_path)
            except:
                # this is somewhat serious as we could run out of tmp space
                print_level(1, f"Warning could not delete tmp file: {grib_file_path}")

            return -2

    # delete the tmp file now that done
    try:
        os.remove(grib_file_path)
    except:
        print_level(1, f"Warning could not delete tmp file: {grib_file_path}")

    return 0

def generate_curl_commands_cmc(timestamp_prefix, url_folder, url_base_file_name, output_dir, params):
    output_dir_timestamp = os.path.join(output_dir, timestamp_prefix)

    # have no index file so use requests.head first
    # this allows us to to check if the directory exists before trying to get all the split grib files
    try:
        res = requests.head(url_folder)
        if not res:
            print_level(3, f"Warning: Could not get requests.head() from {url_folder}")
            return -1

        if res.status_code != 200:
            print_level(3, f"Warning: Could not get requests.head() from {url_folder}")
            return -1
    except:
        print_level(3, f"Warning: Could not get requests.head() from {url_folder}")
        return -1

    for param in params:
        url_base_file_name_with_param = url_base_file_name.replace(r'{PARAM}', param)

        output_file = os.path.join(output_dir_timestamp, f"{timestamp_prefix}_{url_base_file_name_with_param}.grib2")

        overwrite_or_download = True

        if os.path.exists(output_file):
            overwrite_or_download = False

        if overwrite_or_download:
            url = f'{url_folder}{url_base_file_name_with_param}.grib2'
            curl_command = f"curl --create-dirs -y 30 -Y 30 -f -v -s {url} -o {output_file}"
            print_level(4, curl_command)

            # Run the subprocess and capture its output
            result = subprocess.run(curl_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            time.sleep(sleep_time)
            if result.returncode != 0:
                print_level(3, f"Warning: Command failed with exit code {result.returncode}")
                print_level(3, f"Command: {curl_command}")
                print_level(3, f"Error output: {result.stderr.decode('utf-8')}")
                return -2

    return 0

def generate_curl_commands_ecm(timestamp_prefix, idx_file_name, url_folder, url_base_file_name, output_dir, params):
    output_dir_timestamp = os.path.join(output_dir, timestamp_prefix)
    idx_file_path = os.path.join(output_dir, timestamp_prefix, f'{timestamp_prefix}_{idx_file_name}')
    #os.makedirs(output_dir_timestamp, exist_ok=True)
    if not os.path.exists(idx_file_path):
        url_idx = f'{url_folder}{idx_file_name}'
        curl_command = f"curl --create-dirs -y 30 -Y 30 -f -v -s {url_idx} -o {idx_file_path}"
        print_level(4, curl_command)

        # Run the subprocess and capture its output
        result = subprocess.run(curl_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        time.sleep(sleep_time)
        if result.returncode != 0:
            print_level(3, f"Warning: Command failed with exit code {result.returncode}")
            print_level(3, f"Command: {curl_command}")
            print_level(3, f"Error output: {result.stderr.decode('utf-8')}")
            try:
                # clear directory if no files ready yet
                os.rmdir(output_dir_timestamp)
            except:
                pass

            return -1

    data = load_index_file_ecm(idx_file_path)

    for param_dict in params:
        levtype = param_dict["levtype"]
        levlist = param_dict.get("levlist", "")
        param = param_dict["param"]

        matching_lines = [entry for entry in data if entry["levtype"] == levtype and entry["param"] == param and entry.get("levelist", "") == levlist]

        for entry in matching_lines:
            start = int(entry["_offset"])
            length = int(entry["_length"])
            size = length
            end = start + length - 1

            param_name = entry["param"].replace(':', '_').replace(' ', '_').lower()
            if levlist == "":
                full_name = f'{param_name}_{levtype}'
            else:
                full_name = f'{param_name}_{levlist}_{levtype}'

            full_name = full_name.replace(':', '_').replace(' ', '_').lower()
            range_str = f"{start}-{end}"

            output_file = os.path.join(output_dir_timestamp, f"{timestamp_prefix}_{url_base_file_name}_{full_name}.grib2")

            overwrite_or_download = True

            if os.path.exists(output_file):
                if os.path.getsize(output_file) == size:
                    overwrite_or_download = False

            if overwrite_or_download:
                url = f'{url_folder}{url_base_file_name}.grib2'
                curl_command = f"curl --create-dirs -y 30 -Y 30 -f -v -s -r {range_str} {url} -o {output_file}"
                print_level(4, curl_command)

                # Run the subprocess and capture its output
                result = subprocess.run(curl_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                time.sleep(sleep_time)
                if result.returncode != 0:
                    print_level(3, f"Warning: Command failed with exit code {result.returncode}")
                    print_level(3, f"Command: {curl_command}")
                    print_level(3, f"Error output: {result.stderr.decode('utf-8')}")
                    return -2

    return 0

def generate_curl_commands_gfs(timestamp_prefix, idx_file_name, url_folder, url_base_file_name, output_dir, params):
    output_dir_timestamp = os.path.join(output_dir, timestamp_prefix)
    idx_file_path = os.path.join(output_dir, timestamp_prefix, f'{timestamp_prefix}_{idx_file_name}')
    #os.makedirs(output_dir_timestamp, exist_ok=True)
    if not os.path.exists(idx_file_path):
        url_idx = f'{url_folder}{idx_file_name}'
        curl_command = f"curl --create-dirs -y 30 -Y 30 -f -v -s {url_idx} -o {idx_file_path}"
        print_level(4, curl_command)

        # Run the subprocess and capture its output
        result = subprocess.run(curl_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        time.sleep(sleep_time)
        if result.returncode != 0:
            print_level(3, f"Warning: Command failed with exit code {result.returncode}")
            print_level(3, f"Command: {curl_command}")
            print_level(3, f"Error output: {result.stderr.decode('utf-8')}")
            try:
                # clear directory if no files ready yet
                os.rmdir(output_dir_timestamp)
            except:
                pass

            return -1

    all_lines, matching_lines = find_matching_lines_gfs(idx_file_path, params)
    total_lines = len(all_lines)

    for i, (line_num, line) in enumerate(matching_lines):
        fields = line.split(':')
        start = int(fields[1])
        if i == total_lines - 1:
            range_str = f"{start}-"
            # TODO, right now always overwrite last file? no, change from getting the http size of the url first and setting that as end-1
            size = -1
        else:
            next_line_fields = all_lines[line_num + 1][1].split(':')
            end = int(next_line_fields[1]) - 1
            range_str = f"{start}-{end}"
            size = end - start + 1

        param_name = fields[3].replace(':', '_').replace(' ', '_').lower()
        param_level = fields[4].replace(':', '_').replace(' ', '_').lower()
        output_file = os.path.join(output_dir_timestamp, f"{timestamp_prefix}_{os.path.basename(url_base_file_name)}_{param_name}_{param_level}.grib2")

        overwrite_or_download = True

        if os.path.exists(output_file):
            if os.path.getsize(output_file) == size:
                overwrite_or_download = False

        if overwrite_or_download:
            url = f'{url_folder}{url_base_file_name}'
            curl_command = f"curl --create-dirs -y 30 -Y 30 -f -v -s -r {range_str} {url} -o {output_file}"
            print_level(4, curl_command)

            # Run the subprocess and capture its output
            result = subprocess.run(curl_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            time.sleep(sleep_time)
            if result.returncode != 0:
                print_level(3, f"Warning: Command failed with exit code {result.returncode}")
                print_level(3, f"Command: {curl_command}")
                print_level(3, f"Error output: {result.stderr.decode('utf-8')}")
                return -2

    return 0

# the full timestep string as used in the file names we are parsing from the model data
# include any leading zeros up that make sense (only up to what the model covers)
def convert_model_time_step_to_str(model_name, model_time_step_int):
    str_format = model_time_step_str_format[model_name]
    return f'{model_time_step_int:{str_format}}'

def get_model_time_steps(model_name, model_timestamp):
    hour = model_timestamp.hour
    hour_str = f'{hour:02}'
    return all_time_steps_by_model[model_name][hour_str]

def get_completed_and_missing_downloaded_model_steps(model_name, model_timestamp):
    model_time_steps = get_model_time_steps(model_name, model_timestamp)
    model_base_dir = model_data_folders_by_model_name[model_name]
    model_date_str_with_hour = datetime.strftime(model_timestamp, '%Y%m%d%H')
    model_date_str = datetime.strftime(model_timestamp, '%Y%m%d')
    model_dir = os.path.join(model_base_dir, model_date_str_with_hour)
    model_hour = datetime.strftime(model_timestamp, '%H')
    model_time_step_re = re.compile(time_step_re_str_by_model_name[model_name])
    if not os.path.exists(model_dir):
        model_time_steps_int = [int(x) for x in model_time_steps]
        return [], model_time_steps_int
    files = os.listdir(model_dir)
    steps_downloaded = []
    steps_missing = []

    expected_num_grib_files_per_step = expected_num_grib_files_by_model_name[model_name]
    for model_time_step in model_time_steps:
        if type(model_time_step) is str:
            model_time_step_str = model
        elif type(model_time_step) is int:
            model_time_step_str = convert_model_time_step_to_str(model_name, model_time_step)

        # may not be a trailing slash or not
        if not os.path.isdir(model_dir):
            model_timestamp_str = os.path.basename(os.path.dirname(model_dir))
        else:
            model_timestamp_str = os.path.basename(model_dir)

        model_step_timestamp = datetime.strptime(model_timestamp_str, '%Y%m%d%H')

        grib_files = []

        for f in files:
            f_ext = f.split('.')[-1]
            if f_ext not in ignored_file_extensions:
                res = re.match(model_time_step_re, f)
                if res:
                    file_model_time_step_str = res['time_step']
                    if file_model_time_step_str == model_time_step_str:
                        grib_files.append(os.path.join(model_dir, f))

        num_grib_files_in_step = len(grib_files)
        if num_grib_files_in_step >= expected_num_grib_files_per_step:
            steps_downloaded.append(int(model_time_step_str))
        else:
            steps_missing.append(int(model_time_step_str))
    return steps_downloaded, steps_missing

# returns complete (boolean), downloaded steps (int list), missing steps (int list)
def model_run_steps_downloaded_info(model_name, model_timestamp):
    model_hour = datetime.strftime(model_timestamp, '%H')
    expected_num_total_steps = total_model_time_steps[model_name][model_hour]
    steps_downloaded, steps_missing = get_completed_and_missing_downloaded_model_steps(model_name, model_timestamp)

    return steps_downloaded, steps_missing

def download_step(model_name, model_timestamp, time_step_int):
    model_date = datetime.strftime(model_timestamp, '%Y%m%d')
    model_hour = datetime.strftime(model_timestamp, '%H')
    time_step = convert_model_time_step_to_str(model_name, time_step_int)

    output_dir = model_data_folders_by_model_name[model_name]
    # include trailing slash in url_folder (not in url_folder_by_model)
    model_url_folder = url_folder_by_model[model_name]
    # todo generalize:

    timestamp_prefix = f'{model_date}{model_hour}'
    params = params_by_model_name[model_name]

    if model_name == 'GFS':
        url_folder = f'{model_url_folder}/gfs.{model_date}/{model_hour}/atmos/'
        idx_file_name = f'gfs.t{model_hour}z.pgrb2.0p25.f{time_step}.idx'
        url_base_file_name = f'gfs.t{model_hour}z.pgrb2.0p25.f{time_step}'
        r = generate_curl_commands_gfs(timestamp_prefix, idx_file_name, url_folder, url_base_file_name, output_dir, params)
    elif model_name == 'ECM':
        url_folder = f'{model_url_folder}/{model_date}/{model_hour}z/0p4-beta/oper/'
        idx_file_name = f'{model_date}{model_hour}0000-{time_step}h-oper-fc.index'
        # we will add the .grib2 extension before we download
        url_base_file_name = f'{model_date}{model_hour}0000-{time_step}h-oper-fc'
        r = generate_curl_commands_ecm(timestamp_prefix, idx_file_name, url_folder, url_base_file_name, output_dir, params)
    elif model_name == 'CMC':
        url_folder = f'{model_url_folder}/{model_date}/WXO-DD/model_gem_global/15km/grib2/lat_lon/{model_hour}/{time_step}/'
        # we will add the .grib2 extension before we download and substitute PARAM ourselves
        url_base_file_name = r'CMC_glb_{PARAM}' + f'_latlon.15x.15_{model_date}{model_hour}_P{time_step}'
        r = generate_curl_commands_cmc(timestamp_prefix, url_folder, url_base_file_name, output_dir, params)
    elif model_name == 'NAV':
        url_folder = f'{model_url_folder}/navgem.{model_date}/'
        # we will add the .grib2 extension before we download and substitute PARAM ourselves
        url_base_file_name = f'navgem_{model_date}{model_hour}f{time_step}'
        r = generate_curl_grib_copy_commands_nav(timestamp_prefix, url_folder, url_base_file_name, output_dir, params)

    return r

def download_model_run(model_name, model_timestamp):
    already_calculated_time_steps, missing_calculated_time_steps = get_calculated_and_missing_time_steps(model_name, model_timestamp)

    if missing_calculated_time_steps is None:
        return 0

    steps_downloaded, steps_missing = model_run_steps_downloaded_info(model_name, model_timestamp)

    # remove already downloaded, calculated, and deleted steps
    steps_missing = sorted(list(set(steps_missing).intersection(set(missing_calculated_time_steps))))

    none_downloaded = (len(steps_downloaded) == 0)
    none_could_download = False
    r = 0
    if steps_missing:
        for time_step_int in steps_missing:
            if not have_enough_disk_space():
                print_level(1, "Fatal error: Exiting since disk space is below the minimum set.")
                exit()
            cur_r = download_step(model_name, model_timestamp, time_step_int)
            if cur_r != 0:
                if r != -1:
                    # OK or could not get one of the gribs
                    r = cur_r
                elif cur_r == -1:
                    # could not get an index
                    r = cur_r

                if none_downloaded:
                    none_could_download = True
                    r = -3
                    break
                # assume all time steps are complete (if there are missing time steps this will fail to download rest)
                break
            else:
                none_could_download = False
                none_downloaded = False
    return r

def download_thread(model_names):
    try:
        model_timestamps = {}
        complete_model_runs = {}
        for model_name in model_names:
            complete_model_runs[model_name] = []

        while not exit_event.is_set():
            exiting = False
            for model_name in model_names:
                model_timestamps[model_name] = get_latest_n_model_times(model_name)
                for model_timestamp in model_timestamps[model_name]:
                    if exit_event.is_set():
                        exiting = True
                        break
                    if model_timestamp in complete_model_runs[model_name]:
                        print_level(3, f'Already have {model_timestamp} for {model_name}.')
                        r = 0
                        continue
                    # download latest run with available timesteps
                    # there may be some cycling but the staleness check should always avoid infinite cycling between old runs
                    r = download_model_run(model_name, model_timestamp)
                    if r == -2 or r == -1:
                        print_level(3, f'Failed to finish downloading latest {model_name} run from {model_timestamp}. Retrying later.')
                    elif r == -3:
                        # could not even download any files, try the prior run
                        print_level(3, f'{model_name} run from {model_timestamp} not available.')
                    elif r == 0:
                        # successfully downloaded the latest run
                        print_level(2, f'Finished downloading latest {model_name} run for {model_name} {model_timestamp} (or already exists).')
                        # get the next one after backing off
                        if model_timestamp not in complete_model_runs[model_name]:
                            complete_model_runs[model_name].append(model_timestamp)
                if exiting:
                    break
            if exiting:
                break

            print_level(3, f'Backing off for {backoff_time} seconds in thread {threading.current_thread().name}.')
            time.sleep(backoff_time)

        if exiting:
            print_level(1, f'Thread {threading.current_thread().name} exiting.')

    except Exception as e:
        print_level(1, f"Thread {threading.current_thread().name} encountered an error: {e}")
        exit_event.set()
        sys.exit(1)

# Register a signal handler to catch Ctrl+C and exit gracefully
def signal_handler(sig, frame):
    print_level(1, f"\nCtrl+C received. Exiting once download runs are finished. May take {backoff_time} s to exit. Repeat to forcefully exit (may corrupt downloads)")
    exit_event.set()
    global ctrl_c_count
    ctrl_c_count += 1

    if ctrl_c_count >= 4:
        print("Received multiple Ctrl-C. Exiting forcefully immediately.")
        os._exit(1)

if __name__ == '__main__':
    # run the downloads per thread (default )
    threads = []

    ctrl_c_count = 0

    signal.signal(signal.SIGINT, signal_handler)

    for group in model_names_to_download_thread_groupings:
        thread = threading.Thread(target=download_thread, args=(group,), name='_'.join(group))
        thread.start()
        threads.append(thread)

    for thread in threads:
        thread.join()

    print_level(1, "All threads have finished.")


def espar(num1):
    if(num1 % 2 == 0):
        return 1
    else:
        return 0

num1 = int(input("ingrese un numero: "))

if(espar(num1)):
    print("Es par")
else:
    print("Es impar")

# Crear la función –es_par que tenga como parámetro un numero, y dependiendo de
#este devolver 1 si es par y 0 si impar


import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, FallingEdge, Timer, ClockCycles

@cocotb.test()
async def test_hex_sr(dut):
    dut._log.info("start")
    clock = Clock(dut.clk, 10, units="us")
    cocotb.start_soon(clock.start())

    cycles = 512
    sr_len = 100

    dut.recirc.value = 0
    for cycle in range(cycles):
        dut.sr_in = cycle % 64
        await RisingEdge(dut.clk)
        if cycle >= sr_len:
            v = int(dut.sr_out)
            assert v == (cycle - sr_len) % 64
            # output matches input with 40 cycle latency
        await FallingEdge(dut.clk)

    dut.recirc.value = 1
    for cycle in range(cycles, 2 * cycles):
        dut.sr_in = cycle % 64
        await RisingEdge(dut.clk)
        v = int(dut.sr_out)
        if cycle >= cycles + sr_len:
            v = int(dut.sr_out)
            assert v == (cycle - sr_len) # 64
        await FallingEdge(dut.clk)


import re

class Card:
    def __init__(self, card_expression: str) -> None:
        card_elements = re.match(r"^([AKQJ]|10|[2-9])([♤♡♧♢])$", card_expression)

        if not card_elements:
            raise ValueError("Expecting card_expression to be in this format: A♡")

        self.value = card_elements.group(1)
        self.suit = card_elements.group(2)

    def __str__(self) -> str:
        """
        Returns card in this format: A♡
        """
        return f"{self.value}{self.suit}"

    def __repr__(self) -> str:
        """
        Returns card in this format: A♡
        """
        return f"{self.value}{self.suit}"

    def get_numeric_value(self) -> int:
        """
        Returns the numeric value of the card, where A is 14, K is 13, Q is 12, J is 11, and all other cards
        have their face value as their numeric value (e.g. 10 is 10, 9 is 9, etc.).
        """

        card_values = {
            "A": 14,
            "K": 13,
            "Q": 12,
            "J": 11,
        }

        if self.value in card_values:
            return card_values[self.value]

        return int(self.value)

    def __lt__(self, other) -> bool:
        """
        Compares the numeric value of the current card with another card.

        Args:
            other: Another card to compare with.

        Returns:
            True if the numeric value of the current card is less than the numeric value of the other object.
            False otherwise.
        """
        if not isinstance(other, Card):
            raise NotImplemented

        return self.get_numeric_value() < other.get_numeric_value()

    def __eq__(self, other) -> bool:
        """
        Checks if numeric values of both cards are equal

        Args:
            other: Another card to compare with.

        Returns:
            True if the numeric values of both cards are equal.
            False otherwise.
        """

        if not isinstance(other, Card):
            raise NotImplemented

        return self.get_numeric_value() == other.get_numeric_value()


import torch
import torch.nn as nn
import functools
from loss import *

class Res_Enhancement(nn.Module):
    def __init__(self):
        super(Res_Enhancement, self).__init__()

        self.conv1 = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1),
            nn.ReLU(inplace=True))
        self.conv2_0 = nn.Sequential(
            nn.Conv2d(in_channels=16, out_channels=16, kernel_size=3, padding=1),
            nn.BatchNorm2d(16),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=16, out_channels=16, kernel_size=3, padding=1),
            nn.BatchNorm2d(16))

        self.conv2_1 = nn.Sequential(
            nn.Conv2d(in_channels=16, out_channels=16, kernel_size=3, padding=1),
            nn.BatchNorm2d(16),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=16, out_channels=16, kernel_size=3, padding=1),
            nn.BatchNorm2d(16))

        self.conv2_2 = nn.Sequential(
            nn.Conv2d(in_channels=16, out_channels=16, kernel_size=3, padding=1),
            nn.BatchNorm2d(16),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=16, out_channels=16, kernel_size=3, padding=1),
            nn.BatchNorm2d(16))

        self.delta = nn.Sequential(
            nn.Conv2d(in_channels=16, out_channels=3, kernel_size=1, padding=0),
            nn.Sigmoid())

        self.en_loss = LossFunction()

    def forward(self, x):
        block1 = self.conv1(x)
        block2 = self.conv2_0(block1)
        block2 = block1 + block2
        block3 = self.conv2_1(block2)
        block3 = block2 + block3
        block4 = self.conv2_2(block3)
        block4 = block3 + block4
        delta = self.delta(block4)
        illu = delta + x

        illu = torch.clamp(illu, 0.001, 1.0)
        enhance = x / illu
        enhance = torch.clamp(enhance, 0.0, 1.0)
        return enhance, illu

    def loss(self, x):
        illu, enhance = self(x)
        loss = self.en_loss(x, illu)
        return loss


"""
[베스트 앨범]
https://school.programmers.co.kr/learn/courses/30/lessons/42579

스트리밍 사이트에서 장르 별로 가장 많이 재생된 노래를 두 개씩 모아 베스트 앨범을 출시
노래를 수록하는 기준
- 속한 노래가 많이 재생된 장르를 먼저 수록합니다.
- 장르 내에서 많이 재생된 노래를 먼저 수록합니다.
- 장르 내에서 재생 횟수가 같은 노래 중에서는 고유 번호가 낮은 노래를 먼저 수록합니다.

제한 사항
- genres와 plays의 길이는 동일
- 장르 종류 < 100개
- 장르에 속한 곡이 하나라면, 하나의 곡만 선택합니다.
- 모든 장르는 재생된 횟수가 다르다.
"""
from typing import List
from collections import defaultdict

def solution(genres:List, plays:List) -> List:
    answer = []

    # key에 대한 기본 데이터 형태가 정해지면 defaultdict를 사용하기
    total_info = defaultdict(list)
    songs_per_genre = defaultdict(int)

    for i, (genre, play_count) in enumerate(zip(genres, plays)):
        # {'classic': [(0, 500), (2, 150), (3, 800)], 'pop': [(1, 600), (4, 2500)]}
        total_info[genre].append((i, play_count))

        # {'classic': 1450, 'pop': 3100}
        songs_per_genre[genre] += play_count

    # item의 1 인덱스에 있는 게 재생 횟수이므로, 이를 기준으로 내림차순으로 정렬한다.
    # 정렬하여 재생횟수가 제일 많은 것부터 플레이 리스트에 추가한다.
    for genre, _ in sorted(songs_per_genre.items(), key=lambda x:x[1], reverse=True):
        for i, _ in sorted(total_info[genre], key=lambda x:x[1], reverse=True)[:2]:
            answer.append(i)

    return answer

if __name__ == "__main__":
    genres = ["classic", "pop", "classic", "classic", "pop"]
    plays = [500, 600, 150, 800, 2500]
    print(solution(genres, plays))


SIZE = 8

def save_positions(searched_letter, r, index):
    if searched_letter in board[r]:
        positions[index].append(r)
        positions[index].append(board[r].index(searched_letter))

board = []
positions = [[], []]

for row in range(SIZE):
    board.append(input().split())

    # if 'w' in board[row]:
    # positions[0].append(row)
    # positions[0].append(board[row].index('w'))

    # elif 'b' in board[row]:
    # positions[1].append(row)
    # positions[1].append(board[row].index('b'))

    save_positions('w', row, 0)
    save_positions('b', row, 1)

# if the two paws are in columns next to each other, in other words one can take the other this means that the subtract of the columns will always be = 1
# this is the scenario in which we do not have them one next to each other
# size- positions(white) - 1 - ogledalen obraz na white , it is like the two paws are going into the same direction . -1 is get a valid index

if abs(positions[0][1] - positions[1][1]) != 1 or positions[1][0] > positions[0][0]:
    if SIZE - positions[0][0] - 1 <= positions[1][0]:
        print(f"Game over! Black pawn is promoted to a queen at {chr(97 + positions[1][1])}1.")
    else:
        print(f"Game over! White pawn is promoted to a queen at {chr(97 + positions[0][1])}8.")

else:
    place = (positions[0][0] + positions[1][0]) // 2
    # if w and black are on even or w and black are both on odd positions black wins
    if positions[0][0] % 2 == positions[1][0] % 2:
        print(f"Game over! Black win, capture on {chr(97 + positions[0][1])}{SIZE - place}.")

    else:
        print(f"Game over! White win, capture on {chr(97 + positions[1][1])}{SIZE - place}.")


#!/usr/bin/env python3

def calculate_sum(data):
    # first value gives the total number of child nodes of current node
    # second value gives total number of metadata for the node
    child_nodes, metadata = data[:2]
    data = data[2:]
    s = 0

    for i in range(child_nodes):
        val, data = calculate_sum(data)
        s += val

    return s + sum(data[:metadata]), data[metadata:]

def main():
    data = list(map(int, open('input').read().strip().split()))
    # data = list(map(int, "2 3 0 3 10 11 12 1 1 0 1 99 2 1 1 2".split()))
    val = calculate_sum(data)
    print(val)

if __name__ == "__main__":
    main()


#!/usr/bin/python3

# Given two strings s and t, return true if t is an anagram of s, and false otherwise.
# An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:

        # Solution 1
        # Time: O(nlogn)
        # Space: O(n)
        if len(s) == len(t):
            if sorted(s) == sorted(t):
                return True
        return False

        # Solution 2
        # Time: O(n)
        # Space: O(n)
        if len(s) == len(t):

            s_hash_map = {}
            for char in s:
                s_hash_map[char] = 1 + s_hash_map.get(char,0)

            t_hash_map = {}
            for char in t:
                t_hash_map[char] = 1 + t_hash_map.get(char,0)

            if len(s_hash_map) != len(t_hash_map):
                return False

            for c in s_hash_map:
                if s_hash_map.get(c) != t_hash_map.get(c):
                    return False
            return True
        return False


from copy import deepcopy
import json, os
from random import random

DEBUG = True

BRUCE_STORY_LINE = "bsl"
BRUCE_SHOW_UP = "bsu"
BRUCE_LOVE = "brl"
BRUCE_INTRODUCE = "bri"
OLIVER_STORY_LINE = "osl"
OLIVER_LOVE = "oll"
SINESTRO_STORY_LINE = "ssl"
SINESTRO_LOVE = "sil"
SINESTRO_TAME = "sit"
BARRY_LOVE = "bal"

SWORD_HOT_TIME = "sht"
KNOWLEDGE = "knw"
INTELLIGENCE = "int"
TEMPORARY = "tmp"
PROPS = "pro"
PEGASUS = "pgs"
DRAGON_EGG = "dge"

TEAMMATE = "tmm"
BRUCE_CODE = 1
SINESTRO_CODE = 2
OLIVER_CODE = 3
BARRY_CODE = 4

GAME_OVER = "game over"
FINAL_BATTLE = "final_battle"

character_para = {
    BRUCE_STORY_LINE: 0,
    BRUCE_SHOW_UP: 0,
    BRUCE_LOVE: 0,
    BRUCE_INTRODUCE: 0,
    OLIVER_STORY_LINE: 0,
    OLIVER_LOVE: 0,
    SINESTRO_STORY_LINE: 0,
    SINESTRO_LOVE: 0,
    SINESTRO_TAME: 0,
    BARRY_LOVE: 0,
}
stroy_para = {
    TEAMMATE: 0,
    SWORD_HOT_TIME: 0,
    KNOWLEDGE: 0,
    INTELLIGENCE: 0,
    TEMPORARY: 0,
    PROPS: 0,
    PEGASUS: 0,
    DRAGON_EGG: 0,
}

def res_path(fn):
    '''相关文件路径'''
    if DEBUG:
        root = os.path.abspath(".")
    else:
        root = os.getenv("APPDATA")
        root = os.path.join(root, "EmeraldKnight")
    return os.path.join(root, fn)

class gk:
    '''全局静态类'''
    debug_para = {**character_para, **stroy_para}
    scene = ""
    paras = {}
    pos = 0
    sn: dict[str, str] = {}
    battle = None

    @staticmethod
    def init():
        f = open(res_path("story/menu.json"), "r", encoding="utf8")
        gk.sn = json.loads(f.read())
        f.close()
        f = open(res_path("story/info.json"), "r", encoding="utf8")
        gk.info_map = json.loads(f.read())
        f.close()
        if not os.path.exists(res_path("save")):
            os.makedirs(res_path("save"))
        try:
            open(res_path("save/0.eks"), "r")
        except FileNotFoundError:
            f = open(res_path("save/0.eks"), "w")
            f.write("{}")
            f.close()

    @staticmethod
    def default_para():
        return deepcopy(gk.debug_para)

    @staticmethod
    def targetName(scene) -> str:
        '''选项名'''
        return gk.sn.get(scene, "找不到选项名")

    @staticmethod
    def openPara(para_name):
        f = open(res_path("save/0.eks"), "r")
        p = json.loads(f.read())
        f.close()
        p[para_name] = 1
        f = open(res_path("save/0.eks"), "w")
        f.write(json.dumps(p) + "\n")
        f.close()

    @staticmethod
    def readPara(para_name):
        f = open(res_path("save/0.eks"), "r")
        p = json.loads(f.read())
        f.close()
        return p[para_name]

    @staticmethod
    def fight():
        hp = gk.paras[TEMPORARY]
        hp += 3 * gk.paras[INTELLIGENCE]
        hp += gk.paras[KNOWLEDGE]
        hp += 5 * gk.paras[BRUCE_LOVE]
        hp += 5 * (gk.paras[SINESTRO_LOVE] + gk.paras[SINESTRO_TAME])
        hp += 20 * (gk.paras[TEAMMATE] != 0)
        for i in range(10):
            hp -= random() * 16
        return hp > 0

    @staticmethod
    def open_info_entry(name):
        for i in gk.info_map.keys():
            if name in gk.info_map[i]["scene"]:
                gk.openPara(i)


from sqlalchemy.ext.associationproxy import _AssociationList

class RightMixin:
    @staticmethod
    def _fields_base_read():
        return set()

    @staticmethod
    def _fields_export_csv():
        return {
            "software",
            "software_version",
            "http_referrer",
            "user_agent_browser",
            "user_agent_version",
            "user_agent_language",
            "user_agent_platform",
            "timestamp",
        }

    @classmethod
    def fields_export_csv(cls):
        return cls._fields_base_read().union(cls._fields_export_csv())

    def __getitem__(self, key):
        if not hasattr(self, "__dump__"):
            self.__dump__ = {}
        return self.__dump__.get(key)

    def __setitem__(self, key, value):
        if not hasattr(self, "__dump__"):
            self.__dump__ = {}
        self.__dump__[key] = value

    def dump(self):
        dico = {k: getattr(self, k) for k in self.fields_export_csv()}
        if hasattr(self, "__dump__"):
            dico.update(self.__dump__)
        for key, value in dico.items():  # preventing association proxy to die
            if isinstance(value, _AssociationList):
                dico[key] = list(value)
        return dico


# import random
# prob_rn = [0, 0, 0]
# prob_rn[0] = random.randint(0, 9)
# prob_rn[1] = random.randint(0, 9)
# while prob_rn[0] == prob_rn[1]:
#     prob_rn[1] = random.randint(0, 9)
#
# prob_rn[2] = random.randint(0, 9)
# while (prob_rn[0] == prob_rn[2]) or (prob_rn[1] == prob_rn[2]) :
#     prob_rn[2] = random.randint(0, 9)
#
# prob = str(prob_rn[0]) + str(prob_rn[1]) + str(prob_rn[2])
# print(prob)
#  스트링으로 해야 ans가 스트링이기 때문에 스트링으로 바꿔서 리스트로 변환해줘야한다.
#
# count = 0
# sum_strike = 0
# sum_ball = 0
# while True:
#     ans = input("Input number:")
    # 숫자가 안들어오면 다시 입력하게 하는 과정
    # if not ans.isdigit():
    #     print("Please input only number.")
    #     continue
    #
    # count +=1
    # strike = 0
    # ball = 0
    #
    # avg_strike = 0
    # avg_ball = 0
    # strikes = []
    # balls = []
    # t = list(set(ans))
    # info = []
    # set 은 그 자체로 갯수를 셀 수 없으므로 리스트로 다시 캐스팅
    # set 은 중복되면 아에 element를 지워버림
    # 버블정렬 찾아볼것!
    # if len(t) !=3:
    #     print(" Do not 중복")
    #     continue

    # for k in range(0,3):
    #     for l in range(0,3):
    #         if ans[k] == ans [l]:
    #             continue

    # for i in range(len(prob)):
    #     for j in range(len(ans)):
    #         if i == j and prob[i] == ans[j]:
    #             strike += 1
    #         if i != j and prob[i] == ans[j]:
    #             ball += 1
    # print("strike is:",strike)
    # print("ball is:",ball)

    # sum_strike = sum_strike + strike
    # sum_ball = sum_ball + ball
    # 밑에꺼는 리스트로 평균 스트라이크, 볼 구하는것
    #  strikes.append(strike)
    # balls.append(ball)
    # sum = 0
    # for i in strikes[i]:
    #     sum +=1
    # strike_avg = sum / count

    # sum = 0
    # for j in balls[j]:
    #     sum +=1
    # ball_avg = sum / count
    # info.append(strike,ball)

    # if strike == 3:
    #     print("You did.")
    #     print("Average of strike is",sum_strike/count)
    #     print("Average of ball is",sum_ball/count)
    #     리스트로 한꺼번에 처리하는 방법
    #     sum = [0,0]
    #     for i in range  생각해봐 ㅅㅂ
        # break
    # 버블정렬
    # a = [4,5,7,2,1]
    # for i in range(len(a)-1):
    #     for j in range(i+1,len(a)):
    #         if a[i] > a[j]:
    #             t = a[i]
    #             a[i] = a[j]
    #             a[j] = t
    #
    # print(a)
import random
a=random.randint(0,9)

while True:
    b=random.randint(0,9)
    if a==b:
        continue
    else:
        break
while True:
    c=random.randint(0,9)
    if a==c or b==c:
        continue
    else:
        break
answer = [a,b,c]
print(answer)

while True:
    strike = 0
    ball = 0
    realball = 0
    i = 0
    j = 0
    attack= []
    # hund = int(input("Enter 백의자리 : "))
    # ten = int(input("Enter 십의자리 : "))
    # one = int(input("Enter 일의자리 : "))
    # guess = [hund,ten,one]
    guess = int(input("맞춰보세요! :"))
    if guess > 999 or guess < 100:
        continue
    c = (guess//100)
    d = ((guess//10)%10)
    e = (guess%10)
    attack = [c,d,e]
    print(attack)

    for i in range(0, 3):
        if attack[i]== answer[i]:
            strike+=1
    print('strike is:',strike)

    for i in range(0,3):
        for j in range(0,3):
            if attack[i]==answer[j]:
                ball+=1
    realball = ball - strike

    print('ball is:', realball)
    if strike !=3:
        continue
    else:
        break

print('You are Correct')


"""
Tests for tunes app.
"""

from django.test import TestCase
from tunes.models import Song

class SongDeserializeTest(TestCase):
    def _test_deserialize(self, line, fields):
        """
        Runs a serialization test.
        """
        song = Song()
        song.deserialize_itunes_tsv(line)
        song_fields = dict([(field.name, getattr(song, field.name)) for field in song._meta.fields if field.name != 'id'])
        self.assertEquals(song_fields, fields)

    def test_with_total_tracks(self):
        """
        Tests that a row copied from iTunes with a track and the total number of tracks deserializes.
        """
        line = 'On the Beatiful Blue Danube, Walz, Op. 314\t10:05\tJohann Strauss II\tClassical Masterpiece of Music\t1 of 9\tClassical\t\t11\n'
        fields = {'album': u'Classical Masterpiece of Music', 'name': u'On the Beatiful Blue Danube, Walz, Op. 314', 'artist': u'Johann Strauss II', 'track': 1, 'total_tracks': 9, 'time': 605}
        self._test_deserialize(line, fields)

    def test_with_track(self):
        """
        Tests that a row copied from iTunes with a track deserializes.
        """
        line = '08 08 08 08 08 - Act I, Tableau 1, The Nutcracker battles against the Army of the Mouse King, etc.\t3:20\tKirov Orchestra, Valery Gergiev\tTchaikovsky: The Nutcracker, Op. 71 (Complete Ballet)\t8\tClassical\t\t3\n'
        fields = {'album': 'Tchaikovsky: The Nutcracker, Op. 71 (Complete Ballet)', 'name': '08 08 08 08 08 - Act I, Tableau 1, The Nutcracker battles against the Army of the Mouse King, etc.', 'artist': 'Kirov Orchestra, Valery Gergiev', 'track': 8, 'total_tracks': None, 'time': 200}
        self._test_deserialize(line, fields)

    def test_without_track(self):
        """
        Tests that a row copied from iTunes with a track deserializes.
        """
        line = '01 Maple Leaf Rag (1899)\t3:19\tScott Joplin\t\t\tClassical\t\t6\n'
        fields = {'album': '', 'name': '01 Maple Leaf Rag (1899)', 'artist': 'Scott Joplin', 'track': None, 'total_tracks': None, 'time': 199}
        self._test_deserialize(line, fields)


from django.db.models.aggregates import Sum
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from whereithurtsapi.models import Patient, Healing, Treatment, Hurt
from rest_framework import status
from django.utils import timezone
from datetime import timedelta

class ProfileHealingSerializer(ModelSerializer):
    class Meta:
        model = Healing
        fields = ('id', 'duration', 'date_added', 'notes')

class ProfileTreatmentSerializer(ModelSerializer):
    class Meta:
        model = Treatment
        fields = ('id', 'name', 'notes', 'bodypart')
        depth = 1

class ProfileHurtSerializer(ModelSerializer):
    class Meta:
        model = Hurt
        fields = ('id', 'name', 'date_added', 'pain_level', 'latest_pain_level')

class ProfileViewSet(ViewSet):

    """ Method to access specific details of a Patient's use
    of the app based on timeframe. Patient id lookup is the PK
    of a /profiles route decorated by <patientId>/snapshot

    By default, will look up information for the past 7 days.
    """
    @action(detail=True)
    def snapshot(self, request, pk=None):
        try:
            patient = Patient.objects.get(pk=pk)
        except Patient.DoesNotExist:
            return Response({'message': 'patient does not exist'}, status=status.HTTP_404_NOT_FOUND)

        snapshot = {}

        #retrieve qset of this patient's healings for the past week
        recent_healings =  patient.healing_set.filter(added_on__gte=timezone.now() - timedelta(days=7))

        # only try to format healing time if there are any recent healings
        if len(recent_healings) > 0:
            recent_healing_duration = recent_healings.aggregate(Sum('duration'))
            formatted_healing_time = timedelta(seconds=recent_healing_duration["duration__sum"])
        else:
            formatted_healing_time = 0
        recent_treatments = Treatment.objects.filter(healing_treatments__healing__in=recent_healings).distinct()
        recent_hurts = Hurt.objects.filter(hurt_healings__healing__in=recent_healings).distinct()

        snapshot["recent_healings"] = ProfileHealingSerializer(recent_healings, many=True, context={'requet': request}).data
        snapshot["recent_treatments"] = ProfileTreatmentSerializer(recent_treatments, many=True, context={'request': request}).data
        snapshot["recent_hurts"] = ProfileHurtSerializer(recent_hurts, many=True, context={'request': request}).data
        snapshot["recent_healing_time"] = str(formatted_healing_time)

        return Response(snapshot)


one_crore = list(range(10000000))
#print(one_crore)

evens =list(range(2, 222, 2))
print(evens)

#adding values

x=[]
y = [1,2,3]

z= [1,2,2,3,4,5,6,7,8,9]
z.insert(1,3)
y.insert(10,4)

#insert adds a item at a particular position.

x.append(19)
y.append(8)
print(x)
print(y)
print(z)

 # append adds an item .
x.extend([22, 34, 45])
y.extend(x)
z.extend(y)

print(x)
print(y)
print(z)

#extend adds multiple items in one go.


from collections import Iterable, Mapping

import kjbuckets

from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.db.models import Model, Q
from django.template.loader import render_to_string
from django.db.models.loading import get_model
from django.contrib.sites.models import Site
from celery.task import task

from openPLM.plmapp.models import User, DelegationLink, ROLE_OWNER, ROLE_SIGN

def get_recipients(obj, roles, users):
    recipients = set(users)
    if hasattr(obj, "plmobjectuserlink_plmobject"):
        manager = obj.plmobjectuserlink_plmobject.order_by()
        roles_filter = Q()

        for role in roles:
            if role == ROLE_SIGN:
                roles_filter |= Q(role__startswith=role)
            else:
                roles_filter |= Q(role=role)
        users = manager.filter(roles_filter).values_list("user", flat=True)
        recipients.update(users)
        links = DelegationLink.objects.filter(roles_filter)\
                        .values_list("delegator", "delegatee")
        gr = kjbuckets.kjGraph(tuple(links))
        for u in users:
            recipients.update(gr.reachable(u).items())
    elif roles == [ROLE_OWNER]:
        if hasattr(obj, "owner"):
            recipients.add(obj.owner_id)
        elif isinstance(obj, User):
            recipients.add(obj.id)
    return recipients

def convert_users(users):
    if users:
        r = iter(users).next()
        if hasattr(r, "id"):
            users = [x.id for x in users]
    return users

class CT(object):
    __slots__ = ("app_label", "module_name", "pk")

    def __init__(self, app_label, module_name, pk):
        self.app_label = app_label
        self.module_name = module_name
        self.pk = pk

    def __getstate__(self):
        return dict(app_label=self.app_label,
                    module_name=self.module_name,
                    pk=self.pk)

    def __setstate__(self, state):
        self.app_label = state["app_label"]
        self.module_name = state["module_name"]
        self.pk = state["pk"]

    @classmethod
    def from_object(cls, obj):
        return cls(obj._meta.app_label, obj._meta.module_name, obj.pk)

    def get_object(self):
        model_class = get_model(self.app_label, self.module_name)
        return model_class.objects.get(pk=self.pk)

def serialize(obj):
    if isinstance(obj, basestring):
        return obj
    if isinstance(obj, Model):
        return CT.from_object(obj)
    elif isinstance(obj, Mapping):
        new_ctx = {}
        for key, value in obj.iteritems():
            new_ctx[key] = serialize(value)
        return new_ctx
    elif isinstance(obj, Iterable):
        return [serialize(o) for o in obj]
    return obj

def unserialize(obj):
    if isinstance(obj, basestring):
        return obj
    if isinstance(obj, CT):
        return obj.get_object()
    elif isinstance(obj, Mapping):
        new_ctx = {}
        for key, value in obj.iteritems():
            new_ctx[key] = unserialize(value)
        return new_ctx
    elif isinstance(obj, Iterable):
        return [unserialize(o) for o in obj]
    return obj

@task(ignore_result=True)
def do_send_histories_mail(plmobject, roles, last_action, histories, user, blacklist=(),
              users=(), template="mails/history"):
    """
    Sends a mail to users who have role *role* for *plmobject*.

    :param plmobject: object which was modified
    :type plmobject: :class:`.PLMObject`
    :param str roles: list of roles of the users who should be notified
    :param str last_action: type of modification
    :param str histories: list of :class:`.AbstractHistory`
    :param user: user who made the modification
    :type user: :class:`~django.contrib.auth.models.User`
    :param blacklist: list of emails whose no mail should be sent (empty by default).

    .. note::

        This function fails silently if it can not send the mail.
        The mail is sent in a separated thread.
    """
    plmobject = unserialize(plmobject)
    recipients = get_recipients(plmobject, roles, users)
    if recipients:
        user = unserialize(user)
        subject = "[PLM] " + unicode(plmobject)
        ctx = {
                "last_action" : last_action,
                "histories" : histories,
                "plmobject" : plmobject,
                "user" : user,
            }
        do_send_mail(subject, recipients, ctx, template, blacklist)

@task(ignore_result=True)
def do_send_mail(subject, recipients, ctx, template, blacklist=()):
    if recipients:
        if len(recipients) == 1:
            email = User.objects.get(id=recipients.pop()).email
            if not email or email in blacklist:
                return
            emails = (email,)
        else:
            emails = User.objects.filter(id__in=recipients).exclude(email="")\
                            .values_list("email", flat=True)
            emails = set(emails) - set(blacklist)
            if not emails:
                return
        ctx = unserialize(ctx)
        ctx["site"] = Site.objects.get_current()
        html_content = render_to_string(template + ".htm", ctx)
        message = render_to_string(template + ".txt", ctx)
        msg = EmailMultiAlternatives(subject, message, settings.EMAIL_OPENPLM,
            emails)
        msg.attach_alternative(html_content, "text/html")
        msg.send(fail_silently=True)

def send_mail(subject, recipients, ctx, template, blacklist=()):
    ctx = serialize(ctx)
    do_send_mail.delay(subject, convert_users(recipients),
            ctx, template, blacklist)

def send_histories_mail(plmobject, roles, last_action, histories, user, blacklist=(),
              users=(), template="mails/history"):
    if hasattr(plmobject, "object"):
        plmobject = plmobject.object
    plmobject = CT.from_object(plmobject)
    histories = serialize(histories)
    user = CT.from_object(user)
    do_send_histories_mail.delay(plmobject, roles, last_action, histories,
            user, blacklist, convert_users(users), template)


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pytest
import sys
import tempfile
import json

import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import config
from arm_compozure.arm_file import ArmFile

def test_init_check_attribute_src():
    test = ArmFile('https://raw.githubusercontent.com/TimSwart/azure-quickstart-templates/master/101-storage-account-create/azuredeploy.json', 'test_template')
    assert hasattr(test, 'src')

def test_init_check_attribute_dest():
    test = ArmFile('https://raw.githubusercontent.com/TimSwart/azure-quickstart-templates/master/101-storage-account-create/azuredeploy.json', 'test_template')
    assert hasattr(test, 'dest')

def test_init_check_bad_src_link_url():
    with pytest.raises(ValueError, match=ArmFile.Errors.BAD_SRC_PATH):
        ArmFile('badlink.json', 'test_template')

def test_init_check_src_link_local_file():
    ArmFile('tests/test_template.json', 'test_template')

def test_get_value_valid():
    arm = ArmFile('tests/test_template.json')
    assert arm.get_value('parameters.storageAccountType.defaultValue') == 'Standard_LRS'

def test_get_value_changed_outside_of_class():
    arm = ArmFile('tests/test_template.json')
    v = arm.get_value('parameters.storageAccountType')
    v['type'] = 'changed'
    assert v != arm.get_value('parameters.storageAccountType')

def test_get_value_error():
    arm = ArmFile('tests/test_template.json')
    key = 'parameters.storageAccountType.badValue'
    with pytest.raises(KeyError, match=ArmFile.Errors.MISSING_KEY.format(key)):
        arm.get_value(key)

def test_get_value_error_2():
    arm = ArmFile('tests/test_template.json')
    key = 'parameters.storageAccountType.defaultValue.bad'
    with pytest.raises(KeyError, match=ArmFile.Errors.MISSING_KEY.format(key)):
        arm.get_value('parameters.storageAccountType.defaultValue.bad')

def test_get_value_no_key():
    arm = ArmFile('tests/test_template.json')
    assert arm.get_value('') == arm._ArmFile__data

def test_get_value_list_index():
    arm = ArmFile('tests/test_template.json')
    key = 'resources'
    assert arm.get_value(key, 0) == arm._ArmFile__data[key][0]

def test_get_value_orig():
    arm = ArmFile('tests/test_template.json')
    arm.set_value('parameters.storageAccountType.defaultValue', 'changed')
    assert 'changed' == arm.get_value('parameters.storageAccountType.defaultValue')
    assert 'Standard_LRS' == arm.get_original_value('parameters.storageAccountType.defaultValue')

def test_set_value():
    arm = ArmFile('tests/test_template.json')
    arm.set_value('parameters.storageAccountType.defaultValue', 'changed')
    assert 'changed' == arm.get_value('parameters.storageAccountType.defaultValue')

def test_set_value_bad_key():
    arm = ArmFile('tests/test_template.json')
    key = 'parameters.storageAccountType.defaultValue.bad'
    with pytest.raises(KeyError, match=ArmFile.Errors.MISSING_KEY.format(key)):
        arm.set_value(key, 'changed')

def test_set_value_list():
    arm = ArmFile('tests/test_template.json')
    key = 'resources'
    my_list = ['a', 'b', 'c']
    arm.set_value(key, my_list)
    changes = arm.get_change_log()
    old = 'old_value'
    new = 'new_value'
    assert changes[key][old] != changes[key][new]
    assert changes[key][new] == my_list
    assert arm.get_value(key) == my_list

def test_set_value_list_index():
    arm = ArmFile('tests/test_template.json')
    key = 'resources'
    my_dict = {"a": 1, "b": "string", "c": True}
    arm.set_value(key, my_dict, 0)
    changes = arm.get_change_log()
    old = 'old_value'
    new = 'new_value'
    assert changes[key][old] != changes[key][new]
    assert changes[key][new][0] == my_dict
    assert arm.get_value(key, 0) == my_dict

def test_set_value_log_change():
    arm = ArmFile('tests/test_template.json')
    key = 'parameters.storageAccountType.defaultValue'
    arm.set_value(key, 'changed')
    assert key in arm._ArmFile__changed_values

def test_change_log():
    arm = ArmFile('tests/test_template.json')
    key = 'parameters.storageAccountType.defaultValue'
    key2 = 'parameters.location.type'
    old = 'old_value'
    new = 'new_value'
    arm.set_value(key, 'changed')
    arm.set_value(key2, 'updated')
    changes = arm.get_change_log()
    assert changes[key][old] == 'Standard_LRS'
    assert changes[key][new] == 'changed'
    assert changes[key2][old] == 'string'
    assert changes[key2][new] == 'updated'

def test_write_file():
    tmp_dir = tempfile.gettempdir()
    tmp_file = '{}/test_out.json'.format(tmp_dir)
    arm = ArmFile('tests/test_template.json', tmp_file)
    arm.write_file()
    with open(tmp_file, 'r') as f:
        j = json.load(f)
    assert j == arm._ArmFile__data

def test_key_exists_null():
    arm = ArmFile('tests/test_template.json')
    assert arm.key_exists('') == False

def test_key_exists_valid_1():
    arm = ArmFile('tests/test_template.json')
    assert arm.key_exists('parameters') == True

def test_key_exists_valid_2():
    arm = ArmFile('tests/test_template.json')
    assert arm.key_exists('parameters.storageAccountType') == True

def test_key_exists_valid_3():
    arm = ArmFile('tests/test_template.json')
    assert arm.key_exists('parameters.storageAccountType.defaultValue') == True

def test_key_exists_invalid_1():
    arm = ArmFile('tests/test_template.json')
    assert arm.key_exists('bad') == False

def test_key_exists_invalid_2():
    arm = ArmFile('tests/test_template.json')
    assert arm.key_exists('parameters.bad') == False


# Importing required modules
from config import *
from email_service import send_email
from scraper_service import get_latest_interruption_pdf, download_pdf
from ocr_service import ocr_pdf, extract_date_time
import os
from datetime import datetime

def read_file(file_path):
    with open(f"templates/{file_path}", "r") as f:
        return f.read()

def main():
    file_path = None
    scheduled_html = read_file('scheduled_interruption.html')
    no_scheduled_html = read_file('no_scheduled_interruption.html')
    styles = read_file('email_styles.css')
    try:
        pdf_link = get_latest_interruption_pdf(URL_TO_SCRAPE)
        if pdf_link:
            logging.info(f"Downloading PDF: {pdf_link}")
            file_path = download_pdf(pdf_link)
            text = ocr_pdf(file_path)
            if len(text) < 50:
                logging.error("OCR output is suspiciously short. Please verify the content of the PDF.")

        today = datetime.now()

        for i in range(len(recipients)):
            date, time = extract_date_time(text, locations[i])
            if date and time:
                try:
                    scheduled_date = datetime.strptime(date, "%A %d.%m.%Y")
                except ValueError as e:
                    logging.error(f"Failed to parse the date: {e}")
                    continue

                if scheduled_date.date() < today.date():
                    logging.info("The scheduled interruption date has already passed. No email will be sent.")
                    continue

                subject = f"Scheduled Power Interruption for '{locations[i]}'"
                msg = scheduled_html.format(location=locations[i].upper(), date=date, time=time, styles=styles)

            else:
                subject = f"No Scheduled Maintenance for '{locations[i]}'"
                msg = no_scheduled_html.format(location=locations[i].upper(), styles=styles)

            send_email(subject, msg, [recipients[i]], email, password, file_path)

    finally:
        if file_path:
            os.remove(file_path)

# Main entry point of the script
if __name__ == "__main__":
    main()


"""
How many letter of the Alphabet?

So I wanted to write a function to find out how many letters
of the alphabet would appear in a given text.

I've learned that Python has made some optimizations to string
manipulations to the point where creating functions to match
C-style programming is not needed.
"""

import time

def check_alphabets_v1(words):
  """
  This was my first attempt. Comming from a C-programming
  background, my approach was to create as much from
  scratch as possible.
  """
  alphabets = [0] * 26
  a = ord('a')
  z = ord('z')
  for x in words.lower():
    char = ord(x)
    if a <= char and char <= z:
      alphabets[char - a] = 1
  results = 0
  for x in alphabets:
    results += x
  return results

def check_alphabets_v2(words):
  """
  This was my optimization of the first approach.
  """
  alphabets = [True] * 26
  a = ord('a')
  z = ord('z')
  results = 0
  for x in words.lower():
    char = ord(x)
    if a <= char and char <= z:
      pos = char - a
      if alphabets[pos]:
        alphabets[pos] = False
        results += 1
        if results == 26:
          return results
  return results

def check_alphabets_v3(words):
  """
  This is an approach that utilizes python for
  what it is. The stats on this seems to look
  pretty good.
  """
  alphabet = "abcdefghijklmnopqrstuvwxyz"
  results = 0
  for letter in alphabet:
    if letter not in words:
      break
    results += 1
  return results

def debug(text, func):
  start_time = time.time()
  characters = func(text)
  end_time = time.time()
  print("========= Start Test ===============")
  print("text: {0}".format(text))
  print("function: {0}".format(func))
  print("contains {0} letters of the alphabet".format(characters))
  print("time: {0}".format(end_time - start_time))
  print("========= End   Test ===============\n\n")

sample = "Hello World"

debug(sample, check_alphabets_v1)
debug(sample, check_alphabets_v2)

sample = "the quick brown fox jumps over the lazy dog" * 100
debug(sample, check_alphabets_v1)
debug(sample, check_alphabets_v2)
debug(sample, check_alphabets_v3)


import random

class Battlefield:

    def __init__(self, first_player_name, second_player_name):
        self.fields = [['0', '0', '0'], ['0', '0', '0'], ['0', '0', '0']]
        self.names = [first_player_name, second_player_name]
        self.number_of_steps = 0
        self.actual_step = random.randint(0, 1)
        print(str(self.names[self.actual_step]) + ' goes first, his symbol is x')

    def whose_step(self):
        return str(self.names[self.actual_step])

    def is_game_ended(self):
        is_ended = False
        for line in self.fields:
            if line == ['o', 'o', 'o'] or line == ['x', 'x', 'x']:
                is_ended = True
        for i in range(3):
            if not ['x', 'x', 'x'] == [self.fields[i][0], self.fields[i][1], self.fields[i][2]] or [self.fields[0][i], self.fields[1][i], self.fields[2][i]] == ['o', 'o', 'o']:
                is_ended = True
        if self.fields[0][0] == self.fields[1][1] and self.fields[2][2] == self.fields[1][1]:
            is_ended = True
        return is_ended

    def who_win(self):
        if not self.is_game_ended():
            return 0
        return self.whose_step()

    def make_a_move(self, x, y):
        """
        (x, y) - the location of the cell, if the field is numbered as a two dimensional array
        :param x:
        :param y:
        :return printing 'accepted' or 'impossible step':
        """
        if self.is_game_ended():
            print('Game ended')
            return
        if max(y, x) > 2 or min(x, y) < 0 or self.fields[x][y] != '0':
            print('It\'s an impossible step')
        else:
            if self.number_of_steps % 2 == 0:
                self.fields[x][y] = 'x'
            else:
                self.fields[x][y] = 'o'
            print('Accepted, ' + str(self.names[self.actual_step]))
            self.number_of_steps += 1
            self.actual_step = 1 - self.actual_step

    def print_battlefield(self):
        for line in self.fields:
            line_appearance = ''
            for field in line:
                line_appearance += field
            print(line_appearance)

b = Battlefield('1', '2')
print (b.whose_step())
b.make_a_move(0, 0)
b.make_a_move(1, 1)
b.make_a_move(0, 2)
b.print_battlefield()


#!/usr/bin/env python
#-*- coding:utf-8 -*-

from Text import *

class PaperSize:
    def __init__(self, s, n, wi, hi):
        self.qtsize = s
        self.name = n
        self.w = wi
        self.h = hi
def MM(x):
    return x/INCH

paperSizes = [
      PaperSize(QPrinter.A4,      "A4",        MM(210),  MM(297)),
      PaperSize(QPrinter.B5,      "B5",        MM(176),  MM(250)),
      PaperSize(QPrinter.Letter,  "Letter",    8.5,      11),
      PaperSize(QPrinter.Legal,   "Legal",     8.5,      14),
      PaperSize(QPrinter.Executive,"Executive",7.5,      10),
      PaperSize(QPrinter.A0,      "A0",        MM(841),  MM(1189)),
      PaperSize(QPrinter.A1,      "A1",        MM(594),  MM(841)),
      PaperSize(QPrinter.A2,      "A2",        MM(420),  MM(594)),
      PaperSize(QPrinter.A3,      "A3",        MM(297),  MM(420)),
      PaperSize(QPrinter.A5,      "A5",        MM(148),  MM(210)),
      PaperSize(QPrinter.A6,      "A6",        MM(105),  MM(148)),
      PaperSize(QPrinter.A7,      "A7",        MM(74),   MM(105)),
      PaperSize(QPrinter.A8,      "A8",        MM(52),   MM(74)),
      PaperSize(QPrinter.A9,      "A9",        MM(37),   MM(52)),
      PaperSize(QPrinter.B0,      "B0",        MM(1000), MM(1414)),
      PaperSize(QPrinter.B1,      "B1",        MM(707),  MM(1000)),
      PaperSize(QPrinter.B10,     "B10",       MM(31),   MM(44)),
      PaperSize(QPrinter.B2,      "B2",        MM(500),  MM(707)),
      PaperSize(QPrinter.B3,      "B3",        MM(353),  MM(500)),
      PaperSize(QPrinter.B4,      "B4",        MM(250),  MM(353)),
      PaperSize(QPrinter.B5,      "B5",        MM(125),  MM(176)),
      PaperSize(QPrinter.B6,      "B6",        MM(88),   MM(125)),
      PaperSize(QPrinter.B7,      "B7",        MM(62),   MM(88)),
      PaperSize(QPrinter.B8,      "B8",        MM(44),   MM(62)),
      PaperSize(QPrinter.B9,      "B9",        MM(163),  MM(229)),
      PaperSize(QPrinter.Comm10E, "Comm10E",   MM(105),  MM(241)),
      PaperSize(QPrinter.DLE,     "DLE",       MM(110),  MM(220)),
      PaperSize(QPrinter.Folio,   "Folio",     MM(210),  MM(330)),
      PaperSize(QPrinter.Ledger,  "Ledger",    MM(432),  MM(279)),
      PaperSize(QPrinter.Tabloid, "Tabloid",   MM(279),  MM(432)),
      PaperSize(QPrinter.Custom,  "Custom",    MM(210),  MM(297)),
      PaperSize(QPrinter.A4, 0, 0, 0  )
    ]

class PageFormat:

    def __init__(self):
        self.size = Preferences().paperSize
        self._width = Preferences().paperWidth
        self._height = Preferences().paperHeight
        self.evenLeftMargin = 10.0 / INCH
        self.evenRightMargin = 10.0 / INCH
        self.evenTopMargin = 10.0 / INCH
        self.evenBottomMargin = 20.0 / INCH
        self.oddLeftMargin = 10.0 / INCH
        self.oddRightMargin = 10.0 / INCH
        self.oddTopMargin = 10.0 / INCH
        self.oddBottomMargin = 20.0 / INCH
        self.landscape = Preferences().landscape
        self.twosided = Preferences().twosided
        self._pageOffset = 0

    def width(self):
        global paperSizes
        if paperSizes[self.size].qtsize == QPrinter.Custom:
            if self.landscape:
                return self._height
            else:
                return self._width
        if self.landscape:
            return paperSizes[self.size].h
        else:
            return paperSizes[self.size].w

    def height(self):
        global paperSizes
        if paperSizes[self.size].qtsize == QPrinter.Custom:
            if self.landscape:
                return self._width
            else:
                return self._height
        if self.landscape:
            return paperSizes[self.size].w
        else:
            return paperSizes[self.size].h

class Page(Element):

    def __init__(self, s):
        Element.__init__(self, s)
        self._systems = list()
        self._no = 0
        self._pageNo = 0
        self._copyright = 0

    def setNo(self, n):
        self._no = n

    def no(self):
        return self._no

    def loWidth(self):
        return self.score().pageFormat().width() * GL.DPI

    def loHeight(self):
        return self.score().pageFormat().height() * GL.DPI

    def score(self):
        return self._score

    def layout(self):
        self.setbbox(QRectF(0.0, 0.0, self.loWidth(), self.loHeight()))
        n = self.no() + 1 + self._score.pageFormat()._pageOffset
        if self.score().styleB( StyleIdx.ST_showPageNumber) and (self.no() > 0 or self.score().styleB( StyleIdx.ST_showPageNumberOne)):
            if n & 1:
               subtype =  TEXT.TEXT_PAGE_NUMBER_ODD
            else:
                subtype = TEXT.TEXT_PAGE_NUMBER_EVEN
            if n & 1:
                style  = TEXT.TEXT_STYLE_PAGE_NUMBER_ODD
            else:
                style  = TEXT.TEXT_STYLE_PAGE_NUMBER_EVEN
            if self._pageNo == 0:
                self._pageNo = Text(self.score())
                self._pageNo.setParent(self)
            if subtype != self._pageNo.subtype():
                self._pageNo.setSubtype(subtype)
                self._pageNo.setTextStyle(style)
            s = QString("%1").arg(n)
            if self._pageNo.getText() != s:
                self._pageNo.setText(s)
                self._pageNo.layout()
        else:
            self._pageNo = 0

        if self._score.rights:
            if self._copyright == 0:
                self._copyright = TextC(self._score.rights)
                self._copyright.setParent(self)
                self._copyright.setTextStyle(TEXT.TEXT_STYLE_COPYRIGHT)
                self._copyright.layout()
        else:
                self._copyright = 0


#$ Vars
#: N - Number of prompts
#: B - Base of the function
#: U - Upper bound on what terms will be asked
def sol(N,B,U):
  arr = [None] * (U+1)

  for _ in range(N):
    target = int(input())
    if arr[target-B] is None:
      ret = sol_help(target,B,U)
      arr[target-B] = ret
    print(arr[target-B])

def sol_help(n,B,U):
  if n < B: return n

  else:
    s = 0
    for i in range(1,B+1):
      s += sol_help(n-i,B,U)
    return s

N,B,U = list(map(int,input().split(" ")))
sol(N,B,U)


#! pip install transformers
#! pip install -U sentence-transformers
#! pip install konlpy

##########################################
### 1. Load Clean Korean Legal Dataset ###
##########################################
import konlpy
from konlpy.tag import Mecab

file_path = "./data/cklc-small.txt"
with open(file_path) as f:
    lines = f.readlines()
    sent_list = [line.rstrip('\n') for line in lines]

print(">> Total Number of Corpus : {}".format(len(sent_list)))

#######################################
## 2. Denoising Auto-Encoder Dataset ##
#######################################
from torch.utils.data import Dataset
from sentence_transformers.readers.InputExample import InputExample
import numpy as np

class DenoisingAutoEncoderDataset(Dataset):
    def __init__(self, sentences, noise_fn = lambda sent :  DenoisingAutoEncoderDataset.delete(sent)):
        self.sentences = sentences
        self.noise_fn = noise_fn
        self.mecab = Mecab()

    def __getitem__(self, item):
        sent = self.sentences[item]
        return InputExample(texts=[self.noise_fn(sent), sent])  # label : Similar[0], texts : {Original text; Noised text}

    def __len__(self):
        return len(self.sentences)

    # Noise function
    @staticmethod
    def delete(text, del_ratio = 0.55): # ratio 60 % is best performance
        mecab = Mecab()
        words_tok = mecab.morphs(text)
        n = len(words_tok)
        if n == 0:
            return text

        keep_or_not = np.random.rand(n) > del_ratio  # [False, Fasle, True, True, False, True]
        if sum(keep_or_not) == 0:                    # number of [True]
            keep_or_not[np.random.choice(n)] = True
        words_processed = " ".join(np.array(words_tok)[keep_or_not])
        return words_processed

#############################################
## 3. Denoising Auto-Encoder Loss Function ##
#############################################
import torch
from torch import nn, Tensor
from sentence_transformers import SentenceTransformer, models
from transformers import AutoConfig, AutoTokenizer, AutoModelForCausalLM, PreTrainedModel

class DenoisingAutoEncoderLoss(nn.Module):
    def __init__(self, model, decoder_name = None, tie_encoder_decoder = True):
        super(DenoisingAutoEncoderLoss, self).__init__()
        self.encoder = model
        self.tokenizer_encoder = model.tokenizer # model tokenizer

        encoder_name = model[0].auto_model.config._name_or_path       # model[0] : Transformer, model[0].auto_model : 'bert-base-uncased' / model[1] : Pooling

        if decoder_name == None:
            assert tie_encoder_decoder, "Must indicate the decoder_name argument when tie_encoder_decoder = False"
        if tie_encoder_decoder:
            decoder_name = encoder_name

        self.tokenizer_decoder = AutoTokenizer.from_pretrained(decoder_name)
        self.need_retokenization = not (type(self.tokenizer_encoder) == type(self.tokenizer_decoder))

        # Bert Config with Decoder & Cross attentions
        decoder_config = AutoConfig.from_pretrained(decoder_name)
        decoder_config.is_decoder = True
        decoder_config.add_cross_attention = True
        kwargs_decoder = {'config': decoder_config}
        # Make Decoder : LM Cross Attentions
        self.decoder = AutoModelForCausalLM.from_pretrained(decoder_name, **kwargs_decoder)

        # if you use GPT-2
        if self.tokenizer_decoder.pad_token is None:
            self.tokenizer_decoder.pad_token = self.tokenizer_decoder.eos_token
            self.decoder.config.pad_token_id = self.decoder.config.eos_token_id

        if tie_encoder_decoder:
            if len(self.tokenizer_encoder) != len(self.tokenizer_decoder):
                self.tokenizer_decoder = self.tokenizer_encoder
                self.decoder.resize_token_embeddings(len(self.tokenizer_decoder))  # Vocab_size(30522)
            decoder_base_model_prefix = self.decoder.base_model_prefix
            PreTrainedModel._tie_encoder_decoder_weights(
                model[0].auto_model,
                self.decoder._modules[decoder_base_model_prefix],
                self.decoder.base_model_prefix
            )

    def retokenize(self, sentence_features):
        input_ids = sentence_features['input_ids']
        device = input_ids.device
        sentences_decoded = self.tokenizer_encoder.batch_decode(
            input_ids,
            skip_special_tokens=True,
            clean_up_tokenization_spaces=True
        )
        retokenized = self.tokenizer_decoder(
            sentences_decoded,
            padding=True,
            truncation='longest_first',
            return_tensors="pt",
            max_length=None).to(device)
        return retokenized

    def forward(self, sentence_features, labels):
        source_features, target_features = tuple(sentence_features)  # (noised text, orginal text)
        if self.need_retokenization:
            target_features = self.retokenize(target_features)

        # 1. Sentence Embedding from Encoder
        reps = self.encoder(source_features)['sentence_embedding']  # [batch_size, hidden_dim]

        target_length = target_features['input_ids'].shape[1]
        decoder_input_ids = target_features['input_ids'].clone()[:, :target_length - 1]     # Decoder Input : input - [102] token
        label_ids = target_features['input_ids'][:, 1:]                                     # Label : input - [102] token

        # 2. Sentence Embedding from Decoder : output is CausalLMOutput with Cross Attentions
        decoder_outputs = self.decoder(
            input_ids = decoder_input_ids,
            inputs_embeds = None,
            attention_mask = None,
            encoder_hidden_states = reps[:, None],  # (batch_size, hidden_dim) -> (batch_size, 1, hidden_dim)
            encoder_attention_mask = source_features['attention_mask'][:, 0:1],
            labels = None,
            return_dict = None,
            use_cache = False)  # decoder_outputs : [loss = None, logits]

        # 3. Calculate Loss
        lm_logits = decoder_outputs[0]  # logits : [batch_size, seq_length, vocab_size]
        ce_loss_fct = nn.CrossEntropyLoss(ignore_index = self.tokenizer_decoder.pad_token_id)
        loss = ce_loss_fct(lm_logits.view(-1, lm_logits.shape[-1]), label_ids.reshape(-1)) # CE_Loss([seq_length, vocab_size], [seq_length])
        return loss

################################################
## 4. TSDAE unsupervised-embeddings training  ##
################################################
from torch.utils.data import DataLoader
from sentence_transformers import SentenceTransformer, models

# dataset with noise function
train_data = DenoisingAutoEncoderDataset(sent_list)

# dataloader
loader = DataLoader(
    train_data,
    batch_size = 8,
    shuffle = True,
    drop_last = True)

# Transformers models
model_name = 'klue/bert-base'
bert = models.Transformer(model_name)

# Sentence Embedding using [CLS] token or Mean/Max Pooling
pooling = models.Pooling(bert.get_word_embedding_dimension(), 'mean') # cls, mean, max
model = SentenceTransformer(modules = [bert, pooling])

# Use Loss function
loss = DenoisingAutoEncoderLoss(model, tie_encoder_decoder = True)

# Train
epochs = 3
warmup_steps = int(len(loader) * epochs * 0.10) # Warmup 10 %

model.fit(
    train_objectives=[(loader, loss)],
    epochs = epochs,
    warmup_steps = warmup_steps,
    checkpoint_path = './output/tsdae-bert',
    checkpoint_save_steps= 3000,
    weight_decay = 0,
    scheduler = 'constantlr',
    optimizer_params = {'lr': 3e-5},
    show_progress_bar = True
)
# Save final model
model.save('output/tsdae-krlawbert')


import io
import sys

_INPUT = """\
4 4
1 2
2 3
3 4
4 1
"""
sys.stdin = io.StringIO(_INPUT)

N, M = map(int, input().split())

connect = [[] for _ in range(N+1)]

for i in range(M):
    A, B = map(int, input().split())
    connect[A].append(B)

from collections import deque

def BFS(start):
    visited = [False] * (N+1)
    visited[start] = True
    cnt = 1
    que = deque()
    que.append(start)
    while 0 < len(que):
        now_city = que.popleft()
        for to_city in connect[now_city]:
            if visited[to_city] == False:
                visited[to_city] = True
                cnt += 1
                que.append(to_city)

    return cnt

ans = 0

for i in range(1, N+1):
    ans += BFS(i)

print(ans)


import pytest

INSTANCE_ID = 1
PROJECT_ID = 1
MODEL_ID = 1
FLOW_ID = 1
BK_USERNAME = 'unittest'
VERSION_ID = '96wsipgxk5hc4nzd7jqey1mbv8fau0lt'
MAIN_TABLE_ID = '591_main_table'
BK_BIZ_ID = 591
INDICATOR_RESULT_TABLE_ID = '591_indicator_table'
CALCULATION_ATOM_NAME = 'max_price'

def _init_complete_model_instance():
    """构建一个完整的模型实例作为初始化的测试数据"""
    from datamanage.pro.datamodel.models import application as ins_models

    ins_models.DmmModelInstance.objects.create(
        instance_id=INSTANCE_ID,
        project_id=PROJECT_ID,
        model_id=MODEL_ID,
        flow_id=FLOW_ID,
        version_id=VERSION_ID,
        created_by=BK_USERNAME,
    )
    ins_models.DmmModelInstanceSource.objects.create(
        instance_id=INSTANCE_ID,
        input_type='main_table',
        input_result_table_id='591_source_table1',
        created_by=BK_USERNAME,
    )
    ins_models.DmmModelInstanceSource.objects.create(
        instance_id=INSTANCE_ID,
        input_type='dimension_table',
        input_result_table_id='591_source_dim_table1',
        created_by=BK_USERNAME,
    )
    ins_models.DmmModelInstanceTable.objects.create(
        result_table_id='591_main_table',
        bk_biz_id=BK_BIZ_ID,
        instance_id=INSTANCE_ID,
        model_id=MODEL_ID,
        flow_node_id=1,
        created_by=BK_USERNAME,
    )
    ins_models.DmmModelInstanceField.objects.create(
        instance_id=INSTANCE_ID,
        model_id=MODEL_ID,
        field_name='price',
        input_result_table_id='591_source_table1',
        input_field_name='source_field1',
        application_clean_content={
            'clean_option': 'SQL',
            'clean_content': 'source_field1 * 100 as price',
        },
        created_by=BK_USERNAME,
    )
    ins_models.DmmModelInstanceField.objects.create(
        instance_id=INSTANCE_ID,
        model_id=MODEL_ID,
        field_name='channel_id',
        input_result_table_id='591_source_table1',
        input_field_name='channel_id',
        application_clean_content={
            'clean_option': 'SQL',
            'clean_content': '',
        },
        created_by=BK_USERNAME,
    )
    ins_models.DmmModelInstanceRelation.objects.create(
        instance_id=INSTANCE_ID,
        model_id=MODEL_ID,
        related_model_id=2,
        field_name='channel_id',
        input_result_table_id='591_source_dim_table1',
        input_field_name='channel_id',
        created_by=BK_USERNAME,
    )
    ins_models.DmmModelInstanceField.objects.create(
        instance_id=INSTANCE_ID,
        model_id=MODEL_ID,
        field_name='channel_name',
        input_result_table_id='591_source_dim_table1',
        input_field_name='channel_name',
        application_clean_content={
            'clean_option': 'SQL',
            'clean_content': '',
        },
        created_by=BK_USERNAME,
    )
    ins_models.DmmModelInstanceIndicator.objects.create(
        result_table_id=INDICATOR_RESULT_TABLE_ID,
        project_id=PROJECT_ID,
        bk_biz_id=BK_BIZ_ID,
        instance_id=INSTANCE_ID,
        model_id=MODEL_ID,
        parent_result_table_id='591_main_table',
        flow_node_id=1000,
        calculation_atom_name=CALCULATION_ATOM_NAME,
        aggregation_fields=[],
        filter_formula='',
        scheduling_type='stream',
        scheduling_content={
            'window_type': 'slide',  # 窗口类型
            'window_time': 0,  # 窗口长度
            'waiting_time': 0,  # 延迟时间
            'count_freq': 30,  # 统计频率
            'window_lateness': {
                'allowed_lateness': True,  # 是否计算延迟数据
                'lateness_time': 1,  # 延迟时间
                'lateness_count_freq': 60,  # 延迟统计频率
            },
            'session_gap': None,  # 会话窗口时间
            'expired_time': 60,  # 会话过期时间
        },
    )

@pytest.fixture(scope='class')
def init_complete_model_instance():
    """构建一个完整的模型实例作为初始化的测试数据"""
    _init_complete_model_instance()