Built by Niklas Britz, Leander Diaz-Bone, and Tilman Otto for the Antrophic Hackathon 2026 in Zurich. (And we won.π)
Picture a category manager at an Austrian electronics retailer. Every morning she opens a spreadsheet with hundreds of products β TVs, soundbars, washing machines β and wonders: what are my competitors charging for the same thing?
She knows the LG OLED55C31LA on her shelf. But what's Expert AT listing it for? Is Cyberport offering a close alternative at a lower price? Is e-tec.at even carrying it?
Answering that manually β crawling retailer sites, matching product names that never quite line up, comparing specs across inconsistent data formats β would take a team of people days. And the prices will have changed by the time they're done.
That's the problem we set out to solve.
A fully automated competitor product matching system. Give it your product catalog. It scrapes rival Austrian retailers, finds every equivalent product, and ranks the matches β all without a human in the loop.
The result lands in a structured JSON file ready to submit to the Zenline scoring system, or you can explore matches interactively through a conversational chat interface.
Step 1 β Upload your catalog.
Drop in a source_products_*.json file from the Zenline data explorer. Your products are the starting point.
Step 2 β Index the target pool. Run the indexer against the provided competitor target pool. Each product gets enriched by GPT-4o into a rich semantic description β not just a product name, but a 300β500 word embedding text that captures screen size, display technology, resolution tier, price bracket, and more in both German and English. These descriptions go into Weaviate, a cloud vector database.
Step 3 β Scrape the hidden retailers. The scraper fans out to expert.at, e-tec.at, and electronic4you.at. It searches each site by product name, follows the result links, and extracts structured data from JSON-LD, Open Graph meta tags, and Nuxt-embedded JavaScript. The scraped products get indexed into the same Weaviate collection, tagged separately so we can control retrieval from each bucket independently.
Step 4 β Match. For every source product, we run the full matching pipeline: GPT-4o turns the source product into an embedding query, Weaviate retrieves the top-K semantic neighbours from both buckets, and then a second GPT-4o call reranks them against strict substitutability criteria β same subcategory, similar size and specs, comparable price range.
Step 5 β Explore or submit. Results land in a JSON file. Or spin up the FastAPI backend and the Next.js chat UI, describe a product in plain language, and get ranked competitor matches back in seconds.
See visualization.html for the full diagram. In brief:
Data Sources (target pool JSON + scraped retailer HTML)
β
βΌ
Indexing Pipeline
GPT-4o semantic enrichment β Weaviate vector DB + local JSON cache
β
βΌ
Matching Pipeline
GPT-4o source enrichment β Weaviate nearText search β GPT-4o reranking
β
βΌ
Interfaces: REST API | Next.js Chat UI | CLI | Batch runner
src/
preprocessor.py # GPT-4o: raw product β rich embedding text + metadata
indexer.py # Ingest JSON files into Weaviate + local store
searcher.py # Preprocess source product, query Weaviate
reranker.py # GPT-4o: filter and rank candidates
batch_match.py # Process all source products in one run
scrape_searches.py # Crawl expert.at / e-tec.at / electronic4you.at
scrape_source_products.py
weaviate_client.py # Weaviate Cloud connection, collection, queries
local_store.py # JSON-backed cache: reference β embedding + product data
api.py # FastAPI: /analyze, /chat, /suggestions
frontend/ # Next.js 15 + TypeScript + Tailwind chat UI
config.py # API keys, collection name, defaults
- Python 3.11+
- Node.js 18+ (for the frontend)
- A
.envfile with:
OPENROUTER_API_KEY=...
WEAVIATE_URL=...
WEAVIATE_API_KEY=...
pip install -r requirements.txt
cd frontend && npm installpython -m src.main index --file target_pool_tv_\&_audio.json --reset3. Scrape hidden retailers and index scraped products
from src.scrape_searches import scrape_searches
scrape_searches(["LG OLED55C31LA", "Samsung UE65DU7170"])python -m src.main index --file all_products.json --scrapedpython -m src.batch_match \
--source source_products_tv_\&_audio.json \
--output match_results_tv_audio.jsonProduces match_results_tv_audio.json (target pool matches) and match_results_tv_audio_scraped.json (scraped matches).
# Backend
uvicorn src.api:app --reload
# Frontend (separate terminal)
cd frontend && npm run devOpen http://localhost:3000 and start searching.
# By product reference
python -m src.main search \
--reference P_0A7A0D68 \
--source source_products_tv_\&_audio.json
# By free text
python -m src.main search --text "Samsung 65 Zoll 4K QLED unter 800 Euro"Raw product data β inconsistent names, missing specs, varying formats across retailers β makes naive string or even vector matching unreliable. Instead, we use GPT-4o to expand each product into a structured 300β500 word description before embedding. The prompt instructs the model to lead with the most discriminative attributes (screen size, display technology, resolution) repeated multiple times in German and English, so that semantically equivalent products land close together in vector space even when their raw names look nothing alike.
Weaviate's nearText gives us the top-K semantic neighbours quickly. But semantic similarity is not the same as substitutability β a 65" QLED and a 65" soundbar might share embedding space. The second GPT-4o pass is given the source product description and the candidate list, and asked to select only genuine substitutes: same product subcategory, comparable size (within ~10%), same display technology, same resolution tier, similar price bracket (within ~50%). This two-pass approach keeps latency low while ensuring precision.
Known-retailer products (from the Zenline target pool) and scraped products (from our crawler) are stored in the same Weaviate collection but tagged with a scraped boolean. Batch matching queries each bucket separately so we always return matches from both sources, regardless of which bucket happens to be more populated.
![]() |
![]() |
| Chat interface to search for products | Competitor pricing at a glance analyzed in seconds |
- Niklas Britz
- Leander Diaz-Bone
- Tilman Otto

