Re:作成の順番

参考
api/
└─ app/
   ├─ main.py                           # FastAPI起動。各routerをマウント
   ├─ config.py                         # 環境変数読み込み(DB/Chroma等)
   ├─ db.py                             # DB接続(SQLAlchemy/psycopg2等)
   ├─ routers/
   │  ├─ extract.py                     # ① 抽出: /extract/view_common, /extract/field
   │  ├─ translate.py                   # ② 翻訳: /translate/run, /translate(GET)
   │  ├─ writeback.py                   # ③ 書き戻し(任意): /writeback/view_common, /writeback/field
   │  ├─ package.py                     # ④ パッケージ: /chroma/package, /chroma/docs(GET)
   │  ├─ chroma.py                      # ⑤ Upsert: /chroma/upsert
   │  ├─ status.py                      # ダッシュボード: /status/summary, /samples/trace
   │  └─ __init__.py
   ├─ schemas/
   │  ├─ extract_view_common.py         # 入出力DTO(action_xmlids/targets等)
   │  ├─ extract_field.py               # 入出力DTO(models/fields等)
   │  ├─ translate_run.py               # 入出力DTO(limit/entities等)
   │  ├─ writeback_view_common.py       # 入出力DTO(mode等)
   │  ├─ writeback_field.py             # 入出力DTO
   │  ├─ chroma_package.py              # 入出力DTO(collections/lang等)
   │  ├─ chroma_upsert.py               # 入出力DTO(collections/limit等)
   │  ├─ status.py                      # サマリ応答DTO
   │  └─ common.py                      # 共通型: NaturalKey, Entity, Lang, Summary
   ├─ services/
   │  ├─ extract.py                     # 抽出オーケストレーション(view_common/fieldを切替)
   │  ├─ translate.py                   # 翻訳ワーカー(pending→translated)
   │  ├─ writeback.py                   # 英語書き戻し(任意)
   │  ├─ package.py                     # translated→ready_for_chroma + chroma_doc作成
   │  ├─ chroma_client.py               # Chroma接続(upsert/query)
   │  └─ natural_text.py                # doc_text生成(日本語テンプレ整形)
   ├─ repos/
   │  ├─ portal_translate_repo.py       # portal_translate CRUD/検索
   │  ├─ portal_chroma_doc_repo.py      # portal_chroma_doc CRUD/検索
   │  ├─ portal_view_common_repo.py     # portal_view_common 読取
   │  ├─ portal_field_repo.py           # portal_fields 読取
   │  └─ portal_model_repo.py           # portal_model 読取(model_table等補完)
   ├─ utils/
   │  ├─ natural_key.py                 # natural_key生成/検証(正規化・lower/trim)
   │  ├─ lang_detect.py                 # 日本語判定(ぁ-ん/ァ-ン/一-龥)
   │  ├─ html_strip.py                  # HTML除去(help_*_html→text)
   │  ├─ text_hash.py                   # source_hash計算(差分判定)
   │  └─ time.py                        # ISO時刻などユーティリティ
   ├─ openapi/
   │  └─ openapi.yaml                   # 手書き/自動出力の固定化(必要なら)
   └─ sql/
      ├─ 000_extensions.sql             # pgcrypto/tg_touch等の共通関数
      ├─ 010_portal_core.sql            # portal_model/fields/view_common/view…(既存)
      ├─ 020_portal_translate.sql       # portal_translate DDL
      └─ 030_portal_chroma_doc.sql      # portal_chroma_doc DDL

routers(HTTP I/F)

  • extract.py
    • /extract/view_common:action_xmlid群→portal_translateにUPSERT(ai_purpose/helpのみ、JAあり&EN未入力を判定)
    • /extract/field:model/field指定→portal_translateにUPSERT(JAラベル/用途)
  • translate.py
    • /translate/runstatus=pending を翻訳し translated_* を保存→translated
    • GET /translate:一覧・検索
  • writeback.py(任意)
    • /writeback/view_commonai_purpose_i18n.en_US / help_en_text を更新
    • /writeback/fieldlabel_i18n.en_US を更新(方針次第)
  • package.py
    • /chroma/packagetranslated日本語doc+厳密メタに整形→portal_chroma_docへUPSERT(queued)+ready_for_chroma
    • GET /chroma/docs:ステージング一覧
  • chroma.py
    • /chroma/upsertportal_chroma_doc.status='queued' をChromaへ投入→upserted
  • status.py
    • /status/summary:件数サマリ
    • /samples/trace:natural_key単位の通し状況

services(ドメインロジック)

  • extract.py:テーブル読み→日本語判定→natural_key生成→translate UPSERT(mode: upsert/skip/if_changed)
  • translate.py:翻訳方向/数の制御、失敗リトライ、source_hashで二度訳抑止
  • writeback.py:英語列の更新(ポリシー:overwrite/skip_if_exists)
  • package.py:テンプレ(natural_text.py)で日本語doc生成、必須メタ収集、portal_chroma_doc UPSERT
  • chroma_client.py:コレクション作成/Upsert/ヘルスチェック

repos(DBアクセス)

  • portal_translate_repo.pypending/translated の取得・UPSERT
  • portal_chroma_doc_repo.pyqueued の取得・UPSERT
  • そのほか view_common/field/model リード用

utils(共通関数)

  • natural_key.pyview_common::<xmlid>::help 等の規約化
  • lang_detect.py[ぁ-んァ-ン一-龥] 判定(高速)
  • html_strip.pyhelp_i18n_htmlhelp_ja_text 生成時の保険
  • text_hash.pylabel+purpose+payload から source_hash 生成(冪等化)

sql(DDL固定化)

  • 020_portal_translate.sql / 030_portal_chroma_doc.sql:すでにお渡ししたDDLを入れる場所

エンドポイント ↔ ファイル対応早見表

エンドポイントrouter主サービス主テーブル
POST /extract/view_commonrouters/extract.pyservices/extract.pyportal_view_common → portal_translate
POST /extract/fieldrouters/extract.pyservices/extract.pyportal_fields → portal_translate
POST /translate/runrouters/translate.pyservices/translate.pyportal_translate
POST /writeback/view_commonrouters/writeback.pyservices/writeback.pyportal_view_common
POST /writeback/fieldrouters/writeback.pyservices/writeback.pyportal_fields
POST /chroma/packagerouters/package.pyservices/package.pyportal_translate → portal_chroma_doc
POST /chroma/upsertrouters/chroma.pyservices/chroma_client.pyportal_chroma_doc → Chroma
GET /status/summaryrouters/status.py(複合)translate/chroma_doc 集計
GET /samples/tracerouters/status.py(複合)natural_keyトレース

1) ルータ構成(ファイル)

api/app/routers/
  portal_model.py         # /portal/model …
  portal_field.py         # /portal/field …
  portal_view_common.py   # /portal/view_common …
  portal_view.py          # /portal/view …
  portal_tab.py           # /portal/tab …
  portal_smart_button.py  # /portal/smart_button …
  portal_menu.py          # /portal/menu …

それぞれの内部では、DB操作は repos/portal_*.py、Odoo標準の参照は repos/ir_*_src_repo.py を呼ぶ想定です。
翻訳キュー投入(translate)は既存の /extract/* に任せる運用でも、各 portal ルータから“キュー投入”の補助アクションを提供してもOKです。


2) エンドポイント一覧(役割別・入出力の概要)

■ モデル:/portal/model

  • GET /portal/model
    一覧(q, model, limit, cursor など)
  • GET /portal/model/{id}
    詳細取得
  • POST /portal/model
    新規作成(model, model_table, label_i18n, notes
  • PATCH /portal/model/{id}
    部分更新
  • DELETE /portal/model/{id}(運用方針で制限可)
  • POST /portal/model/import
    Odooミラー(ir_model_src)から選択取込
    入力: { "models": ["sale.order", ...], "scaffold": true }
    出力: { created, updated, skipped } scaffold=true なら初期portal_viewportal_fieldsの骨組み生成を同時に実施。

■ フィールド:/portal/field

  • GET /portal/field
    一覧(model, field_name, origin, limit …)
  • GET /portal/field/{id}
    詳細
  • POST /portal/field
    新規作成(個別)
  • PATCH /portal/field/{id}
    更新(label_i18n, notes, ttype など)
  • DELETE /portal/field/{id}
  • POST /portal/field/import
    ir_field_src からモデル単位/フィールド単位で選択取込
    入力: { "model": "sale.order", "fields": ["partner_id","amount_total"] }
    出力: { created, updated, skipped }
  • POST /portal/field/extract(任意)
    指定のモデル/フィールドを translate にUPSERT(翻訳キュー投入)。

■ ビュー共通(アクション起点):/portal/view_common

  • GET /portal/view_common
    一覧(action_xmlid, model, primary_view_type など)
  • GET /portal/view_common/{id}
    詳細
  • POST /portal/view_common
    手動作成・更新用(あまり使わない想定)
  • PATCH /portal/view_common/{id}
  • DELETE /portal/view_common/{id}
  • POST /portal/view_common/import
    ir_view_src (action-centric) からaction_xmlid選択取込(UPSERT)
    入力: { "action_xmlids": ["sale.action_quotations", ...] }
    出力: { created, updated, skipped }
  • POST /portal/view_common/bootstrap_view
    取込済み view_types[] を展開し、対応する portal_view の骨組みを一括作成。
    入力: { "action_xmlids": [...], "set_primary_from_common": true }
  • POST /portal/view_common/extract(任意)
    指定 action について ai_purpose / help_ja_text を translate にUPSERT
    入力: { "action_xmlids": [...], "targets": ["ai_purpose","help"] }

■ ビュー(種別ごとの設定):/portal/view

  • GET /portal/view
    一覧(common_id, view_type, model, is_primary …)
  • GET /portal/view/{id}
    詳細
  • POST /portal/view
    手動作成(common_id, view_type, 付随設定)
  • PATCH /portal/view/{id}
    種別ごとの設定を更新(list/form/calendar…)
  • POST /portal/view/set_primary
    1つの common_id 内で 主ビューを設定(トリガで単一化)。
    入力: { "common_id": 123, "view_type": "list" }
  • POST /portal/view/bootstrap_from_common
    view_common.bootstrap_view と同義(呼び分けたい場合に)

■ タブ:/portal/tab

  • GET /portal/tab
    一覧(view_id, tab_key など)
  • GET /portal/tab/{id}
  • POST /portal/tab
    作成(view_id, tab_key, tab_label_ja/en, オプション群)
  • PATCH /portal/tab/{id}
  • DELETE /portal/tab/{id}
  • POST /portal/tab/bulk_upsert
    複数タブを一括登録。
    入力: [{ "view_id": 1, "tab_key": "main", "tab_label_ja": "基本", ...}, ...]

■ スマートボタン:/portal/smart_button

  • GET /portal/smart_button
    一覧(view_id, button_key, action_type …)
  • GET /portal/smart_button/{id}
  • POST /portal/smart_button
    作成(view_id, button_key, label_i18n, action_type 等)
  • PATCH /portal/smart_button/{id}
  • DELETE /portal/smart_button/{id}
  • POST /portal/smart_button/bulk_upsert
    複数ボタン一括登録

■ メニュー:/portal/menu

ir_menu_src をミラーしていない前提なら、まずは手動管理+将来の取込口を予約。

  • GET /portal/menu / GET /portal/menu/{id}
  • POST /portal/menu / PATCH /portal/menu/{id} / DELETE
  • POST /portal/menu/bulk_upsert
  • POST /portal/menu/import(将来)
    ir_menu_src を用意したら action_xmlid/parent をキーに取込。

3) 既存のパイプラインAPIとの関係

  • portal_* ルータは ポータル本体の状態を作る/保つエンドポイント群です(CRUD・標準取込・ブートストラップ)。
  • 翻訳・Chroma系は、既案の以下で運用:
    • /extract/* … translate にキュー投入(各 portal_* からでも実行可)
    • /translate/run … 翻訳
    • /chroma/package … 日本語doc+厳密メタのパッケージング
    • /chroma/upsert … ベクトル投入

つまり、まず /portal/* でポータルの土台を作る → 必要なところだけ /extract/* に渡す、が日常の流れです。


4) 典型フロー(例)

  1. アクションを選んで取り込む:
    POST /portal/view_common/import(action_xmlid群)
  2. 対応するビュー行を生成:
    POST /portal/view_common/bootstrap_view
  3. 必要なフィールドを取り込む:
    POST /portal/field/import(model+fields)
  4. 翻訳キューに積む(必要な時だけ):
    POST /portal/view_common/extractPOST /portal/field/extract
  5. 翻訳→Chroma: /translate/run/chroma/package/chroma/upsert

フェーズ0 まずOdooにアクセス。pgadiminで表示

フェーズA:骨格固定(1〜2スプリントの核)

目的:後戻りが大きい部分を最初に確定し、以降は実装を足すだけにする

  1. API仕様の凍結
    • 成果物:OpenAPI 3.1(既に提示のYAML)を/openapi/openapi.yamlとして確定
    • DoD:/docsで表示可・基本パスが全て列挙されている(モックOK)
  2. DDLの確定・マイグレーション基盤
    • 成果物:sql/000_extensions.sql010_portal_core.sql020_portal_translate.sql030_portal_chroma_doc.sql
    • 変更点反映:portal_viewcalendar_*_fieldtext に変更済み
    • DoD:ローカル/CIで“初期化→再適用”が冪等
  3. プロジェクト骨格+ヘルスエンドポイント
    • 成果物:/app/main.py(healthz/livez/startupz)、/app/config.py/app/db.py/app/repos/* 雛形
    • DoD:GET /healthz = 200、ログと設定の読み込みが通る

フェーズB:Repo層の完成(DB I/Oの中核)

目的:全てのルータがここを呼べば済む状態にする

  1. Portal Repos(書込あり)
    • portal_model_repo.py / portal_field_repo.py / portal_view_common_repo.py / portal_view_repo.py
    • 共通:UPSERT/ページング/自然キー生成、updated_atの自動タッチ
    • DoD:ユニットテストでCRUDと一意制約が通る
  2. IRソース Repos(読み取り専用)
    • ir_model_src_repo.py / ir_field_src_repo.py / ir_view_src_repo.py(action-centric)
    • DoD:SELECT系が想定件数を返す(当初はスタブ可)

フェーズC:Odooミラー(action-centric)を“参照可能”にする

目的:本番に近いデータでPortalの取込を試せる状態

  • 成果物:あなたが提示したSQLpublic.ir_*_src を再構築するスクリプト(ローカル&Cron両対応)
  • DoD:ir_model_src / ir_field_src / ir_view_src の件数が取得できる
    ※K8s用のCronJobは後段。ここでは実行SQLとRepoの動作確認のみ

フェーズD:Portalの“取込API”から先に実装

目的:ポータルの背骨を最速で成立させる

  1. /portal/view_common/import(action_xmlid 選択UPSERT)
  2. /portal/view_common/bootstrap_viewview_types[]portal_view骨組み作成)
  3. /portal/field/import(モデル/フィールド選択UPSERT)
    4.(必要に応じて)/portal/model/import
  • DoD:手動呼び出しで portal_view_common / portal_view / portal_fields一意UPSERTされる

フェーズE:抽出(translate投入)

目的:翻訳対象を translate に冪等UPSERT

  1. translateテーブルの状態遷移/自然キー/ソースハッシュの確定(仕様は保持)
  2. /extract/view_commonai_purpose / help_ja_text:JAあり & EN空のみ)
  3. /extract/field(label/notesのJAから)
  • DoD:同じリクエストを繰り返しても pending重複せずに集約される

フェーズF:翻訳(ジョブ)

目的pending → translated を安定運転

  1. 翻訳アダプタ(プロバイダ差替え可能)
  2. /translate/run(limit/対象entity/エラー時failed)
  • ルール:同一source_hashは再翻訳しない
  • DoD:正常・失敗・再実行の各ケースがE2Eで確認できる

フェーズG:パッケージング(日本語doc+厳密メタ)

目的:Chromaに投げられる完成形を作る

  1. /chroma/packagetranslatedのみ→portal_chroma_doc にUPSERT(queued
  2. docテンプレ(日本語):Field/Viewのフォーマット・メタ詰め(model, model_table, field_name or action_xmlid など)
  • DoD:doc_id = natural_key::lang で一意、同一source_hashスキップされる

フェーズH:Chroma upsert

目的:検索に載せる

  1. Chroma Client(接続/collection作成/embeddings)
  2. /chroma/upsertqueuedのみ対象、dry_runあり)
  • DoD:upserted が期待件数に増分、簡単な日本語クエリでヒット

フェーズI:書き戻し(任意)&ステータス

目的:UIとの整合・運用の見える化

  1. /writeback/view_common/writeback/field(デフォルトは skip_if_exists
  2. /status/summary, /samples/trace
  • DoD:自然キー単位で抽出→翻訳→パッケージ→upsertの通し状況が見える

フェーズJ:Kubernetes向けの“開発最適化”実装

目的:K8sで開発/検証が回しやすい足回り

  1. ヘルスチェック(readiness/liveness/startup)を本実装に
  2. Bearer認証/監査ログ/リクエストIDミドルウェア
  3. Idempotency-Key(PUT/POSTの多重防止:後方互換で追加)
  4. Config分離(dev/prodでConfigMap/Secret差替えが効くこと)
  • DoD:K8s上でのローリング更新/再実行で“二重実行・ダウン”が起きない

フェーズK:Cron層(K8s化)

目的:人手なしで回る

  • 既存のCronJob雛形に合わせ、APIコール型extract → translate → package → upsert を接続
  • DoD:Cronの連鎖で queued → upserted が自然に進む(失敗時の再試行ポリシー確認)

依存関係まとめ(最短径)

  • D → E → F → G → H が“検索までの最短ライン”
  • C(Odooミラー)はDの直前までに参照可能にしておけばOK
  • I/J/K は品質・運用の層。最短ラインが通った後に入れる

テストの当て方(各フェーズのDoDに直結)

  • ユニット:RepoのCRUD/UPSERT/自然キー/ハッシュ
  • API結合:各エンドポイントの冪等性とバリデーション
  • E2E(最短ライン)
    1. view_common.importbootstrap_viewfield.import
    2. extract.*translate/runchroma/packagechroma/upsert
    3. 日本語クエリでヒット(サンプル固定)

先に作るべき“最小セット”(実装順の目印)

  1. DDL & Repo(Portal/IR)
  2. view_common.import / bootstrap_view / field.import
  3. extract(view_common/field)
  4. translate/run
  5. chroma/package
  6. chroma/upsert
    (以降:writeback → status → k8s最適化/cron)

この順で進めると、“Odooを主に使うユーザー”視点で、最低限の取込と検索が先に成立し、その後に品質と運用性を段階的に積み増せます。


Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です