0% found this document useful (0 votes)
14 views10 pages

Overtime Forecasting API Documentation

Uploaded by

yousifhedayah
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views10 pages

Overtime Forecasting API Documentation

Uploaded by

yousifhedayah
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

from fastapi import FastAPI, UploadFile, File, HTTPException, Header, Body

from [Link] import CORSMiddleware


from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
import pandas as pd
import tempfile
import os
import logging
from datetime import datetime
from [Link] import RotatingFileHandler

# Handle imports
try:
from .model_pipeline import (
predict_future_from_dataframe,
predict_from_features,
get_monthly_features_from_dataframe
)
except ImportError:
from model_pipeline import (
predict_future_from_dataframe,
predict_from_features,
get_monthly_features_from_dataframe
)

# Ensure logs directory exists


LOGS_DIR =
[Link]([Link]([Link]([Link](__file__))), "logs")
[Link](LOGS_DIR, exist_ok=True)

# Configure logging
logger = [Link](__name__)
[Link]([Link])

file_handler = RotatingFileHandler(
[Link](LOGS_DIR, "[Link]"),
maxBytes=10*1024*1024,
backupCount=5
)
file_handler.setLevel([Link])

console_handler = [Link]()
console_handler.setLevel([Link])

formatter = [Link]('%(asctime)s - %(name)s - %(levelname)s - %


(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

[Link](file_handler)
[Link](console_handler)

# ===================================
# 🔐 USERS DATABASE (Dictionary)
# ===================================
USERS_DB = {
"admin": {
"password": "admin123",
"name": "Administrator",
"role": "Admin"
},
"manager": {
"password": "manager456",
"name": "Manager User",
"role": "Manager"
},
"yousif": {
"password": "yousif",
"name": "John Doe",
"role": "Analyst"
}
}

def verify_credentials(username: str, password: str):


"""Verify username and password"""
if username not in USERS_DB:
[Link](f"❌ Login attempt with unknown username: {username}")
return None

user = USERS_DB[username]
if user["password"] != password:
[Link](f"❌ Invalid password for user: {username}")
return None

[Link](f"✅ User authenticated: {username} ({user['name']})")


return user

# ===================================
# 📋 PYDANTIC MODELS
# ===================================

class RawDataRequest(BaseModel):
"""Request model for raw transaction data"""
data: List[Dict[str, Any]] = Field(
...,
description="List of raw transaction records",
example=[
{
"TransactionDate": "2024-01-15",
"NumberOfHours": 8.5,
"EmployeeID": "E001",
"Position": "Engineer",
"Hierarchy": "Level3",
"OvertimeHourlyRate": 50.0,
"EmployeeCount": 100,
"DepartmentCount": 5
}
]
)
n_months: int = Field(
default=6,
ge=1,
le=6,
description="Number of months to forecast (1-6)"
)
class FeaturesRequest(BaseModel):
"""Request model for predict-from-features endpoint"""
features: List[Dict[str, Any]] = Field(
...,
description="List of monthly feature dictionaries",
example=[
{
"Date": "2024-01-01",
"Total_NumberOfHours": 1000,
"AVG_OvertimeRate": 50,
"AVG_EmployeeCount": 100,
"AVG_DepartmentCount": 10,
"Position_mode_monthly": 1,
"Hierarchy_mode_monthly": 2,
"Is_HolidayOrWeekend": 5,
"Total_Employees_Worked": 80
}
]
)
last_date: str = Field(
...,
description="Last historical date (YYYY-MM-DD)",
example="2024-12-01"
)
n_months: int = Field(
default=6,
ge=1,
le=6,
description="Number of months to forecast (1-6)"
)

# ===================================
# FastAPI Application
# ===================================

app = FastAPI(
title="Overtime Forecasting API",
version="2.0.0",
description="Secure API for predicting future overtime hours with flexible
forecasting duration"
)

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

@[Link]("/")
async def root():
"""Root endpoint - service information."""
return {
"service": "Overtime Forecasting API",
"version": "2.0.0",
"message": "API is running",
"status": "running",
"authentication": "Required - Use X-Username and X-Password headers",
"total_users": len(USERS_DB),
"endpoints": {
"/predict": "Send raw JSON data and predict (1-6 months)",
"/predict-from-features": "Predict from pre-processed features (1-6
months)",
"/extract-features": "Extract monthly features from raw JSON data"
}
}

@[Link]("/health")
async def health_check():
"""Health check endpoint - No auth required."""
[Link]("Health check requested")
return {"status": "healthy", "version": "2.0.0"}

@[Link]("/hello")
async def hello():
"""Simple hello endpoint - No auth required."""
return {"message": "hello world", "version": "2.0.0"}

@[Link]("/predict")
async def predict_overtime(
request: RawDataRequest,
x_username: str = Header(None, alias="X-Username"),
x_password: str = Header(None, alias="X-Password")
):
"""

This endpoint accepts raw transaction data in JSON format and runs
the complete pipeline: preprocessing → aggregation → feature engineering →
prediction.

Args:
request: RawDataRequest with transaction data and n_months
x_username: Username header
x_password: Password header

Returns:
JSON with predictions and metadata

Example request body:


{
"data": [
{
"TransactionDate": "2024-01-15",
"NumberOfHours": 8.5,
"EmployeeID": "E001",
"Position": "Engineer",
"Hierarchy": "Level3",
"OvertimeHourlyRate": 50.0,
"EmployeeCount": 100,
"DepartmentCount": 5
},
...
],
"n_months": 3
}
"""

# ✅ Verify credentials
if not x_username or not x_password:
[Link]("❌ Missing authentication headers")
raise HTTPException(
status_code=401,
detail="Authentication required. Provide X-Username and X-Password
headers."
)

user = verify_credentials(x_username, x_password)


if not user:
raise HTTPException(
status_code=403,
detail="Invalid username or password"
)

[Link](f"✅ Authenticated user: {x_username} ({user['name']}) - Role:


{user['role']}")
[Link](f"📅 Forecast duration: {request.n_months} months")
[Link](f"📊 Received {len([Link])} transaction records")

try:
# Convert JSON data to DataFrame
raw_df = [Link]([Link])

# Validate required columns


required_cols = [
'TransactionDate', 'NumberOfHours', 'EmployeeID',
'Position', 'Hierarchy', 'OvertimeHourlyRate',
'EmployeeCount', 'DepartmentCount'
]

missing_cols = [col for col in required_cols if col not in raw_df.columns]


if missing_cols:
raise HTTPException(
status_code=400,
detail=f"Missing required columns: {missing_cols}"
)

[Link](f"Data validation passed")

# Generate predictions through full pipeline


predictions_df = predict_future_from_dataframe(raw_df,
n_months=request.n_months)

# Format response
response = {
"status": "success",
"predictions": predictions_df.to_dict("records"),
"metadata": {
"forecast_months": request.n_months,
"model_type": "XGBoost",
"start_date": predictions_df['Date'].iloc[0] if len(predictions_df)
> 0 else None,
"end_date": predictions_df['Date'].iloc[-1] if len(predictions_df)
> 0 else None,
"total_predictions": len(predictions_df),
"input_records": len([Link]),
"generated_at": [Link]().strftime("%Y-%m-%d %H:%M:%S"),
"generated_by": user['name'],
"user_role": user['role']
}
}

[Link](f"✅ Prediction successful for {x_username}:


{len(predictions_df)} months forecasted")
return response

except ValueError as e:
[Link](f"❌ Validation error for {x_username}: {str(e)}")
raise HTTPException(status_code=400, detail=str(e))

except Exception as e:
[Link](f"❌ Prediction failed for {x_username}: {str(e)}",
exc_info=True)
raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")

@[Link]("/predict-from-features")
async def predict_from_features_endpoint(
request: FeaturesRequest,
x_username: str = Header(None, alias="X-Username"),
x_password: str = Header(None, alias="X-Password")
):
"""

This endpoint skips data processing and uses pre-aggregated features.


Much faster than /predict endpoint.

Args:
request: FeaturesRequest with features, last_date, and n_months
x_username: Username header
x_password: Password header

Returns:
JSON with predictions and metadata
"""

# ✅ Verify credentials
if not x_username or not x_password:
[Link]("❌ Missing authentication headers")
raise HTTPException(
status_code=401,
detail="Authentication required. Provide X-Username and X-Password
headers."
)

user = verify_credentials(x_username, x_password)


if not user:
raise HTTPException(
status_code=403,
detail="Invalid username or password"
)
[Link](f"✅ Authenticated user: {x_username} ({user['name']}) - Role:
{user['role']}")
[Link](f"📅 Forecast duration: {request.n_months} months from features")

try:
# Convert features to DataFrame
features_df = [Link]([Link])
[Link](f"Received {len(features_df)} months of features")

# Generate predictions
predictions_df = predict_from_features(
features_df,
request.last_date,
n_months=request.n_months
)

# Format response
response = {
"status": "success",
"predictions": predictions_df.to_dict("records"),
"metadata": {
"forecast_months": request.n_months,
"model_type": "XGBoost",
"start_date": predictions_df['Date'].iloc[0] if len(predictions_df)
> 0 else None,
"end_date": predictions_df['Date'].iloc[-1] if len(predictions_df)
> 0 else None,
"total_predictions": len(predictions_df),
"generated_at": [Link]().strftime("%Y-%m-%d %H:%M:%S"),
"generated_by": user['name'],
"user_role": user['role'],
"input_features_count": len(features_df)
}
}

[Link](f"✅ Prediction from features successful for {x_username}:


{len(predictions_df)} months forecasted")
return response

except ValueError as e:
[Link](f"❌ Validation error for {x_username}: {str(e)}")
raise HTTPException(status_code=400, detail=str(e))

except Exception as e:
[Link](f"❌ Prediction failed for {x_username}: {str(e)}",
exc_info=True)
raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")

@[Link]("/extract-features")
async def extract_features(
request: RawDataRequest,
x_username: str = Header(None, alias="X-Username"),
x_password: str = Header(None, alias="X-Password")
):
"""
Extract monthly aggregated features from raw JSON data.
Args:
request: RawDataRequest with transaction data (n_months ignored here)
x_username: Username header
x_password: Password header

Returns:
JSON with monthly features and last_date
"""

# ✅ Verify credentials
if not x_username or not x_password:
[Link]("❌ Missing authentication headers")
raise HTTPException(
status_code=401,
detail="Authentication required. Provide X-Username and X-Password
headers."
)

user = verify_credentials(x_username, x_password)


if not user:
raise HTTPException(
status_code=403,
detail="Invalid username or password"
)

[Link](f"✅ Authenticated user: {x_username} ({user['name']}) - Role:


{user['role']}")
[Link](f"📊 Received {len([Link])} transaction records for feature
extraction")

try:
# Convert JSON data to DataFrame
raw_df = [Link]([Link])

# Validate required columns


required_cols = [
'TransactionDate', 'NumberOfHours', 'EmployeeID',
'Position', 'Hierarchy', 'OvertimeHourlyRate',
'EmployeeCount', 'DepartmentCount'
]

missing_cols = [col for col in required_cols if col not in raw_df.columns]


if missing_cols:
raise HTTPException(
status_code=400,
detail=f"Missing required columns: {missing_cols}"
)

[Link](f"✅ Data validation passed")

# Extract features
monthly_df, last_date = get_monthly_features_from_dataframe(raw_df)

# Format response
response = {
"status": "success",
"features": monthly_df.to_dict("records"),
"last_date": last_date.strftime("%Y-%m-%d"),
"metadata": {
"total_months": len(monthly_df),
"date_range": {
"start": monthly_df['Date'].min().strftime("%Y-%m-%d"),
"end": last_date.strftime("%Y-%m-%d")
},
"input_records": len([Link]),
"extracted_at": [Link]().strftime("%Y-%m-%d %H:%M:%S"),
"extracted_by": user['name'],
"user_role": user['role']
}
}

[Link](f"✅ Feature extraction successful for {x_username}:


{len(monthly_df)} months")
return response

except Exception as e:
[Link](f"❌ Feature extraction failed for {x_username}: {str(e)}",
exc_info=True)
raise HTTPException(status_code=500, detail=f"Feature extraction failed:
{str(e)}")

@[Link]("/users")
async def list_users(
x_username: str = Header(None, alias="X-Username"),
x_password: str = Header(None, alias="X-Password")
):
"""
🔒 PROTECTED - List all registered users (for admin purposes).
Requires admin authentication.
Returns usernames and names only, no passwords.
"""
# Verify credentials
if not x_username or not x_password:
[Link]("❌ Unauthorized access attempt to /users")
raise HTTPException(
status_code=401,
detail="Authentication required"
)

user = verify_credentials(x_username, x_password)


if not user:
raise HTTPException(
status_code=403,
detail="Invalid username or password"
)

# Only admins can view users list


if user["role"] != "Admin":
[Link](f"❌ Non-admin user {x_username} tried to access /users")
raise HTTPException(
status_code=403,
detail="Only administrators can view users list"
)

[Link](f"✅ Users list accessed by admin: {x_username}")


users_list = [
{
"username": username,
"name": data["name"],
"role": data["role"]
}
for username, data in USERS_DB.items()
]
return {
"total_users": len(users_list),
"users": users_list,
"accessed_by": user["name"]
}

You might also like