export-markdown.test.ts
4,237 bytes
| 1 | import { describe, expect, it } from 'vitest'; |
|---|---|
| 2 | import { emptyUsage, type DebateResult } from '@/core/types'; |
| 3 | import { debateToMarkdown } from './export-markdown'; |
| 4 | |
| 5 | function makeResult(): DebateResult { |
| 6 | const config = { |
| 7 | question: 'Monolith or microservices?', |
| 8 | models: ['openai/gpt-4o', 'anthropic/claude-3.5-sonnet'], |
| 9 | chairmanModel: 'x-ai/grok-2', |
| 10 | convergenceModel: 'google/gemini-2.0-flash-001', |
| 11 | maxRounds: 2, |
| 12 | convergenceThreshold: 85, |
| 13 | temperature: 0.7, |
| 14 | perModelTimeoutMs: 90_000, |
| 15 | }; |
| 16 | const participants = [ |
| 17 | { id: 'p0', model: 'openai/gpt-4o', displayName: 'GPT 4o' }, |
| 18 | { id: 'p1', model: 'anthropic/claude-3.5-sonnet', displayName: 'Claude 3.5 Sonnet' }, |
| 19 | ]; |
| 20 | return { |
| 21 | debateId: 'd1', |
| 22 | config, |
| 23 | participants, |
| 24 | status: 'completed', |
| 25 | initialAnswers: [ |
| 26 | { participantId: 'p0', model: 'openai/gpt-4o', round: 0, content: 'Monolith.', usage: emptyUsage(), latencyMs: 0 }, |
| 27 | { participantId: 'p1', model: 'anthropic/claude-3.5-sonnet', round: 0, content: 'Depends.', usage: emptyUsage(), latencyMs: 0 }, |
| 28 | ], |
| 29 | rounds: [ |
| 30 | { |
| 31 | round: 1, |
| 32 | critiques: [ |
| 33 | { |
| 34 | round: 1, |
| 35 | reviewerParticipantId: 'p0', |
| 36 | reviewerModel: 'openai/gpt-4o', |
| 37 | reviews: [{ label: 'A', targetParticipantId: 'p1', weaknesses: ['vague'], strengths: [], score: 6, justification: 'meh' }], |
| 38 | usage: emptyUsage(), |
| 39 | latencyMs: 0, |
| 40 | }, |
| 41 | ], |
| 42 | revisions: [ |
| 43 | { |
| 44 | round: 1, |
| 45 | participantId: 'p1', |
| 46 | model: 'anthropic/claude-3.5-sonnet', |
| 47 | content: 'Monolith, with caveats.', |
| 48 | changelog: { changed: true, summary: 'narrowed', bullets: ['a'] }, |
| 49 | usage: emptyUsage(), |
| 50 | latencyMs: 0, |
| 51 | }, |
| 52 | ], |
| 53 | convergence: { round: 1, model: 'google/gemini-2.0-flash-001', score: 88, converged: true, disagreements: [], usage: emptyUsage(), latencyMs: 0 }, |
| 54 | }, |
| 55 | ], |
| 56 | synthesis: { |
| 57 | model: 'x-ai/grok-2', |
| 58 | finalAnswer: 'Start with a modular monolith.', |
| 59 | dissent: [{ topic: 'edge case', positions: [{ participantId: 'p0', model: 'openai/gpt-4o', position: 'no split' }] }], |
| 60 | usage: emptyUsage(), |
| 61 | latencyMs: 0, |
| 62 | }, |
| 63 | provenance: { |
| 64 | model: 'google/gemini-2.0-flash-001', |
| 65 | round: 1, |
| 66 | claims: [ |
| 67 | { |
| 68 | text: 'Start with a monolith.', |
| 69 | supportedBy: [{ participantId: 'p0', model: 'openai/gpt-4o' }], |
| 70 | contestedBy: [], |
| 71 | unsourced: false, |
| 72 | }, |
| 73 | { text: 'Revisit in a year.', supportedBy: [], contestedBy: [], unsourced: true }, |
| 74 | ], |
| 75 | usage: emptyUsage(), |
| 76 | latencyMs: 0, |
| 77 | }, |
| 78 | failures: [], |
| 79 | finalAnswers: [], |
| 80 | totals: { costUsd: 0.02, promptTokens: 100, completionTokens: 200, rounds: 1, durationMs: 5000, costByModel: {} }, |
| 81 | }; |
| 82 | } |
| 83 | |
| 84 | describe('debateToMarkdown', () => { |
| 85 | const md = debateToMarkdown(makeResult()); |
| 86 | |
| 87 | it('includes the question and council', () => { |
| 88 | expect(md).toContain('# Roundtable Deliberation Report'); |
| 89 | expect(md).toContain('Monolith or microservices?'); |
| 90 | expect(md).toContain('GPT 4o'); |
| 91 | expect(md).toContain('`x-ai/grok-2`'); |
| 92 | }); |
| 93 | |
| 94 | it('includes the final answer and dissent', () => { |
| 95 | expect(md).toContain('## Final Answer'); |
| 96 | expect(md).toContain('Start with a modular monolith.'); |
| 97 | expect(md).toContain('Dissent Report'); |
| 98 | expect(md).toContain('no split'); |
| 99 | }); |
| 100 | |
| 101 | it('renders the round with a critique score table and revision', () => { |
| 102 | expect(md).toContain('## Round 1'); |
| 103 | expect(md).toContain('Critique scores'); |
| 104 | expect(md).toContain('| GPT 4o |'); // reviewer row |
| 105 | expect(md).toContain('Monolith, with caveats.'); |
| 106 | expect(md).toContain('Convergence score: 88/100'); |
| 107 | }); |
| 108 | |
| 109 | it('renders the claim check with sourced and unsourced claims', () => { |
| 110 | expect(md).toContain('### Claim Check'); |
| 111 | expect(md).toContain('Start with a monolith. (supported by GPT 4o)'); |
| 112 | expect(md).toContain("Revisit in a year. (**chairman's own addition"); |
| 113 | }); |
| 114 | |
| 115 | it('renders round 0 answers', () => { |
| 116 | expect(md).toContain('## Round 0'); |
| 117 | expect(md).toContain('Monolith.'); |
| 118 | expect(md).toContain('Depends.'); |
| 119 | }); |
| 120 | }); |
| 121 | |