Skip to content
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

Empty appointments bug fix #153

Merged
merged 8 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [1.5.1] - 2024-12-23

### Added
- Error warmings incase no appointments are provided
- Added ssl-certificate fix in deploy script
- Added expetion tests

### Changed
- Saving of predictions is now located in helper function

## [1.5.0] - 2024-12-10

Expand Down
3 changes: 3 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ do
fi
done

# necessary for certifi to find the right certificate if run without conda forge's ca-certificates
export SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt

read -p "What do you want to deploy? Options: 'admin-dash'/1 ; 'calling-dash'/2 ; 'calling-dash-test'/3 ; 'api'/4 ; 'api-test'/5 " APPLICATION
APPLICATION=${APPLICATION:-N}

Expand Down
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.5.0"
version = "1.5.1"
authors = [
{ name="Ruben Peters", email="[email protected]" },
{ name="Eric Wolters", email="[email protected]" }
Expand Down
78 changes: 10 additions & 68 deletions src/noshow/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@
fix_outdated_appointments,
load_model,
remove_sensitive_info,
store_predictions,
)
from noshow.api.pydantic_models import Appointment
from noshow.config import CLINIC_CONFIG, KEEP_SENSITIVE_DATA
from noshow.database.models import (
ApiPatient,
ApiPrediction,
ApiRequest,
ApiSensitiveInfo,
Base,
)
from noshow.model.predict import create_prediction
Expand Down Expand Up @@ -119,10 +117,18 @@ async def predict(
project_path = Path(__file__).parents[3]
start_time = datetime.now()

if len(input) == 0:
raise HTTPException(status_code=400, detail="Input cannot be empty.")

input_df = load_appointment_pydantic(input)
appointments_df = process_appointments(input_df, CLINIC_CONFIG, start_date)
all_postalcodes = process_postal_codes(project_path / "data" / "raw" / "NL.txt")

if appointments_df.empty:
raise HTTPException(
status_code=400, detail="No appointments for the start date and filters"
)

model = load_model()
prediction_df = create_prediction(
model,
Expand Down Expand Up @@ -160,71 +166,7 @@ async def predict(
)
db.add(apirequest)

for _, row in prediction_df.iterrows():
apisensitive = db.get(ApiSensitiveInfo, row["pseudo_id"])

if not apisensitive:
if row["name_text"] is None:
row["name_text"] = ""
logger.warning(
f"Patient {row['pseudo_id']} has no name_text, "
"replacing with empty string"
)

apisensitive = ApiSensitiveInfo(
patient_id=row["pseudo_id"],
hix_number=row["patient_id"],
full_name=row["name_text"],
first_name=row["name_given1_callMe"],
birth_date=row["birthDate"],
mobile_phone=row["telecom1_value"],
home_phone=row["telecom2_value"],
other_phone=row["telecom3_value"],
)
else:
# name and birthdate can't change, but phone number might
apisensitive.mobile_phone = row["telecom1_value"]
apisensitive.home_phone = row["telecom2_value"]
apisensitive.other_phone = row["telecom3_value"]

apipatient = db.get(ApiPatient, row["pseudo_id"])
if not apipatient:
apipatient = ApiPatient(
id=row["pseudo_id"],
)
apipatient.treatment_group = int(row["treatment_group"])
apiprediction = db.get(ApiPrediction, row["APP_ID"])
if not apiprediction:
apiprediction = ApiPrediction(
id=row["APP_ID"],
patient_id=row["pseudo_id"],
prediction=row["prediction"],
start_time=row["start"],
request_relation=apirequest,
patient_relation=apipatient,
clinic_name=row["hoofdagenda"],
clinic_reception=row["description"],
clinic_phone_number=CLINIC_CONFIG[row["clinic"]].phone_number,
clinic_teleq_unit=CLINIC_CONFIG[row["clinic"]].teleq_name,
active=True,
)
else:
# All values of a prediction can be updated except the ID and treatment
apiprediction.prediction = row["prediction"]
apiprediction.start_time = row["start"]
apiprediction.request_relation = apirequest
apiprediction.clinic_name = row["hoofdagenda"]
apiprediction.clinic_reception = row["description"]
apiprediction.clinic_phone_number = CLINIC_CONFIG[
row["clinic"]
].phone_number
apiprediction.clinic_teleq_unit = CLINIC_CONFIG[row["clinic"]].teleq_name
apiprediction.active = True

db.merge(apisensitive)
db.merge(apiprediction)
db.merge(apipatient)
db.commit()
store_predictions(prediction_df, db, logger, apirequest, CLINIC_CONFIG)

fix_outdated_appointments(db, prediction_df["APP_ID"], start_date)

Expand Down
90 changes: 90 additions & 0 deletions src/noshow/api/app_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,93 @@ def create_treatment_groups(
predictions = predictions.drop(columns="score_bin")
predictions["treatment_group"] = predictions["treatment_group"].astype(int)
return predictions


def store_predictions(
prediction_df: pd.DataFrame,
db: Session,
logger: Any,
apirequest: Any,
CLINIC_CONFIG: Dict[str, Any],
) -> None:
"""
Store predictions in the database.

Parameters
----------
prediction_df : pd.DataFrame
DataFrame containing the predictions.
db : Session
Database session.
logger : Any
Logger for logging warnings and information.
apirequest : Any
API request object related to the predictions.
CLINIC_CONFIG : Dict[str, Any]
Configuration dictionary for clinic details.
"""
for _, row in prediction_df.iterrows():
apisensitive = db.get(ApiSensitiveInfo, row["pseudo_id"])

if not apisensitive:
if row["name_text"] is None:
row["name_text"] = ""
logger.warning(
f"Patient {row['pseudo_id']} has no name_text, "
"replacing with empty string"
)

apisensitive = ApiSensitiveInfo(
patient_id=row["pseudo_id"],
hix_number=row["patient_id"],
full_name=row["name_text"],
first_name=row["name_given1_callMe"],
birth_date=row["birthDate"],
mobile_phone=row["telecom1_value"],
home_phone=row["telecom2_value"],
other_phone=row["telecom3_value"],
)
else:
# name and birthdate can't change, but phone number might
apisensitive.mobile_phone = row["telecom1_value"]
apisensitive.home_phone = row["telecom2_value"]
apisensitive.other_phone = row["telecom3_value"]

apipatient = db.get(ApiPatient, row["pseudo_id"])
if not apipatient:
apipatient = ApiPatient(
id=row["pseudo_id"],
)
apipatient.treatment_group = int(row["treatment_group"])
apiprediction = db.get(ApiPrediction, row["APP_ID"])
if not apiprediction:
apiprediction = ApiPrediction(
id=row["APP_ID"],
patient_id=row["pseudo_id"],
prediction=row["prediction"],
start_time=row["start"],
request_relation=apirequest,
patient_relation=apipatient,
clinic_name=row["hoofdagenda"],
clinic_reception=row["description"],
clinic_phone_number=CLINIC_CONFIG[row["clinic"]].phone_number,
clinic_teleq_unit=CLINIC_CONFIG[row["clinic"]].teleq_name,
active=True,
)
else:
# All values of a prediction can be updated except the ID and treatment
apiprediction.prediction = row["prediction"]
apiprediction.start_time = row["start"]
apiprediction.request_relation = apirequest
apiprediction.clinic_name = row["hoofdagenda"]
apiprediction.clinic_reception = row["description"]
apiprediction.clinic_phone_number = CLINIC_CONFIG[
row["clinic"]
].phone_number
apiprediction.clinic_teleq_unit = CLINIC_CONFIG[row["clinic"]].teleq_name
apiprediction.active = True

db.merge(apisensitive)
db.merge(apiprediction)
db.merge(apipatient)
db.commit()
29 changes: 29 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,32 @@ async def test_predict_endpoint(monkeypatch):
output = await predict(appointments_pydantic, "2024-07-16", FakeDB(), "test")
output_df = pd.DataFrame(output)
assert output_df.shape == (5, 17)


# teste empty appointments
@pytest.mark.asyncio
async def test_predict_endpoint_empty_appointments(monkeypatch):
appointments_pydantic = fake_appointments()
monkeypatch.setattr(app, "get_bins", fake_bins)
monkeypatch.setattr(app, "process_postal_codes", fake_postal_codes)
monkeypatch.setattr(app, "load_model", fake_model)
monkeypatch.setattr(app_helpers, "delete", lambda x: FakeWhere())
# patch create treatment groups and add column to the dataframe
monkeypatch.setattr(
app, "create_treatment_groups", lambda x, y, z, q: x.assign(treatment_group=1)
)
monkeypatch.setattr(app, "CLINIC_CONFIG", create_unit_test_clinic_config())
monkeypatch.setenv("DB_USER", "")
monkeypatch.setenv("X_API_KEY", "test")

# empty appointments
with pytest.raises(Exception) as exc_info_empty:
__ = await predict([], "2024-07-16", FakeDB(), "test")
assert "400: Input cannot be empty." in str(exc_info_empty.value)

# no appointments for the start date
with pytest.raises(Exception) as exc_inf_wrong_date:
__ = await predict(appointments_pydantic, "2024-07-15", FakeDB(), "test")
assert "400: No appointments for the start date and filters" in str(
exc_inf_wrong_date.value
)
Loading