Skip to content

feat(metadata-editor): apply custom logic for multiple confidence scores and target locations#4502

Open
dlasecki-box wants to merge 1 commit intobox:masterfrom
dlasecki-box:deduplicate-confidence-scores
Open

feat(metadata-editor): apply custom logic for multiple confidence scores and target locations#4502
dlasecki-box wants to merge 1 commit intobox:masterfrom
dlasecki-box:deduplicate-confidence-scores

Conversation

@dlasecki-box
Copy link
Copy Markdown
Contributor

@dlasecki-box dlasecki-box commented Apr 13, 2026

For multiSelect metadata fields, the Intelligence API returns per-value confidence scores, confidence levels, and bounding boxes - rather than a single set per field. This doesn't align with the current review/approval (HITL) process or meta-metadata schema, which expect one confidence score and one flat list of bounding boxes per field.
This PR applies the agreed-upon workaround:

  • Confidence scores/levels: when multiple scores are returned for a field, we take the lowest score (and its level) so the user sees the most conservative assessment in the UI
  • Bounding boxes: when nested arrays of references are returned, we flatten them so all bounding boxes are visible and clickable in Preview
  • Reference entries without bounding boxes: filtered out to prevent rendering errors; fields where all references lack bounding boxes correctly trigger deselection
    Some per-value granularity is lost (one score/level stored per field instead of per option), but this is acceptable for the HITL process.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved AI suggestion confidence score extraction from arrays to select the lowest score value.
    • Enhanced reference location normalization to properly flatten nested arrays.
    • Refined metadata field selection deselection logic for better handling of entries without location data.
  • Tests

    • Added test coverage for edge cases in metadata field selection and AI suggestion processing.

@dlasecki-box dlasecki-box requested review from a team as code owners April 13, 2026 13:34
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

Walkthrough

Enhanced metadata field selection and fetching by adding bounding box validation to filter entries lacking required boxes, improving confidence score handling when provided as arrays by selecting the lowest score, and flattening reference arrays before use. Tests validate these new behaviors.

Changes

Cohort / File(s) Summary
useMetadataFieldSelection
src/elements/content-sidebar/__tests__/useMetadataFieldSelection.test.ts, src/elements/content-sidebar/hooks/useMetadataFieldSelection.ts
Added type guard to filter targetLocationEntries by boundingBox presence; updated selection logic to deselect when bounding boxes are missing or empty. Tests cover scenarios with partial and completely missing boundingBox entries.
useSidebarMetadataFetcher
src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx, src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts
Updated confidence-score extraction to select the lowest score from arrays instead of treating as single object; flattens nested reference arrays into flat targetLocation. Tests validate array handling and equal minimum score scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #4481: Updates content-sidebar metadata fetch/processing pipeline with targetLocation and bounding-box handling integration
  • #4494: Introduces bounding-box handling and selection logic feature in useMetadataFieldSelection
  • #4499: Modifies useSidebarMetadataFetcher to handle confidence score details and targetLocation in metadata autofill

Suggested labels

ready-to-merge

Suggested reviewers

  • Lindar90
  • reneshen0328
  • tjiang-box

Poem

🐰 With bounding boxes now checked with care,
Confidence scores parsed fair and square,
References flattened without a frown,
This metadata magic settles down! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: applying custom logic for handling multiple confidence scores and target locations in metadata fields.
Description check ✅ Passed The PR description provides a clear explanation of the context, the implemented workaround, and the tradeoffs involved, though it doesn't follow the repository's standard template structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx (1)

818-875: Add a regression test for empty confidence arrays.

Please add a case where confidence_score.field1 is [] to ensure extraction does not throw and simply omits aiSuggestionConfidenceScore.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx`
around lines 818 - 875, Add a regression test that ensures empty confidence
arrays are handled gracefully: in the test file use
mockAPI.extractStructured.mockResolvedValue to return answer: { field1: 'value1'
} and confidence_score: { field1: [] } (and created_at/completion_reason as in
other tests), call setupHook('123', true) and await result.current.status ===
STATUS.SUCCESS, then call result.current.extractSuggestions('templateKey',
'global') and assert that the returned suggestion for field1 contains
aiSuggestion: 'value1' but does NOT include aiSuggestionConfidenceScore (or that
aiSuggestionConfidenceScore is undefined), referencing the existing tests like
"should pick the lowest confidence score when confidence_score is an array" and
the extractSuggestions flow to place the new test alongside them.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts`:
- Around line 287-293: The current computation of lowestConfidence in
useSidebarMetadataFetcher.ts uses Array.prototype.reduce without guarding
against an empty array, which throws when fieldConfidence === []; fix by
checking Array.isArray(fieldConfidence) && fieldConfidence.length > 0 before
reducing (or use reduce with an initial value such as fieldConfidence[0]) and
provide a safe fallback when the array is empty or fieldConfidence is
null/undefined so that result.aiSuggestionConfidenceScore is only assigned when
a valid lowestConfidence with .score and .level exists (or assign a safe
default/null). Update the logic around the symbols fieldConfidence,
lowestConfidence, and result.aiSuggestionConfidenceScore to handle empty arrays
and avoid TypeError.

---

Nitpick comments:
In `@src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx`:
- Around line 818-875: Add a regression test that ensures empty confidence
arrays are handled gracefully: in the test file use
mockAPI.extractStructured.mockResolvedValue to return answer: { field1: 'value1'
} and confidence_score: { field1: [] } (and created_at/completion_reason as in
other tests), call setupHook('123', true) and await result.current.status ===
STATUS.SUCCESS, then call result.current.extractSuggestions('templateKey',
'global') and assert that the returned suggestion for field1 contains
aiSuggestion: 'value1' but does NOT include aiSuggestionConfidenceScore (or that
aiSuggestionConfidenceScore is undefined), referencing the existing tests like
"should pick the lowest confidence score when confidence_score is an array" and
the extractSuggestions flow to place the new test alongside them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5e18a21d-3f8e-4863-b9cd-700933d38bfe

📥 Commits

Reviewing files that changed from the base of the PR and between b455cdf and f06ee4e.

📒 Files selected for processing (4)
  • src/elements/content-sidebar/__tests__/useMetadataFieldSelection.test.ts
  • src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx
  • src/elements/content-sidebar/hooks/useMetadataFieldSelection.ts
  • src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts

Comment on lines +287 to 293
const lowestConfidence = Array.isArray(fieldConfidence)
? fieldConfidence.reduce((min, curr) => (curr.score < min.score ? curr : min))
: fieldConfidence;
result.aiSuggestionConfidenceScore = {
value: fieldConfidence.score,
level: fieldConfidence.level,
value: lowestConfidence.score,
level: lowestConfidence.level,
isAccepted: false,
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only verification: inspect reducer usage and test coverage for empty confidence arrays
rg -n -C3 "fieldConfidence\\.reduce|confidence_score.*\\[\\s*\\]" src/elements/content-sidebar

Repository: box/box-ui-elements

Length of output: 984


🏁 Script executed:

#!/bin/bash
# Get more context around the problematic code to understand data flow
sed -n '250,310p' src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts

Repository: box/box-ui-elements

Length of output: 2608


🏁 Script executed:

#!/bin/bash
# Search for where confidenceScores is assigned/populated
rg -n "confidenceScores\s*=" src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts -B2 -A2

Repository: box/box-ui-elements

Length of output: 246


🏁 Script executed:

#!/bin/bash
# Look for test files related to this hook
fd -e test.ts -e spec.ts -e test.tsx -e spec.tsx | grep -i "sidebarmeta\|confidence"

Repository: box/box-ui-elements

Length of output: 136


🏁 Script executed:

#!/bin/bash
# Check for type definitions of confidenceScores
rg -n "type.*Confidence|interface.*Confidence" src/elements/content-sidebar/hooks/ -A3

Repository: box/box-ui-elements

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check test file for empty confidence array coverage
cat -n src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx | head -150

Repository: box/box-ui-elements

Length of output: 4767


🏁 Script executed:

#!/bin/bash
# Search for confidence_score mock responses in tests
rg -n "confidence_score|confidenceScores" src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx -B2 -A2

Repository: box/box-ui-elements

Length of output: 3744


🏁 Script executed:

#!/bin/bash
# Look for any array length checks or guards on confidence data
rg -n "length\s*[><=]|\.length\s*\?" src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts

Repository: box/box-ui-elements

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for extractStructured API documentation or type definitions
rg -n "extractStructured|confidence_score" src/api/ -l

Repository: box/box-ui-elements

Length of output: 201


🏁 Script executed:

#!/bin/bash
# Check if there's any filtering on confidenceScores before field access
sed -n '260,290p' src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts

Repository: box/box-ui-elements

Length of output: 1400


🏁 Script executed:

#!/bin/bash
# Look for type definitions of the API response
rg -n "confidence_score.*:" src/ -A2 --type ts --type tsx | head -40

Repository: box/box-ui-elements

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Check the API schema definitions
cat -n src/api/schemas/AiExtractResponse.js

Repository: box/box-ui-elements

Length of output: 1134


🏁 Script executed:

#!/bin/bash
# Also check the other schema file
cat -n src/api/schemas/AiExtractStructured.js

Repository: box/box-ui-elements

Length of output: 3773


🏁 Script executed:

#!/bin/bash
# Check if there are any constraints on confidence_score array values in API tests
rg -n "confidence_score.*\[\s*\]|confidence_score.*\[\]" src/ -A2 -B2

Repository: box/box-ui-elements

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Look at the full test for array confidence scores to see all test cases
sed -n '818,875p' src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx

Repository: box/box-ui-elements

Length of output: 2491


🏁 Script executed:

#!/bin/bash
# Check if confidence_score is validated or processed before being used
rg -n "isEmpty|!.*confidence|validateConfidence" src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts -B2 -A2

Repository: box/box-ui-elements

Length of output: 547


🏁 Script executed:

#!/bin/bash
# Check if there's a filter or validation on confidenceScores before the field mapping
sed -n '258,275p' src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts

Repository: box/box-ui-elements

Length of output: 749


Guard against empty confidence arrays before reduce.

Line 288 will throw a TypeError when fieldConfidence is an empty array [], because if (fieldConfidence) does not prevent empty arrays (they are truthy in JavaScript) and Array.reduce() without an initial value crashes on empty arrays. While type definitions suggest confidence_score contains single objects, the code explicitly handles arrays and tests confirm arrays are a valid response format. This gap should be protected against.

Proposed fix
-                    const lowestConfidence = Array.isArray(fieldConfidence)
-                        ? fieldConfidence.reduce((min, curr) => (curr.score < min.score ? curr : min))
-                        : fieldConfidence;
-                    result.aiSuggestionConfidenceScore = {
-                        value: lowestConfidence.score,
-                        level: lowestConfidence.level,
-                        isAccepted: false,
-                    };
+                    const confidenceEntries = Array.isArray(fieldConfidence) ? fieldConfidence : [fieldConfidence];
+                    const validConfidenceEntries = confidenceEntries.filter(
+                        entry => entry && typeof entry.score === 'number',
+                    );
+
+                    if (validConfidenceEntries.length > 0) {
+                        const lowestConfidence = validConfidenceEntries.reduce((min, curr) =>
+                            curr.score < min.score ? curr : min,
+                        );
+                        result.aiSuggestionConfidenceScore = {
+                            value: lowestConfidence.score,
+                            level: lowestConfidence.level,
+                            isAccepted: false,
+                        };
+                    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const lowestConfidence = Array.isArray(fieldConfidence)
? fieldConfidence.reduce((min, curr) => (curr.score < min.score ? curr : min))
: fieldConfidence;
result.aiSuggestionConfidenceScore = {
value: fieldConfidence.score,
level: fieldConfidence.level,
value: lowestConfidence.score,
level: lowestConfidence.level,
isAccepted: false,
const confidenceEntries = Array.isArray(fieldConfidence) ? fieldConfidence : [fieldConfidence];
const validConfidenceEntries = confidenceEntries.filter(
entry => entry && typeof entry.score === 'number',
);
if (validConfidenceEntries.length > 0) {
const lowestConfidence = validConfidenceEntries.reduce((min, curr) =>
curr.score < min.score ? curr : min,
);
result.aiSuggestionConfidenceScore = {
value: lowestConfidence.score,
level: lowestConfidence.level,
isAccepted: false,
};
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts` around lines
287 - 293, The current computation of lowestConfidence in
useSidebarMetadataFetcher.ts uses Array.prototype.reduce without guarding
against an empty array, which throws when fieldConfidence === []; fix by
checking Array.isArray(fieldConfidence) && fieldConfidence.length > 0 before
reducing (or use reduce with an initial value such as fieldConfidence[0]) and
provide a safe fallback when the array is empty or fieldConfidence is
null/undefined so that result.aiSuggestionConfidenceScore is only assigned when
a valid lowestConfidence with .score and .level exists (or assign a safe
default/null). Update the logic around the symbols fieldConfidence,
lowestConfidence, and result.aiSuggestionConfidenceScore to handle empty arrays
and avoid TypeError.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a chance to get an empty array here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dlasecki-box, understood — if the API guarantees that confidence_score[field.key] is always a non-empty array when present, the reduce call is safe and the guard is unnecessary. Thanks for the clarification!


✏️ Learnings added
Learnt from: dlasecki-box
Repo: box/box-ui-elements PR: 4502
File: src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts:287-293
Timestamp: 2026-04-13T21:25:24.516Z
Learning: In `src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts`, the `confidence_score[field.key]` value from the Intelligence API is guaranteed to be non-empty when present (truthy). An empty array `[]` is not a possible response, so no guard before `Array.prototype.reduce` is needed for `fieldConfidence`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant