Skip to content

Commit 805434f

Browse files
feat(reporter): Markdown report generator + package config (#40)
- generateMarkdownReport() produces a structured Markdown report with: session overview, timeline summary, failures (with severity and suggested steps), suggested next steps, and raw event appendix - AnalysisResult input type representing the analysis pipeline output - 11 tests covering structure, failure inclusion, readability, metadata, severity Closes #24
1 parent 17c2aa0 commit 805434f

9 files changed

Lines changed: 649 additions & 4 deletions

File tree

‎.changeset/reporter-package.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@ocpp-debugkit/reporter': minor
3+
---
4+
5+
Create reporter package with Markdown report generator.
6+
7+
- `generateMarkdownReport()` produces a structured Markdown report with:
8+
session overview, timeline summary, failures (with severity and suggested
9+
steps), suggested next steps, and raw event appendix
10+
- `AnalysisResult` input type representing the analysis pipeline output
11+
- 11 tests covering structure, failure inclusion, readability, metadata, severity

‎CURRENT_STATE.md‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,21 +91,28 @@ report exported. CLI and web inspector.
9191
- ✅ Pinned `changesets/action` to `v1.9.0`
9292
- ✅ Fixed root `changeset` script: `"changeset add"` → `"changeset"` (was causing release workflow failures)
9393

94-
### Scenarios Package (in progress — this PR)
94+
### Scenarios Package (PR #39)
9595

9696
- ✅ `packages/scenarios/` — new package
9797
- ✅ 5 scenarios: normal-session, failed-auth, connector-fault, station-offline, unexpected-stop-reason
9898
- ✅ Scenario registry with `getScenario()` lookup
9999
- ✅ Each scenario's `expectedFailures` aligns with v0.1 detection rules
100100
- ✅ 21 tests (registry, engine integration, synthetic data policy)
101101

102+
### Reporter Package (in progress — this PR)
103+
104+
- ✅ `packages/reporter/` — new package
105+
- ✅ `generateMarkdownReport()` — session overview, timeline summary, failures, suggested steps, event appendix
106+
- ✅ `AnalysisResult` input type
107+
- ✅ 11 tests (structure, failure inclusion, readability, metadata, severity)
108+
102109
## What's Next
103110

104111
1. **Issue #20** → complete (PR #33): data model + parser + normalizer
105112
2. **Issue #21** → complete (PR #34): timeline + detection + summarizer + validator
106113
3. **Issue #22** → complete (PR #35): public API export + package config
107-
4. **Issue #23** (this PR) → complete: scenarios package (format + 5 initial scenarios)
108-
5. **Issue #24**: Reporter package (Markdown report generator)
114+
4. **Issue #23** → complete (PR #39): scenarios package (format + 5 initial scenarios)
115+
5. **Issue #24** (this PR) → complete: reporter package (Markdown report generator)
109116
6. **Issue #25**: CLI package (scaffold + inspect + report + scenario commands)
110117

111118
## Known Blockers / Decisions Pending
@@ -118,7 +125,7 @@ report exported. CLI and web inspector.
118125
|---------|--------|---------|
119126
| `@ocpp-debugkit/core` | in progress (package config finalized, ready for downstream) | 0.0.0 |
120127
| `@ocpp-debugkit/scenarios` | in progress (5 scenarios + registry) | 0.0.0 |
121-
| `@ocpp-debugkit/reporter` | not started | — |
128+
| `@ocpp-debugkit/reporter` | in progress (Markdown generator) | 0.0.0 |
122129
| `@ocpp-debugkit/cli` | not started | — |
123130
| `@ocpp-debugkit/replay` | not started | — |
124131
| `@ocpp-debugkit/react` | not started | — |

‎packages/reporter/package.json‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"name": "@ocpp-debugkit/reporter",
3+
"version": "0.0.0",
4+
"description": "Report generators (Markdown, HTML) for OCPP DebugKit analysis results.",
5+
"license": "Apache-2.0",
6+
"type": "module",
7+
"sideEffects": false,
8+
"main": "./dist/index.js",
9+
"module": "./dist/index.js",
10+
"types": "./dist/index.d.ts",
11+
"exports": {
12+
".": {
13+
"types": "./dist/index.d.ts",
14+
"import": "./dist/index.js"
15+
}
16+
},
17+
"files": [
18+
"dist",
19+
"README.md",
20+
"NOTICE"
21+
],
22+
"keywords": [
23+
"ocpp",
24+
"ocpp1.6",
25+
"ev-charging",
26+
"report",
27+
"markdown",
28+
"debugging"
29+
],
30+
"repository": {
31+
"type": "git",
32+
"url": "https://github.com/ocpp-debugkit/ocpp-debugkit.git",
33+
"directory": "packages/reporter"
34+
},
35+
"homepage": "https://github.com/ocpp-debugkit/ocpp-debugkit/tree/main/packages/reporter",
36+
"bugs": {
37+
"url": "https://github.com/ocpp-debugkit/ocpp-debugkit/issues"
38+
},
39+
"scripts": {
40+
"build": "tsc -p tsconfig.json",
41+
"typecheck": "tsc --noEmit",
42+
"test": "vitest run",
43+
"clean": "rm -rf dist .turbo"
44+
},
45+
"dependencies": {
46+
"@ocpp-debugkit/core": "workspace:*"
47+
},
48+
"devDependencies": {
49+
"typescript": "^5.7.0",
50+
"vitest": "^3.0.0"
51+
},
52+
"publishConfig": {
53+
"access": "public"
54+
}
55+
}

‎packages/reporter/src/index.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Barrel export for the @ocpp-debugkit/reporter package.
3+
*/
4+
5+
export type { AnalysisResult } from './types.js';
6+
export { generateMarkdownReport } from './markdown.js';
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { generateMarkdownReport } from './markdown.js';
3+
import type { AnalysisResult } from './types.js';
4+
import type { Event, Failure, Session, SessionSummary, RawOcppMessage } from '@ocpp-debugkit/core';
5+
6+
// Helpers
7+
function makeEvent(
8+
id: string,
9+
messageId: string,
10+
messageType: 'Call' | 'CallResult' | 'CallError',
11+
action: string | null,
12+
timestamp: number | null = null,
13+
): Event {
14+
let rawMessage: RawOcppMessage;
15+
if (messageType === 'Call') {
16+
rawMessage = [2, messageId, action as string, {}];
17+
} else if (messageType === 'CallResult') {
18+
rawMessage = [3, messageId, {}];
19+
} else {
20+
rawMessage = [4, messageId, 'Error', 'desc', {}];
21+
}
22+
return {
23+
id,
24+
messageId,
25+
timestamp,
26+
direction: 'CS_TO_CSMS',
27+
messageType,
28+
action,
29+
payload: {},
30+
errorCode: messageType === 'CallError' ? 'Error' : null,
31+
errorDescription: messageType === 'CallError' ? 'desc' : null,
32+
rawMessage,
33+
};
34+
}
35+
36+
function makeSession(
37+
sessionId: string,
38+
events: Event[],
39+
transactionId: number | null = 100001,
40+
): Session {
41+
return {
42+
sessionId,
43+
stationId: 'CS-001',
44+
connectorId: 1,
45+
transactionId,
46+
startTime: events[0]?.timestamp ?? null,
47+
endTime: events[events.length - 1]?.timestamp ?? null,
48+
events,
49+
status: 'completed',
50+
};
51+
}
52+
53+
function makeFailure(): Failure {
54+
return {
55+
code: 'FAILED_AUTHORIZATION',
56+
description: 'Authorization rejected: idTag returned "Invalid" status',
57+
severity: 'warning',
58+
eventIds: ['evt-0001', 'evt-0002'],
59+
suggestedSteps: ['Verify the idTag is valid', 'Check the CSMS local authorization list'],
60+
};
61+
}
62+
63+
// ---------------------------------------------------------------------------
64+
// Tests
65+
// ---------------------------------------------------------------------------
66+
67+
describe('generateMarkdownReport', () => {
68+
it('generates a non-empty Markdown string', () => {
69+
const result: AnalysisResult = {
70+
events: [makeEvent('evt-0001', 'msg-001', 'Call', 'BootNotification', 1000)],
71+
sessions: [],
72+
failures: [],
73+
summaries: [],
74+
warnings: [],
75+
};
76+
77+
const report = generateMarkdownReport(result);
78+
expect(typeof report).toBe('string');
79+
expect(report.length).toBeGreaterThan(0);
80+
});
81+
82+
it('includes the main title', () => {
83+
const result: AnalysisResult = {
84+
events: [],
85+
sessions: [],
86+
failures: [],
87+
summaries: [],
88+
warnings: [],
89+
};
90+
91+
const report = generateMarkdownReport(result);
92+
expect(report).toContain('# OCPP DebugKit — Trace Analysis Report');
93+
});
94+
95+
it('includes session overview section', () => {
96+
const events = [
97+
makeEvent('evt-0001', 'msg-001', 'Call', 'BootNotification', 1000),
98+
makeEvent('evt-0002', 'msg-002', 'Call', 'StartTransaction', 2000),
99+
];
100+
const session = makeSession('session-0', events);
101+
const result: AnalysisResult = {
102+
events,
103+
sessions: [session],
104+
failures: [],
105+
summaries: [
106+
{
107+
sessionId: 'session-0',
108+
stationId: 'CS-001',
109+
connectorId: 1,
110+
transactionId: 100001,
111+
status: 'completed',
112+
eventCount: 2,
113+
durationMs: 1000,
114+
failureCount: 0,
115+
actionSequence: ['BootNotification', 'StartTransaction'],
116+
},
117+
],
118+
warnings: [],
119+
};
120+
121+
const report = generateMarkdownReport(result);
122+
expect(report).toContain('## Session Overview');
123+
expect(report).toContain('CS-001');
124+
expect(report).toContain('session-0');
125+
});
126+
127+
it('includes failures section with details', () => {
128+
const failure = makeFailure();
129+
const result: AnalysisResult = {
130+
events: [],
131+
sessions: [],
132+
failures: [failure],
133+
summaries: [],
134+
warnings: [],
135+
};
136+
137+
const report = generateMarkdownReport(result);
138+
expect(report).toContain('## Failures');
139+
expect(report).toContain('FAILED_AUTHORIZATION');
140+
expect(report).toContain('Authorization rejected');
141+
expect(report).toContain('Verify the idTag is valid');
142+
});
143+
144+
it('shows no failures message when clean', () => {
145+
const result: AnalysisResult = {
146+
events: [],
147+
sessions: [],
148+
failures: [],
149+
summaries: [],
150+
warnings: [],
151+
};
152+
153+
const report = generateMarkdownReport(result);
154+
expect(report).toContain('## Failures');
155+
expect(report).toContain('No failures detected');
156+
});
157+
158+
it('includes timeline summary with action sequence', () => {
159+
const summary: SessionSummary = {
160+
sessionId: 'session-0',
161+
stationId: 'CS-001',
162+
connectorId: 1,
163+
transactionId: 100001,
164+
status: 'completed',
165+
eventCount: 5,
166+
durationMs: 30000,
167+
failureCount: 0,
168+
actionSequence: ['BootNotification', 'Authorize', 'StartTransaction'],
169+
};
170+
171+
const result: AnalysisResult = {
172+
events: [],
173+
sessions: [],
174+
failures: [],
175+
summaries: [summary],
176+
warnings: [],
177+
};
178+
179+
const report = generateMarkdownReport(result);
180+
expect(report).toContain('## Timeline Summary');
181+
expect(report).toContain('BootNotification → Authorize → StartTransaction');
182+
});
183+
184+
it('includes event appendix', () => {
185+
const events = [
186+
makeEvent('evt-0001', 'msg-001', 'Call', 'BootNotification', 1000),
187+
makeEvent('evt-0002', 'msg-001', 'CallResult', null, 1500),
188+
];
189+
const result: AnalysisResult = {
190+
events,
191+
sessions: [],
192+
failures: [],
193+
summaries: [],
194+
warnings: [],
195+
};
196+
197+
const report = generateMarkdownReport(result);
198+
expect(report).toContain('## Event Appendix');
199+
expect(report).toContain('evt-0001');
200+
expect(report).toContain('BootNotification');
201+
});
202+
203+
it('includes parse warnings', () => {
204+
const result: AnalysisResult = {
205+
events: [],
206+
sessions: [],
207+
failures: [],
208+
summaries: [],
209+
warnings: [{ index: 5, message: 'Line 6: invalid JSON' }],
210+
};
211+
212+
const report = generateMarkdownReport(result);
213+
expect(report).toContain('## Parse Warnings');
214+
expect(report).toContain('Line 6: invalid JSON');
215+
});
216+
217+
it('includes metadata when provided', () => {
218+
const result: AnalysisResult = {
219+
events: [],
220+
sessions: [],
221+
failures: [],
222+
summaries: [],
223+
warnings: [],
224+
metadata: {
225+
stationId: 'CS-SYNTHETIC-001',
226+
ocppVersion: '1.6',
227+
source: 'csms-log',
228+
description: 'Test trace',
229+
},
230+
};
231+
232+
const report = generateMarkdownReport(result);
233+
expect(report).toContain('CS-SYNTHETIC-001');
234+
expect(report).toContain('1.6');
235+
expect(report).toContain('csms-log');
236+
expect(report).toContain('Test trace');
237+
});
238+
239+
it('includes suggested next steps from failures', () => {
240+
const failure = makeFailure();
241+
const result: AnalysisResult = {
242+
events: [],
243+
sessions: [],
244+
failures: [failure],
245+
summaries: [],
246+
warnings: [],
247+
};
248+
249+
const report = generateMarkdownReport(result);
250+
expect(report).toContain('## Suggested Next Steps');
251+
expect(report).toContain('Verify the idTag is valid');
252+
expect(report).toContain('Check the CSMS local authorization list');
253+
});
254+
255+
it('includes severity emoji for failures', () => {
256+
const failure: Failure = {
257+
code: 'CONNECTOR_FAULT',
258+
description: 'Connector fault detected',
259+
severity: 'critical',
260+
eventIds: ['evt-0001'],
261+
suggestedSteps: ['Inspect the connector'],
262+
};
263+
264+
const result: AnalysisResult = {
265+
events: [],
266+
sessions: [],
267+
failures: [failure],
268+
summaries: [],
269+
warnings: [],
270+
};
271+
272+
const report = generateMarkdownReport(result);
273+
expect(report).toContain('🔴');
274+
expect(report).toContain('CONNECTOR_FAULT');
275+
});
276+
});

0 commit comments

Comments
 (0)