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