2121from flask import json as flask_json
2222from sqlalchemy .orm import aliased
2323from flask_sqlalchemy .model import DefaultMeta
24- from sqlalchemy import func , delete , cast as sql_cast , literal
24+ from sqlalchemy import func , delete , cast as sql_cast , literal , or_
2525from sqlalchemy .dialects .postgresql import JSONB
2626from sqlalchemy .exc import OperationalError , IntegrityError
2727
4242from application .defs import cre_defs
4343from application .utils import file
4444from application .utils .gap_analysis import (
45+ gap_analysis_cache_key_is_primary ,
4546 get_path_score ,
4647 make_resources_key ,
4748 make_subresources_key ,
49+ primary_gap_analysis_payload_is_material ,
4850)
4951
5052
@@ -2300,10 +2302,16 @@ def add_embedding(
23002302 return existing
23012303
23022304 def gap_analysis_exists (self , cache_key ) -> bool :
2303- q = self .session .query (GapAnalysisResults ).filter (
2304- GapAnalysisResults .cache_key == cache_key
2305+ row = (
2306+ self .session .query (GapAnalysisResults )
2307+ .filter (GapAnalysisResults .cache_key == cache_key )
2308+ .first ()
23052309 )
2306- return self .session .query (q .exists ()).scalar ()
2310+ if row is None :
2311+ return False
2312+ if gap_analysis_cache_key_is_primary (cache_key ):
2313+ return primary_gap_analysis_payload_is_material (row .ga_object )
2314+ return True
23072315
23082316 def get_gap_analysis_result (self , cache_key ) -> str :
23092317 logger .info (f"looking for gap analysis with cache key: { cache_key } " )
@@ -2318,7 +2326,16 @@ def get_gap_analysis_result(self, cache_key) -> str:
23182326 logger .info (f"did not find gap analysis with cache key: { cache_key } " )
23192327
23202328 def add_gap_analysis_result (self , cache_key : str , ga_object : str ):
2321- if not self .gap_analysis_exists (cache_key ):
2329+ existing = (
2330+ self .session .query (GapAnalysisResults )
2331+ .filter (GapAnalysisResults .cache_key == cache_key )
2332+ .first ()
2333+ )
2334+ if existing :
2335+ existing .ga_object = ga_object
2336+ self .session .add (existing )
2337+ self .session .commit ()
2338+ else :
23222339 logger .info (f"adding gap analysis result with cache key: { cache_key } " )
23232340 res = GapAnalysisResults (cache_key = cache_key , ga_object = ga_object )
23242341 self .session .add (res )
@@ -2490,6 +2507,20 @@ def gap_analysis(
24902507 grouped_paths [key ] = {"start" : node , "paths" : {}, "extra" : 0 }
24912508 extra_paths_dict [key ] = {"paths" : {}}
24922509
2510+ # Paths may start from CRE nodes that were not included in ``base_standard`` (or
2511+ # ``base_standard`` entries were skipped due to empty ids). Seed roots from each
2512+ # path's start so we never drop Neo paths or hit KeyError in the merge loop below.
2513+ for path in paths :
2514+ start_doc = path .get ("start" )
2515+ if start_doc is None :
2516+ continue
2517+ key = getattr (start_doc , "id" , "" ) or ""
2518+ if not key :
2519+ continue
2520+ if key not in grouped_paths :
2521+ grouped_paths [key ] = {"start" : start_doc , "paths" : {}, "extra" : 0 }
2522+ extra_paths_dict [key ] = {"paths" : {}}
2523+
24932524 for path in paths :
24942525 key = path ["start" ].id
24952526 end_key = path ["end" ].id
@@ -2525,6 +2556,30 @@ def gap_analysis(
25252556
25262557 if cache_key == "" :
25272558 cache_key = make_resources_key (node_names )
2559+ if not grouped_paths :
2560+ if gap_analysis_cache_key_is_primary (cache_key ):
2561+ stale = (
2562+ cre_db .session .query (GapAnalysisResults )
2563+ .filter (
2564+ or_ (
2565+ GapAnalysisResults .cache_key == cache_key ,
2566+ GapAnalysisResults .cache_key .like (cache_key + "->%" ),
2567+ )
2568+ )
2569+ .all ()
2570+ )
2571+ for row in stale :
2572+ cre_db .session .delete (row )
2573+ if stale :
2574+ cre_db .session .commit ()
2575+ logger .warning (
2576+ "Not persisting gap analysis for %s: grouped_paths is empty "
2577+ "(Neo likely had no parseable base standard or no paths). "
2578+ "Re-run after Neo is populated so the pair is not locked in as an empty cache." ,
2579+ cache_key ,
2580+ )
2581+ return (node_names , grouped_paths , extra_paths_dict )
2582+
25282583 logger .info (f"got gap analysis paths for { '>>>' .join (node_names )} , storing result" )
25292584 cre_db .add_gap_analysis_result (
25302585 cache_key = cache_key , ga_object = flask_json .dumps ({"result" : grouped_paths })
0 commit comments