use std::{collections::HashMap, num::ParseIntError, str::FromStr};
use serde::{
Deserialize, Deserializer, Serialize,
de::{IntoDeserializer, value::MapDeserializer},
};
use crate::{
application::utils::de_with::{
deserialize_empty, deserialize_f32_string, deserialize_semester_type,
deserialize_u32_string,
},
model::SemesterType,
};
use wdpe::element::parser::ElementParser;
use wdpe::{
element::{complex::sap_table::FromSapTable, definition::ElementDefinition},
error::{ElementError, WebDynproError},
};
#[derive(Serialize, Deserialize, Debug)]
#[allow(unused)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct GradeSummary {
attempted_credits: f32,
earned_credits: f32,
grade_points_sum: f32,
grade_points_average: f32,
arithmetic_mean: f32,
pf_earned_credits: f32,
}
impl GradeSummary {
pub(crate) fn new(
attempted_credits: f32,
earned_credits: f32,
gpa: f32,
cgpa: f32,
avg: f32,
pf_earned_credits: f32,
) -> GradeSummary {
GradeSummary {
attempted_credits,
earned_credits,
grade_points_sum: gpa,
grade_points_average: cgpa,
arithmetic_mean: avg,
pf_earned_credits,
}
}
pub fn attempted_credits(&self) -> f32 {
self.attempted_credits
}
pub fn earned_credits(&self) -> f32 {
self.earned_credits
}
pub fn grade_points_sum(&self) -> f32 {
self.grade_points_sum
}
pub fn grade_points_average(&self) -> f32 {
self.grade_points_average
}
pub fn arithmetic_mean(&self) -> f32 {
self.arithmetic_mean
}
pub fn pf_earned_credits(&self) -> f32 {
self.pf_earned_credits
}
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(unused)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct SemesterGrade {
#[serde(
rename(deserialize = "학년도"),
deserialize_with = "deserialize_u32_string"
)]
year: u32,
#[serde(
rename(deserialize = "학기"),
deserialize_with = "deserialize_semester_type"
)]
semester: SemesterType,
#[serde(
rename(deserialize = "신청학점"),
deserialize_with = "deserialize_f32_string"
)]
attempted_credits: f32,
#[serde(
rename(deserialize = "취득학점"),
deserialize_with = "deserialize_f32_string"
)]
earned_credits: f32,
#[serde(
rename(deserialize = "P/F학점"),
deserialize_with = "deserialize_f32_string"
)]
pf_earned_credits: f32,
#[serde(
rename(deserialize = "평점평균"),
deserialize_with = "deserialize_f32_string"
)]
grade_points_average: f32,
#[serde(
rename(deserialize = "평점계"),
deserialize_with = "deserialize_f32_string"
)]
grade_points_sum: f32,
#[serde(
rename(deserialize = "산술평균"),
deserialize_with = "deserialize_f32_string"
)]
arithmetic_mean: f32,
#[serde(
rename(deserialize = "학기별석차"),
deserialize_with = "deserialize_rank"
)]
semester_rank: (u32, u32),
#[serde(
rename(deserialize = "전체석차"),
deserialize_with = "deserialize_rank"
)]
general_rank: (u32, u32),
#[serde(
rename(deserialize = "학사경고"),
default = "return_false",
deserialize_with = "deserialize_empty"
)]
academic_probation: bool,
#[serde(
rename(deserialize = "상담여부"),
deserialize_with = "deserialize_empty"
)]
consult: bool,
#[serde(rename(deserialize = "유급"), deserialize_with = "deserialize_empty")]
flunked: bool,
}
fn return_false() -> bool {
false
}
fn deserialize_rank<'de, D: Deserializer<'de>>(deserializer: D) -> Result<(u32, u32), D::Error> {
let value = String::deserialize(deserializer)?;
let mut spl = value.split("/");
let first: u32 = spl
.next()
.ok_or_else(|| serde::de::Error::custom("input rank is invalid"))?
.parse()
.map_err(|_e| serde::de::Error::custom("input rank is not a number"))?;
let second: u32 = spl
.next()
.ok_or_else(|| serde::de::Error::custom("input rank is invalid"))?
.parse()
.map_err(|_e| serde::de::Error::custom("input rank is not a number"))?;
Ok((first, second))
}
impl SemesterGrade {
pub fn year(&self) -> u32 {
self.year
}
pub fn semester(&self) -> SemesterType {
self.semester
}
pub fn earned_credits(&self) -> f32 {
self.earned_credits
}
pub fn pf_earned_credits(&self) -> f32 {
self.pf_earned_credits
}
pub fn grade_points_average(&self) -> f32 {
self.grade_points_average
}
pub fn grade_points_sum(&self) -> f32 {
self.grade_points_sum
}
pub fn arithmetic_mean(&self) -> f32 {
self.arithmetic_mean
}
pub fn semester_rank(&self) -> (u32, u32) {
self.semester_rank
}
pub fn general_rank(&self) -> (u32, u32) {
self.general_rank
}
pub fn academic_probation(&self) -> bool {
self.academic_probation
}
pub fn consult(&self) -> bool {
self.consult
}
pub fn flunked(&self) -> bool {
self.flunked
}
}
impl<'body> FromSapTable<'body> for SemesterGrade {
fn from_table(
header: Option<&'body wdpe::element::complex::sap_table::SapTableHeader>,
row: &'body wdpe::element::complex::sap_table::SapTableRow,
parser: &'body ElementParser,
) -> Result<Self, WebDynproError> {
let map_string = row.try_row_into::<HashMap<String, String>>(header, parser)?;
let map_de: MapDeserializer<_, serde::de::value::Error> = map_string.into_deserializer();
Ok(
SemesterGrade::deserialize(map_de).map_err(|e| ElementError::InvalidContent {
element: row.table_def().id().to_string(),
content: e.to_string(),
})?,
)
}
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(unused)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct ClassGrade {
year: u32,
semester: SemesterType,
code: String,
class_name: String,
grade_points: f32,
score: ClassScore,
rank: String,
professor: String,
detail: Option<HashMap<String, f32>>,
}
impl ClassGrade {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
year: u32,
semester: SemesterType,
code: String,
class_name: String,
grade_points: f32,
score: ClassScore,
rank: String,
professor: String,
detail: Option<HashMap<String, f32>>,
) -> ClassGrade {
ClassGrade {
year,
semester,
code,
class_name,
grade_points,
score,
rank,
professor,
detail,
}
}
pub fn year(&self) -> u32 {
self.year
}
pub fn semester(&self) -> SemesterType {
self.semester
}
pub fn code(&self) -> &str {
self.code.as_ref()
}
pub fn class_name(&self) -> &str {
self.class_name.as_ref()
}
pub fn grade_points(&self) -> f32 {
self.grade_points
}
pub fn score(&self) -> ClassScore {
self.score
}
pub fn rank(&self) -> &str {
self.rank.as_ref()
}
pub fn professor(&self) -> &str {
self.professor.as_ref()
}
pub fn detail(&self) -> Option<&HashMap<String, f32>> {
self.detail.as_ref()
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[allow(unused)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum CourseType {
Phd, Master, PhdIntergrated, Research, Bachelor, }
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[allow(unused)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum ClassScore {
Pass,
Failed,
Score(u32),
Empty,
}
impl FromStr for ClassScore {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"P" => Self::Pass,
"F" => Self::Failed,
"" => Self::Empty,
_ => Self::Score(s.parse::<u32>()?),
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct GradesByClassification {
student_number: String,
student_name: String,
year_level: String,
college: String,
department: String,
major: String,
audit_date: String,
grades: Vec<ClassGradeItem>,
}
impl GradesByClassification {
pub fn student_number(&self) -> &str {
&self.student_number
}
pub fn student_name(&self) -> &str {
&self.student_name
}
pub fn year_level(&self) -> &str {
&self.year_level
}
pub fn college(&self) -> &str {
&self.college
}
pub fn department(&self) -> &str {
&self.department
}
pub fn major(&self) -> &str {
&self.major
}
pub fn audit_date(&self) -> &str {
&self.audit_date
}
pub fn grades(&self) -> &[ClassGradeItem] {
&self.grades
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct ClassGradeItem {
classification: String,
year: String,
semester: String,
course_code: String,
course_name: String,
credits: String,
score: String,
grade: String,
note: String,
}
impl ClassGradeItem {
pub fn classification(&self) -> &str {
&self.classification
}
pub fn year(&self) -> &str {
&self.year
}
pub fn semester(&self) -> &str {
&self.semester
}
pub fn course_code(&self) -> &str {
&self.course_code
}
pub fn course_name(&self) -> &str {
&self.course_name
}
pub fn credits(&self) -> &str {
&self.credits
}
pub fn score(&self) -> &str {
&self.score
}
pub fn grade(&self) -> &str {
&self.grade
}
pub fn note(&self) -> &str {
&self.note
}
}
use ozra::types::{DataSet, FieldValue};
use crate::{ApplicationError, RusaintError};
fn get_string_field(row: &[(String, FieldValue)], field_name: &str) -> String {
row.iter()
.find(|(name, _)| name == field_name)
.map(|(_, val)| val.to_string_repr())
.unwrap_or_default()
}
fn find_dataset<'a>(datasets: &'a [DataSet], name: &str) -> &'a [Vec<(String, FieldValue)>] {
datasets
.iter()
.find(|(n, _)| n == name)
.map(|(_, rows)| rows.as_slice())
.unwrap_or(&[])
}
impl GradesByClassification {
pub fn from_datasets(datasets: &[DataSet]) -> Result<Self, RusaintError> {
let master_rows = find_dataset(datasets, "Shadow_master");
let master = master_rows.first().ok_or_else(|| {
ApplicationError::OzDataFetchError(
"Shadow_master dataset is empty or missing in OZ response".to_string(),
)
})?;
let student_number = get_string_field(master, "ST_NO");
let student_name = get_string_field(master, "ST_NAME");
let year_level = get_string_field(master, "HUKNYUN").trim().to_string();
let college = get_string_field(master, "CLEG_O_STEXT");
let department = get_string_field(master, "SC_DEPT_STEXT");
let major = get_string_field(master, "SC_MAJO_STEXT");
let audit_date = get_string_field(master, "AUD_DATE");
let grades: Vec<ClassGradeItem> = find_dataset(datasets, "ITAB")
.iter()
.map(|row| ClassGradeItem {
classification: get_string_field(row, "COMPL_TEXT"),
year: get_string_field(row, "PERYR"),
semester: get_string_field(row, "HUKGI"),
course_code: get_string_field(row, "SM_ID"),
course_name: get_string_field(row, "SM_TEXT"),
credits: get_string_field(row, "CPATTEMP"),
score: get_string_field(row, "GRADESYMBOL"),
grade: get_string_field(row, "GRADE"),
note: get_string_field(row, "BIGO"),
})
.collect();
Ok(Self {
student_number,
student_name,
year_level,
college,
department,
major,
audit_date,
grades,
})
}
}