1414 find_many_member_ids_by_identities ,
1515 find_many_organization_ids_by_identities ,
1616 get_repo_affiliation_registry ,
17- insert_member_organizations ,
18- insert_member_segment_affiliations ,
1917 save_service_execution ,
2018 upsert_repo_affiliation_registry ,
2119)
@@ -82,7 +80,8 @@ async def read_text_file(file_path: str) -> str:
8280 return safe_decode (await f .read ())
8381
8482 @staticmethod
85- def compute_file_sha (content : str ) -> str :
83+ def compute_file_hash (content : str ) -> str :
84+ """SHA-256 hex digest of UTF-8 file content (not a Git blob SHA)."""
8685 return hashlib .sha256 (content .encode ("utf-8" )).hexdigest ()
8786
8887 @staticmethod
@@ -163,9 +162,7 @@ async def list_root_text_files(self, repo_path: str) -> list[str]:
163162
164163 return sorted (files )
165164
166- async def read_file_start_preview (
167- self , repo_path : str , relative_path : str
168- ) -> str | None :
165+ async def read_file_start_preview (self , repo_path : str , relative_path : str ) -> str | None :
169166 """Read a short preview of a candidate file for the discovery AI prompt."""
170167 full_path = os .path .join (repo_path , relative_path )
171168 if not await aiofiles .os .path .isfile (full_path ):
@@ -185,9 +182,7 @@ async def read_file_start_preview(
185182 self .logger .debug (f"Could not read preview for { relative_path } : { repr (error )} " )
186183 return None
187184
188- async def format_candidates_with_previews (
189- self , repo_path : str , candidates : list [str ]
190- ) -> str :
185+ async def format_candidates_with_previews (self , repo_path : str , candidates : list [str ]) -> str :
191186 blocks : list [str ] = []
192187 for relative_path in candidates :
193188 preview = await self .read_file_start_preview (repo_path , relative_path )
@@ -285,9 +280,7 @@ async def pick_affiliation_file_with_ai(
285280 f"Picking affiliation file with AI "
286281 f"(batch { batch_index } /{ total_batches } , { len (batch )} candidates)"
287282 )
288- candidates_with_previews = await self .format_candidates_with_previews (
289- repo_path , batch
290- )
283+ candidates_with_previews = await self .format_candidates_with_previews (repo_path , batch )
291284 prompt = self .get_file_picker_prompt (
292285 repo_url ,
293286 candidates_with_previews = candidates_with_previews ,
@@ -475,9 +468,13 @@ async def parse_affiliations(
475468 pydantic_model = AffiliationParseOutput ,
476469 )
477470
478- if parse_result .output .affiliations :
479- raw_count = len (parse_result .output .affiliations )
480- normalized = self .normalize_parsed_affiliations (parse_result .output .affiliations )
471+ affiliations = parse_result .output .affiliations
472+ if affiliations is not None :
473+ if not affiliations :
474+ raise AffiliationAnalysisError ()
475+
476+ raw_count = len (affiliations )
477+ normalized = self .normalize_parsed_affiliations (affiliations )
481478
482479 if not normalized :
483480 raise AffiliationAnalysisError ()
@@ -552,38 +549,31 @@ async def resolve_snapshot(
552549 registry : dict | None ,
553550 file_path : str ,
554551 content : str ,
555- file_sha : str ,
552+ file_hash : str ,
556553 repo_url : str = "" ,
557554 ) -> tuple [list [AffiliationInfoItem ], float ]:
558555 """
559556 Reuse the saved snapshot when the file is unchanged, otherwise re-parse.
560557 """
561- stored_sha = registry .get ("file_sha " ) if registry else None
558+ stored_hash = registry .get ("file_hash " ) if registry else None
562559 existing_snapshot = registry .get ("snapshot" ) if registry else None
563560 needs_parse = (
564- file_sha != stored_sha
565- or existing_snapshot is None
566- or not existing_snapshot
561+ file_hash != stored_hash or existing_snapshot is None or not existing_snapshot
567562 )
568563
569564 if not needs_parse :
570- if existing_snapshot :
571- applyable = self .normalize_parsed_affiliations (existing_snapshot )
565+ applyable = self .normalize_parsed_affiliations (existing_snapshot )
572566
573- if applyable :
574- self .logger .debug ("Using cached snapshot, file unchanged" )
575- return applyable , 0.0
567+ if applyable :
568+ self .logger .debug ("Using cached snapshot, file unchanged" )
569+ return applyable , 0.0
576570
577- self .logger .info ("Cached snapshot had no usable rows, reparsing file" )
578- else :
579- return existing_snapshot , 0.0
571+ self .logger .info ("Cached snapshot had no usable rows, reparsing file" )
580572
581573 affiliations , parse_cost = await self .parse_affiliations (file_path , content , repo_url )
582574 return affiliations , parse_cost
583575
584- async def check_if_interval_elapsed (
585- self , registry : dict | None
586- ) -> tuple [bool , float ]:
576+ async def check_if_interval_elapsed (self , registry : dict | None ) -> tuple [bool , float ]:
587577 """
588578 Check whether enough time has passed since the last affiliation run.
589579
@@ -696,7 +686,7 @@ async def apply_affiliations(
696686 seen_pairs : set [tuple [str , str ]] = set ()
697687 skipped_unresolved = 0
698688
699- for ( member_idx , org_idx ) in row_identity_refs :
689+ for member_idx , org_idx in row_identity_refs :
700690 if member_idx is None or org_idx is None :
701691 skipped_unresolved += 1
702692 continue
@@ -739,9 +729,7 @@ async def apply_affiliations(
739729 existing_msas = segment_affiliations_by_member .get (member_id , [])
740730
741731 if not self .has_undated_affiliation_for_org (existing_mos , organization_id ):
742- mo_inserts .append (
743- {"member_id" : member_id , "organization_id" : organization_id }
744- )
732+ mo_inserts .append ({"member_id" : member_id , "organization_id" : organization_id })
745733
746734 if self .has_undated_affiliation_for_org (existing_msas , organization_id ):
747735 continue
@@ -755,7 +743,7 @@ async def apply_affiliations(
755743 }
756744 )
757745
758- # TODO: Enable CDP writes after testing is complete
746+ # TODO: Enable CDP writes after testing (import insert_member_* from crud)
759747 # await insert_member_organizations(mo_inserts)
760748 # await insert_member_segment_affiliations(msa_inserts)
761749
@@ -795,21 +783,21 @@ async def process_affiliations(
795783 await upsert_repo_affiliation_registry (
796784 repository .id ,
797785 file_path = None ,
798- file_sha = None ,
786+ file_hash = None ,
799787 status = AffiliationRegistryStatus .NOT_FOUND .value ,
800788 snapshot = None ,
801789 )
802790 raise AffiliationFileNotFoundError (ai_cost = ai_cost )
803791
804792 file_path_on_disk = os .path .join (batch_info .repo_path , latest_file_path )
805793 content = await self .read_text_file (file_path_on_disk )
806- file_sha = self .compute_file_sha (content )
794+ file_hash = self .compute_file_hash (content )
807795
808796 affiliations , parse_cost = await self .resolve_snapshot (
809797 registry ,
810798 latest_file_path ,
811799 content ,
812- file_sha ,
800+ file_hash ,
813801 repository .url ,
814802 )
815803 ai_cost += parse_cost
@@ -819,14 +807,12 @@ async def process_affiliations(
819807 await upsert_repo_affiliation_registry (
820808 repository .id ,
821809 file_path = latest_file_path ,
822- file_sha = file_sha ,
810+ file_hash = file_hash ,
823811 status = AffiliationRegistryStatus .SUCCESS .value ,
824812 snapshot = affiliations ,
825813 )
826814
827- self .logger .info (
828- f"Finished with { len (affiliations )} rows from { latest_file_path } "
829- )
815+ self .logger .info (f"Finished with { len (affiliations )} rows from { latest_file_path } " )
830816
831817 except AffiliationIntervalNotElapsedError as e :
832818 execution_status = ExecutionStatus .FAILURE
@@ -847,7 +833,7 @@ async def process_affiliations(
847833 await upsert_repo_affiliation_registry (
848834 repository .id ,
849835 file_path = latest_file_path ,
850- file_sha = None ,
836+ file_hash = None ,
851837 status = AffiliationRegistryStatus .ERROR .value ,
852838 snapshot = registry .get ("snapshot" ) if registry else None ,
853839 )
0 commit comments