Inspiration
Every May, Syracuse throws away furniture.
We mean that literally. Desks, mini fridges, mattress toppers, half-full bottles of detergent, all of it stacked on the curb along Euclid and Ackerman because somebody's lease ended Friday and their car had no roof rack. Three months later the next student buys the same desk new.
Then we went looking for numbers, and they're worse than we assumed.
The average American college student throws away about 640 pounds of waste a year, a figure cited by the EPA and by university sustainability offices across the country. Most of it lands in a few days at the end of spring semester.
The clearest picture comes from the University of New Hampshire, which measured its own campus: a normal month produces around 25 tons of waste. The month of May produces 105 tons. That 80-ton spike is almost entirely move-out. Furniture, mini fridges, bedding, storage bins, clothing, thrown out rather than carried home.
It's not one unusual campus. Tufts reports roughly 230 tons across its May and June move-out window. Boston University estimates that about 8% of everything the university throws away in a year is generated during move-out alone, and its diversion program still pulled 113 tons out of the waste stream in a single May.
Here's the part that actually shaped what we built.
Syracuse already has a program. It's called 'Cuse Collections, it runs storage containers on North and South Campus during the last week of spring semester, and everything collected goes to local nonprofits and the campus food pantries. It works. In spring 2024 it filled more than 70 bins.
And when a student newspaper asked students about it this year, several said they had no idea it existed. One said she had never seen the bins. The sustainability office emails everyone, and, as one student put it, nobody reads those emails.
So the gap isn't goodwill, and it isn't infrastructure. Students are not hostile to donating. The gap is that donating requires you to already know where a bin is, to be on campus in that specific week, and to physically carry a desk there. A junior moving out of an apartment on Ackerman at 11pm on the last day of their lease fails all three tests, and the desk goes on the curb.
That reframed the problem for us. This was never about persuading students to care. It's about matching supply to demand at the exact moment both exist, without anybody needing to drive anywhere or remember an email. A desk sitting unused in May is worth almost nothing to the person who owns it. That same desk is worth a lot to someone moving in that August. Timing and distance matter as much as the item does.
What it does
Cuse-MoveMate is a student-to-student move-out marketplace for Syracuse University.
Students moving out list items as free, for sale, or trade. Students moving in describe what they need the way they'd text a friend:
"I'm moving into an apartment next week and need a desk, chair and kitchen supplies for under $50."
The app turns that into item, budget, distance and deadline, then ranks what other students nearby actually have. Every card says why it showed up: "0.64 km, about 8 min walk ยท free ยท available before you need it."
From there the two students agree a pickup time in two taps, chat if they need to, meet up, and both confirm the handoff. That confirmation is what earns MovePoints, which turn into wallet credit that only spends inside the app.
Giving something away earns more than selling it. That is the entire point.
How we built it
Django 5 and Django REST Framework on SQLite for the backend. React 18 with Vite for the frontend, no UI library, all custom CSS in the Syracuse orange.
Distance is a ranking signal, not a filter
A desk two blocks away beats a slightly better desk across the city, because the student carrying it doesn't have a truck. So we blended distance into relevance instead of applying it afterwards:
$$ S = 0.34\,R \;+\; 0.30\,D \;+\; 0.18\,B \;+\; 0.10\,T \;+\; 0.08\,C $$
\( R \) is text relevance, \( D \) is distance decay, \( B \) is budget fit, \( T \) is timing against the listing's pickup deadline, and \( C \) is condition. Distance decays linearly out to the search radius:
$$ D = 1 - \frac{d}{r}, \qquad d < r $$
where \( d \) comes from the haversine formula between the student's coordinates and the listing's.
The points economy has exactly one chokepoint
Points are created by a single event: both students tapping Confirm handoff. Nothing else in the codebase mints them.
The wallet balance is never edited directly. It's a cached sum of an append-only ledger, with a unique constraint on (wallet, rule, transaction) so a double-tapped button can't award twice. Every point in a balance traces back to a real transaction. Money-adjacent code should be boring.
@transaction.atomic
def confirm_handoff(txn, user):
field = "owner_confirmed_at" if user == txn.owner else "claimant_confirmed_at"
setattr(txn, field, timezone.now())
if txn.owner_confirmed_at and txn.claimant_confirmed_at:
txn.state = "completed"
award_points(txn) # pending ledger rows for both sides
txn.save()
AI where it earns its place
Natural-language requests run through three engines, tried in order. All three return the same structure:
- LangChain.
ChatPromptTemplate | ChatAnthropic | JsonOutputParser, with a Pydantic schema so the model is told the exact shape up front and the response gets validated on the way out. - Anthropic SDK. Same model, hand-rolled JSON handling, for when LangChain isn't installed.
- Rule parser. Deterministic, offline, no API key. It handles budgets, deadlines, radii, category phrases like "kitchen supplies", and starter kits like "free stuff for a dorm room".
That third one is why a demo can't be broken by conference wifi, and it's what runs by default.
There's also a companion graph for what a student needs next. Searching for a paint brush surfaces the roller, the tape and the drop cloth, each ranked against real nearby listings.
The Impact Wallet
A balance on its own is a video-game score, so we never show one by itself. Each category carries three constants in a database table anyone can edit: average weight, COโe avoided, estimated replacement value.
$$ \text{savings} = \sum_{i}\left(V_{c(i)} - p_i\right), \qquad \text{diverted} = \sum_{i} W_{c(i)} $$
Because those constants live in the database instead of the code, Syracuse's own Sustainability Management office could swap in their measured figures without a redeploy. Every number on the card is labelled an estimate.
Everything else
Verified .edu identity gating redemption. Per-transaction chat. A two-tap pickup-time handshake with Google Calendar and .ics export. Travel times for walking, cycling, bus and driving through the Google Distance Matrix API. In-app notifications, with email for the two events that actually matter. Photo upload straight from a phone camera.
Challenges we ran into
A points system is a fraud problem wearing a friendly hat. The moment points convert to money, two roommates on a couch can "trade" a spatula forty times. We added a pair limit of two earning handoffs per 30 days, a 300-point weekly ceiling, a 24-hour pending window before points post, a listing quality gate, and .edu verification before any redemption.
Our own demo data lied to us. The distance filter looked broken because every seeded listing sat within 1 km of campus, so dragging the radius from 2 km to 20 km changed nothing. We spread listings across real Syracuse neighbourhoods, and it still looked broken. We'd assigned every distant location to an item the seed script marked as already handed off. All the far listings existed, and not one of them was visible.
A geocoder that's confidently wrong is worse than one that says nothing. Searching "South Campus" returned Westcott, because OpenStreetMap doesn't index campus names the way students say them, and our code then relabelled the result by whatever neighbourhood surrounded it. We replaced it with a 28-entry gazetteer of campus landmarks, checked before any network call: South Campus, Skytop, Ernie Davis, the Quad, Marshall Street. Real street addresses still fall through to the geocoder.
A silent string replacement shipped a feature that was never called. The patch wiring up that gazetteer targeted a function name that didn't quite match. Python replaced nothing and raised nothing, so we shipped a fix that did absolutely nothing while believing it worked. The build asserts before it packages now.
Schema drift produced a 500 on a perfectly good code path. Migration files were gitignored, so every copy of the project regenerated 0001_initial. A database that already had that name recorded as applied skipped the newer version entirely, three columns silently never got created, and the first request touching one blew up. We stopped ignoring migrations and wrote a repair_schema command that diffs the models against the live table.
An empty page is never the right answer. Early on, searching for something nobody had listed returned nothing at all. Now the server widens the radius, drops the text, and shows what students nearby actually have: "No exact match for that. Here's everything students near you have right now." A dead end teaches someone the app is useless. A near-miss teaches them to come back.
Accomplishments that we're proud of
It's a whole loop, not a demo path. List, match, request, accept, schedule, chat, meet, confirm, review, earn, redeem, list again. Every step works, and every step exists because the one before it left the student with a question they couldn't answer.
We built for the failure mode the data actually shows. Syracuse doesn't have a goodwill problem, it has a friction problem. So we optimised for the student who is out of time: no bin to find, no week to remember, and the nearest match ranked first because they're carrying it on foot.
The points economy would survive contact with real students. Both-sides confirmation, a pending window, pair limits, a weekly ceiling, a quality gate on listings, and .edu verification before redemption. We treated it like money from the first commit, because the moment it converts to credit it is money.
The app tells you what to do next, in your own words. Every request card knows whether you're the seller or the buyer and what stage you're at, and says so: "Agree a time and meet up", "Your tap completes this", "Leave a review. Worth 5 points, and it's what makes pranavi trustworthy to the next student." Two people are coordinating a real-world meetup through an app neither has used before. Telling them what happens next turned out to be worth more than any feature we built.
It never needs the internet to be impressive. No API key, no wifi, no mail server, and the natural-language matching still works, the search still ranks, the demo still runs. Everything clever degrades to something honest instead of to an error.
What we learned
Most of our time didn't go into features. It went into making the app tell the truth.
Every bug in the section above is the same bug wearing a different hat. The interface kept asserting things the system couldn't back up. A slider that filtered nothing. A "code sent" message for an email that only ever got printed to a terminal. A location name pulled from the wrong field. A "free" label on a $35 calculator because a score happened to cross a threshold.
Users forgive missing features. They don't forgive being lied to, and they can't tell the difference between a feature that's absent and one that's quietly broken.
The research taught us something sharper. Campus reuse programs are not failing on intent, they're failing on reach. The bins are there, the nonprofits are there, and students still say they've never seen either. Anything that only works if a student reads an email in finals week is going to lose to a dumpster that's twenty feet from their door.
So the product lesson was simple. Make giving things away the most rewarding action in the system, and make it require less effort than throwing them out. A free giveaway earns 50 points plus a 25-point circulation bonus, while a sale earns 20 plus the same bonus. The item most likely to end up on a curb is the one nobody wants to bother pricing.
What's next for Cuse-MoveMate
Work with 'Cuse Collections instead of around it. The containers already go up on North and South Campus every spring. If those were drop-off points inside the app, a student could list an item, get matched, and hand it to a bin when nobody claims it. That's the version where nothing is wasted either way.
Bulk move-out listings, so four roommates can clear an apartment in one pass rather than twenty-two separate forms.
Pickup and truck-share coordination, paid for with MovePoints. The reason good furniture gets abandoned is almost never price. It's that nobody has a vehicle.
AI-generated move-in checklists, so a first-year lands on a list instead of a search box.
Sublease and apartment discovery, because the people moving furniture are usually moving addresses at the same time.
A diversion dashboard for the university, built on the same per-category constants already sitting in our database. If Syracuse measured a normal month against a May the way UNH did, this is where that number would live.
Move in โ Reuse โ Pass it on โ Move in.
Built With
- anthropic-claude
- css3
- django-rest-framework
- google-calendar-api
- google-distance-matrix-api
- google-geocoding-api
- google-maps
- haversine
- html5
- icalendar
- javascript
- langchain
- nominatim
- openstreetmap
- pydantic
- python
- react
- react-router
- rest-api
- smtp
- sqlite
- svg
- token-authentication
- vite
Log in or sign up for Devpost to join the conversation.