Working on comparison with protoliste
This commit is contained in:
1 parent
b0434d2b0c
commit
cbeec3c9ac
4 files changed
+230
-4
No files matched your search
Binary file not shown.
Binary file not shown.
@@ -606,3 +606,229 @@ def populate_table_dynamic(
|
||||
|
||||
header.reset_filters()
|
||||
table.setUpdatesEnabled(True)
|
||||
|
||||
|
||||
# ── Comparison helpers ────────────────────────────────────────────────────────
|
||||
|
||||
# Columns that uniquely identify an element across both lists
|
||||
_MATCH_KEYS = ("Domain", "Prefix", "Suffix", "Index")
|
||||
|
||||
# Columns that are compared but never diffed (they define identity)
|
||||
_SKIP_DIFF = set(_MATCH_KEYS)
|
||||
|
||||
|
||||
def _match_key(record: dict[str, Any]) -> tuple:
|
||||
"""Return the identity tuple used to match records across both lists."""
|
||||
def _norm(val: Any) -> str:
|
||||
if val is None:
|
||||
return ""
|
||||
# Normalise Index the same way it is displayed
|
||||
return _format_value("Index", val) if False else str(val).strip()
|
||||
return tuple(_norm(record.get(k)) for k in _MATCH_KEYS)
|
||||
|
||||
|
||||
def _values_equal(a: Any, b: Any) -> bool:
|
||||
"""True when two cell values should be considered identical."""
|
||||
if a is None and b is None:
|
||||
return True
|
||||
if a is None or b is None:
|
||||
return False
|
||||
# Numeric: compare with a small tolerance for floats
|
||||
try:
|
||||
fa, fb = float(a), float(b)
|
||||
import math as _math
|
||||
if _math.isfinite(fa) and _math.isfinite(fb):
|
||||
return abs(fa - fb) < 1e-5
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return str(a).strip() == str(b).strip()
|
||||
|
||||
|
||||
def _diff_text(key: str, val_a: Any, val_b: Any) -> str:
|
||||
"""
|
||||
Return the display string for a differing cell.
|
||||
|
||||
For numeric columns: 'formatted_a / formatted_b'
|
||||
For text columns: 'a / b'
|
||||
"""
|
||||
ta = _format_value(key, val_a) if val_a is not None else ""
|
||||
tb = _format_value(key, val_b) if val_b is not None else ""
|
||||
return f"{ta} / {tb}"
|
||||
|
||||
|
||||
def _make_cell(text: str, bg_brush: QBrush, bold: bool = False) -> QTableWidgetItem:
|
||||
cell = QTableWidgetItem(text)
|
||||
cell.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
|
||||
cell.setBackground(bg_brush)
|
||||
cell.setForeground(QBrush(_FONT_COLOR))
|
||||
if bold:
|
||||
f = cell.font()
|
||||
f.setBold(True)
|
||||
cell.setFont(f)
|
||||
return cell
|
||||
|
||||
|
||||
def populate_table_comparison(
|
||||
table: QTableWidget,
|
||||
list_a: list[dict[str, Any]],
|
||||
list_b: list[dict[str, Any]],
|
||||
) -> None:
|
||||
"""
|
||||
Compare two lists of element dicts and display the result in *table*.
|
||||
|
||||
Identity match
|
||||
--------------
|
||||
Two records are considered the same element when they agree on ALL of:
|
||||
Domain, Prefix, Suffix, Index
|
||||
(Index comparison is string-based after stripping whitespace so that
|
||||
"001" and 1 both match.)
|
||||
|
||||
Row rendering rules
|
||||
-------------------
|
||||
Element only in list_a (not found in list_b):
|
||||
→ All columns filled from list_a; Comment = "New Element" in bold.
|
||||
|
||||
Element in both lists, all remaining columns identical:
|
||||
→ Values from list_a; normal font.
|
||||
|
||||
Element in both lists, some columns differ:
|
||||
→ Identity columns (Domain/Prefix/Suffix/Index): value from list_a.
|
||||
→ Identical columns: value from list_a, normal font.
|
||||
→ Differing columns:
|
||||
• Numeric (int/float): "list_a_value / list_b_value" in bold
|
||||
• Text: "list_a_value / list_b_value" in bold
|
||||
→ Comment column: "Difference" in bold (unless already set by list_a).
|
||||
|
||||
Column set
|
||||
----------
|
||||
Uses the fixed COLUMNS list (same as populate_table), with filter
|
||||
buttons on the filterable columns. Row background follows the
|
||||
"Group" value from list_a.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
table : QTableWidget
|
||||
list_a : list[dict] — primary / reference list (fixed column names)
|
||||
list_b : list[dict] — secondary list (e.g. from read_excel_sheet)
|
||||
"""
|
||||
# Index list_b by identity key for O(1) lookup
|
||||
index_b: dict[tuple, dict[str, Any]] = {}
|
||||
for rec in list_b:
|
||||
k = _match_key(rec)
|
||||
index_b[k] = rec # last-wins if duplicates
|
||||
|
||||
# ── table setup ───────────────────────────────────────────────────────────
|
||||
table.setUpdatesEnabled(False)
|
||||
table.clearContents()
|
||||
table.setRowCount(len(list_a))
|
||||
table.setColumnCount(len(COLUMNS))
|
||||
|
||||
existing = table.horizontalHeader()
|
||||
if not isinstance(existing, _FilterHeader):
|
||||
header = _FilterHeader(table)
|
||||
table.setHorizontalHeader(header)
|
||||
else:
|
||||
header = existing
|
||||
|
||||
table.setHorizontalHeaderLabels(COLUMNS)
|
||||
header.setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||
header.setStretchLastSection(True)
|
||||
header.setMinimumSectionSize(60)
|
||||
|
||||
col_value_sets: dict[int, set[str]] = {}
|
||||
for col, key in enumerate(COLUMNS):
|
||||
if key in FILTER_COLS:
|
||||
col_value_sets[col] = set()
|
||||
|
||||
# ── pre-filter: drop rows that are identical in both lists ───────────────
|
||||
def _row_has_diff(rec_a, rec_b):
|
||||
"""True when at least one non-identity column differs."""
|
||||
if rec_b is None:
|
||||
return True # only in list_a → always show
|
||||
return any(
|
||||
not _values_equal(rec_a.get(c), rec_b.get(c))
|
||||
for c in COLUMNS
|
||||
if c not in _SKIP_DIFF and c != "Comment"
|
||||
)
|
||||
|
||||
display_rows = [
|
||||
(rec_a, index_b.get(_match_key(rec_a)))
|
||||
for rec_a in list_a
|
||||
if _row_has_diff(rec_a, index_b.get(_match_key(rec_a)))
|
||||
]
|
||||
table.setRowCount(len(display_rows))
|
||||
|
||||
# ── fill rows ─────────────────────────────────────────────────────────────
|
||||
for row, (rec_a, rec_b) in enumerate(display_rows):
|
||||
group_value = rec_a.get("Group", "") or ""
|
||||
bg_hex = _GROUP_COLORS.get(str(group_value), _DEFAULT_COLOR)
|
||||
bg_brush = QBrush(QColor(bg_hex))
|
||||
|
||||
for col, col_name in enumerate(COLUMNS):
|
||||
val_a = rec_a.get(col_name)
|
||||
|
||||
if rec_b is None:
|
||||
# ── element only in list_a ────────────────────────────────────
|
||||
if col_name == "Comment":
|
||||
cell = _make_cell("New Element", bg_brush, bold=True)
|
||||
else:
|
||||
cell = _make_cell(_format_value(col_name, val_a),
|
||||
bg_brush, bold=False)
|
||||
|
||||
else:
|
||||
# ── element in both lists ─────────────────────────────────────
|
||||
if col_name in _SKIP_DIFF:
|
||||
# Identity columns — always from list_a, no diff
|
||||
cell = _make_cell(_format_value(col_name, val_a),
|
||||
bg_brush, bold=False)
|
||||
|
||||
elif col_name == "Comment":
|
||||
# Comment: check if anything differed in this row
|
||||
# We compute it at the end; use a sentinel for now
|
||||
cell = None # filled in below
|
||||
|
||||
else:
|
||||
val_b = rec_b.get(col_name)
|
||||
if _values_equal(val_a, val_b):
|
||||
cell = _make_cell(_format_value(col_name, val_a),
|
||||
bg_brush, bold=False)
|
||||
else:
|
||||
text = _diff_text(col_name, val_a, val_b)
|
||||
cell = _make_cell(text, bg_brush, bold=True)
|
||||
|
||||
# Fill the Comment column
|
||||
if col_name == "Comment" and rec_b is not None:
|
||||
# Check whether any non-identity column differs
|
||||
has_diff = any(
|
||||
not _values_equal(rec_a.get(c), rec_b.get(c))
|
||||
for c in COLUMNS
|
||||
if c not in _SKIP_DIFF and c != "Comment"
|
||||
)
|
||||
if has_diff:
|
||||
comment_text = "Difference"
|
||||
cell = _make_cell(comment_text, bg_brush, bold=True)
|
||||
else:
|
||||
comment_text = _format_value("Comment",
|
||||
rec_a.get("Comment"))
|
||||
cell = _make_cell(comment_text, bg_brush, bold=False)
|
||||
|
||||
table.setItem(row, col, cell)
|
||||
if col in col_value_sets and cell is not None:
|
||||
col_value_sets[col].add(cell.text())
|
||||
|
||||
# ── header filter widths ──────────────────────────────────────────────────
|
||||
table.setUpdatesEnabled(True)
|
||||
table.setUpdatesEnabled(False)
|
||||
BTN_EXTRA = _FilterHeader._BTN_W + 8
|
||||
for col, key in enumerate(COLUMNS):
|
||||
if key in FILTER_COLS:
|
||||
current = header.sectionSize(col)
|
||||
header.resizeSection(col, current + BTN_EXTRA)
|
||||
header.setSectionResizeMode(col, QHeaderView.Interactive)
|
||||
|
||||
for col, values_set in col_value_sets.items():
|
||||
sorted_vals = sorted(values_set, key=lambda v: (v == "", v.lower()))
|
||||
header.set_column_values(col, sorted_vals)
|
||||
|
||||
header.reset_filters()
|
||||
table.setUpdatesEnabled(True)
|
||||
@@ -8,7 +8,7 @@ from PyQt5 import QtWidgets
|
||||
|
||||
from ui.BeamlineGUI import Ui_BeamlineGUI
|
||||
from beamline_editor import BeamlineEditorWidget
|
||||
from beamline_editor.table_utils import populate_table, populate_table_dynamic
|
||||
from beamline_editor.table_utils import populate_table, populate_table_comparison
|
||||
from beamline_editor.excel_utils import read_excel_sheet
|
||||
|
||||
from elements import *
|
||||
@@ -111,8 +111,8 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
return
|
||||
|
||||
rows = read_excel_sheet(fileName, "Proto-HL")
|
||||
populate_table(self.UIProtoList,rows)
|
||||
print(rows[0])
|
||||
populate_table_comparison(self.UIProtoList,self.plrecs,rows)
|
||||
|
||||
|
||||
def saveTemplates(self):
|
||||
template=self.editor.get_templates()
|
||||
@@ -275,7 +275,7 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
self.PL.generateLayout(self.lines)
|
||||
self.plrecs = [self.PL.info[ele] for ele in self.PL.order]
|
||||
populate_table(self.UIProtoList,self.plrecs)
|
||||
print(self.plrecs[0])
|
||||
|
||||
|
||||
|
||||
def unwrap(self, name, node_id,line,case_num):
|
||||
|
||||
Reference in new issue
Block a user