From 00dfae6187de3cfefc55d6cca2eac84abb198f28 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 12:37:47 -0700 Subject: [PATCH 01/33] fix up and improve search --- viewer/pages/component.py | 206 +++++++++++++++++ viewer/pages/qc.py | 98 +++++++- viewer/templates/component.html | 15 ++ .../component/qc_module_summary.html | 216 ++++++++++++++++++ .../component/related_qc_results.html | 130 +++++++++++ viewer/templates/qcTests.html | 67 +++++- 6 files changed, 720 insertions(+), 12 deletions(-) create mode 100644 viewer/templates/component/qc_module_summary.html create mode 100644 viewer/templates/component/related_qc_results.html diff --git a/viewer/pages/component.py b/viewer/pages/component.py index 12a123a7d..4a6a1678d 100644 --- a/viewer/pages/component.py +++ b/viewer/pages/component.py @@ -658,6 +658,107 @@ def show_component(serialNumber=None): ] + this_component_doc["parents"] break + # Add related QC test results for FE chips + related_tests = {} + if ( + this_component_doc.get("componentType") == "front-end_chip" + and qcresults + and "testType" in qcresults + ): + try: + from pages.qc import get_measurement_date + + current_test_measurement_date = get_measurement_date(qcresults) + if current_test_measurement_date: + # Find the module this FE chip belongs to + parent_relations = list( + localdb.childParentRelation.find( + {"child": str(this_component_doc.get("_id"))} + ) + ) + + module_info = None + expected_chips = 0 + + for relation in parent_relations: + parent = localdb.component.find_one( + {"_id": ObjectId(relation["parent"])} + ) + if parent and parent.get("componentType") == "module": + module_info = parent + # Get all FE chips in this module + from functions.common import get_module_children + + children = get_module_children(parent) + expected_chips = len( + [ + child + for child in children + if child.get("componentType") == "front-end_chip" + ] + ) + break + + if module_info: + # Get all FE chips in the module + from functions.common import get_module_children + + fe_chips = [ + child + for child in get_module_children(module_info) + if child.get("componentType") == "front-end_chip" + ] + + chip_ids = [str(chip.get("_id")) for chip in fe_chips] + + # Find all tests with the same measurement date and test type + related_test_results = list( + localdb.QC.result.find( + { + "component": {"$in": chip_ids}, + "testType": qcresults.get("testType"), + "results.property.MEASUREMENT_DATE": current_test_measurement_date, + } + ) + ) + + # If no results with new format, try old format + if not related_test_results: + related_test_results = list( + localdb.QC.result.find( + { + "component": {"$in": chip_ids}, + "testType": qcresults.get("testType"), + "results.properties.MEASUREMENT_DATE": current_test_measurement_date, + } + ) + ) + + # Add chip serial numbers to the test results + for test in related_test_results: + chip_component = next( + ( + chip + for chip in fe_chips + if str(chip.get("_id")) == test.get("component") + ), + None, + ) + if chip_component: + test["chip_serial"] = chip_component.get("serialNumber") + + related_tests = { + "measurement_date": current_test_measurement_date, + "tests": sorted( + related_test_results, key=lambda x: x.get("chip_serial", "") + ), + "module_info": module_info, + "expected_chips": expected_chips, + } + except Exception as e: + logger.warning(f"Error finding related QC tests: {e!s}") + related_tests = {} + component = { "_id": session.get("this"), "test": session["test"], @@ -679,6 +780,7 @@ def show_component(serialNumber=None): "stageInfo": stageInfo, "config_revision": config_revisions, "fe_summary_dict": fe_summary_dict, + "related_tests": related_tests, } # messages @@ -775,6 +877,110 @@ def show_summary(): return redirect(url) +@component_api.route("/qc_summary", methods=["GET"]) +def qc_summary(): + """Module-level QC dashboard showing aggregated test results for all FE chips.""" + initPage() + + component_id = request.args.get("id") + if not component_id: + flash("Component ID is required", "error") + return redirect(url_for("toppage.toppage")) + + try: + # Get the module component + module = localdb.component.find_one({"_id": ObjectId(component_id)}) + if not module: + flash("Module not found", "error") + return redirect(url_for("toppage.toppage")) + + # Check if this is actually a module + if module.get("componentType") != "module": + flash("This component is not a module", "error") + return redirect(url_for("component_api.show_component", id=component_id)) + + # Get all child FE chips + from functions.common import get_module_children + + children = get_module_children(module) + fe_chips = [ + child + for child in children + if child.get("componentType") == "front-end_chip" + ] + + if not fe_chips: + flash("No FE chips found for this module", "warning") + return redirect(url_for("component_api.show_component", id=component_id)) + + # Get all QC test results for the FE chips + chip_ids = [str(chip.get("_id")) for chip in fe_chips] + all_tests = list( + localdb.QC.result.find( + {"component": {"$in": chip_ids}}, + sort=[("sys.mts", -1)], # Sort by newest first + ) + ) + + # Import the grouping functions from qc.py + from pages.qc import get_measurement_date + + # Group tests by measurement session and test type + test_summary = {} + for test in all_tests: + test_type = test.get("testType") + measurement_date = get_measurement_date(test) + + if test_type not in test_summary: + test_summary[test_type] = {} + + if measurement_date not in test_summary[test_type]: + test_summary[test_type][measurement_date] = [] + + # Add chip serial number for easier identification + chip_component = next( + ( + chip + for chip in fe_chips + if str(chip.get("_id")) == test.get("component") + ), + None, + ) + if chip_component: + test["chip_serial"] = chip_component.get("serialNumber") + test["chip_delim_sn"] = chip_component.get("serialNumber", "").replace( + "-", "" + ) + + test_summary[test_type][measurement_date].append(test) + + # Calculate summary statistics + summary_stats = {} + for test_type, sessions in test_summary.items(): + summary_stats[test_type] = { + "total_sessions": len(sessions), + "total_tests": sum(len(tests) for tests in sessions.values()), + "latest_session": max(sessions.keys()) if sessions else None, + } + + # Prepare template data + template_data = { + "module": module, + "module_delim_sn": module.get("serialNumber", "").replace("-", ""), + "fe_chips": fe_chips, + "test_summary": test_summary, + "summary_stats": summary_stats, + "available_test_types": sorted(test_summary.keys()) if test_summary else [], + } + + return render_template("component/qc_module_summary.html", **template_data) + + except Exception as e: + logger.error(f"Error in qc_summary: {e!s}") + flash(f"Error loading QC summary: {e!s}", "error") + return redirect(url_for("component_api.show_component", id=component_id)) + + ####################### # Display JSON/DAT Data @component_api.route("/display_data", methods=["GET"]) diff --git a/viewer/pages/qc.py b/viewer/pages/qc.py index 082e50fea..6baf86106 100644 --- a/viewer/pages/qc.py +++ b/viewer/pages/qc.py @@ -131,13 +131,25 @@ rawSchema = { def qc_tests(): search_phrase = request.form.get("keyword", type=str, default=None) page_index = request.form.get("page", type=int, default=0) + stage_filter = request.form.get("stage_filter", type=str, default="ALL") + test_type_filter = request.form.get("test_type_filter", type=str, default="ALL") logger.info(f"search phrase = {search_phrase}") logger.info(f"page index = {page_index}") + logger.info(f"stage filter = {stage_filter}") + logger.info(f"test type filter = {test_type_filter}") + + # Get available stages and test types for filter dropdowns + available_stages = ["ALL", *sorted(localdb.QC.result.distinct("stage"))] + available_test_types = ["ALL", *sorted(localdb.QC.result.distinct("testType"))] table_docs = { "messages": format_messages({"component": None}), "search_phrase": search_phrase if search_phrase else "", + "stage_filter": stage_filter, + "test_type_filter": test_type_filter, + "available_stages": available_stages, + "available_test_types": available_test_types, } modules = localdb.component.find( @@ -173,13 +185,23 @@ def qc_tests(): testCount = 0 for subcpt in sub_cpts: + # Build query with optional filters + query = {"component": str(subcpt.get("_id"))} + + # Add stage filter if not "ALL" + if stage_filter != "ALL": + query["stage"] = stage_filter + + # Add test type filter if not "ALL" + if test_type_filter != "ALL": + query["testType"] = test_type_filter + subcpt_tests = list( localdb.QC.result.find( - { - "component": str(subcpt.get("_id")), - "stage": module.get("stage"), - }, - sort=[("component", DESCENDING), ("sys.cts", DESCENDING)], + query, + sort=[ + ("sys.mts", DESCENDING) + ], # Sort by modification time, newest first ) ) for test in subcpt_tests: @@ -217,6 +239,72 @@ def gather_subcomponents(top_id, _list): return _list +def get_measurement_date(test_result): + """ + Extract measurement date from a test result, with fallback for older records. + + Args: + test_result: Dictionary containing test result data + + Returns: + str: ISO date string for grouping measurements, or None if unavailable + """ + try: + # Try new format first + measurement_date = ( + test_result.get("results", {}).get("property", {}).get("MEASUREMENT_DATE") + ) + if measurement_date: + return measurement_date + + # Try older format + measurement_date = ( + test_result.get("results", {}).get("properties", {}).get("MEASUREMENT_DATE") + ) + if measurement_date: + return measurement_date + + # Fallback: use sys.mts rounded to nearest hour for timestamp clustering + if "sys" in test_result and "mts" in test_result["sys"]: + # Round to nearest hour for grouping older records + timestamp = test_result["sys"]["mts"] + # Convert to hour-rounded timestamp for grouping + hour_rounded = timestamp.replace(minute=0, second=0, microsecond=0) + return hour_rounded.isoformat() + + except Exception: + pass + + return None + + +def group_tests_by_measurement_session(tests): + """ + Group test results by measurement session using MEASUREMENT_DATE. + + Args: + tests: List of test result dictionaries + + Returns: + dict: Grouped tests by measurement date + """ + grouped = {} + + for test in tests: + measurement_date = get_measurement_date(test) + if measurement_date: + if measurement_date not in grouped: + grouped[measurement_date] = [] + grouped[measurement_date].append(test) + else: + # Handle tests without measurement date + if "ungrouped" not in grouped: + grouped["ungrouped"] = [] + grouped["ungrouped"].append(test) + + return grouped + + @qc_api.route("/create_configs", methods=["GET", "POST"]) def create_configs(): logger.info("called") diff --git a/viewer/templates/component.html b/viewer/templates/component.html index d73b63594..b5257cae1 100644 --- a/viewer/templates/component.html +++ b/viewer/templates/component.html @@ -224,6 +224,16 @@ + + {% if component['info']['componentType'] == 'module' %} +
+ + View QC Summary + + Aggregated QC test results for all FE chips +
+ {% endif %} +
@@ -387,6 +397,11 @@ {% endif %} {% endif %} + + {% if component['qctest']['results']['testType'] is defined and component['info']['componentType'] == 'front-end_chip' %} + {% include "component/related_qc_results.html" %} + {% endif %} + {% if component['latest_tests'] %} {% include "component/latest_test_index.html" %} {% endif %} diff --git a/viewer/templates/component/qc_module_summary.html b/viewer/templates/component/qc_module_summary.html new file mode 100644 index 000000000..14cb81fbc --- /dev/null +++ b/viewer/templates/component/qc_module_summary.html @@ -0,0 +1,216 @@ +{% import 'widgets/datetime.html' as datetime %} +{% extends "layout.html" %} + +{% block body %} +
+ {% include 'parts/showMessages.html' %} + +
+
+

QC Summary - {{ module_delim_sn }}

+

Module: {{ module.serialNumber }} | Component Type: {{ module.componentType }}

+ +
+ +
+ {{ fe_chips|length }} FE Chips + {{ available_test_types|length }} Test Types +
+
+
+
+ + {% if not test_summary %} +
+ + No QC test results found for this module's FE chips. +
+ {% else %} + +
+
+ + +
+ {% for test_type in available_test_types %} +
+
+
+
{{ test_type }} Results
+ + {{ summary_stats[test_type].total_sessions }} measurement sessions, {{ summary_stats[test_type].total_tests }} total + tests + +
+
+ {% for measurement_date, tests in test_summary[test_type].items() %} +
+
+ + Measurement Session: {{ measurement_date }} + ({{ tests|length }} chips) +
+ +
+ + + + + + + + + + + + + {% for test in tests %} + + + + + + + + + {% endfor %} + +
FE ChipStageStatusUpload TimeAnalysis VersionActions
+ + {{ test.chip_serial or test.component }} + + {{ test.stage }} + {% if test.passed %} + Passed + {% else %} + Failed + {% endif %} + {{ datetime.widget(test.sys.mts) }} + {% if test.results.property and test.results.property.ANALYSIS_VERSION %} + {{ test.results.property.ANALYSIS_VERSION }} + {% elif test.results.properties and test.results.properties.ANALYSIS_VERSION %} + {{ test.results.properties.ANALYSIS_VERSION }} + {% else %} + N/A + {% endif %} + + + View Details + +
+
+ + + {% set passed_count = tests|selectattr("passed")|list|length %} + {% set failed_count = tests|length - passed_count %} +
+
+ + Session Summary: + {{ passed_count }} passed + {% if failed_count > 0 %} + , {{ failed_count }} failed + {% endif %} + +
+
+ {% if tests|length == fe_chips|length %} + Complete + {% else %} + Incomplete ({{ tests|length }}/{{ fe_chips|length }}) + {% endif %} +
+
+
+ {% endfor %} +
+
+
+ {% endfor %} +
+
+
+ {% endif %} + + +
+
+
+
+
FE Chips in this Module
+
+
+
+ {% for chip in fe_chips %} +
+
+
+
+ {{ chip.serialNumber }} +
+ {{ chip.componentType }} +
+
+
+ {% endfor %} +
+
+
+
+
+
+ + +{% endblock %} diff --git a/viewer/templates/component/related_qc_results.html b/viewer/templates/component/related_qc_results.html new file mode 100644 index 000000000..61309e29e --- /dev/null +++ b/viewer/templates/component/related_qc_results.html @@ -0,0 +1,130 @@ +{% import 'widgets/datetime.html' as datetime %} + +{% if component['related_tests'] %} +
+
+ +
+
+
+ {% if component['related_tests']['measurement_date'] %} +

+ + Measurement Date: {{ component['related_tests']['measurement_date'] }} +

+ {% endif %} + + {% if component['related_tests']['module_info'] %} + + {% endif %} + +
+ + + + + + + + + + + + + {% for test in component['related_tests']['tests'] %} + + + + + + + + + {% endfor %} + +
FE ChipStageStatusUpload TimeAnalysis VersionActions
+ + {{ test.chip_serial or test.component }} + + {% if test._id == component['qctest']['results']['_id'] %} + Current + {% endif %} + {{ test.stage }} + {% if test.passed %} + Passed + {% else %} + Failed + {% endif %} + {{ datetime.widget(test.sys.mts) }} + {% if test.results.property and test.results.property.ANALYSIS_VERSION %} + {{ test.results.property.ANALYSIS_VERSION }} + {% elif test.results.properties and test.results.properties.ANALYSIS_VERSION %} + {{ test.results.properties.ANALYSIS_VERSION }} + {% else %} + N/A + {% endif %} + + {% if test._id != component['qctest']['results']['_id'] %} + + View Details + + {% else %} + Current Page + {% endif %} +
+
+ + + {% set passed_count = component['related_tests']['tests']|selectattr("passed")|list|length %} + {% set failed_count = component['related_tests']['tests']|length - passed_count %} + {% set total_count = component['related_tests']['tests']|length %} + +
+
+ + Measurement Session Summary: + {{ passed_count }} passed + {% if failed_count > 0 %} + , {{ failed_count }} failed + {% endif %} + ({{ total_count }} total) + +
+
+ {% if component['related_tests']['expected_chips'] %} + {% if total_count == component['related_tests']['expected_chips'] %} + Complete Session + {% else %} + Incomplete ({{ total_count }}/{{ component['related_tests']['expected_chips'] }}) + {% endif %} + {% endif %} +
+
+
+
+
+{% endif %} diff --git a/viewer/templates/qcTests.html b/viewer/templates/qcTests.html index 8e9bc43e8..82edd5a49 100644 --- a/viewer/templates/qcTests.html +++ b/viewer/templates/qcTests.html @@ -14,24 +14,54 @@

QC Tests

-

(Only displaying QC Tests of the current stage)

-
- Module SN: - -
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
- +
+ @@ -62,7 +92,7 @@ >
{{ moduleInfo['typeInfo'] }}

- Current Stage:
{{ moduleInfo['stage'] }} + Module Stage:
{{ moduleInfo['stage'] }} {% set moduleInfoRendered = true %} {% endif %} @@ -76,6 +106,7 @@
{{ subcpt['typeInfo'] }} +
Module Test Type Component SNStage Timestamp TestRun QC Passed {{ test['stage'] }} {{ datetime.widget(test['timestamp']) }} {% endblock %} + +{% block scripts %} + +{% endblock %} -- GitLab From 42ccb8280141402937018f765a8a4fa86aa59f0e Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 14:01:37 -0700 Subject: [PATCH 02/33] fix up changes --- viewer/functions/common.py | 66 ++++++ viewer/pages/component.py | 31 ++- viewer/pages/qc.py | 205 +++++++----------- viewer/templates/component.html | 4 +- .../component/qc_module_summary.html | 7 - .../component/related_qc_results.html | 11 +- viewer/templates/qcTests.html | 108 ++++----- 7 files changed, 209 insertions(+), 223 deletions(-) diff --git a/viewer/functions/common.py b/viewer/functions/common.py index 2fdfe7d78..62777a483 100755 --- a/viewer/functions/common.py +++ b/viewer/functions/common.py @@ -1866,3 +1866,69 @@ def get_tags_for_test_run(test_run_id): return userdb.viewer.tag.docs.distinct( "name", {"$or": [{"anaid": str(test_run_id)}, {"runId": str(test_run_id)}]} ) + + +def get_measurement_date(test_result): + """ + Extract measurement date from a test result, with fallback for older records. + + Args: + test_result: Dictionary containing test result data + + Returns: + str: ISO date string for grouping measurements, or None if unavailable + """ + try: + # Try new format first + measurement_date = ( + test_result.get("results", {}).get("property", {}).get("MEASUREMENT_DATE") + ) + if measurement_date: + return measurement_date + + # Try older format + measurement_date = ( + test_result.get("results", {}).get("properties", {}).get("MEASUREMENT_DATE") + ) + if measurement_date: + return measurement_date + + # Fallback: use sys.mts rounded to nearest hour for timestamp clustering + if "sys" in test_result and "mts" in test_result["sys"]: + # Round to nearest hour for grouping older records + timestamp = test_result["sys"]["mts"] + # Convert to hour-rounded timestamp for grouping + hour_rounded = timestamp.replace(minute=0, second=0, microsecond=0) + return hour_rounded.isoformat() + + except Exception: + pass + + return None + + +def group_tests_by_measurement_session(tests): + """ + Group test results by measurement session using MEASUREMENT_DATE. + + Args: + tests: List of test result dictionaries + + Returns: + dict: Grouped tests by measurement date + """ + grouped = {} + + for test in tests: + measurement_date = get_measurement_date(test) + if measurement_date: + if measurement_date not in grouped: + grouped[measurement_date] = [] + grouped[measurement_date].append(test) + else: + # Handle tests without measurement date + if "ungrouped" not in grouped: + grouped["ungrouped"] = [] + grouped["ungrouped"].append(test) + + return grouped diff --git a/viewer/pages/component.py b/viewer/pages/component.py index 4a6a1678d..ff4d4fea4 100644 --- a/viewer/pages/component.py +++ b/viewer/pages/component.py @@ -53,6 +53,7 @@ from functions.common import ( delim_SN, format_messages, fs, + get_measurement_date, get_pd_client, get_tags_for_test_run, initPage, @@ -666,8 +667,6 @@ def show_component(serialNumber=None): and "testType" in qcresults ): try: - from pages.qc import get_measurement_date - current_test_measurement_date = get_measurement_date(qcresults) if current_test_measurement_date: # Find the module this FE chip belongs to @@ -877,19 +876,14 @@ def show_summary(): return redirect(url) -@component_api.route("/qc_summary", methods=["GET"]) -def qc_summary(): +@component_api.route("//summary", methods=["GET"]) +def qc_summary(serial_number): """Module-level QC dashboard showing aggregated test results for all FE chips.""" initPage() - component_id = request.args.get("id") - if not component_id: - flash("Component ID is required", "error") - return redirect(url_for("toppage.toppage")) - try: - # Get the module component - module = localdb.component.find_one({"_id": ObjectId(component_id)}) + # Get the module component by serial number + module = localdb.component.find_one({"serialNumber": serial_number}) if not module: flash("Module not found", "error") return redirect(url_for("toppage.toppage")) @@ -897,7 +891,9 @@ def qc_summary(): # Check if this is actually a module if module.get("componentType") != "module": flash("This component is not a module", "error") - return redirect(url_for("component_api.show_component", id=component_id)) + return redirect( + url_for("component_api.show_component_sn", serial_number=serial_number) + ) # Get all child FE chips from functions.common import get_module_children @@ -911,7 +907,9 @@ def qc_summary(): if not fe_chips: flash("No FE chips found for this module", "warning") - return redirect(url_for("component_api.show_component", id=component_id)) + return redirect( + url_for("component_api.show_component_sn", serial_number=serial_number) + ) # Get all QC test results for the FE chips chip_ids = [str(chip.get("_id")) for chip in fe_chips] @@ -922,9 +920,6 @@ def qc_summary(): ) ) - # Import the grouping functions from qc.py - from pages.qc import get_measurement_date - # Group tests by measurement session and test type test_summary = {} for test in all_tests: @@ -978,7 +973,9 @@ def qc_summary(): except Exception as e: logger.error(f"Error in qc_summary: {e!s}") flash(f"Error loading QC summary: {e!s}", "error") - return redirect(url_for("component_api.show_component", id=component_id)) + return redirect( + url_for("component_api.show_component_sn", serial_number=serial_number) + ) ####################### diff --git a/viewer/pages/qc.py b/viewer/pages/qc.py index 6baf86106..8a35f356e 100644 --- a/viewer/pages/qc.py +++ b/viewer/pages/qc.py @@ -140,7 +140,9 @@ def qc_tests(): logger.info(f"test type filter = {test_type_filter}") # Get available stages and test types for filter dropdowns - available_stages = ["ALL", *sorted(localdb.QC.result.distinct("stage"))] + # Keep original order for stages (important workflow order) + available_stages = ["ALL", *list(localdb.QC.result.distinct("stage"))] + # Keep original order for test types (alphabetical is fine) available_test_types = ["ALL", *sorted(localdb.QC.result.distinct("testType"))] table_docs = { @@ -152,74 +154,95 @@ def qc_tests(): "available_test_types": available_test_types, } - modules = localdb.component.find( - {"componentType": "module"}, sort=[("sys.cts", DESCENDING)] - ) - - # tests = list( localdb.QC.result.find( {} , sort = [ ('component', DESCENDING), ('sys.cts', DESCENDING) ] ) ) - - output_tests = [] - - for index, module in enumerate(modules): - if search_phrase is not None: - if search_phrase not in module.get("serialNumber"): - continue - elif index < page_index * 10 or index >= (page_index + 1) * 10: - continue - - module["delim_sn"] = delim_SN(module.get("serialNumber")) - module["typeInfo"] = SN_typeinfo(module.get("serialNumber")) - module["stage"] = localdb.QC.module.status.find_one( - {"component": str(module.get("_id"))} - ).get("stage") - - sub_cpts = gather_subcomponents(str(module.get("_id")), [module]) - - tests = {} - - tests["moduleInfo"] = module - tests["subComponents"] = sub_cpts - - tests["testTypes"] = {} - - testCount = 0 + # Build the base query with filters + query = {} - for subcpt in sub_cpts: - # Build query with optional filters - query = {"component": str(subcpt.get("_id"))} + # Add stage filter if not "ALL" + if stage_filter != "ALL": + query["stage"] = stage_filter - # Add stage filter if not "ALL" - if stage_filter != "ALL": - query["stage"] = stage_filter + # Add test type filter if not "ALL" + if test_type_filter != "ALL": + query["testType"] = test_type_filter - # Add test type filter if not "ALL" - if test_type_filter != "ALL": - query["testType"] = test_type_filter + # Get all QC test results matching filters + all_tests = list( + localdb.QC.result.find( + query, + sort=[("sys.mts", DESCENDING)], # Sort by modification time, newest first + ) + ) - subcpt_tests = list( - localdb.QC.result.find( - query, - sort=[ - ("sys.mts", DESCENDING) - ], # Sort by modification time, newest first - ) + # Filter by module search if provided + if search_phrase: + filtered_tests = [] + for test in all_tests: + # Get component info + component = localdb.component.find_one( + {"_id": ObjectId(test.get("component"))} ) - for test in subcpt_tests: - if test.get("testType") not in tests["testTypes"]: - tests["testTypes"][test.get("testType")] = 0 + if component: + # Check if component itself matches + if search_phrase in component.get("serialNumber", ""): + filtered_tests.append(test) + continue - test.update({"table_index": tests["testTypes"][test.get("testType")]}) - test.update({"timestamp": test.get("sys").get("mts")}) + # Check if parent module matches + parent_relations = localdb.childParentRelation.find( + {"child": test.get("component")} + ) + for relation in parent_relations: + parent = localdb.component.find_one( + {"_id": ObjectId(relation["parent"])} + ) + if ( + parent + and parent.get("componentType") == "module" + and search_phrase in parent.get("serialNumber", "") + ): + filtered_tests.append(test) + break + all_tests = filtered_tests - tests["testTypes"][test.get("testType")] += 1 + # Prepare tests for display + output_tests = [] + for test in all_tests: + # Get component info + component = localdb.component.find_one({"_id": ObjectId(test.get("component"))}) + if not component: + continue - testCount += 1 + # Get module info if this is an FE chip + module_info = None + if component.get("componentType") == "front-end_chip": + parent_relations = localdb.childParentRelation.find( + {"child": test.get("component")} + ) + for relation in parent_relations: + parent = localdb.component.find_one( + {"_id": ObjectId(relation["parent"])} + ) + if parent and parent.get("componentType") == "module": + module_info = parent + break - subcpt.update({"tests": subcpt_tests}) + # Prepare test data + test_data = { + "test": test, + "component": component, + "module": module_info, + "timestamp": test.get("sys", {}).get("mts"), + "delim_sn": delim_SN(component.get("serialNumber", "")), + "typeInfo": SN_typeinfo(component.get("serialNumber", "")), + } - module["testCount"] = testCount + if module_info: + test_data["module_delim_sn"] = delim_SN(module_info.get("serialNumber", "")) + test_data["module_typeInfo"] = SN_typeinfo( + module_info.get("serialNumber", "") + ) - output_tests += [tests] + output_tests.append(test_data) return render_template("qcTests.html", tests=output_tests, table_docs=table_docs) @@ -239,72 +262,6 @@ def gather_subcomponents(top_id, _list): return _list -def get_measurement_date(test_result): - """ - Extract measurement date from a test result, with fallback for older records. - - Args: - test_result: Dictionary containing test result data - - Returns: - str: ISO date string for grouping measurements, or None if unavailable - """ - try: - # Try new format first - measurement_date = ( - test_result.get("results", {}).get("property", {}).get("MEASUREMENT_DATE") - ) - if measurement_date: - return measurement_date - - # Try older format - measurement_date = ( - test_result.get("results", {}).get("properties", {}).get("MEASUREMENT_DATE") - ) - if measurement_date: - return measurement_date - - # Fallback: use sys.mts rounded to nearest hour for timestamp clustering - if "sys" in test_result and "mts" in test_result["sys"]: - # Round to nearest hour for grouping older records - timestamp = test_result["sys"]["mts"] - # Convert to hour-rounded timestamp for grouping - hour_rounded = timestamp.replace(minute=0, second=0, microsecond=0) - return hour_rounded.isoformat() - - except Exception: - pass - - return None - - -def group_tests_by_measurement_session(tests): - """ - Group test results by measurement session using MEASUREMENT_DATE. - - Args: - tests: List of test result dictionaries - - Returns: - dict: Grouped tests by measurement date - """ - grouped = {} - - for test in tests: - measurement_date = get_measurement_date(test) - if measurement_date: - if measurement_date not in grouped: - grouped[measurement_date] = [] - grouped[measurement_date].append(test) - else: - # Handle tests without measurement date - if "ungrouped" not in grouped: - grouped["ungrouped"] = [] - grouped["ungrouped"].append(test) - - return grouped - - @qc_api.route("/create_configs", methods=["GET", "POST"]) def create_configs(): logger.info("called") diff --git a/viewer/templates/component.html b/viewer/templates/component.html index b5257cae1..ac68d807f 100644 --- a/viewer/templates/component.html +++ b/viewer/templates/component.html @@ -225,9 +225,9 @@
- {% if component['info']['componentType'] == 'module' %} + {% if component['info']['type'] == 'module' %}
- + View QC Summary Aggregated QC test results for all FE chips diff --git a/viewer/templates/component/qc_module_summary.html b/viewer/templates/component/qc_module_summary.html index 14cb81fbc..94df5b9c7 100644 --- a/viewer/templates/component/qc_module_summary.html +++ b/viewer/templates/component/qc_module_summary.html @@ -142,13 +142,6 @@ {% endif %}
-
- {% if tests|length == fe_chips|length %} - Complete - {% else %} - Incomplete ({{ tests|length }}/{{ fe_chips|length }}) - {% endif %} -
{% endfor %} diff --git a/viewer/templates/component/related_qc_results.html b/viewer/templates/component/related_qc_results.html index 61309e29e..2fdc307f9 100644 --- a/viewer/templates/component/related_qc_results.html +++ b/viewer/templates/component/related_qc_results.html @@ -30,7 +30,7 @@ View Module QC Summary @@ -114,15 +114,6 @@ ({{ total_count }} total)
-
- {% if component['related_tests']['expected_chips'] %} - {% if total_count == component['related_tests']['expected_chips'] %} - Complete Session - {% else %} - Incomplete ({{ total_count }}/{{ component['related_tests']['expected_chips'] }}) - {% endif %} - {% endif %} -
diff --git a/viewer/templates/qcTests.html b/viewer/templates/qcTests.html index 82edd5a49..8123b5cf1 100644 --- a/viewer/templates/qcTests.html +++ b/viewer/templates/qcTests.html @@ -69,65 +69,50 @@ - {% for module_tests in tests %} - {% set moduleLoop = loop %} - {% set moduleInfo = module_tests['moduleInfo'] %} - {% set moduleInfoRendered = false %} - - {% for testType in module_tests['testTypes'] %} - {% set testTypeLoop = loop %} - {% set testSize = module_tests['testTypes'][testType] %} - - {% for subcpt in module_tests['subComponents'] %} - {% set chipLoop = loop %} - {% for test in subcpt['tests'] %} - {% set testLoop = loop %} - - {% if test['testType'] == testType %} - - {% if testLoop.first and testTypeLoop.first and not moduleInfoRendered %} - - {{ moduleInfo['delim_sn'] }} -
- {{ moduleInfo['typeInfo'] }}

- Module Stage:
{{ moduleInfo['stage'] }} - - {% set moduleInfoRendered = true %} - {% endif %} - - {% if test['table_index'] == 0 %} - {{ test['testType'] }} - {% endif %} - - - {{ subcpt['delim_sn'] }} -
- {{ subcpt['typeInfo'] }} - - {{ test['stage'] }} - {{ datetime.widget(test['timestamp']) }} - - {{ test['_id'] }} - - - {% if test['passed'] %} - Passed - {% else %} - Failed - {% endif %} - - - {% if 'property' in test['results'] %}{{ test['results']['property']['ANALYSIS_VERSION'] }}{% elif 'properties' in test['results'] %}{{ test['results']['properties']['ANALYSIS_VERSION'] }}{% else %}N/A{% endif %} - - - {% endif %} - {% endfor %} - {% endfor %} - {% endfor %} + {% for test_data in tests %} + {% set test = test_data.test %} + {% set component = test_data.component %} + {% set module = test_data.module %} + + + {% if module %} + {{ test_data.module_delim_sn }} +
+ {{ test_data.module_typeInfo }} + {% else %} + No Module + {% endif %} + + {{ test.testType }} + + {{ test_data.delim_sn }} +
+ {{ test_data.typeInfo }} + + {{ test.stage }} + {{ datetime.widget(test_data.timestamp) }} + + + {{ test._id | truncate_uid }} + + + + {% if test.passed %} + Passed + {% else %} + Failed + {% endif %} + + + {% if test.results.property and test.results.property.ANALYSIS_VERSION %} + {{ test.results.property.ANALYSIS_VERSION }} + {% elif test.results.properties and test.results.properties.ANALYSIS_VERSION %} + {{ test.results.properties.ANALYSIS_VERSION }} + {% else %} + N/A + {% endif %} + + {% endfor %} @@ -151,10 +136,7 @@ filter_searchDelay: 300, zebra: ["even", "odd"], }, - headers: { - 0: { sorter: false }, // Module column (has rowspan, disable sorting) - 1: { sorter: false }, // Test Type column (has rowspan, disable sorting) - }, + // All columns can now be sorted since we removed rowspans }); }); -- GitLab From 95ca3fd49c4bb2409fe11d9d35c760c2d91baa2f Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 14:07:13 -0700 Subject: [PATCH 03/33] fix --- viewer/pages/component.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/viewer/pages/component.py b/viewer/pages/component.py index ff4d4fea4..29866c9e9 100644 --- a/viewer/pages/component.py +++ b/viewer/pages/component.py @@ -892,7 +892,7 @@ def qc_summary(serial_number): if module.get("componentType") != "module": flash("This component is not a module", "error") return redirect( - url_for("component_api.show_component_sn", serial_number=serial_number) + url_for("component_api.show_component_sn", serialNumber=serial_number) ) # Get all child FE chips @@ -908,7 +908,7 @@ def qc_summary(serial_number): if not fe_chips: flash("No FE chips found for this module", "warning") return redirect( - url_for("component_api.show_component_sn", serial_number=serial_number) + url_for("component_api.show_component_sn", serialNumber=serial_number) ) # Get all QC test results for the FE chips @@ -966,6 +966,7 @@ def qc_summary(serial_number): "test_summary": test_summary, "summary_stats": summary_stats, "available_test_types": sorted(test_summary.keys()) if test_summary else [], + "table_docs": {"messages": []}, # Add table_docs for message display } return render_template("component/qc_module_summary.html", **template_data) @@ -974,7 +975,7 @@ def qc_summary(serial_number): logger.error(f"Error in qc_summary: {e!s}") flash(f"Error loading QC summary: {e!s}", "error") return redirect( - url_for("component_api.show_component_sn", serial_number=serial_number) + url_for("component_api.show_component_sn", serialNumber=serial_number) ) -- GitLab From d1034e8bc0eb5777880ede67c6b31013d3cfdef7 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 14:28:40 -0700 Subject: [PATCH 04/33] fix some bugs, improve pagination --- viewer/pages/component.py | 100 -------------------------------- viewer/pages/qc.py | 89 +++++++++++++++++++++++----- viewer/templates/component.html | 5 -- viewer/templates/qcTests.html | 97 ++++++++++++++++++++++++++++++- 4 files changed, 171 insertions(+), 120 deletions(-) diff --git a/viewer/pages/component.py b/viewer/pages/component.py index 29866c9e9..a4e0ad1ee 100644 --- a/viewer/pages/component.py +++ b/viewer/pages/component.py @@ -659,105 +659,6 @@ def show_component(serialNumber=None): ] + this_component_doc["parents"] break - # Add related QC test results for FE chips - related_tests = {} - if ( - this_component_doc.get("componentType") == "front-end_chip" - and qcresults - and "testType" in qcresults - ): - try: - current_test_measurement_date = get_measurement_date(qcresults) - if current_test_measurement_date: - # Find the module this FE chip belongs to - parent_relations = list( - localdb.childParentRelation.find( - {"child": str(this_component_doc.get("_id"))} - ) - ) - - module_info = None - expected_chips = 0 - - for relation in parent_relations: - parent = localdb.component.find_one( - {"_id": ObjectId(relation["parent"])} - ) - if parent and parent.get("componentType") == "module": - module_info = parent - # Get all FE chips in this module - from functions.common import get_module_children - - children = get_module_children(parent) - expected_chips = len( - [ - child - for child in children - if child.get("componentType") == "front-end_chip" - ] - ) - break - - if module_info: - # Get all FE chips in the module - from functions.common import get_module_children - - fe_chips = [ - child - for child in get_module_children(module_info) - if child.get("componentType") == "front-end_chip" - ] - - chip_ids = [str(chip.get("_id")) for chip in fe_chips] - - # Find all tests with the same measurement date and test type - related_test_results = list( - localdb.QC.result.find( - { - "component": {"$in": chip_ids}, - "testType": qcresults.get("testType"), - "results.property.MEASUREMENT_DATE": current_test_measurement_date, - } - ) - ) - - # If no results with new format, try old format - if not related_test_results: - related_test_results = list( - localdb.QC.result.find( - { - "component": {"$in": chip_ids}, - "testType": qcresults.get("testType"), - "results.properties.MEASUREMENT_DATE": current_test_measurement_date, - } - ) - ) - - # Add chip serial numbers to the test results - for test in related_test_results: - chip_component = next( - ( - chip - for chip in fe_chips - if str(chip.get("_id")) == test.get("component") - ), - None, - ) - if chip_component: - test["chip_serial"] = chip_component.get("serialNumber") - - related_tests = { - "measurement_date": current_test_measurement_date, - "tests": sorted( - related_test_results, key=lambda x: x.get("chip_serial", "") - ), - "module_info": module_info, - "expected_chips": expected_chips, - } - except Exception as e: - logger.warning(f"Error finding related QC tests: {e!s}") - related_tests = {} - component = { "_id": session.get("this"), "test": session["test"], @@ -779,7 +680,6 @@ def show_component(serialNumber=None): "stageInfo": stageInfo, "config_revision": config_revisions, "fe_summary_dict": fe_summary_dict, - "related_tests": related_tests, } # messages diff --git a/viewer/pages/qc.py b/viewer/pages/qc.py index 8a35f356e..6335ba1af 100644 --- a/viewer/pages/qc.py +++ b/viewer/pages/qc.py @@ -129,10 +129,20 @@ rawSchema = { @qc_api.route("/qc_tests", methods=["GET", "POST"]) def qc_tests(): - search_phrase = request.form.get("keyword", type=str, default=None) - page_index = request.form.get("page", type=int, default=0) - stage_filter = request.form.get("stage_filter", type=str, default="ALL") - test_type_filter = request.form.get("test_type_filter", type=str, default="ALL") + # Handle GET parameters for pagination, POST for form submission + search_phrase = request.args.get("keyword") or request.form.get( + "keyword", type=str, default=None + ) + page_index = request.args.get("page", type=int, default=1) + stage_filter = request.args.get("stage_filter") or request.form.get( + "stage_filter", type=str, default="ALL" + ) + test_type_filter = request.args.get("test_type_filter") or request.form.get( + "test_type_filter", type=str, default="ALL" + ) + + # Pagination settings + page_size = 100 logger.info(f"search phrase = {search_phrase}") logger.info(f"page index = {page_index}") @@ -145,15 +155,6 @@ def qc_tests(): # Keep original order for test types (alphabetical is fine) available_test_types = ["ALL", *sorted(localdb.QC.result.distinct("testType"))] - table_docs = { - "messages": format_messages({"component": None}), - "search_phrase": search_phrase if search_phrase else "", - "stage_filter": stage_filter, - "test_type_filter": test_type_filter, - "available_stages": available_stages, - "available_test_types": available_test_types, - } - # Build the base query with filters query = {} @@ -165,12 +166,54 @@ def qc_tests(): if test_type_filter != "ALL": query["testType"] = test_type_filter - # Get all QC test results matching filters + # Count total results for pagination + total_count = localdb.QC.result.count_documents(query) + + # Apply search filter to count if needed + if search_phrase: + # This is a bit inefficient for large datasets, but maintains current functionality + # In a production system, you'd want to optimize this with proper indexing + all_tests_for_search = list(localdb.QC.result.find(query)) + filtered_count = 0 + for test in all_tests_for_search: + component = localdb.component.find_one( + {"_id": ObjectId(test.get("component"))} + ) + if component: + # Check if component itself matches + if search_phrase in component.get("serialNumber", ""): + filtered_count += 1 + continue + + # Check if parent module matches + parent_relations = localdb.childParentRelation.find( + {"child": test.get("component")} + ) + for relation in parent_relations: + parent = localdb.component.find_one( + {"_id": ObjectId(relation["parent"])} + ) + if ( + parent + and parent.get("componentType") == "module" + and search_phrase in parent.get("serialNumber", "") + ): + filtered_count += 1 + break + total_count = filtered_count + + # Calculate pagination + total_pages = (total_count + page_size - 1) // page_size # Ceiling division + skip = (page_index - 1) * page_size + + # Get QC test results for current page all_tests = list( localdb.QC.result.find( query, sort=[("sys.mts", DESCENDING)], # Sort by modification time, newest first ) + .skip(skip) + .limit(page_size) ) # Filter by module search if provided @@ -244,6 +287,24 @@ def qc_tests(): output_tests.append(test_data) + # Create table_docs with pagination info + table_docs = { + "messages": format_messages({"component": None}), + "search_phrase": search_phrase if search_phrase else "", + "stage_filter": stage_filter, + "test_type_filter": test_type_filter, + "available_stages": available_stages, + "available_test_types": available_test_types, + "page": page_index, + "page_size": page_size, + "total_pages": total_pages, + "total_count": total_count, + "has_prev": page_index > 1, + "has_next": page_index < total_pages, + "prev_page": page_index - 1 if page_index > 1 else None, + "next_page": page_index + 1 if page_index < total_pages else None, + } + return render_template("qcTests.html", tests=output_tests, table_docs=table_docs) diff --git a/viewer/templates/component.html b/viewer/templates/component.html index ac68d807f..f0cd0db5f 100644 --- a/viewer/templates/component.html +++ b/viewer/templates/component.html @@ -397,11 +397,6 @@ {% endif %} {% endif %} - - {% if component['qctest']['results']['testType'] is defined and component['info']['componentType'] == 'front-end_chip' %} - {% include "component/related_qc_results.html" %} - {% endif %} - {% if component['latest_tests'] %} {% include "component/latest_test_index.html" %} {% endif %} diff --git a/viewer/templates/qcTests.html b/viewer/templates/qcTests.html index 8123b5cf1..5accac984 100644 --- a/viewer/templates/qcTests.html +++ b/viewer/templates/qcTests.html @@ -93,7 +93,7 @@ {{ datetime.widget(test_data.timestamp) }} - {{ test._id | truncate_uid }} + {{ test._id | string | truncate_uid }} @@ -118,6 +118,101 @@ + + + {% if table_docs['total_pages'] > 1 %} +
+
+ + + +
+ + Showing page {{ table_docs['page'] }} of {{ table_docs['total_pages'] }} ({{ table_docs['total_count'] }} total results, + {{ table_docs['page_size'] }} per page) + +
+
+
+ {% endif %} -- GitLab From bcfcbc3c7b467346de37108d827a5ac259cd47ec Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 14:41:11 -0700 Subject: [PATCH 05/33] more changes --- viewer/pages/component.py | 8 ++++++ viewer/templates/component.html | 20 +++++++-------- .../component/qc_module_summary.html | 25 +++++++++++++++++-- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/viewer/pages/component.py b/viewer/pages/component.py index a4e0ad1ee..307910819 100644 --- a/viewer/pages/component.py +++ b/viewer/pages/component.py @@ -805,6 +805,10 @@ def qc_summary(serial_number): if child.get("componentType") == "front-end_chip" ] + # Add hexadecimal names to FE chips + for chip in fe_chips: + chip["hex_name"] = chip_serial_number_to_uid(chip.get("serialNumber", "")) + if not fe_chips: flash("No FE chips found for this module", "warning") return redirect( @@ -846,6 +850,10 @@ def qc_summary(serial_number): test["chip_delim_sn"] = chip_component.get("serialNumber", "").replace( "-", "" ) + # Add hexadecimal chip name + test["chip_hex_name"] = chip_serial_number_to_uid( + chip_component.get("serialNumber", "") + ) test_summary[test_type][measurement_date].append(test) diff --git a/viewer/templates/component.html b/viewer/templates/component.html index f0cd0db5f..1367c3e2d 100644 --- a/viewer/templates/component.html +++ b/viewer/templates/component.html @@ -94,6 +94,16 @@
{% endif %} {% endif %} + + + {% if component['info']['type'] == 'module' %} +
+ + View QC Summary + + Aggregated QC test results for all FE chips +
+ {% endif %}
@@ -224,16 +234,6 @@ - - {% if component['info']['type'] == 'module' %} -
- - View QC Summary - - Aggregated QC test results for all FE chips -
- {% endif %} -
diff --git a/viewer/templates/component/qc_module_summary.html b/viewer/templates/component/qc_module_summary.html index 94df5b9c7..ab08a8a05 100644 --- a/viewer/templates/component/qc_module_summary.html +++ b/viewer/templates/component/qc_module_summary.html @@ -3,16 +3,30 @@ {% block body %}
+ + + {% include 'parts/showMessages.html' %}
-

QC Summary - {{ module_delim_sn }}

+

{{ module_delim_sn }} - QC Summary

Module: {{ module.serialNumber }} | Component Type: {{ module.componentType }}

@@ -96,6 +110,10 @@ {{ test.chip_serial or test.component }} + {% if test.chip_hex_name %} +
+ {{ test.chip_hex_name }} + {% endif %} {{ test.stage }} @@ -170,6 +188,9 @@
{{ chip.serialNumber }}
+ {% if chip.hex_name %} + {{ chip.hex_name }} + {% endif %} {{ chip.componentType }}
-- GitLab From a69b4c9f6aa319e38cb944d2a6870f1dd2925027 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 15:01:16 -0700 Subject: [PATCH 06/33] more fix ups --- viewer/pages/component.py | 7 ++++--- viewer/pages/qc.py | 5 ----- viewer/templates/component.html | 8 ++++++-- viewer/templates/component/qc_module_summary.html | 11 ++++++++--- viewer/templates/qcTests.html | 14 ++++++-------- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/viewer/pages/component.py b/viewer/pages/component.py index 307910819..9f092264d 100644 --- a/viewer/pages/component.py +++ b/viewer/pages/component.py @@ -54,6 +54,7 @@ from functions.common import ( format_messages, fs, get_measurement_date, + get_module_children, get_pd_client, get_tags_for_test_run, initPage, @@ -795,9 +796,6 @@ def qc_summary(serial_number): url_for("component_api.show_component_sn", serialNumber=serial_number) ) - # Get all child FE chips - from functions.common import get_module_children - children = get_module_children(module) fe_chips = [ child @@ -866,6 +864,9 @@ def qc_summary(serial_number): "latest_session": max(sessions.keys()) if sessions else None, } + # Add typeinfo to module + module["typeinfo"] = SN_typeinfo(module.get("serialNumber", ""), verbose=1) + # Prepare template data template_data = { "module": module, diff --git a/viewer/pages/qc.py b/viewer/pages/qc.py index 6335ba1af..14f007512 100644 --- a/viewer/pages/qc.py +++ b/viewer/pages/qc.py @@ -144,11 +144,6 @@ def qc_tests(): # Pagination settings page_size = 100 - logger.info(f"search phrase = {search_phrase}") - logger.info(f"page index = {page_index}") - logger.info(f"stage filter = {stage_filter}") - logger.info(f"test type filter = {test_type_filter}") - # Get available stages and test types for filter dropdowns # Keep original order for stages (important workflow order) available_stages = ["ALL", *list(localdb.QC.result.distinct("stage"))] diff --git a/viewer/templates/component.html b/viewer/templates/component.html index 1367c3e2d..0a1672dbe 100644 --- a/viewer/templates/component.html +++ b/viewer/templates/component.html @@ -98,10 +98,14 @@ {% if component['info']['type'] == 'module' %}
- + View QC Summary - Aggregated QC test results for all FE chips
{% endif %}
diff --git a/viewer/templates/component/qc_module_summary.html b/viewer/templates/component/qc_module_summary.html index ab08a8a05..e0642e459 100644 --- a/viewer/templates/component/qc_module_summary.html +++ b/viewer/templates/component/qc_module_summary.html @@ -22,11 +22,16 @@

{{ module_delim_sn }} - QC Summary

-

Module: {{ module.serialNumber }} | Component Type: {{ module.componentType }}

+

+ {{ module['typeinfo'] }} +

@@ -87,7 +92,7 @@
- Measurement Session: {{ measurement_date }} + Measurement Session: {{ datetime.widget(measurement_date) }} ({{ tests|length }} chips)
diff --git a/viewer/templates/qcTests.html b/viewer/templates/qcTests.html index 5accac984..af6e9e264 100644 --- a/viewer/templates/qcTests.html +++ b/viewer/templates/qcTests.html @@ -96,13 +96,11 @@ {{ test._id | string | truncate_uid }} - - {% if test.passed %} - Passed - {% else %} - Failed - {% endif %} - + {% if test.passed %} + Pass + {% else %} + Fail + {% endif %} {% if test.results.property and test.results.property.ANALYSIS_VERSION %} {{ test.results.property.ANALYSIS_VERSION }} @@ -218,7 +216,7 @@
{% endblock %} -{% block scripts %} +{% block javascript %} {% endblock %} -- GitLab From 8b9709fe6ea6b01d109dc4167ff31cc933ff3af2 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 17:13:07 -0700 Subject: [PATCH 22/33] such an idiot, just use bootstrap dropdown --- viewer/templates/qcTests.html | 71 +++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/viewer/templates/qcTests.html b/viewer/templates/qcTests.html index f03b28205..533fef968 100644 --- a/viewer/templates/qcTests.html +++ b/viewer/templates/qcTests.html @@ -152,12 +152,27 @@ {% if start_page > 2 %}
  • - +
  • {% endif %} {% endif %} @@ -181,12 +196,27 @@ {% if end_page < table_docs['total_pages'] %} {% if end_page < table_docs['total_pages'] - 1 %}
  • - +
  • {% endif %}
  • @@ -246,22 +276,5 @@ // All columns can now be sorted since we removed rowspans }); }); - - function jumpToPage(pageNum) { - if (pageNum && pageNum !== "") { - const baseUrl = "{{ url_for('qc_api.qc_tests') }}"; - const keyword = "{{ table_docs['search_phrase'] }}"; - const stageFilter = "{{ table_docs['stage_filter'] }}"; - const testTypeFilter = "{{ table_docs['test_type_filter'] }}"; - - const url = new URL(baseUrl, window.location.origin); - if (keyword) url.searchParams.set("keyword", keyword); - if (stageFilter) url.searchParams.set("stage_filter", stageFilter); - if (testTypeFilter) url.searchParams.set("test_type_filter", testTypeFilter); - url.searchParams.set("page", pageNum); - - window.location.href = url.toString(); - } - } {% endblock %} -- GitLab From b3754c5772d6d209750adcd36682f4f7ff481b47 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 17:13:40 -0700 Subject: [PATCH 23/33] fix doc minor --- docs/workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/workflow.md b/docs/workflow.md index da5aeacca..cbaa6d39e 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -145,7 +145,7 @@ Running a simple electrical test is essentially just running 3 steps: Before going through this section, user is expected to have a basic idea of how the chip config is administrated in LocalDB. If you are not familiar - with, please go through [the doc](../technicalities#chip-configs). + with, please go through [the doc](../technicalities.md#chip-configs). YARR chip scans are simply possible to carry out by the `scanConsole` command. However, initially you need to generate the scan connectivity and chip config -- GitLab From b3e083840a7dc3b15706bdb79631897e9431f492 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 17:20:39 -0700 Subject: [PATCH 24/33] fix up docs --- docs/release-note.md | 15 +++++++++++++++ docs/workflow.md | 2 +- pyproject.toml | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/release-note.md b/docs/release-note.md index 2be199db3..f2e175f4b 100644 --- a/docs/release-note.md +++ b/docs/release-note.md @@ -13,6 +13,21 @@ is linked correctly (!309) - fix link to documentation, add FAQ page (!308) - display tags for non-yarr-related test runs (!310) +- New QC Summary Module Dashboard (/component//summary) (!313) + - Module-level aggregated view of all FE chip QC test results + - Groups tests by measurement sessions and test types with tabbed interface + - Shows hexadecimal chip UIDs alongside serial numbers + - Full breadcrumb navigation and RESTful URLs +- Enhanced QC Tests Page (/qc_tests): (!313) + - Complete rewrite from module-based to individual test result display + - Added pagination (100 results per page) with Bootstrap dropdowns for page + jumping + - Added stage and test type filtering with dropdown selectors + - Fixed TableSorter integration and cell styling (Pass/Fail background colors) + - Removed verbose logging +- Component Page Enhancements: (!313) + - Added prominent "View QC Summary" button for modules with tooltip + - Fixed breadcrumb navigation to use serial number URLs ## [v2.6.7](https://gitlab.cern.ch/YARR/localdb-tools/-/tree/v2.6.7) diff --git a/docs/workflow.md b/docs/workflow.md index cbaa6d39e..38207f5da 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -145,7 +145,7 @@ Running a simple electrical test is essentially just running 3 steps: Before going through this section, user is expected to have a basic idea of how the chip config is administrated in LocalDB. If you are not familiar - with, please go through [the doc](../technicalities.md#chip-configs). + with, please go through [the doc](technicalities.md#chip-configs). YARR chip scans are simply possible to carry out by the `scanConsole` command. However, initially you need to generate the scan connectivity and chip config diff --git a/pyproject.toml b/pyproject.toml index 58ae809f2..d316211a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ dependencies = [ "mdx-truly-sane-lists", # for 2-space indented lists (https://github.com/mkdocs/mkdocs/issues/545) # formatting signatures "black>23", + "click<8.3.0", # https://github.com/pypa/hatch/issues/2050 ] [tool.hatch.envs.docs.env-vars] -- GitLab From 763644f77cc9adb9d4167b6c5f1e313b2627a214 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 17:37:12 -0700 Subject: [PATCH 25/33] more fixup --- viewer/templates/qcTests.html | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/viewer/templates/qcTests.html b/viewer/templates/qcTests.html index 533fef968..471d35538 100644 --- a/viewer/templates/qcTests.html +++ b/viewer/templates/qcTests.html @@ -4,6 +4,11 @@ th { background-color: #eeeeff; } + + .pagination .dropdown-menu { + max-height: 100px; + overflow-y: scroll; + } {% extends "layout.html" %} @@ -161,15 +166,17 @@ aria-haspopup="true" aria-expanded="false" > - +
  • + {{ page_num }} +
  • {% endfor %}
    @@ -205,15 +212,17 @@ aria-haspopup="true" aria-expanded="false" > - +
    -- GitLab From f001d1032283c64ec0b96086098c943a92d3107e Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 17:40:52 -0700 Subject: [PATCH 26/33] fix icons --- viewer/templates/qcTests.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/viewer/templates/qcTests.html b/viewer/templates/qcTests.html index 471d35538..3a410dd81 100644 --- a/viewer/templates/qcTests.html +++ b/viewer/templates/qcTests.html @@ -134,12 +134,12 @@ Previous Previous {% else %}
  • - Previous + Previous
  • {% endif %} @@ -166,7 +166,7 @@ aria-haspopup="true" aria-expanded="false" > - + @@ -216,13 +214,11 @@
    -- GitLab From 08704176012f4c7b40e706b79c0e69799af2c980 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 18:36:06 -0700 Subject: [PATCH 28/33] make the url /component/identifier/summary --- viewer/pages/component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viewer/pages/component.py b/viewer/pages/component.py index 9f092264d..7a8632a5d 100644 --- a/viewer/pages/component.py +++ b/viewer/pages/component.py @@ -777,7 +777,7 @@ def show_summary(): return redirect(url) -@component_api.route("//summary", methods=["GET"]) +@component_api.route("/component//summary", methods=["GET"]) def qc_summary(serial_number): """Module-level QC dashboard showing aggregated test results for all FE chips.""" initPage() -- GitLab From ac475725b182b5e90a4dcaa026d1a6d5955c4716 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 21:18:16 -0700 Subject: [PATCH 29/33] fix the install in gitlab --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7f12be500..75aa1ab52 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,7 +17,7 @@ before_script: " | git credential approve - python -m venv venv - source venv/bin/activate - - python -m pip install -U pip pipx + - python -m pip install -U pip pipx 'click < 8.3.0' - python -m pipx ensurepath - python -m pip freeze --local -- GitLab From deb82338a34c1014a9e60ee232cc2cdd712e44dd Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 22:17:23 -0700 Subject: [PATCH 30/33] fix dep --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 75aa1ab52..f684141ff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,8 +17,9 @@ before_script: " | git credential approve - python -m venv venv - source venv/bin/activate - - python -m pip install -U pip pipx 'click < 8.3.0' + - python -m pip install -U pip pipx - python -m pipx ensurepath + - python -m pipx install --spec "hatch click<8.3.0" hatch - python -m pip freeze --local pre-commit: -- GitLab From 0c03652999d6ce8dabad399c3877415b706f1237 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 22:20:17 -0700 Subject: [PATCH 31/33] fix up the spec for running hatch --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f684141ff..b7aeca103 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,7 +19,6 @@ before_script: - source venv/bin/activate - python -m pip install -U pip pipx - python -m pipx ensurepath - - python -m pipx install --spec "hatch click<8.3.0" hatch - python -m pip freeze --local pre-commit: @@ -59,7 +58,7 @@ make-docs: stage: build rules: - if: $CI_PIPELINE_SOURCE == "push" - script: pipx run hatch run docs:build-check + script: pipx run --spec "click<8.3.0" hatch run docs:build-check artifacts: paths: - site -- GitLab From 1ef959cd5d20630821d1b56ada92e66ae73bc512 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 22:23:03 -0700 Subject: [PATCH 32/33] fix spec again --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b7aeca103..deb66bac9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -58,7 +58,7 @@ make-docs: stage: build rules: - if: $CI_PIPELINE_SOURCE == "push" - script: pipx run --spec "click<8.3.0" hatch run docs:build-check + script: pipx run --spec "hatch click<8.3.0" hatch run docs:build-check artifacts: paths: - site -- GitLab From 873e31c7cc209431499627b4c03c2f0d007b57b6 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 18 Sep 2025 22:26:22 -0700 Subject: [PATCH 33/33] drop pipx ugh --- .gitlab-ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index deb66bac9..fd54e97d4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,8 +17,7 @@ before_script: " | git credential approve - python -m venv venv - source venv/bin/activate - - python -m pip install -U pip pipx - - python -m pipx ensurepath + - python -m pip install -U pip hatch 'click < 8.3.0' - python -m pip freeze --local pre-commit: @@ -58,7 +57,7 @@ make-docs: stage: build rules: - if: $CI_PIPELINE_SOURCE == "push" - script: pipx run --spec "hatch click<8.3.0" hatch run docs:build-check + script: hatch run docs:build-check artifacts: paths: - site -- GitLab