Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
70 changes: 70 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: CI Pipeline for FastAPI

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
# Step 1: Checkout Code
- name: Checkout Code
uses: actions/checkout@v4

# Step 2: Setup Python
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.9'

# Step 3: Install Dependencies
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

# Step 4: Run Pylint
- name: Run Pylint
run: |
pip install pylint
pylint app/ tests/

# Step 5: Run Tests
- name: Run Tests
run: |
pip install pytest
pytest tests/

docker-build-and-test:
runs-on: ubuntu-latest
needs: lint-and-test # Ensures linting and testing are successful before Docker steps

steps:
# Step 1: Checkout Code
- name: Checkout Code
uses: actions/checkout@v4

# Step 2: Build Docker Image
- name: Build Docker Image
run: docker build -t common-assessment-tool .

# Step 3: Run Docker Container
- name: Run Docker Container
run: docker run -d -p 8000:8000 --name test-container common-assessment-tool

# Step 4: Test Docker Container
- name: Test Docker Container
run: |
sleep 5 # Wait for container to start
curl -f http://localhost:8000/docs || exit 1

# Step 5: Stop and Remove Docker Container
- name: Clean Up Docker
run: |
docker stop test-container
docker rm test-container
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use a lightweight Python base image
FROM python:3.10-slim

# Set the working directory
WORKDIR /app

# Copy the dependency file (requirements.txt) to the container
COPY requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy all project files to the container
COPY . .

# Expose the service port (FastAPI uses 8000 by default)
EXPOSE 8000

# Start the FastAPI application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,80 @@ This will contain the model used for the project that based on the input informa
The model works off of dummy data of several combinations of clients alongside the interventions chosen for them as well as their success rate at finding a job afterward. The model will be updated by the case workers by inputing new data for clients with their updated outcome information, and it can be updated on a daily, weekly, or monthly basis.

This also has an API file to interact with the front end, and logic in order to process the interventions coming from the front end. This includes functions to clean data, create a matrix of all possible combinations in order to get the ones with the highest increase of success, and output the results in a way the front end can interact with.


Endpoints
1. Create a New Client
URL: /clients/
Method: POST
Description: Creates a new client record. A unique id for each client will be auto-generated and can be future used when getting, updating and deleting a client record.
Request Body:
{ "id": "",
"name": "John Doe",
"email": "john.doe@example.com",
"age": 30,
"gender": "male"
}
Response:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"name": "John Doe",
"email": "john.doe@example.com",
"age": 30,
"gender": "male"
}
Error Response:
400: Unable to create client.


2. Retrieve Client Data
URL: /clients/{client_id}
Method: GET
Description: Retrieves data for a specific client by their unique ID.
Path Parameter:
client_id: The unique ID of the client.
Response:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"name": "John Doe",
"email": "john.doe@example.com",
"age": 30,
"gender": "male"
}
Error Response:
404: Client not found.

3. Update Client Data
URL: /clients/{client_id}
Method: PUT
Description: Updates a client's information.
Path Parameter:
client_id: The unique ID of the client.
Request Body:
{
"email": "new.email@example.com",
"age": 35
}
Response:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"name": "John Doe",
"email": "new.email@example.com",
"age": 35,
"gender": "male"
}
Error Response:
404: Unable to update client.

4. Delete Client
URL: /clients/{client_id}
Method: DELETE
Description: Deletes a client's data by their unique ID.
Path Parameter:
client_id: The unique ID of the client.
Response:
{
"message": "Client deleted successfully"
}
Error Response:
404: Client not found.
209 changes: 206 additions & 3 deletions app/clients/router.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,218 @@
from fastapi import APIRouter
from fastapi.responses import HTMLResponse
"""
This module defines API routes for client-related operations such as creating,
updating, retrieving, and deleting client data. It also includes prediction
functionality.
"""

import uuid
from fastapi import APIRouter, HTTPException
from mysql.connector import Error
from app.database import get_db
from app.clients.service.logic import interpret_and_calculate
from app.clients.schema import PredictionInput
from app.clients.schema import PredictionInput, ClientData


def generate_client_id():
"""
Generate a unique client ID using UUID4.
"""
return str(uuid.uuid4())

router = APIRouter(prefix="/clients", tags=["clients"])


@router.post("/predictions")
async def predict(data: PredictionInput):
"""
Perform predictions based on the provided input data.

Args:
data (PredictionInput): The input data for the prediction model.

Returns:
dict: The calculated prediction results.
"""
print("HERE")
print(data.model_dump())
return interpret_and_calculate(data.model_dump())


@router.post("/", response_model=ClientData)
async def create_client(client_data: ClientData):
"""
Create a new client record in the system.

Args:
client_data (ClientData): The data for the new client to be created.

Returns:
ClientData: The newly created client data.

Raises:
HTTPException: Returns a 400 error if the client creation fails.
"""
created_client = create_client_data(client_data.dict())
if created_client:
return created_client
raise HTTPException(status_code=400, detail="Unable to create client")


@router.get("/{client_id}", response_model=ClientData)
async def get_client(client_id: str):
"""
Retrieve a single client's data by their unique ID.

Args:
client_id (str): The unique ID of the client.

Returns:
ClientData: The client data corresponding to the specified ID.
"""
client = get_client_data(client_id)
if client:
return client
raise HTTPException(status_code=404, detail="Client not found")


@router.put("/{client_id}", response_model=ClientData)
async def update_client(client_id: str, updates: dict):
"""
Update an existing client's data.

Args:
client_id (str): The unique ID of the client.
updates (dict): A dictionary of fields to update.

Returns:
ClientData: The updated client data.
"""
updated_client = update_client_data(client_id, updates)
if updated_client:
return updated_client
raise HTTPException(status_code=404, detail="Unable to update client")


@router.delete("/{client_id}", response_model=dict)
async def delete_client(client_id: str):
"""
Delete a client's data from the system.

Args:
client_id (str): The unique ID of the client.

Returns:
dict: A message indicating successful deletion.
"""
if delete_client_data(client_id):
return {"message": "Client deleted successfully"}
raise HTTPException(status_code=404, detail="Client not found")


def create_client_data(client_data: dict):
"""
Insert a new client record into the database with a unique ID.

Args:
client_data (dict): A dictionary containing the client data to be inserted.

Returns:
dict: The inserted client data with the generated client ID.
"""
db_connection = next(get_db())
cursor = db_connection.cursor()

# Generate a unique client ID
client_data["id"] = generate_client_id()

# Define the SQL INSERT statement
query = """
INSERT INTO clients (id, name, email, age, gender)
VALUES (%s, %s, %s, %s, %s)
"""
values = (
client_data["id"], client_data["name"], client_data["email"],
client_data["age"], client_data["gender"]
)

try:
cursor.execute(query, values)
db_connection.commit()
return client_data
except Error as e:
print(f"Database error: {e}")
db_connection.rollback()
finally:
cursor.close()
return None


def get_client_data(client_id: str):
"""
Retrieve client data using the unique client ID.

Args:
client_id (str): The unique ID of the client.

Returns:
dict: The client data if found, or None otherwise.
"""
db_connection = next(get_db())
cursor = db_connection.cursor()
query = "SELECT * FROM clients WHERE id = %s"
cursor.execute(query, (client_id,))
result = cursor.fetchone()
column_names = [desc[0] for desc in cursor.description]
cursor.close()
if result:
return dict(zip(column_names, result))
return None


def update_client_data(client_id: str, updates: dict):
"""
Update client data for the given client ID.

Args:
client_id (str): The unique client ID.
updates (dict): The fields to update and their new values.

Returns:
dict: The updated client data if successful, or None if an error occurred.
"""
db_connection = next(get_db())
cursor = db_connection.cursor()

update_fields = ", ".join([f"{key} = %s" for key in updates.keys()])
query = f"UPDATE clients SET {update_fields} WHERE id = %s"
values = tuple(updates.values()) + (client_id,)

try:
cursor.execute(query, values)
db_connection.commit()
return get_client_data(client_id)
except Error as e:
print(f"Database error: {e}")
db_connection.rollback()
finally:
cursor.close()
return None


def delete_client_data(client_id: str):
"""
Delete client data for the given client ID.

Args:
client_id (str): The unique client ID.

Returns:
bool: True if the record was deleted, False otherwise.
"""
db_connection = next(get_db())
cursor = db_connection.cursor()
query = "DELETE FROM clients WHERE id = %s"
cursor.execute(query, (client_id,))
db_connection.commit()
success = cursor.rowcount > 0
cursor.close()
return success
Loading