import logging
import traceback
from decimal import Decimal
from datetime import timedelta, date
from dateutil.relativedelta import relativedelta

from django.db import models
from django.db.models import F, Q, Count, Sum, Value, ExpressionWrapper
from django.db.models.functions import Coalesce
from django.db.models.fields import DecimalField
from django.utils.timezone import localtime, now
from django.urls import reverse
from django.contrib.auth.models import User
from django.conf import settings
from django.utils.functional import cached_property

from nadine.models.membership import Membership, ResourceSubscription
from nadine.models.resource import Resource
from nadine.models.usage import CoworkingDay, Event

logger = logging.getLogger(__name__)

class BatchManager(models.Manager):

    def run(self, start_date=None, end_date=None, created_by=None):
        batch = self.create(created_by=created_by)
        if batch.run(start_date, end_date):
            return batch
        else:
            raise Exception(batch.error)

class BillingBatch(models.Model):
    """Gathers all untracked subscriptions and activity and associates it with a UserBill."""
    objects = BatchManager()
    created_ts = models.DateTimeField(auto_now_add=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="+", null=True, blank=True, on_delete=models.CASCADE)
    completed_ts = models.DateTimeField(blank=True, null=True)
    error = models.TextField(blank=True, null=True)
    bills = models.ManyToManyField('UserBill')

    class Meta:
        app_label = 'nadine'
        ordering = ['-created_ts']
        get_latest_by = 'created_ts'

    def __str__(self):
        return 'BillingBatch %s: %s' % (self.created_ts, self.successful)

    @property
    def successful(self):
        return self.completed_ts != None and self.error == None

    def run(self, start_date=None, end_date=None):
        ''' Run billing for every day since the last successful billing run. '''
        logger.info("run(start_date=%s, end_date=%s)" % (start_date, end_date))
        try:
            # Make sure no other batches are running
            if BillingBatch.objects.filter(completed_ts=None).exclude(id=self.id).count() > 0:
                raise Exception("Found a BillingBatch that has not yet completed!")

            # If no end_date, go until today
            if not end_date:
                end_date = localtime(now()).date()

            # If no start_date, start from the last successful batch run
            target_date = start_date
            if not target_date:
                last_batch = BillingBatch.objects.filter(error__isnull=True).order_by('created_ts').last()
                target_date = localtime(last_batch.created_ts).date()

            # Run for each day in our range
            while target_date <= end_date:
                self.run_billing_for_day(target_date)
                target_date = target_date + timedelta(days=1)

            # Close out batch
            self.close()

            # Update cached totals on all associated bills
            for bill in self.bills.all():
                bill.update_cached_totals()

        except Exception as e:
            # Save all error messages
            self.error = str(e)
            traceback.print_exc()
            logger.error(self.error)
            self.close()

        # Indicate if we ran successsfully or not
        return self.successful

    def run_billing_for_day(self, target_date):
        ''' Run billing for a specific day. '''
        logger.info("run_billing_for_day(%s)" % target_date)

        # Gather up all the subscriptions
        self.run_subscriptions(target_date)

        # Gather up all the trackable usage (CoworkingDays and Events)
        self.run_usage(target_date)

        # Recalculate all the bills that need it
        for bill in self.to_recalculate:
            bill.recalculate()

        # Close all open subscription based bills that end on this day
        self.close_bills_at_end_of_period(target_date)

    def run_subscriptions(self, target_date):
        # Keep track of which bills need to be recalculated
        self.to_recalculate = set()

        # Check every active subscription on this day and add this if neccessary
        for subscription in ResourceSubscription.objects.unbilled(target_date):
            logger.debug("Found Subscription: %s %s, %s to %s" % (subscription.membership.who, subscription.package_name, subscription.start_date, subscription.end_date))
            # Look at the membership of the payer to find the bill period
            membership = Membership.objects.for_user(subscription.payer)
            period_start, period_end = membership.get_period(target_date)
            if period_start is None:
                # If we did not get a period, the payer is not active on this date
                # Look instead at the membership for the individual
                membership = Membership.objects.for_user(subscription.user)
                period_start, period_end = membership.get_period(target_date)

            # Find the open bill for this period and add this subscription
            bill = UserBill.objects.get_or_create_open_bill(subscription.payer, period_start, period_end, check_open_bills=False)
            if not bill.has_subscription(subscription):
                line_item = bill.add_subscription(subscription)
                self.bills.add(bill)
                # If we have any activity for this resource, flag for recalculation
                activity = bill.resource_activity(subscription.resource)
                if activity and activity.count() > 0:
                    self.to_recalculate.add(bill)

    def run_usage(self, target_date):
        # Pull and add all past unbilled CoworkingDays
        for day in CoworkingDay.objects.unbilled(target_date).order_by('visit_date'):
            logger.debug("Found Coworking Day: %s %s %s" % (day.user, day.visit_date, day.payment))
            # Find the open bill for the period of this one day
            bill = UserBill.objects.get_or_create_open_bill(day.payer, day.visit_date, day.visit_date)
            bill.add_coworking_day(day)
            self.bills.add(bill)

        # Pull and add all past unbilled Events
        for event in Event.objects.unbilled(target_date).order_by('start_ts'):
            day = event.start_ts.date()
            logger.debug("Found Event: %s %s" % (event.user, day))
            # Find the open bill for the period of this one day
            bill = UserBill.objects.get_or_create_open_bill(event.payer, day, day)
            bill.add_event(event)

    def close_bills_at_end_of_period(self, target_date):
        ''' Close the open bills at the end of their period. '''
        logger.debug("close_bills_at_end_of_period(target_date=%s)" % target_date)
        for bill in UserBill.objects.filter(closed_ts__isnull=True, period_end=target_date):
            if bill.subscriptions().count() > 0:
                # Only close bills that have subscriptions.
                # Bills with only resource activity remain open until paid
                bill.close()
                self.bills.add(bill)

    def close(self):
        self.completed_ts = localtime(now())
        self.save()

class BillManager(models.Manager):

    def get_open_bill(self, user, period_start, period_end):
        ''' Get one and only one open UserBill for a given user and period. '''
        logger.debug("get_open_bill(%s, %s, %s)" % (user, period_start, period_end))

        bills = self.filter(
            user = user,
            period_start__lte = period_start,
            period_end__gte = period_end,
            closed_ts__isnull = True,
        )

        # One and only one bill
        if bills.count() > 1:
            logger.info("get_open_bill(%s, %s, %s)" % (user, period_start, period_end))
            for bill in bills:
                logger.info("Found %s" % bill)
            raise Exception("Found more than one bill (%s)!" % bills)

        # Returns the one or None if we didn't find anything
        return bills.first()

    def get_or_create_open_bill(self, user, period_start, period_end, check_open_bills=True):
        ''' Get or create a UserBill for the given ResourceSubscription and date. '''
        logger.debug("get_or_create_open_bill(%s, %s, %s, %s)" % (user, period_start, period_end, check_open_bills))

        bill = self.get_open_bill(user, period_start, period_end)
        if bill:
            return bill

        # If there is no bill for this specific period, find any open bill
        if check_open_bills:
            last_open_bill = self.filter(user=user, closed_ts__isnull=True).order_by('due_date').last()
            if last_open_bill:
                # Expand the period to include this visit
                if last_open_bill.period_start > period_start:
                    last_open_bill.period_start = period_start
                if last_open_bill.period_end < period_end:
                    last_open_bill.period_end = period_end
                last_open_bill.save()
                return last_open_bill

        # Create a new UserBill
        if not bill:
            bill = self.create(
                user = user,
                period_start = period_start,
                period_end = period_end,
                due_date = period_end,
            )
        return bill

    def create_for_day(self, user, target_date=None):
        ''' Create a UserBill for the given user for one day only. '''
        if not target_date:
            target_date = localtime(now()).date()
        bill = self.create(
            user = user,
            period_start = target_date,
            period_end = target_date,
            due_date = target_date,
        )
        return bill

    def open(self):
        return self.filter(closed_ts__isnull=True)

    def closed(self):
        return self.filter(closed_ts__isnull=False)

    # def outstanding(self):
    #     ''' Return a set of all outstanding bills. '''
    #     # There is a known bug that results in a bill that has multiple line items
    #     # and a partial payment not showing up in this set as it should.
    #     # https://code.djangoproject.com/ticket/10060
    #     # https://github.com/nadineproject/nadine/issues/300
    #     amount_adjustment = (F('bill_amount') * F('items_distinct')) / F('item_count')
    #     tax_amount_adjustment = (F('bill_tax_amount') * F('items_distinct')) / F('item_count')
    #     payment_adjustment = (F('payment_amount') * F('payments_distinct')) / F('payment_count')
    #     outstanding_query = self.filter(mark_paid=False) \
    #         .annotate(bill_amount=Sum('line_items__amount', output_field=DecimalField())) \
    #         .annotate(bill_tax_amount=Sum('line_items__tax_amount', output_field=DecimalField())) \
    #         .annotate(item_count=Count('line_items')) \
    #         .annotate(items_distinct=Count('line_items', distinct=True)) \
    #         .annotate(payment_amount=Sum('payment__amount', output_field=DecimalField())) \
    #         .annotate(payment_count=Count('payment')) \
    #         .annotate(payments_distinct=Count('payment', distinct=True)) \
    #         .annotate(adjusted_amount=ExpressionWrapper(amount_adjustment, output_field=DecimalField())) \
    #         .annotate(adjusted_tax_amount=ExpressionWrapper(tax_amount_adjustment, output_field=DecimalField())) \
    #         .annotate(adjusted_payments=ExpressionWrapper(payment_adjustment, output_field=DecimalField())) \
    #         .annotate(owed=F('adjusted_amount') + F('adjusted_tax_amount') - F('adjusted_payments'))
    #         # .annotate(total=F('bill_amount') + F('bill_tax_amount')) \
    #     no_payments = Q(payments_distinct = 0)
    #     partial_payment = Q(owed__gt = 0)
    #     return outstanding_query.filter(bill_amount__gt=0).filter(no_payments | partial_payment)

    def outstanding(self):
        return self.filter(mark_paid=False, cached_total_owed__gt = 0) \
            .annotate(bill_total=F('cached_total_amount') + F('cached_total_tax_amount'))

    def non_zero(self):
        return self \
            .annotate(bill_amount=Sum('line_items__amount')) \
            .annotate(bill_tax_amount=Sum('line_items__tax_amount')) \
            .filter(bill_amount__gt=0)

class UserBill(models.Model):
    objects = BillManager()
    created_ts = models.DateTimeField(auto_now_add=True)
    closed_ts = models.DateTimeField(blank=True, null=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="bills", on_delete=models.CASCADE)
    period_start = models.DateField()
    period_end = models.DateField()
    due_date = models.DateField()
    comment = models.TextField(blank=True, null=True, help_text="Public comments visable by the user")
    note = models.TextField(blank=True, null=True, help_text="Private notes about this bill")
    in_progress = models.BooleanField(default=False, blank=False, null=False, help_text="Mark a bill as 'in progress' indicating someone is working on it")
    mark_paid = models.BooleanField(default=False, blank=False, null=False, help_text="Mark a bill as paid even if it is not")

    cached_total_amount = models.DecimalField(max_digits=7, decimal_places=2, default=0)
    cached_total_tax_amount = models.DecimalField(max_digits=7, decimal_places=2, default=0)
    cached_total_paid = models.DecimalField(max_digits=7, decimal_places=2, default=0)
    cached_total_owed = models.DecimalField(max_digits=7, decimal_places=2, default=0)

    def __str__(self):
        return "UserBill %d: %s %s to %s for $%s" % (self.id, self.user, self.period_start, self.period_end, self.total) # self.amount)

    ############################################################################
    # Basic Properties
    ############################################################################

    @property
    def total_paid(self):
        return self.payment_set.aggregate(paid=Coalesce(Sum('amount'), Value(0.00), output_field=DecimalField()))['paid']

    @property
    def total_owed(self):
        return self.total - self.total_paid

    @property
    def amount(self):
        return self.line_items.aggregate(amount=Coalesce(Sum('amount'), Value(0.00), output_field=DecimalField()))['amount']

    @property
    def tax_amount(self):
        return self.total_tax_applied()

    @property
    def total(self):
        return self.amount + self.tax_amount

    @property
    def is_paid(self):
        return self.mark_paid or self.total_owed <= 0

    @property
    def payment_date(self):
        # Date of the last payment
        last_payment = self.payments.order_by('payment_date').last()
        if last_payment:
            return last_payment.payment_date
        else:
            return None

    @property
    def monthly_rate(self):
        ''' Add up all rates for all the subscriptions. '''
        return self.subscriptions().aggregate(rate=Coalesce(Sum('monthly_rate'), Value(0.00), output_field=DecimalField()))['rate']

    @property
    def overage_amount(self):
        ''' The difference between the month_rate and the amount. '''
        if self.monthly_rate:
            if self.amount < self.monthly_rate:
                return 0
            else:
                return self.amount - self.monthly_rate

    @property
    def subscriptions_due(self):
        ''' True if this bill is not paid and the amount paid is less than the montnly rate. '''
        if self.is_paid:
            return False
        if not self.monthly_rate:
            return False
        return self.total_paid < self.monthly_rate

    @property
    def package_name(self):
        ''' If all subscriptions have the same package_name we'll assume that name for this bill as well. '''
        package_name = None
        for s in self.subscriptions():
            if package_name:
                if s.package_name != package_name:
                    return None
            else:
                package_name = s.package_name
        return package_name

    @property
    def is_open(self):
        return self.closed_ts == None

    @property
    def is_closed(self):
        return self.closed_ts != None

    ############################################################################
    # URL Methods
    ############################################################################

    def get_absolute_url(self):
        return reverse('member:receipt', kwargs={'bill_id': self.id})

    def get_staff_url(self):
        return reverse('staff:billing:bill', kwargs={'bill_id': self.id})

    def get_admin_url(self):
        return reverse('admin:nadine_userbill_change', args=[self.id])

    ############################################################################
    # Resource Methods
    ############################################################################

    def resource_allowance(self, resource):
        ''' Look at the subscriptions added to this bill to determine the allowance for the given resource. '''
        query = self.subscriptions().filter(resource=resource)
        return query.aggregate(sum=Coalesce(Sum('allowance'), Value(0.00), output_field=DecimalField()))['sum']

    def resource_overage_rate(self, resource):
        ''' Look at the subscriptions added to this bill and determin the overage rate for the given resource. '''

        # Assume the overage rate for all subscriptions is the same.
        # This will cause problems if there are multiple subscriptions
        # with different rates!!! Since this should not be the case 99%
        # of the time I'm going to do it the simple way --JLS
        subscriptions = self.subscriptions().filter(resource=resource)
        if subscriptions:
            return subscriptions.first().overage_rate

        # No subscriptions means we'll use the default rate
        return resource.default_rate

    def resource_activity(self, resource):
        ''' Return all the activity for the given resource. '''
        if resource == Resource.objects.day_resource:
            return self.coworking_days()
        if resource == Resource.objects.event_resource:
            return self.events()

    @property
    def desk_allowance(self):
        return self.resource_allowance(Resource.objects.desk_resource)

    @property
    def mail_allowance(self):
        return self.resource_allowance(Resource.objects.mail_resource)

    @property
    def key_allowance(self):
        return self.resource_allowance(Resource.objects.key_resource)

    ###########################################################################
    # Subscription Methods
    ############################################################################

    def subscriptions(self):
        ''' Return all the ResourceSubscriptions associated with this bill. '''
        subscription_ids = SubscriptionLineItem.objects.filter(bill=self).values('subscription')
        return ResourceSubscription.objects.filter(id__in=subscription_ids)

    def has_subscription(self, subscription):
        ''' Return True if the given subscription is in the line items. '''
        return subscription in self.subscriptions()

    def add_subscription(self, subscription):
        ''' Create a line item for this ResourceSubscription. '''
        if self.has_subscription(subscription):
            return

        # Start with a generic description
        description = ""
        if subscription.package_name:
            description = subscription.package_name + " "
        description = description + subscription.resource.name + " Subscription "

        # Include the user if this is for an individual that is not the user of this bill
        if subscription.membership.is_individual:
            if subscription.membership.individualmembership.user != self.user:
                description += "(" + subscription.membership.who + ") "

        # If there is already a description on the subscription, add that
        if subscription.description:
            description += subscription.description + " "

        # If the subscription period falls outside this bill period,
        # add the date range to the description
        description_start = self.period_start
        if subscription.start_date > self.period_start:
            description_start = subscription.start_date
        description_end = self.period_end
        if subscription.end_date and subscription.end_date < self.period_end:
            description_end = subscription.end_date
        if description_start != self.period_start or description_end != self.period_end:
            description += "(%s to %s)" % (description_start, description_end)

        # Calculate our amount
        prorate = subscription.prorate_for_period(self.period_start, self.period_end)
        amount = prorate * subscription.monthly_rate

        # Create our new line item
        line_item = SubscriptionLineItem.objects.create(
            bill = self,
            subscription = subscription,
            description = description,
            amount = amount
        )
        self.add_lineitem_taxes(line_item.calculate_taxes())
        return line_item

    ###########################################################################
    # Coworking Day Methods
    ############################################################################

    def coworking_days(self):
        ''' Return all CoworkingDays associated with this bill. '''
        day_ids = CoworkingDayLineItem.objects.filter(bill=self).values('day')
        return CoworkingDay.objects.filter(id__in=day_ids)

    def includes_coworking_day(self, coworking_day):
        ''' Return True if the given day is in the line items. '''
        return coworking_day in self.coworking_days()

    def add_coworking_day(self, day):
        ''' Add the given coworking day to this bill. '''
        resource = Resource.objects.day_resource
        allowance = self.resource_allowance(resource)
        overage_rate = self.resource_overage_rate(resource)

        # Start building our description
        description = "%s %s" % (resource.name, day.visit_date)

        amount = 0
        if day.billable:
            billable_count = self.coworking_day_billable_count + 1
            if billable_count > allowance:
                amount = overage_rate
            description += " (%d) " % billable_count
        else:
            # Indicate why this isn't billed
            description += " (%s) " % day.payment

        # Append the username if it's different from this bill
        if day.user != self.user:
            description += " for " + day.user.username

        # Create the line item
        line_item = CoworkingDayLineItem.objects.create(
            bill = self,
            description = description,
            amount = amount,
            day = day,
        )
        self.add_lineitem_taxes(line_item.calculate_taxes())
        return line_item

    @property
    def coworking_day_count(self):
        ''' The number of coworking days on with this bill. '''
        return self.coworking_days().count()

    @property
    def coworking_day_billable_count(self):
        ''' The number of billable coworking days on this bill. '''
        return self.coworking_days().filter(payment="Bill").count()

    @property
    def coworking_day_allowance(self):
        ''' The number of coworking days allowed based on the subscriptions. '''
        return self.resource_allowance(Resource.objects.day_resource)

    @property
    def coworking_day_overage(self):
        ''' The number of billable coworking days over our allowance. '''
        if self.coworking_day_billable_count < self.coworking_day_allowance:
            return 0
        return self.coworking_day_billable_count - self.coworking_day_allowance

    ###########################################################################
    # Event Methods
    ############################################################################

    def events(self):
        ''' Return all Events associated with this bill. '''
        event_ids = EventLineItem.objects.filter(bill=self).values('event')
        return Event.objects.filter(id__in=event_ids)

    def includes_event(self, event):
        ''' Return True if the given event is in the line items. '''
        return event in self.events()

    def calculate_event_charge(self, event):
        # First check to see if there is a set charge on this event
        if event.charge:
            return event.charge
        else:
            if not event.room:
                raise Exception("Event must have room specified or a specific charge set.")

        if self.user.membership.active_subscriptions():
            total_hours = Decimal(self.event_hours_used + event.hours)
            overage = total_hours - self.event_hour_allowance
            if overage < 0:
                overage = Decimal(0.00)

        # Member only rooms get charged depending on subscriptions
        if event.room.members_only:
            return overage * float(self.event_hour_overage_rate)

        # Calculate the charge based on the default rate for the room
        if self.event_hour_allowance == 0:
            return event.room.default_rate
        else:
            # If there is an allowance, they are an active member and get a discount
            discount = Decimal(getattr(settings, "MEMBER_DISCOUNT_EVENTS", 1.0))
            return Decimal(event.room.default_rate) * discount * overage

    def calculate_event_description(self, event):
        day = str(event.start_ts.date())
        if event.description:
            return "Event on %s: %s" % (day, event.description)

        description = "Booking on %s for %s hours" % (day, event.hours)
        # Append the username if it's different from this bill
        if event.user != self.user:
            description += " by " + event.user.username
        return description

    def add_event(self, event):
        ''' Add the given event to this bill. '''
        logger.debug("add_event(%s)" % event)
        amount = self.calculate_event_charge(event)
        description = self.calculate_event_description(event)
        line_item = EventLineItem.objects.create(
            bill = self,
            description = description,
            amount = amount,
            event = event,
        )
        self.add_lineitem_taxes(line_item.calculate_taxes())
        return line_item

    @property
    def event_count(self):
        ''' The number of events on this bill. '''
        return self.events().count()

    @property
    def event_hours_used(self):
        ''' The number of event hours on this bill. '''
        hours = 0
        for event in self.events():
            hours += event.hours
        return hours

    @property
    def event_hour_allowance(self):
        ''' The number of event hours allowed based on the subscriptions. '''
        return self.resource_allowance(Resource.objects.event_resource)

    @property
    def event_hour_overage_rate(self):
        ''' The rate per hour if over the allowance. '''
        return self.resource_overage_rate(Resource.objects.event_resource)

    @property
    def event_hour_overage(self):
        ''' The number of hours over our allowance. '''
        if self.event_hours < self.event_hour_allowance:
            return 0
        return self.event_hours - self.event_hour_allowance

    ############################################################################
    # Tax Methods
    # TODO: Consider putting these in their own module.
    ############################################################################

    def calculate_taxes(self):
        ''' Calculates applicable taxes based on bill's line items '''
        taxes = []
        for item in SubscriptionLineItem.objects.filter(bill=self):
            taxes += item.calculate_taxes()
        for item in CoworkingDayLineItem.objects.filter(bill=self):
            taxes += item.calculate_taxes()
        for item in EventLineItem.objects.filter(bill=self):
            taxes = item.calculate_taxes()
        return taxes

    def add_lineitem_taxes(self, lineitem_taxes):
        ''' Applies line item taxes to bill (saves them to DB) '''
        for (lineitem_tax) in lineitem_taxes:
            lineitem_tax.save()

    def total_tax_applied(self):
        ''' Provide the total amount of tax added to bill '''
        total = 0
        for _,amount in self.total_tax_applied_by_rate():
            total += amount
        return total

    def total_tax_applied_by_rate(self):
        ''' Produce list of tax rates and amounts (rate<TaxRate>, amount<Decimal>) '''
        taxes = []
        for taxrate in TaxRate.objects.all():
            total = self.total_tax_applied_for_rate(taxrate)
            taxes.append((taxrate, total))
        return taxes

    def total_tax_applied_for_rate(self, rate):
        ''' Total tax amount applied for given rate '''
        total = 0
        for item in BillLineItem.objects.filter(bill=self):
            tax = item.get_applied_tax(rate)
            if tax is not None:
                total += tax.amount
        return total

    ############################################################################
    # Other Methods
    ############################################################################

    def recalculate(self):
        ''' Recalculate bill by evaluating all subscriptions and activity. '''
        logger.info("Recalculating bill %d for %s" % (self.id, self.user))

        # Hold on to the existing data
        subscriptions = list(self.subscriptions())
        coworking_days = list(self.coworking_days().order_by('visit_date'))
        custom_items = list(self.line_items.filter(custom=True))
        total_before = self.amount

        # Delete all the line items
        for line_item in self.line_items.all():
            line_item.delete()

        # Add everything back
        for s in subscriptions:
            self.add_subscription(s)
        for d in coworking_days:
            self.add_coworking_day(d)
        for c in custom_items:
            c.save()

        # Recalculate the cache as well
        self.update_cached_totals()

        logger.debug("Previous amount: %s, New amount: %s" % (total_before, self.amount))

    def combine(self, bill, recalculate=True):
        ''' Combine the given bill with this bill. '''
        if bill.user != self.user:
            raise Exception("Can not combine bills from different users (%s and %s)" % (self.user, bill.user))

        # Change the dates
        if bill.period_start < self.period_start:
            self.period_start = bill.period_start
        if bill.period_end > self.period_end:
            self.period_end = bill.period_end
        if bill.due_date > self.due_date:
            self.due_date = bill.due_date
        self.save()

        # Pull all the line items in to memory and delete the other bill
        subscriptions = list(bill.subscriptions())
        coworking_days = list(bill.coworking_days())
        events = list(bill.events())
        custom_items = list(bill.line_items.filter(custom=True))
        bill.delete()

        # Add all the subscriptions, days, and custom items
        for s in subscriptions:
            self.add_subscription(s)
        for d in coworking_days:
            self.add_coworking_day(d)
        for e in events:
            self.add_event(e)
        for line_item in custom_items:
            line_item.bill = self
            line_item.save()

        if recalculate:
            self.recalculate()

    def update_cached_totals(self):
        self.cached_total_amount = self.amount
        self.cached_total_tax_amount = self.tax_amount
        self.cached_total_paid = self.total_paid
        self.cached_total_owed = self.total_owed
        self.save()

    def close(self):
        if self.closed_ts != None:
            raise Exception("Bill is already closed!")
        self.closed_ts = localtime(now())
        self.in_progress = False
        self.save()

class BillLineItem(models.Model):
    bill = models.ForeignKey(UserBill, related_name="line_items", null=True, on_delete=models.CASCADE)
    description = models.CharField(max_length=200)
    amount = models.DecimalField(max_digits=7, decimal_places=2, default=0)
    custom = models.BooleanField(default=False)

    @property
    def applicable_taxes(self):
        resource = self.get_resource()
        return resource.taxrate_set.all()

    @cached_property
    def applied_taxes(self):
        return self.lineitemtax_set.all()

    """ Get an instance of tax if rate has been applied """
    def get_applied_tax(self, rate):
        return self.lineitemtax_set.filter(tax_rate=rate.id).first()

    def has_tax_applied(self, rate):
        return self.get_applied_tax(rate) is not None

    def calculate_taxes(self):
        taxes = []
        if self.amount == 0:
            return taxes
        for rate in self.applicable_taxes:
            tax = self.get_applied_tax(rate)
            # Ensure we only calculate tax once.
            if tax is None:
                tax = LineItemTax(
                    line_item=self,
                    tax_rate=rate,
                    amount=self.calculate_tax_amount(rate)
                )
            taxes.append(tax)
        return taxes

    def calculate_tax_amount(self, rate):
        return self.amount * rate.percentage

    def __str__(self):
        return self.description

class SubscriptionLineItem(BillLineItem):
    subscription = models.ForeignKey('ResourceSubscription', related_name="line_items", on_delete=models.CASCADE)

    def get_resource(self):
        return self.subscription.resource

class CoworkingDayLineItem(BillLineItem):
    day = models.OneToOneField('CoworkingDay', related_name="line_item", on_delete=models.CASCADE)

    def get_resource(self):
        return Resource.objects.resource_by_key(Resource.objects.DAY_KEY)

class EventLineItem(BillLineItem):
    event = models.OneToOneField('Event', related_name="line_item", on_delete=models.CASCADE)

    def get_resource(self):
        return Resource.objects.resource_by_key(Resource.objects.EVENT_KEY)

class PaymentMethod(models.Model):
    name = models.CharField(max_length=128, help_text="e.g., Stripe, Visa, cash, bank transfer")

    def __str__(self):
        return self.name

class Payment(models.Model):
    created_ts = models.DateTimeField(auto_now_add=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="+", null=True, blank=True, on_delete=models.CASCADE)
    bill = models.ForeignKey(UserBill, on_delete=models.CASCADE)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    amount = models.DecimalField(max_digits=7, decimal_places=2, default=0)
    method = models.ForeignKey(PaymentMethod, on_delete=models.SET_NULL, null=True, blank=True)
    external_id = models.CharField(max_length=128, null=True, blank=True, help_text="ID used by payment service")
    note = models.TextField(blank=True, null=True, help_text="Private notes about this payment")

    def __str__(self):
        return "%s: %s - $%s" % (str(self.created_ts)[:16], self.user, self.amount)

class StripeBillingProfile(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    customer_email = models.EmailField(help_text="Customer email address used with Stripe customer record")
    customer_id = models.CharField(max_length=128, help_text="Stripe customer ID used for billing via Stripe")

    def __str__(self):
        return "{} ({}): {}".format(self.user, self.customer_email, self.customer_id)

class TaxRate(models.Model):
    name = models.CharField(max_length=256, help_text="The name of the tax")
    percentage = models.DecimalField(max_digits=3, decimal_places=2, help_text="Tax percentage")
    resources = models.ManyToManyField(Resource)

    def __str__(self):
        return "{} ({}%)".format(self.name, self.percentage * 100)

class LineItemTax(models.Model):
    line_item = models.ForeignKey(BillLineItem, on_delete=models.CASCADE)
    tax_rate = models.ForeignKey(TaxRate, on_delete=models.CASCADE)
    amount = models.DecimalField(max_digits=7, decimal_places=2)

    def __str__(self):
        return "{}, {} for {}".format(self.line_item.bill, self.tax_rate, self.line_item)

    @cached_property
    def calculate_tax_rate(self):
        # Figure out the tax rate from amount and line_item.amount
        pass

# Copyright 2021 Office Nomads LLC (https://officenomads.com/) Licensed under the AGPL License, Version 3.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.gnu.org/licenses/agpl-3.0.html. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Tests for the corpus module."""

import logging
from unittest import TestCase

from dphon.corpus import PlaintextCorpusLoader

class TestPlaintextCorpusLoader(TestCase):
    """Test the PlaintextCorpusLoader."""

    def setUp(self) -> None:
        """Create a loader for testing."""
        self.load = PlaintextCorpusLoader()

    def test_no_files(self) -> None:
        """should error if no valid files were found"""
        # pass an empty list of paths; should error since no files found
        with self.assertRaises(SystemExit):
            with self.assertLogs(level="ERROR"):
                for _ in self.load([]):
                    pass

    def test_non_file(self) -> None:
        """should warn if passed something that isn't plaintext"""
        # Pass a python file and a text file; should warn
        # re-enable logging so we can check it in the test
        logging.disable(logging.INFO)
        with self.assertLogs(level="WARNING"):
            for _ in self.load(["tests/unit/test_corpus.py",
                                "tests/fixtures/laozi/laozi.txt"]):
                pass

    def test_single_file(self) -> None:
        """should load contents of a single file"""
        # Use a tiny file with no whitespace, etc. to strip
        path = "tests/fixtures/laozi/tiny.txt"
        docs = list(self.load([path]))
        self.assertEqual(docs[0][1], {"id": "tiny"})
        with open(path, encoding="utf8") as file:
            self.assertEqual(docs[0][0], file.read())

    def test_glob(self) -> None:
        """should load all text files in a directory via glob"""
        docs = list(self.load(["tests/fixtures/laozi/*.txt"]))
        self.assertEqual(len(docs), 4)
        doc_ids = [doc[1]["id"] for doc in docs]
        self.assertIn("gd_laozi", doc_ids)
        self.assertIn("tiny", doc_ids)
        self.assertIn("mwd_laozi", doc_ids)
        self.assertIn("laozi", doc_ids)

    def test_file_and_glob(self) -> None:
        """should allow combination of files and globs"""
        docs = list(self.load(["tests/fixtures/laozi/*laozi.txt",
                               "tests/fixtures/laozi/tiny.txt"]))
        self.assertEqual(len(docs), 4)
        doc_ids = [doc[1]["id"] for doc in docs]
        self.assertIn("gd_laozi", doc_ids)
        self.assertIn("tiny", doc_ids)
        self.assertIn("mwd_laozi", doc_ids)
        self.assertIn("laozi", doc_ids)


from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.chrome.service import Service
import pandas as pd
import time
import json
import os
import datetime
from urllib.parse import urlparse
import csv
import re

# Obtener la fecha actual
fecha_actual = datetime.datetime.now()
# Formatear la fecha en AAAAMMDD
fecha_formateada = fecha_actual.strftime("%Y%m%d")
# Formatear la hora en HHMM
hora_formateada = fecha_actual.strftime("%H%M")
# Combinar la fecha y la hora con _
fecha_resultante = fecha_formateada + "_" + hora_formateada
# fecha_resultante = '20230723_2026'
# Obtener la ruta absoluta del directorio actual
directorio_actual = os.path.abspath(os.path.dirname(__file__))
# Nombre del directorio donde deseas guardar el archivo
directory_schedule = 'schedule'
# Crear la ruta completa del directorio
ruta_schedule = os.path.join(directorio_actual, directory_schedule)
# Comprobar si el directorio existe, si no, crearlo
if not os.path.exists(ruta_schedule):
    os.makedirs(ruta_schedule)
# Nombre del archivo que deseas guardar
nombre_archivo_schedule = fecha_resultante +'.txt'
nombre_archivo_schedule_only_link = fecha_resultante +'_link.txt'
# Combinar la ruta del directorio actual con el nombre del archivo
ruta_archivo_schedule = os.path.join(ruta_schedule, nombre_archivo_schedule)
ruta_archivo_schedule_only_link = os.path.join(ruta_schedule, nombre_archivo_schedule_only_link)

# ubicacion = "C:/Users/yfigueroa/Documents/GitHub/yfsDataBase/NBA2023/chromedriver"
# driver = webdriver.Chrome(ubicacion)
# Especifica la ruta al ejecutable de ChromeDriver
# para actualizar el chromedriver https://googlechromelabs.github.io/chrome-for-testing/#stable
ruta_chrome_driver = 'C:/Users/yfigueroa/Documents/GitHub/yfsDataBase/chromedriver.exe'  # Sustituye por tu ruta real
# Crea un objeto Service con la ruta del ejecutable
servicio_chrome = Service(ruta_chrome_driver)
# Crea una instancia de Chrome WebDriver utilizando el objeto Service
driver = webdriver.Chrome(service=servicio_chrome)

listGameTemp = []
listGameLink = []
listGameLinkOnly = []

# url de la data
schedule_link = "https://www.baseball-reference.com/leagues/majors/2023-schedule.shtml"
driver.get(schedule_link)
page = BeautifulSoup(driver.page_source,'html.parser')
#   div_4649142714
#   div_2554848825
#   div_4800524807
# div_1998533194
# div_9550324858
# div_3799355433
# div_1762894190 postseason
# div_2906366340 postseason

div_schedule = page.find('div', id='div_2906366340')
divs = div_schedule.find_all('div')
for div in divs:
    div_h3 = div.find('h3')
    listGameLink.append([div_h3.text])

    div_ps = div.find_all('p')
    # print(div_ps)
    # Bandera para saltar el último ciclo
    skip_last = False

    for i,div_p in enumerate(div_ps):
        # Verificamos si estamos en la penúltima iteración
        if i == len(div_ps) - 1:
            skip_last = True

        # Si la bandera es True, continuamos al siguiente ciclo sin hacer nada
        if skip_last:
            continue

        # print(div_p)
        p_em_a = div_p.find('em').find('a')
        listGameLink.append([p_em_a.text,p_em_a['href']])
        listGameLinkOnly.append(p_em_a['href'])

time.sleep(5)

with open(ruta_archivo_schedule, 'w') as archivo:
    for fila in listGameLink:
       result = ''
       for element in fila:
           result += str(element) +';'
       result = result[:-1] + '\n'
       archivo.write(result)
with open(ruta_archivo_schedule_only_link, 'w') as archivo:
    for fila in listGameLinkOnly:
       archivo.write("'" + str(fila) + "'," + "\n")


import discord
from imdb_manager import IMDbMovieData, IMDbActorData

class GlobalDiscordMethods:
    latest_movie_query = None

    @classmethod
    async def display_actor_in_chat(cls, name, ctx):
        actor = IMDbActorData(name)

        embedded_msg = discord.Embed(title=str(actor.actor_obj), description="%s" % str(actor.biography),
                                     color=0x00ff00)

        name.replace(" ", "_")
        embedded_msg.add_field(name="IMDb page", value=f"{actor.url}\n\n"
                                                       f"View more entries for this query at:"
                                                       f"\nhttps://www.imdb.com/find?q={name.replace(' ', '_')}", inline=False)

        embedded_msg.set_thumbnail(url=actor.thumbnail)
        embedded_msg.set_image(url=actor.cover)

        await ctx.send(embed=embedded_msg)

    @classmethod
    async def display_movie_in_chat(cls, title, ctx):
        imdb = IMDbMovieData(title)
        GlobalDiscordMethods.latest_movie_query = imdb
        print("latest_movie_query set: " + GlobalDiscordMethods.latest_movie_query.title)

        embedded_msg = discord.Embed(title=imdb.title, description=imdb.plot, color=0x00ff00)
        embedded_msg.set_thumbnail(url=imdb.image)
        embedded_msg.add_field(name="Director", value=imdb.director, inline=False)
        embedded_msg.add_field(name="Cast", value=imdb.stars, inline=False)
        embedded_msg.add_field(name="Genres", value=imdb.genre, inline=False)
        embedded_msg.add_field(name="IMDb rating", value=imdb.rating, inline=False)
        embedded_msg.add_field(name="IMDb link", value=imdb.url, inline=False)
        embedded_msg.add_field(name="Release year", value=imdb.year, inline=False)
        embedded_msg.add_field(name="Runtime", value=f"{imdb.runtime}"
                                                     f"\n\nView more entries for this query at:"
                                                     f"\nhttps://www.imdb.com/find?q={imdb.title.replace(' ', '_')}"
                                                     f"\n\nWant to add this title to your watchlist? Type '!temflix save'", inline=False)

        await ctx.send(embed=embedded_msg)


from pathlib import Path
from typing import Iterable, Iterator, Optional

from pipeline.es_doc import EsDoc
from pipeline.global_infra import GlobalInfra
from util.print import eprint

class Group:
    def __init__(
        self,
        name: str,
        logs: Iterable[EsDoc] = [],
        global_infra: Optional[GlobalInfra] = None,
        generation: Optional[int] = None,  # an optional integer to use in the safe name's suffix
    ):
        self.logs = logs
        self.name = name
        self.global_infra = global_infra
        self._generation = generation

    def __str__(self) -> str:
        """Logging-friendly representation"""
        optional_fields = []
        url = self.job_url()
        if url:
            optional_fields.append(f"url={url}")
        return f"<Group name={self.name} {' '.join(optional_fields)}>"

    def infer_global_infra(self) -> None:
        eprint(f"Inferring global infra for {str(self)}... (Log stream will be fully loaded into memory)")
        self.logs = list(self.logs)
        self.global_infra = GlobalInfra.fromLogs(self.logs)
        eprint(f"Done inferring global infra for {str(self)}.")

    @classmethod
    def group_name_to_pot_name(Self, gname: str) -> str:
        return gname.split("__")[-1].split("-")[0]

    def pot_name(self) -> str:
        # Example group ID: "boundary_nodes_pre_master__boundary_nodes_pot-2784039865"
        # Corresponding pot name: "boundary_nodes_pot"
        return self.group_name_to_pot_name(self.name)

    @staticmethod
    def is_group_name_local(jid: str) -> bool:
        return sum([1 for c in jid if c == "-"]) > 1

    def job_id(self) -> Optional[int]:
        # Example group IDs:
        # "boundary_nodes_pre_master__boundary_nodes_pot-2784039865"
        #   ==> test ran in CI
        # "boundary_nodes_pre_master__boundary_nodes_pot-username-zh1-spm99_zh7_dfinity_network-2784039865"
        #   ==> test ran locally (no CI job exists; None will be returned)
        # "boundary_nodes_pre_master__boundary_nodes_pot-2784039865--pseudo"
        #   ==> test ran in CI; replaying it via local log; CI job should exist
        # Corresponding job ID: 2784039865
        # Note:
        # - A group name might not have a CI job ID associated with it if it has been created for mainnet logs
        #   In that case, this method will return None.
        original_name = self.name.replace("--pseudo", "")
        if self.is_group_name_local(original_name):
            return None
        suffix = original_name.split("-")[-1]
        if suffix.isdigit():
            return int(suffix)
        return None

    def job_url(self) -> Optional[str]:
        id = self.job_id()
        if not id:
            return None
        else:
            return f"https://gitlab.com/dfinity-lab/public/ic/-/jobs/{id}"

    def safe_name(self) -> str:
        """Similar to self.name but avoids overriding existing files"""
        if self._generation is None:
            return self.name
        return f"{self.name}--{self._generation}"

    @staticmethod
    def _safe_log_stream(file: Path) -> Iterator[EsDoc]:
        with open(file, "r", encoding="utf-8") as fh:
            while True:
                line = fh.readline()
                if not line:
                    return
                obj_str = line.strip().strip("[],")
                if not obj_str:
                    continue
                repr = eval(obj_str)
                yield EsDoc(repr)

    @classmethod
    def _StreamFromFile(Self, log_file: Path) -> "Group":
        """Create group and set up a logs stream"""
        gname = Path(log_file).stem.split(".")[0] + "--pseudo"
        return Group(gname, logs=Self._safe_log_stream(log_file))

    @classmethod
    def _LoadFromFile(Self, log_file: Path) -> "Group":
        """Create group and load logs from file"""
        eprint(f"Loading logs from {log_file} ...")
        with open(log_file, "r") as input_file:
            all_doc_reprs = eval(input_file.read())
            logs = [EsDoc(repr) for repr in all_doc_reprs]
        eprint(f"Done loading logs from {log_file}.")
        # Example file name: "boundary_nodes_pre_master__boundary_nodes_pot-2784039865--0.raw.log"
        components = log_file.stem.split("--")
        return Group(name=components[0], logs=logs, generation=len(components) - 1)

    @classmethod
    def fromFile(Self, log_file: Path, as_stream=True) -> "Group":
        if as_stream:
            return Self._StreamFromFile(log_file)
        else:
            return Self._LoadFromFile(log_file)


import pandas as pd
import json
import math as m

df = pd.read_excel('data/BD_Docentes.xlsx', header=0)

members = []

def getAreaReference(arg):
    switcher = {
        'SOCIO-HUMANÍSTICA': {
            'label': arg,
            'reference':'academic-areas/socio-humanistica'
        },
        'ADMINISTRATIVA': {
            'label': arg,
            'reference':'academic-areas/administrativa'
        },
        'BIOLÓGICA Y BIOMÉDICA': {
            'label': arg,
            'reference':'academic-areas/biologica-biomedica'
        },
        'TÉCNICA': {
            'label': arg,
            'reference':'academic-areas/tecnica'
        }
    }
    return switcher.get(arg, 'Invalid area')

def getDepartmentReference(arg):
    switcher = {
        'ARQUITECTURA Y ARTES':{
            'label': arg,
            'reference':'departments/arquitectura-artes'
        },
        'CIENCIAS BIOLÓGICAS':{
            'label': arg,
            'reference':'departments/ciencias-biologicas'
        },
        'CIENCIAS DE LA SALUD':{
            'label': arg,
            'reference':'departments/ciencias-salud'
        },
        'CIENCIAS EMPRESARIALES':{
            'label': arg,
            'reference':'departments/ciencias-empresariales'
        },
        'CIENCIAS DE LA COMPUTACIÓN Y ELECTRÓNICA':{
            'label': arg,
            'reference':'departments/ciencias-computacion-electronica'
        },
        'CIENCIAS DE LA COMUNICACIÓN':{
            'label': arg,
            'reference':'departments/ciencias-comunicacion'
        },
        'PSICOLOGÍA':{
            'label': arg,
            'reference':'departments/psicologia'
        },
        'QUÍMICA Y CIENCIAS EXACTAS':{
            'label': arg,
            'reference':'departments/quimica-ciencias-exactas'
        },
        'GEOLOGÍA Y MINAS E INGENIERÍA CIVIL':{
            'label': arg,
            'reference':'departments/geologia-minas-ingenieria-civil'
        },
        'CIENCIAS JURÍDICAS':{
            'label': arg,
            'reference':'departments/ciencias-juridicas'
        },
        'ECONOMÍA':{
            'label': arg,
            'reference':'departments/economia'
        },
        'CIENCIAS DE LA EDUCACIÓN':{
            'label': arg,
            'reference':'departments/ciencias-educacion'
        }
    }
    return switcher.get(arg, 'Invalid dept')

def getSectionReference(arg):
    switcher = {
        'FILOSOFÍA Y TEOLOGÍA':{
            'label': arg,
            'reference':'sections/filosofia-teologia'
        },
        'LENGUAS CONTEMPORÁNEAS':{
            'label': arg,
            'reference':'sections/lenguas-contemporaneas'
        },
        'LENGUAS HISPÁNICAS Y LITERATURA':{
            'label': arg,
            'reference':'sections/lenguas-hispanicas-literatura'
        },
        'PEDAGOGÍA DE LAS CIENCIAS EXPERIMENTALES':{
            'label': arg,
            'reference':'sections/pedagogia-ciencias-experimentales'
        },
        'PSICOPEDAGOGÍA':{
            'label': arg,
            'reference':'sections/psicopedagogia'
        },
        'ARQUITECTURA Y URBANISMO':{
            'label': arg,
            'reference':'sections/arquitectura-urbanismo'
        },
        'ARTE, TEORÍA Y CONSERVACIÓN DEL PATRIMONIO':{
            'label': arg,
            'reference':'sections/arte-teoria-conservacion-patrimonio'
        },
        'BIOLOGÍA Y GENÉTICA':{
            'label': arg,
            'reference':'sections/biologia-genetica'
        },
        'BIOTECNOLOGIA Y PRODUCCION':{
            'label': arg,
            'reference':'sections/biotecnologia-produccion'
        },
        'CIENCIAS DE LA EDUCACIÓN BÁSICA':{
            'label': arg,
            'reference':'sections/ciencias-educacion-basica'
        },
        'CLÍNICO QUIRÚRGICA':{
            'label': arg,
            'reference':'sections/clinico-quirurgica'
        },
        'COMUNICACIÓN ORGANIZACIONAL':{
            'label': arg,
            'reference':'sections/comunicacion-organizacional'
        },
        'COMUNICACIÓN TECNOLOGIAS':{
            'label': arg,
            'reference':'sections/comunicacion-tecnologias'
        },
        'CONTABILIDAD Y AUDITORIA':{
            'label': arg,
            'reference':'sections/contabilidad-auditoria'
        },
        'DERECHO PRIVADO':{
            'label': arg,
            'reference':'sections/derecho-privado'
        },
        'DERECHO PÚBLICO':{
            'label': arg,
            'reference':'sections/derecho-publico'
        },
        'DESARROLLO ECONÓMICO':{
            'label': arg,
            'reference':'sections/desarrollo-economico'
        },
        'DISEÑO Y EXPRESIÓN ARTÍSTICA':{
            'label': arg,
            'reference':'sections/diseno-expresion-artistica'
        },
        'ECOLOGÍA Y SISTEMÁTICA':{
            'label': arg,
            'reference':'sections/ecologia-sistematica'
        },
        "ELECTRONICA Y TELECOMUNICACIONES": {
            'label': arg,
            "reference": "sections/electronica-telecomunicaciones",
        },
        "ESTRUCTURAS, TRANSPORTE Y CONSTRUCCION": {
            'label': arg,
            "reference": "sections/estructuras-transporte-construccion",
        },
        "EVALUACIÓN Y DIAGNÓSTICO PSICOLÓGICO": {
            'label': arg,
            "reference": "sections/evaluacion-diagnostico-psicologico",
        },
        "FILOSOFÍA Y TEOLOGÍA":{
            'label': arg,
            "reference": "sections/filosofia-teologia",
        },
        "FINANZAS Y GESTIÓN BANCARIA":{
            "label": arg,
            "reference": "sections/finanzas-gestion-bancaria"
        },
        "FISICOQUIMÍCA Y MATEMÁTICAS":{
            "label": arg,
            "reference": "sections/fisicoquimica-matematicas"
        },
        "GENÉTICA HUMANA, MICROBIOLOGÍA Y BIOQUÍMICA CLÍNICA":{
            "label": arg,
            "reference": "sections/genetica-humana-microbiologia-bioquimica-clinica"
        },
        "GEODINÁMICA, MINERÍA Y METALURGIA":{
            "label": arg,
            "reference": "sections/geodinamica-mineria-metalurgia"
        },
        "HOTELERÍA Y TURISMO":{
            "label": arg,
            "reference": "sections/hoteleria-turismo",
        },
        "INGENIERÍA AMBIENTAL":{
            "label": arg,
            "reference": "sections/ingenieria-ambiental",
        },
        "INGENIERÍA DE PROCESOS":{
            "label": arg,
            "reference": "sections/ingenieria-procesos"
        },
        "INGENIERÍA DE SOFTWARE Y GTI": {
            "label": arg,
            "reference": "sections/ingenieria-software-gti",
        },
        "INGENIERÍA ARTIFICIAL":{
            "reference": "sections/inteligencia-artificial",
            "label": arg,
        },
        "INTERVENCIÓN PSICOLÓGICA":{
            "reference": "sections/intervencion-psicologica",
            "label": arg,
        },
        "MANEJO Y GESTIÓN DE RECURSOS NATURALES":{
            "reference": "sections/manejo-gestion-recursos-naturales",
            "label": arg
        },
        "MÉTODOS CUANTITATIVOS":{
            "reference": "sections/metodos-cuantitativos",
            "label": arg
        },
        "NARRATIVAS AUDIOVISUALES":{
            "reference": "sections/narrativas-audiovisuales",
            "label": arg
        },
        "NUEVAS TENDENCIAS DEL DERECHO":{
            "reference": "sections/nuevas-tendencias-derecho",
            "label": arg
        },
        "ORGANIZACIÓN Y GESTIÓN EMPRESARIAL":{
            "reference": "sections/organizacion-gestion-empresarial",
            "label": arg
        },
        "PEDAGOGÍA DE LAS CIENCIAS EXPERIMENTALES":{
            "reference": "sections/pedagogia-las-ciencias-experimentales",
            "label": arg
        },
        "POLÍTICAS PÚBLICAS":{
            "reference": "sections/politicas-publicas",
            "label": arg
        },
        "PRECLÍNICA":{
            "reference": "sections/preclinica",
            "label": arg
        },
        "PSICOLOGÍA BÁSICA":{
            "reference": "sections/psicologia-basica",
            "label": arg
        },
        "PSICOLOGÍA EVOLUTIVA":{
            "reference": "sections/psicologia-evolutiva",
            "label": arg
        },
        "PSICOPEDAGOGÍA":{
            "reference": "sections/psicopedagogia",
            "label": arg
        },
        "QUÍMICA BÁSICA Y APLICADA": {
            "reference": "sections/quimica-basica-aplicada",
            "label": arg
        },
        "RECURSOS HÍDRICOS":{
            "reference": "sections/recursos-hidricos",
            "label": arg
        },
        "SOCIOHUMANÍSTICA, SALUD PÚBLICA Y GESTIÓN EN SALUD":{
            "reference": "sections/sociohumanistica-salud-publica-gestion-en-salud",
            "label": arg
        },
        "TECNOLOGIAS AVANZADAS DE LA WEB Y SBC":{
            "reference": "sections/tecnologias-avanzadas-web-sbc",
            "label": arg
        },
        "TEORÍA ECONÓMICA":{
            "reference": "sections/teoria-economica",
            "label": arg
        }
    }
    return switcher.get(arg, 'Invalid section')

for index, row in df.iterrows():
    if(type(row['mail']) is str):
        level_fourth = [row['level_fourth']]
        level_third = [row['level_third']]

        temp = {
            "id": row['mail'],
            "ci":row['ci'],
            "mail":row['mail'],
            "area": getAreaReference(row['area']),
            "department":getDepartmentReference(row['department']),
            "displayName":row['displayName'],
            "level_fourth": level_fourth,
            "level_third": level_third,
            "modality": row['modality'],
            "section":getSectionReference(row['section'])
        }
        members.append(temp)

with open('users.json', 'w',encoding='utf8') as outfile:
    json.dump(members, outfile, ensure_ascii=False, indent=4)


import ast
from sklearn_crfsuite import CRF
from sklearn.model_selection import train_test_split
from inspect import Attribute
import warnings
from sklearn_crfsuite import CRF
from sklearn.metrics import f1_score
from unidecode import unidecode
from sklearn.metrics import classification_report, recall_score, precision_score, accuracy_score

encoding = "utf-8"

def get_otomi_corpus():
    try:
        sentences = []
        with open("corpus_otomi.txt", "r", encoding=encoding) as file:
            for line in file:
                try:
                    # Evaluar la línea como una expresión de Python y guardarla en una variable
                    sentences.append(ast.literal_eval(line.strip()))
                except Exception as e:
                    print(f"Error al evaluar la línea: {line}")
                    print(e)
        return sentences
    except FileNotFoundError:
        print("El archivo 'corpus_otomi.txt' no se encontró en la carpeta actual.")
    except Exception as e:
        print("Ocurrió un error al abrir el archivo:")
        print(e)

def format_sentence(sent: list):
    data = []
    for word in sent:
        _word = {}
        _word["tag"] = word[-1]
        _word["word"] = "".join(map(lambda s: s[0],word[:-1]))
        _word["morphemes"] = word[:-1]
        data.append(_word)
    return data

def map_tag(tag: str, tags_map : list) -> str:
    return tags_map.get(tag.lower(), "N/F")

def word_to_features(sent, i):
    word = sent[i]
    stem = list(filter(lambda x: x[1] == "stem", word['morphemes']))
    # para las features tomé las que nos dió la profesora como sugerencia
    features = {
        'word.lower()': word["word"].lower(),
        'word[:3]': word["word"][:3],
        'word[:2]': word["word"][:2],
        'word.isdigit()': word["word"].isdigit(),
        'length': len(word["word"]),
        'position': i
    }

    if len(stem) > 0: features.update({'stem': stem[0][0]})

    return features

# Extract features and labels
def sent_to_features(sent):
    return [word_to_features(sent, i) for i in range(len(sent))]

def sent_to_labels(sent):
    return [label for token, label in sent]

corpus = get_otomi_corpus()
data = [format_sentence(s) for s in corpus]

# Prepare data for CRF
X = [[word_to_features(sent, i) for i in range(len(sent))] for sent in data]
y = [[unidecode(word["tag"]) for word in sent] for sent in data]

# split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# para tener una salida limpia y que no se vean los warnings
with warnings.catch_warnings():
    warnings.simplefilter("ignore")

    # Initialize and train the CRF tagger: https://sklearn-crfsuite.readthedocs.io/en/latest/api.html
    crf = CRF(algorithm='lbfgs', c1=0.1, c2=0.1, max_iterations=100, all_possible_transitions=True, verbose=True)

    try:
        crf.fit(X_train, y_train)
    except AttributeError as e:
        print(e)

    y_pred = crf.predict(X_test)

    # Flatten the true and predicted labels
    y_test_flat = [label for sent_labels in y_test for label in sent_labels]
    y_pred_flat = [label for sent_labels in y_pred for label in sent_labels]

    # Evaluate the model
    report = classification_report(y_true=y_test_flat, y_pred=y_pred_flat)
    print("\n\n******* Evaluación del modelo *******")
    print(report)
    print("******* ACCURACY *******")
    print(accuracy_score(y_pred_flat, y_test_flat))
    print("******* PRESICION *******")
    print(precision_score(y_pred_flat, y_test_flat, average="macro"))
    print("******* RECALL *******")
    print(recall_score(y_pred_flat, y_test_flat, average="macro"))
    print("******* F1_SCORE *******")
    print(f1_score(y_pred_flat, y_test_flat, average="macro"))
    print("\n\n\n******* Ejemplo de oración etiquetada *******")
    sentence = X_test[0]
    string = " ".join([w['word.lower()'] for w in sentence])
    print(f"\nOración: {string}")
    prediction = crf.predict([sentence])
    print(f"\nEtiquetado obtenido: {', '.join(prediction[0])}")
    print(f"\nEtiquetado correcto: {', '.join(y_test[0])}")


import json, ast
from optparse import OptionParser
from datetime import datetime
from elasticsearch import Elasticsearch

def get_options():
    parser=OptionParser()
    parser.add_option('-s', '--es-host', dest='es_host', help='ES host name')
    parser.add_option('-o', dest='es_output', help='ES Output file name')
    (options, args) = parser.parse_args()

    for arg in required_args():
        if getattr(options, arg) is None:
            parser.error('Missing required argument %s' %arg)

    return options

def required_args():
    return ['es_host']

if __name__ == '__main__':
    start_time = datetime.now()
    options = get_options()


from rest_framework import serializers

from subscriptions.models import SubscriptionPlan

class SubscriptionPlanSerializer(serializers.ModelSerializer):
    apple_product_id = serializers.SerializerMethodField()
    price = serializers.SerializerMethodField()

    def get_apple_product_id(self, plan):
        if 'request' not in self.context:
            return plan.apple_product_id

        user = self.context['request'].user
        if user.is_anonymous:
            return plan.apple_product_id
        return plan.apple_product_id_notrial

    def get_price(self, plan):
        return str(plan.get_price_card().price)

    class Meta:
        model = SubscriptionPlan
        fields = (
            'id',
            'name',
            'price',
            'period',
            'trial_days',
            'apple_product_id',
            'google_product_id',
        )


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

import requests
from bs4 import BeautifulSoup
import time
import random

link = "http://www.santostang.com/"

def scrap(link):
    headers = {'User-Agent' : 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'}
    r = requests.get(link, headers= headers)
    html = r.text
    soup = BeautifulSoup(html, "lxml")
    return soup

soup = scrap(link)
title_list = soup.find_all("h1", class_="post-title")
for eachone in title_list:
    url = eachone.a['href']
    print ('开始爬取这篇博客: ', url)
    soup_article = scrap(url)
    title = soup_article.find("h1", class_="view-title").text.strip()
    print ('这篇博客的标题为: ', title)
    sleep_time = random.randint(0,2) + random.random()
    print ('开始休息: ', sleep_time, '秒')
    time.sleep(sleep_time)

'''
scrap_times = 0
for eachone in title_list:
    url = eachone.a['href']
    print ('开始爬取这篇博客: ', url)
    soup_article = scrap(url)
    title = soup_article.find("h1", class_="view-title").text.strip()
    print ('这篇博客的标题为: ', title)

    scrap_times += 1
    if scrap_times % 5 == 0:
        sleep_time = 10 + random.random()
    else:
        sleep_time = random.randint(0,2) + random.random()
    time.sleep(sleep_time)
    print ('开始休息: ', sleep_time, '秒')
'''


#https://lda.readthedocs.io/en/latest/autoapi/lda/index.html
import numpy as np
import pandas as pd
import lda
import re
import os

import gensim
from gensim.utils import simple_preprocess
import nltk
nltk.download('stopwords')
from nltk.corpus import stopwords
import gensim.corpora as corpora
from pprint import pprint

import pickle
import pyLDAvis

#https://radimrehurek.com/gensim/models/ldamulticore.html
nw = 5
alpha = "auto"
eta = "auto"
passes = 200

def sent_to_words(sentences):
    for sentence in sentences:
        # deacc=True removes punctuations
        yield(gensim.utils.simple_preprocess(str(sentence), deacc=True))

def remove_stopwords(texts):
    return [[word for word in simple_preprocess(str(doc))
             if word not in stop_words] for doc in texts]

df = pd.read_csv("dataset.csv", dtype=object)
for index, row in df.iterrows():
    print("- Record {}/{}".format(str(index+1), str(len(df.index))))

    # Remove punctuation
    # Convert the titles to lowercase
    row['article_processed'] = str(row['article']).lower()
    row['article_processed'] = re.sub('[,!?]', '', row['article_processed'])

    #to list of sentences
    text = row['article_processed']
    new_data = pd.DataFrame()
    for index, t in enumerate(text.split('.')):
        new_data.loc[index,"article_processed"] = t.replace(".",'')

    #Prepare text for LDA analysis
    stop_words = stopwords.words('english')
    stop_words.extend(['from', 'subject', 're', 'edu', 'use'])

    data_ready = new_data.article_processed.values.tolist()
    #data_ready = data.title_processed.values.tolist() + data_ready
    data_words = list(sent_to_words(data_ready))
    # remove stop words
    data_words = remove_stopwords(data_words)

    #Create dictionary and terms frequencies
    id2word = corpora.Dictionary(data_words)
    # Create Corpus
    texts = data_words
    # Term Document Frequency
    corpus = [id2word.doc2bow(text) for text in texts]
    if corpus == [[]]:
        continue

    #LDA modeling
    # number of topics
    num_topics = 3
    # Build LDA model
    lda_model = gensim.models.LdaModel(corpus=corpus,id2word=id2word,num_topics=num_topics, eta=eta, alpha=alpha, passes=passes, random_state=30 )
    # Print the Keyword in the 3 topics
    for i in range(0,num_topics):
        df.loc[index,"topic "+str(i+1)] = str((lda_model.print_topics(num_words=nw))[1])

df.to_csv("results/res.csv")


import random

import pygame
from pygame.locals import *
from pygame.color import *

import pymunk
from pymunk import Vec2d
import pymunk.pygame_util

import numpy as np
import math
import copy
import time

pygame.init()
screen = pygame.display.set_mode((600, 600))
clock = pygame.time.Clock()
running = True

### Physics stuff
space = pymunk.Space()
#space.gravity = (0.0, -900.0)
space._set_gravity(Vec2d(0, -981))
#space.gravity = (0.0, 0.0)
draw_options = pymunk.pygame_util.DrawOptions(screen)

w = 400
h = 20
fp = [(w/2,-h/2), (-w/2, h/2), (w/2, h/2), (-w/2, -h/2)]
mass = 100
moment = pymunk.moment_for_poly(mass, fp[0:2])

x_pos = 300
y_pos = 100
angle = np.pi/24

trayBody = pymunk.Body(mass, moment)
trayBody.position = x_pos, y_pos
trayBody.angle = angle
trayShape = pymunk.Poly(trayBody, fp)
space.add(trayBody, trayShape)

trayJointBody = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
trayJointBody.position = trayBody.position
j = pymunk.PinJoint(trayBody, trayJointBody, (0, 0), (0, 0))
space.add(j)

#Q-learning

reset = False
addBall = False

def remove_ball(ball_to_remove):
    space.remove(ball_to_remove, ball_to_remove.body)

def get_ball_pos_x(ball):
    pos = -1
    dist_squared = (ball.body.position.x - x_pos)**2 + (ball.body.position.y - y_pos)**2 - (ball_radius+h/2)**2
    if(dist_squared < 0):# and abs(dist_squared) < 100):
        pos = 0
    else:
        pos = math.sqrt(dist_squared)
    if ball.body.position.x < x_pos:
        pos = pos * -1
    return pos

def get_q_idx_pos(pos):  # Get the indices of the Q matrix based on mapping the position and velocity to the buckets
    pos = pos + w/2  # Map from relative to centre of tray to relative from start of tray
    bins = []
    bin_count = 0
    while bin_count < w-1:
        bins.append(bin_count)
        bin_count += round(w/NUM_X_DIVS)
    x = [pos]
    pos_bin = np.digitize(x, bins)
    return pos_bin[0]-1

def get_q_idx_vel(vel):
    vel = vel + MAX_VELOCITY  # Map from relative to centre of tray to relative from start of tray
    bins = []
    bin_count = 0
    while bin_count < MAX_VELOCITY*2-1:
        bins.append(bin_count)
        bin_count += round((MAX_VELOCITY*2)/NUM_VELOCITIES)
    x = [vel]
    vel_bin = np.digitize(x, bins)
    return vel_bin[0]-1  # -1 because starts at 0

def get_state(pos, vel, ang):
    p_bin = math.floor(((pos + (w/2)) / (w)) * NUM_X_DIVS)

    v_bin = math.floor(((vel + MAX_VELOCITY) / (MAX_VELOCITY*2)) * NUM_VELOCITIES)
    a_bin = (math.floor(((ang + np.pi) / (2*np.pi)) * NUM_ANGLES + NUM_ANGLES//4))%NUM_ANGLES #starting from right is 0, then increases going anticlockwise
    #In terms of normal angles, when horizontal, angle is 0
    #When going from horizontal and turning anticlockwise angle goes from 0 and increases, with pi at other horizontal
    #When going clockwise, angle goes below 0 and then decreases
    if abs(pos) >= w/2:
        p_bin = -1
    if abs(vel) >= MAX_VELOCITY:
        v_bin = -1
    return p_bin, v_bin, a_bin

def get_centre_of_ang_bin(b):
    jump = 2*np.pi / NUM_ANGLES  # Size of a bin
    bin_pos = (b + 0.5) % NUM_ANGLES  # The bth bin, and then half way through that
    centre_angle = (jump * bin_pos - np.pi - np.pi/2)  # -pi since tray angles go from -pi to +pi, and want 0 to 2pi
    return centre_angle

def add_ball(xpos, ypos, mass, radius):
    inertia = pymunk.moment_for_circle(mass, 0, radius, (0,0))
    body = pymunk.Body(1, inertia)
    body.position = random.randint(x_pos - w/4, x_pos + w/4), ypos
    shape = pymunk.Circle(body, radius, (0,0))
    shape.elasticity = 0#0.95
    space.add(body, shape)
    shape.current_velocity = 0
    shape.current_position = get_ball_pos_x(shape)
    shape.current_action = 0
    shape.current_angle = 0
    shape.previous_action = 0
    shape.previous_velocity = 0
    shape.previous_position = 0
    shape.previous_angle = 0
    return shape

def choose_action(p_var, v_var, a_var, q_var, exp_var, num_act):
    action = 0
    if p_var >= 0 and v_var >= 0:
        if random.random() < exp_var:
            action = random.randint(0, num_act-1)
        else:
            action = np.argmax(q_var[p_var][v_var][a_var])
    return action

def change_state(ball_var, tray, strength, dist, a_bin, sp):
    action = ball_var.current_action
    if action == 0:
        while tray.angle > get_centre_of_ang_bin((a_bin-1)):
            tray.apply_force_at_local_point(Vec2d.unit() * strength, (-dist, 0))  # rotate flipper clockwise
            dt = 1.0/60.0/5.
            for x in range(5):
                sp.step(dt)
            ball_var.current_position = get_ball_pos_x(ball_var)
            ball_var.current_velocity = ball_var.body.velocity[0]
            ball_var.current_angle = tray.angle
            #p_new, v_new, a_new = get_state(ball_var.current_position, ball_var.current_velocity, ball_var.current_angle)
    elif action == 1:
        while tray.angle < get_centre_of_ang_bin((a_bin+1)):
            tray.apply_force_at_local_point(Vec2d.unit() * strength, (dist, 0))  # rotate flipper anticlockwise
            dt = 1.0/60.0/5.
            for x in range(5):
                sp.step(dt)

            ball_var.current_position = get_ball_pos_x(ball_var)
            ball_var.current_velocity = ball_var.body.velocity[0]
            ball_var.current_angle = tray.angle
    ppp, vvv, aaa = get_state(ball_var.current_position, ball_var.current_velocity, ball_var.current_angle)
    tray.angular_velocity = 0
    return ppp, vvv, aaa

def calculate_reward(ball_var, a_bin, a_bin_new):
    reward = 0
    if ball_var.current_velocity > 0 and a_bin_new > a_bin:
        reward += 1
    if ball_var.current_velocity < 0 and a_bin_new < a_bin:
        reward += 1
    return reward

def normalised(q_var, p_var, v_var, a_var):
    if sum(Q[p_var][v_var][a_var]) > 0:
        for i in range(0, len(Q[p_var][v_var][a_var])):
            Q[p_var][v_var][a_var][i] = Q[p_var][v_var][a_var][i]/sum(Q[p_var][v_var][a_var])
    return q_var

# TODO:
# Allow it to loop different parameters in order to compare training times
# Save to file the results of the training

# PARAMETERS AND MAIN LOOP
NUM_ACTIONS = 2  # Number of actions that can be take. Normally 2, rotate clockwise or anticlockwise
NUM_X_DIVS = 2  # Number of divisions x plane is split into, for whole tray
NUM_VELOCITIES = 2  # Number of velocity buckets
NUM_ANGLES = 200 #2 * np.pi
#MAX_ANGLE = math.atan((y_pos - stop_y)/abs(x_pos - stop_x_left))
MAX_VELOCITY = 400#math.sqrt(2 * mass/100 * 981/100 * math.sin(MAX_ANGLE) * w)  # Using SUVAT and horizontal component of gravity, /100 because of earlier values seem to be *100

#Q = np.zeros((NUM_X_DIVS, NUM_VELOCITIES, NUM_ANGLES, NUM_ACTIONS))

ball_radius = 25
ball = add_ball(400, 200, 1, ball_radius)

iterations = 0

rotation = 10000

TIME_THRESHOLDS = [0.4, 0.6]
SCALE_REDUCTIONS = [1.7]
LEARN_RATES = [0.7]
NUM_ANGLES_OPTIONS = [540, 720]
NUM_VELOCITIES_OPTIONS = [15, 20]
NUM_X_DIVS_OPTIONS = [15, 25]
P_params = [TIME_THRESHOLDS, SCALE_REDUCTIONS, LEARN_RATES, NUM_ANGLES_OPTIONS, NUM_VELOCITIES_OPTIONS, NUM_X_DIVS_OPTIONS]
P_defaults = [0.3, 1.6, 0.5, NUM_ANGLES, NUM_VELOCITIES, NUM_X_DIVS]

Q = np.zeros((P_defaults[5], P_defaults[4], P_defaults[3], NUM_ACTIONS))

best_time = 0
very_best_time = 0
start_time = time.time()
time_threshold = 0.7
real_time_threshold = 30

explore_rate = 0
learnRate = 0.5
scale_reduction = 1.7

threshold_counter = 0
num_thresholds = 15
max_num_iterations = 1000

log = open("log.txt", "a")

defo_running = True
DO_PARAM_TEST = False
DONT_SHOW_ONCE_DONE = False
TRAINING = True
if TRAINING:
    scale_reduction = 1.7
    explore_rate = 1
    log.write("\n\nStarting training\n\n")
else:
    Q = np.load("qGood.npy")

P_vars = [time_threshold, scale_reduction, learnRate, NUM_ANGLES, NUM_VELOCITIES, NUM_X_DIVS]

def next_params(var_name, var_pos, defaults, params):
    res = copy.copy(defaults)
    if var_pos >= 0:
        res[var_name] = params[var_name][var_pos]
    return res

var = 0
val = -1
while var < len(P_params) and defo_running:
    if DO_PARAM_TEST:
        time_threshold, scale_reduction, learnRate, NUM_ANGLES, NUM_VELOCITIES, NUM_X_DIVS = next_params(var, val, P_defaults, P_params)
        Q = np.zeros((NUM_X_DIVS, NUM_VELOCITIES, NUM_ANGLES, NUM_ACTIONS))
        print(time_threshold, scale_reduction, learnRate, NUM_ANGLES, NUM_VELOCITIES, NUM_X_DIVS, var, val)
    while running and defo_running:
        for event in pygame.event.get():
            if event.type == QUIT:
                running = False
            elif event.type == KEYDOWN and event.key == K_ESCAPE:
                running = False
                defo_running = False

        # GET THE STATES
        p, v, a = get_state(ball.previous_position, ball.previous_velocity, ball.previous_angle)

        # DECIDE ON THE BEST ACTION
        ball.current_action = choose_action(p, v, a, Q, explore_rate, NUM_ACTIONS)

        # GET THE STATES ONCE UPDATED
        p_new, v_new, a_new = change_state(ball, trayBody, rotation, w/2, a, space)

        # UPDATE Q
        if p_new >= 0 and v_new >= 0 and TRAINING:
            reward = calculate_reward(ball, a, a_new)
            Q[p][v][a][ball.current_action] = (1 - learnRate) * Q[p][v][a][ball.current_action] + learnRate * (reward)# + 0.01*((max(Q[p2][v2]))))# - Q[p][v][ball.current_action])))
            Q = normalised(Q, p, v, a)

        # RESET THE SCENARIO
        if reset:
            ball = add_ball(400, 150, 1, ball_radius)
            iterations += 1
            trayBody.angle = random.randrange(-15, 15, 1)/100
            trayBody.angular_velocity = 0

            t = time.time() - start_time
            if t > best_time:
                best_time = t
            if t > time_threshold and threshold_counter < num_thresholds:
                threshold_counter += 1
                print("Threshold", threshold_counter, "Iterations:", iterations, "Time:", t)
            if iterations % 100 == 0:
                explore_rate /= scale_reduction
                print("Iterations:", iterations, "Best:", best_time)

            start_time = time.time()
            reset = False

        # IF GOOD ENOUGH
        if (threshold_counter >= num_thresholds) or not TRAINING:
            explore_rate = 0.0
            screen.fill(THECOLORS["white"])
            space.debug_draw(draw_options)

            clock.tick(10)
            if DONT_SHOW_ONCE_DONE:
                running = False

        if best_time > real_time_threshold and DONT_SHOW_ONCE_DONE:
            running = False

        # IF TRAINED TOO MUCH
        if time.time() - start_time > 100:
            best_time = time.time() - start_time
            running = False
        if iterations >= max_num_iterations and DONT_SHOW_ONCE_DONE:
            running = False

        # Update velocities
        ball.previous_velocity = ball.current_velocity
        ball.previous_position = ball.current_position
        ball.previous_action = ball.current_action
        ball.previous_angle = trayBody.angle

        if abs(ball.current_position) > w/2:
            remove_ball(ball)
            reset = True

        # Flip screen
        pygame.display.flip()

        #END OF EPISODE LOOP

    if TRAINING and defo_running:
        spaces = "    "
        if iterations >= 10000:
            spaces = " "
        elif iterations >= 1000:
            spaces = "  "
        elif iterations >= 100:
            spaces = "   "
        log.write("ITERATIONS: " + str(iterations) + spaces + "BEST_TIME: " + str(best_time) + "   TIME_THRESHOLD " + str(time_threshold) + "   SCALE_REDUCTION: " + str(scale_reduction) + "   LEARN_RATE: " + str(learnRate) + "   NUM ACTIONS: " + str(NUM_ACTIONS) + "   NUM_X_DIVS: " + str(NUM_X_DIVS) + "   NUM_VELOCITIES: " + str(NUM_VELOCITIES) + "   NUM_ANGLES: " + str(NUM_ANGLES) + "\n")
        print("WRITING")
    if TRAINING and best_time > very_best_time and defo_running:
        np.save("q", Q)
        very_best_time = best_time
    if DO_PARAM_TEST:
        if val == len(P_params[var])-1:
            val = 0
            var += 1
        else:
            val += 1
        running = True
        best_time = 0
        iterations = 0
        threshold_counter = 0
        explore_rate = 1
        start_time = time.time()
    else:
        defo_running = False
log.close()


from django import forms
from apps.presentacion.models import Presentacion

class PresentacionForm(forms.ModelForm):

    class Meta:
        model = Presentacion

        fields = "__all__"

        labels = {
            'cod_presentacion': 'Codigo de la presetacion',
            'nom_presentacion': 'Nombre de la presentacion',
        }

    def __init__(self, *args, **kwargs):
        super(PresentacionForm, self).__init__(*args, **kwargs)

        for field in self.fields:
            self.fields[field].widget.attrs.update({'class': 'form-control'})


#!/usr/bin/python

def person(name,age,**kw):
    print('name:',name,'age:',age,'other:',kw)

person('Bob',35,city='Beijing')

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run(host="0.0.0.0")


#!/usr/bin/env python3

'''
Given an unsorted array nums, reorder it in-place such that nums[0] < nums[1] > nums[2] < nums[3].... Example:

Input: nums = [3,5,2,1,6,4]
Output: One possible answer is [3,5,1,6,2,4]
'''

from typing import List

class Solution:
	def wiggleSort(self, nums: List[int]) -> None:
		nums.sort()
		nums1 = nums[:]

		j = int((len(nums)-1)/2)
		k = len(nums)-1
		jj = j
		i = 0
		while i < len(nums)-1:
			if j > -1:
				nums[i] = nums1[j]
				i += 1
				j -= 1
			if k > jj:
				nums[i] = nums1[k]
				i += 1
				k -= 1

def main():
	sol = Solution()
	nums = [1,4,2,4,4,6]

	sol.wiggleSort(nums)
	print(nums)

if __name__ == "__main__":
	main()

1. 1-D Array (19:19)/314. Wiggle Sort II/wiggleSort.py


#!/usr/bin/env python3
"""Session authentication module for the API.
"""
from uuid import uuid4
from flask import request
from .auth import Auth
from models.user import User

class SessionAuth(Auth):
    """Session authentication class.
    """
    user_id_by_session_id = {}

    def create_session(self, user_id: str = None) -> str:
        """creates a session id for a user_id
        """
        if type(user_id) is str:
            session_id = str(uuid4())
            self.user_id_by_session_id[session_id] = user_id
            return session_id

    def user_id_for_session_id(self, session_id: str = None) -> str:
        """returns a user ID based on a session ID
        """

        if type(session_id) is str:
            return self.user_id_by_session_id.get(session_id)

    def current_user(self, request=None) -> User:
        """Retrieves the user associated with the request.
        """
        user_id = self.user_id_for_session_id(self.session_cookie(request))
        return User.get(user_id)

    def destroy_session(self, request=None) -> bool:
        """
        destroy_session.
        """
        if not request:
            return False
        session_cookie = self.session_cookie(request)
        if not session_cookie:
            return False
        user_id = self.user_id_for_session_id(session_cookie)
        if not user_id:
            return False
        self.user_id_by_session_id.pop(session_cookie, None)
        return True


import requests
import json
from pytube import YouTube
import os
from defaults import API_KEY, DOWNLOAD_PATH

def getVids():
    params = {
            'key': API_KEY.strip(),
            'part': 'snippet',
            'q': 'dog+barking+compilation',
            }
    url = 'https://www.googleapis.com/youtube/v3/search'
    response = requests.get(url, params)
    jsonResponseVids = json.loads(response.text)
    videoIDs = [video['id']['videoId'] for video in jsonResponseVids['items']]
    return videoIDs

def downloadVids(vidlist):
    requestUrl = 'https://youtu.be/'

    if not os.path.exists(DOWNLOAD_PATH):
        os.makedirs(DOWNLOAD_PATH)

    for vidID in vidlist:
        YouTube(requestUrl+vidID).streams.filter(progressive=True, file_extension='mp4').first().download(output_path=DOWNLOAD_PATH)
        print('Successfully downloaded vid')

VideoIDList = getVids()
downloadVids(VideoIDList)

print('Finished downloading 5 vids at path ', DOWNLOAD_PATH)


from interstellar import Interstellar
from direction import Direction
from resources import Resources

import enum

class Enemy(Interstellar):
    def __init__(self,
                 images,
                 speed,
                 explode_images=Resources.explosion,
                 explode_sounds=Resources.wav_explosion,
                 x=0, y=0):
        super().__init__(images=images,
                         speed=speed,
                         explode_images=explode_images,
                         explode_sounds=explode_sounds,
                         x=x, y=y)
        self.score = 0

    class Type(enum.Enum):
        asteroid = 0
        invader = 1

    def get_score(self):
        """
        Return the score of this enemy
        """
        return self.score

    def set_speed(self, speed):
        """
        Set enemy's speed (given as a tuple of horizontal and vertical speeds)
        """
        self.speed = speed

    def get_horizontal_direction(self):
        """
        Returns the horizontal direction of the enemy
        """
        if self.speed[0] > 0:
            return Direction.right
        elif self.speed[0] < 0:
            return Direction.left
        else:
            return Direction.none

    def move(self):
        if not self.away:
            self.x, self.y = tuple(map(sum, zip((self.x, self.y), self.speed)))
            if self.off_the_screen():
                self.away = True
        super().move()