Unify reader cleanup rules, lock writer table guards, and register the exact fictional timestamp collision. Preserve existing ordinary-report safety contracts and source-data gaps. Validation: report Node 165/165, final safety 29/29, Python 101/101, Chrome 28/28; both PDFs retain all 130 rows. Full Node 3704 tests with the same 91 baseline failures. Privacy test: 62 passed, 1 failed due to 17 protected-file READ_ERRORs; not a green gate. Build, DB, manual checklist and controlled-login gaps remain documented. User explicitly authorized staging push with these gaps disclosed. Co-Authored-By: Claude Code <noreply@anthropic.com>
37 lines
2.5 KiB
TypeScript
37 lines
2.5 KiB
TypeScript
import { findForbiddenContent } from "./personal-report-contract";
|
|
import { PLANET_ZH, SIGN_ORDER, SIGN_ZH, type PlanetName, type SignName } from "./chart-view-labels";
|
|
|
|
export const record = (value: unknown): Record<string, unknown> => value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
export const at = (value: unknown, path: string): unknown => path.split(".").reduce((next, key) => record(next)[key], value);
|
|
export const entries = (value: unknown) => Object.entries(record(value)).filter(([key]) => /^[A-Za-z0-9_-]+$/.test(key));
|
|
export const list = (value: unknown): unknown[] => Array.isArray(value) ? value : [];
|
|
export const decimal = (value: unknown): string | null => typeof value === "number" && Number.isFinite(value) ? value.toFixed(2) : null;
|
|
export const yesNo = (value: unknown): string | null => typeof value === "boolean" ? value ? "是" : "否" : null;
|
|
/** Display-only allowlist: unknown/internal labels never become prose. */
|
|
export const readable = (value: unknown): string | null => typeof value === "string" && value.length > 0 && value.length <= 300
|
|
&& !/[_<>\[\]{}\/\\]|https?:|\b\w+\.\w+\b/.test(value) && !findForbiddenContent(value).length ? value : null;
|
|
export const date = (value: unknown): string | null => typeof value === "string" && /^\d{4}-\d{2}-\d{2}(?:T|$)/.test(value) ? value.slice(0, 10) : null;
|
|
export function planet(value: unknown, chinese?: unknown): string | null {
|
|
const label = readable(chinese);
|
|
if (label && /[㐀-鿿]/.test(label)) return label;
|
|
return typeof value === "string" ? PLANET_ZH[value as PlanetName] ?? (value === "Lagna" ? "上升" : null) : null;
|
|
}
|
|
export function sign(value: unknown, chinese?: unknown, index?: unknown): string | null {
|
|
const label = readable(chinese);
|
|
if (label && /[㐀-鿿]/.test(label)) return label;
|
|
if (typeof value === "string") {
|
|
const mapped = SIGN_ZH[value as SignName];
|
|
if (mapped) return mapped;
|
|
if (Object.values(SIGN_ZH).some(name => value === name || value === `${name}座`)) return value;
|
|
}
|
|
return typeof index === "number" && Number.isInteger(index) && index >= 0 && index < 12 ? SIGN_ZH[SIGN_ORDER[index]] : null;
|
|
}
|
|
export const position = (value: unknown) => {
|
|
const raw = record(value);
|
|
return [sign(raw.sign, raw.sign_cn, raw.sign_idx), decimal(raw.degree_in_sign ?? raw.sign_degree)] as const;
|
|
};
|
|
export const isBlocked = (value: unknown) => {
|
|
const raw = record(value);
|
|
return raw.status === "blocked" || raw.status === "missing_in_local" || raw.claimStatus === "blocked" || raw.claim_status === "blocked";
|
|
};
|