docs: monitoring links for both environments, and a correctness pass on the README - #160
Merged
Merged
Conversation
The deployment section listed only production's health check page, so staging's — which exists and is already linked from CONTRIBUTING.md — was undiscoverable from the README. It is now beside production's. Adds the Grafana dashboard covering both environments, built on the counters `/metrics` exposes, and links it back to that section so a reader can see where the numbers come from. The existing status badges are labelled **Production**, since four unlabelled badges gave no indication of which environment or which check each one was. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VU7YUhP71KSsotWQ1H7CRH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VU7YUhP71KSsotWQ1H7CRH
Both environments run in Singapore, not the United States. The claim predates this and was simply wrong; it also understated the service, since Singapore is a great deal closer than the US both to the users and to PESU Academy itself, which every authentication waits on. Reworded to cover both environments rather than "the live version", now that the README links staging as well. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VU7YUhP71KSsotWQ1H7CRH
Four unlabelled badges in a row said nothing about which check each one was, and left no room for a second environment. A table names every check and gives staging a column to drop into. The metrics dashboard gets a badge of its own beneath it, so all the monitoring surfaces are in one place rather than split between prose and images. The staging column is not filled in. A cron-job.org badge URL carries a per-job token that exists only inside the owning account: the status page is a client-side app that returns the same HTML for every path, and its API (`X-API-Method: GetPublicStatusPage`) returns the four monitor titles with no identifiers attached. The URLs have to be copied out of the console. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VU7YUhP71KSsotWQ1H7CRH
Replaces the two-column layout with rows keyed by environment and check, and moves the metrics dashboard into the table as a row of its own so every monitoring surface is in one place. Drops the "seven days of history" line. Staging's four cells link to its status page rather than carrying badges, and they are identical because there is nothing yet to tell them apart: a cron-job.org badge URL carries a per-job token that only exists inside the owning account. They become real badges as soon as those four URLs are copied out of the console. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VU7YUhP71KSsotWQ1H7CRH
Fills the four staging cells, so the table now carries all eight checks plus the dashboard. Each badge was fetched and matched by the job name embedded in its own SVG title rather than by the order it arrived in, which is how "Simple authentication" and "Profile data" were placed against the right rows. All eight return 200, and production's existing four were re-checked the same way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VU7YUhP71KSsotWQ1H7CRH
Found auditing the README against the published schema rather than reading it. **Only `/metrics` documented its response codes.** `/authenticate` can answer with six — 200, 400, 401, 422, 500 and 502 — and the README named none of them, so a caller integrating against it had no way to learn that a `502` means PESU Academy is unreachable or that a `422` means their profile page changed shape. It now has a table. `/health` and `/readme` get a sentence each instead, since a table listing a success and the universal catch-all `500` would be padding. **Both example timestamps were malformed.** They showed `2024-07-28 22:30:10.103368+05:30` with a space, but the API serialises with `datetime.isoformat()`, which always separates with a `T`. A reader parsing against the documented shape would have failed on real responses. The OpenAPI examples had it right, so the two disagreed. **The cURL example's response heading was a level too shallow**, making it a sibling of `#### cURL` rather than part of it, so it read as a section of its own in the outline while Python's matching block was nested correctly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VU7YUhP71KSsotWQ1H7CRH
42 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📌 Description
The deployment section at the top of the README listed one monitoring link — production's health
check page — and four unlabelled status badges. It now covers both environments, and one factual
error is corrected.
All monitoring in one table, keyed by environment and check, so a reader can tell what each
badge is measuring. The four production badges were previously bare images in a row with nothing
saying which check or which environment they belonged to.
Links added. Staging's health check page — https://6ns95sgb.status.cron-job.org — existed and
was already referenced from
CONTRIBUTING.md, so it was reachable to contributors but invisible toanyone reading the README. The Grafana dashboard covering both environments is now linked too, tied
back to the
/metricssection so a reader can see where its numbers come from.Region corrected. The warning claimed the live version is hosted in the United States. Both
environments run in Singapore, which also understated the service: Singapore is far closer than
the US both to the users and to PESU Academy itself, which every authentication waits on.
Also drops a "seven days of history per check" line that the table makes redundant.
📋 And a correctness pass on the rest of the README
Auditing the whole file against the published schema — comparing documented endpoints, status codes
and model fields against
app.openapi(),RequestModel,ProfileModel,ResponseModelandMetricsModel— turned up three defects that predate this PR.Only
/metricsdocumented its response codes./authenticatecan answer with six (200, 400,401, 422, 500, 502) and the README named none, so nothing told a caller that a
502means PESUAcademy is unreachable or that a
422means their profile page changed shape. It now has a table;/healthand/readmeget a sentence each, since tabulating a success plus the universal500would be padding.
Both example timestamps were malformed —
2024-07-28 22:30:10.103368+05:30with a space, wherethe API serialises via
datetime.isoformat()and always emits aT. Anyone parsing against thedocumented shape would have failed on a real response, and the OpenAPI examples disagreed with the
README.
The cURL example's response heading was a level too shallow, so it rendered as a section of its
own rather than part of
#### cURL, while Python's matching block was nested correctly.What the audit confirmed was already right: all four endpoints have sections, every
RequestModel/ResponseModel/ProfileModel/MetricsModelfield appears (campusCodeandfriends by their camelCase wire names, not the Python attribute names), all 24 metric families and
all 20 JSON fields are covered, and the Python version claims match
requires-python = ">=3.14".🧱 Type of Change
requirements.txt,pyproject.toml🧪 How Has This Been Tested?
tests/unit/)tests/functional/)tests/integration/)276 tests, 100.00% coverage, unchanged — no code is touched.
pre-commit run --all-filespasses 12/12 hooks.
Manual checks, since this PR is almost entirely URLs:
200— and each was matched to its row by the job nameembedded in its own SVG
<title>, not by the order it was supplied in. That is what placed"Simple authentication" and "Profile data" against the right checks; production's existing four
were re-verified the same way.
200.Grafana: metrics & KPIs.#metricsone.Grafana URL present.
docker runforms,uv run python -m app.app, and the test runner.Unchecked: nothing in the image or its behaviour changes.
✅ Checklist
scripts/run_tests.py)pre-commit run --all-files).envvars updated (if applicable)scripts/benchmark/benchmark_requests.py)Unchecked items carry no work here: no code, so no docstrings and no tests; no image change; no new
variables; nothing on a request path a benchmark measures.
🛠️ Affected API Behaviour
app/app.py– Modified/authenticateroute logicapp/pesu.py– Updated scraping or authentication handlingOnly
README.md.🧩 Models
app/models/request.py– Input validation or request schema changesapp/models/response.py– Authentication response formattingapp/models/profile.py– Profile extraction logic🐳 DevOps & Config
Dockerfile– Changes to base image or build process.github/workflows/*.yaml– CI/CD pipeline or deployment updatespyproject.toml/requirements.txt– Dependency version changes.pre-commit-config.yaml– Linting or formatting hook changesProject version only, 4.4.0 → 4.5.0, plus the matching
uv.lock. No dependency changes.📊 Benchmarks & Analysis
scripts/benchmark/benchmark_requests.py– Performance or latency measurement changesscripts/benchmark/analyze_benchmark.py– Benchmark result analysis changesscripts/run_tests.py– Custom test runner logic or behavior updates🧠 Additional Notes
The staging badge URLs could not be derived and had to come from the maintainer. Recorded because
the next person will look: a cron-job.org badge is
api.cron-job.org/jobs/<id>/<token>/status-7.svg, where the token is a per-job secret. The statuspage is a client-side app that serves the same HTML for every path, and its private API
(
POST https://api.cron-job.org/withX-API-Method: GetPublicStatusPage) returns the fourmonitor titles with no identifiers attached. They exist only in the console.
The Grafana badge is static. A live one would need shields.io to read a value, and both routes
are closed: the public-dashboard query API is POST-only where shields issues GET, and
/metricsnowrequires a bearer token shields cannot send.
/healthis open and could drive a dynamic badge, atthe cost of shields polling the service on every README view.
🤖 Generated with Claude Code
https://claude.ai/code/session_01VU7YUhP71KSsotWQ1H7CRH