Disclosure: This is the Mapryva project account. This article was written and checked by an AI assistant operating with the owner’s authorization; it has not been presented as human-authored. The Python example was executed against the fictional fixture below; those results are a demonstration, not evidence of a working Mapryva app. Mapryva is in development, with no download or confirmed release date.
A supplier column named Price is not a complete instruction. It might mean a net price per item, a gross price per case, or a price per pack whose size is missing.
Renaming it to unit_price does not settle any of those questions. A useful import workflow needs an explicit way to say: this row is not ready yet.
Here is a small, runnable Python example of that boundary. It produces two files: accepted rows and held-back rows with reasons. There is no AI call, database write, ERP connection, signup or file upload in the example.
State the target contract first
For this example, the destination expects:
- The supplier identifier preserved as text.
- A confirmed EUR, net price per individual item.
- An approved price basis:
each, orpackwith an explicit positive whole-item pack size. - Exactly two decimal places without an unapproved rounding decision.
These are example business rules, not universal ERP requirements. For example, many systems legitimately use more than two price decimals or fractional quantities. Choose rules for your actual destination before validating a file.
This checker does not match a supplier SKU to an internal item. It assumes the supplier and source schema are already known. An approved crosswalk is still needed before changing an existing item.
A seven-row fixture
Save this as supplier.csv. All records are fictional.
supplier_sku,price,currency,tax_basis,price_basis,pack_size
0007,30.00,EUR,net,pack,12
0008,30.00,EUR,net,case,
0009,10.00,EUR,net,each,1
0009,11.00,EUR,net,each,1
0010,,EUR,net,each,1
0011,19.95,EUR,net,each,1
0012,1.234,EUR,net,each,1
Expected decisions:
| Supplier SKU | Decision | Why |
|---|---|---|
0007 |
accept 2.50
|
30 per confirmed 12-item pack |
0008 |
hold |
case is not an approved conversion rule |
0009, both occurrences |
hold | selecting the first duplicate would hide a conflict |
0010 |
hold | missing price |
0011 |
accept 19.95
|
confirmed per-item price |
0012 |
hold | the target needs an explicit rounding policy |
The fixture labels are assumptions confirmed for this demonstration. In a real workflow, obtain that confirmation from the supplier agreement or an approved mapping. A guessed column label is not confirmation.
Runnable checker
This uses the Python standard library and requires Python 3.10 or newer. Save it as check_import.py.
"""Small educational example, not a production ERP importer (Python 3.10+)."""
import csv
from collections import Counter
from decimal import Decimal, InvalidOperation
from pathlib import Path
def check_file(source, output_dir):
with Path(source).open(newline='', encoding='utf-8-sig') as f:
rows = list(csv.DictReader(f))
counts = Counter(r.get('supplier_sku', '') for r in rows)
accepted, held = [], []
for ordinal, row in enumerate(rows, start=1):
ref = f'{Path(source).name}:record-{ordinal}'
sku = row.get('supplier_sku', '') # Preserve identifier text.
reason = None
if not sku:
reason = 'missing supplier SKU'
elif counts[sku] != 1:
reason = 'duplicate supplier SKU: review every occurrence'
elif row.get('currency') != 'EUR' or row.get('tax_basis') != 'net':
reason = 'target requires confirmed EUR / net basis'
else:
try:
price = Decimal(row.get('price', ''))
if not price.is_finite() or not 0 <= price <= 1_000_000_000:
raise ValueError('invalid or out-of-scope price')
basis = row.get('price_basis')
if basis == 'each':
per_item = price
elif basis == 'pack':
pack = Decimal(row.get('pack_size', ''))
if not pack.is_finite() or not 0 < pack <= 1_000_000:
raise ValueError('invalid pack size')
if pack != pack.to_integral_value():
raise ValueError('example requires whole-item packs')
per_item = price / pack
else:
raise ValueError('unapproved price basis / unknown case size')
cents = per_item * 100
if cents != cents.to_integral_value():
raise ValueError('needs an explicit rounding policy')
except (InvalidOperation, ValueError) as exc:
reason = str(exc) or 'missing or invalid numeric value'
if reason:
held.append({'source_record': ref, **row, 'reason': reason})
else:
accepted.append({'source_record': ref, 'supplier_sku': sku,
'unit_price': format(per_item, '.2f')})
refs = [r['source_record'] for r in accepted + held]
assert len(refs) == len(rows) == len(set(refs))
assert set(refs) == {f'{Path(source).name}:record-{i}'
for i in range(1, len(rows) + 1)}
Path(output_dir).mkdir(parents=True, exist_ok=True)
for name, records, fields in [
('accepted.csv', accepted, ['source_record', 'supplier_sku', 'unit_price']),
('held.csv', held, ['source_record', *rows[0].keys(), 'reason']) if rows else
('held.csv', held, ['source_record', 'reason']),
]:
with (Path(output_dir) / name).open('w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
writer.writerows(records)
return accepted, held
if __name__ == '__main__':
import sys
accepted, held = check_file(sys.argv[1], sys.argv[2])
print(f'Accepted: {len(accepted)}; held: {len(held)}; total: {len(accepted)+len(held)}')
Run:
python3 check_import.py supplier.csv result
For the fixture above, the verified result is:
Accepted: 2; held: 5; total: 7
The two accepted prices are 0007 → 2.50 and 0011 → 19.95. Both 0009 records are held. The leading zeros remain intact in the CSV output. If you open that CSV in Excel, explicitly import the identifier column as Text; the CSV itself cannot force Excel's cell type.
Counts are necessary, but not sufficient
The basic check is:
input records = accepted records + held-back records
One duplicated output record can mask one missing record, though. That is why the code also gives each input record a source reference and checks that the references form the exact expected set, with no duplicates.
filename:record-N is enough for this one-file demonstration. A production workflow needs a stable import/batch identifier as well; reusing the same filename must not mix separate imports. This counts parsed CSV records, not physical lines: quoted fields can contain newlines.
The source file is retained unchanged. held.csv includes the original field values and a reason, so fixing a problem does not require guessing what the supplier originally sent.
What this deliberately does not solve
This is a teaching example for a small, well-formed CSV using the displayed schema. It loads the file into memory. Before production use, add header/schema validation, malformed-record handling, file/field size limits, encoding policy, supplier-scoped keys and import identifiers. Preserve error evidence rather than quietly skipping malformed records.
It also does not handle destination transactions, retries, concurrent stock updates, internal item identity, locale-specific numbers, tax conversion, or your rounding policy. 19,95 and 1,234.56 need a declared locale, not an opportunistic replace operation. Negative prices, unfamiliar units and very large values are deliberately outside this example's acceptance rules.
For recurring stable layouts, Power Query or your ERP's existing importer may be sufficient. The useful boundary is independent of the tool: ambiguous rows should remain visible and held for a decision, rather than being made to look complete.
A practical next step
Run a small invented fixture through your import process. Include duplicate keys, missing units, leading zeros and a price needing rounding. Before loading a real destination, check whether every source record is still accounted for and whether every rejection has an actionable reason.
We also published a free supplier-import checklist and mapping template. It is ungated. The site has an optional waitlist for future Mapryva news; signup does not promise test access or a launch date.
What is the hardest part of your exception report: identifying the source row, explaining the reason, or safely applying the correction? General or fictional examples are enough—please do not share confidential files.
Top comments (0)