[
‘Drive’ => ‘Slow urgency; resist solution-seeking.’,
‘Awareness’ => ‘Move from abstraction to lived experience.’,
‘Engagement’ => ‘Separate enjoyment from obligation.’,
‘Commitment’ => ‘Remove pressure to decide.’,
‘Flexibility’ => ‘Narrow focus to one concrete example.’,
‘Groundedness’ => ‘Legitimise inner signals without idealism.’,
‘Heart’ => ‘Reduce moral weight; widen permission.’,
],
‘Clarify’ => [
‘Drive’ => ‘Test certainty; ground conclusions in evidence.’,
‘Awareness’ => ‘Distil patterns without over-analysis.’,
‘Engagement’ => ‘Identify what sustains interest over time.’,
‘Commitment’ => ‘Clarify without locking in.’,
‘Flexibility’ => ‘Hold one interpretation long enough to examine it.’,
‘Groundedness’ => ‘Reality-check meaning without cynicism.’,
‘Heart’ => ‘Name emotional meaning without escalation.’,
],
‘Commit’ => [
‘Drive’ => ‘Channel momentum toward choice and trade-offs.’,
‘Awareness’ => ‘Translate insight into a deliberate decision.’,
‘Engagement’ => ‘Commit to what energises sustainably.’,
‘Commitment’ => ‘Use consistency to support choice.’,
‘Flexibility’ => ‘Constrain options to support follow-through.’,
‘Groundedness’ => ‘Anticipate constraints and consequences.’,
‘Heart’ => ‘Connect choice to meaning without self-sacrifice.’,
],
‘Execute’ => [
‘Drive’ => ‘Harness momentum; add pacing and recovery.’,
‘Awareness’ => ‘Monitor progress without overthinking.’,
‘Engagement’ => ‘Design for intrinsic motivation.’,
‘Commitment’ => ‘Reinforce habits and routines.’,
‘Flexibility’ => ‘Allow adjustment within clear boundaries.’,
‘Groundedness’ => ‘Keep plans realistic and measurable.’,
‘Heart’ => ‘Protect wellbeing while acting.’,
],
‘Reflect’ => [
‘Drive’ => ‘Contain urge to resolve or move on.’,
‘Awareness’ => ‘Integrate insights rather than analyse further.’,
‘Engagement’ => ‘Notice emotional resonance without chasing it.’,
‘Commitment’ => ‘Release pressure to conclude.’,
‘Flexibility’ => ‘Let meaning settle without reframing.’,
‘Groundedness’ => ‘Hold experience without fixing.’,
‘Heart’ => ‘Allow emotional weight without interpretation.’,
],
];
}
function cp_ai_section_to_mode($section_num) {
switch ((int)$section_num) {
case 1: return ‘Explore’;
case 2: return ‘Clarify’;
case 3: return ‘Commit’;
case 4: return ‘Execute’;
case 5: return ‘Reflect’;
default: return ‘Explore’;
}
}
/* =========================
DATA ACCESS — Insights + Videos + ChapterDefs
========================= */
function cp_ai_db() {
global $wpdb;
return $wpdb;
}
function cp_ai_meta_value($item_id, $field_id) {
$db = cp_ai_db();
$m = $db->prefix . ‘frm_item_metas’;
return $db->get_var($db->prepare(
“SELECT meta_value FROM {$m} WHERE item_id = %d AND field_id = %d LIMIT 1”,
(int)$item_id, (int)$field_id
));
}
function cp_ai_get_insight_row_by_sequence($sequence) {
if (!CP_FORM_INSIGHTS) return null;
$db = cp_ai_db();
$i = $db->prefix . ‘frm_items’;
$m = $db->prefix . ‘frm_item_metas’;
// Find the insight item id by matching sequence meta
$item_id = $db->get_var($db->prepare(
“SELECT it.id
FROM {$i} it
JOIN {$m} ms ON ms.item_id = it.id AND ms.field_id = %d
WHERE it.form_id = %d
AND ms.meta_value = %s
ORDER BY it.id ASC
LIMIT 1″,
CP_FID_INSIGHT_SEQUENCE,
CP_FORM_INSIGHTS,
(string)$sequence
));
if (!$item_id) return null;
$row = [
‘item_id’ => (int)$item_id,
‘sequence’ => (string)$sequence,
‘title’ => (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_TITLE),
‘quote’ => (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_QUOTE),
‘key’ => (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_KEY),
‘next’ => (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_NEXT),
‘prev’ => (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_PREV),
‘end_flag’ => (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_ENDCH),
];
// Prefer workplace fields if populated
$text_w = (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_TEXT_W);
$q_w = (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_Q_W);
$guide_w = (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_GUIDE_W);
$row[‘text’] = trim($text_w) !== ” ? $text_w : (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_TEXT);
$row[‘question’] = trim($q_w) !== ” ? $q_w : (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_Q);
$row[‘guide’] = trim($guide_w) !== ” ? $guide_w : (string)cp_ai_meta_value($item_id, CP_FID_INSIGHT_GUIDE);
// AI Coach rails (Insight-specific)
$row[‘ai_coach_alignment_rule’] = (string) cp_ai_meta_value($item_id, CP_FID_AI_COACH_ALIGNMENT_RULE);
$row[‘ai_coach_guide_steps’] = (string) cp_ai_meta_value($item_id, CP_FID_AI_COACH_GUIDE_STEPS);
return $row;
}
function cp_ai_get_videos_for_insight_key($edition, $insight_key) {
$db = cp_ai_db();
$i = $db->prefix . ‘frm_items’;
$m = $db->prefix . ‘frm_item_metas’;
if (!$insight_key) return [];
// Find video items linked to insight_key AND edition
$sql = $db->prepare(
“SELECT it.id
FROM {$i} it
JOIN {$m} lk ON lk.item_id = it.id AND lk.field_id = %d
JOIN {$m} ed ON ed.item_id = it.id AND ed.field_id = %d
WHERE it.form_id = %d
AND lk.meta_value = %s
AND ed.meta_value = %s
ORDER BY CAST((SELECT meta_value FROM {$m} WHERE item_id = it.id AND field_id = %d LIMIT 1) AS UNSIGNED) ASC,
it.id ASC”,
CP_FID_VIDEO_LINKED_INS_KEY,
CP_FID_VIDEO_EDITION,
CP_FORM_VIDEOS,
(string)$insight_key,
(string)$edition,
CP_FID_VIDEO_SEQ
);
$ids = $db->get_col($sql);
if (!$ids) return [];
$out = [];
foreach ($ids as $vid_id) {
$out[] = [
‘item_id’ => (int)$vid_id,
‘sequence’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_SEQ),
‘youtube’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_YT_ADDR),
‘title’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_TITLE),
‘channel’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_CHANNEL),
‘heading’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_HEADING),
‘quote’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_QUOTE),
‘book_text’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_BOOKTEXT),
‘clip_start’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_CLIP_START),
‘clip_end’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_CLIP_END),
‘clip_title’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_CLIP_TITLE),
‘transcript’ => (string)cp_ai_meta_value($vid_id, CP_FID_VIDEO_TRANSCRIPT),
];
}
return $out;
}
function cp_ai_get_section_number_for_chapter($chapter_num) {
if (!CP_FORM_CHAPTER_DEFS || !CP_FID_CHDEF_CHAPTER_NUMBER || !CP_FID_CHDEF_SECTION_NUMBER) return 1;
$db = cp_ai_db();
$i = $db->prefix . ‘frm_items’;
$m = $db->prefix . ‘frm_item_metas’;
$section = $db->get_var($db->prepare(
“SELECT ms2.meta_value
FROM {$i} it
JOIN {$m} ms1 ON ms1.item_id = it.id AND ms1.field_id = %d
JOIN {$m} ms2 ON ms2.item_id = it.id AND ms2.field_id = %d
WHERE it.form_id = %d
AND ms1.meta_value = %s
ORDER BY it.id ASC
LIMIT 1”,
CP_FID_CHDEF_CHAPTER_NUMBER,
CP_FID_CHDEF_SECTION_NUMBER,
CP_FORM_CHAPTER_DEFS,
(string)((int)$chapter_num)
));
return $section ? (int)$section : 1;
}
function cp_ai_mode_for_insight_sequence($sequence) {
$chapter_num = (int)floor((float)$sequence);
$section_num = cp_ai_get_section_number_for_chapter($chapter_num);
return cp_ai_section_to_mode($section_num);
}
/* =========================
ACTIVE CONVERSATION GUARD (light)
========================= */
function cp_ai_get_active_response_for_user($userpass) {
// Uses your Form 320 “status = Live” convention (same as GTKY).
// We do NOT guess field IDs here; the save-memory endpoint already writes Form 320.
// This helper is only used to display a banner and prevent starting a second Insight.
$db = cp_ai_db();
$i = $db->prefix . ‘frm_items’;
$m = $db->prefix . ‘frm_item_metas’;
// These field IDs are in your existing MU save-memory logic. If they differ, align to your MU constants.
$fid_userpass = 7009; // from your earlier notes (Form 320 userpass)
$fid_status = 7057; // from your earlier notes (Form 320 KIND/status logic)
$fid_kind = 7057; // if you store KIND in same field; you already said KIND flip is >= 5
$fid_kindname = 7012; // example — if you store kind name; if not, this will just be empty
$fid_seq = 7125; // response sequence (optional)
$row = $db->get_row($db->prepare(
“SELECT it.id
FROM {$i} it
JOIN {$m} mu ON mu.item_id = it.id AND mu.field_id = %d
JOIN {$m} ms ON ms.item_id = it.id AND ms.field_id = %d
WHERE it.form_id = %d
AND mu.meta_value = %s
AND ms.meta_value = ‘Live’
ORDER BY it.id DESC
LIMIT 1”,
$fid_userpass,
$fid_status,
320,
(string)$userpass
), ARRAY_A);
if (!$row) return null;
return [‘entry_id’ => (int)$row[‘id’]];
}
/* =========================
REST ROUTES
========================= */
add_action(‘rest_api_init’, function () {
register_rest_route(‘cp/v1’, ‘/insight-page-context’, [
‘methods’ => ‘GET’,
‘permission_callback’ => ‘__return_true’,
‘callback’ => ‘cp_ai_insight_page_context_handler’,
]);
register_rest_route(‘cp/v1’, ‘/insight-resume’, [
‘methods’ => ‘GET’,
‘permission_callback’ => ‘__return_true’,
‘callback’ => ‘cp_ai_insight_resume_handler’,
]);
});
function cp_ai_insight_page_context_handler(WP_REST_Request $req) {
$auth = function_exists(‘cp_ai_check_token’) ? cp_ai_check_token($req) : true;
if (is_wp_error($auth)) return $auth;
$userpass = trim((string)$req->get_param(‘userpass’));
$sequence = trim((string)$req->get_param(‘insight’)); // e.g. “1.3”
$edition = trim((string)$req->get_param(‘edition’)); // e.g. “WORKPLACE”
if ($edition === ”) $edition = ‘WORKPLACE’;
if ($sequence === ”) $sequence = ‘1.1’;
if ($userpass === ”) {
return new WP_Error(‘bad_request’, ‘Missing userpass’, [‘status’ => 400]);
}
if (!CP_FORM_INSIGHTS) {
return new WP_Error(‘bad_config’, ‘CP_FORM_INSIGHTS not set’, [‘status’ => 500]);
}
$insight = cp_ai_get_insight_row_by_sequence($sequence);
if (!$insight) {
return new WP_Error(‘not_found’, ‘Insight not found for sequence’, [‘status’ => 404]);
}
$mode = cp_ai_mode_for_insight_sequence($sequence);
$trait_spec = cp_ai_trait_modulation_spec();
$videos = cp_ai_get_videos_for_insight_key($edition, $insight[‘key’]);
$active = cp_ai_get_active_response_for_user($userpass);
// Use the same URLs you already expose elsewhere (chapter page / GTKY)
$out = [
‘ok’ => true,
‘userpass’=> $userpass,
‘edition’ => $edition,
‘insight’ => $insight,
‘videos’ => $videos,
‘mode’ => [
‘name’ => $mode,
‘definition’ => ($mode === ‘Explore’) ? ‘Sense-making; uncertainty allowed.’ :
($mode === ‘Clarify’) ? ‘Distil patterns; meaning without commitment.’ :
($mode === ‘Commit’) ? ‘Choose, prioritise, set direction.’ :
($mode === ‘Execute’) ? ‘Plans, habits, accountability, progress.’ :
($mode === ‘Reflect’) ? ‘Slower, deeper integration of experience.’ : ”,
‘trait_modulation’ => $trait_spec[$mode] ?? [],
],
‘active’ => $active, // for FE banner; do not rely on FE for enforcement
‘api’ => [
‘aiUrl’ => rest_url(‘cp/v1/ai’),
‘saveUrl’ => rest_url(‘cp/v1/save-memory’),
‘resumeUrl’ => rest_url(‘cp/v1/insight-resume’),
],
];
return new WP_REST_Response($out, 200);
}
function cp_ai_insight_resume_handler(WP_REST_Request $req) {
$auth = function_exists(‘cp_ai_check_token’) ? cp_ai_check_token($req) : true;
if (is_wp_error($auth)) return $auth;
$userpass = trim((string)$req->get_param(‘userpass’));
$sequence = trim((string)$req->get_param(‘insight’));
if ($userpass === ” || $sequence === ”) {
return new WP_Error(‘bad_request’, ‘Missing userpass or insight’, [‘status’ => 400]);
}
// Pull latest saved dialogue/summary for this Insight.
// This assumes your save-memory endpoint stores insight_sequence in Form 320 (payload-r JSON or a meta field).
// If your Form 320 storage differs, align this query to your actual schema (we won’t guess here).
$db = cp_ai_db();
$i = $db->prefix . ‘frm_items’;
$m = $db->prefix . ‘frm_item_metas’;
$fid_userpass = 7009; // align to your MU save-memory
$fid_payload = 7181; // your MU defines CP_FIELD_KEY_FEEDBACK as 7181; payload field usage may differ
$form_id = 320;
$row = $db->get_row($db->prepare(
“SELECT it.id
FROM {$i} it
JOIN {$m} mu ON mu.item_id = it.id AND mu.field_id = %d
WHERE it.form_id = %d
AND mu.meta_value = %s
ORDER BY it.id DESC
LIMIT 1″,
$fid_userpass,
$form_id,
(string)$userpass
), ARRAY_A);
// Minimal resume contract — FE will fall back to empty if null
return new WP_REST_Response([
‘ok’ => true,
‘resume’ => [
‘found’ => $row ? true : false,
‘entry_id’ => $row ? (int)$row[‘id’] : 0,
‘summary’ => ”,
‘dialogue’ => ”,
]
], 200);
}
/* =========================
SHORTCODE:
========================= */
add_shortcode(‘cp_ai_insight’, function ($atts = []) {
$atts = shortcode_atts([
‘use_snippet’ => ‘0’
], $atts);
// Mirror your existing pattern: FE injects cpVars + root div.
ob_start(); ?>