Spaces:
Running
Running
Download intel/ingest/calendar.py from Success-dev/venthrive-api: direct link, hf CLI and curl.
- Browser
- Download file 5.56 kB
-
https://huggingface.co/spaces/Success-dev/venthrive-api/resolve/main/intel/ingest/calendar.py
- Command line
-
hf download hf://spaces/Success-dev/venthrive-api/intel/ingest/calendar.py
-
curl -L -o calendar.py https://huggingface.co/spaces/Success-dev/venthrive-api/resolve/main/intel/ingest/calendar.py
5.56 kB
| """ | |
| Nigeria's national calendar as events: federal public holidays, the Islamic | |
| holidays and Ramadan (computed, so marked is_estimate), and the December festive | |
| trading season. Generated rather than scraped, so history back to 2000 costs | |
| nothing — which matters, because the correlation step learns from history. | |
| Known limits, kept honest by is_estimate and the design doc: | |
| - Islamic dates use the Umm al-Qura calendar; Nigeria's dates follow the | |
| Sultan of Sokoto's moon-sighting announcement and can differ by a day. | |
| - The Federal Government sometimes declares extra or substitute days (e.g. a | |
| Monday when a holiday falls on Sunday, or a one-off holiday). Those aren't | |
| predictable; the news pipeline is where declarations will be caught. | |
| - School terms vary by state and aren't included yet. | |
| """ | |
| import sqlite3 | |
| from datetime import date, timedelta | |
| from hijridate import Gregorian, Hijri | |
| from intel.db import now_iso | |
| from intel.freshness import record_attempt | |
| from intel.handoff import DATA_STREAM, INTERFACE, produce | |
| SOURCE_ID = "calendar" | |
| COUNTRY = "ng" | |
| FIRST_YEAR = 2000 | |
| YEARS_AHEAD = 10 | |
| def easter(year: int) -> date: | |
| """Western Easter Sunday (anonymous Gregorian algorithm).""" | |
| a, b, c = year % 19, year // 100, year % 100 | |
| d, e = b // 4, b % 4 | |
| f = (b + 8) // 25 | |
| g = (b - f + 1) // 3 | |
| h = (19 * a + b - d - g + 15) % 30 | |
| i, k = c // 4, c % 4 | |
| l = (32 + 2 * e + 2 * i - h - k) % 7 | |
| m = (a + 11 * h + 22 * l) // 451 | |
| month = (h + l - 7 * m + 114) // 31 | |
| day = (h + l - 7 * m + 114) % 31 + 1 | |
| return date(year, month, day) | |
| def _hijri_to_greg(hy: int, hm: int, hd: int) -> date: | |
| return Hijri(hy, hm, hd).to_gregorian() | |
| def _hijri_years_overlapping(year: int) -> range: | |
| """The Hijri years whose dates can fall in this Gregorian year (a Hijri year | |
| is ~11 days shorter, so it's always the one in progress on 1 January and the | |
| next).""" | |
| hy = Gregorian(year, 1, 1).to_hijri().year | |
| return range(hy, hy + 2) | |
| def events_for_year(year: int) -> list[dict]: | |
| """[{key, type, title, starts_on, ends_on, is_estimate}] for one Gregorian year.""" | |
| out = [] | |
| def add(key, type_, title, start, end=None, estimate=False): | |
| if start.year == year or (end and end.year == year): | |
| out.append({"key": key, "type": type_, "title": title, "starts_on": start, "ends_on": end or start, | |
| "is_estimate": estimate}) | |
| add("new_year", "public_holiday", "New Year's Day", date(year, 1, 1)) | |
| gf = easter(year) - timedelta(days=2) | |
| add("good_friday", "public_holiday", "Good Friday", gf) | |
| add("easter_monday", "public_holiday", "Easter Monday", gf + timedelta(days=3)) | |
| add("workers_day", "public_holiday", "Workers' Day", date(year, 5, 1)) | |
| # Democracy Day moved from 29 May to 12 June from 2019. | |
| add("democracy_day", "public_holiday", "Democracy Day", date(year, 6, 12) if year >= 2019 else date(year, 5, 29)) | |
| add("independence_day", "public_holiday", "Independence Day", date(year, 10, 1)) | |
| add("christmas", "public_holiday", "Christmas Day", date(year, 12, 25)) | |
| add("boxing_day", "public_holiday", "Boxing Day", date(year, 12, 26)) | |
| # Festive trading season: VenThrive's own window for the December spending peak. | |
| add("festive_season", "season", "Festive trading season", date(year, 12, 15), date(year + 1, 1, 5), estimate=True) | |
| for hy in _hijri_years_overlapping(year): | |
| ramadan_start = _hijri_to_greg(hy, 9, 1) | |
| eid_fitr = _hijri_to_greg(hy, 10, 1) | |
| add("ramadan", "season", "Ramadan", ramadan_start, eid_fitr - timedelta(days=1), estimate=True) | |
| add("eid_al_fitr", "religious_holiday", "Eid al-Fitr", eid_fitr, eid_fitr + timedelta(days=1), estimate=True) | |
| eid_adha = _hijri_to_greg(hy, 12, 10) | |
| add("eid_al_adha", "religious_holiday", "Eid al-Adha", eid_adha, eid_adha + timedelta(days=1), estimate=True) | |
| add("mawlid", "religious_holiday", "Eid-el-Maulud (Mawlid)", _hijri_to_greg(hy, 3, 12), estimate=True) | |
| return out | |
| def ingest(con: sqlite3.Connection, first_year: int = FIRST_YEAR, last_year: int | None = None, | |
| run_id: str = "manual") -> dict: | |
| last_year = last_year or date.today().year + YEARS_AHEAD | |
| now = now_iso() | |
| n = 0 | |
| try: | |
| for year in range(first_year, last_year + 1): | |
| for e in events_for_year(year): | |
| ref = f"{e['key']}:{e['starts_on'].isoformat()}" | |
| con.execute( | |
| """insert into events (id, family, type, location_id, starts_on, ends_on, title, detail, | |
| is_estimate, source_id, source_ref, ingested_at) | |
| values (?, 'calendar', ?, ?, ?, ?, ?, null, ?, ?, ?, ?) | |
| on conflict (source_id, source_ref) do update set title = excluded.title, | |
| ends_on = excluded.ends_on, is_estimate = excluded.is_estimate""", | |
| (f"{SOURCE_ID}:{ref}", e["type"], COUNTRY, e["starts_on"].isoformat(), e["ends_on"].isoformat(), | |
| e["title"], int(e["is_estimate"]), SOURCE_ID, ref, now), | |
| ) | |
| n += 1 | |
| con.commit() | |
| except Exception as e: | |
| record_attempt(con, SOURCE_ID, ok=False, error=str(e)) | |
| raise | |
| record_attempt(con, SOURCE_ID, ok=True, latest_data_period=f"{last_year}-12-31") | |
| summary = {"years": f"{first_year}-{last_year}", "events": n} | |
| produce(con, run_id, DATA_STREAM, INTERFACE, {"kind": "calendar", **summary}) | |
| return summary | |