SanskrutiChopade commited on
Commit
443b256
·
1 Parent(s): 9fde0f6

Initial commit

Browse files
Files changed (5) hide show
  1. .gitignore +3 -0
  2. Dockerfile +13 -0
  3. app.py +456 -0
  4. requirements.txt +5 -0
  5. templates/index.html +837 -0
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .env
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+ COPY ./app.py /code/app.py
7
+ COPY ./templates /code/templates
8
+
9
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
10
+
11
+ EXPOSE 7860
12
+
13
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,456 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import time
2
+
3
+ from flask import Flask, render_template, request
4
+ import pandas as pd
5
+ import plotly.express as px
6
+ import io
7
+ import base64
8
+ import google.generativeai as genai
9
+ from collections import defaultdict
10
+ import numpy as np
11
+ import google.generativeai as genai
12
+
13
+ app = Flask(__name__)
14
+
15
+ # Configure Gemini API
16
+ genai.configure(api_key="AIzaSyDHqRXzvsVneTaeeszHOoSwBh4atyX7xKc")
17
+ model = genai.GenerativeModel("gemini-1.5-flash")
18
+
19
+ WEIGHTS = {
20
+ 'experience': 20,
21
+ 'degree': 20,
22
+ 'research': 20,
23
+ 'publication': 20,
24
+ 'skills': 20
25
+ }
26
+
27
+ # Ideal student-to-faculty ratio
28
+ IDEAL_RATIO = 20
29
+
30
+
31
+ def calculate_grades(row, weights):
32
+ """Function to calculate grades."""
33
+ experience_grade = normalize(row['Years_of_Experience'], 0, 35) * weights['experience']
34
+ degree_grade = (1 if row['Degree_Held'] in ['PhD', 'MPhil'] else 0.75) * weights['degree']
35
+ research_grade = normalize(row['Research_Count'], 0, 20) * weights['research']
36
+ publication_grade = normalize(row['Publications_Count'], 0, 50) * weights['publication']
37
+ skills_grade = normalize(len(str(row['Skills']).split(',')), 0, 10) * weights['skills']
38
+
39
+ total_grade = (experience_grade + degree_grade + research_grade + publication_grade + skills_grade) / sum(weights.values())
40
+
41
+ if row['Publications_Count'] > 30:
42
+ total_grade += 0.05
43
+ if row['Years_of_Experience'] < 2:
44
+ total_grade -= 0.05
45
+
46
+ return min(1.0, max(0.0, total_grade))
47
+
48
+
49
+ def normalize(value, min_value, max_value):
50
+ """Function to normalize values."""
51
+ return (value - min_value) / (max_value - min_value) if max_value - min_value != 0 else 0
52
+
53
+ def generate_with_retry(query, retries=3, delay=2):
54
+ for attempt in range(retries):
55
+ try:
56
+ gemini_response = model.generate_content(query)
57
+ return gemini_response.text
58
+ except Exception as e:
59
+ if "429" in str(e) and attempt < retries - 1:
60
+ time.sleep(delay * (2 ** attempt)) # Correctly use time.sleep
61
+ else:
62
+ raise e
63
+
64
+
65
+ def perform_swot_analysis(faculty_df, teaching_responses):
66
+ """Enhanced SWOT analysis based on faculty data and teaching responses."""
67
+
68
+ strengths = []
69
+ weaknesses = []
70
+ opportunities = []
71
+ threats = []
72
+
73
+ # Analyze faculty data
74
+ avg_experience = faculty_df['Years_of_Experience'].mean()
75
+ avg_publications = faculty_df['Publications_Count'].mean()
76
+ avg_research = faculty_df['Research_Count'].mean()
77
+ phd_count = len(faculty_df[faculty_df['Degree_Held'] == 'PhD'])
78
+ phd_percentage = (phd_count / len(faculty_df)) * 100
79
+
80
+ # Faculty Qualifications Analysis
81
+ if phd_percentage > 60:
82
+ strengths.append("High percentage of PhD holders ({}%)".format(round(phd_percentage)))
83
+ elif phd_percentage < 30:
84
+ weaknesses.append("Low percentage of PhD holders ({}%)".format(round(phd_percentage)))
85
+ opportunities.append("Encourage faculty to pursue higher education")
86
+
87
+ if avg_experience > 10:
88
+ strengths.append("Strong experienced faculty base (avg. {} years)".format(round(avg_experience)))
89
+ elif avg_experience < 5:
90
+ weaknesses.append("Relatively inexperienced faculty (avg. {} years)".format(round(avg_experience)))
91
+ opportunities.append("Implement mentorship programs")
92
+
93
+ # Research Output Analysis
94
+ if avg_publications > 20:
95
+ strengths.append("High research output through publications")
96
+ elif avg_publications < 10:
97
+ weaknesses.append("Low publication count")
98
+ opportunities.append("Create research incentives")
99
+
100
+ # Teaching Methodology Analysis
101
+ if teaching_responses:
102
+ # Course Design Analysis
103
+ course_revision = teaching_responses.get('course_revision', '')
104
+ if course_revision == 'Annually':
105
+ strengths.append("Regular curriculum updates")
106
+ elif course_revision == 'Rarely':
107
+ weaknesses.append("Infrequent curriculum revision")
108
+ threats.append("Risk of outdated curriculum")
109
+
110
+ # Technology Integration
111
+ tech_usage = teaching_responses.get('tech_usage', '')
112
+ if tech_usage == 'Yes':
113
+ strengths.append("Strong technology integration in teaching")
114
+ else:
115
+ weaknesses.append("Limited use of technology in teaching")
116
+ opportunities.append("Implement modern teaching technologies")
117
+
118
+ # Assessment Methods
119
+ assessment_methods = teaching_responses.get('assessment_methods', [])
120
+ if len(assessment_methods) >= 3:
121
+ strengths.append("Diverse assessment methods")
122
+ elif len(assessment_methods) < 2:
123
+ weaknesses.append("Limited assessment variety")
124
+ opportunities.append("Diversify assessment methods")
125
+
126
+ # Practical Learning
127
+ practical_percentage = int(teaching_responses.get('practical_percentage', 0))
128
+ if practical_percentage > 50:
129
+ strengths.append("Strong practical learning focus")
130
+ elif practical_percentage < 30:
131
+ weaknesses.append("Limited practical exposure")
132
+ opportunities.append("Increase hands-on learning activities")
133
+
134
+ # Student Engagement
135
+ student_participation = int(teaching_responses.get('student_participation', 0))
136
+ if student_participation > 75:
137
+ strengths.append("High student engagement")
138
+ elif student_participation < 50:
139
+ weaknesses.append("Low student participation")
140
+ opportunities.append("Implement engagement strategies")
141
+
142
+ # Teaching Methods
143
+ teaching_methods = teaching_responses.get('teaching_methods', [])
144
+ if len(teaching_methods) >= 3:
145
+ strengths.append("Diverse teaching methodologies")
146
+ elif len(teaching_methods) < 2:
147
+ weaknesses.append("Limited teaching methods")
148
+ opportunities.append("Expand teaching methodology")
149
+
150
+ # Professional Development
151
+ prof_dev = int(teaching_responses.get('professional_development', 0))
152
+ if prof_dev > 3:
153
+ strengths.append("Strong commitment to professional development")
154
+ elif prof_dev < 2:
155
+ weaknesses.append("Limited professional development")
156
+ opportunities.append("Increase faculty development programs")
157
+
158
+ # Industry Relevance
159
+ curriculum_relevance = int(teaching_responses.get('curriculum_relevance', 0))
160
+ if curriculum_relevance >= 8:
161
+ strengths.append("High industry relevance")
162
+ elif curriculum_relevance <= 5:
163
+ weaknesses.append("Low industry alignment")
164
+ threats.append("Risk of skill-industry mismatch")
165
+
166
+ # Add general threats
167
+ threats.extend([
168
+ "Rapid technological changes in education",
169
+ "Increasing competition from online education",
170
+ "Changing student learning preferences"
171
+ ])
172
+
173
+ # Add general opportunities
174
+ opportunities.extend([
175
+ "Integration of emerging technologies",
176
+ "Industry collaboration potential",
177
+ "International academic partnerships"
178
+ ])
179
+
180
+ return {
181
+ 'strengths': strengths,
182
+ 'weaknesses': weaknesses,
183
+ 'opportunities': opportunities,
184
+ 'threats': threats
185
+ }
186
+
187
+ @app.route('/', methods=['GET', 'POST'])
188
+ def index():
189
+ plots = {}
190
+ graded_csv = None
191
+ department_tables = {}
192
+ deficiency_table = None
193
+ departments = []
194
+ swot_results=None
195
+ teaching_responses=None
196
+ gemini_insights = {}
197
+
198
+ if request.method == 'POST':
199
+ if 'faculty_file' not in request.files:
200
+ return render_template('index.html', error="Error: Faculty file must be uploaded.")
201
+
202
+ faculty_file = request.files['faculty_file']
203
+
204
+ if faculty_file.filename == '':
205
+ return render_template('index.html', error="Error: Faculty file must be selected for upload.")
206
+
207
+ try:
208
+ # Load faculty data
209
+ faculty_df = pd.read_csv(faculty_file)
210
+
211
+ # Validate columns in faculty data
212
+ required_faculty_columns = {'Name', 'Department', 'Post', 'Years_of_Experience', 'Degree_Held',
213
+ 'Research_Count', 'Publications_Count', 'Skills'}
214
+ missing_faculty_columns = required_faculty_columns - set(faculty_df.columns)
215
+ if missing_faculty_columns:
216
+ return render_template('index.html',
217
+ error=f"Error: The faculty CSV is missing the following columns: {', '.join(missing_faculty_columns)}")
218
+
219
+ # Calculate grades
220
+ faculty_df['Grade'] = faculty_df.apply(lambda row: calculate_grades(row, WEIGHTS), axis=1)
221
+
222
+ # Get student counts from the form
223
+ student_counts = {department: int(request.form.get(f'students_{department}', 0) or 0) for department in faculty_df['Department'].unique()}
224
+
225
+
226
+ # Separate tables for each department
227
+ for department in faculty_df['Department'].unique():
228
+ department_data = faculty_df[faculty_df['Department'] == department]
229
+ department_tables[department] = {
230
+ 'columns': department_data.columns.tolist(),
231
+ 'rows': department_data.values.tolist()
232
+ }
233
+ departments.append(department)
234
+
235
+ graph_data = [
236
+ {
237
+ "title": "Count of Faculty by Department",
238
+ "data": faculty_df['Department'].value_counts().reset_index(name='count'),
239
+ "graph": lambda df: px.bar(df, x='Department', y='count', title="Count of Faculty by Department",
240
+ labels={'Department': 'Department', 'count': 'Count'}),
241
+ "query": "Provide insights into the distribution of faculty across departments based on this data."
242
+ },
243
+ {
244
+ "title": "Students vs Faculty by Department",
245
+ "data": pd.DataFrame({
246
+ "Department": faculty_df['Department'].unique(),
247
+ "Number_of_Students": [int(request.form.get(f'students_{dep}', 0)) for dep in
248
+ faculty_df['Department'].unique()],
249
+ "Number_of_Faculty": faculty_df['Department'].value_counts().values
250
+ }),
251
+ "graph": lambda df: px.bar(df, x='Department', y=['Number_of_Students', 'Number_of_Faculty'],
252
+ barmode='group',
253
+ title="Students vs Faculty by Department"),
254
+ "query": "Analyze the relationship between the number of students and faculty by department based on this data."
255
+ },
256
+ {
257
+ "title": "Post vs Skills",
258
+ "data": faculty_df[['Post', 'Skills']].assign(
259
+ Skills_Count=lambda x: x['Skills'].apply(lambda y: len(str(y).split(',')))),
260
+ "graph": lambda df: px.scatter(df, x='Post', y='Skills_Count', title="Post vs Skills"),
261
+ "query": "Explain the relationship between Post and Skills based on this data."
262
+ },
263
+ {
264
+ "title": "Degree vs Publications",
265
+ "data": faculty_df[['Degree_Held', 'Publications_Count']],
266
+ "graph": lambda df: px.box(df, x='Degree_Held', y='Publications_Count',
267
+ title="Degree vs Publications", color='Degree_Held'),
268
+ "query": "Describe the distribution of publications by degree based on this data."
269
+ },
270
+ {
271
+ "title": "Department-wise Faculty Count by Degree",
272
+ "data": faculty_df.groupby(['Department', 'Degree_Held']).size().reset_index(name='Count'),
273
+ "graph": lambda df: px.bar(df, x='Department', y='Count', color='Degree_Held', barmode='group',
274
+ title="Department-wise Faculty Count by Degree"),
275
+ "query": "What can we infer about the qualifications of faculty across departments from this data?"
276
+ },
277
+ {
278
+ "title": "Experience Distribution by Degree",
279
+ "data": faculty_df[['Degree_Held', 'Years_of_Experience']],
280
+ "graph": lambda df: px.violin(df, x='Degree_Held', y='Years_of_Experience',
281
+ title="Experience Distribution by Degree",
282
+ color='Degree_Held'),
283
+ "query": "Analyze the distribution of experience across different degree levels using this data."
284
+ },
285
+ {
286
+ "title": "Research Count by Department",
287
+ "data": faculty_df.groupby('Department')['Research_Count'].sum().reset_index(),
288
+ "graph": lambda df: px.bar(df, x='Department', y='Research_Count',
289
+ title="Research Count by Department"),
290
+ "query": "What insights can be drawn about the research output of each department based on this data?"
291
+ },
292
+ {
293
+ "title": "Publications Count by Department",
294
+ "data": faculty_df.groupby('Department')['Publications_Count'].sum().reset_index(),
295
+ "graph": lambda df: px.bar(df, x='Department', y='Publications_Count',
296
+ title="Publications Count by Department"),
297
+ "query": "Describe the publication trends across departments using this data."
298
+ },
299
+ {
300
+ "title": "Skills Count by Department",
301
+ "data": faculty_df.groupby('Department').apply(
302
+ lambda x: x['Skills'].apply(lambda y: len(str(y).split(','))).sum()
303
+ ).reset_index(name='Skills_Count'),
304
+ "graph": lambda df: px.bar(df, x='Department', y='Skills_Count',
305
+ title="Skills Count by Department"),
306
+ "query": "Explain the distribution of skills among faculty across different departments based on this data."
307
+ },
308
+ {
309
+ "title": "Grades Distribution",
310
+ "data": faculty_df[['Department', 'Grade']],
311
+ "graph": lambda df: px.box(df, x='Department', y='Grade', title="Grades Distribution by Department",
312
+ color='Department'),
313
+ "query": "What insights can we infer from the grades distribution of faculty across departments?"
314
+ },
315
+ {
316
+ "title": "Experience vs Publications",
317
+ "data": faculty_df[['Years_of_Experience', 'Publications_Count']],
318
+ "graph": lambda df: px.scatter(df, x='Years_of_Experience', y='Publications_Count',
319
+ title="Experience vs Publications",
320
+ labels={'Years_of_Experience': 'Years of Experience',
321
+ 'Publications_Count': 'Publications Count'}),
322
+ "query": "Analyze the relationship between years of experience and the number of publications based on this data."
323
+ },
324
+ {
325
+ "title": "Top Departments by Research",
326
+ "data": faculty_df.groupby('Department')['Research_Count'].sum().reset_index().sort_values(
327
+ by='Research_Count', ascending=False).head(5),
328
+ "graph": lambda df: px.bar(df, x='Department', y='Research_Count',
329
+ title="Top 5 Departments by Research Output"),
330
+ "query": "Identify the top departments by research output and analyze their characteristics."
331
+ }
332
+ ]
333
+
334
+ for graph in graph_data:
335
+ # Generate the graph
336
+ graph_df = graph["data"]
337
+ fig = graph["graph"](graph_df)
338
+ plot_html = fig.to_html(full_html=False)
339
+ plots[graph["title"]] = plot_html
340
+
341
+ # Prepare the query for Gemini
342
+ query = (
343
+ f"{graph['query']}\n\n"
344
+ "Data:\n"
345
+ f"{graph_df.to_csv(index=False)}\n\n"
346
+ "Provide a concise summary in 100 words, formatted without special characters like '*'. "
347
+ "Use proper sentences and highlight key points using **bold text**."
348
+ )
349
+
350
+ # Use retry logic for Gemini API
351
+ try:
352
+ gemini_response = generate_with_retry(query)
353
+ raw_text = gemini_response.replace('*', '').strip()
354
+
355
+ # Truncate if necessary
356
+ if len(raw_text) > 150:
357
+ raw_text = raw_text[:147].rsplit(' ', 1)[0] + "..."
358
+ gemini_insights[graph["title"]] = raw_text
359
+
360
+ except Exception as e:
361
+ gemini_insights[graph["title"]] = f"Error generating insight: {str(e)}"
362
+
363
+ # Move all faculty_counts calculations inside the try block
364
+ faculty_counts = faculty_df['Department'].value_counts().reset_index()
365
+ faculty_counts.columns = ['Department', 'Number_of_Faculty']
366
+
367
+ # Map number of students
368
+ faculty_counts['Number_of_Students'] = faculty_counts['Department'].map(student_counts)
369
+
370
+ # Calculate ideal numbers based on S (students), R=9 (1+2+6)
371
+ faculty_counts['Total_Ideal_Faculty'] = (faculty_counts['Number_of_Students'] / IDEAL_RATIO).apply(
372
+ lambda x: int(x) if x.is_integer() else int(x) + 1
373
+ )
374
+
375
+ # Role-specific ideal faculty counts
376
+ faculty_counts['Ideal_Principal'] = 1 # Always 1
377
+ faculty_counts['Ideal_Professor'] = (faculty_counts['Number_of_Students'] * 1 / (20 * 9)).apply(
378
+ lambda x: int(x) if x.is_integer() else int(x) + 1
379
+ )
380
+ faculty_counts['Ideal_Associate_Professor'] = (faculty_counts['Number_of_Students'] * 2 / (20 * 9)).apply(
381
+ lambda x: int(x) if x.is_integer() else int(x) + 1
382
+ )
383
+ faculty_counts['Ideal_Assistant_Professor'] = (faculty_counts['Number_of_Students'] * 6 / (20 * 9)).apply(
384
+ lambda x: int(x) if x.is_integer() else int(x) + 1
385
+ )
386
+
387
+ # Calculate deficiencies for each role
388
+ faculty_counts['Deficiency_Principal'] = faculty_counts['Ideal_Principal'] - faculty_df[
389
+ faculty_df['Post'] == 'Principal'].groupby('Department')['Post'].count().reindex(faculty_counts['Department']).fillna(0).astype(int)
390
+ faculty_counts['Deficiency_Professor'] = faculty_counts['Ideal_Professor'] - faculty_df[
391
+ faculty_df['Post'] == 'Professor'].groupby('Department')['Post'].count().reindex(faculty_counts['Department']).fillna(0).astype(int)
392
+ faculty_counts['Deficiency_Associate_Professor'] = faculty_counts['Ideal_Associate_Professor'] - faculty_df[
393
+ faculty_df['Post'] == 'Associate Professor'].groupby('Department')['Post'].count().reindex(faculty_counts['Department']).fillna(0).astype(int)
394
+ faculty_counts['Deficiency_Assistant_Professor'] = faculty_counts['Ideal_Assistant_Professor'] - faculty_df[
395
+ faculty_df['Post'] == 'Assistant Professor'].groupby('Department')['Post'].count().reindex(faculty_counts['Department']).fillna(0).astype(int)
396
+
397
+ # Overall deficiency
398
+ faculty_counts['Meets_Ratio'] = (faculty_counts['Deficiency_Principal'] <= 0) & \
399
+ (faculty_counts['Deficiency_Professor'] <= 0) & \
400
+ (faculty_counts['Deficiency_Associate_Professor'] <= 0) & \
401
+ (faculty_counts['Deficiency_Assistant_Professor'] <= 0)
402
+
403
+ faculty_counts['Meets_Ratio'] = faculty_counts['Meets_Ratio'].apply(lambda x: "✔️" if x else "❌")
404
+
405
+ # Prepare the final deficiency table
406
+ deficiency_table = faculty_counts[[
407
+ 'Department', 'Number_of_Students', 'Number_of_Faculty',
408
+ 'Ideal_Principal', 'Ideal_Professor', 'Ideal_Associate_Professor', 'Ideal_Assistant_Professor',
409
+ 'Deficiency_Principal', 'Deficiency_Professor', 'Deficiency_Associate_Professor', 'Deficiency_Assistant_Professor',
410
+ 'Meets_Ratio']].to_html(classes="table table-bordered table-hover", index=False, escape=False)
411
+
412
+ # Encode graded CSV
413
+ csv_output = io.BytesIO()
414
+ faculty_df.to_csv(csv_output, index=False)
415
+ csv_output.seek(0)
416
+ graded_csv = base64.b64encode(csv_output.getvalue()).decode()
417
+
418
+ # Collect teaching evaluation responses
419
+ teaching_responses = {
420
+ 'course_revision': request.form.get('course_revision', ''),
421
+ 'case_studies': request.form.get('case_studies', ''),
422
+ 'assessment_methods': request.form.getlist('assessment_methods') or [],
423
+ 'practical_percentage': int(request.form.get('practical_percentage', 0) or 0),
424
+ 'curriculum_relevance': int(request.form.get('curriculum_relevance', 0) or 0),
425
+ 'interactive_sessions': request.form.get('interactive_sessions', ''),
426
+ 'student_participation': int(request.form.get('student_participation', 0) or 0),
427
+ 'personalized_feedback': request.form.get('personalized_feedback', ''),
428
+ 'student_interest': request.form.get('student_interest', ''),
429
+ 'tech_usage': request.form.get('tech_usage', ''),
430
+ 'teaching_methods': request.form.getlist('teaching_methods') or [],
431
+ 'critical_thinking': request.form.get('critical_thinking', ''),
432
+ 'student_feedback': request.form.get('student_feedback', ''),
433
+ 'feedback_actions': request.form.get('feedback_actions', ''),
434
+ 'professional_development': int(request.form.get('professional_development', 0) or 0)
435
+ }
436
+
437
+ # Perform SWOT analysis
438
+ swot_results = perform_swot_analysis(faculty_df, teaching_responses)
439
+
440
+ except Exception as e:
441
+ return render_template('index.html', error=f"An unexpected error occurred: {str(e)}")
442
+
443
+ return render_template('index.html',
444
+ plots=plots,
445
+ graded_csv=graded_csv,
446
+ department_tables=department_tables,
447
+ departments=departments,
448
+ deficiency_table=deficiency_table,
449
+ gemini_insights=gemini_insights,
450
+ swot_results=swot_results,
451
+ teaching_responses=teaching_responses)
452
+
453
+
454
+ if __name__ == '__main__':
455
+ app.run(host='0.0.0.0', port=7860)
456
+
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Flask==2.2.5
2
+ pandas==1.5.3
3
+ plotly==5.15.0
4
+ numpy==1.23.5
5
+ google-generativeai==0.4.0
templates/index.html ADDED
@@ -0,0 +1,837 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Faculty Data Analysis</title>
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
9
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
13
+ <style>
14
+ /* Modern and Enhanced Styles */
15
+ body {
16
+ font-family: 'Inter', sans-serif;
17
+ background: linear-gradient(135deg, #18579693 0%, #0c5db9 100%); /* Light professional gradient */
18
+ color: #415880;
19
+ line-height: 1.6;
20
+ }
21
+
22
+ /* Add this for a subtle pattern overlay */
23
+ body::before {
24
+ content: '';
25
+ position: fixed;
26
+ top: 0;
27
+ left: 0;
28
+ width: 100%;
29
+ height: 100%;
30
+ background-image:
31
+ linear-gradient(45deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%),
32
+ linear-gradient(-45deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%),
33
+ linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.1) 75%),
34
+ linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.1) 75%);
35
+ background-size: 20px 20px;
36
+ z-index: -1;
37
+ }
38
+
39
+ /* Update card and form backgrounds for better contrast */
40
+ .card, form {
41
+ background: rgba(255, 255, 255, 0.95);
42
+ backdrop-filter: blur(10px);
43
+ }
44
+
45
+ /* Update header colors for better visibility */
46
+ h1 {
47
+ background: linear-gradient(120deg, #2c5282, #2b6cb0);
48
+ -webkit-background-clip: text;
49
+ -webkit-text-fill-color: transparent;
50
+ }
51
+
52
+ h2 {
53
+ color: #2d3748;
54
+ }
55
+
56
+ /* Update accordion headers */
57
+ .accordion-button:not(.collapsed) {
58
+ background: linear-gradient(135deg, #2c5282 0%, #2b6cb0 100%);
59
+ color: white;
60
+ }
61
+
62
+ /* Update card headers */
63
+ .card-header {
64
+ background: linear-gradient(135deg, #2c5282 0%, #2b6cb0 100%);
65
+ color: white;
66
+ }
67
+
68
+ .container {
69
+ max-width: 1400px;
70
+ margin: 40px auto;
71
+ padding: 0 30px;
72
+ }
73
+
74
+ h1 {
75
+ font-size: 3rem;
76
+ font-weight: 700;
77
+ background: #1e3c6d;
78
+ -webkit-background-clip: text;
79
+ -webkit-text-fill-color: transparent;
80
+ text-align: center;
81
+ margin-bottom: 2rem;
82
+ animation: fadeIn 1s ease-in;
83
+ }
84
+
85
+ h2 {
86
+ font-size: 2.2rem;
87
+ color: #1a365d;
88
+ font-weight: 600;
89
+ margin: 2rem 0;
90
+ text-align: center;
91
+ }
92
+
93
+ /* Enhanced Form Styles */
94
+ form {
95
+ background: rgba(255, 255, 255, 0.9);
96
+ backdrop-filter: blur(10px);
97
+ padding: 2.5rem;
98
+ border-radius: 20px;
99
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
100
+ margin-bottom: 3rem;
101
+ transition: all 0.3s ease;
102
+ }
103
+
104
+ form:hover {
105
+ transform: translateY(-5px);
106
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
107
+ border-radius: 25px solid #1e3c6d;
108
+ }
109
+
110
+ .form-label {
111
+ font-weight: 600;
112
+ color: #4a5568;
113
+ margin-bottom: 0.75rem;
114
+ font-size: 1.1rem;
115
+ }
116
+
117
+ .form-control {
118
+ border: 2px solid #e2e8f0;
119
+ border-radius: 12px;
120
+ padding: 1rem;
121
+ transition: all 0.3s ease;
122
+ }
123
+
124
+ .form-control:focus {
125
+ border-color: #3182ce;
126
+ box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.2);
127
+ }
128
+
129
+ /* Modern Button Styles */
130
+ .btn {
131
+ padding: 1rem 2rem;
132
+ border-radius: 12px;
133
+ font-weight: 600;
134
+ letter-spacing: 0.5px;
135
+ transition: all 0.3s ease;
136
+ }
137
+
138
+ .btn-primary {
139
+ background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
140
+ border: none;
141
+ box-shadow: 0 4px 15px rgba(37, 99, 235, 0.2);
142
+ }
143
+
144
+ .btn-primary:hover {
145
+ transform: translateY(-2px);
146
+ box-shadow: 0 6px 20px rgba(37, 99, 235, 0.3);
147
+ background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
148
+ }
149
+
150
+ /* Enhanced Table Styles */
151
+ .table-container {
152
+ background: white;
153
+ border-radius: 20px;
154
+ box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1);
155
+ overflow: hidden;
156
+ margin-bottom: 2rem;
157
+ }
158
+
159
+ .table {
160
+ margin-bottom: 0;
161
+ }
162
+
163
+ .table thead th {
164
+ background: linear-gradient(135deg, #063292 0%, #3b82f6 100%);
165
+ color: white;
166
+ font-weight: 600;
167
+ text-transform: uppercase;
168
+ font-size: 0.9rem;
169
+ letter-spacing: 1px;
170
+ padding: 1.2rem solid #063292;
171
+ border: none;
172
+ }
173
+
174
+ .table tbody tr {
175
+ transition: all 0.2s ease;
176
+ }
177
+
178
+ .table tbody tr:hover {
179
+ background-color: #f8fafc;
180
+ transform: scale(1.01);
181
+ }
182
+
183
+ /* Card Styles for Insights */
184
+ .card {
185
+ border: none;
186
+ border-radius: 20px;
187
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
188
+ transition: all 0.3s ease;
189
+ overflow: hidden;
190
+ }
191
+
192
+ .card:hover {
193
+ transform: translateY(-5px);
194
+ box-shadow: 0 15px 35px rgba(0, 0, 0, 0.12);
195
+ }
196
+
197
+ .card-header {
198
+ background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
199
+ color: white;
200
+ font-weight: 600;
201
+ padding: 1.2rem;
202
+ border: none;
203
+ }
204
+
205
+ .card-body {
206
+ padding: 1.5rem;
207
+ }
208
+
209
+ /* Accordion Styles */
210
+ .accordion-item {
211
+ border: none;
212
+ margin-bottom: 1rem;
213
+ border-radius: 15px;
214
+ overflow: hidden;
215
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
216
+ }
217
+
218
+ .accordion-button {
219
+ padding: 1.2rem;
220
+ font-weight: 600;
221
+ background: white;
222
+ }
223
+
224
+ .accordion-button:not(.collapsed) {
225
+ background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
226
+ color: white;
227
+ }
228
+
229
+ /* Animation Classes */
230
+ .fade-in {
231
+ animation: fadeIn 1s ease-in;
232
+ }
233
+
234
+ .slide-up {
235
+ animation: slideInUp 0.5s ease-out;
236
+ }
237
+
238
+ /* Responsive Design */
239
+ @media (max-width: 768px) {
240
+ .container {
241
+ padding: 0 15px;
242
+ }
243
+
244
+ h1 {
245
+ font-size: 2.2rem;
246
+ }
247
+
248
+ h2 {
249
+ font-size: 1.8rem;
250
+ }
251
+
252
+ form {
253
+ padding: 1.5rem;
254
+ }
255
+
256
+ .btn {
257
+ padding: 0.8rem 1.5rem;
258
+ }
259
+ }
260
+
261
+ /* Custom Scrollbar */
262
+ ::-webkit-scrollbar {
263
+ width: 10px;
264
+ }
265
+
266
+ ::-webkit-scrollbar-track {
267
+ background: #f1f1f1;
268
+ }
269
+
270
+ ::-webkit-scrollbar-thumb {
271
+ background: #3b82f6;
272
+ border-radius: 5px;
273
+ }
274
+
275
+ ::-webkit-scrollbar-thumb:hover {
276
+ background: #2563eb;
277
+ }
278
+
279
+ /* Modern Variables */
280
+ :root {
281
+ --primary-color: #2563eb;
282
+ --secondary-color: #3b82f6;
283
+ --accent-color: #7c3aed;
284
+ --success-color: #10b981;
285
+ --warning-color: #f59e0b;
286
+ --danger-color: #ef4444;
287
+ --background-color: #f8fafc;
288
+ --card-bg: #ffffff;
289
+ --text-primary: #1e293b;
290
+ --text-secondary: #64748b;
291
+ --border-radius: 12px;
292
+ --transition: all 0.3s ease;
293
+ }
294
+
295
+ /* Enhanced Base Styles */
296
+ body {
297
+ font-family: 'Inter', sans-serif;
298
+ background: linear-gradient(135deg, var(--background-color) 0%, #e2e8f0 100%);
299
+ color: var(--text-primary);
300
+ line-height: 1.6;
301
+ }
302
+
303
+ .container {
304
+ max-width: 1400px;
305
+ margin: 40px auto;
306
+ padding: 0 30px;
307
+ }
308
+
309
+ /* Enhanced Form Elements */
310
+ .form-control, .form-select {
311
+ border: 2px solid #e2e8f0;
312
+ border-radius: var(--border-radius);
313
+ padding: 0.75rem 1rem;
314
+ transition: var(--transition);
315
+ background-color: rgba(255, 255, 255, 0.9);
316
+ }
317
+
318
+ .form-control:focus, .form-select:focus {
319
+ border-color: var(--primary-color);
320
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
321
+ transform: translateY(-1px);
322
+ }
323
+
324
+ /* Custom Range Slider */
325
+ .custom-range {
326
+ -webkit-appearance: none;
327
+ width: 100%;
328
+ height: 8px;
329
+ border-radius: 5px;
330
+ background: #e2e8f0;
331
+ outline: none;
332
+ margin: 15px 0;
333
+ }
334
+
335
+ .custom-range::-webkit-slider-thumb {
336
+ -webkit-appearance: none;
337
+ width: 24px;
338
+ height: 24px;
339
+ border-radius: 50%;
340
+ background: var(--primary-color);
341
+ cursor: pointer;
342
+ transition: var(--transition);
343
+ border: 2px solid white;
344
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
345
+ }
346
+
347
+ .custom-range::-webkit-slider-thumb:hover {
348
+ transform: scale(1.1);
349
+ }
350
+
351
+ /* Range Value Display */
352
+ .range-value {
353
+ text-align: center;
354
+ font-weight: 600;
355
+ color: var(--primary-color);
356
+ margin-top: 8px;
357
+ }
358
+
359
+ /* Checkbox Group */
360
+ .checkbox-group {
361
+ display: grid;
362
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
363
+ gap: 1rem;
364
+ margin-top: 0.5rem;
365
+ }
366
+
367
+ .form-check {
368
+ padding: 0.5rem;
369
+ border-radius: var(--border-radius);
370
+ transition: var(--transition);
371
+ }
372
+
373
+ .form-check:hover {
374
+ background: rgba(37, 99, 235, 0.05);
375
+ }
376
+
377
+ .form-check-input {
378
+ width: 1.2em;
379
+ height: 1.2em;
380
+ margin-top: 0.2em;
381
+ cursor: pointer;
382
+ }
383
+
384
+ .form-check-input:checked {
385
+ background-color: var(--primary-color);
386
+ border-color: var(--primary-color);
387
+ }
388
+
389
+ /* Card Enhancements */
390
+ .card {
391
+ border: none;
392
+ border-radius: var(--border-radius);
393
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
394
+ transition: var(--transition);
395
+ overflow: hidden;
396
+ background: rgba(255, 255, 255, 0.95);
397
+ backdrop-filter: blur(10px);
398
+ }
399
+
400
+ .card:hover {
401
+ transform: translateY(-5px);
402
+ box-shadow: 0 15px 35px rgba(0, 0, 0, 0.12);
403
+ }
404
+
405
+ /* Section Headers */
406
+ h4 {
407
+ color: var(--text-primary);
408
+ font-weight: 600;
409
+ margin-bottom: 1.5rem;
410
+ padding-bottom: 0.5rem;
411
+ border-bottom: 2px solid var(--primary-color);
412
+ display: inline-block;
413
+ }
414
+
415
+ /* Textarea Enhancement */
416
+ textarea.form-control {
417
+ resize: vertical;
418
+ min-height: 100px;
419
+ }
420
+
421
+ /* Animation Classes */
422
+ .fade-in {
423
+ animation: fadeIn 0.5s ease-in;
424
+ }
425
+
426
+ @keyframes fadeIn {
427
+ from { opacity: 0; transform: translateY(10px); }
428
+ to { opacity: 1; transform: translateY(0); }
429
+ }
430
+
431
+ /* Responsive Design */
432
+ @media (max-width: 768px) {
433
+ .checkbox-group {
434
+ grid-template-columns: 1fr;
435
+ }
436
+
437
+ .container {
438
+ padding: 0 15px;
439
+ }
440
+
441
+ .card {
442
+ margin: 1rem 0;
443
+ }
444
+ }
445
+
446
+ /* Loading State */
447
+ .loading {
448
+ position: relative;
449
+ opacity: 0.8;
450
+ pointer-events: none;
451
+ }
452
+
453
+ .loading::after {
454
+ content: "";
455
+ position: absolute;
456
+ top: 50%;
457
+ left: 50%;
458
+ width: 2rem;
459
+ height: 2rem;
460
+ border: 3px solid #f3f3f3;
461
+ border-top: 3px solid var(--primary-color);
462
+ border-radius: 50%;
463
+ animation: spin 1s linear infinite;
464
+ transform: translate(-50%, -50%);
465
+ }
466
+
467
+ @keyframes spin {
468
+ 0% { transform: translate(-50%, -50%) rotate(0deg); }
469
+ 100% { transform: translate(-50%, -50%) rotate(360deg); }
470
+ }
471
+
472
+ /* Print Button Styling */
473
+ .print-btn {
474
+ position: fixed;
475
+ bottom: 30px;
476
+ right: 30px;
477
+ z-index: 1000;
478
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
479
+ color: white;
480
+ border: none;
481
+ padding: 12px 24px;
482
+ border-radius: 50px;
483
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
484
+ transition: all 0.3s ease;
485
+ }
486
+
487
+ .print-btn:hover {
488
+ transform: translateY(-2px);
489
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
490
+ background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
491
+ color: white;
492
+ }
493
+
494
+ /* Print Media Styles */
495
+ @media print {
496
+ .print-btn {
497
+ display: none;
498
+ }
499
+
500
+ /* Hide elements not needed in print */
501
+ .btn-primary,
502
+ .form-control,
503
+ input[type="file"],
504
+ .accordion-button {
505
+ display: none !important;
506
+ }
507
+
508
+ /* Ensure all content is visible */
509
+ .accordion-collapse {
510
+ display: block !important;
511
+ }
512
+
513
+ /* Better page breaks */
514
+ .card,
515
+ .table-responsive,
516
+ .accordion-item {
517
+ page-break-inside: avoid;
518
+ }
519
+
520
+ /* Adjust colors for better printing */
521
+ body {
522
+ background: white !important;
523
+ color: black !important;
524
+ }
525
+
526
+ /* Ensure text contrast */
527
+ h1, h2, h3, h4 {
528
+ color: black !important;
529
+ -webkit-text-fill-color: black !important;
530
+ }
531
+
532
+ /* Adjust table styling for print */
533
+ .table {
534
+ border: 1px solid #ddd !important;
535
+ }
536
+
537
+ .table th,
538
+ .table td {
539
+ border: 1px solid #ddd !important;
540
+ }
541
+
542
+ /* Ensure graphs are properly sized */
543
+ .plotly-graph-div {
544
+ width: 100% !important;
545
+ height: auto !important;
546
+ }
547
+
548
+ /* Add page numbers */
549
+ @page {
550
+ margin: 2cm;
551
+ }
552
+
553
+ body::after {
554
+ content: counter(page);
555
+ counter-increment: page;
556
+ position: fixed;
557
+ bottom: 0;
558
+ right: 0;
559
+ font-size: 12px;
560
+ }
561
+ }
562
+
563
+ /* Add this to your existing styles */
564
+ .fa-sync-alt {
565
+ transition: transform 0.3s ease;
566
+ }
567
+
568
+ .fa-sync-alt:hover {
569
+ transform: rotate(180deg);
570
+ }
571
+
572
+ .fa-chart-bar, .fa-chart-line, .fa-chart-pie {
573
+ transition: transform 0.3s ease;
574
+ }
575
+
576
+ .fa-chart-bar:hover, .fa-chart-line:hover, .fa-chart-pie:hover {
577
+ transform: scale(1.2);
578
+ }
579
+
580
+ .fa-download {
581
+ animation: bounce 1s infinite;
582
+ }
583
+
584
+ @keyframes bounce {
585
+ 0%, 100% { transform: translateY(0); }
586
+ 50% { transform: translateY(-3px); }
587
+ }
588
+
589
+ /* Enhanced button hover effects with icons */
590
+ .btn:hover i {
591
+ transform: scale(1.1);
592
+ }
593
+
594
+ /* Icon spacing */
595
+ .me-2 {
596
+ margin-right: 0.5rem;
597
+ }
598
+ </style>
599
+ </head>
600
+
601
+ <body>
602
+ <div class="container mt-5">
603
+ <h1 class="text-center text-primary mb-4">
604
+ <i class="fas fa-university me-2"></i> Faculty Data Analysis
605
+ </h1>
606
+
607
+ <!-- File Upload Form -->
608
+ <form method="POST" enctype="multipart/form-data" class="mb-5">
609
+ <div class="mb-3">
610
+ <label for="faculty_file" class="form-label">
611
+ <i class="fas fa-file-upload me-2"></i>Upload Faculty Data CSV
612
+ </label>
613
+ <input type="file" id="faculty_file" name="faculty_file" class="form-control" accept=".csv" required>
614
+ </div>
615
+
616
+ <!-- Input Fields for Student Counts -->
617
+ {% if departments %}
618
+ <h3 class="mt-4">
619
+ <i class="fas fa-users me-2"></i>Enter Student Counts by Department
620
+ </h3>
621
+ {% for department in departments %}
622
+ <div class="mb-3">
623
+ <label for="students_{{ department }}" class="form-label">{{ department }}</label>
624
+ <input type="number" id="students_{{ department }}" name="students_{{ department }}" class="form-control" min="0" required>
625
+ </div>
626
+ {% endfor %}
627
+ {% endif %}
628
+
629
+ <button type="submit" class="btn btn-primary w-100">
630
+ <i class="fas fa-chart-bar me-2"></i>Analyze
631
+ </button>
632
+ </form>
633
+
634
+ <!-- Error Message -->
635
+ {% if error %}
636
+ <div class="alert alert-danger text-center">
637
+ {{ error }}
638
+ </div>
639
+ {% endif %}
640
+
641
+ <!-- Download Graded CSV -->
642
+ {% if graded_csv %}
643
+ <div class="text-end mb-4">
644
+ <a href="data:text/csv;base64,{{ graded_csv }}" download="graded_faculty_data.csv" class="btn btn-success">
645
+ <i class="fas fa-download me-2"></i>Download Graded CSV
646
+ </a>
647
+ </div>
648
+ {% endif %}
649
+
650
+ {% if plots %}
651
+ <h2 class="text-center text-secondary">
652
+ <i class="fas fa-chart-line me-2"></i>Visualizations with Insights
653
+ </h2>
654
+ <div class="accordion" id="plotsAccordion">
655
+ {% for plot_title, plot_html in plots.items() %}
656
+ <div class="accordion-item">
657
+ <h2 class="accordion-header" id="heading-{{ loop.index }}">
658
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
659
+ data-bs-target="#collapse-{{ loop.index }}" aria-expanded="false" aria-controls="collapse-{{ loop.index }}">
660
+ {{ plot_title }}
661
+ </button>
662
+ </h2>
663
+ <div id="collapse-{{ loop.index }}" class="accordion-collapse collapse"
664
+ aria-labelledby="heading-{{ loop.index }}" data-bs-parent="#plotsAccordion">
665
+ <div class="accordion-body">
666
+ <div class="row">
667
+ <!-- Graph Section -->
668
+ <div class="col-md-7">
669
+ <div>{{ plot_html | safe }}</div>
670
+ </div>
671
+ <!-- Gemini Insight Section -->
672
+ <div class="col-md-5">
673
+ <div class="card h-100">
674
+ <div class="card-header bg-info text-white">
675
+ <strong>Insights for {{ plot_title }}</strong>
676
+ </div>
677
+ <div class="card-body">
678
+ <p>{{ gemini_insights[plot_title] }}</p>
679
+ </div>
680
+ </div>
681
+ </div>
682
+ </div>
683
+ </div>
684
+ </div>
685
+ </div>
686
+ {% endfor %}
687
+ </div>
688
+ {% endif %}
689
+
690
+
691
+ <!-- Department Tables Section -->
692
+ {% if department_tables %}
693
+ <h2 class="text-center text-warning mt-5">Faculty Data by Department</h2>
694
+ {% for department, table in department_tables.items() %}
695
+ <div class="mb-4">
696
+ <h3 class="text-center text-secondary">{{ department }}</h3>
697
+ <div class="table-responsive">
698
+ <table class="table table-striped table-bordered">
699
+ <thead>
700
+ <tr>
701
+ {% for col in table.columns %}
702
+ <th>{{ col }}</th>
703
+ {% endfor %}
704
+ </tr>
705
+ </thead>
706
+ <tbody>
707
+ {% for row in table.rows %}
708
+ <tr>
709
+ {% for cell in row %}
710
+ <td>{{ cell }}</td>
711
+ {% endfor %}
712
+ </tr>
713
+ {% endfor %}
714
+ </tbody>
715
+ </table>
716
+ </div>
717
+ </div>
718
+ {% endfor %}
719
+ {% endif %}
720
+
721
+ <!-- Deficiency Table Section -->
722
+ {% if deficiency_table %}
723
+ <h2 class="text-center text-danger mt-5">Deficiency Comparison: Faculty vs Students</h2>
724
+ <div class="table-responsive">
725
+ {{ deficiency_table | safe }}
726
+ </div>
727
+ {% endif %}
728
+
729
+ <!-- SWOT Analysis Results -->
730
+ {% if swot_results %}
731
+ <div class="card mt-5">
732
+ <div class="card-header bg-info text-white">
733
+ <h3 class="mb-0"><i class="fas fa-chart-pie me-2"></i>SWOT Analysis Results</h3>
734
+ </div>
735
+ <div class="card-body">
736
+ <div class="row">
737
+ <div class="col-md-6">
738
+ <h4 class="text-success"><i class="fas fa-star me-2"></i>Strengths</h4>
739
+ <ul>
740
+ {% for strength in swot_results.strengths %}
741
+ <li>{{ strength }}</li>
742
+ {% endfor %}
743
+ </ul>
744
+ </div>
745
+ <div class="col-md-6">
746
+ <h4 class="text-danger"><i class="fas fa-exclamation-triangle me-2"></i>Weaknesses</h4>
747
+ <ul>
748
+ {% for weakness in swot_results.weaknesses %}
749
+ <li>{{ weakness }}</li>
750
+ {% endfor %}
751
+ </ul>
752
+ </div>
753
+ </div>
754
+ <div class="row">
755
+ <div class="col-md-6">
756
+ <h4 class="text-primary"><i class="fas fa-lightbulb me-2"></i>Opportunities</h4>
757
+ <ul>
758
+ {% for opportunity in swot_results.opportunities %}
759
+ <li>{{ opportunity }}</li>
760
+ {% endfor %}
761
+ </ul>
762
+ </div>
763
+ <div class="col-md-6">
764
+ <h4 class="text-warning"><i class="fas fa-shield-alt me-2"></i>Threats</h4>
765
+ <ul>
766
+ {% for threat in swot_results.threats %}
767
+ <li>{{ threat }}</li>
768
+ {% endfor %}
769
+ </ul>
770
+ </div>
771
+ </div>
772
+ </div>
773
+ </div>
774
+ {% endif %}
775
+
776
+ <button class="btn btn-secondary print-btn" onclick="window.print()">
777
+ <i class="fas fa-print"></i> Get Report
778
+ </button>
779
+ <!-- Scripts -->
780
+ <script>
781
+ // Initialize Bootstrap Popovers
782
+ document.addEventListener('DOMContentLoaded', function () {
783
+ var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
784
+ var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
785
+ return new bootstrap.Popover(popoverTriggerEl, {
786
+ html: true,
787
+ container: 'body'
788
+ });
789
+ });
790
+ });
791
+
792
+ // Range slider value display
793
+ document.querySelectorAll('.custom-range').forEach(range => {
794
+ const valueDisplay = document.getElementById(range.id.replace('range', 'value'));
795
+ range.addEventListener('input', (e) => {
796
+ valueDisplay.textContent = e.target.value;
797
+ });
798
+ });
799
+
800
+ // Form submission loading state
801
+ document.querySelector('form').addEventListener('submit', function(e) {
802
+ this.classList.add('loading');
803
+ });
804
+
805
+ // Enhanced print functionality
806
+ function printReport() {
807
+ // Expand all accordion items before printing
808
+ const accordionItems = document.querySelectorAll('.accordion-collapse');
809
+ accordionItems.forEach(item => {
810
+ item.classList.add('show');
811
+ });
812
+
813
+ // Wait for any graphs to finish rendering
814
+ setTimeout(() => {
815
+ window.print();
816
+
817
+ // Restore accordion state after printing
818
+ accordionItems.forEach(item => {
819
+ item.classList.remove('show');
820
+ });
821
+ }, 500);
822
+ }
823
+
824
+ // Add click handler to print button
825
+ document.querySelector('.print-btn').addEventListener('click', printReport);
826
+
827
+ // Add keyboard shortcut (Ctrl/Cmd + P)
828
+ document.addEventListener('keydown', function(e) {
829
+ if ((e.ctrlKey || e.metaKey) && e.key === 'p') {
830
+ e.preventDefault();
831
+ printReport();
832
+ }
833
+ });
834
+ </script>
835
+ </body>
836
+
837
+ </html>