Skip to content

Swap app — removals and changes for the developer

Swap app — removals and changes for the developer

Section titled “Swap app — removals and changes for the developer”

Running log kept while redesigning the swap app off the live build. The design prototypes show what the app should become; this file states what must be taken out of the current implementation, which a screenshot alone will not communicate.

Source prototype: products/rechaj-swap-app/prototypes/swap-station-manager.html (was swap-energy-capture-v1.html until the prototypes were consolidated into roles) Live reference it was built from: swap-prod-v1.html, now removed — the station manager prototype IS the reference

Status: removed in prototype

The dark-green wallet hero at the top of the dashboard comes out entirely. That is:

  • The Wallet balance label and the balance amount (₦8,010.00).
  • The View history link in the card header, which opened the transaction screen from the dashboard.
  • The Top up wallet button.
  • The card’s decorative map artwork and dark gradient background.

Keep: the transaction/wallet history screen itself. It is still reached from inside the swap payment step, only no longer from the dashboard. The station wallet is not the handler’s to top up from here.

Why: the dashboard’s job is the shift and the swap queue, not a balance. The handler operates the station’s wallet during a swap; surfacing a standing balance at the top of home implies an account they own and manage.

R2 · Dashboard quick actions that were never wired

Section titled “R2 · Dashboard quick actions that were never wired”

Status: replaced in prototype

The old quick action row was Energy capture / Transfer Request / Faulty Battery / Push Battery, where only Energy capture did anything. The new row is eight actions:

Report case · My cases · Parked swap · Energy capture · Transfer request · Charge status · Push battery · Faulty swap

  • Renamed: Faulty Battery → Faulty swap. Transfer Request → Transfer request (sentence case).
  • Icon change: Transfer request used a bank-card glyph. It is a battery transfer, not a payment — the icon is now a battery with a directional arrow.
  • Tiles are square, not the previous rectangles.

Status: shown in prototype, swap payment screen + swap detail screen

The daily swap fee is no longer charged alone. VAT at 7.5% is calculated on it and the rider is debited the total.

Line Value
Daily subscription ₦12,000.00
VAT (7.5%) ₦900.00
Total charged ₦12,900.00

VAT is added on top of the fee, not treated as included in it. The breakdown must be itemised wherever the charge is shown, not collapsed to a single total, so the rider and the handler can both see what the VAT portion was.

Status: shown in prototype, swap payment screen

The daily subscription is charged on the rider’s first swap of the day. Every return swap that same day is free — no payment step is required.

The fee breakdown renders in both cases, so the handler always sees the same three lines:

First swap of day Return swap
Daily subscription ₦12,000.00 ₦0.00
VAT (7.5%) ₦900.00 ₦0.00
Total ₦12,900.00 ₦0.00

So VAT is 0 on a free swap — it is charged on the fee, and where there is no fee there is no VAT. The note under the total states which case applies: “First swap today · the daily subscription applies” vs “Return swap · the daily subscription was already paid today”.

Both states are viewable from the /flows Scenarios drawer: First swap — fee + VAT and Return swap — no charge.

Still to confirm (not yet reflected in the prototype):

  • The rider wallet transaction history, where a completed swap debit currently reads ₦12,000.00. If VAT applies it should read ₦12,900.00, and return swaps that day should show ₦0.00.

Status: replaced in prototype, Complete Swap screen

The whole “try Bluetooth, wait for it to fail, then reveal the fallbacks” sequence comes out. Specifically:

  • Tap-the-lock-to-unlock. The big green padlock over the keke art was the unlock trigger, and it always meant try Bluetooth. It is now a status indicator only and is no longer tappable.
  • The Bluetooth timeout gate. Internet and one-time code were unreachable until Bluetooth had timed out (5s). That wait is gone.
  • The failure card (“No response after Bluetooth pairing” / “No internet path to the keke either”) and its escalating copy. There is no longer a failure state to escalate from — the methods are peers.
  • The staged fallback buttons Try internet → Use one-time code, which appeared only after a failure and changed style as the cascade advanced.

Why: the handler often knows up front that Bluetooth will not work at this station, or that the keke’s display unit is the fastest route. Making them sit through a timeout to reach a method they already knew they needed is wasted time on a swap that should take about two minutes.

See C3 for what replaces it.

C3 · Unlock is two explicit stages, three methods each

Section titled “C3 · Unlock is two explicit stages, three methods each”

Status: built in prototype, Complete Swap screen

Three methods, offered at once. Bluetooth, Internet and One-time code are listed together and any one can be picked first. A method that fails marks itself “No response” and leaves the other two available — no cascade, no ordering. Picking one disables the others only while that attempt is in flight.

Two sequential unlocks, clearly labelled. A swap opens the compartment twice, and the screen always says which one is happening:

Stage 1 Stage 2
Stepper Old battery out · active New battery in · to do
Card label “Unlocking for the old battery” “Unlocking for the new battery”
On unlock “Compartment open · remove the old battery” “Compartment open · put the new battery in”
Fields shown Old battery S/N (auto-filled) New battery S/N + mileage
CTA Old battery removed Assign battery

Stage 2 starts from locked and offers all three methods again, so the unlock event for the new battery is recorded separately from the one for the old battery. This is the point of the change: an unlock in the audit trail must be attributable to old-battery-out or new-battery-in, which the single-unlock model could not express.

An Unlock again control sits on the success strip for when the compartment relocks before the battery is swapped.

Persistent in-swap actions. Park, Report issue and Cancel sit at the top of the screen, directly under the timer and rider chip — deliberately far from the primary CTA at the bottom, which is a destructive mis-tap risk when the two sit together. They are available at every point of both stages, including mid-attempt. Each opens a bottom sheet, takes a reason, and confirms. The swap timer keeps running throughout — park and cancel end the swap and return to the dashboard, report raises a case with support and returns to the unlock where it left off.

The three methods render as a single 3-up row, not a vertical list, which was costing height for no benefit. Per-method subtitles moved into one shared note line under the row that only appears when there is something to say.

C4 · A sent command is not an opened compartment

Section titled “C4 · A sent command is not an opened compartment”

Status: built in prototype, Complete Swap screen

The app can confirm it sent an unlock command. It cannot confirm the solenoid actually released — and in practice it often does not. The old design treated “command sent” as “unlocked” and moved straight on, which left the handler stuck with a shut compartment and an app that believed the swap was progressing.

Every method now ends in an explicit question rather than an assumption:

Unlock command sent over Bluetooth Check the keke. Did the compartment actually open? [ Yes, it is open ] [ No, nothing moved ]

This applies to all three methods, including the one-time code — its button is now “I have entered the code”, which leads to the same question, because a keyed code can be accepted and still not throw the lock.

There are therefore two distinct failure modes, and they must be recorded separately:

Failure Method tile reads Meaning
No response No response The command never reached the keke. Nothing was attempted at the lock.
Sent, no release Sent, no release The keke accepted the command and the solenoid did not move. Hardware fault, not connectivity.

On “No, nothing moved” everything reopens: send again with the same method, switch to another method, or report it. The reported-issue list leads with “Unlock sent but the lock never released” so the case lands as a solenoid fault rather than a generic unlock complaint.

Scenarios in the /flows drawer: Unlock 1 — old battery out, Unlock 2 — new battery in, Sent — did it open?, Sent but lock never released, Bluetooth got no response, Bluetooth + internet both out, Unlock by one-time code.

R3 · Station wallet balance on the payment screen

Section titled “R3 · Station wallet balance on the payment screen”

Status: removed in prototype

The Station wallet balance strip at the bottom of the Swap Payment screen (₦8,010.00) comes out. The payment step concerns the rider’s wallet and what they are charged; the station’s balance is not part of that decision and does not belong on this screen.

C5 · Report Issue matches the production screen

Section titled “C5 · Report Issue matches the production screen”

Status: built in prototype

The in-swap “Report issue” bottom sheet with a short reason list is replaced by the production Report Issue screen, replicated from the live app: green info banner, required Issue type picker, What is this about? Station/Rider toggle (Rider reveals a search field plus Scan Rider QR), Describe your issue free text, Evidence dashed dropzone with Take a photo / Optional, and a sticky footer holding the amber “Please provide details about the issue” warning above a disabled Submit Report.

Issue type opens a grouped picker sheet — “Select issue type · What’s happening? Pick the closest match.” — with tinted group headers and radio rows.

One entry point, three ways in: the in-swap Report issue action, the dashboard Report case quick action, and Report an issue on a past swap’s detail screen. Each returns to where it came from.

Flagged for confirmation: the production screenshot is cut off below WALLET & PAYMENT, so the full taxonomy is not visible. Replicated as seen: BATTERY, OTHER, STATION ASSIGNMENT, WALLET & PAYMENT. A SWAP UNLOCK group was added (“Unlock Sent But Lock Never Released”, “Keke Not Responding At All”) because the in-swap report is opened from the unlock step and C4 depends on a solenoid fault being filable as its own type. Confirm whether production already has this group under a different name before implementing.

Status: fixed in prototype

The timer sat top-right on Swap Payment and Wallet Transactions but jumped to the left of the band on Complete Swap. It now sits in the same top-right slot of the page header on every screen of the swap, so it does not move as the handler advances.

Status: removed in prototype, Energy module

Capture no longer begins by scanning a QR on the meter. Removed entirely:

  • The Scan a meter QR screen (the QR viewfinder and its scan-progress counter).
  • The identify screen that followed it, which existed only to confirm which meter the QR had resolved to, and its Skip / Recapture branch.
  • The per-meter QR as a required step in the capture path. The meter code is still shown on every row and every reading; it is just no longer scanned to get there.

The handler now taps Capture readings (or any meter in the list) and goes straight to the camera. After saving, Capture next meter continues down the list without returning to a scanner.

Why: a station has 72 metering points captured twice a day. Two extra screens per point is roughly 288 avoidable screens a day for a walk the handler is doing in physical order anyway.

R6 · Deleting the meter photo after extraction

Section titled “R6 · Deleting the meter photo after extraction”

Status: reversed in prototype

The original v1 scope was upload → extract → delete the local photo, and the third lifecycle step read “Photo deleted from device”. That is reversed: the photo is now kept on file with the reading.

  • Lifecycle step 3 is now “Photo kept on file” — “The image stays with the reading so it can be checked later.”
  • The saved screen no longer says the photo will be deleted.
  • The captured row on a completed point reads “photo kept on file” instead of “photo deleted”.

Why: a reading that cannot be checked against the image it came from cannot be disputed or corrected. Retention is what makes the extracted number auditable.

C7 · Energy capture happens twice a day, both rounds under each point

Section titled “C7 · Energy capture happens twice a day, both rounds under each point”

Status: built in prototype, Energy module

One round a day becomes two: a Morning round and an Evening round. They are not a filter or a tab — the existing single list of meter points is kept, and each point now carries both rounds underneath it:

[icon] Solar String 45
SOL-TG-45 · Solar
Morning Daily 1.8 kWh · Total 1,284.6 kWh · At 08:12
Evening Not captured [ Capture ] [ Faulty ]
  • A round that is captured shows its extracted values in place.
  • A round that is not captured shows a Capture button and a Faulty action.
  • The two rounds are separate records. Capturing in the morning does not satisfy the evening, and does not overwrite it.
  • Past days show both rounds per point, read-only.

Which round the main button captures is decided by the clock, not by a selection. Between 06:00 and 12:00 it reads Capture morning; at any other time it reads Capture evening. It carries no meter name — it simply starts the next uncaptured point in the current round. The per-point Capture buttons remain available for either round regardless of the time, so a missed morning round can still be filled in.

Every capture carries its time. The time is stamped from the device clock when the shutter fires, not when extraction returns — the reading only means anything against the moment the meter was actually read. It therefore appears immediately: on the saved screen while the energy values still read “Reading…”, and on the meter row while the photo is still uploading. Capture time is a required field on every reading, morning or evening, MPPT or generator.

Station summary. A three-count summary sits above the list: Captured (points with nothing outstanding), Not captured, and Faulty, over a progress bar, with a per-round line beneath (Morning 45 of 72 · Evening 9 of 72).

C9 · A meter point can be faulty for one round only

Section titled “C9 · A meter point can be faulty for one round only”

Status: built in prototype, Energy module

Faulty is not a permanent property of the meter point. It is set per round, per day:

  • A point can be faulty this morning and working by the evening.
  • A point can be faulty today and read normally tomorrow.
  • Marking one round faulty leaves the other round untouched and still capturable.

Marking asks for a reason in a bottom sheet: Display not readable · Meter not powering on · Physically damaged · Cannot reach it right now. The round then shows a red Faulty chip with the reason, and a Working again action that clears it and puts the round back to needing a photo.

A faulty round counts as settled for the day’s progress — it is not left as an outstanding capture the handler can never complete — but the point is also counted in the Faulty total so the state stays visible rather than being hidden inside “captured”.

For implementation: faulty must be stored against (meter point, date, round), never against the meter point alone.

C8 · A meter photo yields several values, not one number

Section titled “C8 · A meter photo yields several values, not one number”

Status: built in prototype, Energy module

Extraction used to produce a single kWh figure per point. What actually comes off a meter depends on the meter:

Meter Values extracted
MPPT (solar) Daily energy + Total energy + capture time
Generator Total energy only + capture time

Every logged row now shows its full set inline, the saved screen lists them under “Read off the photo”, and the lifecycle’s reading step names what it is looking for per meter type. The row chip shows daily energy for an MPPT and total energy for a generator, since that is the number that means most for each.

Scenarios: Energy — morning round, Energy — evening round, Capture a meter reading, Upload → read → photo kept.

Section titled “C10 · The camera is not the only way in — photos can be uploaded from the gallery, in bulk”

Status: built in prototype, Energy module

Capture assumed the handler is standing at the meter with signal, in the app. That is not what happens: they walk the meters photographing as they go, and upload later. Two ways in now:

Entry Where What it does
Capture readings Energy tab, primary Unchanged — live camera, one meter at a time
Upload photos from gallery Energy tab, secondary Multi-select the phone gallery, extract every photo in one pass
Use a photo from the gallery Camera screen, under the shutter One photo for the meter whose camera is open

For implementation: extraction is trusted to read, not to file. A bulk upload produces a review before anything is written, because four things go wrong and each needs a different answer:

Outcome Row shows Action
Meter and numbers both read Meter point, time, values Saves
Numbers read, label unreadable The values, “label not readable” Choose meter — a sheet listing points of the same meter kind whose round is still open
That round already captured The saved time and this photo’s time Replace, or leave it out
Not a meter photo Nothing to read Remove

Only clean rows save, and the Save button counts them. A photo picked for a specific meter on the camera screen is refused if extraction reads a different meter off it, naming which one — filing one meter’s reading against another is the failure this whole review exists to prevent.

The photo is kept on file with the reading, exactly as with a camera capture.

C11 · The round comes off the photo’s own timestamp, and can be overridden

Section titled “C11 · The round comes off the photo’s own timestamp, and can be overridden”

Status: built in prototype, Energy module

The clock picked the round (06:00–12:00 morning, otherwise evening). That is right when the photo is taken in the app and wrong the moment it is not — a morning reading uploaded at 18:00 would have filed as an evening one, which corrupts both rounds at once.

For implementation: two separate rules, and the order matters.

  1. An uploaded photo carries its own capture time, and that timestamp decides its round. No user decision, no override needed: a photo taken at 07:52 files as a morning reading whenever it reaches the server. One bulk upload can therefore fill morning and evening rounds at the same time, and the review screen groups the rows by the round each landed in.
  2. A photo taken in the app still follows the clock, but the round in force is now shown under the capture buttons (“Evening round · set by the clock”) with a Change control. Changing it is for the case where the handler is filing a reading they took earlier and could not upload. An override is never silent — it stays on screen as “Filing under the morning round” with an Undo until it is cleared.

The capture time stored is the time on the photo, not the time of the upload.

Also corrected: total energy is a lifetime counter and must never decrease between the morning and evening readings of the same meter, and for an MPPT the gap between the two lifetime readings must equal the day’s generation (evening daily − morning daily). The prototype’s seed data broke both. Any implementation should reject a reading lower than the same meter’s previous one rather than store it.

Scenarios: Pick photos from the gallery, Bulk upload — read + review, Change the round, Filing an earlier round.

C12 · Readings belong to a DAY, and any day can still be captured into

Section titled “C12 · Readings belong to a DAY, and any day can still be captured into”

Status: built in prototype, Energy module

A past date used to be a read-only report generated from a summary row. There was nowhere for a backdated reading to land, and the capture and upload buttons were hidden on any day but today. Two consequences, both wrong: a day the handler missed was a dead end, and after a bulk upload finished today’s round the capture button vanished from the screen entirely.

For implementation: the meter points are the station’s, but the READINGS belong to a day. Every day holds the same points with the same two rounds, today included. There is now no such thing as a read-only day.

  • A day nobody captured opens as eight uncaptured points, not an empty state.
  • The capture button never disappears. When the round in force has nothing outstanding it stops being a shortcut to “the next one” and becomes Capture a reading, opening a sheet listing every point and round — including ones already read, so a bad reading can be replaced. Logged rounds also gained a Retake action in the list.
  • Anything filed into a day that has already ended is stored with a late flag and shown as Added later. The record has to say when a value was added, not only what it says.

C13 · The meter’s own date and time are extracted, and they are what file the reading

Section titled “C13 · The meter’s own date and time are extracted, and they are what file the reading”

Status: built in prototype, Energy module

Ridwan’s point: the MPPT display shows a date and time. That is the validation — it proves which day and round a reading actually answers for.

For implementation: extraction now yields date + time from the meter display alongside the energy values, and those are what the reading files by. A photo has two clocks and they are not the same thing:

What it is Used for
Phone taken / at when the shutter fired grouping the gallery, and the fallback when the meter clock is wrong
Meter mday / mat what the display itself shows the day and round the reading files under, and the stored capture time

This is what makes backdating trustworthy rather than a claim. An MPPT holds its daily history on screen, so a reading for a missed day can be photographed today and still file against that day — the date on the display is the evidence. A bulk upload can therefore fill several days and both rounds in one pass; the review groups rows by day, then round.

A fifth review outcome was added for the failure this catches: the meter clock is wrong (a controller whose clock reset after a power cut). The row shows the date the meter is claiming, is excluded from the save, and offers to file under the day the handler had open, taking the round from the phone’s clock instead.

Also corrected while rebuilding the day model: lifetime totals now climb across days as well as within one, so any two readings of the same meter are consistent whichever days they come from.

Scenarios: A day nobody captured, Backfill a missed day, Capture any meter again.

C14 · The register is the real one: labels, and what each screen actually reports

Section titled “C14 · The register is the real one: labels, and what each screen actually reports”

Status: built in prototype, Energy module. Contains an open question — read the genset note.

Ridwan supplied photographs of the real controllers. Three things in them changed the model, and the prototype was rebuilt on them so no figure in it is invented.

1. The LABEL is the identity. It is a printed sticker on the wall above each controller — M9, M76, M136, G1 — and it is what extraction reads off the photo. The invented SOL-TG-45 / “Solar String 45” scheme is gone. Labels are only unique inside a station, matching the Atlas meter-point register. The station is now 12 MPPT controllers and 1 genset; the “72 points” figure was invented and has been removed with it.

2. The MPPT screen carries two counters in two different units.

On the display Unit What it means
Daily : Wh resets overnight — near zero on the morning round, the day’s generation by the evening one
Total : whole kWh lifetime counter

Consequence for the build: the day’s generation needs no subtraction — the meter’s own Daily counter has already done it, and the evening round alone reports it. Because Total is displayed rounded to whole kWh, Total and Daily agree only to that rounding; do not assert equality beyond 1 kWh. Every reading in the prototype is anchored to a real observed value (M9 total 12 kWh, M76 2485 kWh, dailies 8.8–16.0 kWh, morning dailies 0.00–0.21 kWh).

3. The genset reports a lifetime total, and is read once a day. (Resolved 2026-08-13 by Ridwan — this was the open question below and is now answered.)

The SmartGen controller face shows 405V 50.0Hz · 201A 174kW 72% · 28.4V 1502RPM · 85℃ 296kPa · Gens normal running — instantaneous output, not energy — and carries no date. That face is not what is captured. The generator round photographs the energy page, which carries a lifetime Total in kWh, and that total is the only number recorded:

  • Output and load are not captured at all. A snapshot of load is not a reading anybody can add up, so it is not in the record.
  • The generator is read ONCE A DAY, not twice. A lifetime total does not say anything different at 08:00 than it does at 17:00. It files under the evening round, which is when the walk that carries it happens. The morning round therefore has 12 points, not 13, and every count, filter and denominator in the module follows roundsOf(point) rather than assuming two rounds.
  • Because the total is cumulative, the generator’s contribution to a day is a difference between days, never a subtraction inside one.
  • The generator photo still cannot date itself, so its day comes from the phone.

⚠ Still needed from the field: unlike every MPPT figure in the prototype, G1’s seeded total and perDay are not anchored to a photographed display. They need one real reading off the genset energy page to replace them.

C15 · Filter the meter points, and open the photo behind any reading

Section titled “C15 · Filter the meter points, and open the photo behind any reading”

Status: built in prototype, Energy module

Two direct requests.

Filter. A track of four chips over the point list — All · Not captured · Captured · Faulty — each carrying its live count. “Not captured” is the question the handler opens the screen with. Not captured and Captured use the same test as the summary above them, so the two can never disagree and every point falls under exactly one; Faulty sits across both, because a point read in one round and faulty in the other has nothing outstanding but still needs someone to know. An empty result says which filter emptied it rather than showing a blank page.

Photo preview. Every logged round now carries a thumbnail of the photo it came off, one tap from the number. The preview shows the full image plus everything read off it — meter, date, time, round, and the values — and offers a retake. For an MPPT it credits the meter’s clock; for the genset, the phone’s. A reading nobody can check against its image is only an assertion, and this is where a wrong one gets caught.

Scenarios: Filter to what is left, The photo behind a reading.

These were prototype defects, not product decisions. Noted only so they are not mistaken for intended behaviour.

  • Sheets could not be dismissed. goTo() clears the active class only from screens in its SCREENS list, and the newer sheets (park, report, cancel, shift, swap detail) had not been added to it. They rendered on top and never hid, so Cancel and Report Issue trapped the user with no way back. Every screen goTo() can show is now in that list.
  • Duplicate action row. Moving Park / Report issue / Cancel to the top of the screen left a second copy still sitting in the bottom CTA bar. Removed.

Recorded here only so the developer can tell added-vs-removed apart when diffing against the live app.

  • Shift card on the dashboard: start and end a shift, so Atlas can report who was on duty on a given day instead of inferring duty from who happened to perform a swap.
  • Swap history rows now show swap time and unlock method, and are tappable.
  • Swap detail screen opened from a history row, with report-an-issue and download-receipt actions.

Update this file on every removal for the rest of the swap app redesign.