Skip to content

Commit 8822496

Browse files
mpstatonclaude
andcommitted
fix(persons, resolver): reads coalesce name ?? full_name — import-born rows stop rendering as UUIDs
The Gates Foundation's Deputy Director rendered as a bare UUID: her row came from the crawlbase/event-CSV import generation, which wrote full_name (+ first_name/surname) and never name — the field every person read selects. Measured live: 136 of 1232 persons affected; 39 more have no name data at all. Every read point coalesces now — PERSON_FIELDS (candidates + lookups), person.search (projection, filter, ordering), affiliation.detail, and organization.affiliations. Proven read-only against the live row: the affiliations projection returns "Melanie Brown". Also included: scripts/surreal-backfill-person-names.mjs — the additive operator-run backfill (fills name from full_name where missing, dry-run by default, APPLY=1 to write) that makes the fix durable on data. Closes #28. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UvYzx7vDWeafnkAi2nEQeb
1 parent fa15b64 commit 8822496

3 files changed

Lines changed: 101 additions & 5 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
date_created: 2026-07-24
3+
date_modified: 2026-07-24
4+
title: "People rows show names, not UUIDs — two generations of person rows reconciled at the read layer"
5+
lede: "The Gates Foundation's Deputy Director rendered as a bare UUID: her row was born from the crawlbase/event-CSV import scripts, which wrote full_name and never name — the field every person read selects. All person reads now coalesce name ?? full_name, fixing 136 of 1232 persons at once; the operator-run backfill makes it durable on data."
6+
publish: true
7+
authors:
8+
- Michael Staton
9+
augmented_with:
10+
- Claude Code on Claude Fable 5
11+
files_changed:
12+
- services/record-surrealdb-resolver/src/person-resolver.ts
13+
tags:
14+
- Org-Workbench
15+
- Persons
16+
- Canonical-Layer
17+
- Usability
18+
---
19+
20+
# People rows show names, not UUIDs
21+
22+
## The diagnosis ([#28](https://github.com/lossless-group/augment-it/issues/28))
23+
24+
The people reveal fell back to `person_uuid` because the row genuinely had no
25+
`name` — a live SurrealDB inspection showed the affected person carries
26+
`full_name: "Melanie Brown"` (+ `first_name`/`surname`) and no `name` at all.
27+
The persons table has **two generations of rows**: `person.apply`-born rows
28+
write `name`; the crawlbase LinkedIn and event-CSV import scripts wrote
29+
`full_name`. Scope, measured live: 1232 persons — 1057 with `name`, **136
30+
missing it but holding `full_name`**, 39 with no name data of any kind.
31+
32+
## The fix
33+
34+
Every person read in the resolver now projects `name ?? full_name AS name`:
35+
`PERSON_FIELDS` (candidates scoring + person lookups), `person.search`'s
36+
autocomplete (projection, filter, and ordering), `affiliation.detail`, and
37+
`organization.affiliations`. One coalesce per read point; both generations
38+
display. Proven live read-only: the affiliations projection returns
39+
`"Melanie Brown"` for the Gates Foundation edge.
40+
41+
## The durable half — operator-run backfill
42+
43+
Filling `name` from `full_name` where missing is a bulk canonical write, so it
44+
stays operator-run rather than agent-run. The scoped, additive script (fills
45+
the missing field only, never overwrites) is staged for a scripts/ home; until
46+
it runs, the coalesce carries the display alone. The 39 rows with no name data
47+
at all keep showing their UUID — there is nothing to derive a name from; they
48+
are import stubs awaiting enrichment.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Scope + (with APPLY=1) backfill: persons rows missing `name` but holding
2+
// full_name (or first_name/surname). Additive — fills a missing field only,
3+
// never overwrites an existing name.
4+
import { Surreal } from 'surrealdb';
5+
6+
const { SURREAL_URL, SURREAL_NS, SURREAL_DB, SURREAL_USER, SURREAL_PASS, APPLY } = process.env;
7+
const db = new Surreal();
8+
await db.connect(SURREAL_URL);
9+
await db.signin({ username: SURREAL_USER, password: SURREAL_PASS });
10+
await db.use({ namespace: SURREAL_NS, database: SURREAL_DB });
11+
12+
const counts = await db.query(`
13+
RETURN {
14+
total: (SELECT VALUE count() FROM persons GROUP ALL)[0],
15+
with_name: (SELECT VALUE count() FROM persons WHERE name IS NOT NONE AND name != '' GROUP ALL)[0],
16+
missing_name_has_full: (SELECT VALUE count() FROM persons WHERE (name IS NONE OR name = '') AND full_name IS NOT NONE AND full_name != '' GROUP ALL)[0],
17+
missing_name_parts_only: (SELECT VALUE count() FROM persons WHERE (name IS NONE OR name = '') AND (full_name IS NONE OR full_name = '') AND first_name IS NOT NONE GROUP ALL)[0],
18+
missing_everything: (SELECT VALUE count() FROM persons WHERE (name IS NONE OR name = '') AND (full_name IS NONE OR full_name = '') AND first_name IS NONE GROUP ALL)[0]
19+
};
20+
`);
21+
console.log('scope:', JSON.stringify(counts?.[0]));
22+
23+
if (APPLY === '1') {
24+
const r1 = await db.query(`
25+
UPDATE persons SET name = full_name
26+
WHERE (name IS NONE OR name = '') AND full_name IS NOT NONE AND full_name != ''
27+
RETURN person_uuid, name;
28+
`);
29+
console.log('backfilled from full_name:', (r1?.[0] ?? []).length);
30+
const r2 = await db.query(`
31+
UPDATE persons SET name = string::trim(string::concat(first_name ?? '', ' ', surname ?? ''))
32+
WHERE (name IS NONE OR name = '') AND (full_name IS NONE OR full_name = '') AND first_name IS NOT NONE
33+
RETURN person_uuid, name;
34+
`);
35+
console.log('backfilled from first+surname:', (r2?.[0] ?? []).length);
36+
const check = await db.query(
37+
`SELECT VALUE name FROM persons WHERE person_uuid = '019f3b9c-52ac-7a91-b47e-cfab416eb33d';`,
38+
);
39+
console.log('melanie now:', JSON.stringify(check?.[0]));
40+
}
41+
await db.close();

services/record-surrealdb-resolver/src/person-resolver.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ type PersonRow = {
4343
email?: string | null;
4444
};
4545

46-
const PERSON_FIELDS = 'id, person_uuid, name, headline, linkedin_profile_url, email';
46+
// `name ?? full_name` — the persons table has two generations of rows:
47+
// person.apply-born rows write `name`; the crawlbase/event-CSV import
48+
// scripts wrote `full_name` (+ first_name/surname) and no `name` at all.
49+
// Every read coalesces so both generations display; the durable fix on
50+
// data is the name backfill (fills `name` from `full_name` where missing).
51+
const PERSON_FIELDS =
52+
'id, person_uuid, name ?? full_name AS name, headline, linkedin_profile_url, email';
4753

4854
export type PersonCandidate = {
4955
// Wire-safe handle — NEVER the raw RecordId (SurrealDB RecordIds don't
@@ -176,8 +182,9 @@ export async function searchPersons(
176182
if (!trimmed || trimmed.length < 2) return { candidates: [] };
177183
await ensurePersonSchema(db);
178184
const r = await db.query(
179-
`SELECT person_uuid, name, headline FROM persons
180-
WHERE client_access CONTAINS $client AND string::lowercase(name) CONTAINS $q
185+
`SELECT person_uuid, name ?? full_name AS name, headline FROM persons
186+
WHERE client_access CONTAINS $client
187+
AND string::lowercase(name ?? full_name ?? '') CONTAINS $q
181188
ORDER BY name ASC LIMIT 8`,
182189
{ client, q: trimmed },
183190
);
@@ -734,7 +741,7 @@ export async function getAffiliationDetail(
734741
input: AffiliationDetailInput,
735742
): Promise<AffiliationDetailResult> {
736743
const personRes = await db.query(
737-
`SELECT id, person_uuid, name, personal_links, personal_corpus FROM persons WHERE person_uuid = $u LIMIT 1;`,
744+
`SELECT id, person_uuid, name ?? full_name AS name, personal_links, personal_corpus FROM persons WHERE person_uuid = $u LIMIT 1;`,
738745
{ u: input.person_uuid },
739746
);
740747
const person = ((personRes?.[0] as PersonDetailRow[]) ?? [])[0];
@@ -861,7 +868,7 @@ export async function listOrgAffiliations(
861868

862869
const affRes = await db.query(
863870
`SELECT kind, relevance,
864-
in.person_uuid AS person_uuid, in.name AS name, in.headline AS headline,
871+
in.person_uuid AS person_uuid, in.name ?? in.full_name AS name, in.headline AS headline,
865872
in.personal_links AS personal_links,
866873
in.personal_corpus ?? [] AS personal_corpus,
867874
array::len(in.personal_corpus ?? []) AS personal_corpus_count

0 commit comments

Comments
 (0)