Skip to content

Commit 54c49a1

Browse files
committed
Fetch descriptoin
1 parent 85145df commit 54c49a1

5 files changed

Lines changed: 47 additions & 10 deletions

File tree

domain/audit.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface RequirementRule {
99
progressUnit: RequirementProgressUnit;
1010
status: Status;
1111
courses: CourseId[];
12+
summary?: string;
1213
}
1314

1415
export interface AuditRequirement {
@@ -56,11 +57,6 @@ export interface AuditHistoryData {
5657
error?: string;
5758
}
5859

59-
/**
60-
* The display name for an audit history entry, title-first: the audit's own
61-
* title, else its majors joined, else null. Callers supply their own final
62-
* fallback (an id, "Degree Requirements", etc.).
63-
*/
6460
export function getAuditDisplayName(
6561
entry: AuditHistoryEntry | undefined,
6662
): string | null {

features/audit-scraping/audit-page-parser.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,21 @@ export function scrapeRequirementSections(
147147
(cells[5] as HTMLElement).innerText,
148148
);
149149

150+
// After the progress columns, GPA rules carry a summary sentence
151+
// ("X hours for a total of Y points were used to calculate the GPA.")
152+
// while course-based rules carry a course-preview cell. Capture the
153+
// former: the first trailing cell that is neither a course preview nor
154+
// the show/hide-details link.
155+
const summaryCell = Array.from(cells)
156+
.slice(6)
157+
.find(
158+
(cell) =>
159+
!cell.classList.contains("course_preview") &&
160+
!cell.querySelector("a.details"),
161+
);
162+
const summary =
163+
(summaryCell as HTMLElement | undefined)?.innerText.trim() || undefined;
164+
150165
const rule: RequirementRule = {
151166
text: (cells[2] as HTMLElement).innerText.trim(),
152167
requiredHours: requiredProgress.value,
@@ -155,6 +170,7 @@ export function scrapeRequirementSections(
155170
progressUnit: requiredProgress.unit,
156171
status: getRuleStatus(row.classList),
157172
courses: [],
173+
...(summary ? { summary } : {}),
158174
};
159175

160176
// Parse details row (courses)

features/dashboard/degree-audit-page.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,12 @@ const SidePanel = () => {
1919

2020
const gpaSection = sections.find((section) => isGpaSection(section.title));
2121
const gpaRule = gpaSection?.rules[0];
22-
const gpaSummary = parseGpaSummary(gpaRule?.text);
22+
const gpaSummary = parseGpaSummary(gpaRule?.summary);
2323

24-
// TODO(debug): remove once real GPA is confirmed. If `counted` logs as a whole
25-
// number (e.g. 3) the stored audit was scraped before the parseFloat fix and
26-
// needs a re-scrape; the raw scraped text is in gpaRule.text.
2724
console.log("[GPA card]", {
2825
required: gpaRule?.requiredHours,
2926
counted: gpaRule?.appliedHours,
30-
ruleText: gpaRule?.text,
27+
summaryText: gpaRule?.summary,
3128
parsedSummary: gpaSummary,
3229
});
3330

tests/scraping/__snapshots__/audit-scraper.test.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ OR
674674
"remainingHours": 0,
675675
"requiredHours": 2,
676676
"status": "Completed",
677+
"summary": "42 hours for a total of 158.62 points were used to calculate the GPA.",
677678
"text": "A University GPA of 2.00 is required on all courses undertaken (including credit by examination, correspondence, and extension) for which a grade or symbol other than Q, W, X, or CR is recorded.",
678679
},
679680
{
@@ -693,6 +694,7 @@ OR
693694
"remainingHours": 0,
694695
"requiredHours": 2,
695696
"status": "Completed",
697+
"summary": "24 hours for a total of 86.62 points were used to calculate the GPA.",
696698
"text": "Students must earn a grade point average of 2.0 in all mathematics and science courses required by the degree.",
697699
},
698700
],

tests/scraping/audit-scraper.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,30 @@ describe("degree audit scraper", () => {
7171
log.mockRestore();
7272
}
7373
});
74+
75+
test("captures the GPA summary sentence, not course-rule previews", async () => {
76+
const document = await loadAuditDocument("audit-results-real.html");
77+
const log = spyOn(console, "log").mockImplementation(() => {});
78+
79+
try {
80+
const { requirements } = parseAuditPage(document);
81+
const rules = requirements.flatMap((requirement) => requirement.rules);
82+
83+
const summaries = rules
84+
.map((rule) => rule.summary)
85+
.filter((summary): summary is string => summary !== undefined);
86+
87+
// The GPA rules carry their calculation sentence...
88+
expect(summaries.length).toBeGreaterThan(0);
89+
// ...and every captured summary is that sentence — never a course preview
90+
// or other trailing-cell content.
91+
for (const summary of summaries) {
92+
expect(summary).toMatch(
93+
/^\d+(?:\.\d+)? hours for a total of \d+(?:\.\d+)? points/,
94+
);
95+
}
96+
} finally {
97+
log.mockRestore();
98+
}
99+
});
74100
});

0 commit comments

Comments
 (0)