From a0cd49c6c8f8c6ac6527bda7c6f60aa0f1d201a3 Mon Sep 17 00:00:00 2001 From: Hideyuki Oide Date: Tue, 28 Oct 2025 16:18:04 +0900 Subject: [PATCH 1/6] flagging on signoff implementing --- .../itkpd_interface/recursiveUploader.py | 9 + viewer/json-lists/flagging_rule.json | 214 +++++++++++++ viewer/pages/qc.py | 124 ++++++++ viewer/templates/component.html | 12 +- viewer/templates/component/breadcrumbs.html | 11 +- viewer/templates/localSignoff.html | 22 +- viewer/templates/select_QC_table.html | 300 ++++++++++++++++-- 7 files changed, 644 insertions(+), 48 deletions(-) create mode 100644 viewer/json-lists/flagging_rule.json diff --git a/viewer/functions/itkpd_interface/recursiveUploader.py b/viewer/functions/itkpd_interface/recursiveUploader.py index 825817072..a00ae9630 100755 --- a/viewer/functions/itkpd_interface/recursiveUploader.py +++ b/viewer/functions/itkpd_interface/recursiveUploader.py @@ -1003,6 +1003,15 @@ class RecursiveUploader(PDInterface.PDInterface): json={"component": parent["proID"], "comments": newComments}, ) + # flags processing + if parent["componentType"] == "module": + # 1) delete existing flags on PDB + for flag in componentDoc.get("flags"): + self.pd_client.post( "deleteComponentFlag", json = { "component" : parent["proID"], "flag" : flag.get("code") } ) + + for flag in parent.get("flags"): + self.pd_client.post( "addComponentFlag", json = { "component" : parent["proID"], "flag" : flag } ) + msg = f"processed {parent['name']} ({parent['componentType']})" logger.info(msg) diff --git a/viewer/json-lists/flagging_rule.json b/viewer/json-lists/flagging_rule.json new file mode 100644 index 000000000..6448f2370 --- /dev/null +++ b/viewer/json-lists/flagging_rule.json @@ -0,0 +1,214 @@ +{ + "flagging_page1" : { + "title" : " Flagging #1: General Failures", + "contents" : + [ { "header_question" : "Q: Is the module not wire-bondable
(e.g., glue on pads or missing wire-bond pads)?", + "stage" : "ASSEMBLY", + "flags" : [ { "code" : "BAD_NOT_WIREBONDABLE", + "stage" : "ASSEMBLY", + "description" : "use when the module can't be wirebonded (there is glue on pad, wirebonding pads on the chips are missing etc..)" } ], + "rule" : "immediate_graveyard", + "note" : " If you check this, the module will move to GRAVEYARD." + }, + + { "header_question" : "Q: Was the module mechanically damaged during testing
(e.g., fire in test box, crushed wire-bonds, cracked sensor)?", + "stage" : null, + "flags" : [ { "code" : "BAD_DURING_ASSEMBLY", + "stage" : "ASSEMBLY", + "description" : "when something goes wrong during assembly and this cause a permanent damage to the module (module get stuck to the pin, gets dropped etc..)" }, + { "code" : "BAD_DURING_WB", + "stage" : "WIREBONDING", + "description" : "unrecoverable failure during wirebonding" }, + { "code" : "BAD_DURING_MASKING_UNMASKING", + "stage" : "MASKING", + "description" : "unrecoverable failure during parylene masking or umasking" }, + { "code" : "BAD_DURING_COATING", + "stage" : "COATING", + "description" : "unrecoverable failure during parylene coating" }, + { "code" : "BAD_DURING_OBWBP_ASSEMBLY", + "stage" : "PROTECTION", + "description" : "unrecoverable failure during parylene OB wirebond protection assembly" }, + { "code" : "BAD_DURING_THERMAL_CYCLING", + "stage" : "THERMAL", + "description" : "a mechanical damage to the module is done while in thermal cycling stage (the testing box catches fire, the wirebond are smashes loading/unloading the module from the testing box etc..)" }, + { "code" : "BAD_DURING_TESTING", + "stage" : null, + "description" : "a mechanical damage to the module is done during testing (the testing box catches fire, the wirebond are smashes loading/unloading the module from the testing box, the sensor cracks etc..)" }, + { "code" : "BAD_DURING_RECEPTION", + "stage" : null, + "description" : "a mechanical damage to the module at reception (cover shipping and damage at reception)" }, + { "code" : "BAD_DURING_OTHER", + "stage" : null, + "description" : "catastrophic events not covered (like eathquake that destroy all modules in cabinets)" } ], + "rule" : "immediate_graveyard", + "note" : " If you check this, the module will move to GRAVEYARD." + }, + + { "header_question" : "Q: Did one or more chips detach from the sensor?", + "stage" : null, + "flags" : [ { "code" : "BAD_FALL_FECHIP", + "stage" : null, + "description": "use when the module can't be wirebonded (there is glue on pad, wirebonding pads on the chips are missing etc..)" } ], + "rule" : "immediate_graveyard", + "note" : " If you check this, the module will move to GRAVEYARD." + }, + + { "header_question" : "Q: Is the PCB tabs detached or damaged?", + "stage" : null, + "flags" : [ { "code" : "INFO_BROKEN_PCB_TAB", + "stage" : null, + "description" : "mark it when realise this happens" } ], + "rule" : "info_only", + "note" : " This flag is INFO-ONLY: Checking this does not block QC stages to proceed." + } + + ] + }, + + "flagging_page2" : { + "header" : " Flagging #2: Failures in Electrical Testing -- Part I", + "contents" : [ + + { "header_question" : "Q: Does the module fail to reach nominal voltage when powered, and cannot be communicated with (indicating a possible short or wire-bonding issue)?", + "stage" : null, + "flags" : [ { "code" : "BAD_MODULE_POWER", + "stage" : null, + "description" : "if module, once powered up, does not reach nominal voltage, and can't be communicated with. This most likley point to either a short or mistake in wirebonding. The module should be investigated." } ], + "rule" : "conditional_graveyard", + "note" : " If you check this, the module will move to the UNHAPPY stage if reworkable. Otherwise, it should go to GRAVEYARD." + }, + + { "header_question" : "Q: Is this chip not wire-bonded properly?", + "stage" : null, + "flags" : [ { "code" : "BAD_FECHIP1_NOT_CONNECTED", + "stage" : null, + "description" : "FE1: when chip is not wirebonded" }, + { "code" : "BAD_FECHIP2_NOT_CONNECTED", + "stage" : null, + "description" : "FE2: when chip is not wirebonded" }, + { "code" : "BAD_FECHIP3_NOT_CONNECTED", + "stage" : null, + "description" : "FE3: when chip is not wirebonded" }, + { "code" : "BAD_FECHIP4_NOT_CONNECTED", + "stage" : null, + "description" : "FE4: when chip is not wirebonded" } + ], + "rule" : "conditional_graveyard", + "note" : " If you check any of the above, the module will move to the UNHAPPY stage if reworkable. Otherwise, it should go to GRAVEYARD." + }, + + { "header_question" : "Q: Did you disable this chip manually in the connectivity file because disabling all CCs did not fix the issue, or due to physical damage?", + "stage" : null, + "flags" : [ { "code" : "BAD_FECHIP1_DISABLED", + "stage" : null, + "description" : "FE1: set it when you disable chip by hand in the connectivity file: only when disabling all CC does not fix the issue or when you have physical damage." }, + { "code" : "BAD_FECHIP2_DISABLED", + "stage" : null, + "description" : "FE2: set it when you disable chip by hand in the connectivity file: only when disabling all CC does not fix the issue or when you have physical damage." }, + { "code" : "BAD_FECHIP3_DISABLED", + "stage" : null, + "description" : "FE3: set it when you disable chip by hand in the connectivity file: only when disabling all CC does not fix the issue or when you have physical damage." }, + { "code" : "BAD_FECHIP4_DISABLED", + "stage" : null, + "description" : "FE4: set it when you disable chip by hand in the connectivity file: only when disabling all CC does not fix the issue or when you have physical damage." } + ], + "rule" : "conditional_graveyard", + "note" : " These flags are automatically analyzed by referring to the registered E_SUMMARY by default. If E_SUMMARY is missing you need to fill them manually. If you check this, the module will move to the UNHAPPY stage if reworkable. Otherwise, it should go to GRAVEYARD." + }, + + { "header_question" : "Q: Did the chip fail to communicate — e.g., no eye opening during the eye-diagram test or a data-transmission failure?", + "stage" : null, + "flags" : [ { "code" : "BAD_FECHIP1_COMMUNICATION", + "stage" : null, + "description" : "FE1: if failure to communicate with the chip at all, so no eye opening when running eyeDiagram or if data transmission test fails" }, + { "code" : "BAD_FECHIP2_COMMUNICATION", + "stage" : null, + "description" : "FE2: if failure to communicate with the chip at all, so no eye opening when running eyeDiagram or if data transmission test fails" }, + { "code" : "BAD_FECHIP3_COMMUNICATION", + "stage" : null, + "description" : "FE3: if failure to communicate with the chip at all, so no eye opening when running eyeDiagram or if data transmission test fails" }, + { "code" : "BAD_FECHIP4_COMMUNICATION", + "stage" : null, + "description" : "FE4: if failure to communicate with the chip at all, so no eye opening when running eyeDiagram or if data transmission test fails" } + ], + "rule" : "unhappy", + "note" : " If you check this, the module will move to UNHAPPY." + }, + + + { "header_question" : "Q: Did the LP-mode communication fail so that the test could not complete?", + "stage" : null, + "flags" : [ { "code" : "BAD_FECHIP1_COM_LPMODE", + "stage" : null, + "description" : "FE1: in case the LP module communication failes and test can't complete" }, + { "code" : "BAD_FECHIP2_COM_LPMODE", + "stage" : null, + "description" : "FE2: in case the LP module communication failes and test can't complete" }, + { "code" : "BAD_FECHIP3_COM_LPMODE", + "stage" : null, + "description" : "FE3: in case the LP module communication failes and test can't complete" }, + { "code" : "BAD_FECHIP4_COM_LPMODE", + "stage" : null, + "description" : "FE4: in case the LP module communication failes and test can't complete" } + ], + "rule" : "unhappy", + "note" : " If you check this, the module will move to UNHAPPY." + } + + ] + }, + + "flagging_page3" : { + "header" : " Flagging #3: Failures in Electrical Testing -- Part II", + "contents" : [ + { "header_question" : "Q: Does the IV analysis appear inconsistent — passing when it should fail or failing when it should pass?", + "stage" : null, + "flags" : [ { "code" : "ANOMALOUS_IV", + "stage" : null, + "description" : "mark it when realise this happens" } + ], + "rule" : "info_only", + "note" : " This flag is INFO-ONLY: Checking this does not block QC stages to proceed." + }, + + { "header_question" : "Q: Does the module show core-column (“CC”) issues that cannot be masked with existing tools, appearing only during tuning?", + "stage" : null, + "flags" : [ { "code" : "PROBLEMATIC_CORE_COLUMN", + "stage" : null, + "description" : "when the module has CC issues that can't be masked with existing tools, aka CC issues appearing only during tuning" } + ], + "rule" : "unhappy", + "note" : " If you check this, the module will move to UNHAPPY." + } + + ] + }, + + "flagging_page4" : { + "header" : " Flagging #4: Final Question", + "contents" : [ + { "header_question" : "Q: Is the component used for any of the following QA purpose?", + "stage" : null, + "flags" : [ { "code" : "QA_FECHIP", + "stage" : null, + "description" : "QA module" }, + { "code" : "QA_SENSOR", + "stage" : null, + "description" : "QA module" }, + { "code" : "QA_IRRADIATION", + "stage" : null, + "description" : "QA module" }, + { "code" : "QA_SYSTEM_TEST", + "stage" : null, + "description" : "QA module" }, + { "code" : "QA_DELAMINATION", + "stage" : null, + "description" : "QA module" } + ], + "rule" : "qa", + "note" : " If you check this, the module will move to NOTCONSIDEREDINYIELDS." + } + + ] + } +} diff --git a/viewer/pages/qc.py b/viewer/pages/qc.py index 1251df47e..d346ee81e 100644 --- a/viewer/pages/qc.py +++ b/viewer/pages/qc.py @@ -10,6 +10,7 @@ import logging import os import pprint import re +import fnmatch import shutil import statistics import subprocess @@ -45,6 +46,7 @@ from functions.checkouter import make_archive_threadsafe from functions.common import ( AT_DIR, THUMBNAIL_DIR, + VIEWER_DIR, TMP_DIR, SN_typeinfo, check_and_process, @@ -2615,6 +2617,7 @@ def select_test(): thisComponent = localdb.component.find_one( {"_id": ObjectId(page_docs["componentId"])} ) + page_docs["component"] = thisComponent page_docs["componentName"] = str(thisComponent["name"]) # logger.debug( 'select_test(): thisComponent = '+pprint.pformat( thisComponent ) ) @@ -2669,6 +2672,96 @@ def select_test(): else: entitledTests[testType] = selected + # flagging rule loading + rule_file = f"{VIEWER_DIR}/json-lists/flagging_rule.json" + page_docs["flagging_rule"] = json.loads(Path(rule_file).read_text()) + + # module FLAGS + if page_docs["mode"] == "input": + page_docs["module_flags_on"] = thisComponent.get("flags") + else: + page_docs["module_flags_on"] = [ k for k in request.form.keys() if request.form.getlist(k)[-1] == "on" ] + + page_docs["module_flags_off"] = [ k for k in request.form.keys() if request.form.getlist(k)[-1] == "off" ] + + is_all_good = True + + # Handling of automatic flags from E-Summary + e_tests = [ t for t in page_docs["tests"] if t.get("testType") == "E_SUMMARY" and t.get("selected") ] + if len( e_tests ) > 0: + e_test_result = e_tests[0].get("results") + if e_test_result.get("MODULE_DISABLED_COLUMNS_NUMBER") > 0: + page_docs["module_flags_on"] += ["INFO_CORE_COLUMN"] + is_all_good = False + + # triplet or not + is_triplet = any( + XXYY in thisComponent.get("serialNumber") + for XXYY in [ + "PIMS", + "PIM0", + "PIM5", + "PIR6", + "PIR7", + "PIR8", + "PIX3", + "PIX4", + "PIX5", + ] + ) + + # count the number of missing FEs in any of essential tests + essential_tests = ["ADC_CALIBRATION", + "ANALOG_READBACK", + "SLDO", + "INJECTION_CAPACITANCE", + "VCAL_CALIBRATION", + "DATA_TRANSMISSION", + "MIN_HEALTH_TEST", + "TUNING", + "PIXEL_FAILURE_ANALYSIS" ] + + for fe in range(1, (1+3) if is_triplet else (1+4)): + for essential_test in essential_tests: + keyname = "_".join( [ "MODULE", essential_test, "FE", "LINK", str(fe) ] ) + if not e_test_result.get( keyname ): + is_all_good = False + if page_docs["mode"] == "flagging_page2": + page_docs["module_flags_on"] += [ f"BAD_FECHIP{fe}_DISABLED" ] + break + + # End of handling of automatic flags from E-Summary + for t in page_docs["tests"]: + if not t.get("selected"): + continue + + if not t.get("passed"): + is_all_good = False + + page_docs["flagging_skippable"] = is_all_good + + immediate_graveyard_flags = [ "BAD_FECHIP[1-4]_DISABLED", "BAD_FALL_FECHIP", "BAD_NOT_WIREBONDABLE", "BAD_DURING_*" ] + conditional_graveyard_flags = [ "BAD_MODULE_POWER", "BAD_FECHIP[1-4]_NOT_CONNECTED" ] + unhappy_flags = [ "PROBLEMATIC_CORE_COLUMN", "BAD_FECHIP[1-4]_COMMUNICATION", "BAD_FECHIP[1-4]_COM_LPMODE" ] + qa_flags = [ "QA_*" ] + + page_docs["flagging_modes"] = { "immediate_graveyard" : any( fnmatch.fnmatchcase(a, pattern) for pattern in immediate_graveyard_flags for a in page_docs["module_flags_on"] ), + "conditional_graveyard" : any( fnmatch.fnmatchcase(a, pattern) for pattern in conditional_graveyard_flags for a in page_docs["module_flags_on"] ), + "unhappy" : any( fnmatch.fnmatchcase(a, pattern) for pattern in unhappy_flags for a in page_docs["module_flags_on"] ), + "qa" : any( fnmatch.fnmatchcase(a, pattern) for pattern in qa_flags for a in page_docs["module_flags_on"] ) } + + + + conditional_state = None + if "complete_sync." in page_docs["mode"]: + conditional_state = page_docs["mode"].split(".")[-1] + page_docs["mode"] = "complete_sync" + + # Local signoff mode + if request.form.get("local_signoff") == "enable" and page_docs["mode"] == "complete_sync": + page_docs["mode"] = "complete" + + if page_docs["mode"] == "complete" or page_docs["mode"] == "complete_sync": localdb.QC.module.status.update_one( {"component": str(thisComponent["_id"])}, @@ -2748,6 +2841,17 @@ def select_test(): # Check if current stage is a special stage - if so, don't increment stage if current_stage in special_stages: next_stage = current_stage + elif page_docs["flagging_modes"].get("immediate_graveyard"): + next_stage = "MODULE/GRAVEYARD" + elif page_docs["flagging_modes"].get("conditional_graveyard"): + if conditional_state == "unhappy": + next_stage = "MODULE/UNHAPPY" + else: + next_stage = "MODULE/GRAVEYARD" + elif page_docs["flagging_modes"].get("unhappy"): + next_stage = "MODULE/UNHAPPY" + elif page_docs["flagging_modes"].get("qa"): + next_stage = "MODULE/NOTCONSIDEREDINYIELDS" else: next_stage_index = min( len(stageInfo["stage_flow"]) - 1, @@ -2774,6 +2878,7 @@ def select_test(): next_stage = next(next_stages_required) except StopIteration: next_stage = current_stage + # endif changed_components = sync_component_stages( localdb, @@ -2785,6 +2890,25 @@ def select_test(): msg = f"status of changing component stages recursively: {changed_components}" logger.info(msg) + # Set module flags + localdb.component.update_one( { "_id" : thisComponent.get("_id") }, + { "$set" : { "flags" : page_docs["module_flags_on"] } } ) + + # Comment treatment + signoff_comment = request.form.get("signoff_comment") + thistime = datetime.now(timezone.utc) + user = userdb.viewer.user.find_one( { "_id" : ObjectId( str(session["user_id"]) ) } ) + comment_info = { + "sys": {"rev": 0, "cts": thistime, "mts": thistime}, + "component": str( thisComponent.get("_id") ), + "comment": f"sign-off at {current_stage}; appended flags: {page_docs['module_flags_on']}; next stage: {next_stage}: comment: {signoff_comment}", + "componentType": componentType, + "user_id": str(session["user_id"]), + "name" : f"{user.get('name')} ({user.get('institution')})", + "datetime": thistime, + } + localdb.comments.insert_one(comment_info) + if page_docs["mode"] == "complete": return redirect( url_for( diff --git a/viewer/templates/component.html b/viewer/templates/component.html index d458ce845..afb535da7 100644 --- a/viewer/templates/component.html +++ b/viewer/templates/component.html @@ -251,7 +251,17 @@ {% for flag_code, flag in component['info']['flags']|dictsort %} {% if flag["status"] %} - • {{ flag["name"] }} + •  + {% if "COM" in flag_code %} + + {% elif "BAD" in flag_code %} + + {% elif "QA" in flag_code %} + + {% else %} + + {% endif %} + {{ flag_code }}
{% endif %} {% endfor %} diff --git a/viewer/templates/component/breadcrumbs.html b/viewer/templates/component/breadcrumbs.html index 4181242e2..655fa47bf 100644 --- a/viewer/templates/component/breadcrumbs.html +++ b/viewer/templates/component/breadcrumbs.html @@ -1,13 +1,16 @@ -{% set showQCResult = component['qctest']['results'] and 'testType' in component['qctest']['results'] %} +{% set showQCResult = ('qctest' in component and component['qctest']['results'] and 'testType' in component['qctest']['results']) or none %}