If the file’s record boundaries are broken, an apparently valid early row should not be acted on first. I separated whole-file syntax preflight from semantic row review in a small synthetic module that reports what failed and which candidates remain. It opens no files and imports no products.

Parse the complete file before reviewing rows

review_csv accepts an explicitly decoded string, rather than a file path or uploaded object. It parses the complete input into CSV records before reviewing the header and rows. An unclosed quote near the end cannot leave an earlier record exposed as a review candidate. Temporary rows accumulated during parsing are discarded from the review result if syntax fails.

The parser supports quoted commas and line breaks, doubled quotes, a leading BOM and CRLF. It rejects unescaped quotes in plain cells and unexpected text after a closing quote, while allowing spaces and tabs after the closing quote. It performs no encoding detection, file opening or source download.

Declare three columns and a deliberately small scope

The header is exactly label,amount,note after trimming header cells. A different order or a different number of header columns rejects the file. Input is limited to 12,000 characters and one hundred data records. After trimming, label must contain one to eighty characters and note at most two hundred. These are small synthetic-lab limits, not inferred rules for a company’s real files.

Blank records are skipped, while quoted line breaks remain cell content. A reported record number identifies a logical nonblank CSV record rather than a physical line in the text. That makes the reported unit explicit when a cell spans several lines. Connecting these results to a real editor’s line positions requires additional source-location data.

Return an integer amount without inferring its unit

amount is a positive decimal string with at most two fractional digits. Correct thousands grouping is supported, with an upper bound of 999999.99. Decimal interprets the value before multiplying by one hundred to produce amount_minor as an integer. This is an explicit synthetic two-decimal unit, not currency identification or exchange-rate inference.

Letters, symbols, unit suffixes, negative or zero values, scientific notation, malformed grouping and three fractional digits are rejected. The native app has a separate positive-finite-number contract that allows broader decimal forms. This package is therefore not advertised as a drop-in replacement accepting exactly the same inputs. A host must define its real amount unit and limits before integration.

Explore the implementation

Complete independent synthetic implementation

"""Independent synthetic CSV review. No file I/O, callbacks, storage or network."""
import re
from decimal import Decimal

class CsvSyntaxError(ValueError):
    pass

def parse_records(text):
    if type(text) is not str:
        raise TypeError('TEXT_REQUIRED')
    if len(text)>12000:
        raise ValueError('TEXT_LIMIT')
    text=text.removeprefix('\ufeff')
    rows=[];row=[];cell=[];mode='start';index=0
    def end_cell():
        nonlocal cell,mode
        row.append(''.join(cell));cell=[];mode='start'
    def end_row():
        nonlocal row
        end_cell()
        if any(part.strip() for part in row):rows.append(row)
        row=[]
    while index<len(text):
        char=text[index]
        if mode=='quoted':
            if char=='"':
                if text[index+1:index+2]=='"':cell.append('"');index+=1
                else:mode='closed'
            else:cell.append(char)
        elif char==',':end_cell()
        elif char in '\r\n':
            if char=='\r' and text[index+1:index+2]=='\n':index+=1
            end_row()
        elif mode=='closed':
            if char not in ' \t':raise CsvSyntaxError('AFTER_QUOTE')
        elif char=='"':
            if mode!='start':raise CsvSyntaxError('QUOTE_IN_PLAIN_CELL')
            mode='quoted'
        else:cell.append(char);mode='plain'
        index+=1
    if mode=='quoted':raise CsvSyntaxError('UNCLOSED_QUOTE')
    end_row()
    return rows

def amount_minor(text):
    if type(text) is not str:raise ValueError('AMOUNT_TEXT')
    text=text.strip()
    if not re.fullmatch(r'(?:[0-9]+|[0-9]{1,3}(?:,[0-9]{3})+)(?:\.[0-9]{1,2})?',text):
        raise ValueError('AMOUNT_FORMAT')
    value=Decimal(text.replace(',',''))
    if not 0<value<=Decimal('999999.99'):raise ValueError('AMOUNT_RANGE')
    return int(value*100)

def review_csv(text):
    """The input is a small, explicitly two-decimal synthetic contract.

    record numbers are logical nonblank CSV records, never physical file lines.
    Even clean rows are only candidates. There is no automatic commit policy.
    """
    base={'file_ok':False,'all_rows_valid':False,'candidates':[],
          'issues':[],'imported_count':0,'review_only':True,'performs_actions':False}
    try:rows=parse_records(text)
    except CsvSyntaxError as exc:return {**base,'phase':'syntax','issues':[{'record':None,'code':str(exc)}]}
    except ValueError as exc:return {**base,'phase':'limit','issues':[{'record':None,'code':str(exc)}]}
    if not rows or [part.strip() for part in rows[0]]!=['label','amount','note']:
        return {**base,'phase':'header','issues':[{'record':1,'code':'HEADER'}]}
    if len(rows)>101:return {**base,'phase':'limit','issues':[{'record':None,'code':'RECORD_LIMIT'}]}
    candidates=[];issues=[]
    for number,row in enumerate(rows[1:],2):
        if len(row)!=3:
            issues.append({'record':number,'code':'WIDTH'});continue
        label,raw,note=[part.strip() for part in row]
        codes=[]
        if not label or len(label)>80:codes.append('LABEL')
        if len(note)>200:codes.append('NOTE')
        try:value=amount_minor(raw)
        except ValueError:codes.append('AMOUNT')
        if codes:issues.extend({'record':number,'code':code} for code in codes)
        else:candidates.append({'label':label,'amount_minor':value,'note':note})
    if len(rows)==1:issues.append({'record':None,'code':'EMPTY_BATCH'})
    return {**base,'file_ok':True,'phase':'rows','candidates':candidates,
            'issues':issues,'all_rows_valid':bool(candidates) and not issues}

A valid file does not mean every row is valid

file_ok means syntax, header and file limits passed. A wrong record width or an invalid label, amount or note produces issues for that record while preserving unrelated valid candidates. Any issue leaves all_rows_valid false. A header without data produces EMPTY_BATCH instead of making an empty batch look like a successful application.

Even when every candidate is valid, imported_count remains zero, review_only true and performs_actions false. This code executes no registration callback and writes no store. Whether to apply some candidates, stop the entire batch or require a person’s review is host policy. Neither file_ok nor all_rows_valid is an authentication or approval decision.

Read the failure boundary in five execution records

Five independently authored synthetic cases show valid quoted input, a late syntax failure, a valid candidate alongside row issues, a nonnumeric amount and a header without records. The selector below displays stored JSON rather than executing Python. No original product information or business file is included in these records.

The bilingual selectors’ initial display and all five selections are compared with DOM stubs running the actual inline scripts. That check concerns the record viewer’s event wiring. It is not rendered-browser, keyboard, file-upload or live CSV-input verification. Parser execution is checked separately through the Python tests in the ZIP.

A read-only viewer of synthetic local records

Connect responsibilities without claiming an automatic integration

The public ZIP contains the independent code, twenty-four tests, an example runner, five results, a README and the MIT license. The same checks run in the source directory and after extraction. They cover valid quoting, no candidates after syntax failure, coexistence of row issues and valid candidates, amount forms and limits, and separation between input and returned results.

The existing parsed-row module’s id,item,quantity contract differs from this example’s label,amount,note contract. The product-field provenance module is not automatically integrated either. A host still needs real identity rules, duplicate handling, revision checks, permissions, retries and storage. Modification, redistribution and commercial reuse are allowed, but this is neither a real customer migration nor a package installed in the native product.

Download code, tests and records
Python standard library, synthetic data, modification and reuse permitted