from __future__ import annotations from typing import Any try: from .krea_action_context import ( axis_values_text, is_climax_text, is_foreplay_text, is_oral_text, is_outercourse_text, is_toy_assisted_double_text, is_vaginal_penetration_text, ) except ImportError: # Allows local smoke tests with `python -c`. from krea_action_context import ( axis_values_text, is_climax_text, is_foreplay_text, is_oral_text, is_outercourse_text, is_toy_assisted_double_text, is_vaginal_penetration_text, ) ACTION_CLIMAX = "climax" ACTION_FOREPLAY = "foreplay" ACTION_OUTERCOURSE = "outercourse" ACTION_ORAL = "oral" ACTION_PENETRATION = "penetration" ACTION_TOY_DOUBLE = "toy_double" ACTION_DEFAULT = "default" HARDCORE_ACTION_FAMILY_CHOICES = { ACTION_CLIMAX, ACTION_FOREPLAY, ACTION_OUTERCOURSE, ACTION_ORAL, ACTION_PENETRATION, ACTION_TOY_DOUBLE, ACTION_DEFAULT, } def normalize_hardcore_action_family(value: Any, default: str = "") -> str: text = str(value or "").strip().lower() if text == "penetrative": text = ACTION_PENETRATION return text if text in HARDCORE_ACTION_FAMILY_CHOICES else default def infer_hardcore_action_family( role_graph: str, hard_item: str, composition: str = "", axis_values: Any = None, *, is_climax: bool | None = None, ) -> str: axis_text = axis_values_text(axis_values) if is_climax is None: is_climax = is_climax_text(role_graph, hard_item, composition, axis_text) if is_climax: return ACTION_CLIMAX if is_foreplay_text(role_graph, hard_item, composition, axis_text): return ACTION_FOREPLAY if is_outercourse_text(role_graph, hard_item, composition, axis_text): return ACTION_OUTERCOURSE if is_oral_text(role_graph, hard_item, composition, axis_text): return ACTION_ORAL if is_vaginal_penetration_text(role_graph, hard_item, composition, axis_text): return ACTION_PENETRATION if is_toy_assisted_double_text(role_graph, hard_item, composition, axis_text): return ACTION_TOY_DOUBLE return ACTION_DEFAULT def source_hardcore_action_family( source_family: Any, role_graph: str, hard_item: str, composition: str = "", axis_values: Any = None, ) -> str: inferred = infer_hardcore_action_family(role_graph, hard_item, composition, axis_values) if inferred in (ACTION_CLIMAX, ACTION_TOY_DOUBLE): return inferred family = str(source_family or "").strip().lower() source_mapping = { "penetrative": ACTION_PENETRATION, "foreplay": ACTION_FOREPLAY, "interaction": ACTION_FOREPLAY, "manual": ACTION_FOREPLAY, "oral": ACTION_ORAL, "outercourse": ACTION_OUTERCOURSE, "climax": ACTION_CLIMAX, } if family == "anal": return ACTION_DEFAULT return source_mapping.get(family, inferred)