From 3b9e381cd42c56cc4543869c5b80c8e0014b4948 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 28 Oct 2026 21:39:22 +0800 Subject: [PATCH 1/4] Plan MIL-009: verification fix (issue #50) --- docs/artifact-registry.md | 4 +- docs/milestones/mil-009-verification-fix.md | 73 +++++++++++++++++++ docs/project-plan.md | 9 ++- .../rc-023-mil-009-verification-fix.md | 64 ++++++++++++++++ docs/sqa/traceability-matrix.md | 4 +- 5 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 docs/milestones/mil-009-verification-fix.md create mode 100644 docs/sqa/reviews/rc-023-mil-009-verification-fix.md diff --git a/docs/artifact-registry.md b/docs/artifact-registry.md index 61617f8..7bfe6cb 100644 --- a/docs/artifact-registry.md +++ b/docs/artifact-registry.md @@ -14,9 +14,9 @@ document of a type. `Primary File` may contain a glob (e.g. | BC | Business Case | docs/business-case.md | 002 | | SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 | | PP | Project Plan | docs/project-plan.md | 002 | -| MIL | Milestone / Gateway | docs/milestones/*.md | 009 | +| MIL | Milestone / Gateway | docs/milestones/*.md | 010 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0004 | -| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 023 | +| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 024 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | DICT | Domain Dictionary | docs/dictionary.md | 002 | diff --git a/docs/milestones/mil-009-verification-fix.md b/docs/milestones/mil-009-verification-fix.md new file mode 100644 index 0000000..6a458a1 --- /dev/null +++ b/docs/milestones/mil-009-verification-fix.md @@ -0,0 +1,73 @@ +# MIL-009 Verification fix: only compare the bookings of the run + +## Metadata +| Key | Value | +| --- | --- | +| ID | MIL-009 | +| CrossReference | [BC-001], [MIL-006] | +| Language | en | +| Domain | it | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S02 | Initial version, added after a real run (issue #50) | pending | + +--- + +## Purpose + +Decide whether the My Bookings check ignores bookings that lie outside the window the bot worked on, so that a real account with bookings from earlier runs ends in SUCCESS, and whether the report never prints a negative count. + +## Deliverable + +A corrected comparison in `domain/verification.py` and its use in `application/verification.py`, unit tests and an end-to-end fixture with extra future bookings, new screenshots for lecture steps 7 to 9 from a real run, and updated README and wiki notes. + +## Go / No-Go Criteria + +| # | Criterion (objectively checkable) | Go | No-Go | +| --- | --- | --- | --- | +| 1 | Unit tests show that entries outside the window of the run are listed as a note and do not cause MISMATCH | 0 failures; a test uses entries after the last handled class | A test fails or is missing | +| 2 | Unit tests show that the missing count is never negative and that a class the bot booked but the page lacks is still a MISMATCH | 0 failures | A test fails or is missing | +| 3 | Unit and container tests of the existing steps still pass with at least 80% coverage of the logic modules | Both true | Either false | +| 4 | The code review against `qc-programming-python.md` is `Go` | `RC-*` is `Go` | `No-Go` | +| 5 | `docs/images/step-8.png` from a real run shows SUCCESS | S01 reran the bot with their own account and the image is committed | The image shows MISMATCH or is not replaced | + +## Dependencies + +| Depends on | Reason | +| --- | --- | +| MIL-006 | Changes the verification that MIL-006 built | + +## Traceability + +| Business Case objective / KPI / user story | Reference | +| --- | --- | +| O5, success criterion 3 | [BC-001] | +| CN-001, CN-003 | [SA-001] | + +## Ownership + +| Role | Stakeholder ID (SA) | +| --- | --- | +| Owner | S01 | +| Approving reviewer | S02 (AI as stand-in, autonomous mode, not independent) | + +## Target Date + +2026-11-06 — inside the planned window 2026-10-28 to 2026-11-06 of [PP-001]; the Business Case sets no deadline. + +## Tasks + +| # | Task | Summary | Needs its own Use Case/User Story? | Reference | +| --- | --- | --- | --- | --- | +| 1 | Limit the comparison to the window of the run | In `build_verification` count only page entries dated from the first to the last class the bot handled; list the other entries as "also booked, not checked". The missing count never goes below zero. | No | O5 | +| 2 | Test the corrected comparison | Unit tests for entries outside the window, a booked class that is missing on the page, and the count; an end-to-end fixture with extra future bookings. | No | O5 | +| 3 | Refresh the step 7 to 9 screenshots and the notes | S01 reruns the bot with their own account and `--screenshot-dir`; the AI commits the images and removes the MISMATCH note from the README and the step 8 wiki pages in three languages. | No | O5, O8 | + +--- + +[BC-001]: ../business-case.md +[MIL-006]: ./mil-006-verify-and-time-travel.md +[PP-001]: ../project-plan.md +[SA-001]: ../stakeholder-analysis.md diff --git a/docs/project-plan.md b/docs/project-plan.md index 936b550..7a9c7e7 100644 --- a/docs/project-plan.md +++ b/docs/project-plan.md @@ -4,7 +4,7 @@ | Key | Value | | --- | --- | | ID | PP-001 | -| CrossReference | [BC-001], [SA-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008] | +| CrossReference | [BC-001], [SA-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009] | | Language | en | | Domain | it | @@ -12,12 +12,13 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 2026-10-28 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version: eight gateways | [50e8331] | +| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | S01 | Add MIL-009, a verification fix found in a real run (S01 accepted the plan in chat) | pending | --- ## Purpose -This plan schedules eight gateways (`MIL-001` to `MIL-008`) that take the Snack and Lift booking bot from an accepted baseline to a documented, tested and published repository. The Business Case sets no deadline, so the window 2026-10-28 to 2026-11-06 is a planning assumption of this plan. Each gateway ends in a decision and a deliverable and is delivered by one pull request on its own branch. +This plan schedules nine gateways (`MIL-001` to `MIL-009`) that take the Snack and Lift booking bot from an accepted baseline to a documented, tested and published repository. The Business Case sets no deadline, so the window 2026-10-28 to 2026-11-06 is a planning assumption of this plan. Each gateway ends in a decision and a deliverable and is delivered by one pull request on its own branch. ## Planning Assumptions @@ -39,6 +40,7 @@ This plan schedules eight gateways (`MIL-001` to `MIL-008`) that take the Snack | Lecture steps 7 and 8 | [MIL-006] | 2026-11-01 to 2026-11-02 | 2026-11-02 | S01 | none (technical tasks) | My Bookings verification, time travel, screenshots 7 and 8 | | | Lecture step 9 | [MIL-007] | 2026-11-02 to 2026-11-04 | 2026-11-04 | S01 | none (technical tasks) | `retry`, resilient `login`, `book_class`, `get_my_bookings`, screenshot 9 | | | Documentation, wiki and release | [MIL-008] | 2026-11-04 to 2026-11-06 | 2026-11-06 | S01 | none (documentation) | README, Doxygen, wiki in en, da and ms, final checks | | +| Verification fix | [MIL-009] | 2026-11-04 to 2026-11-06 | 2026-11-06 | S01 | none (technical tasks) | Verification compares only the window of the run; new screenshots 7 to 9 | | ```plantuml @startgantt @@ -77,7 +79,7 @@ Project starts 2026-10-28 ## Dependencies ``` -MIL-001 → MIL-002 → MIL-003 → MIL-004 → MIL-005 → MIL-006 → MIL-007 → MIL-008 +MIL-001 → MIL-002 → MIL-003 → MIL-004 → MIL-005 → MIL-006 → MIL-007 → MIL-008 → MIL-009 ``` A No-Go on a gateway stops all later gateways until it is fixed and re-reviewed. The end date then moves by the days spent on the rework; S01 decides a move of the end date. @@ -109,4 +111,5 @@ A No-Go on a gateway stops all later gateways until it is fixed and re-reviewed. [MIL-006]: ./milestones/mil-006-verify-and-time-travel.md [MIL-007]: ./milestones/mil-007-resilience.md [MIL-008]: ./milestones/mil-008-documentation.md +[MIL-009]: ./milestones/mil-009-verification-fix.md [50e8331]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/049-snack-and-lift/commit/50e8331bd21910a72ae56b74c9ff6a3677a709b3 diff --git a/docs/sqa/reviews/rc-023-mil-009-verification-fix.md b/docs/sqa/reviews/rc-023-mil-009-verification-fix.md new file mode 100644 index 0000000..4a5b963 --- /dev/null +++ b/docs/sqa/reviews/rc-023-mil-009-verification-fix.md @@ -0,0 +1,64 @@ +# SQA Review Record + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-023 | +| CrossReference | [MIL-009], [QC-MIL-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-28 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S02 | Review record, full review | pending | + +--- + +## Artifact Under Review + +- Instance reviewed: [MIL-009] +- Checklist used: [QC-MIL-001] (`QC-MIL-001`) +- Scope: full review +- Language and domain: en / it +- Language reviewer: none +- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S02`) + +## Checklist Results + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | A concrete deliverable is defined for every gate | Pass | Deliverable section names concrete files, runs or documents. | +| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Go/No-Go table has objectively checkable criteria with Go and No-Go columns. | +| 3 | Dependencies on other milestones are explicitly mapped | Pass | Dependencies table names the preceding milestone and the reason. | +| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | Traceability cites O5 of BC-001 and the concerns of SA-001. | +| 5 | Milestone owner and approving reviewer are identified | Pass | Ownership names S01 as owner and S02 (AI as stand-in) as approving reviewer. | +| 6 | Milestone has a defined target date consistent with project constraints | Pass | Target Date lies inside the window 2026-10-28 to 2026-11-06 of PP-001. | + +## Language and Domain Results + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | The Metadata table has a `Language` row and a `Domain` row, and neither is a placeholder | Pass | Metadata has `Language` = en and `Domain` = it. | +| 2 | `Language` is a BCP 47 code and `Domain` is a value from the registry's domain list | Pass | `en` is BCP 47; `it` is in the registry domain list. | +| 3 | The content (prose and table cells) is written in the stated language | Pass | All prose and table cells of MIL-009 are English (stand-in assessment, see the verdict). | +| 4 | The register matches the one the registry gives for the artifact type | Pass | Register of the type is given in the registry (Languages); the text keeps to it. | +| 5 | Domain terms are the PO terms of the domain's dictionary, with no synonyms | Pass | Terms follow DICT-001: bot, booking, verification (stand-in assessment, see the verdict). | +| 6 | Metadata keys, section headings, IDs and statuses are in English | Pass | Keys, headings, IDs and statuses are English. | +| 7 | No translated twin (`..md`) exists beside the document | Pass | No translated twin exists in `docs/`. | +| 8 | A change of language or domain since the previous accepted version has a Version History row and was reviewed again | N-A | First version; no language change. | +| 9 | A reviewer competent in the domain, and in the language, has confirmed that the domain terms are used correctly | Pass | The stand-in reviewer reads English and checked the IT terms in context (stand-in assessment, see the verdict). | +| 10 | Abbreviations are spelled out on first use, in the stated language | Pass | Abbreviations are spelled out or defined where first used, or are the framework's own IDs. | + +## Overall Verdict + +Go — MIL-009 meets every Mandatory criterion. No blocking finding. The review is not independent: the AI authored and reviewed it as stand-in for S02, and S01 can challenge it in the end-of-run summary. Criteria 3, 5 and 9 of QC-LANG-001 were assessed by the stand-in only; the framework would hold them `Pending` for an independent language reviewer. S01 is asked to confirm them in the end-of-run summary. + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| None | S01 | - | + +--- + +[MIL-009]: ../../milestones/mil-009-verification-fix.md +[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md diff --git a/docs/sqa/traceability-matrix.md b/docs/sqa/traceability-matrix.md index 4694ea3..b1c2f54 100644 --- a/docs/sqa/traceability-matrix.md +++ b/docs/sqa/traceability-matrix.md @@ -23,7 +23,7 @@ Tracks backward and forward links between artifact instances so that the Busines | --- | --- | --- | --- | --- | --- | --- | | [BC-001] | Business Case | en | it | - | [SA-001], [PP-001] | RC-001 | | [SA-001] | Stakeholder Analysis | en | it | [BC-001] | [PP-001] | RC-002 | -| [PP-001] | Project Plan | en | it | [BC-001], [SA-001] | [MIL-001] to [MIL-008] | RC-003 | +| [PP-001] | Project Plan | en | it | [BC-001], [SA-001] | [MIL-001] to [MIL-009] | RC-003 | | [DICT-001] | Domain Dictionary | en | it | [BC-001], [SA-001] | - | RC-004 | | [ADR-0001] | ADR | - | - | [BC-001] | [PP-001] | RC-005 | | [ADR-0002] | ADR | - | - | [BC-001], [SA-001] | code from MIL-002 onward | RC-006 | @@ -36,6 +36,7 @@ Tracks backward and forward links between artifact instances so that the Busines | [MIL-006] | Milestone | en | it | [BC-001], [PP-001] | [MIL-007] | RC-013 | | [MIL-007] | Milestone | en | it | [BC-001], [PP-001] | [MIL-008] | RC-014 | | [MIL-008] | Milestone | en | it | [BC-001], [PP-001] | - | RC-015 | +| [MIL-009] | Milestone | en | it | [BC-001], [MIL-006] | - | RC-023 | | Source and tests of [MIL-002] | Python source code | - | - | [MIL-002], [ADR-0002], [ADR-0003] | - | RC-016 | | Source and tests of [MIL-003] | Python source code | - | - | [MIL-003], [ADR-0002], [ADR-0003] | - | RC-017 (Go-with-conditions) | | Source and tests of [MIL-004] | Python source code | - | - | [MIL-004], [ADR-0002] | - | RC-018 (Go-with-conditions) | @@ -75,4 +76,5 @@ Tracks backward and forward links between artifact instances so that the Busines [MIL-006]: ../milestones/mil-006-verify-and-time-travel.md [MIL-007]: ../milestones/mil-007-resilience.md [MIL-008]: ../milestones/mil-008-documentation.md +[MIL-009]: ../milestones/mil-009-verification-fix.md [50e8331]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/049-snack-and-lift/commit/50e8331bd21910a72ae56b74c9ff6a3677a709b3 -- 2.54.0 From 353d157e22732d6fb4669a14a6b773b37429b64f Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 28 Oct 2026 21:45:48 +0800 Subject: [PATCH 2/4] Compare only the days of the run in the verification Task: MIL-009#1 Task: MIL-009#2 --- src/snack_and_lift/constants.py | 23 +++ src/snack_and_lift/domain/verification.py | 66 ++++++++- tests/e2e/fixtures/my_bookings.html | 15 +- tests/e2e/test_selenium_gym.py | 14 ++ tests/unit/test_application_verification.py | 10 ++ tests/unit/test_cli.py | 15 ++ tests/unit/test_verification.py | 152 +++++++++++++++++++- 7 files changed, 281 insertions(+), 14 deletions(-) diff --git a/src/snack_and_lift/constants.py b/src/snack_and_lift/constants.py index d6496f4..cdc3449 100644 --- a/src/snack_and_lift/constants.py +++ b/src/snack_and_lift/constants.py @@ -228,3 +228,26 @@ VERIFY_SUCCESS: Final[str] = "✅ SUCCESS: All bookings verified!" VERIFY_MISMATCH: Final[str] = "❌ MISMATCH: Missing {count} bookings" VERIFY_LINE_MISSING: Final[str] = " ✗ Missing: {entry}" VERIFY_LINE_UNEXPECTED: Final[str] = " ? Unexpected: {entry}" +VERIFY_LINE_ALSO_BOOKED: Final[str] = " i Also booked (not checked): {entry}" + +## Month abbreviations of the dates on the pages, January first. +MONTH_ABBREVIATIONS: Final[tuple[str, ...]] = ( + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +) + +## Days of a year, with the 29th of February, used to compare dates that have no year. +DAYS_IN_DATE_CYCLE: Final[int] = 366 + +## A leap year, so that the 29th of February is a valid date. +DATE_CYCLE_YEAR: Final[int] = 2000 diff --git a/src/snack_and_lift/domain/verification.py b/src/snack_and_lift/domain/verification.py index c2581d6..32a865b 100644 --- a/src/snack_and_lift/domain/verification.py +++ b/src/snack_and_lift/domain/verification.py @@ -5,9 +5,11 @@ from collections import Counter from collections.abc import Sequence from dataclasses import dataclass +from datetime import date from snack_and_lift import constants from snack_and_lift.domain.booking_entry import BookingEntry +from snack_and_lift.domain.errors import PageFormatError from snack_and_lift.domain.models import ClassResult, Outcome _WAITLISTED_OUTCOMES = (Outcome.NEW_WAITLIST, Outcome.ALREADY_WAITLISTED) @@ -20,8 +22,11 @@ _Key = tuple[str, str, bool] class VerificationResult: """@brief What the My Bookings page shows compared with what the bot did. - `verified`, `missing` and `unexpected` hold display names, with the waitlist suffix for a - waitlist entry; `missing` and `unexpected` also say the day (`Spin Class on Tue, Aug 12`). + `verified`, `missing`, `unexpected` and `also_booked` hold display names, with the waitlist + suffix for a waitlist entry; all but `verified` also say the day (`Spin Class on Tue, Aug 12`). + Only entries from the earliest to the latest class the bot handled are checked: `found` + counts them, and `also_booked` lists the entries outside that window, which never cause + a mismatch. """ expected: int @@ -29,6 +34,7 @@ class VerificationResult: verified: list[str] missing: list[str] unexpected: list[str] + also_booked: list[str] @property def is_success(self) -> bool: @@ -57,6 +63,46 @@ def _describe(key: _Key) -> str: return f"{_display_name(name, waitlisted)} on {day}" +def _day_number(date_text: str) -> int: + """@brief The position of a date (`Tue, Aug 12`) in a year; the page shows no year. + + @exception PageFormatError The text has no valid month and day. + """ + month_text, _, day_text = date_text.partition(", ")[2].partition(" ") + try: + month = constants.MONTH_ABBREVIATIONS.index(month_text) + 1 + day = date(constants.DATE_CYCLE_YEAR, month, int(day_text)) + except ValueError as error: + raise PageFormatError(f"Unexpected date: {date_text!r}") from error + return day.timetuple().tm_yday + + +def _offset(day: int, start: int) -> int: + """@brief The days from `start` to `day`, counting over the turn of the year.""" + return (day - start) % constants.DAYS_IN_DATE_CYCLE + + +def _window(keys: Sequence[_Key]) -> tuple[int, int] | None: + """@brief The first day and the length in days of the shortest span holding all the keys. + + @return None when there is no key. + """ + days = [_day_number(key[1]) for key in keys] + spans = [(max(_offset(day, start) for day in days), start) for start in days] + if not spans: + return None + span, start = min(spans) + return start, span + + +def _is_in_window(key: _Key, window: tuple[int, int] | None) -> bool: + """@brief Tell whether the date of the key lies in the window, ends included.""" + if window is None: + return False + start, span = window + return _offset(_day_number(key[1]), start) <= span + + def build_verification( results: Sequence[ClassResult], entries: Sequence[BookingEntry] ) -> VerificationResult: @@ -64,11 +110,16 @@ def build_verification( @param results What happened to each class; waitlisted when the outcome is a waitlist one. @param entries The Tuesday and Thursday 6pm entries found on the page. - @return The comparison; the counts are the numbers of results and of entries. + @return The comparison; only the entries from the earliest to the latest class handled + are checked, the others are `also_booked`, and no class handled means no check. @exception PageFormatError An entry or a card has no valid date. """ expected = _expected_keys(results) - remaining = Counter(_entry_keys(entries)) + window = _window(expected) + keys = _entry_keys(entries) + checked = [key for key in keys if _is_in_window(key, window)] + also_booked = [_describe(key) for key in keys if not _is_in_window(key, window)] + remaining = Counter(checked) verified: list[str] = [] missing: list[str] = [] for key in expected: @@ -78,7 +129,9 @@ def build_verification( else: missing.append(_describe(key)) unexpected = [_describe(key) for key in remaining.elements()] - return VerificationResult(len(expected), len(entries), verified, missing, unexpected) + return VerificationResult( + len(expected), len(checked), verified, missing, unexpected, also_booked + ) def _count_text(template: str, count: int) -> str: @@ -99,10 +152,11 @@ def format_verification(result: VerificationResult) -> str: lines.append(constants.VERIFY_RESULT_TITLE) lines.append(_count_text(constants.VERIFY_EXPECTED, result.expected)) lines.append(_count_text(constants.VERIFY_FOUND, result.found)) + lines.extend(constants.VERIFY_LINE_ALSO_BOOKED.format(entry=t) for t in result.also_booked) if result.is_success: lines.append(constants.VERIFY_SUCCESS) return "\n".join(lines) - lines.append(constants.VERIFY_MISMATCH.format(count=result.expected - result.found)) + lines.append(constants.VERIFY_MISMATCH.format(count=len(result.missing))) lines.extend(constants.VERIFY_LINE_MISSING.format(entry=text) for text in result.missing) lines.extend(constants.VERIFY_LINE_UNEXPECTED.format(entry=text) for text in result.unexpected) return "\n".join(lines) diff --git a/tests/e2e/fixtures/my_bookings.html b/tests/e2e/fixtures/my_bookings.html index 34ad107..e0ddf53 100644 --- a/tests/e2e/fixtures/my_bookings.html +++ b/tests/e2e/fixtures/my_bookings.html @@ -10,7 +10,8 @@