/
home
/
techb158
/
trellopowerup.abdallabala.com
/
src
/
/home/techb158/trellopowerup.abdallabala.com/src
mkdir
upload
Name
Size
Mode
Actions
assessmentAnalytics.js
7144
0644
edit
dl
rm
riskEngine.js
5756
0644
edit
dl
rm
server.js
7079
0644
edit
dl
rm
store.js
3545
0644
edit
dl
rm
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`; }
Save
cmd:
run