APM Shift Pilot · © 2026 Yuchi
Current version: v0.1.1
- Who is affected:
- APMs who need to check upcoming shift duties.
- APMs who need to request cover when they cannot attend a shift.
- What is the issue:
- The original schedule is a wide SharePoint-style CSV/Excel sheet with many repeated event, classroom, and shift columns.
- APMs must manually scan the sheet to find their own duties and identify who may be free.
- Swap coordination can become scattered across chat messages, making it harder to know which requests are pending, accepted, rejected, or cancelled.
- What was achieved:
- Turned a wide schedule CSV into a local coordination dashboard where APMs can quickly see their own duties and manage cover requests.
- Reduced manual schedule scanning by filtering shifts to the logged-in APM and grouping duties by week, day, event, classroom, and shift time.
- Moved swap coordination from informal chat-only tracking into a visible request workflow with pending, accepted, rejected, and cancelled states.
- Made accepted swaps actionable by updating the local shift owner and generating a PM Sync message for the official schedule update.
- Added safeguards for common coordination problems, including duplicate requests, requester cancellation, and same-time cover conflicts.
From the APM's perspective:
- Open the dashboard.
- Log in with approved initials, such as
YC. - See the current date in the dashboard header.
- Review upcoming shifts grouped by week, day, event, classroom, and shift time.
- Click
Find Coveron a future shift. - Review available peers in the available-peers panel.
- Select
RGand clickRequest Swap. - Log out from
YC. - Log in as
RG. - Review the pending incoming request popup.
- Open the Swap Requests panel from the popup.
- Accept the incoming request.
- Log out from
RG. - Log back in as
YC. - Review the accepted requester popup.
- Open the Swap Requests panel and switch between
Pending,Accepted,Rejected, andCancelled. - In the accepted outgoing request, show
PM SyncandCopy PM Update. - In an outgoing pending request, show
Copy Message.
Screenshots:
Demo media:
HTMLfor the dashboard page structure.CSSfor layout, cards, status colors, shift state styling, buttons, modal popup, and responsive behavior.- Vanilla
JavaScriptfor login flow, schedule loading, date grouping, cover lookup, swap request actions, status filtering, collapsible panels, popup reminders, and the dashboard date display.
Pythonfor parsing, service logic, and local persistence handling.Flaskfor web routes and REST API endpoints.Pandasfor CSV parsing and schedule transformation.CSVroster storage throughdata/apm_list.csv.JSONlocal persistence throughdata/shifts.jsonanddata/swap_requests.json.pytestfor automated regression tests.
-
AI tools, services, models, and purposes:
Geminifor idea framing, architecture discussion, and prompt suggestions before implementation.
-
AI agents:
Codexas coding co-developer for parser implementation, Flask routes, dashboard iteration, swap workflow, UI refinements, bug fixes, local validation, documentation updates, and demo asset generation.
-
Process:
- Discussed the initial idea of an APM Shift Pilot with Gemini and shaped it into a 3-tier system blueprint.
- Used the blueprint prompt to begin the project in Codex with a Flask dashboard, CSV parser, roster file, and local JSON data.
- Iterated in Codex from a simple schedule lookup prototype into a v0.1.1 coordination dashboard with swap requests, automatic schedule updates, validation tests, screenshots, demo media, and cleaner frontend/documentation structure.
-
Key prompts used:
- Initial Idea And Setup:
- “System Blueprint: APM Shift Pilot”
- “Create a Web Dashboard: A simple frontend where APMs can log in to see their schedule.”
- Data And Roster:
- “Create a APM list file.”
- “Only initials in apm_list file can login.”
- “Update shifts.json to catch data from the month before this month to next month.”
- “Only get the data of initials in apm_list.”
- Dashboard UX:
- “The shifts in same day can display in same row.”
- “Let past shifts displayed when user load past info, it would be better to show future shifts when login.”
- “Let user fold swap requests area, so they can see week shifts info.”
- “When user login, show the current date.”
- Swap Workflow:
- “Let users request swap, accept/reject in web.”
- “If somebody accepts a swap, update the shift data and notify the requester.”
- “If one peer accepts a swap, auto-cancel duplicate pending requests for the same original shift.”
- “Let users find cover for all shifts in the same event and group same-event request status.”
- “If a cover APM accepts one request, auto-reject other pending requests for the same date and shift time.”
- Documentation:
- “Create a VERSION_NOTES.md.”
- “Update all files based on the latest version.”
- Initial Idea And Setup:
-
Key review points and corresponding decisions made:
Review Point Decision Made The source schedule has repeated event/classroom/shift columns. Built a parser that detects event blocks and shift columns instead of relying on manual row scanning. Some shift labels were inconsistent, such as 8am - 9vs8am - 9.30am.Fixed column normalization so decimal times are preserved and standard shift labels are used. APM login should not allow arbitrary initials. Added data/apm_list.csvas the approved roster and validated login/API access against it.The dashboard should not overwhelm users with old shifts or unavailable actions. Defaulted login to upcoming shifts only, allowed past weeks through the date filter, and removed cover actions from past shifts. Swap requests sent to many peers can become stale after one peer accepts. Auto-cancelled other pending requests for the same original shift when one request is accepted. Event shifts often need to be handled together. Added event-level cover requests and grouped same-event incoming/outgoing request cards. Requesters need a full request lifecycle. Added pending, accepted, rejected, and cancelled tabs, plus requester-side cancellation for outgoing pending requests. One APM cannot cover two duties at the same time. Auto-rejected conflicting pending requests to the same cover APM for the same date and shift time. Request updates need visible follow-up. Added pending/accepted/rejected popups, routed Review to the matching tab, and added PM Sync copy messages for accepted swaps. Auto-refresh rebuilt shift cards and hid open cover results. Preserved open Find Coverand event-cover panels across refreshes, while clearing them when request data changes.
Steps to run the project.
- Create a virtual environment:
python3 -m venv .venv- Activate it:
source .venv/bin/activate- Install dependencies:
pip install -r requirements.txt- Generate structured shift data:
python3 src/extract_data.py- Start the app:
python3 src/app.py- Open:
http://127.0.0.1:8765
Validate roster, shift, and swap request data:
python3 scripts/validate_data.pyThe validation script checks:
apm_list.csvhas the requiredname,initialsheader.- APM initials are uppercase, unique, and 2-4 letters.
- Every
shifts.jsonrecord has required fields. - Every shift initial exists in
apm_list.csv. - Shift dates and shift labels match the expected format.
swap_requests.jsonis a valid request list.- Swap request statuses are one of
pending,accepted,rejected, orcancelled. - Swap request requester/target initials exist in
apm_list.csv. - Swap request timestamps use Singapore time (
+08:00).
Regenerate shift data:
python3 src/extract_data.pySummarize current data:
python3 scripts/summarize_data.pyReset local swap requests:
python3 scripts/reset_swap_requests.py --yesRun the automated tests:
python3 -m pytestThe current 17-test suite covers roster-only login, future and past shift lookup, cover recommendations, CSV extraction filtering, shift label normalization, rolling extraction windows, swap request creation, Singapore-time timestamps, target-only responses, accept/reject behavior, shift ownership updates, duplicate request auto-cancellation, same-time conflict auto-rejection, requester cancellation, and event-level swap responses.
How to use the project.
- Start the app with
python3 src/app.py. - Open
http://127.0.0.1:8765. - Log in with initials from
data/apm_list.csv, for example:YCRGYY
- Expected behaviour:
- Login:
- Initials must exist in
data/apm_list.csv. - Invalid initials are blocked.
- Initials must exist in
- Schedule:
- Upcoming shifts show by default after login.
- Past shifts show when the user selects a past week/date.
- Shifts are grouped by week, day, event, classroom, and shift time.
- Past shifts are muted and do not show cover/swap actions.
- Future shifts show active styling and swap actions.
- Cover lookup:
Find Covershows APMs who are not assigned to that date and shift time.- Available peers are shown in a blue info panel.
- Individual
Find Coverpanels stay visible after dashboard auto-refresh. Find Cover for Eventchecks who can cover every future shift in the same event block.- Event cover requests send requests for all shifts in that event block.
- Individual and event-cover availability panels reset after login/logout and clear when request status data changes, so stale availability does not stay open.
- Swap requests:
Request Swapsends an in-app request to the selected peer.- Pending incoming requests expand the Swap Requests panel and trigger a popup reminder after login.
- Accepted/rejected outgoing requests trigger one-time requester notifications.
- Pending, accepted, and rejected popup reminders are color-coded and queued when multiple reminders exist.
- Incoming requests can be accepted or rejected individually.
- Same-event incoming requests are grouped with per-shift actions and
Accept All/Reject All. - Accepted swaps update
data/shifts.json. - Duplicate pending requests are blocked, and same-time conflicts for the same cover APM are auto-rejected after one is accepted.
- Outgoing pending requests can be cancelled by the requester.
- Outgoing same-event requests are grouped with combined copy/cancel controls for pending requests and one PM Sync message for accepted requests.
- Request history:
- Request filters include
Pending,Accepted,Rejected, andCancelled. - Same-event accepted, rejected, and cancelled records are grouped for easier scanning.
- Request lists show the latest two records first and can load up to five.
- Pending requests show all pending records without the five-record cap.
- Users can switch between status filters even while pending requests still exist.
- Request filters include
- Copy messages:
- Outgoing pending requests include
Copy Messageas a backup message. - Accepted outgoing requests include
Copy PM Updateunder PM Sync.
- Outgoing pending requests include
- Login:
apm_shift_pilot/
├── README.md
├── LICENSE
├── .gitignore
├── requirements.txt
├── src/
│ ├── app.py
│ ├── extract_data.py
│ ├── templates/
│ │ └── index.html
│ └── static/
│ ├── app.js
│ └── style.css
├── tests/
│ ├── conftest.py
│ ├── test_app_routes.py
│ ├── test_extract_data.py
│ └── test_swap_requests.py
├── docs/
│ ├── API_EXAMPLES.md
│ └── VERSION_NOTES.md
├── scripts/
│ ├── reset_swap_requests.py
│ ├── summarize_data.py
│ └── validate_data.py
├── assets/
│ ├── logo/
│ │ ├── brand-banner.svg
│ │ ├── favicon.svg
│ │ └── icon.svg
│ ├── screenshots/
│ │ ├── dashboard-upcoming.png
│ │ ├── find-cover.png
│ │ ├── login.png
│ │ ├── swap-requests-accepted-pm-sync.png
│ │ └── swap-requests-pending.png
│ └── demo/
│ ├── demo.gif
│ └── demo.mp4
└── data/
├── PM_Schedule(Schedule).csv
├── apm_list.csv
├── shifts.json
└── swap_requests.json
Key folders and files:
src/contains the main Flask app, schedule extractor, and frontend source files.src/app.pycontains Flask routes, roster validation, schedule lookup, cover lookup, swap request APIs, accepted-swap reassignment, duplicate request blocking, requester cancellation, event-level responses, same-time conflict rejection, and Singapore-time request timestamps.src/extract_data.pyconverts the wide schedule CSV into normalized shift records and filters extracted initials through the approved APM roster.src/templates/index.htmlcontains the dashboard page structure.src/static/app.jscontains the dashboard login flow, schedule loading, cover lookup, swap request actions, popup reminders, status filters, copy messages, and auto-refresh behavior.src/static/style.csscontains the dashboard layout, colors, cards, buttons, modal, and responsive styling.tests/contains pytest coverage for parser behavior, API routes, roster login, shift lookup, cover recommendations, and swap request workflows.docs/VERSION_NOTES.mdrecords the project progress from v0 to v0.1.1.docs/API_EXAMPLES.mddocuments optional backend curl examples for testing and review.scripts/validate_data.pyvalidates roster, shift, and swap request data.scripts/summarize_data.pyprints summary counts by APM, month, and request status.scripts/reset_swap_requests.pyclears local swap request state before demos or manual retesting.assets/logo/contains the banner, favicon, and app icon SVG assets.assets/screenshots/contains screenshots for the login, schedule, cover lookup, and request views.assets/demo/contains the recorded MP4 demo and compressed GIF preview.data/PM_Schedule(Schedule).csvis the source schedule export.data/apm_list.csvis the approved APM roster.data/shifts.jsonis the normalized shift store used by the app.data/swap_requests.jsonstores local swap request state.requirements.txtlists Python dependencies.
-
What worked:
- The simple Flask structure made the prototype easy to build, run locally, and explain.
- Normalizing the wide schedule CSV into
shifts.jsonmade schedule lookup and grouping much easier. - Using
apm_list.csvas a roster source gave the prototype a clear access boundary. - The dashboard flow now supports the real coordination loop: view shift, find cover, request swap, accept/reject, and update ownership.
- The collapsible Swap Requests panel, status tabs, grouped request cards, and colored popups balance focus with urgency.
- AI was useful as a co-developer for parser design, frontend iteration, API implementation, smoke testing, and documentation.
-
What failed or needed iteration:
- Early schedule display repeated event/classroom information too much, so the layout needed grouped event/classroom rows.
- Past shifts initially still showed cover-related messaging, so past and future shift behavior needed clearer separation.
- Swap requests needed extra logic for duplicate auto-cancel and same-time conflict auto-reject after testing multi-peer requests.
- Event-cover availability could stay open with stale frontend state, so login, logout, and request-change clearing needed refinement.
- Accepted and rejected requester notifications competed for one modal slot, so popup reminders needed queueing.
-
Changes made and rationale:
Change Made Rationale Added normalized CSV extraction into shifts.json.The wide schedule is hard to use directly in a dashboard. Added apm_list.csvroster validation.Only approved APM initials should log in, appear in extracted data, and be recommended for cover. Improved schedule display and filtering. APMs see upcoming duties first, can still load past weeks, and can scan shifts grouped by event and classroom. Updated shifts.jsonwhen a swap is accepted.Accepted swaps should change the actual visible shift owner. Added duplicate request protection. APMs should not receive repeated pending requests, and stale duplicate requests should close after one peer accepts. Added requester-side cancellation and same-time conflict rejection. Requesters can withdraw pending requests, and cover APMs should not accidentally accept overlapping duties. Added grouped same-event requests. Event-level requests should be easier to scan, accept/reject, cancel, and report as a group. Added requester notifications and queued popups. Pending, accepted, and rejected reminders should be visible without hiding each other. Added copy messages and PM Sync. Outgoing pending requests may need external follow-up, and accepted schedule updates still need a clean PM notification. Preserved cover lookup panels across refresh. APMs should not lose available-peer results just because the dashboard auto-refreshes.