"""Build a source-verified v2 research batch for all nine domain entities.""" import collections import hashlib import json import math import os from pathlib import Path, PurePosixPath import sqlite3 import tempfile import zipfile import voc_db as v from intake_contract import VERSION, ENTITIES from product_insights import ANNOTATION_DDL, THEMES, append_text, text_hash def typed(value, kind, required=False): from agent_batch import identifier if value is None: v.require(not required, 'REQUIRED_VALUE_MISSING'); return if kind == 'id': identifier(value) elif kind == 'text': v.require(isinstance(value,str) and (not required or value.strip()), 'TEXT_REQUIRED') elif kind == 'date': v.require(isinstance(value,str) and v.calendar_date(value)==value, 'ISO_DATE_REQUIRED') elif kind == 'bool': v.require(type(value) is bool, 'BOOLEAN_REQUIRED') elif kind in ('count','integer'): v.require(type(value) is int and value >= (1 if kind=='integer' else 0), 'NONNEGATIVE_COUNT_OR_POSITIVE_INTEGER_REQUIRED') elif kind == 'positive': v.require(type(value) in (int,float) and math.isfinite(value) and value>0, 'POSITIVE_NUMBER_REQUIRED') elif kind == 'amount': v.require(isinstance(value,str) or type(value) in (int,float) and math.isfinite(value), 'RAW_AMOUNT_REQUIRED') elif kind == 'json': v.require(isinstance(value,(str,dict,list)), 'TEXT_OR_JSON_REQUIRED') def load(batch_path): from agent_batch import identifier, pointer path = Path(batch_path).absolute() v.require(path.is_file() and not any(p.is_symlink() for p in (path,*path.parents)), 'BATCH_NOT_FOUND_OR_REDIRECTED') raw_manifest = path.read_bytes(); v.require(len(raw_manifest)<=20000000, 'BATCH_TOO_LARGE') batch=json.loads(raw_manifest.decode('utf-8-sig')) v.require(isinstance(batch,dict) and batch.get('schema')==VERSION and not set(batch)-({'schema','batchId','producer','sources','attachments','themes'}|set(ENTITIES)), 'INVALID_BATCH_SCHEMA_OR_FIELDS') identifier(batch.get('batchId')); identifier(batch.get('producer')) v.require(isinstance(batch.get('sources'),list) and 0