Skip to content

Commit

Permalink
Merge pull request #1 from souvik03-136/dev
Browse files Browse the repository at this point in the history
feat: Add Flask application with Docker deployment
  • Loading branch information
TheSilentSage authored Nov 11, 2024
2 parents de08f46 + 7d608c9 commit 02342fe
Show file tree
Hide file tree
Showing 41 changed files with 100,891 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

* text=auto
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

.env
data
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/Entity-Det(text).iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/book-keeping-ai.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 17 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,31 @@


## Features
- [ ] < feature >
- [ ] < feature >
- [ ] < feature >
- [ ] < feature >
- [x] curl -X POST http://127.0.0.1:5000/extract -H "Content-Type: application/json" -d "{\"text\": \"John Doe bought 2 apples for $5\"}"

{"CustomerName":"John Doe","ItemName":"apples","ItemQuantity":"2","Price":"5"}

- [x] curl -X POST "http://127.0.0.1:5000/extract_entities" -H "Content-Type: application/json" -d "{\"text\":\"apples less than 50 rs\"}"

{"action":"less","object":"apples","range":"50"}


<br>

## Dependencies
- < dependency >
- < dependency >
- Flask==2.3.3
- groq==0.9.0
- python-dotenv==1.0.1
- Werkzeug==2.3.7


## Running


< directions to install >
```bash
< insert code >
docker-compose up --build
docker-compose up
```

< directions to execute >
Expand All @@ -46,15 +53,15 @@
<table>
<tr align="center">
<td>
John Doe
Souvik Mahanta
<p align="center">
<img src = "https://dscvit.com/images/dsc-logo-square.svg" width="150" height="150" alt="Your Name Here (Insert Your Image Link In Src">
</p>
<p align="center">
<a href = "https://github.com/person1">
<a href = "https://github.com/souvik03-136">
<img src = "http://www.iconninja.com/files/241/825/211/round-collaboration-social-github-code-circle-network-icon.svg" width="36" height = "36" alt="GitHub"/>
</a>
<a href = "https://www.linkedin.com/in/person1">
<a href = "https://www.linkedin.com/in/souvik-mahanta/">
<img src = "http://www.iconninja.com/files/863/607/751/network-linkedin-social-connection-circular-circle-media-icon.svg" width="36" height="36" alt="LinkedIn"/>
</a>
</p>
Expand Down
Binary file added __pycache__/app.cpython-39.pyc
Binary file not shown.
20 changes: 20 additions & 0 deletions demand_forecast/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use an official Python runtime as a parent image
FROM python:3.9

# Set the working directory in the container
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Upgrade pip to the latest version before installing dependencies
RUN pip install --upgrade pip

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

# Expose port 5000 for Flask to run on
EXPOSE 5000

# Set the default command to run the application
CMD ["python", "app.py"]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added demand_forecast/__pycache__/utils.cpython-310.pyc
Binary file not shown.
Binary file added demand_forecast/__pycache__/utils.cpython-39.pyc
Binary file not shown.
126 changes: 126 additions & 0 deletions demand_forecast/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from flask import Flask, request, jsonify, send_file
from flask_cors import CORS
from pathlib import Path
from forecasting import predict_demand, check_stock_and_alert
from bokeh_forecast import create_bokeh_plots
from utils import load_data
import os
import logging

app = Flask(__name__)

# Enable CORS
CORS(app, resources={r"/*": {"origins": "*"}})

# Configure upload folder and file size limit
UPLOAD_FOLDER = Path(os.getenv('UPLOAD_FOLDER', 'uploads'))
UPLOAD_FOLDER.mkdir(parents=True, exist_ok=True)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16 MB

# Logging setup
app.logger.setLevel(logging.INFO)

# Allowed file extensions
ALLOWED_EXTENSIONS = {'csv', 'xlsx'}
data_file_path = None


def allowed_file(filename):
"""Check if the uploaded file has an allowed extension."""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/')
def home():
return "Welcome to the Bookkeeping AI API! Use /upload to upload a file and /forecast for forecasting."


@app.route('/upload', methods=['POST'])
def upload_file():
global data_file_path

if 'file' not in request.files:
return jsonify({"error": "No file part in the request"}), 400

file = request.files['file']
if file.filename == '':
return jsonify({"error": "No file selected"}), 400

if not allowed_file(file.filename):
return jsonify({"error": "Only .csv and .xlsx files are allowed"}), 400

try:
# Save the new file and overwrite the existing one
data_file_path = UPLOAD_FOLDER / file.filename
file.save(data_file_path)

# Cleanup old plots
for plot in UPLOAD_FOLDER.glob("*.html"):
plot.unlink()

app.logger.info(f"File uploaded successfully: {data_file_path}")
return jsonify({"message": "File uploaded successfully", "file_path": str(data_file_path)})

except Exception as e:
app.logger.error(f"File upload error: {e}")
return jsonify({"error": "Failed to upload file"}), 500


@app.route('/forecast', methods=['POST'])
def forecast():
global data_file_path
if not data_file_path:
return jsonify({"error": "No data file uploaded yet. Please upload a file first."}), 400

data = request.get_json()
item_id = data.get('item_id')

if item_id is None:
return jsonify({"error": "Item ID is required."}), 400

try:
# Load data
df = load_data(data_file_path)

# Validate item ID
if item_id not in df['item_id'].values:
app.logger.error(f"Item ID {item_id} not found in the dataset")
return jsonify({"error": f"Item ID {item_id} not found in the dataset."}), 404

# Generate predictions
future_months, predicted_demand = predict_demand(df, item_id)

if predicted_demand is None:
return jsonify({"error": f"Could not generate forecasts for item ID {item_id}."}), 500

# Generate alerts and plots
alerts = check_stock_and_alert(df, item_id, predicted_demand, future_months)
plot_path = create_bokeh_plots(df, item_id, future_months, predicted_demand)

# Safely convert variables for JSON serialization
response = {
"future_months": future_months if isinstance(future_months, list) else future_months.tolist(),
"predicted_demand": predicted_demand if isinstance(predicted_demand, list) else predicted_demand.tolist(),
"alerts": alerts,
"plot_url": f"/plot/{item_id}"
}

app.logger.info(f"Forecast successfully generated for item ID {item_id}")
return jsonify(response)

except Exception as e:
app.logger.error(f"Error during forecasting: {e}")
return jsonify({"error": "An error occurred during forecasting. Please try again."}), 500


@app.route('/plot/<item_id>')
def plot(item_id):
plot_path = UPLOAD_FOLDER / f"demand_forecast_{item_id}.html"
if plot_path.exists():
return send_file(plot_path)
else:
return jsonify({"error": "Plot not found"}), 404


if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000, debug=True)
Binary file added demand_forecast/best_model.keras
Binary file not shown.
91 changes: 91 additions & 0 deletions demand_forecast/bokeh_forecast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from bokeh.plotting import figure, output_file, save
from bokeh.layouts import column
from bokeh.models import ColumnDataSource
import os


def create_bokeh_plots(df, item_id, future_months, predicted_demand):
# Filter data for the specified item_id
item_data = df[df['item_id'] == item_id].copy()

# Convert transaction date to year-month format
item_data['year_month'] = item_data['transaction_date'].dt.to_period('M')

# Aggregate actual demand data by month
actual_demand = item_data.groupby('year_month')['quantity'].sum().reset_index()

# Prepare ColumnDataSource for actual demand
actual_source = ColumnDataSource(data=dict(
month=actual_demand['year_month'].dt.to_timestamp(),
quantity=actual_demand['quantity']
))

# Prepare ColumnDataSource for predicted demand
predicted_source = ColumnDataSource(data=dict(
month=future_months,
quantity=predicted_demand
))

# Create the actual demand plot
actual_plot = figure(
title=f'Actual Demand for Item ID {item_id}',
x_axis_label='Date',
y_axis_label='Quantity',
x_axis_type='datetime',
width=800,
height=400,
toolbar_location='above',
background_fill_color='#f9f9f9'
)

actual_plot.line(
'month', 'quantity',
source=actual_source,
line_width=2,
color='blue',
legend_label='Actual Demand'
)

actual_plot.scatter(
'month', 'quantity',
source=actual_source,
size=8,
color='blue'
)

# Create the predicted demand plot
predicted_plot = figure(
title=f'Predicted Demand for Item ID {item_id}',
x_axis_label='Date',
y_axis_label='Quantity',
x_axis_type='datetime',
width=800,
height=400,
toolbar_location='above',
background_fill_color='#f9f9f9'
)

predicted_plot.line(
'month', 'quantity',
source=predicted_source,
line_width=2,
color='orange',
legend_label='Predicted Demand'
)

predicted_plot.scatter(
'month', 'quantity',
source=predicted_source,
size=8,
color='orange'
)

# Define the HTML file path
plot_filename = f"uploads/demand_forecast_{item_id}.html"
os.makedirs(os.path.dirname(plot_filename), exist_ok=True)

# Output the Bokeh plots to an HTML file
output_file(plot_filename)
save(column(actual_plot, predicted_plot)) # Save the layout to the file instead of showing it

return plot_filename # Return the path to the HTML file
10 changes: 10 additions & 0 deletions demand_forecast/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: '3'
services:
app:
build: .
ports:
- "5000:5000"
volumes:
- .:/app
environment:
- FLASK_DEBUG=1
Loading

0 comments on commit 02342fe

Please sign in to comment.