@@ -553,6 +553,109 @@ CSVなので、アプリ側は「気づいた」を貯めて渡すところま
553553
554554` /[type]/admin ` の「travel-log-dataへの還元用エクスポート」(spot_admin/admin)が、①` origin='manual' ` の公開スポット(スポットCSVへの収録候補。key列なし=収録時にtravel-log-data側の規則で振る)と、②削除の墓標(exclude.txtへの追記候補、GET ` /api/spot-deletions?type= ` )を1つのMarkdownにまとめてダウンロードする。** 還元作業はClaude Code等が読む前提のため、意図的に機械取り込み可能な形式にしていない** 。還元後にkeyを付けたCSVを再インポートすると、一致した行は内容が同一でも` origin='csv' ` にPATCHされ(インポートの同一判定が` origin !== 'csv' ` を「変更あり」とみなす)、次回のエクスポートから自動的に外れる。
555555
556+ ### 周辺を探す(` /api/spots/nearby ` ・` /api/spots/discover ` ・` AiSpotDiscoverySearchModal ` ・` AiSpotDiscoveryPanel ` )
557+
558+ 地図の右クリック/長押しメニューの「この周辺を探す」(spot_admin/admin専用)。
559+ 検索語・半径・件数(10/20/30)と** 探し方** を選ぶと、候補が地図に番号つきの印で並び、
560+ 右のパネルで選んでまとめて登録できる。
561+
562+ ** 探し方は2段。既定は速いほう。**
563+
564+ | 段 | 口 | かかる時間 | 得意なもの |
565+ | ---| ---| ---| ---|
566+ | ** 地図データ** (既定) | ` GET /api/spots/nearby ` | ** 240ms以下** (実測) | 正確な座標。AIの枠を使わない |
567+ | ** AIでweb検索** | ` POST /api/spots/discover ` | 17秒〜2分 | 地図に無い新しい店、一言の説明 |
568+
569+ ** この順にしたのは「近くの飲食店をちょっと見たい」が主な使い方だから。** AIだけの頃は
570+ 1回に3分近くかかって使い物にならなかった。** まず地図データで雑に集め、足りなければ
571+ AIで探し足す** (パネルの「もう一度探す」。同じ一覧に混ざり、行に出どころの印が付く)。
572+
573+ ** どちらも同じ` DiscoveryResult ` を返す** ので、地図の印もパネルも出どころを問わず
574+ 同じものを並べられる。違いは` source ` (` osm ` /` ai ` )だけで、画面はそれを印にする。
575+
576+ ** 出す条件は3つそろって初めて** (GET ` /api/spots/discover?type= ` が` enabled ` を返し、
577+ ` MapView ` はfalseならメニュー項目を出さない): ①接続先` CHIEZO_BASE_URL ` がある
578+ (どちらの段もchiezo経由。未設定なら機能そのものが出ない。IPはコードにもDBにも置かない)、
579+ ②種別の` ai_discovery_enabled ` がon(** 既定off** )、③呼んだ人がspot_admin/admin。
580+
581+ #### 1段目: 地図データ(` app/api/spots/nearby/route.ts ` )
582+
583+ chiezoのOSM辞典(` osm_japan ` )を引くだけ。** 探し方は2つ投げて混ぜる** (` lib/osmNearby.ts ` ):
584+
585+ - ** 全文検索** (` /search ` )は「ラーメン」のように** 名前に出る語** に強い
586+ - ** 種別での絞り込み** (` /filter?feature= ` )は「ランチ」のように** 名前に出ない語** に要る。
587+ 片方だけだと、どちらかの語で何も出ない。` feature ` はOSMの主タグ1つしか受け付けない
588+ (` cuisine=ramen ` で絞ろうとすると0件。実測)ので、語→主タグの表を持つ
589+ - 全文検索の応答には座標が載らないので、当たった題名で` doc ` を引き直す。** 並列に投げる**
590+
591+ ** 座標は地図データそのもの** なので` location_verified ` は常にtrue。ジャンルは` cuisine ` →
592+ 主タグの順で日本語に直す。** 説明文は持たない** (OSMに無いので` summary ` はnull)。
593+ ** 日本の種別だけ** (chiezoのOSM辞典が日本の抽出)。
594+
595+ ** 限界は測ってある** : 新宿1kmに飲食店884件あるのに、AIが挙げた有名店4件のうち
596+ OSMで引けたのは1件だけだった(タカノフルーツパーラーのような老舗も載っていない)。
597+ 営業時間が入っているのは新宿で23%・郡山で1%。** だからAIの段を残してある。**
598+
599+ #### 2段目: AIでweb検索(` app/api/spots/discover/route.ts ` )
600+
601+ ** その場で答えが欲しい機能なので、正確さより速さを採る。** 実測で効いたのは3つ:
602+
603+ | 変えたところ | 理由 |
604+ | ---| ---|
605+ | ** 検索の回数に上限を言い渡す** (` quick ` ) | ** これが一番効いた。41秒→17秒** (同じ相手・同じ件数)。エージェントとして動く相手は、放っておくと候補ごとに裏取りの検索を回す。「webの検索は多くても2回」と書き、URLも必須にしないだけで半分以下になり、** 遅い側のばらつきも収まる** 。裏取りが要るときは画面で「しっかり」を選ぶ |
606+ | ** プロンプトを短くし、頼む項目を最小限にする** | 項目を1つ増やすと候補の数だけ文が増える。` region ` (中心座標から引ける)・` name_kana ` ・` rank_reason ` は落とし、` rank ` は** その種別がランクを使うときだけ** 聞く |
607+ | ** モデルとeffortを選べるようにする** | 同じ問いで ** 32秒〜3分半** の開きが出た(実測・単発: antigravity+low 32秒で10件 / haiku+low 64秒 / codex 100秒 / sonnet+medium 215秒)。未選択なら` low ` (` DEFAULT_DISCOVERY_EFFORT ` 。** 相手がeffortを持つときだけ送る** —— 持たない相手=codexに送るとchiezoが400で断る) |
608+ | ** 位置の確認を候補ごとに直列でNominatimに聞くのをやめる** | 1秒1回の間隔制限がそのまま待ちになり、10件で10秒以上。しかもchiezoに無い店(=新店)は同じOSM由来のNominatimにも無いことが多く、待った割に当たらなかった |
609+
610+ ** 相手・モデル・深さは探索の画面で選ぶ** (「AIの設定」。AIを選んだときだけ出る折り畳み)。
611+ ** 管理画面に置かない** のは、その場の目的で軽くも重くもしたい設定だから —— 全員ぶんを
612+ 1つに決めさせると使う人が選び直せない。** 選択はサーバーに保存せず、その端末の
613+ localStorageに覚える** (** DBに列を持たせない** 判断でもある —— 種別ごとのon/offと違い、
614+ これは人ごと・回ごとに変わる値)。選ばなければ` CHIEZO_AI_BACKEND ` か既定に落ちる。
615+ 選択肢はGET ` /api/spots/discover?type= ` が配る(** web検索を持つ相手だけ** )。POSTは
616+ ` resolveDiscoveryChoice ` で確かめてから投げるが、** 相手の一覧が取れなければ素通しする**
617+ (確かめられないことを理由に探索そのものを止めない)。
618+
619+ ** AIの答えは信用せず確かめてから出す。** 位置はchiezoのOSM辞典を候補ぶん並列に引き、
620+ 当たれば「位置確認済み」、当たらなければAIの座標のまま「未確認」(新店はOSMに無いのが
621+ 普通なので珍しくない)。重複は名前一致か50m以内で「登録済み」。** 口コミ本文・点数・
622+ ランキング順位の転記はプロンプトで禁じ** 、要約はAI自身の言葉に限る。
623+ 地域はAIに聞かず中心座標から引いたものを全候補に使う。
624+
625+ ** 待つ上限は180秒** (` CHIEZO_AI_TIMEOUT_SECONDS ` )。** 待たされた末に失敗するより、
626+ 早く諦めて条件を変えて出し直せるほうがよい** 。失敗の文言には** 必ず相手の名前を入れる**
627+ —— どの相手が失敗したのか分からないと切り替えの判断ができない。
628+
629+ ** 投げた本文と返ってきた本文は結果に載せ、画面から見られるようにしてある**
630+ (` DiscoveryExchange ` ・` AiExchangeDialog ` 。パネルの「AIとのやり取りを見る」)。
631+ ** 手を入れずに出す** —— 整形すると直したいプロンプトの実物と食い違う。
632+ 遅い・少ない・的外れの原因は、プロンプトなのか相手なのか件数なのか、ここを見ないと
633+ 切り分けられない。** JSONとして読み取れなかったときはエラー文にも本文の頭を載せる**
634+ (そのときこそ何が返ったか見たい)。
635+
636+ ** 相手によっては同時に1本しか動かない。** chiezoのブリッジは認証情報がホーム配下で回る
637+ CLI(codex / antigravity)の起動を直列にするので、2人が同時に探すと後の人は待つ
638+ (実測: 3本同時で1本目238秒、残りは600秒待っても返らなかった。1本ずつなら32秒)。
639+
640+ #### 画面と登録
641+
642+ 候補は番号つきの印(AIは紫、位置未確認は破線の縁、登録済みは灰色、チェックを外したものは薄く)で、
643+ ** 行を押すとそのピンへ寄り、ピンを押すとその行が目立つ** 。パネルでできるのは、
644+ チェック・×(候補から外す)・行ごとのランク・「もう一度探す」(** 同じ一覧に足す** 。
645+ 正規化した名前が同じ候補は二度入れない)・「追加」・「終了」。
646+
647+ ** 探索の結果は保存しない。** 追加したものだけが通常のPOST ` /api/spots ` (` origin='manual' ` )で
648+ 入る。説明文は` buildDiscoveredDescription ` が組み、** 出どころを必ず添える**
649+ (地図データなら「OpenStreetMapから取得(日付)」、AIなら「AIがwebから収集(日付)、参照: URL」)
650+ —— 手で書いた説明と見分けるため。ジャンルはカテゴリに入れる。状態・シリーズは全行共通。
651+ 公開で追加した行は` spotCache.applySpotChange ` で次のダウンロードを待たずに地図へ載せる。
652+
653+ ** OSMのライセンス(ODbL)について。** この機能が引くのは** 自分のインスタンスで使うため** で、
654+ 配布ではない(地図表示には` © OpenStreetMap contributors ` を出している)。ただし
655+ ` origin='manual' ` は還元用エクスポートの抽出条件なので、** OSM由来の名前・座標を
656+ travel-log-data(MIT側)へ持ち込まないよう、還元するときは出どころを見て外すこと**
657+ (「外部データソース…注意」の「ヒントに留める」と同じ線)。
658+
556659### 公開スポットの全削除・スポット種別の削除
557660
558661管理画面の`/[type]/admin`にはadmin専用の「公開スポットの全削除」(`app/api/spots/purge/route.ts`)と「スポット種別の削除」(`DELETE /api/spot-types/[id]`、同ファイルのPATCHと同居)がある。前者は`spot_types`の行自体は消さず、対象種別の公開(published)スポットのみを全件削除する(承認待ち・却下・非公開のスポットは残す。CSVで作り直す対象=CSVインポートが取り込む公開スポットに限定するため)。削除される公開スポットに紐づく`visits`/`visit_plans`/`reviews`(FKの`on delete cascade`)・写真ファイルと、対象種別のルート(`spot_routes`。status問わず丸ごと)も一括で消す。後者はstatus問わず対象種別の全スポットを削除(紐づくデータ・写真ファイルの扱いは前者と同じ)した上で`spot_types`の行自体も削除する(「別のスポット種別の管理」一覧には現在表示中の種別も含めて全種別を出すが、現在表示中の種別だけはリンク化・削除ボタンをUI側で出さないことで自分が今開いている種別を誤って消せないようにしている)。後者は`public_visible`がtrue(一般公開中)の種別、または対象種別が`app_settings.active_spot_type_id`(ルート`/`リダイレクトのフォールバック既定)の場合はAPIレベルで拒否する(既定の種別は常にpublic_visible=trueであるため後者は実質前者に含まれるが、防御的に両方チェックしている)。どちらもCSVでデータを作り直す前提の機能で、spot_adminには許可していない(ユーザー管理と同様、他ユーザーのデータを巻き込むため)。ルート`/`アクセス時に開く既定の種別(最後に開いていた種別のCookieが無い・開けないときのフォールバック)の変更は、この一括削除等の管理系操作とは別の独立したセレクトボックス(`app_settings.active_spot_type_id`を更新)として`/[type]/admin`に置いている。
0 commit comments