Keep family on D12+D7 instead of mixing it into D4, score D5 on education, and show a natal recast plus technique audit after adopt without unique-minute claims. Co-authored-by: Cursor <cursoragent@cursor.com>
147 lines
4.8 KiB
TypeScript
147 lines
4.8 KiB
TypeScript
/**
|
|
* D9/D10 observations for follow-up routing only.
|
|
*
|
|
* The engine may know Navamsa / Dasamsa lagna indices. This module projects
|
|
* counts, differ flags and change minutes. It never emits sign names, spouse
|
|
* types, career archetypes, or any other user-facing D9/D10 label.
|
|
*/
|
|
|
|
import {
|
|
parseWindowScanTransitions,
|
|
type WindowScanTransition,
|
|
} from "./refinement-packet";
|
|
|
|
export type WindowScan = Readonly<{
|
|
scanned: boolean;
|
|
confirmation_allowed: false;
|
|
unique_minute_claim: false;
|
|
d1_lagna_count: number;
|
|
d9_lagna_count: number;
|
|
d10_lagna_count: number;
|
|
d4_lagna_count: number;
|
|
d5_lagna_count: number;
|
|
d7_lagna_count: number;
|
|
d12_lagna_count: number;
|
|
d1_candidates_differ: boolean;
|
|
d9_candidates_differ: boolean;
|
|
d10_candidates_differ: boolean;
|
|
d4_candidates_differ: boolean;
|
|
d5_candidates_differ: boolean;
|
|
d7_candidates_differ: boolean;
|
|
d12_candidates_differ: boolean;
|
|
transitions: readonly WindowScanTransition[];
|
|
}>;
|
|
|
|
export type InternalVargaObservation = Readonly<{
|
|
layer: "d9" | "d10" | "d4" | "d5" | "d7" | "d12";
|
|
candidates_differ: boolean;
|
|
ask_theme: "relationship_style" | "career_style" | "home_change" | "education_style" | "family_event" | null;
|
|
}>;
|
|
|
|
function asRecord(value: unknown): Readonly<Record<string, unknown>> | null {
|
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
? value as Readonly<Record<string, unknown>>
|
|
: null;
|
|
}
|
|
|
|
function asCount(value: unknown): number | null {
|
|
if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
|
|
if (typeof value === "string" && value.trim()) {
|
|
const parsed = Number(value);
|
|
if (Number.isInteger(parsed) && parsed >= 0) return parsed;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function optionalCount(value: unknown): number {
|
|
return asCount(value) ?? 0;
|
|
}
|
|
|
|
/**
|
|
* Keep only index counts, differ flags and reconstructed change minutes.
|
|
* Extra keys (sign names, lagna lists, type tables) are dropped.
|
|
*/
|
|
export function parseWindowScan(value: unknown): WindowScan | null {
|
|
const row = asRecord(value);
|
|
if (!row || row.scanned !== true) return null;
|
|
const d9Count = asCount(row.d9_lagna_count);
|
|
const d10Count = asCount(row.d10_lagna_count);
|
|
if (d9Count === null || d10Count === null) return null;
|
|
const d1Count = optionalCount(row.d1_lagna_count);
|
|
const d4Count = optionalCount(row.d4_lagna_count);
|
|
const d5Count = optionalCount(row.d5_lagna_count);
|
|
const d7Count = optionalCount(row.d7_lagna_count);
|
|
const d12Count = optionalCount(row.d12_lagna_count);
|
|
const d9Differ = row.d9_candidates_differ === true || d9Count > 1;
|
|
const d10Differ = row.d10_candidates_differ === true || d10Count > 1;
|
|
const d1Differ = row.d1_candidates_differ === true || d1Count > 1;
|
|
const d4Differ = row.d4_candidates_differ === true || d4Count > 1;
|
|
const d5Differ = row.d5_candidates_differ === true || d5Count > 1;
|
|
const d7Differ = row.d7_candidates_differ === true || d7Count > 1;
|
|
const d12Differ = row.d12_candidates_differ === true || d12Count > 1;
|
|
return {
|
|
scanned: true,
|
|
confirmation_allowed: false,
|
|
unique_minute_claim: false,
|
|
d1_lagna_count: d1Count,
|
|
d9_lagna_count: d9Count,
|
|
d10_lagna_count: d10Count,
|
|
d4_lagna_count: d4Count,
|
|
d5_lagna_count: d5Count,
|
|
d7_lagna_count: d7Count,
|
|
d12_lagna_count: d12Count,
|
|
d1_candidates_differ: d1Differ,
|
|
d9_candidates_differ: d9Differ,
|
|
d10_candidates_differ: d10Differ,
|
|
d4_candidates_differ: d4Differ,
|
|
d5_candidates_differ: d5Differ,
|
|
d7_candidates_differ: d7Differ,
|
|
d12_candidates_differ: d12Differ,
|
|
transitions: parseWindowScanTransitions(row.transitions),
|
|
};
|
|
}
|
|
|
|
export function windowScanFromDecisionReceipt(
|
|
receipt: Readonly<Record<string, unknown>> | null | undefined,
|
|
): WindowScan | null {
|
|
return parseWindowScan(receipt?.window_scan);
|
|
}
|
|
|
|
export function internalObservationsFromWindowScan(
|
|
scan: WindowScan | null,
|
|
): readonly InternalVargaObservation[] {
|
|
if (!scan) return [];
|
|
return [
|
|
{
|
|
layer: "d9",
|
|
candidates_differ: scan.d9_candidates_differ,
|
|
ask_theme: scan.d9_candidates_differ ? "relationship_style" : null,
|
|
},
|
|
{
|
|
layer: "d10",
|
|
candidates_differ: scan.d10_candidates_differ,
|
|
ask_theme: scan.d10_candidates_differ ? "career_style" : null,
|
|
},
|
|
{
|
|
layer: "d4",
|
|
candidates_differ: scan.d4_candidates_differ,
|
|
ask_theme: scan.d4_candidates_differ ? "home_change" : null,
|
|
},
|
|
{
|
|
layer: "d5",
|
|
candidates_differ: scan.d5_candidates_differ,
|
|
ask_theme: scan.d5_candidates_differ ? "education_style" : null,
|
|
},
|
|
{
|
|
layer: "d7",
|
|
candidates_differ: scan.d7_candidates_differ,
|
|
ask_theme: scan.d7_candidates_differ ? "family_event" : null,
|
|
},
|
|
{
|
|
layer: "d12",
|
|
candidates_differ: scan.d12_candidates_differ,
|
|
ask_theme: scan.d12_candidates_differ ? "family_event" : null,
|
|
},
|
|
];
|
|
}
|