brpuneet898 commited on
Commit
1dfcad5
·
1 Parent(s): 0e56e07

initial commit

Browse files
Files changed (45) hide show
  1. .gitattributes +1 -0
  2. Dockerfile +12 -0
  3. app.py +31 -0
  4. modules/__init__.py +0 -0
  5. modules/__pycache__/__init__.cpython-312.pyc +0 -0
  6. modules/__pycache__/__init__.cpython-38.pyc +0 -0
  7. modules/__pycache__/campaign_analysis.cpython-312.pyc +0 -0
  8. modules/__pycache__/campaign_analysis.cpython-38.pyc +0 -0
  9. modules/__pycache__/inventory_optimization.cpython-312.pyc +0 -0
  10. modules/__pycache__/inventory_optimization.cpython-38.pyc +0 -0
  11. modules/__pycache__/machine_failure.cpython-38.pyc +0 -0
  12. modules/__pycache__/pricing_intelligence.cpython-312.pyc +0 -0
  13. modules/__pycache__/pricing_intelligence.cpython-38.pyc +0 -0
  14. modules/__pycache__/sales_perdiction.cpython-312.pyc +0 -0
  15. modules/__pycache__/sales_perdiction.cpython-38.pyc +0 -0
  16. modules/__pycache__/supply_failure.cpython-38.pyc +0 -0
  17. modules/__pycache__/underperformance.cpython-312.pyc +0 -0
  18. modules/__pycache__/underperformance.cpython-38.pyc +0 -0
  19. modules/campaign_analysis.py +162 -0
  20. modules/inventory_optimization.py +7 -0
  21. modules/machine_failure.py +376 -0
  22. modules/pricing_intelligence.py +7 -0
  23. modules/sales_perdiction.py +246 -0
  24. modules/supply_failure.py +363 -0
  25. modules/underperformance.py +7 -0
  26. requirements.txt +11 -0
  27. static/images/campaign.jpg +0 -0
  28. static/images/cover.jpeg +0 -0
  29. static/images/inventory.png +3 -0
  30. static/images/machine.jpeg +0 -0
  31. static/images/pricing.jpg +0 -0
  32. static/images/sales prediction.png +3 -0
  33. static/images/supply.jpeg +0 -0
  34. static/images/underperforming.png +3 -0
  35. static/index.js +690 -0
  36. static/styles.css +99 -0
  37. templates/base.html +38 -0
  38. templates/campaign_analysis.html +105 -0
  39. templates/index.html +104 -0
  40. templates/inventory_optimization.html +11 -0
  41. templates/machine_failure.html +101 -0
  42. templates/pricing_intelligence.html +11 -0
  43. templates/sales_prediction.html +147 -0
  44. templates/supply_failure.html +99 -0
  45. templates/underperformance.html +11 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.png filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 7860
11
+
12
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template
2
+ from modules.sales_perdiction import sales_prediction_bp
3
+ from modules.campaign_analysis import campaign_analysis_bp
4
+ from modules.inventory_optimization import inventory_optimization_bp
5
+ from modules.pricing_intelligence import pricing_intelligence_bp
6
+ from modules.underperformance import underperformance_bp
7
+ from modules.machine_failure import machine_failure_bp
8
+ from modules.supply_failure import supply_failure_bp
9
+
10
+ app = Flask(__name__)
11
+ app.secret_key = "secret_key"
12
+ app.config['SESSION_TYPE'] = 'filesystem'
13
+
14
+ app.register_blueprint(sales_prediction_bp)
15
+ app.register_blueprint(campaign_analysis_bp)
16
+ app.register_blueprint(inventory_optimization_bp)
17
+ app.register_blueprint(pricing_intelligence_bp)
18
+ app.register_blueprint(underperformance_bp)
19
+ app.register_blueprint(machine_failure_bp)
20
+ app.register_blueprint(supply_failure_bp)
21
+
22
+ @app.route('/')
23
+ def index():
24
+ return render_template('index.html', title="Home - Predictive Analytics Tool")
25
+
26
+ # if __name__ == '__main__':
27
+ # app.run(debug=True)
28
+
29
+ if __name__ == "__main__":
30
+ port = 7860
31
+ app.run(debug=True, host="0.0.0.0", port=port)
modules/__init__.py ADDED
File without changes
modules/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (180 Bytes). View file
 
modules/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (164 Bytes). View file
 
modules/__pycache__/campaign_analysis.cpython-312.pyc ADDED
Binary file (654 Bytes). View file
 
modules/__pycache__/campaign_analysis.cpython-38.pyc ADDED
Binary file (5.8 kB). View file
 
modules/__pycache__/inventory_optimization.cpython-312.pyc ADDED
Binary file (686 Bytes). View file
 
modules/__pycache__/inventory_optimization.cpython-38.pyc ADDED
Binary file (562 Bytes). View file
 
modules/__pycache__/machine_failure.cpython-38.pyc ADDED
Binary file (10.5 kB). View file
 
modules/__pycache__/pricing_intelligence.cpython-312.pyc ADDED
Binary file (672 Bytes). View file
 
modules/__pycache__/pricing_intelligence.cpython-38.pyc ADDED
Binary file (548 Bytes). View file
 
modules/__pycache__/sales_perdiction.cpython-312.pyc ADDED
Binary file (14.1 kB). View file
 
modules/__pycache__/sales_perdiction.cpython-38.pyc ADDED
Binary file (7.03 kB). View file
 
modules/__pycache__/supply_failure.cpython-38.pyc ADDED
Binary file (10.3 kB). View file
 
modules/__pycache__/underperformance.cpython-312.pyc ADDED
Binary file (666 Bytes). View file
 
modules/__pycache__/underperformance.cpython-38.pyc ADDED
Binary file (542 Bytes). View file
 
modules/campaign_analysis.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, render_template, request, flash, redirect, url_for
2
+ import pandas as pd
3
+ import numpy as np
4
+ import json
5
+
6
+ campaign_analysis_bp = Blueprint('campaign_analysis', __name__, url_prefix='/campaign')
7
+
8
+ _current_sales_df = None
9
+ _current_campaign_df = None
10
+
11
+ def normalize(s):
12
+ return str(s).strip().lower()
13
+
14
+ # OPTIONAL: channel mapping between campaign and sales if desired
15
+ CHANNEL_MAP = {
16
+ 'whatsapp': 'online',
17
+ 'email': 'online',
18
+ 'mobile app': 'online',
19
+ 'tv': 'offline',
20
+ # add more as needed
21
+ }
22
+
23
+ @campaign_analysis_bp.route('/', methods=['GET'])
24
+ def show_campaign_analysis():
25
+ return render_template('campaign_analysis.html', title="Campaign Analysis Engine")
26
+
27
+ @campaign_analysis_bp.route('/upload', methods=['POST'])
28
+ def upload_files():
29
+ global _current_sales_df, _current_campaign_df
30
+
31
+ sales_file = request.files.get('sales_file')
32
+ campaign_file = request.files.get('campaign_file')
33
+ if not sales_file or not campaign_file:
34
+ flash('Please upload both sales and campaign CSV files.')
35
+ return redirect(url_for('campaign_analysis.show_campaign_analysis'))
36
+
37
+ try:
38
+ _current_sales_df = pd.read_csv(sales_file, parse_dates=['date'], dayfirst=True)
39
+ _current_campaign_df = pd.read_csv(campaign_file, parse_dates=['start_date', 'end_date'], dayfirst=True)
40
+ except Exception as e:
41
+ flash(f'Error reading CSVs: {e}')
42
+ return redirect(url_for('campaign_analysis.show_campaign_analysis'))
43
+
44
+ try:
45
+ analysis = analyze_campaigns(_current_sales_df, _current_campaign_df)
46
+ plot_data = make_plot_data(analysis['campaigns'])
47
+ except Exception as e:
48
+ flash(f'Error during analysis: {e}')
49
+ return redirect(url_for('campaign_analysis.show_campaign_analysis'))
50
+
51
+ return render_template('campaign_analysis.html',
52
+ title="Campaign Analysis Engine",
53
+ campaigns=analysis['campaigns'],
54
+ summary=analysis['summary'],
55
+ columns=_current_campaign_df.columns.tolist(),
56
+ plot_data=plot_data # <--- DO NOT json.dumps here!
57
+ )
58
+
59
+ def make_plot_data(campaigns):
60
+ # For bar and line charts
61
+ names = [c['name'] if c['name'] else c['id'] for c in campaigns]
62
+ roi = [c['roi'] if c['roi']==c['roi'] else 0 for c in campaigns]
63
+ uplift = [c['uplift_pct'] if c['uplift_pct']==c['uplift_pct'] else 0 for c in campaigns]
64
+ revenue = [c['total_revenue'] for c in campaigns]
65
+ start_dates = [str(c['start_date']) for c in campaigns]
66
+ types = [c['name'] for c in campaigns]
67
+ regions = [c['region'] for c in campaigns]
68
+
69
+ # Pie chart of types
70
+ from collections import Counter
71
+ type_counts = Counter(types)
72
+ region_counts = Counter(regions)
73
+
74
+ return {
75
+ "bar_uplift": {"x": names, "y": uplift},
76
+ "bar_roi": {"x": names, "y": roi},
77
+ "line_revenue": {"x": start_dates, "y": revenue, "names": names},
78
+ "pie_type": {"labels": list(type_counts.keys()), "values": list(type_counts.values())},
79
+ "pie_region": {"labels": list(region_counts.keys()), "values": list(region_counts.values())}
80
+ }
81
+
82
+ def map_campaign_channel_to_sales(campaign_channel):
83
+ # Normalize and map campaign channel to sales channel if needed
84
+ ch = normalize(campaign_channel)
85
+ return CHANNEL_MAP.get(ch, ch)
86
+
87
+ def analyze_campaigns(sales_df, campaign_df):
88
+ """
89
+ For each campaign, compute total units/revenue during the campaign period,
90
+ estimate uplift vs. pre-campaign, and simple ROI.
91
+ """
92
+ sales_df['channel'] = sales_df['channel'].astype(str)
93
+ campaign_results = []
94
+ for _, camp in campaign_df.iterrows():
95
+ # Split target_skus and iterate
96
+ target_skus = [sku.strip() for sku in str(camp['target_skus']).split(',')]
97
+ region = camp['region']
98
+ # Map campaign channel to sales channel if needed
99
+ campaign_channel = camp['channel']
100
+ mapped_channel = map_campaign_channel_to_sales(campaign_channel)
101
+ start_date = camp['start_date']
102
+ end_date = camp['end_date']
103
+
104
+ # Filter sales for this campaign
105
+ sales_mask = (
106
+ sales_df['sku'].isin(target_skus)
107
+ & (sales_df['region'] == region)
108
+ & (sales_df['channel'].str.lower() == mapped_channel)
109
+ & (sales_df['date'] >= start_date)
110
+ & (sales_df['date'] <= end_date)
111
+ )
112
+ sales_during = sales_df[sales_mask]
113
+ total_units = int(sales_during['sales_units'].sum())
114
+ total_revenue = float(sales_during['sales_revenue'].sum())
115
+
116
+ # Uplift: compare average daily units in campaign vs. 14 days prior
117
+ pre_mask = (
118
+ sales_df['sku'].isin(target_skus)
119
+ & (sales_df['region'] == region)
120
+ & (sales_df['channel'].str.lower() == mapped_channel)
121
+ & (sales_df['date'] >= (start_date - pd.Timedelta(days=14)))
122
+ & (sales_df['date'] < start_date)
123
+ )
124
+ sales_pre = sales_df[pre_mask]
125
+ days_campaign = (end_date - start_date).days + 1
126
+ days_pre = (sales_pre['date'].max() - sales_pre['date'].min()).days + 1 if not sales_pre.empty else 1
127
+ avg_daily_campaign = total_units / days_campaign if days_campaign > 0 else 0
128
+ avg_daily_pre = sales_pre['sales_units'].sum() / days_pre if days_pre > 0 and not sales_pre.empty else 0
129
+ uplift_pct = ((avg_daily_campaign - avg_daily_pre) / avg_daily_pre * 100) if avg_daily_pre > 0 else np.nan
130
+
131
+ # ROI: (Incremental revenue - budget) / budget
132
+ budget = camp.get('budget', np.nan)
133
+ try:
134
+ budget = float(budget)
135
+ except Exception:
136
+ budget = np.nan
137
+ incremental_revenue = total_revenue - sales_pre['sales_revenue'].sum() if not sales_pre.empty else total_revenue
138
+ roi = ((incremental_revenue - budget) / budget * 100) if pd.notna(budget) and budget > 0 else np.nan
139
+
140
+ campaign_results.append({
141
+ 'id': camp.get('campaign_id', ''),
142
+ 'name': camp.get('type', ''),
143
+ 'sku': ','.join(target_skus),
144
+ 'region': region,
145
+ 'channel': campaign_channel,
146
+ 'start_date': start_date.date() if not pd.isnull(start_date) else '',
147
+ 'end_date': end_date.date() if not pd.isnull(end_date) else '',
148
+ 'budget': budget if not pd.isnull(budget) else '-',
149
+ 'total_units': total_units,
150
+ 'total_revenue': total_revenue,
151
+ 'uplift_pct': uplift_pct,
152
+ 'roi': roi
153
+ })
154
+
155
+ # Simple summary for display
156
+ summary = {
157
+ 'total_campaigns': len(campaign_results),
158
+ 'total_revenue': float(np.nansum([c['total_revenue'] for c in campaign_results])),
159
+ 'avg_uplift_pct': float(np.nanmean([c['uplift_pct'] for c in campaign_results if not np.isnan(c['uplift_pct'])])) if campaign_results else 0,
160
+ 'avg_roi': float(np.nanmean([c['roi'] for c in campaign_results if not np.isnan(c['roi'])])) if campaign_results else 0,
161
+ }
162
+ return {'campaigns': campaign_results, 'summary': summary}
modules/inventory_optimization.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, render_template
2
+
3
+ inventory_optimization_bp = Blueprint('inventory_optimization', __name__, url_prefix='/inventory')
4
+
5
+ @inventory_optimization_bp.route('/')
6
+ def show_inventory_optimization():
7
+ return render_template('inventory_optimization.html', title="Inventory Optimization")
modules/machine_failure.py ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, render_template, request, jsonify, redirect, url_for, flash, session
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.utils
6
+ import json
7
+ import os
8
+ import joblib
9
+ from datetime import datetime
10
+ from sklearn.model_selection import train_test_split
11
+ from sklearn.preprocessing import StandardScaler, LabelEncoder
12
+ from sklearn.ensemble import RandomForestClassifier
13
+ from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
14
+ import random
15
+
16
+ machine_failure_bp = Blueprint('machine_failure', __name__, url_prefix='/predict/machine_failure')
17
+
18
+ UPLOAD_FOLDER = 'temp_uploads'
19
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
20
+
21
+ def get_current_df():
22
+ try:
23
+ csv_path = session.get('machine_csv_path')
24
+ print(f"Debug - CSV Path from session: {csv_path}")
25
+
26
+ if csv_path and os.path.exists(csv_path):
27
+ print(f"Debug - File exists at path: {csv_path}")
28
+ return pd.read_csv(csv_path)
29
+
30
+ print("Debug - No valid CSV path found")
31
+ return None
32
+ except Exception as e:
33
+ print(f"Debug - Error in get_current_df: {str(e)}")
34
+ return None
35
+
36
+ def get_summary_stats(df):
37
+ return {
38
+ 'total_rows': len(df),
39
+ 'total_columns': len(df.columns),
40
+ 'columns': list(df.columns),
41
+ 'numeric_columns': list(df.select_dtypes(include=[np.number]).columns),
42
+ 'categorical_columns': list(df.select_dtypes(exclude=[np.number]).columns),
43
+ 'missing_values': df.isnull().sum().to_dict()
44
+ }
45
+
46
+ def preprocess_data(df, for_prediction=False, label_encoders=None):
47
+ """Preprocess the dataframe for machine learning
48
+
49
+ Args:
50
+ df (pd.DataFrame): The input DataFrame.
51
+ for_prediction (bool): True if preprocessing for a single prediction, False for training.
52
+ label_encoders (dict): Dictionary of pre-fitted LabelEncoders for single prediction.
53
+ """
54
+ df_processed = df.copy()
55
+
56
+ categorical_columns = []
57
+ numerical_columns = []
58
+
59
+ # Dynamically determine column types based on the current DataFrame
60
+ for column in df_processed.columns:
61
+ if column in ['timestamp', 'maintenance_timestamp']:
62
+ continue
63
+
64
+ # Check if column is numeric or can be converted to numeric (after dropping NaNs for check)
65
+ if pd.api.types.is_numeric_dtype(df_processed[column]):
66
+ numerical_columns.append(column)
67
+ else:
68
+ try:
69
+ # Attempt to convert to numeric, if successful, it's numeric
70
+ if pd.to_numeric(df_processed[column].dropna()).notna().all():
71
+ numerical_columns.append(column)
72
+ else:
73
+ categorical_columns.append(column)
74
+ except ValueError:
75
+ categorical_columns.append(column)
76
+
77
+ # Handle timestamps
78
+ for time_col in ['timestamp', 'maintenance_timestamp']:
79
+ if time_col in df_processed.columns:
80
+ # Convert to datetime, coercing errors
81
+ df_processed[time_col] = pd.to_datetime(df_processed[time_col], errors='coerce')
82
+
83
+ # Extract features only if the column contains valid datetime values
84
+ if not df_processed[time_col].isnull().all():
85
+ df_processed[f'{time_col}_hour'] = df_processed[time_col].dt.hour.fillna(0) # Fill NaN with 0 if time part is missing
86
+ df_processed[f'{time_col}_day'] = df_processed[time_col].dt.day.fillna(0)
87
+ df_processed[f'{time_col}_month'] = df_processed[time_col].dt.month.fillna(0)
88
+ else:
89
+ df_processed[f'{time_col}_hour'] = 0
90
+ df_processed[f'{time_col}_day'] = 0
91
+ df_processed[f'{time_col}_month'] = 0
92
+
93
+ df_processed = df_processed.drop(columns=[time_col])
94
+
95
+ # Encode categorical variables
96
+ current_label_encoders = {}
97
+ if not for_prediction: # During training, fit and save encoders
98
+ for col in categorical_columns:
99
+ if col in df_processed.columns:
100
+ le = LabelEncoder()
101
+ df_processed[col] = le.fit_transform(df_processed[col].astype(str))
102
+ current_label_encoders[col] = le
103
+ else:
104
+ for col, le in label_encoders.items():
105
+ if col in df_processed.columns:
106
+ # Use a lambda function to handle unseen labels: map to -1 or a default if not in classes
107
+ df_processed[col] = df_processed[col].astype(str).apply(
108
+ lambda x: le.transform([x])[0] if x in le.classes_ else -1
109
+ )
110
+ return df_processed, current_label_encoders
111
+
112
+
113
+ @machine_failure_bp.route('/', methods=['GET'])
114
+ def show_machine_failure():
115
+ return render_template('machine_failure.html', title="Machine Failure Prediction")
116
+
117
+ @machine_failure_bp.route('/upload', methods=['POST'])
118
+ def upload_file():
119
+ if 'machine_file' not in request.files:
120
+ flash('No file selected')
121
+ return redirect(url_for('machine_failure.show_machine_failure'))
122
+
123
+ file = request.files['machine_file']
124
+ if file.filename == '':
125
+ flash('No file selected')
126
+ return redirect(url_for('machine_failure.show_machine_failure'))
127
+
128
+ try:
129
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
130
+ safe_filename = f"machine_data_{timestamp}.csv"
131
+ file_path = os.path.join(UPLOAD_FOLDER, safe_filename)
132
+
133
+ file.save(file_path)
134
+ session['machine_csv_path'] = file_path
135
+ print(f"Debug - Saved file to: {file_path}")
136
+
137
+ df = pd.read_csv(file_path)
138
+ preview_data = df.head().to_dict('records')
139
+ summary_stats = get_summary_stats(df)
140
+
141
+ session['original_columns'] = df.columns.tolist()
142
+
143
+ return render_template('machine_failure.html',
144
+ title="Machine Failure Prediction",
145
+ preview_data=preview_data,
146
+ columns=df.columns.tolist(),
147
+ summary_stats=summary_stats)
148
+
149
+ except Exception as e:
150
+ print(f"Debug - Upload error: {str(e)}")
151
+ flash(f'Error processing file: {str(e)}')
152
+ return redirect(url_for('machine_failure.show_machine_failure'))
153
+
154
+ @machine_failure_bp.route('/run_prediction', methods=['POST'])
155
+ def run_prediction():
156
+ try:
157
+ df = get_current_df()
158
+ if df is None:
159
+ return jsonify({'success': False, 'error': 'No data available. Please upload a CSV file first.'})
160
+
161
+ target_col = request.form.get('target_col')
162
+ if not target_col:
163
+ return jsonify({'success': False, 'error': 'Target column not selected.'})
164
+
165
+ # Preprocess the data for training
166
+ df_processed, label_encoders = preprocess_data(df.copy(), for_prediction=False)
167
+
168
+ encoders_path = os.path.join(UPLOAD_FOLDER, f'encoders_{datetime.now().strftime("%Y%m%d_%H%M%S")}.joblib')
169
+ joblib.dump(label_encoders, encoders_path)
170
+ session['encoders_path'] = encoders_path
171
+
172
+ # Prepare features and target
173
+ if target_col not in df_processed.columns:
174
+ return jsonify({'success': False, 'error': f"Target column '{target_col}' not found after preprocessing. Check if it was dropped or transformed incorrectly."})
175
+
176
+ X = df_processed.drop(columns=[target_col])
177
+ y = df_processed[target_col]
178
+
179
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
180
+ scaler = StandardScaler()
181
+ X_train_scaled = scaler.fit_transform(X_train)
182
+ X_test_scaled = scaler.transform(X_test)
183
+
184
+ clf = RandomForestClassifier(random_state=42)
185
+ clf.fit(X_train_scaled, y_train)
186
+ y_pred = clf.predict(X_test_scaled)
187
+
188
+ importances = clf.feature_importances_
189
+ feature_names = X.columns
190
+ feature_importance = sorted(
191
+ zip(feature_names, importances),
192
+ key=lambda x: x[1],
193
+ reverse=True
194
+ )[:5]
195
+
196
+ top_features = [{'feature': f, 'importance': float(imp)} for f, imp in feature_importance]
197
+
198
+ session['feature_names'] = X.columns.tolist()
199
+ session['target_column_name'] = target_col
200
+
201
+ metrics = {
202
+ 'Accuracy': accuracy_score(y_test, y_pred),
203
+ 'Precision': precision_score(y_test, y_pred, average='weighted', zero_division=0),
204
+ 'Recall': recall_score(y_test, y_pred, average='weighted', zero_division=0),
205
+ 'F1 Score': f1_score(y_test, y_pred, average='weighted', zero_division=0)
206
+ }
207
+
208
+ model_path = os.path.join(UPLOAD_FOLDER, f'model_{datetime.now().strftime("%Y%m%d_%H%M%S")}.joblib')
209
+ scaler_path = os.path.join(UPLOAD_FOLDER, f'scaler_{datetime.now().strftime("%Y%m%d_%H%M%S")}.joblib')
210
+
211
+ joblib.dump(clf, model_path)
212
+ joblib.dump(scaler, scaler_path)
213
+
214
+ session['model_path'] = model_path
215
+ session['scaler_path'] = scaler_path
216
+
217
+ return jsonify({
218
+ 'success': True,
219
+ 'metrics': metrics,
220
+ 'top_features': top_features,
221
+ })
222
+
223
+ except Exception as e:
224
+ print(f"Error in run_prediction: {e}")
225
+ return jsonify({'success': False, 'error': str(e)})
226
+
227
+ @machine_failure_bp.route('/get_form_data', methods=['GET'])
228
+ def get_form_data():
229
+ try:
230
+ df = get_current_df()
231
+ if df is None:
232
+ return jsonify({'success': False, 'error': 'No data available. Please upload a file first.'})
233
+
234
+ target_col = session.get('target_column_name')
235
+ if not target_col:
236
+ return jsonify({'success': False, 'error': 'Target column not found in session. Please run prediction first.'})
237
+
238
+ exclude_cols = ['error_severity', 'downtime_minutes', 'failure_type', target_col]
239
+
240
+ form_fields = []
241
+ for col in df.columns:
242
+ if col.lower() in [ec.lower() for ec in exclude_cols]:
243
+ continue
244
+
245
+ default_value = None
246
+ if not df[col].dropna().empty:
247
+ if pd.api.types.is_numeric_dtype(df[col]):
248
+ min_val = df[col].min()
249
+ max_val = df[col].max()
250
+ if pd.isna(min_val) or pd.isna(max_val):
251
+ default_value = 0.0
252
+ else:
253
+ default_value = round(random.uniform(float(min_val), float(max_val)), 2)
254
+ elif col in ['timestamp', 'maintenance_timestamp']:
255
+ sample_date = random.choice(df[col].dropna().tolist())
256
+ try:
257
+ parsed_date = pd.to_datetime(sample_date)
258
+ if pd.isna(parsed_date):
259
+ default_value = "YYYY-MM-DD HH:MM:SS" # Fallback for invalid dates
260
+ else:
261
+ default_value = parsed_date.strftime('%Y-%m-%d %H:%M:%S')
262
+ except Exception:
263
+ default_value = "YYYY-MM-DD HH:MM:SS"
264
+ else:
265
+ unique_vals_str = [str(x) for x in df[col].dropna().unique()]
266
+ if unique_vals_str:
267
+ default_value = random.choice(unique_vals_str)
268
+ else:
269
+ default_value = ""
270
+
271
+ if pd.api.types.is_numeric_dtype(df[col]):
272
+ form_fields.append({
273
+ 'name': col,
274
+ 'type': 'number',
275
+ 'default_value': default_value
276
+ })
277
+ elif col in ['timestamp', 'maintenance_timestamp']:
278
+ form_fields.append({
279
+ 'name': col,
280
+ 'type': 'text',
281
+ 'placeholder': 'YYYY-MM-DD HH:MM:SS (optional)',
282
+ 'default_value': default_value
283
+ })
284
+ else:
285
+ unique_values = [str(x) for x in df[col].dropna().unique().tolist()]
286
+ form_fields.append({
287
+ 'name': col,
288
+ 'type': 'select',
289
+ 'options': unique_values,
290
+ 'default_value': default_value
291
+ })
292
+
293
+ return jsonify({'success': True, 'form_fields': form_fields})
294
+
295
+ except Exception as e:
296
+ print(f"Error in get_form_data: {e}")
297
+ return jsonify({'success': False, 'error': str(e)})
298
+
299
+
300
+ @machine_failure_bp.route('/predict_single', methods=['POST'])
301
+ def predict_single():
302
+ try:
303
+ model_path = session.get('model_path')
304
+ scaler_path = session.get('scaler_path')
305
+ encoders_path = session.get('encoders_path')
306
+ feature_names = session.get('feature_names')
307
+ target_col = session.get('target_column_name')
308
+
309
+ if not all([model_path, scaler_path, encoders_path, feature_names, target_col]):
310
+ return jsonify({'success': False, 'error': 'Model or preprocessing artifacts not found. Please train a model first.'})
311
+
312
+ model = joblib.load(model_path)
313
+ scaler = joblib.load(scaler_path)
314
+ label_encoders = joblib.load(encoders_path)
315
+
316
+ input_data = request.json
317
+ if not input_data:
318
+ return jsonify({'success': False, 'error': 'No input data provided.'})
319
+
320
+ original_uploaded_columns = session.get('original_columns')
321
+
322
+ if not original_uploaded_columns:
323
+ return jsonify({'success': False, 'error': 'Original dataset column names not found in session. Please upload a file.'})
324
+
325
+ full_input_df = pd.DataFrame(columns=original_uploaded_columns)
326
+
327
+ single_row_input_df = pd.DataFrame([input_data])
328
+
329
+ for col in original_uploaded_columns:
330
+ if col in single_row_input_df.columns:
331
+ full_input_df.loc[0, col] = single_row_input_df.loc[0, col]
332
+ else:
333
+ full_input_df.loc[0, col] = np.nan
334
+
335
+
336
+ preprocessed_input_df, _ = preprocess_data(full_input_df.copy(), for_prediction=True, label_encoders=label_encoders)
337
+
338
+ final_input_features = pd.DataFrame(columns=feature_names)
339
+
340
+ for col in feature_names:
341
+ if col in preprocessed_input_df.columns:
342
+ final_input_features[col] = pd.to_numeric(preprocessed_input_df[col], errors='coerce').values
343
+ else:
344
+ final_input_features[col] = 0.0
345
+
346
+ final_input_features = final_input_features.fillna(0.0)
347
+
348
+ input_scaled = scaler.transform(final_input_features)
349
+
350
+ prediction_value = model.predict(input_scaled)[0]
351
+
352
+ prediction_display = prediction_value
353
+ if target_col in label_encoders:
354
+ if isinstance(prediction_value, (int, np.integer)) and prediction_value < len(label_encoders[target_col].classes_):
355
+ prediction_display = str(label_encoders[target_col].inverse_transform([prediction_value])[0])
356
+ else:
357
+ prediction_display = str(prediction_value) + " (Unknown Class)"
358
+ else:
359
+ if isinstance(prediction_value, np.number):
360
+ prediction_display = float(prediction_value)
361
+ else:
362
+ prediction_display = prediction_value
363
+
364
+ probability = None
365
+ if hasattr(model, 'predict_proba'):
366
+ probability = model.predict_proba(input_scaled)[0].tolist()
367
+ probability = [float(p) for p in probability]
368
+
369
+ return jsonify({
370
+ 'success': True,
371
+ 'prediction': prediction_display,
372
+ 'probability': probability
373
+ })
374
+ except Exception as e:
375
+ print(f"Error in predict_single: {e}")
376
+ return jsonify({'success': False, 'error': str(e)})
modules/pricing_intelligence.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, render_template
2
+
3
+ pricing_intelligence_bp = Blueprint('pricing_intelligence', __name__, url_prefix='/pricing')
4
+
5
+ @pricing_intelligence_bp.route('/')
6
+ def show_pricing_intelligence():
7
+ return render_template('pricing_intelligence.html', title="Pricing Intelligence")
modules/sales_perdiction.py ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, render_template, request, jsonify, redirect, url_for, flash
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.utils
6
+ import json
7
+
8
+ sales_prediction_bp = Blueprint('sales_prediction', __name__, url_prefix='/forecast/sales')
9
+
10
+ _current_df = None
11
+
12
+ def get_summary_stats(df):
13
+ return {
14
+ 'total_rows': len(df),
15
+ 'total_columns': len(df.columns),
16
+ 'columns': list(df.columns),
17
+ 'numeric_columns': list(df.select_dtypes(include=[np.number]).columns),
18
+ 'categorical_columns': list(df.select_dtypes(exclude=[np.number]).columns),
19
+ 'missing_values': df.isnull().sum().to_dict()
20
+ }
21
+
22
+ @sales_prediction_bp.route('/', methods=['GET'])
23
+ def show_sales_prediction():
24
+ return render_template('sales_prediction.html', title="Sales Prediction Engine")
25
+
26
+ @sales_prediction_bp.route('/upload', methods=['POST'])
27
+ def upload_file():
28
+ if 'sales_file' not in request.files:
29
+ flash('No file selected')
30
+ return redirect(url_for('sales_prediction.show_sales_prediction'))
31
+
32
+ file = request.files['sales_file']
33
+ if file.filename == '':
34
+ flash('No file selected')
35
+ return redirect(url_for('sales_prediction.show_sales_prediction'))
36
+
37
+ try:
38
+ global _current_df
39
+ _current_df = pd.read_csv(file)
40
+ preview_data = _current_df.head().to_dict('records')
41
+ summary_stats = get_summary_stats(_current_df)
42
+ return render_template('sales_prediction.html',
43
+ title="Sales Prediction Engine",
44
+ preview_data=preview_data,
45
+ columns=_current_df.columns.tolist(),
46
+ summary_stats=summary_stats)
47
+ except Exception as e:
48
+ flash(f'Error processing file: {str(e)}')
49
+ return redirect(url_for('sales_prediction.show_sales_prediction'))
50
+
51
+ @sales_prediction_bp.route('/plot', methods=['POST'])
52
+ def create_plot():
53
+ try:
54
+ global _current_df
55
+ if _current_df is None:
56
+ return jsonify(success=False,
57
+ error="Please upload the CSV first.")
58
+
59
+ column = request.form.get('column')
60
+ plot_type = request.form.get('plot_type')
61
+ NUMERIC = {'sales_units', 'sales_revenue', 'price'}
62
+ CATEGORICAL = {'sku', 'category', 'region', 'channel', 'brand'}
63
+
64
+ if column not in NUMERIC.union(CATEGORICAL):
65
+ return jsonify(success=False,
66
+ error=f"Column '{column}' not supported.")
67
+
68
+ if column in NUMERIC:
69
+ _current_df[column] = pd.to_numeric(_current_df[column],
70
+ errors='coerce')
71
+
72
+ df = _current_df.dropna(subset=[column])
73
+ if plot_type == 'histogram' and column in NUMERIC:
74
+ fig = px.histogram(df, x=column, nbins=40,
75
+ title=f'Distribution of {column}')
76
+
77
+ elif plot_type == 'box' and column in NUMERIC:
78
+ fig = px.box(df, y=column,
79
+ title=f'Box-plot of {column}')
80
+
81
+ elif plot_type == 'bar' and column in CATEGORICAL:
82
+ agg_df = (df.groupby(column, as_index=False)
83
+ ['sales_units'].sum()
84
+ .sort_values('sales_units', ascending=False)
85
+ .head(25))
86
+
87
+ fig = px.bar(agg_df, x=column, y='sales_units',
88
+ title=f'{column} – total units sold',
89
+ labels={'sales_units': 'Units sold'})
90
+
91
+ fig.update_layout(xaxis_categoryorder='total descending')
92
+
93
+ else:
94
+ return jsonify(success=False,
95
+ error=f"'{plot_type}' not allowed for '{column}'")
96
+
97
+ plot_json = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
98
+ return jsonify(success=True, plot=plot_json)
99
+
100
+ except Exception as e:
101
+ return jsonify(success=False, error=str(e))
102
+
103
+ @sales_prediction_bp.route('/fix_nulls', methods=['POST'])
104
+ def fix_nulls():
105
+ try:
106
+ global _current_df
107
+ if _current_df is None:
108
+ return jsonify(success=False, error="No data available. Please upload a CSV file first.")
109
+ column = request.form.get('column')
110
+ method = request.form.get('method')
111
+ if column not in _current_df.columns:
112
+ return jsonify(success=False, error=f"Column '{column}' not found")
113
+ nulls_before = int(_current_df[column].isnull().sum())
114
+ if nulls_before == 0:
115
+ return jsonify(success=False, error=f"No nulls to fix in '{column}'")
116
+ if method == 'drop':
117
+ _current_df = _current_df.dropna(subset=[column])
118
+ elif method == 'mean':
119
+ _current_df[column] = _current_df[column].fillna(_current_df[column].mean())
120
+ elif method == 'median':
121
+ _current_df[column] = _current_df[column].fillna(_current_df[column].median())
122
+ elif method == 'mode':
123
+ _current_df[column] = _current_df[column].fillna(_current_df[column].mode()[0])
124
+ else:
125
+ return jsonify(success=False, error="Invalid method")
126
+ nulls_after = int(_current_df[column].isnull().sum())
127
+ summary_stats = get_summary_stats(_current_df)
128
+ preview_data = _current_df.head().to_dict('records')
129
+ columns = list(_current_df.columns)
130
+ return jsonify(
131
+ success=True,
132
+ message=f"Fixed {nulls_before - nulls_after} nulls in '{column}' using {method}.",
133
+ summary_stats=summary_stats,
134
+ preview_data=preview_data,
135
+ columns=columns
136
+ )
137
+ except Exception as e:
138
+ return jsonify(success=False, error=str(e))
139
+
140
+ @sales_prediction_bp.route('/run_forecast', methods=['POST'])
141
+ def run_forecast():
142
+ try:
143
+ global _current_df
144
+ if _current_df is None:
145
+ return jsonify(success=False, error="No data available. Please upload a CSV file first.")
146
+ model = request.form.get('model')
147
+ date_col = request.form.get('date_col')
148
+ target_col = request.form.get('target_col')
149
+ horizon = int(request.form.get('horizon', 30))
150
+
151
+ if date_col not in _current_df.columns or target_col not in _current_df.columns:
152
+ return jsonify(success=False, error="Invalid column selection.")
153
+
154
+ df = _current_df.copy()
155
+ df = df[[date_col, target_col]].dropna()
156
+ # Try to parse date with dayfirst and fallback to default
157
+ try:
158
+ df[date_col] = pd.to_datetime(df[date_col], dayfirst=True, errors='coerce')
159
+ except Exception:
160
+ df[date_col] = pd.to_datetime(df[date_col], errors='coerce')
161
+ df = df.dropna(subset=[date_col])
162
+ df = df.sort_values(date_col)
163
+ df = df.groupby(date_col)[target_col].sum().reset_index()
164
+
165
+ # Only forecast if enough data
166
+ if len(df) < horizon + 5:
167
+ return jsonify(success=False, error="Not enough data for the selected forecast horizon.")
168
+
169
+ train = df.iloc[:-horizon]
170
+ test = df.iloc[-horizon:]
171
+
172
+ forecast = []
173
+ conf_int = []
174
+ trend = []
175
+ seasonality = []
176
+ metrics = {}
177
+
178
+ if model == "ARIMA":
179
+ try:
180
+ from statsmodels.tsa.arima.model import ARIMA
181
+ ts = train.set_index(date_col)[target_col]
182
+ arima = ARIMA(ts, order=(1,1,1)).fit()
183
+ pred = arima.get_forecast(steps=horizon)
184
+ forecast = pred.predicted_mean.values.tolist()
185
+ conf_int = pred.conf_int().values.tolist()
186
+ trend = [np.mean(ts)] * horizon
187
+ seasonality = [0] * horizon
188
+ y_true = test[target_col].values
189
+ y_pred = forecast
190
+ metrics = {
191
+ "MAPE": float(np.mean(np.abs((y_true - y_pred) / y_true)) * 100),
192
+ "RMSE": float(np.sqrt(np.mean((y_true - y_pred) ** 2))),
193
+ "R2": float(1 - np.sum((y_true - y_pred) ** 2) / np.sum((y_true - np.mean(y_true)) ** 2))
194
+ }
195
+ except Exception as e:
196
+ return jsonify(success=False, error=f"ARIMA error: {e}")
197
+
198
+ elif model == "Prophet":
199
+ try:
200
+ from prophet import Prophet
201
+ prophet_df = train.rename(columns={date_col: "ds", target_col: "y"})
202
+ m = Prophet()
203
+ m.fit(prophet_df)
204
+ future = m.make_future_dataframe(periods=horizon)
205
+ forecast_df = m.predict(future)
206
+ forecast = forecast_df['yhat'][-horizon:].values.tolist()
207
+ conf_int = list(zip(forecast_df['yhat_lower'][-horizon:].values, forecast_df['yhat_upper'][-horizon:].values))
208
+ trend = forecast_df['trend'][-horizon:].values.tolist()
209
+ seasonality = (forecast_df['seasonal'][-horizon:].values.tolist() if 'seasonal' in forecast_df else [0]*horizon)
210
+ y_true = test[target_col].values
211
+ y_pred = forecast
212
+ metrics = {
213
+ "MAPE": float(np.mean(np.abs((y_true - y_pred) / y_true)) * 100),
214
+ "RMSE": float(np.sqrt(np.mean((y_true - y_pred) ** 2))),
215
+ "R2": float(1 - np.sum((y_true - y_pred) ** 2) / np.sum((y_true - np.mean(y_true)) ** 2))
216
+ }
217
+ except Exception as e:
218
+ return jsonify(success=False, error=f"Prophet error: {e}")
219
+
220
+ elif model in ["Random Forest", "XGBoost", "LSTM"]:
221
+ y_true = test[target_col].values
222
+ y_pred = [np.mean(train[target_col])] * horizon
223
+ forecast = y_pred
224
+ conf_int = [[y-5, y+5] for y in y_pred]
225
+ trend = [np.mean(train[target_col])] * horizon
226
+ seasonality = [0] * horizon
227
+ metrics = {
228
+ "MAPE": float(np.mean(np.abs((y_true - y_pred) / y_true)) * 100),
229
+ "RMSE": float(np.sqrt(np.mean((y_true - y_pred) ** 2))),
230
+ "R2": float(1 - np.sum((y_true - y_pred) ** 2) / np.sum((y_true - np.mean(y_true)) ** 2))
231
+ }
232
+
233
+ forecast_dates = test[date_col].dt.strftime('%Y-%m-%d').tolist()
234
+
235
+ return jsonify(
236
+ success=True,
237
+ forecast=forecast,
238
+ conf_int=conf_int,
239
+ trend=trend,
240
+ seasonality=seasonality,
241
+ metrics=metrics,
242
+ dates=forecast_dates,
243
+ model=model
244
+ )
245
+ except Exception as e:
246
+ return jsonify(success=False, error=str(e))
modules/supply_failure.py ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, render_template, request, jsonify, redirect, url_for, flash, session
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.utils
6
+ import json
7
+ import os
8
+ import joblib
9
+ from datetime import datetime
10
+ from sklearn.model_selection import train_test_split
11
+ from sklearn.preprocessing import StandardScaler, LabelEncoder
12
+ from sklearn.ensemble import RandomForestClassifier
13
+ from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
14
+ import random
15
+
16
+ supply_failure_bp = Blueprint('supply_failure', __name__, url_prefix='/predict/supply_failure')
17
+
18
+ UPLOAD_FOLDER = 'temp_uploads'
19
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
20
+
21
+ def get_current_df_supply():
22
+ try:
23
+ csv_path = session.get('supply_csv_path')
24
+ if csv_path and os.path.exists(csv_path):
25
+ return pd.read_csv(csv_path)
26
+ return None
27
+ except Exception as e:
28
+ print(f"Error in get_current_df_supply: {str(e)}")
29
+ return None
30
+
31
+ def get_summary_stats_supply(df):
32
+ return {
33
+ 'total_rows': len(df),
34
+ 'total_columns': len(df.columns),
35
+ 'columns': list(df.columns),
36
+ 'numeric_columns': list(df.select_dtypes(include=[np.number]).columns),
37
+ 'categorical_columns': list(df.select_dtypes(exclude=[np.number]).columns),
38
+ 'missing_values': df.isnull().sum().to_dict()
39
+ }
40
+
41
+ def preprocess_data_supply(df, for_prediction=False, label_encoders=None):
42
+ df_processed = df.copy()
43
+
44
+ # Identify date columns based on known names
45
+ date_cols = ['order_date', 'promised_delivery_date', 'actual_delivery_date']
46
+
47
+ # Process date columns: extract features and drop original
48
+ for col in date_cols:
49
+ if col in df_processed.columns:
50
+ # Convert to datetime, coercing errors to NaT
51
+ df_processed[col] = pd.to_datetime(df_processed[col], errors='coerce')
52
+
53
+ # Extract features only if there are valid datetime values
54
+ if not df_processed[col].isnull().all():
55
+ df_processed[f'{col}_day_of_week'] = df_processed[col].dt.dayofweek.fillna(-1) # -1 for NaN dates
56
+ df_processed[f'{col}_month'] = df_processed[col].dt.month.fillna(-1)
57
+ df_processed[f'{col}_year'] = df_processed[col].dt.year.fillna(-1)
58
+ df_processed[f'{col}_day'] = df_processed[col].dt.day.fillna(-1)
59
+ else: # If all dates are NaT, add dummy columns filled with -1
60
+ df_processed[f'{col}_day_of_week'] = -1
61
+ df_processed[f'{col}_month'] = -1
62
+ df_processed[f'{col}_year'] = -1
63
+ df_processed[f'{col}_day'] = -1
64
+ df_processed = df_processed.drop(columns=[col])
65
+
66
+ # Identify numerical and categorical columns after date processing
67
+ categorical_columns = []
68
+ numerical_columns = []
69
+
70
+ for column in df_processed.columns:
71
+ if pd.api.types.is_numeric_dtype(df_processed[column]):
72
+ numerical_columns.append(column)
73
+ else:
74
+ try:
75
+ # Attempt to convert to numeric, if successful, it's numeric
76
+ if pd.to_numeric(df_processed[column].dropna()).notna().all():
77
+ numerical_columns.append(column)
78
+ else:
79
+ categorical_columns.append(column)
80
+ except ValueError:
81
+ categorical_columns.append(column)
82
+
83
+ # Encode categorical variables
84
+ current_label_encoders = {}
85
+ if not for_prediction: # During training, fit and save encoders
86
+ for col in categorical_columns:
87
+ if col in df_processed.columns:
88
+ le = LabelEncoder()
89
+ df_processed[col] = le.fit_transform(df_processed[col].astype(str).fillna('missing_value'))
90
+ current_label_encoders[col] = le
91
+ else: # For prediction, use provided encoders
92
+ for col, le in label_encoders.items():
93
+ if col in df_processed.columns:
94
+ df_processed[col] = df_processed[col].astype(str).apply(
95
+ lambda x: le.transform([x])[0] if x in le.classes_ else -1
96
+ )
97
+
98
+ # Ensure numerical columns are truly numeric and fill any NaNs
99
+ for col in numerical_columns:
100
+ if col in df_processed.columns:
101
+ df_processed[col] = pd.to_numeric(df_processed[col], errors='coerce').fillna(0) # Fill numerical NaNs with 0
102
+
103
+ return df_processed, current_label_encoders
104
+
105
+
106
+ @supply_failure_bp.route('/', methods=['GET'])
107
+ def show_supply_failure():
108
+ return render_template('supply_failure.html', title="Supply Failure Prediction")
109
+
110
+ @supply_failure_bp.route('/upload', methods=['POST'])
111
+ def upload_file_supply():
112
+ if 'supply_file' not in request.files:
113
+ flash('No file selected')
114
+ return redirect(url_for('supply_failure.show_supply_failure'))
115
+
116
+ file = request.files['supply_file']
117
+ if file.filename == '':
118
+ flash('No file selected')
119
+ return redirect(url_for('supply_failure.show_supply_failure'))
120
+
121
+ try:
122
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
123
+ safe_filename = f"supply_data_{timestamp}.csv"
124
+ file_path = os.path.join(UPLOAD_FOLDER, safe_filename)
125
+
126
+ file.save(file_path)
127
+ session['supply_csv_path'] = file_path
128
+
129
+ df = pd.read_csv(file_path)
130
+ preview_data = df.head().to_dict('records')
131
+ summary_stats = get_summary_stats_supply(df)
132
+ session['original_columns_supply'] = df.columns.tolist()
133
+
134
+ return render_template('supply_failure.html',
135
+ title="Supply Failure Prediction",
136
+ preview_data=preview_data,
137
+ columns=df.columns.tolist(),
138
+ summary_stats=summary_stats)
139
+
140
+ except Exception as e:
141
+ flash(f'Error processing file: {str(e)}')
142
+ return redirect(url_for('supply_failure.show_supply_failure'))
143
+
144
+ @supply_failure_bp.route('/run_prediction', methods=['POST'])
145
+ def run_prediction_supply():
146
+ try:
147
+ df = get_current_df_supply()
148
+ if df is None:
149
+ return jsonify({'success': False, 'error': 'No data available. Please upload a CSV file first.'})
150
+
151
+ target_col = 'failure_flag' # Fixed target column as per definition
152
+
153
+ df_processed, label_encoders = preprocess_data_supply(df.copy(), for_prediction=False)
154
+
155
+ encoders_path = os.path.join(UPLOAD_FOLDER, f'supply_encoders_{datetime.now().strftime("%Y%m%d_%H%M%S")}.joblib')
156
+ joblib.dump(label_encoders, encoders_path)
157
+ session['supply_encoders_path'] = encoders_path
158
+
159
+ if target_col not in df_processed.columns:
160
+ return jsonify({'success': False, 'error': f"Target column '{target_col}' not found after preprocessing. Check if it was dropped or transformed incorrectly."})
161
+
162
+ X = df_processed.drop(columns=[target_col])
163
+ y = df_processed[target_col]
164
+
165
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
166
+ scaler = StandardScaler()
167
+ X_train_scaled = scaler.fit_transform(X_train)
168
+ X_test_scaled = scaler.transform(X_test)
169
+
170
+ clf = RandomForestClassifier(random_state=42)
171
+ clf.fit(X_train_scaled, y_train)
172
+ y_pred = clf.predict(X_test_scaled)
173
+
174
+ importances = clf.feature_importances_
175
+ feature_names = X.columns
176
+ feature_importance = sorted(
177
+ zip(feature_names, importances),
178
+ key=lambda x: x[1],
179
+ reverse=True
180
+ )[:5]
181
+
182
+ top_features = [{'feature': f, 'importance': float(imp)} for f, imp in feature_importance]
183
+
184
+ session['supply_feature_names'] = X.columns.tolist()
185
+ session['supply_target_column_name'] = target_col
186
+
187
+ metrics = {
188
+ 'Accuracy': accuracy_score(y_test, y_pred),
189
+ 'Precision': precision_score(y_test, y_pred, average='weighted', zero_division=0),
190
+ 'Recall': recall_score(y_test, y_pred, average='weighted', zero_division=0),
191
+ 'F1 Score': f1_score(y_test, y_pred, average='weighted', zero_division=0)
192
+ }
193
+
194
+ model_path = os.path.join(UPLOAD_FOLDER, f'supply_model_{datetime.now().strftime("%Y%m%d_%H%M%S")}.joblib')
195
+ scaler_path = os.path.join(UPLOAD_FOLDER, f'supply_scaler_{datetime.now().strftime("%Y%m%d_%H%M%S")}.joblib')
196
+
197
+ joblib.dump(clf, model_path)
198
+ joblib.dump(scaler, scaler_path)
199
+
200
+ session['supply_model_path'] = model_path
201
+ session['supply_scaler_path'] = scaler_path
202
+
203
+ return jsonify({
204
+ 'success': True,
205
+ 'metrics': metrics,
206
+ 'top_features': top_features,
207
+ })
208
+
209
+ except Exception as e:
210
+ print(f"Error in run_prediction_supply: {e}")
211
+ return jsonify({'success': False, 'error': str(e)})
212
+
213
+ @supply_failure_bp.route('/get_form_data', methods=['GET'])
214
+ def get_form_data_supply():
215
+ try:
216
+ df = get_current_df_supply()
217
+ if df is None:
218
+ return jsonify({'success': False, 'error': 'No data available. Please upload a file first.'})
219
+
220
+ target_col = 'failure_flag' # Fixed target column for supply chain
221
+ if not target_col: # Should not happen if fixed, but good for robustness
222
+ return jsonify({'success': False, 'error': 'Target column not found in session. Please run prediction first.'})
223
+
224
+ # Columns to exclude from the form as requested by the user
225
+ exclude_cols = ['delivery_delay_days', 'delivered_quantity', 'return_reason', 'delivery_status', 'failure_type', target_col, 'order_id', 'component_id', 'po_approval_delay_days', 'customs_clearance_days', 'actual_delivery_date']
226
+
227
+ form_fields = []
228
+ for col in df.columns:
229
+ if col.lower() in [ec.lower() for ec in exclude_cols]:
230
+ continue
231
+
232
+ default_value = None
233
+ if not df[col].dropna().empty:
234
+ if pd.api.types.is_numeric_dtype(df[col]):
235
+ min_val = df[col].min()
236
+ max_val = df[col].max()
237
+ if pd.isna(min_val) or pd.isna(max_val):
238
+ default_value = 0.0
239
+ else:
240
+ default_value = round(random.uniform(float(min_val), float(max_val)), 2)
241
+ elif col in ['order_date', 'promised_delivery_date', 'actual_delivery_date']:
242
+ sample_date = random.choice(df[col].dropna().tolist())
243
+ try:
244
+ parsed_date = pd.to_datetime(sample_date)
245
+ if pd.isna(parsed_date):
246
+ default_value = "YYYY-MM-DD HH:MM:SS"
247
+ else:
248
+ default_value = parsed_date.strftime('%Y-%m-%d %H:%M:%S')
249
+ except Exception:
250
+ default_value = "YYYY-MM-DD HH:MM:SS"
251
+ else: # Categorical or other types
252
+ unique_vals_str = [str(x) for x in df[col].dropna().unique()]
253
+ if unique_vals_str:
254
+ default_value = random.choice(unique_vals_str)
255
+ else:
256
+ default_value = ""
257
+
258
+ if pd.api.types.is_numeric_dtype(df[col]):
259
+ form_fields.append({
260
+ 'name': col,
261
+ 'type': 'number',
262
+ 'default_value': default_value
263
+ })
264
+ elif col in ['order_date', 'promised_delivery_date', 'actual_delivery_date']:
265
+ form_fields.append({
266
+ 'name': col,
267
+ 'type': 'text',
268
+ 'placeholder': 'YYYY-MM-DD HH:MM:SS',
269
+ 'default_value': default_value
270
+ })
271
+ else: # Categorical
272
+ unique_values = [str(x) for x in df[col].dropna().unique().tolist()]
273
+ form_fields.append({
274
+ 'name': col,
275
+ 'type': 'select',
276
+ 'options': unique_values,
277
+ 'default_value': default_value
278
+ })
279
+
280
+ return jsonify({'success': True, 'form_fields': form_fields})
281
+
282
+ except Exception as e:
283
+ print(f"Error in get_form_data_supply: {e}")
284
+ return jsonify({'success': False, 'error': str(e)})
285
+
286
+
287
+ @supply_failure_bp.route('/predict_single', methods=['POST'])
288
+ def predict_single_supply():
289
+ try:
290
+ model_path = session.get('supply_model_path')
291
+ scaler_path = session.get('supply_scaler_path')
292
+ encoders_path = session.get('supply_encoders_path')
293
+ feature_names = session.get('supply_feature_names')
294
+ target_col = session.get('supply_target_column_name')
295
+ original_uploaded_columns = session.get('original_columns_supply')
296
+
297
+ if not all([model_path, scaler_path, encoders_path, feature_names, target_col, original_uploaded_columns]):
298
+ return jsonify({'success': False, 'error': 'Model or preprocessing artifacts not found for supply chain. Please train a model first.'})
299
+
300
+ model = joblib.load(model_path)
301
+ scaler = joblib.load(scaler_path)
302
+ label_encoders = joblib.load(encoders_path)
303
+
304
+ input_data = request.json
305
+ if not input_data:
306
+ return jsonify({'success': False, 'error': 'No input data provided.'})
307
+
308
+ full_input_df = pd.DataFrame(columns=original_uploaded_columns)
309
+ single_row_input_df = pd.DataFrame([input_data])
310
+
311
+ for col in original_uploaded_columns:
312
+ if col in single_row_input_df.columns:
313
+ full_input_df.loc[0, col] = single_row_input_df.loc[0, col]
314
+ else:
315
+ full_input_df.loc[0, col] = np.nan
316
+
317
+ preprocessed_input_df, _ = preprocess_data_supply(full_input_df.copy(), for_prediction=True, label_encoders=label_encoders)
318
+
319
+ final_input_features = pd.DataFrame(columns=feature_names)
320
+
321
+ for col in feature_names:
322
+ if col in preprocessed_input_df.columns:
323
+ final_input_features[col] = pd.to_numeric(preprocessed_input_df[col], errors='coerce').values
324
+ else:
325
+ final_input_features[col] = 0.0
326
+
327
+ final_input_features = final_input_features.fillna(0.0)
328
+
329
+ input_scaled = scaler.transform(final_input_features)
330
+
331
+ prediction_value = model.predict(input_scaled)[0]
332
+
333
+ # Convert prediction_value to standard Python int/float/str
334
+ prediction_display = prediction_value
335
+ if target_col in label_encoders and prediction_value in label_encoders[target_col].classes_: # Check if target was encoded and value is in classes
336
+ prediction_display = str(label_encoders[target_col].inverse_transform([prediction_value])[0])
337
+ else:
338
+ if isinstance(prediction_value, np.number):
339
+ prediction_display = float(prediction_value)
340
+ else:
341
+ prediction_display = prediction_value # Keep as is if not np.number
342
+
343
+ # Convert 0/1 to "No Failure"/"Failure" based on the definition for failure_flag
344
+ if prediction_display == 0 or prediction_display == "0":
345
+ user_friendly_prediction = "Delivery Successful"
346
+ elif prediction_display == 1 or prediction_display == "1":
347
+ user_friendly_prediction = "Delivery Failed"
348
+ else:
349
+ user_friendly_prediction = str(prediction_display) # Fallback if target is something else
350
+
351
+ probability = None
352
+ if hasattr(model, 'predict_proba'):
353
+ probability = model.predict_proba(input_scaled)[0].tolist()
354
+ probability = [float(p) for p in probability]
355
+
356
+ return jsonify({
357
+ 'success': True,
358
+ 'prediction': user_friendly_prediction,
359
+ 'probability': probability
360
+ })
361
+ except Exception as e:
362
+ print(f"Error in predict_single_supply: {e}")
363
+ return jsonify({'success': False, 'error': str(e)})
modules/underperformance.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, render_template
2
+
3
+ underperformance_bp = Blueprint('underperformance', __name__, url_prefix='/underperformance')
4
+
5
+ @underperformance_bp.route('/')
6
+ def show_underperformance():
7
+ return render_template('underperformance.html', title="Underperformance Detector")
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ flask
2
+ jinja2
3
+ scikit-learn
4
+ xgboost
5
+ pandas
6
+ numpy
7
+ flask-session
8
+ flask-cors
9
+ plotly
10
+ matplotlib
11
+ huggingface_hub[CLI]
static/images/campaign.jpg ADDED
static/images/cover.jpeg ADDED
static/images/inventory.png ADDED

Git LFS Details

  • SHA256: fb172b161dd641ea809cf06a8b3d4272578908f56c8e89a8384699a23562682f
  • Pointer size: 131 Bytes
  • Size of remote file: 515 kB
static/images/machine.jpeg ADDED
static/images/pricing.jpg ADDED
static/images/sales prediction.png ADDED

Git LFS Details

  • SHA256: ddba7f4ad7b12d470076c079590c0c1fdfceba26a79a9b2a6b0027c10fe798ea
  • Pointer size: 131 Bytes
  • Size of remote file: 143 kB
static/images/supply.jpeg ADDED
static/images/underperforming.png ADDED

Git LFS Details

  • SHA256: ce9919ef9d46cc7caaf460f6d0dd86470033cf5b717e175e5ec6f419654a506b
  • Pointer size: 130 Bytes
  • Size of remote file: 60 kB
static/index.js ADDED
@@ -0,0 +1,690 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ console.log('Landing page loaded.');
3
+
4
+ // Animate elements on scroll using Intersection Observer
5
+ const animatedElements = document.querySelectorAll('.animate-on-scroll');
6
+ const observer = new IntersectionObserver(entries => {
7
+ entries.forEach(entry => {
8
+ if (entry.isIntersecting) {
9
+ entry.target.classList.add('animate-fadeIn');
10
+ observer.unobserve(entry.target);
11
+ }
12
+ });
13
+ }, { threshold: 0.2 });
14
+
15
+ animatedElements.forEach(el => observer.observe(el));
16
+
17
+ // Plotly initial plot (if present)
18
+ const initialPlotDiv = document.getElementById('initialPlot');
19
+ if (initialPlotDiv) {
20
+ const plotData = JSON.parse(initialPlotDiv.dataset.plot);
21
+ Plotly.newPlot('plotDiv', plotData.data, plotData.layout);
22
+ }
23
+
24
+ // Initialize toast notifications
25
+ if (!document.getElementById('toast-container')) {
26
+ const toastContainer = document.createElement('div');
27
+ toastContainer.id = 'toast-container';
28
+ toastContainer.className = 'fixed top-4 right-4 z-50';
29
+ document.body.appendChild(toastContainer);
30
+ }
31
+ });
32
+
33
+ function showToast(message, type = 'success') {
34
+ const toast = document.createElement('div');
35
+ toast.className = `p-4 mb-4 rounded shadow-lg ${type === 'success' ? 'bg-green-500' : 'bg-red-500'} text-white`;
36
+ toast.textContent = message;
37
+
38
+ const container = document.getElementById('toast-container');
39
+ container.appendChild(toast);
40
+
41
+ setTimeout(() => {
42
+ toast.remove();
43
+ }, 3000);
44
+ }
45
+
46
+ window.createPlot = function() {
47
+ const column = document.getElementById('plotColumn').value;
48
+ const plotType = document.getElementById('plotType').value;
49
+
50
+ fetch('/forecast/sales/plot', {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/x-www-form-urlencoded',
54
+ },
55
+ body: `column=${column}&plot_type=${plotType}`
56
+ })
57
+ .then(response => response.json())
58
+ .then(data => {
59
+ if (data.success) {
60
+ const plotData = JSON.parse(data.plot);
61
+ Plotly.newPlot('plotDiv', plotData.data, plotData.layout);
62
+ showToast(`Generated ${plotType} for ${column}`);
63
+ } else {
64
+ showToast(data.error, 'error');
65
+ }
66
+ })
67
+ .catch(error => {
68
+ console.error('Error:', error);
69
+ showToast(error.message, 'error');
70
+ });
71
+ }
72
+
73
+ window.fixNulls = function(column) {
74
+ const method = document.getElementById('method-' + column).value;
75
+ fetch('/forecast/sales/fix_nulls', {
76
+ method: 'POST',
77
+ headers: {'Content-Type': 'application/x-www-form-urlencoded'},
78
+ body: `column=${column}&method=${method}`
79
+ })
80
+ .then(response => response.json())
81
+ .then(data => {
82
+ const msgDiv = document.getElementById('null-fix-message');
83
+ if (data.success) {
84
+ msgDiv.textContent = data.message;
85
+ msgDiv.classList.remove('text-red-700');
86
+ msgDiv.classList.add('text-green-700');
87
+ showToast(data.message);
88
+
89
+ // Update summary stats
90
+ updateSummaryStats(data.summary_stats);
91
+
92
+ // Update preview table
93
+ updatePreviewTable(data.columns, data.preview_data);
94
+
95
+ // Update nulls UI
96
+ updateNullsUI(data.summary_stats.missing_values);
97
+
98
+ } else {
99
+ msgDiv.textContent = data.error;
100
+ msgDiv.classList.remove('text-green-700');
101
+ msgDiv.classList.add('text-red-700');
102
+ showToast(data.error, 'error');
103
+ }
104
+ });
105
+ }
106
+
107
+ // Helper to update summary stats
108
+ function updateSummaryStats(stats) {
109
+ document.querySelectorAll('.summary-total-rows').forEach(el => el.textContent = stats.total_rows);
110
+ document.querySelectorAll('.summary-total-cols').forEach(el => el.textContent = stats.total_columns);
111
+ document.querySelectorAll('.summary-numeric-cols').forEach(el => el.textContent = stats.numeric_columns.join(', '));
112
+ document.querySelectorAll('.summary-categorical-cols').forEach(el => el.textContent = stats.categorical_columns.join(', '));
113
+ }
114
+
115
+ // Helper to update preview table
116
+ function updatePreviewTable(columns, previewData) {
117
+ const table = document.getElementById('preview-table');
118
+ if (!table) return;
119
+ // Update header
120
+ let thead = table.querySelector('thead');
121
+ let tbody = table.querySelector('tbody');
122
+ thead.innerHTML = '<tr>' + columns.map(col => `<th class="px-4 py-2 bg-gray-100">${col}</th>`).join('') + '</tr>';
123
+ // Update body
124
+ tbody.innerHTML = previewData.map(row =>
125
+ '<tr>' + columns.map(col => `<td class="border px-4 py-2">${row[col]}</td>`).join('') + '</tr>'
126
+ ).join('');
127
+ }
128
+
129
+ // Helper to update nulls UI
130
+ function updateNullsUI(missingValues) {
131
+ const nullList = document.getElementById('null-list');
132
+ if (!nullList) return;
133
+ let html = '';
134
+ let hasNulls = false;
135
+ for (const [col, count] of Object.entries(missingValues)) {
136
+ if (count > 0) {
137
+ hasNulls = true;
138
+ html += `
139
+ <li>
140
+ <span>${col}: ${count} missing values</span>
141
+ <select id="method-${col}" class="border rounded p-1 mx-2">
142
+ <option value="drop">Drop Rows</option>
143
+ <option value="mean">Fill Mean</option>
144
+ <option value="median">Fill Median</option>
145
+ <option value="mode">Fill Mode</option>
146
+ </select>
147
+ <button class="btn-learn-more" onclick="fixNulls('${col}')">Fix</button>
148
+ </li>
149
+ `;
150
+ }
151
+ }
152
+ nullList.innerHTML = html;
153
+ // If no nulls left, show a message
154
+ if (!hasNulls) {
155
+ nullList.innerHTML = '<li class="text-green-700 font-semibold">No missing values remaining!</li>';
156
+ }
157
+ }
158
+ window.runForecast = function() {
159
+ const dateCol = document.getElementById('forecastDateCol').value;
160
+ const targetCol = document.getElementById('forecastTargetCol').value;
161
+ const model = document.getElementById('forecastModel').value;
162
+ const horizon = document.getElementById('forecastHorizon').value;
163
+ const metricsDiv = document.getElementById('forecast-metrics');
164
+ const plotDiv = document.getElementById('forecast-plot');
165
+ const errorDiv = document.getElementById('forecast-error');
166
+ metricsDiv.innerHTML = '';
167
+ plotDiv.innerHTML = '';
168
+ errorDiv.textContent = '';
169
+
170
+ fetch('/forecast/sales/run_forecast', {
171
+ method: 'POST',
172
+ headers: {'Content-Type': 'application/x-www-form-urlencoded'},
173
+ body: `date_col=${dateCol}&target_col=${targetCol}&model=${model}&horizon=${horizon}`
174
+ })
175
+ .then(response => response.json())
176
+ .then(data => {
177
+ if (data.success) {
178
+ // Show metrics
179
+ metricsDiv.innerHTML = `
180
+ <div class="mb-2">
181
+ <strong>Model:</strong> ${data.model}
182
+ </div>
183
+ <div class="mb-2">
184
+ <strong>MAPE:</strong> ${data.metrics.MAPE.toFixed(2)}%
185
+ <strong class="ml-4">RMSE:</strong> ${data.metrics.RMSE.toFixed(2)}
186
+ <strong class="ml-4">R²:</strong> ${data.metrics.R2.toFixed(3)}
187
+ </div>
188
+ `;
189
+ // Plot forecast with confidence intervals
190
+ const trace = {
191
+ x: data.dates,
192
+ y: data.forecast,
193
+ mode: 'lines+markers',
194
+ name: 'Forecast'
195
+ };
196
+ const lower = data.conf_int.map(ci => ci[0]);
197
+ const upper = data.conf_int.map(ci => ci[1]);
198
+ const ciTrace = {
199
+ x: [...data.dates, ...data.dates.slice().reverse()],
200
+ y: [...upper, ...lower.reverse()],
201
+ fill: 'toself',
202
+ fillcolor: 'rgba(0,100,80,0.2)',
203
+ line: {color: 'transparent'},
204
+ name: 'Confidence Interval',
205
+ showlegend: true,
206
+ type: 'scatter'
207
+ };
208
+ const layout = {
209
+ title: 'Forecasted Sales',
210
+ xaxis: {title: 'Date'},
211
+ yaxis: {title: 'Forecast'},
212
+ showlegend: true
213
+ };
214
+ Plotly.newPlot(plotDiv, [trace, ciTrace], layout);
215
+ showToast('Forecast generated!');
216
+ } else {
217
+ errorDiv.textContent = data.error;
218
+ showToast(data.error, 'error');
219
+ }
220
+ })
221
+ .catch(error => {
222
+ errorDiv.textContent = error.message;
223
+ showToast(error.message, 'error');
224
+ });
225
+ }
226
+
227
+ // Machine Failure Prediction
228
+
229
+ function runPrediction() {
230
+ // Show loading state
231
+ const metricsDiv = document.getElementById('predict-metrics');
232
+ const errorDiv = document.getElementById('predict-error');
233
+ const singlePredictionSection = document.getElementById('single-prediction-section');
234
+
235
+ metricsDiv.innerHTML = '<p>Running prediction...</p>';
236
+ errorDiv.textContent = '';
237
+ singlePredictionSection.classList.add('hidden'); // Hide form until model is ready
238
+
239
+ // Get selected values
240
+ const targetCol = document.getElementById('predictTargetCol').value;
241
+ const model = document.getElementById('predictModel').value; // Model is currently fixed to RF in backend, but UI allows selection
242
+
243
+ // Create form data
244
+ const formData = new FormData();
245
+ formData.append('target_col', targetCol);
246
+ formData.append('model', model);
247
+
248
+ // Make API call
249
+ fetch('/predict/machine_failure/run_prediction', {
250
+ method: 'POST',
251
+ body: formData
252
+ })
253
+ .then(response => response.json())
254
+ .then(data => {
255
+ if (data.success) {
256
+ // Display metrics in a grid
257
+ let metricsHtml =
258
+ '<div class="grid grid-cols-2 md:grid-cols-4 gap-4">';
259
+ for (const [key, value] of Object.entries(data.metrics)) {
260
+ metricsHtml += `
261
+ <div class="p-4 bg-blue-50 rounded">
262
+ <p class="font-semibold">${key}</p>
263
+ <p class="text-xl">${value.toFixed(4)}</p>
264
+ </div>
265
+ `;
266
+ }
267
+ metricsHtml += "</div>";
268
+ metricsDiv.innerHTML = metricsHtml;
269
+
270
+ // --- Feature Importance ---
271
+ if (data.top_features && Array.isArray(data.top_features)) {
272
+ const fiDiv = document.getElementById("feature-importance");
273
+ const fiList = document.getElementById("feature-importance-list");
274
+ fiDiv.classList.remove("hidden");
275
+ fiList.innerHTML = `
276
+ <table class="min-w-full table-auto">
277
+ <thead>
278
+ <tr>
279
+ <th class="px-4 py-2 bg-gray-100">Feature</th>
280
+ <th class="px-4 py-2 bg-gray-100">Importance</th>
281
+ </tr>
282
+ </thead>
283
+ <tbody>
284
+ ${data.top_features
285
+ .map(
286
+ (f) =>
287
+ `<tr>
288
+ <td class="border px-4 py-2">${
289
+ f.feature
290
+ }</td>
291
+ <td class="border px-4 py-2">${f.importance.toFixed(
292
+ 4
293
+ )}</td>
294
+ </tr>`
295
+ )
296
+ .join("")}
297
+ </tbody>
298
+ </table>
299
+ `;
300
+ }
301
+
302
+ showToast("Model trained successfully");
303
+
304
+ // Now that the model is trained, fetch form data and display the single prediction form
305
+ fetchSinglePredictionForm();
306
+ } else {
307
+ errorDiv.textContent = data.error || 'An error occurred';
308
+ showToast(data.error || 'An error occurred', 'error');
309
+ }
310
+ })
311
+ .catch(error => {
312
+ errorDiv.textContent = 'Error: ' + error.message;
313
+ showToast('Error: ' + error.message, 'error');
314
+ });
315
+ }
316
+
317
+ window.runPrediction = runPrediction;
318
+
319
+ // Function to fetch data for and generate the single prediction form
320
+ function fetchSinglePredictionForm() {
321
+ fetch('/predict/machine_failure/get_form_data')
322
+ .then(response => response.json())
323
+ .then(data => {
324
+ if (data.success) {
325
+ generatePredictionForm(data.form_fields);
326
+ document.getElementById('single-prediction-section').classList.remove('hidden');
327
+ } else {
328
+ showToast(data.error, 'error');
329
+ document.getElementById('single-prediction-error').textContent = data.error;
330
+ }
331
+ })
332
+ .catch(error => {
333
+ console.error('Error fetching form data:', error);
334
+ showToast('Error fetching form data: ' + error.message, 'error');
335
+ document.getElementById('single-prediction-error').textContent = 'Error fetching form data: ' + error.message;
336
+ });
337
+ }
338
+
339
+ // Function to dynamically generate the prediction form
340
+ function generatePredictionForm(formFields) {
341
+ const formContainer = document.getElementById('single-prediction-form');
342
+ formContainer.innerHTML = ''; // Clear previous fields
343
+
344
+ formFields.forEach(field => {
345
+ const div = document.createElement('div');
346
+ div.className = 'mb-4';
347
+
348
+ const label = document.createElement('label');
349
+ label.className = 'block text-gray-700 text-sm font-bold mb-2';
350
+ label.textContent = field.name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); // Capitalize and replace underscores
351
+
352
+ div.appendChild(label);
353
+
354
+ if (field.type === 'select') {
355
+ const select = document.createElement('select');
356
+ select.id = `input-${field.name}`;
357
+ select.name = field.name;
358
+ select.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline';
359
+
360
+ field.options.forEach(option => {
361
+ const optionElement = document.createElement('option');
362
+ optionElement.value = option;
363
+ optionElement.textContent = option;
364
+ // Set default selected option
365
+ if (field.default_value !== undefined && String(field.default_value) === String(option)) { // Compare as strings
366
+ optionElement.selected = true;
367
+ }
368
+ select.appendChild(optionElement);
369
+ });
370
+ div.appendChild(select);
371
+ } else if (field.type === 'number') {
372
+ const input = document.createElement('input');
373
+ input.id = `input-${field.name}`;
374
+ input.name = field.name;
375
+ input.type = 'number';
376
+ input.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline';
377
+ input.placeholder = `Enter ${field.name.replace(/_/g, ' ')}`;
378
+ if (field.default_value !== undefined) {
379
+ input.value = field.default_value;
380
+ }
381
+ div.appendChild(input);
382
+ } else { // Default to text for other types, including timestamps
383
+ const input = document.createElement('input');
384
+ input.id = `input-${field.name}`;
385
+ input.name = field.name;
386
+ input.type = 'text';
387
+ input.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline';
388
+ input.placeholder = field.placeholder || `Enter ${field.name.replace(/_/g, ' ')}`;
389
+ if (field.default_value !== undefined) {
390
+ input.value = field.default_value;
391
+ }
392
+ div.appendChild(input);
393
+ }
394
+ formContainer.appendChild(div);
395
+ });
396
+ }
397
+
398
+ // Function to handle single instance prediction submission
399
+ window.predictSingleInstance = function() {
400
+ const form = document.getElementById('single-prediction-form');
401
+ const formData = {};
402
+ const inputs = form.querySelectorAll('input, select');
403
+
404
+ inputs.forEach(input => {
405
+ formData[input.name] = input.value;
406
+ });
407
+
408
+ const resultDiv = document.getElementById('single-prediction-result');
409
+ const predictionOutput = document.getElementById('prediction-output');
410
+ const probabilityOutput = document.getElementById('probability-output');
411
+ const errorDiv = document.getElementById('single-prediction-error');
412
+
413
+ resultDiv.classList.add('hidden');
414
+ errorDiv.textContent = '';
415
+ predictionOutput.textContent = 'Predicting...';
416
+ probabilityOutput.innerHTML = '';
417
+
418
+ fetch('/predict/machine_failure/predict_single', {
419
+ method: 'POST',
420
+ headers: {
421
+ 'Content-Type': 'application/json',
422
+ },
423
+ body: JSON.stringify(formData)
424
+ })
425
+ .then(response => response.json())
426
+ .then(data => {
427
+ if (data.success) {
428
+ let displayPrediction = 'Unknown';
429
+ if (data.prediction === 0 || data.prediction === '0') {
430
+ displayPrediction = 'No Failure';
431
+ } else if (data.prediction === 1 || data.prediction === '1') {
432
+ displayPrediction = 'Failure';
433
+ } else {
434
+ displayPrediction = data.prediction; // Fallback for other values if backend sends different strings
435
+ }
436
+ predictionOutput.textContent = displayPrediction;
437
+
438
+ if (data.probability && Array.isArray(data.probability)) {
439
+ if (data.probability.length === 2) { // Binary classification
440
+ const probNoFailure = data.probability[0];
441
+ const probFailure = data.probability[1];
442
+ probabilityOutput.innerHTML = `Probability of No Failure: ${(probNoFailure * 100).toFixed(2)}%<br>
443
+ Probability of Failure: ${(probFailure * 100).toFixed(2)}%`;
444
+ } else { // Multi-class classification
445
+ probabilityOutput.innerHTML = 'Probabilities: ' + data.probability.map((p, i) => `Class ${i}: ${(p * 100).toFixed(2)}%`).join(', ');
446
+ }
447
+ } else {
448
+ probabilityOutput.innerHTML = 'Probability: N/A (not a classification model)';
449
+ }
450
+ resultDiv.classList.remove('hidden');
451
+ showToast('Single prediction successful!');
452
+ } else {
453
+ errorDiv.textContent = data.error || 'An error occurred during single prediction.';
454
+ showToast(data.error || 'An error occurred', 'error');
455
+ }
456
+ })
457
+ .catch(error => {
458
+ console.error('Error during single prediction:', error);
459
+ errorDiv.textContent = 'Error: ' + error.message;
460
+ showToast('Error: ' + error.message, 'error');
461
+ });
462
+ };
463
+
464
+
465
+ // Supply Failure Prediction (New functions similar to Machine Failure)
466
+
467
+ window.runSupplyPrediction = function() {
468
+ const metricsDiv = document.getElementById('predict-metrics-supply');
469
+ const errorDiv = document.getElementById('predict-error-supply');
470
+ const singlePredictionSection = document.getElementById('single-prediction-section-supply');
471
+
472
+ metricsDiv.innerHTML = '<p>Running prediction...</p>';
473
+ errorDiv.textContent = '';
474
+ singlePredictionSection.classList.add('hidden');
475
+
476
+ // Target column is fixed to 'failure_flag'
477
+ const targetCol = 'failure_flag';
478
+ const model = document.getElementById('predictModelSupply').value;
479
+
480
+ const formData = new FormData();
481
+ formData.append('target_col', targetCol);
482
+ formData.append('model', model);
483
+
484
+ fetch('/predict/supply_failure/run_prediction', {
485
+ method: 'POST',
486
+ body: formData
487
+ })
488
+ .then(response => response.json())
489
+ .then(data => {
490
+ if (data.success) {
491
+ let metricsHtml =
492
+ '<div class="grid grid-cols-2 md:grid-cols-4 gap-4">';
493
+ for (const [key, value] of Object.entries(data.metrics)) {
494
+ metricsHtml += `
495
+ <div class="p-4 bg-blue-50 rounded">
496
+ <p class="font-semibold">${key}</p>
497
+ <p class="text-xl">${value.toFixed(4)}</p>
498
+ </div>
499
+ `;
500
+ }
501
+ metricsHtml += "</div>";
502
+ metricsDiv.innerHTML = metricsHtml;
503
+
504
+ // --- Feature Importance ---
505
+ if (data.top_features && Array.isArray(data.top_features)) {
506
+ const fiDiv = document.getElementById("feature-importance");
507
+ const fiList = document.getElementById("feature-importance-list");
508
+ fiDiv.classList.remove("hidden");
509
+ fiList.innerHTML = `
510
+ <table class="min-w-full table-auto">
511
+ <thead>
512
+ <tr>
513
+ <th class="px-4 py-2 bg-gray-100">Feature</th>
514
+ <th class="px-4 py-2 bg-gray-100">Importance</th>
515
+ </tr>
516
+ </thead>
517
+ <tbody>
518
+ ${data.top_features
519
+ .map(
520
+ (f) =>
521
+ `<tr>
522
+ <td class="border px-4 py-2">${
523
+ f.feature
524
+ }</td>
525
+ <td class="border px-4 py-2">${f.importance.toFixed(
526
+ 4
527
+ )}</td>
528
+ </tr>`
529
+ )
530
+ .join("")}
531
+ </tbody>
532
+ </table>
533
+ `;
534
+ }
535
+
536
+ showToast("Supply Model trained successfully");
537
+ fetchSupplySinglePredictionForm();
538
+ } else {
539
+ errorDiv.textContent = data.error || 'An error occurred';
540
+ showToast(data.error || 'An error occurred', 'error');
541
+ }
542
+ })
543
+ .catch(error => {
544
+ errorDiv.textContent = 'Error: ' + error.message;
545
+ showToast('Error: ' + error.message, 'error');
546
+ });
547
+ }
548
+
549
+ function fetchSupplySinglePredictionForm() {
550
+ fetch('/predict/supply_failure/get_form_data')
551
+ .then(response => response.json())
552
+ .then(data => {
553
+ if (data.success) {
554
+ generateSupplyPredictionForm(data.form_fields);
555
+ document.getElementById('single-prediction-section-supply').classList.remove('hidden');
556
+ } else {
557
+ showToast(data.error, 'error');
558
+ document.getElementById('single-prediction-error-supply').textContent = data.error;
559
+ }
560
+ })
561
+ .catch(error => {
562
+ console.error('Error fetching supply form data:', error);
563
+ showToast('Error fetching supply form data: ' + error.message, 'error');
564
+ document.getElementById('single-prediction-error-supply').textContent = 'Error fetching supply form data: ' + error.message;
565
+ });
566
+ }
567
+
568
+ function generateSupplyPredictionForm(formFields) {
569
+ const formContainer = document.getElementById('single-prediction-form-supply');
570
+ formContainer.innerHTML = '';
571
+
572
+ formFields.forEach(field => {
573
+ const div = document.createElement('div');
574
+ div.className = 'mb-4';
575
+
576
+ const label = document.createElement('label');
577
+ label.className = 'block text-gray-700 text-sm font-bold mb-2';
578
+ label.textContent = field.name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
579
+
580
+ div.appendChild(label);
581
+
582
+ if (field.type === 'select') {
583
+ const select = document.createElement('select');
584
+ select.id = `input-supply-${field.name}`;
585
+ select.name = field.name;
586
+ select.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline';
587
+
588
+ field.options.forEach(option => {
589
+ const optionElement = document.createElement('option');
590
+ optionElement.value = option;
591
+ optionElement.textContent = option;
592
+ if (field.default_value !== undefined && String(field.default_value) === String(option)) {
593
+ optionElement.selected = true;
594
+ }
595
+ select.appendChild(optionElement);
596
+ });
597
+ div.appendChild(select);
598
+ } else if (field.type === 'number') {
599
+ const input = document.createElement('input');
600
+ input.id = `input-supply-${field.name}`;
601
+ input.name = field.name;
602
+ input.type = 'number';
603
+ input.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline';
604
+ input.placeholder = `Enter ${field.name.replace(/_/g, ' ')}`;
605
+ if (field.default_value !== undefined) {
606
+ input.value = field.default_value;
607
+ }
608
+ div.appendChild(input);
609
+ } else { // Default to text for other types, including timestamps
610
+ const input = document.createElement('input');
611
+ input.id = `input-supply-${field.name}`;
612
+ input.name = field.name;
613
+ input.type = 'text';
614
+ input.className = 'shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline';
615
+ input.placeholder = field.placeholder || `Enter ${field.name.replace(/_/g, ' ')}`;
616
+ if (field.default_value !== undefined) {
617
+ input.value = field.default_value;
618
+ }
619
+ div.appendChild(input);
620
+ }
621
+ formContainer.appendChild(div);
622
+ });
623
+ }
624
+
625
+ window.predictSupplySingleInstance = function() {
626
+ const form = document.getElementById('single-prediction-form-supply');
627
+ const formData = {};
628
+ const inputs = form.querySelectorAll('input, select');
629
+
630
+ inputs.forEach(input => {
631
+ formData[input.name] = input.value;
632
+ });
633
+
634
+ const resultDiv = document.getElementById('single-prediction-result-supply');
635
+ const predictionOutput = document.getElementById('prediction-output-supply');
636
+ const probabilityOutput = document.getElementById('probability-output-supply');
637
+ const errorDiv = document.getElementById('single-prediction-error-supply');
638
+
639
+ resultDiv.classList.add('hidden');
640
+ errorDiv.textContent = '';
641
+ predictionOutput.textContent = 'Predicting...';
642
+ probabilityOutput.innerHTML = '';
643
+
644
+ fetch('/predict/supply_failure/predict_single', {
645
+ method: 'POST',
646
+ headers: {
647
+ 'Content-Type': 'application/json',
648
+ },
649
+ body: JSON.stringify(formData)
650
+ })
651
+ .then(response => response.json())
652
+ .then(data => {
653
+ if (data.success) {
654
+ // User-friendly mapping for Supply Failure
655
+ let displayPrediction = 'Unknown';
656
+ if (data.prediction === "Delivery Successful") { // Backend sends "Delivery Successful" or "Delivery Failed"
657
+ displayPrediction = 'Delivery Successful';
658
+ } else if (data.prediction === "Delivery Failed") {
659
+ displayPrediction = 'Delivery Failed';
660
+ } else {
661
+ displayPrediction = data.prediction; // Fallback
662
+ }
663
+ predictionOutput.textContent = displayPrediction;
664
+
665
+ if (data.probability && Array.isArray(data.probability)) {
666
+ if (data.probability.length === 2) {
667
+ // Assuming data.probability[0] corresponds to 'Delivery Successful' and data.probability[1] to 'Delivery Failed'
668
+ const probSuccessful = data.probability[0];
669
+ const probFailed = data.probability[1];
670
+ probabilityOutput.innerHTML = `Probability of Delivery Successful: ${(probSuccessful * 100).toFixed(2)}%<br>
671
+ Probability of Delivery Failed: ${(probFailed * 100).toFixed(2)}%`;
672
+ } else {
673
+ probabilityOutput.innerHTML = 'Probabilities: ' + data.probability.map((p, i) => `Class ${i}: ${(p * 100).toFixed(2)}%`).join(', ');
674
+ }
675
+ } else {
676
+ probabilityOutput.innerHTML = 'Probability: N/A (not a classification model)';
677
+ }
678
+ resultDiv.classList.remove('hidden');
679
+ showToast('Single prediction successful!');
680
+ } else {
681
+ errorDiv.textContent = data.error || 'An error occurred during single prediction.';
682
+ showToast(data.error || 'An error occurred', 'error');
683
+ }
684
+ })
685
+ .catch(error => {
686
+ console.error('Error during single prediction:', error);
687
+ errorDiv.textContent = 'Error: ' + error.message;
688
+ showToast('Error: ' + error.message, 'error');
689
+ });
690
+ };
static/styles.css ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Base typography and container */
2
+ body {
3
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
4
+ }
5
+
6
+ /* Container max width override */
7
+ .container {
8
+ max-width: 1200px;
9
+ }
10
+
11
+ /* Custom keyframes for fade in */
12
+ @keyframes fadeIn {
13
+ from {
14
+ opacity: 0;
15
+ transform: translateY(20px);
16
+ }
17
+ to {
18
+ opacity: 1;
19
+ transform: translateY(0);
20
+ }
21
+ }
22
+
23
+ /* Animation helper */
24
+ .animate-fadeIn {
25
+ animation: fadeIn 1s ease-out forwards;
26
+ }
27
+
28
+ /* Hero section background and overlay */
29
+ .hero-bg {
30
+ background-image: url('https://source.unsplash.com/1600x900/?business,analytics');
31
+ background-size: cover;
32
+ background-position: center;
33
+ position: relative;
34
+ height: 80vh;
35
+ }
36
+ .hero-overlay {
37
+ position: absolute;
38
+ top: 0;
39
+ left: 0;
40
+ right: 0;
41
+ bottom: 0;
42
+ background: rgba(0, 0, 0, 0.5);
43
+ }
44
+
45
+ /* Button styles */
46
+ .btn-learn-more {
47
+ display: inline-block;
48
+ padding: 0.75rem 1.5rem;
49
+ font-size: 0.875rem;
50
+ font-weight: 600;
51
+ border-radius: 0.5rem;
52
+ transition: background-color 0.3s ease;
53
+ background-color: #3b82f6; /* Tailwind blue-500 */
54
+ color: #ffffff;
55
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
56
+ }
57
+ .btn-learn-more:hover {
58
+ background-color: #2563eb; /* Tailwind blue-600 */
59
+ }
60
+ .btn-custom {
61
+ transition: transform 0.3s ease;
62
+ }
63
+ .btn-custom:hover {
64
+ transform: scale(1.05);
65
+ }
66
+
67
+ /* ----- Module Base Styles (Reusable for all modules) ----- */
68
+ .module {
69
+ padding: 1rem;
70
+ }
71
+ .module-title {
72
+ font-size: 1.875rem; /* 30px equivalent */
73
+ font-weight: 700;
74
+ color: #1f2937; /* Tailwind gray-800 */
75
+ }
76
+ .module-intro {
77
+ font-size: 1.125rem;
78
+ color: #4b5563; /* Tailwind gray-600 */
79
+ }
80
+ .module-section {
81
+ background-color: #ffffff;
82
+ padding: 1.5rem;
83
+ border-radius: 0.5rem;
84
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
85
+ margin-bottom: 1.5rem;
86
+ }
87
+ .module-section-header {
88
+ font-size: 1.5rem; /* 24px equivalent */
89
+ font-weight: 700;
90
+ color: #374151; /* Tailwind gray-700 */
91
+ margin-bottom: 0.5rem;
92
+ }
93
+ .module-section-content {
94
+ font-size: 1rem;
95
+ color: #4b5563;
96
+ }
97
+ .module-list li {
98
+ margin-bottom: 0.5rem;
99
+ }
templates/base.html ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ title | default("Predictive Analytics Tool") }}</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
9
+ <script src="{{ url_for('static', filename='index.js') }}" defer></script>
10
+ </head>
11
+ <body class="bg-gray-100 text-gray-900">
12
+ <header class="bg-white shadow">
13
+ <div class="container mx-auto px-4 py-6 flex justify-between items-center">
14
+ <div class="text-2xl font-bold">Predictive Analytics Tool</div>
15
+ <nav>
16
+ <ul class="flex space-x-4">
17
+ <li><a href="/" class="hover:text-blue-500 transition">Home</a></li>
18
+ <li><a href="/forecast/sales" class="hover:text-blue-500 transition">Sales Prediction</a></li>
19
+ <li><a href="/campaign" class="hover:text-blue-500 transition">Campaign Analysis</a></li>
20
+ <li><a href="/inventory" class="hover:text-blue-500 transition">Inventory Optimization</a></li>
21
+ <li><a href="/pricing" class="hover:text-blue-500 transition">Pricing Intelligence</a></li>
22
+ <li><a href="/underperformance" class="hover:text-blue-500 transition">Underperformance</a></li>
23
+ <li><a href="/predict/machine_failure" class="hover:text-blue-500 transition">Machine Failure</a></li>
24
+ <li><a href="/predict/supply_failure" class="hover:text-blue-500 transition">Supply Failure</a></li>
25
+ </ul>
26
+ </nav>
27
+ </div>
28
+ </header>
29
+ <main>
30
+ {% block content %}{% endblock %}
31
+ </main>
32
+ <footer class="bg-white border-t">
33
+ <div class="container mx-auto px-4 py-4 text-center text-sm">
34
+ &copy; 2025 Predictive Analytics Tool. All rights reserved.
35
+ </div>
36
+ </footer>
37
+ </body>
38
+ </html>
templates/campaign_analysis.html ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+ {% block content %}
3
+ <section class="container mx-auto py-8">
4
+ <h1 class="text-3xl font-bold mb-4">Campaign Analysis</h1>
5
+ <p class="mb-6">Analyze the performance of your promotions and campaigns to calculate uplift, ROI, and customer engagement.</p>
6
+ <form method="POST" action="{{ url_for('campaign_analysis.upload_files') }}" enctype="multipart/form-data" class="mb-8 bg-white p-6 rounded shadow flex flex-col gap-4">
7
+ <div class="flex flex-col gap-2">
8
+ <label class="font-semibold">Sales Data CSV</label>
9
+ <input type="file" name="sales_file" accept=".csv" required class="file:border file:rounded file:px-3 file:py-1">
10
+ </div>
11
+ <div class="flex flex-col gap-2">
12
+ <label class="font-semibold">Campaign Data CSV</label>
13
+ <input type="file" name="campaign_file" accept=".csv" required class="file:border file:rounded file:px-3 file:py-1">
14
+ </div>
15
+ <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition">Analyze Campaigns</button>
16
+ </form>
17
+
18
+ {% if summary %}
19
+ <div class="bg-white p-6 rounded shadow mb-8">
20
+ <h2 class="text-xl font-bold mb-2">Summary</h2>
21
+ <ul class="list-disc pl-5">
22
+ <li><b>Total Campaigns:</b> {{ summary.total_campaigns }}</li>
23
+ <li><b>Total Campaign Revenue:</b> ${{ "%.2f"|format(summary.total_revenue) }}</li>
24
+ <li><b>Average Campaign Uplift:</b> {{ "%.2f"|format(summary.avg_uplift_pct) }}%</li>
25
+ <li><b>Average ROI:</b> {{ "%.2f"|format(summary.avg_roi) }}%</li>
26
+ </ul>
27
+ </div>
28
+ {% endif %}
29
+
30
+ {% if plot_data %}
31
+ <div class="bg-white p-6 rounded shadow mb-8">
32
+ <h2 class="text-xl font-bold mb-4">Campaign Visualizations</h2>
33
+ <div class="flex flex-col gap-8 w-full">
34
+ <div class="w-full">
35
+ <h3 class="font-semibold mb-2 text-blue-600">Uplift % by Campaign</h3>
36
+ <div id="uplift_chart" class="w-full" style="min-height:350px;"></div>
37
+ </div>
38
+ <div class="w-full">
39
+ <h3 class="font-semibold mb-2 text-blue-600">ROI % by Campaign</h3>
40
+ <div id="roi_chart" class="w-full" style="min-height:350px;"></div>
41
+ </div>
42
+ <div class="w-full">
43
+ <h3 class="font-semibold mb-2 text-blue-600">Campaign Revenue Over Time</h3>
44
+ <div id="revenue_chart" class="w-full" style="min-height:350px;"></div>
45
+ </div>
46
+ <div class="w-full">
47
+ <h3 class="font-semibold mb-2 text-blue-600">Campaign Types</h3>
48
+ <div id="type_pie" class="w-full" style="min-height:350px;"></div>
49
+ </div>
50
+ <div class="w-full">
51
+ <h3 class="font-semibold mb-2 text-blue-600">Top Regions</h3>
52
+ <div id="region_pie" class="w-full" style="min-height:350px;"></div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ <script src="https://cdn.plot.ly/plotly-2.31.1.min.js"></script>
57
+ <script>
58
+ const plots = {{ plot_data|tojson }};
59
+ if (plots && plots.bar_uplift && plots.bar_uplift.x && plots.bar_uplift.x.length) {
60
+ Plotly.newPlot('uplift_chart', [{
61
+ x: plots.bar_uplift.x,
62
+ y: plots.bar_uplift.y,
63
+ type: 'bar',
64
+ marker: {color: '#3b82f6'},
65
+ text: plots.bar_uplift.y.map(v=>v.toFixed(2)+'%')
66
+ }], {title: 'Uplift % by Campaign', margin: {t:30}, width: 900, height: 350});
67
+ }
68
+ if (plots && plots.bar_roi && plots.bar_roi.x && plots.bar_roi.x.length) {
69
+ Plotly.newPlot('roi_chart', [{
70
+ x: plots.bar_roi.x,
71
+ y: plots.bar_roi.y,
72
+ type: 'bar',
73
+ marker: {color: '#10b981'},
74
+ text: plots.bar_roi.y.map(v=>v.toFixed(2)+'%')
75
+ }], {title: 'ROI % by Campaign', margin: {t:30}, width: 900, height: 350});
76
+ }
77
+ if (plots && plots.line_revenue && plots.line_revenue.x && plots.line_revenue.x.length) {
78
+ Plotly.newPlot('revenue_chart', [{
79
+ x: plots.line_revenue.x,
80
+ y: plots.line_revenue.y,
81
+ type: 'scatter',
82
+ mode: 'lines+markers',
83
+ marker: {color: '#6366f1'},
84
+ text: plots.line_revenue.names
85
+ }], {title: 'Revenue by Campaign Start Date', margin: {t:30}, width: 900, height: 350});
86
+ }
87
+ if (plots && plots.pie_type && plots.pie_type.labels && plots.pie_type.labels.length) {
88
+ Plotly.newPlot('type_pie', [{
89
+ labels: plots.pie_type.labels,
90
+ values: plots.pie_type.values,
91
+ type: 'pie'
92
+ }], {title: 'Campaign Type Distribution', margin: {t:30}, width: 900, height: 350});
93
+ }
94
+ if (plots && plots.pie_region && plots.pie_region.labels && plots.pie_region.labels.length) {
95
+ Plotly.newPlot('region_pie', [{
96
+ labels: plots.pie_region.labels,
97
+ values: plots.pie_region.values,
98
+ type: 'pie'
99
+ }], {title: 'Top Regions', margin: {t:30}, width: 900, height: 350});
100
+ }
101
+ </script>
102
+ {% endif %}
103
+
104
+ </section>
105
+ {% endblock %}
templates/index.html ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+ {% block content %}
3
+ <!-- Hero section -->
4
+ <section class="hero-bg">
5
+ <div class="hero-overlay flex flex-col justify-center items-center">
6
+ <h1 class="text-5xl text-white font-bold mb-4 animate-on-scroll">Transform Your Data</h1>
7
+ <p class="mb-6 text-xl text-white animate-on-scroll">Empower your sales & inventory decisions with advanced predictive analytics.</p>
8
+ <a href="/forecast/sales" class="btn-custom bg-blue-500 text-white px-8 py-3 rounded-full shadow-lg hover:bg-blue-600 transition animate-on-scroll">
9
+ Get Started
10
+ </a>
11
+ </div>
12
+ </section>
13
+
14
+ <!-- Features Section -->
15
+ <section id="features" class="py-16 bg-gray-50">
16
+ <div class="container mx-auto px-4">
17
+ <!-- Sales Prediction -->
18
+ <div class="flex flex-col md:flex-row items-center my-12 animate-on-scroll">
19
+ <div class="md:w-1/2 md:pr-8">
20
+ <h2 class="text-3xl font-bold mb-4">Sales Prediction Engine</h2>
21
+ <p class="mb-4">Forecast daily sales with high accuracy using state-of-the-art machine learning and time-series models.</p>
22
+ <a href="/forecast/sales" class="text-blue-500 hover:underline">Learn More</a>
23
+ </div>
24
+ <div class="md:w-1/2">
25
+ <img src="static\images\sales prediction.png" alt="Sales Prediction" class="w-full rounded-lg shadow-lg">
26
+ </div>
27
+ </div>
28
+
29
+ <!-- Campaign Analysis -->
30
+ <div class="flex flex-col md:flex-row-reverse items-center my-12 animate-on-scroll">
31
+ <div class="md:w-1/2 md:pl-8">
32
+ <h2 class="text-3xl font-bold mb-4">Campaign Analysis</h2>
33
+ <p class="mb-4">Analyze campaign performance to optimize ROI and boost customer engagement through comprehensive insights.</p>
34
+ <a href="/campaign" class="text-blue-500 hover:underline">Learn More</a>
35
+ </div>
36
+ <div class="md:w-1/2">
37
+ <img src="static\images\campaign.jpg" alt="Campaign Analysis" class="w-full rounded-lg shadow-lg">
38
+ </div>
39
+ </div>
40
+
41
+ <!-- Inventory Optimization -->
42
+ <div class="flex flex-col md:flex-row items-center my-12 animate-on-scroll">
43
+ <div class="md:w-1/2 md:pr-8">
44
+ <h2 class="text-3xl font-bold mb-4">Inventory Optimization</h2>
45
+ <p class="mb-4">Utilize demand forecasting and sophisticated optimization algorithms to reduce surplus and prevent stockouts.</p>
46
+ <a href="/inventory" class="text-blue-500 hover:underline">Learn More</a>
47
+ </div>
48
+ <div class="md:w-1/2">
49
+ <img src="static\images\inventory.png" alt="Inventory Optimization" class="w-full rounded-lg shadow-lg">
50
+ </div>
51
+ </div>
52
+
53
+ <!-- Pricing Intelligence -->
54
+ <div class="flex flex-col md:flex-row-reverse items-center my-12 animate-on-scroll">
55
+ <div class="md:w-1/2 md:pl-8">
56
+ <h2 class="text-3xl font-bold mb-4">Pricing Intelligence</h2>
57
+ <p class="mb-4">Optimize pricing strategies with advanced conversion analytics and price elasticity models for maximum revenue.</p>
58
+ <a href="/pricing" class="text-blue-500 hover:underline">Learn More</a>
59
+ </div>
60
+ <div class="md:w-1/2">
61
+ <img src="static\images\pricing.jpg" alt="Pricing Intelligence" class="w-full rounded-lg shadow-lg">
62
+ </div>
63
+ </div>
64
+
65
+ <!-- Underperformance Detector -->
66
+ <div class="flex flex-col md:flex-row items-center my-12 animate-on-scroll">
67
+ <div class="md:w-1/2 md:pr-8">
68
+ <h2 class="text-3xl font-bold mb-4">Underperformance Detector</h2>
69
+ <p class="mb-4">Identify low-performing segments and uncover upsell opportunities through advanced analytics insights.</p>
70
+ <a href="/underperformance" class="text-blue-500 hover:underline">Learn More</a>
71
+ </div>
72
+ <div class="md:w-1/2">
73
+ <img src="static\images\underperforming.png" alt="Underperformance Detector" class="w-full rounded-lg shadow-lg">
74
+ </div>
75
+ </div>
76
+
77
+ <!-- Machine Failure -->
78
+ <div class="flex flex-col md:flex-row-reverse items-center my-12 animate-on-scroll">
79
+ <div class="md:w-1/2 md:pl-8">
80
+ <h2 class="text-3xl font-bold mb-4">Machine Failure Predictor</h2>
81
+ <p class="mb-4">Predict machine failure before breakdown.</p>
82
+ <a href="/predict/machine_failure" class="text-blue-500 hover:underline">Learn More</a>
83
+ </div>
84
+ <div class="md:w-1/2">
85
+ <img src="static\images\machine.jpeg" alt="Underperformance Detector" class="w-full rounded-lg shadow-lg">
86
+ </div>
87
+ </div>
88
+
89
+ <!-- Supply Failure -->
90
+ <div class="flex flex-col md:flex-row items-center my-12 animate-on-scroll">
91
+ <div class="md:w-1/2 md:pr-8">
92
+ <h2 class="text-3xl font-bold mb-4">Supply Chain Failure Predictor</h2>
93
+ <p class="mb-4">Predict supply chain failure before breakdown.</p>
94
+ <a href="/predict/supply_failure" class="text-blue-500 hover:underline">Learn More</a>
95
+ </div>
96
+ <div class="md:w-1/2">
97
+ <img src="static\images\supply.jpeg" alt="Underperformance Detector" class="w-full rounded-lg shadow-lg">
98
+ </div>
99
+ </div>
100
+
101
+
102
+ </div>
103
+ </section>
104
+ {% endblock %}
templates/inventory_optimization.html ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+ {% block content %}
3
+ <section>
4
+ <h1 class="text-3xl font-bold mb-4">Inventory Optimization</h1>
5
+ <p class="mb-6">Minimize stockouts and reduce surplus by leveraging demand forecasting and optimization algorithms.</p>
6
+ <div class="bg-white p-6 rounded shadow">
7
+ <!-- Future dashboard elements for inventory planning -->
8
+ <p>[Inventory optimization dashboard components here...]</p>
9
+ </div>
10
+ </section>
11
+ {% endblock %}
templates/machine_failure.html ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+ {% block content %}
3
+ <section class="py-8">
4
+ <h1 class="text-3xl font-bold mb-4">Machine Failure Prediction</h1>
5
+
6
+ <div class="bg-gray-50 p-6 rounded shadow mb-6">
7
+ <h2 class="text-2xl font-semibold mb-2">Upload Machine Failure Data (CSV)</h2>
8
+ <form action="{{ url_for('machine_failure.upload_file') }}" method="POST" enctype="multipart/form-data">
9
+ <input type="file" name="machine_file" accept=".csv" class="mb-4 block">
10
+ <button type="submit" class="btn-learn-more">Upload</button>
11
+ </form>
12
+ </div>
13
+
14
+ {% if preview_data %}
15
+ <div class="bg-white p-6 rounded shadow mb-6">
16
+ <h2 class="text-2xl font-semibold mb-4">Data Preview (First 5 Rows)</h2>
17
+ <div class="overflow-x-auto">
18
+ <table id="preview-table" class="min-w-full table-auto">
19
+ <thead>
20
+ <tr>
21
+ {% for column in columns %}
22
+ <th class="px-4 py-2 bg-gray-100">{{ column }}</th>
23
+ {% endfor %}
24
+ </tr>
25
+ </thead>
26
+ <tbody>
27
+ {% for row in preview_data %}
28
+ <tr>
29
+ {% for column in columns %}
30
+ <td class="border px-4 py-2">{{ row[column] }}</td>
31
+ {% endfor %}
32
+ </tr>
33
+ {% endfor %}
34
+ </tbody>
35
+ </table>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="bg-white p-6 rounded shadow">
40
+ <h2 class="text-2xl font-semibold mb-4">Summary Statistics</h2>
41
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
42
+ <div class="p-4 bg-blue-50 rounded">
43
+ <p><strong>Total Rows:</strong> <span class="summary-total-rows">{{ summary_stats.total_rows }}</span></p>
44
+ <p><strong>Total Columns:</strong> <span class="summary-total-cols">{{ summary_stats.total_columns }}</span></p>
45
+ </div>
46
+ <div class="p-4 bg-green-50 rounded">
47
+ <p><strong>Numeric Columns:</strong> <span class="summary-numeric-cols">{{ summary_stats.numeric_columns|join(', ') }}</span></p>
48
+ <p><strong>Categorical Columns:</strong> <span class="summary-categorical-cols">{{ summary_stats.categorical_columns|join(', ') }}</span></p>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+ <div id="feature-importance" class="bg-white p-6 rounded shadow mt-6 hidden">
54
+ <h2 class="text-2xl font-semibold mb-4">Top 5 Feature Importances</h2>
55
+ <div id="feature-importance-list"></div>
56
+ </div>
57
+
58
+ <div class="bg-white p-6 rounded shadow mt-6">
59
+ <h2 class="text-2xl font-semibold mb-4">Failure Prediction Metrics</h2>
60
+ <div class="flex flex-wrap gap-4 mb-4">
61
+ <div>
62
+ <label class="block font-semibold mb-1">Target Column</label>
63
+ <select id="predictTargetCol" class="border rounded p-2">
64
+ {% for column in columns %}
65
+ <option value="{{ column }}">{{ column }}</option>
66
+ {% endfor %}
67
+ </select>
68
+ </div>
69
+ <div>
70
+ <label class="block font-semibold mb-1">Model</label>
71
+ <select id="predictModel" class="border rounded p-2">
72
+ <option value="Random Forest">Random Forest</option>
73
+ <option value="XGBoost">XGBoost</option>
74
+ <option value="Logistic Regression">Logistic Regression</option>
75
+ </select>
76
+ </div>
77
+ <div class="flex items-end">
78
+ <button onclick="runPrediction()" class="btn-learn-more">Run Prediction</button>
79
+ </div>
80
+ </div>
81
+ <div id="predict-metrics" class="mb-4"></div>
82
+ <div id="predict-error" class="text-red-600 font-semibold mt-2"></div>
83
+ </div>
84
+
85
+ <div id="single-prediction-section" class="bg-white p-6 rounded shadow mt-6 hidden">
86
+ <h2 class="text-2xl font-semibold mb-4">Predict for a Single Instance</h2>
87
+ <form id="single-prediction-form" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4">
88
+ </form>
89
+ <button onclick="predictSingleInstance()" class="btn-learn-more">Predict</button>
90
+ <div id="single-prediction-result" class="mt-4 p-4 bg-blue-100 rounded hidden">
91
+ <p class="font-semibold text-lg">Prediction: <span id="prediction-output" class="font-bold text-blue-800"></span></p>
92
+ <div id="probability-output" class="mt-2 text-sm"></div>
93
+ </div>
94
+ <div id="single-prediction-error" class="text-red-600 font-semibold mt-2"></div>
95
+ </div>
96
+ {% endif %}
97
+
98
+ </section>
99
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
100
+ <script src="{{ url_for('static', filename='index.js') }}"></script>
101
+ {% endblock %}
templates/pricing_intelligence.html ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+ {% block content %}
3
+ <section>
4
+ <h1 class="text-3xl font-bold mb-4">Pricing Intelligence</h1>
5
+ <p class="mb-6">Discover optimal pricing strategies based on price elasticity and conversion analysis to maximize revenue.</p>
6
+ <div class="bg-white p-6 rounded shadow">
7
+ <!-- Future pricing analytics components -->
8
+ <p>[Pricing intelligence dashboard components here...]</p>
9
+ </div>
10
+ </section>
11
+ {% endblock %}
templates/sales_prediction.html ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+ {% block content %}
3
+ <section class="py-8">
4
+ <h1 class="text-3xl font-bold mb-4">Sales Prediction Engine</h1>
5
+
6
+ <!-- File Upload Form -->
7
+ <div class="bg-gray-50 p-6 rounded shadow mb-6">
8
+ <h2 class="text-2xl font-semibold mb-2">Upload Sales Data (CSV)</h2>
9
+ <form action="{{ url_for('sales_prediction.upload_file') }}" method="POST" enctype="multipart/form-data">
10
+ <input type="file" name="sales_file" accept=".csv" class="mb-4 block">
11
+ <button type="submit" class="btn-learn-more">Upload</button>
12
+ </form>
13
+ </div>
14
+
15
+ {% if preview_data %}
16
+ <!-- Data Preview -->
17
+ <div class="bg-white p-6 rounded shadow mb-6">
18
+ <h2 class="text-2xl font-semibold mb-4">Data Preview (First 5 Rows)</h2>
19
+ <div class="overflow-x-auto">
20
+ <table id="preview-table" class="min-w-full table-auto">
21
+ <thead>
22
+ <tr>
23
+ {% for column in columns %}
24
+ <th class="px-4 py-2 bg-gray-100">{{ column }}</th>
25
+ {% endfor %}
26
+ </tr>
27
+ </thead>
28
+ <tbody>
29
+ {% for row in preview_data %}
30
+ <tr>
31
+ {% for column in columns %}
32
+ <td class="border px-4 py-2">{{ row[column] }}</td>
33
+ {% endfor %}
34
+ </tr>
35
+ {% endfor %}
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ </div>
40
+
41
+ <!-- Summary Statistics -->
42
+ <div class="bg-white p-6 rounded shadow">
43
+ <h2 class="text-2xl font-semibold mb-4">Summary Statistics</h2>
44
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
45
+ <div class="p-4 bg-blue-50 rounded">
46
+ <p><strong>Total Rows:</strong> <span class="summary-total-rows">{{ summary_stats.total_rows }}</span></p>
47
+ <p><strong>Total Columns:</strong> <span class="summary-total-cols">{{ summary_stats.total_columns }}</span></p>
48
+ </div>
49
+ <div class="p-4 bg-green-50 rounded">
50
+ <p><strong>Numeric Columns:</strong> <span class="summary-numeric-cols">{{ summary_stats.numeric_columns|join(', ') }}</span></p>
51
+ <p><strong>Categorical Columns:</strong> <span class="summary-categorical-cols">{{ summary_stats.categorical_columns|join(', ') }}</span></p>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- Missing Values Summary & No-code Fix UI -->
56
+ {% set null_columns = [] %}
57
+ {% for column, count in summary_stats.missing_values.items() %}
58
+ {% if count > 0 %}
59
+ {% set _ = null_columns.append((column, count)) %}
60
+ {% endif %}
61
+ {% endfor %}
62
+ <div class="mt-4 p-4 bg-yellow-50 rounded">
63
+ <h3 class="font-semibold mb-2">Missing Values</h3>
64
+ <ul id="null-list">
65
+ {% for column, count in null_columns %}
66
+ <li>
67
+ <span>{{ column }}: {{ count }} missing values</span>
68
+ <select id="method-{{ column }}" class="border rounded p-1 mx-2">
69
+ <option value="drop">Drop Rows</option>
70
+ <option value="mean">Fill Mean</option>
71
+ <option value="median">Fill Median</option>
72
+ <option value="mode">Fill Mode</option>
73
+ </select>
74
+ <button class="btn-learn-more" onclick="fixNulls('{{ column }}')">Fix</button>
75
+ </li>
76
+ {% endfor %}
77
+ </ul>
78
+ <div id="null-fix-message" class="mt-2 text-green-700 font-semibold"></div>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- Data Visualization -->
83
+ <div class="bg-white p-6 rounded shadow mt-6">
84
+ <h2 class="text-2xl font-semibold mb-4">Data Visualization</h2>
85
+ <div class="flex gap-4 mb-4">
86
+ <select id="plotColumn" class="border rounded p-2">
87
+ {% for column in columns %}
88
+ <option value="{{ column }}">{{ column }}</option>
89
+ {% endfor %}
90
+ </select>
91
+ <select id="plotType" class="border rounded p-2">
92
+ <option value="histogram">Histogram (Numeric)</option>
93
+ <option value="box">Box Plot (Numeric)</option>
94
+ <option value="bar">Bar Plot (Categorical)</option>
95
+ </select>
96
+ <button onclick="createPlot()" class="btn-learn-more">Generate Plot</button>
97
+ </div>
98
+ <div id="plotDiv" class="w-full h-96"></div>
99
+ </div>
100
+
101
+ <!-- Forecasting -->
102
+ <div class="bg-white p-6 rounded shadow mt-6">
103
+ <h2 class="text-2xl font-semibold mb-4">Forecasting</h2>
104
+ <div class="flex flex-wrap gap-4 mb-4">
105
+ <div>
106
+ <label class="block font-semibold mb-1">Date Column</label>
107
+ <select id="forecastDateCol" class="border rounded p-2">
108
+ {% for column in columns %}
109
+ <option value="{{ column }}">{{ column }}</option>
110
+ {% endfor %}
111
+ </select>
112
+ </div>
113
+ <div>
114
+ <label class="block font-semibold mb-1">Target Column</label>
115
+ <select id="forecastTargetCol" class="border rounded p-2">
116
+ {% for column in columns %}
117
+ <option value="{{ column }}">{{ column }}</option>
118
+ {% endfor %}
119
+ </select>
120
+ </div>
121
+ <div>
122
+ <label class="block font-semibold mb-1">Model</label>
123
+ <select id="forecastModel" class="border rounded p-2">
124
+ <option value="ARIMA">ARIMA</option>
125
+ <option value="Prophet">Prophet</option>
126
+ <option value="Random Forest">Random Forest</option>
127
+ <option value="XGBoost">XGBoost</option>
128
+ <option value="LSTM">LSTM</option>
129
+ </select>
130
+ </div>
131
+ <div>
132
+ <label class="block font-semibold mb-1">Forecast Horizon</label>
133
+ <input id="forecastHorizon" type="number" value="30" min="1" max="365" class="border rounded p-2 w-20">
134
+ </div>
135
+ <div class="flex items-end">
136
+ <button onclick="runForecast()" class="btn-learn-more">Run Forecast</button>
137
+ </div>
138
+ </div>
139
+ <div id="forecast-metrics" class="mb-4"></div>
140
+ <div id="forecast-plot" class="w-full h-96"></div>
141
+ <div id="forecast-error" class="text-red-600 font-semibold mt-2"></div>
142
+ </div>
143
+ {% endif %}
144
+ </section>
145
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
146
+ <script src="{{ url_for('static', filename='index.js') }}"></script>
147
+ {% endblock %}
templates/supply_failure.html ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+ {% block content %}
3
+ <section class="py-8">
4
+ <h1 class="text-3xl font-bold mb-4">Supply Failure Prediction</h1>
5
+
6
+ <div class="bg-gray-50 p-6 rounded shadow mb-6">
7
+ <h2 class="text-2xl font-semibold mb-2">Upload Supply Chain Data (CSV)</h2>
8
+ <form action="{{ url_for('supply_failure.upload_file_supply') }}" method="POST" enctype="multipart/form-data">
9
+ <input type="file" name="supply_file" accept=".csv" class="mb-4 block">
10
+ <button type="submit" class="btn-learn-more">Upload</button>
11
+ </form>
12
+ </div>
13
+
14
+ {% if preview_data %}
15
+ <div class="bg-white p-6 rounded shadow mb-6">
16
+ <h2 class="text-2xl font-semibold mb-4">Data Preview (First 5 Rows)</h2>
17
+ <div class="overflow-x-auto">
18
+ <table id="preview-table" class="min-w-full table-auto">
19
+ <thead>
20
+ <tr>
21
+ {% for column in columns %}
22
+ <th class="px-4 py-2 bg-gray-100">{{ column }}</th>
23
+ {% endfor %}
24
+ </tr>
25
+ </thead>
26
+ <tbody>
27
+ {% for row in preview_data %}
28
+ <tr>
29
+ {% for column in columns %}
30
+ <td class="border px-4 py-2">{{ row[column] }}</td>
31
+ {% endfor %}
32
+ </tr>
33
+ {% endfor %}
34
+ </tbody>
35
+ </table>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="bg-white p-6 rounded shadow">
40
+ <h2 class="text-2xl font-semibold mb-4">Summary Statistics</h2>
41
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
42
+ <div class="p-4 bg-blue-50 rounded">
43
+ <p><strong>Total Rows:</strong> <span class="summary-total-rows">{{ summary_stats.total_rows }}</span></p>
44
+ <p><strong>Total Columns:</strong> <span class="summary-total-cols">{{ summary_stats.total_columns }}</span></p>
45
+ </div>
46
+ <div class="p-4 bg-green-50 rounded">
47
+ <p><strong>Numeric Columns:</strong> <span class="summary-numeric-cols">{{ summary_stats.numeric_columns|join(', ') }}</span></p>
48
+ <p><strong>Categorical Columns:</strong> <span class="summary-categorical-cols">{{ summary_stats.categorical_columns|join(', ') }}</span></p>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+ <div id="feature-importance" class="bg-white p-6 rounded shadow mt-6 hidden">
54
+ <h2 class="text-2xl font-semibold mb-4">Top 5 Feature Importances</h2>
55
+ <div id="feature-importance-list"></div>
56
+ </div>
57
+
58
+ <div class="bg-white p-6 rounded shadow mt-6">
59
+ <h2 class="text-2xl font-semibold mb-4">Supply Failure Prediction Metrics</h2>
60
+ <div class="flex flex-wrap gap-4 mb-4">
61
+ <div>
62
+ <label class="block font-semibold mb-1">Target Column</label>
63
+ <select id="predictTargetColSupply" class="border rounded p-2" disabled>
64
+ <option value="failure_flag">failure_flag</option>
65
+ </select>
66
+ </div>
67
+ <div>
68
+ <label class="block font-semibold mb-1">Model</label>
69
+ <select id="predictModelSupply" class="border rounded p-2">
70
+ <option value="Random Forest">Random Forest</option>
71
+ <option value="XGBoost">XGBoost</option>
72
+ <option value="Logistic Regression">Logistic Regression</option>
73
+ </select>
74
+ </div>
75
+ <div class="flex items-end">
76
+ <button onclick="runSupplyPrediction()" class="btn-learn-more">Run Prediction</button>
77
+ </div>
78
+ </div>
79
+ <div id="predict-metrics-supply" class="mb-4"></div>
80
+ <div id="predict-error-supply" class="text-red-600 font-semibold mt-2"></div>
81
+ </div>
82
+
83
+ <div id="single-prediction-section-supply" class="bg-white p-6 rounded shadow mt-6 hidden">
84
+ <h2 class="text-2xl font-semibold mb-4">Predict for a Single Supply Instance</h2>
85
+ <form id="single-prediction-form-supply" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4">
86
+ </form>
87
+ <button onclick="predictSupplySingleInstance()" class="btn-learn-more">Predict</button>
88
+ <div id="single-prediction-result-supply" class="mt-4 p-4 bg-blue-100 rounded hidden">
89
+ <p class="font-semibold text-lg">Prediction: <span id="prediction-output-supply" class="font-bold text-blue-800"></span></p>
90
+ <div id="probability-output-supply" class="mt-2 text-sm"></div>
91
+ </div>
92
+ <div id="single-prediction-error-supply" class="text-red-600 font-semibold mt-2"></div>
93
+ </div>
94
+ {% endif %}
95
+
96
+ </section>
97
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
98
+ <script src="{{ url_for('static', filename='index.js') }}"></script>
99
+ {% endblock %}
templates/underperformance.html ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+ {% block content %}
3
+ <section>
4
+ <h1 class="text-3xl font-bold mb-4">Underperformance & Upsell Detector</h1>
5
+ <p class="mb-6">Identify products and segments that are underperforming along with upsell opportunities using advanced analytics.</p>
6
+ <div class="bg-white p-6 rounded shadow">
7
+ <!-- Future dashboard view for underperformance -->
8
+ <p>[Underperformance analysis components here...]</p>
9
+ </div>
10
+ </section>
11
+ {% endblock %}