from __future__ import annotations from dataclasses import dataclass from typing import Any, Callable try: from . import formatter_input as input_policy from . import formatter_route_trace as trace_policy from . import formatter_target as target_policy except ImportError: # pragma: no cover - plain-script smoke tests import formatter_input as input_policy import formatter_route_trace as trace_policy import formatter_target as target_policy @dataclass(frozen=True) class CaptionFormatRequest: source_text: str metadata_json: str = "" input_hint: str = "auto" target: str = "auto" trigger: str = "" include_trigger: bool = True detail_level: str = "balanced" style_policy: str = "drop_style_tail" caption_profile: str = "manual_controls" @dataclass(frozen=True) class CaptionFormatRoute: caption: str method: str branch: str input_hint: str target: str detail_level: str style_policy: str include_trigger: bool keep_style: bool route_trace_json: str = "" def as_tuple(self) -> tuple[str, str]: return self.caption, self.method def as_trace_tuple(self) -> tuple[str, str, str]: return self.caption, self.method, self.route_trace_json @dataclass(frozen=True) class CaptionFormatDependencies: apply_caption_profile: Callable[[str, str, str, bool], tuple[str, str, bool]] keep_style_terms: Callable[[str], bool] row_from_inputs: Callable[[str, str, str], tuple[dict[str, Any] | None, str]] metadata_to_prose: Callable[..., tuple[str, str]] text_to_prose: Callable[[str, str, bool], tuple[str, str]] with_trigger: Callable[[str, str, bool], str] sanitize_prose_text: Callable[..., str] def naturalize_caption_result( request: CaptionFormatRequest, deps: CaptionFormatDependencies, ) -> CaptionFormatRoute: input_hint = input_policy.normalize_input_hint(request.input_hint, text_hint=input_policy.INPUT_HINT_CAPTION_OR_PROMPT) target = target_policy.normalize_target(request.target) detail_level, style_policy, include_trigger = deps.apply_caption_profile( request.caption_profile, request.detail_level, request.style_policy, request.include_trigger, ) keep_style = deps.keep_style_terms(style_policy) row, row_method = deps.row_from_inputs(request.source_text, request.metadata_json, input_hint) if row is not None: prose, method = deps.metadata_to_prose(row, detail_level, keep_style, target) caption = deps.sanitize_prose_text( deps.with_trigger(prose, request.trigger, include_trigger), triggers=(request.trigger,), ) full_method = f"{row_method}:{method}" route_trace = trace_policy.route_trace_json( formatter="caption", branch="metadata", method=full_method, input_hint=input_hint, target=target, detail_level=detail_level, style_policy=style_policy, include_trigger=include_trigger, keep_style=keep_style, ) return CaptionFormatRoute( caption=caption, method=full_method, branch="metadata", input_hint=input_hint, target=target, detail_level=detail_level, style_policy=style_policy, include_trigger=include_trigger, keep_style=keep_style, route_trace_json=route_trace, ) prose, method = deps.text_to_prose(request.source_text, detail_level, keep_style) caption = deps.sanitize_prose_text( deps.with_trigger(prose, request.trigger, include_trigger), triggers=(request.trigger,), ) route_trace = trace_policy.route_trace_json( formatter="caption", branch="text", method=method, input_hint=input_hint, target=target, detail_level=detail_level, style_policy=style_policy, include_trigger=include_trigger, keep_style=keep_style, ) return CaptionFormatRoute( caption=caption, method=method, branch="text", input_hint=input_hint, target=target, detail_level=detail_level, style_policy=style_policy, include_trigger=include_trigger, keep_style=keep_style, route_trace_json=route_trace, ) def naturalize_caption( request: CaptionFormatRequest, deps: CaptionFormatDependencies, ) -> tuple[str, str]: return naturalize_caption_result(request, deps).as_tuple()