“通報アプリ × 軽いSNS × 地図 × ポイント × AI(AIは黒子)” 簡易実装イメージ

“通報アプリ × 軽いSNS × 地図 × ポイント × AI(AIは黒子)” 簡易実装イメージ(アイデアメモ パート2)
主役はデータ収集と利活用。AIは整理・匿名化・推計の黒子。ぼかしは顔/ナンバーではなく車体まるごと。生データは管理団体のみ。
本稿は「いますぐ作り始められる」ことを最優先に、Web+Webアプリ前提での最小構成、DB(マスタ/業務/監査)、API、非同期タスク、重い演算を切り離した“AI API/ワーカー”の形を一気に示します。難しいところは後回し、でも穴は開けない。直球でいきます。
1. 全体観(最小でも回る構成)
フロント
API
DB/PostGIS
S3(anon/raw)
Queue
AIワーカー
定点カメラ
[利用者PWA/ブラウザ] ─── HTTPS ───> [API Gateway/ALB → Web API(FastAPI)]
       │                                 │
       │(事前署名URLで直UP)             │SQL(PostGIS)
       ├──────────────> [S3 anon/]      ├────> [Aurora PG + PostGIS]
       │                                 │
       │                                 └────> [SQS] → [AIワーカー群(内部API)]
       │                                              ├─ anonymize_vehicle(車体ぼかし)
       │                                              ├─ classify(カテゴリ推定/NG検知)
       │                                              ├─ dedupe(重複クラスタ)
       │                                              └─ traffic_aggregate(利用率集計)
[定点カメラ/エッジ] ──(匿名化/カウント済みメタ)──> [API /devices/{id}/events]
  
ポイント:アップロードは匿名化側 S3 anon/ をデフォ。S3 raw/ は原則使わず、管理団体の明示ワークフロー時のみ。重い処理はSQS/ワーカーへ。
2. 初期に用意するクラウド要素(MVP)
- 静的フロント: S3 + CDN(PWA、Map描画、オフライン対応)
- Web API: FastAPI(Fargate か Functions/Lambda)
- DB: Aurora PostgreSQL + PostGIS
- ストレージ: S3(anon/=ぼかし済み公開用、raw/=管理団体限定)
- キュー: SQS(ingest/anonymize/classify/dedupe/traffic_aggregate)
- 認証: Cognito/OIDC(LINEログイン併用可)
- 監視: CloudWatch + APM(例:OpenTelemetry)
3. データモデル(最小セット)
マスタ
- organizations:自治体/団体
- categories:通報カテゴリ(10個程度に絞る)
- areas:担当エリア(MultiPolygon)
- road_links:道路リンク(LineString)
業務
- reports:通報(Point, status, attributes)
- report_media:画像/動画(- anon/,- raw/鍵)
- report_status_history:進捗履歴
- devices:定点カメラ/エッジ
- camera_events:カウント結果(車/人)
- traffic_agg_hourly:道路利用率(リンク×時刻)
-- 位置はPostGIS。公開時は丸める(例:50m格子)
CREATE TABLE core.reports(
  id UUID PRIMARY KEY,
  org_id UUID NOT NULL,
  user_id UUID NULL,
  category_id INT NOT NULL,
  title TEXT, body TEXT,
  status TEXT CHECK(status IN ('received','triaged','in_progress','resolved','rejected')) DEFAULT 'received',
  location geometry(Point,4326),
  location_precision_m INT DEFAULT 50,
  attributes JSONB DEFAULT '{}'::jsonb,
  dedupe_group_id UUID,
  created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
);
CREATE INDEX ON core.reports USING GIST(location);
4. API(公開REST)
| メソッド | パス | 概要 | 
|---|---|---|
| GET | /api/v1/master/categories | カテゴリ一覧 | 
| POST | /api/v1/reports | 通報作成(メタ登録→S3事前署名返却) | 
| POST | /api/v1/media/presign | S3事前署名の発行 | 
| POST | /api/v1/media/confirm | アップ完了通知→anonymize_vehicle投入 | 
| GET | /api/v1/reports?bbox&status | 地図用リスト(クラスタリング済み) | 
| PATCH | /api/v1/reports/{id}/status | 進捗変更(operator以上) | 
| POST | /api/v1/devices/{id}/events | 定点の集計メタ投入(匿名化済み計数) | 
| GET | /api/v1/traffic/links/{id}/hourly | 道路利用率(時系列) | 
例:通報登録→アップロード
// 1) メタ登録
POST /api/v1/reports
{
  "category_code": "road_damage",
  "title": "舗装の穴",
  "body": "夜間に危ない",
  "event_time": "2025-09-14T09:30:00+09:00",
  "location": {"lat": 35.68, "lng": 139.76, "precision_m": 50},
  "media": [{"kind":"image","presign":true}]
}
// 2) レスポンス(S3 anon/ への事前署名)
{
  "report_id": "d1c7-...",
  "uploads": [
    {"media_id":"m-1","url":"https://s3/anon/...","fields":{"key":"anon/..","policy":".."}}
  ]
}
// 3) クライアントがS3へ直接PUTした後
POST /api/v1/media/confirm
{ "media_id": "m-1", "sha256":"abc...", "raw_requested": false }
5. “難しい演算部”はAPI/ワーカーに切り離す
原則: フロントとWeb APIは薄く。重い処理(画像処理・類似検索・時系列集計)はキュー経由でワーカーが実施。Web APIは結果だけを見る。
(A) 内部API(サービス間)
- POST /_internal/anonymize/vehicle:車体まるごとぼかし
- POST /_internal/classify:カテゴリ推定・NG判定
- POST /_internal/dedupe:重複クラスタ更新
- POST /_internal/traffic/aggregate:道路利用率集計
VPC内/認証必須。外部からアクセス不可。
(B) ワーカー(擬似コード/Python)
@app.task(name="anonymize_vehicle")
def anonymize_vehicle(media_id: str):
    media = db.get_media(media_id)
    img = load_from_s3(media.s3_key)       # ぼかしは車体全体
    boxes = detect_vehicles(img)           # car/bus/truck/bike...
    img2 = mosaic_outside_safe_margin(img, boxes)
    save_to_s3(img2, media.s3_key)         # anon/ に上書き保存
    enqueue("classify", {"report_id": media.report_id})
    
重複クラスタ(dedupe)イメージ
def recompute_cluster(report_id):
  r = db.reports[report_id]
  candidates = search_nearby(
     point=r.location, radius_m=120,
     time_window="±24h",
     text_embed=r.attributes['text_embed'])
  cluster_id = union_find_by_similarity(candidates + [r])
  db.update(report_id, dedupe_group_id=cluster_id)
道路利用率(定点カメラ→リンク集計)
POST /api/v1/devices/{id}/events
{
  "captured_at": "2025-09-14T00:00:00Z",
  "counters": {"vehicle": 86, "pedestrian": 240},
  "link_id": 10234
}
# 集計ジョブ(hourly)
INSERT INTO traffic_agg_hourly(link_id,hour,vehicle_cnt,ped_cnt)
VALUES(...) ON CONFLICT (...) DO UPDATE ...
6. フロント(PWA/Map)実装イメージ
画面
- ホーム:地図+進捗フィルタ(クラスタ表示)
- 新規通報:撮る→位置→送る(3タップ)
- 詳細:写真(ぼかし済み)、進捗、類似報告
- マイページ:ポイント、履歴
- 運用:重複束ね、優先度キュー、道路利用率グラフ
コード(超要点)
// 位置丸めはサーバ側。クライアントは細かいことしない。
async function createReport(meta){
  const r = await fetch('/api/v1/reports',{method:'POST',
    headers:{'Content-Type':'application/json'},
    body: JSON.stringify(meta)});
  const {uploads} = await r.json();
  // presigned URLに直PUT
  await fetch(uploads[0].url,{method:'PUT',body: selectedFile});
  await fetch('/api/v1/media/confirm',{method:'POST',
    headers:{'Content-Type':'application/json'},
    body: JSON.stringify({media_id: uploads[0].media_id, sha256: fileHash})});
}
    
地図は MapLibre GL 等。アクセシビリティのためボタンは大きく、音声入力も許容。オフライン時はService Workerで下書き保存→復帰時に送信。
7. セキュリティ/プライバシ運用の肝
- ぼかし原則: 顔/ナンバー単体ではなく車体まるごと。誤検知を避ける。
- rawの管理: 原則使わない。必要時のみ管理団体の承認付きで保管/閲覧。アクセスは監査ログ必須。
- 公開データ: 座標は丸め(例:50m)、期間限定、再識別チェック。
- RBAC: resident / moderator / operator / admin を分離。
- レート制御/WAF: 荒らし対策はフロントではなくゲートで止める。
8. 運用KPI(MVPで見る数字)
- 重複統合率(dedupe成功率)
- 匿名化成功率(車体ぼかし漏れ0を目標)
- 受付→現地確認リードタイム
- 道路リンク×時間のカバレッジ率
- 初回→翌月継続率(ポイント依存度は低く)
9. ディレクトリと設定例
repo/ ├─ frontend/ # Next.js PWA ├─ api/ # FastAPI (REST) │ ├─ routes/ # reports, media, devices... │ ├─ services/ # db, s3, presign, auth │ └─ schemas/ # pydantic ├─ workers/ # AIワーカー(anonymize/classify/dedupe/aggregate) │ └─ tasks/ ├─ infra/ # IaC(Terraform) / pipelines └─ docs/
# .env(例:API) DATABASE_URL=postgresql+psycopg2://... S3_BUCKET=city-app-bucket ANON_PREFIX=anon/ RAW_PREFIX=raw/ QUEUE_URL_ANONYMIZE=... JWT_AUDIENCE=...
10. フェーズ2で足すもの(必要になったら)
- GraphQL(AppSync等)でリアルタイム更新
- OpenSearchで全文/類似検索
- Step Functionsでワークフローの可視化(失敗時の再試行/DLQ)
- ダッシュボード(QuickSight/Looker Studio)
- Open311互換API公開、オープンデータ自動エクスポート
11. 用語説明集(自治体担当者向け・横文字を噛み砕く)
技術用語を「要はどういうこと?」に言い換えました。会議で出てきたら、この表をそのまま使ってOKです。
| 用語 | 意味(かんたん) | 役割・たとえ | 
|---|---|---|
| Webアプリ / PWA | ブラウザで動くアプリ。ホーム画面に入れられ、電波が弱くても一部動く。 | 「インストールいらずのアプリ」。 | 
| CDN | 全国にある中継所から近場で配る仕組み。 | 配達拠点が多い宅配便。 | 
| S3 | 画像や動画を置く大きな倉庫。 | 「anon = ぼかし済み棚」「raw = 生データ棚」。 | 
| 事前署名URL(Presigned URL) | 一時的に倉庫へ直接アップできる合鍵。 | 期限つきの搬入口パス。 | 
| API | アプリ同士の受け口(やり取りの決まり)。 | 役所の「窓口の様式」。 | 
| FastAPI / NestJS | APIを作るための道具。 | 窓口の仕切りや帳票を作るツール。 | 
| ALB / API Gateway | 外からの通信の玄関。 | 正面ロビーの受付。 | 
| ECS Fargate | サーバ管理なしでアプリを動かす台。 | 「レンタカー、燃料補給も自動」。 | 
| Lambda | 呼ばれたときだけ動く小プログラム。 | 必要な時だけ呼ぶタクシー。 | 
| Aurora PostgreSQL | 信頼性の高いデータベース。 | 台帳の本丸。 | 
| PostGIS | DBに地図の頭脳を足す。 | 点や道路線を扱える地図機能。 | 
| OpenSearch | 全文検索・似ている文章探し。 | 図書館の司書+レコメンド。 | 
| Redis | 超速い一時置き場。 | 受付の手元メモ。 | 
| SQS(キュー) | 順番待ちの箱。重い処理はここに並べる。 | 整理券発券機。 | 
| Step Functions | 処理の流れ図をそのまま機械に実行させる。 | 業務フローの標準化。 | 
| AppSync / WebSocket | 画面を自動更新する仕組み。 | 電光掲示板が勝手に更新。 | 
| Cognito / OIDC | ログインの仕組み。LINEログインも接続可。 | 入館証の発行・確認。 | 
| RBAC | 役割ごとに権限を分ける。 | 住民/職員/管理者で見える範囲が違う。 | 
| IAM | 誰が何をして良いかの許可証。 | 庁内の権限台帳。 | 
| KMS / Secrets Manager | 暗号鍵とパスワードの金庫。 | 鍵付きロッカー。 | 
| VPC / AZ | 自分専用ネットワーク/データセンターの区画。 | 専用フロア/別館。 | 
| VPC Endpoint | 倉庫(S3)へ裏口直結で安全に接続。 | 荷捌き場の専用通路。 | 
| CloudWatch / X-Ray / OpenTelemetry | 動作状況の見える化。 | 監視カメラ+動線の可視化。 | 
| MapLibre GL | 地図を画面に描く道具。 | 地図描画エンジン。 | 
| タイル / クラスタリング | 地図を小分けに配る/近いピンをまとめて表示。 | 分割配布/人数カウントの丸め表示。 | 
| bbox | 今見えている地図の四角い範囲。 | 地図の窓枠。 | 
| JSON/JSONB | 柔らかい形式の箱に情報を入れる。 | 可変の追記事項メモ。 | 
| GiST / GIN(索引) | 地理/文章検索を速くする仕掛け。 | しおり・目次。 | 
| geometry(Point/LineString) | 点/線として位置情報を保存。 | 地点/道路線。 | 
| 位置丸め | 50mなど大まかな位置だけ公開。 | 自宅の真上にピンを立てない。 | 
| 匿名化 | 個人が特定されないよう隠す。 | 画角・解像度・位置の配慮。 | 
| 車体まるごとぼかし | 顔/ナンバー単体より安全側で隠す。 | 誤検知対策・再識別防止。 | 
| エッジ処理 | カメラ側で先に匿名化やカウントして送る。 | 現場で下ごしらえ。 | 
| データレイク / Glue / Athena / QuickSight | 倉庫に貯めたデータをSQLで分析・可視化。 | 統計室と壁一面のグラフ。 | 
| Open311 | 通報の標準仕様(カテゴリ/進捗)。 | 他システムと連携しやすい型。 | 
| Webhook | 外部システムへ自動でお知らせを送る。 | 連絡票の自動FAX。 | 
| DLQ | 失敗タスクの保管箱。 | 要再処理トレイ。 | 
| IQR | 外れ値(おかしな数)を見つける指標。 | 異常検知の物差し。 | 
| A/Bテスト | 2案を同時に試して良い方を選ぶ。 | 案内文AとBで比較。 | 
| Embedding(埋め込み) | 文章や画像を数列に変えて似ている度合いを見る。 | 似てる度=コサイン類似度。 | 
| 重複検知 / クラスタ | 似た報告をまとめる。 | 同じ穴の報告を1束に。 | 
| マップマッチング / Link ID | カウントを道路の区間番号に結びつける。 | 集計のひと単位。 | 
「この言葉が出たらこう理解」でOK(超要約)
- PWA=ブラウザで動くアプリ。
- API=システム間の窓口。
- PostGIS=地図に強いDB。
- キュー=順番待ち箱(重い処理を後で)。
- 匿名化/ぼかし=個人に結びつく情報を隠す。原則は車体まるごと。
- データレイク=とりあえず貯めて後から分析。
- Open311=通報の共通ルール。
まとめ:横文字は多いですが、要は「データを安全に集め、見える化し、根拠を持って手を打つ」ための道具立てです。
©株式会社ビー・ナレッジ・デザイン

