# Yacht Assets Reviewer

> One shared review app for 115 yacht interior assets: swipe, agree live, export the picks in one click. Built in a day.
>
> https://pravda.systems/work/yacht-assets-reviewer · 2026-06-13

**Problem.** A yacht design team had to shortlist 115 premium 3D furnishing assets over a spreadsheet of links with no images and no shared state.

**Solution.** A web app where reviewers swipe through product images, see each other's picks live, and export approvals in one click.

**Outcome.** All 115 assets land in one shared, image-led review pass, and approved picks export as a numbered ZIP the 3D designer can use with zero reassembly.

## Key metrics

- **115** Assets reviewed — Premium assets reviewed and exported across 10 interior design categories _(verified: README statistics section, confirmed by row count in yacht_assets_catalog.csv (115 data rows))_
- **115** Product URLs scraped — Supplier product URLs collected by the scraper (91 from one supplier, 24 from the other) _(verified: Direct count of URL strings in assets_by_category dict in scrape_yacht_assets.py, matches CSV row count (115 data rows).)_
- **10** Review categories — Asset categories: tableware, seating, tables, storage, decor, plants, lighting, audio, textiles, accessories _(verified: CATEGORIES dict in scrape_yacht_assets.py, confirmed by CSV category column.)_
- **922** Python source lines — Python powering the scraper, uploader, and Flask backend _(verified: stats.json loc_by_ext_top .py for yacht-assets-reviewer)_
- **Render.com** Deployment target — Live cloud web service (gunicorn), deploy in about 30 minutes per the deployment docs. _(verified: Procfile, DEPLOYMENT_INSTRUCTIONS.md, README quick deploy section)_

## The problem

**A team had to agree on 115 premium furnishing assets from two suppliers, working over a spreadsheet of links with no images and no shared state.**

A yacht interior design project required shortlisting premium 3D furnishing models across 10 categories, including seating, tables, tableware, lighting, decor, and more. The catalog covered 115 products. The team needed a way to review product images side by side, agree on approvals, flag top picks, and hand off a clean selection to the 3D designer, all without a shared license to a dedicated asset management tool.

Reviewers worked from URLs alone, pasted screenshots into chat, and reconciled decisions by email. Approvals collided, picks went missing, and the **3D designer reassembled the final set by hand**.

## What I built

**A web app where the whole team swipes through product images, approves or rejects each one, and sees everyone's decisions in real time.**

A purpose-built multi-user review tool: a Flask backend serving a single-page interface where reviewers navigate assets one by one, approve or decline each, and mark favourites. All review state persists in a shared JSON catalog stored in Dropbox, so any reviewer's decision is visible to everyone else immediately.

A separate scraper pipeline was built first to collect and organise all **115 product URLs** from both supplier sites, download product images, and upload them to Dropbox alongside the catalog file. No accounts, no installs, any browser. This gave the review app a self-contained data layer with nothing to log into at review time.

## How it works

**Shared state lives in one Dropbox JSON file, making every decision instantly visible to everyone, with no database to run.**

The Flask backend exposes a REST API with endpoints for catalog read/write, per-image link generation, ZIP export, CSV export, and stats. The frontend loads the full catalog on page load, then fetches Dropbox temporary image links on demand to avoid **115 calls at once** on startup.

Reviewers use keyboard shortcuts or on-screen buttons: approve, decline, top-rated, back, skip. A live sidebar tracks progress and allows filtering by category or review status, and the filter narrows the navigation queue in real time without a page reload. The whole interface is a single HTML file with no build step and no framework dependencies.

## The handoff

**One click exports approved assets as a numbered ZIP plus a links manifest, ready for the designer with no manual cleanup.**

When review is complete, one click generates a ZIP in memory: it includes a `links.txt` manifest grouped by category with ordered item numbers, plus all approved images renamed with numeric prefixes so anyone can say "use item #7." The 3D designer receives a clean, numbered set with no further assembly needed. A CSV export covers the full catalog with order, category, source URL, status, and rating columns for direct Notion import.

Deployment uses Render.com with Dropbox OAuth2 refresh tokens, and no credentials are committed to the repository.

## Results

**Built, documented, and deployed to cloud hosting in a single day, replacing hours of spreadsheet and screenshot back-and-forth.**

The scraper automated all image prep, the app turned **115 assets across 10 categories** into one image-led review pass, and the export hands off a clean selection with no manual cleanup. The entire stack (scraper, uploader, Flask backend, and single-page frontend) runs in **922 lines of Python**. Deployed on **Render.com in about 30 minutes** with token-based Dropbox sign-in and no passwords stored.

## Features

- **Card-swipe review interface** — Reviewers approve, decline, top-rate, back, or skip each asset by keyboard or button, with live progress stats and category filters.
- **Shared Dropbox state** — The catalog JSON lives in a Dropbox App Folder, so every reviewer's decision persists instantly and shows up in all other sessions.
- **Image scraper pipeline** — A scraper pulls product images from two supplier sites, sorts them by category, and uploads the full dataset to Dropbox before review begins.
- **ZIP export** — One click builds an in-memory ZIP of approved images, renamed with numeric prefixes and bundled with a category-grouped links manifest.
- **CSV / Notion export** — The full catalog exports as a CSV with order, category, source, URL, status, and rating columns, ready to import straight into Notion.

## Architecture

Flask REST API backend + single-page HTML/JS frontend. Dropbox App Folder acts as the shared database and image store. Scraper pipeline runs separately to populate Dropbox before the review app is used. Deployed to Render.com via Procfile (gunicorn).

- **scrape_yacht_assets.py** — Scrapes 115 product URLs from two suppliers, downloads images into category subfolders, and produces the initial JSON catalog
- **upload_to_dropbox.py** — Uploads the local catalog JSON and image folders to the Dropbox App Folder that the web app reads from
- **app.py** — Flask backend: serves the SPA, exposes REST endpoints for catalog read/write, per-image link generation, ZIP export, CSV export, and stats
- **templates/asset_reviewer.html** — Single-page frontend: card review UI, sidebar filters, stats panel, keyboard shortcut handler

## Tech

- **Languages:** Python, JavaScript, HTML, CSS
- **Frameworks:** Flask, gunicorn
- **Infrastructure:** Render.com, Dropbox API (OAuth2)

## Skills demonstrated

- Same-day delivery of a working tool
- Multi-user review apps without a database
- Web scraping across different site structures
- Flask REST API design
- Dropbox OAuth2 integration
- Cloud deployment (Render.com)
- Clean export and handoff pipelines
- Replacing manual workflows with automation
