# modules/database.py from sqlalchemy import create_engine, Column, Integer, String, Float, Text, ForeignKey, DateTime, Boolean, inspect, text from sqlalchemy.orm import declarative_base, sessionmaker from datetime import datetime import hashlib import os from pathlib import Path # Storage setup IS_SPACES = os.environ.get('SPACE_ID') is not None if IS_SPACES: STORAGE_DIR = Path('/data') STORAGE_DIR.mkdir(exist_ok=True) DB_PATH = STORAGE_DIR / 'empower.db' else: STORAGE_DIR = Path('.') DB_PATH = Path('empower.db') # Database setup ENGINE = create_engine(f'sqlite:///{DB_PATH}', connect_args={'check_same_thread': False}) Base = declarative_base() Session = sessionmaker(bind=ENGINE) # Models class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String) email = Column(String, unique=True) role = Column(String) password_hash = Column(String) subjects_taught = Column(String) class_teacher_for = Column(String) gender = Column(String) phone_number = Column(String) class Student(Base): __tablename__ = 'students' id = Column(Integer, primary_key=True) name = Column(String) year = Column(Integer) class_name = Column(String) registration_number = Column(String) subjects = Column(String) subject_history = Column(Text) gender = Column(String) enrollment_date = Column(String) class AcademicTerm(Base): __tablename__ = 'academic_terms' id = Column(Integer, primary_key=True) year = Column(Integer) term_number = Column(Integer) term_name = Column(String) start_date = Column(String) end_date = Column(String) next_term_begins = Column(String) is_active = Column(Boolean, default=False) class ComponentMark(Base): __tablename__ = 'component_marks' id = Column(Integer, primary_key=True) student_id = Column(Integer, ForeignKey('students.id')) subject = Column(String) term_id = Column(Integer, ForeignKey('academic_terms.id')) component_type = Column(String) component_name = Column(String) score = Column(Float) total = Column(Float) submitted_by = Column(Integer, ForeignKey('users.id')) submitted_at = Column(String, default=lambda: datetime.now().isoformat()) class Mark(Base): __tablename__ = 'marks' id = Column(Integer, primary_key=True) student_id = Column(Integer, ForeignKey('students.id')) subject = Column(String) term_id = Column(Integer, ForeignKey('academic_terms.id')) coursework_score = Column(Float) coursework_total = Column(Float) coursework_out_of_20 = Column(Float) midterm_score = Column(Float) midterm_total = Column(Float) midterm_out_of_20 = Column(Float) endterm_score = Column(Float) endterm_total = Column(Float) endterm_out_of_60 = Column(Float) total = Column(Float) grade = Column(String) comment = Column(Text) submitted_by = Column(Integer, ForeignKey('users.id')) submitted_at = Column(String, default=lambda: datetime.now().isoformat()) class DisciplineReport(Base): __tablename__ = 'discipline_reports' id = Column(Integer, primary_key=True) student_id = Column(Integer, ForeignKey('students.id')) reported_by = Column(Integer, ForeignKey('users.id')) incident_date = Column(String) incident_type = Column(String) description = Column(Text) action_taken = Column(Text) status = Column(String, default="Pending") admin_notes = Column(Text) created_at = Column(String, default=lambda: datetime.now().isoformat()) class ReportDesign(Base): __tablename__ = 'report_design' id = Column(Integer, primary_key=True) school_name = Column(String, default="EMPOWER INTERNATIONAL ACADEMY") school_subtitle = Column(String, default="A Christian Boarding International School") school_address = Column(String, default="Nswanjere - Off Kampala-Mityana Road") school_po_box = Column(String, default="P.O BOX 1030, Kampala-Uganda") school_phone = Column(String, default="") school_email = Column(String, default="") school_website = Column(String, default="") logo_data = Column(Text) primary_color = Column(String, default="#8B4513") report_footer = Column(Text, default="") class AuditLog(Base): __tablename__ = 'audit_logs' id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey('users.id')) action = Column(String) details = Column(Text) timestamp = Column(DateTime, default=datetime.utcnow) class ClassroomBehavior(Base): __tablename__ = 'classroom_behavior' id = Column(Integer, primary_key=True) student_id = Column(Integer, ForeignKey('students.id')) term_id = Column(Integer, ForeignKey('academic_terms.id')) evaluated_by = Column(Integer, ForeignKey('users.id')) punctuality = Column(String) attendance = Column(String) manners = Column(String) general_behavior = Column(String) organisational_skills = Column(String) adherence_to_uniform = Column(String) leadership_skills = Column(String) commitment_to_school = Column(String) cooperation_with_peers = Column(String) cooperation_with_staff = Column(String) participation_in_lessons = Column(String) completion_of_homework = Column(String) evaluated_at = Column(String, default=lambda: datetime.now().isoformat()) class StudentDecision(Base): __tablename__ = 'student_decisions' id = Column(Integer, primary_key=True) student_id = Column(Integer, ForeignKey('students.id')) term_id = Column(Integer, ForeignKey('academic_terms.id')) decision = Column(String) decision_made_by = Column(Integer, ForeignKey('users.id')) decision_date = Column(String, default=lambda: datetime.now().isoformat()) notes = Column(Text) class VisitationDay(Base): __tablename__ = 'visitation_days' id = Column(Integer, primary_key=True) student_id = Column(Integer, ForeignKey('students.id')) term_id = Column(Integer, ForeignKey('academic_terms.id')) visitation_date = Column(String) parent_attended = Column(Boolean, default=False) report_given = Column(Boolean, default=False) notes = Column(Text) created_by = Column(Integer, ForeignKey('users.id')) created_at = Column(String, default=lambda: datetime.now().isoformat()) # Helper functions def update_database_schema(): """Update database schema with new tables and columns""" inspector = inspect(ENGINE) if 'visitation_days' not in inspector.get_table_names(): VisitationDay.__table__.create(ENGINE) if 'student_decisions' not in inspector.get_table_names(): StudentDecision.__table__.create(ENGINE) if 'component_marks' not in inspector.get_table_names(): ComponentMark.__table__.create(ENGINE) def init_database(): """Initialize database and create all tables""" Base.metadata.create_all(ENGINE) update_database_schema() def init_admin(): """Create default admin user if not exists""" session = Session() admin = session.query(User).filter_by(email='admin').first() if not admin: admin = User( name='System Admin', email='admin', role='admin', password_hash=hashlib.sha256('admin123'.encode()).hexdigest(), subjects_taught='', class_teacher_for='', gender='', phone_number='' ) session.add(admin) session.commit() session.close() def init_report_design(): """Initialize report design if not exists""" session = Session() design = session.query(ReportDesign).first() if not design: design = ReportDesign() session.add(design) session.commit() session.close() def log_audit(session, user_id, action, details=""): """Log an audit entry""" log = AuditLog(user_id=user_id, action=action, details=details) session.add(log) session.commit()