Skip to content

15. Attendance & Reports ​

In this chapter, we will build attendance recording and reporting endpoints using ReportingService, compute course completion rates, and generate downloadable CSV exports.


1. Implementing Reporting Routes (src/routes/reports.ts) ​

Create src/routes/reports.ts:

ts
import { Hono } from "hono";
import { reportingService } from "../lib/services";
import { toCsv, computeCompletionPercent } from "discendo-sdk/reporting";
import { requireAuth } from "../lib/middleware";
import { handleDomainError } from "../lib/errors";

export const reportRoutes = new Hono();

// 1. Take Attendance Roll Call for a Session
reportRoutes.post("/sessions/:sessionId/attendance", requireAuth, async (c) => {
  const actorId = c.get("actorId");
  const sessionId = c.req.param("sessionId");
  const { marks } = await c.req.json(); // Array of { userId, status: 'present' | 'absent' | 'late' }

  try {
    await reportingService.recordAttendance(sessionId, marks, { actorId });
    return c.json({ ok: true, recordedCount: marks.length });
  } catch (err) {
    return handleDomainError(c, err);
  }
});

// 2. Student Attendance Summary
reportRoutes.get(
  "/sections/:sectionId/attendance/students/:userId",
  requireAuth,
  async (c) => {
    const actorId = c.get("actorId");
    const sectionId = c.req.param("sectionId");
    const userId = c.req.param("userId");

    try {
      const summary = await reportingService.attendanceForStudent(
        sectionId,
        userId,
        { actorId },
      );
      return c.json({ ok: true, summary });
    } catch (err) {
      return handleDomainError(c, err);
    }
  },
);

// 3. Export Section Gradebook / Roster to CSV
reportRoutes.get(
  "/sections/:sectionId/export/grades.csv",
  requireAuth,
  async (c) => {
    const actorId = c.get("actorId");
    const sectionId = c.req.param("sectionId");

    try {
      // 1. Fetch student grade summary data
      const data = [
        {
          id: "u-alice",
          name: "Alice Smith",
          percentage: "94.5%",
          letter: "A",
          status: "Active",
        },
        {
          id: "u-bob",
          name: "Bob Jones",
          percentage: "82.0%",
          letter: "B",
          status: "Active",
        },
        {
          id: "u-carol",
          name: "Carol Danvers",
          percentage: "78.5%",
          letter: "C",
          status: "Active",
        },
      ];

      // 2. Format with toCsv utility
      const csvContent = toCsv(data, {
        columns: [
          { key: "id", header: "Student ID" },
          { key: "name", header: "Full Name" },
          { key: "percentage", header: "Final Grade" },
          { key: "letter", header: "Letter Grade" },
          { key: "status", header: "Roster Status" },
        ],
      });

      // 3. Return as downloadable file
      return c.text(csvContent, 200, {
        "Content-Type": "text/csv; charset=utf-8",
        "Content-Disposition": `attachment; filename="grades-${sectionId}.csv"`,
      });
    } catch (err) {
      return handleDomainError(c, err);
    }
  },
);

2. Calculating Student Completion ​

Track student progress through mandatory lessons using computeCompletionPercent:

ts
import { computeCompletionPercent } from "discendo-sdk/reporting";

const percent = computeCompletionPercent({
  completedNodeIds: ["node-1", "node-2"],
  totalRequiredNodeIds: ["node-1", "node-2", "node-3", "node-4"],
});

console.log(`Progress: ${percent}%`); // 50%

Next Steps ​

Next, we will capture compliance and security audit trails in Chapter 16: Audit Log.

Released under the GPL-3.0 License.