feat: AI schedule JSON parser

This commit is contained in:
2026-05-20 17:45:46 +02:00
parent 2e0c6f7f8f
commit 0a69ccabfb
2 changed files with 49 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
import { parseScheduleJson } from '../../ai/parser';
describe('parseScheduleJson', () => {
it('parses a valid JSON array', () => {
const input = JSON.stringify([
{ date: '2026-05-22', planned_min: 60, rationale: 'Continue level' },
{ date: '2026-05-24', planned_min: 75, rationale: 'Step up' },
]);
const result = parseScheduleJson(input);
expect(result).toHaveLength(2);
expect(result[0]).toEqual({ date: '2026-05-22', planned_min: 60, rationale: 'Continue level' });
});
it('parses JSON wrapped in a markdown code block', () => {
const input = '```json\n[{"date":"2026-05-22","planned_min":60,"rationale":"test"}]\n```';
const result = parseScheduleJson(input);
expect(result).toHaveLength(1);
expect(result[0]!.date).toBe('2026-05-22');
});
it('throws on invalid JSON', () => {
expect(() => parseScheduleJson('not json')).toThrow();
});
it('throws if entries are missing required fields', () => {
expect(() => parseScheduleJson(JSON.stringify([{ date: '2026-05-22' }]))).toThrow();
});
});
+21
View File
@@ -0,0 +1,21 @@
export interface ScheduleProposal {
date: string;
planned_min: number;
rationale: string;
}
export function parseScheduleJson(raw: string): ScheduleProposal[] {
const cleaned = raw.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '').trim();
const parsed = JSON.parse(cleaned);
if (!Array.isArray(parsed)) throw new Error('Expected a JSON array');
return parsed.map((entry: unknown, i: number) => {
if (typeof entry !== 'object' || entry === null) throw new Error(`Entry ${i} is not an object`);
const e = entry as Record<string, unknown>;
if (typeof e['date'] !== 'string') throw new Error(`Entry ${i} missing "date"`);
if (typeof e['planned_min'] !== 'number') throw new Error(`Entry ${i} missing "planned_min"`);
if (typeof e['rationale'] !== 'string') throw new Error(`Entry ${i} missing "rationale"`);
return { date: e['date'], planned_min: e['planned_min'], rationale: e['rationale'] };
});
}