/home/techb158/trellopowerup.abdallabala.com/src
NameSizeModeActions
assessmentAnalytics.js71440644editdlrm
riskEngine.js57560644editdlrm
server.js70790644editdlrm
store.js35450644editdlrm
Edit: /home/techb158/trellopowerup.abdallabala.com/src/assessmentAnalytics.js (7144B)
const MS_PER_DAY = 24 * 60 * 60 * 1000; export function scoreValue(assessment) { return Number(assessment?.score?.score || 0); } export function assessmentAgeDays(assessment, now = new Date()) { const created = new Date(assessment?.createdAt).getTime(); if (!Number.isFinite(created)) return null; return Math.max(0, Math.floor((now.getTime() - created) / MS_PER_DAY)); } export function withTrend(assessment, previousAssessment) { if (!assessment) return null; const previousScore = previousAssessment ? scoreValue(previousAssessment) : null; const scoreDelta = previousAssessment ? scoreValue(assessment) - previousScore : null; return { ...assessment, previousScore, scoreDelta, trend: scoreDelta === null ? 'new' : scoreDelta > 0 ? 'worse' : scoreDelta < 0 ? 'improved' : 'unchanged' }; } export function withReviewSignal(assessment, options = {}) { if (!assessment) return null; const now = options.now || new Date(); const reviewStaleDays = options.reviewStaleDays || 30; const reasons = []; const actions = []; const ageDays = assessmentAgeDays(assessment, now); const stale = ageDays !== null && ageDays >= reviewStaleDays; if (assessment.score?.gate?.deploymentReady === false) { reasons.push('Deployment gate blocked'); actions.push('Resolve deployment gate blockers before release.'); } if (assessment.trend === 'worse') { reasons.push('Risk score worsened'); actions.push('Review changed assumptions and update the mitigation plan.'); } if (stale) { reasons.push(`Assessment older than ${reviewStaleDays} days`); actions.push('Refresh the assessment and confirm current controls.'); } return { ...assessment, review: { needed: reasons.length > 0, reasons, actions, stale, ageDays, staleAfterDays: reviewStaleDays } }; } export function decorateAssessment(assessment, previousAssessment, options = {}) { return withReviewSignal(withTrend(assessment, previousAssessment), options); } export function withCardHistoryTrends(assessments, options = {}) { return assessments.map((assessment, index) => decorateAssessment(assessment, assessments[index + 1], options)); } export function latestAssessmentsByCard(assessments, options = {}) { const grouped = new Map(); for (const assessment of assessments) { if (!grouped.has(assessment.cardId)) grouped.set(assessment.cardId, []); grouped.get(assessment.cardId).push(assessment); } return [...grouped.values()].map((cardAssessments) => decorateAssessment(cardAssessments[0], cardAssessments[1], options)); } export function firstRiskTitle(assessment) { return assessment?.score?.highestRisk?.title || assessment?.input?.risks?.[0]?.title || ''; } export function buildBoardSummaryFromAssessments(boardId, assessments, options = {}) { const reviewStaleDays = options.reviewStaleDays || 30; const latest = latestAssessmentsByCard(assessments, options); const statusCounts = latest.reduce((acc, item) => { const status = item.score?.status || 'unknown'; acc[status] = (acc[status] || 0) + 1; return acc; }, {}); const averageScore = latest.length ? Math.round(latest.reduce((sum, item) => sum + (item.score?.score || 0), 0) / latest.length) : 0; return { boardId, generatedAt: new Date().toISOString(), cardCount: latest.length, averageScore, statusCounts, deploymentBlocked: latest.filter((item) => item.score?.gate?.deploymentReady === false).length, improved: latest.filter((item) => item.trend === 'improved').length, worsened: latest.filter((item) => item.trend === 'worse').length, unchanged: latest.filter((item) => item.trend === 'unchanged').length, reviewNeeded: latest.filter((item) => item.review?.needed).length, staleAssessments: latest.filter((item) => item.review?.stale).length, reviewStaleDays, latestAssessments: latest }; } function reviewPriority(item) { const reasons = item.review?.reasons || []; if (item.score?.gate?.deploymentReady === false) return 0; if (item.trend === 'worse') return 1; if (item.review?.stale) return 2; if (reasons.length) return 3; return 4; } export function buildBoardReviewQueueFromSummary(summary) { const items = [...(summary.latestAssessments || [])] .filter((item) => item.review?.needed) .sort((a, b) => { const priorityDelta = reviewPriority(a) - reviewPriority(b); const scoreDelta = (b.score?.score || 0) - (a.score?.score || 0); const ageDelta = (b.review?.ageDays || 0) - (a.review?.ageDays || 0); return priorityDelta || scoreDelta || ageDelta; }); return { boardId: summary.boardId, generatedAt: summary.generatedAt, reviewStaleDays: summary.reviewStaleDays, count: items.length, items }; } function csvCell(value) { const text = value === undefined || value === null ? '' : String(value); return `"${text.replaceAll('"', '""')}"`; } export function buildBoardCsv(summary) { const rows = [[ 'board_id', 'card_id', 'assessment_id', 'created_at', 'risk_title', 'score', 'previous_score', 'score_delta', 'trend', 'status', 'deployment_ready', 'review_needed', 'review_reasons', 'recommended_actions', 'assessment_age_days', 'stale_assessment', 'blocking_reasons', 'project_typology', 'lifecycle_phase', 'measurement_maturity' ]]; for (const item of summary.latestAssessments) { rows.push([ summary.boardId, item.cardId, item.id, item.createdAt, firstRiskTitle(item), item.score?.score, item.previousScore, item.scoreDelta, item.trend, item.score?.status, item.score?.gate?.deploymentReady, item.review?.needed, (item.review?.reasons || []).join('; '), (item.review?.actions || []).join('; '), item.review?.ageDays, item.review?.stale, (item.score?.gate?.blockingReasons || []).join('; '), item.input?.projectTypology, item.input?.lifecyclePhase, item.input?.measurementMaturity ]); } return `${rows.map((row) => row.map(csvCell).join(',')).join('\n')}\n`; } export function buildReviewQueueCsv(queue) { const rows = [[ 'board_id', 'card_id', 'assessment_id', 'created_at', 'risk_title', 'score', 'trend', 'status', 'deployment_ready', 'review_reasons', 'recommended_actions', 'assessment_age_days', 'stale_assessment', 'blocking_reasons' ]]; for (const item of queue.items || []) { rows.push([ queue.boardId, item.cardId, item.id, item.createdAt, firstRiskTitle(item), item.score?.score, item.trend, item.score?.status, item.score?.gate?.deploymentReady, (item.review?.reasons || []).join('; '), (item.review?.actions || []).join('; '), item.review?.ageDays, item.review?.stale, (item.score?.gate?.blockingReasons || []).join('; ') ]); } return `${rows.map((row) => row.map(csvCell).join(',')).join('\n')}\n`; }