Initial report

This commit is contained in:
ivan-pelly
2026-03-29 18:49:13 -07:00
parent 637c59d95d
commit bd360b42ff
24 changed files with 803 additions and 1 deletions
+44
View File
@@ -621,4 +621,48 @@ public class StudentController : BaseController
Message = updated ? "Changes applied successfully." : "No changes were applied."
});
}
[HttpGet("{idStudent:guid}/progress-report")]
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.Paraeducator},{UserRoles.ProgramAdmin}")]
[ProducesResponseType(typeof(ResponseResult<string>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ResponseResult<string>), StatusCodes.Status404NotFound)]
public async Task<ActionResult<ResponseResult<string>>> GetProgressReport(
Guid idStudent, [FromQuery] DateTime fromDate, [FromQuery] DateTime toDate, [FromQuery] string? goalIds = null)
{
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
if (error is not null)
{
return error;
}
var students = await _studentRepository.GetMyStudentsAsync(userId, programId, role);
if (!students.Select(s => s.StudentId).Contains(idStudent))
{
return NotFound(new ResponseResult<string>
{
Success = false,
Message = "Student not found."
});
}
var report = await _studentRepository.GetProgressReportAsync(idStudent, fromDate, toDate, goalIds);
if (report is null)
{
return NotFound(new ResponseResult<string>
{
Success = false,
Message = "Student not found."
});
}
var markdown = ProgressReportBuilder.BuildMarkdown(report, fromDate, toDate);
return Ok(new ResponseResult<string>
{
Success = true,
Message = "Progress report generated successfully.",
Data = markdown
});
}
}
@@ -0,0 +1,8 @@
namespace WinStudentGoalTracker.DataAccess;
public class dbProgressReportGoalRow
{
public required Guid GoalId { get; set; }
public string? Category { get; set; }
public string? Description { get; set; }
}
@@ -0,0 +1,10 @@
namespace WinStudentGoalTracker.DataAccess;
public class dbProgressReportRow
{
public required Guid GoalId { get; set; }
public required Guid ProgressEventId { get; set; }
public string? Content { get; set; }
public DateTime? CreatedAt { get; set; }
public string? BenchmarkNames { get; set; }
}
@@ -351,4 +351,54 @@ public class StudentRepository
return rowsAffected > 0;
}
// *****************************************************************
// Returns a full progress report for a student within the given
// date range. Calls sp_ProgressReport_GetByStudentId which returns
// two result sets: goals and progress events with benchmark names.
// *****************************************************************
public async Task<StudentProgressReportResponse?> GetProgressReportAsync(
Guid studentId, DateTime fromDate, DateTime toDate, string? goalIds = null)
{
var student = await GetByIdAsync(studentId);
if (student is null) return null;
using var db = Connection;
using var multi = await db.QueryMultipleAsync(
"sp_ProgressReport_GetByStudentId",
new
{
p_id_student = studentId.ToString(),
p_from_date = fromDate.ToString("yyyy-MM-dd"),
p_to_date = toDate.ToString("yyyy-MM-dd"),
p_goal_ids = goalIds
},
commandType: CommandType.StoredProcedure);
var goalRows = (await multi.ReadAsync<dbProgressReportGoalRow>()).ToList();
var eventRows = (await multi.ReadAsync<dbProgressReportRow>()).ToList();
var eventsByGoal = eventRows.GroupBy(e => e.GoalId)
.ToDictionary(g => g.Key, g => g.ToList());
return new StudentProgressReportResponse
{
StudentIdentifier = student.Identifier,
Goals = goalRows.Select(g => new ProgressReportGoal
{
GoalId = g.GoalId,
Category = g.Category,
Description = g.Description,
ProgressEvents = eventsByGoal.TryGetValue(g.GoalId, out var events)
? events.Select(e => new ProgressReportEvent
{
ProgressEventId = e.ProgressEventId,
Content = e.Content,
CreatedAt = e.CreatedAt,
BenchmarkNames = e.BenchmarkNames
}).ToList()
: []
}).ToList()
};
}
}
@@ -0,0 +1,23 @@
namespace WinStudentGoalTracker.Models;
public class StudentProgressReportResponse
{
public string? StudentIdentifier { get; set; }
public List<ProgressReportGoal> Goals { get; set; } = [];
}
public class ProgressReportGoal
{
public Guid GoalId { get; set; }
public string? Category { get; set; }
public string? Description { get; set; }
public List<ProgressReportEvent> ProgressEvents { get; set; } = [];
}
public class ProgressReportEvent
{
public Guid ProgressEventId { get; set; }
public string? Content { get; set; }
public DateTime? CreatedAt { get; set; }
public string? BenchmarkNames { get; set; }
}
@@ -5,6 +5,7 @@ public class StudentResponse
public Guid StudentId { get; set; }
public string? Identifier { get; set; }
public DateTime? NextIepDate { get; set; }
public DateTime? FirstEntryDate { get; set; }
public DateTime? LastEntryDate { get; set; }
public int GoalCount { get; set; }
public int ProgressEventCount { get; set; }
+65
View File
@@ -0,0 +1,65 @@
using WinStudentGoalTracker.Models;
namespace WinStudentGoalTracker.Services;
public static class ProgressReportBuilder
{
// *****************************************************************
// Builds a markdown document from a StudentProgressReportResponse.
// Returns the complete markdown string ready for download.
// *****************************************************************
public static string BuildMarkdown(StudentProgressReportResponse report, DateTime fromDate, DateTime toDate)
{
var lines = new List<string>();
var fromDisplay = fromDate.ToString("MMMM d, yyyy");
var toDisplay = toDate.ToString("MMMM d, yyyy");
lines.Add("# Student Progress Report");
lines.Add("");
lines.Add($"**Student:** {report.StudentIdentifier}");
lines.Add($"**Report Period:** {fromDisplay} {toDisplay}");
lines.Add("");
lines.Add("---");
if (report.Goals.Count == 0)
{
lines.Add("");
lines.Add("*No progress events found in the selected date range.*");
return string.Join("\n", lines);
}
var goalIndex = 0;
foreach (var goal in report.Goals)
{
goalIndex++;
lines.Add("");
lines.Add($"## {goalIndex}. {goal.Category}");
if (!string.IsNullOrWhiteSpace(goal.Description))
{
lines.Add("");
lines.Add(goal.Description);
}
lines.Add("");
foreach (var ev in goal.ProgressEvents)
{
var eventDate = ev.CreatedAt?.ToString("MMMM d, yyyy") ?? "Unknown date";
lines.Add($"### {eventDate}");
lines.Add("");
lines.Add(ev.Content ?? "");
if (!string.IsNullOrWhiteSpace(ev.BenchmarkNames))
{
lines.Add("");
lines.Add($"**Benchmarks:** {ev.BenchmarkNames}");
}
lines.Add("");
}
lines.Add("---");
}
return string.Join("\n", lines);
}
}