Skip to content

Commit 19e1fdd

Browse files
patconclaude
andcommitted
Fix votes column name matching and embedding switching
- Support hyphenated column names from valency-anndata (voter-id, comment-id) in addition to underscored variants - Store file buffer in a ref so embedding switching works after the file picker is unmounted from the DOM Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3c1346b commit 19e1fdd

2 files changed

Lines changed: 31 additions & 33 deletions

File tree

src/components/convo-explorer/App.h5ad.stories.tsx

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ function H5adFileLoader() {
1414
const [loading, setLoading] = React.useState(false);
1515
const [error, setError] = React.useState<string | null>(null);
1616
const [selectedEmbedding, setSelectedEmbedding] = React.useState<string | null>(null);
17+
// Store the file buffer so we can re-parse with a different embedding
18+
// without needing the file input element (which is removed from DOM after loading)
19+
const bufferRef = React.useRef<ArrayBuffer | null>(null);
1720

1821
const handleFileChange = React.useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
1922
const file = event.target.files?.[0];
@@ -24,6 +27,7 @@ function H5adFileLoader() {
2427

2528
try {
2629
const buffer = await file.arrayBuffer();
30+
bufferRef.current = buffer;
2731
const parsed = await loadH5adFile(buffer);
2832
setData(parsed);
2933
// Set the initially-selected embedding (first in preferred order)
@@ -40,33 +44,21 @@ function H5adFileLoader() {
4044
}, []);
4145

4246
const handleEmbeddingChange = React.useCallback(async (newEmbedding: string) => {
43-
if (!data) return;
47+
if (!bufferRef.current) return;
4448
setSelectedEmbedding(newEmbedding);
4549
setLoading(true);
4650
setError(null);
4751

4852
try {
49-
// Re-read the file from the emscripten FS isn't possible after close,
50-
// so we need the user to re-upload. Instead, store the buffer.
51-
// For simplicity, we ask user to re-select. But actually we can
52-
// use the fileInputRef to get the file again.
53-
const fileInput = document.querySelector<HTMLInputElement>('#h5ad-file-input');
54-
const file = fileInput?.files?.[0];
55-
if (!file) {
56-
setError('Please re-select the file to change embeddings');
57-
setLoading(false);
58-
return;
59-
}
60-
const buffer = await file.arrayBuffer();
61-
const parsed = await loadH5adFile(buffer, newEmbedding);
53+
const parsed = await loadH5adFile(bufferRef.current, newEmbedding);
6254
setData(parsed);
6355
} catch (err) {
6456
console.error('Failed to reload with new embedding:', err);
6557
setError(err instanceof Error ? err.message : 'Failed to reload embedding');
6658
} finally {
6759
setLoading(false);
6860
}
69-
}, [data]);
61+
}, []);
7062

7163
// File picker screen
7264
if (!data) {
@@ -90,7 +82,6 @@ function H5adFileLoader() {
9082
>
9183
{loading ? 'Loading...' : 'Choose .h5ad file'}
9284
<input
93-
id="h5ad-file-input"
9485
type="file"
9586
accept=".h5ad,.h5,.hdf5"
9687
onChange={handleFileChange}
@@ -155,7 +146,7 @@ statements, and votes directly in the browser using h5wasm.
155146
- \`obs\` — participant index (IDs)
156147
- \`obsm/X_*\` — 2D embeddings (e.g. \`X_localmap\`, \`X_umap\`)
157148
- \`var\` — statement index + \`content\` and \`moderation_state\` columns
158-
- \`uns/votes\` — DataFrame with \`voter_id\`, \`comment_id\`, \`vote\` columns
149+
- \`uns/votes\` — DataFrame with \`voter-id\`, \`comment-id\`, \`vote\` columns
159150
`,
160151
},
161152
},

src/lib/h5ad-loader.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,17 @@ export async function loadH5adFile(
238238
}
239239
}
240240

241+
/**
242+
* Find the first matching key from a list of candidates.
243+
*/
244+
function findKey(available: string[], candidates: string[]): string | undefined {
245+
return candidates.find(k => available.includes(k));
246+
}
247+
241248
/**
242249
* Read votes DataFrame from uns/votes.
243-
* Expected columns: voter_id (or participant_id), comment_id, vote
250+
* Handles both hyphenated (valency-anndata: voter-id, comment-id) and
251+
* underscored (voter_id, comment_id, participant_id) column naming conventions.
244252
*/
245253
function readVotes(file: H5File): H5adData['votesRows'] {
246254
const unsGroup = file.get('uns') as Group | null;
@@ -251,30 +259,29 @@ function readVotes(file: H5File): H5adData['votesRows'] {
251259

252260
const votesKeys = votesGroup.keys();
253261

254-
// Read participant IDs - try voter_id first, then participant_id
255-
let participantIds: (string | number)[];
256-
if (votesKeys.includes('voter_id')) {
257-
participantIds = readColumn(votesGroup, 'voter_id');
258-
} else if (votesKeys.includes('participant_id')) {
259-
participantIds = readColumn(votesGroup, 'participant_id');
260-
} else {
261-
console.warn('No voter_id or participant_id column found in uns/votes');
262+
// Read participant IDs - try multiple naming conventions
263+
const participantKey = findKey(votesKeys, ['voter-id', 'voter_id', 'participant_id', 'participant-id']);
264+
if (!participantKey) {
265+
console.warn('No voter/participant ID column found in uns/votes. Available keys:', votesKeys);
262266
return [];
263267
}
268+
const participantIds = readColumn(votesGroup, participantKey);
264269

265-
// Read comment_id
266-
if (!votesKeys.includes('comment_id')) {
267-
console.warn('No comment_id column found in uns/votes');
270+
// Read comment/statement IDs
271+
const commentKey = findKey(votesKeys, ['comment-id', 'comment_id', 'statement_id', 'statement-id']);
272+
if (!commentKey) {
273+
console.warn('No comment/statement ID column found in uns/votes. Available keys:', votesKeys);
268274
return [];
269275
}
270-
const commentIds = readColumn(votesGroup, 'comment_id');
276+
const commentIds = readColumn(votesGroup, commentKey);
271277

272278
// Read vote values
273-
if (!votesKeys.includes('vote')) {
274-
console.warn('No vote column found in uns/votes');
279+
const voteKey = findKey(votesKeys, ['vote', 'votes']);
280+
if (!voteKey) {
281+
console.warn('No vote column found in uns/votes. Available keys:', votesKeys);
275282
return [];
276283
}
277-
const votes = readColumn(votesGroup, 'vote');
284+
const votes = readColumn(votesGroup, voteKey);
278285

279286
const rows: H5adData['votesRows'] = [];
280287
for (let i = 0; i < participantIds.length; i++) {

0 commit comments

Comments
 (0)