Files
pinmap-to-pinlist/Code/src/validator.py

190 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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