Skip to content

Commit

Permalink
Bugfix empty predictions (#147)
Browse files Browse the repository at this point in the history
* possible solution to bug

* Fix outdated prediction bug

* refactor render functions

---------

Co-authored-by: EricWolters <[email protected]>
  • Loading branch information
rubenpeters91 and WoltersEric authored Dec 4, 2024
1 parent 47ecd9e commit 2ba5e2b
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 202 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.4.11] - 2024-12-03

### Changed
- Dashboard now displays a warning when a patient is currently being called, instead of preventing the user from going to the next patient
- Dashboard will give an informative error when a prediction is no longer available, instead of crashing. Furthermore the user can still navigate to the next patient when this occurs
- Patient selection buttons now moved to a separate function
- Refactor render functions to new module

## [1.4.10] - 2024-12-03

### Changed
Expand Down
41 changes: 16 additions & 25 deletions manifest_dash.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,13 @@
},
"files": {
"requirements.txt": {
"checksum": "f70c5842ab5ed362f02f9e51e8056a6f"
},
".dvcignore": {
"checksum": "34659c119a2634fe318cd6a4822df435"
},
".example.env": {
"checksum": "a851d2b36a975d3155f448d08a4b7527"
},
".gitignore": {
"checksum": "ca95646077c76f4dc6e795b7ef13d27c"
},
".python-version": {
"checksum": "c0479ff484dacd51dc5ba0461b16b9bf"
"checksum": "8e5f1ee0ff8ac9a0f4133f995a4d578b"
},
".streamlit/config.toml": {
"checksum": "99594759045ead1f8535775e35169a3d"
},
"CHANGELOG.md": {
"checksum": "0f741890be14219f021df1965cf70426"
"checksum": "6eb11113c6b074c6762bb4cb187a20b1"
},
"LICENSE": {
"checksum": "770bb10345b6c86b9f5dc743de74aadf"
Expand All @@ -57,28 +45,28 @@
"checksum": "0de252d918314092524baf73e003c014"
},
"dvc.lock": {
"checksum": "c7cc4aa03c818f205dcd4cd8044b887a"
"checksum": "862a01ed24829ab773a883c2b26c2139"
},
"dvc.yaml": {
"checksum": "e14888b51b20aa7ed8d1a7a8d63e33a9"
},
"manifest_api.json": {
"checksum": "ddf3dbd0fd8b52b16d7a241a173b6f09"
"checksum": "2dba93768018274c47a539775246bfac"
},
"manifest_dash.json": {
"checksum": "0dec95869d4660d05d8759834bdcd574"
"checksum": "174e7efbf55e8986322e363508770b4e"
},
"pyproject.toml": {
"checksum": "5dd2d2470bd8383f9cff197ccee8f8d6"
"checksum": "097b224357fed9d87fb1142a40a9a069"
},
"run/app.py": {
"checksum": "07ea1ee70ce2cfdd157a5d234a54cbfb"
},
"run/calling_dash.py": {
"checksum": "8f812b7cca6d9649df19332017f0f930"
"checksum": "49ac44e5ac340369b217e059925fa00f"
},
"run/config/config.toml": {
"checksum": "fc166438569074175c386c4a31d0184c"
"checksum": "922735dc7dbcb39f8ab28e8cb7bb1297"
},
"src/noshow/__init__.py": {
"checksum": "d41d8cd98f00b204e9800998ecf8427e"
Expand All @@ -102,7 +90,10 @@
"checksum": "c7491be3828089c576062b3a933a8bdf"
},
"src/noshow/dashboard/helper.py": {
"checksum": "e1d6be5d1c69dd3ba2979fc9b0a166ff"
"checksum": "f0c16d371e6fef191b66612295710c1c"
},
"src/noshow/dashboard/layout.py": {
"checksum": "a9e441059474f4c1598d3792615eb839"
},
"src/noshow/database/__init__.py": {
"checksum": "d41d8cd98f00b204e9800998ecf8427e"
Expand All @@ -111,7 +102,7 @@
"checksum": "9c1fab4632ca7e9b353a906d4719d243"
},
"src/noshow/database/models.py": {
"checksum": "14812e855bd5dced17e236848feef11e"
"checksum": "1063625fca0b14299530df484ca568dc"
},
"src/noshow/features/appointment_features.py": {
"checksum": "3c58c90ca0b5df7bbce4a328ca235e58"
Expand All @@ -135,7 +126,7 @@
"checksum": "37a9c108b731c632608f5058c9e0f308"
},
"src/noshow/model/train_model.py": {
"checksum": "00964a947199825f721ebbbe0bb23da6"
"checksum": "12587629eddb081d94bc997c8d100dd4"
},
"src/noshow/preprocessing/__init__.py": {
"checksum": "d41d8cd98f00b204e9800998ecf8427e"
Expand All @@ -144,7 +135,7 @@
"checksum": "ad3285f5a7f55f0ca27a6c496212a05e"
},
"src/noshow/preprocessing/load_data.py": {
"checksum": "f63a1f950ab95b38fcf982b733f39f62"
"checksum": "879806e9891c1941e9bb6d87f8dd8b24"
},
"src/noshow/preprocessing/utils.py": {
"checksum": "5c84176aa1d08e3c246120ff5e0b1abf"
Expand All @@ -156,7 +147,7 @@
"checksum": "4bc6f06a7afb732c57147c4f011256d3"
},
"uv.lock": {
"checksum": "603a1be208265e9a0ed7a57aa64e8535"
"checksum": "4552ffe53847634578747e254a08112f"
}
}
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "noshow"
version = "1.4.10"
version = "1.4.11"
authors = [
{ name="Ruben Peters", email="[email protected]" },
{ name="Eric Wolters", email="[email protected]" }
Expand Down
135 changes: 23 additions & 112 deletions run/calling_dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@
from sqlalchemy import select

from noshow.dashboard.connection import get_patient_list, init_session
from noshow.dashboard.helper import (
get_user,
highlight_row,
navigate_patients,
next_preds,
previous_preds,
from noshow.dashboard.helper import get_user
from noshow.dashboard.layout import (
render_appointment_overview,
render_patient_info,
search_number,
render_patient_selection,
)
from noshow.database.models import (
ApiCallResponse,
Expand Down Expand Up @@ -97,6 +94,7 @@ def main():
current_patient = session.get(
ApiSensitiveInfo, patient_ids[st.session_state["name_idx"]]
)

patient_predictions = session.execute(
select(
ApiPrediction.id,
Expand All @@ -116,8 +114,17 @@ def main():
.where(ApiPrediction.active)
.order_by(ApiPrediction.start_time)
).all()

render_patient_selection(patient_ids, Session, enable_dev_mode)
all_predictions_df = pd.DataFrame(patient_predictions)
last_updated = max(all_predictions_df["timestamp"])
if all_predictions_df.empty:
st.error(
"Voorspellingen voor deze patient zijn niet meer beschikbaar. "
"Ga naar de volgende patient.",
icon="🚫",
)
return
last_updated = all_predictions_df["timestamp"].max()
all_predictions_df = all_predictions_df.drop(columns="timestamp")
all_predictions_df.loc[
all_predictions_df["call_status"] == "Gebeld", "call_status"
Expand Down Expand Up @@ -149,117 +156,21 @@ def main():
if current_patient_nmbr.call_number is None:
current_patient_nmbr.call_number = 0

status_list = [
"Niet gebeld",
"Wordt gebeld",
"Gebeld",
"Onbereikbaar",
]
res_list = [
"Herinnerd",
"Verzet/Geannuleerd",
"Geen",
"Bel me niet",
"Voicemail ingesproken",
]
call_number_list = [
"Niet van toepassing",
"Mobielnummer",
"Thuis telefoonnummer",
"Overig telefoonnummer",
]

# Main content of streamlit app
st.write(f"## Patient {st.session_state['name_idx'] + 1}/{len(patient_ids)}")
col1, col2, col3 = st.columns(3)
with col1:
st.button(
"Vorige patient",
on_click=navigate_patients,
args=(len(patient_ids), False, current_response.call_status),
)
with col2:
with st.popover(
"🔍 Zoeken", help="Zoek op telefoonnummer om een patient te vinden."
):
phone_number = st.text_input("Zoek op telefoonnummer...")
st.button(
"Zoek",
on_click=search_number,
args=(Session, phone_number, patient_ids),
)
with col3:
st.button(
"Volgende patient",
on_click=navigate_patients,
args=(len(patient_ids), True, current_response.call_status),
)
st.header("Patient-gegevens")
if enable_dev_mode:
st.write(f"- ID: {patient_ids[st.session_state['name_idx']]}")

render_patient_info(
Session,
current_response,
current_patient,
current_patient_nmbr,
call_number_list,
)

st.header("Afspraakoverzicht")
if not enable_dev_mode:
all_predictions_df = all_predictions_df.drop(columns="id")
st.dataframe(
all_predictions_df.style.apply(highlight_row, axis=1),
use_container_width=True,
hide_index=True,
)

with st.form("patient_form", clear_on_submit=True):
st.selectbox(
"Status gesprek:",
options=status_list,
index=status_list.index(current_response.call_status),
key="status_input",
)
st.selectbox(
"Resultaat gesprek: ",
options=res_list,
index=res_list.index(current_response.call_outcome),
key="res_input",
)
options_idx = [0, 1, 2, 3]
st.selectbox(
"Contact gemaakt via: ",
options=options_idx,
format_func=lambda x: call_number_list[x],
index=options_idx.index(current_patient_nmbr.call_number),
key="number_input",
)
st.text_input("Opmerkingen: ", value=current_response.remarks, key="opm_input")
st.form_submit_button(
"Opslaan",
on_click=next_preds,
args=(
len(all_predictions_df),
Session,
current_response,
current_patient_nmbr,
user_name,
),
type="primary",
)
if current_response.timestamp is not None:
st.caption(
f"Laatst opgeslagen om: {current_response.timestamp:%Y-%m-%d %H:%M:%S}"
)
st.button(
"Vorige",
on_click=previous_preds,
)
st.divider()
st.caption(
f"Laatste voorspellingen gegenereerd om: {last_updated:%Y-%m-%d %H:%M:%S}"
render_appointment_overview(
all_predictions_df,
Session,
user_name,
current_response,
current_patient_nmbr,
last_updated,
enable_dev_mode,
)


Expand Down
66 changes: 2 additions & 64 deletions src/noshow/dashboard/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,60 +14,6 @@
logger = logging.getLogger(__name__)


def render_patient_info(
Session: sessionmaker,
current_response: ApiCallResponse,
current_patient: ApiSensitiveInfo,
current_patient_nmbr: ApiPatient,
call_number_list: List[str],
) -> None:
"""Render patient information
This function is responsible for rendering the patient information on the dashboard.
Parameters
----------
Session : sessionmaker
The SQLAlchemy sessionmaker object used for database operations.
current_response : ApiCallResponse
The current call response object.
current_patient : ApiSensitiveInfo
The current patient object containing sensitive information.
current_patient_nmbr : ApiPatient
The current patient object containing the call number.
call_number_list : List[str]
The list of call number types.
"""
if current_response.call_status == "Niet gebeld":
st.button(
"Start met bellen patient",
on_click=start_calling,
args=(
Session,
current_response,
),
type="primary",
)
else:
if current_patient:
if current_response.call_status != "Wordt gebeld":
st.warning("Deze patient is al gebeld!", icon="⚠️")
st.write(f"- Naam: {current_patient.full_name or 'Onbekend'}")
st.write(f"- Voornaam: {current_patient.first_name or 'Onbekend'}")
st.write(f"- Patientnummer: {current_patient.hix_number or 'Onbekend'}")
st.write(f"- Geboortedatum: {current_patient.birth_date or 'Onbekend'}")
st.write(f"- Mobiel: {current_patient.mobile_phone or 'Onbekend'}")
st.write(f"- Thuis: {current_patient.home_phone or 'Onbekend'}")
st.write(f"- Overig nummer: {current_patient.other_phone or 'Onbekend'}")
st.write("")
if not current_patient_nmbr.call_number:
current_patient_nmbr.call_number = 0
call_number_type = call_number_list[current_patient_nmbr.call_number]
st.write(f"- Eerder contact ging via: {call_number_type or 'Onbekend'}")
else:
st.write("Patientgegevens zijn verwijderd.")


def highlight_row(row: pd.Series) -> List[str]:
"""Highlight a row in a pandas dataframe
Expand Down Expand Up @@ -172,9 +118,7 @@ def next_preds(
st.session_state["pred_idx"] += 1


def navigate_patients(
list_len: int, navigate_forward: bool = True, call_status: str = "Niet gebeld"
):
def navigate_patients(list_len: int, navigate_forward: bool = True):
"""Navigate through patients
Navigates through the patients list and reset the prediction index
Expand All @@ -186,13 +130,7 @@ def navigate_patients(
navigate_forward : bool, optional
Whether to navigate forward or backword, by default True
"""
if call_status == "Wordt gebeld":
st.error(
"Status is 'Wordt gebeld', verander de status voordat je verder gaat.",
icon="🛑",
)
return
elif navigate_forward:
if navigate_forward:
if st.session_state["name_idx"] + 1 < list_len:
st.session_state["name_idx"] += 1
st.session_state["pred_idx"] = 0
Expand Down
Loading

0 comments on commit 2ba5e2b

Please sign in to comment.