"""PinMAP data validator. Validates a parsed PinMAP for structural and data integrity: 1. Pin-number uniqueness 2. Pin-number continuity (1..N with no gaps) 3. Missing PinName detection (warning, defaults to "NC") 4. Rectangular-structure sanity Usage ----- >>> from validator import validate_pinmap >>> result = validate_pinmap(pinmap) >>> if result.is_valid: ... print("All good") ... else: ... for e in result.errors: ... print(f"[ERROR] {e.message}: {e.details}") """ from collections import Counter from models import PinMAP, ValidationResult, ValidationError def validate_pinmap(pinmap: PinMAP) -> ValidationResult: """Validate a PinMAP and return a ValidationResult. Checks performed ---------------- 1. **Uniqueness** — every pin number must appear exactly once. 2. **Continuity** — pin numbers must form the sequence 1, 2, …, N with no gaps. 3. **PinName completeness** — pins with empty / whitespace-only names generate a *warning* (they will default to "NC" in the output). 4. **Structure** — width and height must each be ≥ 2. Parameters ---------- pinmap : PinMAP A pin map produced by ``pinmap_parser.parse_pinmap``. Returns ------- ValidationResult """ result = ValidationResult(is_valid=True, errors=[], warnings=[]) numbers = [p.number for p in pinmap.pins] # ── 1. Uniqueness ──────────────────────────────────────────── if len(numbers) != len(set(numbers)): counts = Counter(numbers) duplicates = sorted(n for n, c in counts.items() if c > 1) result.errors.append(ValidationError( level="error", message="Pin序号重复", details=f"重复的序号: {duplicates}", )) # ── 2. Continuity ──────────────────────────────────────────── if numbers: expected = set(range(1, max(numbers) + 1)) actual = set(numbers) missing = expected - actual if missing: result.errors.append(ValidationError( level="error", message="Pin序号不连续", details=f"缺失的序号: {sorted(missing)}", )) # ── 3. PinName completeness ────────────────────────────────── missing_names = [ p for p in pinmap.pins if not p.name or not p.name.strip() ] if missing_names: result.warnings.append(ValidationError( level="warning", message=( f"检测到 {len(missing_names)} 个引脚缺少 PinName" ), details=( f"缺失引脚序号: {[p.number for p in missing_names]}," f"将默认为 NC" ), )) # ── 4. Structure sanity ────────────────────────────────────── if pinmap.width < 2 or pinmap.height < 2: result.errors.append(ValidationError( level="error", message="方形结构不完整", details=( f"尺寸: {pinmap.width}x{pinmap.height},至少需要 2x2" ), )) # ── Final verdict ──────────────────────────────────────────── if result.errors: result.is_valid = False return result def validate_pinlist_for_map( entries: list, rows: int, cols: int, ) -> ValidationResult: """验证 PinList 数据是否适合转换为 PinMAP。 Checks performed ---------------- 1. **Continuity** — pin numbers must start from 1 with no gaps. 2. **Uniqueness** — no duplicate pin numbers. 3. **Perimeter match** — total pin count must equal (rows + cols) × 2 (the grid perimeter). 4. **Non-multiple-of-4** — if pin count is not a multiple of 4, a warning is issued (but conversion is not blocked). Parameters ---------- entries : list[PinListEntry] PinList entries, each with ``number`` and ``name`` attributes. rows : int Target PinMAP row count. cols : int Target PinMAP column count. Returns ------- ValidationResult """ result = ValidationResult(is_valid=True, errors=[], warnings=[]) numbers = [e.number for e in entries] # ── 1. Continuity (1..N, no gaps) ──────────────────────────── expected_numbers = list(range(1, len(numbers) + 1)) if numbers != expected_numbers: missing = set(expected_numbers) - set(numbers) if missing: result.errors.append(ValidationError( level="error", message="Pin序号不连续", details=f"缺失的序号: {sorted(missing)}", )) # ── 2. Uniqueness ──────────────────────────────────────────── if len(numbers) != len(set(numbers)): counts = Counter(numbers) duplicates = sorted(n for n, c in counts.items() if c > 1) result.errors.append(ValidationError( level="error", message="Pin序号存在重复", details=f"重复的序号: {duplicates}", )) # ── 3. Perimeter match ─────────────────────────────────────── # 周长公式:(rows + cols) * 2 expected_total = (rows + cols) * 2 actual_total = len(entries) if actual_total != expected_total: result.errors.append(ValidationError( level="error", message="Pin数量与网格周长不匹配", details=( f"网格 {rows}×{cols} 需要 {expected_total} 个引脚," f"但 PinList 有 {actual_total} 个" ), )) # ── 4. Non-multiple-of-4 warning ───────────────────────────── if actual_total % 4 != 0: result.warnings.append(ValidationError( level="warning", message="Pin数量不是4的倍数", details=( f"Pin数量 ({actual_total}) 不是 4 的倍数," f"四条边将不均匀分布" ), )) # ── Final verdict ──────────────────────────────────────────── if result.errors: result.is_valid = False return result