from django.db.models import Value, IntegerField, CharField, F from django.db.models.functions import Coalesce, Cast, Replace, NullIf, KeyTextTransform # Define the path to the match score # Based on your tracebacks, the path is: ai_analysis_data -> analysis_data -> match_score SCORE_PATH_RAW = F('ai_analysis_data__analysis_data__match_score') # Define a robust annotation expression for safely extracting and casting the match score. # This sequence handles three common failure points: # 1. Missing keys (handled by Coalesce). # 2. Textual scores (e.g., "N/A" or "") (handled by NullIf). # 3. Quoted numeric scores (e.g., "50") from JSONB extraction (handled by Replace). def get_safe_score_annotation(): """ Returns a Django Expression object that safely extracts a score from the JSONField, cleans it, and casts it to an IntegerField. """ # 1. Extract the JSON value as text and force a CharField for cleaning functions # Using the double-underscore path is equivalent to the KeyTextTransform # for the final nested key in a PostgreSQL JSONField. extracted_text = Cast(SCORE_PATH_RAW, output_field=CharField()) # 2. Clean up any residual double-quotes that sometimes remain if the data # was stored as a quoted string (e.g., "50") cleaned_text = Replace(extracted_text, Value('"'), Value('')) # 3. Use NullIf to convert the cleaned text to NULL if it is an empty string # (or if it was a non-numeric string like "N/A" after quote removal) null_if_empty = NullIf(cleaned_text, Value('')) # 4. Cast the result (which is now either a clean numeric string or NULL) to an IntegerField. final_cast = Cast(null_if_empty, output_field=IntegerField()) # 5. Use Coalesce to ensure NULL scores (from errors or missing data) default to 0. return Coalesce(final_cast, Value(0))