Personal · Web Service · 2024–Now

ヤスイミセ

近所のスーパーの価格を一画面で比較する。

複数のスーパーの商品価格を比較し、「どこで買えば一番安いか」を直感的に判断できる価格比較サービス。企画、画面設計、API、DB、インフラ、チラシ解析まで一通り自分で実装しています。

"国産 たまご 10個"4 stores
国産 たまご 10個price · ¥
オオゼキ350m¥228
業務スーパー620m¥258
ライフ1.1km¥288
サミット900m¥308
↳ Gemini OCR · 解析中updated 14:32
Stack
Next.jsNestJSTypeScriptPostgreSQLGCPCloud RunVertex AIGeminiDockerOpenAPI
Role / Scope
  • 企画
  • MVP スコープ策定
  • UI/UX 設計
  • DB / API 設計
  • Frontend / Backend
  • GCP インフラ
  • CI/CD
  • 監視・エラートラッキング
  • Gemini / Vertex AI による商品データ解析・チラシ OCR
  • OpenAPI / orval による型安全な API 連携
Challenge

スーパーごとにチラシのレイアウトが違う。商品名の表記も「卵」「たまご」「玉子」みたいに揺れる。人手で正規化マスタを直し続ける運用は早晩破綻するのが見えていて、ここを自動で吸収できる仕組みを最初から組まないとサービスとして成立しないと判断した。

Design Decisions
01

商品マスタ正規化

ベクター類似度だけだと精度が頭打ちになったので、ルール(数量・容量・ブランド名)を上に重ねたハイブリッドにした。「100% 正しくマージする」は早々に諦めて、誤マージは UI から訂正できる導線を残す方針に倒した。

02

OCR パイプライン

チラシ画像をそのまま Gemini に投げて、商品名・価格・有効期限の構造化 JSON で受ける。失敗したものはキューに戻して再試行。「OCR の後段で人手チェック」みたいな運用は最初から作らないと決めた。

03

最小スタック

Next.js + NestJS + Postgres + Cloud Run。最初から「スケールアウトに強い構成」を組むより、一人で運用していて頭の中に全部入る構成を優先した。Cloud Run のオートスケールに任せれば当面は足りる。

04

型安全な連携

API は OpenAPI で書いて、orval でフロントの SDK を自動生成。手書きの fetch は 0 にする。API の形を変えるとフロントが先に壊れてくれるので、変更が伝わるのが早い。

Deep dive · System Design

アーキテクチャ・Agent・運用バッチ。

企画から運用までの設計判断をいくつか抜粋しました。 システム構成、Agent の段階設計、チラシ解析、店舗収集バッチ、そして全体ロードマップ。

§ ASystem Architecture

Cloud Run · Vertex AI · Firebase の最小スタック。

Service + Jobs を Cloud Run に寄せ、認証は FE/BE で責任を分け、AI は Vertex AI で揃える。1人で運用しきれる構成に絞っています。

external
ユーザー
ブラウザ
cloud run
Next.js 16
App Router · SSR
cloud run
NestJS 10
REST API · OpenAPI
auth
Firebase Auth
Client SDK + Admin SDK
ci/cd
GitHub Actions
CI/CD · OIDC
registry
Artifact Registry
Container images
vertex ai
Gemini 3.0 Flash
チラシ画像解析
vertex ai
Claude Sonnet / Opus
Lv3-4 Agent · 将来
trigger
Cloud Scheduler
日次 / 月次
batch
Cloud Run Jobs
scrape · store-collect
db
Cloud SQL
PostgreSQL 16 · Unix socket
config
Secret Manager
DB · Firebase · API keys
external
Google Places API
店舗マスタ収集
external
チラシ集約サイト
チラシ画像取得
external
チェーン公式サイト
チラシ取得 (chain別)
observability
Sentry · GA
観測 · エラー · 解析
Service + Jobs
Cloud Run を Service (常駐 frontend/backend) と Jobs (scrape/store-collect) の 2 系統で運用。
非対称な認証設計
ログイン: Firebase Client (FE)。検証: Admin SDK (BE)。fe/be で責任を分割。
secrets を env で配る
Secret Manager → Cloud Run の env に自動注入。コードからは普通の `process.env` で読む。
§ BAgent Architecture

Lv1 → Lv4 への段階設計と、MCP に寄せた基盤。

既存のスクレイパー (Lv2) を壊さず、Lv3-4 で必要なときに LLM を被せる。クライアント (バッチ / Desktop / 管理画面 Chat) は MCP を共有する設計に揃えています。

Lv1
コードベタ書き
従来の各チェーン個別 scraper
legacy
Lv2
Config 駆動
chain-profiles.ts + 汎用 resolver · 管理画面で更新可
now · production
Lv3
Config + LLM フォールバック
未知チェーン / HTML 変更時のみ Agent 起動
next
Lv4
フル Agent
管理者チャット UI から自然言語で操作
design ready
MCP-centric (Lv3-4)
バッチ Agent
DiscoveryAgent · RepairAgent
Claude Desktop
開発用クライアント
管理画面 Chat UI
Vercel AI SDK
hub
MCP Server
backend/src/mcp/server.ts
tools · existing Lv2 wrapped
chirashi.fetch
chirashi.analyze
stores.search
stores.apply_change · HITL
prices.upsert
Gemini 3.0 Flash
画像解析
Claude Sonnet 4.6
resolve · 管理者 Chat
Claude Opus 4.7
難ケース · Extended Thinking
obs
Langfuse
LLM 呼び出しトレース
Lv2 を変えない
既存 scraper はそのまま MCP ツールにラップ。Agent は薄く載せる。
書き込み系は HITL
stores.apply_change のような DB 書き換えは自動承認しない。
クライアントは選べる
バッチ · Desktop · 管理画面 Chat を共通の MCP で結ぶ。
§ CScraping Pipeline

チラシ画像から Gemini で構造化、DB に upsert。

Scheduler → Job → Fetch → Gemini 解析 → Upsert の単線パイプライン。失敗時は dummy にフォールバックし、同一画像は chirashi_analyses でキャッシュ。

01
Cloud Scheduler / 管理画面
日次 03:00 JST または手動起動
02
Cloud Run Job · scrape
yasui-mise-batch-scrape
03
ChainFetcher × N
leaflet 画像を Base64 化 · 店舗別
04
GeminiAnalyzer → Gemini 3.0 Flash
ScrapedPrice 配列 (name · origin · category · unit · price)
05
DB Upsert
generic_products → products → prices
06
PostgreSQL
source='scrape' で由来を保持
Fallback
// 失敗時の挙動
fetch 失敗 → dummy
vertex 未認証 → dummy
専用リトライ Q は未実装
Rate limit
画像 max 3 / 店舗
request gap 5s
Cache
chirashi_analyses
image_url 単位で Gemini 出力を再利用
§ DStore Collection Batch

discover → enrich → resolve → persist → report。

店舗マスタを自動収集する 5 フェーズの冪等バッチ。confidence で resolved / review / below に分け、書き込みは必ず人間の --apply 経由。

phase
discover
格子状検索セルで Places Nearby を叩き、新規 place を raw_places に upsert。
raw_places · last_run_id
phase
enrich
Places Details で住所・営業状態・電話を補強。30日 TTL で再 enrich。
raw_places · enriched_at
phase
resolve
chain-profiles に namePatterns 照合 → strategy 実行 → Jaro-Winkler + 距離で confidence。
resolved / review / below
phase
persist
raw_places ↔ stores 照合し 7 種の action に分類。--apply 時のみ TX で書き込み。
NEW · UPDATE · CLOSED · REVIEW · SKIP×3
phase
report
CLI / Scheduler は Slack 通知、UI 起動はコンソールのみ (suppressSlack)。
summary
③ resolve · confidence ladder
≥ 0.9
resolved
stores へ反映候補
0.7 – 0.9
review
人間レビュー待ち
< 0.7
below
破棄 / 次 run 再評価
冪等
フェーズ間で raw_places を中間成果物として持ち、部分再実行を許容。
HITL
cron は dry-run のみ。--apply は人間が叩く。
保護
manual_override=true は一切上書きしない。閉店は status='closed' で論理削除。
§ ERoadmap

MVP 10 週 + 公開後 Phase B-E。

MVP は基盤 → 認証 → 推移 → 管理 → デプロイの 5 Phase に分割し、公開後は Push / ネイティブ化 / AI / B2B API の順で拡張する計画。

1
基盤 + 基本機能
W1-3
2
認証 + ユーザー機能
W4-5
3
推移グラフ + 品質
W6-7
4
管理機能
W8-9
5
CI/CD + 監視
W10
B
Web Push / LINE
+1-3 mo
C
ネイティブ化
+6-12 mo
D
買物最適化 AI
+12+ mo
E
B2B データ API
+12-18 mo
done · MVP 1-4active · 仕上げ中plan · 公開後
Next project
SpecPilot
Contact