BACK_TO_LOGS// STEP_8_OF_10
HANDBOOK_GUIDE2026-06-29

Human Development Index Predictor: End-to-End Machine Learning Guide

By Rayan Syed
45 min read

Part 8: Frontend User Interface Files

To create a clean interface, we split our frontend assets into three files: index.html, result.html, and style.css.

8.1 How the Frontend Connects with the Backend

Web applications operate on a Client-Server Architecture.

  • The Client (Frontend): This is the user interface running in the browser (index.html, result.html, style.css). Its job is to collect information from the user and display results.
  • The Server (Backend): This is our Flask script (app.py) running on our local machine. It listens for incoming HTTP requests, feeds features to our machine learning model, and returns the prediction.
  • The Communication (HTTP POST & Form Action): In this project, when the user clicks the submit button, the HTML form issues an HTTP POST request directly to the /predict URL. The Flask backend intercepts this request, runs the calculations, and returns result.html compiled with the predicted score.
  • Why split into HTML and CSS?: We separate structure (HTML) and style (CSS) to maintain clean code. This follows the Separation of Concerns principle, making it easy to redesign the UI without modifying backend code.

Frontend Template (templates/index.html)

Save this layout configuration inside templates/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HDI Predictor Portal</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
    <div class="container">
        <h2>Human Development Index Predictor</h2>
        <form action="/predict" method="POST">
            <label for="life_expectancy">Life Expectancy (Years):</label>
            <input type="number" step="any" id="life_expectancy" name="life_expectancy" required placeholder="30 - 110">
            
            <label for="expec_yr_school">Expected Years of Schooling:</label>
            <input type="number" step="any" id="expec_yr_school" name="expec_yr_school" required placeholder="0 - 30">
            
            <label for="mean_yr_school">Mean Years of Schooling:</label>
            <input type="number" step="any" id="mean_yr_school" name="mean_yr_school" required placeholder="0 - 30">
            
            <label for="gross_inc_percap">Gross National Income per Capita ($):</label>
            <input type="number" step="any" id="gross_inc_percap" name="gross_inc_percap" required placeholder="100 - 250,000">
            
            <button type="submit">Predict Score!</button>
        </form>
    </div>
</body>
</html>

Results Template (templates/result.html)

Save this layout configuration inside templates/result.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Prediction Result</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
    <div class="container text-center">
        <h2>Prediction Complete!</h2>
        <h1 class="score-display">{{ score }}</h1>
        <p class="description">(HDI scores are between 0 and 1. Closer to 1 represents higher development.)</p>
        <br>
        <a href="/" class="button">Go Back</a>
    </div>
</body>
</html>

Style Sheet (static/style.css)

Save this CSS configuration inside static/style.css to build our form wrapper:

body {
    background-color: #0c0c0e;
    color: #f3f4f6;
    font-family: system-ui, -apple-system, sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    margin: 0;
    padding: 20px 0;
}

.container {
    background-color: #16161a;
    border: 1px solid #2e2e38;
    padding: 32px;
    border-radius: 16px;
    width: 380px;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
    box-sizing: border-box;
}

.text-center {
    text-align: center;
}

h2 {
    text-align: center;
    margin-bottom: 24px;
    font-size: 1.3rem;
    color: #ffffff;
}

label {
    display: block;
    margin-bottom: 6px;
    font-size: 0.85rem;
    font-weight: 500;
    color: #a1a1aa;
}

input, button, .button {
    width: 100%;
    margin-bottom: 16px;
    padding: 10px;
    border-radius: 8px;
    border: 1px solid #27272a;
    background-color: #1a1a1e;
    color: #ffffff;
    box-sizing: border-box;
    font-size: 0.9rem;
    transition: all 0.2s;
    text-decoration: none;
    display: block;
    text-align: center;
}

input:focus {
    border-color: #6366f1;
    outline: none;
}

button, .button {
    background-color: #6366f1;
    color: white;
    border: none;
    font-weight: 650;
    cursor: pointer;
    margin-top: 10px;
}

button:hover, .button:hover {
    background-color: #4f46e5;
}

.score-display {
    color: #818cf8;
    font-size: 3.5rem;
    margin: 16px 0;
    font-weight: 850;
}

.description {
    font-size: 0.85rem;
    color: #a1a1aa;
}

8.2 Executing the Model Training Script

Before our server can process predictions, we must generate our serialized model weights:

  1. Open your terminal in the root of your hdi_project directory.
  2. Execute the training pipeline:
    python train.py
    
  3. Inspect the Console Outputs: You will see step-by-step progress as Scikit-Learn cleans headers, splits train/test subsets, applies log transforms, compares accuracies, selects the winner, and confirms serialization:
    Step 1: Loading GNI / HDI dataset...
    Step 2: Resolving missing values...
    Step 3: Transforming income via natural log...
    
    Step 4: Training and comparing regression models...
    -> Linear Regression | R2 Score: 95.80% | MSE: 0.000185
    -> Ridge Regression | R2 Score: 95.20% | MSE: 0.000210
    -> Random Forest Regressor | R2 Score: 96.90% | MSE: 0.000135
    
    Winner Selected: Random Forest Regressor with R2 Score of 96.90%.
    
    Step 5: Serializing and saving best model...
    ✓ Output model: models/hdi_model.joblib
    
  4. Verify Output Files: Confirm that hdi_model.joblib has been successfully created inside your models/ directory.

8.3 Running the Flask Web Server

With the model weights and all frontend/backend files created, we can spin up our local development web server:

  1. Rerun the Flask server in your terminal:
    python app.py
    
  2. Confirm Server Initialization: The console will output:
     * Serving Flask app 'app'
     * Debug mode: on
     * Running on http://127.0.0.1:5000
    
  3. Access the Portal: Open your web browser and navigate to:
    http://localhost:5000
    
    You should see your newly created HDI Predictor form displayed.

8.4 Manual Verification & Test Scenarios

To test that your machine learning model and web interface are communicating correctly, try inputting these validation profiles:

  • Test Scenario 1: Highly Developed Country Profile
    • Life Expectancy: 82.5
    • Expected Schooling: 17
    • Mean Schooling: 14.2
    • GNI per Capita: 60,000
    • Expected Result: Prediction Complete! page opens displaying a high development score around 0.940.
  • Test Scenario 2: Underdeveloped Country Profile
    • Life Expectancy: 55.3
    • Expected Schooling: 8
    • Mean Schooling: 4.5
    • GNI per Capita: 1,300
    • Expected Result: Prediction Complete! page opens displaying a lower development score around 0.450.
  • Test Scenario 3: High-Risk Out-of-Bounds Input
    • Enter a Life Expectancy of 15.0.
    • Click Predict Score!.
    • Expected Result: An HTTP 400 error message is displayed saying: Error: Life expectancy must be between 30 and 110 years.

8.5 Critical Fallbacks & Common Run Issues

As a developer, you may encounter these common startup errors. Here is how to diagnose and resolve them:

  • Error: Address already in use (Port Conflict): If you see a crash saying OSError: [Errno 98] Address already in use, it means another service on your computer is already listening on port 5000 (such as macOS AirPlay Receiver, or an orphaned Python task running in the background).
    • The Fix: Open app.py, scroll to the very bottom, and change the port configuration to 5001 or 8080:
      app.run(port=5001, debug=True)
      
      Then, rerun python app.py and navigate to http://localhost:5001.
  • Error: TemplateNotFound: If you navigate to the page and see a Flask traceback error stating jinja2.exceptions.TemplateNotFound: index.html, it means your HTML file is in the wrong directory.
    • The Fix: Double check your workspace sidebar. Ensure index.html and result.html reside inside a folder named templates. If they are in the root or inside static, Flask’s engine will not find them.