Transcript Extraction
Everything downstream depends on one unglamorous step: turning a scanned or PDF transcript into clean, structured coursework rows. Garbage in here becomes wrong findings later, so extraction gets confidence scores, normalization, and a human correction path.
From document to structured rows
Transcripts arrive as digital PDFs (text extractable directly) or scans (image-only, needing OCR). The practical .NET play is a document intelligence service β Azure AI Document Intelligence is the natural fit on this stack β called from the background worker you built in Module 10. You POST the document, poll for the result, and get back words, tables, and layout with per-element confidence.
var op = await client.AnalyzeDocumentAsync(
WaitUntil.Completed, "prebuilt-layout", stream);
var tables = op.Value.Tables; // rows/cells with confidence
Wrap the vendor SDK behind your own ITranscriptExtractionService interface in the business
layer. That keeps the vendor swappable, and β more importantly for this codebase β keeps
controllers and jobs testable with a fake extractor.
The hard part isn't the API call; it's that every institution formats transcripts differently.
Columns shift, terms interleave, transfer credits appear in footnotes. A hybrid approach works
best: the document service finds the tables, then your own parsing layer (or an LLM prompt over
the extracted text, for the messy cases) maps them into candidate Course rows.
Normalization
Raw extraction gives you "MATH 110A", "Math110-a", and "MTH 110" β possibly for the same
course. Before matching, normalize:
- Course codes β uppercase, strip punctuation, separate subject prefix from number.
- Units β semester vs quarter units is easy to get wrong; convert to one canonical unit system and record which conversion was applied.
- Grades β map letter grades, pass/no-pass, and numeric scales onto a canonical set; transcripts from decades ago use conventions you'll meet in production, not in testing.
- Institutions β resolve the printed name against your
Institutiontable with fuzzy matching; create-and-flag when no match clears the bar.
Normalization is deterministic code with unit tests, not AI. Keep it that way β it's the layer you can prove correct.
Confidence per field, correction by humans
Every extracted field carries a confidence score persisted on the Course row (the OCR
confidence, degraded further if normalization had to guess). Set thresholds per field β units
and grades matter more than course title casing β and any row with a low-confidence field is
flagged NeedsCorrection.
The correction UI is deliberately simple MVC: the analyst (or a data-entry role) sees the extracted rows in an editable Kendo grid beside the source document image, with low-confidence cells highlighted. Corrections are saved as new values with the original preserved β the audit trail from Module 10 applies here too. Fixed rows feed back as labeled examples, which becomes your extraction quality dashboard in Lesson 4. Fail-closed, human-corrected, measured: the Module 11 pattern, applied for the first time.