From c725014788d6482c29dec6d9a2f7d62358e51bcb Mon Sep 17 00:00:00 2001 From: Narayanan Madaswamy Date: Tue, 14 Jul 2026 16:26:49 +0530 Subject: [PATCH] Template mapping and detecting done --- docengine/app/utils/sanitizers.py | 33 +++++++++++++++++++ .../src/app/templates/templates.component.ts | 20 +++++++++-- 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 docengine/app/utils/sanitizers.py diff --git a/docengine/app/utils/sanitizers.py b/docengine/app/utils/sanitizers.py new file mode 100644 index 0000000..bb6f50c --- /dev/null +++ b/docengine/app/utils/sanitizers.py @@ -0,0 +1,33 @@ +import re +from typing import Optional, Union + +def sanitize_amount(text: str) -> Optional[float]: + """ + Sanitizes a string representing an amount/number (e.g., "$1,234.56", "€ 50,000", "50 USD") + by removing currency symbols, commas, and other non-numeric characters (except for the decimal separator), + and casts it to a float. + + Args: + text (str): The raw extracted text from the document. + + Returns: + Optional[float]: The sanitized numeric value, or None if no number could be extracted. + """ + if not text: + return None + + # Remove obvious alphabetic currency codes, spaces, and commas + # We keep digits, period, and minus sign + cleaned_text = re.sub(r'[^\d\.-]', '', text) + + if not cleaned_text: + return None + + try: + # Handle cases where multiple periods or dashes might exist incorrectly + # We just try to cast to float. If the OCR produced something like "1.23.45", this will fail. + # A more robust regex can handle exact capture if needed. + # But this basic cast covers 95% of standard sanitized strings. + return float(cleaned_text) + except ValueError: + return None diff --git a/frontend/src/app/templates/templates.component.ts b/frontend/src/app/templates/templates.component.ts index 44cddfe..728a13a 100644 --- a/frontend/src/app/templates/templates.component.ts +++ b/frontend/src/app/templates/templates.component.ts @@ -181,11 +181,11 @@ export class TemplatesComponent implements OnInit { } } - // If we found a matching node (at least some overlap), move it from canvas to mappings + // If we found a matching node (at least some overlap), clone it to mappings if (bestMatchIdx !== -1) { - const matchedNode = page.nodes.splice(bestMatchIdx, 1)[0]; + const matchedNode = page.nodes[bestMatchIdx]; - // Because *ngFor tracks objects by reference, we clone it to force Angular to render it in the right panel cleanly + // Because *ngFor tracks objects by reference, we clone it this.mappings[field.pk_template_field_id].push({...matchedNode}); } } @@ -289,6 +289,20 @@ export class TemplatesComponent implements OnInit { // We use event.item.data which perfectly tracks the dragged object regardless of DOM indexes const clonedNode = JSON.parse(JSON.stringify(event.item.data)); + // Sanitize the value if dropped on an AMOUNT or NUMBER field + if (fieldId) { + const field = this.templateFields.find(f => f.pk_template_field_id === fieldId); + if (field && (field.field_type === 'AMOUNT' || field.field_type === 'NUMBER') && clonedNode.text_value) { + const numericText = clonedNode.text_value.replace(/[^\d\.-]/g, ''); + const floatValue = parseFloat(numericText); + if (!isNaN(floatValue)) { + clonedNode.text_value = floatValue.toString(); + } else { + clonedNode.text_value = ''; + } + } + } + // Insert the clone into the destination mapping field event.container.data.splice(event.currentIndex, 0, clonedNode);