New reports skip the writer, persist pl9 Markdown as the body, and settle zero-token usage on the catalog model. Planned longform sections now emit blocked rows instead of vanishing. Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
3.4 KiB
TypeScript
92 lines
3.4 KiB
TypeScript
import {
|
|
globalBirthProfileFromAccountRow,
|
|
} from "./server-owned-birth-profile";
|
|
|
|
export function utcToday(now = new Date()): string {
|
|
return now.toISOString().slice(0, 10);
|
|
}
|
|
|
|
function accountText(row: unknown, key: string): string | undefined {
|
|
if (!row || typeof row !== "object" || Array.isArray(row)) return undefined;
|
|
const value = (row as Record<string, unknown>)[key];
|
|
if (typeof value !== "string") return undefined;
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
}
|
|
|
|
function clockToMinutes(value: string): number | null {
|
|
const match = /^(\d{2}):(\d{2})$/.exec(value);
|
|
if (!match) return null;
|
|
return Number.parseInt(match[1], 10) * 60 + Number.parseInt(match[2], 10);
|
|
}
|
|
|
|
export function provenanceUncertaintyMinutes(
|
|
row: unknown,
|
|
candidateRange: { start_time: string; end_time: string } | null,
|
|
): number | undefined {
|
|
if (candidateRange) {
|
|
const start = clockToMinutes(candidateRange.start_time);
|
|
const end = clockToMinutes(candidateRange.end_time);
|
|
if (start !== null && end !== null) return Math.max(0, end - start);
|
|
}
|
|
if (!row || typeof row !== "object" || Array.isArray(row)) return undefined;
|
|
const record = row as Record<string, unknown>;
|
|
const before = typeof record.uncertainty_before_minutes === "number"
|
|
? record.uncertainty_before_minutes
|
|
: Number(record.uncertainty_before_minutes);
|
|
const after = typeof record.uncertainty_after_minutes === "number"
|
|
? record.uncertainty_after_minutes
|
|
: Number(record.uncertainty_after_minutes);
|
|
const hasBefore = Number.isFinite(before);
|
|
const hasAfter = Number.isFinite(after);
|
|
if (!hasBefore && !hasAfter) return undefined;
|
|
return Math.max(0, (hasBefore ? before : 0) + (hasAfter ? after : 0));
|
|
}
|
|
|
|
export function buildLongformBirthPayload(
|
|
row: unknown,
|
|
extras: Readonly<{
|
|
today: string;
|
|
targetYear: number;
|
|
candidateRange: { start_time: string; end_time: string } | null;
|
|
birthTimeAccuracy: "confirmed" | "provisional";
|
|
}>,
|
|
): Record<string, unknown> | null {
|
|
const profile = globalBirthProfileFromAccountRow(row);
|
|
const dateMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(profile.date ?? "");
|
|
const timeMatch = /^(\d{2}):(\d{2})$/.exec(profile.time ?? "");
|
|
if (
|
|
!dateMatch
|
|
|| !timeMatch
|
|
|| profile.latitude === null
|
|
|| profile.longitude === null
|
|
|| profile.timezoneOffset === null
|
|
) {
|
|
return null;
|
|
}
|
|
const year = Number.parseInt(dateMatch[1], 10);
|
|
return {
|
|
year,
|
|
month: Number.parseInt(dateMatch[2], 10),
|
|
day: Number.parseInt(dateMatch[3], 10),
|
|
hour: Number.parseInt(timeMatch[1], 10),
|
|
minute: Number.parseInt(timeMatch[2], 10),
|
|
lat: profile.latitude,
|
|
lon: profile.longitude,
|
|
tz: profile.timezoneOffset,
|
|
ayanamsa: profile.ayanamsa,
|
|
format: "markdown",
|
|
packs: ["full"],
|
|
today: extras.today,
|
|
target_year: extras.targetYear,
|
|
age: extras.targetYear - year,
|
|
birth_time_accuracy: extras.birthTimeAccuracy,
|
|
...(extras.candidateRange ? { candidate_range: extras.candidateRange } : {}),
|
|
birthplace_label: accountText(row, "birth_place_label") ?? undefined,
|
|
coordinate_source: "user_reported",
|
|
coordinate_precision: "unverified_user_coordinates",
|
|
time_source: accountText(row, "birth_time_source") ?? accountText(row, "birth_time_status") ?? undefined,
|
|
uncertainty_minutes: provenanceUncertaintyMinutes(row, extras.candidateRange),
|
|
};
|
|
}
|