Personal Product Watch
I’m building a criteria-driven product watcher that compares unit prices and explains whether offers match. Try it with fictional data in the public demo.
- TypeScript
- Cloudflare Workers
- D1
- SQLite
- GitHub Actions
Software engineering portfolio
Senior Software Engineer
I build and modernise Java and Angular insurance software at Keylane. I lead ongoing SOAP-to-REST migration, focusing on compatibility, authorisation and automated testing.
Outside work, I explore practical problems through personal software projects, with a focus on useful tools, reliable systems and clear interfaces.
My engineering work at Keylane and three personal projects. Open a case study for the full story.
Explore by focus
I’m building a criteria-driven product watcher that compares unit prices and explains whether offers match. Try it with fictional data in the public demo.
Java and Angular insurance software, with ongoing SOAP-to-REST modernisation, authorisation, database migrations and automated testing.
I’m building a club platform for player registration, profiles and member administration.
I’m building tools to collect smart-meter readings, explore energy history and compare when to use electricity.
Engineering practice: Story refinement, code review, technical notes and release documentation.
Shared toolkit: Git · IntelliJ IDEA · Gradle · Maven · JUnit · Playwright · CI/CD.
Explore how things work
Investigate a system, challenge an API contract or compare search strategies. Change a condition and inspect what follows.
Inspect the request, the saved effect, client compatibility and access decisions.
Trace complete · 2 attempts · 1 record · 201 Created
Operation inspector
The first task commits before its reply is lost. The repeated request reuses the same idempotency key and returns the original task.
Selected operation data
{
"id": "task-42",
"title": "Review API contract"
}
The first write commits before its reply is lost. An idempotency key connects the retry to the same operation.
Simulated example; timings do not measure a real system. Keys apply to the same request body; key expiry, concurrent requests and cancellation are outside this example. No network requests or stored data.
Keep the client fixed and change the API. See why valid JSON alone does not guarantee compatibility.
The existing client is the application calling this API. It sends { "title": "Review API contract" }
to POST /demo/tasks. It expects 201 Created, reads id and title
as strings, and ignores extra response fields.
Compatible with this client · Reading the API response. Client reads task-42: Review API contract.
This client ignores fields it does not read, so adding priority leaves its existing behaviour intact.
Required request fields: title
201 Created
{
"id": "task-42",
"title": "Review API contract"
}Required request fields: title
201 Created
{
"id": "task-42",
"title": "Review API contract",
"priority": "normal"
}Safer rollout: Keep the added field optional for existing clients; check stricter clients separately.
These results apply to the client defined above. A stricter client could reject extra fields; other clients may tolerate different changes. Review actual consumers before changing a real API.
Simulated example · runs in your browser · no network requests or saved changes. Read about my API work
Can this person act on this record? Change the selections to see which actions are allowed.
BLOCK · 403 Forbidden. Reviewer and editor roles cannot publish in this example.
Action blocked. The record is neither read nor changed.
Signed-in reviewers, editors and administrators may read records in their assigned workspace. Only administrators may publish drafts there. No role can access another workspace or publish archived records. Missing or unknown context permits no operation.
Fictional permissions demo with no real accounts, network requests or saved changes. Real services must check permissions on the server for every request. OWASP guidance (opens in a new tab).
Same grid, different search strategy. Compare breadth-first search (BFS) with depth-first search (DFS).
....... .#####. .#####. S..#..G .#...#. .#####. .......
S StartG Goal# Wall· Visited○ Pending@ Current+ Route
BFS explores in layers; DFS follows a branch before backtracking.
BFS uses a queue. With equally weighted moves, its first route to the goal is a shortest route. DFS uses a stack and can find a longer route.
Moves have equal cost: up, right, down or left. Walls cannot be crossed. The same selections produce the same search. The example runs entirely in your browser.
BFS takes the oldest pending cell; DFS takes the newest. Neighbours are considered up, right, down, left. The choice changes both the route and how much of the board is explored. Neither algorithm is always faster on every board.
| Search | Moves | Explored |
|---|---|---|
| BFS | 8 | 24 |
| DFS | 12 | 13 |
In the two-route example, the shortest route takes 8 moves through the lower passage. Following the upper branch takes 12 moves. A blocked goal has no route.
Software engineering in the Netherlands and Scotland, following earlier roles in banking.
Keylane
I deliver insurance features across Java, Angular and relational databases, from solution analysis to release.
Scotland’s Rural College
I helped design and implement a portal for field-trials data.
First Bank
I led cash operations and customer service, including vault and ATM processes.
I studied economics and worked in banking operations before completing an MSc in Information Technology at the University of Aberdeen.
My banking roles covered customer service, cash handling, and ATM operations. In software work, I still start by asking how a process works in practice before translating it into requirements.
MSc, Information Technology University of Aberdeen, Scotland
2018 — 2019BSc, Economics University of Nigeria, Nsukka
2006 — 2010Send me an email about engineering roles or any of the projects here.