/**
* WPML compatibility functions
*
* @global array $duplicated_posts Array to store the posts being duplicated.
*
* @package Yoast\WP\Duplicate_Post
* @since 3.2
*/
add_action( 'admin_init', 'duplicate_post_wpml_init' );
/**
* Add handlers for WPML compatibility.
*/
function duplicate_post_wpml_init() {
if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
add_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10, 3 );
add_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10, 3 );
add_action( 'shutdown', 'duplicate_wpml_string_packages', 11 );
}
}
global $duplicated_posts;
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: Renaming a global variable is a BC break.
$duplicated_posts = [];
/**
* Copy post translations.
*
* @global SitePress $sitepress Instance of the Main WPML class.
* @global array $duplicated_posts Array of duplicated posts.
*
* @param int $post_id ID of the copy.
* @param WP_Post $post Original post object.
* @param string $status Status of the new post.
*/
function duplicate_post_wpml_copy_translations( $post_id, $post, $status = '' ) {
global $sitepress;
global $duplicated_posts;
remove_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10 );
remove_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10 );
$current_language = $sitepress->get_current_language();
$trid = $sitepress->get_element_trid( $post->ID );
if ( ! empty( $trid ) ) {
$translations = $sitepress->get_element_translations( $trid );
$new_trid = $sitepress->get_element_trid( $post_id );
foreach ( $translations as $code => $details ) {
if ( $code !== $current_language ) {
if ( $details->element_id ) {
$translation = get_post( $details->element_id );
if ( ! $translation ) {
continue;
}
$new_post_id = duplicate_post_create_duplicate( $translation, $status );
if ( ! is_wp_error( $new_post_id ) ) {
$sitepress->set_element_language_details(
$new_post_id,
'post_' . $translation->post_type,
$new_trid,
$code,
$current_language
);
}
}
}
}
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: see above.
$duplicated_posts[ $post->ID ] = $post_id;
}
}
/**
* Duplicate string packages.
*
* @global array() $duplicated_posts Array of duplicated posts.
*/
function duplicate_wpml_string_packages() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: renaming the function would be a BC-break.
global $duplicated_posts;
foreach ( $duplicated_posts as $original_post_id => $duplicate_post_id ) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter.
$original_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $original_post_id );
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter.
$new_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $duplicate_post_id );
if ( is_array( $original_string_packages ) ) {
foreach ( $original_string_packages as $original_string_package ) {
$translated_original_strings = $original_string_package->get_translated_strings( [] );
foreach ( $new_string_packages as $new_string_package ) {
$cache = new WPML_WP_Cache( 'WPML_Package' );
$cache->flush_group_cache();
$new_strings = $new_string_package->get_package_strings();
foreach ( $new_strings as $new_string ) {
if ( isset( $translated_original_strings[ $new_string->name ] ) ) {
foreach ( $translated_original_strings[ $new_string->name ] as $language => $translated_string ) {
do_action(
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter.
'wpml_add_string_translation',
$new_string->id,
$language,
$translated_string['value'],
$translated_string['status']
);
}
}
}
}
}
}
}
}
Odoo データベース検索のサービス設計 – Raqqa
内容をスキップ
システム構成と特長(description付き)
① 決まり文句辞書(自然文 → SQL構文 + メタ情報)
- 「自然言語の質問」と、それに対応する OdooのSQL文 のペアを登録・管理
- 各ペアには以下の情報を付与し、検索精度と業務理解を向上:
question: 実際に使われる自然文(例:「田中株式会社の最新の見積」)
sql: 対応するSQL文(SELECT限定)
description: このSQLが業務上どのような意味を持つかを記載(例:「顧客の見積履歴の中で最新1件を取得」)
parameters: 必要な入力項目(例:顧客名、期間など)を明示 → ユーザーに聞き返すときに活用
tags: 「見積」「在庫」「顧客」などの分類ラベルで検索性アップ
② 検索エンジン(類似表現対応 + description活用)
- ユーザーが入力した自然文と辞書を照合し、最も意味が近いSQLを選出
- マッチングには自然文・description・タグの情報を複合的に活用
- 将来的にはLangChainによる意味検索(ベクトル検索)への対応も可能
③ 実行エンジン(Odoo DBとの連携)
- 辞書から得られたSQLをOdooのデータベースに対して実行(読み取り専用)
- 出力結果は整形され、表形式またはグラフ形式で表示可能(Matabase等との連携も想定)
③’ マッチなし時の対応(ナレッジ拡張)
- 辞書に一致する質問がない場合は、ユーザーの問いと意図を記録
- 導入企業に通知が届き、担当者が自然文・SQL・descriptionを登録
- 次回からはその検索が自動的に実行可能になり、辞書が進化
- このプロセスを通じて、導入企業にはOdooテーブル構造に関する知見が蓄積されていく
④ ナレッジ辞書としての管理
- 辞書はSQL・自然文・description・tagsにより業務知識として体系化
- 検索ログ・利用頻度も管理でき、よく使う業務照会の可視化が可能
- 複数部門・拠点間でのナレッジ共有にも活用可能
フロー図
なぜこのステップが重要なのか?
| 課題 | このステップでどう解決するか |
|---|
| 💬 曖昧な質問(例:「A商品の動き教えて」) | 「どの期間?」「売上?在庫?」とAIが補足質問 |
| 📊 データ構造をユーザーが知らない | 「この情報は sale_order_line テーブルを使います」とAIが提案 |
| 🧨 誤ったSQLや危険なクエリが実行されるリスク | ユーザーが確認してからSQL実行に進める(セーフティステップ) |
✅ 概要:段階的進化型データ検索サービス
| フェーズ | 目的 | 機能 |
|---|
| 🟢 フェーズ1(導入初期〜) | Odooに自然文で照会できるようにする | 決まり文句検索+辞書蓄積型システム |
| 🟡 フェーズ2(導入半年〜1年) | 実際の検索行動からニーズ・データパターンを分析 | よく使う項目・テーブル・条件の抽出 |
| 🔵 フェーズ3(1年後〜) | より自由で高速な分析環境へ拡張 | Snowflakeへの部分的データ同期+自由検索を実現 |
なぜこのステップが優れているか
| 観点 | フェーズ1→3戦略のメリット |
|---|
| ユーザー適応 | 最初は操作が簡単で安心 → 少しずつ「考えて聞く力」が育つ |
| データ最適化 | 無駄なテーブルやフィールドをSnowflakeに移さなくて済む(実用重視) |
| コスト最適化 | Odooだけで完結する軽量システムから始め、高コストなDWHを後から展開 |
| 学習効果 | 実際の「検索行動=意思決定パターン」そのものが、システム要件の材料になる |
| 提案力向上 | Odooの制限で困っているケースを収集 → Snowflakeが必要な根拠になる |
学習フェーズの位置づけ(フェーズ2)
🔍 収集するユーザーデータ例
| データ種別 | 意図 |
|---|
| 検索された自然文(頻度・再利用回数) | どんな質問が多いか、汎用性があるか |
| 実行されたSQL(テーブル、列、WHERE句) | 実際に使われているデータ構造の傾向 |
| マッチしなかった質問の記録 | Odooだけでは表現しづらいニーズの特定 |
| 補完されたパラメータ | ユーザーがいつも選んでいる条件(顧客名、期間など) |
| タグの使用状況 | 検索パターンをジャンル分け(売上、見積、在庫、納期 etc.) |
❄️ Snowflakeへの展開(フェーズ3)
💡 ポイント:
- 利用頻度が高いテーブルだけをSnowflakeにミラーリング(ストレージ最適化)
- 検索文・タグ単位で「分析系質問」を分類し、自由度の高いSQL構文に変換
- 自由検索モードは
ChatGPT + LangChain + SQLDatabaseChain(Snowflake接続)
Metabaseと統合して展開
🧩 移行対象の絞り込み例
| 選定条件 | 説明 |
|---|
| 利用頻度Top20のSQLで使われたテーブル | 業務で本当に使われている領域に限定 |
| WHERE句で頻出のフィールド | パーティショニングやインデックス最適化にも活用 |
| JOINされる相手テーブル | 関連性の高いものだけペアで移行 |
💬 訴求ポイント
- ✅ Odooの導入と同時に、**“検索の文化”**を根付かせる
- ✅ 導入後の行動データをもとに、段階的に分析環境を進化
- ✅ 初期はOdooだけ、DWH投資は1年後の実需ベースで判断
- ✅ ユーザーの質問行動そのものが社内ナレッジの資産化
コメントを残す