From c215024ef2ccc25a0261eb0933ef1d49ab4f48a1 Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Wed, 18 Mar 2026 18:50:51 +0000 Subject: [PATCH] feat(frontend): add deleted user filter Reddit often contains "[Deleted]" when a user is banned or deletes their post/comment. Keeping the backend faithful to the original dataset is important so the filtering is being done on the frontend. --- frontend/src/pages/Stats.tsx | 69 ++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/Stats.tsx b/frontend/src/pages/Stats.tsx index 910b0a0..f3f483f 100644 --- a/frontend/src/pages/Stats.tsx +++ b/frontend/src/pages/Stats.tsx @@ -23,6 +23,11 @@ import { const API_BASE_URL = import.meta.env.VITE_BACKEND_URL const styles = StatsStyling; +const DELETED_USERS = ["[deleted]"]; + +const isDeletedUser = (value: string | null | undefined) => ( + DELETED_USERS.includes((value ?? "").trim().toLowerCase()) +); const StatPage = () => { const { datasetId: routeDatasetId } = useParams<{ datasetId: string }>(); @@ -124,9 +129,56 @@ const StatPage = () => { }), ]) .then(([timeRes, userRes, linguisticRes, emotionalRes, interactionRes, summaryRes, culturalRes]) => { + const usersList = userRes.data.users ?? []; + const topUsersList = userRes.data.top_users ?? []; + const interactionGraphRaw = interactionRes.data?.interaction_graph ?? {}; + const topPairsRaw = interactionRes.data?.top_interaction_pairs ?? []; + + const filteredUsers: typeof usersList = []; + for (const user of usersList) { + if (isDeletedUser(user.author)) continue; + filteredUsers.push(user); + } + + const filteredTopUsers: typeof topUsersList = []; + for (const user of topUsersList) { + if (isDeletedUser(user.author)) continue; + filteredTopUsers.push(user); + } + + const filteredInteractionGraph: Record> = {}; + for (const [source, targets] of Object.entries(interactionGraphRaw)) { + if (isDeletedUser(source)) { + continue; + } + + const nextTargets: Record = {}; + for (const [target, count] of Object.entries(targets)) { + if (isDeletedUser(target)) { + continue; + } + nextTargets[target] = count; + } + + filteredInteractionGraph[source] = nextTargets; + } + + const filteredTopInteractionPairs: typeof topPairsRaw = []; + for (const pairEntry of topPairsRaw) { + const pair = pairEntry[0]; + const source = pair[0]; + const target = pair[1]; + if (isDeletedUser(source) || isDeletedUser(target)) { + continue; + } + filteredTopInteractionPairs.push(pairEntry); + } + const combinedUserData: UserAnalysisResponse = { ...userRes.data, - interaction_graph: interactionRes.data?.interaction_graph ?? {}, + users: filteredUsers, + top_users: filteredTopUsers, + interaction_graph: filteredInteractionGraph, }; const combinedContentData: ContentAnalysisResponse = { @@ -134,13 +186,24 @@ const StatPage = () => { ...emotionalRes.data, }; + const filteredInteractionData: InteractionAnalysisResponse = { + ...interactionRes.data, + interaction_graph: filteredInteractionGraph, + top_interaction_pairs: filteredTopInteractionPairs, + }; + + const filteredSummary: SummaryResponse = { + ...summaryRes.data, + unique_users: filteredUsers.length, + }; + setUserData(combinedUserData); setTimeData(timeRes.data || null); setContentData(combinedContentData); setLinguisticData(linguisticRes.data || null); - setInteractionData(interactionRes.data || null); + setInteractionData(filteredInteractionData || null); setCulturalData(culturalRes.data || null); - setSummary(summaryRes.data || null); + setSummary(filteredSummary || null); }) .catch((e) => setError("Failed to load statistics: " + String(e))) .finally(() => setLoading(false));