190 lines
6.8 KiB
Python
190 lines
6.8 KiB
Python
"""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
|