infinity-context 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1053) hide show
  1. infinity_context-0.1.0.dist-info/METADATA +239 -0
  2. infinity_context-0.1.0.dist-info/RECORD +1053 -0
  3. infinity_context-0.1.0.dist-info/WHEEL +5 -0
  4. infinity_context-0.1.0.dist-info/entry_points.txt +9 -0
  5. infinity_context-0.1.0.dist-info/licenses/LICENSE +201 -0
  6. infinity_context-0.1.0.dist-info/top_level.txt +8 -0
  7. infinity_context_adapters/__init__.py +1 -0
  8. infinity_context_adapters/cognee/__init__.py +5 -0
  9. infinity_context_adapters/cognee/adapter.py +364 -0
  10. infinity_context_adapters/embeddings/__init__.py +6 -0
  11. infinity_context_adapters/embeddings/noop_adapter.py +5 -0
  12. infinity_context_adapters/embeddings/openai_adapter.py +104 -0
  13. infinity_context_adapters/extraction/__init__.py +41 -0
  14. infinity_context_adapters/extraction/content.py +828 -0
  15. infinity_context_adapters/extraction/docling_engine.py +255 -0
  16. infinity_context_adapters/extraction/document_normalization.py +434 -0
  17. infinity_context_adapters/extraction/factory.py +69 -0
  18. infinity_context_adapters/extraction/fallback_context.py +137 -0
  19. infinity_context_adapters/extraction/file_detection.py +378 -0
  20. infinity_context_adapters/extraction/image_evidence.py +319 -0
  21. infinity_context_adapters/extraction/media_tools.py +579 -0
  22. infinity_context_adapters/extraction/openai_json.py +356 -0
  23. infinity_context_adapters/extraction/openai_vision.py +722 -0
  24. infinity_context_adapters/extraction/prompts/__init__.py +1 -0
  25. infinity_context_adapters/extraction/prompts/semantic_memory_v1.md +20 -0
  26. infinity_context_adapters/extraction/schemas/__init__.py +1 -0
  27. infinity_context_adapters/extraction/schemas/semantic_memory_v1.json +62 -0
  28. infinity_context_adapters/extraction/transcription/__init__.py +11 -0
  29. infinity_context_adapters/extraction/transcription/openai_adapter.py +400 -0
  30. infinity_context_adapters/extraction/transcription_engine.py +544 -0
  31. infinity_context_adapters/extraction/video_evidence.py +129 -0
  32. infinity_context_adapters/extraction/whisper_engine.py +386 -0
  33. infinity_context_adapters/features/__init__.py +7 -0
  34. infinity_context_adapters/features/context_building/__init__.py +53 -0
  35. infinity_context_adapters/features/context_building/candidate_provider_chain.py +68 -0
  36. infinity_context_adapters/features/context_building/graphiti_candidate_provider.py +44 -0
  37. infinity_context_adapters/features/context_building/in_memory_candidate_provider.py +62 -0
  38. infinity_context_adapters/features/context_building/postgres_candidate_provider.py +44 -0
  39. infinity_context_adapters/features/context_building/qdrant_candidate_provider.py +44 -0
  40. infinity_context_adapters/features/context_building/query_request.py +99 -0
  41. infinity_context_adapters/features/context_building/records.py +113 -0
  42. infinity_context_adapters/features/document_ingestion/__init__.py +74 -0
  43. infinity_context_adapters/features/document_ingestion/composition.py +69 -0
  44. infinity_context_adapters/features/document_ingestion/extraction_adapter.py +114 -0
  45. infinity_context_adapters/features/document_ingestion/in_memory_chunk_index.py +67 -0
  46. infinity_context_adapters/features/document_ingestion/in_memory_document_store.py +176 -0
  47. infinity_context_adapters/features/document_ingestion/postgres_document_store.py +110 -0
  48. infinity_context_adapters/features/document_ingestion/qdrant_chunk_index.py +49 -0
  49. infinity_context_adapters/features/document_ingestion/records.py +88 -0
  50. infinity_context_adapters/features/memory_facts/__init__.py +53 -0
  51. infinity_context_adapters/features/memory_facts/graphiti_fact_projection.py +111 -0
  52. infinity_context_adapters/features/memory_facts/in_memory_fact_store.py +200 -0
  53. infinity_context_adapters/features/memory_facts/postgres_fact_store.py +126 -0
  54. infinity_context_adapters/features/memory_facts/qdrant_fact_projection.py +100 -0
  55. infinity_context_adapters/features/memory_scopes/__init__.py +44 -0
  56. infinity_context_adapters/features/memory_scopes/in_memory_scope_store.py +189 -0
  57. infinity_context_adapters/features/memory_scopes/postgres_scope_store.py +121 -0
  58. infinity_context_adapters/features/memory_scopes/records.py +86 -0
  59. infinity_context_adapters/graphiti/__init__.py +5 -0
  60. infinity_context_adapters/graphiti/adapter.py +613 -0
  61. infinity_context_adapters/graphiti/identity_evidence.py +711 -0
  62. infinity_context_adapters/graphiti/scope_identity.py +31 -0
  63. infinity_context_adapters/local_blob.py +101 -0
  64. infinity_context_adapters/noop/__init__.py +16 -0
  65. infinity_context_adapters/noop/adapters.py +156 -0
  66. infinity_context_adapters/noop/runtime.py +21 -0
  67. infinity_context_adapters/postgres/__init__.py +19 -0
  68. infinity_context_adapters/postgres/asset_mappers.py +348 -0
  69. infinity_context_adapters/postgres/asset_repositories.py +865 -0
  70. infinity_context_adapters/postgres/benchmark_projection_schema.py +241 -0
  71. infinity_context_adapters/postgres/benchmark_run_completion.py +645 -0
  72. infinity_context_adapters/postgres/benchmark_run_repositories.py +908 -0
  73. infinity_context_adapters/postgres/benchmark_writer_fence.py +139 -0
  74. infinity_context_adapters/postgres/canonical_keyword_trigram.py +31 -0
  75. infinity_context_adapters/postgres/canonical_retrieval_batching.py +388 -0
  76. infinity_context_adapters/postgres/fact_repositories.py +766 -0
  77. infinity_context_adapters/postgres/mappers.py +745 -0
  78. infinity_context_adapters/postgres/migrations/0001_core_facts.sql +95 -0
  79. infinity_context_adapters/postgres/migrations/0002_threads_documents_chunks.sql +97 -0
  80. infinity_context_adapters/postgres/migrations/0003_suggestions.sql +24 -0
  81. infinity_context_adapters/postgres/migrations/0004_service_tokens.sql +16 -0
  82. infinity_context_adapters/postgres/migrations/0005_data_classification_lite.sql +8 -0
  83. infinity_context_adapters/postgres/migrations/0006_service_token_usage_metadata.sql +5 -0
  84. infinity_context_adapters/postgres/migrations/0007_service_token_permissions.sql +2 -0
  85. infinity_context_adapters/postgres/migrations/0008_service_token_memory_scope.sql +2 -0
  86. infinity_context_adapters/postgres/migrations/0009_outbox_lifecycle_hardening.sql +15 -0
  87. infinity_context_adapters/postgres/migrations/0010_fact_taxonomy.sql +14 -0
  88. infinity_context_adapters/postgres/migrations/0011_asset_extractions.sql +66 -0
  89. infinity_context_adapters/postgres/migrations/0012_asset_extraction_execution_hardening.sql +20 -0
  90. infinity_context_adapters/postgres/migrations/0012_usage_governance.sql +33 -0
  91. infinity_context_adapters/postgres/migrations/0013_context_link_suggestions.sql +44 -0
  92. infinity_context_adapters/postgres/migrations/0014_memory_anchors.sql +26 -0
  93. infinity_context_adapters/postgres/migrations/0015_users_acl.sql +36 -0
  94. infinity_context_adapters/postgres/migrations/0016_managed_benchmark_runs.sql +27 -0
  95. infinity_context_adapters/postgres/migrations/0017_managed_benchmark_writer_fence.sql +104 -0
  96. infinity_context_adapters/postgres/migrations/0018_benchmark_projection_manifest.sql +51 -0
  97. infinity_context_adapters/postgres/migrations/0019_managed_benchmark_sealed_fence.sql +131 -0
  98. infinity_context_adapters/postgres/migrations/0020_benchmark_cleanup_completion.sql +219 -0
  99. infinity_context_adapters/postgres/migrations/0021_benchmark_unsealed_abort.sql +64 -0
  100. infinity_context_adapters/postgres/migrations/0022_canonical_keyword_trigram.sql +5 -0
  101. infinity_context_adapters/postgres/models.py +878 -0
  102. infinity_context_adapters/postgres/projection_fence.py +80 -0
  103. infinity_context_adapters/postgres/repositories.py +906 -0
  104. infinity_context_adapters/postgres/repository_helpers.py +115 -0
  105. infinity_context_adapters/postgres/scope_repositories.py +540 -0
  106. infinity_context_adapters/postgres/unit_of_work.py +785 -0
  107. infinity_context_adapters/postgres/usage_repositories.py +128 -0
  108. infinity_context_adapters/postgres/user_repositories.py +164 -0
  109. infinity_context_adapters/provider_errors.py +73 -0
  110. infinity_context_adapters/qdrant/__init__.py +5 -0
  111. infinity_context_adapters/qdrant/identity_evidence.py +504 -0
  112. infinity_context_adapters/qdrant/vector_adapter.py +718 -0
  113. infinity_context_adapters/s3_blob.py +242 -0
  114. infinity_context_cli/__init__.py +5 -0
  115. infinity_context_cli/__main__.py +6 -0
  116. infinity_context_cli/cli.py +964 -0
  117. infinity_context_cli/config.py +184 -0
  118. infinity_context_cli/doctor.py +397 -0
  119. infinity_context_cli/local_experience.py +250 -0
  120. infinity_context_cli/mcp_config.py +124 -0
  121. infinity_context_cli/onboarding.py +542 -0
  122. infinity_context_cli/runtime.py +179 -0
  123. infinity_context_contracts/__init__.py +16 -0
  124. infinity_context_contracts/_json.py +32 -0
  125. infinity_context_contracts/capabilities.py +67 -0
  126. infinity_context_contracts/common.py +54 -0
  127. infinity_context_contracts/features/__init__.py +12 -0
  128. infinity_context_contracts/features/context_building.py +248 -0
  129. infinity_context_contracts/features/document_ingestion.py +277 -0
  130. infinity_context_contracts/features/memory_facts.py +236 -0
  131. infinity_context_contracts/features/memory_scopes.py +329 -0
  132. infinity_context_contracts/health.py +23 -0
  133. infinity_context_core/__init__.py +76 -0
  134. infinity_context_core/agent_behavior_contract.py +72 -0
  135. infinity_context_core/application/__init__.py +580 -0
  136. infinity_context_core/application/anchor_creative_work_filters.py +148 -0
  137. infinity_context_core/application/anchor_event_extraction.py +1571 -0
  138. infinity_context_core/application/anchor_extraction.py +1144 -0
  139. infinity_context_core/application/anchor_identity_normalization.py +90 -0
  140. infinity_context_core/application/anchor_relation_projection.py +203 -0
  141. infinity_context_core/application/anchor_temporal_window.py +172 -0
  142. infinity_context_core/application/asset_extraction_mapping.py +236 -0
  143. infinity_context_core/application/asset_upload_policy.py +952 -0
  144. infinity_context_core/application/auto_apply.py +72 -0
  145. infinity_context_core/application/auto_memory.py +527 -0
  146. infinity_context_core/application/capture_policy.py +61 -0
  147. infinity_context_core/application/chunker.py +93 -0
  148. infinity_context_core/application/context_action_role_evidence.py +890 -0
  149. infinity_context_core/application/context_action_role_labels.py +176 -0
  150. infinity_context_core/application/context_action_roles.py +2365 -0
  151. infinity_context_core/application/context_action_subject_policy.py +71 -0
  152. infinity_context_core/application/context_activity_companion.py +120 -0
  153. infinity_context_core/application/context_activity_duration_rerank.py +118 -0
  154. infinity_context_core/application/context_activity_participant_grounding.py +87 -0
  155. infinity_context_core/application/context_aggregation_answer_slots.py +586 -0
  156. infinity_context_core/application/context_aggregation_evidence_text.py +409 -0
  157. infinity_context_core/application/context_aggregation_rerank.py +145 -0
  158. infinity_context_core/application/context_aggregation_subject.py +137 -0
  159. infinity_context_core/application/context_allergy_condition_inference.py +146 -0
  160. infinity_context_core/application/context_anchor_identity.py +157 -0
  161. infinity_context_core/application/context_anchor_intent_ranking.py +84 -0
  162. infinity_context_core/application/context_anchor_relations.py +67 -0
  163. infinity_context_core/application/context_anchors.py +393 -0
  164. infinity_context_core/application/context_answer_evidence_types.py +14 -0
  165. infinity_context_core/application/context_answer_unit_shapes.py +207 -0
  166. infinity_context_core/application/context_artifact_evidence.py +893 -0
  167. infinity_context_core/application/context_attribution_lexicon.py +254 -0
  168. infinity_context_core/application/context_awareness_cause_rerank.py +109 -0
  169. infinity_context_core/application/context_baby_birth_evidence.py +91 -0
  170. infinity_context_core/application/context_birdwatching_schedule_rerank.py +130 -0
  171. infinity_context_core/application/context_bm25_ranking.py +450 -0
  172. infinity_context_core/application/context_book_author_preference_exact_turns.py +110 -0
  173. infinity_context_core/application/context_building_legacy_candidate_adapter.py +530 -0
  174. infinity_context_core/application/context_canonical_fact_retrieval.py +409 -0
  175. infinity_context_core/application/context_causal_reason_rerank.py +145 -0
  176. infinity_context_core/application/context_choice_reason_rerank.py +109 -0
  177. infinity_context_core/application/context_collectors.py +931 -0
  178. infinity_context_core/application/context_commonality_rerank.py +428 -0
  179. infinity_context_core/application/context_community_membership_inference.py +139 -0
  180. infinity_context_core/application/context_competition_count_exact_turns.py +383 -0
  181. infinity_context_core/application/context_conflicting_evidence_rerank.py +282 -0
  182. infinity_context_core/application/context_conversation_counterparty.py +260 -0
  183. infinity_context_core/application/context_count_cardinality.py +164 -0
  184. infinity_context_core/application/context_creative_work_count_exact_turns.py +517 -0
  185. infinity_context_core/application/context_diagnostics.py +55 -0
  186. infinity_context_core/application/context_diagnostics_bundle_keys.py +433 -0
  187. infinity_context_core/application/context_diagnostics_bundle_projection.py +711 -0
  188. infinity_context_core/application/context_diagnostics_item_projection.py +410 -0
  189. infinity_context_core/application/context_diagnostics_primitives.py +131 -0
  190. infinity_context_core/application/context_diagnostics_ranking.py +143 -0
  191. infinity_context_core/application/context_diagnostics_signals.py +209 -0
  192. infinity_context_core/application/context_diagnostics_sources.py +82 -0
  193. infinity_context_core/application/context_dialogue_authority.py +382 -0
  194. infinity_context_core/application/context_distinct_set_evidence.py +994 -0
  195. infinity_context_core/application/context_distinct_set_selection.py +335 -0
  196. infinity_context_core/application/context_domain_rerank_apply.py +310 -0
  197. infinity_context_core/application/context_domain_rerank_signals.py +2391 -0
  198. infinity_context_core/application/context_domain_rerank_types.py +14 -0
  199. infinity_context_core/application/context_english_activity_displacement_answer_support.py +128 -0
  200. infinity_context_core/application/context_english_lifestyle_inference.py +122 -0
  201. infinity_context_core/application/context_english_lifestyle_inference_answer_support.py +291 -0
  202. infinity_context_core/application/context_english_temporal_dates.py +53 -0
  203. infinity_context_core/application/context_evidence_priority.py +262 -0
  204. infinity_context_core/application/context_evidence_reservation_safety.py +208 -0
  205. infinity_context_core/application/context_exercise_performance_rerank.py +71 -0
  206. infinity_context_core/application/context_food_inventory_exact_turns.py +583 -0
  207. infinity_context_core/application/context_frequency_rerank.py +109 -0
  208. infinity_context_core/application/context_future_plan_timing_rerank.py +110 -0
  209. infinity_context_core/application/context_generic_behavior_inference.py +348 -0
  210. infinity_context_core/application/context_geo_residence_aliases.py +149 -0
  211. infinity_context_core/application/context_hydration.py +329 -0
  212. infinity_context_core/application/context_identity_fact_rerank.py +362 -0
  213. infinity_context_core/application/context_identity_rerank.py +76 -0
  214. infinity_context_core/application/context_identity_terms.py +15 -0
  215. infinity_context_core/application/context_inference_evidence.py +484 -0
  216. infinity_context_core/application/context_inference_evidence_terms.py +744 -0
  217. infinity_context_core/application/context_internal_diagnostics.py +61 -0
  218. infinity_context_core/application/context_item_evidence.py +108 -0
  219. infinity_context_core/application/context_item_purchase_evidence.py +49 -0
  220. infinity_context_core/application/context_keyword_aggregation_prefilter.py +63 -0
  221. infinity_context_core/application/context_keyword_ranking.py +178 -0
  222. infinity_context_core/application/context_lexical.py +750 -0
  223. infinity_context_core/application/context_lifecycle_rerank.py +502 -0
  224. infinity_context_core/application/context_link_candidate_policy.py +864 -0
  225. infinity_context_core/application/context_link_expansion.py +727 -0
  226. infinity_context_core/application/context_link_expansion_items.py +441 -0
  227. infinity_context_core/application/context_link_policy.py +451 -0
  228. infinity_context_core/application/context_link_source_enrichment.py +120 -0
  229. infinity_context_core/application/context_media_time.py +368 -0
  230. infinity_context_core/application/context_museum_visit_policy.py +66 -0
  231. infinity_context_core/application/context_named_person_preference.py +313 -0
  232. infinity_context_core/application/context_national_park_inference.py +93 -0
  233. infinity_context_core/application/context_object_attribute_rerank.py +416 -0
  234. infinity_context_core/application/context_object_mismatch.py +63 -0
  235. infinity_context_core/application/context_occupational_role_identity.py +961 -0
  236. infinity_context_core/application/context_ordinal_sequence.py +134 -0
  237. infinity_context_core/application/context_owner_grounding.py +408 -0
  238. infinity_context_core/application/context_packer.py +858 -0
  239. infinity_context_core/application/context_packer_answer_slots.py +733 -0
  240. infinity_context_core/application/context_packer_answer_support.py +3101 -0
  241. infinity_context_core/application/context_packer_answer_support_patterns.py +1267 -0
  242. infinity_context_core/application/context_packer_answer_support_slots.py +1209 -0
  243. infinity_context_core/application/context_packer_answer_support_utils.py +473 -0
  244. infinity_context_core/application/context_packer_diagnostics.py +48 -0
  245. infinity_context_core/application/context_packer_exact_literal_turns.py +697 -0
  246. infinity_context_core/application/context_packer_exact_prepasses.py +758 -0
  247. infinity_context_core/application/context_packer_exact_turn_utils.py +133 -0
  248. infinity_context_core/application/context_packer_identity_policy.py +155 -0
  249. infinity_context_core/application/context_packer_inference_reservation.py +113 -0
  250. infinity_context_core/application/context_packer_inventory_prepasses.py +359 -0
  251. infinity_context_core/application/context_packer_inventory_slots.py +771 -0
  252. infinity_context_core/application/context_packer_marker_coverage.py +409 -0
  253. infinity_context_core/application/context_packer_rendering.py +338 -0
  254. infinity_context_core/application/context_packer_selection.py +487 -0
  255. infinity_context_core/application/context_packer_source_policy.py +123 -0
  256. infinity_context_core/application/context_paired_evidence_requirements.py +78 -0
  257. infinity_context_core/application/context_paired_evidence_roles.py +64 -0
  258. infinity_context_core/application/context_person_activity_exact_turns.py +375 -0
  259. infinity_context_core/application/context_person_aliases.py +49 -0
  260. infinity_context_core/application/context_person_kinship.py +296 -0
  261. infinity_context_core/application/context_person_occupation.py +240 -0
  262. infinity_context_core/application/context_person_relation_inventory.py +755 -0
  263. infinity_context_core/application/context_person_residence.py +398 -0
  264. infinity_context_core/application/context_person_team_membership.py +305 -0
  265. infinity_context_core/application/context_pet_ownership.py +223 -0
  266. infinity_context_core/application/context_polarity_rerank.py +338 -0
  267. infinity_context_core/application/context_policy.py +107 -0
  268. infinity_context_core/application/context_political_inference.py +128 -0
  269. infinity_context_core/application/context_possession_source.py +161 -0
  270. infinity_context_core/application/context_precise_temporal_evidence.py +156 -0
  271. infinity_context_core/application/context_preference_goal_rerank.py +428 -0
  272. infinity_context_core/application/context_preference_inference.py +335 -0
  273. infinity_context_core/application/context_property_interaction_evidence.py +659 -0
  274. infinity_context_core/application/context_provider_retrieval_terms.py +95 -0
  275. infinity_context_core/application/context_quality.py +488 -0
  276. infinity_context_core/application/context_quantity_evidence_patterns.py +254 -0
  277. infinity_context_core/application/context_quantity_evidence_slots.py +750 -0
  278. infinity_context_core/application/context_quantity_pending_clothing.py +200 -0
  279. infinity_context_core/application/context_query_artifact_inventory_expansions.py +76 -0
  280. infinity_context_core/application/context_query_decomposition.py +879 -0
  281. infinity_context_core/application/context_query_decomposition_answer_policy.py +707 -0
  282. infinity_context_core/application/context_query_decomposition_contracts.py +28 -0
  283. infinity_context_core/application/context_query_decomposition_event_policy.py +535 -0
  284. infinity_context_core/application/context_query_decomposition_inventory_policy.py +767 -0
  285. infinity_context_core/application/context_query_decomposition_shared.py +300 -0
  286. infinity_context_core/application/context_query_duration.py +171 -0
  287. infinity_context_core/application/context_query_entity_relation_expansions.py +212 -0
  288. infinity_context_core/application/context_query_event_summary_expansions.py +80 -0
  289. infinity_context_core/application/context_query_expansion.py +200 -0
  290. infinity_context_core/application/context_query_expansion_rule_catalog_part1.py +1279 -0
  291. infinity_context_core/application/context_query_expansion_rule_catalog_part2.py +770 -0
  292. infinity_context_core/application/context_query_expansion_rule_catalog_part3.py +982 -0
  293. infinity_context_core/application/context_query_expansion_rule_catalog_part4.py +1334 -0
  294. infinity_context_core/application/context_query_expansion_rule_catalog_part5.py +517 -0
  295. infinity_context_core/application/context_query_expansion_rule_terms.py +269 -0
  296. infinity_context_core/application/context_query_expansion_rules.py +66 -0
  297. infinity_context_core/application/context_query_expansion_selection.py +892 -0
  298. infinity_context_core/application/context_query_financial_resources_inference.py +92 -0
  299. infinity_context_core/application/context_query_food_expansions.py +89 -0
  300. infinity_context_core/application/context_query_frequency.py +194 -0
  301. infinity_context_core/application/context_query_game_expansions.py +141 -0
  302. infinity_context_core/application/context_query_health_lifestyle_expansions.py +178 -0
  303. infinity_context_core/application/context_query_identity_terms.py +81 -0
  304. infinity_context_core/application/context_query_intent.py +33 -0
  305. infinity_context_core/application/context_query_intent_common.py +29 -0
  306. infinity_context_core/application/context_query_intent_contracts.py +92 -0
  307. infinity_context_core/application/context_query_intent_extraction.py +614 -0
  308. infinity_context_core/application/context_query_intent_keys.py +499 -0
  309. infinity_context_core/application/context_query_intent_matching.py +407 -0
  310. infinity_context_core/application/context_query_organization_summary_expansions.py +75 -0
  311. infinity_context_core/application/context_query_personal_fact_expansions.py +314 -0
  312. infinity_context_core/application/context_query_pet_activity_expansions.py +329 -0
  313. infinity_context_core/application/context_query_preference_expansions.py +97 -0
  314. infinity_context_core/application/context_query_project_summary_expansions.py +60 -0
  315. infinity_context_core/application/context_query_relevance_ranking.py +76 -0
  316. infinity_context_core/application/context_query_sports_expansions.py +48 -0
  317. infinity_context_core/application/context_query_state_transition.py +53 -0
  318. infinity_context_core/application/context_query_support_role.py +165 -0
  319. infinity_context_core/application/context_query_temporal_expansions.py +18 -0
  320. infinity_context_core/application/context_query_travel_expansions.py +134 -0
  321. infinity_context_core/application/context_query_vehicle_expansions.py +79 -0
  322. infinity_context_core/application/context_query_workflow_expansions.py +233 -0
  323. infinity_context_core/application/context_query_workflow_intent.py +65 -0
  324. infinity_context_core/application/context_quote_scanner.py +342 -0
  325. infinity_context_core/application/context_rank_dedupe.py +171 -0
  326. infinity_context_core/application/context_rank_fusion.py +191 -0
  327. infinity_context_core/application/context_ranked_activity_reservation.py +586 -0
  328. infinity_context_core/application/context_ranked_evidence_coverage_reservation.py +427 -0
  329. infinity_context_core/application/context_ranked_evidence_priority_reservation.py +37 -0
  330. infinity_context_core/application/context_ranked_evidence_selection.py +260 -0
  331. infinity_context_core/application/context_ranking.py +3681 -0
  332. infinity_context_core/application/context_ranking_reason_policy.py +31 -0
  333. infinity_context_core/application/context_ranking_reason_priorities.py +859 -0
  334. infinity_context_core/application/context_ranking_reason_scores.py +519 -0
  335. infinity_context_core/application/context_recommendation_answer_support.py +358 -0
  336. infinity_context_core/application/context_recommendation_exact_turns.py +360 -0
  337. infinity_context_core/application/context_relation_requirement.py +498 -0
  338. infinity_context_core/application/context_relationship_status_evidence.py +83 -0
  339. infinity_context_core/application/context_relative_duration_evidence.py +61 -0
  340. infinity_context_core/application/context_relevance.py +430 -0
  341. infinity_context_core/application/context_reported_obligation_attribution.py +416 -0
  342. infinity_context_core/application/context_reporting_frames.py +338 -0
  343. infinity_context_core/application/context_requirement_coverage.py +767 -0
  344. infinity_context_core/application/context_requirement_guard.py +503 -0
  345. infinity_context_core/application/context_requirement_patterns.py +943 -0
  346. infinity_context_core/application/context_requirement_ranking.py +181 -0
  347. infinity_context_core/application/context_requirement_signals.py +101 -0
  348. infinity_context_core/application/context_rerank_domain_heuristics.py +433 -0
  349. infinity_context_core/application/context_rerank_evidence_signals.py +400 -0
  350. infinity_context_core/application/context_rerank_relevance.py +131 -0
  351. infinity_context_core/application/context_retrieval_performance.py +153 -0
  352. infinity_context_core/application/context_review_items.py +338 -0
  353. infinity_context_core/application/context_score_signal_rerank.py +21 -0
  354. infinity_context_core/application/context_short_story_progress_evidence.py +119 -0
  355. infinity_context_core/application/context_snippets.py +545 -0
  356. infinity_context_core/application/context_social_education_inference.py +265 -0
  357. infinity_context_core/application/context_social_inventory_rerank.py +329 -0
  358. infinity_context_core/application/context_source_family.py +100 -0
  359. infinity_context_core/application/context_source_grounding.py +277 -0
  360. infinity_context_core/application/context_source_sibling_answer_evidence_repair.py +1983 -0
  361. infinity_context_core/application/context_source_sibling_contracts.py +35 -0
  362. infinity_context_core/application/context_source_sibling_evidence.py +405 -0
  363. infinity_context_core/application/context_source_sibling_evidence_rules.py +798 -0
  364. infinity_context_core/application/context_source_sibling_evidence_shared.py +449 -0
  365. infinity_context_core/application/context_source_sibling_identity.py +114 -0
  366. infinity_context_core/application/context_source_sibling_inventory_evidence.py +28 -0
  367. infinity_context_core/application/context_source_sibling_patterns.py +946 -0
  368. infinity_context_core/application/context_source_sibling_place_evidence.py +561 -0
  369. infinity_context_core/application/context_source_sibling_policy.py +988 -0
  370. infinity_context_core/application/context_source_sibling_scoring.py +721 -0
  371. infinity_context_core/application/context_source_siblings.py +363 -0
  372. infinity_context_core/application/context_speaker_attribution.py +574 -0
  373. infinity_context_core/application/context_speaker_disambiguation.py +108 -0
  374. infinity_context_core/application/context_stage_diagnostics.py +111 -0
  375. infinity_context_core/application/context_state_evidence.py +196 -0
  376. infinity_context_core/application/context_state_residence_inference.py +163 -0
  377. infinity_context_core/application/context_state_transition_pairing.py +401 -0
  378. infinity_context_core/application/context_temporal_answer_grounding.py +187 -0
  379. infinity_context_core/application/context_temporal_hints.py +485 -0
  380. infinity_context_core/application/context_temporal_intent_policy.py +146 -0
  381. infinity_context_core/application/context_temporal_interval_requirements.py +107 -0
  382. infinity_context_core/application/context_temporal_metadata.py +29 -0
  383. infinity_context_core/application/context_temporal_query.py +1358 -0
  384. infinity_context_core/application/context_temporal_ranges.py +471 -0
  385. infinity_context_core/application/context_temporal_session_order.py +439 -0
  386. infinity_context_core/application/context_temporal_source_identity.py +426 -0
  387. infinity_context_core/application/context_temporal_source_turn.py +1290 -0
  388. infinity_context_core/application/context_temporal_source_turn_labels.py +265 -0
  389. infinity_context_core/application/context_temporal_source_turn_patterns.py +186 -0
  390. infinity_context_core/application/context_temporal_source_turn_types.py +62 -0
  391. infinity_context_core/application/context_travel_hobby_writing_evidence.py +176 -0
  392. infinity_context_core/application/context_travel_place_evidence.py +23 -0
  393. infinity_context_core/application/document_fragments.py +206 -0
  394. infinity_context_core/application/document_text.py +203 -0
  395. infinity_context_core/application/dto.py +311 -0
  396. infinity_context_core/application/dto_anchors.py +143 -0
  397. infinity_context_core/application/dto_assets.py +234 -0
  398. infinity_context_core/application/dto_benchmark_runs.py +114 -0
  399. infinity_context_core/application/dto_context.py +176 -0
  400. infinity_context_core/application/dto_context_links.py +170 -0
  401. infinity_context_core/application/dto_facts.py +275 -0
  402. infinity_context_core/application/dto_suggestions_capture.py +199 -0
  403. infinity_context_core/application/dto_workspace.py +101 -0
  404. infinity_context_core/application/extraction_coordinates.py +56 -0
  405. infinity_context_core/application/extraction_resource_policy.py +796 -0
  406. infinity_context_core/application/extractor.py +147 -0
  407. infinity_context_core/application/memory_digest_renderer.py +215 -0
  408. infinity_context_core/application/multimodal_manifest.py +418 -0
  409. infinity_context_core/application/normalize.py +29 -0
  410. infinity_context_core/application/observed_anchor_resolution.py +243 -0
  411. infinity_context_core/application/related_facts.py +144 -0
  412. infinity_context_core/application/review_payloads.py +104 -0
  413. infinity_context_core/application/safe_payload.py +86 -0
  414. infinity_context_core/application/semantic_dedupe.py +943 -0
  415. infinity_context_core/application/sensitive_text.py +39 -0
  416. infinity_context_core/application/source_refs.py +263 -0
  417. infinity_context_core/application/storage_key_safety.py +13 -0
  418. infinity_context_core/application/temporal_validity.py +31 -0
  419. infinity_context_core/application/use_cases/__init__.py +1 -0
  420. infinity_context_core/application/use_cases/anchors.py +1165 -0
  421. infinity_context_core/application/use_cases/asset_extraction_support.py +273 -0
  422. infinity_context_core/application/use_cases/asset_extractions.py +1454 -0
  423. infinity_context_core/application/use_cases/assets.py +463 -0
  424. infinity_context_core/application/use_cases/benchmark_runs.py +877 -0
  425. infinity_context_core/application/use_cases/benchmark_unsealed_abort.py +147 -0
  426. infinity_context_core/application/use_cases/blob_storage_cleanup.py +289 -0
  427. infinity_context_core/application/use_cases/blob_storage_integrity.py +351 -0
  428. infinity_context_core/application/use_cases/build_context.py +995 -0
  429. infinity_context_core/application/use_cases/build_context_final_packing.py +59 -0
  430. infinity_context_core/application/use_cases/build_context_item_projection.py +839 -0
  431. infinity_context_core/application/use_cases/build_context_keyword_aggregation.py +908 -0
  432. infinity_context_core/application/use_cases/build_context_keyword_aggregation_seeds.py +132 -0
  433. infinity_context_core/application/use_cases/build_context_keyword_aggregation_selection.py +280 -0
  434. infinity_context_core/application/use_cases/build_context_keyword_aggregation_support.py +161 -0
  435. infinity_context_core/application/use_cases/build_context_requirement_guard.py +156 -0
  436. infinity_context_core/application/use_cases/build_context_source_selection.py +935 -0
  437. infinity_context_core/application/use_cases/build_memory_digest.py +203 -0
  438. infinity_context_core/application/use_cases/build_memory_insights.py +979 -0
  439. infinity_context_core/application/use_cases/consolidate_capture.py +806 -0
  440. infinity_context_core/application/use_cases/context_link_crud.py +196 -0
  441. infinity_context_core/application/use_cases/context_link_reviews.py +441 -0
  442. infinity_context_core/application/use_cases/context_link_suggestions.py +1060 -0
  443. infinity_context_core/application/use_cases/context_link_visibility.py +106 -0
  444. infinity_context_core/application/use_cases/context_links.py +33 -0
  445. infinity_context_core/application/use_cases/delete_document.py +71 -0
  446. infinity_context_core/application/use_cases/delete_thread_memory.py +55 -0
  447. infinity_context_core/application/use_cases/duplicate_merge_resolution.py +307 -0
  448. infinity_context_core/application/use_cases/ensure_scope.py +29 -0
  449. infinity_context_core/application/use_cases/expire_suggestions.py +36 -0
  450. infinity_context_core/application/use_cases/export_graph.py +684 -0
  451. infinity_context_core/application/use_cases/fact_relations.py +204 -0
  452. infinity_context_core/application/use_cases/forget_fact.py +43 -0
  453. infinity_context_core/application/use_cases/get_capabilities.py +213 -0
  454. infinity_context_core/application/use_cases/get_capture.py +16 -0
  455. infinity_context_core/application/use_cases/get_session_status.py +23 -0
  456. infinity_context_core/application/use_cases/ingest_document.py +376 -0
  457. infinity_context_core/application/use_cases/ingest_episode.py +263 -0
  458. infinity_context_core/application/use_cases/list_captures.py +25 -0
  459. infinity_context_core/application/use_cases/memory_browser.py +414 -0
  460. infinity_context_core/application/use_cases/operations_console.py +293 -0
  461. infinity_context_core/application/use_cases/process_document.py +83 -0
  462. infinity_context_core/application/use_cases/purge_capture.py +34 -0
  463. infinity_context_core/application/use_cases/query_documents.py +42 -0
  464. infinity_context_core/application/use_cases/query_facts.py +57 -0
  465. infinity_context_core/application/use_cases/receive_capture.py +238 -0
  466. infinity_context_core/application/use_cases/related_facts.py +47 -0
  467. infinity_context_core/application/use_cases/remember_fact.py +145 -0
  468. infinity_context_core/application/use_cases/spaces_memory_scopes.py +134 -0
  469. infinity_context_core/application/use_cases/suggestions.py +804 -0
  470. infinity_context_core/application/use_cases/update_fact.py +43 -0
  471. infinity_context_core/application/use_cases/usage.py +49 -0
  472. infinity_context_core/application/use_cases/users.py +138 -0
  473. infinity_context_core/domain/__init__.py +1 -0
  474. infinity_context_core/domain/aggregation_admission.py +200 -0
  475. infinity_context_core/domain/anchor_entities.py +316 -0
  476. infinity_context_core/domain/assets.py +654 -0
  477. infinity_context_core/domain/capture.py +321 -0
  478. infinity_context_core/domain/distinct_set_membership.py +69 -0
  479. infinity_context_core/domain/entities.py +103 -0
  480. infinity_context_core/domain/entity_policies.py +119 -0
  481. infinity_context_core/domain/entity_types.py +150 -0
  482. infinity_context_core/domain/errors.py +53 -0
  483. infinity_context_core/domain/events.py +14 -0
  484. infinity_context_core/domain/extraction.py +529 -0
  485. infinity_context_core/domain/idempotency.py +12 -0
  486. infinity_context_core/domain/memory_item_entities.py +426 -0
  487. infinity_context_core/domain/source_refs.py +91 -0
  488. infinity_context_core/domain/suggestion_entities.py +236 -0
  489. infinity_context_core/domain/taxonomy.py +121 -0
  490. infinity_context_core/domain/usage.py +221 -0
  491. infinity_context_core/domain/workspace_entities.py +269 -0
  492. infinity_context_core/features/__init__.py +7 -0
  493. infinity_context_core/features/context_building/__init__.py +3 -0
  494. infinity_context_core/features/context_building/application/__init__.py +92 -0
  495. infinity_context_core/features/context_building/application/candidate_fusion.py +136 -0
  496. infinity_context_core/features/context_building/application/coverage_reservation_selector.py +212 -0
  497. infinity_context_core/features/context_building/application/handlers.py +155 -0
  498. infinity_context_core/features/context_building/application/inference_evidence_reservation.py +592 -0
  499. infinity_context_core/features/context_building/application/provider_pipeline.py +61 -0
  500. infinity_context_core/features/context_building/application/queries.py +107 -0
  501. infinity_context_core/features/context_building/application/use_cases.py +78 -0
  502. infinity_context_core/features/context_building/domain/__init__.py +95 -0
  503. infinity_context_core/features/context_building/domain/budget.py +97 -0
  504. infinity_context_core/features/context_building/domain/context.py +157 -0
  505. infinity_context_core/features/context_building/domain/evidence_obligations.py +60 -0
  506. infinity_context_core/features/context_building/domain/feature.py +15 -0
  507. infinity_context_core/features/context_building/domain/prompt_sections.py +150 -0
  508. infinity_context_core/features/context_building/domain/query_pipeline.py +276 -0
  509. infinity_context_core/features/context_building/domain/rendering.py +102 -0
  510. infinity_context_core/features/context_building/ports/__init__.py +8 -0
  511. infinity_context_core/features/context_building/ports/candidates.py +36 -0
  512. infinity_context_core/features/context_building/public.py +165 -0
  513. infinity_context_core/features/context_building/tests/test_application_ports_public_skeleton.py +575 -0
  514. infinity_context_core/features/context_building/tests/test_domain_context_building_skeleton.py +402 -0
  515. infinity_context_core/features/document_ingestion/__init__.py +3 -0
  516. infinity_context_core/features/document_ingestion/application/__init__.py +31 -0
  517. infinity_context_core/features/document_ingestion/application/commands.py +62 -0
  518. infinity_context_core/features/document_ingestion/application/use_cases.py +273 -0
  519. infinity_context_core/features/document_ingestion/domain/__init__.py +61 -0
  520. infinity_context_core/features/document_ingestion/domain/chunking.py +297 -0
  521. infinity_context_core/features/document_ingestion/domain/errors.py +22 -0
  522. infinity_context_core/features/document_ingestion/domain/feature.py +18 -0
  523. infinity_context_core/features/document_ingestion/domain/source_document.py +228 -0
  524. infinity_context_core/features/document_ingestion/ports/__init__.py +21 -0
  525. infinity_context_core/features/document_ingestion/ports/indexing.py +51 -0
  526. infinity_context_core/features/document_ingestion/ports/repositories.py +56 -0
  527. infinity_context_core/features/document_ingestion/public.py +95 -0
  528. infinity_context_core/features/document_ingestion/tests/test_application_handlers.py +226 -0
  529. infinity_context_core/features/document_ingestion/tests/test_application_ports_public_skeleton.py +334 -0
  530. infinity_context_core/features/document_ingestion/tests/test_domain_chunking_skeleton.py +79 -0
  531. infinity_context_core/features/document_ingestion/tests/test_domain_source_document_skeleton.py +101 -0
  532. infinity_context_core/features/memory_facts/__init__.py +3 -0
  533. infinity_context_core/features/memory_facts/application/__init__.py +37 -0
  534. infinity_context_core/features/memory_facts/application/commands.py +89 -0
  535. infinity_context_core/features/memory_facts/application/handlers.py +241 -0
  536. infinity_context_core/features/memory_facts/application/use_cases.py +47 -0
  537. infinity_context_core/features/memory_facts/domain/__init__.py +35 -0
  538. infinity_context_core/features/memory_facts/domain/fact.py +98 -0
  539. infinity_context_core/features/memory_facts/domain/feature.py +15 -0
  540. infinity_context_core/features/memory_facts/ports/__init__.py +31 -0
  541. infinity_context_core/features/memory_facts/ports/clock.py +14 -0
  542. infinity_context_core/features/memory_facts/ports/ids.py +19 -0
  543. infinity_context_core/features/memory_facts/ports/outbox.py +26 -0
  544. infinity_context_core/features/memory_facts/ports/repositories.py +30 -0
  545. infinity_context_core/features/memory_facts/ports/unit_of_work.py +43 -0
  546. infinity_context_core/features/memory_facts/public.py +79 -0
  547. infinity_context_core/features/memory_facts/tests/test_application_handlers.py +332 -0
  548. infinity_context_core/features/memory_facts/tests/test_application_ports_public_skeleton.py +318 -0
  549. infinity_context_core/features/memory_facts/tests/test_domain_fact_skeleton.py +222 -0
  550. infinity_context_core/features/memory_scopes/__init__.py +3 -0
  551. infinity_context_core/features/memory_scopes/application/__init__.py +55 -0
  552. infinity_context_core/features/memory_scopes/application/commands.py +102 -0
  553. infinity_context_core/features/memory_scopes/application/errors.py +27 -0
  554. infinity_context_core/features/memory_scopes/application/handlers.py +226 -0
  555. infinity_context_core/features/memory_scopes/application/use_cases.py +68 -0
  556. infinity_context_core/features/memory_scopes/domain/__init__.py +59 -0
  557. infinity_context_core/features/memory_scopes/domain/errors.py +22 -0
  558. infinity_context_core/features/memory_scopes/domain/feature.py +18 -0
  559. infinity_context_core/features/memory_scopes/domain/policies.py +197 -0
  560. infinity_context_core/features/memory_scopes/domain/scope.py +178 -0
  561. infinity_context_core/features/memory_scopes/ports/__init__.py +25 -0
  562. infinity_context_core/features/memory_scopes/ports/clock.py +14 -0
  563. infinity_context_core/features/memory_scopes/ports/ids.py +13 -0
  564. infinity_context_core/features/memory_scopes/ports/repositories.py +40 -0
  565. infinity_context_core/features/memory_scopes/ports/unit_of_work.py +39 -0
  566. infinity_context_core/features/memory_scopes/public.py +111 -0
  567. infinity_context_core/features/memory_scopes/tests/test_application_ports_public_skeleton.py +824 -0
  568. infinity_context_core/features/memory_scopes/tests/test_domain_scope_skeleton.py +428 -0
  569. infinity_context_core/memory_scope_snapshot_preview.py +863 -0
  570. infinity_context_core/memory_scope_snapshots.py +206 -0
  571. infinity_context_core/ports/__init__.py +164 -0
  572. infinity_context_core/ports/adapters.py +217 -0
  573. infinity_context_core/ports/assets.py +270 -0
  574. infinity_context_core/ports/auth.py +68 -0
  575. infinity_context_core/ports/auto_memory.py +71 -0
  576. infinity_context_core/ports/benchmark_runs.py +205 -0
  577. infinity_context_core/ports/capabilities.py +324 -0
  578. infinity_context_core/ports/captures.py +53 -0
  579. infinity_context_core/ports/clock.py +11 -0
  580. infinity_context_core/ports/derived_projection_policy.py +72 -0
  581. infinity_context_core/ports/extraction.py +201 -0
  582. infinity_context_core/ports/graph_evidence.py +170 -0
  583. infinity_context_core/ports/ids.py +11 -0
  584. infinity_context_core/ports/projection_fence.py +27 -0
  585. infinity_context_core/ports/repositories.py +563 -0
  586. infinity_context_core/ports/transcription.py +63 -0
  587. infinity_context_core/ports/unit_of_work.py +77 -0
  588. infinity_context_core/ports/usage.py +36 -0
  589. infinity_context_core/ports/vector_projection_evidence.py +230 -0
  590. infinity_context_core/ports/vision.py +36 -0
  591. infinity_context_core/processes/__init__.py +3 -0
  592. infinity_context_core/processes/document_memory_pipeline/__init__.py +3 -0
  593. infinity_context_core/processes/document_memory_pipeline/contracts.py +44 -0
  594. infinity_context_core/processes/document_memory_pipeline/process.py +72 -0
  595. infinity_context_core/processes/document_memory_pipeline/public.py +21 -0
  596. infinity_context_core/reporting.py +105 -0
  597. infinity_context_mcp/__init__.py +5 -0
  598. infinity_context_mcp/__main__.py +6 -0
  599. infinity_context_mcp/adapters/__init__.py +1 -0
  600. infinity_context_mcp/adapters/http_gateway.py +940 -0
  601. infinity_context_mcp/agent_behavior_bench.py +1731 -0
  602. infinity_context_mcp/agent_behavior_bench_metrics.py +419 -0
  603. infinity_context_mcp/agent_behavior_bench_redaction.py +149 -0
  604. infinity_context_mcp/agent_behavior_bench_types.py +127 -0
  605. infinity_context_mcp/agent_behavior_scenarios.py +35 -0
  606. infinity_context_mcp/agent_behavior_scenarios_core.py +484 -0
  607. infinity_context_mcp/agent_behavior_scenarios_realistic.py +473 -0
  608. infinity_context_mcp/agent_behavior_scenarios_transcript.py +196 -0
  609. infinity_context_mcp/agent_behavior_transcripts.py +231 -0
  610. infinity_context_mcp/agent_behavior_types.py +66 -0
  611. infinity_context_mcp/agent_behavior_utils.py +29 -0
  612. infinity_context_mcp/application/__init__.py +1 -0
  613. infinity_context_mcp/application/context_link_review.py +109 -0
  614. infinity_context_mcp/application/local_runtime.py +473 -0
  615. infinity_context_mcp/application/normalization.py +36 -0
  616. infinity_context_mcp/application/obsidian.py +568 -0
  617. infinity_context_mcp/application/policy.py +160 -0
  618. infinity_context_mcp/application/ports.py +324 -0
  619. infinity_context_mcp/application/prepare.py +254 -0
  620. infinity_context_mcp/application/readiness.py +71 -0
  621. infinity_context_mcp/application/review_batch.py +62 -0
  622. infinity_context_mcp/application/service.py +701 -0
  623. infinity_context_mcp/application/service_base.py +29 -0
  624. infinity_context_mcp/application/service_constants.py +89 -0
  625. infinity_context_mcp/application/service_context_links.py +224 -0
  626. infinity_context_mcp/application/service_duplicates.py +62 -0
  627. infinity_context_mcp/application/service_facts.py +461 -0
  628. infinity_context_mcp/application/service_helpers.py +179 -0
  629. infinity_context_mcp/application/service_lifecycle.py +348 -0
  630. infinity_context_mcp/application/service_policy.py +74 -0
  631. infinity_context_mcp/application/service_query.py +384 -0
  632. infinity_context_mcp/application/service_resources.py +192 -0
  633. infinity_context_mcp/application/service_response.py +95 -0
  634. infinity_context_mcp/application/service_scope.py +132 -0
  635. infinity_context_mcp/application/service_suggestions.py +820 -0
  636. infinity_context_mcp/application/suggest_batch.py +71 -0
  637. infinity_context_mcp/application/usage_guide.py +74 -0
  638. infinity_context_mcp/bench.py +279 -0
  639. infinity_context_mcp/config.py +213 -0
  640. infinity_context_mcp/domain/__init__.py +1 -0
  641. infinity_context_mcp/domain/context_links.py +117 -0
  642. infinity_context_mcp/domain/local_runtime_models.py +56 -0
  643. infinity_context_mcp/domain/memory_browser.py +35 -0
  644. infinity_context_mcp/domain/models.py +1036 -0
  645. infinity_context_mcp/domain/obsidian_models.py +67 -0
  646. infinity_context_mcp/domain/policy.py +112 -0
  647. infinity_context_mcp/domain/prepare_models.py +24 -0
  648. infinity_context_mcp/plugin_hook.py +873 -0
  649. infinity_context_mcp/plugin_hook_settings.py +253 -0
  650. infinity_context_mcp/server.py +133 -0
  651. infinity_context_mcp/server_context_tools.py +404 -0
  652. infinity_context_mcp/server_fact_tools.py +928 -0
  653. infinity_context_mcp/server_hardening.py +54 -0
  654. infinity_context_mcp/server_local_runtime_tools.py +164 -0
  655. infinity_context_mcp/server_obsidian_tools.py +380 -0
  656. infinity_context_mcp/server_query_tools.py +626 -0
  657. infinity_context_mcp/server_request_mapping.py +73 -0
  658. infinity_context_mcp/server_resources.py +187 -0
  659. infinity_context_mcp/server_response.py +26 -0
  660. infinity_context_obsidian/__init__.py +5 -0
  661. infinity_context_obsidian/cli.py +594 -0
  662. infinity_context_obsidian/conflicts.py +110 -0
  663. infinity_context_obsidian/doctor.py +319 -0
  664. infinity_context_obsidian/domain.py +92 -0
  665. infinity_context_obsidian/frontmatter.py +68 -0
  666. infinity_context_obsidian/gateway.py +66 -0
  667. infinity_context_obsidian/layout.py +179 -0
  668. infinity_context_obsidian/note_format.py +159 -0
  669. infinity_context_obsidian/plugin_bundle/__init__.py +1 -0
  670. infinity_context_obsidian/plugin_bundle/main.js +5 -0
  671. infinity_context_obsidian/plugin_bundle/manifest.json +10 -0
  672. infinity_context_obsidian/plugin_bundle/styles.css +123 -0
  673. infinity_context_obsidian/plugin_install.py +139 -0
  674. infinity_context_obsidian/ports.py +72 -0
  675. infinity_context_obsidian/setup.py +202 -0
  676. infinity_context_obsidian/state.py +85 -0
  677. infinity_context_obsidian/sync.py +741 -0
  678. infinity_context_obsidian/vault.py +61 -0
  679. infinity_context_sdk/__init__.py +1049 -0
  680. infinity_context_sdk/_payloads.py +401 -0
  681. infinity_context_sdk/_redaction.py +31 -0
  682. infinity_context_sdk/anchors.py +249 -0
  683. infinity_context_sdk/assets.py +554 -0
  684. infinity_context_sdk/capabilities.py +199 -0
  685. infinity_context_sdk/context.py +540 -0
  686. infinity_context_sdk/context_bundle_diagnostics.py +905 -0
  687. infinity_context_sdk/context_payload_utils.py +219 -0
  688. infinity_context_sdk/export.py +123 -0
  689. infinity_context_sdk/scopes.py +74 -0
  690. infinity_context_sdk/thread_memory.py +55 -0
  691. infinity_context_server/__init__.py +5 -0
  692. infinity_context_server/__main__.py +19 -0
  693. infinity_context_server/admin.py +832 -0
  694. infinity_context_server/admin_invariants.py +388 -0
  695. infinity_context_server/admin_outbox.py +130 -0
  696. infinity_context_server/admin_projection_repair.py +374 -0
  697. infinity_context_server/api/__init__.py +1 -0
  698. infinity_context_server/api/auth.py +432 -0
  699. infinity_context_server/api/dependencies.py +9 -0
  700. infinity_context_server/api/errors.py +94 -0
  701. infinity_context_server/api/legacy_client.py +356 -0
  702. infinity_context_server/api/policy.py +35 -0
  703. infinity_context_server/api/public_payload.py +27 -0
  704. infinity_context_server/api/v1/__init__.py +54 -0
  705. infinity_context_server/api/v1/anchors.py +469 -0
  706. infinity_context_server/api/v1/assets.py +409 -0
  707. infinity_context_server/api/v1/capabilities.py +144 -0
  708. infinity_context_server/api/v1/captures.py +329 -0
  709. infinity_context_server/api/v1/context.py +362 -0
  710. infinity_context_server/api/v1/context_links.py +611 -0
  711. infinity_context_server/api/v1/diagnostics.py +333 -0
  712. infinity_context_server/api/v1/digest.py +107 -0
  713. infinity_context_server/api/v1/documents.py +164 -0
  714. infinity_context_server/api/v1/episodes.py +146 -0
  715. infinity_context_server/api/v1/export.py +184 -0
  716. infinity_context_server/api/v1/facts.py +265 -0
  717. infinity_context_server/api/v1/health.py +24 -0
  718. infinity_context_server/api/v1/insights.py +100 -0
  719. infinity_context_server/api/v1/internal_memory_comparison_runs.py +406 -0
  720. infinity_context_server/api/v1/memory_browser.py +183 -0
  721. infinity_context_server/api/v1/operations.py +81 -0
  722. infinity_context_server/api/v1/scope_resolution.py +389 -0
  723. infinity_context_server/api/v1/source_refs.py +111 -0
  724. infinity_context_server/api/v1/spaces_memory_scopes.py +141 -0
  725. infinity_context_server/api/v1/suggestions.py +237 -0
  726. infinity_context_server/api/v1/thread_memory.py +74 -0
  727. infinity_context_server/api/v1/usage.py +75 -0
  728. infinity_context_server/api/v1/users.py +164 -0
  729. infinity_context_server/auth_scope.py +492 -0
  730. infinity_context_server/auth_tokens.py +283 -0
  731. infinity_context_server/backpressure.py +47 -0
  732. infinity_context_server/benchmark_projection_absence.py +155 -0
  733. infinity_context_server/benchmark_projection_delete_outbox.py +162 -0
  734. infinity_context_server/composition.py +897 -0
  735. infinity_context_server/config.py +223 -0
  736. infinity_context_server/db.py +39 -0
  737. infinity_context_server/deployment_readiness.py +205 -0
  738. infinity_context_server/derived_identity_evidence.py +987 -0
  739. infinity_context_server/derived_identity_target.py +39 -0
  740. infinity_context_server/derived_projection_policy.py +85 -0
  741. infinity_context_server/diagnostics.py +536 -0
  742. infinity_context_server/doctor.py +71 -0
  743. infinity_context_server/eval.py +3066 -0
  744. infinity_context_server/eval_auto_memory.py +1735 -0
  745. infinity_context_server/eval_auto_memory_metrics.py +224 -0
  746. infinity_context_server/eval_auto_memory_types.py +75 -0
  747. infinity_context_server/eval_case_catalog.py +1590 -0
  748. infinity_context_server/eval_case_runner.py +1100 -0
  749. infinity_context_server/eval_common.py +336 -0
  750. infinity_context_server/eval_constants.py +310 -0
  751. infinity_context_server/eval_fixtures.py +150 -0
  752. infinity_context_server/eval_graph.py +92 -0
  753. infinity_context_server/eval_hybrid.py +99 -0
  754. infinity_context_server/eval_multimodal_offline.py +697 -0
  755. infinity_context_server/eval_prompt_contract.py +402 -0
  756. infinity_context_server/eval_scorecard.py +2627 -0
  757. infinity_context_server/eval_semantic_linking.py +1526 -0
  758. infinity_context_server/eval_semantic_retrieval_api_canary.py +405 -0
  759. infinity_context_server/eval_semantic_retrieval_canary.py +359 -0
  760. infinity_context_server/eval_types.py +88 -0
  761. infinity_context_server/evidence_bundle.py +523 -0
  762. infinity_context_server/extraction_capabilities.py +1234 -0
  763. infinity_context_server/features/__init__.py +3 -0
  764. infinity_context_server/features/context_building/__init__.py +5 -0
  765. infinity_context_server/features/context_building/benchmark_budget.py +29 -0
  766. infinity_context_server/features/context_building/composition.py +49 -0
  767. infinity_context_server/features/context_building/context_requests.py +151 -0
  768. infinity_context_server/features/context_building/contracts.py +96 -0
  769. infinity_context_server/features/context_building/digest_api_responses.py +225 -0
  770. infinity_context_server/features/context_building/digest_requests.py +65 -0
  771. infinity_context_server/features/context_building/insights_api_responses.py +132 -0
  772. infinity_context_server/features/context_building/legacy_api_responses.py +875 -0
  773. infinity_context_server/features/context_building/mappers.py +205 -0
  774. infinity_context_server/features/context_building/public.py +66 -0
  775. infinity_context_server/features/context_building/routes.py +44 -0
  776. infinity_context_server/features/document_ingestion/__init__.py +5 -0
  777. infinity_context_server/features/document_ingestion/asset_requests.py +45 -0
  778. infinity_context_server/features/document_ingestion/asset_responses.py +365 -0
  779. infinity_context_server/features/document_ingestion/composition.py +49 -0
  780. infinity_context_server/features/document_ingestion/contracts.py +104 -0
  781. infinity_context_server/features/document_ingestion/mappers.py +386 -0
  782. infinity_context_server/features/document_ingestion/public.py +58 -0
  783. infinity_context_server/features/document_ingestion/routes.py +44 -0
  784. infinity_context_server/features/memory_facts/__init__.py +7 -0
  785. infinity_context_server/features/memory_facts/compatibility.py +204 -0
  786. infinity_context_server/features/memory_facts/composition.py +72 -0
  787. infinity_context_server/features/memory_facts/contracts.py +187 -0
  788. infinity_context_server/features/memory_facts/mappers.py +620 -0
  789. infinity_context_server/features/memory_facts/public.py +168 -0
  790. infinity_context_server/features/memory_facts/routes.py +118 -0
  791. infinity_context_server/features/memory_facts/suggestion_requests.py +283 -0
  792. infinity_context_server/features/memory_facts/suggestion_responses.py +382 -0
  793. infinity_context_server/features/memory_scopes/__init__.py +5 -0
  794. infinity_context_server/features/memory_scopes/compatibility.py +231 -0
  795. infinity_context_server/features/memory_scopes/composition.py +49 -0
  796. infinity_context_server/features/memory_scopes/contracts.py +103 -0
  797. infinity_context_server/features/memory_scopes/graph_export_responses.py +93 -0
  798. infinity_context_server/features/memory_scopes/mappers.py +290 -0
  799. infinity_context_server/features/memory_scopes/public.py +136 -0
  800. infinity_context_server/features/memory_scopes/routes.py +185 -0
  801. infinity_context_server/features/memory_scopes/snapshot_compatibility.py +146 -0
  802. infinity_context_server/features/memory_scopes/thread_memory_requests.py +127 -0
  803. infinity_context_server/llm_provider_readiness.py +267 -0
  804. infinity_context_server/longmemeval_session_identity.py +167 -0
  805. infinity_context_server/main.py +68 -0
  806. infinity_context_server/mem0_benchmark_auth_challenge.py +160 -0
  807. infinity_context_server/memora_comparison.py +658 -0
  808. infinity_context_server/memory_comparison_answer_context.py +2442 -0
  809. infinity_context_server/memory_comparison_answer_context_backfill.py +1027 -0
  810. infinity_context_server/memory_comparison_answer_context_diagnostics.py +694 -0
  811. infinity_context_server/memory_comparison_answer_context_inspection.py +103 -0
  812. infinity_context_server/memory_comparison_answer_context_retrieval_slice.py +175 -0
  813. infinity_context_server/memory_comparison_answer_context_risks.py +223 -0
  814. infinity_context_server/memory_comparison_backend_target.py +41 -0
  815. infinity_context_server/memory_comparison_benchmark.py +651 -0
  816. infinity_context_server/memory_comparison_benchmark_compact.py +387 -0
  817. infinity_context_server/memory_comparison_benchmark_compact_fast_gate.py +387 -0
  818. infinity_context_server/memory_comparison_benchmark_compact_samples.py +931 -0
  819. infinity_context_server/memory_comparison_benchmark_config.py +105 -0
  820. infinity_context_server/memory_comparison_benchmark_control.py +215 -0
  821. infinity_context_server/memory_comparison_benchmark_execution.py +238 -0
  822. infinity_context_server/memory_comparison_benchmark_identity.py +65 -0
  823. infinity_context_server/memory_comparison_benchmark_metrics.py +686 -0
  824. infinity_context_server/memory_comparison_benchmark_replay.py +400 -0
  825. infinity_context_server/memory_comparison_benchmark_shared.py +284 -0
  826. infinity_context_server/memory_comparison_bounded_provider.py +295 -0
  827. infinity_context_server/memory_comparison_bundle_partial_support.py +107 -0
  828. infinity_context_server/memory_comparison_bundle_planner.py +3521 -0
  829. infinity_context_server/memory_comparison_bundle_source_windows.py +247 -0
  830. infinity_context_server/memory_comparison_candidate_features.py +2014 -0
  831. infinity_context_server/memory_comparison_candidate_fusion.py +893 -0
  832. infinity_context_server/memory_comparison_candidate_risks.py +124 -0
  833. infinity_context_server/memory_comparison_canonical_source_hash.py +109 -0
  834. infinity_context_server/memory_comparison_case_identity.py +100 -0
  835. infinity_context_server/memory_comparison_case_loader.py +123 -0
  836. infinity_context_server/memory_comparison_chat_completions.py +402 -0
  837. infinity_context_server/memory_comparison_clean_state.py +607 -0
  838. infinity_context_server/memory_comparison_clean_state_http.py +120 -0
  839. infinity_context_server/memory_comparison_clean_state_runner.py +103 -0
  840. infinity_context_server/memory_comparison_cli.py +295 -0
  841. infinity_context_server/memory_comparison_codex_canary.py +315 -0
  842. infinity_context_server/memory_comparison_codex_llm.py +345 -0
  843. infinity_context_server/memory_comparison_compact_gap_report.py +289 -0
  844. infinity_context_server/memory_comparison_comparative_preference.py +71 -0
  845. infinity_context_server/memory_comparison_conversation_ingestion.py +198 -0
  846. infinity_context_server/memory_comparison_evidence.py +877 -0
  847. infinity_context_server/memory_comparison_evidence_commitment.py +20 -0
  848. infinity_context_server/memory_comparison_failure_diagnostics.py +809 -0
  849. infinity_context_server/memory_comparison_full_canonical_evidence.py +746 -0
  850. infinity_context_server/memory_comparison_full_canonical_source_evidence_trust.py +450 -0
  851. infinity_context_server/memory_comparison_full_delete_evidence.py +670 -0
  852. infinity_context_server/memory_comparison_full_delete_evidence_trust.py +441 -0
  853. infinity_context_server/memory_comparison_full_delete_evidence_witnesses.py +308 -0
  854. infinity_context_server/memory_comparison_full_execution_validation.py +697 -0
  855. infinity_context_server/memory_comparison_full_execution_validation_slots.py +655 -0
  856. infinity_context_server/memory_comparison_full_methodology.py +556 -0
  857. infinity_context_server/memory_comparison_full_policy_component_validation.py +899 -0
  858. infinity_context_server/memory_comparison_full_profiles.py +398 -0
  859. infinity_context_server/memory_comparison_full_run_component_sets.py +304 -0
  860. infinity_context_server/memory_comparison_full_run_components.py +787 -0
  861. infinity_context_server/memory_comparison_full_run_evidence.py +759 -0
  862. infinity_context_server/memory_comparison_full_scope.py +244 -0
  863. infinity_context_server/memory_comparison_full_source_evidence.py +866 -0
  864. infinity_context_server/memory_comparison_full_verdict.py +252 -0
  865. infinity_context_server/memory_comparison_gold_blind.py +654 -0
  866. infinity_context_server/memory_comparison_gold_blind_answer_contract.py +492 -0
  867. infinity_context_server/memory_comparison_gold_blind_contract.py +760 -0
  868. infinity_context_server/memory_comparison_gold_blind_dispatch_models.py +34 -0
  869. infinity_context_server/memory_comparison_gold_blind_execution_validation.py +170 -0
  870. infinity_context_server/memory_comparison_gold_blind_judge_binding.py +167 -0
  871. infinity_context_server/memory_comparison_gold_blind_judge_capability.py +387 -0
  872. infinity_context_server/memory_comparison_gold_blind_retrieval_contract.py +413 -0
  873. infinity_context_server/memory_comparison_gold_blind_run_proof.py +880 -0
  874. infinity_context_server/memory_comparison_gold_blind_run_validation.py +109 -0
  875. infinity_context_server/memory_comparison_gold_blind_validation.py +319 -0
  876. infinity_context_server/memory_comparison_http.py +921 -0
  877. infinity_context_server/memory_comparison_http_ingest_observation.py +710 -0
  878. infinity_context_server/memory_comparison_http_ingest_request.py +204 -0
  879. infinity_context_server/memory_comparison_intent.py +2925 -0
  880. infinity_context_server/memory_comparison_latency.py +455 -0
  881. infinity_context_server/memory_comparison_llm.py +473 -0
  882. infinity_context_server/memory_comparison_location_roles.py +169 -0
  883. infinity_context_server/memory_comparison_locomo_cases.py +328 -0
  884. infinity_context_server/memory_comparison_locomo_expected_turn.py +297 -0
  885. infinity_context_server/memory_comparison_locomo_transport.py +873 -0
  886. infinity_context_server/memory_comparison_longmemeval_cases.py +339 -0
  887. infinity_context_server/memory_comparison_managed_attestation.py +771 -0
  888. infinity_context_server/memory_comparison_managed_benchmark_registry_abort_http.py +120 -0
  889. infinity_context_server/memory_comparison_managed_benchmark_registry_contracts.py +715 -0
  890. infinity_context_server/memory_comparison_managed_benchmark_registry_http.py +855 -0
  891. infinity_context_server/memory_comparison_managed_benchmark_registry_recovery_http.py +164 -0
  892. infinity_context_server/memory_comparison_managed_benchmark_registry_wire.py +890 -0
  893. infinity_context_server/memory_comparison_managed_composite_assembler.py +524 -0
  894. infinity_context_server/memory_comparison_managed_corpus_projection.py +653 -0
  895. infinity_context_server/memory_comparison_managed_execution_receipts.py +991 -0
  896. infinity_context_server/memory_comparison_managed_http_derived_evidence.py +724 -0
  897. infinity_context_server/memory_comparison_managed_http_exact_cleanup.py +476 -0
  898. infinity_context_server/memory_comparison_managed_http_execution.py +673 -0
  899. infinity_context_server/memory_comparison_managed_http_lifecycle.py +905 -0
  900. infinity_context_server/memory_comparison_managed_http_lifecycle_credentials.py +83 -0
  901. infinity_context_server/memory_comparison_managed_http_lifecycle_evidence.py +366 -0
  902. infinity_context_server/memory_comparison_managed_http_lifecycle_registration.py +106 -0
  903. infinity_context_server/memory_comparison_managed_http_policy_lifecycle.py +894 -0
  904. infinity_context_server/memory_comparison_managed_http_policy_material_projection.py +315 -0
  905. infinity_context_server/memory_comparison_managed_http_policy_receipts.py +219 -0
  906. infinity_context_server/memory_comparison_managed_http_policy_registry_evidence.py +177 -0
  907. infinity_context_server/memory_comparison_managed_http_policy_requirements.py +824 -0
  908. infinity_context_server/memory_comparison_managed_http_policy_support.py +108 -0
  909. infinity_context_server/memory_comparison_managed_http_policy_validation.py +762 -0
  910. infinity_context_server/memory_comparison_managed_ingest_manifest.py +449 -0
  911. infinity_context_server/memory_comparison_managed_live_admission.py +886 -0
  912. infinity_context_server/memory_comparison_managed_live_cli.py +536 -0
  913. infinity_context_server/memory_comparison_managed_live_composition.py +636 -0
  914. infinity_context_server/memory_comparison_managed_llm_execution.py +850 -0
  915. infinity_context_server/memory_comparison_managed_llm_execution_dispatch.py +269 -0
  916. infinity_context_server/memory_comparison_managed_llm_execution_ports.py +225 -0
  917. infinity_context_server/memory_comparison_managed_mem0_auth.py +65 -0
  918. infinity_context_server/memory_comparison_managed_mem0_runtime_authority.py +241 -0
  919. infinity_context_server/memory_comparison_managed_mem0_runtime_http.py +541 -0
  920. infinity_context_server/memory_comparison_managed_plan_builder.py +685 -0
  921. infinity_context_server/memory_comparison_managed_preflight.py +907 -0
  922. infinity_context_server/memory_comparison_managed_preflight_cli.py +431 -0
  923. infinity_context_server/memory_comparison_managed_production_cli.py +325 -0
  924. infinity_context_server/memory_comparison_managed_production_composition.py +140 -0
  925. infinity_context_server/memory_comparison_managed_production_ports.py +194 -0
  926. infinity_context_server/memory_comparison_managed_production_runner.py +450 -0
  927. infinity_context_server/memory_comparison_managed_projection_manifest.py +438 -0
  928. infinity_context_server/memory_comparison_managed_provider_calls.py +450 -0
  929. infinity_context_server/memory_comparison_managed_quality_projection.py +347 -0
  930. infinity_context_server/memory_comparison_managed_registry_policy_lifecycle.py +677 -0
  931. infinity_context_server/memory_comparison_managed_run.py +892 -0
  932. infinity_context_server/memory_comparison_managed_run_contract.py +454 -0
  933. infinity_context_server/memory_comparison_managed_run_ports.py +79 -0
  934. infinity_context_server/memory_comparison_managed_runtime_credentials.py +916 -0
  935. infinity_context_server/memory_comparison_managed_runtime_credentials_capability.py +361 -0
  936. infinity_context_server/memory_comparison_managed_runtime_credentials_context.py +132 -0
  937. infinity_context_server/memory_comparison_managed_runtime_credentials_integrity.py +213 -0
  938. infinity_context_server/memory_comparison_managed_runtime_credentials_models.py +81 -0
  939. infinity_context_server/memory_comparison_managed_runtime_credentials_targets.py +28 -0
  940. infinity_context_server/memory_comparison_mem0_contract.py +491 -0
  941. infinity_context_server/memory_comparison_mem0_http_observation.py +296 -0
  942. infinity_context_server/memory_comparison_mem0_locomo_prompts.py +328 -0
  943. infinity_context_server/memory_comparison_mem0_longmemeval_prompts.py +365 -0
  944. infinity_context_server/memory_comparison_mem0_official_chat.py +504 -0
  945. infinity_context_server/memory_comparison_mem0_official_prompt_renderer.py +307 -0
  946. infinity_context_server/memory_comparison_mem0_oss_contract.py +456 -0
  947. infinity_context_server/memory_comparison_mem0_oss_manifest.py +563 -0
  948. infinity_context_server/memory_comparison_mem0_platform_contract.py +467 -0
  949. infinity_context_server/memory_comparison_mem0_runtime_attestation.py +861 -0
  950. infinity_context_server/memory_comparison_models.py +398 -0
  951. infinity_context_server/memory_comparison_openai_official_transport.py +525 -0
  952. infinity_context_server/memory_comparison_preflight.py +1147 -0
  953. infinity_context_server/memory_comparison_probe_transport.py +154 -0
  954. infinity_context_server/memory_comparison_prompt_policy.py +603 -0
  955. infinity_context_server/memory_comparison_provider_provenance.py +392 -0
  956. infinity_context_server/memory_comparison_quality_accessors.py +339 -0
  957. infinity_context_server/memory_comparison_quality_actionable_gaps.py +2038 -0
  958. infinity_context_server/memory_comparison_quality_answer_context_gaps.py +450 -0
  959. infinity_context_server/memory_comparison_quality_answer_context_risks.py +75 -0
  960. infinity_context_server/memory_comparison_quality_bundle_gaps.py +860 -0
  961. infinity_context_server/memory_comparison_quality_bundle_table.py +805 -0
  962. infinity_context_server/memory_comparison_quality_diagnostics.py +2798 -0
  963. infinity_context_server/memory_comparison_quality_fusion.py +241 -0
  964. infinity_context_server/memory_comparison_quality_query_plan_gaps.py +533 -0
  965. infinity_context_server/memory_comparison_quality_query_role_gaps.py +845 -0
  966. infinity_context_server/memory_comparison_quality_query_roles.py +815 -0
  967. infinity_context_server/memory_comparison_quality_rerank_gaps.py +492 -0
  968. infinity_context_server/memory_comparison_quality_selected_weakness.py +544 -0
  969. infinity_context_server/memory_comparison_quality_support.py +921 -0
  970. infinity_context_server/memory_comparison_query_integrity.py +301 -0
  971. infinity_context_server/memory_comparison_query_plan.py +524 -0
  972. infinity_context_server/memory_comparison_query_terms.py +2055 -0
  973. infinity_context_server/memory_comparison_relation_support.py +2100 -0
  974. infinity_context_server/memory_comparison_rerank.py +3209 -0
  975. infinity_context_server/memory_comparison_rerank_intents.py +405 -0
  976. infinity_context_server/memory_comparison_rerank_policies.py +1726 -0
  977. infinity_context_server/memory_comparison_rerank_policy.py +675 -0
  978. infinity_context_server/memory_comparison_rerank_shapes.py +152 -0
  979. infinity_context_server/memory_comparison_rerank_terms.py +973 -0
  980. infinity_context_server/memory_comparison_rerank_text.py +722 -0
  981. infinity_context_server/memory_comparison_retrieval_policy.py +108 -0
  982. infinity_context_server/memory_comparison_retrieval_width.py +383 -0
  983. infinity_context_server/memory_comparison_service_probe.py +278 -0
  984. infinity_context_server/memory_comparison_service_probes.py +547 -0
  985. infinity_context_server/memory_comparison_session_identity_contract.py +328 -0
  986. infinity_context_server/memory_comparison_source_identity.py +1655 -0
  987. infinity_context_server/memory_comparison_subscription_chat.py +717 -0
  988. infinity_context_server/memory_comparison_subscription_live_probe.py +323 -0
  989. infinity_context_server/memory_comparison_subscription_probe.py +368 -0
  990. infinity_context_server/memory_comparison_target_identity.py +42 -0
  991. infinity_context_server/memory_comparison_temporal_grounding.py +771 -0
  992. infinity_context_server/memory_comparison_token_budget_contract.py +665 -0
  993. infinity_context_server/memory_comparison_token_latency.py +136 -0
  994. infinity_context_server/memory_scope_transfer.py +933 -0
  995. infinity_context_server/memory_scope_transfer_assets.py +181 -0
  996. infinity_context_server/memory_scope_transfer_conflicts.py +565 -0
  997. infinity_context_server/memory_scope_transfer_context.py +295 -0
  998. infinity_context_server/memory_scope_transfer_extractions.py +390 -0
  999. infinity_context_server/memory_scope_transfer_facts.py +65 -0
  1000. infinity_context_server/memory_scope_transfer_records.py +630 -0
  1001. infinity_context_server/memory_scope_transfer_relations.py +84 -0
  1002. infinity_context_server/memory_scope_transfer_remap.py +710 -0
  1003. infinity_context_server/memory_scope_transfer_scope.py +267 -0
  1004. infinity_context_server/memory_scope_transfer_support.py +108 -0
  1005. infinity_context_server/memory_scope_transfer_temporal.py +24 -0
  1006. infinity_context_server/metrics.py +264 -0
  1007. infinity_context_server/official_public_benchmark.py +979 -0
  1008. infinity_context_server/pagination.py +60 -0
  1009. infinity_context_server/processes/__init__.py +38 -0
  1010. infinity_context_server/processes/extractions.py +40 -0
  1011. infinity_context_server/processes/outbox.py +47 -0
  1012. infinity_context_server/processes/projections.py +372 -0
  1013. infinity_context_server/provider_budget.py +59 -0
  1014. infinity_context_server/provider_circuit.py +333 -0
  1015. infinity_context_server/public_benchmark.py +2532 -0
  1016. infinity_context_server/public_benchmark_artifacts.py +91 -0
  1017. infinity_context_server/public_benchmark_case_diagnostics.py +231 -0
  1018. infinity_context_server/public_benchmark_checkpoint.py +751 -0
  1019. infinity_context_server/public_benchmark_environment.py +102 -0
  1020. infinity_context_server/public_benchmark_execution.py +445 -0
  1021. infinity_context_server/public_benchmark_http.py +39 -0
  1022. infinity_context_server/public_benchmark_manifest.py +129 -0
  1023. infinity_context_server/public_benchmark_metrics.py +645 -0
  1024. infinity_context_server/public_benchmark_metrics_policy.py +117 -0
  1025. infinity_context_server/public_benchmark_models.py +112 -0
  1026. infinity_context_server/public_benchmark_progress.py +224 -0
  1027. infinity_context_server/public_benchmark_results.py +76 -0
  1028. infinity_context_server/public_benchmark_run_diagnostics.py +163 -0
  1029. infinity_context_server/public_benchmark_selection.py +379 -0
  1030. infinity_context_server/public_benchmark_transport.py +62 -0
  1031. infinity_context_server/public_benchmark_unsupported.py +228 -0
  1032. infinity_context_server/ranked_evidence_answer_support.py +665 -0
  1033. infinity_context_server/ranked_evidence_document_seed_contract.py +30 -0
  1034. infinity_context_server/ranked_evidence_evaluator_helpers.py +135 -0
  1035. infinity_context_server/ranked_evidence_retrieval_request.py +36 -0
  1036. infinity_context_server/ranked_evidence_seed_case.py +109 -0
  1037. infinity_context_server/ranked_evidence_semantic_gate.py +980 -0
  1038. infinity_context_server/ranked_evidence_semantic_gate_cli.py +137 -0
  1039. infinity_context_server/ranked_evidence_semantic_gate_decision.py +163 -0
  1040. infinity_context_server/ranked_evidence_semantic_metrics.py +375 -0
  1041. infinity_context_server/ranked_evidence_session_identity.py +100 -0
  1042. infinity_context_server/storage_maintenance.py +187 -0
  1043. infinity_context_server/top_evidence_dataset_profile.py +442 -0
  1044. infinity_context_server/top_evidence_policy.py +274 -0
  1045. infinity_context_server/top_evidence_preflight.py +693 -0
  1046. infinity_context_server/web/assets/memory-browser-capture.js +252 -0
  1047. infinity_context_server/web/assets/memory-browser-operations.js +143 -0
  1048. infinity_context_server/web/assets/memory-browser-review.js +687 -0
  1049. infinity_context_server/web/assets/memory-browser.css +854 -0
  1050. infinity_context_server/web/assets/memory-browser.js +3299 -0
  1051. infinity_context_server/web/index.html +261 -0
  1052. infinity_context_server/web_ui.py +29 -0
  1053. infinity_context_server/worker.py +380 -0
@@ -0,0 +1,3681 @@
1
+ """Context dedupe, rank fusion and deterministic ranking helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import re
7
+ from collections.abc import Mapping, Sequence
8
+ from dataclasses import dataclass, replace
9
+ from functools import lru_cache
10
+
11
+ from infinity_context_core.application.context_action_roles import action_role_rerank_signal
12
+ from infinity_context_core.application.context_activity_companion import (
13
+ activity_companion_signal,
14
+ )
15
+ from infinity_context_core.application.context_aggregation_answer_slots import (
16
+ aggregation_answer_slot_count,
17
+ )
18
+ from infinity_context_core.application.context_answer_unit_shapes import (
19
+ is_typed_answer_unit_shape,
20
+ )
21
+ from infinity_context_core.application.context_conflicting_evidence_rerank import (
22
+ apply_conflicting_evidence_currentness_rerank,
23
+ )
24
+ from infinity_context_core.application.context_conversation_counterparty import (
25
+ conversation_counterparty_evidence_signal,
26
+ conversation_recency_evidence_signal,
27
+ conversation_recency_missing_temporal_signal,
28
+ conversation_recency_temporal_hint_signal,
29
+ conversation_topic_evidence_signal,
30
+ )
31
+ from infinity_context_core.application.context_count_cardinality import (
32
+ has_exact_count_cardinality_evidence,
33
+ )
34
+ from infinity_context_core.application.context_diagnostics import (
35
+ context_duplicate_primary_key,
36
+ context_rank_key,
37
+ diagnostic_retrieval_sources,
38
+ merge_context_diagnostics,
39
+ merge_diagnostic_retrieval_sources,
40
+ normalize_context_diagnostics,
41
+ normalize_context_item_diagnostics,
42
+ safe_diagnostic_mapping,
43
+ safe_score_signals,
44
+ )
45
+ from infinity_context_core.application.context_domain_rerank_apply import (
46
+ apply_domain_rerank_signals,
47
+ )
48
+ from infinity_context_core.application.context_domain_rerank_signals import (
49
+ commonality_who_else_anchor_override,
50
+ has_current_state_correction_evidence,
51
+ has_multi_evidence_aggregation_candidate,
52
+ )
53
+ from infinity_context_core.application.context_inference_evidence import (
54
+ answer_evidence_rerank_signal,
55
+ )
56
+ from infinity_context_core.application.context_internal_diagnostics import (
57
+ diagnostic_mapping,
58
+ item_diagnostic_source_id,
59
+ nested_diagnostic_mapping,
60
+ provenance_flag_is_true,
61
+ )
62
+ from infinity_context_core.application.context_item_purchase_evidence import (
63
+ has_item_purchase_object_evidence,
64
+ )
65
+ from infinity_context_core.application.context_lexical import (
66
+ LexicalQueryTerm,
67
+ query_term_frequency,
68
+ query_terms,
69
+ text_variant_profile,
70
+ text_variant_stats,
71
+ )
72
+ from infinity_context_core.application.context_named_person_preference import (
73
+ named_person_preference_signal,
74
+ )
75
+ from infinity_context_core.application.context_object_mismatch import (
76
+ object_kind_mismatch_signal,
77
+ )
78
+ from infinity_context_core.application.context_ordinal_sequence import (
79
+ ordinal_sequence_rerank_signal,
80
+ )
81
+ from infinity_context_core.application.context_owner_grounding import (
82
+ owner_grounding_signal,
83
+ )
84
+ from infinity_context_core.application.context_person_kinship import (
85
+ person_kinship_signal,
86
+ )
87
+ from infinity_context_core.application.context_person_occupation import (
88
+ person_occupation_signal,
89
+ )
90
+ from infinity_context_core.application.context_person_relation_inventory import (
91
+ person_relation_inventory_signal,
92
+ )
93
+ from infinity_context_core.application.context_person_residence import (
94
+ person_residence_signal,
95
+ )
96
+ from infinity_context_core.application.context_person_team_membership import (
97
+ person_team_membership_signal,
98
+ )
99
+ from infinity_context_core.application.context_pet_ownership import (
100
+ pet_ownership_signal,
101
+ )
102
+ from infinity_context_core.application.context_polarity_rerank import (
103
+ absence_contrast_signal,
104
+ absence_negation_signal,
105
+ negative_preference_signal,
106
+ schedule_change_signal,
107
+ status_polarity_signal,
108
+ )
109
+ from infinity_context_core.application.context_possession_source import (
110
+ possession_source_signal,
111
+ )
112
+ from infinity_context_core.application.context_precise_temporal_evidence import (
113
+ requests_temporal_ranking_context,
114
+ )
115
+ from infinity_context_core.application.context_query_expansion import QueryExpansionPlan
116
+ from infinity_context_core.application.context_query_intent import (
117
+ QueryAnchorIntent,
118
+ match_query_anchor_intent_to_text,
119
+ query_anchor_intent_text_conflicts,
120
+ )
121
+ from infinity_context_core.application.context_ranking_reason_policy import (
122
+ ACTIVITY_OBSERVATION_SOURCE_REASONS as _ACTIVITY_OBSERVATION_SOURCE_REASONS,
123
+ )
124
+ from infinity_context_core.application.context_ranking_reason_policy import (
125
+ ACTIVITY_OWNER_REASONS as _ACTIVITY_OWNER_REASONS,
126
+ )
127
+ from infinity_context_core.application.context_ranking_reason_policy import (
128
+ CONTEXT_ITEM_REASON_PRIORITY as _CONTEXT_ITEM_REASON_PRIORITY,
129
+ )
130
+ from infinity_context_core.application.context_ranking_reason_policy import (
131
+ DERIVED_SUMMARY_SOURCE_MIN_DISTINCTIVE_HITS as _DERIVED_SUMMARY_SOURCE_MIN_DISTINCTIVE_HITS,
132
+ )
133
+ from infinity_context_core.application.context_ranking_reason_policy import (
134
+ DERIVED_SUMMARY_SOURCE_REASONS as _DERIVED_SUMMARY_SOURCE_REASONS,
135
+ )
136
+ from infinity_context_core.application.context_ranking_reason_policy import (
137
+ KEYWORD_EXPANSION_REASON_BOOSTS as _KEYWORD_EXPANSION_REASON_BOOSTS,
138
+ )
139
+ from infinity_context_core.application.context_ranking_reason_policy import (
140
+ KEYWORD_EXPANSION_SCORE_CAPS as _KEYWORD_EXPANSION_SCORE_CAPS,
141
+ )
142
+ from infinity_context_core.application.context_ranking_reason_policy import (
143
+ QUERY_REASON_PRIORITY as _QUERY_REASON_PRIORITY,
144
+ )
145
+ from infinity_context_core.application.context_ranking_reason_policy import (
146
+ QUERY_REASON_PRIORITY_MIN_DISTINCTIVE_HITS as _QUERY_REASON_PRIORITY_MIN_DISTINCTIVE_HITS,
147
+ )
148
+ from infinity_context_core.application.context_relation_requirement import (
149
+ relation_requirement_signal,
150
+ )
151
+ from infinity_context_core.application.context_relevance import (
152
+ QueryRelevance,
153
+ has_project_identity_mismatch,
154
+ is_query_relevance_sufficient,
155
+ score_query_relevance,
156
+ score_query_terms_relevance_against_profile,
157
+ )
158
+ from infinity_context_core.application.context_requirement_coverage import (
159
+ context_requirement_coverage,
160
+ )
161
+ from infinity_context_core.application.context_source_grounding import (
162
+ source_grounding_signal,
163
+ )
164
+ from infinity_context_core.application.context_speaker_attribution import (
165
+ communication_direction_signal,
166
+ speaker_attribution_signal,
167
+ )
168
+ from infinity_context_core.application.context_speaker_disambiguation import (
169
+ apply_attributed_speaker_exact_name_disambiguation,
170
+ )
171
+ from infinity_context_core.application.context_temporal_answer_grounding import (
172
+ temporal_answer_grounding,
173
+ )
174
+ from infinity_context_core.application.context_temporal_metadata import (
175
+ temporal_hint_code_from_metadata,
176
+ )
177
+ from infinity_context_core.application.context_temporal_query import (
178
+ TemporalQueryIntent,
179
+ build_temporal_query_intent,
180
+ temporal_query_boost_signal,
181
+ )
182
+ from infinity_context_core.application.dto import ContextItem
183
+ from infinity_context_core.domain.entities import (
184
+ MAX_SOURCE_REFS_PER_ITEM,
185
+ MemoryAnchorKind,
186
+ SourceRef,
187
+ )
188
+
189
+ _RRF_RANK_CONSTANT = 60.0
190
+ _RRF_MAX_RANK_PER_SOURCE = 50
191
+ _RRF_MAX_BOOST = 0.045
192
+ _DEFAULT_RRF_SOURCE_WEIGHTS = {
193
+ "approved_context_linked_anchors": 1.18,
194
+ "approved_context_linked_asset_manifest_evidence": 1.14,
195
+ "approved_context_linked_assets": 1.08,
196
+ "approved_context_linked_chunks": 1.12,
197
+ "approved_context_linked_extraction_artifacts": 1.2,
198
+ "approved_context_linked_facts": 1.14,
199
+ "artifact_evidence": 1.2,
200
+ "canonical_anchor_relations": 1.12,
201
+ "canonical_anchors": 1.15,
202
+ "graph_hydrated": 1.08,
203
+ "keyword_aggregation_chunks": 1.12,
204
+ "keyword_chunks": 1.04,
205
+ "keyword_neighbor_chunks": 1.04,
206
+ "keyword_source_sibling_chunks": 1.08,
207
+ "postgres_facts": 1.06,
208
+ "rag_recall": 1.06,
209
+ "temporal_supersedes_relation": 1.12,
210
+ "vector_chunks": 1.08,
211
+ }
212
+ _BM25_K1 = 1.2
213
+ _BM25_B = 0.75
214
+ _BM25_MAX_BOOST = 0.035
215
+ _QueryExpansionTerms = tuple[tuple[str, str, tuple[LexicalQueryTerm, ...]], ...]
216
+ _STRICT_QUERY_REASON_MIN_DISTINCTIVE_HITS = {
217
+ "book_suggestion_bridge": 4,
218
+ "music_artist_answer_bridge": 4,
219
+ }
220
+ _DIRECT_VISUAL_TEXT_QUERY_RE = re.compile(
221
+ r"\b(?:what|which)\b(?=.{0,120}\b(?:ocr|text|written|label|title)\b)"
222
+ r"(?=.{0,120}\b(?:screenshot|screen|image|visual)\b)",
223
+ re.IGNORECASE | re.DOTALL,
224
+ )
225
+ _ARTIFACT_CONTEXT_COMPOSITE_QUERY_RE = re.compile(
226
+ r"\b(?:changed|after|before|call|meeting|conversation|about)\b",
227
+ re.IGNORECASE,
228
+ )
229
+ _BOOK_SUGGESTION_PROJECT_FALSE_FRIEND_RE = re.compile(
230
+ r"\b(?:project|launch|planning|proposal|task|workflow)\s+suggestion\b|"
231
+ r"\bsuggestion\s+(?:from|about)\b(?=.{0,80}\b(?:project|launch|planning|proposal|task)\b)",
232
+ re.IGNORECASE | re.DOTALL,
233
+ )
234
+ _BOOK_SUGGESTION_EVIDENCE_CUE_RE = re.compile(
235
+ r"[\"“][^\"”]{2,80}[\"”]|"
236
+ r"\b(?:book|novel|memoir|author|chapter|must[-\s]?see|great\s+read|"
237
+ r"read\s+(?:the\s+)?book|recommended\s+(?:the\s+)?book|"
238
+ r"suggested\s+(?:the\s+)?book)\b",
239
+ re.IGNORECASE,
240
+ )
241
+ _QUERY_ANCHOR_INTENT_MAX_BOOST = 0.035
242
+ _CONTEXT_REQUIREMENT_MAX_BOOST = 0.04
243
+ _CONTEXT_REQUIREMENT_ANCHOR_BOOST = 0.008
244
+ _CONTEXT_REQUIREMENT_MODALITY_BOOST = 0.022
245
+ _CONTEXT_REQUIREMENT_FEATURE_BOOST = 0.014
246
+ _CONTEXT_REQUIREMENT_ANSWER_SHAPE_BOOST = 0.012
247
+ _CONTEXT_REQUIREMENT_EXACT_COUNT_CARDINALITY_BOOST = 0.008
248
+ _CONTEXT_REQUIREMENT_TYPED_ANSWER_UNIT_BOOST = 0.018
249
+ _CONTEXT_REQUIREMENT_TYPED_ANSWER_UNIT_MISMATCH_PENALTY = 0.012
250
+ _GENERIC_BOOSTABLE_ANSWER_SHAPES = frozenset((
251
+ "causal",
252
+ "choice",
253
+ "commonality",
254
+ "commitment",
255
+ "constraint",
256
+ "conversation_participant",
257
+ "conversation_topic",
258
+ "count",
259
+ "existence",
260
+ "gotcha",
261
+ "inference",
262
+ "list",
263
+ "location",
264
+ "ordinal",
265
+ "preference",
266
+ "relationship",
267
+ "speaker",
268
+ "summary",
269
+ "temporal",
270
+ ))
271
+ _DETERMINISTIC_RERANK_MAX_BOOST = 0.055
272
+ _DETERMINISTIC_RERANK_MAX_PENALTY = 0.11
273
+ _CANONICAL_ANCHOR_SUMMARY_RERANK_MAX_BOOST = 0.018
274
+ _CITATION_EVIDENCE_RERANK_MAX_BOOST = 0.024
275
+ _SOURCE_QUOTE_ANSWER_RERANK_MAX_BOOST = 0.01
276
+ _ARTIFACT_INVENTORY_EVIDENCE_RERANK_MAX_BOOST = 0.028
277
+ _DECOMPOSITION_COVERAGE_RERANK_MAX_BOOST = 0.022
278
+ _LOCALIZED_EVIDENCE_RERANK_MAX_BOOST = 0.022
279
+ _SAME_PERSON_TOPIC_DISAMBIGUATION_MAX_PENALTY = 0.064
280
+ _DUPLICATE_SOURCE_SCORE_TOLERANCE = 0.015
281
+ _BROAD_DECOMPOSITION_COVERAGE_REASONS = frozenset(
282
+ {
283
+ "decomposition_clause",
284
+ "decomposition_inventory_list",
285
+ "decomposition_quantity_count",
286
+ "decomposition_temporal_answer",
287
+ }
288
+ )
289
+ _STRONG_EVIDENCE_RERANK_SOURCES = frozenset(
290
+ {
291
+ "approved_context_linked_anchors",
292
+ "approved_context_linked_asset_manifest_evidence",
293
+ "approved_context_linked_assets",
294
+ "approved_context_linked_chunks",
295
+ "approved_context_linked_extraction_artifacts",
296
+ "approved_context_linked_facts",
297
+ "artifact_evidence",
298
+ "canonical_anchor_relations",
299
+ "canonical_anchors",
300
+ "temporal_supersedes_relation",
301
+ }
302
+ )
303
+ _ARTIFACT_INVENTORY_RERANK_SOURCES = frozenset(
304
+ {
305
+ "approved_context_linked_asset_manifest_evidence",
306
+ "approved_context_linked_assets",
307
+ "approved_context_linked_extraction_artifacts",
308
+ "artifact_evidence",
309
+ }
310
+ )
311
+ _ARTIFACT_INVENTORY_SOURCE_REF_TYPES = frozenset(
312
+ {
313
+ "asset",
314
+ "extraction_artifact",
315
+ }
316
+ )
317
+ _DERIVED_SUMMARY_SOURCE_BOOST = 0.06
318
+ _DERIVED_SUMMARY_SOURCE_CAP = 0.985
319
+ _ACTIVITY_DERIVED_SUMMARY_SOURCE_BOOST = 0.09
320
+ _ACTIVITY_DERIVED_SUMMARY_SOURCE_CAP = 0.99
321
+ _ACTIVITY_DERIVED_SUMMARY_STRONG_HITS = 10
322
+ _DERIVED_SUMMARY_SOURCE_SUFFIXES = (":observation", ":summary", ":events")
323
+ _ACTIVITY_EVIDENCE_SOURCE_SUFFIXES = (":turn", ":observation", ":summary", ":events")
324
+ _ATTRIBUTE_EVIDENCE_SOURCE_SUFFIXES = (":turn", ":observation", ":events")
325
+ _PRECISE_EVIDENCE_SOURCE_REASONS = frozenset(
326
+ {
327
+ "adoption_current_goal_bridge",
328
+ "adoption_current_milestone_bridge",
329
+ "activity_competition_evidence_bridge",
330
+ "allergy_condition_inference_bridge",
331
+ "allergy_inventory_bridge",
332
+ "business_networking_event_bridge",
333
+ "business_opening_timeline_bridge",
334
+ "business_promotion_event_bridge",
335
+ "business_store_promotion_event_bridge",
336
+ "business_start_reason_bridge",
337
+ "book_reading_list_bridge",
338
+ "church_friend_activity_inventory_bridge",
339
+ "charity_brand_sponsorship_bridge",
340
+ "charity_tournament_count_bridge",
341
+ "children_count_event_bridge",
342
+ "choice_reason_bridge",
343
+ "business_commonality_bridge",
344
+ "store_promotion_inventory_bridge",
345
+ "camping_location_bridge",
346
+ "degree_policy_inference_bridge",
347
+ "exercise_activity_inventory_bridge",
348
+ "endorsement_gear_brand_bridge",
349
+ "gaming_medium_bridge",
350
+ "hiking_trail_count_bridge",
351
+ "hobby_interest_bridge",
352
+ "instrument_play_bridge",
353
+ "letter_count_bridge",
354
+ "meteor_shower_feeling_bridge",
355
+ "national_park_inference_bridge",
356
+ "patriotic_service_inference_bridge",
357
+ "pet_count_bridge",
358
+ "pet_inventory_bridge",
359
+ "personality_authenticity_bridge",
360
+ "personality_drive_bridge",
361
+ "personality_thoughtfulness_bridge",
362
+ "personality_trait_bridge",
363
+ "post_athletic_career_bridge",
364
+ "running_reason_bridge",
365
+ "running_reason_question_bridge",
366
+ "screenplay_count_bridge",
367
+ "shelter_comfort_reason_bridge",
368
+ "state_residence_inference_bridge",
369
+ "symbol_importance_bridge",
370
+ "temporal_event_detail_bridge",
371
+ "future_plan_timing_bridge",
372
+ "tournament_count_bridge",
373
+ "volunteer_career_inference_bridge",
374
+ "volunteering_people_inventory_bridge",
375
+ "yoga_delay_gaming_bridge",
376
+ }
377
+ )
378
+ _TURN_ONLY_EVIDENCE_SOURCE_REASONS = frozenset(
379
+ {
380
+ "personality_authenticity_bridge",
381
+ "personality_drive_bridge",
382
+ "personality_thoughtfulness_bridge",
383
+ "personality_trait_bridge",
384
+ "national_park_inference_bridge",
385
+ "state_residence_inference_bridge",
386
+ }
387
+ )
388
+ _ACTIVITY_OBSERVATION_MIN_DISTINCTIVE_HITS = 8
389
+ _ACTIVITY_OWNER_MATCH_BOOST = 0.012
390
+ _ACTIVITY_OWNER_MISMATCH_PENALTY = 0.042
391
+ _VOLUNTEER_CAREER_EVIDENCE_RE = re.compile(
392
+ r"\b("
393
+ r"volunteer(?:ed|ing|s)?|homeless\s+shelter|shelter|front\s+desk|"
394
+ r"food|bed|talks?|compliments?|fulfilling|make\s+a\s+difference|"
395
+ r"brighten|aunt|struggling|residents?|social\s+work|"
396
+ r"counsel(?:or|ing)?|coordinator"
397
+ r")\b",
398
+ re.IGNORECASE,
399
+ )
400
+ _VOLUNTEER_CAREER_CONTEXT_RE = re.compile(
401
+ r"\b("
402
+ r"volunteer(?:ed|ing|s)?|homeless\s+shelter|shelter|front\s+desk|"
403
+ r"food|bed|talks?|compliments?|residents?|social\s+work|"
404
+ r"counsel(?:or|ing)?|coordinator"
405
+ r")\b",
406
+ re.IGNORECASE,
407
+ )
408
+ _VOLUNTEER_CAREER_STRONG_NON_TURN_EVIDENCE_RE = re.compile(
409
+ r"\b("
410
+ r"front\s+desk|talks?|compliments?|residents?|bed|food|"
411
+ r"counsel(?:or|ing)?|coordinator|started\s+volunteering"
412
+ r")\b",
413
+ re.IGNORECASE,
414
+ )
415
+ _POST_EVENT_ACTIVITY_TIMING_CONTEXT_RE = re.compile(
416
+ r"\b(?:road\s*trip|roadtrip)\b(?=.{0,180}\b(?:yesterday|recent|"
417
+ r"just\s+did|after\s+the\s+(?:road\s*trip|drive)|relax))|"
418
+ r"\b(?:yesterday|just\s+did|recent|relax)\b(?=.{0,180}\b(?:road\s*trip|roadtrip))",
419
+ re.IGNORECASE | re.DOTALL,
420
+ )
421
+ _SHOE_USAGE_CONTEXT_RE = re.compile(
422
+ r"\b(?:shoes?|sneakers?)\b|walking\s+or\s+running|for\s+running|"
423
+ r"purple\s+running\s+shoe",
424
+ re.IGNORECASE,
425
+ )
426
+ _EVENT_PARTICIPATION_QUERY_RE = re.compile(
427
+ r"\b(attend(?:ed|ing)?|participat(?:e|ed|ing)|partook|joined|went)\b",
428
+ re.IGNORECASE,
429
+ )
430
+ _EVENT_TERM_QUERY_RE = re.compile(r"\b(events?|parade|conference|group|program)\b", re.IGNORECASE)
431
+ _MISSED_EVENT_TEXT_RE = re.compile(r"\bmissed\s+(?:it|the|that)?\b", re.IGNORECASE)
432
+ _SELF_MISSED_EVENT_TEXT_RE = re.compile(r"\b(?:i|we)\s+missed\s+(?:it|the|that)?\b", re.IGNORECASE)
433
+ _POSITIVE_EVENT_TEXT_RE = re.compile(
434
+ r"\b(attended|participated|joined|went|marched|took part)\b",
435
+ re.IGNORECASE,
436
+ )
437
+ _POSITIVE_EVENT_PARTICIPATION_TEXT_RE = re.compile(
438
+ r"\b(?:i|we)\s+(?:recently\s+|also\s+|just\s+|last\s+\w+\s+)?"
439
+ r"(?:went|attended|participated|joined|marched|took\s+part)\b|"
440
+ r"\b(?:went\s+to|attended|participated\s+in|joined)\b.{0,80}"
441
+ r"\b(?:events?|parade|conference|group|program|campaign)\b",
442
+ re.IGNORECASE | re.DOTALL,
443
+ )
444
+ _POSITIVE_ACTIVITY_TEXT_RE = re.compile(
445
+ r"\b(?:hikes?|hiking|camp(?:ing|fire|ed)?|marshmallows?|forest|trail|"
446
+ r"outdoors?|nature|museum|dinosaur|exhibit|park|playground|pottery|"
447
+ r"workshop|clay|pots?|painting|painted|swimm(?:ing)?|swam|beach|"
448
+ r"waterfall|mountains?|concert|running|race)\b",
449
+ re.IGNORECASE,
450
+ )
451
+ _SPEAKER_LABEL_RE = r"[A-ZА-ЯЁ][A-Za-zА-Яа-яЁё._-]{1,39}"
452
+ _DIALOGUE_SPEAKER_RE = re.compile(
453
+ rf"\bD\d+:\d+\s+(?P<speaker>{_SPEAKER_LABEL_RE}):",
454
+ re.IGNORECASE,
455
+ )
456
+ _DIALOGUE_MARKER_RE = re.compile(r"\bD\d+:\d+\b")
457
+ _SAME_PERSON_TOPIC_DECOY_TEXT_RE = re.compile(
458
+ r"\b(?:about|around|regarding|topic|subject|agenda|covered|covers?|"
459
+ r"discuss(?:ed|ion)?|mention(?:ed)?|said|told|wrote|asked|"
460
+ r"recommended|suggested|project|plan|initiative|campaign|issue)\b",
461
+ re.IGNORECASE,
462
+ )
463
+ _SAME_PERSON_TOPIC_DISAMBIGUATION_QUERY_RE = re.compile(
464
+ r"\b(?:about|around|regarding|topic|subject)\b|"
465
+ r"\b(?:what|which|who)\b(?=.{0,120}\b(?:ask(?:ed)?|discuss(?:ed)?|"
466
+ r"mention(?:ed)?|recommend(?:ed)?|said|say|suggest(?:ed)?|wrote|write)\b)",
467
+ re.IGNORECASE | re.DOTALL,
468
+ )
469
+ _SAME_PERSON_TOPIC_QUERY_STOP_TERMS = frozenset(
470
+ {
471
+ "about",
472
+ "after",
473
+ "ago",
474
+ "and",
475
+ "around",
476
+ "ask",
477
+ "asked",
478
+ "call",
479
+ "called",
480
+ "chat",
481
+ "chatted",
482
+ "conversation",
483
+ "day",
484
+ "days",
485
+ "did",
486
+ "discuss",
487
+ "discussed",
488
+ "do",
489
+ "does",
490
+ "during",
491
+ "hour",
492
+ "hours",
493
+ "latest",
494
+ "last",
495
+ "meeting",
496
+ "mention",
497
+ "mentioned",
498
+ "message",
499
+ "messaged",
500
+ "recent",
501
+ "said",
502
+ "say",
503
+ "sent",
504
+ "spoke",
505
+ "subject",
506
+ "talk",
507
+ "talked",
508
+ "text",
509
+ "texted",
510
+ "topic",
511
+ "was",
512
+ "week",
513
+ "weeks",
514
+ "what",
515
+ "when",
516
+ "where",
517
+ "which",
518
+ "who",
519
+ "with",
520
+ "write",
521
+ "wrote",
522
+ "о",
523
+ "об",
524
+ "про",
525
+ "разговор",
526
+ "сказал",
527
+ "сказала",
528
+ "созвон",
529
+ "что",
530
+ }
531
+ )
532
+
533
+
534
+ def dedupe_rank_items(items: tuple[ContextItem, ...]) -> tuple[ContextItem, ...]:
535
+ by_key: dict[tuple[str, str], ContextItem] = {}
536
+ for raw_item in items:
537
+ item = normalize_context_item_diagnostics(raw_item)
538
+ key = (item.item_type, item.item_id)
539
+ existing = by_key.get(key)
540
+ if existing is None:
541
+ by_key[key] = item
542
+ elif _should_replace_context_item(candidate=item, existing=existing):
543
+ by_key[key] = _merge_context_items(primary=item, secondary=existing)
544
+ else:
545
+ by_key[key] = _merge_context_items(primary=existing, secondary=item)
546
+ return tuple(sorted(by_key.values(), key=context_rank_key))
547
+
548
+
549
+ def apply_rank_fusion_boosts(
550
+ items: tuple[ContextItem, ...],
551
+ *,
552
+ rank_constant: float = _RRF_RANK_CONSTANT,
553
+ max_rank_per_source: int = _RRF_MAX_RANK_PER_SOURCE,
554
+ max_boost: float = _RRF_MAX_BOOST,
555
+ source_weights: Mapping[str, float] | None = None,
556
+ ) -> tuple[ContextItem, ...]:
557
+ if len(items) <= 1 or rank_constant <= 0 or max_rank_per_source <= 0 or max_boost <= 0:
558
+ return items
559
+ rankings = _ranked_items_by_retrieval_source(items)
560
+ if len(rankings) <= 1:
561
+ return items
562
+ fusion_scores = reciprocal_rank_fusion_scores(
563
+ rankings,
564
+ rank_constant=rank_constant,
565
+ max_rank_per_source=max_rank_per_source,
566
+ source_weights=(_DEFAULT_RRF_SOURCE_WEIGHTS if source_weights is None else source_weights),
567
+ )
568
+ max_fusion_score = max(fusion_scores.values(), default=0.0)
569
+ if max_fusion_score <= 0:
570
+ return items
571
+ return tuple(
572
+ _with_rank_fusion_boost(
573
+ item,
574
+ fusion_score=fusion_scores.get((item.item_type, item.item_id), 0.0),
575
+ max_fusion_score=max_fusion_score,
576
+ max_boost=max_boost,
577
+ source_count=len(rankings),
578
+ source_weighted=bool(
579
+ _DEFAULT_RRF_SOURCE_WEIGHTS if source_weights is None else source_weights
580
+ ),
581
+ )
582
+ for item in items
583
+ )
584
+
585
+
586
+ def apply_bm25_lexical_boosts(
587
+ items: tuple[ContextItem, ...],
588
+ *,
589
+ query: str,
590
+ k1: float = _BM25_K1,
591
+ b: float = _BM25_B,
592
+ max_boost: float = _BM25_MAX_BOOST,
593
+ ) -> tuple[ContextItem, ...]:
594
+ if len(items) <= 1 or k1 <= 0 or not 0 <= b <= 1 or max_boost <= 0:
595
+ return items
596
+ terms = query_terms(query)
597
+ if not terms:
598
+ return items
599
+ documents, raw_scores = _bm25_raw_scores(items=items, terms=terms, k1=k1, b=b)
600
+ max_raw_score = max(raw_scores, default=0.0)
601
+ if max_raw_score <= 0:
602
+ return items
603
+ return tuple(
604
+ _with_bm25_lexical_boost(
605
+ document.item,
606
+ raw_score=raw_score,
607
+ max_raw_score=max_raw_score,
608
+ max_boost=max_boost,
609
+ query_term_count=len(terms),
610
+ matched_term_count=sum(1 for frequency in document.term_frequencies if frequency > 0),
611
+ )
612
+ for document, raw_score in zip(documents, raw_scores, strict=True)
613
+ )
614
+
615
+
616
+ def apply_query_plan_bm25_lexical_boosts(
617
+ items: tuple[ContextItem, ...],
618
+ *,
619
+ plan: QueryExpansionPlan,
620
+ bm25_text_stats_cache: dict[str, tuple[Mapping[str, int], int]] | None = None,
621
+ k1: float = _BM25_K1,
622
+ b: float = _BM25_B,
623
+ max_boost: float = _BM25_MAX_BOOST,
624
+ ) -> tuple[ContextItem, ...]:
625
+ if len(items) <= 1 or k1 <= 0 or not 0 <= b <= 1 or max_boost <= 0:
626
+ return items
627
+ matches = _best_bm25_query_matches(
628
+ items=items,
629
+ plan=plan,
630
+ k1=k1,
631
+ b=b,
632
+ bm25_text_stats_cache=bm25_text_stats_cache,
633
+ )
634
+ if not any(match.normalized_score > 0 for match in matches):
635
+ return items
636
+ return tuple(
637
+ _with_bm25_lexical_boost(
638
+ item,
639
+ raw_score=match.normalized_score,
640
+ max_raw_score=1.0,
641
+ max_boost=max_boost,
642
+ query_term_count=match.query_term_count,
643
+ matched_term_count=match.matched_term_count,
644
+ query_reason=match.query_reason,
645
+ query_coverage=match.query_coverage,
646
+ )
647
+ for item, match in zip(items, matches, strict=True)
648
+ )
649
+
650
+
651
+ def apply_query_anchor_intent_boosts(
652
+ items: tuple[ContextItem, ...],
653
+ *,
654
+ intent: QueryAnchorIntent,
655
+ max_boost: float = _QUERY_ANCHOR_INTENT_MAX_BOOST,
656
+ ) -> tuple[ContextItem, ...]:
657
+ if not items or intent.empty or max_boost <= 0:
658
+ return items
659
+ return tuple(
660
+ _with_query_anchor_intent_boost(
661
+ item,
662
+ match=match_query_anchor_intent_to_text(intent, item.text),
663
+ max_boost=max_boost,
664
+ )
665
+ for item in items
666
+ )
667
+
668
+
669
+ def apply_context_requirement_boosts(
670
+ items: tuple[ContextItem, ...],
671
+ *,
672
+ query: str,
673
+ query_anchor_intent: QueryAnchorIntent,
674
+ max_boost: float = _CONTEXT_REQUIREMENT_MAX_BOOST,
675
+ ) -> tuple[ContextItem, ...]:
676
+ if not items or max_boost <= 0:
677
+ return items
678
+ requested = context_requirement_coverage(
679
+ query=query,
680
+ query_anchor_intent=query_anchor_intent,
681
+ items=(),
682
+ )
683
+ requested_anchor_kinds = _coverage_value_set(requested.get("requested_anchor_kinds"))
684
+ requested_modalities = _coverage_value_set(requested.get("requested_modalities"))
685
+ requested_features = _coverage_value_set(requested.get("requested_evidence_features"))
686
+ requested_answer_shapes = _coverage_value_set(requested.get("requested_answer_shapes"))
687
+ if (
688
+ not requested_anchor_kinds
689
+ and not requested_modalities
690
+ and not requested_features
691
+ and not requested_answer_shapes
692
+ ):
693
+ return items
694
+ return tuple(
695
+ _with_context_requirement_boost(
696
+ item,
697
+ query=query,
698
+ query_anchor_intent=query_anchor_intent,
699
+ requested_anchor_kinds=requested_anchor_kinds,
700
+ requested_modalities=requested_modalities,
701
+ requested_features=requested_features,
702
+ requested_answer_shapes=requested_answer_shapes,
703
+ max_boost=max_boost,
704
+ )
705
+ for item in items
706
+ )
707
+
708
+
709
+ def apply_deterministic_rerank_adjustments(
710
+ items: tuple[ContextItem, ...],
711
+ *,
712
+ query: str,
713
+ plan: QueryExpansionPlan,
714
+ query_anchor_intent: QueryAnchorIntent,
715
+ query_relevance_cache: dict[str, tuple[str, str, QueryRelevance]] | None = None,
716
+ max_boost: float = _DETERMINISTIC_RERANK_MAX_BOOST,
717
+ max_penalty: float = _DETERMINISTIC_RERANK_MAX_PENALTY,
718
+ ) -> tuple[ContextItem, ...]:
719
+ if not items or (max_boost <= 0 and max_penalty <= 0):
720
+ return items
721
+ requested_coverage = context_requirement_coverage(
722
+ query=query,
723
+ query_anchor_intent=query_anchor_intent,
724
+ items=(),
725
+ )
726
+ temporal_query_intent = build_temporal_query_intent(query)
727
+ requested_total = _coverage_int(requested_coverage.get("requested_total"))
728
+ has_multi_aggregation_candidate = has_multi_evidence_aggregation_candidate(
729
+ query=query,
730
+ items=items,
731
+ )
732
+ adjusted = tuple(
733
+ _with_deterministic_rerank_adjustment(
734
+ item,
735
+ query=query,
736
+ plan=plan,
737
+ query_anchor_intent=query_anchor_intent,
738
+ temporal_query_intent=temporal_query_intent,
739
+ requested_total=requested_total,
740
+ has_multi_evidence_aggregation_candidate=has_multi_aggregation_candidate,
741
+ query_relevance_cache=query_relevance_cache,
742
+ max_boost=max_boost,
743
+ max_penalty=max_penalty,
744
+ )
745
+ for item in items
746
+ )
747
+ adjusted = apply_attributed_speaker_exact_name_disambiguation(
748
+ adjusted,
749
+ query=query,
750
+ max_penalty=max_penalty,
751
+ )
752
+ adjusted = _apply_same_person_topic_disambiguation_penalties(
753
+ adjusted,
754
+ query=query,
755
+ query_anchor_intent=query_anchor_intent,
756
+ max_penalty=max_penalty,
757
+ )
758
+ return apply_conflicting_evidence_currentness_rerank(
759
+ adjusted,
760
+ query=query,
761
+ max_boost=max_boost,
762
+ max_penalty=max_penalty,
763
+ )
764
+
765
+
766
+ def reciprocal_rank_fusion_scores(
767
+ rankings: Mapping[str, Sequence[ContextItem]],
768
+ *,
769
+ rank_constant: float = _RRF_RANK_CONSTANT,
770
+ max_rank_per_source: int = _RRF_MAX_RANK_PER_SOURCE,
771
+ source_weights: Mapping[str, float] | None = None,
772
+ ) -> dict[tuple[str, str], float]:
773
+ if rank_constant <= 0:
774
+ raise ValueError("rank_constant must be positive")
775
+ if max_rank_per_source <= 0:
776
+ raise ValueError("max_rank_per_source must be positive")
777
+ scores: dict[tuple[str, str], float] = {}
778
+ for source, ranked_items in rankings.items():
779
+ weight = _bounded_source_weight(source_weights.get(source, 1.0) if source_weights else 1.0)
780
+ if weight <= 0:
781
+ continue
782
+ seen: set[tuple[str, str]] = set()
783
+ for rank, item in enumerate(ranked_items, start=1):
784
+ if rank > max_rank_per_source:
785
+ break
786
+ key = (item.item_type, item.item_id)
787
+ if key in seen:
788
+ continue
789
+ seen.add(key)
790
+ scores[key] = round(
791
+ scores.get(key, 0.0) + weight / (rank_constant + rank),
792
+ 8,
793
+ )
794
+ return scores
795
+
796
+
797
+ @lru_cache(maxsize=512)
798
+ def _query_expansion_terms_for_signature(
799
+ signature: tuple[tuple[str, str], ...],
800
+ ) -> _QueryExpansionTerms:
801
+ return tuple((query, reason, query_terms(query)) for query, reason in signature)
802
+
803
+
804
+ def _query_expansion_terms(plan: QueryExpansionPlan) -> _QueryExpansionTerms:
805
+ return _query_expansion_terms_for_signature(
806
+ tuple((expansion.query, expansion.reason) for expansion in plan.retrieval_queries)
807
+ )
808
+
809
+
810
+ def best_query_relevance(
811
+ plan: QueryExpansionPlan,
812
+ *,
813
+ text: str,
814
+ ) -> tuple[str, str, QueryRelevance]:
815
+ text_counts, text_variants = text_variant_profile(text)
816
+ scored = tuple(
817
+ (
818
+ query,
819
+ reason,
820
+ score_query_terms_relevance_against_profile(
821
+ terms=terms,
822
+ text_counts=text_counts,
823
+ text_variants=text_variants,
824
+ ),
825
+ )
826
+ for query, reason, terms in _query_expansion_terms(plan)
827
+ )
828
+ strict_filtered = tuple(
829
+ item
830
+ for item in scored
831
+ if not _strict_query_reason_below_minimum(reason=item[1], relevance=item[2])
832
+ )
833
+ filtered = tuple(
834
+ item
835
+ for item in strict_filtered or scored
836
+ if not _query_relevance_false_friend(plan=plan, item=item, text=text)
837
+ )
838
+ return max(
839
+ filtered or strict_filtered or scored,
840
+ key=lambda item: _query_relevance_rank_key_for_plan(plan, item),
841
+ )
842
+
843
+
844
+ def query_relevance_rank_key(
845
+ item: tuple[str, str, QueryRelevance],
846
+ ) -> tuple[bool, int, int, int, float, bool]:
847
+ _, reason, relevance = item
848
+ return (
849
+ is_query_relevance_sufficient(relevance),
850
+ _query_reason_priority_for_relevance(reason, relevance),
851
+ relevance.distinctive_term_hits,
852
+ relevance.unique_term_hits,
853
+ relevance.score_boost,
854
+ reason == "original_query",
855
+ )
856
+
857
+
858
+ def _query_relevance_rank_key_for_plan(
859
+ plan: QueryExpansionPlan,
860
+ item: tuple[str, str, QueryRelevance],
861
+ ) -> tuple[bool, int, int, int, float, bool]:
862
+ query, reason, relevance = item
863
+ priority = _query_reason_priority_for_relevance(reason, relevance)
864
+ if (
865
+ reason == "visual_text_evidence_bridge"
866
+ and _is_direct_visual_text_query(plan.original_query)
867
+ ):
868
+ priority += 2
869
+ if reason == "decomposition_quantity_count" and _is_direct_quantity_query(
870
+ plan.original_query
871
+ ):
872
+ priority += 2
873
+ if reason == "decomposition_sport_team_attribute" and any(
874
+ item.reason == "decomposition_activity_duration" for item in plan.decompositions
875
+ ):
876
+ # Preserve the domain-specific decomposition when a compound query also
877
+ # requests a generic measured-duration answer unit.
878
+ priority += 2
879
+ return (
880
+ is_query_relevance_sufficient(relevance),
881
+ priority,
882
+ relevance.distinctive_term_hits,
883
+ relevance.unique_term_hits,
884
+ relevance.score_boost,
885
+ reason == "original_query",
886
+ )
887
+
888
+
889
+ def _is_direct_visual_text_query(query: str) -> bool:
890
+ return bool(
891
+ _DIRECT_VISUAL_TEXT_QUERY_RE.search(query)
892
+ and _ARTIFACT_CONTEXT_COMPOSITE_QUERY_RE.search(query) is None
893
+ )
894
+
895
+
896
+ def _is_direct_quantity_query(query: str) -> bool:
897
+ return bool(
898
+ re.search(
899
+ r"\b(?:how many|how much|number of|count|total)\b",
900
+ query,
901
+ re.IGNORECASE,
902
+ )
903
+ )
904
+
905
+
906
+ def _query_relevance_false_friend(
907
+ *,
908
+ plan: QueryExpansionPlan,
909
+ item: tuple[str, str, QueryRelevance],
910
+ text: str,
911
+ ) -> bool:
912
+ _, reason, relevance = item
913
+ if reason != "book_suggestion_bridge":
914
+ return False
915
+ if relevance.distinctive_term_hits < 5:
916
+ return False
917
+ query = plan.original_query.casefold()
918
+ if "book" not in query:
919
+ return False
920
+ if _BOOK_SUGGESTION_EVIDENCE_CUE_RE.search(text):
921
+ return False
922
+ return _BOOK_SUGGESTION_PROJECT_FALSE_FRIEND_RE.search(text) is not None
923
+
924
+
925
+ def _strict_query_reason_below_minimum(
926
+ *,
927
+ reason: str,
928
+ relevance: QueryRelevance,
929
+ ) -> bool:
930
+ min_hits = _STRICT_QUERY_REASON_MIN_DISTINCTIVE_HITS.get(reason)
931
+ return min_hits is not None and relevance.distinctive_term_hits < min_hits
932
+
933
+
934
+ def _query_reason_priority_for_relevance(
935
+ reason: str,
936
+ relevance: QueryRelevance,
937
+ ) -> int:
938
+ min_hits = _QUERY_REASON_PRIORITY_MIN_DISTINCTIVE_HITS.get(reason, 0)
939
+ if relevance.distinctive_term_hits < min_hits:
940
+ return 0
941
+ return _QUERY_REASON_PRIORITY.get(reason, 0)
942
+
943
+
944
+ def keyword_chunk_score(
945
+ relevance: QueryRelevance,
946
+ *,
947
+ query_expansion_reason: str,
948
+ ) -> float:
949
+ distinctive_boost = min(0.028, relevance.distinctive_term_hits * 0.007)
950
+ phrase_boost = min(0.018, relevance.phrase_bigram_hits * 0.006)
951
+ frequency_boost = min(0.014, relevance.capped_frequency_hits * 0.0015)
952
+ expansion_boost = 0.004 if query_expansion_reason != "original_query" else 0.0
953
+ reason_boost = _KEYWORD_EXPANSION_REASON_BOOSTS.get(query_expansion_reason, 0.0)
954
+ score_cap = _KEYWORD_EXPANSION_SCORE_CAPS.get(query_expansion_reason, 0.93)
955
+ return min(
956
+ score_cap,
957
+ round(
958
+ 0.75
959
+ + relevance.score_boost
960
+ + distinctive_boost
961
+ + phrase_boost
962
+ + frequency_boost
963
+ + expansion_boost
964
+ + reason_boost,
965
+ 4,
966
+ ),
967
+ )
968
+
969
+
970
+ def keyword_chunk_source_score_boost(
971
+ relevance: QueryRelevance,
972
+ *,
973
+ query_expansion_reason: str,
974
+ source_external_id: str,
975
+ ) -> float:
976
+ """Prefer compact derived observations/summaries for broad aggregation answers."""
977
+
978
+ if query_expansion_reason not in _DERIVED_SUMMARY_SOURCE_REASONS:
979
+ return 0.0
980
+ normalized_source_id = source_external_id.casefold()
981
+ if query_expansion_reason in _ACTIVITY_OBSERVATION_SOURCE_REASONS:
982
+ allowed_suffixes = _ACTIVITY_EVIDENCE_SOURCE_SUFFIXES
983
+ elif query_expansion_reason in _TURN_ONLY_EVIDENCE_SOURCE_REASONS:
984
+ allowed_suffixes = (":turn",)
985
+ elif (
986
+ query_expansion_reason.startswith("attribute_")
987
+ or query_expansion_reason in _PRECISE_EVIDENCE_SOURCE_REASONS
988
+ ):
989
+ allowed_suffixes = _ATTRIBUTE_EVIDENCE_SOURCE_SUFFIXES
990
+ else:
991
+ allowed_suffixes = _DERIVED_SUMMARY_SOURCE_SUFFIXES
992
+ if not normalized_source_id.endswith(allowed_suffixes):
993
+ return 0.0
994
+ min_hits = _DERIVED_SUMMARY_SOURCE_MIN_DISTINCTIVE_HITS.get(
995
+ query_expansion_reason,
996
+ _ACTIVITY_OBSERVATION_MIN_DISTINCTIVE_HITS,
997
+ )
998
+ if relevance.distinctive_term_hits < min_hits:
999
+ return 0.0
1000
+ if (
1001
+ query_expansion_reason in _ACTIVITY_OBSERVATION_SOURCE_REASONS
1002
+ and relevance.distinctive_term_hits >= _ACTIVITY_DERIVED_SUMMARY_STRONG_HITS
1003
+ ):
1004
+ return _ACTIVITY_DERIVED_SUMMARY_SOURCE_BOOST
1005
+ return _DERIVED_SUMMARY_SOURCE_BOOST
1006
+
1007
+
1008
+ def query_expansion_reason_priority(reason: str) -> int:
1009
+ return _CONTEXT_ITEM_REASON_PRIORITY.get(reason, 0)
1010
+
1011
+
1012
+ def apply_keyword_chunk_source_score_boost(
1013
+ score: float,
1014
+ relevance: QueryRelevance,
1015
+ *,
1016
+ query_expansion_reason: str,
1017
+ source_external_id: str,
1018
+ ) -> tuple[float, float]:
1019
+ boost = keyword_chunk_source_score_boost(
1020
+ relevance,
1021
+ query_expansion_reason=query_expansion_reason,
1022
+ source_external_id=source_external_id,
1023
+ )
1024
+ if boost <= 0:
1025
+ return score, 0.0
1026
+ cap = (
1027
+ _ACTIVITY_DERIVED_SUMMARY_SOURCE_CAP
1028
+ if (
1029
+ query_expansion_reason in _ACTIVITY_OBSERVATION_SOURCE_REASONS
1030
+ and boost >= _ACTIVITY_DERIVED_SUMMARY_SOURCE_BOOST
1031
+ )
1032
+ else _DERIVED_SUMMARY_SOURCE_CAP
1033
+ )
1034
+ return (
1035
+ min(cap, round(score + boost, 4)),
1036
+ boost,
1037
+ )
1038
+
1039
+
1040
+ def _should_replace_context_item(*, candidate: ContextItem, existing: ContextItem) -> bool:
1041
+ score_delta = round(candidate.score - existing.score, 8)
1042
+ if abs(score_delta) <= _DUPLICATE_SOURCE_SCORE_TOLERANCE:
1043
+ candidate_priority = _context_item_reason_priority(candidate)
1044
+ existing_priority = _context_item_reason_priority(existing)
1045
+ if candidate_priority != existing_priority:
1046
+ return candidate_priority > existing_priority
1047
+ if score_delta != 0:
1048
+ return score_delta > 0
1049
+ return context_duplicate_primary_key(candidate) < context_duplicate_primary_key(existing)
1050
+ return score_delta > 0
1051
+
1052
+
1053
+ def _context_item_reason_priority(item: ContextItem) -> float:
1054
+ diagnostics = safe_diagnostic_mapping(item.diagnostics)
1055
+ signals = safe_score_signals(diagnostics.get("score_signals"))
1056
+ value = signals.get("query_expansion_reason_priority")
1057
+ if isinstance(value, bool):
1058
+ return 0.0
1059
+ if isinstance(value, int | float):
1060
+ return float(value)
1061
+ return 0.0
1062
+
1063
+
1064
+ def _merge_context_items(*, primary: ContextItem, secondary: ContextItem) -> ContextItem:
1065
+ source_refs = _merge_source_refs(primary.source_refs, secondary.source_refs)
1066
+ retrieval_sources = merge_diagnostic_retrieval_sources(
1067
+ primary.diagnostics,
1068
+ secondary.diagnostics,
1069
+ )
1070
+ hybrid_boost = _hybrid_boost(
1071
+ retrieval_source_count=len(retrieval_sources),
1072
+ source_ref_count=len(source_refs),
1073
+ )
1074
+ score = min(0.99, round(max(primary.score, secondary.score) + hybrid_boost, 4))
1075
+ body_item = _preferred_merge_body_item(primary=primary, secondary=secondary)
1076
+ return replace(
1077
+ body_item,
1078
+ score=score,
1079
+ source_refs=source_refs,
1080
+ diagnostics=merge_context_diagnostics(
1081
+ primary=primary.diagnostics,
1082
+ secondary=secondary.diagnostics,
1083
+ retrieval_sources=retrieval_sources,
1084
+ source_ref_count=len(source_refs),
1085
+ primary_score=primary.score,
1086
+ secondary_score=secondary.score,
1087
+ hybrid_boost=hybrid_boost,
1088
+ ),
1089
+ )
1090
+
1091
+
1092
+ def _preferred_merge_body_item(*, primary: ContextItem, secondary: ContextItem) -> ContextItem:
1093
+ if (primary.item_type, primary.item_id) != (secondary.item_type, secondary.item_id):
1094
+ return primary
1095
+ primary_exact_answer_rank = _exact_source_sibling_answer_body_rank(primary)
1096
+ secondary_exact_answer_rank = _exact_source_sibling_answer_body_rank(secondary)
1097
+ if primary_exact_answer_rank != secondary_exact_answer_rank:
1098
+ return primary if primary_exact_answer_rank < secondary_exact_answer_rank else secondary
1099
+ primary_exact_source_sibling = _is_strong_exact_source_sibling_turn(primary)
1100
+ secondary_exact_source_sibling = _is_strong_exact_source_sibling_turn(secondary)
1101
+ if primary_exact_source_sibling and not secondary_exact_source_sibling:
1102
+ return primary
1103
+ if secondary_exact_source_sibling and not primary_exact_source_sibling:
1104
+ return secondary
1105
+ primary_sources = set(diagnostic_retrieval_sources(primary.diagnostics))
1106
+ secondary_sources = set(diagnostic_retrieval_sources(secondary.diagnostics))
1107
+ if (
1108
+ "keyword_aggregation_chunks" in secondary_sources
1109
+ and "keyword_aggregation_chunks" not in primary_sources
1110
+ ):
1111
+ return secondary
1112
+ if (
1113
+ "keyword_aggregation_chunks" in primary_sources
1114
+ and "keyword_aggregation_chunks" not in secondary_sources
1115
+ ):
1116
+ return primary
1117
+ return primary
1118
+
1119
+
1120
+ def _exact_source_sibling_answer_body_rank(item: ContextItem) -> int:
1121
+ turn_ref_count = sum(1 for ref in item.source_refs if _source_ref_is_turn(ref))
1122
+ if turn_ref_count <= 0:
1123
+ return 4
1124
+ if _has_dialogue_turn_body_for_source_ref(item):
1125
+ return 0 if len(item.source_refs) == 1 else 1
1126
+ if "keyword_source_sibling_chunks" not in diagnostic_retrieval_sources(item.diagnostics):
1127
+ return 4
1128
+ diagnostics = safe_diagnostic_mapping(item.diagnostics)
1129
+ signals = safe_score_signals(diagnostics.get("score_signals"))
1130
+ if _numeric_signal(signals.get("source_sibling_answer_evidence")) <= 0:
1131
+ return 4
1132
+ if len(item.source_refs) == 1:
1133
+ return 2
1134
+ return 3
1135
+
1136
+
1137
+ def _has_dialogue_turn_body_for_source_ref(item: ContextItem) -> bool:
1138
+ for ref in item.source_refs:
1139
+ marker = _source_ref_dialogue_marker(ref)
1140
+ if marker and _text_has_dialogue_turn_body(item.text, marker=marker):
1141
+ return True
1142
+ return False
1143
+
1144
+
1145
+ def _source_ref_dialogue_marker(ref: SourceRef) -> str:
1146
+ match = _DIALOGUE_MARKER_RE.search(str(ref.source_id or ""))
1147
+ return match.group(0) if match is not None else ""
1148
+
1149
+
1150
+ def _text_has_dialogue_turn_body(text: str, *, marker: str) -> bool:
1151
+ for match in re.finditer(rf"\b{re.escape(marker)}\b", text):
1152
+ following = text[match.end() : match.end() + 64]
1153
+ if re.match(r"\s+(?!\.\.\.)[A-Z][^:\n]{0,40}:", following):
1154
+ return True
1155
+ return False
1156
+
1157
+
1158
+ def _is_strong_exact_source_sibling_turn(item: ContextItem) -> bool:
1159
+ if "keyword_source_sibling_chunks" not in diagnostic_retrieval_sources(item.diagnostics):
1160
+ return False
1161
+ if not any(_source_ref_is_turn(ref) for ref in item.source_refs):
1162
+ return False
1163
+ diagnostics = safe_diagnostic_mapping(item.diagnostics)
1164
+ signals = safe_score_signals(diagnostics.get("score_signals"))
1165
+ if _numeric_signal(signals.get("source_sibling_answer_evidence")) > 0:
1166
+ return True
1167
+ if _numeric_signal(signals.get("query_expansion_reason_priority")) < 3:
1168
+ return False
1169
+ return _numeric_signal(signals.get("distinctive_term_hits")) >= 4
1170
+
1171
+
1172
+ def _source_ref_is_turn(ref: SourceRef) -> bool:
1173
+ return str(ref.source_id).casefold().endswith(":turn")
1174
+
1175
+
1176
+ def _merge_source_refs(
1177
+ primary: tuple[SourceRef, ...],
1178
+ secondary: tuple[SourceRef, ...],
1179
+ ) -> tuple[SourceRef, ...]:
1180
+ refs: list[SourceRef] = []
1181
+ seen: set[tuple[object, ...]] = set()
1182
+ for ref in (*primary, *secondary):
1183
+ key = (
1184
+ ref.source_type,
1185
+ ref.source_id,
1186
+ ref.chunk_id,
1187
+ ref.char_start,
1188
+ ref.char_end,
1189
+ ref.quote_preview,
1190
+ ref.page_number,
1191
+ ref.time_start_ms,
1192
+ ref.time_end_ms,
1193
+ ref.bbox,
1194
+ )
1195
+ if key in seen:
1196
+ continue
1197
+ seen.add(key)
1198
+ refs.append(ref)
1199
+ if len(refs) >= MAX_SOURCE_REFS_PER_ITEM:
1200
+ break
1201
+ return tuple(refs)
1202
+
1203
+
1204
+ def _hybrid_boost(*, retrieval_source_count: int, source_ref_count: int) -> float:
1205
+ if retrieval_source_count <= 1:
1206
+ return 0.0
1207
+ source_boost = 0.035 * (retrieval_source_count - 1)
1208
+ provenance_boost = 0.01 * min(3, max(0, source_ref_count - 1))
1209
+ return min(0.08, source_boost + provenance_boost)
1210
+
1211
+
1212
+ def _ranked_items_by_retrieval_source(
1213
+ items: tuple[ContextItem, ...],
1214
+ ) -> dict[str, tuple[ContextItem, ...]]:
1215
+ by_source: dict[str, list[ContextItem]] = {}
1216
+ for item in items:
1217
+ sources = diagnostic_retrieval_sources(item.diagnostics)
1218
+ for source in sources:
1219
+ by_source.setdefault(source, []).append(item)
1220
+ return {
1221
+ source: tuple(sorted(source_items, key=context_rank_key))
1222
+ for source, source_items in by_source.items()
1223
+ }
1224
+
1225
+
1226
+ def _bounded_source_weight(value: float) -> float:
1227
+ if value <= 0:
1228
+ return 0.0
1229
+ return min(5.0, float(value))
1230
+
1231
+
1232
+ @dataclass(frozen=True)
1233
+ class _Bm25Document:
1234
+ item: ContextItem
1235
+ term_frequencies: tuple[int, ...]
1236
+ length: int
1237
+
1238
+
1239
+ @dataclass(frozen=True)
1240
+ class _Bm25PreparedItem:
1241
+ item: ContextItem
1242
+ text_counts: Mapping[str, int]
1243
+ length: int
1244
+
1245
+
1246
+ @dataclass(frozen=True)
1247
+ class _Bm25QueryMatch:
1248
+ normalized_score: float
1249
+ query_term_count: int
1250
+ matched_term_count: int
1251
+ query_reason: str
1252
+ query_coverage: float = 0.0
1253
+
1254
+
1255
+ _Bm25TermFrequencyCache = dict[tuple[int, LexicalQueryTerm], int]
1256
+
1257
+
1258
+ @dataclass(frozen=True)
1259
+ class _DeterministicRerankSignals:
1260
+ boost: float
1261
+ penalty: float
1262
+ reasons: tuple[str, ...]
1263
+ rank_signals: tuple[tuple[str, float], ...]
1264
+ source_count: int
1265
+ strong_source_count: int
1266
+ coverage_ratio: float
1267
+ anchor_conflict: bool
1268
+ query_reason: str
1269
+
1270
+ @property
1271
+ def net_adjustment(self) -> float:
1272
+ return round(self.boost - self.penalty, 4)
1273
+
1274
+
1275
+ @dataclass(frozen=True)
1276
+ class _RequirementCoverageSignals:
1277
+ ratio: float
1278
+ requested_anchor_kinds: frozenset[str]
1279
+ covered_anchor_kinds: frozenset[str]
1280
+ missing_anchor_kinds: frozenset[str]
1281
+ requested_modalities: frozenset[str]
1282
+ missing_modalities: frozenset[str]
1283
+ requested_evidence_features: frozenset[str]
1284
+ missing_evidence_features: frozenset[str]
1285
+ requested_answer_shapes: frozenset[str]
1286
+ covered_answer_shapes: frozenset[str]
1287
+ missing_answer_shapes: frozenset[str]
1288
+
1289
+ @property
1290
+ def answer_shape_ratio(self) -> float:
1291
+ if not self.requested_answer_shapes:
1292
+ return 0.0
1293
+ return len(self.covered_answer_shapes) / len(self.requested_answer_shapes)
1294
+
1295
+
1296
+ @dataclass(frozen=True)
1297
+ class _SamePersonTopicProfile:
1298
+ item: ContextItem
1299
+ matches_query_person: bool
1300
+ has_anchor_conflict: bool
1301
+ topic_hits: int
1302
+ covers_topic: bool
1303
+ explicit_topic_decoy_shape: bool
1304
+
1305
+
1306
+ def _best_bm25_query_matches(
1307
+ *,
1308
+ items: tuple[ContextItem, ...],
1309
+ plan: QueryExpansionPlan,
1310
+ k1: float,
1311
+ b: float,
1312
+ bm25_text_stats_cache: dict[str, tuple[Mapping[str, int], int]] | None = None,
1313
+ ) -> tuple[_Bm25QueryMatch, ...]:
1314
+ prepared_items = tuple(
1315
+ _bm25_prepared_item(item, text_stats_cache=bm25_text_stats_cache)
1316
+ for item in items
1317
+ )
1318
+ term_frequency_cache: _Bm25TermFrequencyCache = {}
1319
+ best_matches = tuple(
1320
+ _Bm25QueryMatch(
1321
+ normalized_score=0.0,
1322
+ query_term_count=0,
1323
+ matched_term_count=0,
1324
+ query_reason="",
1325
+ query_coverage=0.0,
1326
+ )
1327
+ for _ in items
1328
+ )
1329
+ for _, reason, terms in _query_expansion_terms(plan):
1330
+ if not terms:
1331
+ continue
1332
+ documents, raw_scores = _bm25_raw_scores_for_prepared(
1333
+ prepared_items=prepared_items,
1334
+ terms=terms,
1335
+ k1=k1,
1336
+ b=b,
1337
+ term_frequency_cache=term_frequency_cache,
1338
+ )
1339
+ max_raw_score = max(raw_scores, default=0.0)
1340
+ if max_raw_score <= 0:
1341
+ continue
1342
+ query_matches: list[_Bm25QueryMatch] = []
1343
+ for document, raw_score in zip(documents, raw_scores, strict=True):
1344
+ matched_term_count = sum(1 for frequency in document.term_frequencies if frequency > 0)
1345
+ coverage = _bm25_query_coverage(
1346
+ matched_term_count=matched_term_count,
1347
+ query_term_count=len(terms),
1348
+ )
1349
+ query_matches.append(
1350
+ _Bm25QueryMatch(
1351
+ normalized_score=round(
1352
+ min(1.0, raw_score / max_raw_score) * coverage,
1353
+ 6,
1354
+ ),
1355
+ query_term_count=len(terms),
1356
+ matched_term_count=matched_term_count,
1357
+ query_reason=reason,
1358
+ query_coverage=coverage,
1359
+ )
1360
+ )
1361
+ best_matches = tuple(
1362
+ _select_bm25_query_match(best, candidate)
1363
+ for best, candidate in zip(best_matches, tuple(query_matches), strict=True)
1364
+ )
1365
+ return best_matches
1366
+
1367
+
1368
+ def _select_bm25_query_match(
1369
+ best: _Bm25QueryMatch,
1370
+ candidate: _Bm25QueryMatch,
1371
+ ) -> _Bm25QueryMatch:
1372
+ if candidate.matched_term_count <= 0:
1373
+ return best
1374
+ if candidate.normalized_score > best.normalized_score:
1375
+ return candidate
1376
+ if candidate.normalized_score < best.normalized_score:
1377
+ return best
1378
+ if candidate.matched_term_count > best.matched_term_count:
1379
+ return candidate
1380
+ if candidate.matched_term_count < best.matched_term_count:
1381
+ return best
1382
+ candidate_priority = _QUERY_REASON_PRIORITY.get(candidate.query_reason, 0)
1383
+ best_priority = _QUERY_REASON_PRIORITY.get(best.query_reason, 0)
1384
+ if candidate_priority > best_priority:
1385
+ return candidate
1386
+ if candidate_priority < best_priority:
1387
+ return best
1388
+ if candidate.query_reason == "original_query" and best.query_reason != "original_query":
1389
+ return candidate
1390
+ return best
1391
+
1392
+
1393
+ def _bm25_raw_scores(
1394
+ *,
1395
+ items: tuple[ContextItem, ...],
1396
+ terms: tuple[LexicalQueryTerm, ...],
1397
+ k1: float,
1398
+ b: float,
1399
+ ) -> tuple[tuple[_Bm25Document, ...], tuple[float, ...]]:
1400
+ prepared_items = tuple(_bm25_prepared_item(item) for item in items)
1401
+ return _bm25_raw_scores_for_prepared(
1402
+ prepared_items=prepared_items,
1403
+ terms=terms,
1404
+ k1=k1,
1405
+ b=b,
1406
+ )
1407
+
1408
+
1409
+ def _bm25_raw_scores_for_prepared(
1410
+ *,
1411
+ prepared_items: tuple[_Bm25PreparedItem, ...],
1412
+ terms: tuple[LexicalQueryTerm, ...],
1413
+ k1: float,
1414
+ b: float,
1415
+ term_frequency_cache: _Bm25TermFrequencyCache | None = None,
1416
+ ) -> tuple[tuple[_Bm25Document, ...], tuple[float, ...]]:
1417
+ documents = tuple(
1418
+ _bm25_document(
1419
+ prepared=item,
1420
+ prepared_index=index,
1421
+ terms=terms,
1422
+ term_frequency_cache=term_frequency_cache,
1423
+ )
1424
+ for index, item in enumerate(prepared_items)
1425
+ )
1426
+ average_length = sum(document.length for document in documents) / len(documents)
1427
+ document_frequencies = tuple(
1428
+ sum(1 for document in documents if document.term_frequencies[index] > 0)
1429
+ for index, _ in enumerate(terms)
1430
+ )
1431
+ raw_scores = tuple(
1432
+ _bm25_score(
1433
+ term_frequencies=document.term_frequencies,
1434
+ document_frequencies=document_frequencies,
1435
+ document_count=len(documents),
1436
+ document_length=document.length,
1437
+ average_document_length=max(1.0, average_length),
1438
+ k1=k1,
1439
+ b=b,
1440
+ )
1441
+ for document in documents
1442
+ )
1443
+ return documents, raw_scores
1444
+
1445
+
1446
+ def _bm25_query_coverage(*, matched_term_count: int, query_term_count: int) -> float:
1447
+ if matched_term_count <= 0 or query_term_count <= 0:
1448
+ return 0.0
1449
+ denominator = min(8, max(3, query_term_count))
1450
+ return round(min(1.0, matched_term_count / denominator), 4)
1451
+
1452
+
1453
+ def _bm25_document(
1454
+ *,
1455
+ prepared: _Bm25PreparedItem,
1456
+ prepared_index: int,
1457
+ terms: tuple[LexicalQueryTerm, ...],
1458
+ term_frequency_cache: _Bm25TermFrequencyCache | None = None,
1459
+ ) -> _Bm25Document:
1460
+ return _Bm25Document(
1461
+ item=prepared.item,
1462
+ term_frequencies=tuple(
1463
+ _bm25_term_frequency(
1464
+ prepared=prepared,
1465
+ prepared_index=prepared_index,
1466
+ term=term,
1467
+ term_frequency_cache=term_frequency_cache,
1468
+ )
1469
+ for term in terms
1470
+ ),
1471
+ length=prepared.length,
1472
+ )
1473
+
1474
+
1475
+ def _bm25_term_frequency(
1476
+ *,
1477
+ prepared: _Bm25PreparedItem,
1478
+ prepared_index: int,
1479
+ term: LexicalQueryTerm,
1480
+ term_frequency_cache: _Bm25TermFrequencyCache | None,
1481
+ ) -> int:
1482
+ if term_frequency_cache is None:
1483
+ return query_term_frequency(term, prepared.text_counts)
1484
+ cache_key = (prepared_index, term)
1485
+ cached = term_frequency_cache.get(cache_key)
1486
+ if cached is not None:
1487
+ return cached
1488
+ frequency = query_term_frequency(term, prepared.text_counts)
1489
+ term_frequency_cache[cache_key] = frequency
1490
+ return frequency
1491
+
1492
+
1493
+ def _bm25_prepared_item(
1494
+ item: ContextItem,
1495
+ *,
1496
+ text_stats_cache: dict[str, tuple[Mapping[str, int], int]] | None = None,
1497
+ ) -> _Bm25PreparedItem:
1498
+ if text_stats_cache is not None:
1499
+ cached = text_stats_cache.get(item.text)
1500
+ if cached is not None:
1501
+ counts, length = cached
1502
+ return _Bm25PreparedItem(item=item, text_counts=counts, length=length)
1503
+ counts, sequence_length = text_variant_stats(item.text)
1504
+ length = max(1, sequence_length)
1505
+ if text_stats_cache is not None:
1506
+ text_stats_cache[item.text] = (counts, length)
1507
+ return _Bm25PreparedItem(
1508
+ item=item,
1509
+ text_counts=counts,
1510
+ length=length,
1511
+ )
1512
+
1513
+
1514
+ def _bm25_score(
1515
+ *,
1516
+ term_frequencies: tuple[int, ...],
1517
+ document_frequencies: tuple[int, ...],
1518
+ document_count: int,
1519
+ document_length: int,
1520
+ average_document_length: float,
1521
+ k1: float,
1522
+ b: float,
1523
+ ) -> float:
1524
+ score = 0.0
1525
+ length_ratio = document_length / average_document_length
1526
+ normalizer = k1 * (1 - b + b * length_ratio)
1527
+ for frequency, document_frequency in zip(
1528
+ term_frequencies,
1529
+ document_frequencies,
1530
+ strict=True,
1531
+ ):
1532
+ if frequency <= 0 or document_frequency <= 0:
1533
+ continue
1534
+ idf = math.log(1 + (document_count - document_frequency + 0.5) / (document_frequency + 0.5))
1535
+ score += idf * (frequency * (k1 + 1)) / (frequency + normalizer)
1536
+ return round(score, 8)
1537
+
1538
+
1539
+ def _with_bm25_lexical_boost(
1540
+ item: ContextItem,
1541
+ *,
1542
+ raw_score: float,
1543
+ max_raw_score: float,
1544
+ max_boost: float,
1545
+ query_term_count: int,
1546
+ matched_term_count: int,
1547
+ query_reason: str = "original_query",
1548
+ query_coverage: float | None = None,
1549
+ ) -> ContextItem:
1550
+ if _bm25_lexical_already_applied(item):
1551
+ return item
1552
+ if raw_score <= 0 or max_raw_score <= 0 or matched_term_count <= 0:
1553
+ return item
1554
+ normalized_score = min(1.0, raw_score / max_raw_score)
1555
+ boost = round(max_boost * normalized_score, 4)
1556
+ if boost <= 0:
1557
+ return item
1558
+ diagnostics = normalize_context_diagnostics(item.diagnostics)
1559
+ diagnostics["bm25_lexical_reason"] = "BM25 lexical rerank over candidate pool"
1560
+ diagnostics["score_signals"] = {
1561
+ **safe_score_signals(diagnostics.get("score_signals")),
1562
+ "bm25_lexical_raw_score": round(raw_score, 6),
1563
+ "bm25_lexical_normalized_score": round(normalized_score, 4),
1564
+ "bm25_lexical_boost": boost,
1565
+ "bm25_lexical_query_term_count": query_term_count,
1566
+ "bm25_lexical_matched_term_count": matched_term_count,
1567
+ "bm25_lexical_query_reason": query_reason,
1568
+ }
1569
+ if query_coverage is not None:
1570
+ diagnostics["score_signals"]["bm25_lexical_query_coverage"] = round(
1571
+ query_coverage,
1572
+ 4,
1573
+ )
1574
+ diagnostics["provenance"] = {
1575
+ **safe_diagnostic_mapping(diagnostics.get("provenance")),
1576
+ "bm25_lexical_applied": True,
1577
+ "bm25_lexical_query_reason": query_reason,
1578
+ }
1579
+ return replace(
1580
+ item,
1581
+ score=min(0.99, round(item.score + boost, 4)),
1582
+ diagnostics=normalize_context_diagnostics(diagnostics),
1583
+ )
1584
+
1585
+
1586
+ def _bm25_lexical_already_applied(item: ContextItem) -> bool:
1587
+ return _provenance_flag_is_true(item.diagnostics, "bm25_lexical_applied")
1588
+
1589
+
1590
+ def _with_query_anchor_intent_boost(
1591
+ item: ContextItem,
1592
+ *,
1593
+ match: object,
1594
+ max_boost: float,
1595
+ ) -> ContextItem:
1596
+ if match is None or _query_anchor_intent_already_applied(item):
1597
+ return item
1598
+ try:
1599
+ raw_boost = float(getattr(match, "score_boost", 0.0))
1600
+ except (TypeError, ValueError):
1601
+ return item
1602
+ boost = min(max_boost, max(0.0, round(raw_boost, 4)))
1603
+ if boost <= 0:
1604
+ return item
1605
+ diagnostics = normalize_context_diagnostics(item.diagnostics)
1606
+ reasons = tuple(getattr(match, "reasons", ()) or ())
1607
+ matched_keys = tuple(getattr(match, "matched_keys", ()) or ())
1608
+ diagnostics["query_anchor_intent_reason"] = "query anchor identity matched context item text"
1609
+ diagnostics["score_signals"] = {
1610
+ **safe_score_signals(diagnostics.get("score_signals")),
1611
+ "query_anchor_intent_boost": boost,
1612
+ "query_anchor_intent_reason_count": len(reasons),
1613
+ "query_anchor_intent_matched_key_count": len(matched_keys),
1614
+ }
1615
+ diagnostics["provenance"] = {
1616
+ **safe_diagnostic_mapping(diagnostics.get("provenance")),
1617
+ "query_anchor_intent_applied": True,
1618
+ "query_anchor_intent_reasons": list(reasons[:8]),
1619
+ "query_anchor_intent_matched_keys": list(matched_keys[:8]),
1620
+ }
1621
+ return replace(
1622
+ item,
1623
+ score=min(0.99, round(item.score + boost, 4)),
1624
+ diagnostics=normalize_context_diagnostics(diagnostics),
1625
+ )
1626
+
1627
+
1628
+ def _query_anchor_intent_already_applied(item: ContextItem) -> bool:
1629
+ return _provenance_flag_is_true(item.diagnostics, "query_anchor_intent_applied")
1630
+
1631
+
1632
+ def _with_context_requirement_boost(
1633
+ item: ContextItem,
1634
+ *,
1635
+ query: str,
1636
+ query_anchor_intent: QueryAnchorIntent,
1637
+ requested_anchor_kinds: frozenset[str],
1638
+ requested_modalities: frozenset[str],
1639
+ requested_features: frozenset[str],
1640
+ requested_answer_shapes: frozenset[str],
1641
+ max_boost: float,
1642
+ ) -> ContextItem:
1643
+ if _context_requirement_boost_already_applied(item):
1644
+ return item
1645
+ normalized_item = normalize_context_item_diagnostics(item)
1646
+ coverage = context_requirement_coverage(
1647
+ query=query,
1648
+ query_anchor_intent=query_anchor_intent,
1649
+ items=(normalized_item,),
1650
+ )
1651
+ matched_anchor_kinds = _sorted_coverage_matches(
1652
+ requested_anchor_kinds,
1653
+ coverage.get("covered_anchor_kinds"),
1654
+ )
1655
+ matched_modalities = _sorted_coverage_matches(
1656
+ requested_modalities,
1657
+ coverage.get("covered_modalities"),
1658
+ )
1659
+ matched_features = _sorted_coverage_matches(
1660
+ requested_features,
1661
+ coverage.get("covered_evidence_features"),
1662
+ )
1663
+ covered_answer_shape_value = coverage.get("covered_answer_shapes")
1664
+ covered_answer_shapes = _coverage_value_set(covered_answer_shape_value)
1665
+ matched_answer_shapes = _sorted_coverage_matches(
1666
+ requested_answer_shapes,
1667
+ covered_answer_shape_value,
1668
+ )
1669
+ score_boosted_answer_shapes = tuple(
1670
+ shape for shape in matched_answer_shapes if shape in _GENERIC_BOOSTABLE_ANSWER_SHAPES
1671
+ )
1672
+ typed_unit_answer_shapes = tuple(
1673
+ shape for shape in matched_answer_shapes if is_typed_answer_unit_shape(shape)
1674
+ )
1675
+ typed_unit_mismatch_shapes = _typed_answer_unit_mismatch_shapes(
1676
+ requested_answer_shapes=requested_answer_shapes,
1677
+ covered_answer_shapes=covered_answer_shapes,
1678
+ )
1679
+ exact_count_cardinality_boost = (
1680
+ _CONTEXT_REQUIREMENT_EXACT_COUNT_CARDINALITY_BOOST
1681
+ if "count" in score_boosted_answer_shapes
1682
+ and has_exact_count_cardinality_evidence(normalized_item.text)
1683
+ else 0.0
1684
+ )
1685
+ typed_unit_mismatch_penalty = (
1686
+ _CONTEXT_REQUIREMENT_TYPED_ANSWER_UNIT_MISMATCH_PENALTY
1687
+ if typed_unit_mismatch_shapes
1688
+ else 0.0
1689
+ )
1690
+ raw_boost = (
1691
+ len(matched_anchor_kinds) * _CONTEXT_REQUIREMENT_ANCHOR_BOOST
1692
+ + len(matched_modalities) * _CONTEXT_REQUIREMENT_MODALITY_BOOST
1693
+ + len(matched_features) * _CONTEXT_REQUIREMENT_FEATURE_BOOST
1694
+ + len(score_boosted_answer_shapes) * _CONTEXT_REQUIREMENT_ANSWER_SHAPE_BOOST
1695
+ + exact_count_cardinality_boost
1696
+ + len(typed_unit_answer_shapes) * _CONTEXT_REQUIREMENT_TYPED_ANSWER_UNIT_BOOST
1697
+ )
1698
+ boost = min(max_boost, round(raw_boost, 4))
1699
+ if boost <= 0 and typed_unit_mismatch_penalty <= 0:
1700
+ return item
1701
+ diagnostics = normalize_context_diagnostics(normalized_item.diagnostics)
1702
+ diagnostics["context_requirement_reason"] = "explicit query requirement matched item evidence"
1703
+ diagnostics["score_signals"] = {
1704
+ **safe_score_signals(diagnostics.get("score_signals")),
1705
+ "context_requirement_boost": boost,
1706
+ "context_requirement_matched_anchor_kind_count": len(matched_anchor_kinds),
1707
+ "context_requirement_matched_modality_count": len(matched_modalities),
1708
+ "context_requirement_matched_feature_count": len(matched_features),
1709
+ "context_requirement_matched_answer_shape_count": len(score_boosted_answer_shapes),
1710
+ "context_requirement_matched_typed_answer_unit_count": len(typed_unit_answer_shapes),
1711
+ "context_requirement_typed_answer_unit_mismatch_count": len(
1712
+ typed_unit_mismatch_shapes
1713
+ ),
1714
+ **(
1715
+ {
1716
+ "context_requirement_exact_count_cardinality_boost": (
1717
+ exact_count_cardinality_boost
1718
+ )
1719
+ }
1720
+ if exact_count_cardinality_boost > 0
1721
+ else {}
1722
+ ),
1723
+ **(
1724
+ {
1725
+ "context_requirement_typed_answer_unit_boost": (
1726
+ len(typed_unit_answer_shapes)
1727
+ * _CONTEXT_REQUIREMENT_TYPED_ANSWER_UNIT_BOOST
1728
+ )
1729
+ }
1730
+ if typed_unit_answer_shapes
1731
+ else {}
1732
+ ),
1733
+ **(
1734
+ {
1735
+ "context_requirement_typed_answer_unit_mismatch_penalty": (
1736
+ typed_unit_mismatch_penalty
1737
+ )
1738
+ }
1739
+ if typed_unit_mismatch_penalty > 0
1740
+ else {}
1741
+ ),
1742
+ }
1743
+ diagnostics["provenance"] = {
1744
+ **safe_diagnostic_mapping(diagnostics.get("provenance")),
1745
+ "context_requirement_boost_applied": True,
1746
+ "context_requirement_matched_anchor_kinds": list(matched_anchor_kinds),
1747
+ "context_requirement_matched_modalities": list(matched_modalities),
1748
+ "context_requirement_matched_evidence_features": list(matched_features),
1749
+ "context_requirement_matched_answer_shapes": list(matched_answer_shapes),
1750
+ "context_requirement_mismatched_answer_unit_shapes": list(
1751
+ typed_unit_mismatch_shapes
1752
+ ),
1753
+ }
1754
+ return replace(
1755
+ normalized_item,
1756
+ score=min(
1757
+ 0.99,
1758
+ max(
1759
+ 0.0,
1760
+ round(normalized_item.score + boost - typed_unit_mismatch_penalty, 4),
1761
+ ),
1762
+ ),
1763
+ diagnostics=normalize_context_diagnostics(diagnostics),
1764
+ )
1765
+
1766
+
1767
+ def _context_requirement_boost_already_applied(item: ContextItem) -> bool:
1768
+ return _provenance_flag_is_true(item.diagnostics, "context_requirement_boost_applied")
1769
+
1770
+
1771
+ def _typed_answer_unit_mismatch_shapes(
1772
+ *,
1773
+ requested_answer_shapes: frozenset[str],
1774
+ covered_answer_shapes: frozenset[str],
1775
+ ) -> tuple[str, ...]:
1776
+ requested_units = frozenset(
1777
+ shape for shape in requested_answer_shapes if is_typed_answer_unit_shape(shape)
1778
+ )
1779
+ if not requested_units:
1780
+ return ()
1781
+ covered_units = frozenset(
1782
+ shape for shape in covered_answer_shapes if is_typed_answer_unit_shape(shape)
1783
+ )
1784
+ if not covered_units or requested_units & covered_units:
1785
+ return ()
1786
+ return tuple(sorted(covered_units))
1787
+
1788
+
1789
+ def _with_deterministic_rerank_adjustment(
1790
+ item: ContextItem,
1791
+ *,
1792
+ query: str,
1793
+ plan: QueryExpansionPlan,
1794
+ query_anchor_intent: QueryAnchorIntent,
1795
+ temporal_query_intent: TemporalQueryIntent,
1796
+ requested_total: int,
1797
+ has_multi_evidence_aggregation_candidate: bool,
1798
+ query_relevance_cache: dict[str, tuple[str, str, QueryRelevance]] | None,
1799
+ max_boost: float,
1800
+ max_penalty: float,
1801
+ ) -> ContextItem:
1802
+ normalized_item = normalize_context_item_diagnostics(item)
1803
+ if _deterministic_rerank_already_applied_in_diagnostics(normalized_item.diagnostics):
1804
+ return item
1805
+ signals = _deterministic_rerank_signals(
1806
+ normalized_item,
1807
+ query=query,
1808
+ plan=plan,
1809
+ query_anchor_intent=query_anchor_intent,
1810
+ temporal_query_intent=temporal_query_intent,
1811
+ requested_total=requested_total,
1812
+ has_multi_evidence_aggregation_candidate=has_multi_evidence_aggregation_candidate,
1813
+ query_relevance_cache=query_relevance_cache,
1814
+ max_boost=max_boost,
1815
+ max_penalty=max_penalty,
1816
+ )
1817
+ if signals.net_adjustment == 0 and not signals.rank_signals:
1818
+ return item
1819
+ diagnostics = normalize_context_diagnostics(normalized_item.diagnostics)
1820
+ diagnostics["deterministic_rerank_reason"] = (
1821
+ "query-aware deterministic rerank over fused candidates"
1822
+ )
1823
+ diagnostics["score_signals"] = {
1824
+ **safe_score_signals(diagnostics.get("score_signals")),
1825
+ "deterministic_rerank_boost": signals.boost,
1826
+ "deterministic_rerank_penalty": signals.penalty,
1827
+ "deterministic_rerank_net_adjustment": signals.net_adjustment,
1828
+ "deterministic_rerank_source_count": signals.source_count,
1829
+ "deterministic_rerank_strong_source_count": signals.strong_source_count,
1830
+ "deterministic_rerank_requirement_coverage": signals.coverage_ratio,
1831
+ "deterministic_rerank_query_reason": signals.query_reason,
1832
+ **{key: value for key, value in signals.rank_signals},
1833
+ }
1834
+ diagnostics["provenance"] = {
1835
+ **safe_diagnostic_mapping(diagnostics.get("provenance")),
1836
+ "deterministic_rerank_applied": True,
1837
+ "deterministic_rerank_reasons": list(signals.reasons[:8]),
1838
+ "deterministic_rerank_anchor_conflict": signals.anchor_conflict,
1839
+ }
1840
+ reranked_score = min(0.99, max(0.0, round(normalized_item.score + signals.net_adjustment, 4)))
1841
+ reranked_score = max(reranked_score, _strong_artifact_evidence_score_floor(diagnostics))
1842
+ return replace(
1843
+ normalized_item,
1844
+ score=reranked_score,
1845
+ diagnostics=normalize_context_diagnostics(diagnostics),
1846
+ )
1847
+
1848
+
1849
+ def _strong_artifact_evidence_score_floor(diagnostics: dict[str, object]) -> float:
1850
+ score_signals = safe_score_signals(diagnostics.get("score_signals"))
1851
+ evidence_confidence = _non_negative_float_signal(score_signals.get("evidence_confidence"))
1852
+ coordinate_boost = _non_negative_float_signal(score_signals.get("coordinate_boost"))
1853
+ if (
1854
+ diagnostics.get("retrieval_source") == "artifact_evidence"
1855
+ and diagnostics.get("evidence_kind") in {"ocr_region", "transcript_segment"}
1856
+ and evidence_confidence is not None
1857
+ and evidence_confidence >= 0.9
1858
+ and coordinate_boost is not None
1859
+ and coordinate_boost > 0
1860
+ and (
1861
+ _coverage_int(score_signals.get("unique_term_hits")) >= 3
1862
+ or _coverage_int(score_signals.get("phrase_bigram_hits")) >= 1
1863
+ )
1864
+ ):
1865
+ return 0.9
1866
+ return 0.0
1867
+
1868
+
1869
+ def _deterministic_rerank_signals(
1870
+ item: ContextItem,
1871
+ *,
1872
+ query: str,
1873
+ plan: QueryExpansionPlan,
1874
+ query_anchor_intent: QueryAnchorIntent,
1875
+ temporal_query_intent: TemporalQueryIntent,
1876
+ requested_total: int,
1877
+ has_multi_evidence_aggregation_candidate: bool,
1878
+ query_relevance_cache: dict[str, tuple[str, str, QueryRelevance]] | None,
1879
+ max_boost: float,
1880
+ max_penalty: float,
1881
+ ) -> _DeterministicRerankSignals:
1882
+ sources = diagnostic_retrieval_sources(item.diagnostics)
1883
+ strong_source_count = len(set(sources).intersection(_STRONG_EVIDENCE_RERANK_SOURCES))
1884
+ query_text, query_reason, relevance = _best_query_relevance_for_rerank(
1885
+ plan,
1886
+ item=item,
1887
+ cache=query_relevance_cache,
1888
+ )
1889
+ del query_text
1890
+ coverage = _item_requirement_coverage_signals(
1891
+ item,
1892
+ query=query,
1893
+ query_anchor_intent=query_anchor_intent,
1894
+ requested_total=requested_total,
1895
+ )
1896
+ coverage_ratio = coverage.ratio
1897
+ anchor_match = match_query_anchor_intent_to_text(query_anchor_intent, item.text)
1898
+ source_quote_anchor_matched = (
1899
+ "citation" in coverage.requested_evidence_features
1900
+ and _source_quote_anchor_matches_query(
1901
+ item=item,
1902
+ plan=plan,
1903
+ query_anchor_intent=query_anchor_intent,
1904
+ )
1905
+ )
1906
+ text_anchor_conflict = query_anchor_intent_text_conflicts(
1907
+ query_anchor_intent,
1908
+ item.text,
1909
+ )
1910
+ project_identity_conflict = has_project_identity_mismatch(query=query, text=item.text)
1911
+ anchor_conflict = text_anchor_conflict or project_identity_conflict
1912
+ source_speaker_anchor_override = (
1913
+ text_anchor_conflict
1914
+ and not project_identity_conflict
1915
+ and _dialogue_speaker_confirms_query_anchor(
1916
+ item=item,
1917
+ query_anchor_intent=query_anchor_intent,
1918
+ relevance=relevance,
1919
+ )
1920
+ )
1921
+ commonality_anchor_override = (
1922
+ text_anchor_conflict
1923
+ and not project_identity_conflict
1924
+ and commonality_who_else_anchor_override(
1925
+ query=query,
1926
+ query_reason=query_reason,
1927
+ item=item,
1928
+ )
1929
+ )
1930
+ temporal_supersedes_anchor_override = (
1931
+ anchor_conflict
1932
+ and "temporal_supersedes_relation" in sources
1933
+ )
1934
+ current_state_correction_anchor_override = (
1935
+ anchor_conflict
1936
+ and temporal_query_intent.prefers_current
1937
+ and has_current_state_correction_evidence(item.text)
1938
+ )
1939
+ boost = 0.0
1940
+ penalty = 0.0
1941
+ reasons: list[str] = []
1942
+ rank_signals: dict[str, float] = {}
1943
+ if len(sources) >= 2:
1944
+ boost += min(0.018, 0.006 * len(sources))
1945
+ reasons.append("hybrid_source_diversity")
1946
+ if strong_source_count:
1947
+ boost += min(0.018, 0.008 * strong_source_count)
1948
+ reasons.append("strong_evidence_source")
1949
+ if anchor_match is not None:
1950
+ boost += min(0.018, max(0.0, anchor_match.score_boost) * 0.35)
1951
+ reasons.append("query_anchor_match")
1952
+ elif source_quote_anchor_matched:
1953
+ boost += 0.012
1954
+ reasons.append("source_quote_query_anchor_match")
1955
+ if requested_total > 0 and coverage_ratio > 0:
1956
+ boost += 0.014 * coverage_ratio
1957
+ reasons.append("explicit_requirement_covered")
1958
+ answer_shape_boost = 0.0
1959
+ boostable_requested_answer_shapes = (
1960
+ coverage.requested_answer_shapes & _GENERIC_BOOSTABLE_ANSWER_SHAPES
1961
+ )
1962
+ boostable_covered_answer_shapes = (
1963
+ coverage.covered_answer_shapes & _GENERIC_BOOSTABLE_ANSWER_SHAPES
1964
+ )
1965
+ if boostable_requested_answer_shapes and boostable_covered_answer_shapes:
1966
+ answer_shape_boost = 0.018 * (
1967
+ len(boostable_covered_answer_shapes) / len(boostable_requested_answer_shapes)
1968
+ )
1969
+ typed_requested_answer_shapes = frozenset(
1970
+ shape
1971
+ for shape in coverage.requested_answer_shapes
1972
+ if is_typed_answer_unit_shape(shape)
1973
+ )
1974
+ typed_covered_answer_shapes = frozenset(
1975
+ shape
1976
+ for shape in coverage.covered_answer_shapes
1977
+ if is_typed_answer_unit_shape(shape)
1978
+ )
1979
+ if typed_requested_answer_shapes and typed_covered_answer_shapes:
1980
+ answer_shape_boost += 0.024 * (
1981
+ len(typed_covered_answer_shapes) / len(typed_requested_answer_shapes)
1982
+ )
1983
+ if "speaker" in coverage.covered_answer_shapes:
1984
+ boost += 0.018
1985
+ reasons.append("speaker_answer_shape_covered")
1986
+ if is_query_relevance_sufficient(relevance):
1987
+ relevance_boost = min(
1988
+ 0.012,
1989
+ relevance.score_boost * 0.1 + relevance.distinctive_term_hits * 0.003,
1990
+ )
1991
+ if relevance_boost > 0:
1992
+ boost += relevance_boost
1993
+ reasons.append("query_relevance_supported")
1994
+ localized_evidence_boost, localized_evidence_reason = _localized_evidence_support_signal(
1995
+ item=item,
1996
+ relevance=relevance,
1997
+ coverage_ratio=coverage_ratio,
1998
+ anchor_matched=anchor_match is not None or source_quote_anchor_matched,
1999
+ strong_source_count=strong_source_count,
2000
+ )
2001
+ if localized_evidence_boost > 0:
2002
+ boost += localized_evidence_boost
2003
+ reasons.append(localized_evidence_reason)
2004
+ decomposition_boost, decomposition_reason, decomposition_signals = (
2005
+ _decomposition_coverage_support_signal(
2006
+ plan=plan,
2007
+ text=item.text,
2008
+ query_reason=query_reason,
2009
+ )
2010
+ )
2011
+ if decomposition_boost > 0:
2012
+ boost += decomposition_boost
2013
+ reasons.append(decomposition_reason)
2014
+ rank_signals.update(decomposition_signals)
2015
+ canonical_summary_boost, canonical_summary_reason = (
2016
+ _canonical_anchor_summary_support_signal(
2017
+ item=item,
2018
+ coverage=coverage,
2019
+ relevance=relevance,
2020
+ anchor_matched=anchor_match is not None or source_quote_anchor_matched,
2021
+ anchor_conflict=anchor_conflict,
2022
+ sources=sources,
2023
+ strong_source_count=strong_source_count,
2024
+ )
2025
+ )
2026
+ if canonical_summary_boost > 0:
2027
+ boost += canonical_summary_boost
2028
+ reasons.append(canonical_summary_reason)
2029
+ citation_evidence_boost, citation_evidence_reason, citation_evidence_signals = (
2030
+ _citation_evidence_support_signal(
2031
+ item=item,
2032
+ plan=plan,
2033
+ coverage=coverage,
2034
+ relevance=relevance,
2035
+ anchor_matched=anchor_match is not None or source_quote_anchor_matched,
2036
+ strong_source_count=strong_source_count,
2037
+ )
2038
+ )
2039
+ if citation_evidence_boost > 0:
2040
+ boost += citation_evidence_boost
2041
+ reasons.append(citation_evidence_reason)
2042
+ rank_signals.update(citation_evidence_signals)
2043
+ (
2044
+ artifact_inventory_boost,
2045
+ artifact_inventory_penalty,
2046
+ artifact_inventory_reason,
2047
+ artifact_inventory_signals,
2048
+ ) = _artifact_inventory_evidence_support_signal(
2049
+ item=item,
2050
+ plan=plan,
2051
+ query_reason=query_reason,
2052
+ relevance=relevance,
2053
+ coverage_ratio=coverage_ratio,
2054
+ anchor_matched=anchor_match is not None or source_quote_anchor_matched,
2055
+ )
2056
+ if artifact_inventory_boost > 0:
2057
+ boost += artifact_inventory_boost
2058
+ reasons.append(artifact_inventory_reason)
2059
+ rank_signals.update(artifact_inventory_signals)
2060
+ if artifact_inventory_penalty > 0:
2061
+ penalty += artifact_inventory_penalty
2062
+ reasons.append(artifact_inventory_reason)
2063
+ rank_signals.update(artifact_inventory_signals)
2064
+ owner_boost, owner_penalty, owner_reason = _activity_owner_signal(
2065
+ query_anchor_intent=query_anchor_intent,
2066
+ query_reason=query_reason,
2067
+ text=item.text,
2068
+ )
2069
+ if owner_boost > 0:
2070
+ boost += owner_boost
2071
+ reasons.append(owner_reason)
2072
+ if owner_penalty > 0:
2073
+ penalty += owner_penalty
2074
+ reasons.append(owner_reason)
2075
+ speaker_boost, speaker_penalty, speaker_reason = speaker_attribution_signal(
2076
+ query=query,
2077
+ text=item.text,
2078
+ )
2079
+ if speaker_boost > 0:
2080
+ boost += speaker_boost
2081
+ reasons.append(speaker_reason)
2082
+ if speaker_penalty > 0:
2083
+ penalty += speaker_penalty
2084
+ reasons.append(speaker_reason)
2085
+ source_grounding_boost, source_grounding_penalty, source_grounding_reason = (
2086
+ source_grounding_signal(
2087
+ query=query,
2088
+ text=item.text,
2089
+ source_refs=item.source_refs,
2090
+ )
2091
+ )
2092
+ if source_grounding_boost > 0:
2093
+ boost += source_grounding_boost
2094
+ reasons.append(source_grounding_reason)
2095
+ if source_grounding_penalty > 0:
2096
+ penalty += source_grounding_penalty
2097
+ reasons.append(source_grounding_reason)
2098
+ communication_boost, communication_penalty, communication_reason = (
2099
+ communication_direction_signal(
2100
+ query=query,
2101
+ text=item.text,
2102
+ )
2103
+ )
2104
+ if communication_boost > 0:
2105
+ boost += communication_boost
2106
+ reasons.append(communication_reason)
2107
+ if communication_penalty > 0:
2108
+ penalty += communication_penalty
2109
+ reasons.append(communication_reason)
2110
+ if (
2111
+ answer_shape_boost > 0
2112
+ and owner_penalty <= 0
2113
+ and speaker_penalty <= 0
2114
+ and source_grounding_penalty <= 0
2115
+ and communication_penalty <= 0
2116
+ and not anchor_conflict
2117
+ ):
2118
+ boost += answer_shape_boost
2119
+ reasons.append("explicit_answer_shape_covered")
2120
+ action_signal = action_role_rerank_signal(query=query, text=item.text)
2121
+ if action_signal.boost > 0:
2122
+ boost += action_signal.boost
2123
+ reasons.append(action_signal.reason)
2124
+ if action_signal.penalty > 0:
2125
+ penalty += action_signal.penalty
2126
+ reasons.append(action_signal.reason)
2127
+ possession_boost, possession_penalty, possession_reason = possession_source_signal(
2128
+ query=query,
2129
+ item=item,
2130
+ )
2131
+ if possession_boost > 0:
2132
+ boost += possession_boost
2133
+ reasons.append(possession_reason)
2134
+ if possession_penalty > 0:
2135
+ penalty += possession_penalty
2136
+ reasons.append(possession_reason)
2137
+ inference_signal = answer_evidence_rerank_signal(query=query, text=item.text)
2138
+ if inference_signal.boost > 0:
2139
+ boost += inference_signal.boost
2140
+ reasons.append(inference_signal.reason)
2141
+ if inference_signal.penalty > 0:
2142
+ penalty += inference_signal.penalty
2143
+ reasons.append(inference_signal.reason)
2144
+ polarity_boost, polarity_penalty, polarity_reason = status_polarity_signal(
2145
+ query=query,
2146
+ text=item.text,
2147
+ )
2148
+ if polarity_boost > 0:
2149
+ boost += polarity_boost
2150
+ reasons.append(polarity_reason)
2151
+ if polarity_penalty > 0:
2152
+ penalty += polarity_penalty
2153
+ reasons.append(polarity_reason)
2154
+ negative_boost, negative_penalty, negative_reason = negative_preference_signal(
2155
+ query=query,
2156
+ text=item.text,
2157
+ )
2158
+ if negative_boost > 0:
2159
+ boost += negative_boost
2160
+ reasons.append(negative_reason)
2161
+ if negative_penalty > 0:
2162
+ penalty += negative_penalty
2163
+ reasons.append(negative_reason)
2164
+ absence_boost, absence_penalty, absence_reason = absence_negation_signal(
2165
+ query=query,
2166
+ text=item.text,
2167
+ )
2168
+ if absence_boost > 0:
2169
+ boost += absence_boost
2170
+ reasons.append(absence_reason)
2171
+ if absence_penalty > 0:
2172
+ penalty += absence_penalty
2173
+ reasons.append(absence_reason)
2174
+ schedule_boost, schedule_penalty, schedule_reason = schedule_change_signal(
2175
+ query=query,
2176
+ text=item.text,
2177
+ )
2178
+ if schedule_boost > 0:
2179
+ boost += schedule_boost
2180
+ reasons.append(schedule_reason)
2181
+ if schedule_penalty > 0:
2182
+ penalty += schedule_penalty
2183
+ reasons.append(schedule_reason)
2184
+ named_preference_signal = named_person_preference_signal(
2185
+ query=query,
2186
+ text=item.text,
2187
+ )
2188
+ if named_preference_signal.boost > 0:
2189
+ boost += named_preference_signal.boost
2190
+ reasons.append(named_preference_signal.reason)
2191
+ if named_preference_signal.penalty > 0:
2192
+ penalty += named_preference_signal.penalty
2193
+ reasons.append(named_preference_signal.reason)
2194
+ pet_signal = pet_ownership_signal(query=query, text=item.text)
2195
+ if pet_signal.boost > 0:
2196
+ boost += pet_signal.boost
2197
+ reasons.append(pet_signal.reason)
2198
+ if pet_signal.penalty > 0:
2199
+ penalty += pet_signal.penalty
2200
+ reasons.append(pet_signal.reason)
2201
+ owner_grounding = owner_grounding_signal(query=query, text=item.text)
2202
+ if owner_grounding.boost > 0:
2203
+ boost += owner_grounding.boost
2204
+ reasons.append(owner_grounding.reason)
2205
+ if owner_grounding.penalty > 0:
2206
+ penalty += owner_grounding.penalty
2207
+ reasons.append(owner_grounding.reason)
2208
+ contrast_boost, contrast_penalty, contrast_reason = absence_contrast_signal(
2209
+ query=query,
2210
+ text=item.text,
2211
+ )
2212
+ if contrast_boost > 0:
2213
+ boost += contrast_boost
2214
+ reasons.append(contrast_reason)
2215
+ if contrast_penalty > 0:
2216
+ penalty += contrast_penalty
2217
+ reasons.append(contrast_reason)
2218
+ object_boost, object_penalty, object_reason = object_kind_mismatch_signal(
2219
+ query=query,
2220
+ text=item.text,
2221
+ )
2222
+ if object_boost > 0:
2223
+ boost += object_boost
2224
+ reasons.append(object_reason)
2225
+ if object_penalty > 0:
2226
+ penalty += object_penalty
2227
+ reasons.append(object_reason)
2228
+ ordinal_sequence_signal = ordinal_sequence_rerank_signal(
2229
+ query=query,
2230
+ text=item.text,
2231
+ )
2232
+ if ordinal_sequence_signal.boost > 0:
2233
+ boost += ordinal_sequence_signal.boost
2234
+ reasons.append(ordinal_sequence_signal.reason)
2235
+ if ordinal_sequence_signal.penalty > 0:
2236
+ penalty += ordinal_sequence_signal.penalty
2237
+ reasons.append(ordinal_sequence_signal.reason)
2238
+ relation_signal = relation_requirement_signal(query=query, text=item.text)
2239
+ if relation_signal.boost > 0:
2240
+ boost += relation_signal.boost
2241
+ reasons.append(relation_signal.reason)
2242
+ if (
2243
+ relation_signal.penalty > 0
2244
+ and not _is_item_purchase_temporal_answer_evidence(query_reason=query_reason, item=item)
2245
+ ):
2246
+ penalty += relation_signal.penalty
2247
+ reasons.append(relation_signal.reason)
2248
+ person_relation_signal = person_relation_inventory_signal(
2249
+ query=query,
2250
+ text=item.text,
2251
+ )
2252
+ if person_relation_signal.boost > 0:
2253
+ boost += person_relation_signal.boost
2254
+ reasons.append(person_relation_signal.reason)
2255
+ if person_relation_signal.penalty > 0:
2256
+ penalty += person_relation_signal.penalty
2257
+ reasons.append(person_relation_signal.reason)
2258
+ occupation_signal = person_occupation_signal(query=query, text=item.text)
2259
+ if occupation_signal.boost > 0:
2260
+ boost += occupation_signal.boost
2261
+ reasons.append(occupation_signal.reason)
2262
+ if occupation_signal.penalty > 0:
2263
+ penalty += occupation_signal.penalty
2264
+ reasons.append(occupation_signal.reason)
2265
+ residence_signal = person_residence_signal(query=query, text=item.text)
2266
+ if residence_signal.boost > 0:
2267
+ boost += residence_signal.boost
2268
+ reasons.append(residence_signal.reason)
2269
+ if residence_signal.penalty > 0:
2270
+ penalty += residence_signal.penalty
2271
+ reasons.append(residence_signal.reason)
2272
+ kinship_signal = person_kinship_signal(query=query, text=item.text)
2273
+ if kinship_signal.boost > 0:
2274
+ boost += kinship_signal.boost
2275
+ reasons.append(kinship_signal.reason)
2276
+ if kinship_signal.penalty > 0:
2277
+ penalty += kinship_signal.penalty
2278
+ reasons.append(kinship_signal.reason)
2279
+ team_signal = person_team_membership_signal(query=query, text=item.text)
2280
+ if team_signal.boost > 0:
2281
+ boost += team_signal.boost
2282
+ reasons.append(team_signal.reason)
2283
+ if team_signal.penalty > 0:
2284
+ penalty += team_signal.penalty
2285
+ reasons.append(team_signal.reason)
2286
+ conversation_boost, conversation_penalty, conversation_reason = (
2287
+ conversation_counterparty_evidence_signal(
2288
+ query=query,
2289
+ text=item.text,
2290
+ )
2291
+ )
2292
+ if conversation_boost > 0:
2293
+ boost += conversation_boost
2294
+ reasons.append(conversation_reason)
2295
+ if conversation_penalty > 0:
2296
+ penalty += conversation_penalty
2297
+ reasons.append(conversation_reason)
2298
+ topic_boost, topic_penalty, topic_reason = conversation_topic_evidence_signal(
2299
+ query=query,
2300
+ text=item.text,
2301
+ )
2302
+ if topic_boost > 0:
2303
+ boost += topic_boost
2304
+ reasons.append(topic_reason)
2305
+ if topic_penalty > 0:
2306
+ penalty += topic_penalty
2307
+ reasons.append(topic_reason)
2308
+ recency_boost, recency_penalty, recency_reason = conversation_recency_evidence_signal(
2309
+ query=query,
2310
+ text=item.text,
2311
+ )
2312
+ if recency_boost > 0:
2313
+ boost += recency_boost
2314
+ reasons.append(recency_reason)
2315
+ if recency_penalty > 0:
2316
+ penalty += recency_penalty
2317
+ reasons.append(recency_reason)
2318
+ temporal_hint_code = _item_temporal_hint_code(item)
2319
+ recency_hint_boost, recency_hint_reason = conversation_recency_temporal_hint_signal(
2320
+ query=query,
2321
+ temporal_hint_code=temporal_hint_code,
2322
+ )
2323
+ if recency_hint_boost > 0:
2324
+ boost += recency_hint_boost
2325
+ reasons.append(recency_hint_reason)
2326
+ recency_missing_time_penalty, recency_missing_time_reason = (
2327
+ conversation_recency_missing_temporal_signal(
2328
+ query=query,
2329
+ text=item.text,
2330
+ temporal_hint_code=temporal_hint_code,
2331
+ )
2332
+ )
2333
+ if recency_missing_time_penalty > 0:
2334
+ penalty += recency_missing_time_penalty
2335
+ reasons.append(recency_missing_time_reason)
2336
+ if not _temporal_query_signal_already_applied(item):
2337
+ temporal_signal = temporal_query_boost_signal(
2338
+ item,
2339
+ intent=temporal_query_intent,
2340
+ )
2341
+ if temporal_signal.boost > 0:
2342
+ boost += temporal_signal.boost
2343
+ reasons.append(f"temporal_query_{temporal_signal.code}")
2344
+ if temporal_signal.boost < 0:
2345
+ penalty += abs(temporal_signal.boost)
2346
+ reasons.append(f"temporal_query_{temporal_signal.code}")
2347
+ temporal_answer_boost, temporal_answer_penalty, temporal_answer_reason = (
2348
+ _temporal_answer_signal(query=query, query_reason=query_reason, item=item)
2349
+ )
2350
+ if temporal_answer_boost > 0:
2351
+ boost += temporal_answer_boost
2352
+ reasons.append(temporal_answer_reason)
2353
+ if temporal_answer_penalty > 0:
2354
+ penalty += temporal_answer_penalty
2355
+ reasons.append(temporal_answer_reason)
2356
+ if source_speaker_anchor_override:
2357
+ reasons.append("query_anchor_conflict_overridden_by_source_speaker")
2358
+ elif commonality_anchor_override:
2359
+ reasons.append("query_anchor_conflict_overridden_by_commonality_who_else")
2360
+ elif temporal_supersedes_anchor_override:
2361
+ reasons.append("query_anchor_conflict_overridden_by_temporal_supersedes")
2362
+ elif current_state_correction_anchor_override:
2363
+ reasons.append("query_anchor_conflict_overridden_by_current_state_correction")
2364
+ elif anchor_conflict and source_quote_anchor_matched:
2365
+ reasons.append("query_anchor_conflict_overridden_by_source_quote")
2366
+ elif anchor_conflict and not _action_role_confirms_requested_relation(action_signal.reason):
2367
+ penalty += 0.07
2368
+ reasons.append("query_anchor_conflict")
2369
+ elif anchor_conflict:
2370
+ reasons.append("query_anchor_conflict_overridden_by_action_role")
2371
+ if _event_participation_mismatch(query=query, text=item.text):
2372
+ penalty += 0.075
2373
+ reasons.append("event_participation_mismatch")
2374
+ elif _event_participation_positive_match(query=query, text=item.text):
2375
+ boost += 0.018
2376
+ reasons.append("event_participation_positive_match")
2377
+ elif _event_participation_source_sibling_noise(query=query, item=item):
2378
+ penalty += 0.07
2379
+ reasons.append("event_participation_source_sibling_noise")
2380
+ companion_boost, companion_penalty, companion_reason = activity_companion_signal(
2381
+ query=query,
2382
+ item=item,
2383
+ query_anchor_intent=query_anchor_intent,
2384
+ )
2385
+ if companion_boost > 0:
2386
+ boost += companion_boost
2387
+ reasons.append(companion_reason)
2388
+ if companion_penalty > 0:
2389
+ penalty += companion_penalty
2390
+ reasons.append(companion_reason)
2391
+ if _activity_source_sibling_noise(item=item):
2392
+ penalty += 0.04
2393
+ reasons.append("activity_source_sibling_noise")
2394
+ if _capped_source_sibling_low_signal(item=item):
2395
+ penalty += 0.06
2396
+ reasons.append("capped_source_sibling_low_signal")
2397
+ if _allergy_condition_weak_evidence(query_reason=query_reason, relevance=relevance):
2398
+ penalty += 0.07
2399
+ reasons.append("allergy_condition_weak_evidence")
2400
+ if _patriotic_service_weak_evidence(query_reason=query_reason, relevance=relevance):
2401
+ penalty += 0.055
2402
+ reasons.append("patriotic_service_weak_evidence")
2403
+ if _running_reason_weak_evidence(query_reason=query_reason, relevance=relevance):
2404
+ penalty += 0.055
2405
+ reasons.append("running_reason_weak_evidence")
2406
+ if _volunteer_career_exact_turn_evidence(query_reason=query_reason, item=item):
2407
+ boost += 0.028
2408
+ reasons.append("volunteer_career_exact_turn_evidence")
2409
+ if _volunteer_career_weak_evidence(
2410
+ query_anchor_intent=query_anchor_intent,
2411
+ query_reason=query_reason,
2412
+ item=item,
2413
+ ):
2414
+ penalty += 0.07
2415
+ reasons.append("volunteer_career_weak_evidence")
2416
+ if _volunteer_career_broad_evidence(query_reason=query_reason, item=item):
2417
+ penalty += 0.07
2418
+ reasons.append("volunteer_career_broad_evidence")
2419
+ if _post_event_activity_timing_exact_evidence(query_reason=query_reason, item=item):
2420
+ boost += 0.032
2421
+ reasons.append("post_event_activity_timing_exact_evidence")
2422
+ if _post_event_activity_timing_weak_evidence(query_reason=query_reason, item=item):
2423
+ penalty += 0.12
2424
+ reasons.append("post_event_activity_timing_weak_evidence")
2425
+ if _shoe_usage_exact_evidence(query_reason=query_reason, item=item):
2426
+ boost += 0.024
2427
+ reasons.append("shoe_usage_exact_evidence")
2428
+ if _shoe_usage_weak_evidence(query_reason=query_reason, item=item):
2429
+ penalty += 0.12
2430
+ reasons.append("shoe_usage_weak_evidence")
2431
+ domain_adjustment = apply_domain_rerank_signals(
2432
+ query=query,
2433
+ query_reason=query_reason,
2434
+ item=item,
2435
+ relevance=relevance,
2436
+ has_multi_evidence_aggregation_candidate=has_multi_evidence_aggregation_candidate,
2437
+ )
2438
+ boost += domain_adjustment.boost
2439
+ ordinal_sequence_requirement_support = (
2440
+ ordinal_sequence_signal.boost > 0
2441
+ and ordinal_sequence_signal.reason == "ordinal_sequence_exact_evidence"
2442
+ )
2443
+ if (
2444
+ ordinal_sequence_requirement_support
2445
+ and "item_purchase_temporal_weak_evidence" in domain_adjustment.reasons
2446
+ ):
2447
+ reasons.append("item_purchase_weak_evidence_overridden_by_ordinal_sequence")
2448
+ else:
2449
+ penalty += domain_adjustment.penalty
2450
+ reasons.extend(domain_adjustment.reasons)
2451
+ rank_signals.update(domain_adjustment.rank_signals)
2452
+ slot_diverse_aggregation = aggregation_answer_slot_count(query=query, text=item.text) >= 2
2453
+ event_detail_requirement_support = "temporal_camping_detail_evidence" in reasons
2454
+ artifact_inventory_requirement_support = (
2455
+ "artifact_inventory_first_party_evidence" in reasons
2456
+ )
2457
+ if requested_total > 0:
2458
+ if coverage_ratio <= 0 and (
2459
+ event_detail_requirement_support
2460
+ or artifact_inventory_requirement_support
2461
+ or ordinal_sequence_requirement_support
2462
+ ):
2463
+ if event_detail_requirement_support:
2464
+ reasons.append("explicit_requirement_supported_by_event_detail")
2465
+ if artifact_inventory_requirement_support:
2466
+ reasons.append("explicit_requirement_supported_by_artifact_inventory")
2467
+ if ordinal_sequence_requirement_support:
2468
+ reasons.append("explicit_requirement_supported_by_ordinal_sequence")
2469
+ elif coverage_ratio <= 0:
2470
+ penalty += 0.025
2471
+ reasons.append("explicit_requirement_missing")
2472
+ elif coverage_ratio < 0.5 and not slot_diverse_aggregation:
2473
+ penalty += 0.012
2474
+ reasons.append("explicit_requirement_partial")
2475
+ if (
2476
+ coverage.missing_answer_shapes
2477
+ and not slot_diverse_aggregation
2478
+ and not ordinal_sequence_requirement_support
2479
+ ):
2480
+ penalty += min(
2481
+ 0.02,
2482
+ 0.014 * len(coverage.missing_answer_shapes),
2483
+ )
2484
+ reasons.append("explicit_requirement_missing")
2485
+ reasons.append("explicit_answer_shape_missing")
2486
+ if coverage.missing_modalities and not artifact_inventory_requirement_support:
2487
+ penalty += min(
2488
+ 0.032,
2489
+ 0.024 * len(coverage.missing_modalities),
2490
+ )
2491
+ reasons.append("explicit_requirement_missing")
2492
+ reasons.append("explicit_modality_missing")
2493
+ if (
2494
+ coverage.missing_evidence_features
2495
+ and not slot_diverse_aggregation
2496
+ and not artifact_inventory_requirement_support
2497
+ ):
2498
+ penalty += min(
2499
+ 0.028,
2500
+ 0.02 * len(coverage.missing_evidence_features),
2501
+ )
2502
+ reasons.append("explicit_requirement_missing")
2503
+ reasons.append("explicit_evidence_feature_missing")
2504
+ if (
2505
+ not is_query_relevance_sufficient(relevance)
2506
+ and anchor_match is None
2507
+ and coverage_ratio <= 0
2508
+ ):
2509
+ penalty += 0.018
2510
+ reasons.append("weak_query_relevance")
2511
+ elif (
2512
+ _is_long_query_weak_overlap(relevance)
2513
+ and anchor_match is None
2514
+ and coverage_ratio <= 0
2515
+ and strong_source_count <= 0
2516
+ and len(sources) <= 1
2517
+ ):
2518
+ penalty += 0.016
2519
+ reasons.append("weak_long_query_overlap")
2520
+ return _DeterministicRerankSignals(
2521
+ boost=round(min(max_boost, boost), 4),
2522
+ penalty=round(min(max_penalty, penalty), 4),
2523
+ reasons=tuple(dict.fromkeys(reasons)),
2524
+ rank_signals=tuple(sorted(rank_signals.items())),
2525
+ source_count=len(sources),
2526
+ strong_source_count=strong_source_count,
2527
+ coverage_ratio=round(coverage_ratio, 4),
2528
+ anchor_conflict=anchor_conflict,
2529
+ query_reason=query_reason,
2530
+ )
2531
+
2532
+
2533
+ def _is_long_query_weak_overlap(relevance: QueryRelevance) -> bool:
2534
+ if relevance.query_term_count < 6:
2535
+ return False
2536
+ if relevance.phrase_bigram_hits > 0:
2537
+ return False
2538
+ return relevance.distinctive_term_hits <= 1 and relevance.unique_term_hits <= 2
2539
+
2540
+
2541
+ def _apply_same_person_topic_disambiguation_penalties(
2542
+ items: tuple[ContextItem, ...],
2543
+ *,
2544
+ query: str,
2545
+ query_anchor_intent: QueryAnchorIntent,
2546
+ max_penalty: float,
2547
+ ) -> tuple[ContextItem, ...]:
2548
+ topic_terms = _same_person_topic_terms(
2549
+ query=query,
2550
+ query_anchor_intent=query_anchor_intent,
2551
+ )
2552
+ if not topic_terms:
2553
+ return items
2554
+ support_threshold = min(2, len(topic_terms))
2555
+ profiles = tuple(
2556
+ _same_person_topic_profile(
2557
+ item,
2558
+ query=query,
2559
+ query_anchor_intent=query_anchor_intent,
2560
+ topic_terms=topic_terms,
2561
+ support_threshold=support_threshold,
2562
+ )
2563
+ for item in items
2564
+ )
2565
+ support_count = sum(
2566
+ 1
2567
+ for profile in profiles
2568
+ if profile.matches_query_person
2569
+ and not profile.has_anchor_conflict
2570
+ and profile.covers_topic
2571
+ )
2572
+ if support_count <= 0:
2573
+ return items
2574
+ return tuple(
2575
+ _with_same_person_topic_disambiguation_penalty(
2576
+ profile.item,
2577
+ profile=profile,
2578
+ support_count=support_count,
2579
+ topic_term_count=len(topic_terms),
2580
+ max_penalty=max_penalty,
2581
+ )
2582
+ for profile in profiles
2583
+ )
2584
+
2585
+
2586
+ def _same_person_topic_terms(
2587
+ *,
2588
+ query: str,
2589
+ query_anchor_intent: QueryAnchorIntent,
2590
+ ) -> tuple[LexicalQueryTerm, ...]:
2591
+ if _SAME_PERSON_TOPIC_DISAMBIGUATION_QUERY_RE.search(query) is None:
2592
+ return ()
2593
+ person_keys = query_anchor_intent.keys_for_kind(MemoryAnchorKind.PERSON)
2594
+ if len(person_keys) != 1:
2595
+ return ()
2596
+ terms: list[LexicalQueryTerm] = []
2597
+ seen: set[tuple[str, ...]] = set()
2598
+ for term in query_terms(query):
2599
+ variants = set(term.variants)
2600
+ if variants.intersection(person_keys):
2601
+ continue
2602
+ if variants.intersection(_SAME_PERSON_TOPIC_QUERY_STOP_TERMS):
2603
+ continue
2604
+ if not any(_same_person_topic_variant_is_specific(variant) for variant in variants):
2605
+ continue
2606
+ key = tuple(term.variants)
2607
+ if key in seen:
2608
+ continue
2609
+ seen.add(key)
2610
+ terms.append(term)
2611
+ return tuple(terms[:8])
2612
+
2613
+
2614
+ def _same_person_topic_variant_is_specific(variant: str) -> bool:
2615
+ return len(variant) >= 3 and not variant.isdigit()
2616
+
2617
+
2618
+ def _same_person_topic_profile(
2619
+ item: ContextItem,
2620
+ *,
2621
+ query: str,
2622
+ query_anchor_intent: QueryAnchorIntent,
2623
+ topic_terms: tuple[LexicalQueryTerm, ...],
2624
+ support_threshold: int,
2625
+ ) -> _SamePersonTopicProfile:
2626
+ anchor_match = match_query_anchor_intent_to_text(query_anchor_intent, item.text)
2627
+ matches_query_person = bool(
2628
+ anchor_match is not None
2629
+ and "query_person_identity_match" in anchor_match.reasons
2630
+ )
2631
+ has_anchor_conflict = (
2632
+ query_anchor_intent_text_conflicts(query_anchor_intent, item.text)
2633
+ or has_project_identity_mismatch(query=query, text=item.text)
2634
+ )
2635
+ text_counts, _ = text_variant_profile(item.text)
2636
+ topic_hits = sum(
2637
+ 1 for term in topic_terms if query_term_frequency(term, text_counts) > 0
2638
+ )
2639
+ return _SamePersonTopicProfile(
2640
+ item=item,
2641
+ matches_query_person=matches_query_person,
2642
+ has_anchor_conflict=has_anchor_conflict,
2643
+ topic_hits=topic_hits,
2644
+ covers_topic=topic_hits >= support_threshold,
2645
+ explicit_topic_decoy_shape=bool(
2646
+ _SAME_PERSON_TOPIC_DECOY_TEXT_RE.search(item.text)
2647
+ ),
2648
+ )
2649
+
2650
+
2651
+ def _with_same_person_topic_disambiguation_penalty(
2652
+ item: ContextItem,
2653
+ *,
2654
+ profile: _SamePersonTopicProfile,
2655
+ support_count: int,
2656
+ topic_term_count: int,
2657
+ max_penalty: float,
2658
+ ) -> ContextItem:
2659
+ if (
2660
+ not profile.matches_query_person
2661
+ or profile.has_anchor_conflict
2662
+ or profile.covers_topic
2663
+ or not profile.explicit_topic_decoy_shape
2664
+ ):
2665
+ return item
2666
+ diagnostics = normalize_context_diagnostics(item.diagnostics)
2667
+ score_signals = safe_score_signals(diagnostics.get("score_signals"))
2668
+ existing_penalty = _non_negative_float_signal(
2669
+ score_signals.get("deterministic_rerank_penalty")
2670
+ ) or 0.0
2671
+ available_penalty = max(0.0, max_penalty - existing_penalty)
2672
+ penalty = min(
2673
+ available_penalty,
2674
+ _SAME_PERSON_TOPIC_DISAMBIGUATION_MAX_PENALTY,
2675
+ )
2676
+ if penalty <= 0:
2677
+ return item
2678
+ existing_boost = _non_negative_float_signal(
2679
+ score_signals.get("deterministic_rerank_boost")
2680
+ ) or 0.0
2681
+ new_penalty = round(existing_penalty + penalty, 4)
2682
+ score_signals.update(
2683
+ {
2684
+ "deterministic_rerank_boost": round(existing_boost, 4),
2685
+ "deterministic_rerank_penalty": new_penalty,
2686
+ "deterministic_rerank_net_adjustment": round(
2687
+ existing_boost - new_penalty,
2688
+ 4,
2689
+ ),
2690
+ "same_person_topic_disambiguation_penalty": round(penalty, 4),
2691
+ "same_person_topic_disambiguation_support_count": support_count,
2692
+ "same_person_topic_disambiguation_topic_hits": profile.topic_hits,
2693
+ "same_person_topic_disambiguation_topic_term_count": topic_term_count,
2694
+ }
2695
+ )
2696
+ provenance = safe_diagnostic_mapping(diagnostics.get("provenance"))
2697
+ reasons = list(provenance.get("deterministic_rerank_reasons") or ())
2698
+ reasons.append("same_person_wrong_topic_decoy")
2699
+ diagnostics["deterministic_rerank_reason"] = (
2700
+ "query-aware deterministic rerank over fused candidates"
2701
+ )
2702
+ diagnostics["score_signals"] = score_signals
2703
+ diagnostics["provenance"] = {
2704
+ **provenance,
2705
+ "deterministic_rerank_applied": True,
2706
+ "deterministic_rerank_reasons": list(dict.fromkeys(reasons))[:8],
2707
+ }
2708
+ return replace(
2709
+ item,
2710
+ score=max(0.0, round(item.score - penalty, 4)),
2711
+ diagnostics=normalize_context_diagnostics(diagnostics),
2712
+ )
2713
+
2714
+
2715
+ def _localized_evidence_support_signal(
2716
+ *,
2717
+ item: ContextItem,
2718
+ relevance: QueryRelevance,
2719
+ coverage_ratio: float,
2720
+ anchor_matched: bool,
2721
+ strong_source_count: int,
2722
+ ) -> tuple[float, str]:
2723
+ localized_refs = tuple(ref for ref in item.source_refs if _source_ref_has_precise_location(ref))
2724
+ if not localized_refs:
2725
+ return 0.0, ""
2726
+ if (
2727
+ not is_query_relevance_sufficient(relevance)
2728
+ and coverage_ratio <= 0
2729
+ and not anchor_matched
2730
+ and strong_source_count <= 0
2731
+ ):
2732
+ return 0.0, ""
2733
+ source_count = len({(ref.source_type, ref.source_id) for ref in localized_refs})
2734
+ feature_count = len(
2735
+ {
2736
+ feature
2737
+ for ref in localized_refs
2738
+ for feature in _source_ref_location_features(ref)
2739
+ }
2740
+ )
2741
+ boost = 0.008
2742
+ boost += min(0.006, 0.003 * max(0, len(localized_refs) - 1))
2743
+ boost += min(0.004, 0.002 * max(0, source_count - 1))
2744
+ boost += min(0.004, 0.002 * max(0, feature_count - 1))
2745
+ diagnostics = safe_diagnostic_mapping(item.diagnostics)
2746
+ if _safe_evidence_kind_or_modality(diagnostics):
2747
+ boost += 0.003
2748
+ reason = (
2749
+ "multi_localized_evidence_source"
2750
+ if len(localized_refs) >= 2 or source_count >= 2 or feature_count >= 2
2751
+ else "localized_evidence_source"
2752
+ )
2753
+ return round(min(_LOCALIZED_EVIDENCE_RERANK_MAX_BOOST, boost), 4), reason
2754
+
2755
+
2756
+ def _decomposition_coverage_support_signal(
2757
+ *,
2758
+ plan: QueryExpansionPlan,
2759
+ text: str,
2760
+ query_reason: str,
2761
+ ) -> tuple[float, str, dict[str, float]]:
2762
+ if len(plan.decompositions) < 2:
2763
+ return 0.0, "", {}
2764
+ if (
2765
+ not query_reason.startswith("decomposition_")
2766
+ or query_reason in _BROAD_DECOMPOSITION_COVERAGE_REASONS
2767
+ ):
2768
+ return 0.0, "", {}
2769
+ matched_reasons: set[str] = set()
2770
+ priority_total = 0
2771
+ distinctive_hit_total = 0
2772
+ for decomposition in plan.decompositions:
2773
+ reason = decomposition.reason
2774
+ if (
2775
+ reason in _BROAD_DECOMPOSITION_COVERAGE_REASONS
2776
+ or not reason.startswith("decomposition_")
2777
+ ):
2778
+ continue
2779
+ relevance = score_query_relevance(query=decomposition.query, text=text)
2780
+ priority = _query_reason_priority_for_relevance(reason, relevance)
2781
+ if priority <= 0 and reason.startswith("decomposition_"):
2782
+ priority = 3
2783
+ min_distinctive_hits = max(
2784
+ 4,
2785
+ _QUERY_REASON_PRIORITY_MIN_DISTINCTIVE_HITS.get(reason, 0),
2786
+ )
2787
+ if (
2788
+ priority <= 0
2789
+ or relevance.distinctive_term_hits < min_distinctive_hits
2790
+ or (relevance.hit_ratio < 0.12 and relevance.phrase_bigram_hits <= 0)
2791
+ ):
2792
+ continue
2793
+ matched_reasons.add(reason)
2794
+ priority_total += priority
2795
+ distinctive_hit_total += relevance.distinctive_term_hits
2796
+ if len(matched_reasons) < 2:
2797
+ return 0.0, "", {}
2798
+
2799
+ boost = 0.006 + min(0.012, 0.005 * len(matched_reasons))
2800
+ if priority_total >= 8:
2801
+ boost += 0.002
2802
+ if distinctive_hit_total >= 8:
2803
+ boost += 0.002
2804
+ boost = round(min(_DECOMPOSITION_COVERAGE_RERANK_MAX_BOOST, boost), 4)
2805
+ return (
2806
+ boost,
2807
+ "query_decomposition_multi_intent_covered",
2808
+ {
2809
+ "query_decomposition_covered_reason_count": float(len(matched_reasons)),
2810
+ "query_decomposition_coverage_boost": boost,
2811
+ },
2812
+ )
2813
+
2814
+
2815
+ def _canonical_anchor_summary_support_signal(
2816
+ *,
2817
+ item: ContextItem,
2818
+ coverage: _RequirementCoverageSignals,
2819
+ relevance: QueryRelevance,
2820
+ anchor_matched: bool,
2821
+ anchor_conflict: bool,
2822
+ sources: tuple[str, ...],
2823
+ strong_source_count: int,
2824
+ ) -> tuple[float, str]:
2825
+ if anchor_conflict or "summary" not in coverage.requested_answer_shapes:
2826
+ return 0.0, ""
2827
+ if not coverage.requested_anchor_kinds & coverage.covered_anchor_kinds:
2828
+ return 0.0, ""
2829
+ if not _is_canonical_anchor_source(item=item, sources=sources):
2830
+ return 0.0, ""
2831
+ if (
2832
+ not is_query_relevance_sufficient(relevance)
2833
+ and not anchor_matched
2834
+ and coverage.ratio <= 0
2835
+ ):
2836
+ return 0.0, ""
2837
+ if not _has_canonical_anchor_profile_evidence(item):
2838
+ return 0.0, ""
2839
+
2840
+ boost = 0.01
2841
+ if item.item_type == "anchor":
2842
+ boost += 0.003
2843
+ if anchor_matched:
2844
+ boost += 0.003
2845
+ if strong_source_count:
2846
+ boost += 0.002
2847
+ return (
2848
+ round(min(_CANONICAL_ANCHOR_SUMMARY_RERANK_MAX_BOOST, boost), 4),
2849
+ "canonical_anchor_summary_profile",
2850
+ )
2851
+
2852
+
2853
+ def _is_canonical_anchor_source(*, item: ContextItem, sources: tuple[str, ...]) -> bool:
2854
+ if item.item_type == "anchor":
2855
+ return True
2856
+ return bool(
2857
+ set(sources).intersection(
2858
+ {
2859
+ "approved_context_linked_anchors",
2860
+ "canonical_anchor_relations",
2861
+ "canonical_anchors",
2862
+ }
2863
+ )
2864
+ )
2865
+
2866
+
2867
+ def _has_canonical_anchor_profile_evidence(item: ContextItem) -> bool:
2868
+ diagnostics = safe_diagnostic_mapping(item.diagnostics)
2869
+ provenance = safe_diagnostic_mapping(diagnostics.get("provenance"))
2870
+ profile = safe_diagnostic_mapping(
2871
+ diagnostics.get("anchor_identity_profile")
2872
+ or provenance.get("anchor_identity_profile")
2873
+ )
2874
+ score_signals = safe_score_signals(diagnostics.get("score_signals"))
2875
+ identity_term_count = max(
2876
+ _coverage_int(profile.get("identity_term_count")),
2877
+ _coverage_int(score_signals.get("anchor_identity_term_count")),
2878
+ )
2879
+ alias_identity_term_count = max(
2880
+ _coverage_int(profile.get("alias_identity_term_count")),
2881
+ _coverage_int(score_signals.get("anchor_alias_identity_term_count")),
2882
+ )
2883
+ source_ref_count = max(
2884
+ len(item.source_refs),
2885
+ _coverage_int(provenance.get("source_ref_count")),
2886
+ )
2887
+ text = item.text.casefold()
2888
+ has_rendered_profile_text = any(
2889
+ marker in text for marker in ("aliases:", "description:", "identity:")
2890
+ )
2891
+ return (
2892
+ has_rendered_profile_text
2893
+ or identity_term_count > 0
2894
+ or alias_identity_term_count > 0
2895
+ or source_ref_count > 0
2896
+ )
2897
+
2898
+
2899
+ def _source_ref_has_precise_location(ref: SourceRef) -> bool:
2900
+ return bool(_source_ref_location_features(ref))
2901
+
2902
+
2903
+ def _source_quote_anchor_matches_query(
2904
+ *,
2905
+ item: ContextItem,
2906
+ plan: QueryExpansionPlan,
2907
+ query_anchor_intent: QueryAnchorIntent,
2908
+ ) -> bool:
2909
+ if query_anchor_intent.empty:
2910
+ return False
2911
+ for ref in item.source_refs:
2912
+ quote = (ref.quote_preview or "").strip()
2913
+ if not quote:
2914
+ continue
2915
+ if match_query_anchor_intent_to_text(query_anchor_intent, quote) is None:
2916
+ continue
2917
+ _, _, relevance = best_query_relevance(plan, text=quote)
2918
+ if _source_quote_relevance_supports_answer(relevance):
2919
+ return True
2920
+ return False
2921
+
2922
+
2923
+ def _citation_evidence_support_signal(
2924
+ *,
2925
+ item: ContextItem,
2926
+ plan: QueryExpansionPlan,
2927
+ coverage: _RequirementCoverageSignals,
2928
+ relevance: QueryRelevance,
2929
+ anchor_matched: bool,
2930
+ strong_source_count: int,
2931
+ ) -> tuple[float, str, dict[str, float]]:
2932
+ if "citation" not in coverage.requested_evidence_features:
2933
+ return 0.0, "", {}
2934
+ if (
2935
+ not is_query_relevance_sufficient(relevance)
2936
+ and coverage.ratio <= 0
2937
+ and not anchor_matched
2938
+ and strong_source_count <= 0
2939
+ ):
2940
+ return 0.0, "", {}
2941
+ quote_count = sum(1 for ref in item.source_refs if (ref.quote_preview or "").strip())
2942
+ localized_count = sum(1 for ref in item.source_refs if _source_ref_has_precise_location(ref))
2943
+ if quote_count <= 0 and localized_count <= 0:
2944
+ return 0.0, "", {}
2945
+ boost = 0.01
2946
+ boost += min(0.008, quote_count * 0.004)
2947
+ boost += min(0.006, localized_count * 0.003)
2948
+ quote_answer_boost, quote_answer_signals = _source_quote_answer_support_signal(
2949
+ item=item,
2950
+ plan=plan,
2951
+ )
2952
+ boost += quote_answer_boost
2953
+ reason = "citation_quote_evidence" if quote_count > 0 else "citation_localized_evidence"
2954
+ return (
2955
+ round(min(_CITATION_EVIDENCE_RERANK_MAX_BOOST, boost), 4),
2956
+ reason,
2957
+ quote_answer_signals,
2958
+ )
2959
+
2960
+
2961
+ def _source_quote_answer_support_signal(
2962
+ *,
2963
+ item: ContextItem,
2964
+ plan: QueryExpansionPlan,
2965
+ ) -> tuple[float, dict[str, float]]:
2966
+ best_relevance: QueryRelevance | None = None
2967
+ for ref in item.source_refs:
2968
+ quote = (ref.quote_preview or "").strip()
2969
+ if not quote:
2970
+ continue
2971
+ _, _, relevance = best_query_relevance(plan, text=quote)
2972
+ if not _source_quote_relevance_supports_answer(relevance):
2973
+ continue
2974
+ if best_relevance is None or query_relevance_rank_key(
2975
+ ("", "source_quote", relevance)
2976
+ ) > query_relevance_rank_key(("", "source_quote", best_relevance)):
2977
+ best_relevance = relevance
2978
+ if best_relevance is None:
2979
+ return 0.0, {}
2980
+
2981
+ boost = 0.003
2982
+ boost += min(0.005, best_relevance.distinctive_term_hits * 0.002)
2983
+ boost += min(0.002, best_relevance.phrase_bigram_hits * 0.002)
2984
+ boost = round(min(_SOURCE_QUOTE_ANSWER_RERANK_MAX_BOOST, boost), 4)
2985
+ return boost, {
2986
+ "source_quote_answer_support": 1.0,
2987
+ "source_quote_answer_boost": boost,
2988
+ "source_quote_answer_relevance": round(best_relevance.score_boost, 4),
2989
+ "source_quote_answer_distinctive_hits": float(
2990
+ best_relevance.distinctive_term_hits
2991
+ ),
2992
+ }
2993
+
2994
+
2995
+ def _source_quote_relevance_supports_answer(relevance: QueryRelevance) -> bool:
2996
+ if relevance.query_term_count <= 0:
2997
+ return False
2998
+ if relevance.distinctive_term_hits >= 2:
2999
+ return True
3000
+ return relevance.distinctive_term_hits >= 1 and (
3001
+ relevance.phrase_bigram_hits > 0 or relevance.unique_term_hits >= 2
3002
+ )
3003
+
3004
+
3005
+ def _artifact_inventory_evidence_support_signal(
3006
+ *,
3007
+ item: ContextItem,
3008
+ plan: QueryExpansionPlan,
3009
+ query_reason: str,
3010
+ relevance: QueryRelevance,
3011
+ coverage_ratio: float,
3012
+ anchor_matched: bool,
3013
+ ) -> tuple[float, float, str, dict[str, float]]:
3014
+ has_artifact_inventory_query = any(
3015
+ expansion.reason == "artifact_inventory_bridge"
3016
+ for expansion in plan.retrieval_queries
3017
+ )
3018
+ if not has_artifact_inventory_query and not _matches_query_or_score_signal_reason(
3019
+ query_reason=query_reason,
3020
+ item=item,
3021
+ target_reason="artifact_inventory_bridge",
3022
+ ):
3023
+ return 0.0, 0.0, "", {}
3024
+ if not (
3025
+ is_query_relevance_sufficient(relevance)
3026
+ or coverage_ratio > 0
3027
+ or anchor_matched
3028
+ ):
3029
+ return 0.0, 0.0, "", {}
3030
+
3031
+ sources = set(diagnostic_retrieval_sources(item.diagnostics))
3032
+ source_ref_types = {ref.source_type for ref in item.source_refs}
3033
+ has_document_evidence_ref = any(
3034
+ ref.source_type == "document"
3035
+ and (_source_ref_has_precise_location(ref) or (ref.quote_preview or "").strip())
3036
+ for ref in item.source_refs
3037
+ )
3038
+ has_first_party_artifact = (
3039
+ item.item_type == "extraction_artifact"
3040
+ or bool(sources.intersection(_ARTIFACT_INVENTORY_RERANK_SOURCES))
3041
+ or bool(source_ref_types.intersection(_ARTIFACT_INVENTORY_SOURCE_REF_TYPES))
3042
+ or has_document_evidence_ref
3043
+ )
3044
+ if not has_first_party_artifact:
3045
+ return (
3046
+ 0.0,
3047
+ 0.018,
3048
+ "artifact_inventory_unbacked_reference",
3049
+ {
3050
+ "artifact_inventory_first_party_evidence": 0.0,
3051
+ "artifact_inventory_unbacked_reference": 1.0,
3052
+ },
3053
+ )
3054
+
3055
+ diagnostics = safe_diagnostic_mapping(item.diagnostics)
3056
+ evidence_signal_count = 1
3057
+ if _safe_evidence_kind_or_modality(diagnostics):
3058
+ evidence_signal_count += 1
3059
+ if any(_source_ref_has_precise_location(ref) for ref in item.source_refs):
3060
+ evidence_signal_count += 1
3061
+ if any((ref.quote_preview or "").strip() for ref in item.source_refs):
3062
+ evidence_signal_count += 1
3063
+ if len(sources) >= 2:
3064
+ evidence_signal_count += 1
3065
+
3066
+ boost = 0.016 + min(0.012, 0.003 * evidence_signal_count)
3067
+ if "artifact_evidence" in sources:
3068
+ boost += 0.003
3069
+
3070
+ return (
3071
+ round(min(_ARTIFACT_INVENTORY_EVIDENCE_RERANK_MAX_BOOST, boost), 4),
3072
+ 0.0,
3073
+ "artifact_inventory_first_party_evidence",
3074
+ {
3075
+ "artifact_inventory_first_party_evidence": 1.0,
3076
+ "artifact_inventory_evidence_signal_count": float(evidence_signal_count),
3077
+ },
3078
+ )
3079
+
3080
+
3081
+ def _source_ref_location_features(ref: SourceRef) -> tuple[str, ...]:
3082
+ features: list[str] = []
3083
+ if ref.page_number is not None:
3084
+ features.append("page")
3085
+ if ref.char_start is not None or ref.char_end is not None:
3086
+ features.append("char")
3087
+ if ref.time_start_ms is not None or ref.time_end_ms is not None:
3088
+ features.append("time")
3089
+ if ref.bbox is not None:
3090
+ features.append("bbox")
3091
+ return tuple(features)
3092
+
3093
+
3094
+ def _safe_evidence_kind_or_modality(diagnostics: Mapping[str, object]) -> bool:
3095
+ for key in ("evidence_kind", "evidence_modality", "artifact_type"):
3096
+ value = diagnostics.get(key)
3097
+ if isinstance(value, str) and value.strip():
3098
+ return True
3099
+ return False
3100
+
3101
+
3102
+ def _best_query_relevance_for_rerank(
3103
+ plan: QueryExpansionPlan,
3104
+ *,
3105
+ item: ContextItem,
3106
+ cache: dict[str, tuple[str, str, QueryRelevance]] | None,
3107
+ ) -> tuple[str, str, QueryRelevance]:
3108
+ diagnostics_relevance = _query_relevance_from_item_diagnostics(plan, item)
3109
+ if diagnostics_relevance is not None:
3110
+ return diagnostics_relevance
3111
+ text = item.text
3112
+ if cache is None:
3113
+ return best_query_relevance(plan, text=text)
3114
+ cached = cache.get(text)
3115
+ if cached is not None:
3116
+ return cached
3117
+ result = best_query_relevance(plan, text=text)
3118
+ cache[text] = result
3119
+ return result
3120
+
3121
+
3122
+ def _query_relevance_from_item_diagnostics(
3123
+ plan: QueryExpansionPlan,
3124
+ item: ContextItem,
3125
+ ) -> tuple[str, str, QueryRelevance] | None:
3126
+ diagnostics = diagnostic_mapping(item.diagnostics)
3127
+ signals = nested_diagnostic_mapping(diagnostics, "score_signals")
3128
+ reason_value = signals.get("query_expansion_reason") or diagnostics.get(
3129
+ "query_expansion_reason"
3130
+ )
3131
+ if not isinstance(reason_value, str) or not reason_value:
3132
+ return None
3133
+ query_text = _query_text_for_expansion_reason(plan, reason_value)
3134
+ if query_text is None:
3135
+ return None
3136
+ relevance = _query_relevance_from_score_signals(signals)
3137
+ if relevance is None:
3138
+ relevance = score_query_relevance(query=query_text, text=item.text)
3139
+ return query_text, reason_value, relevance
3140
+
3141
+
3142
+ def _query_text_for_expansion_reason(
3143
+ plan: QueryExpansionPlan,
3144
+ reason: str,
3145
+ ) -> str | None:
3146
+ for expansion in plan.retrieval_queries:
3147
+ if expansion.reason == reason:
3148
+ return expansion.query
3149
+ return None
3150
+
3151
+
3152
+ def _query_relevance_from_score_signals(
3153
+ signals: Mapping[str, object],
3154
+ ) -> QueryRelevance | None:
3155
+ query_term_count = _non_negative_int_signal(signals.get("query_term_count"))
3156
+ unique_term_hits = _non_negative_int_signal(signals.get("unique_term_hits"))
3157
+ capped_frequency_hits = _non_negative_int_signal(signals.get("capped_frequency_hits"))
3158
+ distinctive_term_count = _non_negative_int_signal(signals.get("distinctive_term_count"))
3159
+ distinctive_term_hits = _non_negative_int_signal(signals.get("distinctive_term_hits"))
3160
+ phrase_bigram_count = _non_negative_int_signal(signals.get("phrase_bigram_count"))
3161
+ phrase_bigram_hits = _non_negative_int_signal(signals.get("phrase_bigram_hits"))
3162
+ if (
3163
+ query_term_count is None
3164
+ or unique_term_hits is None
3165
+ or capped_frequency_hits is None
3166
+ or distinctive_term_count is None
3167
+ or distinctive_term_hits is None
3168
+ or phrase_bigram_count is None
3169
+ or phrase_bigram_hits is None
3170
+ ):
3171
+ return None
3172
+ hit_ratio = _non_negative_float_signal(signals.get("hit_ratio"))
3173
+ score_boost = _non_negative_float_signal(signals.get("query_relevance_boost"))
3174
+ phrase_boost = _non_negative_float_signal(signals.get("phrase_boost"))
3175
+ if hit_ratio is None or score_boost is None or phrase_boost is None:
3176
+ return None
3177
+ return QueryRelevance(
3178
+ score_boost=score_boost,
3179
+ query_term_count=query_term_count,
3180
+ unique_term_hits=unique_term_hits,
3181
+ capped_frequency_hits=capped_frequency_hits,
3182
+ hit_ratio=hit_ratio,
3183
+ distinctive_term_count=distinctive_term_count,
3184
+ distinctive_term_hits=distinctive_term_hits,
3185
+ phrase_bigram_count=phrase_bigram_count,
3186
+ phrase_bigram_hits=phrase_bigram_hits,
3187
+ phrase_boost=phrase_boost,
3188
+ )
3189
+
3190
+
3191
+ def _non_negative_int_signal(value: object) -> int | None:
3192
+ if isinstance(value, bool):
3193
+ return None
3194
+ if isinstance(value, int):
3195
+ return max(0, value)
3196
+ if isinstance(value, float) and value.is_integer():
3197
+ return max(0, int(value))
3198
+ return None
3199
+
3200
+
3201
+ def _non_negative_float_signal(value: object) -> float | None:
3202
+ if isinstance(value, bool):
3203
+ return None
3204
+ if isinstance(value, int | float):
3205
+ return max(0.0, float(value))
3206
+ return None
3207
+
3208
+
3209
+ def _allergy_condition_weak_evidence(
3210
+ *,
3211
+ query_reason: str,
3212
+ relevance: QueryRelevance,
3213
+ ) -> bool:
3214
+ return (
3215
+ query_reason == "allergy_condition_inference_bridge"
3216
+ and relevance.distinctive_term_hits < 4
3217
+ )
3218
+
3219
+
3220
+ def _patriotic_service_weak_evidence(
3221
+ *,
3222
+ query_reason: str,
3223
+ relevance: QueryRelevance,
3224
+ ) -> bool:
3225
+ return (
3226
+ query_reason == "patriotic_service_inference_bridge"
3227
+ and relevance.distinctive_term_hits < 4
3228
+ )
3229
+
3230
+
3231
+ def _running_reason_weak_evidence(
3232
+ *,
3233
+ query_reason: str,
3234
+ relevance: QueryRelevance,
3235
+ ) -> bool:
3236
+ return query_reason == "running_reason_bridge" and relevance.distinctive_term_hits < 3
3237
+
3238
+
3239
+ def _temporal_query_signal_already_applied(item: ContextItem) -> bool:
3240
+ return _provenance_flag_is_true(item.diagnostics, "temporal_query_intent_applied")
3241
+
3242
+
3243
+ def _item_temporal_hint_code(item: ContextItem) -> str:
3244
+ diagnostics = diagnostic_mapping(item.diagnostics)
3245
+ provenance = nested_diagnostic_mapping(diagnostics, "provenance")
3246
+ return temporal_hint_code_from_metadata(diagnostics, provenance)
3247
+
3248
+
3249
+ def _temporal_answer_signal(
3250
+ *,
3251
+ query: str,
3252
+ query_reason: str,
3253
+ item: ContextItem,
3254
+ ) -> tuple[float, float, str]:
3255
+ if not requests_temporal_ranking_context(query):
3256
+ return 0.0, 0.0, ""
3257
+ if (
3258
+ query_reason == "item_purchase_bridge"
3259
+ and not _is_item_purchase_temporal_answer_evidence(query_reason=query_reason, item=item)
3260
+ ):
3261
+ return 0.0, 0.012, "temporal_answer_evidence_missing"
3262
+ grounding = temporal_answer_grounding(query, item)
3263
+ if grounding.grounded:
3264
+ return 0.026, 0.0, "temporal_answer_evidence"
3265
+ if grounding.ungrounded:
3266
+ return 0.0, 0.03, "temporal_answer_ungrounded_evidence"
3267
+ return 0.0, 0.012, "temporal_answer_evidence_missing"
3268
+
3269
+
3270
+ def _is_item_purchase_temporal_answer_evidence(
3271
+ *,
3272
+ query_reason: str,
3273
+ item: ContextItem,
3274
+ ) -> bool:
3275
+ if query_reason != "item_purchase_bridge":
3276
+ return False
3277
+ return has_item_purchase_object_evidence(item.text)
3278
+
3279
+
3280
+ def _event_participation_mismatch(*, query: str, text: str) -> bool:
3281
+ if not _EVENT_PARTICIPATION_QUERY_RE.search(query):
3282
+ return False
3283
+ if not _EVENT_TERM_QUERY_RE.search(query):
3284
+ return False
3285
+ if _SELF_MISSED_EVENT_TEXT_RE.search(text):
3286
+ return True
3287
+ if not _MISSED_EVENT_TEXT_RE.search(text):
3288
+ return False
3289
+ return not _POSITIVE_EVENT_TEXT_RE.search(text)
3290
+
3291
+
3292
+ def _event_participation_positive_match(*, query: str, text: str) -> bool:
3293
+ if not _EVENT_PARTICIPATION_QUERY_RE.search(query):
3294
+ return False
3295
+ if not _EVENT_TERM_QUERY_RE.search(query):
3296
+ return False
3297
+ return bool(_POSITIVE_EVENT_PARTICIPATION_TEXT_RE.search(text))
3298
+
3299
+
3300
+ def _event_participation_source_sibling_noise(*, query: str, item: ContextItem) -> bool:
3301
+ if not _EVENT_PARTICIPATION_QUERY_RE.search(query):
3302
+ return False
3303
+ if not _EVENT_TERM_QUERY_RE.search(query):
3304
+ return False
3305
+ if "keyword_source_sibling_chunks" not in diagnostic_retrieval_sources(item.diagnostics):
3306
+ return False
3307
+ signals = safe_score_signals(safe_diagnostic_mapping(item.diagnostics).get("score_signals"))
3308
+ if str(signals.get("source_sibling_dialogue_visual_reference") or "").casefold() in {
3309
+ "1",
3310
+ "true",
3311
+ }:
3312
+ return False
3313
+ return not _POSITIVE_EVENT_TEXT_RE.search(item.text)
3314
+
3315
+
3316
+ def _activity_source_sibling_noise(*, item: ContextItem) -> bool:
3317
+ if "keyword_source_sibling_chunks" not in diagnostic_retrieval_sources(item.diagnostics):
3318
+ return False
3319
+ signals = safe_score_signals(safe_diagnostic_mapping(item.diagnostics).get("score_signals"))
3320
+ reason = str(signals.get("query_expansion_reason") or "").strip()
3321
+ if reason not in _ACTIVITY_OBSERVATION_SOURCE_REASONS.union(_ACTIVITY_OWNER_REASONS):
3322
+ return False
3323
+ return not _POSITIVE_ACTIVITY_TEXT_RE.search(item.text)
3324
+
3325
+
3326
+ def _capped_source_sibling_low_signal(*, item: ContextItem) -> bool:
3327
+ if "keyword_source_sibling_chunks" not in diagnostic_retrieval_sources(item.diagnostics):
3328
+ return False
3329
+ signals = safe_score_signals(safe_diagnostic_mapping(item.diagnostics).get("score_signals"))
3330
+ return _positive_signal(signals.get("source_sibling_score_cap_applied"))
3331
+
3332
+
3333
+ def _volunteer_career_weak_evidence(
3334
+ *,
3335
+ query_anchor_intent: QueryAnchorIntent,
3336
+ query_reason: str,
3337
+ item: ContextItem,
3338
+ ) -> bool:
3339
+ if query_reason != "volunteer_career_inference_bridge":
3340
+ return False
3341
+ if _VOLUNTEER_CAREER_CONTEXT_RE.search(item.text) is None:
3342
+ return True
3343
+ query_people = _query_person_labels(query_anchor_intent)
3344
+ if not query_people:
3345
+ return False
3346
+ speakers = _dialogue_speaker_labels(item.text)
3347
+ return bool(speakers and not speakers.intersection(query_people))
3348
+
3349
+
3350
+ def _volunteer_career_exact_turn_evidence(*, query_reason: str, item: ContextItem) -> bool:
3351
+ if query_reason != "volunteer_career_inference_bridge":
3352
+ return False
3353
+ if _VOLUNTEER_CAREER_CONTEXT_RE.search(item.text) is None:
3354
+ return False
3355
+ return _item_source_is_turn(item)
3356
+
3357
+
3358
+ def _volunteer_career_broad_evidence(*, query_reason: str, item: ContextItem) -> bool:
3359
+ if query_reason != "volunteer_career_inference_bridge":
3360
+ return False
3361
+ if _item_source_is_turn(item):
3362
+ return False
3363
+ if _VOLUNTEER_CAREER_STRONG_NON_TURN_EVIDENCE_RE.search(item.text) is not None:
3364
+ return False
3365
+ return _VOLUNTEER_CAREER_CONTEXT_RE.search(item.text) is not None
3366
+
3367
+
3368
+ def _post_event_activity_timing_exact_evidence(*, query_reason: str, item: ContextItem) -> bool:
3369
+ if not _is_post_event_activity_timing_candidate(query_reason=query_reason, item=item):
3370
+ return False
3371
+ if _POST_EVENT_ACTIVITY_TIMING_CONTEXT_RE.search(item.text) is None:
3372
+ return False
3373
+ return _item_source_is_turn(item)
3374
+
3375
+
3376
+ def _post_event_activity_timing_weak_evidence(*, query_reason: str, item: ContextItem) -> bool:
3377
+ if not _is_post_event_activity_timing_candidate(query_reason=query_reason, item=item):
3378
+ return False
3379
+ return _POST_EVENT_ACTIVITY_TIMING_CONTEXT_RE.search(item.text) is None
3380
+
3381
+
3382
+ def _is_post_event_activity_timing_candidate(*, query_reason: str, item: ContextItem) -> bool:
3383
+ return _matches_query_or_score_signal_reason(
3384
+ query_reason=query_reason,
3385
+ item=item,
3386
+ target_reason="post_event_activity_timing_bridge",
3387
+ )
3388
+
3389
+
3390
+ def _shoe_usage_exact_evidence(*, query_reason: str, item: ContextItem) -> bool:
3391
+ if not _is_shoe_usage_candidate(query_reason=query_reason, item=item):
3392
+ return False
3393
+ if _SHOE_USAGE_CONTEXT_RE.search(item.text) is None:
3394
+ return False
3395
+ return _item_source_is_turn(item)
3396
+
3397
+
3398
+ def _shoe_usage_weak_evidence(*, query_reason: str, item: ContextItem) -> bool:
3399
+ if not _is_shoe_usage_candidate(query_reason=query_reason, item=item):
3400
+ return False
3401
+ return _SHOE_USAGE_CONTEXT_RE.search(item.text) is None
3402
+
3403
+
3404
+ def _is_shoe_usage_candidate(*, query_reason: str, item: ContextItem) -> bool:
3405
+ return _matches_query_or_score_signal_reason(
3406
+ query_reason=query_reason,
3407
+ item=item,
3408
+ target_reason="shoe_usage_bridge",
3409
+ )
3410
+
3411
+
3412
+ def _matches_query_or_score_signal_reason(
3413
+ *,
3414
+ query_reason: str,
3415
+ item: ContextItem,
3416
+ target_reason: str,
3417
+ ) -> bool:
3418
+ if query_reason == target_reason:
3419
+ return True
3420
+ signals = nested_diagnostic_mapping(
3421
+ diagnostic_mapping(item.diagnostics), "score_signals"
3422
+ )
3423
+ return signals.get("query_expansion_reason") == target_reason
3424
+
3425
+
3426
+ def _item_source_is_turn(item: ContextItem) -> bool:
3427
+ source_id = item_diagnostic_source_id(item)
3428
+ if source_id:
3429
+ return source_id.casefold().endswith(":turn")
3430
+ return any(_source_ref_is_turn(ref) for ref in item.source_refs)
3431
+
3432
+
3433
+ def _action_role_confirms_requested_relation(reason: str) -> bool:
3434
+ return reason in {
3435
+ "action_role_actor_recipient_match",
3436
+ "action_role_actor_to_recipient_evidence",
3437
+ "action_role_information_source_evidence",
3438
+ "action_role_recipient_match",
3439
+ }
3440
+
3441
+
3442
+ def _dialogue_speaker_confirms_query_anchor(
3443
+ *,
3444
+ item: ContextItem,
3445
+ query_anchor_intent: QueryAnchorIntent,
3446
+ relevance: QueryRelevance,
3447
+ ) -> bool:
3448
+ sources = diagnostic_retrieval_sources(item.diagnostics)
3449
+ if "keyword_source_sibling_chunks" in sources:
3450
+ signals = safe_score_signals(
3451
+ safe_diagnostic_mapping(item.diagnostics).get("score_signals")
3452
+ )
3453
+ if not _positive_signal(signals.get("source_sibling_group_level_seed")):
3454
+ return False
3455
+ if _numeric_signal(signals.get("query_expansion_reason_priority")) < 3:
3456
+ return False
3457
+ if relevance.distinctive_term_hits < 4 or relevance.unique_term_hits < 4:
3458
+ return False
3459
+ query_people = _query_person_labels(query_anchor_intent)
3460
+ if not query_people:
3461
+ return False
3462
+ speakers = _dialogue_speaker_labels(item.text)
3463
+ return bool(speakers.intersection(query_people))
3464
+
3465
+
3466
+ def _positive_signal(value: object) -> bool:
3467
+ return _numeric_signal(value) > 0
3468
+
3469
+
3470
+ def _numeric_signal(value: object) -> float:
3471
+ if isinstance(value, bool):
3472
+ return 0.0
3473
+ if isinstance(value, int | float):
3474
+ return float(value)
3475
+ return 0.0
3476
+
3477
+
3478
+ def _activity_owner_signal(
3479
+ *,
3480
+ query_anchor_intent: QueryAnchorIntent,
3481
+ query_reason: str,
3482
+ text: str,
3483
+ ) -> tuple[float, float, str]:
3484
+ if query_reason not in _ACTIVITY_OWNER_REASONS:
3485
+ return 0.0, 0.0, ""
3486
+ query_people = _query_person_labels(query_anchor_intent)
3487
+ if not query_people:
3488
+ return 0.0, 0.0, ""
3489
+ speakers = _dialogue_speaker_labels(text)
3490
+ if not speakers:
3491
+ return 0.0, 0.0, ""
3492
+ if speakers.intersection(query_people):
3493
+ return _ACTIVITY_OWNER_MATCH_BOOST, 0.0, "activity_owner_speaker_match"
3494
+ return 0.0, _ACTIVITY_OWNER_MISMATCH_PENALTY, "activity_owner_speaker_mismatch"
3495
+
3496
+
3497
+ def _query_person_labels(query_anchor_intent: QueryAnchorIntent) -> frozenset[str]:
3498
+ labels: set[str] = set()
3499
+ for hint in query_anchor_intent.hints:
3500
+ if hint.kind != MemoryAnchorKind.PERSON:
3501
+ continue
3502
+ label = _normalized_dialogue_label(hint.label)
3503
+ if label:
3504
+ labels.add(label)
3505
+ canonical = _normalized_dialogue_label(hint.canonical_key)
3506
+ if canonical:
3507
+ labels.add(canonical)
3508
+ return frozenset(labels)
3509
+
3510
+
3511
+ def _dialogue_speaker_labels(text: str) -> frozenset[str]:
3512
+ return frozenset(
3513
+ label
3514
+ for label in (
3515
+ _normalized_dialogue_label(match.group("speaker"))
3516
+ for match in _DIALOGUE_SPEAKER_RE.finditer(text)
3517
+ )
3518
+ if label
3519
+ )
3520
+
3521
+
3522
+ def _normalized_dialogue_label(value: str) -> str:
3523
+ return "".join(char for char in value.casefold() if char.isalnum())
3524
+
3525
+
3526
+ def _item_requirement_coverage_signals(
3527
+ item: ContextItem,
3528
+ *,
3529
+ query: str,
3530
+ query_anchor_intent: QueryAnchorIntent,
3531
+ requested_total: int,
3532
+ ) -> _RequirementCoverageSignals:
3533
+ if requested_total <= 0:
3534
+ return _RequirementCoverageSignals(
3535
+ ratio=0.0,
3536
+ requested_anchor_kinds=frozenset(),
3537
+ covered_anchor_kinds=frozenset(),
3538
+ missing_anchor_kinds=frozenset(),
3539
+ requested_modalities=frozenset(),
3540
+ missing_modalities=frozenset(),
3541
+ requested_evidence_features=frozenset(),
3542
+ missing_evidence_features=frozenset(),
3543
+ requested_answer_shapes=frozenset(),
3544
+ covered_answer_shapes=frozenset(),
3545
+ missing_answer_shapes=frozenset(),
3546
+ )
3547
+ coverage = context_requirement_coverage(
3548
+ query=query,
3549
+ query_anchor_intent=query_anchor_intent,
3550
+ items=(item,),
3551
+ )
3552
+ requested_anchor_kinds = _coverage_value_set(coverage.get("requested_anchor_kinds"))
3553
+ covered_anchor_kinds = _coverage_value_set(coverage.get("covered_anchor_kinds"))
3554
+ missing_anchor_kinds = _coverage_value_set(coverage.get("missing_anchor_kinds"))
3555
+ requested_modalities = _coverage_value_set(coverage.get("requested_modalities"))
3556
+ missing_modalities = _coverage_value_set(coverage.get("missing_modalities"))
3557
+ requested_evidence_features = _coverage_value_set(
3558
+ coverage.get("requested_evidence_features")
3559
+ )
3560
+ missing_evidence_features = _coverage_value_set(coverage.get("missing_evidence_features"))
3561
+ requested_answer_shapes = _coverage_value_set(coverage.get("requested_answer_shapes"))
3562
+ covered_answer_shapes = _coverage_value_set(coverage.get("covered_answer_shapes"))
3563
+ missing_answer_shapes = _coverage_value_set(coverage.get("missing_answer_shapes"))
3564
+ return _RequirementCoverageSignals(
3565
+ ratio=_coverage_ratio(coverage.get("coverage_ratio")),
3566
+ requested_anchor_kinds=requested_anchor_kinds,
3567
+ covered_anchor_kinds=requested_anchor_kinds & covered_anchor_kinds,
3568
+ missing_anchor_kinds=requested_anchor_kinds & missing_anchor_kinds,
3569
+ requested_modalities=requested_modalities,
3570
+ missing_modalities=requested_modalities & missing_modalities,
3571
+ requested_evidence_features=requested_evidence_features,
3572
+ missing_evidence_features=requested_evidence_features & missing_evidence_features,
3573
+ requested_answer_shapes=requested_answer_shapes,
3574
+ covered_answer_shapes=requested_answer_shapes & covered_answer_shapes,
3575
+ missing_answer_shapes=requested_answer_shapes & missing_answer_shapes,
3576
+ )
3577
+
3578
+
3579
+ def _coverage_int(value: object) -> int:
3580
+ if isinstance(value, bool):
3581
+ return 0
3582
+ if isinstance(value, int):
3583
+ return max(0, value)
3584
+ if isinstance(value, float):
3585
+ return max(0, int(value))
3586
+ return 0
3587
+
3588
+
3589
+ def _coverage_ratio(value: object) -> float:
3590
+ if isinstance(value, bool):
3591
+ return 0.0
3592
+ if isinstance(value, int | float):
3593
+ return min(1.0, max(0.0, float(value)))
3594
+ return 0.0
3595
+
3596
+
3597
+ def _deterministic_rerank_already_applied(item: ContextItem) -> bool:
3598
+ return _provenance_flag_is_true(item.diagnostics, "deterministic_rerank_applied")
3599
+
3600
+
3601
+ def _deterministic_rerank_already_applied_in_diagnostics(
3602
+ diagnostics: object,
3603
+ ) -> bool:
3604
+ return _provenance_flag_is_true(
3605
+ diagnostics,
3606
+ "deterministic_rerank_applied",
3607
+ normalized=True,
3608
+ )
3609
+
3610
+
3611
+ def _coverage_value_set(value: object) -> frozenset[str]:
3612
+ if not isinstance(value, list | tuple):
3613
+ return frozenset()
3614
+ return frozenset(
3615
+ text for item in value if isinstance(item, str) and (text := item.strip().casefold())
3616
+ )
3617
+
3618
+
3619
+ def _sorted_coverage_matches(
3620
+ requested: frozenset[str],
3621
+ covered: object,
3622
+ ) -> tuple[str, ...]:
3623
+ return tuple(sorted(requested & _coverage_value_set(covered)))
3624
+
3625
+
3626
+ def _with_rank_fusion_boost(
3627
+ item: ContextItem,
3628
+ *,
3629
+ fusion_score: float,
3630
+ max_fusion_score: float,
3631
+ max_boost: float,
3632
+ source_count: int,
3633
+ source_weighted: bool = False,
3634
+ ) -> ContextItem:
3635
+ if _rank_fusion_already_applied(item):
3636
+ return item
3637
+ if fusion_score <= 0 or max_fusion_score <= 0:
3638
+ return item
3639
+ normalized_score = min(1.0, fusion_score / max_fusion_score)
3640
+ boost = round(max_boost * normalized_score, 4)
3641
+ if boost <= 0:
3642
+ return item
3643
+ diagnostics = normalize_context_diagnostics(item.diagnostics)
3644
+ diagnostics["ranking_reason"] = diagnostics.get(
3645
+ "ranking_reason",
3646
+ "ranked by retrieval score",
3647
+ )
3648
+ diagnostics["rank_fusion_reason"] = f"RRF over {source_count} retrieval sources"
3649
+ diagnostics["score_signals"] = {
3650
+ **safe_score_signals(diagnostics.get("score_signals")),
3651
+ "rank_fusion_score": round(fusion_score, 6),
3652
+ "rank_fusion_normalized_score": round(normalized_score, 4),
3653
+ "rank_fusion_boost": boost,
3654
+ "rank_fusion_source_count": source_count,
3655
+ "rank_fusion_source_weighted": source_weighted,
3656
+ }
3657
+ diagnostics["provenance"] = {
3658
+ **safe_diagnostic_mapping(diagnostics.get("provenance")),
3659
+ "rank_fusion_applied": True,
3660
+ "rank_fusion_source_count": source_count,
3661
+ "rank_fusion_source_weighted": source_weighted,
3662
+ }
3663
+ return replace(
3664
+ item,
3665
+ score=min(0.99, round(item.score + boost, 4)),
3666
+ diagnostics=normalize_context_diagnostics(diagnostics),
3667
+ )
3668
+
3669
+
3670
+ def _rank_fusion_already_applied(item: ContextItem) -> bool:
3671
+ return _provenance_flag_is_true(item.diagnostics, "rank_fusion_applied")
3672
+
3673
+
3674
+ def _provenance_flag_is_true(
3675
+ diagnostics: object,
3676
+ flag: str,
3677
+ *,
3678
+ normalized: bool = False,
3679
+ ) -> bool:
3680
+ del normalized
3681
+ return provenance_flag_is_true(diagnostics, flag)