Last active
June 10, 2025 11:52
-
-
Save aurthurm/368371649508d68a95099bac4a9c6ff1 to your computer and use it in GitHub Desktop.
Fix invalid categories and reassign analysis
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from nmrl.lims.scripts import setup_script_environment | |
from bika.lims import api | |
import transaction | |
from senaite.core.catalog import SETUP_CATALOG | |
import re | |
def create_category(parent, title): | |
category = api.create(parent, "AnalysisCategory", title=title) | |
category.reindexObject() | |
return category | |
def search_category(title): | |
brains = api.search({"portal_type": "AnalysisCategory", "title": title}, SETUP_CATALOG) | |
if not brains: | |
return None | |
return [api.get_object(brain) for brain in brains][0] | |
def validate_categories(app): | |
print("π Scanning for valid and invalid Analysis Categories...") | |
ac_brains = api.search({"portal_type": "AnalysisCategory"}, SETUP_CATALOG) | |
all_cats = [api.get_object(brain) for brain in ac_brains] | |
print("Total categories found: {}".format(len(all_cats))) | |
valid_cats = [c for c in all_cats if re.match(r'^analysiscategory-\d+$', c.getId())] | |
invalid_cats = [c for c in all_cats if not re.match(r'^analysiscategory-\d+$', c.getId())] | |
# Senaite application-level configured categories (often includes wrong ones) | |
# Senaite setup categories | |
setup_cats = app.senaite.setup.analysiscategories | |
setup_cats = [setup_cats[cid] for cid in setup_cats] | |
setup_cat_ids = [c.getId() for c in setup_cats] | |
print("\nπ§ Categories in `app.senaite.setup.analysiscategories` ({}): {}".format( | |
len(setup_cat_ids), | |
setup_cat_ids | |
)) | |
# β Assert all valid categories are present in setup | |
missing_from_setup = [c for c in valid_cats if c.getId() not in setup_cat_ids] | |
if missing_from_setup: | |
print("\nπ« ERROR: Valid categories missing from Senaite setup:") | |
for c in missing_from_setup: | |
print(" - {}: {}".format(c.getId(), c.Title())) | |
raise AssertionError("Some valid categories are missing from `app.senaite.setup.analysiscategories`.") | |
# β οΈ Add setup-level invalids to invalid_cats | |
invalid_cats = list(set(invalid_cats + [ | |
c for c in setup_cats | |
if not re.match(r'^analysiscategory-\d+$', c.getId()) | |
])) | |
print("β Valid categories: {} -> {}".format(len(valid_cats), [c.getId() for c in valid_cats])) | |
print("β οΈ Invalid categories: {} -> {}".format(len(invalid_cats), [c.getId() for c in invalid_cats])) | |
return valid_cats, invalid_cats | |
def fix_services(app): | |
print("π Fixing services ...") | |
valid_cats, invalid_cats = validate_categories(app) | |
service_brains = api.search({"portal_type": "AnalysisService"}) | |
analysis_services = [api.get_object(x) for x in service_brains] | |
fixed = [] | |
for invalid_cat in invalid_cats: | |
title = invalid_cat.Title() | |
services = list( | |
filter(lambda a: a.getCategory().getId() == invalid_cat.getId(), analysis_services) | |
) | |
print(" - AnalysisServices using this category: {}".format(len(services))) | |
# get a valid category with the same title or creare a new one | |
valid_cat = search_category(title) | |
if not valid_cat: | |
print(" -> Creating new valid category for: {}".format(title)) | |
valid_cat = create_category(valid_cats[0].aq_parent, title=title) | |
fixed.append(valid_cat) | |
for s in services: | |
s.setCategory(valid_cat) | |
s.reindexObject() | |
transaction.commit() | |
print("β Done fixing services: {} -> {}".format(len(fixed), [c.getId() for c in fixed])) | |
return fixed, valid_cats, invalid_cats | |
def fix_(app): | |
print("π Fixing categories related issues ...") | |
fixed, valid_, invalid_cats = fix_services(app) | |
valid_cats = fixed + valid_ | |
reassigned_count = 0 | |
batch_counter = 0 | |
for invalid_cat in invalid_cats: | |
title = invalid_cat.Title() | |
print("\nInvalid ID found: {} ({})".format(invalid_cat.getId(), title)) | |
match = next((c for c in valid_cats if c.Title() == title), None) | |
analyses = api.search({"portal_type": "Analysis", "getCategoryUID": invalid_cat.UID()}) | |
print(" - Analyses using this category: {}".format(len(analyses))) | |
if match: | |
print(" -> Found valid category with same title: {}".format(match.getId())) | |
else: | |
# this must not happen ideally since all must be fixed by fix_services at the top | |
print(" -> [Not expected] Creating new valid category for: {}".format(title)) | |
match = create_category(valid_cats[0].aq_parent, title=title) | |
valid_cats.append(match) | |
for brain in analyses: | |
a = api.get_object(brain) | |
a.setCategory(match) | |
a.reindexObject() | |
reassigned_count += 1 | |
batch_counter += 1 | |
if batch_counter >= 10: | |
transaction.commit() | |
print(" π Intermediate commit after 10 updates") | |
batch_counter = 0 | |
print("\nπ Reassigned {} analyses from invalid categories.".format(reassigned_count)) | |
# Clean up invalid categories | |
print("\nπ§Ή Deleting {} invalid categories...".format(len(invalid_cats))) | |
for cat in invalid_cats: | |
print(" - Deleting: {} ({})".format(cat.getId(), cat.Title())) | |
api.delete(cat, False) | |
transaction.commit() | |
print("β Cleanup and reassignment complete.\n") | |
return valid_cats, invalid_cats | |
def main(app): | |
setup_script_environment(app) | |
fix_(app) | |
if __name__ == "__main__": | |
main(app) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment