Heart Disease Risk Prediction API

Train an ML model and serve heart disease risk predictions via FastAPI.

Introduction

30 Second Summary

Every year, heart disease kills more people than any other condition. Doctors use clinical measurements like cholesterol levels, blood pressure, and heart rate to assess a patient's risk. Machine learning can analyze these numbers and flag at-risk patients instantly.

In this project, you will train a machine learning model on real patient data, evaluate it with healthcare-appropriate metrics, and wrap it in an API that accepts patient vitals and returns a risk prediction with a confidence score.

What You'll Build

You send patient clinical data to your API, and it returns an instant heart disease risk assessment with a probability percentage. Open the interactive docs in your browser, fill in a form, and watch the prediction come back in real time.

By the end of this project, you'll have:

  • A trained prediction model that takes 13 clinical features (age, cholesterol, blood pressure, chest pain type) and returns whether a patient is at risk of heart disease.
  • A running FastAPI prediction endpoint with interactive browser-based documentation where you can test predictions by filling in a form.
  • A healthcare-aware evaluation that prioritizes not missing sick patients over chasing raw accuracy scores.
  • Secret Mission: Add a Streamlit dashboard where anyone can input patient vitals with sliders and see a live risk prediction with color-coded results.

Are there any prerequisites?

You should be comfortable with Python basics (variables, functions, loops). No machine learning experience is required.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters. Taking a moment to articulate the goal now makes every step ahead more intentional.

Set Up Your Environment

Machine learning projects pull in a lot of libraries. Without a clean, isolated environment, package conflicts can cause confusing errors that waste hours of debugging time.

In this step, you will create a dedicated project folder, set up a Python virtual environment, and install the four packages that power your prediction API.

In this step, get ready to:
  • Create a project folder and virtual environment.
  • Install scikit-learn, pandas, FastAPI, and joblib.
  • Verify all imports work from a single command.
Create your project folder

Every terminal starts in a different directory depending on your system. Moving to the Desktop first guarantees your project folder ends up somewhere you can find it.

  • Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir heart-disease-predictor
cd heart-disease-predictor

Your terminal prompt now shows heart-disease-predictor as the current directory.

Set up your virtual environment

A virtual environment is an isolated copy of Python where you can install packages without affecting anything else on your machine.

  • Create a virtual environment by running this command:
python -m venv .venv
  • Activate the virtual environment so that every package you install goes into this isolated space:

macOS

source .venv/bin/activate

Windows

.venv\Scripts\Activate.ps1

After activation, your terminal prompt changes to show (.venv) at the beginning. This confirms every command you run now uses the isolated environment.

  • Confirm your Python version by running:
python --version

You should see Python 3.11 or higher printed in the output.

Seeing a version lower than 3.11?

This project requires Python 3.11 or higher because scikit-learn 1.9.0 needs it. If you see an older version, install Python 3.11+ from python.org and recreate your virtual environment using the new Python binary.

On macOS, the system python3 may resolve to an older version even after installing a newer one. Use the full path (e.g. python3.11) or a version manager like pyenv to point python to your new installation.

Help me fix my Python version

Install required packages

Your project needs four packages. Each one handles a different part of the ML pipeline.

  • Install all four packages by running this command:
pip install scikit-learn pandas "fastapi[standard]" joblib

What do these packages do?

  • scikit-learn provides machine learning algorithms and evaluation tools.
  • pandas handles data loading and manipulation.
  • fastapi[standard] gives you the web framework for your prediction API, bundled with a development server.
  • joblib saves and loads trained models to disk.

The install takes a minute or two. You will see a progress bar for each package being downloaded.

Install failing or hanging?

Make sure you see (.venv) in your terminal prompt. If it is missing, rerun the activation command from the previous substep.

If you see a permissions error, do NOT use sudo pip install. Deactivate, delete the .venv folder, recreate the virtual environment, activate it, and try again.

Help me troubleshoot my pip install

  • Open your project in VS Code by running:
code .

What does code . do?

The code command launches VS Code from the terminal. The dot (.) tells it to open the current folder as a workspace. You will see your heart-disease-predictor folder in the VS Code sidebar.

Before you write any code, confirm that all packages imported correctly. What do you think this verification command will print if everything installed properly?

  • Verify all imports work by running this command in your terminal:
python -c "import sklearn; import pandas; import fastapi; print('All good!')"

You should see All good! printed with no errors.

Seeing a ModuleNotFoundError?

Double check that (.venv) appears in your prompt. If it does not, your terminal is using the system Python which does not have the packages installed. Rerun the activation command for your platform.

If you activated but still see the error, run pip list to see what is installed. Look for scikit-learn, pandas, and fastapi in the output. If any are missing, rerun the install command.

Help me fix my ModuleNotFoundError

Your environment is ready. Next up, you will load a real clinical dataset and explore what patient data looks like before training a model on it.

Load and Explore the Heart Disease Data

Before you can predict anything, you need to understand the raw material you are working with.

The UCI Heart Disease dataset is a real clinical dataset collected from patients at the Cleveland Clinic and used by ML researchers worldwide. Loading and inspecting it reveals what features doctors actually measure and what you are trying to predict.

In this step, get ready to:
  • Load the Cleveland Heart Disease dataset from the UCI repository into a pandas DataFrame.
  • Clean and binarize the target variable for binary classification.
  • Print dataset statistics to understand the shape, features, and class balance.
Create train.py and load the dataset

Your training script needs to pull real patient data from the UCI Machine Learning Repository, clean it up, and show you what you are working with.

  • In VS Code, create a new file called train.py in your heart-disease-predictor folder.
  • Copy the following code into train.py:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report, accuracy_score
import joblib

# Load the dataset
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
columns = [
    "age", "sex", "cp", "trestbps", "chol", "fbs", "restecg",
    "thalach", "exang", "oldpeak", "slope", "ca", "thal", "target"
]
df = pd.read_csv(url, names=columns, na_values="?")

# Handle missing values
df = df.dropna()

# Binarize target: 0 = no disease, 1 = disease present
df["target"] = (df["target"] > 0).astype(int)

# Print dataset info
print(f"Dataset shape: {df.shape}")
print(f"\nFeatures: {list(df.columns[:-1])}")
print(f"\nTarget distribution:\n{df['target'].value_counts()}")
print(f"\nFirst 5 rows:\n{df.head()}")

What does this code do?

  • The imports bring in everything you will need across this project: pandas for data handling, scikit-learn for ML, and joblib for saving your trained model later.
  • pd.read_csv(url, names=columns, na_values="?") downloads the Cleveland dataset directly from the UCI repository. The na_values="?" flag tells pandas to treat question marks in the raw file as missing data.
  • df.dropna() removes the handful of rows with missing values (about 6 rows in the ca and thal columns).
  • The original target column uses 0 for no disease and 1-4 for varying presence. The binarize line converts it to a simple 0 (healthy) vs 1 (disease present) for binary classification.

Before you run this, what do you think the output will show? Consider how many rows might remain after dropping missing values, and how balanced the two classes might be.

  • Save train.py.
  • Run your script by entering this in the terminal:
python train.py

You should see a printed summary showing the dataset shape (approximately 297 rows and 14 columns after dropping missing values), the 13 feature names, the target distribution showing how many patients have vs. do not have heart disease, and the first 5 rows of data.

Seeing a connection error?

The script downloads data from the UCI repository, so it needs an internet connection. Check that your Wi-Fi is connected and try again.

If you see ModuleNotFoundError, your virtual environment may not be active. Run source .venv/bin/activate (macOS) or .venv\Scripts\activate (Windows) and try again.

Help me debug my train.py script

Understand your dataset

Look at the output in your terminal. You have 13 clinical features that doctors actually measure during patient evaluations.

What do these 13 features mean?

  • age, sex — basic patient demographics.
  • cp — chest pain type (4 categories from typical angina to asymptomatic).
  • trestbps, chol, fbs — resting blood pressure, cholesterol, and fasting blood sugar.
  • restecg, thalach, exang — resting ECG results, max heart rate achieved, and exercise-induced angina.
  • oldpeak, slope — ST depression induced by exercise and the slope of the peak ST segment.
  • ca, thal — number of major vessels colored by fluoroscopy and thalassemia type.

Check the target distribution in your output. The two classes (0 for no disease, 1 for disease present) should be relatively balanced. This means neither class overwhelmingly dominates the other.

Why does class balance matter?

If 95% of patients had no disease, a model could just say "no disease" every time and score 95% accuracy. With a roughly balanced dataset, your model has to actually learn patterns to perform well.

✔️ Awesome, I've got everything!

Great. Make sure you have saved train.py and that your terminal output shows the dataset shape, features, target distribution, and first 5 rows.

ⓧ I'd like to double check the full code

Here is the complete train.py at this point:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report, accuracy_score
import joblib

# Load the dataset
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"
columns = [
    "age", "sex", "cp", "trestbps", "chol", "fbs", "restecg",
    "thalach", "exang", "oldpeak", "slope", "ca", "thal", "target"
]
df = pd.read_csv(url, names=columns, na_values="?")

# Handle missing values
df = df.dropna()

# Binarize target: 0 = no disease, 1 = disease present
df["target"] = (df["target"] > 0).astype(int)

# Print dataset info
print(f"Dataset shape: {df.shape}")
print(f"\nFeatures: {list(df.columns[:-1])}")
print(f"\nTarget distribution:\n{df['target'].value_counts()}")
print(f"\nFirst 5 rows:\n{df.head()}")

You now have real clinical data loaded, cleaned, and ready for machine learning. Next up, you will train your first model on this data and discover why accuracy alone can be dangerous in healthcare.

Train a Baseline Model

In the previous step, you loaded the UCI Heart Disease dataset and explored its structure. You have 297 patient records with 13 clinical features and a binary target indicating disease presence.

Now it is time to train your first model and evaluate it. The catch: accuracy alone hides a critical flaw in healthcare. A model that says "no disease" for everyone can still score 54% accuracy on this dataset. The classification report reveals whether your model actually catches sick patients or just guesses the majority class.

In this step, get ready to:
  • Split the data into training and test sets with stratification.
  • Train a Random Forest classifier and measure its accuracy.
  • Examine the classification report to see if accuracy tells the full story.
Split the data into training and test sets

Before training a model, you need to separate your data into two groups. The training set teaches the model patterns, and the test set evaluates how well it performs on data it has never seen.

  • In train.py, scroll to the bottom of the file (after the existing print statements).
  • Add the following code to split your features and target, then create the train/test split:
# Split features and target
X = df.drop("target", axis=1)
y = df["target"]

# Train/test split with stratification
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

What does this code do?

X holds all 13 feature columns (the patient measurements), and y holds just the target column (0 or 1).

train_test_split randomly divides the data so 80% goes to training and 20% to testing. The stratify=y parameter ensures both sets keep the same ratio of disease-present to disease-absent patients. The random_state=42 locks in the randomness so you get reproducible results every time.

  • Save train.py.
Train the baseline model and evaluate it

A Random Forest is an ensemble of decision trees that votes on each prediction. You will train one with default settings and print both its accuracy and a full classification report.

  • Add the following code below your train/test split in train.py:
# Baseline model
print("\n--- Baseline Model (Default Random Forest) ---")
baseline = RandomForestClassifier(random_state=42)
baseline.fit(X_train, y_train)
y_pred_baseline = baseline.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred_baseline):.3f}")
print(f"\nClassification Report:\n{classification_report(y_test, y_pred_baseline)}")

What does this code do?

RandomForestClassifier(random_state=42) creates a forest of decision trees with reproducible randomness. Calling .fit() trains the model on your training data.

.predict() generates predictions on the test set. Then you print two metrics: raw accuracy (percentage of correct guesses overall) and the full classification report, which breaks performance down by class.

  • Save train.py.

Before you run this, think: if accuracy comes back at 80%, does that mean the model is good enough for a hospital? What would you want to know beyond accuracy?

  • Run your training script by entering this command in your terminal:
python train.py

You will see your dataset info from before, followed by a new section showing the baseline model accuracy and a full classification report with precision, recall, and F1-score for both classes.

Look at the recall value for class 1 (disease present). This number tells you what percentage of actually sick patients your model catches.

This is the problem

If recall for class 1 is below 0.90, your model misses more than 10% of sick patients. In a hospital, that means sending genuinely ill people home with a clean bill of health.

The accuracy might look reasonable, but it hides this dangerous gap. A model that misses sick patients is worse than no model at all. The next step fixes this.

Seeing an error when you run the script?

If you see NameError: name 'X_train' is not defined, make sure the split code appears before the baseline model code in your file.

If you see a network error when loading the dataset, check your internet connection. The script downloads data from the UCI repository each time it runs.

Help me debug my train.py script

✔️ Awesome, I've got everything!

Great. Double check you have saved train.py with both the split and the baseline model code.

ⓧ I'd like to double check the full code

Your train.py should contain these sections in order. Scroll through your file and confirm each one is present:

  • Imports (pandas, sklearn modules, joblib).
  • Dataset loading from the UCI URL with column names and na_values="?".
  • Missing value handling with dropna().
  • Target binarization (df["target"] > 0).
  • Dataset info prints (shape, features, target distribution, first 5 rows).
  • Feature/target split (X and y).
  • Train/test split with stratify=y and random_state=42.
  • Baseline model: RandomForestClassifier(random_state=42), fit, predict, accuracy print, classification report print.

You have now seen the gap that raw accuracy hides. Your model might score well overall, but it lets sick patients slip through. Next up, you will fix this by tuning the model to prioritize catching every sick patient, even at the cost of more false alarms.

Improve the Model for Healthcare Safety

Your baseline model from the last step revealed a critical problem. The accuracy score looked reasonable, but the classification report showed something alarming: the model is missing sick patients.

In healthcare, a missed diagnosis (false negative) is far more dangerous than a false alarm (false positive). In this step, you will fix the model by building a proper Pipeline with feature scaling and class weighting that penalizes the model for missing sick patients.

In this step, get ready to:
  • Build a scikit-learn Pipeline that chains feature scaling and classification together.
  • Tune the model with balanced class weights to prioritize catching sick patients.
  • Save the trained model to disk so your API can load it later.
Build the improved model with a Pipeline

A Pipeline in scikit-learn chains multiple processing steps into a single object. Your pipeline will first scale the features with StandardScaler (so no single feature dominates), then pass the scaled data to a Random Forest for classification.

The key fix is class_weight="balanced". This tells the model to automatically penalize misclassifications of the minority class more heavily. It adjusts weights inversely proportional to class frequencies, forcing the model to work harder to catch sick patients.

  • In train.py, find the last line of your baseline section:
print(f"\nClassification Report:\n{classification_report(y_test, y_pred_baseline)}")
  • Add the following code below that line to build, train, evaluate, and save your improved model:
# Improved model with pipeline and class weighting
print("\n--- Improved Model (Scaled + Balanced Class Weights) ---")
pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("classifier", RandomForestClassifier(
        n_estimators=100,
        class_weight="balanced",
        random_state=42
    ))
])

pipeline.fit(X_train, y_train)
y_pred_improved = pipeline.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred_improved):.3f}")
print(f"\nClassification Report:\n{classification_report(y_test, y_pred_improved)}")

# Save the model
joblib.dump(pipeline, "model.joblib")
print("\nModel saved to model.joblib")

What does this code do?

  • Pipeline([...]) chains a StandardScaler (normalizes feature values) with a RandomForestClassifier. This prevents data leakage because the scaler only learns from training data, never test data.
  • class_weight="balanced" automatically adjusts weights inversely proportional to class frequencies. The model gets penalized more for missing a sick patient than for a false alarm.
  • joblib.dump(pipeline, "model.joblib") saves the entire trained pipeline (scaler + classifier) to a single file. Your API will load this file instead of retraining from scratch.

Why a Pipeline instead of separate steps?

Without a Pipeline, you would scale training data with one scaler object and need to remember to apply that same scaler to new data at prediction time. A Pipeline bundles everything together. When the API loads model.joblib, it gets the scaler AND the classifier in one call.

  • Save train.py.
Run and compare the results

Before you run the script, think about this: what do you expect to happen to the recall for class 1 (disease present) compared to your baseline?

  • Run the updated training script:
python train.py

You will see both the baseline and improved classification reports printed one after the other. Compare the recall values for class 1 (disease present) between the two models. The improved model's recall for class 1 should be higher than the baseline.

At the bottom of the output, you should see the confirmation message: Model saved to model.joblib.

  • Confirm the model file was created by running:
ls model.joblib

You should see model.joblib listed. This file contains your entire trained pipeline ready for the API to load.

Why is higher recall worth the trade-off?

Higher recall for the disease-present class means fewer sick patients slip through undetected. The trade-off is that precision might drop slightly, meaning more false alarms. In a hospital, a false alarm leads to extra tests. A missed diagnosis can be fatal.

Seeing an error when running the script?

  • If you see IndentationError, check that the Pipeline definition uses consistent indentation (4 spaces per level).
  • If you see NameError: name 'Pipeline' is not defined, make sure the import line from sklearn.pipeline import Pipeline is at the top of your file.
  • If the recall for class 1 did not improve, double-check that you included class_weight="balanced" inside the RandomForestClassifier() call.

Help me debug my train.py script

✔️ Awesome, I've got everything!

Great. Double check you have saved your file and that model.joblib exists in your project folder.

ⓧ I'd like to double check the full code

Your train.py should contain these sections in order. Scroll through your file and confirm each one is present:

  • Imports (pandas, sklearn modules, joblib).
  • Dataset loading from the UCI URL with column names and missing value handling.
  • Target binarization and dataset info printing.
  • Train/test split with stratification.
  • Baseline model (default RandomForestClassifier) with accuracy and classification report.
  • Improved model section: Pipeline definition with StandardScaler and balanced RandomForestClassifier, training, and classification report.
  • Model save: joblib.dump(pipeline, "model.joblib") followed by a print confirmation.

Your model now prioritizes patient safety over raw accuracy. Next up, you will wrap this model in a FastAPI endpoint so any application can send patient data and get back a risk prediction instantly.

Build the Prediction API

Your model is trained, evaluated, and saved to disk. Right now it lives in a script that only you can run.

In this step, you will wrap your model in a FastAPI endpoint that accepts patient data as JSON and returns a risk prediction. This is exactly how ML models are deployed in production.

In this step, get ready to:
  • Define a Pydantic data model that validates all 13 clinical features.
  • Create a prediction endpoint that loads the saved model and returns a risk assessment.
  • Test the API using FastAPI's auto-generated interactive docs.
Create the FastAPI app and patient data model

FastAPI uses Pydantic models to validate incoming request data. You will define a class that lists every clinical feature the model expects, with its correct type.

  • Create a new file called main.py in your heart-disease-predictor folder and paste the following code:
from fastapi import FastAPI
from pydantic import BaseModel
import pandas as pd
import joblib

app = FastAPI(
    title="Heart Disease Risk Predictor",
    description="Predicts heart disease risk from patient clinical data"
)

# Load model at startup
model = joblib.load("model.joblib")


class PatientData(BaseModel):
    age: int
    sex: int
    cp: int
    trestbps: int
    chol: int
    fbs: int
    restecg: int
    thalach: int
    exang: int
    oldpeak: float
    slope: int
    ca: int
    thal: int

What does this code do?

This sets up the foundation of your prediction API.

  • joblib.load("model.joblib") loads your trained pipeline into memory when the app starts. Every request reuses this loaded model instead of retraining.
  • PatientData is a Pydantic model that defines the exact shape of valid input. If someone sends a string where an integer is expected, FastAPI automatically returns a clear error.
  • oldpeak is the only float field because ST depression is measured in decimal increments. All other features are integers.
  • Save main.py.
  • Verify the app loads correctly by running this command:
python -c "from main import app; print(app.title)"

You should see Heart Disease Risk Predictor printed. This confirms your imports work and the model loaded from disk.

Seeing a ModuleNotFoundError?

  • Make sure your virtual environment is activated. You should see (.venv) at the start of your terminal prompt.
  • Check that model.joblib exists in the same folder as main.py. If it is missing, run python train.py again to regenerate it.

Why is my FastAPI app failing to import?

Add the prediction endpoint

The app can load data, but it has no endpoints yet. You need a response model for outgoing predictions and a POST route that runs the model on incoming patient data.

  • In main.py, add the following code below the PatientData class:
class PredictionResult(BaseModel):
    prediction: int
    probability: float
    risk_level: str


@app.get("/")
def read_root():
    return {"message": "Heart Disease Risk Predictor API. Visit /docs for interactive documentation."}

What does this code do?

  • PredictionResult defines the shape of the response. FastAPI uses this to validate outgoing data and generate documentation.
  • read_root() is a simple health-check endpoint. Visiting the root URL confirms the API is running.
  • Now add the main prediction logic below the read_root function:
@app.post("/predict", response_model=PredictionResult)
def predict(patient: PatientData):
    # Convert input to DataFrame
    data = pd.DataFrame([patient.model_dump()])

    # Make prediction
    prediction = model.predict(data)[0]
    probability = model.predict_proba(data)[0][1]

    # Determine risk level
    if probability < 0.3:
        risk_level = "Low Risk"
    elif probability < 0.6:
        risk_level = "Moderate Risk"
    else:
        risk_level = "High Risk"

    return PredictionResult(
        prediction=int(prediction),
        probability=round(float(probability), 3),
        risk_level=risk_level
    )

What does the predict endpoint do?

  • patient.model_dump() converts the Pydantic object into a dictionary, which pd.DataFrame() wraps into the tabular format your scikit-learn pipeline expects.
  • predict_proba(data)[0][1] gets the probability for class 1 (disease present). This gives a nuanced confidence score instead of just a binary yes or no.
  • The risk level label maps that probability into a human-readable category: Low Risk (below 30%), Moderate Risk (30-60%), or High Risk (above 60%).
  • Save main.py.

Not sure where to paste?

The PredictionResult class should start on the line after thal: int (the last field in PatientData). Leave two blank lines between the classes.

Help me figure out where to add my prediction endpoint code.

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py with both the PatientData and PredictionResult classes, plus both endpoints.

ⓧ I'd like to double check the full code

Scroll up and confirm your main.py contains these sections in order:

  • Imports (fastapi, pydantic, pandas, joblib).
  • FastAPI app creation with title and description.
  • Model loaded at startup with joblib.load("model.joblib").
  • PatientData class with 13 fields (all int except oldpeak: float).
  • PredictionResult class with prediction, probability, and risk_level.
  • GET / root endpoint.
  • POST /predict endpoint with DataFrame conversion, prediction, probability, and risk level logic.
Run the server and test your API

Your API code is complete. Time to start the server and send it a real prediction request through the browser.

  • Start the FastAPI development server by running:
fastapi dev main.py

What does this command do?

fastapi dev starts a local development server at http://127.0.0.1:8000 with auto-reload enabled. Any time you save main.py, the server restarts automatically.

This command takes over your terminal. You will not be able to type other commands in this terminal while the server is running. Press Ctrl+C to stop the server when you are done.

You should see output showing the server started at http://127.0.0.1:8000.

  • Open your browser and go to http://127.0.0.1:8000/docs to see the interactive API documentation.

You should see a page listing your two endpoints: GET / and POST /predict. FastAPI generates this documentation automatically from your Pydantic models.

Before you test, what do you think the response will contain when you submit patient data to the /predict endpoint?

  • Click the POST /predict endpoint to expand it.
  • Click Try it out.
  • Replace the example JSON body with this sample patient data:
{
  "age": 55,
  "sex": 1,
  "cp": 4,
  "trestbps": 140,
  "chol": 260,
  "fbs": 0,
  "restecg": 0,
  "thalach": 130,
  "exang": 1,
  "oldpeak": 2.5,
  "slope": 2,
  "ca": 2,
  "thal": 7
}
  • Click Execute.

You should see a JSON response containing three fields: prediction (0 or 1), probability (a decimal between 0.0 and 1.0), and risk_level (a human-readable label). This 55-year-old patient with asymptomatic chest pain, high cholesterol, and exercise-induced angina should come back as elevated risk.

Seeing a 422 Validation Error?

  • Make sure you replaced the entire example body with the JSON above. Do not leave any placeholder values.
  • Check that oldpeak has a decimal value (2.5, not "2.5" as a string). All numeric fields must be numbers, not strings in quotes.
  • Confirm all 13 fields are present in the JSON body. FastAPI rejects the request if any field is missing.

Why am I getting a 422 error from my FastAPI predict endpoint?

Your trained model is now a running API. Any application can send patient data and get back an instant risk prediction with a confidence score. Next up, you will extend this into a visual dashboard that anyone can interact with.

Secret mission

Add a Streamlit Dashboard

Your API works great for developers who know how to send JSON requests. But what about non-technical people, like a recruiter or a doctor who wants to try your model? In this secret mission, you will build a visual dashboard with Streamlit that lets anyone input patient data and see a live risk prediction with colored indicators.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources ready for demos, pause them, or delete them entirely.

Resources you used:

  • Project folder heart-disease-predictor on your Desktop (contains train.py, main.py, dashboard.py).
  • Virtual environment (.venv) with installed Python packages (scikit-learn, pandas, FastAPI, Streamlit, joblib).
  • Trained model file (model.joblib).

Keep everything running

No action needed. Choose this if you plan to demo this project to a recruiter or continue experimenting with the model.

  • Your project folder contains everything needed to run the API anytime.
  • To start the API again, activate the virtual environment and run fastapi dev main.py.
  • To start the Streamlit dashboard, run streamlit run dashboard.py in a separate terminal.

Pause - I'll come back to this later

Stop any running processes to free up system memory. Your files stay intact on disk.

  • Press Ctrl+C in any terminal running the FastAPI server or Streamlit dashboard to stop them.
  • Deactivate your virtual environment by running:
deactivate

When you come back, reactivate the environment and start the servers again.

  • Navigate to your project folder and run source .venv/bin/activate (macOS/Linux) or .venv\Scripts\activate (Windows).

Delete - I don't want to use this again

Remove all project files and reclaim disk space. You can recreate everything from scratch using the install commands if you change your mind.

  • Press Ctrl+C in any terminal running the FastAPI server or Streamlit dashboard.
  • Deactivate your virtual environment by running:
deactivate
  • Navigate to your Desktop and delete the entire project folder.

On macOS/Linux:

cd ~/Desktop
rm -rf heart-disease-predictor

On Windows:

cd ~\Desktop
rmdir /s /q heart-disease-predictor

This removes all project files, the virtual environment, and the trained model in one step.

Nice Work!

Nice Work!

You just built a full heart disease risk prediction API from scratch. You took raw clinical data, trained a machine learning model with healthcare-appropriate evaluation, and deployed it as a live endpoint that anyone can query.

You've learned how to:

  • Train and evaluate a Random Forest classifier on real clinical data, discovering why accuracy alone is dangerous in healthcare and using recall to prioritize catching sick patients.
  • Build a production-style scikit-learn Pipeline with feature scaling and class weighting, then save the trained model with joblib for deployment.
  • Deploy your model as a FastAPI prediction endpoint with Pydantic input validation and auto-generated interactive documentation.
  • Secret Mission: Created a Streamlit dashboard giving anyone a visual interface to interact with your ML model and see live risk predictions.

Ready to quiz yourself?