Exemplo n.º 1
0
class RealPropertyLoanSBA(FinancialRecoveryProgram):
    """A class to represent an SBA real property loan program.

    Methods:
    __init__
    process(self, entity, callbacks = None):
    setLoanAmount(self, entity):
    writeDeadline(self, entity):
    writeApplied(self, entity):
    writeDeniedCredit(self, entity):
    writeInspected(self, entity):
    writeFirstDisbursement(self, entity):
    writeSecondDisbursement(self, entity):
    writeOnlyDisbursement(self, entity):
    
    Inheritance:
    financial.FinancialRecoveryProgram
    """
    def __init__(self,
                 env,
                 duration,
                 inspectors=float('inf'),
                 officers=float('inf'),
                 budget=float('inf'),
                 max_loan=float('inf'),
                 min_credit=0,
                 debt_income_ratio=0.2,
                 loan_term=30.0,
                 interest_rate=0.04,
                 declaration=0,
                 deadline=60):
        """Initiate SBA real property loan recovery program.

        Keyword Arguments:
        env -- simpy.Envionment() object
        duration -- io.ProbabilityDistribution() object
        inspectors -- Integer, indicating number of building inspectors assigned to the programs
        officers -- Number of program staff that reviews and approves loan applications
        budget -- Integer or float, indicating the initial budget available from
                    the recovery program.
        max_loan -- The maximum amount ($) of loan that any one entity can receive
        debt_income_ratio -- Monthly SBA loan payment / entity income ; used to estimate
                                loan amount.
        min_credit -- A FICO-like credit score used as threshold for approving loan.
        declaration -- A value indicating how many days after the event
                                a federal disaster was declared.
        deadline -- A value indicating how many days after the
                                federal disaster declaration was made the applications
                                must be submitted.

        """
        FinancialRecoveryProgram.__init__(self, env, duration, budget)

        # Define staff/personnel specific to this class
        self.officers = Resource(self.env, capacity=officers)
        self.inspectors = Resource(self.env, capacity=inspectors)

        # New attributes
        self.min_credit = min_credit
        self.debt_income_ratio = debt_income_ratio
        self.loan_term = loan_term  # years
        self.max_loan = max_loan
        self.interest_rate = interest_rate  # annual rate
        self.deadline = deadline
        self.declaration = declaration

    def process(self, entity, callbacks=None):
        """Define process for entity to submit request for SBA loan.

        entity -- An entity object from the entities.py module, for example
                    entities.Household().
        callbacks -- a generator function containing processes to start after the
                        completion of this process.

        Returns or Attribute Changes:
        entity.sba_put -- Records sim time of loan request
        entity.sba_get -- Records sim time of loan reciept
        entity.sba_amount -- The amount of loan requested.
        entity.story -- Append natural language sentences to entities story.
        """
        # Exception handling in case interrupted by another process.
        try:
            # Check to see declaration has occurred; if not, wait
            if self.env.now < self.declaration:
                yield self.env.timeout(self.declaration - self.env.now)

            # Record time application submitted.
            entity.sba_put = self.env.now

            # Call function to set entity.sba_amount
            entity.sba_amount = self.setLoanAmount(entity)

            # Ensure entity does not have enough funds.
            if entity.sba_amount <= 0:
                return  # Don't qualify for or need SBA loan, end process

            # Check to see if missed application deadline
            if self.env.now > self.deadline:
                self.writeDeadline(entity)
                return  # Application rejected, end process

            self.writeApplied(entity)

            # Request a loan processor.
            officer_request = self.officers.request()
            yield officer_request

            # # Yield process timeout for duration needed for officer to process application.
            yield self.env.timeout(self.duration.rvs())

            if entity.credit < self.min_credit:
                self.writeDeniedCredit(entity)
                return

            # Release loan officer so that they can process other loans.
            self.officers.release(officer_request)

            # If approved (enough credit), request an inspector. Then release it.
            # %%% This increases duration by amount of time it takes
            # to get an inspector. Duration of 1 day assumed, currently. %%%%
            inspector_request = self.inspectors.request()
            yield inspector_request
            yield self.env.timeout(1)  # Assumed 1 day inspection duration.
            self.inspectors.release(inspector_request)

            # Update loan amount (in case other processes in parallel)
            entity.sba_amount = self.setLoanAmount(entity)

            self.writeInspected(entity)

            if entity.sba_amount <= 0:
                self.writeWithdraw(entity, 'SBA')
                return

            # If loan amount is greater than $25k, it requires collateral and more paperwork
            if entity.sba_amount > 25000:

                # Receives $25k immediately as initial disbursement
                yield self.budget.get(25000)
                yield entity.recovery_funds.put(25000)

                self.writeFirstDisbursement(entity)

                #
                # %%%% EVENTUALLY MAKE WAIT FOR A BUILDING PERMIT TO BE ISSUED %%%
                # %%% FOR NOW: Yield another timeout equal to initial process application duration %%%
                #

                yield self.env.timeout(self.duration.rvs())

                # Update loan amount (in case other processes in parallel)
                entity.sba_amount = self.setLoanAmount(entity)

                if entity.sba_amount <= 0:
                    self.writeWithdraw(entity, 'SBA')
                    return

                yield self.budget.get(entity.sba_amount - 25000)
                yield entity.recovery_funds.put(entity.sba_amount - 25000)

                self.writeSecondDisbursement(entity)

            else:

                # Add loan amount to entity's money to repair.
                yield entity.recovery_funds.put(entity.sba_amount)

                self.writeOnlyDisbursement(entity)

            # Record time full loan is approved.
            entity.sba_get = self.env.now

        # Handle any interrupt from another process.
        except Interrupt as i:
            self.writeGaveUp(entity, 'SBA')

        if callbacks is not None:
            yield self.env.process(callbacks)
        else:
            pass

    def setLoanAmount(self, entity):
        required_loan = max(
            0, entity.property.damage_value - entity.claim_amount -
            entity.fema_amount)

        # Just in case entity doesn't have income attribute (e.g., landlord)
        try:
            monthly_rate = self.interest_rate / 12
            qualified_monthly_payment = (entity.income /
                                         12) * self.debt_income_ratio
            qualified_loan = -1.0 * np.pv(monthly_rate, self.loan_term * 12,
                                          qualified_monthly_payment)
        except AttributeError:
            qualified_loan = required_loan * 0.44  # Result of regression analysis of past SBA loans

        return min(required_loan, qualified_loan, self.max_loan)

    def writeDeadline(self, entity):
        if entity.write_story:
            entity.story.append(
                '{0} applied for a ${1:,.0f} SBA loan {2} days after the event. Their application was rejected because it was submitted after the {3}-day deadline after the disaster declaration made on day {4}. '
                .format(entity.name.title(), entity.sba_amount, entity.sba_put,
                        self.deadline, self.declaration))

    def writeApplied(self, entity):
        if entity.write_story:

            required_loan = max(
                0, entity.property.damage_value - entity.claim_amount -
                entity.fema_amount)
            applied_loan = min(required_loan, self.max_loan)

            entity.story.append(
                '{0} applied for a ${1:,.0f} SBA loan {2} days after the event.'
                .format(entity.name.title(), applied_loan, entity.sba_put))

    def writeDeniedCredit(self, entity):
        if entity.write_story:
            entity.story.append(
                '{0}\'s SBA loan application was denied because {0} had a credit score of {1}. '
                .format(entity.name.title(), entity.credit))

    def writeInspected(self, entity):
        if entity.write_story:
            entity.story.append(
                "SBA inspected {0}\'s home on day {1} after the event. ".
                format(entity.name.title(), self.env.now))

    def writeFirstDisbursement(self, entity):
        if entity.write_story:
            entity.story.append(
                "{0} received an initial SBA loan disbursement of $25,000 {1} days after the event. "
                .format(entity.name.title(), self.env.now))

    def writeSecondDisbursement(self, entity):
        if entity.write_story:
            entity.story.append(
                "{0} received a second SBA loan disbursement of ${1:,.0f} {2} days after the event. "
                .format(entity.name.title(), (entity.sba_amount - 25000),
                        self.env.now))

    def writeOnlyDisbursement(self, entity):
        if entity.write_story:
            entity.story.append(
                "{0} received a SBA loan of ${1:,.0f} {2} days after the event. "
                .format(entity.name.title(), entity.sba_amount, self.env.now))
Exemplo n.º 2
0
class Block:
    """
    Provides simulation-specific behavior of a basic block.
    Entities are accepted via the process_entity entry-point, and standard processing steps are executed.
    Actual processing behavior is to be provided by subclasses, implementing the actual_processing method.
    """

    def __init__(self, env: Environment, name: str, block_capacity=float("inf")):
        self.name = name
        self.env: Environment = env
        self.overall_count_in = 0
        on_enter_or_exit_method_callable = Callable[
            [Entity, Optional[Block], Optional[Block]], None
        ]
        self.do_on_enter_list: List[on_enter_or_exit_method_callable] = []
        self.do_on_exit_list: List[on_enter_or_exit_method_callable] = []
        self.entities: List[Entity] = []
        self.successors: List[Block] = []
        self.block_resource = Resource(env=env, capacity=block_capacity)
        self.state_manager = StateManager(self.on_block_state_change)

        self.env.process(self.late_state_evaluation())

    def on_enter(self, entity: Entity):
        """called when process_entity starts"""
        for method in self.do_on_enter_list:
            method(entity, None, self)

    def on_exit(self: "Block", entity: Entity, successor: "Block"):
        """called when an entities leaves the block"""
        self.entities.remove(entity)
        for method in self.do_on_exit_list:
            method(entity, self, successor)
        self.on_block_change()
        self.on_entity_movement(entity, successor)

    def process_entity(self, entity: Entity):
        """main entry point for entities coming from predecessors"""
        entity.time_of_last_arrival = self.env.now
        self.on_enter(entity)
        self.overall_count_in += 1
        yield self.env.process(self._process_entity(entity))

    def _process_entity(self, entity: Entity):
        """internal entry point for start of entity processing without calling on_enter or setting entity.time_of_last_arrival.
        useful e.g. for wip init"""
        self.entities.append(entity)
        self.on_block_change()

        # processing
        self.state_manager.increment_state_count(States.busy)
        entity.current_process = self.env.process(self.actual_processing(entity))
        yield entity.current_process  # might be used for interrupt

        # wait for successor
        successor = self.find_successor(entity)
        req = successor.block_resource.request()
        self.state_manager.increment_state_count(States.blocked)
        self.state_manager.decrement_state_count(States.busy)
        yield req  # wait until the chosen successor is ready

        # leaving
        self.block_resource.release(entity.block_resource_request)
        entity.block_resource_request = req  # remember to be released

        self.on_exit(entity, successor)
        self.state_manager.decrement_state_count(States.blocked)

        self.env.process(successor.process_entity(entity))

    def find_successor(self, entity: Entity):
        """find next block to send entity to"""
        return self.successors[0]

    def on_block_change(self):
        """called on block change"""

    def on_block_state_change(self, state, new_value):
        """called when state of the block changes"""

    def on_entity_movement(self, entity: Entity, successor: "Block"):
        """called on entity movement from this block to the successor"""

    def late_state_evaluation(self):
        """schedule evaluation on sim start,
        when the visualizer has been loaded"""
        yield self.env.timeout(0)
        self.state_manager.evaluate_state_count()

    def actual_processing(self, entity: Entity):
        """to be implemented by concrete subclasses"""
        raise NotImplementedError()
Exemplo n.º 3
0
class FinancialRecoveryProgram(object):
    """The base class for operationalizing financial recovery programs.
    All such programs staff and budget implemented as simpy resources or containers.

    All other classes of financial recovery programs should inherit from this class,
    either directly or indirectly. The process for FinancialRecoveryProgram is
    useless and should only be used as an example of how to implement a process in a
    subclass of  FinancialRecoveryProgram.
    
    Methods:
    __init__
    process(self, entity = None, callbacks = None):
    writeCompleted(self, entity):
    writeGaveUp(self, entity, recovery_program):
    writeWithdraw(self, entity, recovery_program):
    
    
    """
    def __init__(self, env, duration, staff=float('inf'), budget=float('inf')):
        """Initiate financial recovery program attributes.

        Keyword Arguments:
        env -- simpy.Envionment() object
        duration -- io.ProbabilityDistribution() object
        staff -- Integer, indicating number of staff assigned to the programs
        budget -- Integer or float, indicating the initial budget available from
                    the recovery program.

        Attribute Changes:
        self.staff -- A simpy.Resource() object with a capacity == staff arg
        self.budget -- A simpy.Container() object with a initial value == budget arg
        self.duration -- A function that is used to calculate random durations
                            for the program process
        """
        self.env = env
        self.staff = Resource(self.env, capacity=staff)
        self.budget = Container(self.env, init=budget)
        self.duration = duration

    def process(self, entity=None, callbacks=None):
        """Define generic financial recovery program process for entity.

        entity -- An entity object from the entities.py module, for example
                    entities.Household().
        callbacks -- a generator function containing processes to start after the
                        completion of this process.

        Returns or Attribute Changes:
        entity.story -- Entity's story list.
        """
        ###
        ### The contents of this method are an example of what can be done
        ### in a subclass of this class. It demonstrates the use of SimPy
        ### Resources and Containiners. The results of the function itself are
        ### useless. It is meant to help create your own function after creating
        ### a subclass that inherits from this class.
        ###

        # Request staff
        staff_request = self.staff.request()
        yield staff_request

        # Yield timeout equivalent to program's process duration
        yield self.env.timeout(self.duration.rvs())

        # Release release staff after process duation is complete.
        self.staff.release(staff_request)

        cost = 1

        # Get out amount equal to cost.
        yield self.budget.get(cost)

        # Put back amount equal to cost.
        yield self.budget.put(cost)

        self.writeCompleted(entity)

        if callbacks is not None:
            yield self.env.process(callbacks)
        else:
            pass

    def writeCompleted(self, entity):
        if entity.write_story:
            entity.story.append(
                "{0} process completed for {1} after {2} days, leaving a program budget of ${3:,.0f}. "
                .format(self.__class__, entity.name.title(), self.env.now,
                        self.budget.level))

    def writeGaveUp(self, entity, recovery_program):
        if entity.write_story:
            entity.story.append(
                "{0} gave up waiting for recovery funds from {1} {2} days after the event. "
                .format(entity.name.title(), recovery_program, self.env.now))

    def writeWithdraw(self, entity, recovery_program):
        #If true, write interrupt outcome to story.
        if entity.write_story:
            entity.story.append(
                '{0} withdrew their application to {1} {2} days after the event because enough recovery funds were found from other sources. '
                .format(entity.name.title(), recovery_program, self.env.now))