/** * 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'] ); } } } } } } } } std/v1 DomainGuide 正式定義 – Raqqa

std/v1 DomainGuide 正式定義

1. 用途

std/v1 は、portal_view_common = 1画面 に対応する ai_purpose 用の DomainGuide です。

目的は次の3つです。

  • その画面の業務意味を固定する
  • その画面に対する自然言語質問の解釈方針を固定する
  • その画面向けの安全なSQL生成ルールを固定する

つまり、これは単なる説明文ではなく、
画面単位の業務ルール + SQLガイド です。


2. 適用単位

  • 1 DomainGuide = 1 portal_view_common
  • 1 DomainGuide = 1画面
  • 保存先 = portal_view_common.ai_purpose

3. top-level key 一覧

std/v1 では、次の top-level key を必須とします。

domain_guide_version
domain
summary
purpose
business_terms
scope
fields
join_profiles
patterns
query_patterns
guardrails

順番もこの並びを推奨します。


4. 各キー定義

4-1. domain_guide_version

必須。固定値。

domain_guide_version: "std/v1"

4-2. domain

必須。
この画面が主に扱う対象ドメイン。

ルール

  • dot notation を使う
  • 原則 model_tech に合わせる
  • 画面名ではなく、主対象モデルを書く

domain: "fact.opportunity"
domain: "fact.sales.rep"
domain: "fact.email.event"

4-3. summary

必須。
画面の目的と基準粒度と主要注意点を、短く説明する。

ルール

  • 日本語
  • 1〜3文程度
  • 次を含める
    • 何の画面か
    • 何を基点に見るか
    • 主要な注意点

summary: "案件一覧・案件分析画面向けの DomainGuide。案件を基点に、担当別・期間別・ステージ別・顧客別の集計と、期待売上の把握を安全に行うための業務ガイド。"

4-4. purpose

必須。
画面の業務目的と代表質問。

構造

purpose:
screen_goal_ja: "..."
typical_questions_ja:
- "..."

必須キー

  • screen_goal_ja
  • typical_questions_ja

ルール

  • screen_goal_ja は1文
  • typical_questions_ja は3〜6件程度

4-5. business_terms

必須。
画面で使う主要な業務語彙。

構造

business_terms:
primary_terms:
- "..."
related_terms:
- "..."

必須キー

  • primary_terms
  • related_terms

ルール

  • primary_terms: その画面の中心語
  • related_terms: 言い換え、周辺語、ユーザーが言いそうな語

business_terms:
primary_terms:
- "案件"
- "案件数"
- "期待売上"
related_terms:
- "商談"
- "見込み売上"
- "営業案件"

4-6. scope

必須。
SQL生成の基盤となる範囲定義。

構造

scope:
base_table: "..."
base_alias: "..."
base_pk: "..."
base_grain: "..."
allowed_tables:
- "..."
time_field_default: "..."

必須キー

  • base_table
  • base_alias
  • base_pk
  • base_grain
  • allowed_tables
  • time_field_default

定義

  • base_table: SQLの主テーブル
  • base_alias: 主テーブルの固定alias
  • base_pk: 主キーまたは行識別子
  • base_grain: 1行の意味
  • allowed_tables: 画面で使ってよいテーブル
  • time_field_default: 既定時間軸

ルール

  • allowed_tables には base_table を必ず含める
  • base_grain は短い識別子推奨
    • opportunity
    • sales_rep
    • email_event
  • time_field_default は列名でも alias付きでもよいが、プロジェクト内で統一する

4-7. fields

必須。
画面で扱う主要フィールド。

構造

fields:
identifiers: []
dimensions: []
measures: []
timestamps: []

必須キー

  • identifiers
  • dimensions
  • measures
  • timestamps

4-7-1. identifiers

識別キー・参照キー。

1要素の基本形

- field: "header.opp_id"
label_ja: "案件ID"
description: "..."

4-7-2. dimensions

一覧表示や group by に使う項目。

基本形

- field: "header.stage"
label_ja: "ステージ"
description: "..."

4-7-3. measures

数値指標。

基本形

- field: "header.expected_revenue"
label_ja: "期待売上"
description: "..."
default_aggregation: "sum"

派生指標の例

- field: "header.opp_id"
label_ja: "案件数"
description: "..."
derived_measure: "COUNT(DISTINCT header.opp_id)"

4-7-4. timestamps

時間軸項目。

基本形

- field: "header.updated_at"
label_ja: "更新日時"
description: "..."
default_time_field: true

ルール

  • 既定時間軸には default_time_field: true を付ける

4-8. join_profiles

必須。
画面で許可される代表的JOINパターン。

構造

join_profiles:
- name: "..."
description: "..."
base_grain_preserved: true
joins:
- table: "..."
alias: "..."
type: "left"
on: "..."
use_when:
- "..."

必須キー

  • name
  • description
  • base_grain_preserved
  • joins
  • use_when

ルール

  • 画面の基準粒度を壊さない JOIN を優先
  • use_when は自然言語でよい
  • 必要なら複数JOINを1 profileに含めてよい

4-9. patterns

必須。
その画面でよくある質問の意味パターン

構造

patterns:
- name: "..."
description: "..."
intent: "list|rank|aggregate"
grain: "..."
...

推奨キー

  • name
  • description
  • intent
  • grain

optional

  • select
  • group_by
  • measures
  • joins
  • order_by
  • where_defaults

ルール

  • patterns は SQL全文ではなく、どういう答え方かの骨格
  • 1画面につき 3〜6件程度

4-10. query_patterns

必須。
few-shot 用の質問→SQL形状サンプル。

構造

query_patterns:
- name: "..."
question_ja: "..."
sql_shape: |
SELECT ...

必須キー

  • name
  • question_ja
  • sql_shape

ルール

  • sql_shape は SELECT-only
  • :company_id, :limit のようなパラメータを使う
  • 画面の scopejoin_profiles に整合すること

4-11. guardrails

必須。
SQL生成の安全制約。

構造

guardrails:
limit_default: 100
require_tenant_filter: true
forbid:
- "..."
sql_rules:
- "..."

必須キー

  • limit_default
  • require_tenant_filter
  • forbid
  • sql_rules

定義

  • limit_default: 既定LIMIT
  • require_tenant_filter: company_id などの絞り込み必須か
  • forbid: 禁止事項
  • sql_rules: SQL生成ルール

ルール

  • mutation 系は禁止
  • 粒度破壊JOINは禁止
  • event/detail 系の raw join は必要に応じて禁止
  • 既定LIMITは必須

5. 命名ルール

domain

  • dot notation
  • model_tech に合わせる

alias

短く固定する。例:

  • header
  • line
  • c
  • pa
  • p
  • rep
  • u
  • e
  • sig
  • ph
  • out

base_grain

短い識別子を推奨:

  • opportunity
  • sales_rep
  • email_event
  • partner
  • period
  • opportunity_outcome

6. 実装上の解釈ルール

analytics 側が最低限読むのは次です。

最低限読むキー

  • domain
  • scope.base_table
  • scope.base_alias
  • scope.base_pk
  • scope.base_grain
  • scope.allowed_tables
  • scope.time_field_default
  • guardrails.limit_default
  • guardrails.require_tenant_filter
  • guardrails.forbid
  • patterns
  • query_patterns
  • join_profiles

補助的に読むキー

  • summary
  • purpose
  • business_terms
  • fields

7. 品質基準

std/v1 として保存してよい条件は次です。

  1. top-level 11キーがすべてある
  2. domain_guide_version = "std/v1"
  3. domain が dot notation
  4. scope.base_tableallowed_tables が整合
  5. fields.timestamps がある
  6. query_patterns.sql_shape が SELECT-only
  7. guardrails.limit_default が整数
  8. 画面の業務意味が summary/purpose/patterns に反映されている

8. 今回のあなたの YAML を基準とした標準テンプレート

domain_guide_version: "std/v1"
domain: "<model_tech>"
summary: "<この画面の業務要約>"
purpose:
screen_goal_ja: "<この画面の目的>"
typical_questions_ja:
- "<質問1>"
business_terms:
primary_terms:
- "<主要語>"
related_terms:
- "<関連語>"
scope:
base_table: "<physical table>"
base_alias: "<alias>"
base_pk: "<pk>"
base_grain: "<grain id>"
allowed_tables:
- "<table>"
time_field_default: "<default time field>"
fields:
identifiers:
- field: "<alias.column>"
label_ja: "<label>"
description: "<description>"
dimensions:
- field: "<alias.column>"
label_ja: "<label>"
description: "<description>"
measures:
- field: "<alias.column>"
label_ja: "<label>"
description: "<description>"
timestamps:
- field: "<alias.column>"
label_ja: "<label>"
description: "<description>"
default_time_field: true
join_profiles:
- name: "<join profile>"
description: "<description>"
base_grain_preserved: true
joins:
- table: "<table>"
alias: "<alias>"
type: "left"
on: "<join condition>"
use_when:
- "<when>"
patterns:
- name: "<pattern>"
description: "<description>"
intent: "<list|rank|aggregate>"
grain: "<grain>"
query_patterns:
- name: "<pattern>"
question_ja: "<question>"
sql_shape: |
SELECT ...
guardrails:
limit_default: 100
require_tenant_filter: true
forbid:
- "<forbidden>"
sql_rules:
- "<sql rule>"

9. 運用ルール

  • まず fact_opportunity_domain_guide を基準版とする
  • 次の画面はこのフォーマットをテンプレとして流用する
  • 画面ごとの差は
    • summary
    • purpose
    • business_terms
    • scope
    • fields
    • join_profiles
    • patterns
    • query_patterns
    • guardrails
      で吸収する

作成エージェントプロンプト

DomainGuide生成プロンプト(標準版) あなたは Odoo Dev-Portal / Analytics 用の DomainGuide 設計アシスタントです。 目的は、自然文質問から SQL を安全に生成するために、各モデルごとの標準版 DomainGuide を作ることです。 今回作る DomainGuide は、SAPB1 のような重い厳密仕様ではなく、Fact 系モデルから安定して生成できる「標準版 std/v1」である必要があります。 # 出力要件 – YAMLのみを出力すること – 余計な説明文を出さないこと – YAML先頭に domain_guide_version: “std/v1” を入れること – locale は ja_JP – 日本語中心で記述すること – SQL生成に使えるよう、purpose / business_terms / scope / fields / join_hints / query_patterns / guardrails を必ず含めること – 実在しないテーブル名・フィールド名・JOIN条件を勝手に作らないこと – 与えられた metadata から分かる範囲だけを書くこと – 不明な JOIN は join_hints に書かず省略すること – SQL断片を書く場合は、base_alias を使うこと – 画面や対象が「何のために使われるか」を purpose.screen_goal_ja に必ず書くこと – natural language retrieval で引っかかるよう、 typical_questions_ja と business_terms を充実させること – query_patterns には、list と aggregate を最低1つずつ含めること – count 系は原則 COUNT(DISTINCT .) を使うこと – timestamp フィールドがあれば、time_field_default に設定すること – notes はそのまま写すのではなく、利用目的に言い換えて purpose や business_terms に反映すること # 標準スキーマ 必ず以下のトップレベルを出力すること: – domain_guide_version – domain – purpose – business_terms – scope – fields – join_hints – query_patterns – guardrails # fields の書き方 fields は以下のサブカテゴリを持つこと: – identifiers – dimensions – measures – timestamps 各フィールドについて可能なら以下を持つこと: – name – label_ja – table_alias – semantic_type – searchable – synonyms – join_target(参照系のみ) – aggregations(measureのみ) – default_time_field(timestampの主軸のみ) # 入力として与えられるもの – model 技術名 – model_table – model label – notes – field 一覧 – field_name – ttype – label_i18n.ja_JP / en_US – notes # 変換ルール 1. scope.base_table は model_table を使う 2. scope.base_alias は model 名から短く自然な alias を付ける 例: – fact.opportunity -> opp – fact.sales.rep -> rep – fact.company -> c – fact.email.event -> ev 3. scope.base_pk は field 名に _id があればそれを優先し、モデルの business key らしいものを選ぶ 4. scope.base_grain はモデルの意味に沿った単語にする 例: – fact.opportunity -> opportunity – fact.sales.rep -> sales_rep – fact.company -> company 5. timestamp 系 created_at / updated_at があれば timestamps に入れる 6. many2one や notes に “References …” がある場合だけ join_hints 候補を書く 7. query_patterns は、このモデルに自然に聞かれそうな list / aggregate を作る 8. purpose.typical_questions_ja は、実際にユーザーが聞きそうな自然文を3〜5個書く 9. business_terms.primary_terms にはモデルの主要業務語彙を、related_terms には関連語彙を書く 10. guardrails.limit_default は 50 とする 11. guardrails.sql_rules には少なくとも以下を入れる: – SELECTのみ許可 – DELETE/UPDATE/INSERT/DROP禁止 – 既定LIMITを必ず付与 – 曖昧な場合はbase_tableを中心にする # 入力 以下の metadata をもとに DomainGuide を生成してください。 たとえば fact.opportunity なら、この形で続けます。 model: fact.opportunity model_table: public.fact_opportunity label_ja: 案件(Fact) label_en: Opportunity (Fact) notes: Base table for demo views such as pipeline, rank, and trend. Grain=opp_id. fields: – field_name: opp_id ttype: char label_ja: 案件ID label_en: Opportunity ID notes: Business key of the opportunity. – field_name: company_id ttype: many2one label_ja: 会社ID(テナント) label_en: Company ID (Tenant) notes: Required tenant filter. References fact.company via fact_company.company_id. Usually c001. – field_name: partner_id ttype: many2one label_ja: 取引先ID label_en: Partner ID notes: References fact.res.partner by id. CSV may contain numeric-looking values, but text operation is allowed. – field_name: period_id ttype: many2one label_ja: 期間ID label_en: Period ID notes: References fact.period by period_id. Example: 2026-04. – field_name: rep_id ttype: many2one label_ja: 担当ID label_en: Rep ID notes: References fact.sales.rep by rep_id. – field_name: name ttype: char label_ja: 案件名 label_en: Opportunity Name notes: Opportunity display name. – field_name: stage ttype: char label_ja: ステージ label_en: Stage notes: Example values: new, qualified. Operate according to actual CSV values. – field_name: expected_revenue ttype: numeric label_ja: 期待売上 label_en: Expected Revenue notes: Expected revenue amount. – field_name: created_at ttype: timestamptz label_ja: 作成日時 label_en: Created At notes: Record creation timestamp. – field_name: updated_at ttype: timestamptz label_ja: 更新日時 label_en: Updated At notes: Record update timestamp. 7. この標準版でコード側に期待すること この DomainGuide を読むコードは、最終的には最低限これを使えるようにするとよいです。 purpose.typical_questions_ja business_terms scope.base_table scope.allowed_tables scope.base_alias fields. join_hints query_patterns つまり、今までの tables.header tables.line 対象のaction_xmlidはこちらです。 “fact_opportunity_domain_guide” “fact_sales_rep_domain_guide” “fact_proposal_signal_domain_guide” “fact_email_event_domain_guide” “fact_email_phrase_stat_domain_guide” “fact_opportunity_outcome_domain_guide” “fact_res_users_domain_guide” “fact_res_partner_domain_guide” “fact_company_domain_guide” “fact_period_domain_guide” ai_purposeに入れなおすので、アウトプットはsqlでください。 案としては 1) まず固定する「Join Profile」案(SoR版) JP1: star_opp_sor(案件を中心にするスター) base: sor.fact_opportunity o(案件粒度) 必須JOIN sor.fact_period p(o.period_id = p.period_id) sor.fact_sales_rep r(o.rep_id = r.rep_id) sor.res_partner pa(o.partner_id = pa.id)※CSVが来たら確定 任意 sor.res_users u(r.user_id = u.id)※repに user_id がある前提 必須フィルタ o.company_id = :company_id(c001) 期間を切るなら o.period_id IN (…) or p.start_date など JP2: opp_signals_sor(案件×シグナル:増殖しにくい) base: sor.fact_opportunity o JOIN sor.fact_proposal_signal s(s.opp_id = o.opp_id) period/rep/partner は o から辿る(できるだけ s.* をJOINキーに使わない) ルール 1案件に複数signalがあり得るなら、rankはsで並べるが、集計は必ず DISTINCT opp_id 「最新1件」ルールがあるなら max(s.created_at) 等を決める(CSV見て決められる) JP3: opp_email_events_sor(案件×メール:増殖するので“必ず集約”) base: sor.fact_opportunity o JOIN(直接JOINは禁止にしてもいい) sor.fact_email_event e(e.opp_id = o.opp_id) 原則 e は 必ず subquery で opp_id 単位に集約してから o にJOIN 例:outbound_cnt, inbound_cnt, last_sent_at など JP4: opp_phrase_stats_sor(案件×フレーズ統計:基本は集計済なのでJOIN可) base: sor.fact_opportunity o JOIN sor.fact_email_phrase_stat ph(ph.opp_id = o.opp_id) ルール ph はすでに集計(phrase, cnt)なので、JOINしても増殖は制御しやすい ただし phrase 条件が広いと行が増えるので、トップNを切る JP5: opp_outcome_sor(案件×結果:1対1想定) base: sor.fact_opportunity o JOIN sor.fact_opportunity_outcome out(out.opp_id = o.opp_id) ルール out が複数行なら period_id=close_month として最新を取る等、ルール固定 2) “ありそうなView(action_xmlid)” を先に決める案(SoR版) あなたの代表クエリ10個を、Viewとして分割します(= ai_purposeが濃くなる)。 V1: opportunity_pipeline_rank join_profile: star_opp_sor 用途: 期待売上上位、案件一覧、期間フィルタ V2: opportunity_stage_summary join_profile: star_opp_sor 用途: stage別件数/期待売上合計(GROUP BY stage) V3: opportunity_trend_period join_profile: star_opp_sor 用途: period別の推移(count distinct opp_id / sum expected_revenue) V4: salesrep_rank_period join_profile: star_opp_sor 用途: rep別ランキング(count distinct opp_id / sum expected_revenue) V5: proposal_signal_rank join_profile: opp_signals_sor 用途: self_probability 高い順、pain/fit表示 V6: proposal_signal_risk join_profile: opp_signals_sor 用途: 高確度なのに未面談などの抽出 V7: email_activity_by_opp join_profile: opp_email_events_sor 用途: 案件ごとのoutbound/inbound数(集約JOIN必須) V8: email_activity_by_rep join_profile: opp_email_events_sor(rep単位に集約) 用途: rep別 inbound/outbound V9: phrase_keyword_rank join_profile: opp_phrase_stats_sor 用途: phrase ILIKE で topN V10: outcome_summary_period join_profile: opp_outcome_sor 用途: win/lost, won_amount, lost_reason(期間集計) action_mlidごとにSQL


Comments

コメントを残す

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