SanskrutiChopade commited on
Commit
f94dafd
·
1 Parent(s): 60c5fc9

Initial commit

Browse files
Files changed (5) hide show
  1. .gitignore +4 -0
  2. Dockerfile +13 -0
  3. app.py +457 -0
  4. requirements.txt +5 -0
  5. templates/index.html +837 -0
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .env
4
+ .DS_Store
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
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,457 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import time
2
+ import os
3
+
4
+ from flask import Flask, render_template, request
5
+ import pandas as pd
6
+ import plotly.express as px
7
+ import io
8
+ import base64
9
+ import google.generativeai as genai
10
+ from collections import defaultdict
11
+ import numpy as np
12
+ import google.generativeai as genai
13
+
14
+ app = Flask(__name__)
15
+
16
+ # Configure Gemini API
17
+ genai.configure(api_key=os.getenv('GEMINI_API_KEY'))
18
+ model = genai.GenerativeModel("gemini-1.5-flash")
19
+
20
+ WEIGHTS = {
21
+ 'experience': 20,
22
+ 'degree': 20,
23
+ 'research': 20,
24
+ 'publication': 20,
25
+ 'skills': 20
26
+ }
27
+
28
+ # Ideal student-to-faculty ratio
29
+ IDEAL_RATIO = 20
30
+
31
+
32
+ def calculate_grades(row, weights):
33
+ """Function to calculate grades."""
34
+ experience_grade = normalize(row['Years_of_Experience'], 0, 35) * weights['experience']
35
+ degree_grade = (1 if row['Degree_Held'] in ['PhD', 'MPhil'] else 0.75) * weights['degree']
36
+ research_grade = normalize(row['Research_Count'], 0, 20) * weights['research']
37
+ publication_grade = normalize(row['Publications_Count'], 0, 50) * weights['publication']
38
+ skills_grade = normalize(len(str(row['Skills']).split(',')), 0, 10) * weights['skills']
39
+
40
+ total_grade = (experience_grade + degree_grade + research_grade + publication_grade + skills_grade) / sum(weights.values())
41
+
42
+ if row['Publications_Count'] > 30:
43
+ total_grade += 0.05
44
+ if row['Years_of_Experience'] < 2:
45
+ total_grade -= 0.05
46
+
47
+ return min(1.0, max(0.0, total_grade))
48
+
49
+
50
+ def normalize(value, min_value, max_value):
51
+ """Function to normalize values."""
52
+ return (value - min_value) / (max_value - min_value) if max_value - min_value != 0 else 0
53
+
54
+ def generate_with_retry(query, retries=3, delay=2):
55
+ for attempt in range(retries):
56
+ try:
57
+ gemini_response = model.generate_content(query)
58
+ return gemini_response.text
59
+ except Exception as e:
60
+ if "429" in str(e) and attempt < retries - 1:
61
+ time.sleep(delay * (2 ** attempt)) # Correctly use time.sleep
62
+ else:
63
+ raise e
64
+
65
+
66
+ def perform_swot_analysis(faculty_df, teaching_responses):
67
+ """Enhanced SWOT analysis based on faculty data and teaching responses."""
68
+
69
+ strengths = []
70
+ weaknesses = []
71
+ opportunities = []
72
+ threats = []
73
+
74
+ # Analyze faculty data
75
+ avg_experience = faculty_df['Years_of_Experience'].mean()
76
+ avg_publications = faculty_df['Publications_Count'].mean()
77
+ avg_research = faculty_df['Research_Count'].mean()
78
+ phd_count = len(faculty_df[faculty_df['Degree_Held'] == 'PhD'])
79
+ phd_percentage = (phd_count / len(faculty_df)) * 100
80
+
81
+ # Faculty Qualifications Analysis
82
+ if phd_percentage > 60:
83
+ strengths.append("High percentage of PhD holders ({}%)".format(round(phd_percentage)))
84
+ elif phd_percentage < 30:
85
+ weaknesses.append("Low percentage of PhD holders ({}%)".format(round(phd_percentage)))
86
+ opportunities.append("Encourage faculty to pursue higher education")
87
+
88
+ if avg_experience > 10:
89
+ strengths.append("Strong experienced faculty base (avg. {} years)".format(round(avg_experience)))
90
+ elif avg_experience < 5:
91
+ weaknesses.append("Relatively inexperienced faculty (avg. {} years)".format(round(avg_experience)))
92
+ opportunities.append("Implement mentorship programs")
93
+
94
+ # Research Output Analysis
95
+ if avg_publications > 20:
96
+ strengths.append("High research output through publications")
97
+ elif avg_publications < 10:
98
+ weaknesses.append("Low publication count")
99
+ opportunities.append("Create research incentives")
100
+
101
+ # Teaching Methodology Analysis
102
+ if teaching_responses:
103
+ # Course Design Analysis
104
+ course_revision = teaching_responses.get('course_revision', '')
105
+ if course_revision == 'Annually':
106
+ strengths.append("Regular curriculum updates")
107
+ elif course_revision == 'Rarely':
108
+ weaknesses.append("Infrequent curriculum revision")
109
+ threats.append("Risk of outdated curriculum")
110
+
111
+ # Technology Integration
112
+ tech_usage = teaching_responses.get('tech_usage', '')
113
+ if tech_usage == 'Yes':
114
+ strengths.append("Strong technology integration in teaching")
115
+ else:
116
+ weaknesses.append("Limited use of technology in teaching")
117
+ opportunities.append("Implement modern teaching technologies")
118
+
119
+ # Assessment Methods
120
+ assessment_methods = teaching_responses.get('assessment_methods', [])
121
+ if len(assessment_methods) >= 3:
122
+ strengths.append("Diverse assessment methods")
123
+ elif len(assessment_methods) < 2:
124
+ weaknesses.append("Limited assessment variety")
125
+ opportunities.append("Diversify assessment methods")
126
+
127
+ # Practical Learning
128
+ practical_percentage = int(teaching_responses.get('practical_percentage', 0))
129
+ if practical_percentage > 50:
130
+ strengths.append("Strong practical learning focus")
131
+ elif practical_percentage < 30:
132
+ weaknesses.append("Limited practical exposure")
133
+ opportunities.append("Increase hands-on learning activities")
134
+
135
+ # Student Engagement
136
+ student_participation = int(teaching_responses.get('student_participation', 0))
137
+ if student_participation > 75:
138
+ strengths.append("High student engagement")
139
+ elif student_participation < 50:
140
+ weaknesses.append("Low student participation")
141
+ opportunities.append("Implement engagement strategies")
142
+
143
+ # Teaching Methods
144
+ teaching_methods = teaching_responses.get('teaching_methods', [])
145
+ if len(teaching_methods) >= 3:
146
+ strengths.append("Diverse teaching methodologies")
147
+ elif len(teaching_methods) < 2:
148
+ weaknesses.append("Limited teaching methods")
149
+ opportunities.append("Expand teaching methodology")
150
+
151
+ # Professional Development
152
+ prof_dev = int(teaching_responses.get('professional_development', 0))
153
+ if prof_dev > 3:
154
+ strengths.append("Strong commitment to professional development")
155
+ elif prof_dev < 2:
156
+ weaknesses.append("Limited professional development")
157
+ opportunities.append("Increase faculty development programs")
158
+
159
+ # Industry Relevance
160
+ curriculum_relevance = int(teaching_responses.get('curriculum_relevance', 0))
161
+ if curriculum_relevance >= 8:
162
+ strengths.append("High industry relevance")
163
+ elif curriculum_relevance <= 5:
164
+ weaknesses.append("Low industry alignment")
165
+ threats.append("Risk of skill-industry mismatch")
166
+
167
+ # Add general threats
168
+ threats.extend([
169
+ "Rapid technological changes in education",
170
+ "Increasing competition from online education",
171
+ "Changing student learning preferences"
172
+ ])
173
+
174
+ # Add general opportunities
175
+ opportunities.extend([
176
+ "Integration of emerging technologies",
177
+ "Industry collaboration potential",
178
+ "International academic partnerships"
179
+ ])
180
+
181
+ return {
182
+ 'strengths': strengths,
183
+ 'weaknesses': weaknesses,
184
+ 'opportunities': opportunities,
185
+ 'threats': threats
186
+ }
187
+
188
+ @app.route('/', methods=['GET', 'POST'])
189
+ def index():
190
+ plots = {}
191
+ graded_csv = None
192
+ department_tables = {}
193
+ deficiency_table = None
194
+ departments = []
195
+ swot_results=None
196
+ teaching_responses=None
197
+ gemini_insights = {}
198
+
199
+ if request.method == 'POST':
200
+ if 'faculty_file' not in request.files:
201
+ return render_template('index.html', error="Error: Faculty file must be uploaded.")
202
+
203
+ faculty_file = request.files['faculty_file']
204
+
205
+ if faculty_file.filename == '':
206
+ return render_template('index.html', error="Error: Faculty file must be selected for upload.")
207
+
208
+ try:
209
+ # Load faculty data
210
+ faculty_df = pd.read_csv(faculty_file)
211
+
212
+ # Validate columns in faculty data
213
+ required_faculty_columns = {'Name', 'Department', 'Post', 'Years_of_Experience', 'Degree_Held',
214
+ 'Research_Count', 'Publications_Count', 'Skills'}
215
+ missing_faculty_columns = required_faculty_columns - set(faculty_df.columns)
216
+ if missing_faculty_columns:
217
+ return render_template('index.html',
218
+ error=f"Error: The faculty CSV is missing the following columns: {', '.join(missing_faculty_columns)}")
219
+
220
+ # Calculate grades
221
+ faculty_df['Grade'] = faculty_df.apply(lambda row: calculate_grades(row, WEIGHTS), axis=1)
222
+
223
+ # Get student counts from the form
224
+ student_counts = {department: int(request.form.get(f'students_{department}', 0) or 0) for department in faculty_df['Department'].unique()}
225
+
226
+
227
+ # Separate tables for each department
228
+ for department in faculty_df['Department'].unique():
229
+ department_data = faculty_df[faculty_df['Department'] == department]
230
+ department_tables[department] = {
231
+ 'columns': department_data.columns.tolist(),
232
+ 'rows': department_data.values.tolist()
233
+ }
234
+ departments.append(department)
235
+
236
+ graph_data = [
237
+ {
238
+ "title": "Count of Faculty by Department",
239
+ "data": faculty_df['Department'].value_counts().reset_index(name='count'),
240
+ "graph": lambda df: px.bar(df, x='Department', y='count', title="Count of Faculty by Department",
241
+ labels={'Department': 'Department', 'count': 'Count'}),
242
+ "query": "Provide insights into the distribution of faculty across departments based on this data."
243
+ },
244
+ {
245
+ "title": "Students vs Faculty by Department",
246
+ "data": pd.DataFrame({
247
+ "Department": faculty_df['Department'].unique(),
248
+ "Number_of_Students": [int(request.form.get(f'students_{dep}', 0)) for dep in
249
+ faculty_df['Department'].unique()],
250
+ "Number_of_Faculty": faculty_df['Department'].value_counts().values
251
+ }),
252
+ "graph": lambda df: px.bar(df, x='Department', y=['Number_of_Students', 'Number_of_Faculty'],
253
+ barmode='group',
254
+ title="Students vs Faculty by Department"),
255
+ "query": "Analyze the relationship between the number of students and faculty by department based on this data."
256
+ },
257
+ {
258
+ "title": "Post vs Skills",
259
+ "data": faculty_df[['Post', 'Skills']].assign(
260
+ Skills_Count=lambda x: x['Skills'].apply(lambda y: len(str(y).split(',')))),
261
+ "graph": lambda df: px.scatter(df, x='Post', y='Skills_Count', title="Post vs Skills"),
262
+ "query": "Explain the relationship between Post and Skills based on this data."
263
+ },
264
+ {
265
+ "title": "Degree vs Publications",
266
+ "data": faculty_df[['Degree_Held', 'Publications_Count']],
267
+ "graph": lambda df: px.box(df, x='Degree_Held', y='Publications_Count',
268
+ title="Degree vs Publications", color='Degree_Held'),
269
+ "query": "Describe the distribution of publications by degree based on this data."
270
+ },
271
+ {
272
+ "title": "Department-wise Faculty Count by Degree",
273
+ "data": faculty_df.groupby(['Department', 'Degree_Held']).size().reset_index(name='Count'),
274
+ "graph": lambda df: px.bar(df, x='Department', y='Count', color='Degree_Held', barmode='group',
275
+ title="Department-wise Faculty Count by Degree"),
276
+ "query": "What can we infer about the qualifications of faculty across departments from this data?"
277
+ },
278
+ {
279
+ "title": "Experience Distribution by Degree",
280
+ "data": faculty_df[['Degree_Held', 'Years_of_Experience']],
281
+ "graph": lambda df: px.violin(df, x='Degree_Held', y='Years_of_Experience',
282
+ title="Experience Distribution by Degree",
283
+ color='Degree_Held'),
284
+ "query": "Analyze the distribution of experience across different degree levels using this data."
285
+ },
286
+ {
287
+ "title": "Research Count by Department",
288
+ "data": faculty_df.groupby('Department')['Research_Count'].sum().reset_index(),
289
+ "graph": lambda df: px.bar(df, x='Department', y='Research_Count',
290
+ title="Research Count by Department"),
291
+ "query": "What insights can be drawn about the research output of each department based on this data?"
292
+ },
293
+ {
294
+ "title": "Publications Count by Department",
295
+ "data": faculty_df.groupby('Department')['Publications_Count'].sum().reset_index(),
296
+ "graph": lambda df: px.bar(df, x='Department', y='Publications_Count',
297
+ title="Publications Count by Department"),
298
+ "query": "Describe the publication trends across departments using this data."
299
+ },
300
+ {
301
+ "title": "Skills Count by Department",
302
+ "data": faculty_df.groupby('Department').apply(
303
+ lambda x: x['Skills'].apply(lambda y: len(str(y).split(','))).sum()
304
+ ).reset_index(name='Skills_Count'),
305
+ "graph": lambda df: px.bar(df, x='Department', y='Skills_Count',
306
+ title="Skills Count by Department"),
307
+ "query": "Explain the distribution of skills among faculty across different departments based on this data."
308
+ },
309
+ {
310
+ "title": "Grades Distribution",
311
+ "data": faculty_df[['Department', 'Grade']],
312
+ "graph": lambda df: px.box(df, x='Department', y='Grade', title="Grades Distribution by Department",
313
+ color='Department'),
314
+ "query": "What insights can we infer from the grades distribution of faculty across departments?"
315
+ },
316
+ {
317
+ "title": "Experience vs Publications",
318
+ "data": faculty_df[['Years_of_Experience', 'Publications_Count']],
319
+ "graph": lambda df: px.scatter(df, x='Years_of_Experience', y='Publications_Count',
320
+ title="Experience vs Publications",
321
+ labels={'Years_of_Experience': 'Years of Experience',
322
+ 'Publications_Count': 'Publications Count'}),
323
+ "query": "Analyze the relationship between years of experience and the number of publications based on this data."
324
+ },
325
+ {
326
+ "title": "Top Departments by Research",
327
+ "data": faculty_df.groupby('Department')['Research_Count'].sum().reset_index().sort_values(
328
+ by='Research_Count', ascending=False).head(5),
329
+ "graph": lambda df: px.bar(df, x='Department', y='Research_Count',
330
+ title="Top 5 Departments by Research Output"),
331
+ "query": "Identify the top departments by research output and analyze their characteristics."
332
+ }
333
+ ]
334
+
335
+ for graph in graph_data:
336
+ # Generate the graph
337
+ graph_df = graph["data"]
338
+ fig = graph["graph"](graph_df)
339
+ plot_html = fig.to_html(full_html=False)
340
+ plots[graph["title"]] = plot_html
341
+
342
+ # Prepare the query for Gemini
343
+ query = (
344
+ f"{graph['query']}\n\n"
345
+ "Data:\n"
346
+ f"{graph_df.to_csv(index=False)}\n\n"
347
+ "Provide a concise summary in 100 words, formatted without special characters like '*'. "
348
+ "Use proper sentences and highlight key points using **bold text**."
349
+ )
350
+
351
+ # Use retry logic for Gemini API
352
+ try:
353
+ gemini_response = generate_with_retry(query)
354
+ raw_text = gemini_response.replace('*', '').strip()
355
+
356
+ # Truncate if necessary
357
+ if len(raw_text) > 150:
358
+ raw_text = raw_text[:147].rsplit(' ', 1)[0] + "..."
359
+ gemini_insights[graph["title"]] = raw_text
360
+
361
+ except Exception as e:
362
+ gemini_insights[graph["title"]] = f"Error generating insight: {str(e)}"
363
+
364
+ # Move all faculty_counts calculations inside the try block
365
+ faculty_counts = faculty_df['Department'].value_counts().reset_index()
366
+ faculty_counts.columns = ['Department', 'Number_of_Faculty']
367
+
368
+ # Map number of students
369
+ faculty_counts['Number_of_Students'] = faculty_counts['Department'].map(student_counts)
370
+
371
+ # Calculate ideal numbers based on S (students), R=9 (1+2+6)
372
+ faculty_counts['Total_Ideal_Faculty'] = (faculty_counts['Number_of_Students'] / IDEAL_RATIO).apply(
373
+ lambda x: int(x) if x.is_integer() else int(x) + 1
374
+ )
375
+
376
+ # Role-specific ideal faculty counts
377
+ faculty_counts['Ideal_Principal'] = 1 # Always 1
378
+ faculty_counts['Ideal_Professor'] = (faculty_counts['Number_of_Students'] * 1 / (20 * 9)).apply(
379
+ lambda x: int(x) if x.is_integer() else int(x) + 1
380
+ )
381
+ faculty_counts['Ideal_Associate_Professor'] = (faculty_counts['Number_of_Students'] * 2 / (20 * 9)).apply(
382
+ lambda x: int(x) if x.is_integer() else int(x) + 1
383
+ )
384
+ faculty_counts['Ideal_Assistant_Professor'] = (faculty_counts['Number_of_Students'] * 6 / (20 * 9)).apply(
385
+ lambda x: int(x) if x.is_integer() else int(x) + 1
386
+ )
387
+
388
+ # Calculate deficiencies for each role
389
+ faculty_counts['Deficiency_Principal'] = faculty_counts['Ideal_Principal'] - faculty_df[
390
+ faculty_df['Post'] == 'Principal'].groupby('Department')['Post'].count().reindex(faculty_counts['Department']).fillna(0).astype(int)
391
+ faculty_counts['Deficiency_Professor'] = faculty_counts['Ideal_Professor'] - faculty_df[
392
+ faculty_df['Post'] == 'Professor'].groupby('Department')['Post'].count().reindex(faculty_counts['Department']).fillna(0).astype(int)
393
+ faculty_counts['Deficiency_Associate_Professor'] = faculty_counts['Ideal_Associate_Professor'] - faculty_df[
394
+ faculty_df['Post'] == 'Associate Professor'].groupby('Department')['Post'].count().reindex(faculty_counts['Department']).fillna(0).astype(int)
395
+ faculty_counts['Deficiency_Assistant_Professor'] = faculty_counts['Ideal_Assistant_Professor'] - faculty_df[
396
+ faculty_df['Post'] == 'Assistant Professor'].groupby('Department')['Post'].count().reindex(faculty_counts['Department']).fillna(0).astype(int)
397
+
398
+ # Overall deficiency
399
+ faculty_counts['Meets_Ratio'] = (faculty_counts['Deficiency_Principal'] <= 0) & \
400
+ (faculty_counts['Deficiency_Professor'] <= 0) & \
401
+ (faculty_counts['Deficiency_Associate_Professor'] <= 0) & \
402
+ (faculty_counts['Deficiency_Assistant_Professor'] <= 0)
403
+
404
+ faculty_counts['Meets_Ratio'] = faculty_counts['Meets_Ratio'].apply(lambda x: "✔️" if x else "❌")
405
+
406
+ # Prepare the final deficiency table
407
+ deficiency_table = faculty_counts[[
408
+ 'Department', 'Number_of_Students', 'Number_of_Faculty',
409
+ 'Ideal_Principal', 'Ideal_Professor', 'Ideal_Associate_Professor', 'Ideal_Assistant_Professor',
410
+ 'Deficiency_Principal', 'Deficiency_Professor', 'Deficiency_Associate_Professor', 'Deficiency_Assistant_Professor',
411
+ 'Meets_Ratio']].to_html(classes="table table-bordered table-hover", index=False, escape=False)
412
+
413
+ # Encode graded CSV
414
+ csv_output = io.BytesIO()
415
+ faculty_df.to_csv(csv_output, index=False)
416
+ csv_output.seek(0)
417
+ graded_csv = base64.b64encode(csv_output.getvalue()).decode()
418
+
419
+ # Collect teaching evaluation responses
420
+ teaching_responses = {
421
+ 'course_revision': request.form.get('course_revision', ''),
422
+ 'case_studies': request.form.get('case_studies', ''),
423
+ 'assessment_methods': request.form.getlist('assessment_methods') or [],
424
+ 'practical_percentage': int(request.form.get('practical_percentage', 0) or 0),
425
+ 'curriculum_relevance': int(request.form.get('curriculum_relevance', 0) or 0),
426
+ 'interactive_sessions': request.form.get('interactive_sessions', ''),
427
+ 'student_participation': int(request.form.get('student_participation', 0) or 0),
428
+ 'personalized_feedback': request.form.get('personalized_feedback', ''),
429
+ 'student_interest': request.form.get('student_interest', ''),
430
+ 'tech_usage': request.form.get('tech_usage', ''),
431
+ 'teaching_methods': request.form.getlist('teaching_methods') or [],
432
+ 'critical_thinking': request.form.get('critical_thinking', ''),
433
+ 'student_feedback': request.form.get('student_feedback', ''),
434
+ 'feedback_actions': request.form.get('feedback_actions', ''),
435
+ 'professional_development': int(request.form.get('professional_development', 0) or 0)
436
+ }
437
+
438
+ # Perform SWOT analysis
439
+ swot_results = perform_swot_analysis(faculty_df, teaching_responses)
440
+
441
+ except Exception as e:
442
+ return render_template('index.html', error=f"An unexpected error occurred: {str(e)}")
443
+
444
+ return render_template('index.html',
445
+ plots=plots,
446
+ graded_csv=graded_csv,
447
+ department_tables=department_tables,
448
+ departments=departments,
449
+ deficiency_table=deficiency_table,
450
+ gemini_insights=gemini_insights,
451
+ swot_results=swot_results,
452
+ teaching_responses=teaching_responses)
453
+
454
+
455
+ if __name__ == '__main__':
456
+ app.run(debug=True, port=5632)
457
+
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ flask
2
+ pandas
3
+ plotly
4
+ google-generativeai
5
+ numpy
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>