import pandas as pd
import numpy as np
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.styles.differential import DifferentialStyle
from openpyxl.formatting.rule import Rule, FormulaRule
from openpyxl.chart import BarChart, PieChart, Reference
from openpyxl.chart.label import DataLabelList
from openpyxl.worksheet.datavalidation import DataValidation
from openpyxl.utils import get_column_letter
from openpyxl.drawing.image import Image
import datetime
import os
def create_class_assessment_tool(filename='Class_Assessment_Tool.xlsx',
num_students=30):
"""
Create an Excel workbook for class assessment analysis with three sheets:
1. Mark Schedule
2. Summary Analysis
3. Dashboard
Args:
filename (str): Output Excel filename
num_students (int): Number of sample student rows to include
"""
# Create a new workbook
wb = openpyxl.Workbook()
# Create the three sheets
mark_schedule = wb.active
mark_schedule.title = "Mark Schedule"
summary_analysis = wb.create_sheet(title="Summary Analysis")
dashboard = wb.create_sheet(title="Dashboard")
# Set up each sheet
setup_mark_schedule(mark_schedule, num_students)
setup_summary_analysis(summary_analysis)
setup_dashboard(dashboard)
# Define named ranges for easier formula references
define_named_ranges(wb)
# Adjust column widths and row heights
adjust_column_widths(wb)
# Add cell protection
add_cell_protection(wb)
# Add instructions
add_instructions(wb)
# Set print areas
set_print_areas(wb)
# Save the workbook
wb.save(filename)
print(f"Excel file created: {filename}")
def setup_mark_schedule(ws, num_students):
"""
Set up the Mark Schedule sheet with headers, data validation, and conditional
formatting
"""
# Define headers
headers = ["SN", "Name", "Sex", "Assessment 1", "Assessment 2", "Assessment 3",
"Average", "Grade", "Standard"]
# Create headers with formatting
for col_idx, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col_idx, value=header)
cell.font = Font(bold=True)
cell.alignment = Alignment(horizontal='center')
# Apply border to header row
border = Border(bottom=Side(style='medium'))
for col_idx in range(1, len(headers) + 1):
ws.cell(row=1, column=col_idx).border = border
# Add sample data
add_sample_data(ws, num_students)
# Set data validation for Sex column (M/F only)
sex_validation = DataValidation(type="list", formula1='"M,F"',
allow_blank=True)
ws.add_data_validation(sex_validation)
# Apply sex validation to all rows
for row in range(2, num_students + 2):
sex_validation.add(f'C{row}')
# Set data validation for Assessment scores (0-100)
score_validation = DataValidation(type="decimal", operator="between",
formula1="0", formula2="100", allow_blank=True)
ws.add_data_validation(score_validation)
# Apply score validation to all assessment columns
for col in ['D', 'E', 'F']:
for row in range(2, num_students + 2):
score_validation.add(f'{col}{row}')
# Add formulas for Average, Grade, and Standard
for row in range(2, num_students + 2):
# Average formula
ws[f'G{row}'] =
f'=IF(OR(D{row}="",E{row}="",F{row}=""),"",AVERAGE(D{row}:F{row}))'
# Grade formula
ws[f'H{row}'] =
f'=IF(G{row}="","",IF(ISNA(G{row}),"X",IF(G{row}>=75,"ONE",IF(G{row}>=60,"TWO",IF(G
{row}>=50,"THREE",IF(G{row}>=40,"FOUR",IF(G{row}>=0,"F","X")))))))'
# Standard formula
ws[f'I{row}'] =
f'=IF(H{row}="","",IF(H{row}="X","ABSENT",IF(H{row}="F","FAIL",IF(H{row}="FOUR","PA
SS",IF(H{row}="THREE","CREDIT",IF(H{row}="TWO","MERIT","DISTINCTION"))))))'
# Set conditional formatting for grades
add_conditional_formatting(ws, num_students)
def add_sample_data(ws, num_students):
"""
Add sample student data to the Mark Schedule sheet
"""
# Sample first names
first_names = ['John', 'Mary', 'Michael', 'Emma', 'James', 'Sarah', 'David',
'Jessica',
'Robert', 'Jennifer', 'William', 'Elizabeth', 'Joseph', 'Linda',
'Richard',
'Susan', 'Thomas', 'Karen', 'Charles', 'Nancy', 'Daniel',
'Lisa', 'Matthew',
'Emily', 'Andrew', 'Amanda', 'Anthony', 'Barbara',
'Christopher', 'Melissa']
# Sample last names
last_names = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia',
'Miller', 'Davis',
'Rodriguez', 'Martinez', 'Wilson', 'Anderson', 'Taylor', 'Thomas',
'Moore',
'Jackson', 'Martin', 'Lee', 'Harris', 'Clark', 'Lewis',
'Robinson', 'Walker',
'Young', 'Allen', 'King', 'Wright', 'Scott', 'Green', 'White']
# Gender distribution (approx. 50-50)
genders = ['M', 'F'] * (num_students // 2)
if num_students % 2 == 1:
genders.append(np.random.choice(['M', 'F']))
np.random.shuffle(genders)
# Generate sample data
for i in range(num_students):
row = i + 2 # Start from row 2
# Serial number
ws.cell(row=row, column=1, value=i+1)
# Name (random combination of first and last name)
first_name = np.random.choice(first_names)
last_name = np.random.choice(last_names)
ws.cell(row=row, column=2, value=f"{first_name} {last_name}")
# Sex (M/F)
ws.cell(row=row, column=3, value=genders[i])
# Generate assessment scores (adjusted to create a realistic distribution)
# Some students will have missing scores (absent)
if np.random.random() < 0.1: # 10% chance of being absent
# Leave assessments blank for absent students
pass
else:
# Normal distribution around 70 with std dev of 15
for j in range(3):
score = np.random.normal(70, 15)
score = max(0, min(100, score)) # Clamp between 0 and 100
ws.cell(row=row, column=j+4, value=round(score, 1))
def add_conditional_formatting(ws, num_students):
"""
Add conditional formatting to the Grade column
"""
# Define fill colors for each grade
gold_fill = PatternFill(start_color="FFD700", end_color="FFD700",
fill_type="solid") # ONE/DISTINCTION
silver_fill = PatternFill(start_color="ADD8E6", end_color="ADD8E6",
fill_type="solid") # TWO/MERIT
bronze_fill = PatternFill(start_color="D2B48C", end_color="D2B48C",
fill_type="solid") # THREE/CREDIT
green_fill = PatternFill(start_color="90EE90", end_color="90EE90",
fill_type="solid") # FOUR/PASS
red_fill = PatternFill(start_color="FF9999", end_color="FF9999",
fill_type="solid") # F/FAIL
gray_fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3",
fill_type="solid") # X/ABSENT
# Apply conditional formatting rules to the Grade column (H)
# ONE (DISTINCTION): Gold/Yellow
ws.conditional_formatting.add(f'H2:H{num_students+1}',
FormulaRule(formula=['$H2="ONE"'],
fill=gold_fill))
# TWO (MERIT): Silver/Light Blue
ws.conditional_formatting.add(f'H2:H{num_students+1}',
FormulaRule(formula=['$H2="TWO"'],
fill=silver_fill))
# THREE (CREDIT): Bronze/Light Brown
ws.conditional_formatting.add(f'H2:H{num_students+1}',
FormulaRule(formula=['$H2="THREE"'],
fill=bronze_fill))
# FOUR (PASS): Green
ws.conditional_formatting.add(f'H2:H{num_students+1}',
FormulaRule(formula=['$H2="FOUR"'],
fill=green_fill))
# F (FAIL): Red
ws.conditional_formatting.add(f'H2:H{num_students+1}',
FormulaRule(formula=['$H2="F"'], fill=red_fill))
# X (ABSENT): Gray
ws.conditional_formatting.add(f'H2:H{num_students+1}',
FormulaRule(formula=['$H2="X"'], fill=gray_fill))
# Apply similar formatting to the Standard column (I)
# DISTINCTION: Gold/Yellow
ws.conditional_formatting.add(f'I2:I{num_students+1}',
FormulaRule(formula=['$I2="DISTINCTION"'],
fill=gold_fill))
# MERIT: Silver/Light Blue
ws.conditional_formatting.add(f'I2:I{num_students+1}',
FormulaRule(formula=['$I2="MERIT"'],
fill=silver_fill))
# CREDIT: Bronze/Light Brown
ws.conditional_formatting.add(f'I2:I{num_students+1}',
FormulaRule(formula=['$I2="CREDIT"'],
fill=bronze_fill))
# PASS: Green
ws.conditional_formatting.add(f'I2:I{num_students+1}',
FormulaRule(formula=['$I2="PASS"'],
fill=green_fill))
# FAIL: Red
ws.conditional_formatting.add(f'I2:I{num_students+1}',
FormulaRule(formula=['$I2="FAIL"'],
fill=red_fill))
# ABSENT: Gray
ws.conditional_formatting.add(f'I2:I{num_students+1}',
FormulaRule(formula=['$I2="ABSENT"'],
fill=gray_fill))
def setup_summary_analysis(ws):
"""
Set up the Summary Analysis sheet with various summary tables
"""
# Add sheet title
ws['A1'] = "Summary Analysis"
ws['A1'].font = Font(bold=True, size=14)
# Table 1: Grade Distribution by Gender
ws['A3'] = "Table 1: Grade Distribution by Gender"
ws['A3'].font = Font(bold=True, size=12)
# Headers for Table 1
headers = ["Grade", "Male Count", "Male %", "Female Count", "Female %", "Total
Count", "Total %"]
for col_idx, header in enumerate(headers):
cell = ws.cell(row=4, column=col_idx+1, value=header)
cell.font = Font(bold=True)
cell.alignment = Alignment(horizontal='center')
# Add grade rows
grades = ["ONE", "TWO", "THREE", "FOUR", "F", "X", "TOTAL"]
for row_idx, grade in enumerate(grades):
ws.cell(row=row_idx+5, column=1, value=grade)
# Add formulas for counts and percentages
for row_idx, grade in enumerate(grades):
row = row_idx + 5
if grade == "TOTAL":
# Total Males
ws.cell(row=row, column=2, value=f"=SUM(B5:B{row-1})")
# Total Females
ws.cell(row=row, column=4, value=f"=SUM(D5:D{row-1})")
# Total Students
ws.cell(row=row, column=6, value=f"=SUM(F5:F{row-1})")
# Percentages will be 100%
ws.cell(row=row, column=3, value="100%")
ws.cell(row=row, column=5, value="100%")
ws.cell(row=row, column=7, value="100%")
else:
# Male Count
ws.cell(row=row, column=2, value=f'=COUNTIFS(\'Mark Schedule\'!$C:
$C,"M",\'Mark Schedule\'!$H:$H,"{grade}")')
# Female Count
ws.cell(row=row, column=4, value=f'=COUNTIFS(\'Mark Schedule\'!$C:
$C,"F",\'Mark Schedule\'!$H:$H,"{grade}")')
# Total Count
ws.cell(row=row, column=6, value=f'=COUNTIFS(\'Mark Schedule\'!$H:
$H,"{grade}")')
# Male Percentage
ws.cell(row=row, column=3, value=f'=IF(B11=0,0,B{row}/B11)')
# Female Percentage
ws.cell(row=row, column=5, value=f'=IF(D11=0,0,D{row}/D11)')
# Total Percentage
ws.cell(row=row, column=7, value=f'=IF(F11=0,0,F{row}/F11)')
# Format percentage cells
for row in range(5, 12):
for col in [3, 5, 7]:
ws.cell(row=row, column=col).number_format = "0.0%"
# Table 2: Performance Categories Summary
ws['A14'] = "Table 2: Performance Categories Summary"
ws['A14'].font = Font(bold=True, size=12)
# Headers for Table 2
for col_idx, header in enumerate(headers):
if col_idx == 0:
header = "Standard"
cell = ws.cell(row=15, column=col_idx+1, value=header)
cell.font = Font(bold=True)
cell.alignment = Alignment(horizontal='center')
# Add standard rows
standards = ["DISTINCTION", "MERIT", "CREDIT", "PASS", "FAIL", "ABSENT",
"TOTAL"]
for row_idx, standard in enumerate(standards):
ws.cell(row=row_idx+16, column=1, value=standard)
# Add formulas for counts and percentages
for row_idx, standard in enumerate(standards):
row = row_idx + 16
if standard == "TOTAL":
# Total Males
ws.cell(row=row, column=2, value=f"=SUM(B16:B{row-1})")
# Total Females
ws.cell(row=row, column=4, value=f"=SUM(D16:D{row-1})")
# Total Students
ws.cell(row=row, column=6, value=f"=SUM(F16:F{row-1})")
# Percentages will be 100%
ws.cell(row=row, column=3, value="100%")
ws.cell(row=row, column=5, value="100%")
ws.cell(row=row, column=7, value="100%")
else:
# Male Count
ws.cell(row=row, column=2, value=f'=COUNTIFS(\'Mark Schedule\'!$C:
$C,"M",\'Mark Schedule\'!$I:$I,"{standard}")')
# Female Count
ws.cell(row=row, column=4, value=f'=COUNTIFS(\'Mark Schedule\'!$C:
$C,"F",\'Mark Schedule\'!$I:$I,"{standard}")')
# Total Count
ws.cell(row=row, column=6, value=f'=COUNTIFS(\'Mark Schedule\'!$I:
$I,"{standard}")')
# Male Percentage
ws.cell(row=row, column=3, value=f'=IF(B22=0,0,B{row}/B22)')
# Female Percentage
ws.cell(row=row, column=5, value=f'=IF(D22=0,0,D{row}/D22)')
# Total Percentage
ws.cell(row=row, column=7, value=f'=IF(F22=0,0,F{row}/F22)')
# Format percentage cells
for row in range(16, 23):
for col in [3, 5, 7]:
ws.cell(row=row, column=col).number_format = "0.0%"
# Table 3: Key Performance Indicators
ws['A25'] = "Table 3: Key Performance Indicators"
ws['A25'].font = Font(bold=True, size=12)
# Headers for Table 3
kpi_headers = ["Indicator", "Male", "Female", "Overall"]
for col_idx, header in enumerate(kpi_headers):
cell = ws.cell(row=26, column=col_idx+1, value=header)
cell.font = Font(bold=True)
cell.alignment = Alignment(horizontal='center')
# Add KPI rows
kpis = [
"Pass Rate (ONE-FOUR)",
"Fail Rate (F)",
"Absence Rate (X)",
"Male to Female Ratio",
"Average Score"
]
for row_idx, kpi in enumerate(kpis):
ws.cell(row=row_idx+27, column=1, value=kpi)
# Add formulas for KPIs
# Pass Rate (ONE-FOUR)
ws['B27'] = '=COUNTIFS(\'Mark Schedule\'!$C:$C,"M",\'Mark Schedule\'!$H:
$H,"ONE")+COUNTIFS(\'Mark Schedule\'!$C:$C,"M",\'Mark Schedule\'!$H:$H,"TWO")
+COUNTIFS(\'Mark Schedule\'!$C:$C,"M",\'Mark Schedule\'!$H:$H,"THREE")
+COUNTIFS(\'Mark Schedule\'!$C:$C,"M",\'Mark Schedule\'!$H:$H,"FOUR")'
ws['B27'].number_format = "0.0%"
ws['C27'] = '=COUNTIFS(\'Mark Schedule\'!$C:$C,"F",\'Mark Schedule\'!$H:
$H,"ONE")+COUNTIFS(\'Mark Schedule\'!$C:$C,"F",\'Mark Schedule\'!$H:$H,"TWO")
+COUNTIFS(\'Mark Schedule\'!$C:$C,"F",\'Mark Schedule\'!$H:$H,"THREE")
+COUNTIFS(\'Mark Schedule\'!$C:$C,"F",\'Mark Schedule\'!$H:$H,"FOUR")'
ws['C27'].number_format = "0.0%"
ws['D27'] = '=COUNTIFS(\'Mark Schedule\'!$H:$H,"ONE")+COUNTIFS(\'Mark
Schedule\'!$H:$H,"TWO")+COUNTIFS(\'Mark Schedule\'!$H:$H,"THREE")+COUNTIFS(\'Mark
Schedule\'!$H:$H,"FOUR")'
ws['D27'].number_format = "0.0%"
# Fail Rate (F)
ws['B28'] = '=COUNTIFS(\'Mark Schedule\'!$C:$C,"M",\'Mark Schedule\'!$H:
$H,"F")/COUNTIFS(\'Mark Schedule\'!$C:$C,"M")'
ws['B28'].number_format = "0.0%"
ws['C28'] = '=COUNTIFS(\'Mark Schedule\'!$C:$C,"F",\'Mark Schedule\'!$H:
$H,"F")/COUNTIFS(\'Mark Schedule\'!$C:$C,"F")'
ws['C28'].number_format = "0.0%"
ws['D28'] = '=COUNTIFS(\'Mark Schedule\'!$H:$H,"F")/COUNTIFS(\'Mark Schedule\'!
$A:$A,"<>"")'
ws['D28'].number_format = "0.0%"
# Absence Rate (X)
ws['B29'] = '=COUNTIFS(\'Mark Schedule\'!$C:$C,"M",\'Mark Schedule\'!$H:
$H,"X")/COUNTIFS(\'Mark Schedule\'!$C:$C,"M")'
ws['B29'].number_format = "0.0%"
ws['C29'] = '=COUNTIFS(\'Mark Schedule\'!$C:$C,"F",\'Mark Schedule\'!$H:
$H,"X")/COUNTIFS(\'Mark Schedule\'!$C:$C,"F")'
ws['C29'].number_format = "0.0%"
ws['D29'] = '=COUNTIFS(\'Mark Schedule\'!$H:$H,"X")/COUNTIFS(\'Mark Schedule\'!
$A:$A,"<>"")'
ws['D29'].number_format = "0.0%"
# Male to Female Ratio
ws['B30'] = '=COUNTIFS(\'Mark Schedule\'!$C:$C,"M")'
ws['C30'] = '=COUNTIFS(\'Mark Schedule\'!$C:$C,"F")'
ws['D30'] = '=B30&":"&C30'
# Average Score
ws['B31'] = '=AVERAGEIFS(\'Mark Schedule\'!$G:$G,\'Mark Schedule\'!$C:$C,"M")'
ws['B31'].number_format = "0.00"
ws['C31'] = '=AVERAGEIFS(\'Mark Schedule\'!$G:$G,\'Mark Schedule\'!$C:$C,"F")'
ws['C31'].number_format = "0.00"
ws['D31'] = '=AVERAGE(\'Mark Schedule\'!$G:$G)'
ws['D31'].number_format = "0.00"
def setup_dashboard(ws):
"""
Set up the Dashboard sheet with KPIs and charts
"""
# Add sheet title and information
ws['A1'] = "Class Assessment Dashboard"
ws['A1'].font = Font(bold=True, size=16)
ws['A2'] = "Class Performance Analysis"
ws['A2'].font = Font(italic=True, size=12)
ws['A3'] = f"Report Generated: {datetime.datetime.now().strftime('%Y-%m-%d')}"
# KPI Section
ws['A5'] = "Key Performance Indicators"
ws['A5'].font = Font(bold=True, size=14)
# KPI Headers
kpi_titles = [
"Class Average",
"Male Average",
"Female Average",
"Pass Rate",
"Distinction Rate",
"Merit Rate",
"Credit Rate",
"Pass Rate (Grade 4)",
"Failure Rate",
"Absence Rate",
"Total Students"
]
# Add KPI boxes
for i, title in enumerate(kpi_titles):
row = i + 6
ws.cell(row=row, column=1, value=title)
ws.cell(row=row, column=2, value=get_kpi_formula(title))
# Format numbers
if "Rate" in title:
ws.cell(row=row, column=2).number_format = "0.0%"
elif "Average" in title:
ws.cell(row=row, column=2).number_format = "0.00"
# Add charts
create_charts(ws)
def get_kpi_formula(kpi_title):
"""
Get the appropriate formula for each KPI
"""
if kpi_title == "Class Average":
return "=AVERAGE('Mark Schedule'!G:G)"
elif kpi_title == "Male Average":
return "=AVERAGEIFS('Mark Schedule'!G:G,'Mark Schedule'!C:C,\"M\")"
elif kpi_title == "Female Average":
return "=AVERAGEIFS('Mark Schedule'!G:G,'Mark Schedule'!C:C,\"F\")"
elif kpi_title == "Pass Rate":
return "=COUNTIFS('Mark Schedule'!H:H,\"ONE\")+COUNTIFS('Mark Schedule'!
H:H,\"TWO\")+COUNTIFS('Mark Schedule'!H:H,\"THREE\")+COUNTIFS('Mark Schedule'!
H:H,\"FOUR\")/COUNTIFS('Mark Schedule'!A:A,\"<>\"\"\")"
elif kpi_title == "Distinction Rate":
return "=COUNTIFS('Mark Schedule'!H:H,\"ONE\")/COUNTIFS('Mark Schedule'!
A:A,\"<>\"\"\")"
elif kpi_title == "Merit Rate":
return "=COUNTIFS('Mark Schedule'!H:H,\"TWO\")/COUNTIFS('Mark Schedule'!
A:A,\"<>\"\"\")"
elif kpi_title == "Credit Rate":
return "=COUNTIFS('Mark Schedule'!H:H,\"THREE\")/COUNTIFS('Mark Schedule'!
A:A,\"<>\"\"\")"
elif kpi_title == "Pass Rate (Grade 4)":
return "=COUNTIFS('Mark Schedule'!H:H,\"FOUR\")/COUNTIFS('Mark Schedule'!
A:A,\"<>\"\"\")"
elif kpi_title == "Failure Rate":
return "=COUNTIFS('Mark Schedule'!H:H,\"F\")/COUNTIFS('Mark Schedule'!
A:A,\"<>\"\"\")"
elif kpi_title == "Absence Rate":
return "=COUNTIFS('Mark Schedule'!H:H,\"X\")/COUNTIFS('Mark Schedule'!
A:A,\"<>\"\"\")"
elif kpi_title == "Total Students":
return "=COUNTIFS('Mark Schedule'!A:A,\"<>\"\"\")-1" # Subtract 1 for
header row
else:
return "=0"
def create_charts(ws):
"""
Create charts for the dashboard
"""
# Create a bar chart for grades by gender
bar_chart = BarChart()
bar_chart.title = "Grade Distribution by Gender"
bar_chart.style = 10
bar_chart.x_axis.title = "Grade"
bar_chart.y_axis.title = "Number of Students"
# Add data to the chart
# In a real implementation, this would need to be adjusted based on the actual
data
# For this example, we'll just show the concept
# Create data table for the chart
ws['D5'] = "Grade Distribution Data"
ws['D5'].font = Font(bold=True)
# Headers
grade_headers = ["Grade", "Male", "Female", "Total"]
for i, header in enumerate(grade_headers):
ws.cell(row=6, column=i+4, value=header)
ws.cell(row=6, column=i+4).font = Font(bold=True)
# Add data rows
grades = ["ONE", "TWO", "THREE", "FOUR", "F", "X"]
for i, grade in enumerate(grades):
row = i + 7
ws.cell(row=row, column=4, value=grade)
ws.cell(row=row, column=5, value=f"=COUNTIFS('Mark Schedule'!$C:
$C,\"M\",'Mark Schedule'!$H:$H,\"{grade}\")")
ws.cell(row=row, column=6, value=f"=COUNTIFS('Mark Schedule'!$C:
$C,\"F\",'Mark Schedule'!$H:$H,\"{grade}\")")
ws.cell(row=row, column=7, value=f"=COUNTIFS('Mark Schedule'!$H:
$H,\"{grade}\")")
# Add a bar chart for grade distribution
grade_chart = BarChart()
grade_chart.type = "col"
grade_chart.style = 10
grade_chart.title = "Grade Distribution by Gender"
grade_chart.x_axis.title = "Grade"
grade_chart.y_axis.title = "Number of Students"
data = Reference(ws, min_col=5, min_row=6, max_row=12, max_col=7)
cats = Reference(ws, min_col=4, min_row=7, max_row=12)
grade_chart.add_data(data, titles_from_data=True)
grade_chart.set_categories(cats)
grade_chart.shape = 4
ws.add_chart(grade_chart, "D15")
# Create data for pie charts
ws['I5'] = "Performance Distribution Data"
ws['I5'].font = Font(bold=True)
# Headers
perf_headers = ["Standard", "Male", "Female", "Total"]
for i, header in enumerate(perf_headers):
ws.cell(row=6, column=i+9, value=header)
ws.cell(row=6, column=i+9).font = Font(bold=True)
# Add data rows
standards = ["DISTINCTION", "MERIT", "CREDIT", "PASS", "FAIL", "ABSENT"]
for i, standard in enumerate(standards):
row = i + 7
ws.cell(row=row, column=9, value=standard)
ws.cell(row=row, column=10, value=f"=COUNTIFS('Mark Schedule'!$C:
$C,\"M\",'Mark Schedule'!$I:$I,\"{standard}\")")
ws.cell(row=row, column=11, value=f"=COUNTIFS('Mark Schedule'!$C:
$C,\"F\",'Mark Schedule'!$I:$I,\"{standard}\")")
ws.cell(row=row, column=12, value=f"=COUNTIFS('Mark Schedule'!$I:
$I,\"{standard}\")")
# Add a pie chart for performance distribution
pie_chart = PieChart()
pie_chart.title = "Performance Distribution by Standard"
data = Reference(ws, min_col=12, min_row=6, max_row=12)
cats = Reference(ws, min_col=9, min_row=7, max_row=12)
pie_chart.add_data(data, titles_from_data=True)
pie_chart.set_categories(cats)
pie_chart.dataLabels = DataLabelList()
pie_chart.dataLabels.showPercent = True
ws.add_chart(pie_chart, "I15")
# Add a pie chart for gender distribution
gender_pie_chart = PieChart()
gender_pie_chart.title = "Gender Distribution"
ws['L5'] = "Gender Distribution Data"
ws['L5'].font = Font(bold=True)
# Headers
gender_headers = ["Gender", "Count"]
for i, header in enumerate(gender_headers):
ws.cell(row=6, column=i+12, value=header)
ws.cell(row=6, column=i+12).font = Font(bold=True)
# Add data rows
genders = ["M", "F"]
for i, gender in enumerate(genders):
row = i + 7
ws.cell(row=row, column=12, value=gender)
ws.cell(row=row, column=13, value=f"=COUNTIFS('Mark Schedule'!$C:
$C,\"{gender}\")")
data = Reference(ws, min_col=13, min_row=6, max_row=8)
cats = Reference(ws, min_col=12, min_row=7, max_row=8)
gender_pie_chart.add_data(data, titles_from_data=True)
gender_pie_chart.set_categories(cats)
gender_pie_chart.dataLabels = DataLabelList()
gender_pie_chart.dataLabels.showPercent = True
ws.add_chart(gender_pie_chart, "L15")
def define_named_ranges(wb):
"""
Define named ranges for easier formula references
"""
# Define named ranges for the Mark Schedule sheet
mark_schedule = wb['Mark Schedule']
# Named range for the list of student names
wb.defined_names.append(openpyxl.workbook.defined_name.DefinedName('StudentNames',
attr_text=f"'Mark Schedule'!$B$2:$B${mark_schedule.max_row}"))
# Named range for the list of grades
wb.defined_names.append(openpyxl.workbook.defined_name.DefinedName('Grades',
attr_text=f"'Mark Schedule'!$H$2:$H${mark_schedule.max_row}"))
# Named range for the list of standards
wb.defined_names.append(openpyxl.workbook.defined_name.DefinedName('Standards',
attr_text=f"'Mark Schedule'!$I$2:$I${mark_schedule.max_row}"))
def adjust_column_widths(wb):
"""
Adjust column widths for better readability
"""
# Adjust column widths for the Mark Schedule sheet
mark_schedule = wb['Mark Schedule']
mark_schedule.column_dimensions['A'].width = 5 # SN
mark_schedule.column_dimensions['B'].width = 20 # Name
mark_schedule.column_dimensions['C'].width = 8 # Sex
mark_schedule.column_dimensions['D'].width = 12 # Assessment 1
mark_schedule.column_dimensions['E'].width = 12 # Assessment 2
mark_schedule.column_dimensions['F'].width = 12 # Assessment 3
mark_schedule.column_dimensions['G'].width = 12 # Average
mark_schedule.column_dimensions['H'].width = 10 # Grade
mark_schedule.column_dimensions['I'].width = 12 # Standard
# Adjust column widths for the Summary Analysis sheet
summary_analysis = wb['Summary Analysis']
summary_analysis.column_dimensions['A'].width = 20 # Grade/Standard
summary_analysis.column_dimensions['B'].width = 12 # Male Count
summary_analysis.column_dimensions['C'].width = 12 # Male %
summary_analysis.column_dimensions['D'].width = 12 # Female Count
summary_analysis.column_dimensions['E'].width = 12 # Female %
summary_analysis.column_dimensions['F'].width = 12 # Total Count
summary_analysis.column_dimensions['G'].width = 12 # Total %
# Adjust column widths for the Dashboard sheet
dashboard = wb['Dashboard']
dashboard.column_dimensions['A'].width = 25 # KPI Titles
dashboard.column_dimensions['B'].width = 15 # KPI Values
def add_cell_protection(wb):
"""
Add cell protection to prevent accidental edits
"""
# Protect all sheets
for sheet in wb:
sheet.protection.sheet = True
# Unprotect cells that need to be editable
mark_schedule = wb['Mark Schedule']
for row in range(2, mark_schedule.max_row + 1):
for col in ['B', 'C', 'D', 'E', 'F']: # Name, Sex, Assessment 1, 2, 3
mark_schedule[f'{col}{row}'].protection =
openpyxl.styles.Protection(locked=False)
def add_instructions(wb):
"""
Add instructions to the Mark Schedule sheet
"""
mark_schedule = wb['Mark Schedule']
# Add instructions at the bottom of the sheet
instructions = [
"Instructions:",
"1. Enter student names in column B.",
"2. Select student sex (M/F) in column C.",
"3. Enter assessment scores (0-100) in columns D-F.",
"4. The Average, Grade, and Standard columns will be calculated
automatically.",
"5. Do not edit columns G-I (calculated fields)."
]
for i, instruction in enumerate(instructions):
mark_schedule.cell(row=mark_schedule.max_row + 2, column=1,
value=instruction)
def set_print_areas(wb):
"""
Set print areas for each sheet
"""
# Set print area for Mark Schedule
mark_schedule = wb['Mark Schedule']
mark_schedule.print_area = f"A1:I{mark_schedule.max_row}"
# Set print area for Summary Analysis
summary_analysis = wb['Summary Analysis']
summary_analysis.print_area = f"A1:G{summary_analysis.max_row}"
# Set print area for Dashboard
dashboard = wb['Dashboard']
dashboard.print_area = f"A1:M{dashboard.max_row}"
# Run the function to create the Excel file
create_class_assessment_tool()