Spaces:
Running
Running
| """Academic-calendar key dates β the cohort filter the Today feed uses, and the | |
| unfiltered term list behind the My VU page's `/api/key-dates`. | |
| The academic calendar publishes cohort-specific deadlines with no audience tag β | |
| `Fall 2026-AC` is on 78 of the 85 records and `key-dates` on only 16 β so the | |
| title is the only signal available. Matching it is narrow and explicit rather | |
| than clever: an unrecognized title is always kept. The real fix is per-audience | |
| tagging in the collector, the same gap the event domain tags have. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| from .kb import queries | |
| # (keyword in the title, the school it belongs to) | |
| COHORT_KEY_DATES = ( | |
| ("business module", "Owen Graduate School of Management"), | |
| ("engineering module", "School of Engineering"), | |
| ("with no music fee", "Blair School of Music"), | |
| ) | |
| # Only meaningful to students enrolled in the English Language Center, which the | |
| # background question is the closest signal we have for. | |
| ELC = "english language center" | |
| # Registrar-internal milestones. A student can't act on them and has no idea what | |
| # they mean; they're on the public calendar because staff read it too. | |
| INTERNAL_KEY_DATES = ("discrepancy reporting", "census data") | |
| # Wide enough to hold every record on the calendar (85 today). | |
| _LIMIT = 200 | |
| def norm_name(value: str | None) -> str: | |
| return re.sub(r"\s+", " ", (value or "").strip()).casefold() | |
| def student_schools(profile: dict, index) -> set[str]: | |
| """The schools the student's majors and minors sit in. | |
| Resolved through the index rather than by reading `programs.json` again, so the | |
| server and the catalog picker in the frontend agree on what a major is called. | |
| Empty when nothing resolves β and an empty set means "don't filter", never | |
| "filter everything". | |
| """ | |
| if index is None: | |
| return set() | |
| wanted = {norm_name(p) for key in ("majors", "minors") | |
| for p in (profile.get(key) or []) if isinstance(p, str)} | |
| if not wanted: | |
| return set() | |
| out: set[str] = set() | |
| for doc in index.docs: | |
| if doc.kind != "program" or not doc.extra.get("schools"): | |
| continue | |
| # Catalog titles carry a "(Major)" / "(Minor)" suffix the picker strips. | |
| bare = norm_name(re.sub(r"\s*\((?:major|minor)\)\s*$", "", doc.title, | |
| flags=re.I)) | |
| if bare in wanted: | |
| out.update(doc.extra["schools"]) | |
| return out | |
| def relevant_to_student(title: str, schools: set[str], background: list) -> bool: | |
| """Whether this academic-calendar entry is this student's business.""" | |
| low = (title or "").casefold() | |
| if any(n in low for n in INTERNAL_KEY_DATES): | |
| return False | |
| if ELC in low: | |
| return "international" in (background or []) | |
| for needle, school in COHORT_KEY_DATES: | |
| if needle in low: | |
| # Unknown school: keep it rather than hide something on a guess. | |
| return not schools or school in schools | |
| return True | |
| def payload(index, start: str, end: str) -> dict: | |
| """Every academic-calendar date in [start, end], soonest first β unfiltered. | |
| Deliberately the WHOLE feed (everything events.vanderbilt.edu publishes under | |
| the academic-calendar flag), by product decision: on the My VU page the | |
| student is browsing the calendar itself, so cohort dates for other schools | |
| and registrar milestones stay visible rather than silently vanishing. The | |
| Today *feed* is the opposite surface β five interleaved cards where an | |
| irrelevant date costs real attention β so it keeps filtering through | |
| `relevant_to_student` above. | |
| """ | |
| items = [] | |
| for hit in queries.key_dates_between(index, start, end, limit=_LIMIT): | |
| items.append({ | |
| "date": (hit.when or "")[:10], | |
| "title": hit.doc.title, | |
| "url": hit.doc.url, | |
| "terms": hit.doc.extra.get("terms") or [], | |
| }) | |
| return {"ready": True, "items": items} | |