RcmEmailAutomation / sa-orchestration /includes /class-sa-llm-provider-simulated.php
NujacsaintS's picture
reseed
f51c224
Raw
History Blame Contribute Delete
12.3 kB
<?php
defined( 'ABSPATH' ) || exit;
/**
* Simulated Strategy LLM provider.
*
* No external API call. Pattern-based heuristic expansion of the trace +
* deterministic audit, written to feel LLM-like (interpretive narrative,
* additional risk identification, suggested next action) without making
* any network request.
*
* Always available as the fallback provider.
*/
class SA_Orch_LLM_Provider_Simulated implements SA_Orch_LLM_Provider_Interface {
public function id(): string { return 'simulated'; }
public function name(): string { return 'Simulated (no API call)'; }
public function requires_api_key(): bool { return false; }
public function review_trace( array $trace, array $deterministic_audit ): array {
$arbs = isset( $trace['arbitrations'] ) ? (array) $trace['arbitrations'] : [];
$links = isset( $trace['plan_links'] ) ? (array) $trace['plan_links'] : [];
$events = isset( $trace['acceptance'] ) ? (array) $trace['acceptance'] : [];
$authority = $trace['closure_authority_valid'] ?? null;
$audit_conf = $deterministic_audit['closure_confidence'] ?? 'n/a';
$needs_hum = ! empty( $deterministic_audit['needs_human_arbitration'] );
$summary = $this->build_narrative( $arbs, $links, $events, $trace, $deterministic_audit );
$additional_risks = $this->identify_additional_risks( $arbs, $links, $events, $trace );
$agreement = $this->determine_agreement( $additional_risks, $deterministic_audit );
$next_action = $this->suggest_next_action( $audit_conf, $trace, $needs_hum, count( $additional_risks ) );
$confidence = $this->compute_confidence( $audit_conf, count( $additional_risks ), $needs_hum );
return [
'summary' => $summary,
'agreement_with_audit' => $agreement,
'additional_risks' => $additional_risks,
'suggested_next_action' => $next_action,
'confidence' => $confidence,
];
}
/* ---------- internals ---------- */
private function build_narrative( $arbs, $links, $events, $trace, $audit ) {
$arb_n = count( $arbs );
$event_n = count( $events );
$closure_basic = $trace['closure_state_basic'] ?? null;
$authority = $trace['closure_authority_valid'] ?? null;
if ( $arb_n === 0 && $event_n === 0 ) {
return "No trace data found for this Demand reference. The system has no record of the Demand passing through arbitration or acceptance — the surface_ref may be wrong, or this Demand has not yet been arbitrated.";
}
$patterns = [];
foreach ( $arbs as $a ) {
if ( ( $a->truth_class ?? null ) === 'derived' ) $patterns['derived'] = true;
if ( ( $a->projection_type ?? null ) === 'scope-direct-no-plan' ) $patterns['no_plan'] = true;
}
if ( $arb_n > 1 ) $patterns['multi_arb'] = true;
if ( $authority === false ) $patterns['agent_self'] = true;
$had_dispute = false;
foreach ( $events as $e ) {
if ( ( $e->event_type ?? null ) === 'acceptance_disputed' ) { $had_dispute = true; break; }
}
if ( $had_dispute ) $patterns['dispute'] = true;
if ( $closure_basic === 'closed' && $authority === true && ! $had_dispute ) $patterns['clean'] = true;
if ( is_string( $closure_basic ) && strpos( $closure_basic, 'open:' ) === 0 ) $patterns['open'] = true;
$bits = [];
// Opening sentence — lead with the dominant pattern
if ( isset( $patterns['agent_self'] ) ) {
$bits[] = "The closure pattern here is concerning: the same role that claimed the result also affirmed acceptance, bypassing the originating stakeholder.";
} elseif ( isset( $patterns['derived'] ) && $event_n === 0 ) {
$bits[] = "The Demand was reconstructed from Plan-side evidence rather than captured at the originating surface. With no acceptance events on record, there is no evidence that the actual stakeholder agrees this Plan addresses their need.";
} elseif ( isset( $patterns['open'] ) && isset( $patterns['dispute'] ) ) {
$bits[] = "The Demand was claimed resolved but disputed; the dispute remains unresolved. The surface state may signal in-progress, but the strategic reality is that the original demand-constraint is unmet.";
} elseif ( isset( $patterns['open'] ) ) {
$bits[] = "The Demand is in-flight: the claim and acceptance steps are not yet complete.";
} elseif ( isset( $patterns['clean'] ) ) {
$bits[] = "This Demand followed a routine path: scoped, executed, and confirmed by the stakeholder.";
} else {
$bits[] = "The Demand chain has " . $arb_n . " arbitration" . ( $arb_n === 1 ? '' : 's' )
. " and " . $event_n . " acceptance event" . ( $event_n === 1 ? '' : 's' ) . " on record.";
}
// Mid-narrative annotations
if ( isset( $patterns['no_plan'] ) && isset( $patterns['clean'] ) ) {
$bits[] = "The Plan layer was bypassed (`scope-direct-no-plan`), suggesting an inline operational fix rather than a tracked work unit. Appropriate for small UX changes, but a recurring pattern would erode the Plan layer's value as a work-tracking signal.";
}
if ( isset( $patterns['multi_arb'] ) ) {
$bits[] = "Multiple arbitrations on this Demand suggest the scope was re-interpreted at least once. Each re-arbitration is an opportunity for meaning to drift from the original demand-constraint.";
}
if ( isset( $patterns['dispute'] ) && ! isset( $patterns['open'] ) ) {
$bits[] = "A dispute appeared earlier in the chain but was followed by an affirmation. Verify the dispute root cause was actually addressed, not merely time-elapsed.";
}
// Closing strategic verdict
if ( ! empty( $audit['needs_human_arbitration'] ) ) {
$bits[] = "Strategy interpretation: this trace requires human arbitration before closure can be trusted. The deterministic audit has flagged this; Strategy Review concurs.";
} elseif ( isset( $patterns['clean'] ) ) {
$bits[] = "Strategy interpretation: routine resolution. No further action required.";
} else {
$bits[] = "Strategy interpretation: defer to the deterministic audit's confidence rating before treating this Demand as resolved.";
}
return implode( ' ', $bits );
}
private function identify_additional_risks( $arbs, $links, $events, $trace ) {
$risks = [];
// Scope-direct-no-plan as a recurring shortcut
foreach ( $arbs as $a ) {
if ( ( $a->projection_type ?? null ) === 'scope-direct-no-plan' ) {
$risks[] = "Pattern shortcut watch: 'scope-direct-no-plan' arbitration may indicate a class of Demand that routinely bypasses Plan tracking. Recommend monitoring its frequency to catch systemic Plan-layer underuse.";
break;
}
}
// Reconstruction-then-affirmation
$has_derived = false;
foreach ( $arbs as $a ) {
if ( ( $a->truth_class ?? null ) === 'derived' ) { $has_derived = true; break; }
}
$has_affirm = false;
foreach ( $events as $e ) {
if ( ( $e->event_type ?? null ) === 'acceptance_affirmed' ) { $has_affirm = true; break; }
}
if ( $has_derived && $has_affirm ) {
$risks[] = "Reconstruction-then-affirmation: the Demand was reconstructed from Plan-side evidence and later affirmed. Verify the affirming actor saw the original Demand intent, not just the Plan they were asked to validate.";
}
// Stale affirmation: long gap between claim and affirm
$claim_time = null;
$affirm_time = null;
foreach ( $events as $e ) {
if ( ( $e->event_type ?? null ) === 'result_claimed' && $claim_time === null ) {
$claim_time = strtotime( (string) $e->observed_at );
}
if ( ( $e->event_type ?? null ) === 'acceptance_affirmed' ) {
$affirm_time = strtotime( (string) $e->observed_at );
}
}
if ( $claim_time && $affirm_time ) {
$delta_days = ( $affirm_time - $claim_time ) / 86400;
if ( $delta_days > 30 ) {
$risks[] = sprintf(
"Stale affirmation: %d days between result_claimed and acceptance_affirmed. The stakeholder's confirmation may not reflect current state.",
(int) round( $delta_days )
);
}
}
// Plan complexity
if ( count( $links ) > 2 ) {
$risks[] = "Plan complexity: this Demand fans out to " . count( $links ) . " plan refs. Verify each sub-plan is individually traceable to the original demand-constraint.";
}
// Long event chain
if ( count( $events ) >= 4 ) {
$risks[] = "State-transition complexity: " . count( $events ) . " acceptance events on this Demand. Each transition is an opportunity for meaning to drift; review for chain coherence.";
}
return $risks;
}
private function determine_agreement( $additional_risks, $audit ) {
// The simulated layer reads the same data — it does not contradict the audit's
// factual findings. Agreement is 'true' when nothing additional is identified;
// 'partial' when extra risk dimensions are added but the audit's findings stand.
if ( count( $additional_risks ) === 0 ) {
return 'true';
}
return 'partial';
}
private function suggest_next_action( $audit_conf, $trace, $needs_hum, $extra_risk_count ) {
if ( $needs_hum ) {
return "Escalate to human arbiter. The deterministic audit has flagged this trace as requiring human review; Strategy Review concurs that closure cannot be trusted without it.";
}
$closure_basic = $trace['closure_state_basic'] ?? null;
if ( $audit_conf === 'high' && $extra_risk_count === 0 ) {
return "No further action needed; routine closure.";
}
if ( $audit_conf === 'high' && $extra_risk_count > 0 ) {
return "Closure is sound. Optionally surface the additional risks above to the relevant stakeholders for awareness; no blocking action required.";
}
if ( $audit_conf === 'medium' ) {
return "Closure is sound but with notable history. Recommend a follow-up audit in 30 days to confirm the resolution held.";
}
if ( $audit_conf === 'low' && $closure_basic === 'closed' ) {
return "Closure is recorded but not authoritative. Drive to stakeholder affirmation before treating as final.";
}
if ( $audit_conf === 'low' && is_string( $closure_basic ) && strpos( $closure_basic, 'open:' ) === 0 ) {
return "Demand is open. Drive to result_claimed and stakeholder affirmation; or escalate if blocked.";
}
if ( $audit_conf === 'n/a' ) {
return "Demand has no acceptance events. Either drive through claim → affirmation, or close as scope-direct-no-plan if no work is owed.";
}
return "Review the deterministic audit's recommendations and the human-arbitration flag before next action.";
}
private function compute_confidence( $audit_conf, $extra_risk_count, $needs_hum ) {
if ( $needs_hum ) return 'low';
if ( $audit_conf === 'high' && $extra_risk_count === 0 ) return 'high';
if ( $audit_conf === 'high' && $extra_risk_count <= 2 ) return 'medium';
if ( $audit_conf === 'medium' ) return 'medium';
if ( $audit_conf === 'n/a' ) return 'low';
return 'low';
}
}