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 { htmlToPlainText } from "../utils";
const fetchJSON = async (url: string, body: any) => {
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?", {
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) {
console.error("[Assignments job] Failed to fetch upcoming assessments:", e);
return [];
@@ -38,68 +38,53 @@ const fetchSubjects = async () => {
const fetchPastAssessments = async (student: number = 69, subjects: any[]) => {
const map: Record<number, any> = {};
for (const subject of subjects) {
try {
const res = await fetchJSON("/seqta/student/assessment/list/past?", {
student,
metaclass: subject.metaclass,
programme: subject.programme,
});
// Fetch past assessments for all subjects in parallel (like assessmentsOverview does)
// This is much faster than sequential fetching
await Promise.all(
subjects.map(async (subject) => {
try {
// 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
if (res.payload && res.payload.tasks && Array.isArray(res.payload.tasks)) {
res.payload.tasks.forEach((assessment: any) => {
// Past assessments API can return data in payload.tasks OR payload.pending (or both)
// Based on analytics.rs fetch_past_assessments, we need to check both arrays
const processAssessment = (assessment: any) => {
if (assessment && assessment.id) {
// 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] = {
...assessment,
programme: assessment.programme || assessment.programmeID || subject.programme,
programmeID: assessment.programmeID || assessment.programme || subject.programme,
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
res.payload.forEach((assessment: any) => {
if (assessment && assessment.id) {
map[assessment.id] = {
...assessment,
programme: assessment.programme || assessment.programmeID || subject.programme,
metaclass: assessment.metaclass || assessment.metaclassID || subject.metaclass,
};
}
});
};
// Match analytics.rs: Check both pending and tasks arrays
// Check for pending array first (matching Rust code order)
if (res.payload?.pending && Array.isArray(res.payload.pending)) {
res.payload.pending.forEach(processAssessment);
}
// 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);
};
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 = {
id: "assignments",
label: "Assignments",
@@ -136,21 +121,28 @@ export const assignmentsJob: Job = {
},
run: async (ctx) => {
const existingIds = new Set(
(await ctx.getStoredItems("assignments")).map((i) => i.id),
);
// Don't filter by existing IDs - we want to process ALL assessments (both new and old)
// 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
console.debug("[Assignments job] Starting indexing - fetching all assessments (upcoming and past)...");
// Fetch data in parallel
const [upcoming, subjects] = await Promise.all([
fetchUpcomingAssessments(student),
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);
console.debug(`[Assignments job] Fetched ${past.length} past assessments`);
// Create a lookup map from subject code to programme/metaclass
const subjectLookup = new Map<string, { programme: number; metaclass: number }>();
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
// 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) {
const batch = assessmentArray.slice(i, i + batchSize);
@@ -250,8 +241,8 @@ export const assignmentsJob: Job = {
processedIds.add(id);
// Process all assessments (both new and existing) to ensure metadata is up-to-date
// The indexer's merge logic will handle updates properly
// Process ALL assessments (both new and existing, upcoming and past)
// This ensures all historical assignments are indexed and metadata is up-to-date
// Skip fetching details - API endpoint doesn't exist
const description = "";
@@ -364,13 +355,14 @@ export const assignmentsJob: Job = {
},
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) => {
// Keep upcoming assignments and assignments from the last year
if (i.metadata.isUpcoming) {
return true;
}
return i.dateAdded >= oneYearAgo;
// Keep all items that have valid metadata
return i.metadata &&
i.metadata.assessmentId &&
i.metadata.programmeId !== undefined &&
i.metadata.metaclassId !== undefined;
});
},
};