Spaces:
Running
Running
| """Mushaf coverage gaps for a recitation, in concise notation. | |
| Given the verses a recitation actually covers, derive what's missing vs the full | |
| mushaf and render it compactly for the release ``catalog.json`` + the changelog | |
| table. Whole missing surahs and within-surah verse gaps are split into two | |
| strings so even a partial recitation (e.g. a Juz' ʿAmma reciter) stays concise. | |
| Shared by ``qua_jobs/cut_release.py`` for both the catalog field and the | |
| changelog member rows, so the JSON and the human table can't drift. | |
| """ | |
| from __future__ import annotations | |
| from collections.abc import Iterable | |
| def verse_counts_from_surah_info(surah_info: dict) -> dict[int, int]: | |
| """``{surah: num_verses}`` from a loaded surah_info dict.""" | |
| return {int(s): int(info["num_verses"]) for s, info in surah_info.items()} | |
| def _compress_ints(nums: Iterable[int]) -> str: | |
| """Collapse ints into comma-separated ascending runs: ``1-3,5,7-9``.""" | |
| ordered = sorted(set(nums)) | |
| if not ordered: | |
| return "" | |
| runs: list[str] = [] | |
| start = prev = ordered[0] | |
| for n in ordered[1:]: | |
| if n == prev + 1: | |
| prev = n | |
| continue | |
| runs.append(f"{start}-{prev}" if start != prev else f"{start}") | |
| start = prev = n | |
| runs.append(f"{start}-{prev}" if start != prev else f"{start}") | |
| return ",".join(runs) | |
| def missing_coverage( | |
| present: Iterable[tuple[int, int]], surah_verse_counts: dict[int, int] | |
| ) -> tuple[str, str]: | |
| """Return ``(missing_surahs, missing_verses)`` for a recitation. | |
| ``present`` is the ``(surah, ayah)`` the recitation covers; | |
| ``surah_verse_counts`` is ``{surah: num_verses}`` for the full mushaf. | |
| - ``missing_surahs`` — surahs with ZERO covered verses, as compact ascending | |
| runs of surah numbers (``"1-84"``, ``"4,7,9,37,39-40,45,65"``). | |
| - ``missing_verses`` — within-surah gaps only (a surah that IS partly | |
| covered), as ``surah:ayah`` runs, surahs joined by ``", "`` | |
| (``"7:116, 41:15"``, ``"2:30,32-34, 5:11"``, ``"75:18-40"``). | |
| Both are ``""`` when nothing is missing in that category. A whole missing | |
| surah never appears in ``missing_verses`` and vice-versa, so the two strings | |
| partition the gap and each stays short. | |
| """ | |
| present_by_surah: dict[int, set[int]] = {} | |
| for surah, ayah in present: | |
| present_by_surah.setdefault(int(surah), set()).add(int(ayah)) | |
| whole_missing: list[int] = [] | |
| partial_parts: list[str] = [] | |
| for surah in sorted(surah_verse_counts): | |
| covered = present_by_surah.get(surah, set()) | |
| if not covered: | |
| whole_missing.append(surah) | |
| continue | |
| gaps = set(range(1, surah_verse_counts[surah] + 1)) - covered | |
| if gaps: | |
| partial_parts.append(f"{surah}:{_compress_ints(gaps)}") | |
| return _compress_ints(whole_missing), ", ".join(partial_parts) | |