import '../models/habit.dart'; import 'frame_candidate.dart'; /// Parses function-calling JSON into FrameCandidate list. Throws /// [FormatException] on missing/invalid top-level shape; silently skips /// individual malformed items. List parseFrameCandidates(Map json) { final raw = json['candidates']; if (raw == null) { throw const FormatException('candidates missing'); } if (raw is! List) { throw const FormatException('candidates not array'); } final result = []; for (final item in raw) { if (item is! Map) continue; final map = item.map((k, v) => MapEntry(k.toString(), v)); final levelStr = map['level']; if (levelStr is! String) continue; final level = _parseLevel(levelStr); if (level == null) continue; final framedText = map['framed_text']; if (framedText is! String) continue; final trimmed = framedText.trim(); if (trimmed.isEmpty || trimmed.length > 120) continue; double confidence = 0.5; final c = map['confidence']; if (c is num) { confidence = c.toDouble().clamp(0.0, 1.0); } final src = map['source_pattern_id']; result.add(FrameCandidate( level: level, framedText: trimmed, confidence: confidence, sourcePatternId: src is String ? src : null, )); } return result; } FrameLevel? _parseLevel(String s) { switch (s.toUpperCase()) { case 'L0': return FrameLevel.l0; case 'L1': return FrameLevel.l1; case 'L2': return FrameLevel.l2; case 'L3': return FrameLevel.l3; } return null; }