- Chose
- Withholding identifying details from public listings, and verifying a claim by asking the claimant to supply them.
- Instead of
- Listing found items in full and resolving claims first-come.
- Why
- A complete public listing is a script for stealing the item: anyone can read the description back. Inverting it makes the unpublished detail the proof: the finder records everything, the listing shows only enough to be recognisable, and the claimant has to produce the rest. It is worse UX and it is the only version that works.
WorkAcademic
Lost and found tracking system
BRAC University · 2025
A Flask application for reporting lost and found items, with automated matching between the two, authentication, and role-based access. It looks like a straightforward CRUD project and mostly is, except for one part, which is that a system connecting owners to their property is also a system for handing property to whoever asks first.
The problem
Campus lost-and-found runs on noticeboards and group chats, so the two halves never meet: the person who lost a bag and the person who found it are both posting, in different places, days apart. The obvious fix is a shared database with matching over it. The less obvious problem is what happens the moment a found item is listed in full detail, at which point the listing itself tells anyone reading exactly what to claim.
The system
Report
lost or found, one schema
Match
category, place, window
Candidate
a human confirms
Challenge
the claimant supplies it
Release
administrator resolves
Two report types over one schema
Lost reports and found reports are the same entity with a direction, so matching is a query across one table rather than a reconciliation between two. The relational schema is normalised around items, users, reports, and claims.
Automated matching
New reports are matched against open reports of the opposite direction on category, location, and time window, producing candidate matches for a human to confirm rather than automatic resolutions.
Authentication and roles
Ordinary users report and claim; administrators moderate listings and resolve disputes. Role checks sit in the service layer rather than in templates, so hiding a button is a presentation detail and not the security boundary.
MVC structure
Routes, services, and models kept separate, with SQLAlchemy handling persistence, which is enough structure that the matching logic is testable without a request in flight.
Technical decisions
What was chosen, what was chosen against, and what the trade cost.
- Chose
- Matching as a database query over category, location, and time.
- Instead of
- A similarity model over the free-text descriptions.
- Why
- The dataset is small and cold-starts empty, which is the condition under which learned similarity performs worst and is hardest to debug. A deterministic query is explainable to the user, tunable by changing a number, and correct on day one, and matches only need to be good enough for a person to confirm, not good enough to act alone.
- Chose
- Candidate matches surfaced for confirmation, never auto-resolved.
- Instead of
- Automatically closing a pair above a confidence threshold.
- Why
- The two failure directions are not symmetric. A missed match wastes a little time; a wrong automatic match hands someone else's property away and the system was the one that decided it. Keeping a person in the loop costs a click.
- Chose
- SQLAlchemy against SQLite locally and PostgreSQL in production.
- Instead of
- Writing SQL directly, or using SQLite everywhere.
- Why
- The ORM keeps one schema definition working against both, so local development needs no database server while production still gets real concurrency and real constraint enforcement. The cost is a layer between the code and the query plan, which is a bad trade at scale and a good one here.
The result
A working recovery platform, and a lesson that had nothing to do with the stack: the feature that decided whether the system was usable at all was not matching, ranking, or the interface. It was the question of what a listing is allowed to reveal, and that only became visible after building the version that got it wrong.