Spaces:
Sleeping
Sleeping
| """Backend facade that composes all service modules.""" | |
| from __future__ import annotations | |
| from pathlib import Path | |
| import threading | |
| from typing import Any | |
| from sqlalchemy import event as sa_event | |
| from sqlmodel import SQLModel, create_engine | |
| from backend import catalog as _catalog_mod | |
| from backend import content_types as _content_types_mod | |
| from backend import db as _db_mod | |
| from backend import enrichment as _enrichment_mod | |
| from backend import fetlife_data as _fetlife_data_mod | |
| from backend import fetlife_queue as _fetlife_queue_mod | |
| from backend import ingestion as _ingestion_mod | |
| from backend import partners as _partners_mod | |
| from backend import recommendations as _recommendations_mod | |
| from backend import scenarios as _scenarios_mod | |
| from backend import search as _search_mod | |
| from backend import shared_play as _shared_play_mod | |
| from backend import similarity as _similarity_mod | |
| from backend import users as _users_mod | |
| from backend.models import RecsysSettings | |
| from backend.recsys_env_overrides import load_recsys_settings | |
| class Backend: | |
| def __init__(self, path: Path, *, recsys_settings: RecsysSettings | None = None) -> None: | |
| self.path = path | |
| self.recsys_settings = load_recsys_settings(path, recsys_settings) | |
| self.path.parent.mkdir(parents=True, exist_ok=True) | |
| from sqlalchemy.pool import NullPool | |
| self.engine = create_engine( | |
| f"sqlite:///{self.path}", | |
| echo=False, | |
| connect_args={"timeout": 30, "check_same_thread": False}, | |
| poolclass=NullPool, | |
| ) | |
| def _set_sqlite_pragmas(dbapi_connection, _connection_record) -> None: | |
| cursor = dbapi_connection.cursor() | |
| cursor.execute("PRAGMA journal_mode=WAL") | |
| cursor.execute("PRAGMA synchronous=NORMAL") | |
| cursor.execute("PRAGMA busy_timeout=30000") | |
| cursor.execute("PRAGMA foreign_keys=ON") | |
| cursor.close() | |
| SQLModel.metadata.create_all(self.engine) | |
| self._migrate_user_table() | |
| self._migrate_legacy_ratings() | |
| self._migrate_partner_group_member() | |
| self._migrate_duplicate_pair_groups() | |
| self._seed_type_overrides() | |
| self._ensure_runtime_indexes() | |
| self._catalog_cache: dict[str, Any] | None = None | |
| self._catalog_token: tuple[Any, ...] | None = None | |
| self._catalog_build_lock = threading.Lock() | |
| self._last_cache_invalidation_at = 0.0 | |
| self._ppr_graph_cache: Any = None | |
| self._ppr_transition_cache: Any = None | |
| self._top_edges_by_source_cache: dict[str, list[dict[str, Any]]] = {} | |
| self._ensure_fts_schema() | |
| def invalidate_recommendation_cache(self) -> None: | |
| """Compatibility hook for play mutations. | |
| Recommendations are recomputed against fresh user state on every call; the old | |
| per-process HTTP cache was removed because it could serve stale decks in | |
| multi-process deployments. | |
| """ | |
| return | |
| def peek_catalog_kink_id(self) -> str | None: | |
| """Return any stored kink id (single-row probe for deploy smoke tests).""" | |
| from sqlmodel import Session, select | |
| from models import Kink | |
| with Session(self.engine) as session: | |
| row = session.exec(select(Kink.id).limit(1)).first() | |
| return str(row) if row is not None else None | |
| # --- db --- | |
| _sqlite = _db_mod._sqlite | |
| _now_iso = _db_mod._now_iso | |
| _ensure_fts_schema = _db_mod._ensure_fts_schema | |
| _ensure_runtime_indexes = _db_mod._ensure_runtime_indexes | |
| _normalize_interest_state = _db_mod._normalize_interest_state | |
| _migrate_user_table = _db_mod._migrate_user_table | |
| _migrate_partner_group_member = _db_mod._migrate_partner_group_member | |
| _migrate_duplicate_pair_groups = _db_mod._migrate_duplicate_pair_groups | |
| _migrate_legacy_ratings = _db_mod._migrate_legacy_ratings | |
| # --- similarity --- | |
| _rebuild_similarity_edges = _similarity_mod._rebuild_similarity_edges | |
| rebuild_similarity_edges = _similarity_mod.rebuild_similarity_edges | |
| _edges_for_kink = _similarity_mod._edges_for_kink | |
| add_similarity_edge = _similarity_mod.add_similarity_edge | |
| # --- content_types --- | |
| _seed_type_overrides = _content_types_mod._seed_type_overrides | |
| _derive_content_kind = _content_types_mod._derive_content_kind | |
| _sync_content_types = _content_types_mod._sync_content_types | |
| _content_type_map = _content_types_mod._content_type_map | |
| _content_kind = _content_types_mod._content_kind | |
| # --- enrichment --- | |
| _is_role_query = _enrichment_mod._is_role_query | |
| _discoverability_penalty = _enrichment_mod._discoverability_penalty | |
| _looks_role_like = _enrichment_mod._looks_role_like | |
| _looks_play_like = _enrichment_mod._looks_play_like | |
| _display_summary = _enrichment_mod._display_summary | |
| _normalized_play_name = _enrichment_mod._normalized_play_name | |
| _canonical_preference_tuple = _enrichment_mod._canonical_preference_tuple | |
| _normalize_directions = _enrichment_mod._normalize_directions | |
| _serialize_directions = _enrichment_mod._serialize_directions | |
| _deserialize_directions = _enrichment_mod._deserialize_directions | |
| _direction_mode_for_kink = _enrichment_mod._direction_mode_for_kink | |
| _default_directions_for_kink = _enrichment_mod._default_directions_for_kink | |
| _parse_count_value = _enrichment_mod._parse_count_value | |
| _starter_score = _enrichment_mod._starter_score | |
| _starter_reason_for_kink = _enrichment_mod._starter_reason_for_kink | |
| _starter_tier_for_kink = _enrichment_mod._starter_tier_for_kink | |
| _starter_tier_rank = _enrichment_mod._starter_tier_rank | |
| _detail_summary_text = _enrichment_mod._detail_summary_text | |
| _image_trust_state = _enrichment_mod._image_trust_state | |
| _image_relevance = _enrichment_mod._image_relevance | |
| _derived_product_flags = _enrichment_mod._derived_product_flags | |
| _starter_candidates = _enrichment_mod._starter_candidates | |
| _extract_popularity_from_notes = _enrichment_mod._extract_popularity_from_notes | |
| slugify = _enrichment_mod.slugify | |
| stable_fragment = _enrichment_mod.stable_fragment | |
| _make_memorable_id = _enrichment_mod._make_memorable_id | |
| _make_memorable_pass = _enrichment_mod._make_memorable_pass | |
| # --- catalog --- | |
| _invalidate_catalog_cache = _catalog_mod._invalidate_catalog_cache | |
| _build_catalog_cache = _catalog_mod._build_catalog_cache | |
| _mark_runtime_duplicates = _catalog_mod._mark_runtime_duplicates | |
| _rebuild_fts_index = _catalog_mod._rebuild_fts_index | |
| _maybe_rebuild_fts_index = _catalog_mod._maybe_rebuild_fts_index | |
| _catalog = _catalog_mod._catalog | |
| catalog_is_ready = _catalog_mod.catalog_is_ready | |
| warm_up_catalog = _catalog_mod.warm_up_catalog | |
| refresh_cache = _catalog_mod.refresh_cache | |
| # --- search --- | |
| _query_tokens = _search_mod._query_tokens | |
| _fts_query = _search_mod._fts_query | |
| _fallback_doc_search = _search_mod._fallback_doc_search | |
| search_kinks = _search_mod.search_kinks | |
| search_roles = _search_mod.search_roles | |
| list_kinks = _search_mod.list_kinks | |
| list_kink_summaries = _search_mod.list_kink_summaries | |
| get_kink = _search_mod.get_kink | |
| get_kinks_batch = _search_mod.get_kinks_batch | |
| similar_kinks = _search_mod.similar_kinks | |
| _card_for_kink = _search_mod._card_for_kink | |
| # --- ingestion --- | |
| upsert_source = _ingestion_mod.upsert_source | |
| match_kink_id = _ingestion_mod.match_kink_id | |
| ensure_kink = _ingestion_mod.ensure_kink | |
| add_alias = _ingestion_mod.add_alias | |
| add_definition = _ingestion_mod.add_definition | |
| add_example = _ingestion_mod.add_example | |
| add_asset = _ingestion_mod.add_asset | |
| upsert_fetlife_picture_ref = _ingestion_mod.upsert_fetlife_picture_ref | |
| catalog_stats = _ingestion_mod.catalog_stats | |
| # --- users --- | |
| create_user = _users_mod.create_user | |
| _load_play_preferences = _users_mod._load_play_preferences | |
| _load_role_preferences = _users_mod._load_role_preferences | |
| get_user = _users_mod.get_user | |
| authenticate_user = _users_mod.authenticate_user | |
| all_users = _users_mod.all_users | |
| delete_user = _users_mod.delete_user | |
| export_user_data = _users_mod.export_user_data | |
| partner_activity_hash = _users_mod.partner_activity_hash | |
| save_preferences = _users_mod.save_preferences | |
| remove_play_preference = _users_mod.remove_play_preference | |
| save_play_preference = _users_mod.save_play_preference | |
| save_role_preference = _users_mod.save_role_preference | |
| role_cards = _users_mod.role_cards | |
| link_users = _users_mod.link_users | |
| request_partner_link = _users_mod.request_partner_link | |
| accept_partner_request = _users_mod.accept_partner_request | |
| decline_partner_request = _users_mod.decline_partner_request | |
| cancel_partner_request = _users_mod.cancel_partner_request | |
| dismiss_prompt = _users_mod.dismiss_prompt | |
| play_board = _users_mod.play_board | |
| # --- partners --- | |
| _linked_partner_ids = _partners_mod._linked_partner_ids | |
| list_partner_groups = _partners_mod.list_partner_groups | |
| ensure_pair_partner_groups = _partners_mod.ensure_pair_partner_groups | |
| create_partner_group = _partners_mod.create_partner_group | |
| add_partner_group_members = _partners_mod.add_partner_group_members | |
| remove_partner_group_member = _partners_mod.remove_partner_group_member | |
| _group_participants = _partners_mod._group_participants | |
| can_access_partner_group = _partners_mod.can_access_partner_group | |
| toggle_share_full_list = _partners_mod.toggle_share_full_list | |
| partner_full_board = _partners_mod.partner_full_board | |
| # --- scenarios --- | |
| _load_scenario_preferences = _scenarios_mod._load_scenario_preferences | |
| scenario_parent_links = _scenarios_mod.scenario_parent_links | |
| list_scenarios_for_parent = _scenarios_mod.list_scenarios_for_parent | |
| scenario_parents_for_user = _scenarios_mod.scenario_parents_for_user | |
| upsert_scenario_parent = _scenarios_mod.upsert_scenario_parent | |
| candidate_scenario_parent_links = _scenarios_mod.candidate_scenario_parent_links | |
| save_scenario_preference = _scenarios_mod.save_scenario_preference | |
| remove_scenario_preference = _scenarios_mod.remove_scenario_preference | |
| # --- recommendations --- | |
| _recommendation_item = _recommendations_mod._recommendation_item | |
| _finalize_reasons = _recommendations_mod._finalize_reasons | |
| _strip_rec_sampler_meta = _recommendations_mod._strip_rec_sampler_meta | |
| _stochastic_select_recommendations = _recommendations_mod._stochastic_select_recommendations | |
| _starter_recommendations = _recommendations_mod._starter_recommendations | |
| _main_recommendation_target = _recommendations_mod._main_recommendation_target | |
| _legacy_edge_sum_scores = _recommendations_mod._legacy_edge_sum_scores | |
| _negative_edge_adjustments = _recommendations_mod._negative_edge_adjustments | |
| _positive_seed_reasons_only = _recommendations_mod._positive_seed_reasons_only | |
| _personalized_recommendations = _recommendations_mod._personalized_recommendations | |
| _dedupe_recommendations = _recommendations_mod._dedupe_recommendations | |
| _take_unique_kink_items = _recommendations_mod._take_unique_kink_items | |
| _explore_backfill = _recommendations_mod._explore_backfill | |
| _blended_recommendations = _recommendations_mod._blended_recommendations | |
| _partner_influenced_recommendations = _recommendations_mod._partner_influenced_recommendations | |
| _load_ots_candidate_cache = _recommendations_mod._load_ots_candidate_cache | |
| _ots_recommendations = _recommendations_mod._ots_recommendations | |
| _graph_recommend = _recommendations_mod._graph_recommend | |
| recommend = _recommendations_mod.recommend | |
| recommendation_debug = _recommendations_mod.recommendation_debug | |
| _group_recommendations = _recommendations_mod._group_recommendations | |
| # --- shared_play --- | |
| _directions_compatible = _shared_play_mod._directions_compatible | |
| _directions_compatible_group = _shared_play_mod._directions_compatible_group | |
| _safe_prompt_candidate = _shared_play_mod._safe_prompt_candidate | |
| _direct_shared_ids = _shared_play_mod._direct_shared_ids | |
| _similarity_edges_by_source = _shared_play_mod._similarity_edges_by_source | |
| _positive_scenario_states = _shared_play_mod._positive_scenario_states | |
| _related_direction_compatible = _shared_play_mod._related_direction_compatible | |
| _related_match_entry = _shared_play_mod._related_match_entry | |
| _related_positive_matches_for_pair = _shared_play_mod._related_positive_matches_for_pair | |
| _related_positive_matches_for_users = _shared_play_mod._related_positive_matches_for_users | |
| _scenario_direct_matches_for_users = _shared_play_mod._scenario_direct_matches_for_users | |
| _scenario_related_matches_for_pair = _shared_play_mod._scenario_related_matches_for_pair | |
| _scenario_related_matches_for_users = _shared_play_mod._scenario_related_matches_for_users | |
| _pair_safe_similarity_floor = _shared_play_mod._pair_safe_similarity_floor | |
| _direct_shared_ids_for_users = _shared_play_mod._direct_shared_ids_for_users | |
| _shared_play_list_for_users = _shared_play_mod._shared_play_list_for_users | |
| shared_play_list = _shared_play_mod.shared_play_list | |
| shared_play_list_for_group = _shared_play_mod.shared_play_list_for_group | |
| prompt_candidates = _shared_play_mod.prompt_candidates | |
| group_prompt_candidates = _shared_play_mod.group_prompt_candidates | |
| # --- fetlife_queue --- | |
| queue_fetlife_job = _fetlife_queue_mod.queue_fetlife_job | |
| start_fetlife_supervisor_run = _fetlife_queue_mod.start_fetlife_supervisor_run | |
| heartbeat_fetlife_supervisor = _fetlife_queue_mod.heartbeat_fetlife_supervisor | |
| finish_fetlife_supervisor_run = _fetlife_queue_mod.finish_fetlife_supervisor_run | |
| _process_is_live = _fetlife_queue_mod._process_is_live | |
| fetlife_supervisor_status = _fetlife_queue_mod.fetlife_supervisor_status | |
| requeue_stale_fetlife_jobs = _fetlife_queue_mod.requeue_stale_fetlife_jobs | |
| claim_fetlife_jobs = _fetlife_queue_mod.claim_fetlife_jobs | |
| complete_fetlife_job = _fetlife_queue_mod.complete_fetlife_job | |
| fetlife_job_stats = _fetlife_queue_mod.fetlife_job_stats | |
| fetlife_jobs = _fetlife_queue_mod.fetlife_jobs | |
| fetlife_queue_preview = _fetlife_queue_mod.fetlife_queue_preview | |
| queue_fetlife_priority_debt = _fetlife_queue_mod.queue_fetlife_priority_debt | |
| # --- fetlife_data --- | |
| upsert_fetlife_kink_meta = _fetlife_data_mod.upsert_fetlife_kink_meta | |
| fetlife_source_coverage = _fetlife_data_mod.fetlife_source_coverage | |
| fetlife_coverage_debt = _fetlife_data_mod.fetlife_coverage_debt | |
| fetlife_data_health = _fetlife_data_mod.fetlife_data_health | |
| content_type_coverage = _fetlife_data_mod.content_type_coverage | |
| upsert_fetlife_user_sample = _fetlife_data_mod.upsert_fetlife_user_sample | |
| replace_fetlife_user_fetishes = _fetlife_data_mod.replace_fetlife_user_fetishes | |
| rebuild_fetlife_collaborative_edges = _fetlife_data_mod.rebuild_fetlife_collaborative_edges | |
| fetlife_sample_coverage = _fetlife_data_mod.fetlife_sample_coverage | |