Example #1
0
 def get_timetables(self, start_date, motive_id, agenda_ids):
     # Keldoc needs an end date, but if no appointment are found,
     # it still returns the next available appointment. Bigger end date
     # makes Keldoc responses slower.
     calendar_url = API_KELDOC_CALENDAR.format(motive_id)
     end_date = (isoparse(start_date) +
                 timedelta(days=KELDOC_SLOT_LIMIT)).strftime("%Y-%m-%d")
     logger.debug(
         f"get_timetables -> start_date: {start_date} end_date: {end_date} motive: {motive_id} agenda: {agenda_ids}"
     )
     calendar_params = {
         "from": start_date,
         "to": end_date,
         "agenda_ids[]": agenda_ids,
     }
     try:
         calendar_req = self.client.get(calendar_url,
                                        params=calendar_params)
         calendar_req.raise_for_status()
     except httpx.TimeoutException as hex:
         logger.warning(
             f"Keldoc request timed out for center: {self.base_url} (calendar request)"
             f" celendar_url: {calendar_url}"
             f" calendar_params: {calendar_params}")
         return None
     except httpx.HTTPStatusError as hex:
         logger.warning(
             f"Keldoc request returned error {hex.response.status_code} "
             f"for center: {self.base_url} (calendar request)")
         return None
     return calendar_req.json()
Example #2
0
    def find_first_availability(self, start_date, end_date):
        if not self.vaccine_motives:
            return None

        # Find next availabilities
        first_availability = None
        for relevant_motive in self.vaccine_motives:
            if not 'id' in relevant_motive or not 'agendas' in relevant_motive:
                continue
            motive_id = relevant_motive.get('id', None)
            calendar_url = API_KELDOC_CALENDAR.format(motive_id)
            calendar_params = {
                'from': start_date,
                'to': end_date,
                'agenda_ids[]': relevant_motive.get('agendas', [])
            }
            try:
                calendar_req = session.get(calendar_url, params=calendar_params)
            except TimeoutException:
                # Some requests on Keldoc are taking too much time (for few centers)
                # and block the process completion.
                continue
            calendar_req.raise_for_status()
            date = parse_keldoc_availability(calendar_req.json())
            if date is None:
                continue
            # Compare first available date
            if first_availability is None or date < first_availability:
                first_availability = date
        return first_availability
Example #3
0
    def find_first_availability(self, start_date, end_date):
        if not self.vaccine_motives:
            return None, 0

        # Find next availabilities
        first_availability = None
        appointments = []
        for relevant_motive in self.vaccine_motives:
            if 'id' not in relevant_motive or 'agendas' not in relevant_motive:
                continue
            motive_id = relevant_motive.get('id', None)
            calendar_url = API_KELDOC_CALENDAR.format(motive_id)
            calendar_params = {
                'from': start_date,
                'to': end_date,
                'agenda_ids[]': relevant_motive.get('agendas', [])
            }
            try:
                calendar_req = self.client.get(calendar_url, params=calendar_params)
                calendar_req.raise_for_status()
            except httpx.TimeoutException as hex:
                logger.warning(f"Keldoc request timed out for center: {self.base_url} (calendar request)")
                continue
            except httpx.HTTPStatusError as hex:
                logger.warning(f"Keldoc request returned error {hex.response.status_code} "
                               f"for center: {self.base_url} (calendar request)")
                continue
            date, appointments = parse_keldoc_availability(calendar_req.json(), appointments)
            if date is None:
                continue
            self.request.add_vaccine_type(relevant_motive.get('vaccine_type'))
            # Compare first available date
            if first_availability is None or date < first_availability:
                first_availability = date
        return first_availability, len(appointments)
    def find_first_availability(self, start_date: str):
        if not self.vaccine_motives:
            return None, 0, None

        # Find next availabilities
        first_availability = None
        appointments = []
        appointment_schedules = []
        for relevant_motive in self.vaccine_motives:
            if "id" not in relevant_motive or "agendas" not in relevant_motive:
                continue
            motive_id = relevant_motive.get("id", None)
            calendar_url = API_KELDOC_CALENDAR.format(motive_id)

            agenda_ids = relevant_motive.get("agendas", None)
            if not agenda_ids:
                continue
            timetables, runtime = self.get_timetables(isoparse(start_date),
                                                      motive_id, agenda_ids)
            logger.debug(
                f"get_timetables -> result [motive: {motive_id} agenda: {agenda_ids}] -> runtime: {round(runtime, 2)}s"
            )
            date, appointments = parse_keldoc_availability(
                timetables, appointments)
            if date is None:
                continue
            self.request.add_vaccine_type(relevant_motive.get("vaccine_type"))
            # Compare first available date
            if first_availability is None or date < first_availability:
                first_availability = date
            if not timetables or "availabilities" not in timetables:
                continue
        # update appointment_schedules
        datenow = dt.datetime.now()
        s_date = (paris_tz.localize(datenow +
                                    dt.timedelta(days=0))).isoformat()
        n_date = (
            paris_tz.localize(datenow +
                              dt.timedelta(days=1, seconds=-1))).isoformat()
        appointment_schedules.append(
            self.get_appointment_schedule(appointments, s_date, n_date,
                                          "chronodose"))
        for n in INTERVAL_SPLIT_DAYS:
            s_date = (
                paris_tz.localize(isoparse(start_date) +
                                  dt.timedelta(days=0))).isoformat()
            n_date = (paris_tz.localize(
                isoparse(start_date) +
                dt.timedelta(days=n, seconds=-1))).isoformat()
            appointment_schedules.append(
                self.get_appointment_schedule(appointments, s_date, n_date,
                                              f"{n}_days"))
        return first_availability, len(appointments), appointment_schedules
Example #5
0
    def find_first_availability(self, start_date: str):
        if not self.vaccine_motives:
            return None, 0, None

        # Find next availabilities
        first_availability = None
        appointments = []
        appointment_schedules = []
        for relevant_motive in self.vaccine_motives:
            if "id" not in relevant_motive or "agendas" not in relevant_motive:
                continue
            motive_id = relevant_motive.get("id", None)
            calendar_url = API_KELDOC_CALENDAR.format(motive_id)

            agenda_ids = relevant_motive.get("agendas", None)
            if not agenda_ids:
                continue
            timetables = self.get_timetables(start_date, motive_id, agenda_ids)
            date, appointments = parse_keldoc_availability(
                timetables, appointments)
            if date is None:
                continue
            self.request.add_vaccine_type(relevant_motive.get("vaccine_type"))
            # Compare first available date
            if first_availability is None or date < first_availability:
                first_availability = date
            if not timetables or "availabilities" not in timetables:
                continue
        # update appointment_schedules
        s_date = (paris_tz.localize(isoparse(start_date) +
                                    timedelta(days=0))).isoformat()
        n_date = (paris_tz.localize(
            isoparse(start_date) +
            timedelta(days=CHRONODOSES["Interval"], seconds=-1))).isoformat()
        appointment_schedules.append(
            self.get_appointment_schedule(appointments, s_date, n_date,
                                          "chronodose"))
        for n in INTERVAL_SPLIT_DAYS:
            n_date = (paris_tz.localize(
                isoparse(start_date) +
                timedelta(days=n, seconds=-1))).isoformat()
            appointment_schedules.append(
                self.get_appointment_schedule(appointments, s_date, n_date,
                                              f"{n}_days"))
        return first_availability, len(appointments), appointment_schedules
Example #6
0
    def find_first_availability(self, start_date, end_date):
        if not self.vaccine_motives:
            return None, 0

        # Find next availabilities
        first_availability = None
        appointments = []
        for relevant_motive in self.vaccine_motives:
            if 'id' not in relevant_motive or 'agendas' not in relevant_motive:
                continue
            motive_id = relevant_motive.get('id', None)
            calendar_url = API_KELDOC_CALENDAR.format(motive_id)
            calendar_params = {
                'from': start_date,
                'to': end_date,
                'agenda_ids[]': relevant_motive.get('agendas', [])
            }
            try:
                calendar_req = self.client.get(calendar_url,
                                               params=calendar_params)
            except TimeoutException:
                logger.warning(
                    f"Keldoc request timed out for center: {self.base_url} (calendar request)"
                )
                # Some requests on Keldoc are taking too much time (for few centers)
                # and block the process completion.
                continue
            calendar_req.raise_for_status()
            date, appointments = parse_keldoc_availability(
                calendar_req.json(), appointments)
            if date is None:
                continue
            self.request.add_vaccine_type(relevant_motive.get('vaccine_type'))
            # Compare first available date
            if first_availability is None or date < first_availability:
                first_availability = date
        return first_availability, len(appointments)
    def get_timetables(
        self,
        start_date: dt.datetime,
        motive_id: str,
        agenda_ids: List[int],
        page: int = 1,
        timetable=None,
        run: float = 0,
    ) -> Iterable[Union[Optional[dict], float]]:
        """
        Get timetables recursively with KELDOC_DAYS_PER_PAGE as the number of days to query.
        Recursively limited by KELDOC_SLOT_PAGES and appends new availabilities to a ’timetable’,
        freshly initialized at the beginning.
        Uses date as a reference for next availability and in order to avoid useless requests when
        we already know if a timetable is empty.
        """
        start_time = time.time()
        if timetable is None:
            timetable = {}
        if run >= KELDOC_SLOT_TIMEOUT:
            return timetable, run
        calendar_url = API_KELDOC_CALENDAR.format(motive_id)

        end_date = (
            start_date +
            dt.timedelta(days=KELDOC_DAYS_PER_PAGE)).strftime("%Y-%m-%d")
        logger.debug(
            f"get_timetables -> start_date: {start_date} end_date: {end_date} "
            f"motive: {motive_id} agenda: {agenda_ids} (page: {page})")
        calendar_params = {
            "from": start_date.strftime("%Y-%m-%d"),
            "to": end_date,
            "agenda_ids[]": agenda_ids,
        }
        self.request.increase_request_count("slots")
        try:
            calendar_req = self.client.get(calendar_url,
                                           params=calendar_params)
            calendar_req.raise_for_status()
        except httpx.TimeoutException as hex:
            logger.warning(
                f"Keldoc request timed out for center: {self.base_url} (calendar request)"
                f" celendar_url: {calendar_url}"
                f" calendar_params: {calendar_params}")
            return timetable, run
        except httpx.HTTPStatusError as hex:
            logger.warning(
                f"Keldoc request returned error {hex.response.status_code} "
                f"for center: {self.base_url} (calendar request)")
            return timetable, run
        except (httpx.RemoteProtocolError, httpx.ConnectError) as hex:
            logger.warning(
                f"Keldoc raise error {hex} for center: {self.base_url} (calendar request)"
            )
            return timetable, run

        current_timetable = calendar_req.json()
        run += time.time() - start_time
        # No fresh timetable
        if not current_timetable:
            return timetable, run
        # Get the first date only
        if "date" in current_timetable:
            if "date" not in timetable:
                timetable["date"] = current_timetable.get("date")
            """
            Optimize query count by jumping directly to the first availability date by using ’date’ key
            Checks for the presence of the ’availabilities’ attribute, even if it's not supposed to be set
            """
            if "availabilities" not in current_timetable:
                next_expected_date = start_date + dt.timedelta(
                    days=KELDOC_DAYS_PER_PAGE)
                next_fetch_date = isoparse(current_timetable["date"])
                diff = next_fetch_date.replace(
                    tzinfo=None) - next_expected_date.replace(tzinfo=None)

                if page >= KELDOC_SLOT_PAGES or run >= KELDOC_SLOT_TIMEOUT:
                    return timetable, run
                return self.get_timetables(
                    next_fetch_date,
                    motive_id,
                    agenda_ids,
                    1 + max(0, floor(diff.days / KELDOC_DAYS_PER_PAGE)) + page,
                    timetable,
                    run,
                )

        # Insert availabilities
        if "availabilities" in current_timetable:
            if "availabilities" not in timetable:
                timetable["availabilities"] = current_timetable.get(
                    "availabilities")
            else:
                timetable["availabilities"].update(
                    current_timetable.get("availabilities"))
        # Pop date because it prevents availability count
        if timetable.get("availabilities") and timetable.get("date"):
            timetable.pop("date")

        if page >= KELDOC_SLOT_PAGES or run >= KELDOC_SLOT_TIMEOUT:
            return timetable, run
        return self.get_timetables(
            start_date + dt.timedelta(days=KELDOC_DAYS_PER_PAGE), motive_id,
            agenda_ids, 1 + page, timetable, run)