feat: AI schedule JSON parser
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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'] };
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user