import 'ExcuseCluster.dart'; /// The weekly parent-teacher conference. One assigned action for next week — /// not five. class ReportCard { String? id; DateTime? periodStart; DateTime? periodEnd; /// Completion rate 0..1, keyed by commitment class name. Map completionByClass; /// Completion rate 0..1, keyed by category. Map completionByCategory; /// Completion rate 0..1, keyed by weekday (1 = Monday). Map completionByWeekday; /// The recurring window where things go to die, as an hour of the day. int worstHour; /// Debt score sampled per day across the period. List debtTrend; /// The commitments dodged most, highest first. List deferralLeaderboard; List excuseTaxonomy; /// Estimation multiplier per category, surfaced here rather than hidden. Map estimationAccuracy; /// Training adherence 0..1 across the period. double trainingAdherence; double programIntegrity; /// The single assigned action for next week. String assignedAction; /// Cosmetic but effective. String grade; /// Rationed, specific praise — empty when nothing was genuinely earned. String praise; ReportCard({ this.id, this.periodStart, this.periodEnd, Map? completionByClass, Map? completionByCategory, Map? completionByWeekday, this.worstHour = -1, List? debtTrend, List? deferralLeaderboard, List? excuseTaxonomy, Map? estimationAccuracy, this.trainingAdherence = 0, this.programIntegrity = 0, this.assignedAction = "", this.grade = "", this.praise = "", }) : completionByClass = completionByClass ?? {}, completionByCategory = completionByCategory ?? {}, completionByWeekday = completionByWeekday ?? {}, debtTrend = debtTrend ?? [], deferralLeaderboard = deferralLeaderboard ?? [], excuseTaxonomy = excuseTaxonomy ?? [], estimationAccuracy = estimationAccuracy ?? {}; factory ReportCard.fromJson(Map json) { final Map byClass = {}; if (json['completionByClass'] != null) { (json['completionByClass'] as Map).forEach((key, value) { byClass[key] = (value ?? 0).toDouble(); }); } final Map byCategory = {}; if (json['completionByCategory'] != null) { (json['completionByCategory'] as Map) .forEach((key, value) { byCategory[key] = (value ?? 0).toDouble(); }); } final Map byWeekday = {}; if (json['completionByWeekday'] != null) { (json['completionByWeekday'] as Map) .forEach((key, value) { byWeekday[int.tryParse(key) ?? 1] = (value ?? 0).toDouble(); }); } final Map estimation = {}; if (json['estimationAccuracy'] != null) { (json['estimationAccuracy'] as Map) .forEach((key, value) { estimation[key] = (value ?? 1).toDouble(); }); } return ReportCard( id: json['id'], periodStart: DateTime.tryParse(json['periodStart'] ?? ""), periodEnd: DateTime.tryParse(json['periodEnd'] ?? ""), completionByClass: byClass, completionByCategory: byCategory, completionByWeekday: byWeekday, worstHour: json['worstHour'] ?? -1, debtTrend: json['debtTrend'] == null ? [] : (json['debtTrend'] as List) .map((item) => (item ?? 0).toDouble() as double) .toList(), deferralLeaderboard: json['deferralLeaderboard'] == null ? [] : (json['deferralLeaderboard'] as List) .map((item) => DeferralCount.fromJson(item)) .toList(), excuseTaxonomy: json['excuseTaxonomy'] == null ? [] : (json['excuseTaxonomy'] as List) .map((item) => ExcuseCluster.fromJson(item)) .toList(), estimationAccuracy: estimation, trainingAdherence: (json['trainingAdherence'] ?? 0).toDouble(), programIntegrity: (json['programIntegrity'] ?? 0).toDouble(), assignedAction: json['assignedAction'] ?? "", grade: json['grade'] ?? "", praise: json['praise'] ?? "", ); } Map toJson() { final Map data = {}; data['id'] = id; data['periodStart'] = periodStart?.toIso8601String(); data['periodEnd'] = periodEnd?.toIso8601String(); data['completionByClass'] = completionByClass; data['completionByCategory'] = completionByCategory; data['completionByWeekday'] = completionByWeekday.map((key, value) => MapEntry(key.toString(), value)); data['worstHour'] = worstHour; data['debtTrend'] = debtTrend; data['deferralLeaderboard'] = deferralLeaderboard.map((item) => item.toJson()).toList(); data['excuseTaxonomy'] = excuseTaxonomy.map((item) => item.toJson()).toList(); data['estimationAccuracy'] = estimationAccuracy; data['trainingAdherence'] = trainingAdherence; data['programIntegrity'] = programIntegrity; data['assignedAction'] = assignedAction; data['grade'] = grade; data['praise'] = praise; return data; } } /// One row of the deferral leaderboard — the tasks dodged most. class DeferralCount { String commitmentId; String title; int count; DeferralCount({ this.commitmentId = "", this.title = "", this.count = 0, }); factory DeferralCount.fromJson(Map json) { return DeferralCount( commitmentId: json['commitmentId'] ?? "", title: json['title'] ?? "", count: json['count'] ?? 0, ); } Map toJson() { final Map data = {}; data['commitmentId'] = commitmentId; data['title'] = title; data['count'] = count; return data; } }