test : changed test format + added playwright support
Check Deno code / Check Deno code (pull_request) Has been cancelled
Tests / Unit tests (pull_request) Has been cancelled
Tests / Integration tests (pull_request) Has been cancelled
Check Deno code / Check Deno code (push) Has been cancelled
Tests / Unit tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Check Deno code / Check Deno code (pull_request) Has been cancelled
Tests / Unit tests (pull_request) Has been cancelled
Tests / Integration tests (pull_request) Has been cancelled
Check Deno code / Check Deno code (push) Has been cancelled
Tests / Unit tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
This commit was merged in pull request #153.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Logique métier pour le calcul des notes et moyennes.
|
||||
*/
|
||||
|
||||
export interface Note {
|
||||
note: number;
|
||||
noteSession2: number | null;
|
||||
}
|
||||
|
||||
export interface UEModule {
|
||||
idModule: string;
|
||||
coeff: number;
|
||||
}
|
||||
|
||||
export interface Ajustement {
|
||||
valeur: number;
|
||||
malus: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la note effective (Session 2 si présente, sinon Session 1).
|
||||
*/
|
||||
export function getEffectiveNote(n: Note): number {
|
||||
return n.noteSession2 ?? n.note;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule la moyenne pondérée d'une liste de modules.
|
||||
* Retourne null si aucun module n'est noté.
|
||||
*/
|
||||
export function calculateWeightedAverage(
|
||||
ueModules: UEModule[],
|
||||
notesMap: Record<string, Note>,
|
||||
): number | null {
|
||||
let weightedSum = 0;
|
||||
let coveredCoeff = 0;
|
||||
|
||||
for (const um of ueModules) {
|
||||
const noteObj = notesMap[um.idModule];
|
||||
if (noteObj) {
|
||||
const val = getEffectiveNote(noteObj);
|
||||
weightedSum += val * um.coeff;
|
||||
coveredCoeff += um.coeff;
|
||||
}
|
||||
}
|
||||
|
||||
if (coveredCoeff === 0) return null;
|
||||
return weightedSum / coveredCoeff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique l'ajustement et le malus à une moyenne.
|
||||
* L'ajustement REMPLACE la moyenne calculée si présent.
|
||||
*/
|
||||
export function applyAjustement(
|
||||
calculatedAvg: number | null,
|
||||
ajustement: Ajustement | null,
|
||||
): number | null {
|
||||
let finalAvg = calculatedAvg;
|
||||
|
||||
if (ajustement) {
|
||||
// L'ajustement remplace la moyenne
|
||||
finalAvg = ajustement.valeur;
|
||||
if (ajustement.malus > 0) {
|
||||
finalAvg = (finalAvg ?? 0) - ajustement.malus;
|
||||
}
|
||||
}
|
||||
|
||||
return finalAvg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arrondit une note à 2 décimales.
|
||||
*/
|
||||
export function roundGrade(grade: number): number {
|
||||
return Math.round(grade * 100) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate une note pour l'affichage (2 décimales).
|
||||
*/
|
||||
export function formatGrade(grade: number | null): string {
|
||||
if (grade === null) return "—";
|
||||
return grade.toFixed(2);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// @deno-types="https://cdn.sheetjs.com/xlsx-0.20.3/package/types/index.d.ts"
|
||||
import * as XLSX from "https://cdn.sheetjs.com/xlsx-0.20.3/package/xlsx.mjs";
|
||||
|
||||
export type ParsedUE = {
|
||||
code: string | null;
|
||||
name: string;
|
||||
ects: number | null;
|
||||
modules: ParsedModule[];
|
||||
};
|
||||
|
||||
export type ParsedModule = {
|
||||
code: string;
|
||||
name: string;
|
||||
coeff: number;
|
||||
};
|
||||
|
||||
export type ParsedYear = {
|
||||
label: string;
|
||||
ues: ParsedUE[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Analyse un classeur Excel pour en extraire la maquette pédagogique.
|
||||
*/
|
||||
export function parseMaquette(workbook: XLSX.WorkBook): ParsedYear[] {
|
||||
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
const rows = XLSX.utils.sheet_to_json<(string | number | null)[]>(sheet, {
|
||||
header: 1,
|
||||
});
|
||||
|
||||
const years: ParsedYear[] = [];
|
||||
let currentYear: ParsedYear | null = null;
|
||||
let currentUE: ParsedUE | null = null;
|
||||
let moduleIndex = 0;
|
||||
|
||||
for (const row of rows) {
|
||||
if (!row || row.length === 0) continue;
|
||||
|
||||
const col0 = row[0] != null ? String(row[0]).trim() : "";
|
||||
|
||||
// Detect year row: INFO3A, INFO4A, INFO5A, INFOAPP3A, INFOAPP4A, etc.
|
||||
if (/^(INFO|INFOAPP)\s*\d+A$/i.test(col0)) {
|
||||
currentYear = { label: col0, ues: [] };
|
||||
years.push(currentYear);
|
||||
currentUE = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect UE row: col[0] === "UE" or starts with "UE" (e.g., "UE51")
|
||||
if (col0 === "UE" || (col0.startsWith("UE") && /^UE\d/.test(col0))) {
|
||||
const ueCode = row[1] != null ? String(row[1]).trim() : null;
|
||||
const ueName = row[2] != null ? String(row[2]).trim() : "UE sans nom";
|
||||
const ects = typeof row[4] === "number" ? row[4] : null;
|
||||
|
||||
currentUE = { code: ueCode, name: ueName, ects, modules: [] };
|
||||
if (currentYear) {
|
||||
currentYear.ues.push(currentUE);
|
||||
} else {
|
||||
// No year detected yet — create a default one
|
||||
currentYear = { label: "Maquette", ues: [currentUE] };
|
||||
years.push(currentYear);
|
||||
}
|
||||
moduleIndex = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect semester header rows — just skip, don't reset UE
|
||||
if (/^SEM\s*\d/i.test(col0)) {
|
||||
currentUE = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect module row: inside a UE, col[3] has a name, col[5] is a number (coeff)
|
||||
if (currentUE && row[3] != null && typeof row[5] === "number") {
|
||||
const modName = String(row[3]).trim();
|
||||
if (!modName) continue;
|
||||
|
||||
let modCode = row[1] != null ? String(row[1]).trim() : "";
|
||||
if (!modCode) {
|
||||
const uePrefix = (currentUE.code || "MOD").replace(/[^A-Z0-9]/gi, "");
|
||||
modCode = `${uePrefix}_${String(moduleIndex).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
currentUE.modules.push({ code: modCode, name: modName, coeff: row[5] });
|
||||
moduleIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return years;
|
||||
}
|
||||
Reference in New Issue
Block a user