-
Notifications
You must be signed in to change notification settings - Fork 52
Feature/menstrual predictor #314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Liad-n
wants to merge
26
commits into
PythonFreeCourse:develop
Choose a base branch
from
Liad-n:feature/menstrual-predictor
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 8 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
5ab86e3
Created menstrual model and partial join form
44d4d09
Fixed merge conflicts
b59eb57
merging develop
783493f
commit to pull develop
665e3a3
merging develop
64c889b
Menstrual predictor before tests
a1a5062
Menstrual predictor with part of tests
f928e94
Merge branch 'develop' of github.com:PythonFreeCourse/calendar into f…
6bef286
partial tests not working properly
d131ac6
Fixed merging conflicts
1f0ba12
Removed conftest from changes
16b0112
Added conftest
9e0d0ca
Fixed some issues with dependency injection
2a93d84
Added annotations in utils
a49878c
Fixed merging conflicts
85eee50
Fixed CR change requests by Yam
b74d3c9
Fixed merging conflicts
73def22
fixed wrong import caused by changed file in develop
9a8b92e
Fixed yam code review change requests
7375a5d
Merge branch 'develop' of github.com:PythonFreeCourse/calendar into f…
dfa0761
small change to re run check
91c69b9
Tried to add to settings
8470ff7
Fixed more CR change requests, improved coverage
103e18d
Merge branch 'develop' of github.com:PythonFreeCourse/calendar into f…
f66da5a
Fixed more CR change requests, improved coverage, retry because tests…
ac4ceca
Fixed CR requests, fixed merge conflicts
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import datetime | ||
from typing import List | ||
|
||
from fastapi import APIRouter, Depends, Request, HTTPException | ||
from fastapi.responses import RedirectResponse | ||
from sqlalchemy.exc import SQLAlchemyError | ||
from sqlalchemy.orm import Session | ||
from starlette.status import HTTP_302_FOUND | ||
|
||
from app.database.models import Event, UserMenstrualPeriodLength | ||
from app.dependencies import get_db, templates | ||
from app.routers.event import create_event | ||
from app.internal.utils import create_model, get_current_user | ||
|
||
from loguru import logger | ||
|
||
|
||
router = APIRouter( | ||
prefix="/menstrual_predictor", | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tags=["menstrual_predictor"], | ||
dependencies=[Depends(get_db)] | ||
) | ||
|
||
MENSTRUAL_PERIOD_CATEGORY_ID = 111 | ||
|
||
|
||
@router.get("/") | ||
def join(request: Request, db: Session = Depends(get_db)): | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
current_user_id = get_current_user(db).id | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if not is_user_signed_up_to_menstrual_predictor(db, current_user_id): | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
logger.info('getting menstrual predictor') | ||
return templates.TemplateResponse("join_menstrual_predictor.html", { | ||
"request": request, | ||
}) | ||
else: | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return RedirectResponse(url='/', status_code=HTTP_302_FOUND) | ||
|
||
|
||
@router.get("/add-period-start/{start_date}") | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def add_period_start(request: Request, start_date: str, | ||
db: Session = Depends(get_db)): | ||
try: | ||
period_start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d') | ||
except ValueError as err: | ||
logger.exception(err) | ||
raise HTTPException( | ||
status_code=400, | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
detail="The given date doesn't match a date format YYYY-MM-DD") | ||
else: | ||
current_user_id = get_current_user(db).id | ||
user_period_length = is_user_signed_up_to_menstrual_predictor( | ||
db, current_user_id) | ||
|
||
remove_existing_period_dates(db, current_user_id) | ||
if user_period_length: | ||
add_3_month_predictions( | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
db, user_period_length, period_start_date, current_user_id) | ||
logger.info('adding menstrual start date') | ||
return RedirectResponse('/', status_code=303) | ||
|
||
|
||
@router.post("/") | ||
async def submit_join_form( | ||
request: Request, | ||
db: Session = Depends(get_db) | ||
): | ||
|
||
data = await request.form() | ||
current_user = get_current_user(session=db) | ||
|
||
user_menstrual_period_length = { | ||
"user_id": current_user.id, | ||
"period_length": data['avg_period_length'], | ||
} | ||
last_period_date = datetime.datetime.strptime( | ||
data['last_period_date'], '%Y-%m-%d') | ||
try: | ||
new_signed_up_to_period = create_model( | ||
session=db, | ||
model_class=UserMenstrualPeriodLength, | ||
**user_menstrual_period_length) | ||
except SQLAlchemyError: | ||
logger.info( | ||
f'User {new_signed_up_to_period.user_id}' | ||
'already signed up to the service, hurray') | ||
db.rollback() | ||
url = '/' | ||
generate_predicted_period_dates( | ||
db, data['avg_period_length'], last_period_date, current_user.id) | ||
|
||
return RedirectResponse(url=url, status_code=HTTP_302_FOUND) | ||
|
||
|
||
@router.get("/get-period-dates/") | ||
def get_period_days(request: Request, db: Session = Depends(get_db)): | ||
period_days = get_all_period_days(db, 1) | ||
gaps_list = [] | ||
for i in range(len(period_days) - 1): | ||
gap = get_date_diff(period_days[i].start, period_days[i + 1].start) | ||
gaps_list.append(gap.days) | ||
logger.critical(get_list_avg(gaps_list)) | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
def get_avg_period_gap(db: Session, user_id): | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
period_days = get_all_period_days(db, user_id) | ||
gaps_list = [] | ||
|
||
for i in range(len(period_days) - 1): | ||
gap = get_date_diff(period_days[i].start, period_days[i + 1].start) | ||
gaps_list.append(gap.days) | ||
return get_list_avg(gaps_list) | ||
|
||
|
||
def get_date_diff(date_1: datetime, date_2: datetime): | ||
return date_2 - date_1 | ||
|
||
|
||
def get_list_avg(received_list: List): | ||
return sum(received_list) // len(received_list) | ||
|
||
|
||
def remove_existing_period_dates(db: Session, user_id: int): | ||
(db.query(Event). | ||
filter(Event.owner_id == user_id). | ||
filter(Event.category_id == MENSTRUAL_PERIOD_CATEGORY_ID). | ||
filter(Event.start > datetime.datetime.now()). | ||
delete()) | ||
db.commit() | ||
logger.info('Removed all period predictions to create new ones') | ||
|
||
|
||
def generate_predicted_period_dates( | ||
db: Session, | ||
period_length: str, | ||
period_start_date: datetime, | ||
user_id: int): | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
delta = datetime.timedelta(int(period_length)) | ||
period_end_date = period_start_date + delta | ||
create_event(db, 'period', period_start_date, period_end_date, | ||
user_id, category_id=MENSTRUAL_PERIOD_CATEGORY_ID) | ||
get_all_period_days(db, user_id=user_id) | ||
|
||
|
||
def add_3_month_predictions(db, period_length, period_start_date, user_id): | ||
avg_gap = get_avg_period_gap(db, user_id) | ||
avg_gap_delta = datetime.timedelta(avg_gap) | ||
for i in range(4): | ||
generate_predicted_period_dates( | ||
db, period_length, period_start_date, user_id) | ||
period_start_date += avg_gap_delta | ||
logger.error(period_start_date) | ||
logger.error(avg_gap_delta) | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
def get_all_period_days(session: Session, user_id: int) -> List[Event]: | ||
"""Returns all period days filtered by user id.""" | ||
|
||
try: | ||
period_days = sorted((session.query(Event). | ||
filter(Event.owner_id == user_id). | ||
filter(Event.category_id | ||
== MENSTRUAL_PERIOD_CATEGORY_ID). | ||
all()), key=lambda d: d.start) | ||
|
||
except SQLAlchemyError as err: | ||
logger.debug(err) | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return [] | ||
else: | ||
return period_days | ||
|
||
|
||
def is_user_signed_up_to_menstrual_predictor(session: Session, user_id: int): | ||
user_menstrual_period_length = session.query( | ||
UserMenstrualPeriodLength).filter(user_id == user_id).first() | ||
if user_menstrual_period_length: | ||
return user_menstrual_period_length.period_length | ||
else: | ||
return False |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
{% extends "base.html" %} | ||
{% block content %} | ||
<div class="container"> | ||
<h3>Please fill in your details</h3> | ||
<form method="post"> | ||
<div class="row"> | ||
<label for="avg_period_length" class="col-sm-2 col-form-label">Average period length</label> | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<div class="col-auto"> | ||
<input type="number" class="form-control" name="avg_period_length" id="avg_period_length" min=1 placeholder="Required" autofocus required><br> | ||
</div> | ||
<div class="col-auto"> | ||
<span id="PeriodLengthHelpInline" class="form-text"> | ||
Must be above 1 day. | ||
</span> | ||
</div> | ||
</div> | ||
<div class="row"> | ||
<label for="last_period_date" class="col-sm-2 col-form-label">Last period date</label> | ||
<div class="col-auto"> | ||
<input type="date" class="form-control" name="last_period_date" id="last_period_date" placeholder="Required" required><br> | ||
</div> | ||
</div> | ||
|
||
<input type="submit" class="btn-sm btn btn-outline-primary" value="Sign Up"> | ||
</form> | ||
</div> | ||
<script> | ||
function change_max_to_today_date(el){ | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
today = new Date(); | ||
today_str = today.toISOString().substring(0,10); | ||
el.max = today_str; | ||
} | ||
function validate_date_older_than_today(received_date){ | ||
return received_date < new Date(); | ||
} | ||
let last_period_date_element = document.getElementById('last_period_date'); | ||
change_max_to_today_date(last_period_date_element); | ||
</script> | ||
{% endblock %} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
class TestMenstrualPredictor: | ||
PREDICTOR_PREFIX = "/menstrual_predictor" | ||
ADD_PERIOD_START = "/add-period-start" | ||
Liad-n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@staticmethod | ||
def test_menstrual_predictor_page_not_signed_up( | ||
client, session): | ||
resp = client.get(TestMenstrualPredictor.PREDICTOR_PREFIX) | ||
assert resp.ok | ||
|
||
@staticmethod | ||
def test_add_period_date( | ||
client, session | ||
): | ||
resp = client.get( | ||
TestMenstrualPredictor.PREDICTOR_PREFIX + | ||
TestMenstrualPredictor.ADD_PERIOD_START + "/2020-12-11") | ||
assert resp.ok |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.