Initial commit.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from tinydb import TinyDB, Query
|
||||
import secrets
|
||||
import pandas as pd
|
||||
from pydantic import BaseModel, field_serializer
|
||||
|
||||
from .his_io import dtypes
|
||||
|
||||
|
||||
pandas_json_format = 'split'
|
||||
|
||||
|
||||
class Session(BaseModel):
|
||||
token: str
|
||||
table_name: str = ''
|
||||
students_df: pd.DataFrame
|
||||
points_df: pd.DataFrame
|
||||
his_header: pd.DataFrame
|
||||
grade_1_0: float = 0.92
|
||||
grade_4_0: float = 0.4
|
||||
max_points: int = 100
|
||||
created: datetime = datetime.now()
|
||||
edited: datetime = datetime.now()
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, Session) and \
|
||||
self.token == other.token and \
|
||||
self.table_name == other.table_name and \
|
||||
self.students_df.equals(other.students_df) and \
|
||||
self.points_df.equals(other.points_df) and \
|
||||
self.his_header.equals(other.his_header) and \
|
||||
self.grade_1_0 == other.grade_1_0 and \
|
||||
self.grade_4_0 == other.grade_4_0 and \
|
||||
self.max_points == other.max_points and \
|
||||
self.created == other.created and \
|
||||
self.edited == other.edited
|
||||
|
||||
@field_serializer('students_df', 'points_df')
|
||||
def serialize_df(self, df: pd.DataFrame):
|
||||
return df.reset_index().to_json(orient=pandas_json_format)
|
||||
|
||||
@field_serializer('his_header')
|
||||
def serialize_his_header(self, df):
|
||||
return df.to_json(orient=pandas_json_format)
|
||||
|
||||
@field_serializer('created', 'edited')
|
||||
def serialize_datetime(self, dt):
|
||||
return dt.isoformat()
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class DB:
|
||||
def __init__(self, db_path='db.json'):
|
||||
self.db_path = Path(db_path)
|
||||
self.db = TinyDB(self.db_path, access_mode='r+')
|
||||
|
||||
def create_session(self):
|
||||
token = secrets.token_urlsafe(16)
|
||||
self.db.insert({'token': token})
|
||||
|
||||
return Session(**{
|
||||
'token': token,
|
||||
'students_df': pd.DataFrame(),
|
||||
'points_df': pd.DataFrame(),
|
||||
'his_header': pd.DataFrame(),
|
||||
})
|
||||
|
||||
def get_session(self, token: str) -> Session:
|
||||
results = self.db.search(Query().token == token)
|
||||
|
||||
if len(results) == 0:
|
||||
raise ValueError(f'Session with token {token} not found')
|
||||
|
||||
session = results[0].copy()
|
||||
session['points_df'] = pd.read_json(StringIO(session['points_df']), dtype=dtypes.students_table, orient=pandas_json_format).set_index('Matrikelnummer')
|
||||
session['students_df'] = pd.read_json(StringIO(session['students_df']), dtype=dtypes.students_table, orient=pandas_json_format).set_index('Matrikelnummer')
|
||||
session['his_header'] = pd.read_json(StringIO(session['his_header']), dtype=dtypes.his_header, orient=pandas_json_format)
|
||||
return Session(**session)
|
||||
|
||||
def save_session(self, session: Session):
|
||||
write_session = session.model_dump()
|
||||
|
||||
self.db.upsert(write_session, Query().token == write_session['token'])
|
||||
|
||||
def delete_session(self, session: Session):
|
||||
self.db.remove(Query().token == session.token)
|
||||
|
||||
def list_tokens(self):
|
||||
return [session['token'] for session in self.db.all()]
|
||||
@@ -0,0 +1,81 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
pandas_json_format = 'table'
|
||||
|
||||
|
||||
class dtypes:
|
||||
students_table = {'Examplan.id': 'int64', 'PrüfungsNr.': 'int64', 'Titel': 'O', 'Nachname': 'O', 'Vorname': 'O', 'Leistung': 'O', 'Versuch': 'int64', 'Status': 'O', 'ECTS': 'O', 'Semester': 'O', 'Jahr': 'int64', 'Prüfungsperiode': 'int64', 'Vermerk': 'O', 'Thema': 'O', 'Beginn': 'O', 'Gepl. Ende': 'O', 'Tatsächl. Ende': 'O', 'Prüfungsart': 'O', 'Prüfungsform': 'O', 'LockVersion': 'O'}
|
||||
his_header = 'O'
|
||||
|
||||
|
||||
def read_excel(bytes: BytesIO) -> tuple[pd.DataFrame, pd.DataFrame]:
|
||||
try:
|
||||
excelfile = pd.ExcelFile(bytes, engine='openpyxl')
|
||||
except:
|
||||
return pd.DataFrame()
|
||||
|
||||
full_table = pd.read_excel(excelfile, header=None)
|
||||
|
||||
# Finde Indizes der Zeilen mit den Werten "startHISsheet" und "endHISsheet"
|
||||
def find_row_with_value(df, value):
|
||||
mask = full_table.iloc[:, 0] == value
|
||||
if not mask.any():
|
||||
raise ValueError(f'{value} nicht gefunden')
|
||||
return mask.idxmax()
|
||||
|
||||
start_idx = find_row_with_value(full_table, 'startHISsheet')
|
||||
end_idx = find_row_with_value(full_table, 'endHISsheet')
|
||||
|
||||
# Lade den HISinOne Header (die ersten start_idx+1 Zeilen)
|
||||
his_header = pd.read_excel(excelfile, nrows=start_idx+1, header=None)
|
||||
|
||||
# Lade Studentendaten (zwischen start_idx+1 und end_idx)
|
||||
students_table = pd.read_excel(excelfile,
|
||||
header=start_idx + 1,
|
||||
nrows=end_idx - start_idx - 2,
|
||||
index_col='Matrikelnummer',
|
||||
dtype=dtypes.students_table)
|
||||
|
||||
students_table = students_table.replace({np.nan: None})
|
||||
his_header = his_header.replace({np.nan: None})
|
||||
|
||||
return students_table, his_header
|
||||
|
||||
|
||||
def prepare_table(students_table: pd.DataFrame) -> pd.DataFrame:
|
||||
|
||||
# Erzeuge Tabelle zur Punkteeingabe
|
||||
points_table = students_table
|
||||
|
||||
new_columns = {'Punkte': np.nan, 'Note': np.nan}
|
||||
points_table = points_table.assign(**new_columns)
|
||||
|
||||
return points_table
|
||||
|
||||
|
||||
def export(edited_df: pd.DataFrame, his_header: pd.DataFrame, exercise_columns: list[str], grades_table: pd.DataFrame) -> BytesIO:
|
||||
edited_df['Leistung'] = edited_df['Note'].replace({np.nan: 'NT'})
|
||||
export_students_table = edited_df.drop(columns=['Punkte', 'Note']+exercise_columns).reset_index()
|
||||
|
||||
# Examplan.id muss erste Spalte sein (HISinOne prüft Spaltennamen nicht)
|
||||
export_students_table = export_students_table.reindex(columns=['Examplan.id'] + [col for col in export_students_table.columns if col != 'Examplan.id'])
|
||||
|
||||
export_full_table = pd.DataFrame(
|
||||
np.vstack([
|
||||
his_header.values, # HISinOne Header
|
||||
np.array(list(export_students_table)), # Tabellenheader (Matrikelnummer, Nachname, Vorname, ...)
|
||||
export_students_table.values, # Daten
|
||||
np.array(['endHISsheet'] + [''] * (len(list(export_students_table))-1)) # HISinOne Footer
|
||||
]),
|
||||
columns=export_students_table.columns)
|
||||
|
||||
# Exportiere Tabelle
|
||||
bytesio = BytesIO()
|
||||
with pd.ExcelWriter(bytesio, engine='openpyxl') as writer:
|
||||
export_full_table.to_excel(writer, sheet_name='First Sheet', header=False, index=False) # HISinOne Tabelle
|
||||
edited_df[['Nachname', 'Vorname', 'Punkte', 'Note']+exercise_columns].to_excel(writer, sheet_name='Aufgaben') # Punktergebnisse als extra Sheet
|
||||
grades_table.to_excel(writer, sheet_name='Notenschlüssel', header=False, index=False) # Notenberechnung als extra Sheet
|
||||
return bytesio
|
||||
Reference in New Issue
Block a user