CoIFish Longitudinal Profile
Maintained by
Mathieu Pelletier
A local plugin that builds cross-course student profiles and lecturer performance analytics, extending the CoIFish grade report with institutional-level insights.
Price option: Free
Supports Moodle 5.0-5.2
See all versions
Latest release: 3 weeks ago
Installations: 8
Downloads (last 90 days): 200
Frankenstyle name:
local_coifish
Plugin type:
Local plugins
Versions
Current version
1.7.0 (2026061800)
Moodle 5.0, 5.1, 5.2
Uploaded on Friday, 26 June 2026 at 11:50 (GMT+0)
1.7.0
Plugin type: Local plugins
Frankenstyle component name: local_coifish
Version build number: 2026061800
Version release name: 1.7.0
Maturity: Beta
Supported Moodle versions: 5.0, 5.1, 5.2
Repository URL (Git): https://github.com/mkpelletier/moodle-local_coifish.git
Repository branch:
Repository tag:
Issue/bug tracker URL: https://github.com/mkpelletier/moodle-local_coifish/issues
Changelog
[1.7.0] - 2026-06-18
Changed
- Complete/incomplete (self-study) assignments are now auto-excluded from feedback analytics by a grade-type heuristic, replacing the purely manual flag. Scale-graded assignments (
assign.grade < 0) typically never receive written feedback, so they are treated as not feedback-relevant by default instead of dragging coverage down. The programme coordinator stays in control: the drill-down now shows every assignment (excluded ones greyed with an Auto-excluded / Excluded badge) rather than hiding them, and each row carries an inline override — a fa-ban icon (tooltip "Exclude") to drop a feedback-relevant assignment, and a fa-plus-square icon (tooltip "Include") to count an auto-excluded one anyway. Overrides are stored minimally as two config keys (feedback_excluded_cmids= forced out,feedback_included_cmids= forced back in); a choice matching the heuristic stores nothing.feedback_exclusionsnow exposesis_scale_graded(),get_included_cmids(), andapply_state()(returning the effective state), andis_excluded()reflects heuristic-plus-override. The "How is this determined?" methodology card documents the heuristic. - The drill-down web service annotates instead of filters.
get_assignment_feedbackreturns each row withscalegraded,excluded, andexcludedautoflags (sourced from the siblinggradereport_coifishbreakdown's newscalegradedfield) so the coordinator can see and reverse what the heuristic decided;toggle_feedback_exclusionreturns the effectiveexcluded/excludedautostate. The AMD module re-renders the toggled row in place from its client-side cache.
[1.6.0] - 2026-06-17
Added
- Coordinators can mark an assignment as "not feedback-relevant" directly from the lecturer-profile "Feedback by course" drill-down — for complete/incomplete self-study activities that never receive written feedback and would otherwise drag feedback coverage down. Each assignment row gains an inline "Not feedback-relevant" action (coordinators only, gated on
local/coifish:viewlecturerprofile); clicking it calls the new AJAX web servicelocal_coifish_toggle_feedback_exclusion(cmid, excluded)and removes the row. The exclusion is global per assignment and backed bylocal_coifish\feedback_exclusions(config keylocal_coifish/feedback_excluded_cmids);get_assignment_feedbacknow filters excluded cmids out of the drill-down before returning, and the siblinggradereport_coifishchange drops them from the feedback-coverage denominator.
[1.5.1] - 2026-06-17
Fixed
- Assignment drill-down rows now line up with the course-table columns. The per-assignment breakdown was rendered as a separate nested
<table>inside a full-width cell, so its columns auto-sized independently and drifted out of alignment with the course header row. The rows are now rendered as<tr>s in the same table (each course's detail is a collapsible<tbody>), so every column aligns with its heading. Assignment names are indented to keep the nested-under-course cue.
[1.5.0] - 2026-06-17
Added
- Feedback-by-course drill-down on the lecturer profile so a coordinator can see which courses make up a lecturer's composite feedback-quality score, rather than the number being opaque. A collapsible "Feedback by course" card lists each course (worst composite first) with its coverage/depth/quality/personalisation, composite, and with-feedback/graded counts, via
lecturer_api::get_feedback_breakdown()— one query over thegradereport_coifish_feedbackcache, scoped to the same in-scope courses (and course filter) as the rest of the profile, and consistent with the dilution fix (only courses withtotalgraded > 0). Read-only. - Lazy per-assignment expand. Each course row expands on demand to its per-assignment feedback breakdown, fetched once over a new AJAX web service
local_coifish_get_assignment_feedback(userid, courseid)and cached client-side per course (never eager-loaded). The web service guards\gradereport_coifish\report::get_assignment_feedback_breakdown()withclass_exists/method_existsand returns[]when the optional grade report is absent. New AMD modulelocal_coifish/feedback_breakdown.
Changed
- Feedback-quality scores are no longer diluted by ungraded courses. A lecturer's
avgfeedbackquality(and the coverage/depth/personalisation averages) now average only over courses where they actually graded something (totalgraded > 0), instead of being dragged toward zero by courses they hold a teaching role in but do not grade. Applied consistently across the cached profile, the filtered/range view, and the weekly trend snapshot. - Removed an N+1 query from the daily profile build.
build_lecturer_profilespreviously issued one forum-post count query per course inside the per-lecturer loop; it now uses a single grouped query per lecturer.
[1.4.3] - 2026-06-12
Changed
- Active-snapshot task is much lighter on the database.
build_active_snapshotsnow skips students whose snapshot is unchanged — it compares each student's last logstore activity and last gradebook change (two per-course aggregate queries) against the snapshot's compute time, and only recomputes the ones that actually moved. A jittered 7-day TTL forces a periodic rebuild so course-structure drift still self-heals, spread across days to avoid a synchronised spike. The run logsrefreshed N, skipped Mvia mtrace. - Per-course-invariant work hoisted out of the per-student loop. The expected-activity count and the forum-discussion list (previously re-queried for every student inside
capture_student_metrics) are now fetched once per course and passed in, removing two queries per student.capture_student_metricsgained optional$totalactivities/$discussionsparameters (callers that omit them are unaffected), and the discussion query is exposed asmetrics_helper::get_course_discussions().
[1.4.2] - 2026-06-12
Fixed
- The
course_categorysetting now applies to lecturer profiles. Previously the category limit was honoured only on the student/risk-overview side, so the lecturer profile (cached and filtered), the profile course dropdown, the weekly trend snapshots, and the per-course time report counted courses in every category (including dev/test courses outside the configured tree). Every lecturer-side course-selection query now appliesfilter_helper::get_category_scope_sql(), consistent with the risk overview. The time report additionally gained thelecturer_excluded_coursesfilter it was missing. - Student-outcome averaging no longer silently drops course rows.
build_lecturer_profiles()grouped the per-course outcome query by(courseid, courseenddate)while keying the result oncourseid, so a course with more than one end-date value lost rows (and skewed the average). Now grouped bycourseidalone withMAX(courseenddate)for trend ordering. - Filtered lecturer profiles no longer emit a
fullname()debugging notice.compute_lecturer_profile_for_range()now fetches the full set of user name fields, matching the other two profile paths.
[1.4.1] - 2026-06-12
Changed
- Grading turnaround now clocks from the first grade, not the last edit. All three turnaround sites (
lecturer_api::compute_lecturer_profile_for_range(),lecturer_api::build_lecturer_profiles(), andlecturer_period_snapshot::upsert()) now measure submission 'assign_grades.timecreatedinstead ofassign_grades.timemodified, so a grade correction made months later no longer retroactively inflates a lecturer's turnaround. The weekly snapshot's period bound is likewise on first-grade time. The shared expression matches thegradereport_coifishplugin for consistency. - Integrity referrals pause the turnaround clock. Where the optional
local_unifiedgrader_referraltable is present (Unified Grader installed), an item escalated for an academic-integrity review stops the lecturer's clock at the moment of referral (1-stamp model: submission ' escalation), so delays outside the lecturer's control are not counted against them. Every query that touches the table is guarded bytable_exists()and degrades gracefully to the first-grade base when it is absent.
Added
- Per-assignment turnaround drill-down via
lecturer_api::get_turnaround_breakdown()and a collapsible "Turnaround by assignment" card on the lecturer profile. Each row shows the raw (first-grade) vs adjusted (referral-paused) average days, the number graded, and a badge counting items held for integrity review. Assignments averaging more than 7 days are highlighted. Read-only.
[1.4.0] - 2026-06-09
Added
- Historical lecturer-activity trends with weekly per-lecturer caching. A new
local_coifish_lecturer_period_snapshottable holds one row per lecturer per ISO week. The existing daily task writes the current week's row; a new hourlybackfill_lecturer_snapshotstask fills missing past weeks at a bounded rate per run (default 50/run) until the configured horizon is met. Past weeks are written exactly once and never recomputed — read path on the lecturer profile is a single indexed SELECT, no log scans. - "Historical trends" card on the lecturer profile rendering inline SVG sparklines (no chart-library dependency) for feedback quality, grading turnaround, forum posts per week, intervention effectiveness, hours total, and average student grade. Each sparkline shows the latest value with a Δ% vs the window start.
- Admin setting
lecturer_backfill_years(default 3, range 1–10) controls how far back the historical backfill goes. - Dynamic paginated tables on Student Risk Overview and Lecturer list using Moodle's
core_table\dynamicAPI. AJAX pagination, sortable columns, and configurable page size. Summary count cards (high/moderate/total) move to a separate aggregate query so the numbers reflect the full filtered population rather than just the visible page. - Defensive hard cap (
MAX_RISK_OVERVIEW_ROWS = 1000) onapi::get_risk_overview()to guard programmatic / web-service callers against accidentally pulling unbounded result sets.
Changed
- Risk Overview filter dropdowns preserved as URL state but the table itself now refreshes via AJAX. Bookmarks with existing
?categoryid=…&risklevel=…&enrolstatus=…continue to work. metrics_helper::capture_student_metrics()signature gains a$starttimeparameter applied to the threelogstore_standard_logqueries (engagement, feedback view, self-regulation) plus theforum_postsandassign_gradestime-bounded supporting queries. Callers populate from the course start date.lecturer_api::estimate_activity_hours()signature changed from(uid, courseids, $now)to(uid, courseids, $timefrom, $timeto)with both bounds applied to the marking and communicationlogstore_standard_logqueries. Callers pass either the user-selected date range or a 120-day cron lookback. Each UNION branch in thelocal_unifiedgraderfallback now uses unique:tfromN/:ttoNplaceholders.- Cohort programme pattern resolution factored into
filter_helper::get_course_ids_matching_pattern(). Simple anchored patterns (^THE,^BIB2) translate to SQLLIKE 'THE%'and run server-side. Complex patterns fall back to a category-scoped PHP regex pass — never the wholecoursetable. Per-request memoisation cache included. - Renderables transitioned to the Output API. The CSV export form's PHP-echoed HTML moved to
templates/export_form.mustachewith a new\local_coifish\output\export_formrenderable. The cohort-programme admin setting's inline<script>and HTML moved totemplates/cohort_patterns_setting.mustache+amd/src/cohort_patterns_setting.jsloaded via$PAGE->requires->js_call_amd(). No remaining PHP-built HTML strings or inline JS in the plugin.
Fixed
- N+1 query patterns eliminated in seven hot paths by bulk-loading users and existing snapshot rows ahead of loops.
refreshcurrent.phppre-loads the student's existing active-snapshot rows;build_active_snapshots::refresh_course()does the same per-course;build_profiles::snapshot_course()pre-loads existing userids viaget_fieldset_select;api::get_early_warnings(),lecturer_api::get_all_lecturer_profiles(),local_coifish_generate_export_data(), andexternal\get_lecturer_time_reportall bulk-load user records using\core_user\fields::for_name(). The two lecturer-time call sites also bulk-load(lecturer, course)role assignments in one query. Query counts now constant per page render. logstore_standard_logqueries no longer scan unbounded history. All five queries in the plugin apply strict time bounds aligned with the table's shippedcourse-timeanduser-moduleindexes (both ending intimecreated), turning range scans into index seeks. Addresses peer-reviewer concern about production log-table size.get_lecturer_profile()no longer triggers Moodle 5.x "missing name fields" debugging notices. User record fetch now uses\core_user\fields::for_name()so all fullname-relevant columns (including phonetic and alternate names) are present on the object passed tofullname().
[1.3.4] - 2026-05-20
Changed
- Longitudinal feedback-review metric now counts Unified Grader feedback views alongside the native
mod_assignevents, via the shared\gradereport_coifish\report::get_feedback_view_event_sql()helper. Snapshots taken against courses that use UG for assignments / quizzes / forums / BBB no longer under-report student engagement with feedback.
Fixed
- Peer-review blockers: added GPL v3
LICENSEfile in the plugin root (was missing), and frankenstyle-prefixed the only global helpergenerate_export_data()'local_coifish_generate_export_data()inexport.phpto remove the namespace-collision risk flagged by the Moodle plugins-DB reviewer.
[1.3.3] - 2026-05-19
Fixed
- False "persistent / across courses" recommendations on single-course students.
social_isolationandfeedback_neglectrisk factors fire on the per-course average, so they correctly flag a one-course student with very low forum/feedback activity — but the recommendation text claimed a cross-course pattern that didn't exist yet.api::generate_recommendations()now selects a_singlewording variant ("In their recent course, …, watch whether the pattern repeats") whencoursescompleted < 2. Same treatment forintervention_positive/intervention_unresponsivebased ontotalinterventions < 2so a single intervention isn't described in the plural. Badge labelsrisk_factor_social_isolationandrisk_factor_feedback_neglectare also reworded to drop the "persistent / across courses" framing. - Snapshots for concluded courses no longer skew longitudinal data with post-course activity.
metrics_helper::capture_student_metrics()now accepts an$endtimeparameter; thebuild_profilestask passes$course->enddateso engagement views, forum posts, feedback views, assignment-grade windows, and grade-checks are clamped at course end when the snapshot is written.build_active_snapshotsis unchanged — it already excludes concluded courses. - Engagement metric in longitudinal snapshots now honours grade-category drop/keep rules via the shared
\gradereport_coifish\report::get_expected_activity_count()helper. Optional assignments/quizzes in "best N of M" or "drop lowest N" categories no longer inflate the engagement denominator. - install.xml now declares the XMLDB schema namespace (resolves
core\db\plugin_checks_test::test_db_install_filefailure).
[1.3.2] - 2026-05-13
Added
- Enrolment-status filter on Student Risk Overview — new dropdown with three options: "Currently enrolled" (default), "All", and "No current enrolment". Reduces noise from students on a study break, and surfaces re-engagement opportunities. Sourced directly from
user_enrolmentsso it works even before the active-snapshot task has run.
Fixed
- Empty Current Enrolments card for new-term students —
capture_student_metricsnow returns a row withgrade = null(instead of bailing out) when a student has nograde_gradesentry yet or when the course has nograde_itemsrow of typecourse. The active-snapshot task and the on-demand refresh both write the row in this case. Post-coursebuild_profilescontinues to skip students without a final grade. - Non-academic courses leaking into current-enrolment queries — the existing
local_coifish/course_category"Limit course matching to category" admin setting is now honoured by the active-snapshot task, the on-demand refresh, the Current Enrolments display, and the "Currently enrolled" filter on the Risk Overview. A new helperfilter_helper::get_category_scope_sql()centralises the include logic. - Stale active-snapsh