Skip to content

Commit

Permalink
First implementation of schedule (#4)
Browse files Browse the repository at this point in the history
* Add Schedule of filtering (#3)

* Add Run class

* duration in hours

* complete tests

* Add schedule

* Remove unused stuff

* Update README.md

* Type hint and missing parameter.
  • Loading branch information
oncleben31 authored May 9, 2020
1 parent 05962cf commit 3e53636
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 288 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018 oncleben31
Copyright (c) 2018-2020 @oncleben31

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**PyPoolPump** is a python module providing classes for computing the duration needed for a swimming pool filtering pump.
This module will provide methods to define the filtering daily schedule.

Each classes is an implementation of a different algorithm found when searching Internet to find best way to compute the filtering pump duration.

Expand All @@ -11,7 +12,7 @@ Each classes is an implementation of a different algorithm found when searching
### Base class

`FilteringDuration()` class is the base class with no duration computation. It gather all the common code for each implementation.
You will find a way to add a percentage modifier on the duration computed.
You will find a way to add a percentage modifier on the duration computed and a way to construct the daily filtering schedule.

You should not call directly this class except if you want to implement a new algorithm.

Expand Down
133 changes: 0 additions & 133 deletions example/.ipynb_checkpoints/comparison-checkpoint.ipynb

This file was deleted.

133 changes: 0 additions & 133 deletions example/comparison.ipynb

This file was deleted.

9 changes: 2 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
from setuptools import Command, find_packages, setup

# Package meta-data.
NAME = "pypool-pump"
NAME = "pypool_pump"
PYTHON_PACKAGE_FOLDER = "pypool_pump"
DESCRIPTION = "Pool filtering pump duration"
DESCRIPTION = "Calculate pool filtering pump duration and schedule"
URL = "https://github.com/oncleben31/pypool-pump"
EMAIL = "[email protected]"
AUTHOR = "Oncleben31"
REQUIRES_PYTHON = ">=2.7.0"
VERSION = None

# What packages are required for this module to be executed?
Expand Down Expand Up @@ -91,7 +90,6 @@ def run(self):
long_description_content_type="text/markdown",
author=AUTHOR,
author_email=EMAIL,
python_requires=REQUIRES_PYTHON,
url=URL,
packages=find_packages("src"),
package_dir={"": "src"},
Expand All @@ -103,10 +101,7 @@ def run(self):
# Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
],
# $ setup.py publish support.
cmdclass={"upload": UploadCommand},
Expand Down
37 changes: 27 additions & 10 deletions src/pypool_pump/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,47 @@
filtering.
"""

from .__version__ import __version__, VERSION
from .__version__ import VERSION, __version__
from .run import Run

from datetime import timedelta, datetime
from typing import List

class FilteringDuration(object):
"""Root class with common parts"""

def __init__(self, percentage: float = None) -> None:
def __init__(self, percentage: float = 100, schedule_config:dict = {}) -> None:
self._computed_filtering_duration: float = None
self._modifier_pecentage: float = percentage
self._total_duration = None
self._schedule_config = schedule_config


def duration(self) -> float:
#TODO: rename method
"""Filtering duration in hours
If modifier have been set, they will be applied to the computed filtering
duration.
Maximum duration is always 24 hours.
"""
consolidated_duration: float = max(
min(self._computed_filtering_duration, 24), 0
self._total_duration: float = max(
min(self._computed_filtering_duration * self._modifier_pecentage / 100, 24), 0
)
if self._modifier_pecentage is None:
return consolidated_duration
else:
return consolidated_duration * self._modifier_pecentage / 100

return self._total_duration

def update_schedule(self,pivot_time:datetime) -> List[Run]:

# TODO: Add protection on total duration and schedule config
# TODO: define strategy if total duration + break > 24
first_start = pivot_time - timedelta(hours=(self._total_duration + self._schedule_config['break_duration']) / 3)
first_duration = self._total_duration / 3
second_start = pivot_time + timedelta(hours=2/3 * self._schedule_config['break_duration'])
second_duration = 2 * first_duration

return [Run(first_start, first_duration), Run(second_start, second_duration)]



class DumbFilteringDuration(FilteringDuration):
Expand Down Expand Up @@ -94,14 +111,14 @@ class PumpCaracteristicFilteringDuration(FilteringDuration):
"""

def __init__(
self, pool_volume: float, pump_flow: float, percentage: float = None
self, pool_volume: float, pump_flow: float, percentage: float = 100
) -> None:
self.pool_volume = pool_volume
self.pump_flow = pump_flow
super().__init__(percentage)

def duration(
self, pool_temperature: float, number_of_bathers: float = None
self, pool_temperature: float, number_of_bathers: float = None, schedule_config:dict = {}
) -> float:
"""Filtering duration in hours"""
cycle_duration: float = self.pool_volume / self.pump_flow
Expand Down
52 changes: 52 additions & 0 deletions src/pypool_pump/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# coding: utf-8
"""Run class to manage time interval where the pool pump need to be active.
"""

from datetime import timedelta, datetime


class Run:
"""Represents a single run of the pool pump."""

def __init__(self, start_time_local_tz, duration_in_hours):
"""Initialise run."""
self._start_time = start_time_local_tz
self._duration = duration_in_hours

def __repr__(self):
"""Return string representation of this feed."""
return "<{}(start={}, stop={}, duration={})>".format(
self.__class__.__name__, self.start_time, self.stop_time, self.duration
)

@property
def duration(self):
"""Return duration of this run."""
return self._duration

@property
def start_time(self):
"""Return start time of this run."""
return self._start_time

@property
def stop_time(self):
"""Return stop time of this run."""
return self.start_time + timedelta(hours=self.duration)

def run_now(self, local_time):
"""Check if the provided time falls within this run's timeframe."""
return self.start_time <= local_time < self.stop_time

def is_next_run(self, local_time):
"""Check if this is the next run after the provided time."""
return local_time <= self.stop_time

def pretty_print(self):
"""Provide a usable representation of start and stop time."""
if self.start_time.day != datetime.now().day:
start = self.start_time.strftime("%a, %H:%M")
else:
start = self.start_time.strftime("%H:%M")
end = self.stop_time.strftime("%H:%M")
return "{} - {}".format(start, end)
2 changes: 1 addition & 1 deletion tests/test_abacus_filtering_duration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# coding: utf-8
"""tests for pypool_pump module - AbacusFilteringDuration"""

import pytest

from pypool_pump import AbacusFilteringDuration
Expand All @@ -26,4 +27,3 @@ def test_abacus_duration(temperature, duration):
pool_controler = AbacusFilteringDuration()
# assert pool_controler.duration == duration
assert abs(pool_controler.duration(temperature) - duration) < 0.1

2 changes: 1 addition & 1 deletion tests/test_basic_filtering_duration.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
def test_basic_duration(temperature, duration):
"""Test duration calculation."""
pool_controler = BasicFilteringDuration()
assert pool_controler.duration(temperature) == duration
assert abs(pool_controler.duration(temperature) - duration) < 0.1


def test_basic_duration_with_modifier():
Expand Down
31 changes: 30 additions & 1 deletion tests/test_filtering_duration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,34 @@
"""tests for pypool_pump module - FilteringDuration"""
import pytest

from pypool_pump import FilteringDuration
from pypool_pump import FilteringDuration, Run
from datetime import datetime

@pytest.mark.parametrize(
"pourcentage, result",
[
(-10,0),
(0,0),
(10,1),
(50,5),
(100,10),
(150,15),
(300,24)
],
)
def test_pourcentage(pourcentage, result):
"""Test modifier of the computed duration."""
pool_controler = FilteringDuration(pourcentage)
#Fake computed duration for testing.
pool_controler._computed_filtering_duration = 10
assert pool_controler.duration() == result

def test_schedule():
"""Test schedule generated."""
schedule_config = { "break_duration": 3}
noon = datetime(2020,5,5,12,0)
pool_controler = FilteringDuration(schedule_config=schedule_config)
#Fake computed duration for testing.
pool_controler._total_duration = 6
assert "{}".format(pool_controler.update_schedule(noon)) == "{}".format([Run(datetime(2020,5,5,9,0),2.0), Run(datetime(2020,5,5,14,0),4.0) ])
80 changes: 80 additions & 0 deletions tests/test_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# coding: utf-8
"""tests for pypool_pump module - Run class"""

import pytest
from datetime import datetime, timedelta

from pypool_pump import Run

def test_run_propeties():
"""Test Run() properties"""
date = datetime(2020,5,5,12,0)
duration_in_minutes = 65
run = Run(date, duration_in_minutes/60)

assert [run.start_time, run.duration, run.stop_time] == [date, duration_in_minutes/60, datetime(2020,5,5,13,5,0)]

@pytest.mark.parametrize(
"curent_time, state",
[
(datetime(2020,5,4,12,0), False),
(datetime(2020,5,5,11,59), False),
(datetime(2020,5,5,12,0), True),
(datetime(2020,5,5,12,10), True),
(datetime(2020,5,5,13,0), True),
(datetime(2020,5,5,13,4,59), True),
(datetime(2020,5,5,13,5), False),
(datetime(2020,5,5,13,6), False),
(datetime(2020,5,6,12,50), False),
],
)
def test_run_now(curent_time,state):
"""Test Run() run_now() methods"""
date = datetime(2020,5,5,12,0)
duration_in_minutes = 65
run = Run(date, duration_in_minutes/60)

assert run.run_now(curent_time) == state

@pytest.mark.parametrize(
"curent_time, state",
[
(datetime(2020,5,4,12,0), True),
(datetime(2020,5,5,11,59), True),
(datetime(2020,5,5,12,0), True),
(datetime(2020,5,5,12,10), True),
(datetime(2020,5,5,13,0), True),
(datetime(2020,5,5,13,4,59), True),
(datetime(2020,5,5,13,5), True),
(datetime(2020,5,5,13,6), False),
(datetime(2020,5,6,12,50), False),
],
)
def test_run_is_next_run(curent_time, state):
""" Test Run() is_next_run() methods"""
date = datetime(2020,5,5,12,0)
duration_in_minutes = 65
run = Run(date, duration_in_minutes/60)

assert run.is_next_run(curent_time) == state

def test_run_print():
""" Test Run() prints methods"""
date = datetime(2020,5,5,12,0)
duration_in_minutes = 60
run = Run(date, duration_in_minutes/60)

assert "{}".format(run) == "<Run(start={}, stop={}, duration=1.0)>".format(datetime(2020,5,5,12,0),datetime(2020,5,5,13,0))

def test_run_pretty_print():
""" Test Run() prints methods"""
date = datetime.now()
date2 = date + timedelta(days=2)
duration_in_minutes = 65
run1 = Run(date, duration_in_minutes/60)
run2 = Run(date2, duration_in_minutes/60)

assert [run1.pretty_print(), run2.pretty_print() ] == [
"{} - {}".format(date.strftime("%H:%M"), (date + timedelta(minutes=65)).strftime("%H:%M")),
"{} - {}".format(date2.strftime("%a, %H:%M"), (date2 + timedelta(minutes=65)).strftime("%H:%M")),
]

0 comments on commit 3e53636

Please sign in to comment.