include all past assessments

This commit is contained in:
StroepWafel
2026-01-22 18:58:03 +10:30
parent 705c106da8
commit 39f8cb1634
@@ -1,5 +1,4 @@
import type { Job, IndexItem } from "../types"; import type { Job, IndexItem } from "../types";
import { htmlToPlainText } from "../utils";
const fetchJSON = async (url: string, body: any) => { const fetchJSON = async (url: string, body: any) => {
const res = await fetch(`${location.origin}${url}`, { const res = await fetch(`${location.origin}${url}`, {
@@ -16,7 +15,8 @@ const fetchUpcomingAssessments = async (student: number = 69) => {
const res = await fetchJSON("/seqta/student/assessment/list/upcoming?", { const res = await fetchJSON("/seqta/student/assessment/list/upcoming?", {
student, student,
}); });
return res.payload || []; // Match analytics.rs: payload is an array, return empty array if not found
return Array.isArray(res.payload) ? res.payload : [];
} catch (e) { } catch (e) {
console.error("[Assignments job] Failed to fetch upcoming assessments:", e); console.error("[Assignments job] Failed to fetch upcoming assessments:", e);
return []; return [];
@@ -38,68 +38,53 @@ const fetchSubjects = async () => {
const fetchPastAssessments = async (student: number = 69, subjects: any[]) => { const fetchPastAssessments = async (student: number = 69, subjects: any[]) => {
const map: Record<number, any> = {}; const map: Record<number, any> = {};
for (const subject of subjects) { // Fetch past assessments for all subjects in parallel (like assessmentsOverview does)
try { // This is much faster than sequential fetching
const res = await fetchJSON("/seqta/student/assessment/list/past?", { await Promise.all(
student, subjects.map(async (subject) => {
metaclass: subject.metaclass, try {
programme: subject.programme, // Match analytics.rs exactly: parameter order is programme, metaclass, student
}); const res = await fetchJSON("/seqta/student/assessment/list/past?", {
programme: subject.programme,
metaclass: subject.metaclass,
student,
});
// Past assessments API returns data in payload.tasks, not payload directly // Past assessments API can return data in payload.tasks OR payload.pending (or both)
if (res.payload && res.payload.tasks && Array.isArray(res.payload.tasks)) { // Based on analytics.rs fetch_past_assessments, we need to check both arrays
res.payload.tasks.forEach((assessment: any) => { const processAssessment = (assessment: any) => {
if (assessment && assessment.id) { if (assessment && assessment.id) {
// Ensure programme and metaclass are included from the subject // Ensure programme and metaclass are included from the subject
// Use the assessment's IDs if available, otherwise fall back to subject's
map[assessment.id] = { map[assessment.id] = {
...assessment, ...assessment,
programme: assessment.programme || assessment.programmeID || subject.programme, programme: assessment.programme || assessment.programmeID || subject.programme,
programmeID: assessment.programmeID || assessment.programme || subject.programme,
metaclass: assessment.metaclass || assessment.metaclassID || subject.metaclass, metaclass: assessment.metaclass || assessment.metaclassID || subject.metaclass,
metaclassID: assessment.metaclassID || assessment.metaclass || subject.metaclass,
}; };
} }
}); };
} else if (res.payload && Array.isArray(res.payload)) {
// Fallback: some APIs might return array directly // Match analytics.rs: Check both pending and tasks arrays
res.payload.forEach((assessment: any) => { // Check for pending array first (matching Rust code order)
if (assessment && assessment.id) { if (res.payload?.pending && Array.isArray(res.payload.pending)) {
map[assessment.id] = { res.payload.pending.forEach(processAssessment);
...assessment, }
programme: assessment.programme || assessment.programmeID || subject.programme,
metaclass: assessment.metaclass || assessment.metaclassID || subject.metaclass, // Check for tasks array
}; if (res.payload?.tasks && Array.isArray(res.payload.tasks)) {
} res.payload.tasks.forEach(processAssessment);
}); }
} catch (e) {
console.warn(`[Assignments job] Failed to fetch past assessments for subject ${subject.code || subject.subject || 'unknown'}:`, e);
} }
} catch (e) { })
console.warn(`[Assignments job] Failed to fetch past assessments for subject ${subject.code}:`, e); );
}
}
return Object.values(map); return Object.values(map);
}; };
const fetchAssessmentDetails = async (
assessmentId: number,
metaclassId: number,
programmeId: number,
): Promise<string | null> => {
try {
const res = await fetchJSON("/seqta/student/assessment/view?", {
id: assessmentId,
metaclass: metaclassId,
programme: programmeId,
});
if (res.payload && res.payload.description) {
return htmlToPlainText(res.payload.description);
}
return null;
} catch (e) {
console.warn(`[Assignments job] Failed to fetch details for assessment ${assessmentId}:`, e);
return null;
}
};
export const assignmentsJob: Job = { export const assignmentsJob: Job = {
id: "assignments", id: "assignments",
label: "Assignments", label: "Assignments",
@@ -136,21 +121,28 @@ export const assignmentsJob: Job = {
}, },
run: async (ctx) => { run: async (ctx) => {
const existingIds = new Set( // Don't filter by existing IDs - we want to process ALL assessments (both new and old)
(await ctx.getStoredItems("assignments")).map((i) => i.id), // to ensure metadata is up-to-date and all past assignments are indexed
); const existingItems = await ctx.getStoredItems("assignments");
const existingIds = new Set(existingItems.map((i) => i.id));
const student = 69; // TODO: Get from context if available const student = 69; // TODO: Get from context if available
console.debug("[Assignments job] Starting indexing - fetching all assessments (upcoming and past)...");
// Fetch data in parallel // Fetch data in parallel
const [upcoming, subjects] = await Promise.all([ const [upcoming, subjects] = await Promise.all([
fetchUpcomingAssessments(student), fetchUpcomingAssessments(student),
fetchSubjects(), fetchSubjects(),
]); ]);
// Fetch past assessments console.debug(`[Assignments job] Fetched ${upcoming.length} upcoming assessments and ${subjects.length} subjects`);
// Fetch past assessments for ALL subjects to ensure we get all historical assignments
const past = await fetchPastAssessments(student, subjects); const past = await fetchPastAssessments(student, subjects);
console.debug(`[Assignments job] Fetched ${past.length} past assessments`);
// Create a lookup map from subject code to programme/metaclass // Create a lookup map from subject code to programme/metaclass
const subjectLookup = new Map<string, { programme: number; metaclass: number }>(); const subjectLookup = new Map<string, { programme: number; metaclass: number }>();
subjects.forEach((s: any) => { subjects.forEach((s: any) => {
@@ -233,9 +225,8 @@ export const assignmentsJob: Job = {
// Skip fetching assessment details - the API endpoint doesn't exist or returns 404 // Skip fetching assessment details - the API endpoint doesn't exist or returns 404
// Details are optional and not critical for search functionality // Details are optional and not critical for search functionality
const detailPromises = new Map<string, Promise<string | null>>();
// Process all assessments // Process ALL assessments (both upcoming and past) to ensure everything is indexed
for (let i = 0; i < assessmentArray.length; i += batchSize) { for (let i = 0; i < assessmentArray.length; i += batchSize) {
const batch = assessmentArray.slice(i, i + batchSize); const batch = assessmentArray.slice(i, i + batchSize);
@@ -250,8 +241,8 @@ export const assignmentsJob: Job = {
processedIds.add(id); processedIds.add(id);
// Process all assessments (both new and existing) to ensure metadata is up-to-date // Process ALL assessments (both new and existing, upcoming and past)
// The indexer's merge logic will handle updates properly // This ensures all historical assignments are indexed and metadata is up-to-date
// Skip fetching details - API endpoint doesn't exist // Skip fetching details - API endpoint doesn't exist
const description = ""; const description = "";
@@ -364,13 +355,14 @@ export const assignmentsJob: Job = {
}, },
purge: (items) => { purge: (items) => {
const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1000; // Keep ALL assignments - don't purge old ones as users may want to search for them
// Only remove items that are truly invalid (missing required metadata)
return items.filter((i) => { return items.filter((i) => {
// Keep upcoming assignments and assignments from the last year // Keep all items that have valid metadata
if (i.metadata.isUpcoming) { return i.metadata &&
return true; i.metadata.assessmentId &&
} i.metadata.programmeId !== undefined &&
return i.dateAdded >= oneYearAgo; i.metadata.metaclassId !== undefined;
}); });
}, },
}; };