"""Versioned, private quote imports and read-only runtime access.""" import json import re import sqlite3 from decimal import Decimal, InvalidOperation def init_quote_tables(connection): connection.execute('''CREATE TABLE IF NOT EXISTS quote_books ( id INTEGER PRIMARY KEY, source_sha256 TEXT NOT NULL UNIQUE, source_name TEXT NOT NULL, active INTEGER NOT NULL DEFAULT 0)''') connection.execute('''CREATE TABLE IF NOT EXISTS quote_rates ( id INTEGER PRIMARY KEY, book_id INTEGER NOT NULL, service TEXT NOT NULL, kind TEXT NOT NULL, origin TEXT NOT NULL, zone TEXT NOT NULL, unit TEXT NOT NULL, tax TEXT NOT NULL, min_weight TEXT NOT NULL, price TEXT NOT NULL, eta TEXT NOT NULL, destinations_json TEXT NOT NULL, source_sheet TEXT NOT NULL, source_cell TEXT NOT NULL)''') connection.execute('CREATE INDEX IF NOT EXISTS idx_quote_rates_book ON quote_rates(book_id)') connection.execute('''CREATE TABLE IF NOT EXISTS quote_warehouses ( id INTEGER PRIMARY KEY, book_id INTEGER NOT NULL, code TEXT NOT NULL, zone TEXT NOT NULL, state TEXT NOT NULL, postal TEXT NOT NULL, address TEXT NOT NULL, source_sheet TEXT NOT NULL, source_cell TEXT NOT NULL)''') connection.execute('CREATE INDEX IF NOT EXISTS idx_quote_warehouse_code ON quote_warehouses(book_id,code)') connection.execute('''CREATE TABLE IF NOT EXISTS quote_postcodes ( book_id INTEGER NOT NULL, postal TEXT NOT NULL, severity TEXT NOT NULL, fee TEXT, PRIMARY KEY(book_id,postal))''') connection.execute('''CREATE TABLE IF NOT EXISTS quote_documents ( id INTEGER PRIMARY KEY, book_id INTEGER NOT NULL, sheet TEXT NOT NULL, source_cell TEXT NOT NULL, content TEXT NOT NULL)''') def decimal_text(value, positive=False): try: number = Decimal(str(value)) except (InvalidOperation, ValueError): raise ValueError('Invalid quote amount') from None if not number.is_finite() or number < 0 or number > 100000000 or (positive and not number): raise ValueError('Invalid quote amount') return format(number, 'f') def import_snapshot(connection, snapshot, *, activate=False): """Validate before writing; replace an active book atomically, never order data.""" if not isinstance(snapshot, dict) or snapshot.get('schema') != 1: raise ValueError('Unsupported quote snapshot') digest = snapshot.get('source_sha256', '') if not re.fullmatch('[0-9a-f]{64}', digest): raise ValueError('Invalid quote source checksum') source_name = str(snapshot.get('source_name', ''))[:240] if not source_name: raise ValueError('Missing quote source name') limits = {'rates':10000, 'warehouses':10000, 'postcodes':50000, 'documents':5000} for key, limit in limits.items(): if not isinstance(snapshot.get(key), list) or len(snapshot[key]) > limit: raise ValueError('Invalid quote records') rates = [] for row in snapshot['rates']: if (row.get('kind') not in ('truck', 'parcel') or row.get('unit') not in ('kg', 'cbm') or row.get('tax') not in ('included', 'excluded') or not row.get('service') or not isinstance(row.get('destinations'), list) or len(row['destinations']) > 200): raise ValueError('Invalid quote rate') rates.append((str(row['service'])[:100], row['kind'], str(row.get('origin', ''))[:40], str(row.get('zone', ''))[:40], row['unit'], row['tax'], decimal_text(row.get('min_weight', 0)), decimal_text(row['price'], positive=True), str(row.get('eta', ''))[:200], json.dumps(row['destinations'], ensure_ascii=False), str(row['source_sheet'])[:100], str(row['source_cell'])[:20])) warehouses = [(str(r.get('code', '')).strip().upper()[:40], str(r.get('zone', ''))[:40], str(r.get('state', ''))[:40], str(r.get('postal', ''))[:20], str(r.get('address', ''))[:1000], str(r['source_sheet'])[:100], str(r['source_cell'])[:20]) for r in snapshot['warehouses']] postcodes = [] for row in snapshot['postcodes']: if not re.fullmatch('[0-9]{5}', row.get('postal', '')) or row.get('severity') not in ('remote','extended','extreme'): raise ValueError('Invalid quote postcode') postcodes.append((row['postal'], row['severity'], None if row.get('fee') is None else decimal_text(row['fee']))) documents = [(str(r['sheet'])[:100], str(r['cell'])[:20], str(r['text'])[:16000]) for r in snapshot['documents']] if not rates: raise ValueError('Quote snapshot contains no rates') init_quote_tables(connection) if not connection.in_transaction: connection.execute('BEGIN IMMEDIATE') connection.execute('SAVEPOINT quote_import') try: existing = connection.execute('SELECT id FROM quote_books WHERE source_sha256=?', (digest,)).fetchone() if existing: book_id = existing[0] else: book_id = connection.execute('INSERT INTO quote_books(source_sha256,source_name) VALUES(?,?)', (digest, source_name)).lastrowid connection.executemany('''INSERT INTO quote_rates(book_id,service,kind,origin,zone,unit,tax, min_weight,price,eta,destinations_json,source_sheet,source_cell) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)''', [(book_id, *r) for r in rates]) connection.executemany('''INSERT INTO quote_warehouses(book_id,code,zone,state,postal,address,source_sheet,source_cell) VALUES(?,?,?,?,?,?,?,?)''', [(book_id, *r) for r in warehouses]) connection.executemany('INSERT INTO quote_postcodes(book_id,postal,severity,fee) VALUES(?,?,?,?)', [(book_id, *r) for r in postcodes]) connection.executemany('INSERT INTO quote_documents(book_id,sheet,source_cell,content) VALUES(?,?,?,?)', [(book_id, *r) for r in documents]) if activate: connection.execute('UPDATE quote_books SET active=0 WHERE active<>0') connection.execute('UPDATE quote_books SET active=1 WHERE id=?', (book_id,)) connection.execute('RELEASE quote_import') except Exception: connection.execute('ROLLBACK TO quote_import') connection.execute('RELEASE quote_import') raise return {'book_id':book_id, 'rates':len(rates), 'warehouses':len(warehouses), 'postcodes':len(postcodes), 'documents':len(documents), 'active':bool(activate), 'existing':bool(existing)} def load_book(): """One read transaction prevents mixed versions during a concurrent import.""" from .db import connect connection = connect() try: connection.row_factory = sqlite3.Row connection.execute('BEGIN') book = connection.execute('SELECT id FROM quote_books WHERE active=1 ORDER BY id DESC LIMIT 1').fetchone() if not book: return None book_id = book['id'] result = {'id':book_id} for table, name in (('quote_rates','rates'), ('quote_warehouses','warehouses'), ('quote_documents','documents')): result[name] = [dict(r) for r in connection.execute(f'SELECT * FROM {table} WHERE book_id=?', (book_id,))] for rate in result['rates']: rate['destinations'] = json.loads(rate.pop('destinations_json')) return result finally: connection.close() def postcode_fee(book_id, postal): from .db import connect connection = connect() try: connection.row_factory = sqlite3.Row row = connection.execute('SELECT severity,fee FROM quote_postcodes WHERE book_id=? AND postal=?', (book_id, postal)).fetchone() return dict(row) if row else None finally: connection.close()