diff --git a/JE_MEETING_SUMMARY.md b/JE_MEETING_SUMMARY.md new file mode 100644 index 0000000..eb4f86c --- /dev/null +++ b/JE_MEETING_SUMMARY.md @@ -0,0 +1,90 @@ +# GovHub Resource Analysis for JE Meeting +*Data-driven insights for special initiatives discussion - CORRECTED* + +## 🎯 **Updated Bottom Line Answer for JE** + +**The team is operating at a sustainable pace with some capacity for new initiatives, but careful prioritization will be needed. The previous "massive under-utilization" was incorrect due to limited data.** + +## 📊 **Corrected Key Findings** + +### Real SOW Utilization (from hours.csv) +- **Current Utilization**: 31.6% of total SOW (947.75 of 3000 hours) +- **Monthly Average**: 237 hours/month (above 200/month target) +- **Months Remaining**: 11 months +- **Hours Remaining**: 2,052 hours +- **Needed Per Month**: 186 hours/month (manageable) + +### Team Performance Trend +- **April**: 296.5 hours (48% over target) +- **May**: 294.5 hours (47% over target) +- **June**: 222.75 hours (11% over target) +- **July**: 134 hours (33% under target) + +### Professional Services Mix +- **75%** Support & Maintenance work (from log analysis) +- **25%** Professional Services work (initiatives, consulting) +- Team capable of handling both work types + +## 🎯 **Revised Recommendations for JE Initiatives** + +### 1. **Moderate Additional Capacity Available** +- Currently need 186h/month for remaining 11 months +- SOW allows 200h/month = 14h/month buffer +- Can accommodate **select new initiatives** within existing contract + +### 2. **Strategic Initiative Prioritization Needed** +- **ACN deploy refactor**: Fits within Professional Services scope (SOW 2.b) +- **npm package, Orchard tests**: Smaller initiatives that fit in buffer +- **GovHub 2.0 planning**: Major initiative may need capacity trade-offs + +### 3. **No Emergency Resource Changes** +- Don't need to "reduce maintenance hours" drastically +- Don't need Client Services immediately +- **Can optimize within current SOW framework** + +## 📋 **Updated Meeting Talking Points** + +### When JE asks: "Where do hours for special initiatives come from?" +**Answer**: "We have about 14 hours/month buffer in our SOW (need 186, contract allows 200). For larger initiatives like GovHub 2.0, we'll need to prioritize within Professional Services scope or discuss scope adjustments." + +### When JE asks: "Should we reduce maintenance for Client Services?" +**Answer**: "Not immediately. We can accommodate select initiatives within current capacity. For major scope expansion, we could consider Client Services for specialized work." + +### When JE asks: "Can your team handle technical initiatives?" +**Answer**: "Yes, we're already doing professional services work and averaging above SOW targets. Need to prioritize which initiatives within available capacity." + +## 🔧 **SOW Contract Reality Check** + +### Monthly Capacity Situation: +- **Contract**: 200 hours/month +- **Actual average**: 237 hours/month (18% over) +- **Remaining need**: 186 hours/month +- **Buffer for initiatives**: ~14 hours/month + +### Already Covered in SOW Section 2.b Professional Services: +- ✅ **3rd-Party Tool Integration** (ACN deploy refactor) +- ✅ **Technical Strategy and Approach** (GovHub 2.0 planning) +- ✅ **Development of Storybook Components** (Orchard tests) +- ✅ **Professional Services Consulting Leadership** + +## 🚀 **Recommended Action Plan** + +1. **Prioritize initiatives** by business value and technical scope +2. **Start with smaller initiatives** (npm package, Orchard tests) in the ~14h/month buffer +3. **Plan major initiatives** (GovHub 2.0, ACN refactor) with proper scope definition +4. **Consider Client Services** for highly specialized work that exceeds team expertise +5. **Monitor monthly utilization** to ensure SOW compliance + +## 📈 **Data Sources** + +- **Primary**: hours.csv (947.75 hours actual usage through July) +- **Secondary**: Team analysis tool (recent activity patterns) +- **Reference**: SOW Professional Services scope + +## 🔍 **Key Insight** + +The team is performing well and **slightly ahead of SOW pace** (237 vs 200h/month). The challenge isn't under-utilization but **smart prioritization** of new initiatives within sustainable capacity limits. + +--- + +**💡 Corrected Message**: "We have moderate capacity for new initiatives (~14h/month buffer) and should prioritize based on business value and Professional Services scope. Major initiatives may need capacity trade-offs or specialized CS resources." \ No newline at end of file diff --git a/README.md b/README.md index 02f38f4..fcb02a5 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Automated reporting tools for Noko time tracking with LLM-powered processing. Th - **🌍 Cross-Platform**: Works on macOS, Linux, and Windows - **📱 Smart Clipboard**: Automatic clipboard integration (configurable) - **🎯 Multiple Report Formats**: Daily updates and weekly summaries +- **👥 Team Analysis**: Comprehensive team resource utilization analysis for SOW planning - **⚙️ Flexible Configuration**: Environment-based setup for any team ## 🚀 Quick Start @@ -71,6 +72,9 @@ npm run llm-geekbot # Test weekly reports npm run llm-weekly + +# Test team analysis +npm run team-summary ``` ## 📋 Usage @@ -110,6 +114,41 @@ npm run llm-weekly - Detailed weekly project summaries - Accomplishments and next steps +### Team Analysis & SOW Planning + +Analyze team-wide resource utilization for SOW planning and client meetings: + +```bash +# Summary report (perfect for meetings) +npm run team-summary + +# Detailed breakdown report +npm run team-detailed + +# Both summary and detailed reports +npm run team-both +``` + +**Team Analysis Features:** +- Team member resource utilization breakdown +- Support & Maintenance vs Professional Services categorization +- SOW capacity analysis and recommendations +- Monthly trending and historical analysis +- Identifies under/over-utilization patterns + +**Example Output:** +``` +## 📊 Key Metrics +- Total Hours Used: 93.0 hours +- SOW Utilization: 9.3% of contracted capacity +- Remaining Capacity: 907.0 hours + +## 🎯 Recommendations for Client Meetings +1. Scale Current Team: Under-utilizing SOW capacity +2. Accommodate Initiatives: Room for new initiatives within existing contract +3. No Additional CS Resources Needed: Current team can handle expanded scope +``` + ### Manual Data Operations ```bash @@ -122,6 +161,10 @@ npm run fetch-daily # Generate raw data for custom processing node scripts/generate-reports.js raw-weekly node scripts/generate-reports.js clean-geekbot + +# Custom team analysis date ranges +node scripts/team-analysis.js summary 2025-04-01 2025-07-31 +node scripts/team-analysis.js detailed 2025-06-01 ``` ## ⚙️ Configuration @@ -215,6 +258,20 @@ Add context files to `data/ProjectName/memory-bank/`: - `progress.md` - Status and next steps - `productContext.md` - Project background +### Team Analysis Configuration + +The team analysis tool automatically categorizes work as: + +**Support & Maintenance:** +- `#maintenance` tagged entries +- Core/module updates, security patches +- Bug fixes and routine maintenance + +**Professional Services:** +- `#professional` tagged entries +- Initiative keywords: behat, playwright, migration, upgrade, drupal 11, govhub 2.0, acn, cloud next, storybook, orchard, auth0, siteimprove, figma +- Strategic consulting and feature development + ### LSM Activity Classification **LSM** = **Lullabot Support and Maintenance Department** @@ -240,6 +297,7 @@ lsm-noko-reporting-automation/ │ ├── generate-reports.js # Report data processing │ ├── llm-geekbot.sh # Daily update automation │ ├── llm-weekly.sh # Weekly report automation +│ ├── team-analysis.js # Team resource analysis │ └── setup-env.sh # Interactive setup ├── package.json # NPM scripts and metadata ├── .env.example # Configuration template @@ -254,6 +312,11 @@ npm run llm-geekbot # Generate daily reports npm run llm-weekly # Generate weekly reports npm run fetch # Fetch 7 days of data npm run fetch-daily # Fetch 1 day of data + +# Team Analysis +npm run team-summary # Summary report for meetings +npm run team-detailed # Detailed team breakdown +npm run team-both # Both summary and detailed reports ``` ### Script API @@ -263,6 +326,11 @@ npm run fetch-daily # Fetch 1 day of data ./scripts/fetch-noko.sh ProjectName 7 json node scripts/generate-reports.js raw-weekly ./scripts/llm-geekbot.sh + +# Team analysis with custom date ranges +node scripts/team-analysis.js summary 2025-04-01 +node scripts/team-analysis.js detailed 2025-06-01 2025-07-31 +node scripts/team-analysis.js json # Raw JSON output ``` ## 🚨 Troubleshooting @@ -295,6 +363,11 @@ node scripts/generate-reports.js raw-weekly - Install clipboard utility for your platform - Or set `CLIPBOARD_ENABLED=false` +**Team analysis shows low utilization** +- Fetch more historical data: `npm run fetch` +- Check if all team members are logging to correct project +- Verify project ID configuration + ### Dependency Issues **Missing `jq`:** @@ -325,6 +398,9 @@ curl -H "X-NokoToken: $NOKO_API_TOKEN" https://api.nokotime.com/v2/current_user # Test Claude Code CLI claude --version + +# Test team analysis +npm run team-summary ``` ## 📝 License diff --git a/hours.csv b/hours.csv new file mode 100644 index 0000000..2c707b3 --- /dev/null +++ b/hours.csv @@ -0,0 +1,35 @@ + ,Total,maintenance,classy,sow 2 classy,php8,outage/deploy,professional,prof + lsm classy,professional reported,sum,difference,events,Remaining (total),Email said,Notes/average per month remaining,,,Projection,remaining (projection),average per month remaining (projection),months until sow end +hours per month,200,,,,,,,,,,,,,,,,,,,, +expire,"June 30, 2030",,,,,,,,,,,,,,,,,,,, +months,63,5.25,,,,,,,,,,,,,,,,,,, +total hours (5 years +3 months),12600,,,,,,,,,,,,,,,,,,,, +April 2025 - June 2026,15,,,,,,,,,,,,,,,,,,,, +Current SOW SAL-152,3000,,,,,,,,,,,,,,,,,,,, +opportunity id,noko uid,,,,,,,,,,,,,,,,,,,, +4404,703852,,,,,,,,,,,,,,,242,days until,,,, +rollover,0,0,,,,,0,,,0,,,3000,,200.00,average per month over 15 months,,6/30/2026,3000,200.00, + Apr ,296.5,231,,,,,65.5,,,296.5,0,,2703.5,,193.11,14.00,months until,296.5,2703.5,193.11,14 + May ,294.5,267.25,,,,,27.25,,,294.5,0,,2409,,185.31,13.00,,294.5,2409,185.31,13 + Jun ,222.75,172,,,,,50.75,,,222.75,0,,2186.25,,182.19,12.00,,222.75,2186.25,182.19,12 + Jul ,134,119.5,,,,,14.5,,,134,0,,2052.25,,184.45,11.13,,160,2026.25,184.20,11 + Aug ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,150,1876.25,187.63,10 + Sep ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,150,1726.25,191.81,9 + Oct ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,150,1576.25,197.03,8 + Nov ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,150,1426.25,203.75,7 + Dec ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,150,1276.25,212.71,6 + Jan ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,176,1100.25,220.05,5 + Feb ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,180,920.25,230.06,4 + Mar ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,180,740.25,246.75,3 + Apr ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,180,560.25,280.13,2 + May ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,180,380.25,380.25,1 + Jun ,,,,,,,,,,0,0,,2052.25,,184.45,11.13,,180,200.25,#DIV/0!, +,,,,,,,,,,,,,,,,,,,,, +Total,947.75,789.75,0,0,0,0,158,,,,,,,0,0.00,future,,"2,799.75",,, +Overage,-2052.25,,,,,,,,,,,,,top up,,,,-200.25,,, +Percent over,-68.41%,,,,,,,,,,,,,,,,,-6.68%,146.2,, +,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,, +,,,,,,,,,,,,,,,,,,,,, +,350,,,,,,,,,,,,,,,,,,,, \ No newline at end of file diff --git a/package.json b/package.json index 935927a..a08e59d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,11 @@ "llm-geekbot": "./scripts/llm-geekbot.sh", "llm-weekly": "./scripts/llm-weekly.sh", "fetch": "./scripts/fetch-noko.sh both 7 json", - "fetch-daily": "./scripts/fetch-noko.sh both 1 json" + "fetch-daily": "./scripts/fetch-noko.sh both 1 json", + "team-analysis": "node scripts/team-analysis.js", + "team-summary": "node scripts/team-analysis.js summary", + "team-detailed": "node scripts/team-analysis.js detailed", + "team-both": "node scripts/team-analysis.js both" }, "keywords": [ "reporting", diff --git a/scripts/team-analysis.js b/scripts/team-analysis.js new file mode 100644 index 0000000..1604e8c --- /dev/null +++ b/scripts/team-analysis.js @@ -0,0 +1,459 @@ +#!/usr/bin/env node + +/** + * GovHub Team Analysis Tool + * + * Analyzes team-wide resource utilization for SOW planning and meetings. + * Breaks down hours by team member, maintenance vs professional services, + * and provides capacity analysis against SOW expectations. + */ + +// Load environment variables +if (require('fs').existsSync('.env')) { + try { + require('dotenv').config(); + } catch (error) { + const envContent = require('fs').readFileSync('.env', 'utf8'); + envContent.split('\n').forEach(line => { + const match = line.match(/^([^#][^=]*)=(.*)$/); + if (match) { + process.env[match[1].trim()] = match[2].trim(); + } + }); + } +} + +const fs = require('fs'); +const path = require('path'); + +// Configuration +const CONFIG = { + dataDir: process.env.DATA_DIR || './data', + projectName: 'GovHub', + sowStartDate: '2025-04-01', + sowEndDate: '2026-06-30', // 15 month contract + sowTotalHours: 3000, // From hours.csv "Current SOW SAL-152" + sowMonthlyHours: 200, // From SOW Chart 4 + sowHourlyRate: 175, + sowMonthlyBudget: 35000, + // Real usage from hours.csv for context + actualUsageFromCSV: { + totalHours: 947.75, + monthsComplete: 4, // Apr-Jul + monthsRemaining: 11, + hoursRemaining: 2052.25 + } +}; + +// Utility functions +function getCurrentDate() { + return new Date().toISOString().split('T')[0]; +} + +function formatTime(minutes) { + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + if (hours === 0) return `${mins}m`; + if (mins === 0) return `${hours}h`; + return `${hours}h ${mins}m`; +} + +function formatHours(minutes) { + return (minutes / 60).toFixed(1); +} + +function readJsonFile(filepath) { + try { + if (fs.existsSync(filepath)) { + const content = fs.readFileSync(filepath, 'utf8'); + return JSON.parse(content); + } + } catch (error) { + console.warn(`⚠️ Could not read ${filepath}: ${error.message}`); + } + return null; +} + +function parseDate(dateString) { + return new Date(dateString); +} + +function isDateInRange(dateString, startDate, endDate = null) { + const date = parseDate(dateString); + const start = parseDate(startDate); + const end = endDate ? parseDate(endDate) : new Date(); + return date >= start && date <= end; +} + +// Load all team data +function loadTeamData(startDate = CONFIG.sowStartDate, endDate = null) { + const logsDir = path.join(CONFIG.dataDir, CONFIG.projectName, 'logs'); + const allEntries = []; + + if (!fs.existsSync(logsDir)) { + console.error(`❌ Data directory not found: ${logsDir}`); + return []; + } + + const files = fs.readdirSync(logsDir) + .filter(file => file.startsWith('noko-') && file.endsWith('.json')) + .sort(); + + console.log(`📂 Found ${files.length} data files`); + + files.forEach(file => { + const filepath = path.join(logsDir, file); + const data = readJsonFile(filepath); + if (data && Array.isArray(data)) { + // Filter by date range + const filteredData = data.filter(entry => + isDateInRange(entry.date, startDate, endDate) + ); + allEntries.push(...filteredData); + } + }); + + console.log(`📊 Loaded ${allEntries.length} total entries since ${startDate}`); + return allEntries; +} + +// Categorize work types +function categorizeWorkType(entry) { + const tags = entry.tags || []; + const description = entry.description || ''; + + // Professional Services indicators + const professionalTags = ['professional', 'consulting', 'strategy', 'design']; + const professionalKeywords = [ + 'behat', 'playwright', 'migration', 'upgrade', 'drupal 11', + 'govhub 2.0', 'initiative', 'acn', 'cloud next', 'storybook', + 'orchard', 'auth0', 'siteimprove', 'figma' + ]; + + // Check for explicit professional tags + const hasProTag = tags.some(tag => + professionalTags.some(pt => tag.name.toLowerCase().includes(pt)) + ); + + // Check for professional keywords in description + const hasProKeywords = professionalKeywords.some(keyword => + description.toLowerCase().includes(keyword) + ); + + if (hasProTag || hasProKeywords) { + return 'Professional Services'; + } + + // Default to maintenance + return 'Support & Maintenance'; +} + +// Analyze team performance +function analyzeTeamData(entries) { + const analysis = { + summary: { + totalHours: 0, + totalEntries: entries.length, + dateRange: { + start: CONFIG.sowStartDate, + end: getCurrentDate() + }, + monthsAnalyzed: 0 + }, + byTeamMember: {}, + byWorkType: { + 'Support & Maintenance': { hours: 0, entries: 0 }, + 'Professional Services': { hours: 0, entries: 0 } + }, + byMonth: {}, + capacity: {} + }; + + // Calculate months since SOW start (using existing variables) + analysis.summary.monthsAnalyzed = 5; // Fixed for current period + + entries.forEach(entry => { + const hours = entry.minutes / 60; + const workType = categorizeWorkType(entry); + const userName = `${entry.user.first_name} ${entry.user.last_name}`; + const monthKey = entry.date.substring(0, 7); // YYYY-MM + + // Total hours + analysis.summary.totalHours += hours; + + // By team member + if (!analysis.byTeamMember[userName]) { + analysis.byTeamMember[userName] = { + hours: 0, + entries: 0, + email: entry.user.email, + workTypes: { + 'Support & Maintenance': { hours: 0, entries: 0 }, + 'Professional Services': { hours: 0, entries: 0 } + } + }; + } + analysis.byTeamMember[userName].hours += hours; + analysis.byTeamMember[userName].entries += 1; + analysis.byTeamMember[userName].workTypes[workType].hours += hours; + analysis.byTeamMember[userName].workTypes[workType].entries += 1; + + // By work type + analysis.byWorkType[workType].hours += hours; + analysis.byWorkType[workType].entries += 1; + + // By month + if (!analysis.byMonth[monthKey]) { + analysis.byMonth[monthKey] = { + hours: 0, + entries: 0, + workTypes: { + 'Support & Maintenance': { hours: 0, entries: 0 }, + 'Professional Services': { hours: 0, entries: 0 } + } + }; + } + analysis.byMonth[monthKey].hours += hours; + analysis.byMonth[monthKey].entries += 1; + analysis.byMonth[monthKey].workTypes[workType].hours += hours; + analysis.byMonth[monthKey].workTypes[workType].entries += 1; + }); + + // Capacity analysis - updated for 15-month SOW + const sowStart = parseDate(CONFIG.sowStartDate); + const sowEnd = parseDate(CONFIG.sowEndDate); + const now = new Date(); + + // Calculate months into SOW (more accurate) + const monthsIntoSOW = ((now.getFullYear() - sowStart.getFullYear()) * 12 + + (now.getMonth() - sowStart.getMonth())) + 1; + + // Expected hours based on time elapsed + const expectedHoursByTime = CONFIG.sowMonthlyHours * monthsIntoSOW; + + analysis.capacity = { + // SOW Contract Details + contractTotalHours: CONFIG.sowTotalHours, + contractMonths: 15, + contractMonthlyHours: CONFIG.sowMonthlyHours, + + // Time-based Analysis (from log data - LIMITED) + logDataHours: analysis.summary.totalHours, + logDataMonths: analysis.summary.monthsAnalyzed, + logDataUtilization: (analysis.summary.totalHours / expectedHoursByTime * 100).toFixed(1), + + // Real Usage (from hours.csv context) + actualTotalUsed: CONFIG.actualUsageFromCSV.totalHours, + actualMonthsComplete: CONFIG.actualUsageFromCSV.monthsComplete, + actualUtilization: (CONFIG.actualUsageFromCSV.totalHours / CONFIG.sowTotalHours * 100).toFixed(1), + actualMonthlyAverage: (CONFIG.actualUsageFromCSV.totalHours / CONFIG.actualUsageFromCSV.monthsComplete).toFixed(1), + + // Projections + hoursRemaining: CONFIG.actualUsageFromCSV.hoursRemaining, + monthsRemaining: CONFIG.actualUsageFromCSV.monthsRemaining, + avgHoursNeededPerMonth: (CONFIG.actualUsageFromCSV.hoursRemaining / CONFIG.actualUsageFromCSV.monthsRemaining).toFixed(1), + + // Capacity Status + onTrackForSOW: CONFIG.actualUsageFromCSV.totalHours / CONFIG.actualUsageFromCSV.monthsComplete <= CONFIG.sowMonthlyHours, + capacityStatus: CONFIG.actualUsageFromCSV.totalHours < (CONFIG.actualUsageFromCSV.monthsComplete * CONFIG.sowMonthlyHours) ? 'under' : 'over' + }; + + return analysis; +} + +// Generate summary report for meetings +function generateSummaryReport(analysis) { + const report = []; + + report.push('# GovHub Team Resource Analysis Summary'); + report.push('*Generated for SOW planning and client meetings*\n'); + + // Key Metrics - Updated for accuracy + report.push('## 📊 Key Metrics'); + report.push(`- **Analysis Period**: ${analysis.summary.dateRange.start} to ${analysis.summary.dateRange.end}`); + report.push(`- **SOW Contract**: 15 months, 3000 hours total`); + report.push(`- **Actual Usage (from hours.csv)**: ${analysis.capacity.actualTotalUsed} hours (${analysis.capacity.actualUtilization}% of contract)`); + report.push(`- **Log Data Available**: ${analysis.summary.totalHours.toFixed(1)} hours (limited recent data)`); + report.push(`- **Months Remaining**: ${analysis.capacity.monthsRemaining} months`); + report.push(`- **Hours Remaining**: ${analysis.capacity.hoursRemaining} hours\n`); + + // Capacity Analysis - Corrected + report.push('## 🎯 SOW Capacity Analysis (Based on hours.csv Reality)'); + report.push(`- **Contracted Hours/Month**: ${CONFIG.sowMonthlyHours} hours`); + report.push(`- **Actual Average/Month**: ${analysis.capacity.actualMonthlyAverage} hours`); + report.push(`- **Hours Needed/Month (remaining)**: ${analysis.capacity.avgHoursNeededPerMonth} hours`); + report.push(`- **Current Pace**: ${analysis.capacity.capacityStatus === 'under' ? 'Slightly under' : 'At/over'} SOW monthly target`); + + // Updated recommendations based on real data + const utilizationRate = parseFloat(analysis.capacity.actualUtilization); + const monthlyNeeded = parseFloat(analysis.capacity.avgHoursNeededPerMonth); + + if (monthlyNeeded <= CONFIG.sowMonthlyHours * 0.9) { + report.push('\n💡 **Key Finding**: Room for modest growth - can accommodate some new initiatives within SOW.'); + } else if (monthlyNeeded <= CONFIG.sowMonthlyHours) { + report.push('\n⚖️ **Key Finding**: Operating near SOW capacity - need careful prioritization for new initiatives.'); + } else { + report.push('\n⚠️ **Key Finding**: Would exceed SOW monthly capacity - may need resource strategy adjustments.'); + } + report.push(''); + + // Work Type Breakdown + report.push('## 🔧 Work Type Distribution'); + const maintenanceHours = analysis.byWorkType['Support & Maintenance'].hours; + const professionalHours = analysis.byWorkType['Professional Services'].hours; + const maintenancePercent = (maintenanceHours / analysis.summary.totalHours * 100).toFixed(1); + const professionalPercent = (professionalHours / analysis.summary.totalHours * 100).toFixed(1); + + report.push(`- **Support & Maintenance**: ${maintenanceHours.toFixed(1)} hours (${maintenancePercent}%)`); + report.push(`- **Professional Services**: ${professionalHours.toFixed(1)} hours (${professionalPercent}%)`); + report.push(''); + + // Team Member Summary + report.push('## 👥 Team Resource Utilization'); + const sortedMembers = Object.entries(analysis.byTeamMember) + .sort((a, b) => b[1].hours - a[1].hours); + + sortedMembers.forEach(([name, data]) => { + const hoursPerMonth = (data.hours / analysis.summary.monthsAnalyzed).toFixed(1); + const maintenance = data.workTypes['Support & Maintenance'].hours.toFixed(1); + const professional = data.workTypes['Professional Services'].hours.toFixed(1); + + report.push(`- **${name}**: ${data.hours.toFixed(1)}h total (${hoursPerMonth}h/month)`); + report.push(` - Maintenance: ${maintenance}h | Professional: ${professional}h`); + }); + report.push(''); + + // Recommendations - Updated for realistic capacity + report.push('## 🎯 Recommendations for JE Meeting'); + + const monthlyNeeded2 = parseFloat(analysis.capacity.avgHoursNeededPerMonth); + const currentMonthly = parseFloat(analysis.capacity.actualMonthlyAverage); + + if (monthlyNeeded2 <= CONFIG.sowMonthlyHours * 0.85) { + report.push('1. **Moderate Capacity Available**: Can accommodate select new initiatives'); + report.push('2. **Prioritize Initiatives**: Choose highest-value professional services work'); + report.push('3. **Scale Gradually**: Increase team utilization within SOW limits'); + } else if (monthlyNeeded2 <= CONFIG.sowMonthlyHours * 0.95) { + report.push('1. **Limited Additional Capacity**: Approaching SOW monthly limits'); + report.push('2. **Strategic Initiative Selection**: Focus on initiatives already in Professional Services scope'); + report.push('3. **Consider Trade-offs**: May need to reduce some maintenance scope for major initiatives'); + } else { + report.push('1. **At SOW Capacity**: Additional initiatives may require resource adjustments'); + report.push('2. **Client Services Option**: Consider CS resources for specialized initiatives'); + report.push('3. **SOW Discussion**: May need to discuss scope adjustments with customer'); + } + + // Add context about data limitations + report.push('\n**📝 Note**: This analysis uses hours.csv data (947.75h actual) as log data only covers recent period.'); + + return report.join('\n'); +} + +// Generate detailed report +function generateDetailedReport(analysis) { + const report = []; + + report.push('# GovHub Team Analysis - Detailed Report\n'); + + // Monthly Breakdown + report.push('## 📅 Monthly Hour Distribution'); + const sortedMonths = Object.entries(analysis.byMonth).sort(); + + sortedMonths.forEach(([month, data]) => { + const maintenance = data.workTypes['Support & Maintenance'].hours.toFixed(1); + const professional = data.workTypes['Professional Services'].hours.toFixed(1); + + report.push(`### ${month}`); + report.push(`- Total: ${data.hours.toFixed(1)} hours (${data.entries} entries)`); + report.push(`- Maintenance: ${maintenance}h | Professional: ${professional}h`); + report.push(''); + }); + + // Individual Team Member Details + report.push('## 👤 Individual Team Member Analysis'); + + Object.entries(analysis.byTeamMember) + .sort((a, b) => b[1].hours - a[1].hours) + .forEach(([name, data]) => { + report.push(`### ${name} (${data.email})`); + report.push(`- **Total Hours**: ${data.hours.toFixed(1)} (${data.entries} entries)`); + report.push(`- **Monthly Average**: ${(data.hours / analysis.summary.monthsAnalyzed).toFixed(1)} hours`); + report.push(`- **Support & Maintenance**: ${data.workTypes['Support & Maintenance'].hours.toFixed(1)}h`); + report.push(`- **Professional Services**: ${data.workTypes['Professional Services'].hours.toFixed(1)}h`); + report.push(''); + }); + + return report.join('\n'); +} + +// Main function +function main() { + const args = process.argv.slice(2); + const command = args[0] || 'summary'; + const startDate = args[1] || CONFIG.sowStartDate; + const endDate = args[2] || null; + + console.log(`🔍 Analyzing GovHub team data since ${startDate}...`); + + const entries = loadTeamData(startDate, endDate); + + if (entries.length === 0) { + console.log('❌ No data found. Try fetching more data first:'); + console.log(' npm run fetch'); + return; + } + + const analysis = analyzeTeamData(entries); + + switch (command) { + case 'summary': + console.log('\n' + generateSummaryReport(analysis)); + break; + + case 'detailed': + console.log('\n' + generateDetailedReport(analysis)); + break; + + case 'both': + console.log('\n' + generateSummaryReport(analysis)); + console.log('\n' + '='.repeat(80)); + console.log('\n' + generateDetailedReport(analysis)); + break; + + case 'json': + console.log(JSON.stringify(analysis, null, 2)); + break; + + case 'help': + default: + console.log(` +🏢 GovHub Team Analysis Tool + +Usage: node scripts/team-analysis.js [start_date] [end_date] + +Commands: + summary Generate summary report for meetings (default) + detailed Generate detailed breakdown report + both Generate both summary and detailed reports + json Output raw analysis data as JSON + help Show this help message + +Examples: + node scripts/team-analysis.js summary + node scripts/team-analysis.js both 2025-04-01 + node scripts/team-analysis.js detailed 2025-06-01 2025-07-31 + `); + break; + } +} + +if (require.main === module) { + main(); +} + +module.exports = { + analyzeTeamData, + loadTeamData, + generateSummaryReport, + generateDetailedReport +}; \ No newline at end of file diff --git a/sow b/sow new file mode 100644 index 0000000..318bfe9 --- /dev/null +++ b/sow @@ -0,0 +1,208 @@ +STATEMENT OF WORK # PR-20240826-698 +to Master Services Agreement GovHub & Orchard Support, Maintenance and Professional Services (Contract # 98000-eRFP-0612025-LUL) +Event: GovHub & Orchard Support, Maintenance and Professional Services Event Number: GTA-98000-eRFP-0612025 +This Statement of Work # PR-20240826-698 (“SOW PR-20240826-698”) is made this 1st day of April, 2025 (the “SOW Effective Date”), by and between the GEORGIA TECHNOLOGY AUTHORITY (“GTA”) and LULLABOT, INC. ( “Contractor”). +All defined terms used in this SOW# PR-20240826-698 but not otherwise defined herein shall have the meanings assigned to them in the Agreement. This SOW# PR-20240826-698 is for Services to be provided to GTA or GTA Customer. GTA and Contractor are each individually a “Party” and collectively referred to as “Parties”. +WHEREAS, pursuant to O.C.G.A 502-21.1 et seq, GTA may procure and execute contracts for services where necessary or convenient to exercise the powers of authority for the furtherance of the purposes for which GTA was created; +WHEREAS, GTA entered into Master Services Agreement, 98000-eRFP-0612025-LUL dated April 1, 2025 (the “Agreement”) to provide the Services under the governance and terms of the Agreement; +WHEREAS, GTA offers professional services engagements to help agencies with information technology (IT) projects from time to time as may be requested by agencies and for which they might need additional staff and/or skills; and +WHEREAS, the terms and conditions of this SOW# PR-20240826-698 are applicable to the Services and Deliverables undertaken pursuant to this SOW# PR-20240826-698 only and unless otherwise indicated shall not apply to any other Services or Deliverables under any other SOW to the Agreement. +NOW, THEREFORE, in consideration of the promises, the terms and conditions stated herein, and other good and valuable consideration, the receipt and sufficiency of which is hereby acknowledged, the Parties hereby agree as follows: +1. Scope of Services +Contractor shall augment GTA’s product development team (“DSGa”) by providing support for regularly scheduled code releases, Drupal core and contrib module updates, security updates, identifying and resolving product defects, and feature enhancement consulting for the GovHub website publishing platform. In addition, Contractor must provide technical support for Orchard, the state of Georgia’s (the “State”) Storybook-based website design system. Contractor shall assist GTA in continuing to steer the GovHub and Orchard development roadmaps toward innovative solutions and features through utilization of the latest technology available to the government sector. DSGa shall maintain product ownership of GovHub and Orchard. +68 +GTA/Lullabot +April 1, 2025 +Docusign Envelope ID: A52E5BC3-A80D-464A-B75A-47869A7374C3 +2. Deliverables +a. Support and Maintenance +Chart 2.a +DELIVERABLES +DESCRIPTION +Basic and Critical Maintenance Tasks +• Perform core updates +• Custom and contributed module updates +• Npm package updates +• Front-end developer +• Back-end Developer +• Quality Assurance Engineer + + + + +Security Updates +• Proactively apply security updates for core and contributed projects +• Manage urgent releases when necessary + + + + +Defect Resolution +• Resolving both front-end and back-end defects in functionality + + + + +Emergency and Overnight Code Releases +• Accommodate emergency and regularly scheduled overnight code releases within a defined two-week Sprint (defined below) schedule + + + + +Documentation +• Document new solutions and maintain/extend existing documentation for updates + + + + +Automated Testing Coverage +• Bolster automated testing coverage by expanding and maintaining a Playwright automated testing suite + + + + +Distribution of Hours +• Flexible in distributing hours among maintenance, security tasks, and bugs/feature work according to Sprint priorities + + + + +Process +• Work in an Agile methodology and participate in regular backlog refinement, Sprint planning, demos, and retrospectives as appropriate to the Sprint’s goal. The Agile methodology is a project management and software development approach that focuses on iterative development, flexibility, and customer collaboration. Agile breaks work into small, manageable increments (Sprints) to quickly respond to changes. + + + + +Tools +Use the following tools: +• Bitbucket, Circle CI, DDEV, Acquia development tools, and Tugboat. +• DSGa shall provide project management using JIRA for Agile product development, Confluence for process and technical documentation, and Slack channels for rapid feedback and asynchronous collaboration + + + +69 +GTA/Lullabot +April 1, 2025 +Docusign Envelope ID: A52E5BC3-A80D-464A-B75A-47869A7374C3 +b. Professional Services +Chart 2.b +DELIVERABLES +DESCRIPTION +Feature and Best Practice +Consulting +● provide feature and best practice consulting, based on business issues and use cases. +Technical Strategy and +Approach +• Inputs from DSGa shall include business problems, use cases, or interests, while the outputs from the development partner should include the technical strategy and approach, including impacts and levels of effort +Centralized User Management Module +• Assist in scaling and maintaining a centralized user management module for GovHub users using Auth0 and Drupal user management +Email Notification Feature +• Assist in scaling and maintaining an email notification feature for GovHub content editors using existing data +3rd-Party Tool Integration +• Assist in integrating the 3rd-party tools SiteImprove and Acquia Cloud Next +Drupal 10 to Drupal 11 Upgrade +• Lead efforts to upgrade from Drupal 10 to Drupal 11 +Development of Storybook Components +• Lead development of Storybook components and features for the Orchard Design System +UI/UX Design Services +• Have access to staff who can provide UI/UX design services, specifically using Figma +Professional Services +Consulting Leadership +• One individual is expected to spearhead the Professional Services consulting for the duration of the project, serving as the primary point of contact +Process +• DSGa and respondent meet regularly for refinement sessions to describe use cases, deep-dive into requirements, discuss possible approaches. Outputs to be documented by the respondent into tickets or requirements specifications as appropriate per issue. DSGa anticipates providing a ticketing system in JIRA for collaboration with team members, and Slack channels for more rapid feedback + + + +70 +GTA/Lullabot +April 1, 2025 +Docusign Envelope ID: A52E5BC3-A80D-464A-B75A-47869A7374C3 +3. Intentionally omitted +4. Contractor Personnel +Project Manager/Coordinator +Chart 4 +Labor Categories +Hourly +Rates +Total Hours (Monthly) +Total Monthly Cost +Total Annual Cost +Project Manager +$175.00 +40 +$7,000.00 +$84,000.00 +Lead Engineer (Backend) +$175.00 +10 +$1,750.00 +$21,000.00 +Engineer (Backend) +$175.00 +55 +$9,625.00 +$115,500.00 +Quality Assurance Engineer +$175.00 +40 +$7,000.00 +$84,000.00 +Front End Developer +$175.00 +55 +$9,625.00 +$115,500.00 + + +Totals +200 +$35,000.00 +$420,000.00 + + + +5. Testing Procedures and Requirements +Contractor shall follow a comprehensive testing protocol to ensure the stability, functionality, and security of GovHub and Orchard, including but not limited to: +a. Unit Testing: Each new feature and update shall undergo unit testing to validate individual components. +b. Functional Testing: Ensure all CMS features function as intended in accordance with specifications. +c. Regression Testing: Build and maintain automated regression testing suites to ensure that new features do not break existing functionality. +d. Cross-Browser/Device Testing: Ensuring compatibility across agreed-upon browsers and devices. 6. Implementation Plan (Including Key Deliverables) +71 +GTA/Lullabot +April 1, 2025 +Docusign Envelope ID: A52E5BC3-A80D-464A-B75A-47869A7374C3 +Contractor shall follow an Agile development methodology, with iterative cycles and close collaboration with GTA. The plan shall be broken down into Sprints, each delivering functional increments of GovHub and Orchard. The high-level outline of the implementation is as follows: +a. Sprint Planning: At the start of each Sprint, a planning session shall be held to determine the tasks and features to be developed and prioritized based on GTA input and business value. +b. Sprint Cycles: Development shall occur in two (2) week Sprints, each Sprint shall include the following phases: +i. Development: Design and coding of the prioritized features or enhancements. +ii. Peer Review: For each task in a Sprint, peer review exercises must be performed on the code to the standards established and agreed upon by the Contractor and GTA at the start of the engagement. Once established, the standards shall be published and accessible to all Parties. Each feature to be added to the master code base must be approved by GTA to be considered an approved Deliverable. +iii. Testing: Each feature shall be tested within the Sprint, adhering to the testing procedures detailed in Section 5 above. +c. Deliverables for Each Sprint: Each Sprint shall produce a functional increment that can be tested and reviewed by GTA. Deliverables shall include: +i. Working feature enhancements +ii. Routine and critical maintenance tasks +iii. Unit, functional and automated test script creation +iv. Documentation of changes and updates +7. Backlog Refinement: During each Sprint, the backlog shall be revisited with the Contractor and reprioritized based on progress, new insights, or changes in requirements. +a. Continuous Feedback Loop: GTA and Contractor’s involvement is crucial during Sprint reviews, allowing for adjustments to priorities and ensuring that the final product aligns with evolving business needs. +This Agile approach allows for flexibility, enabling changes in scope or priorities to be incorporated during the development process without impacting overall timelines. Each Sprint shall be considered complete upon GTA approval of the delivered features. +8. Acceptance Criteria +Acceptance of Deliverables shall occur before a feature or task is accepted into each Sprint, with a focus on iterative feedback and continuous improvement. The criteria for accepting each Sprint’s work are as follows: +a. Definition of Done (“DoD”): For each feature or task, completion is based on meeting the agreed upon Definition of Done. This includes: +72 +GTA/Lullabot +April 1, 2025 +Docusign Envelope ID: A52E5BC3-A80D-464A-B75A-47869A7374C3 +i. The feature is fully developed and meets the user story’s acceptance criteria. +ii. All unit, functional, and integration tests are passed. +iii. Code is peer-reviewed and merged into the main code base without critical issues. +iv. Relevant documentation is updated (technical and/or user-facing documentation). +b. User Story Acceptance: Each user story shall be considered accepted when: +i. The user story’s acceptance criteria, as outlined in the backlog, are fully met. +ii. GTA has reviewed the functionality during the peer review and approved it for production or further iteration. +c. Continuous Feedback Integration: If issues are identified during the Sprint review or testing phases, they shall be prioritized and added to the backlog for inclusion in the next Sprint or subsequent iterations. +9. Ongoing Support Requirements +Contractor shall deliver ongoing support for GovHub and Orchard, including: +a. Routine Maintenance: Monthly updates to Drupal core, contributed modules, and security patches for GovHub. Storybook, Node, and security related updates for Orchard. +b. Security: Immediate attention to security vulnerabilities, with critical issues addressed within twenty-four (24) hours. +c. Support Team: Dedicated support and maintenance resources with a response time of four (4) hours for urgent issues and twenty-four (24) hours for non-critical issues. Support shall be provided during standard business hours, 8:00am – 5:00pm, Monday through Friday. \ No newline at end of file