| 138 | } |
| 139 | |
| 140 | function parseGradingFile( |
| 141 | filePath: string, |
| 142 | appsDir: string |
| 143 | ): GradeResult | null { |
| 144 | const pathInfo = parsePathInfo(filePath, appsDir); |
| 145 | if (!pathInfo) return null; |
| 146 | |
| 147 | const content = fs.readFileSync(filePath, 'utf-8'); |
| 148 | |
| 149 | // Extract total score: | **Total Feature Score** | 36 / 36 | |
| 150 | const scoreMatch = content.match( |
| 151 | /\*\*Total Feature Score\*\*\s*\|\s*(\d+(?:\.\d+)?)\s*\/\s*(\d+)/ |
| 152 | ); |
| 153 | if (!scoreMatch) { |
| 154 | console.warn(`Could not parse total score from: ${filePath}`); |
| 155 | return null; |
| 156 | } |
| 157 | |
| 158 | const totalScore = parseFloat(scoreMatch[1]); |
| 159 | const maxScore = parseFloat(scoreMatch[2]); |
| 160 | const percentage = maxScore > 0 ? (totalScore / maxScore) * 100 : 0; |
| 161 | |
| 162 | // Extract date: **Date:** 2026-01-05 |
| 163 | const dateMatch = content.match(/\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})/); |
| 164 | const date = dateMatch ? dateMatch[1] : ''; |
| 165 | |
| 166 | // Extract prompt level: | **Prompt Level Used** | 9 (...) | |
| 167 | const levelMatch = content.match(/\*\*Prompt Level Used\*\*\s*\|\s*(\d+)/); |
| 168 | const promptLevel = levelMatch ? parseInt(levelMatch[1], 10) : null; |
| 169 | |
| 170 | // Extract compile/run status |
| 171 | const compiles = /- \[x\] Compiles/i.test(content); |
| 172 | const runs = /- \[x\] Runs/i.test(content); |
| 173 | |
| 174 | // Extract LOC: | Lines of code (backend) | ~650 | |
| 175 | const locBackendMatch = content.match( |
| 176 | /Lines of code \(backend\)\s*\|\s*~?(\d+)/ |
| 177 | ); |
| 178 | const locFrontendMatch = content.match( |
| 179 | /Lines of code \(frontend\)\s*\|\s*~?(\d+)/ |
| 180 | ); |
| 181 | const numFilesMatch = content.match( |
| 182 | /Number of files(?:\s+created)?\s*\|\s*(\d+)/ |
| 183 | ); |
| 184 | |
| 185 | const locBackend = locBackendMatch ? parseInt(locBackendMatch[1], 10) : null; |
| 186 | const locFrontend = locFrontendMatch |
| 187 | ? parseInt(locFrontendMatch[1], 10) |
| 188 | : null; |
| 189 | const numFiles = numFilesMatch ? parseInt(numFilesMatch[1], 10) : null; |
| 190 | |
| 191 | // Extract per-feature scores: ## Feature 1: Basic Chat Features (Score: 3 / 3) |
| 192 | const featureScores: FeatureScore[] = []; |
| 193 | const featureRegex = |
| 194 | /## Feature (\d+):\s*([^(]+?)\s*\(Score:\s*(\d+(?:\.\d+)?)\s*\/\s*(\d+)\)/g; |
| 195 | let match; |
| 196 | while ((match = featureRegex.exec(content)) !== null) { |
| 197 | featureScores.push({ |