import pytest
from fastapi_jwt_auth import AuthJWT
from fastapi_jwt_auth.exceptions import AuthJWTException
from fastapi import FastAPI, Depends, Request
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
from typing import Optional
from pydantic import BaseSettings

@pytest.fixture(scope='function')
def client():
    app = FastAPI()

    @app.exception_handler(AuthJWTException)
    def authjwt_exception_handler(request: Request, exc: AuthJWTException):
        return JSONResponse(
            status_code=exc.status_code,
            content={"detail": exc.message}
        )

    @app.get('/protected')
    def protected(Authorize: AuthJWT = Depends()):
        Authorize.jwt_required()
        return {'hello':'world'}

    @app.get('/get_headers_access')
    def get_headers_access(Authorize: AuthJWT = Depends()):
        Authorize.jwt_required()
        return Authorize.get_unverified_jwt_headers()

    @app.get('/get_headers_refresh')
    def get_headers_refresh(Authorize: AuthJWT = Depends()):
        Authorize.jwt_refresh_token_required()
        return Authorize.get_unverified_jwt_headers()

    client = TestClient(app)
    return client

def test_header_without_jwt(client):
    response = client.get('/protected', headers={'Authorization':'Bearer'})
    assert response.status_code == 422
    assert response.json() == {'detail': "Bad Authorization header. Expected value 'Bearer <JWT>'"}

    response = client.get('/protected', headers={'Authorization':'Bearer '})
    assert response.status_code == 422
    assert response.json() == {'detail': "Bad Authorization header. Expected value 'Bearer <JWT>'"}

def test_header_without_bearer(client):
    response = client.get('/protected', headers={'Authorization':'Test asd'})
    assert response.status_code == 422
    assert response.json() == {'detail': "Bad Authorization header. Expected value 'Bearer <JWT>'"}

    response = client.get('/protected', headers={'Authorization':'Test '})
    assert response.status_code == 422
    assert response.json() == {'detail': "Bad Authorization header. Expected value 'Bearer <JWT>'"}

def test_header_invalid_jwt(client):
    response = client.get('/protected', headers={'Authorization':'Bearer asd'})
    assert response.status_code == 422
    assert response.json() == {'detail': 'Not enough segments'}

def test_valid_header(client,Authorize):
    token = Authorize.create_access_token(subject='test')
    response = client.get('/protected',headers={'Authorization':f"Bearer {token}"})
    assert response.status_code == 200
    assert response.json() == {'hello':'world'}

def test_jwt_custom_headers(Authorize):
    access_token = Authorize.create_access_token(subject=1,headers={'access':'bar'})
    refresh_token = Authorize.create_refresh_token(subject=2,headers={'refresh':'foo'})

    assert Authorize.get_unverified_jwt_headers(access_token)['access'] == 'bar'
    assert Authorize.get_unverified_jwt_headers(refresh_token)['refresh'] == 'foo'

def test_get_jwt_headers_from_request(client, Authorize):
    access_token = Authorize.create_access_token(subject=1,headers={'access':'bar'})
    refresh_token = Authorize.create_refresh_token(subject=2,headers={'refresh':'foo'})

    response = client.get('/get_headers_access',headers={"Authorization":f"Bearer {access_token}"})
    assert response.json()['access'] == 'bar'

    response = client.get('/get_headers_refresh',headers={"Authorization":f"Bearer {refresh_token}"})
    assert response.json()['refresh'] == 'foo'

def test_custom_header_name(client,Authorize):
    class HeaderName(BaseSettings):
        authjwt_secret_key: str = "secret"
        authjwt_header_name: str = "Foo"

    @AuthJWT.load_config
    def get_header_name():
        return HeaderName()

    token = Authorize.create_access_token(subject=1)
    # Insure 'default' headers no longer work
    response = client.get('/protected',headers={"Authorization":f"Bearer {token}"})
    assert response.status_code == 401
    assert response.json() == {'detail': 'Missing Foo Header'}

    # Insure new headers do work
    response = client.get('/protected',headers={"Foo":f"Bearer {token}"})
    assert response.status_code == 200
    assert response.json() == {'hello':'world'}

    # Invalid headers
    response = client.get('/protected',headers={"Foo":"Bearer test test"})
    assert response.status_code == 422
    assert response.json() == {'detail': "Bad Foo header. Expected value 'Bearer <JWT>'"}

    AuthJWT._header_name = "Authorization"

def test_custom_header_type(client,Authorize):
    class HeaderType(BaseSettings):
        authjwt_secret_key: str = "secret"
        authjwt_header_type: str = "JWT"

    @AuthJWT.load_config
    def get_header_type():
        return HeaderType()

    token = Authorize.create_access_token(subject=1)
    # Insure 'default' headers no longer work
    response = client.get('/protected',headers={"Authorization":f"Bearer {token}"})
    assert response.status_code == 422
    assert response.json() == {'detail': "Bad Authorization header. Expected value 'JWT <JWT>'"}
    # Insure new headers do work
    response = client.get('/protected',headers={"Authorization":f"JWT {token}"})
    assert response.status_code == 200
    assert response.json() == {'hello':'world'}

    class HeaderTypeNone(BaseSettings):
        authjwt_secret_key: str = "secret"
        authjwt_header_type: Optional[str] = None

    @AuthJWT.load_config
    def get_header_type_none():
        return HeaderTypeNone()

    # Insure 'JWT' headers no longer work
    response = client.get('/protected',headers={"Authorization":f"JWT {token}"})
    assert response.status_code == 422
    assert response.json() == {'detail': "Bad Authorization header. Expected value '<JWT>'"}
    # Insure new headers without a type also work
    response = client.get('/protected',headers={"Authorization":f"{token}"})
    assert response.status_code == 200
    assert response.json() == {'hello':'world'}

    AuthJWT._header_type = "Bearer"


# import packages for basic math, plotting, linear algebra, etc.
from numpy import *
from numpy.linalg import *
from numpy.random import *
from matplotlib.pyplot import *
from scipy.special import binom, erf, erfc

def eigSinvH(S,H):
    SinvH = inv(S) @ H
    E, U = eig(SinvH)

    order = argsort(real(E))
    c = zeros((K, K))
    for i in range(K):
        c[:, i] = real(U[:, order[i]])
        c[:, i] = c[:, i] / sqrt(c[:, i] @ S @ c[:, i])
    E = sort(real(E))
    return E, c

# Special function that helps to compute 2-e integrals.
def F0(x):
    if (x < 1e-8):
        return 1 - x/3
    else:
        return 0.5*sqrt(pi)*erf(sqrt(x))/sqrt(x)

# computes the overlap integral according to appendix of Szabo
def compute_overlap(alpha, beta, RA, RB):
    absum = alpha + beta
    abfac = alpha * beta / absum
    dRAB = RA - RB
    dRAB2 = dRAB @ dRAB
    return (pi/absum)**(3/2) * exp(-abfac * dRAB2)

# computes the kinetic integral according to appendix of Szabo
def compute_kinetic_energy(alpha, beta, RA, RB):
    absum = alpha + beta
    abfac = alpha * beta / absum
    dRAB = RA - RB
    dRAB2 = dRAB @ dRAB
    return (pi/absum)**(3/2) * exp(-abfac * dRAB2) * \
            abfac * (3 - 2*abfac * dRAB2)

# computes the attraction integral according to appendix of Szabo
def compute_elec_nuc_energy(alpha, beta, RA, RB, RC):
    absum = alpha + beta
    abfac = alpha * beta / absum
    dRAB = RA - RB
    dRAB2 = dRAB @ dRAB
    RP = (alpha*RA + beta*RB)/absum
    dRPC = RP - RC
    dRPC2 = dRPC @ dRPC
    return -(2*pi/absum)*exp(-abfac * dRAB2) * \
            F0(absum * dRPC2)

# computes the 2e- integral according to appendix of Szabo
def compute_elec_elec_energy(alpha,beta,gamma,delta,RA,RB,RC,RD):
    absum = alpha + beta
    abfac = alpha * beta / absum
    dRAB = RA - RB
    dRAB2 = dRAB @ dRAB
    RP = (alpha*RA + beta*RB)/absum

    gdsum = gamma + delta
    gdfac = gamma * delta / gdsum
    dRCD = RC - RD
    dRCD2 = dRCD @ dRCD
    RQ = (gamma*RC + delta*RD) / gdsum
    dRPQ = RP - RQ
    dRPQ2 = dRPQ @ dRPQ

    abgdsum = absum + gdsum
    abgdfac = absum * gdsum / abgdsum

    return 2 * pi**(5/2) * (absum * gdsum * sqrt(abgdsum))**(-1) * \
            exp(-abfac*dRAB2 - gdfac*dRCD2) * \
            F0(abgdfac * dRPQ2)

N = 2 # number of electrons
N_nuclei = 2
z_nuclei = array([2, 1]) # nuclear charges
K = 2 # number of basis functions
L = 3 # number of Gaussians used to build each basis function

widths = zeros((L,K)) # exponents of the gaussians
contraction_coeffs = zeros((L,K)) # we called these d
centers = zeros((L,K,3)) # position of the gaussians

# specific basis parameters
alpha1s_STO3G = array([0.109818, 0.405771, 2.22766])
d1s_STO3G = array([0.316894, 0.381531, 0.109991])

# 1s on He
zeta_He = 2.0925
widths[:,0] = alpha1s_STO3G * zeta_He**2
contraction_coeffs[:,0] = d1s_STO3G * widths[:,0]**(3/4)

# 1s on H
zeta_H = 1.24
widths[:,1] = alpha1s_STO3G * zeta_H**2
contraction_coeffs[:,1] = d1s_STO3G * widths[:,1]**(3/4)

# initialize nuclear array
R_nuclei = zeros((N_nuclei,3))
# R = 1.4632
# set internuclear distance
R = 100
R_nuclei[1,0] = R

centers[0,1,0] = R
centers[1,1,0] = R
centers[2,1,0] = R

# initialize matrices
# S:overlap, T: kinetic, U1: potential of nuclei1, U2: potential of nuclei2
S = zeros((K,K))
T = zeros((K,K))
U1 = zeros((K,K))
U2 = zeros((K,K,K,K))

# compute matrix elements
for mu in range(K):
    for A in range(L):
        alpha = widths[A,mu]
        dAmu = contraction_coeffs[A,mu]
        RA = centers[A,mu,:]

        for nu in range(K):
            for B in range(L):
                beta = widths[B,nu]
                dBnu = contraction_coeffs[B,nu]
                RB = centers[B,nu,:]

                S[mu,nu] += dAmu * dBnu * compute_overlap(alpha, beta, RA, RB)

                T[mu,nu] += dAmu * dBnu * compute_kinetic_energy(alpha, beta, RA, RB)

                for nucleus in range(N_nuclei):
                    U1[mu,nu] += dAmu * dBnu * z_nuclei[nucleus]  * \
                        compute_elec_nuc_energy(alpha, beta, RA, RB, R_nuclei[nucleus])

                for sigma in range(K):
                    for C in range(L):
                        gamma = widths[C,sigma]
                        dCsigma = contraction_coeffs[C,sigma]
                        RC = centers[C,sigma,:]

                        for lam in range(K):
                            for D in range(L):
                                delta = widths[D,lam]
                                dDlam = contraction_coeffs[D,lam]
                                RD = centers[D,lam,:]

                                U2[mu,nu,sigma,lam] += dAmu * dBnu * dCsigma * dDlam * \
                        compute_elec_elec_energy(alpha, beta, gamma, delta, RA, RB, RC, RD)

# initial guess to the scf procedure
h = T + U1
E, c = eigSinvH(S,h)

Nover2 = int(N/2)

# begin SCF, assumes convergence after 10 iterations
n_iterations = 10
for iterate in range(n_iterations):
    # make density matrix
    P = zeros((K,K))
    for mu in range(K):
        for nu in range(K):
            for j in range(Nover2):
                P[mu,nu] += 2 * c[mu,j] * c[nu,j]
    # make fock matrix
    F = copy(h)
    for mu in range(K):
        for nu in range(K):
            for lam in range(K):
                for sigma in range(K):
                    F[mu,nu] += P[lam,sigma] * \
                                ( U2[mu,nu,lam,sigma] - 0.5*U2[mu,sigma,lam,nu] )
    # make second guess
    E, c = eigSinvH(S,F)

    E_elec = sum(E[0:Nover2]) + 0.5 * trace(P @ h)
    print(E_elec)

# energy of nuclear repulsion
E_nuc = 0
for nucleus1 in range(N_nuclei):
    for nucleus2 in range(nucleus1+1,N_nuclei):
        dR = R_nuclei[nucleus1,:] - R_nuclei[nucleus2,:]
        dR2 = dR @ dR
        E_nuc += z_nuclei[nucleus1] * z_nuclei[nucleus2] / sqrt(dR2)

# total scf energy
E_total = E_elec + E_nuc
print(E_total)

# begin plotting
xvals = arange(-2,4,0.1)
yvals = arange(-3, 3, 0.1)
nvals = size(xvals)
state = 0
psi = zeros((nvals,nvals))
for i in range(nvals):
    x = xvals[i]
    for j in range(nvals):
        y = yvals[j]

        for mu in range(K):
            for A in range(L):
                alpha = widths[A,mu]
                dAmu = contraction_coeffs[A,mu]
                RA = centers[A,mu,:]
                xA = RA[0]
                yA = RA[1]
                phiA = exp(-alpha* ( (x-xA)**2 + (y-yA)**2 ) )

                psi[j,i] += c[mu,state] * dAmu * phiA

clf()
contourf(xvals, yvals, psi, levels=20, cmap=cm.seismic)
axis('equal')
text(0,0,'He',color='White',fontsize=14)
text(R,0,'H',color='White',fontsize=14)


import tweepy
import json

with open('twitter_auth.json') as file:
    secrets = json.load(file)

auth = tweepy.OAuthHandler(secrets['consumer_key'], secrets['consumer_secret'])
auth.set_access_token(secrets['access_token'], secrets['access_token_secret'])

twitter = tweepy.API(auth)

#twitter.update_status(status='My first automated tweet!')

#trythis = twitter.home_timeline()
#print (trythis)
yep=twitter.user_timeline(screen_name='@cnn',count =10)
#print (yep.text)
for yeep in yep:
    tweet=yeep.text
    print (tweet,'\n')


import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')

import requests
from bs4 import BeautifulSoup

source = requests.get('http://www.artistlab.co.kr/reserve/konkuk/a-hall/12-24-12-12/').text
soup = BeautifulSoup(source, "html.parser")

##hotKeys = soup.select("button.new-appt.button")
hotKeys = soup.select("span.timeslot-time")

index = 0
for key in hotKeys:
    index += 1
    print(str(index) + ", " + key.text)
    if index >= 20:
        break

req = requests.get('http://www.artistlab.co.kr/reserve/konkuk/a-hall/12-24-12-12/')
##print(req.text)
status = req.status_code ##제대로 됬으면 응답코드가 200
if status == 200:
    print('dfdf')
    html = req.text
    soup = BeautifulSoup(req.content, 'html.parser')
    print(soup.find("span").get_text())
##    print(soup.select(".span.button-text"))
    ##print(html)
    title1 = soup.select_one('body > div.wrapper > div.wrapper_inner > div > div > div > div > div:nth-child(3) > div > div.web-reservation-calendar-sub-row.wpb_column.vc_column_container.vc_col-sm-8 > div > div > div:nth-child(3) > div > div > div.booked-calendar-wrap.large > table > tbody > tr.entryBlock > td > div > div:nth-child(5) > span.timeslot-people > button > span.button-text')
    ##print (title.get_text())
    title2 = soup.select_one('body > div.wrapper > div.wrapper_inner > div > div > div > div > div:nth-child(3) > div > div.web-reservation-calendar-sub-row.wpb_column.vc_column_container.vc_col-sm-8 > div > div > div:nth-child(3) > div > div > div.booked-calendar-wrap.large > table > tbody > tr.entryBlock > td > div > div:nth-child(6) > span.timeslot-people > button > span.button-text')
    print (title2)
else:
    print('no!!')


import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

formatter = logging.Formatter('%(asctime)s, %(levelname)s, %(funcName)s,(%(lineno)d), %(message)s')

# create a file handler and add the handlers to the logger
handler = logging.FileHandler('hello.log')
handler.setLevel(logging.INFO)
handler.setFormatter(formatter)
logger.addHandler(handler)#

# create a console handler and add the handlers to the logger
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(formatter)
logger.addHandler(consoleHandler)

def main():
    logger.info('Hello baby')

if __name__=='__main__':
    main()


import numpy as np
from utils import transform_datetime_features
from utils import drop_const_cols, filter_columns, std_scaler
from utils import count_encoding

def preprocess(df, model_config, type='train'):
    """preprocessing and feature engineering for input data"""

    print('preprocess data..')

    # extract datetime features
    df = transform_datetime_features(df)
    print('datetime features extracted')

    # categorical count encoding
    if type == 'train':
        df, categorical_values = count_encoding(df)
        model_config['categorical_values'] = categorical_values
    elif type=='test':
        df = count_encoding(df, model_config['categorical_values'])
    print('count encoding of categorical features added')

    # drop constant features
    if type == 'train':
        df = drop_const_cols(df)

    # scaling
    # if mtype == 'train':
    #     df, scaler_mean, scaler_std = std_scaler(df)
    #     model_config['scaler_mean'] = scaler_mean
    #     model_config['scaler_std'] = scaler_std
    # elif type=='test':
    #     df = model_config['scaler'].transform(df)

    # filter columns
    if type == 'train':
        df, used_columns = filter_columns(df, groups=['number', 'count'])
        model_config['used_columns'] = used_columns
    elif type=='test':
        df_pred = df[['line_id']]
        df = df[model_config['used_columns']]

    # missing values
    df.fillna(-1, inplace=True)

    # convert if dataframe is too big
    # if model_config['is_big']:
    #     df = df.astype(np.float16)

    if type == 'train':
        return df, model_config
    else:
        return df, df_pred


from django.http import HttpResponseRedirect
from decimal import Decimal
from django.conf import settings
from datetime import date
from datetime import datetime
from inventory.models import Inventory

class Carrito(object):
	def __init__(self,request):
		self.session = request.session
		cart = self.session.get(settings.CART_SESSION_ID)
		if not cart:
			cart = self.session[settings.CART_SESSION_ID] = {}
		self.cart = cart
		self.request = request

	def save(self):
		self.session[settings.CART_SESSION_ID]=self.cart
		self.session.modified = True

	def add(self,product,quanty):

		total = float(product.price) * float(quanty)
		if str(product.pk) in self.cart:
			if int(quanty) == 0:
				del self.cart[str(product.pk)]
				if int(self.request.session['carrito']) > 0:
					n = int(self.request.session['carrito']) - 1
					self.request.session['carrito'] = n
				self.save()
			else:
				total = float(product.price) * float(quanty)
				self.cart[str(product.pk)]['quanty'] = quanty
				self.cart[str(product.pk)]['total'] = total
				self.save()

		else:
			if int(quanty) >= 1:
				self.request.session['carrito'] +=1
				self.cart[str(product.pk)] = {'pk':product.pk,'code':product.code,'product':product.name,'quanty':quanty,'price':product.price,
															'total':total,'img':'http://localhost:8000'+product.img.url,'shipping_price':product.shipping_price,'user':product.user.pk}
		print(self.cart)
		self.save()

	def remove(self,product):
		product_id = str(product.pk)
		if product_id in self.cart:
			del self.cart[product_id]
			self.save()

	def __iter__(self):
		product_ids = self.cart.keys()
		products = Inventory.objects.filter(id__in=product_ids)
		'''for product in products:
			self.cart[str(product.id)]['product']=product'''

		for item in self.cart.values():
			#item['precio']=Decimal(item['precio'])
			#item['total']=item['precio']*item['cantidad']
			yield item

	def __len__(self):
		return sum(int(item['quanty']) for item in self.cart.values())

	def clear(self):
		del self.session[settings.CART_SESSION_ID]
		self.session.modified = True


import logging
import os
import traceback
from pathlib import Path
from typing import Optional

from vdk.internal.core import errors

log = logging.getLogger(__name__)

def whom_to_blame(
    exception,
    executor_module: Optional[str] = None,
    data_job_path: Optional[Path] = None,
) -> errors.ResolvableBy:
    """
    :param exception: Exception object that has led to Data Job failure.
    :param executor_module: name of module that executes User Code.
                It must be the exact module that starts the user code.
                The next called module must be the user's modules/code.
                Otherwise the logic below will not work.
                 If the name of the module is not known, set to None
    :param data_job_path: path object of the data job directory.
    :return: ResolvableBy.PLATFORM_ERROR if exception was recognized as Platform Team responsibility.
             errors.ResolvableBy.USER_ERROR if exception was recognized as User Error.
    """
    if is_classified(exception):
        return errors.find_whom_to_blame_from_exception(exception)
    if is_user_error(exception, data_job_path):
        return errors.ResolvableBy.USER_ERROR
    if _is_exception_from_vdk_code(
        exception, executor_module
    ):  # We want to avoid blaming users for errors raised from VDK code
        return (
            errors.ResolvableBy.PLATFORM_ERROR
        )  # Because we can't be 100% sure if they are user errors
    else:
        return errors.ResolvableBy.USER_ERROR

def _is_exception_from_vdk_code(exception, executor_module):
    if executor_module is None:
        log.warning(
            "Executor module is not set. "
            "Cannot infer if exception is from user code. "
            "This may lead to misclassification of the error resolvable by type."
        )
        return False
    executor_module = os.path.abspath(executor_module)
    vdk_code_directory = os.path.dirname(executor_module)
    call_list = traceback.format_tb(exception.__traceback__)
    call_list.reverse()  # First element is most recent call
    if len(call_list) == 0:
        return True

    for call in call_list:
        # Extract module path from stacktrace call.
        caller_module = call.split('"')[1]
        if vdk_code_directory in caller_module and caller_module != executor_module:
            return True
        elif (
            caller_module == executor_module
        ):  # User code starts from this module always.
            break

    return False

def is_user_error(
    received_exception: Exception, data_job_path: Optional[Path] = None
) -> bool:
    """
    Returns if exception should be user error
    """
    return (
        _is_file_size_limit_exceeded(received_exception)
        or _is_new_thread_error(received_exception)
        or _is_timeout_error(received_exception)
        or _is_memory_limit_exceeded(received_exception)
        or _is_direct_user_code_error(received_exception, job_path=data_job_path)
    )

def is_classified(exception: BaseException):
    return isinstance(exception, errors.BaseVdkError) or hasattr(
        exception, errors.ATTR_VDK_RESOLVABLE_BY
    )

def _is_memory_limit_exceeded(exception):
    return errors.exception_matches(
        exception,
        classname_with_package="MemoryError",
        exception_message_matcher_regex=".*",
    )

def _is_file_size_limit_exceeded(exception):
    return errors.exception_matches(
        exception,
        classname_with_package="OSError",
        exception_message_matcher_regex=".*File size limit.*",
    )

def _is_new_thread_error(exception):
    return errors.exception_matches(
        exception,
        classname_with_package="RuntimeError",
        exception_message_matcher_regex=".*new thread.*",
    )

def _is_timeout_error(exception):
    return errors.exception_matches(
        exception,
        classname_with_package=".*TimeoutError.*",
        exception_message_matcher_regex=".*Duration of Data Job exceeded.*seconds.*",
    )

def _is_direct_user_code_error(exception: Exception, job_path: Optional[Path]):
    data_job_path = str(job_path) if job_path else None
    if not data_job_path:
        return False

    # Get exception traceback as a list
    call_list = traceback.format_tb(exception.__traceback__)
    if len(call_list) == 0:
        return False

    last_call = call_list[-1]
    last_caller_module = last_call.split('"')[1]

    # Check if the data job path is contained in the last exception call from the exception
    # traceback. If it is, it is safe to assume that the exception was generated directly in
    # user code, and not somewhere else in the vdk code or one of vdk's plugins.
    return True if data_job_path in last_caller_module else False


import requests
import os
from bs4 import BeautifulSoup

euler_url = 'https://projecteuler.net/problem='
last_problem = 506
repo_dir = '/Users/doboyle/code/Project-Euler'

for x in xrange(1, last_problem):
    r = requests.get(euler_url+str(x))
    soup = BeautifulSoup(r.content)
    # print soup.prettify()
    problem_folder = os.path.join(repo_dir, 'Problem' + str(x))

    if not os.access(problem_folder, os.F_OK):
        os.mkdir(problem_folder)

    problem = soup.find_all(class_='problem_content')

    problem_text = problem[0].text.encode('ascii', 'xmlcharrefreplace')

    readme = open(os.path.join(problem_folder, 'readme.md'), 'w')
    readme.write(problem_text)
    readme.close()


# -*- coding: utf-8 -*-
"""
RadBot.py

Telegram bot tracking some Radix metrics
"""
from math import sqrt

import numpy as np
import pandas as pd
import time
import requests
from requests.exceptions import ReadTimeout
from telebot import TeleBot
from myRadixPortfolio import RadixPortfolio
from rewards import RewardTrender
from scipy.optimize import minimize

from constants import *
from uniswap import UniswapInfo
from utils import *

locked_1 = '0x872BCC6C36b512c5FEd0BF947BA574B1cDC0eDD8'
locked_2 = '0x6521b13F91171e0798f0feAE8b48Fb74957E2F2b'
locked_3 = '0xb1c4D9aF8b0aD40A46561f4451354C1Dee8DcE25'
locked_4 = '0x2083810A80924F82B112a0d957C51DeB06888106'
locked_5 = '0x66BF61d5F1a77147ff0011020759726843a006d9'
locked_6 = '0x4B71EB9fC60c438d1E6e7844273f7a369CD188e7'
locked_7 = '0x2b988eE889C3049104C1A5F87AF0f522790CF5F6'
locked_8 = '0xD1d59397abCCFD06d238b12AA95C50A17598b515'
locked_9 = '0xDF191bFbdE2e3E178e3336E63C18DD20d537c421'
locked_10 = '0x6553F21aE00ff0731d9e7da300D2beFE8D38eA4F'
locked_11 = '0xf9f1804D7680F02A8C50af47794eaDB36AdF98db'
locked_12 = '0xE371469D2B557F72aCE9ed15d8A2c856c2C78606'
locked_13 = '0x6468e79A80C0eaB0F9A2B574c8d5bC374Af59414'
locked_14 = '0x10Bc34f232980929Dcf91909E6746034B453b029'
locked_15 = '0x17e00383A843A9922bCA3B280C0ADE9f8BA48449'
locked_16 = '0x684B00a5773679f88598A19976fBeb25a68E9a5f'
locked_17 = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
locked_18 = '0xf9cc02b750c2d22FE57F1b8B9c545A311Bf5bC99'

locked = [locked_1, locked_2, locked_3, locked_4, locked_5, locked_6,
          locked_7, locked_8, locked_9, locked_10, locked_11, locked_12,
          locked_13, locked_14, locked_15, locked_16, locked_17, locked_18]

w3 = initWeb3()

eXRD_Contract = readContract(w3, './eXRD_token.json', eXRD)
vaultContract = readContract(w3, './eXRD_vault.json', eXRD_vault)
rewardsContract = readContract(w3, './eXRD_rewards.json', eXRD_rewards)
USDC_Contract = readContract(w3, './USDC_token.json', USDC)
poolContract = readContract(w3, './UniSwap_pair.json', pool)

with open('./RadBotToken.json') as f:
    RADBOT_TOKEN = json.load(f)['token']

class RadBot:
    lastUnlockTrigger = 1616400000
    nextUnlockTarget = 0.19

    def __init__(self, token=RADBOT_TOKEN):
        self.telegram = TeleBot(token)
        self.portfolio = RadixPortfolio([])
        self.trender = RewardTrender()
        self.uniswap = UniswapInfo()

    def getSMA(self):
        req = requests.get('http://api.coingecko.com/api/v3/coins/e-radix/market_chart?vs_currency=usd&days=7&interval=hourly')
        prices = pd.DataFrame(req.json()['prices'])
        prices = prices[prices[0]>1000*self.lastUnlockTrigger]
        return prices[1].mean()

    def nextUnlock(self):
        t = time.time()
        self.updatePrice()
        SMA = self.getSMA()

        msg = f"Current spot price: {round(self.price,4)} USDC/eXRD\n"

        timeLeft = (self.lastUnlockTrigger + 60*60*24*7) - t
        if timeLeft < 0:
            msg += "Minimum time to next unlock has passed.\n"
        else:
            days, r = divmod(timeLeft, 60*60*24)
            hours, r = divmod(r, 60*60)
            minutes = int(r/60)
            msg += f"Minimum time to next unlock: {int(days)}d, {int(hours)}h, {int(minutes)}m.\n"

        msg += f"Next unlock SMA target: {self.nextUnlockTarget} $\n"
        msg += f"Current CoinGecko SMA: {round(SMA,4)} $"
        return msg

    def updatePrice(self):
        getReserves = poolContract.functions.getReserves()
        (pool_eXRD, pool_USDC, t) = getReserves.call()
        self.pool_eXRD = pool_eXRD
        self.pool_USDC = pool_USDC

        self.price = self.pool_USDC*1e12/self.pool_eXRD
        return self.price

    def calcMarketCap(self):
        self.lockedAmount = 0
        for l in locked:
            balanceOf = eXRD_Contract.functions.balanceOf(l)
            self.lockedAmount += balanceOf.call()

        totalSupply = eXRD_Contract.functions.totalSupply()
        self.supply = totalSupply.call()
        self.unlocked = self.supply - self.lockedAmount

        self.updatePrice()
        self.mcap = self.price*(self.unlocked)/1e18

        SMA7 = self.getSMA()

        msg =  f"Current spot price: {round(self.price,4)} USDC/eXRD\n"
        msg += f"Current 7-day SMA: ${round(SMA7,4)}\n"
        msg += f"Current Market Cap: {round(self.mcap/1e6,2)} MM USDC\n"
        msg += f"Percentage in LP: {round(100*self.pool_eXRD/self.unlocked,2)}%"

        return msg

    def analyseWallets(self, wallets, colors=False):
        try:
            self.portfolio = RadixPortfolio(wallets)
        except Exception as e:
            print('Failed to retrieve wallet information: ' + repr(e))
            return "Failed to retrieve wallet information"

        try:
            uni_balances = self.uniswap.getBalances(wallets)
        except Exception as e:
            print('Failed to load Uniswap Info: ' + repr(e))
            return "Failed to load Uniswap Info"

        LPs = self.portfolio.assets['naked LP'].sum()+self.portfolio.assets['staked LP'].sum()
        poolShare = LPs / self.portfolio.totalLPs
        pooled_USDC = poolShare * self.portfolio.pool_USDC / 1e6
        pooled_eXRD = poolShare * self.portfolio.pool_eXRD / 1e18
        totalRewards = self.portfolio.assets.rewards.sum()

        msg =   "Analysis of requested wallet(s)\n"
        msg += f"Unstaked USDC: {round(self.portfolio.assets.USDC.sum()/1e6,2)}\n"
        msg += f"Unstaked eXRD: {round(self.portfolio.assets.eXRD.sum()/1e18,2)}\n"
        msg += f"Pooled USDC: {round(pooled_USDC,2)}\n"
        msg += f"Pooled eXRD: {round(pooled_eXRD,2)}\n"
        msg += f"Total Rewards: {round(totalRewards,2)}\n"
        msg += "--------------------------------+\n"
        msg += f"Total value: {round(self.portfolio.assets.value.sum(),2)} USDC"

        #t = pd.Timestamp.now()
        #trendDF = self.trender.calcRewardsOverTime(self.portfolio.stakes)
        #d_columns = [c for c in trendDF.columns if 'donated ' in c]
        #donated = trendDF[d_columns].sum(axis=1)
        #donated = donated.groupby(donated.index).last()
        #donated[t] = np.NaN
        #donated = donated.sort_index().interpolate(method='polynomial',order=2)[t]
        #
        #msg += f"\n\nRewards mined through staking: {round(totalRewards-donated,2)}"
        #msg += f"\nRewards donated by early leavers: {round(donated,2)}"

        if len(uni_balances):
            fees_USDC = 0
            fees_eXRD = 0
            ROI_pool = 0
            ROI_HODL = 0
            invested = 0
            for balance in uni_balances:
                price_change_factor = self.portfolio.pool_USDC / self.portfolio.pool_eXRD / balance['USDC'] * balance['eXRD']
                growth_factor = sqrt(price_change_factor)
                stake_share = balance['LP'] / self.portfolio.totalLPs
                current_USDC = stake_share * self.portfolio.pool_USDC / 1e6
                current_eXRD = stake_share * self.portfolio.pool_eXRD / 1e18
                initial_USDC = balance['USDC'] / 1e6
                initial_eXRD = balance['eXRD'] / 1e18
                expected_USDC = initial_USDC * growth_factor
                expected_eXRD = initial_eXRD / growth_factor
                fees_USDC += current_USDC - expected_USDC
                fees_eXRD += current_eXRD - expected_eXRD
                invested += 2 * initial_USDC
                ROI_pool += 2 * expected_USDC - 2 * initial_USDC
                ROI_HODL += initial_eXRD * (current_USDC / current_eXRD - initial_USDC / initial_eXRD)

            rewardsValue = totalRewards * self.portfolio.spot_price
            totalROI = rewardsValue + ROI_pool + 2*fees_USDC
            msg += f"\n\nUnclaimed reward value: {round(rewardsValue,2)} USDC"
            msg += f"\nUniSwap fees value: {round(2 * fees_USDC, 2)} USDC"
            msg += f"\nPool ROI (ex fees): {round(ROI_pool,2)} USDC"
            msg += f"\nTotal LP+LM ROI: {round(totalROI,2)} USDC ({round(100 * totalROI / invested, 1)}%)"

            msg += f"\n\nROI if you had HODL'd: {round(ROI_HODL,2)} USDC ({round(100 * ROI_HODL / invested, 1)}%)"
            msg += f"\nROI if you had YOLO'd: {round(2*ROI_HODL,2)} USDC ({round(200 * ROI_HODL / invested, 1)}%)"

        if len(self.portfolio.stakes): msg += "\n\nStaking details:"
        for i in range(len(self.portfolio.stakes)):
            stake = self.portfolio.stakes.iloc[i]
            age = round((stake.t1 - stake.t0) / (60 * 60 * 24), 2)
            if colors:
                msg += f"\nStake {i} - age {age}d - current APY {round(stake.APY_current, 2)}% - green {round(stake.green, 2)}% - red {round(stake.red, 2)}% - orange {round(stake.orange, 2)}% - blue {round(stake.blue, 2)}%"
            else:
                msg += f"\nStake {i} - age {age}d - rewards {round(stake.rewards, 2)} - bonus {round(6 * stake.bonus, 2)} - current APY {round(stake.APY_current, 2)}% - average APY {round(stake.APY_realized, 2)}%"

        if len(self.portfolio.stakes) > 1:
            overallAPY = sum(self.portfolio.stakes.stake*self.portfolio.stakes.APY_current)/sum(self.portfolio.stakes.stake)
            overallBonus = 6*sum(self.portfolio.stakes.stake*self.portfolio.stakes.bonus)/sum(self.portfolio.stakes.stake)
            msg += f"\n\nWeighted average current APY: {round(overallAPY,2)}%"
            msg += f"\nWeighted average bonus factor: {round(overallBonus,2)}"
            msg += f"\nTotal unclaimed eXRD rewards: {round(self.portfolio.stakes.rewards.sum(),2)}"

        return msg

    def rewardsProjection(self, wallets):
        try:
            self.portfolio = RadixPortfolio(wallets)
        except:
            raise Exception("Failed")

        self.trender.updateEventList()
        return self.trender.plotRewards(self.portfolio.stakes)

    def calcAPY(self):
        msg =  f"Current spot price: {round(self.updatePrice(),4)} USDC/eXRD\n"
        msg += f"Current initial APY: {round(self.portfolio.initial_APY,2)}%\n"
        msg += f"Current nominal APY: {round(self.portfolio.nominal_APY,2)}% ({round(6*self.portfolio.nominal_APY,2)}%)\n"

        return msg

    def helpMessage(self):
        msg = "Welcome to RadBot!\n"
        msg += "\nCurrent commands:"
        msg += "\n  /a <address(es)> --> Analyse wallet(s)"
        msg += "\n  /apy --> Current LM APY"
        msg += "\n  /mcap --> eXRD market cap"
        msg += "\n  /projection <address> --> Rewards trend"
        msg += "\n  /unlock --> next unlock info"
        msg += "\n  /when --> when mainnet"
        msg += "\n  /donate --> when you're feeling generous"

        return msg

    def whenMainNet(self):
        t = time.time()
        timeLeft = (1625111940) - t
        if timeLeft < 0:
            msg = "Radix main net is released. We're currently on Olympia."
            msg += "\n See https://www.radixdlt.com/#roadmap for the roadmap to future versions."
        else:
            msg = "Radix main net (Olympia) to be released on June 30th 2021, 23:59 NY time."
            days, r = divmod(timeLeft, 60*60*24)
            hours, r = divmod(r, 60*60)
            minutes = int(r/60)
            msg += f"\nJust wait a little longer: {int(days)}d, {int(hours)}h, {int(minutes)}m.\n"
        return msg

    def handleCommand(self, message):
        command = message.text.split()[0][1:]
        if command in ['start', 'help']:
            self.telegram.reply_to(message, self.helpMessage())
        elif command in ['apy', 'APY']:
            self.telegram.reply_to(message, self.calcAPY())
        elif command in ['a', 'analyse', 'analyze']:
            self.telegram.reply_to(message, self.analyseWallets(message.text.split()[1:]))
        elif command in ['donate']:
            self.telegram.reply_to(message, "RadBot is and will remain free to use for as long as I'll maintain her. If you find her services valuable, donations to show appreciation are welcome. Thanks for your support! \n\nETH address: 0x451423D5CA2618a3CC6944AD754A60083b3a125f")
        elif command in ['mc', 'mcap']:
            self.telegram.reply_to(message, self.calcMarketCap())
        elif command in ['projection']:
            try:
                with self.rewardsProjection(message.text.split()[1:]) as buffer:
                    self.telegram.reply_to(message, "The below is a graph of what your total rewards look like until now, and how they will develop if nobody (un/re)stakes from now on. Your actual future rewards will be less if more stake is added, and more if stake leaves before reaching 6x multiplier.")
                    self.telegram.send_photo(message.chat.id, buffer)
            except:
                self.telegram.reply_to(message, "Failed to analyze address(es).")
        elif command in ['u', 'unlock']:
            self.telegram.reply_to(message, "To reduce channel spam, unlock is now only available in DM.")
            self.telegram.send_message(message.from_user.id, self.nextUnlock())
        elif command in ['when']:
            self.telegram.reply_to(message, self.whenMainNet())
        else:
            self.telegram.reply_to(message, "Unknown command. Try /help for command list.")

if __name__ == "__main__":
    bot = RadBot()
    print('Bot Started.')

    validCommands = ['start','help','apy','APY','a','analyse','analyze','mc','mcap','projection','u','unlock','when','donate']
    @bot.telegram.message_handler(commands = validCommands)
    def botCommand(message):
        try:
            bot.handleCommand(message)
        except:
            bot.telegram.reply_to(message, "Error during execution.")

    restart = True
    while restart:
        restart = False
        try:
            bot.telegram.polling(none_stop=True)
        except ReadTimeout:
            restart = True


from django.conf import settings
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from core.models import Comment
from transactions.models import (
    Currency, Transaction, Interaction, TopicSnapshot, CommentSnapshot,
    HourPriceSnapshot, CurrencyPriceSnapshot, ContributionCertificate)
from trade.models import (Payment, Reserve)

from users.models import User

from api.v1.core.fields import UserField

class CurrencyListSerializer(serializers.HyperlinkedModelSerializer):

    hour_price = serializers.HyperlinkedRelatedField(
        view_name='hourpricesnapshot-detail',
        queryset=HourPriceSnapshot.objects.all())
    currency_price = serializers.HyperlinkedRelatedField(
        view_name='currencypricesnapshot-detail',
        queryset=CurrencyPriceSnapshot.objects.all())

    class Meta:
        model = Currency
        fields = ('id', 'label', 'in_hours', 'hour_price', 'currency_price', 'enabled')

class InteractionSerializer(serializers.HyperlinkedModelSerializer):

    comment = serializers.HyperlinkedRelatedField(
        view_name='transactions:interaction-detail',
        queryset=Comment.objects.all())
    snapshot = serializers.PrimaryKeyRelatedField(
        queryset=CommentSnapshot.objects.all())

    class Meta:
        model = Interaction
        fields = ('url', 'comment', 'snapshot', 'claimed_hours_to_match')

class TransactionCreateSerializer(serializers.HyperlinkedModelSerializer):

    comment = serializers.HyperlinkedRelatedField(
        view_name='comment-detail', queryset=Comment.objects.all())
    payment_currency = serializers.PrimaryKeyRelatedField(
        queryset=Currency.objects.all())
    payment_sender = serializers.PrimaryKeyRelatedField(
        queryset=User.objects.all())

    class Meta:
        model = Transaction
        fields = ('comment', 'payment_amount', 'payment_currency',
                  'payment_sender')

    def create(self, validated_data):
        comment = validated_data.get('comment')
        amount = validated_data['payment_amount']
        currency = validated_data['payment_currency']
        sender = validated_data['payment_sender']

        quota = Transaction.user_quota_remains_today(sender)
        reserve = Reserve.user_reserve_remains(sender)

        if amount > (quota + reserve):
            raise ValidationError('Hour amount larger than sum of user daily_quota and user_reserve.')

        tx = comment.invest(
            hour_amount=amount,
            payment_currency_label=currency.label.lower(),
            investor=sender,
        )

        if not tx:
            raise ValidationError('Bad data')

        return tx

class TransactionListSerializer(serializers.HyperlinkedModelSerializer):

    comment = serializers.HyperlinkedRelatedField(
        view_name='comment-detail', queryset=Comment.objects.all())
    snapshot = serializers.PrimaryKeyRelatedField(
        queryset=CommentSnapshot.objects.all())
    hour_price = serializers.PrimaryKeyRelatedField(
        queryset=HourPriceSnapshot.objects.all())
    currency_price = serializers.PrimaryKeyRelatedField(
        queryset=CurrencyPriceSnapshot.objects.all())
    payment_currency = serializers.PrimaryKeyRelatedField(
        queryset=Currency.objects.all())
    payment_recipient = UserField(read_only=True)
    payment_sender = UserField(read_only=True)

    class Meta:
        model = Transaction
        fields = ('url', 'comment', 'snapshot', 'hour_price', 'currency_price',
                  'payment_amount', 'payment_currency', 'payment_recipient',
                  'payment_sender', 'hour_unit_cost', 'donated_hours',
                  'matched_hours', 'hour_amount')

class ContributionSerializer(serializers.HyperlinkedModelSerializer):

    transaction = serializers.HyperlinkedRelatedField(
        view_name='transaction-detail',
        queryset=Transaction.objects.all())
    interaction = serializers.HyperlinkedRelatedField(
        view_name='interaction-detail',
        queryset=Interaction.objects.all())
    comment_snapshot = serializers.HyperlinkedRelatedField(
        view_name='commentsnapshot-detail',
        queryset=CommentSnapshot.objects.all())
    received_by = UserField(read_only=True)

    class Meta:
        model = ContributionCertificate
        fields = ('type', 'url', 'interaction', 'transaction', 'received_by',
                  'comment_snapshot', 'broken', 'parent')

class TopicSnapshotSerializer(serializers.HyperlinkedModelSerializer):

    topic = serializers.HyperlinkedRelatedField(
        view_name='topic-detail', queryset=Comment.objects.all())

    class Meta:
        model = TopicSnapshot
        fields = ('id', 'created_date', 'topic', 'data', 'blockchain',
                  'blockchain_tx')

class CommentSnapshotSerializer(serializers.HyperlinkedModelSerializer):

    comment = serializers.HyperlinkedRelatedField(
        view_name='comment-detail', queryset=Comment.objects.all())

    class Meta:
        model = CommentSnapshot
        fields = ('id', 'created_date', 'comment', 'data', 'blockchain',
                  'blockchain_tx')

class HourPriceSnapshotSerializer(serializers.HyperlinkedModelSerializer):
    base = serializers.HyperlinkedIdentityField(view_name="currency-detail")

    class Meta:
        model = HourPriceSnapshot
        fields = ('id', 'url', 'name', 'base', 'endpoint', 'data')

class CurrencyPriceSnapshotSerializer(serializers.HyperlinkedModelSerializer):
    base = serializers.HyperlinkedIdentityField(view_name="currency-detail")

    class Meta:
        model = CurrencyPriceSnapshot
        fields = ('id', 'name', 'base', 'endpoint', 'data')


"""Synpro Version P.04.07.21"""
#----------0.0 To initialise and define values---------

import sys,os, math, statistics

#import pandas as pd

import numpy as np

from Bio import SeqIO

from io import StringIO

import matplotlib.pyplot as plt

from BioMolecule import BioMolecule
#--------------------------------
import progressbar
from progressbar import AnimatedMarker, Bar, BouncingBar, Counter, ETA, \
    AdaptiveETA, FileTransferSpeed, FormatLabel, Percentage, \
    ProgressBar, ReverseBar, RotatingMarker, \
    SimpleProgress, Timer, UnknownLength
"""Progress bar"""
widgets=['Calculating the energy of the proteins', progressbar.Bar('|'), progressbar.Counter(), '(', progressbar.Percentage(), ' complete)',
    ' [', progressbar.Timer(), '] '
    ' (', progressbar.ETA(), ') ']

#dGfAABB= -82.38
 # kcal mol - Amend J. (2000)

# dGfAABB= -344.6779 #KJ mol

#PBB= -20.15
 #kcal mol - Amend J. (2000)

# PBB= -84.3076 #KJ mol

# dGfH2O= -237.3 #kJ mol−1 - McCollom and Amend (2005)

R= 0.008314472 #Gas constant KJ mol K

T= 298.15 # Standard temperature

TK = np.linspace(275,400, num=24)
#------------------ A.-1 To get the information ---------

print('Enter the name of the fasta file containing protein sequences (e.g. Ecoli_proteome.fasta)')

fname = input('File name: ')

print('Choose one of the three model organisms corresponding to the protein sequence')

celltype= input('Is this a bactetria, yeast or mammalian cell? [bacteria/yeast/mammalian]')

temp= input('Do you want to calculate the energy at any specific temperature? [yes/no] ')
if temp == 'yes':
    stemperature= int(input('Type the temperature (in kelvin) you would like to print results for: '))
    TK=np.append(TK,stemperature)
    TK=np.sort(TK)
    selected_temp= np.nonzero(TK == stemperature)[0][0]

#-------------------------A.1 To open a file with biopython-----------

protname = []

proteins = []

fasta = open(fname,'r')

for record in SeqIO.parse(fasta,'fasta'):

   protname.append(record.id)

   proteins.append(str(record.seq))

print('there are', len(proteins), 'sequences in this proteome')

#-------------------B. Define the dictionary--------------------------------

#Glycine is considered a 'Special case'

#The dictionary contains:

#                       0= AA 3 letter code
#                       1= R group dGf (KJ mol) # (KJ mol) - Amend & Helgeson (2000)-(Charged -1,-2 or 1, pH # 0, & Ionic strength of 0)
#                       2= Molecular weight of the AA (Da)
#                       3= Concentration (mol/L)E. Coli - Bennett B. et al 2009 & Park J. et al 2016
#                       4= Concentration (mol/L)E. Mammalian - Park J. et al 2016
#                       5= Concentration (mol/L)E. Yeast - Park J. et al 2016
#                       6 = AA dGf (KJ mol) - McCollom & Amend (2005) - [AA] (Charged -1, 1, or 2, pH 0, & Ionic strength of 0)
#                       7 = R group dGf (KJ mol) - Equilibrator/ Flamholz A. & Noor E. (2012)- [R] Cell #(Neutral charge, pH 7, & ionic strength of 0.1 M)
#                       8 = AA dGf (KJ mol) - Equilibrator/ Flamholz A. & Noor E. (2012) - [AA] Cell #(Neutral charge, pH 7, & ionic strength of 0.1 M)

ReactionGibbs_neu = []
ReactionGibbs_chr = []

stdGibbs_chr = []
stdGibbs_neu = []

dGf_chr_av=[]
dGr_chr_av=[]
dGf_neu_av=[]
dGr_neu_av=[]

for T in progressbar.progressbar(TK, widgets=widgets):

    _AABB = BioMolecule('AABB', 74.05866, 4, T=T)
    _PBB = BioMolecule('PBB', 56.04918, 2, T=T)
    _Water = BioMolecule('H2O(l)', 18, 2, T=T)

    dGfAABB= _AABB.std_formation_gibbs
    biodGfAABB= _AABB.stdbio_formation_gibbs

    PBB = _PBB.std_formation_gibbs
    bioPBB = _PBB.stdbio_formation_gibbs

    dGfH2O = _Water.std_formation_gibbs
    biodGfH2O = _Water.stdbio_formation_gibbs

    _ALA = BioMolecule('Alanine(aq)', 89, 7, T=T)
    _ARG = BioMolecule('Arginine(aq)', 174, 14, T=T)
    _ASN = BioMolecule('Asparagine(aq)', 132, 8, T=T)
    _ASP = BioMolecule('Aspartic-Acid(aq)', 133, 7, T=T)
    _CYS = BioMolecule('Cysteine(aq)', 121, 7, T=T)
    _GLN = BioMolecule('Glutamic-Acid(aq)', 148, 9, T=T)
    _GLU = BioMolecule('Glutamine(aq)', 147, 10, T=T)
    _HIS = BioMolecule('Histidine(aq)', 155, 9, T=T)
    _ILE = BioMolecule('Isoleucine(aq)', 131, 13, T=T)
    _LEU = BioMolecule('Leucine(aq)', 131, 13, T=T)
    _LYS = BioMolecule('Lysine(aq)', 146, 14, T=T)
    _MET = BioMolecule('Methionine(aq)', 149, 11, T=T)
    _PHE = BioMolecule('Phenylalanine(aq)', 165, 11, T=T)
    _PRO = BioMolecule('Proline(aq)', 115, 9, T=T)
    _SER = BioMolecule('Serine(aq)', 105, 7, T=T)
    _THR = BioMolecule('Threonine(aq)', 119, 9, T=T)
    _TRP = BioMolecule('Tryptophan(aq)', 181, 12, T=T)
    _TYR = BioMolecule('Tyrosine(aq)', 181, 11, T=T)
    _VAL = BioMolecule('Valine(aq)', 117, 11, T=T)
    _GLY = BioMolecule('Glycine(aq)', 75, 5, T=T)
    _GLYlink = BioMolecule('GLY', 57, 3, T=T)

    AAdict = {
      'A': ['ALA', _ALA.std_formation_R, 89, 2.6e-03, 6.98e-3, 2.23e-2,
        _ALA.std_formation_gibbs,
        _ALA.stdbio_formation_R,
        _ALA.stdbio_formation_gibbs],
      'R': ['ARG', _ARG.std_formation_R, 174, 5.7e-4, 2.55e-4, 2.18e-2,
        _ARG.std_formation_gibbs,
        _ARG.stdbio_formation_R,
        _ARG.stdbio_formation_gibbs],
      'N': ['ASN', _ASN.std_formation_R, 132, 5.1e-4, 2.15e-4, 5.69e-3,
        _ASN.std_formation_gibbs,
        _ASN.stdbio_formation_R,
        _ASN.stdbio_formation_gibbs],
      'D': ['ASP', _ASP.std_formation_R, 133, 4.2e-3, 1.49e-2, 6.29e-3,
        _ASP.std_formation_gibbs,
        _ASP.stdbio_formation_R,
        _ASP.stdbio_formation_gibbs],
      'C': ['CYS', _CYS.std_formation_R, 121, 3.7e-4, 8.40E-5, 3.7e-4,
        _CYS.std_formation_gibbs,
        _CYS.stdbio_formation_R,
        _CYS.stdbio_formation_gibbs],
      'Q': ['GLN', _GLN.std_formation_R, 146, 3.8e-3, 1.72e-2, 3.55e-2,
        _GLN.std_formation_gibbs,
        _GLN.stdbio_formation_R,
        _GLN.stdbio_formation_gibbs],
      'E': ['GLU', _GLU.std_formation_R, 147, 9.6e-2, 6.38e-2, 3.91e-2,
        _GLU.std_formation_gibbs,
        _GLU.stdbio_formation_R,
        _GLU.stdbio_formation_gibbs],
      'H': ['HIS', _HIS.std_formation_R, 155, 6.8e-5, 4.10e-4, 6.76e-5,
        _HIS.std_formation_gibbs,
        _HIS.stdbio_formation_R,
        _HIS.stdbio_formation_gibbs],
      'I': ['ILE', _ILE.std_formation_R, 131, 1.5e-4, 1.76e-3, 3.53e-4,
        _ILE.std_formation_gibbs,
        _ILE.stdbio_formation_R,
        _ILE.stdbio_formation_gibbs],
      'L': ['LEU', _LEU.std_formation_R, 131, 1.5e-4, 1.76e-3, 3.53e-4,
        _LEU.std_formation_gibbs,
        _LEU.stdbio_formation_R,
        _LEU.stdbio_formation_gibbs],
      'K': ['LYS', _LYS.std_formation_R, 146, 4.1e-4, 5.06e-4, 5.16e-3,
        _LYS.std_formation_gibbs,
        _LYS.stdbio_formation_R,
        _LYS.stdbio_formation_gibbs],
      'M': ['MET', _MET.std_formation_R, 149, 1.5e-4, 6.39e-4, 1.91e-4,
        _MET.std_formation_gibbs,
        _MET.stdbio_formation_R,
        _MET.stdbio_formation_gibbs],
      'F': ['PHE', _PHE.std_formation_R, 165, 1.8e-5, 8.40e-4, 2.73e-4,
        _PHE.std_formation_gibbs,
        _PHE.stdbio_formation_R,
        _PHE.stdbio_formation_gibbs],
      'P': ['PRO', _PRO.std_formation_R, 115, 3.9e-4, 1.23e-3, 1.36e-3,
        _PRO.std_formation_gibbs,
        _PRO.stdbio_formation_R,
        _PRO.stdbio_formation_gibbs],
      'S': ['SER', _SER.std_formation_R, 105, 1.13e-3, 4.86e-3, 3.87e-3,
        _SER.std_formation_gibbs,
        _SER.stdbio_formation_R,
        _SER.stdbio_formation_gibbs],
      'T': ['THR', _THR.std_formation_R, 119, 1.26e-3, 6.69e-3, 6.69e-3,
        _THR.std_formation_gibbs,
        _THR.stdbio_formation_R,
        _THR.stdbio_formation_gibbs],
      'W': ['TRP', _TRP.std_formation_R, 204, 1.2e-5, 1.80e-4, 5.55e-5,
        _TRP.std_formation_gibbs,
        _TRP.stdbio_formation_R,
        _TRP.stdbio_formation_gibbs],
      'Y': ['TYR', _TYR.std_formation_R, 181, 2.9e-5, 9.38e-4, 2.48e-4,
        _TYR.std_formation_gibbs,
        _TYR.stdbio_formation_R,
        _TYR.stdbio_formation_gibbs],
      'V': ['VAL', _VAL.std_formation_R, 117, 4e-3, 1.51e-3, 2.50e-3,
        _VAL.std_formation_gibbs,
        _VAL.stdbio_formation_R,
        _VAL.stdbio_formation_gibbs],
      'G': ['GLY', _GLY.std_formation_R, 75, 5.82e-4, 3.71E-3, 3.71E-3,
        _GLY.std_formation_gibbs,
        _GLY.stdbio_formation_R,
        _GLY.stdbio_formation_gibbs],
      '_G': ['-GLY-', _GLYlink.std_formation_R, 57, 5.82e-4, 3.71E-3, 3.71E-3,
        _GLYlink.std_formation_gibbs,
        _GLYlink.stdbio_formation_R,
        _GLYlink.stdbio_formation_gibbs],}

    #---------------C. To get the count of AA in each sequence-------------------

    #To append the values of each protein, define a counter outside the loop

    sumavgaa=0

    protlen=0

    totmw=0

    #chr
    dGfs_chr=[]
    proteinGs_chr=[]

    #Neu
    dGfs_neu=[]
    proteinGs_neu=[]

    #---- First count the AA in the protein, this block will identify one or/and three letter AA codes

    for protein in proteins:

        CAla=(protein.count('A'))# + protein.count('ALA'))

        CArg=(protein.count('R'))#  + protein.count('ARG'))

        CAsn=(protein.count('N'))#  + protein.count('ASN'))

        CAsp=(protein.count('D'))#  + protein.count('ASP'))

        CCys=(protein.count('C'))#  + protein.count('CYS'))

        CGln=(protein.count('Q'))#  + protein.count('GLN'))

        CGlu=(protein.count('E'))#  + protein.count('GLU'))

        CGly=(protein.count('G'))#  + protein.count('GLY'))

        CHis=(protein.count('H'))#  + protein.count('HIS'))

        CIle=(protein.count('I'))#  + protein.count('ILE'))

        CLeu=(protein.count('L'))#  + protein.count('LEU'))

        CLys=(protein.count('K'))#  + protein.count('LYS'))

        CMet=(protein.count('M'))#  + protein.count('MET'))

        CPhe=(protein.count('F'))#  + protein.count('PHE'))

        CPro=(protein.count('P'))#  + protein.count('PRO'))

        CSer=(protein.count('S'))#  + protein.count('SER'))

        CThr=(protein.count('T'))#  + protein.count('THR'))

        CTrp=(protein.count('W'))#  + protein.count('TRP'))

        CTyr=(protein.count('Y'))#  + protein.count('TYR'))

        CVal=(protein.count('V'))#  + protein.count('VAL'))

        #Number of AA in the protein
 #(gets rid of the special characters and helps identifying specific aa)

        nAA= (CAla  + CArg + CAsn + CAsp

        + CCys + CGln + CGlu + CGly

        + CHis + CIle + CLeu + CLys

        + CMet + CPhe + CPro + CSer

        + CThr + CTrp + CTyr + CVal)

        #Number Glycines in the protein

        nGly= CGly

        #To get the molecular weight of each protein

        protmw = (

        (CAla * AAdict['A'][2]) + (CArg * AAdict['R'][2]) + (CAsn * AAdict['N'][2]) + (CAsp * AAdict['D'][2])

        + (CCys * AAdict['C'][2]) + (CGln * AAdict['Q'][2]) + (CGlu * AAdict['E'][2]) + (CGly * AAdict['G'][2])

        + (CHis * AAdict['H'][2]) + (CIle * AAdict['I'][2]) + (CLeu * AAdict['L'][2]) + (CLys * AAdict['K'][2])

        + (CMet * AAdict['M'][2]) + (CPhe * AAdict['F'][2]) + (CPro * AAdict['P'][2]) + (CSer * AAdict['S'][2])

        + (CThr * AAdict['T'][2]) + (CTrp * AAdict['W'][2]) + (CTyr * AAdict['Y'][2]) + (CVal * AAdict['V'][2])- (nAA*18))

        """Take away nAA H2O weights"""

        #For the average molecular weight of all the amino acids in each protein

        avmw= protmw/nAA

        #print('**avmw** is the average molecular weight of this protein is=', avmw, 'Da')

    #Append the values for:

        sumavgaa+= avmw #1. For the average molecular weight of the amino acids for all the proteins

        protlen+= nAA #2. For the proteins lenght

        totmw+= protmw #3. For the total weight of the proteins

        #D ---To obtain the Gibbs free energy of formation for each protein (dGf[P]) at different conditions ---

        #dGf[P] = dGf[PBB] + ((n - ngly - 1) * dGf[AABB]) + summatory of the dGf[Ri] of each amino acid + dGf[GLY]

        biodGfPBB = (nAA - nGly - 1) * bioPBB

        biodGfPBB_dGfAABB = biodGfPBB + biodGfAABB

        dGfPBB = (nAA - nGly - 1) * PBB
        dGfPBB_dGfAABB = dGfPBB + dGfAABB

        #-----------------------------------Biological---------------------------------

        #sigmAAR_chr will get the dGf of the R group of each amino acid contained in the protein with a net charge of -2, -1 or 1 depending on the AA

        sigmAAR_chr = (

        (CAla * AAdict['A'][7]) + (CArg * AAdict['R'][7]) + (CAsn * AAdict['N'][7]) + (CAsp * AAdict['D'][7])

        + (CCys * AAdict['C'][7]) + (CGln * AAdict['Q'][7]) + (CGlu * AAdict['E'][7]) + (CGly * AAdict['_G'][8])

        + (CHis * AAdict['H'][7]) + (CIle * AAdict['I'][7]) + (CLeu * AAdict['L'][7]) + (CLys * AAdict['K'][7])

        + (CMet * AAdict['M'][7]) + (CPhe * AAdict['F'][7]) + (CPro * AAdict['P'][7]) + (CSer * AAdict['S'][7])

        + (CThr * AAdict['T'][7]) + (CTrp * AAdict['W'][7]) + (CTyr * AAdict['Y'][7]) + (CVal * AAdict['V'][7]))

        #sigmAA wil ge the dGf of each amino acid

        sigmAA_chr = (

        (CAla * AAdict['A'][8]) + (CArg * AAdict['R'][8]) + (CAsn * AAdict['N'][8]) + (CAsp * AAdict['D'][8])

        + (CCys * AAdict['C'][8]) + (CGln * AAdict['Q'][8]) + (CGlu * AAdict['E'][8]) + (CGly * AAdict['G'][8])

        + (CHis * AAdict['H'][8]) + (CIle * AAdict['I'][8]) + (CLeu * AAdict['L'][8]) + (CLys * AAdict['K'][8])

        + (CMet * AAdict['M'][8]) + (CPhe * AAdict['F'][8]) + (CPro * AAdict['P'][8]) + (CSer * AAdict['S'][8])

        + (CThr * AAdict['T'][8]) + (CTrp * AAdict['W'][8]) + (CTyr * AAdict['Y'][8]) + (CVal * AAdict['V'][8]))

        #For the entire equation dGf[P]

        dGfP_chr= biodGfPBB_dGfAABB + sigmAAR_chr

        dGfs_chr.append(dGfP_chr)

        #print('The standard free energy of formation for this protein is',dGfP, 'kJ mol-1')

        #---------E. The Gibbs free energy of reaction dGr[P]-----------------------

        dGr_chr= (((nAA-1)*biodGfH2O) + dGfP_chr) - sigmAA_chr #KJ mol

        proteinGs_chr.append(dGr_chr)

        #---------------------------------Thermodynamic----------------------------

        #sigmAAR_neu will get the dGf of the R group of each amino acid contained in the protein with a net charge of 0 on the AA

        sigmAAR_neu = (

        (CAla * AAdict['A'][1]) + (CArg * AAdict['R'][1]) + (CAsn * AAdict['N'][1]) + (CAsp * AAdict['D'][1])

        + (CCys * AAdict['C'][1]) + (CGln * AAdict['Q'][1]) + (CGlu * AAdict['E'][1]) + (CGly * AAdict['_G'][6])

        + (CHis * AAdict['H'][1]) + (CIle * AAdict['I'][1]) + (CLeu * AAdict['L'][1]) + (CLys * AAdict['K'][1])

        + (CMet * AAdict['M'][1]) + (CPhe * AAdict['F'][1]) + (CPro * AAdict['P'][1]) + (CSer * AAdict['S'][1])

        + (CThr * AAdict['T'][1]) + (CTrp * AAdict['W'][1]) + (CTyr * AAdict['Y'][1]) + (CVal * AAdict['V'][1]))

        #sigmAA wil ge the dGf of each amino acid

        sigmAA_neu = (

        (CAla * AAdict['A'][6]) + (CArg * AAdict['R'][6]) + (CAsn * AAdict['N'][6]) + (CAsp * AAdict['D'][6])

        + (CCys * AAdict['C'][6]) + (CGln * AAdict['Q'][6]) + (CGlu * AAdict['E'][6]) + (CGly * AAdict['G'][6])

        + (CHis * AAdict['H'][6]) + (CIle * AAdict['I'][6]) + (CLeu * AAdict['L'][6]) + (CLys * AAdict['K'][6])

        + (CMet * AAdict['M'][6]) + (CPhe * AAdict['F'][6]) + (CPro * AAdict['P'][6]) + (CSer * AAdict['S'][6])

        + (CThr * AAdict['T'][6]) + (CTrp * AAdict['W'][6]) + (CTyr * AAdict['Y'][6]) + (CVal * AAdict['V'][6]))

        #For the entire equation dGf[P]

        dGfP_neu= dGfPBB_dGfAABB + sigmAAR_neu

        dGfs_neu.append(dGfP_neu)

        #print('The standard free energy of formation for this protein is',dGfP, 'kJ mol-1')

        #-----------------------------------E. The Gibbs free energy of reaction dGr[P]-------------------------------------

        dGr_neu= ((((nAA-1)*dGfH2O) + dGfP_neu) - sigmAA_neu) #KJ mol

        proteinGs_neu.append(dGr_neu)

    #----------------------------------F. Number of proteins per type of cell (estimation)----------------------------

    #As proposed by Milo R. Insights & Perspectives (2013)

    # Protein density = cell density (1.1 g/mL) * Water content * protein fraction of dry mass

    protein_avmw= totmw/len(proteins)
    '''By grams'''
    if celltype == "bacteria":
        prot_grams= 1.57E-13 #g
        cell_moles = prot_grams/protein_avmw #mol
        cell_volume= 1e-15 #L
    if celltype == "yeast":
        prot_grams= 9.75E-12 #g
        cell_moles = prot_grams/protein_avmw #mol
        cell_volume= 1.1e-13 #L
    if celltype == "mammalian":
        prot_grams= 3.15E-10 #g
        cell_moles = prot_grams/protein_avmw #mol
        cell_volume= 3E-12 #L

    prot_mol_Lit= cell_moles/cell_volume

    # p𝑐= 𝐶𝑑 ∗ 𝑤 ∗ 𝑃𝑓

    #For E. coli / bacteria

    pdbacteria= (1.1)*(1-0.7)*(0.55)

    #For Yeast

    pdyeast= (1.1)*(1-0.6)*(0.40)

    #For human (Albe K. J.  theor. Biol. (1990) 143, 163-195)

    pdmammal= (1.1)*(1-0.65)*(0.52)

    # Protein mass = protein density+ Avogadro's number (6.022e23) + Cell volume (10e-12 mL/um3)

    # pm= 𝑐 ∗ 𝐴𝑁 ∗ 𝐶𝑣

    #For E. coli / bacteria

    pmbacteria= ((pdbacteria) * (6.022*10**23) * (10**-12))

    #For Yeast

    pmyeast= ((pdyeast) * (6.022*10**23) * (10**-12))

    #For human

    pmmammalian= ((pdmammal) * (6.022*10**23) * (10**-12))

    #Average protein size of this cell

    avP= totmw/ len(proteins) #average size in Da

    av_lenght= protlen/ len(proteins) #average lenght

    #------------------------------------- Protein concentration per type of cell-----------------------------------------------------

    #Protein number = ((protein mass / (Average lenght, in amino acids, of the proteins * Average amino acid molecular weight in Da)) x (characteristic volume in each type of cell)

    if celltype == "bacteria":

        host_volume= 1 #um3

        host_mass= 2.85e-13 #dry grams

        #---------------------------------------------------------

        """Protein concentration in E. coli"""

        moles = pmbacteria/(avP*6.022e23) # estimated moles of protein per um3 in E. coli

        mol_L = moles/(1e-15) #estimated moles per litre

        protein_conc = (pmbacteria/avP) #Protein concentration in 1um3 (proteins/um3)

        number_prot = (protein_conc * host_volume)#number of proteins (an E. coli cell is 1 um3)

        proteome_weight = number_prot * avP #Total weight of the proteome in Da

    elif celltype == "mammalian":

        host_volume=2000 #um3

        host_mass= 1.23e-09 #dry grams

        #---------------------------------------------------------

        """Protein concentration in a mammalian cell"""

        moles = pmmammalian/(avP*6.022e23) # estimated moles of protein per um3 in E. coli

        mol_L = moles/(1e-15) #estimated moles per litre

        protein_conc= (pmmammalian/avP) #Protein concentration in 1um3 (proteins/um3)

        number_prot= (protein_conc * host_volume) #number of proteins in one mammalian cell

        proteome_weight= number_prot * avP #Total weight of the proteome in Da

    elif celltype == "yeast":

        host_volume= 75 #um3

        host_mass= 3.16e-11 #dry grams

        #---------------------------------------------------------

        """Protein concentration in yeast"""

        moles = pmyeast/(avP*6.022e23) # estimated moles of protein per um3 in E. coli

        mol_L = moles/(1e-15) #estimated moles per litre

        protein_conc= (pmyeast/avP) #Protein concentration in 1um3 (proteins/um3)

        number_prot= (protein_conc * host_volume) #number of proteins in one yeast cell

        proteome_weight = number_prot * avP #Total weight of the proteome in Da

    #--------------------------------------------------------H. For the molal Gibbs Free Energy (Neutral Charge)-----------------------------------

    #PC = mol_L

    PC= prot_mol_Lit

    #make a list of the free energies for each protein

    reactionGs_neu = []

    for protein, stdG in zip(proteins, proteinGs_neu):

        lnQ = math.log(PC/len(proteins))

        for a in protein:

            if a in AAdict.keys():

                if celltype == "bacteria":

                    lnQ -= math.log(AAdict[a][3])

                elif celltype == "mammalian":

                    lnQ -= math.log(AAdict[a][4])

                elif celltype == "yeast":

                    lnQ -= math.log(AAdict[a][5])

        reactionGs_neu.append((stdG+(R*(T)*lnQ)))

    #take the average, which is in kJ/mol

    avgReactionG_kJmol_neu = statistics.mean(reactionGs_neu)

    # convert to kJ / dry g

    # 1e-15*PC/len(proteins) is the mean number of moles of protein per cell

    # 2.8e-13 converts to dry mass

    avgReactionG_kJdryg_neu = avgReactionG_kJmol_neu * 1/(avP)

    ReactionGibbs_neu.append(avgReactionG_kJdryg_neu)

    stdGibbs_neu.append(statistics.mean(proteinGs_neu))

    #--------------------------------------------------------H. For the molal Gibbs Free Energy (Charged)-----------------------------------

    #make a list of the free energies for each protein

    reactionGs_chr = []

    for protein, stdG in zip(proteins, proteinGs_chr):

        lnQ = math.log(PC/len(proteins))

        for a in protein:

            if a in AAdict.keys():

                if celltype == "bacteria":

                    lnQ -= math.log(AAdict[a][3])

                elif celltype == "mammalian":

                    lnQ -= math.log(AAdict[a][4])

                elif celltype == "yeast":

                    lnQ -= math.log(AAdict[a][5])

        reactionGs_chr.append((stdG+(R*(T)*lnQ)))

    #take the average, which is in kJ/mol

    avgReactionG_kJmol_chr = statistics.mean(reactionGs_chr)

    # convert to kJ / dry g

    # 1e-15*PC/len(proteins) is the mean number of moles of protein per cell

    # 2.8e-13 converts to dry mass

    avgReactionG_kJdryg_chr = avgReactionG_kJmol_chr * 1/(avP)

    ReactionGibbs_chr.append(avgReactionG_kJdryg_chr)

    stdGibbs_chr.append(statistics.mean(proteinGs_chr))

    #Averages
    dGf_chr_av.append(statistics.mean(dGfs_chr))
    dGr_chr_av.append(statistics.mean(proteinGs_chr))
    #Neu
    dGf_neu_av.append(statistics.mean(dGfs_neu))
    dGr_neu_av.append(statistics.mean(proteinGs_neu))

if temp == 'yes':

    print('--------These are the theoretical values:--------------------------------')
    print(' ')
    print('The total theoretical size of the proteome according to the input sequence is ', '{:.2e}'.format(number_prot), 'proteins in the cell, with a diversity of', len(proteins),'proteins')
    print('There are', moles,'moles of proteins in the proteome with a concentration of',mol_L,' mol per litre')
    print('The total weight of this proteome is', proteome_weight, ' Da')

    print(' ')
    print('The estimated energy to build this proteome at', stemperature, 'K is:')
    print(' ')
    print('With charged AA:')
    print('Gibbs formation energy:', "%.3f" % dGf_chr_av[selected_temp],'KJ mol')
    print('Gibbs reaction energy:', "%.3f" % dGr_chr_av[selected_temp],'KJ mol')
    print('Gibbs molar energy:',"%.3f" % ReactionGibbs_chr[selected_temp],'KJ/g')
    print(' ')
    print('With neutral AA:')
    print('Gibbs formation energy:', "%.3f" % dGf_neu_av[selected_temp],'KJ mol')
    print('Gibbs reaction energy:', "%.3f" % dGr_neu_av[selected_temp],'KJ mol')
    print('Gibbs molar energy:',"%.3f" % ReactionGibbs_neu[selected_temp],'KJ/g')
    print(' ')

#-----------------------------------------------------I. To plot in different temperatures---------------------------------

fig = plt.figure(figsize = (7,5))

ax= fig.add_subplot(111)

if celltype == "bacteria":

    plt.title('Molar Gibbs Energy calculated at two conditions for a bacteria cell')

elif celltype == "mammalian":

    plt.title('Molar Gibbs Energy calculated at two conditions for a mammalian cell')

elif celltype == "yeast":

    plt.title('Molar Gibbs Energy calculated at two conditions for a yeast cell')

ax.plot(TK, ReactionGibbs_neu, label='Proteome energy using standard energies', color='lawngreen', marker='v', linewidth=3)
ax.plot(TK, ReactionGibbs_chr, label='Proteome energy using biological standard energies', color='steelblue', marker='^', linewidth=2)

ax.set_ylabel(r'Energetic cost [kJ per dry g]', fontsize=14)

ax.set_xlabel('Temperature [K]', fontsize=14)

ax.tick_params(axis='both', which='major', labelsize=14)

ax.set_xlim(270, 400)

plt.legend(fontsize=14)

plt.tight_layout()

plt.show()


# -*- coding: utf-8 -*-
"""
-------------------------------------------------
    File Name:      Crawler__qyxg_xzcf__cbrc.py
    Description:    银监会-行政处罚
    Author:         Jack Deng
    Date:           2017-09-18
    version:        v.1.0
-------------------------------------------------
"""
import copy
import json
import re
import traceback
import uuid

from scrapy import Request

from hive_framework_milk.commons.utils.selector_util import clean_all_space
from hive_framework_milk.scrapy_spiders.spiders.spider_all import SpiderAll
from hive_worker_hot_coffee.scrapy_spiders.specific_spiders.qyxg_xzxk.field_mapping import map_field

class Crawler__qyxg_xzcf__cbrc(SpiderAll):
    """
    银监会-行政处罚
    """

    name = "Crawler__qyxg_xzcf__cbrc"
    start_urls = ["http://www.cbrc.gov.cn/forwardToXZCFPage2.html"]

    def custom_init(self, *args, **kwargs):
        self.which_one = [
            "银监会机关", "银监局", "银监分局"
        ]

    def parse(self, response):
        """
        get three more href to yield
        :param response:
        :return:
        """
        try:
            more_hrefs = response.xpath('//a[contains(., "更多")]/@href').extract()
            for index, href in enumerate(more_hrefs):
                # if index == 0: #debug
                url = response.urljoin(href)
                yield Request(
                    url=url,
                    dont_filter=True,
                    callback=self.get_pages,
                    errback=self.err_parse,
                    meta={'which' : self.which_one[index]}
                )
                self.logger.info("yield List reqeust url: {} which: {}".format(url, self.which_one[index]))
                # break # debug
        except Exception as err:
            self.logger.error(
                msg="Exception occurred on parsing for first page[{url}]:{error} trace{tb}".format(url=response.url,
                                                                                error=err,
                                                                                tb=traceback.format_exc()),
                )

    def get_pages(self, response):
        """
        get page numbers
        :param response:
        :return:
        """
        try:
            which = response.meta['which']
            for req in self.parse_list(response):
                yield req
            pages_count = int(re.search(r'请输入小于(\d+).*?', response.text).group(1))
            for page in range(2, pages_count + 1):
                if which == "银监会机关":
                    url = "".join([response.url.replace('.html', ''), "&current=", str(page)])
                else:
                    url = "".join([response.url, "?current=", str(page)])
                yield Request(
                    url=url,
                    dont_filter=True,
                    callback=self.parse_list,
                    errback=self.err_get_pages,
                    meta={'which' : which, 'page' : str(page)}
                )
                self.logger.info("yield {} page {} to get detail url".format(which, page))
        except Exception as err:
            self.logger.error(
                msg="Exception occurred on parsing for first page[{url}]:{error} trace{tb}".format(url=response.url,
                                                                                error=err,
                                                                                tb=traceback.format_exc()),
                )

    def parse_list(self, response):
        """
        parse one page to yield each detail url
        :param response:
        :return:
        """
        try:
            which = response.meta['which']
            try:
                page = response.meta['page']
            except:
                page = "1"
            trs = response.xpath('//div[@class="xia3"]//tr')
            hrefs = ["".join(td.xpath('.//a/@href').extract()) for td in trs.xpath('.//td[position()=1]')]
            titles = ["".join(td.xpath('.//a/@title').extract()) for td in trs.xpath('.//td[position()=1]')]
            pubdates = [clean_all_space(td.xpath('string(.)').extract()) for td in trs.xpath('.//td[last()]')]
            hrefs_pubs_titles = zip(hrefs[0:-1], pubdates[0:-1], titles[0:-1]) #
            for href, pubdate, title in hrefs_pubs_titles:
                url = response.urljoin(href)
                if "报告" in title or "公告" in title or "目录" in title:
                    self.logger.warning("{} page {} url {} is unrelated, wont crawl".format(
                        which, page, url
                    ))
                    continue
                yield Request(
                    url=url,
                    callback=self.parse_detail,
                    errback=self.err_list,
                    meta={'which' : which, 'page' : page, 'public_date' : pubdate, 'title' : title}
                )
                self.logger.info("yield {} page {} detail url {}".format(
                    which, page, url
                ))

            # yield Request(  # bugfix test
            #     url="http://www.cbrc.gov.cn/chinese/home/docView/CFEDE60A320448CAA3FD920B1F7A1357.html",
            #     callback=self.parse_detail,
            #     errback=self.err_list,
            #     meta={'which': "银监局", 'page': 0, 'public_date': "2016-03-24", 'title': "宁波银监局行政处罚信息公开表"}
            # )
        except Exception as err:
            self.logger.error(
                msg="Exception occurred on parsing for first page[{url}]:{error} trace{tb}".format(url=response.url,
                                                                                error=err,
                                                                                tb=traceback.format_exc()),
                )

    def parse_detail(self, response):
        """
        parse detail
        :param response:
        :return:
        """
        try:
            which = response.meta['which']
            page = response.meta['page']
            part_res = {
                "public_date" : response.meta['public_date'],
                "title" : response.meta['title']
            }
            if "附件信息" in response.text:
                self.logger.warning("{} page {} url {} is just a .pdf link, wont parse".format(
                    which, page, response.url
                ))
                return
            section1 = response.xpath('//div[@class="WordSection1"]')
            if not section1:
                section1 = response.xpath('//div[contains(@class, "Section")]')
            if not section1:
                raise Exception("{} page {} url {} get data failed, please check it out!".format(
                    which, page, response.url
                ))
            tables = section1.xpath('.//table[@class="MsoNormalTable"]')
            if not tables:
                tables = section1.xpath('.//table[@class="MsoTableGrid"]')
            if tables:
                for item in self.deal_tables(tables, part_res, response):
                    yield item
            else:
                for item in self.deal_main(section1, part_res, response):
                    yield item

        except Exception as err:
            self.logger.error(
                msg="Exception occurred on parsing for first page[{url}]:{error} trace{tb}".format(url=response.url,
                                                                                error=err,
                                                                                tb=traceback.format_exc()),
                )

    def deal_main(self, main_html, result, response):
        """
        catch main text
        :param main_html:
        :param result:
        :param response:
        :return:
        """
        try:
            item = self.result_item_assembler(response)
            item['bbd_html'] = ''
            punish_org_text = "".join(main_html.xpath('.//p[contains(., "监管局") and contains(., "中国")]').xpath('string(.)').extract())
            punish_org = re.search(r'(中国.*?监管局).*?', punish_org_text).group(1) if punish_org_text else ""
            punish_code = "".join(main_html.xpath('//p[contains(., "监罚")]').xpath('string(.)').extract())
            if response.meta['which'] != "银监会机关":  # dont parse province for 银监会机关
                province_text = "".join(
                    main_html.xpath('//div[@id="docTitle"]/div[contains(., "文章来源")]/text()').extract())
                province = re.search(r'文章来源.*?([\u4e00-\u9fa5]{2})', province_text).group(1)
            else:
                province = ""
            main = "".join(main_html.xpath('.//p[contains(., "监罚")]//following-sibling::*').extract())
            if not main:
                main = "".join(main_html.xpath('.//*').extract())
            result.update({
                "punish_code" : punish_code,
                "province" : province,
                "main" : main,
                "punish_org" : punish_org
            })
            item['_parsed_data'] = result
            yield item
            self.logger.info("parse main successed, {} page {} url {} data {}".format(
                response.meta['which'], response.meta['page'], response.url, json.dumps(result)
            ))
        except:
            self.logger.error("error on deal_tables {} trace {}".format(
                response.url, traceback.format_exc(),
            ))

    def deal_tables(self, tables, result, response):
        """
        parse table
        :param tables:
        :param result:
        :param response:
        :return:
        """
        try:
            item = self.result_item_assembler(response)
            item['bbd_html'] = ''
            punish_code_num = len(re.findall(r'(行政处罚决定书文号|处罚决定书文号)', response.text))
            if response.meta['which'] != "银监会机关":  # dont parse province for 银监会机关
                province_text = "".join(
                    response.xpath('//div[@id="docTitle"]/div[contains(., "文章来源")]/text()').extract())
                province = re.search(r'文章来源.*?([\u4e00-\u9fa5]{2})', province_text).group(1)
            else:
                province = ""
            result.update({'province' : province})
            if punish_code_num == 1 and len(tables) == 3:
                # 处理　"银监分局" 里的特殊情况
                trs = tables.xpath('.//tr')
                titles, values = [], []
                for tr in trs:
                    tds = tr.xpath('.//td')
                    if len(tds) == 1:
                        continue
                    # http://www.cbrc.gov.cn/chinese/home/docView/7E4321407D13435D99064AA007A538B7.html
                    titles.append(clean_all_space(tds[len(tds) - 2].xpath('string(.)').extract()))
                    values.append("".join(tds[-1].xpath('string(.)').extract()).strip())
                tmp_dict = dict(set(zip(titles, values)))
                tmp_dict.pop("", "")
                maped = map_field(tmp_dict)
                result.update(maped)
                item['_parsed_data'] = result
                yield item
                self.logger.info("parse special table successed, {} page {} url {} data {}".format(
                    response.meta['which'], response.meta['page'], response.url, json.dumps(result)
                ))
            else:
                for table in tables:
                    trs = table.xpath('.//tr')
                    td_firsts = [clean_all_space(td.xpath('string(.)').extract()) for td in trs.xpath('.//td[position()=1]') if
                                 len(trs.xpath('.//td')) > 1]
                    if "序号" in td_firsts:  #  one tr tag, one data
                        #  http://www.cbrc.gov.cn/chinese/home/docView/0C0C87BDA4C3431B925FF7BC2461FA55.html
                        titles = [clean_all_space(td.xpath('string(.)').extract()) \
                                  for td in trs[td_firsts.index("序号")].xpath('.//td')]
                        for tr in trs[td_firsts.index("序号") + 1 : ]:
                            values = ["".join(td.xpath('string(.)').extract()).strip() for td in tr.xpath('.//td')]
                            maped = map_field(dict(zip(titles, values)))
                            maped.pop("序号", "")
                            maped.pop("", "")
                            if re.search(r'[0-9]', maped['punish_date']):
                                punish_date = list(maped.pop('punish_date'))
                                new_punish_date = "{}-{}-{}".format(
                                    "".join(punish_date[:4]),
                                    "".join(punish_date[4:6]),
                                    "".join(punish_date[6:])
                                )
                                maped['punish_date'] = new_punish_date
                            res_dict = copy.deepcopy(result)
                            res_dict.update(maped)
                            new_item = copy.deepcopy(item)
                            new_item['_id'] = "{}".format(uuid.uuid4())
                            new_item['_parsed_data'] = res_dict
                            yield new_item
                            self.logger.info("parse one line one data table successed, {} page {} url {} data {}".format(
                                response.meta['which'], response.meta['page'], response.url, json.dumps(res_dict)
                            ))
                    else:  # title : value in each tr tag
                        titles, values = [], []
                        for tr in trs:
                            tds = tr.xpath('.//td')
                            if len(tds) == 1:
                                continue
                            # http://www.cbrc.gov.cn/chinese/home/docView/D0952CF472AB443486104FB03E6FE862.html
                            titles.append(clean_all_space(tds[len(tds) - 2].xpath('string(.)').extract()))
                            values.append("".join(tds[-1].xpath('string(.)').extract()).strip())

                        if len(titles) == len(values):
                            tmp_dict = dict(zip(titles, values))
                        else:
                            raise Exception('titles ,values get error')
                        tmp_dict.pop("", "")
                        maped = map_field(tmp_dict)
                        if re.search(r'[0-9]', maped['punish_date']) and re.search(r'[年|月|日]', maped['punish_date']):
                            # new_punish_date = maped['punish_date'].replace('年', '-').replace('月', '-').replace('日', '')
                            new_punish_date = "{0}-{1:0>2}-{2:0>2}".format(
                                re.search(r'(\d{4}).*?年', maped['punish_date']).group(1),
                                re.search(r'年.*?(\d{1,2}).*?月', maped['punish_date']).group(1),
                                re.search(r'月.*?(\d{1,2}).*?日', maped['punish_date']).group(1),
                            )
                            maped['punish_date'] = new_punish_date
                        res_dict = copy.deepcopy(result)
                        res_dict.update(maped)
                        new_item = copy.deepcopy(item)
                        new_item['_id'] = "{}".format(uuid.uuid4())
                        new_item['_parsed_data'] = res_dict
                        yield new_item
                        self.logger.info("parse table successed, {} page {} url {} data {}".format(
                            response.meta['which'], response.meta['page'], response.url, json.dumps(res_dict)
                        ))
        except:
            self.logger.error("error on deal_tables {} trace {}".format(
                response.url, traceback.format_exc(),
            ))

    def err_list(self, failure):
        response = failure.value.response
        self.logger.error("Exception occurred on crawling for list page[{url}]:{httpcode}".format(url=response.url,
                                                                                httpcode=response.status))

    def err_get_pages(self, failure):
        response = failure.value.response
        self.logger.error("Exception occurred on crawling for getpage page[{url}]:{httpcode}".format(url=response.url,
                                                                                httpcode=response.status))

    def err_parse(self, failure):
        response = failure.value.response
        self.logger.error("Exception occurred on crawling for first page[{url}]:{httpcode}".format(url=response.url,
                                                                                httpcode=response.status))


"""
Desc: AIND Sudoku project submission code. Program intelligently solves any
sudoku puzzle, includes naked_twins() heuristic code and support for diagonal
sudoku puzzles. Portions of this code are adapted from the Udacity - Artificial
Intelligence Nanodegree (May 2017 session) online MOOC.
Input: initial puzzle configuration
Output: 2-D puzzle solution
Author: Daniel R Rines
Github: drines
Created: Friday, May 23, 2017
Version: 2017.05.25.003
"""
import logging

logging.basicConfig(filename='error.log',
                    format='%(asctime)s %(message)s',
                    datefmt='%m/%d/%Y %I:%M:%S %p',
                    level=logging.ERROR)

def update_log(level, message):
    """
    Function makes it easier to output logging messages instead of adding
    format instructions each time a message is sent to the logging module
    code base. Warning is the default level.
    """
    log_message = ' :: ' + message
    if level == 'debug':
        logging.debug(log_message)
    elif level == 'error':
        logging.error(log_message)
    else:
        logging.warning(log_message)

update_log('debug', 'starting execution: solution.py')
assignments = []

def assign_value(values, box, value):
    """
    Please use this function to update your values dictionary!
    Assigns a value to a given box. If it updates the board record it.
    """
    if values[box] == value:
        return values

    values[box] = value
    if len(value) == 1:
        assignments.append(values.copy())
    return values

def naked_twins(values):
    """Eliminate values using the naked twins strategy.
    Args:
        values(dict): a dictionary of the form {'box_name': '123456789', ...}
    Returns:
        the values dictionary with the naked twins eliminated from peers.
    Overall logic:
        function obtains a list of all boxes that currently contain only two
        digits. It then loops through these boxes on a peer-by-peer manner to
        identify any set of idential two digit boxes. of course, these must
        be within the same peer group. assuming a set of naked twins are found
        in a peer group, then function will search for each of the digits in
        other peer associated boxes and remove them, leaving just the non-
        twin digits.
    """
    # find all instances of naked twins
    values = find_twins(values,
                        [box for box in values.keys()
                         if len(values[box]) == 2])
    return values

def find_twins(values, two_digit_boxes):
    """
    Modularized code to handle the search aspects of the naked_twins algorithm.
    Args:
        two_digit_boxes(list): a list of all the boxes with the two digit
        values.
    Returns:
        the values dictionary with the naked twins eliminated from peers.
    """
    for box in two_digit_boxes:
        for peer_group in units[box]:
            for twin_box in peer_group:    # loop through peer groups
                if values[box] == values[twin_box] and box != twin_box:
                    update_log('debug', 'Found a twin: ' + box +
                               ' -> (' + values[twin_box] + ')')
                    # now isolate and eliminate the digits if possible
                    values = eliminate_twins(values, box, twin_box,
                                             peer_group, values[box])
    return values

def eliminate_twins(values, box, twin_box, group, digits):
    """
    Modularized code to handle the search aspects of the naked_twins algorithm.
    Args:
        values(dict) - a dictionary with key: box and value: digits
        box(string) - the first two digit box in the peer
        twin_box(string) - the second two digit box in the peer
        group(list) - the list of all keys in current peer
        digits(string) - a successfully ident. set of naked twin digits
    Returns:
        the values dictionary with the naked twins eliminated from peers.
    """
    for digit in digits:
        # eliminate the naked twins as possibilities in
        # their peers.
        for fix_box in group:
            # make sure you don't remove digits from either
            # of the naked twins and don't remove any digits
            # from a single digit (assigned) box.
            if box != fix_box and fix_box != twin_box and \
                    len(values[fix_box]) > 1:
                update_log('debug',
                           'eliminating digit from non-twins box: ' +
                           fix_box + '-> ' +
                           values[fix_box] + '(' +
                           digit + ')')
                values = assign_value(values, fix_box,
                                      values[fix_box].replace(digit, ''))
    return values

def cross(A, B):
    "Cross product of elements in A and elements in B."
    return [s+t for s in A for t in B]

def grid_values(grid):
    """
    Convert grid into a dict of {square: char} with '123456789' for empties.
    Args:
        grid(string) - A grid in string form.
    Returns:
        A grid in dictionary form
            Keys: The boxes, e.g., 'A1'
            Values: The value in each box, e.g., '8'. If the box has no value,
            then the value will be '123456789'.
    """
    update_log('debug', 'convert input sudoku grid')
    chars = []
    digits = '123456789'
    for cpos in grid:
        if cpos in digits:
            chars.append(cpos)
        if cpos == '.':
            chars.append(digits)
    assert len(chars) == 81
    return dict(zip(boxes, chars))

def display(values):
    """
    Display the values as a 2-D grid.
    Args:
        values(dict): The sudoku in dictionary form
    """
    width = 1+max(len(values[s]) for s in boxes)
    line = '+'.join(['-'*(width*3)]*3)
    for r in rows:
        print(''.join(values[r+c].center(width)+('|' if c in '36' else '')
                      for c in cols))
        if r in 'CF': print(line)
    return

def eliminate(values):
    """
    Go through all the boxes, and whenever there is a box with a value,
    eliminate this value from the values of all its peers.
    Input: A sudoku in dictionary form.
    Output: The resulting sudoku in dictionary form.
    """
    update_log('debug', 'executing eliminate()')
    solved_values = [box for box in values.keys() if len(values[box]) == 1]
    for box in solved_values:
        digit = values[box]
        for peer in peers[box]:
            values = \
                assign_value(values, peer, values[peer].replace(digit, ''))
    return values

def only_choice(values):
    """
    Go through all the units, and whenever there is a unit with a value that
    only fits in one box, assign the value to this box.
    Input: A sudoku in dictionary form.
    Output: The resulting sudoku in dictionary form.
    """
    update_log('debug', 'executing only_choice()')
    for unit in unitlist:
        for digit in '123456789':
            dplaces = [box for box in unit if digit in values[box]]
            if len(dplaces) == 1:
                # values[dplaces[0]] = digit
                values = assign_value(values, dplaces[0], digit)
    return values

def reduce_puzzle(values):
    """
    Iterate eliminate() and only_choice(). If at some point, there is a box
    with no available values, return False.
    If the sudoku is solved, return the sudoku.
    If after an iteration of both functions, the sudoku remains the same,
    return the sudoku.
    Input: A sudoku in dictionary form.
    Output: The resulting sudoku in dictionary form.
    """
    update_log('debug', 'executing reduce_puzzle()')
    stalled = False
    while not stalled:
        solved_values_before = \
            len([box for box in values.keys() if len(values[box]) == 1])
        # extended the constraint propogation to include the diagonal
        # peer constraint via the eliminate() function execution. the peer
        # groups are initially defined with global 'unit' constants.
        values = eliminate(values)
        values = only_choice(values)
        # also extended the constraint propogation loop to include the
        # test for naked twins (two boxes with idential set of paired
        # digits). this heuristic removes digits from other peer
        # associated boxes that are already included in any twins.
        values = naked_twins(values)
        solved_values_after = \
            len([box for box in values.keys() if len(values[box]) == 1])
        stalled = solved_values_before == solved_values_after
        if stalled:
            update_log('debug', 'reduce_puzzle() stalled')
        if len([box for box in values.keys() if len(values[box]) == 0]):
            return False
    return values

def search(values):
    """Using depth-first search and propagation, create a search tree
    and solve the sudoku."""
    update_log('debug', 'executing search()')
    # First, reduce the puzzle using the previous function
    # Choose one of the unfilled squares with the fewest possibilities
    values = reduce_puzzle(values)
    if values is False:
        return False  # Failed earlier
    if all(len(values[s]) == 1 for s in boxes):
        return values  # Solved!
    # Choose one of the unfilled squares with the fewest possibilities
    _, s = min((len(values[s]), s) for s in boxes if len(values[s]) > 1)
    # Now use recurrence to solve each one of the resulting sudokus, and
    for value in values[s]:
        new_sudoku = values.copy()
        new_sudoku[s] = value
        attempt = search(new_sudoku)
        if attempt:
            return attempt

def solve(grid):
    """
    Find the solution to a Sudoku grid.
    Args:
        grid(string): a string representing a sudoku grid.
            Example:
            '2.............62....1....7...6..8...3...9...7...6..4...4.
            ...8....52.............3'
    Returns:
        The dictionary representation of the final sudoku grid. False if no
        solution exists.
    """
    update_log('debug', 'executing solve()')
    return search(grid_values(grid))

# Initialize global constants
update_log('debug', 'global constant initialization')
rows = 'ABCDEFGHI'
cols = '123456789'
boxes = cross(rows, cols)
row_units = [cross(r, cols) for r in rows]
column_units = [cross(rows, c) for c in cols]
square_units = [cross(rs, cs) for rs in ('ABC', 'DEF', 'GHI')
                for cs in ('123', '456', '789')]
# added two additional peer groups for any units located along
# either of the matrix diagnols. these are then included with the
# unitlist definition and, thus, 'baked in' into the constraint
# propogation process.
diag_units = [[r+c for r, c in zip(rows, cols)],
              [r+c for r, c in zip(rows, cols[::-1])]]
unitlist = row_units + column_units + square_units + diag_units
units = dict((s, [u for u in unitlist if s in u]) for s in boxes)
peers = dict((s, set(sum(units[s], []))-set([s])) for s in boxes)

if __name__ == '__main__':
    """
    Entry point for Sudoku solution.py code base.
    """
    update_log('debug', 'called: __main__')

    diag_sudoku_grid = \
        '2.............62....1....7...6..8...3...9...7' + \
        '...6..4...4....8....52.............3'
    display(solve(diag_sudoku_grid))

    try:
        from visualize import visualize_assignments
        visualize_assignments(assignments)

    except SystemExit:
        pass
    except:
        update_log('debug', 'problems with pygame execution')
        print('We could not visualize your board due to a pygame issue.' +
              ' Not a problem! It is not a requirement.')


from datetime import date
nascimento = int(input("Qual o ano que você nasceu? "))
alistamento = date.today().year - nascimento

if alistamento < 18:
    print("Você ainda irá se alistar!")
    saldo = 18 - alistamento
    if saldo == 1:
        print("Você terá que se alistar daqui {} ano".format(saldo))
    else:
        print("Você terá que se alistar daqui {} anos".format(saldo))
elif alistamento > 18:
    saldo = alistamento - 18
    print("Você deveria ter se alistado!")
    if saldo == 1:
        print("Deveria ter se alistado a {} ano".format(saldo))
    else:
        print("Deveria ter se alistado a {} anos".format(saldo))
else:
    print("Esta na hora de se alistar!!")


import re
import csv
from pathlib import Path

def readTaggedAnimalsCsvFile(path: str) -> list[list[str]]:
    """Read in the latset/greatest animals CSV file"""
    rows: list[list[str]] = []
    with open(path, "r") as csvfile:
        # create a csv reader object
        csvReader = csv.reader(csvfile)

        # extracting field names through first row
        next(csvReader)

        # extracting each data row one by one
        row: list[str]
        for row in csvReader:
            rows.append(row)
    return rows

def getPathImageIndex(path: Path) -> int:
    """Extracts the image # from the file name"""
    stem = path.stem
    numbers = re.findall(r"\d+", stem)
    fileIndexStr = numbers[0]  # File index # as a string
    fileIndex = int(fileIndexStr)  # File index as a number
    return fileIndex

def determineIfNewGroup(previousPath: Path, currentPath: Path) -> bool:
    """Determines if this is a new image group"""
    # First - if the images are in different folders it's obviously a different group
    if previousPath.parent != currentPath.parent:
        return True

    # If the indexes are not offset by 1, then it's a different group
    previousIndex = getPathImageIndex(previousPath)
    currentIndex = getPathImageIndex(currentPath)
    return (previousIndex + 1) != currentIndex

def main():
    inputFilename = "./animals.final.csv"
    animalRecords = readTaggedAnimalsCsvFile(inputFilename)

    previousPath: Path = Path(r"c:\does_not\exist.png")
    for row in animalRecords:
        tagged = row[0] == "TRUE"
        currentPath = Path(str(row[1]).strip())

        # Determine if this image group starts with a tagged image
        # (which I really don't want for DL training purposes)
        isNewGroup = determineIfNewGroup(previousPath, currentPath)
        if isNewGroup and tagged:
            print(f"Tagged image at start of group: {currentPath}")

        # Prepare for the next loop iteration
        previousPath = currentPath

main()