8010245981
Default ayanamsa to Raman with true_pushya support, attach governed Raman packets, restore Path C questionnaires and eight-method verification copy, and keep unique-minute confirmation blocked. Co-authored-by: Cursor <cursoragent@cursor.com>
710 lines
31 KiB
TypeScript
710 lines
31 KiB
TypeScript
import { z } from "zod";
|
||
import { consultationEvidenceCategoryValues, createConsultationPlan, type ConsultationPlan } from "../lib/consultation-plan.ts";
|
||
import { consultationThemeValues, projectConsultationWorkflowRequest } from "../lib/consultation-workflow-request.ts";
|
||
|
||
export const consultationInputSchema = z.object({
|
||
year: z.number().int().min(1900).max(2100),
|
||
month: z.number().int().min(1).max(12),
|
||
day: z.number().int().min(1).max(31),
|
||
hour: z.number().int().min(0).max(23),
|
||
minute: z.number().int().min(0).max(59),
|
||
lat: z.number().min(-90).max(90),
|
||
lon: z.number().min(-180).max(180),
|
||
tz: z.number().min(-12).max(14),
|
||
city: z.string().trim().min(1).max(120),
|
||
question: z.string().trim().min(1).max(500),
|
||
theme: z.enum(consultationThemeValues),
|
||
entryMode: z.enum(["direct_chart", "rectification"]).default("direct_chart"),
|
||
});
|
||
export type ConsultationInput = z.infer<typeof consultationInputSchema>;
|
||
type JsonRecord = Record<string, unknown>;
|
||
|
||
const workflowConsumerContextSchema = z.object({
|
||
route: z.string().min(1),
|
||
core_status: z.enum(["ready", "degraded", "blocked"]),
|
||
available_layers: z.array(z.string()),
|
||
missing_route_layers: z.array(z.string()),
|
||
hard_blockers: z.array(z.string()),
|
||
technique_truth: z.record(z.unknown()).optional(),
|
||
answer_policy: z.object({
|
||
can_answer_direction: z.boolean(),
|
||
can_answer_precise_timing: z.boolean(),
|
||
}).passthrough(),
|
||
}).passthrough();
|
||
|
||
export const consultationWorkflowResponseSchema = z.object({
|
||
success: z.boolean(),
|
||
chart: z.record(z.unknown()),
|
||
routing: z.record(z.unknown()),
|
||
consumer_context: workflowConsumerContextSchema,
|
||
}).passthrough();
|
||
|
||
function record(value: unknown): JsonRecord {
|
||
return value && typeof value === "object" && !Array.isArray(value) ? value as JsonRecord : {};
|
||
}
|
||
|
||
const apiBase = process.env.JYOTISH_API_BASE ?? "http://127.0.0.1:5200";
|
||
|
||
/**
|
||
* Closed vocabulary for why a consultation workflow call failed.
|
||
*
|
||
* The upstream error text is a provider payload and must not reach logs, so
|
||
* the failure is classified here into a machine code that observability can
|
||
* record without carrying free-form content.
|
||
*/
|
||
export const consultationWorkflowFailureCodes = [
|
||
"workflow_bad_request",
|
||
"workflow_forbidden",
|
||
"workflow_unsupported_media_type",
|
||
"workflow_rate_limited",
|
||
"workflow_queue_full",
|
||
"workflow_server_error",
|
||
"workflow_http_error",
|
||
"workflow_empty_response",
|
||
"workflow_contract_invalid",
|
||
"workflow_timeout",
|
||
"workflow_aborted",
|
||
"workflow_unreachable",
|
||
] as const;
|
||
|
||
export type ConsultationWorkflowFailureCode = typeof consultationWorkflowFailureCodes[number];
|
||
|
||
export class ConsultationWorkflowError extends Error {
|
||
readonly code: ConsultationWorkflowFailureCode;
|
||
|
||
constructor(code: ConsultationWorkflowFailureCode, message: string) {
|
||
super(message);
|
||
this.name = "ConsultationWorkflowError";
|
||
this.code = code;
|
||
}
|
||
}
|
||
|
||
function httpFailureCode(status: number): ConsultationWorkflowFailureCode {
|
||
if (status === 400) return "workflow_bad_request";
|
||
if (status === 403) return "workflow_forbidden";
|
||
if (status === 415) return "workflow_unsupported_media_type";
|
||
if (status === 429) return "workflow_rate_limited";
|
||
if (status === 503) return "workflow_queue_full";
|
||
if (status >= 500) return "workflow_server_error";
|
||
return "workflow_http_error";
|
||
}
|
||
|
||
export function consultationWorkflowFailureCode(error: unknown): ConsultationWorkflowFailureCode | undefined {
|
||
if (error instanceof ConsultationWorkflowError) return error.code;
|
||
if (error instanceof DOMException && error.name === "TimeoutError") return "workflow_timeout";
|
||
if (error instanceof DOMException && error.name === "AbortError") return "workflow_aborted";
|
||
return undefined;
|
||
}
|
||
|
||
export async function runConsultationWorkflow(
|
||
input: ConsultationInput,
|
||
options?: { foreground?: boolean; signal?: AbortSignal; plan?: ConsultationPlan; requestId?: string },
|
||
) {
|
||
const { entryMode, question, theme, ...workflowInput } = input;
|
||
const plan = options?.plan ?? createConsultationPlan({ userIntent: question, theme });
|
||
const workflowRequest = projectConsultationWorkflowRequest(question, theme, plan);
|
||
const timeout = AbortSignal.timeout(90_000);
|
||
const signal = options?.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
||
const response = await fetch(`${apiBase}/api/consultation_workflow`, {
|
||
method: "POST",
|
||
headers: { "content-type": "application/json" },
|
||
body: JSON.stringify({
|
||
...workflowInput,
|
||
// Correlates this call with the API access log when a run fails.
|
||
...(options?.requestId ? { request_id: options.requestId } : {}),
|
||
entry_mode: entryMode,
|
||
question: workflowRequest.question,
|
||
question_text: workflowRequest.question,
|
||
theme: workflowRequest.themes,
|
||
defer_optional_external_evidence: options?.foreground === true,
|
||
plan_version: workflowRequest.plan_version,
|
||
strict_workflow_route: workflowRequest.strictWorkflowRoute,
|
||
required_layers: workflowRequest.requiredLayers,
|
||
claim_boundary: workflowRequest.claimBoundary,
|
||
plan_depth: workflowRequest.depth,
|
||
requested_domains: workflowRequest.requested_domains,
|
||
timing_horizon: workflowRequest.timing_horizon,
|
||
precision_boundary: workflowRequest.precision_boundary,
|
||
required_evidence_categories: workflowRequest.required_evidence_categories,
|
||
}),
|
||
signal,
|
||
});
|
||
const data = await response.json().catch(() => null);
|
||
if (!response.ok || !data) {
|
||
throw new ConsultationWorkflowError(
|
||
response.ok ? "workflow_empty_response" : httpFailureCode(response.status),
|
||
data?.error || data?.message || `Jyotish API returned ${response.status}`,
|
||
);
|
||
}
|
||
const parsed = consultationWorkflowResponseSchema.safeParse(data);
|
||
if (!parsed.success) {
|
||
throw new ConsultationWorkflowError(
|
||
"workflow_contract_invalid",
|
||
"Jyotish API returned an incomplete consultation contract",
|
||
);
|
||
}
|
||
return parsed.data;
|
||
}
|
||
|
||
export function consultationWorkflowReceipt(data: JsonRecord) {
|
||
const consumerContext = workflowConsumerContextSchema.parse(data.consumer_context);
|
||
return {
|
||
route: consumerContext.route,
|
||
status: consumerContext.core_status,
|
||
preciseTiming: consumerContext.answer_policy.can_answer_precise_timing ? "allowed" : "blocked",
|
||
missingLayers: consumerContext.missing_route_layers.join(",") || "none",
|
||
techniqueTruth: String(record(consumerContext.technique_truth).status || "unknown"),
|
||
evidenceStatus: record(consumerContext.commercial_evidence_status),
|
||
};
|
||
}
|
||
|
||
type ModelOutputValue = string | number | boolean | null | ModelOutputValue[] | { [key: string]: ModelOutputValue };
|
||
|
||
const modelOutputEvidenceSchema = z.unknown();
|
||
|
||
export const consultationEvidencePacketSchema = z.object({
|
||
packet_version: z.literal("consultation-evidence-packet-v2"),
|
||
question: z.string().optional(),
|
||
route: z.string(),
|
||
status: z.enum(["ready", "degraded", "blocked"]),
|
||
evidence_contract: z.object({
|
||
available_layers: modelOutputEvidenceSchema.optional(),
|
||
missing_route_layers: modelOutputEvidenceSchema.optional(),
|
||
hard_blockers: modelOutputEvidenceSchema.optional(),
|
||
answer_policy: modelOutputEvidenceSchema.optional(),
|
||
user_facing_limitation: modelOutputEvidenceSchema.optional(),
|
||
technique_audit_table: modelOutputEvidenceSchema.optional(),
|
||
varga_spectrum: modelOutputEvidenceSchema.optional(),
|
||
western_spectrum: modelOutputEvidenceSchema.optional(),
|
||
must_use_layers: modelOutputEvidenceSchema.optional(),
|
||
}),
|
||
claim_cards: z.array(z.object({
|
||
category: z.enum(consultationEvidenceCategoryValues),
|
||
source: z.literal("server_chart").or(z.literal("server_workflow")),
|
||
evidence: modelOutputEvidenceSchema,
|
||
})),
|
||
presentation: z.object({
|
||
template: z.literal("skill_level_2"),
|
||
required_blocks: z.array(z.string()),
|
||
}),
|
||
rectification: z.object({ boundary: z.string() }),
|
||
}).strict();
|
||
|
||
export type ConsultationEvidencePacket = z.infer<typeof consultationEvidencePacketSchema>;
|
||
|
||
function normalizeEvidenceKey(key: string) {
|
||
return key.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
||
}
|
||
|
||
function boundedPrimitive(value: unknown): ModelOutputValue | undefined {
|
||
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
|
||
if (typeof value === "string") return value.length > 800 ? `${value.slice(0, 797)}...` : value;
|
||
return undefined;
|
||
}
|
||
|
||
const astrologyEntityKeys = new Set([
|
||
"sun", "moon", "mars", "mercury", "jupiter", "venus", "saturn", "rahu", "ketu",
|
||
"aries", "taurus", "gemini", "cancer", "leo", "virgo", "libra", "scorpio",
|
||
"sagittarius", "capricorn", "aquarius", "pisces",
|
||
"first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth",
|
||
"ninth", "tenth", "eleventh", "twelfth",
|
||
]);
|
||
|
||
function isAstrologyEntityKey(key: string) {
|
||
const normalized = normalizeEvidenceKey(key);
|
||
return astrologyEntityKeys.has(normalized)
|
||
|| /^h(?:ouse)?\d{1,2}$/.test(normalized)
|
||
|| /^\d{1,2}$/.test(normalized);
|
||
}
|
||
|
||
function projectAllowlistedTree(
|
||
value: unknown,
|
||
allowedKeys: ReadonlySet<string>,
|
||
options: Readonly<{ allowAstrologyEntityKeys?: boolean; depth?: number }> = {},
|
||
): ModelOutputValue | undefined {
|
||
const primitive = boundedPrimitive(value);
|
||
if (primitive !== undefined) return primitive;
|
||
const depth = options.depth ?? 0;
|
||
if (depth > 4) return undefined;
|
||
if (Array.isArray(value)) {
|
||
return value.slice(0, 24)
|
||
.map((item) => projectAllowlistedTree(item, allowedKeys, { ...options, depth: depth + 1 }))
|
||
.filter((item): item is ModelOutputValue => item !== undefined);
|
||
}
|
||
if (!value || typeof value !== "object") return undefined;
|
||
const output: { [key: string]: ModelOutputValue } = {};
|
||
for (const [key, item] of Object.entries(value)) {
|
||
const normalized = normalizeEvidenceKey(key);
|
||
if (!allowedKeys.has(normalized) && !(options.allowAstrologyEntityKeys && isAstrologyEntityKey(key))) continue;
|
||
const projected = projectAllowlistedTree(item, allowedKeys, { ...options, depth: depth + 1 });
|
||
if (projected !== undefined) output[key] = projected;
|
||
if (Object.keys(output).length >= 24) break;
|
||
}
|
||
return output;
|
||
}
|
||
|
||
const natalFoundationKeys = new Set([
|
||
"ascendant", "planets", "houses", "shadbala", "ashtakavarga", "sarvashtakavarga",
|
||
"name", "planet", "sign", "signidx", "degree", "degreeinsign", "degreeraw", "lon",
|
||
"nakshatra", "pada", "lord", "house", "number", "occupants", "aspects", "retrograde",
|
||
"isretrograde", "dignity", "status", "total", "totalrupa", "rupa", "ratio", "required",
|
||
"rank", "strength", "score", "value", "sav", "bav", "points", "count", "housescores",
|
||
"components", "matrix", "matrixshape", "signs", "sources", "source", "method", "boundary",
|
||
"functionalbeneficmalefic", "functionalbenefics", "functionalmalefics", "functionalneutrals",
|
||
"yogakarakas", "ownedhouses", "ascendantsign", "effectonconfidence",
|
||
"yogas", "category", "arudhapadas", "padas", "a10", "a7", "ul", "al", "gulika", "mandi", "kakshya",
|
||
"kpcusps", "cusps", "sublord", "star", "starlord", "claimboundary", "reason",
|
||
]);
|
||
|
||
const timingKeys = new Set([
|
||
"dasha", "dashaboundaries", "dashasubperiods", "narayanadasha", "status", "current", "next", "mahadasha",
|
||
"antardasha", "pratyantardasha", "currentdasha", "period", "dashaperiod", "periods", "timeline",
|
||
"boundaries", "boundarycount", "start", "end", "startyear", "endyear", "iscurrent",
|
||
"activationdescription", "sign", "lord", "planet", "name", "strength", "score", "source",
|
||
"method", "confidence", "confidencecap", "summary", "conclusion", "fingerprint",
|
||
"charadasha", "transits", "sadesati", "triggers", "triggercount", "searchperiod", "window", "sequence", "durationyears",
|
||
"fromage", "toage", "phase", "phasename", "moonsign", "saturnsign", "intensity", "active",
|
||
"date", "target", "kind", "orb", "boundary", "claimboundary",
|
||
]);
|
||
|
||
const validationKeys = new Set([
|
||
"techniquetruth", "referencetransparency", "shadbalaboundary", "status", "state", "verified",
|
||
"executed", "blocked", "notapplicable", "available", "coverage", "source", "sources", "method",
|
||
"methods", "technique", "techniques", "reason", "reasons", "boundary", "confidence",
|
||
"confidencecap", "summary", "conclusion", "publiccontextonly", "similarpubliccases",
|
||
"highsimilaritypublicreferencesavailable", "requesteduncovereddomains", "timingstate",
|
||
"partialmatch", "narayanastatus", "transitstatus", "productiontuningallowed", "nomajorityvote",
|
||
"vedastrocrosscheck", "officialclosurestate", "officialclosurereason", "natal", "ascendant",
|
||
]);
|
||
|
||
const domainDetailsKeys = new Set([
|
||
"source", "derived", "fragment", "sign", "signidx", "planets", "planetslabel", "lord", "house",
|
||
"score", "level", "clues", "marriagecount", "d9marriagequality", "headline", "strengths", "risks",
|
||
"boundaries", "monthlyframe", "primarystate", "manifestationmode", "frictionsource", "timeconfidence",
|
||
"current", "summary", "interpretation", "description", "note", "status", "confidence",
|
||
]);
|
||
|
||
function projectVargaChart(value: unknown): ModelOutputValue | undefined {
|
||
const chart = record(value);
|
||
const planetsIn = record(chart.planets);
|
||
const planets: { [key: string]: ModelOutputValue } = {};
|
||
for (const [name, sign] of Object.entries(planetsIn)) {
|
||
const projected = boundedPrimitive(sign);
|
||
if (projected !== undefined) planets[name] = projected;
|
||
}
|
||
const output: { [key: string]: ModelOutputValue } = {};
|
||
const lagna = boundedPrimitive(chart.lagna);
|
||
if (lagna !== undefined) output.lagna = lagna;
|
||
if (Object.keys(planets).length) output.planets = planets;
|
||
for (const key of ["name", "meaning", "boundary"] as const) {
|
||
const projected = boundedPrimitive(chart[key]);
|
||
if (projected !== undefined) output[key] = projected;
|
||
}
|
||
return Object.keys(output).length ? output : undefined;
|
||
}
|
||
|
||
function projectVargaGroup(value: unknown): ModelOutputValue | undefined {
|
||
const group = record(value);
|
||
const output: { [key: string]: ModelOutputValue } = {};
|
||
for (const [code, chart] of Object.entries(group)) {
|
||
const projected = projectVargaChart(chart);
|
||
if (projected !== undefined) output[code] = projected;
|
||
}
|
||
return Object.keys(output).length ? output : undefined;
|
||
}
|
||
|
||
function projectVargaSpectrum(value: unknown): ModelOutputValue | undefined {
|
||
const spectrum = record(value);
|
||
if (!Object.keys(spectrum).length) return undefined;
|
||
const output: { [key: string]: ModelOutputValue } = {};
|
||
for (const key of ["status", "mode", "boundary"] as const) {
|
||
const projected = boundedPrimitive(spectrum[key]);
|
||
if (projected !== undefined) output[key] = projected;
|
||
}
|
||
const counts = record(spectrum.counts);
|
||
if (Object.keys(counts).length) {
|
||
const compact: { [key: string]: ModelOutputValue } = {};
|
||
for (const [key, item] of Object.entries(counts)) {
|
||
const projected = boundedPrimitive(item);
|
||
if (projected !== undefined) compact[key] = projected;
|
||
}
|
||
if (Object.keys(compact).length) output.counts = compact;
|
||
}
|
||
for (const group of ["formal", "research_dn", "extended"] as const) {
|
||
const projected = projectVargaGroup(spectrum[group]);
|
||
if (projected !== undefined) output[group] = projected;
|
||
}
|
||
if (Array.isArray(spectrum.blocked)) {
|
||
output.blocked = spectrum.blocked.filter((item): item is string => typeof item === "string").slice(0, 60);
|
||
}
|
||
return Object.keys(output).length ? output : undefined;
|
||
}
|
||
|
||
function projectTechniqueAudit(value: unknown): ModelOutputValue | undefined {
|
||
if (!Array.isArray(value)) return undefined;
|
||
const rows = value.slice(0, 80).map((item) => {
|
||
const row = record(item);
|
||
const projected: { [key: string]: ModelOutputValue } = {};
|
||
for (const key of ["technique", "status", "system", "boundary", "effect_on_confidence"] as const) {
|
||
const scalar = boundedPrimitive(row[key]);
|
||
if (scalar !== undefined) projected[key] = scalar;
|
||
}
|
||
for (const key of ["key_functional_benefics", "key_functional_malefics"] as const) {
|
||
if (!Array.isArray(row[key])) continue;
|
||
const names = row[key]
|
||
.filter((entry): entry is string => typeof entry === "string" && entry.length > 0)
|
||
.slice(0, 9);
|
||
if (names.length) projected[key] = names;
|
||
}
|
||
return projected;
|
||
}).filter((row) => Object.keys(row).length > 0);
|
||
return rows.length ? rows : undefined;
|
||
}
|
||
|
||
function projectWesternTechnique(value: unknown): ModelOutputValue | undefined {
|
||
const row = record(value);
|
||
if (!Object.keys(row).length) return undefined;
|
||
const projected: { [key: string]: ModelOutputValue } = {};
|
||
for (const key of [
|
||
"status", "boundary", "target_date", "start_date", "end_date", "target_year", "return_date", "event_count",
|
||
] as const) {
|
||
const scalar = boundedPrimitive(row[key]);
|
||
if (scalar !== undefined) projected[key] = scalar;
|
||
}
|
||
if (Array.isArray(row.aspects)) {
|
||
const aspects = row.aspects.slice(0, 6).map((item) => {
|
||
const aspect = record(item);
|
||
const compact: { [key: string]: ModelOutputValue } = {};
|
||
for (const key of ["transit_planet", "natal_point", "aspect", "orb"] as const) {
|
||
const scalar = boundedPrimitive(aspect[key]);
|
||
if (scalar !== undefined) compact[key] = scalar;
|
||
}
|
||
return compact;
|
||
}).filter((item) => Object.keys(item).length);
|
||
if (aspects.length) projected.aspects = aspects;
|
||
}
|
||
if (Array.isArray(row.windows)) {
|
||
const windows = row.windows.slice(0, 6).map((item) => {
|
||
const window = record(item);
|
||
const compact: { [key: string]: ModelOutputValue } = {};
|
||
for (const key of ["transit_planet", "natal_point", "aspect", "start_date", "end_date", "min_orb"] as const) {
|
||
const scalar = boundedPrimitive(window[key]);
|
||
if (scalar !== undefined) compact[key] = scalar;
|
||
}
|
||
return compact;
|
||
}).filter((item) => Object.keys(item).length);
|
||
if (windows.length) projected.windows = windows;
|
||
}
|
||
return Object.keys(projected).length ? projected : undefined;
|
||
}
|
||
|
||
function projectWesternSpectrum(value: unknown): ModelOutputValue | undefined {
|
||
const spectrum = record(value);
|
||
if (!Object.keys(spectrum).length) return undefined;
|
||
const output: { [key: string]: ModelOutputValue } = {};
|
||
for (const key of ["status", "zodiac", "house_system", "boundary"] as const) {
|
||
const projected = boundedPrimitive(spectrum[key]);
|
||
if (projected !== undefined) output[key] = projected;
|
||
}
|
||
const natal = record(spectrum.natal);
|
||
if (Object.keys(natal).length) {
|
||
const compact: { [key: string]: ModelOutputValue } = {};
|
||
for (const [key, item] of Object.entries(natal)) {
|
||
const projected = boundedPrimitive(item);
|
||
if (projected !== undefined) compact[key] = projected;
|
||
}
|
||
if (Object.keys(compact).length) output.natal = compact;
|
||
}
|
||
const techniques = record(spectrum.techniques);
|
||
if (Object.keys(techniques).length) {
|
||
const compact: { [key: string]: ModelOutputValue } = {};
|
||
for (const [name, layer] of Object.entries(techniques)) {
|
||
const projected = projectWesternTechnique(layer);
|
||
if (projected !== undefined) compact[name] = projected;
|
||
}
|
||
if (Object.keys(compact).length) output.techniques = compact;
|
||
}
|
||
return Object.keys(output).length ? output : undefined;
|
||
}
|
||
|
||
function projectNatalFoundation(context: ReturnType<typeof toAgentConsultationContext>) {
|
||
const foundation = projectAllowlistedTree({
|
||
ascendant: context.chart.ascendant,
|
||
planets: context.chart.planets,
|
||
houses: context.chart.houses,
|
||
shadbala: context.chart.shadbala,
|
||
ashtakavarga: context.chart.ashtakavarga,
|
||
functional_benefic_malefic: context.local_layers.functional_benefic_malefic,
|
||
yogas: context.local_layers.yogas,
|
||
arudha_padas: context.local_layers.arudha_padas,
|
||
gulika: context.local_layers.gulika,
|
||
kakshya: context.local_layers.kakshya,
|
||
kp_cusps: context.local_layers.kp_cusps,
|
||
}, natalFoundationKeys, { allowAstrologyEntityKeys: true });
|
||
const output = record(foundation);
|
||
const varga = projectVargaSpectrum(context.local_layers.varga_spectrum);
|
||
if (varga !== undefined) output.varga_spectrum = varga;
|
||
return Object.keys(output).length ? output : undefined;
|
||
}
|
||
|
||
function projectDomainEvidence(value: unknown): ModelOutputValue {
|
||
const theme = record(value);
|
||
const output: { [key: string]: ModelOutputValue } = {};
|
||
for (const key of ["theme", "summary", "narrative", "strength"] as const) {
|
||
const projected = boundedPrimitive(theme[key]);
|
||
if (projected !== undefined) output[key] = projected;
|
||
}
|
||
if (Array.isArray(theme.recommendations)) {
|
||
output.recommendations = theme.recommendations.slice(0, 12)
|
||
.map(boundedPrimitive)
|
||
.filter((item): item is ModelOutputValue => item !== undefined);
|
||
}
|
||
if (Array.isArray(theme.evidence)) {
|
||
output.evidence = theme.evidence.slice(0, 24).map((rawItem) => {
|
||
const item = record(rawItem);
|
||
const projected: { [key: string]: ModelOutputValue } = {};
|
||
for (const key of ["technique", "chart", "conclusion", "sentiment", "strength"] as const) {
|
||
const scalar = boundedPrimitive(item[key]);
|
||
if (scalar !== undefined) projected[key] = scalar;
|
||
}
|
||
const details = projectAllowlistedTree(item.details, domainDetailsKeys, { allowAstrologyEntityKeys: true });
|
||
if (details !== undefined) projected.details = details;
|
||
return projected;
|
||
});
|
||
}
|
||
if (Array.isArray(theme.conflicts)) {
|
||
const conflictKeys = new Set(["techniquea", "conclusiona", "techniqueb", "conclusionb", "resolution", "reasoning", "winner"]);
|
||
output.conflicts = theme.conflicts.slice(0, 12)
|
||
.map((item) => projectAllowlistedTree(item, conflictKeys))
|
||
.filter((item): item is ModelOutputValue => item !== undefined);
|
||
}
|
||
const timing = projectAllowlistedTree(theme.timing, timingKeys, { allowAstrologyEntityKeys: true });
|
||
if (timing !== undefined) output.timing = timing;
|
||
return output;
|
||
}
|
||
|
||
function projectTimingEvidence(context: ReturnType<typeof toAgentConsultationContext>) {
|
||
const timing = projectAllowlistedTree({
|
||
dasha: context.chart.dasha,
|
||
dasha_sub_periods: context.local_layers.dasha_sub_periods,
|
||
narayana_dasha: context.local_layers.narayana_dasha,
|
||
chara_dasha: context.local_layers.chara_dasha,
|
||
transits: context.local_layers.transits,
|
||
}, timingKeys, { allowAstrologyEntityKeys: true });
|
||
const output = record(timing);
|
||
const western = projectWesternSpectrum(context.evidence_contract.western_spectrum);
|
||
if (western !== undefined) output.western_spectrum = western;
|
||
return Object.keys(output).length ? output : undefined;
|
||
}
|
||
|
||
function projectValidationEvidence(context: ReturnType<typeof toAgentConsultationContext>) {
|
||
const validation = projectAllowlistedTree({
|
||
technique_truth: context.evidence_contract.technique_truth,
|
||
reference_transparency: context.reference_transparency,
|
||
shadbala_boundary: context.local_layers.shadbala_boundary,
|
||
vedastro_cross_check: context.vedastro_cross_check,
|
||
}, validationKeys, { allowAstrologyEntityKeys: true });
|
||
const output = record(validation);
|
||
const audit = projectTechniqueAudit(context.evidence_contract.technique_audit_table);
|
||
if (audit !== undefined) output.technique_audit_table = audit;
|
||
return Object.keys(output).length ? output : undefined;
|
||
}
|
||
|
||
function stringList(value: unknown, limit = 24) {
|
||
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string").slice(0, limit) : [];
|
||
}
|
||
|
||
const SHARED_MUST_USE_LAYERS = [
|
||
"Formal Vargas D1–D60",
|
||
"Functional Benefic/Malefic",
|
||
"Yogas",
|
||
"Vimshottari sub-periods",
|
||
"Narayana Dasha",
|
||
"Shadbala components",
|
||
"Ashtakavarga",
|
||
"Arudha / UL / A10",
|
||
"Western natal (tropical)",
|
||
"Transits / Sade Sati",
|
||
"VedAstro Cloud State",
|
||
] as const;
|
||
|
||
const DOMAIN_MUST_USE_LAYERS: Readonly<Record<string, readonly string[]>> = {
|
||
career: ["Formal Vargas D1–D60", "Arudha / UL / A10", "Narayana Dasha", "Chara Dasha"],
|
||
marriage: ["Formal Vargas D1–D60", "Arudha / UL / A10"],
|
||
wealth: ["Formal Vargas D1–D60", "Ashtakavarga"],
|
||
timing: ["Narayana Dasha", "Vimshottari sub-periods", "Transits / Sade Sati", "Chara Dasha"],
|
||
annual: ["Narayana Dasha", "Vimshottari sub-periods", "Chara Dasha"],
|
||
health: ["Formal Vargas D1–D60", "Shadbala components"],
|
||
};
|
||
|
||
function mustUseExecutedLayers(route: string, audit: unknown): string[] {
|
||
const rows = Array.isArray(audit) ? audit : [];
|
||
const executed = new Set(
|
||
rows
|
||
.map((item) => record(item))
|
||
.filter((row) => row.status === "executed")
|
||
.map((row) => String(row.technique || "")),
|
||
);
|
||
const wanted = [...SHARED_MUST_USE_LAYERS, ...(DOMAIN_MUST_USE_LAYERS[route] ?? [])];
|
||
const output: string[] = [];
|
||
const seen = new Set<string>();
|
||
for (const name of wanted) {
|
||
if (!executed.has(name) || seen.has(name)) continue;
|
||
seen.add(name);
|
||
output.push(name);
|
||
}
|
||
return output.slice(0, 24);
|
||
}
|
||
|
||
function projectEvidenceContract(contract: ReturnType<typeof toAgentConsultationContext>["evidence_contract"]) {
|
||
const policy = record(contract.answer_policy);
|
||
const audit = projectTechniqueAudit(contract.technique_audit_table);
|
||
const varga = projectVargaSpectrum(contract.varga_spectrum);
|
||
const western = projectWesternSpectrum(contract.western_spectrum);
|
||
const mustUse = mustUseExecutedLayers(String(contract.route || ""), contract.technique_audit_table);
|
||
return {
|
||
available_layers: stringList(contract.available_layers, 80),
|
||
missing_route_layers: stringList(contract.missing_route_layers, 40),
|
||
hard_blockers: stringList(contract.hard_blockers),
|
||
answer_policy: {
|
||
can_answer_direction: policy.can_answer_direction === true,
|
||
can_answer_precise_timing: policy.can_answer_precise_timing === true,
|
||
...(typeof policy.should_lead_with_limitations === "boolean"
|
||
? { should_lead_with_limitations: policy.should_lead_with_limitations }
|
||
: {}),
|
||
},
|
||
...(typeof contract.user_facing_limitation === "string"
|
||
? { user_facing_limitation: boundedPrimitive(contract.user_facing_limitation) }
|
||
: {}),
|
||
...(audit !== undefined ? { technique_audit_table: audit } : {}),
|
||
...(varga !== undefined ? { varga_spectrum: varga } : {}),
|
||
...(western !== undefined ? { western_spectrum: western } : {}),
|
||
...(mustUse.length > 0 ? { must_use_layers: mustUse } : {}),
|
||
};
|
||
}
|
||
|
||
function hasModelOutputEvidence(value: unknown) {
|
||
return value !== undefined && value !== null && (typeof value !== "object" || Object.keys(value as object).length > 0);
|
||
}
|
||
|
||
/**
|
||
* Keep the complete workflow context for application/audit use, but expose only
|
||
* bounded, server-selected evidence to the model. This is not a claim generator:
|
||
* every card is a projection of an existing server result.
|
||
*/
|
||
export function toModelOutput(context: ReturnType<typeof toAgentConsultationContext>, plan?: ConsultationPlan): ConsultationEvidencePacket {
|
||
const contract = context.evidence_contract;
|
||
const requiredCategories = new Set(plan?.requiredEvidenceCategories ?? consultationEvidenceCategoryValues);
|
||
const cards = [
|
||
{
|
||
category: "natal_foundation" as const,
|
||
source: "server_chart" as const,
|
||
evidence: projectNatalFoundation(context),
|
||
},
|
||
{
|
||
category: "domain" as const,
|
||
source: "server_workflow" as const,
|
||
evidence: projectDomainEvidence(context.thematic_evidence),
|
||
},
|
||
{
|
||
category: "timing" as const,
|
||
source: "server_workflow" as const,
|
||
evidence: projectTimingEvidence(context),
|
||
},
|
||
{
|
||
category: "validation" as const,
|
||
source: "server_workflow" as const,
|
||
evidence: projectValidationEvidence(context),
|
||
},
|
||
]
|
||
.filter((card) => requiredCategories.has(card.category))
|
||
.filter((card) => hasModelOutputEvidence(card.evidence));
|
||
|
||
return consultationEvidencePacketSchema.parse({
|
||
packet_version: "consultation-evidence-packet-v2",
|
||
question: context.question,
|
||
route: contract.route,
|
||
status: contract.core_status,
|
||
evidence_contract: projectEvidenceContract(contract),
|
||
claim_cards: cards,
|
||
presentation: {
|
||
template: "skill_level_2",
|
||
required_blocks: [
|
||
"raw_structure",
|
||
"raman_six_step",
|
||
"yoga_table",
|
||
"timing",
|
||
"synthesis",
|
||
"technique_audit_table",
|
||
"modern_wrap",
|
||
],
|
||
},
|
||
rectification: { boundary: context.rectification.boundary },
|
||
});
|
||
}
|
||
|
||
export function toAgentConsultationContext(data: JsonRecord) {
|
||
const chart = record(data.chart);
|
||
const modules = record(chart.modules);
|
||
const routing = record(data.routing);
|
||
const thematicReport = record(data.thematic_report);
|
||
const themes = record(thematicReport.themes);
|
||
const primaryTheme = String(routing.primary_theme || routing.question_type || "general");
|
||
const selectedTheme = record(themes[primaryTheme]);
|
||
const rectification = record(data.rectification);
|
||
const consumerContext = record(data.consumer_context);
|
||
return {
|
||
success: data.success === true,
|
||
question: data.question,
|
||
routing,
|
||
consumer_context: consumerContext,
|
||
evidence_contract: {
|
||
route: consumerContext.route,
|
||
core_status: consumerContext.core_status,
|
||
available_layers: consumerContext.available_layers,
|
||
missing_route_layers: consumerContext.missing_route_layers,
|
||
hard_blockers: consumerContext.hard_blockers,
|
||
technique_truth: consumerContext.technique_truth,
|
||
commercial_evidence_status: consumerContext.commercial_evidence_status,
|
||
answer_policy: consumerContext.answer_policy,
|
||
user_facing_limitation: consumerContext.user_facing_limitation,
|
||
technique_audit_table: consumerContext.technique_audit_table,
|
||
varga_spectrum: consumerContext.varga_spectrum,
|
||
western_spectrum: consumerContext.western_spectrum,
|
||
},
|
||
chart: {
|
||
birth: chart.birth, ascendant: chart.ascendant, planets: chart.planets, houses: chart.houses,
|
||
dasha: chart.dasha, shadbala: chart.shadbala, ashtakavarga: chart.ashtakavarga, yogas: chart.yogas,
|
||
},
|
||
local_layers: {
|
||
shadbala_boundary: "Shadbala is a locally consistent relative-strength layer; external component-level absolute parity remains partial and must not be stated as closed.",
|
||
varga_full: modules.varga_full, varga_spectrum: modules.varga_spectrum, arudha_padas: modules.arudha_padas, ashtakavarga: modules.ashtakavarga,
|
||
// `modules.dasha_boundaries` was read here for months and never written by the engine, so
|
||
// every answer was composed without sub-period boundaries while the receipt still reported
|
||
// precise timing as allowed. The field now names the layer the engine actually attaches.
|
||
dasha_sub_periods: modules.dasha_sub_periods, narayana_dasha: modules.narayana_dasha, gulika: modules.gulika,
|
||
kakshya: modules.kakshya,
|
||
yogas: modules.yogas || chart.yogas,
|
||
kp_cusps: modules.kp_cusps,
|
||
chara_dasha: modules.chara_dasha,
|
||
transits: modules.transits,
|
||
functional_benefic_malefic: modules.functional_benefic_malefic
|
||
|| record(data.machine_evidence_packet).functional_benefic_malefic,
|
||
},
|
||
rectification: {
|
||
boundary: "not_auto_rectified", summary: rectification.summary,
|
||
enabled_vargas: rectification.enabled_vargas, lagna_boundary: rectification.lagna_boundary,
|
||
},
|
||
candidate_range: record(data.candidate_range),
|
||
range_boundary_contexts: record(data.range_boundary_contexts),
|
||
thematic_evidence: selectedTheme,
|
||
vedastro_gateway: record(data.vedastro_gateway),
|
||
vedastro_cross_check: record(consumerContext.vedastro_cross_check),
|
||
external_engine_evidence: {
|
||
runtime_truth: record(data.runtime_truth), numerical_parity: record(data.external_parity_gate),
|
||
real_case_calibration: record(data.real_case_calibration),
|
||
},
|
||
reference_transparency: record(data.reference_transparency),
|
||
};
|
||
}
|