Skip to content

Commit 35f080f

Browse files
daffaromeroclaude
andcommitted
fix: handle arbitrarily deep BAB nesting in phantom-BAB filter
The previous filter only checked direct children of each top-level BAB node when determining whether it had pasal content. This missed the BAB → Bagian → Paragraf → Pasal nesting depth documented in the schema, causing those BABs to be silently filtered out. Replace with a parent→children map + recursive hasDescendantPasal() that walks the full subtree at any depth, so a BAB is only filtered if no structural node in its entire subtree is a direct parent of a pasal. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 446166f commit 35f080f

File tree

1 file changed

+23
-10
lines changed
  • apps/web/src/app/[locale]/peraturan/[type]/[slug]

1 file changed

+23
-10
lines changed

apps/web/src/app/[locale]/peraturan/[type]/[slug]/page.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ async function LawReaderSection({
301301

302302
const pageUrl = `https://pasal.id/peraturan/${type.toLowerCase()}/${slug}`;
303303

304-
// Build a set of structural node IDs that have at least one pasal child.
304+
// Build a set of structural node IDs that have at least one pasal child (at any depth).
305305
// Used to skip empty structural nodes (e.g. TOC entries parsed as BAB nodes).
306306
const structuralIdsWithPasals = new Set(
307307
(pasalParentIds || []).map((r) => r.parent_id).filter(Boolean),
@@ -311,6 +311,25 @@ async function LawReaderSection({
311311
const allStructuralNodes = structuralNodes || [];
312312
const allPasals = pasalNodes || [];
313313

314+
// Pre-build a parent→children map for O(n) descendant traversal.
315+
const structuralChildrenMap = new Map<number, number[]>();
316+
for (const n of allStructuralNodes) {
317+
if (n.parent_id !== null) {
318+
const siblings = structuralChildrenMap.get(n.parent_id) ?? [];
319+
siblings.push(n.id);
320+
structuralChildrenMap.set(n.parent_id, siblings);
321+
}
322+
}
323+
324+
/** Returns true if nodeId or any of its structural descendants has a pasal. */
325+
function hasDescendantPasal(nodeId: number): boolean {
326+
if (structuralIdsWithPasals.has(nodeId)) return true;
327+
for (const childId of structuralChildrenMap.get(nodeId) ?? []) {
328+
if (hasDescendantPasal(childId)) return true;
329+
}
330+
return false;
331+
}
332+
314333
// Filter out structural nodes (BABs, Bagians) that have no pasal content in the DB.
315334
// This removes table-of-contents entries that the parser mistakenly captures as structural
316335
// nodes — common in ratification laws (e.g. UU 6/2023) where the attached law's TOC
@@ -320,15 +339,9 @@ async function LawReaderSection({
320339
const babNodes = allStructuralNodes.filter((node) => {
321340
// Keep sub-sections unconditionally — they're filtered indirectly via their parent BAB.
322341
if (node.parent_id !== null) return true;
323-
// For top-level structural nodes, keep only those that have at least one pasal
324-
// directly or through any of their direct children (Bagian/Paragraf).
325-
const childIds = new Set(
326-
allStructuralNodes.filter((n) => n.parent_id === node.id).map((n) => n.id),
327-
);
328-
return (
329-
structuralIdsWithPasals.has(node.id) ||
330-
[...childIds].some((id) => structuralIdsWithPasals.has(id))
331-
);
342+
// For top-level structural nodes, keep only those with at least one pasal at any depth
343+
// (handles BAB → Bagian → Paragraf → Pasal nesting, not just direct children).
344+
return hasDescendantPasal(node.id);
332345
});
333346

334347
const mainContent = (

0 commit comments

Comments
 (0)