CSC110 / lectures / Building a Simulation / lec29_entities_tom.py
lec29_entities_tom.py
Raw
from __future__ import annotations
# needed so that Order can refer to Courier

from dataclasses import dataclass
import datetime
from typing import Optional


@dataclass
class Vendor:
    """A vendor that sells groceries or meals.

    This could be a grocery store or restaurant.

    Instance Attributes:
      - name: the name of the vendor
      - address: the address of the vendor
      - menu: the menu of the vendor with the name of the food item mapping to
              its price
      - location: the location of the vendor as (latitude, longitude)

    Representation Invariants:
      - self.name != ''
      - self.address != ''
      - all(self.menu[item] >= 0 for item in self.menu)
      - -90.0 <= self.location[0] <= 90.0
      - -180.0 <= self.location[1] <= 180.0
    """
    name: str
    address: str
    menu: dict[str, float]
    location: tuple[float, float]  # (lat, lon) coordinates


@dataclass
class Customer:
    """A person who orders food.

    Instance Attributes:
      - name: the name of the customer
      - location: the location of the customer as (latitude, longitude)

    Representation Invariants:
      - self.name != ''
      - -90 <= self.location[0] <= 90
      - -180 <= self.location[1] <= 180
    """
    name: str
    location: tuple[float, float]


@dataclass
class Order:
    """A food order from a customer.

    Instance Attributes:
      - customer: the customer who placed this order
      - vendor: the vendor that the order is placed for
      - food_items: a mapping from names of food to the quantity being ordered
      - start_time: the time the order was placed
      - courier: the courier assigned to this order (initially None)
      - end_time: the time the order was completed by the courier (initially None)

    Representation Invariants:
      - all(self.food_items[item] >= 1 for item in self.food_items)
    """
    customer: Customer
    vendor: Vendor
    food_items: dict[str, int]
    start_time: datetime.datetime
    courier: Optional[Courier] = None
    end_time: Optional[datetime.datetime] = None

###############################################################################
# Exercise 1: Designing a `Courier` data class
###############################################################################
@dataclass
class Courier:
    """A person who delivers food orders from vendors to customers.

    Instance Attributes:
        - name: the name of the courier
        - location: the location of the courier as (latitude, longitude)
        - order: the order that the courier is currently assigned to

    Representation Invariants:
        - self.name != ''
        - -90 <= self.location[0] <= 90
        - -180 <= self.location[1] <= 180

        - self.order is None or self.order.courier is self

    >>> courier = Courier('Courier 1', (44.639, -79.215))
    >>> courier.order is None
    True
    """
    name: str
    location: tuple[float, float]
    order: Optional[Order] = None

    # Can two `Order` objects refer to the same `Courier` instance? Why or why not?

    # Yes.
    # The representation invariant in 2 is added to `Courier`, and so we can only
    # check that courier's `order` attribute.
    # If another `Order` object also referred to the same `Courier` object, there
    # would be no way to know (from inside the `Courier` class).
    #
    # But, if a similar representation invariant is added to `Order`, then the answeri
    # changes to No.

    # See posted diagram

    # other possible instance attributes
    # - type of vehicle
    # - average speed
    # - working vs. not working (i.e., available)
    # - max radius
    # - et cetera