Skip to content

fix: false QA checks on numbers#3788

Open
uvais724 wants to merge 1 commit into
tolgee:mainfrom
uvais724:qa-numbers-false-check-fix
Open

fix: false QA checks on numbers#3788
uvais724 wants to merge 1 commit into
tolgee:mainfrom
uvais724:qa-numbers-false-check-fix

Conversation

@uvais724

@uvais724 uvais724 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

The rationale behind the code changes is not to find all the special characters and handle it explicitly within the code but rather strip all the unnecessary characters encountered between the numbers (except .,) and create a normalized string which are then compared to find the errors.

The algorithm works as follows:

  • Finds all the strings which starts and ends with numbers including atleast one special character between 2 numbers.
  • Then strip all the characters which are not number, dot or comma
  • Finally match the string with the NUMBERS_REGEX, if it passes then add it to the list and return.
  • If the extracted numbers which are present in either text or base text are separated with comma then remove comma and perform the comparison because comma has no numerical value it's just a logical separator.

Resolves #3753

Summary by CodeRabbit

  • Bug Fixes
    • Improved number checking so formatted values with separators are handled more consistently.
    • Reduced false alerts for numbers written with different grouping styles, such as spaces or other separators.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Modifies MissingNumbersCheck to normalize extracted numbers by removing commas before comparison and reworks number extraction to use a lenient block regex followed by digit filtering and strict regex validation. Adds tests verifying grouped numbers with different formatting are not flagged as missing.

Changes

Missing Number Check Normalization

Layer / File(s) Summary
Comma-insensitive comparison and lenient extraction
ee/backend/app/.../MissingNumbersCheck.kt
Extracted numbers are normalized by stripping commas before comparing base and text numbers; if equivalent, base numbers are replaced with text numbers. Extraction now uses a lenient digit-block regex, filters to digits/./,, then validates against the strict number regex.
Grouped number formatting tests
ee/backend/tests/.../MissingNumbersCheckTest.kt
Adds two tests asserting no issues are reported when grouped numbers differ by space grouping or special-character grouping between base and translation.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

Not applicable.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing false QA number checks.
Linked Issues check ✅ Passed The changes normalize grouped numbers before comparison, matching issue #3753's locale-format equivalence requirement.
Out of Scope Changes check ✅ Passed The diff is limited to the missing-numbers check and focused tests, with no unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
ee/backend/app/src/main/kotlin/io/tolgee/ee/service/qa/checks/MissingNumbersCheck.kt (1)

33-34: 🎯 Functional Correctness | 🔵 Trivial | ⚖️ Poor tradeoff

Dot-grouped locales (e.g. de-DE "2.000") aren't normalized.

Only commas are stripped before comparison. Locales that use . as a thousands separator (producing an extracted value like "2.000") won't match a plain "2000" from another locale, since NUMBER_REGEX treats the dot as a valid decimal/group separator and it survives extraction unchanged. This may be intentional (dot is ambiguous between decimal point and grouping separator), but it leaves a related false-positive gap unaddressed by this fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ee/backend/app/src/main/kotlin/io/tolgee/ee/service/qa/checks/MissingNumbersCheck.kt`
around lines 33 - 34, The number normalization in MissingNumbersCheck currently
strips only commas, so dot-grouped values like “2.000” can still fail to match
“2000”. Update the normalization logic around baseNumbersNormalized and
textNumbersNormalized to also handle dot-grouped thousands separators in a safe
way, ideally in a shared helper near NUMBER_REGEX, while preserving genuine
decimal values and keeping the comparison behavior consistent across locales.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@ee/backend/app/src/main/kotlin/io/tolgee/ee/service/qa/checks/MissingNumbersCheck.kt`:
- Around line 29-38: The number comparison in MissingNumbersCheck is still
order-sensitive because it gates normalization on baseNumbersNormalized ==
textNumbersNormalized, so reordered or partially matching numeric strings can
skip comma stripping entirely. Update the logic around extractNumbers and the
baseNumbersNormalized/textNumbersNormalized handling to compare numbers
independently rather than as full ordered lists, ideally by matching
comma-stripped values with a multiset-style approach. Keep the normalization
applied per number so equivalent values are recognized even when their order
differs.
- Around line 61-78: The LENIENT_BLOCK_REGEX in
MissingNumbersCheck.extractNumbers is too broad and merges unrelated numeric
tokens like ranges, times, and dates into one block. Tighten the separator
handling so only valid numeric grouping/decimal characters are accepted, or
explicitly document and test the broader behavior if it is intentional. Update
the logic around LENIENT_BLOCK_REGEX and NUMBER_REGEX, and add coverage for
cases like 3-4, 10:30, and 01/02 to prevent regressions.

---

Nitpick comments:
In
`@ee/backend/app/src/main/kotlin/io/tolgee/ee/service/qa/checks/MissingNumbersCheck.kt`:
- Around line 33-34: The number normalization in MissingNumbersCheck currently
strips only commas, so dot-grouped values like “2.000” can still fail to match
“2000”. Update the normalization logic around baseNumbersNormalized and
textNumbersNormalized to also handle dot-grouped thousands separators in a safe
way, ideally in a shared helper near NUMBER_REGEX, while preserving genuine
decimal values and keeping the comparison behavior consistent across locales.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1947a79a-b7e9-4561-ab2a-8268bb896a65

📥 Commits

Reviewing files that changed from the base of the PR and between f14e1b8 and 093709d.

📒 Files selected for processing (2)
  • ee/backend/app/src/main/kotlin/io/tolgee/ee/service/qa/checks/MissingNumbersCheck.kt
  • ee/backend/tests/src/test/kotlin/io/tolgee/ee/service/qa/checks/MissingNumbersCheckTest.kt

Comment on lines +29 to +38
var baseNumbers = extractNumbers(base)
var textNumbers = extractNumbers(text)

//compare both numbers if they have comma then make them same and then compare
val baseNumbersNormalized = baseNumbers.map { it.replace(",", "") }
val textNumbersNormalized = textNumbers.map { it.replace(",", "") }

if(baseNumbersNormalized == textNumbersNormalized) {
baseNumbers = textNumbers
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Ordered-list comparison can miss reordered/formatting-equivalent numbers.

baseNumbersNormalized == textNumbersNormalized is a positional list comparison. If the translation reorders numeric mentions (common across locales) or if any other number differs elsewhere in the string, the whole-array equality check fails and no normalization is applied at all — even for numbers that would otherwise be equivalent after comma-stripping. This re-introduces the false-positive class the PR is meant to fix for anything beyond exact-order, exact-count matches.

Consider normalizing each number independently (e.g., build multisets keyed on the comma-stripped value) instead of gating on whole-list equality:

♻️ Suggested normalization approach
-    var baseNumbers = extractNumbers(base)
-    var textNumbers = extractNumbers(text)
-
-    //compare both numbers if they have comma then make them same and then compare
-    val baseNumbersNormalized = baseNumbers.map { it.replace(",", "") }
-    val textNumbersNormalized = textNumbers.map { it.replace(",", "") }
-
-    if(baseNumbersNormalized == textNumbersNormalized) {
-      baseNumbers = textNumbers
-    }
-
-    val baseMultiset = baseNumbers.groupingBy { it }.eachCount()
-    val textMultiset = textNumbers.groupingBy { it }.eachCount()
+    val baseNumbers = extractNumbers(base).map { it.replace(",", "") }
+    val textNumbers = extractNumbers(text).map { it.replace(",", "") }
+
+    val baseMultiset = baseNumbers.groupingBy { it }.eachCount()
+    val textMultiset = textNumbers.groupingBy { it }.eachCount()
📝 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
var baseNumbers = extractNumbers(base)
var textNumbers = extractNumbers(text)
//compare both numbers if they have comma then make them same and then compare
val baseNumbersNormalized = baseNumbers.map { it.replace(",", "") }
val textNumbersNormalized = textNumbers.map { it.replace(",", "") }
if(baseNumbersNormalized == textNumbersNormalized) {
baseNumbers = textNumbers
}
val baseNumbers = extractNumbers(base).map { it.replace(",", "") }
val textNumbers = extractNumbers(text).map { it.replace(",", "") }
val baseMultiset = baseNumbers.groupingBy { it }.eachCount()
val textMultiset = textNumbers.groupingBy { it }.eachCount()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ee/backend/app/src/main/kotlin/io/tolgee/ee/service/qa/checks/MissingNumbersCheck.kt`
around lines 29 - 38, The number comparison in MissingNumbersCheck is still
order-sensitive because it gates normalization on baseNumbersNormalized ==
textNumbersNormalized, so reordered or partially matching numeric strings can
skip comma stripping entirely. Update the logic around extractNumbers and the
baseNumbersNormalized/textNumbersNormalized handling to compare numbers
independently rather than as full ordered lists, ideally by matching
comma-stripped values with a multiset-style approach. Keep the normalization
applied per number so equivalent values are recognized even when their order
differs.

Comment on lines +61 to +78
private val LENIENT_BLOCK_REGEX = Regex("""\d+(?:[^0-9]\d+)*""")
private val NUMBER_REGEX = Regex("""\d+([.,]\d+)*""")

fun extractNumbers(text: String): List<String> {
return NUMBER_REGEX.findAll(text).map { it.value }.toList()
return LENIENT_BLOCK_REGEX.findAll(text).mapNotNull { matchResult ->
val rawBlock = matchResult.value

// Step 2: Cycle through and keep ONLY digits, periods, and commas
val filteredString = rawBlock.filter { it.isDigit() || it == '.' || it == ',' }

// Step 3: Pass the filtered string through your target regex
// If it matches completely, we return the value; otherwise, we discard it (mapNotNull)
if (NUMBER_REGEX.matches(filteredString)) {
filteredString
} else {
null
}
}.toList()

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Target file ==\n'
sed -n '1,220p' ee/backend/app/src/main/kotlin/io/tolgee/ee/service/qa/checks/MissingNumbersCheck.kt

printf '\n== Search usages/tests ==\n'
rg -n "MissingNumbersCheck|extractNumbers\\(" ee/backend/app/src -g '*.{kt,kts}'

printf '\n== Candidate test files ==\n'
fd -a "MissingNumbersCheck" ee/backend/app/src/test ee/backend/app/src/main || true

Repository: tolgee/tolgee-platform

Length of output: 3704


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Repo files matching MissingNumbersCheck / QA number tests ==\n'
git ls-files | rg 'MissingNumbersCheck|qa|QA|Number|Numbers|MissingNumbers'

printf '\n== Search for extractNumbers test references ==\n'
rg -n "extractNumbers\\(|MISSING_NUMBERS|QA_NUMBERS_MISSING|MissingNumbersCheck" . -g '*.{kt,kts,java,ts,tsx,js,jsx}'

printf '\n== Show nearby QA check files ==\n'
git ls-files ee/backend/app/src/main/kotlin/io/tolgee/ee/service/qa/checks | sed -n '1,120p'

Repository: tolgee/tolgee-platform

Length of output: 19783


🏁 Script executed:

#!/bin/bash
set -euo pipefail

test_file='ee/backend/tests/src/test/kotlin/io/tolgee/ee/service/qa/checks/MissingNumbersCheckTest.kt'
wc -l "$test_file"
sed -n '1,220p' "$test_file"

Repository: tolgee/tolgee-platform

Length of output: 2999


🏁 Script executed:

python3 - <<'PY'
import re

LENIENT_BLOCK_REGEX = re.compile(r"\d+(?:[^0-9]\d+)*")
NUMBER_REGEX = re.compile(r"\d+([.,]\d+)*")

def extract_numbers(text):
    out = []
    for m in LENIENT_BLOCK_REGEX.finditer(text):
        raw = m.group(0)
        filtered = ''.join(ch for ch in raw if ch.isdigit() or ch in '.,')
        if NUMBER_REGEX.fullmatch(filtered):
            out.append(filtered)
    return out

samples = [
    "2 000",
    "2_0_00",
    "1,000",
    "9.99",
    "3-4",
    "16:9",
    "10:30",
    "01/02",
    "2024-07-03",
    "abc 3-4 def 5",
]
for s in samples:
    print(f"{s!r} -> {extract_numbers(s)}")
PY

Repository: tolgee/tolgee-platform

Length of output: 375


Lenient block regex merges unrelated numeric tokens. \d+(?:[^0-9]\d+)* also collapses 3-4, 10:30, 01/02, etc. into a single number, not just grouping separators. Restrict the allowed separators to actual numeric grouping/decimal characters, or add coverage for these cases if this broader behavior is intended.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ee/backend/app/src/main/kotlin/io/tolgee/ee/service/qa/checks/MissingNumbersCheck.kt`
around lines 61 - 78, The LENIENT_BLOCK_REGEX in
MissingNumbersCheck.extractNumbers is too broad and merges unrelated numeric
tokens like ranges, times, and dates into one block. Tighten the separator
handling so only valid numeric grouping/decimal characters are accepted, or
explicitly document and test the broader behavior if it is intentional. Update
the logic around LENIENT_BLOCK_REGEX and NUMBER_REGEX, and add coverage for
cases like 3-4, 10:30, and 01/02 to prevent regressions.

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.

QA checks: strings containing numbers generate false QA checks

1 participant