feedback_stats
Analyze 3D printing success rates, identify common issues, and optimize material settings using combined local and community data.
Instructions
Affiche les statistiques de tes impressions : taux de réussite, score moyen, problèmes fréquents, et meilleurs paramètres par matériau. Combine tes données locales et les données communautaires.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| material | No | Filtrer par matériau |
Implementation Reference
- src/tools/feedback.ts:150-228 (handler)The complete feedback_stats tool registration and handler implementation. The handler loads local feedback data, optionally filters by material, computes statistics using computeStats(), loads community data, and returns a formatted report with print statistics, common issues, and best settings per material.
export function registerFeedbackStats(server: McpServer) { server.registerTool( "feedback_stats", { title: "Statistiques de prints", description: "Affiche les statistiques de tes impressions : taux de réussite, " + "score moyen, problèmes fréquents, et meilleurs paramètres par matériau. " + "Combine tes données locales et les données communautaires.", inputSchema: { material: z.string().optional().describe("Filtrer par matériau"), }, }, async ({ material }) => { try { let feedbacks = await loadFeedback(); if (material) { feedbacks = feedbacks.filter( (f) => f.material.toUpperCase() === material.toUpperCase(), ); } const stats = computeStats(feedbacks); const community = await loadCommunityData(); const lines = [ "## Statistiques d'impression", "", ]; if (stats.totalPrints === 0) { lines.push("Aucun print enregistré. Utilise `submit_feedback` après une impression."); lines.push(""); lines.push(`### Données communautaires (${community.totalPrints} prints)`); if (community.insights.bestPractices.length > 0) { lines.push("**Bonnes pratiques :**"); for (const bp of community.insights.bestPractices) { lines.push(`- ${bp.recommendation} _(confiance: ${Math.round(bp.confidence * 100)}%)_`); } } } else { lines.push(`**Total prints** : ${stats.totalPrints}`); lines.push(`**Taux de réussite** : ${stats.successRate.toFixed(0)}%`); lines.push(`**Score qualité moyen** : ${stats.avgQuality.toFixed(1)}/5`); lines.push(`**Score adhésion moyen** : ${stats.avgAdhesion.toFixed(1)}/5`); lines.push(`**Score solidité moyen** : ${stats.avgStrength.toFixed(1)}/5`); lines.push(""); if (stats.commonIssues.length > 0) { lines.push("### Problèmes fréquents"); for (const issue of stats.commonIssues.slice(0, 5)) { lines.push(`- **${issue.issue}** : ${issue.count}× (${issue.percent.toFixed(0)}% des prints)`); } lines.push(""); } if (stats.bestSettings.length > 0) { lines.push("### Meilleurs paramètres par matériau"); for (const s of stats.bestSettings) { lines.push(`- **${s.material}** : layer ${s.layerHeight}mm → score ${s.avgScore}/5 (${s.count} prints)`); } } } return { content: [{ type: "text" as const, text: lines.join("\n") }], }; } catch (error) { return { isError: true, content: [{ type: "text" as const, text: `Erreur : ${error instanceof Error ? error.message : String(error)}`, }], }; } }, ); } - src/feedback-store.ts:97-170 (helper)The computeStats function that processes PrintFeedback arrays and calculates total prints, success rate, average scores (quality, adhesion, strength), common issues with counts and percentages, and best settings per material with optimal layer heights.
export function computeStats(feedbacks: PrintFeedback[]): FeedbackStats { if (feedbacks.length === 0) { return { totalPrints: 0, successRate: 0, avgQuality: 0, avgAdhesion: 0, avgStrength: 0, commonIssues: [], bestSettings: [], }; } const n = feedbacks.length; const successes = feedbacks.filter((f) => f.success).length; // Averages const avgQuality = feedbacks.reduce((s, f) => s + f.qualityScore, 0) / n; const avgAdhesion = feedbacks.reduce((s, f) => s + f.adhesionScore, 0) / n; const avgStrength = feedbacks.reduce((s, f) => s + f.strengthScore, 0) / n; // Common issues const issueCount = new Map<string, number>(); for (const f of feedbacks) { for (const issue of f.issues) { issueCount.set(issue, (issueCount.get(issue) ?? 0) + 1); } } const commonIssues = [...issueCount.entries()] .map(([issue, count]) => ({ issue, count, percent: (count / n) * 100 })) .sort((a, b) => b.count - a.count); // Best settings per material const materialGroups = new Map<string, PrintFeedback[]>(); for (const f of feedbacks) { const key = f.material.toUpperCase(); const group = materialGroups.get(key) ?? []; group.push(f); materialGroups.set(key, group); } const bestSettings = [...materialGroups.entries()].map(([material, group]) => { // Find the layer height with the best average score const layerGroups = new Map<number, number[]>(); for (const f of group) { const scores = layerGroups.get(f.layerHeight) ?? []; scores.push(f.overallScore); layerGroups.set(f.layerHeight, scores); } let bestLayer = 0.2; let bestAvg = 0; let bestCount = 0; for (const [lh, scores] of layerGroups) { const avg = scores.reduce((a, b) => a + b, 0) / scores.length; if (avg > bestAvg || (avg === bestAvg && scores.length > bestCount)) { bestLayer = lh; bestAvg = avg; bestCount = scores.length; } } return { material, layerHeight: bestLayer, avgScore: Math.round(bestAvg * 10) / 10, count: group.length }; }); return { totalPrints: n, successRate: (successes / n) * 100, avgQuality, avgAdhesion, avgStrength, commonIssues, bestSettings, }; - src/feedback-store.ts:46-59 (schema)The FeedbackStats interface defining the return type structure for statistics, including total prints, success rate, average scores, common issues array, and best settings array per material.
export interface FeedbackStats { totalPrints: number; successRate: number; avgQuality: number; avgAdhesion: number; avgStrength: number; commonIssues: Array<{ issue: string; count: number; percent: number }>; bestSettings: { material: string; layerHeight: number; avgScore: number; count: number; }[]; } - src/feedback-store.ts:72-82 (helper)The loadFeedback helper function that reads and parses feedback data from the local JSON file, handling directory creation and error cases gracefully.
export async function loadFeedback(): Promise<PrintFeedback[]> { await ensureDir(); if (!existsSync(FEEDBACK_FILE)) return []; try { const content = await readFile(FEEDBACK_FILE, "utf-8"); return JSON.parse(content) as PrintFeedback[]; } catch { return []; } } - src/index.ts:21-54 (registration)Registration of the feedback_stats tool: imports registerFeedbackStats from tools/feedback.js and calls it with the McpServer instance at line 54 to register the tool.
import { registerSubmitFeedback, registerFeedbackStats, registerExportFeedback } from "./tools/feedback.js"; import { registerDiagnosePrint } from "./tools/diagnose-print.js"; async function main() { console.error("PrusaMCP v2.1.0 — MCP Server intelligent pour PrusaSlicer"); const config = loadConfig(); if (config.executablePath) { console.error(`PrusaSlicer : ${config.executablePath}`); } if (config.profilesDir) { console.error(`Profils : ${config.profilesDir}`); } const server = new McpServer({ name: "prusa-mcp", version: "2.1.0", }); // Analyse & recommandation (sans PrusaSlicer) registerAnalyzeMesh(server); registerCheckPrintability(server); registerSuggestOrientation(server); registerRecommendProfile(server); registerGenerateConfig(server); registerEstimateCost(server); registerSearchFilament(server); registerPrintWizard(server); registerDiagnosePrint(server); // Feedback & communauté registerSubmitFeedback(server); registerFeedbackStats(server);