fix(migration): legacy bare-mooring lookup + port-nimara berth backfill
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m12s
Build & Push Docker Images / build-and-push (push) Has been skipped

Two issues surfaced when applying the migration to dev:

1. Mooring number format mismatch
   The legacy NocoDB Interests table writes bare mooring strings
   ("D32", "B16", "A4"), but the new berths table (mirroring the
   NocoDB Berths snapshot) uses zero-padded dashed form ("D-32",
   "B-16", "A-04"). The interest→berth lookup missed every reference.

   migration-apply.ts now tries the literal value first, then falls
   back to a normalized form via `normalizeLegacyMooring(raw)`:
     "D32" -> "D-32"
     "A4"  -> "A-04"
     "E18" -> "E-18"
   Multi-mooring strings ("A3, D30") are left as-is so they surface in
   the warnings list for human review rather than silently picking one.

2. port-nimara only had the 12 hand-rolled seed berths, not the 117-
   berth NocoDB snapshot
   The mobile-foundation seed only places those 12 in port-nimara; the
   117-berth snapshot was added later but only seeded into Marina
   Azzurra (the secondary test port). Migrated interests reference
   moorings well beyond A-01..D-03, so most lookups failed.

   New scripts/load-berths-to-port-nimara.ts: idempotently loads any
   missing snapshot berths into port-nimara without disturbing the
   existing 12 (skips moorings that already exist). Run once;
   subsequent runs no-op.

Result of full migration run on dev:
  237 clients inserted (out of 245 total — 8 from prior seed)
  406 contacts, 52 addresses, 38 yachts, 252 interests
  27 interest→berth links resolved (only 13 source rows had a Berth
  field set in NocoDB to begin with — most legacy interests are early
  inquiries with no berth assignment)
  1 unresolved warning: source=277 has multi-mooring "A3, D30"

Verified in UI:
  /port-nimara/clients shows real names (John-michael Seelye, Reza
  Amjad, Etiennette Clamouze, …)
  /port-nimara/clients/<id> renders contacts (gmail.com addresses,
  E.164 phones), tab counts (Interests N, Yachts N), pipeline summary
  Dashboard: 245 clients, 266 active interests, $46.5M pipeline value
  Pipeline funnel chart now shows real distribution (180 Open, 45
  EOI Signed, dropoff through stages)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-03 21:05:11 +02:00
parent 872c75f1a1
commit c612bbdfd9
2 changed files with 153 additions and 1 deletions

View File

@@ -28,6 +28,25 @@ import type { MigrationPlan, PlannedClient, PlannedInterest } from './migration-
const SOURCE_SYSTEM = 'nocodb_interests';
/**
* Convert a legacy bare mooring string like "D32" / "A1" / "E18" to the
* dashed/padded form "D-32" / "A-01" / "E-18" used by the new berths
* schema. If the input doesn't match the bare pattern, returns it
* unchanged so a literal lookup can still hit (handles the case where
* the legacy data already has the dashed form).
*
* Multi-mooring strings ("A3, D30") return the original string —
* those need human review and we don't want to silently pick one half.
*/
function normalizeLegacyMooring(raw: string): string {
// Bare letter+digits, e.g. "D32"
const m = /^([A-E])(\d{1,3})$/i.exec(raw.trim());
if (!m) return raw;
const letter = m[1]!.toUpperCase();
const num = parseInt(m[2]!, 10);
return `${letter}-${num.toString().padStart(2, '0')}`;
}
export interface ApplyResult {
applyId: string;
clientsInserted: number;
@@ -212,7 +231,14 @@ async function applyInterest(
let berthId: string | null = null;
if (planned.berthMooringNumber) {
berthId = mooringToBerthId.get(planned.berthMooringNumber) ?? null;
berthId =
mooringToBerthId.get(planned.berthMooringNumber) ??
// The legacy NocoDB Interests table uses bare mooring strings like
// "D32", "B16", whereas the new berths schema (mirroring the NocoDB
// Berths snapshot) uses zero-padded "D-32", "B-16". Try the dashed
// form as a fallback so legacy references resolve correctly.
mooringToBerthId.get(normalizeLegacyMooring(planned.berthMooringNumber)) ??
null;
if (!berthId) {
result.warnings.push(
`Interest source=${planned.sourceId} references unknown mooring="${planned.berthMooringNumber}" — interest created without berth link`,