fix(consult): put locally computed techniques in the model packet
Independent Staging Quality Gate / validate (push) Successful in 11m10s
Independent Staging Quality Gate / publish (push) Successful in 12m30s

Web consult already computed Yoga, Arudha, KP, and related layers, then
stripped them before the spoken model. Keep those executed structures in
toModelOutput so answers cannot invent degrees from parametric knowledge.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-19 16:53:13 +08:00
co-authored by Cursor
parent 9efd847d2f
commit 0eedf92992
10 changed files with 418 additions and 9 deletions
@@ -34,6 +34,9 @@ const TECHNIQUE_LABELS: Readonly<Record<string, string>> = {
"Arudha / UL / A10": "Arudha / UL / A10",
"Narayana Dasha": "Narayana Dasha",
"Vimshottari sub-periods": "Vimshottari 小运",
Yogas: "格局 Yoga",
"Chara Dasha": "Chara Dasha",
"Transits / Sade Sati": "行运 / 土星过月",
Ashtakavarga: "Ashtakavarga",
"KP cusps": "KP 宫头",
"Gulika / Mandi": "Gulika / Mandi",
@@ -81,6 +84,8 @@ const NOTE_LABELS: Readonly<Record<string, string>> = {
observation_only: "仅观察层",
"Chesta/Sthana/Dig/Drik/Kala/Naisargika as components; absolute Virupa truth is not closed": "绝对 Virupa 真值未闭环",
"observation_only; not verified day/month timing": "仅观察层,不作已核验日期",
"observation windows, not guaranteed events": "观察窗口,不是保证事件",
"commercial claims stay non-deterministic; not a majority-vote truth": "商业断语保持非确定,不以多数引擎表决",
"this run is a natal consultation": "本轮为本命咨询",
horary_path: "问卦路径",
"requires a partner chart": "需要伴侣盘",
@@ -434,6 +434,7 @@ function mergeConsultationEvidencePackets(
...(audit !== undefined ? { technique_audit_table: audit } : {}),
...(varga !== undefined ? { varga_spectrum: varga } : {}),
...(western !== undefined ? { western_spectrum: western } : {}),
must_use_layers: unionStringList(contracts.map((contract) => contract.must_use_layers)),
},
claim_cards: options.claimCards,
// `not_auto_rectified` is the restrictive boundary, so one domain reporting
+68 -1
View File
@@ -176,6 +176,7 @@ export const consultationEvidencePacketSchema = z.object({
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),
@@ -247,6 +248,8 @@ const natalFoundationKeys = new Set([
"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([
@@ -255,6 +258,9 @@ const timingKeys = new Set([
"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", "window", "sequence", "durationyears",
"fromage", "toage", "phase", "phasename", "moonsign", "saturnsign", "intensity", "active",
"date", "target", "kind", "orb", "boundary", "claimboundary",
]);
const validationKeys = new Set([
@@ -334,10 +340,17 @@ function projectTechniqueAudit(value: unknown): ModelOutputValue | 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"] as const) {
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;
@@ -385,6 +398,11 @@ function projectNatalFoundation(context: ReturnType<typeof toAgentConsultationCo
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);
@@ -433,6 +451,8 @@ function projectTimingEvidence(context: ReturnType<typeof toAgentConsultationCon
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);
@@ -456,11 +476,53 @@ 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 D1D60",
"Functional Benefic/Malefic",
"Yogas",
"Vimshottari sub-periods",
"Narayana Dasha",
"Shadbala components",
"Ashtakavarga",
"Arudha / UL / A10",
"Western natal (tropical)",
"Transits / Sade Sati",
] as const;
const DOMAIN_MUST_USE_LAYERS: Readonly<Record<string, readonly string[]>> = {
career: ["Formal Vargas D1D60", "Arudha / UL / A10", "Narayana Dasha", "Chara Dasha"],
marriage: ["Formal Vargas D1D60", "Arudha / UL / A10"],
wealth: ["Formal Vargas D1D60", "Ashtakavarga"],
timing: ["Narayana Dasha", "Vimshottari sub-periods", "Transits / Sade Sati", "Chara Dasha"],
annual: ["Narayana Dasha", "Vimshottari sub-periods", "Chara Dasha"],
health: ["Formal Vargas D1D60", "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),
@@ -478,6 +540,7 @@ function projectEvidenceContract(contract: ReturnType<typeof toAgentConsultation
...(audit !== undefined ? { technique_audit_table: audit } : {}),
...(varga !== undefined ? { varga_spectrum: varga } : {}),
...(western !== undefined ? { western_spectrum: western } : {}),
...(mustUse.length > 0 ? { must_use_layers: mustUse } : {}),
};
}
@@ -570,6 +633,10 @@ export function toAgentConsultationContext(data: JsonRecord) {
// 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,
},
+1
View File
@@ -44,6 +44,7 @@ When reference_transparency is present:
- Only say the chart calculation failed when evidence_contract.hard_blockers is non-empty.
- Never claim D2, D11, D9, D10, A10, UL, Narayana Dasha, a delivered Varga, or a delivered Western layer is missing when it appears in evidence_contract.available_layers, evidence_contract.varga_spectrum, evidence_contract.western_spectrum, chart, or local_layers.
- evidence_contract.technique_audit_table is the invocation record for this run. Use every executed layer that is relevant to the question. Status executed means the server ran it; blocked means it could not; not_applicable means it does not apply. Never treat an omitted row as permission to invent the technique, and never silently skip an executed layer the question needs.
- evidence_contract.must_use_layers is the executed shortlist for this answer. A placement, yoga, dasha boundary, or transit that is not in this packet was not delivered: do not invent it from model knowledge.
- Do not paste that Technique Audit Table into the spoken answer. The product UI renders it collapsed from the delivered rows. This is the one audit surface; do not also dump JSON.
- local_layers.dasha_sub_periods carries the antardasha boundaries inside the running mahadasha. When it is present, use those boundaries and never say sub-periods were not calculated; when it is absent, say so once instead of implying the calculation broke.
- Treat evidence_contract.answer_policy as a hard output contract. When can_answer_precise_timing is false, provide only direction or structure and do not state a month, date, or guaranteed timing outcome.