diff --git a/chatbot/management/commands/regenerate_transliterated_reports.py b/chatbot/management/commands/regenerate_transliterated_reports.py index 69532f26..d1a232e0 100644 --- a/chatbot/management/commands/regenerate_transliterated_reports.py +++ b/chatbot/management/commands/regenerate_transliterated_reports.py @@ -80,9 +80,12 @@ --field-map Patch mode only. STAGE=field pairs, e.g. 'INTRODUCTION=user_name,ORGANIZATION=organization'. +<<<<<<< HEAD +======= --language Override the language passed to report regeneration. If omitted, taken from the existing Story.language, then the ChatSession.language. +>>>>>>> upstream/release-2.2.0 --bot-profile-id Profile id used as the bot sender (its messages are excluded from re-transliteration). Default 1. @@ -90,6 +93,20 @@ --skip-report Skip step 2 (only re-transliterate chats). --limit Process at most N sessions (0 = no limit). --dry-run Report counts only; no DB writes, no report calls. +<<<<<<< HEAD + +Report language +--------------- +The report is always regenerated in the session's own language -- the same value +``/api/end-story/`` passes, so there is no --language override. ``Story.other_params`` +is nevertheless stored in English: ``save_story()`` transliterates the LLM output +back to English using a Transliterate ``Voice`` on the *story* bot. If that Voice is +missing, ``transliterate_to_english_if_needed()`` silently keeps the original script, +``other_params`` ends up in Devanagari, and dashboards reading +``other_params->>'location'`` stop matching the story. This command therefore refuses +to regenerate such sessions instead of corrupting them. +======= +>>>>>>> upstream/release-2.2.0 """ from collections import namedtuple @@ -108,12 +125,19 @@ Voice, VoiceType, ) +<<<<<<< HEAD +======= from chatbot.models.company_models import CompanyStateMachine +>>>>>>> upstream/release-2.2.0 from chatbot.utils.transliterate_utils import ( transliterate_text, get_transliteration_output, ) +<<<<<<< HEAD +from chatbot.utils.story_utils.story_utils import create_story_object, get_story_company_bot +======= from chatbot.utils.story_utils.story_utils import create_story_object +>>>>>>> upstream/release-2.2.0 # Session-type -> report flow fallback. shikshaChaupal reports are generated @@ -204,7 +228,10 @@ def add_arguments(self, parser): "ALL routes. Leave unset to auto-resolve each session's own flow " "(required when processing multiple routes/flows at once).", ) +<<<<<<< HEAD +======= parser.add_argument("--language", type=str, default=None) +>>>>>>> upstream/release-2.2.0 parser.add_argument("--bot-profile-id", type=int, default=1) parser.add_argument( "--patch-fields", @@ -243,9 +270,15 @@ def handle(self, *args, **opts): if not routes: raise CommandError("--route must list at least one bot route.") flow_override = opts["flow"] +<<<<<<< HEAD + bot_profile_id = opts["bot_profile_id"] + self.bot_profile_id = bot_profile_id + self._story_voice_cache = {} +======= language_override = opts["language"] bot_profile_id = opts["bot_profile_id"] self.bot_profile_id = bot_profile_id +>>>>>>> upstream/release-2.2.0 patch_fields = opts["patch_fields"] field_map = self._parse_field_map(opts["field_map"]) if patch_fields else {} skip_transliterate = opts["skip_transliterate"] @@ -268,7 +301,11 @@ def handle(self, *args, **opts): affected, r_success, r_failed = self._process_route( route=route, stages=stages, date_from=date_from, date_to=date_to, session_types=session_types, session_ids=session_ids, +<<<<<<< HEAD + flow_override=flow_override, +======= flow_override=flow_override, language_override=language_override, +>>>>>>> upstream/release-2.2.0 patch_fields=patch_fields, field_map=field_map, skip_transliterate=skip_transliterate, skip_report=skip_report, limit=limit, dry_run=dry_run, @@ -281,7 +318,11 @@ def handle(self, *args, **opts): # ------------------------------------------------------------------ # def _process_route(self, route, stages, date_from, date_to, session_types, +<<<<<<< HEAD + session_ids, flow_override, patch_fields, +======= session_ids, flow_override, language_override, patch_fields, +>>>>>>> upstream/release-2.2.0 field_map, skip_transliterate, skip_report, limit, dry_run): """Scope + Step 1 + Step 2 for a single bot route. Returns (affected_sessions, reports_success, reports_failed).""" @@ -411,7 +452,11 @@ def get_voice(lang): profile_id = chat_session.profile_id flow = flow_override or self._resolve_flow(session, chat_session) +<<<<<<< HEAD + language = self._resolve_language(chat_session) +======= language = language_override or self._resolve_language(session, chat_session) +>>>>>>> upstream/release-2.2.0 # ---- Patch mode: fix only personal fields, no LLM -------------- if patch_fields: @@ -427,6 +472,19 @@ def get_voice(lang): continue # ---- Full regeneration (default) ------------------------------- +<<<<<<< HEAD + if not self._story_bot_can_transliterate(flow, language): + self.stdout.write(self.style.ERROR( + f" Skipping session={session}: no Transliterate Voice for " + f"language='{language}' on the story bot of flow='{flow}'. " + f"Regenerating would store '{language}' text in the English " + f"Story.other_params and drop the story from the dashboard." + )) + r_failed += 1 + continue + +======= +>>>>>>> upstream/release-2.2.0 if dry_run: self.stdout.write( f" [dry-run] would regenerate session={session} " @@ -570,6 +628,39 @@ def _resolve_flow(self, session, chat_session): st = chat_session.session_type return SESSION_TYPE_TO_FLOW.get(st, st) +<<<<<<< HEAD + def _resolve_language(self, chat_session): + """The report is regenerated in the conversation language -- the same value + /api/end-story/ passes. Story.language is always 'en' (save_story hard-codes + it), so it is not a useful fallback and is not consulted.""" + return (chat_session.language if chat_session else None) or "en" + + def _story_bot_can_transliterate(self, flow, language): + """True when the story bot for `flow` has a Transliterate Voice for `language`. + + save_story() builds the *English* Story.other_params by transliterating the + LLM output; with no Voice it silently returns the original script. The story + bot must be resolved via get_story_company_bot() -- it is a different bot from + the conversation route's bot used elsewhere in this command. + """ + if language == "en": + return True + key = (str(flow), language) + if key not in self._story_voice_cache: + try: + story_bot, _validate_bot = get_story_company_bot(profile=None, flow=flow) + except Exception as exc: # noqa: BLE001 + self.stdout.write(self.style.WARNING( + f" Could not resolve story bot for flow='{flow}': {exc}" + )) + self._story_voice_cache[key] = False + else: + self._story_voice_cache[key] = Voice.objects.filter( + company_bot=story_bot, type=VoiceType.Transliterate, language=language + ).exists() + return self._story_voice_cache[key] + +======= def _resolve_language(self, session, chat_session): # The report/PDF is rendered in the conversation language (its # StoryTranslation), while the main Story is often stored in English. @@ -586,6 +677,7 @@ def _resolve_language(self, session, chat_session): pass return (chat_session.language if chat_session else None) or "en" +>>>>>>> upstream/release-2.2.0 def _summary(self, affected, r_success, r_failed, dry_run): self.stdout.write("\n" + "=" * 50) prefix = "[dry-run] " if dry_run else ""