metallkart-erp/scripts/test-m2b-weekly-report.ts
louis 13cf51199f fix(agent): remove temperature param for Opus 4.7 + add M2B test script
Opus 4.7 API rejects the temperature parameter. Conditionally skip it
for opus models. Add E2E test script for weekly report M2B pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-22 18:56:22 +04:00

181 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { join } from 'node:path';
const BASE = process.env.API_BASE_URL ?? 'http://localhost:3001';
const EMAIL = process.env.ADMIN_EMAIL ?? 'gruart@cifem-rus.ru';
const PASSWORD = process.env.ADMIN_PASSWORD;
const OUT_DIR = process.env.OUT_DIR ?? './tmp/m2b-reports';
if (!PASSWORD) {
console.error('ADMIN_PASSWORD required (env var)');
process.exit(1);
}
async function main() {
console.log(`Target: ${BASE}`);
console.log(`Admin: ${EMAIL}`);
// 1. Login
const loginRes = await fetch(`${BASE}/api/v1/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: EMAIL, password: PASSWORD }),
});
if (!loginRes.ok) {
console.error(`Login failed ${loginRes.status}: ${await loginRes.text()}`);
process.exit(1);
}
const { accessToken } = await loginRes.json() as { accessToken: string };
console.log(`OK Logged in (JWT ${accessToken.substring(0, 20)}...)`);
const authHeaders: Record<string, string> = {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};
// 2. Compute period (default = previous ISO week, Mon 00:00 MSK)
const periodFrom = process.env.PERIOD_FROM ?? computeMondayMinus7DaysISO();
const periodTo = process.env.PERIOD_TO ?? computeMondayThisWeekISO();
console.log(`Period: ${periodFrom} -> ${periodTo}`);
// 3. Create DRAFT report via aggregator
const createRes = await fetch(`${BASE}/api/v1/admin/agent/reports/generate`, {
method: 'POST',
headers: authHeaders,
body: JSON.stringify({ periodFrom, periodTo }),
});
if (!createRes.ok) {
console.error(`Create report failed ${createRes.status}: ${await createRes.text()}`);
process.exit(1);
}
const created = await createRes.json() as { id: string; status: string; periodFrom: string };
const reportId = created.id;
console.log(`OK Report DRAFT created: id=${reportId}`);
console.log(` status=${created.status} periodFrom=${created.periodFrom}`);
// 4. Trigger M2B commentary generation (Opus 4.7)
console.log(`Triggering M2B Opus 4.7... (expect 30-90s)`);
const start = Date.now();
const m2bHeaders: Record<string, string> = { 'Authorization': `Bearer ${accessToken}` };
const m2bRes = await fetch(`${BASE}/api/v1/admin/agent/reports/${reportId}/generate-commentary`, {
method: 'POST',
headers: m2bHeaders,
});
const m2bDuration = ((Date.now() - start) / 1000).toFixed(1);
if (!m2bRes.ok) {
console.error(`M2B failed ${m2bRes.status}: ${await m2bRes.text()}`);
process.exit(1);
}
const m2bResult = await m2bRes.json() as {
modelUsed?: string;
promptTokens?: number;
completionTokens?: number;
costUsd?: string;
contentMarkdown?: string;
status?: string;
};
console.log(`OK M2B completed in ${m2bDuration}s`);
console.log(` modelUsed=${m2bResult.modelUsed}`);
console.log(` promptTokens=${m2bResult.promptTokens} completionTokens=${m2bResult.completionTokens}`);
console.log(` costUsd=$${m2bResult.costUsd}`);
console.log(` status=${m2bResult.status}`);
// 5. Fetch report with markdown
const getRes = await fetch(`${BASE}/api/v1/admin/agent/reports/${reportId}`, {
headers: { 'Authorization': `Bearer ${accessToken}` },
});
const report = await getRes.json() as Record<string, unknown>;
const markdown = (report.contentMarkdown ?? '') as string;
if (!markdown) {
console.error(`No markdown in report response`);
console.error(JSON.stringify(report, null, 2));
process.exit(1);
}
// 6. Save to disk
if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true });
const ts = new Date().toISOString().replace(/[:.]/g, '-');
const mdPath = join(OUT_DIR, `m2b-report-${reportId.substring(0, 8)}-${ts}.md`);
const metaPath = join(OUT_DIR, `m2b-report-${reportId.substring(0, 8)}-${ts}.meta.json`);
writeFileSync(mdPath, markdown, 'utf-8');
writeFileSync(metaPath, JSON.stringify(report, null, 2), 'utf-8');
console.log(`OK Saved markdown -> ${mdPath}`);
console.log(`OK Saved meta -> ${metaPath}`);
// 7. Pre-check 7 persona criteria (heuristic)
console.log(`\n=== Persona pre-check ===`);
const checks = runPersonaChecks(markdown);
let allPass = true;
for (const c of checks) {
console.log(` ${c.pass ? 'PASS' : 'FAIL'} ${c.label}`);
if (!c.pass) allPass = false;
}
console.log(`\nPersona: ${allPass ? 'ALL PASS' : 'SOME FAILED'}`);
// 8. Print full markdown to stdout
console.log(`\n=== MARKDOWN GENERE (${markdown.length} chars) ===\n`);
console.log(markdown);
console.log(`\n=== FIN ===`);
console.log(`\nResume: reportId=${reportId} status=${m2bResult.status} model=${m2bResult.modelUsed} tokens=${m2bResult.promptTokens}+${m2bResult.completionTokens} cost=$${m2bResult.costUsd} latency=${m2bDuration}s`);
}
function runPersonaChecks(md: string): Array<{ label: string; pass: boolean }> {
return [
{
label: 'Titre "Еженедельный отчёт" present',
pass: /Еженедельный отчёт/i.test(md),
},
{
label: '6 sections (Резюме, Коммерческий, Закупки, Финансы, Действия, Аномалии)',
pass: ['езюме', 'оммерческ', 'акупк', 'инанс', 'ействи', 'номали'].every(s =>
md.toLowerCase().includes(s.toLowerCase()),
),
},
{
label: 'Signature Август presente',
pass: /Август/i.test(md),
},
{
label: 'Symbole rub correct (pas de "руб" sans signe)',
pass: !/\d+\s*руб\b/i.test(md) || md.includes('₽'),
},
{
label: 'Pas de prenom translittere (Maksim, Daria, Evgeny)',
pass: !/\b(Maksim|Daria|Evgeny|Nikita|Tamara|Anna)\b/.test(md),
},
{
label: 'Pas d\'emoji fantaisie',
pass: !/[\u{1F300}-\u{1FAFF}]/u.test(md),
},
{
label: 'Pas de tournures jugeantes personnelles',
pass: !/(не выполнил|опоздал|ошибся|провалил|забыл)/i.test(md),
},
];
}
function computeMondayMinus7DaysISO(): string {
const now = new Date();
const day = now.getUTCDay();
const diffToMonday = day === 0 ? -6 : 1 - day;
const lastMonday = new Date(now);
lastMonday.setUTCDate(now.getUTCDate() + diffToMonday - 7);
lastMonday.setUTCHours(21, 0, 0, 0);
return lastMonday.toISOString();
}
function computeMondayThisWeekISO(): string {
const now = new Date();
const day = now.getUTCDay();
const diffToMonday = day === 0 ? -6 : 1 - day;
const thisMonday = new Date(now);
thisMonday.setUTCDate(now.getUTCDate() + diffToMonday);
thisMonday.setUTCHours(21, 0, 0, 0);
return thisMonday.toISOString();
}
main().catch(err => {
console.error('Fatal:', err);
process.exit(1);
});