hd-prism-bulk-import

/home/avalon/.hermes/skills/.archive/devops/hd-prism-bulk-import/SKILL.md · raw

HD Prism Bulk Chart Import

Import birth data in bulk into HD Prism without triggering LLM/AI reading generation.

Architecture

Key Modules (all in apps/api/src/)

Module Function Purpose
models/bodygraph.ts createBodygraph(name, dateUtc, location) Computes HD chart from UTC Date + location string
descriptions/descriptionDb.ts createSavedChart({id, userId, name, chartJson, chartFingerprint}) Persists chart to SQLite
descriptions/descriptionDb.ts listSavedChartsForUser(userId) Check for existing charts (dedup)
interpretation/chartFingerprint.ts computeChartFingerprint(bodygraphObj) SHA256 hash for caching

Import Script Pattern

Script must live inside apps/api/ for relative imports to resolve. Run with npx tsx:

cd apps/api
npx tsx scripts/import-<source>.ts [json-path] [userId] [--dry-run]

Template

import * as fs from "fs";
import * as crypto from "crypto";
import { DateTime } from "luxon";
import { createBodygraph } from "../src/models/bodygraph";
import { createSavedChart, listSavedChartsForUser } from "../src/descriptions/descriptionDb";
import { computeChartFingerprint } from "../src/interpretation/chartFingerprint";

const DEFAULT_USER_ID = "e05b2131-3833-4056-b7db-3dc6850da5c2"; // firemountain@gmail.com

// Suppress noisy Swiss Eph console.log
const originalLog = console.log;
function suppressLog() {
  console.log = (...args: any[]) => {
    const msg = args.join(" ");
    if (/^[✅⏭️⚠️❌🔮📋📁✨⏳]/.test(msg)) originalLog(...args);
  };
}

function main() {
  const dryRun = process.argv.includes("--dry-run");
  const userId = /* from args or default */;

  // 1. Load source data
  // 2. Filter to Persons with birth data
  // 3. Parse coordinates, dates, timezones from source format
  // 4. Convert local time + IANA timezone -> UTC via Luxon
  // 5. Call createBodygraph(name, dateUtc, locationString)
  // 6. Attach timezoneUsed + birthDateTimeUTC to bodygraph
  // 7. Call createSavedChart() with fingerprint
  // 8. Deduplicate by name (case-insensitive) against listSavedChartsForUser()

  suppressLog();
  for (const record of records) {
    const dtLocal = DateTime.local(y, m, d, h, min, { zone: tz });
    const dateUtc = dtLocal.toJSDate();
    const bg = createBodygraph(name, dateUtc, locationStr);
    (bg as any).timezoneUsed = tz;
    (bg as any).birthDateTimeUTC = dateUtc.toISOString();
    const fp = computeChartFingerprint(bg as any);
    if (!dryRun) {
      createSavedChart({
        id: crypto.randomUUID(),
        userId,
        name,
        chartJson: JSON.stringify(bg),
        chartFingerprint: fp,
      });
    }
  }
  console.log = originalLog;
}

Coordinate Parsing

Time Nomad uses "33.8650 S, 151.2094 E" format:

const match = coord.match(/([\d.]+)\xb0\s*([NS])\s*,\s*([\d.]+)\xb0\s*([EW])/i);
const lat = parseFloat(match[1]) * (match[2] === "S" ? -1 : 1);
const lon = parseFloat(match[3]) * (match[4] === "W" ? -1 : 1);

Pitfalls