Your autonomous Merchant Center guardian
Gemini 3.5 Flash · Genkit · Vertex AI · Cloud Run · Firestore
Live demo · Demo video: [VIDEO_URL] (replace before submission)
Google Merchant Center silently disapproves products for feed errors — missing prices, invalid GTINs, titles over 150 characters, promotional text baked into images. Each disapproved SKU stops serving on Shopping ads and free listings. In this 50-SKU demo catalog, the dashboard surfaces €1,219/day in revenue at risk from pending feed errors — invisible loss before anyone opens a spreadsheet. Feed Medic exists to catch those errors early, propose fixes, and learn your brand rules — before disapprovals hit your P&L.
Feed Medic runs an autonomous compliance pipeline with a human marketer always in the loop:
- Scans autonomously — Cloud Scheduler triggers a full catalog scan every 6 hours via
POST /api/scan. - Diagnoses with Gemini — the
diagnoseProductGenkit flow checks each product against Merchant Center rules (title length, GTIN, price, description, image promos). - Proposes fixes with confidence scores — every issue becomes a structured fix with an English explanation, a suggested correction, and a 0–1 confidence score shown in the dashboard.
- Human-in-the-loop approval queue — nothing is auto-published. The agent never applies corrections to your feed; every fix waits in
pending_approvaluntil a marketer approves or rejects it. Confidence scores help you defer on borderline cases rather than risk a bad publish. - Learns brand rules from rejections — when you reject a fix and explain why, the
learnBrandRuleflow turns your feedback into a reusable rule stored in Firestore and injected into every future diagnosis.
Every agent action is logged to agent_logs with a reasoning trace — full audit trail from scan to approval.
Most feed tools apply generic Google rules. Feed Medic learns your brand voice from rejection feedback.
Example from the demo catalog:
| Step | What happens |
|---|---|
| Before | PROD-003 is flagged for TITLE_TOO_LONG. Gemini proposes shortening the title but keeps promotional words (Promo, Soldes). |
| You reject | Reason: "Never use promotional words like Promo or Soldes in my titles." |
| Agent learns | learnBrandRule writes a rule to brand_rules (e.g. no promotional words in titles). |
| After | Re-diagnose PROD-012 and PROD-025 — titles are shortened and promo words are stripped, without you repeating the instruction. |
Run the proof yourself after a rejection:
npm run rediagnose:demo # PROD-012 + PROD-025Text-only feed audits miss a common Merchant Center failure mode: promotional overlays burned into product photos (-50% SOLDES, Livraison Gratuite, etc.).
The analyzeProductImage Genkit flow sends product images to Gemini Vision (same Gemini 3.5 Flash model via Vertex AI). Detected overlay text is stored on the fix, highlighted in the approval UI, and surfaced as PROMO_IN_IMAGE corrections — with clickable thumbnails in the dashboard.
npm run scan:images # 6 local images, ~2 minSource diagram (editable):
docs/architecture.mmd— export at mermaid.live if you want to tweak styling.
| Component | Region | Role |
|---|---|---|
| Cloud Scheduler | europe-west1 | Fires feed-medic-scan every 6 h → POST /api/scan with x-cron-secret. |
| Cloud Run | europe-west1 | Hosts the Next.js app (dashboard + API routes). Scale-to-zero between scans. |
| Firestore | europe-west1 | products, fixes, brand_rules, agent_logs — single source of truth. |
| Secret Manager | global (GCP) | Stores CRON_SECRET for the scan endpoint (prod rules deny client Firestore access). |
| Genkit flows | — | diagnoseProduct, analyzeProductImage, learnBrandRule — typed I/O, structured JSON. |
| Vertex AI / Gemini 3.5 Flash | global endpoint | All LLM + vision calls via ADC. Model runs on global (not regional — required for this model). |
Local vs production: one codebase, env-driven. Local dev uses the Firestore emulator + browser Firestore client. Production uses Cloud Firestore + REST API routes (/api/dashboard, approve/reject, delete rule).
| Requirement | How Feed Medic meets it |
|---|---|
| Gemini 3.5 Flash ✓ | Model gemini-3.5-flash on Vertex AI (agent/diagnose.ts, agent/analyze-image.ts, agent/learn-rule.ts). |
| Genkit ✓ | Flows defined with ai.defineFlow, Zod schemas, @genkit-ai/google-genai Vertex plugin. |
| Google Cloud ✓ | Cloud Run (hosting), Firestore (data), Cloud Scheduler (cron scan), Secret Manager (cron auth), Vertex AI (Gemini). |
App stack: Next.js 16 · React 19 · TypeScript · Firebase Admin + client SDK · Recharts · Tailwind CSS 4.
These steps are written for someone cloning the repo on a fresh machine. No guessing required.
| Tool | Version | Purpose |
|---|---|---|
| Node.js | 20+ | App and scripts |
| gcloud CLI | latest | Vertex AI auth (application-default login) |
| GCP project | any | Vertex AI API enabled; billing on. Set GOOGLE_CLOUD_PROJECT in .env.local. |
Forkers: set GOOGLE_CLOUD_PROJECT (and Cloud Run env vars) to your own GCP project id — no hardcoded project in source code.
git clone https://github.com/orlane-create/feed-medic.git
cd feed-medic
npm installcp .env.example .env.localDefault .env.local points the dashboard at the local Firestore emulator. No API keys needed.
gcloud auth application-default login
gcloud config set project feed-medic-505908 # must match GOOGLE_CLOUD_PROJECT in .env.localEnsure the Vertex AI API is enabled on your project.
npm run emulators:startKeep this running. Emulator UI: http://localhost:4010/firestore/demo-no-project/data
Choose one path:
Fast demo (~2 min, 15 Gemini calls) — populates the approval queue only:
npm run reseed:fixesFull catalog scan (~10 min, 50 products) — complete pipeline:
npm run scan:feedOptional — sync local product thumbnails for the dashboard:
npm run sync:local-imagesnpm run devOpen http://localhost:3000. You should see pending fixes, KPIs, and the agent log.
npm run demo:resetRefresh the browser. See DEMO.md for the full 10-minute demo script.
Assumes project feed-medic-505908, region europe-west1, and gcloud authenticated.
A. Create Firestore (once)
gcloud firestore databases create \
--location=europe-west1 \
--type=firestore-native \
--project=feed-medic-505908B. Deploy production security rules
npm run firestore:rules:deployC. Create cron secret (once)
openssl rand -hex 32 | gcloud secrets create CRON_SECRET \
--data-file=- \
--project=feed-medic-505908Grant the Cloud Run service account access to Firestore, Vertex AI, and Secret Manager (Console → IAM, or gcloud projects add-iam-policy-binding).
D. Deploy Cloud Run
gcloud run deploy feed-medic-dashboard \
--source . \
--region europe-west1 \
--project feed-medic-505908 \
--allow-unauthenticated \
--min-instances 0 \
--max-instances 2 \
--timeout 900 \
--memory 1Gi \
--cpu 1 \
--set-env-vars "NEXT_PUBLIC_USE_FIRESTORE_EMULATOR=false,NEXT_PUBLIC_FIREBASE_PROJECT_ID=feed-medic-505908,FIREBASE_PROJECT_ID=feed-medic-505908,GOOGLE_CLOUD_PROJECT=feed-medic-505908" \
--set-secrets "CRON_SECRET=CRON_SECRET:latest"E. Seed production from local emulator (optional)
With the emulator running and demo data loaded:
npm run seed:production -- --confirm feed-medic-505908F. Schedule autonomous scans (once)
CRON_VAL=$(gcloud secrets versions access latest --secret=CRON_SECRET --project=feed-medic-505908)
gcloud scheduler jobs create http feed-medic-scan \
--location=europe-west1 \
--project=feed-medic-505908 \
--schedule "0 */6 * * *" \
--uri "https://YOUR_CLOUD_RUN_URL/api/scan" \
--http-method POST \
--headers "x-cron-secret=${CRON_VAL}"Replace YOUR_CLOUD_RUN_URL with the URL printed by gcloud run deploy.
| Layer | Status | Details |
|---|---|---|
| Merchant Center connector | Simulated | Product catalog is data/products.json — same field shape as the Content API for Shopping, but no live MC account. |
| Gemini / Vertex AI | Real | Live model calls with structured Genkit outputs. |
| Firestore | Real (prod) / Emulator (local) | Same schema either way. |
| Cloud Run + Scheduler | Real | Public URL, cron-triggered scans. |
| Human approvals | Real | Approve/reject writes to Firestore and triggers rule learning. |
15 of 50 catalog products include a _test_expected_error field (e.g. "TITLE_TOO_LONG") so we can validate detection accuracy in demos. This field is never sent to Gemini — stripTestField() removes it before every AI call (agent/diagnose.ts, data/README.md). The remaining 35 products are compliant baselines to guard against false positives.
Designed for hackathon budgets and solo-operator scale-to-zero:
| Service | Typical monthly cost | Why it stays low |
|---|---|---|
| Cloud Run | ~€2–8 | min-instances=0; dashboard idle most of the day; scan bursts only every 6 h. |
| Firestore | ~€1–5 | Small demo dataset (50 products); reads via API polling in prod. |
| Vertex AI (Gemini 3.5 Flash) | ~€2–8 | ~50 products × 4 scans/day × structured short outputs; skip already-scanned products. |
| Cloud Scheduler | ~€0.10 | One job, 4 runs/day. |
| Secret Manager | ~€0.06 | One secret (CRON_SECRET). |
| Total estimate | ~€6–22 / month | No always-on GPU, no API-key free-tier roulette, no duplicate infra. |
Frugal design choices: resume-safe scans (skip scan_status=scanned), batch size 5 with pauses, human gate before any fix goes live, emulator for free local iteration, production seed script copies demo data without re-running Gemini.
feed-medic/
├── agent/ # Genkit flows (diagnose, vision, learn-rule)
├── app/ # Next.js pages + API routes
├── components/ # Dashboard UI
├── data/ # Simulated product catalog (products.json)
├── docs/ # Architecture diagram + Devpost draft
├── hooks/ # Dashboard data (emulator snapshots vs API poll)
├── lib/ # Firestore config, feed scan, fix actions, metrics
├── scripts/ # CLI: scan, seed, demo reset, production seed
├── DEMO.md # 10-minute demo script for video rehearsals
├── Dockerfile # Cloud Run standalone build
└── firebase.json # Emulator config (dev rules)
Hackathon submission — see repository owner for reuse terms.
Built solo by a growth marketer using AI-assisted coding, for the Google All Things Agentic Hackathon.
Build log: I'm a marketer, not a developer — I built an autonomous AI agent that learns my brand rules
