Adds production-grade engineering features and tools: 2D GD&T (feature control frames + datum features) via engineering/gdt.py, ISO‑129 dimension tolerance support (engineering/tolerances.py) and an ezdxf/COM plumbing for toleranced dims, a scalar drawing score + invalidity ratio (engineering/scoring.py) wired into drawing_finalize, in-place entity editors (entity_edit_text/entity_edit_geometry), and a friendly drawing_settings facade. Includes server tool wiring, COM/ezdxf backend implementations, datum-consistency critique, many unit tests, README/CLAUDE/CHANGELOG updates, and a version bump to 1.2.0. Also corrects COM backend capability label (entities_2d).
86 lines
2.7 KiB
Python
86 lines
2.7 KiB
Python
"""Scalar drawing-quality score + invalidity ratio.
|
|
|
|
Every 2025-26 CAD-generation benchmark (MUSE, CadBench) grades an *Invalidity
|
|
Ratio*, not just shape similarity. This module turns the validator's and
|
|
critique's severity counts into a single regression-trackable scalar so the
|
|
premium workflow has an objective function (the prerequisite for an automated
|
|
refiner loop and for smoke-test regression tracking).
|
|
|
|
The weights are a documented convention, not ground truth — they exist so the
|
|
number moves in the right direction (errors hurt most, info barely registers)
|
|
and can be asserted in tests.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
# Penalty weights per severity. Errors dominate; info is nearly free.
|
|
_W_ERROR = 25.0
|
|
_W_WARNING = 8.0
|
|
_W_INFO = 1.0
|
|
|
|
|
|
def score_findings(
|
|
errors: int,
|
|
warnings: int,
|
|
info: int = 0,
|
|
*,
|
|
w_error: float = _W_ERROR,
|
|
w_warning: float = _W_WARNING,
|
|
w_info: float = _W_INFO,
|
|
) -> dict:
|
|
"""Collapse severity counts into a 0-100 score + invalidity ratio.
|
|
|
|
- ``score``: ``100 - (errors·Wₑ + warnings·Wᵥᵥ + info·Wᵢ)`` clamped to [0, 100].
|
|
A clean drawing scores 100.
|
|
- ``invalidity_ratio``: ``errors / max(1, total_findings)`` — the fraction of
|
|
findings that are hard failures (0.0 when there are no findings).
|
|
- ``grade``: coarse A-F bucket for at-a-glance reporting.
|
|
"""
|
|
errors = max(0, int(errors))
|
|
warnings = max(0, int(warnings))
|
|
info = max(0, int(info))
|
|
|
|
penalty = errors * w_error + warnings * w_warning + info * w_info
|
|
score = max(0.0, min(100.0, 100.0 - penalty))
|
|
|
|
total = errors + warnings + info
|
|
invalidity_ratio = (errors / total) if total else 0.0
|
|
|
|
return {
|
|
"score": round(score, 1),
|
|
"invalidity_ratio": round(invalidity_ratio, 3),
|
|
"grade": _grade(score),
|
|
"errors": errors,
|
|
"warnings": warnings,
|
|
"info": info,
|
|
}
|
|
|
|
|
|
def combine(*severity_dicts: dict) -> dict:
|
|
"""Sum several ``{'error': n, 'warning': n, 'info': n}`` summaries then score.
|
|
|
|
Accepts the shapes emitted by both ``DrawingValidator`` (``summary``) and the
|
|
finalize ``critique_summary`` so the finalize gate can score the union of the
|
|
structural validator and the premium critique in one number.
|
|
"""
|
|
errors = warnings = info = 0
|
|
for d in severity_dicts:
|
|
if not d:
|
|
continue
|
|
errors += int(d.get("error", 0))
|
|
warnings += int(d.get("warning", 0))
|
|
info += int(d.get("info", 0))
|
|
return score_findings(errors, warnings, info)
|
|
|
|
|
|
def _grade(score: float) -> str:
|
|
if score >= 95.0:
|
|
return "A"
|
|
if score >= 85.0:
|
|
return "B"
|
|
if score >= 70.0:
|
|
return "C"
|
|
if score >= 50.0:
|
|
return "D"
|
|
return "F"
|