
    E|j܆                     h   d Z ddlZddlZddlZddlZddlmZ  ej                  e      Z	dZ
dZdZde dZd	ee   d
efdZ ej$                  d      Z ej$                  dej(                  ej*                  z        Zded	ee   d
ee   fdZded	ee   d
eedz  ef   fdZ	 	 d>dededed	ee   dee   dz  ded
efdZdeded
efdZ	 	 d>deded	ee   dee   dz  ded
efdZ	 	 d>dedededed	ee   dee   dz  ded
efdZ	 d?dededededee   dz  d
efdZdd d!Zd@d"eded
efd#Z d$ed%ed
efd&Z!d'Z"d(Z#d)e" d*e# d+Z$d,g dd-d.Z%d/e&d0e'e   d
efd1Z(d2edz  d
efd3Z)	 d?d4ee   d5ee   d	ee   d2edz  d
ee   f
d6Z*d7e" d*e# d8Z+	 d?d9ed5ee   d	ee   d2edz  d
ee   f
d:Z,d;ed<ed
efd=Z-y)Au   
Gemini AI Service.
Used for testing — will be replaced with Claude in production.

Two functions:
1. get_first_question()  — called when session starts
2. get_next_question()   — called after every answer submission
    N)settingsINTAKE_COMPLETEu  CRITICAL — Never repeat a question: Rule 1 — HISTORY LOCK: Every Q: line in the conversation history is a question already explicitly asked. Never ask any question whose meaning matches a Q: line already in history. If the patient gave any response at all (even vague or partial), that Q: topic is closed — move on. Rule 2 — CONTENT CHECK: A standard question covering a compound topic (e.g. chest pain with sub-parts) is only fully closed when both the main YES/NO AND every sub-question detail has appeared in Q:/A: history. A patient mentioning a symptom in passing does NOT satisfy this — all sub-parts must be explicitly collected. Rule 3 — AMBIGUOUS ANSWERS: Accept any response and move on. Never re-ask for clarity. Rule 4 — IMPLICIT ANSWERS: Before asking any question, scan ALL A: lines in the conversation history against EVERY remaining standard question, not just the next one you're about to ask — an answer can resolve a question far down the list, one the doctor never asked directly, or several at once. If a patient's previous answer already contains the information a standard question is asking for (e.g. patient said 'cold for two days' — this answers both the chief complaint AND the onset/duration question), that question is resolved — skip it entirely, however many questions this applies to this turn, and move to the next genuinely unanswered one. Re-check this on every single turn using the FULL conversation so far, not just the most recent exchange — a question resolved several turns ago must stay skipped even if the conversation has since moved on to unrelated topics. Always tag your response with the [question_id] from the standard list.u  PATIENT RECORDS — USE TO SKIP QUESTIONS: The uploaded patient records above contain pre-existing medical information. Treat each piece of information in those records as an already-known answer. Before asking any standard question, check whether the records clearly answer it. If the records provide a clear answer for a question — skip that question entirely and move to the next unanswered one. Only ask questions whose answers are NOT present in the patient records.uB  STEP 1 — EXTRACT KNOWN FACTS: Before choosing the next question, mentally read every A: line in the conversation and list all facts the patient has already stated — symptoms, durations, timing, severity, history, medications, etc. Treat every fact in an A: line as already known, regardless of which Q: it was given in response to. Example: if the patient said 'cold and cough from past two days', the facts 'chief complaint = cold/cough' AND 'symptom duration = 2 days' are BOTH already known — do NOT ask about onset/duration again. STEP 2 — SKIP ANSWERED QUESTIONS: Check EVERY remaining standard question against the facts from STEP 1 — the whole list, not only the next one in order. Facts can resolve questions out of order (a patient volunteering something while answering a different question, or during free-form back-and-forth the doctor steers away from the standard script) — catch and skip ALL such questions this turn, however many there are, not just one. Only ask a question if its answer is genuinely not yet known after checking the full list. STEP 2b — REPORT SKIPPED QUESTIONS: List every standard question resolved in STEP 2 — ALL of them, not just the most obvious one. On the FIRST line of your response output: SKIPPED: <comma-separated question IDs from the standard list>. Example: SKIPPED: q_ct_002, q_ct_005. Omit this line entirely if no questions are being skipped. IMPORTANT: The SKIPPED line is supplemental — you MUST still output the next unanswered question on the line immediately after it. Never output ONLY a SKIPPED line with nothing following it. STEP 3 — ASK THE NEXT UNANSWERED QUESTION following these rules:   (a) Ask ONLY the main question first (the part before any 'If yes — ask:' clause).   (b) If the patient answers NO — move to the next standard question.   (c) If the patient answers YES — ask each sub-question listed after 'If yes — ask:'       ONE AT A TIME, one per turn, in the order listed. Use the same [question_id] tag for all sub-questions.   (d) After every sub-question is answered — move to the next standard question. z Always tag your question with its [question_id]. Ask in English. If the intake is complete, respond with exactly: INTAKE_COMPLETE	questionsreturnc                 H    dj                  d t        | d      D              S )N
c              3   B   K   | ]  \  }}| d |d    d|d      yw)z. [idz] textN ).0iqs      8/var/www/html/ai-nims-dev/backend/app/services/gemini.py	<genexpr>z(_build_questions_text.<locals>.<genexpr>R   s-     _As#agYb64_s      )start)join	enumerate)r   s    r   _build_questions_textr   Q   s    99_9\]A^___    z\[\s*([a-zA-Z0-9_\-]+)\s*\]z^SKIPPED:\s*(.+)$responsec                 N   |D ch c]  }|d   	 }}t         j                  |       }|sg S |j                  d      }t        j	                  |      }|r|n,|j                  d      D cg c]  }|j                          c}}|D 	cg c]	  }	|	|v s|	 c}	S c c}w c c}w c c}	w )Nr
   r   ,)_SKIPPED_PATTERNsearchgroup_TAG_PATTERNfindallsplitstrip)
r   r   r   	valid_idsmatchline_content	bracketeds
candidatesid_s
             r   _parse_skipped_idsr)   Y   s    "+,Q4,I,##H-E	;;q>L$$\2I'ASASTWAX-YAaggi-YJ%:C	)9C:: - .Z:s   B)B	B"B"c                    |D ch c]  }|d   	 }}|D ci c]'  }|d   j                         j                         |d   ) }}| j                         }t        j                  |      }|rR|j	                  d      }|d |j                          ||j                         d  z   j                         }||v r||fS d |fS |j                  |j                               }	|	|fS c c}w c c}w )Nr
   r   r   )r!   lowerr   r   r   r   endget)
r   r   r   question_id_set
text_to_idcleanedr#   tagr   
matched_ids
             r   _parse_tagged_questionr3   e   s    (121qw2O2>GH!F)//#))+QtW4HJHnnG(Ekk!n''%))+,*??FFHo-$>>4$>>0Jw 3Hs
   C!,C&patient_infoconversationlatest_answerpatient_recordslanguagec           
      $   d| j                  d       d| j                  d       d| j                  d       d| j                  d      xs d	 }d
t        |       }|rddj                  |      z   }nd}|rLg }	|D ]0  }
|	j                  d|
d           |	j                  d|
d           2 ddj                  |	      z   }nd}||g}|r&|j                  |       |j                  t               ||d| t
        gz  }dj                  |      S )NPatient Info:
- Name: name
- Age: age
- Gender: gender
- Patient ID: daily_idNot providedzStandard Questions to Cover:
z&Patient Records (uploaded documents):

---
 Q: questionA: answerzConversation so far:
r   z/Conversation so far:
(This is the first answer)zLatest patient answer: 

)r-   r   r   append_RECORDS_INSTRUCTION_LLM_INSTRUCTION)r4   r5   r6   r   r7   r8   patient_blockquestions_blockrecords_blockhistory_linesqahistory_blockpartss                r   _build_user_messagerT   t   sN   ##F+, -""5)* +!%%h/0 1%))*5GH		J  77LY7W6XYOAINNSbDcc 	7B  3r*~&6!78  3r(|n!56	7 1499]3KKJO,E]#)*	m6}oFHXYYE;;ur   system_promptuser_messagec           
        K   t        j                  dt        j                   t        j                  t        j
                  dd| dd|dg       d {   }|j                  d   j                  j                  j                         S 7 5w)Nopenai/r   systemrolecontentuser)modelapi_keyapi_basetemperaturemessages
litellmacompletionr   LITELLM_PROVIDER_MODEL_NAMELITELLM_API_KEYLITELLM_PROVIDER_BASE_URLchoicesmessager\   r!   )rU   rV   r   s      r   _call_litellmrk      s     ((<<=>((33-85
	 	H A&&..4466	s   ABB6Bc                    K   t        | g d|||      }t        ||       d {   }t        ||      \  }}|
|r|d   d   }d||diS 7 &w)NrD   )r5   r6   r   r7   r8   r   r
   firstQuestionr
   r   )rT   rk   r3   )	r4   rU   r   r7   r8   rV   r   question_idquestion_texts	            r   get_first_questionrq      sn      '2R_xL #=,??H!7)!LKyl4(KGHH	 @s   !AA
'Ac                 0  K   t        | |||||      }t        ||       d {   }t        j                  d|       t        |v rd dg dS t        ||      }	t        j                  d|      j                         }
t        |
|      \  }}||dd|	dS 7 pw)N)r   r7   r8   z"get_next_question LLM response: %rT)nextQuestionintakeComplete
skippedIdsrD   rn   F)
rT   rk   loggerinfoINTAKE_COMPLETE_TOKENr)   r   subr!   r3   )r4   r5   r6   rU   r   r7   r8   rV   r   skipped_idsresponse_for_parsingro   rp   s                r   get_next_questionr|      s      'lM_xL #=,??H
KK4h?( $BOO$Xy9K ,//H=CCE!78Li!XK#.FZ_oz{{ @s   !BBA1Bintake_datesummary_promptc           
      2  K   d| j                  d       d| j                  d       d| j                  d       d| j                  d      xs d	 d
| 
}g }|D ]0  }|j                  d|d           |j                  d|d           2 ddj                  |      z   }||g}	|r#|	j                  ddj                  |      z          |	j                  d       dj                  |	      }
t        ||
       d{   }t	        j
                  dd|j                               }t	        j
                  dd|      }	 t        j                  |      }t        j                  |      S 7 k# t        j                  $ r t        j                  d|i      cY S w xY ww)z
    Calls LiteLLM to generate a structured patient summary.
    summary_prompt comes from mstr_department.llm_summary_prompt.
    r:   r;   r<   r=   r>   r?   r@   rA   rB   z	
- Date: rE   rF   rG   rH   zFull Intake Conversation:
r   zPatient Records:
rC   z"Generate the patient case summary.rI   N^```(?:json)?\s*rD   \s*```$additionalNotes)r-   rJ   r   rk   rery   r!   jsonloadsdumpsJSONDecodeError)r4   r5   r}   r~   r7   rM   rP   rQ   rR   rS   rV   rawr0   parseds                 r   generate_summaryr      s    ##F+, -""5)* +!%%h/0 1%))*5GH I-	!  M 3s2j>"234s2h<.123 2DIIm4LLMM*E)INN?,KKL	LL56;;u%Lnl;
;Cff("ciik:GffZW-G8G$zz&!! <  8zz,g6778s7   C7F9E%:A F;)E' $F'*FFFFu  Transliterate the following Telugu text to Roman/English script using simple, natural romanization. Do NOT translate the meaning. Use simple spellings — do not double consonants and do not mark long vowels (use 'a' not 'aa', 'u' not 'uu'). If any Telugu script word is a phonetic spelling of an English word (for example ఇయర్స్ for 'years', ఫాస్ట్ for 'past', సర్జరీ for 'surgery'), restore it to the correct English word. Return only the transliterated text, nothing else.

u  Transliterate the following Hindi text to Roman/English script using simple, natural romanization. Do NOT translate the meaning. Use simple spellings — do not double consonants and do not mark long vowels (use 'a' not 'aa', 'u' not 'uu'). If any Devanagari word is a phonetic spelling of an English word, restore it to the correct English word. Return only the transliterated text, nothing else.

)TELUGUHINDIr   c           
      `  K   t         j                  |t         d         }t        j                  dt        j
                   t        j                  t        j                  dddd| d|  dg       d {   }|j                  d	   j                  j                  j                         S 7 5w)
Nr   rX   rY   zwYou are a transliteration tool. Output ONLY the transliterated text. No reasoning, no explanation, no thinking process.rZ   r]   zText: r^   r_   r`   rb   r   )_TRANSLITERATE_PROMPTr-   rd   re   r   rf   rg   rh   ri   rj   r\   r!   )r   r8   promptr   s       r   transliterate_to_romanr     s     "&&x1Fx1PQF((<<=>((33 ! U
 $XVD62	
	 H A&&..4466s   A4B.6B,76B.base64_image	mime_typec                 4  K   t        j                  dt        j                   t        j                  t        j
                  dddd| d|  iddd	d
gdg       d {   }|j                  d   j                  j                  j                         S 7 5w)NrX   r]   	image_urlurlzdata:z;base64,)typer   r   u  Extract all clinical and medical information from this patient record document. Focus on: diagnoses, current medications with dosages, allergies, previous surgeries, test results, vitals, doctor observations, and care plan notes. Do NOT include patient name, age, or gender. Return plain text only — no markdown, no bullet symbols, no asterisks. Use clear section headings followed by the details.)r   r   rZ   r   r   rc   )r   r   r   s      r   extract_from_imager   &  s     ((<<=>((33  !,&+uYKx~-V%W
 !'R

	 H6 A&&..44667s   AB B!6BuC  CONTINUATION CHECK: if a possibly-incomplete previous utterance is given below, decide whether the FIRST turn/entry you produce is clearly the SAME speaker resuming and finishing that exact thought after a pause (not a new, independent statement) — e.g. previous = "I have been" and the new turn is "here it is and they have gradually changed it to worse" is one broken sentence, not two separate ones. If so, set "continuesFromPrevious": true on that FIRST turn only (never on later ones), and use the previous utterance's text as context so your "mappings" for that turn reflect the FULL, completed thought — but still return only the NEW words as its "text"/entry text, not the previous text repeated. Set "continuesFromPrevious": false whenever there is no previous utterance given, or the new turn is not a continuation of it.u.  DOCTOR PRIORITY OVERRIDE: the recent conversation context is given in chronological order (oldest first). If a DOCTOR entry/turn restates, corrects, adds detail to, or otherwise contradicts information already given earlier in this same conversation for a standard question — for example the doctor says 'actually, make that three days, not two' or repeats back and clarifies something the patient said earlier — still map it to that same question with high confidence, and extract the doctor's corrected wording as the answer. This later doctor statement is authoritative: it takes PRIORITY over the earlier answer for that question even though they conflict — never skip or lower your confidence on a doctor's own restatement just because that question already appears answered earlier in the conversation.u   You are a clinical conversation-tagging assistant. You will be shown one or more diarized speech entries from a single audio segment of a live, free-flowing conversation during a medical intake — each entry is one speaker turn, already separated by speaker-diarization, and labeled with an anonymous speaker id (e.g. 'speaker_0') that is only consistent WITHIN this segment, never across segments. You are also given the most recent prior classified utterances for context, and a bank of standard intake questions the doctor is expected to cover during this conversation.

There are usually two roles in this conversation: DOCTOR and PATIENT. Sometimes a family member or other companion accompanies the patient and speaks on their behalf (e.g. answering for them, adding details) — treat ANY such non-doctor speaker as PATIENT context, exactly like the patient's own speech. Never invent a third role — the only valid roles are DOCTOR, PATIENT, and SMALL_TALK (greetings, chit-chat, or speech unrelated to the intake).

SPEAKER-ID CONSISTENCY: entries sharing the SAME speaker id (e.g. two entries both labeled 'speaker_0') are the SAME physical person talking — diarization has already told you who is who. Before classifying any single entry, first decide each DISTINCT speaker id's role ONCE, using everything that speaker id says across ALL their entries in this segment together (not just one entry in isolation) — then apply that SAME role to every entry with that speaker id. NEVER let two entries that share a speaker id end up with different roles just because one entry's text alone is short or ambiguous (e.g. "Or only while walking" from the same speaker id as a clear question a moment earlier is still that speaker's role, not a fresh guess from the fragment alone).

For EACH entry, in the same order given:
1. Assign the speaker role you already decided for that entry's speaker id above: DOCTOR (that speaker is asking questions/giving instructions), PATIENT (that speaker, or anyone accompanying them, is describing symptoms or answering), or SMALL_TALK (greetings/chit-chat unrelated to the intake — only when that speaker's role genuinely isn't DOCTOR or PATIENT, not merely because one entry sounds like small talk while the rest of that speaker's entries are clearly DOCTOR/PATIENT).
2. A single entry very often answers MORE THAN ONE standard question at once — for example 'I have high blood pressure and high cholesterol, but not diabetes' answers three separate standard questions in one sentence. Using the recent context to see what was asked, extract EVERY standard question this entry answers — do not stop after the first one you notice. Only include a mapping when reasonably confident; skip anything you are not sure about.
3. For each mapped question, extract the direct answer to THAT specific question only, stripped of filler words and the parts of the sentence that belong to other questions.
4. If the speaker is DOCTOR, additionally decide whether this entry is a genuinely new clinical question that is NOT covered by the standard bank — a paraphrase or rewording of a standard question does not count, and neither does small talk, an instruction, or an acknowledgement. Only when it is truly a distinct clinical question outside the bank, return it cleaned of filler words as 'customQuestion'. Otherwise return null.

CRITICAL — output alignment: every entry is numbered starting at 0 ("Entry 0", "Entry 1", ...). You MUST return EXACTLY ONE object per entry, and EVERY object MUST include "entryIndex" set to that entry's number. NEVER merge two entries into one object, NEVER split one entry into two objects, and NEVER omit or reorder an index — the caller re-aligns your response by "entryIndex", not by array position, so a missing or duplicated index silently drops that entry's real classification.

rI   a  

Respond with ONLY a JSON array, no markdown, no explanation:
[{"entryIndex": <int>, "speakerRole": "DOCTOR" | "PATIENT" | "SMALL_TALK", "mappings": [{"questionId": "<id>", "answer": "<string>", "confidence": <number between 0.0 and 1.0>}, ...], "customQuestion": "<string or null>", "continuesFromPrevious": <true or false>}, ...]
Return "mappings": [] when nothing maps, "customQuestion": null when it does not apply, and "continuesFromPrevious": false on every entry unless instructed otherwise above.
SMALL_TALKFspeakerRolemappingscustomQuestioncontinuesFromPreviousitemquestion_idsc                    t        | t              st        t              S | j                  d      }|dvrd}| j                  d      }t        |t              sg }g }|D ]  }t        |t              s|j                  d      }||vr*|j                  d      }t        |t
              r|j                         s\	 t        |j                  d      xs d      }|j                  |||d	        | j                  d
      }	|dk7  s t        |	t
              r|	j                         sd }	| j                  d      du }
|||	|
dS # t        t        f$ r d}Y |w xY w)Nr   )DOCTORPATIENTr   r   r   
questionIdrH   
confidenceg        )r   rH   r   r   r   r   Tr   )
isinstancedict_DEFAULT_CLASSIFICATIONr-   liststrr!   float	TypeError
ValueErrorrJ   )r   r   speaker_roleraw_mappingsr   mappingro   rH   r   custom_questioncontinues_from_previouss              r   _parse_one_classificationr     s]   dD!+,,88M*L>>#88J'LlD)H a'4(kk,/l*X&&#&flln	w{{<8?C@J 	{fT^_`a hh/0Oxz/3'GOdOdOf"hh'>?4G $)!8	  :& 	J	s   ;EEEprevious_utterancec                 2    | syd| d    d| d    dt          S )NrD   u   

POSSIBLY INCOMPLETE PREVIOUS UTTERANCE (unresolved — no confident answer matched yet, likely because the speaker paused mid-sentence before finishing): r   : r   r   )_CONTINUATION_INSTRUCTION)r   s    r   _build_previous_utterance_blockr     s:    	FFXYfFgEhhjf%
&b$
%	'r   entriesrecent_utterancesc           	        K   | sg S dt        |       }|r/|D cg c]  }|d    d|d     }}ddj                  |      z   }nd}|t        |      z  }dj                  d t        |       D              }| d	| d
| }	t	        t
        |	       d{   }
t        j                  dd|
j                               }t        j                  dd|      }	 t        j                  |      }t!        |t"              s3t        j                  d|
       | D cg c]  }t        t               c}S |D ch c]  }|d   	 }}i }|D ]B  }t!        |t              s	 t%        |j'                  d            }||v r4t-        ||      ||<   D t/        |      t/        |       k7  r*t        j                  dt/        |       t/        |      |
       t1        t/        |             D cg c]!  }|j'                  |t        t                    # c}S c c}w 7 z# t        j                  $ r< t        j                  d|
       | D cg c]  }t        t               nc c}w c}cY S w xY wc c}w c c}w # t(        t*        f$ r Y ;w xY wc c}w w)ub  Classifies every diarized entry from one audio segment in a single LLM call — one
    result per entry, in order. entries: [{"speakerId": ..., "text": ...}, ...]. previous_utterance,
    when given, is the most recent still-unmapped utterance — a candidate for the FIRST entry to
    continue rather than a fresh turn (see _CONTINUATION_INSTRUCTION).Standard Questions Bank:
r   r   r   ,Recent conversation context (oldest first):
r   C   Recent conversation context:
(none — this is the first utterance)c              3   D   K   | ]  \  }}d | d|d    d|d      yw)zEntry z
 (speaker 	speakerIdz): r   Nr   )r   r   es      r   r   z.classify_and_map_utterances.<locals>.<genexpr>  s5      AEA&:an-S6<s    rI   z*

Diarized entries to classify, in order:
Nr   rD   r   zEclassify_and_map_utterances: could not parse LLM response as JSON: %rzBclassify_and_map_utterances: LLM response was not a JSON array: %rr
   
entryIndexu`   classify_and_map_utterances: entryIndex coverage mismatch — %d entries, %d indexed results: %r)r   r   r   r   rk   _CONVERSATION_CLASSIFY_PROMPTr   ry   r!   r   r   r   rv   warningr   r   r   r   intr-   r   r   r   lenrange)r   r   r   r   rN   ucontext_linescontext_blockentries_blockrV   r   r0   r   _r   r   by_indexr   entry_indexr   s                       r   classify_and_map_utterancesr     s     	23H3S2TUOEVWAm,-R&	{;WWG$))TaJbb^45GHHMII IRSZI[ M 
4 /44A?	D 
 ;\J
JCff("ciik:GffZW-G@G$
 fd#[]`a7>?!,-?? &//AdG/L/ "H 	N$%	dhh|45K (" 9$ M	N 8}G$nL#h-	

 EJ#g,DWXqHLLD!89:XXg X K  @^`cd7>?!,-???@ @ 0 :& 		 Ys   JH	A%JHA JH +*JI#+J2I(>JI-3A-J &J	J-I >I	I JI  J-J <J?J  Ju_  You are a clinical conversation-tagging assistant. You will be shown ONE continuous transcript from a single audio segment of a live, free-flowing conversation during a medical intake. Unlike diarized audio, this transcript has NOT been split by speaker — it is plain text that may contain a doctor's question and the patient's answer (or several such exchanges) run together with no separator, because they were spoken with little pause between them. You are also given the most recent prior classified utterances for context, and a bank of standard intake questions the doctor is expected to cover.

YOUR JOB — in order:
1. SPLIT the transcript into consecutive speaker turns, in the order they occur. Cut at the natural boundary between a question and its answer, or between one exchange and the next — for example "What's your problem? I am suffering from fever. When did it start? Two days ago." splits into FOUR turns: "What's your problem?" / "I am suffering from fever." / "When did it start?" / "Two days ago." Keep each turn's wording as close to verbatim as possible — do not paraphrase, only trim leading/trailing filler if needed. If the transcript is genuinely just one turn with nothing to split, return a single segment.
2. For EACH resulting turn, decide the speaker: DOCTOR (asking a question or giving an instruction), PATIENT (the patient or anyone accompanying them describing symptoms or answering), or SMALL_TALK (greetings, chit-chat, or speech unrelated to the intake). Treat any non-doctor speaker — including a family member or companion answering on the patient's behalf — as PATIENT context.
3. For EACH turn, extract EVERY standard question it answers (a turn can answer more than one at once). Only include a mapping when reasonably confident. For each mapped question, extract the direct answer to THAT question only, stripped of filler words.
4. If a turn's speaker is DOCTOR, decide whether it is a genuinely new clinical question NOT covered by the standard bank (a paraphrase of a standard question does not count). If so, return it cleaned of filler words as 'customQuestion', otherwise null.

uu  

OUTPUT: respond with ONLY a JSON array, no markdown, no explanation. Number your turns starting at 0, in the order they occur, via "segmentIndex" — one object per turn:
[{"segmentIndex": <int>, "speakerRole": "DOCTOR" | "PATIENT" | "SMALL_TALK", "text": "<verbatim turn text>", "mappings": [{"questionId": "<id>", "answer": "<string>", "confidence": <0.0-1.0>}, ...], "customQuestion": "<string or null>", "continuesFromPrevious": <true or false>}, ...]
Return "mappings": [] when nothing maps, "customQuestion": null when it does not apply, and "continuesFromPrevious": false on every turn unless instructed otherwise above.
transcriptc                 L  K   | j                         sg S dt        |       }|r/|D cg c]  }|d    d|d     }}ddj                  |      z   }nd}|t        |      z  }| d| d	|  }t	        t
        |       d
{   }	t        j                  dd|	j                               }
t        j                  dd|
      }
d| g d
ddg}	 t        j                  |
      }t        |t              r|st        j                  d|	       |S |D ch c]  }|d   	 }}g }|D ]  }t        |t              s	 t!        |j#                  d            }|j#                  d      }t        |t(              r|j                         sat+        ||      }|j-                  |i |d|j                         if        |st        j                  d|	       |S |j/                  d        t        t1        t3        |                  }|D cg c]  \  }}|	 c}}|k7  rt        j                  d|	       |D cg c]  \  }}|	 c}}S c c}w 7 # t        j                  $ r t        j                  d|	       |cY S w xY wc c}w # t$        t&        f$ r Y sw xY wc c}}w c c}}w w)u  Text-based alternative to audio diarization — takes ONE flat, non-diarized transcript
    and asks the LLM to both split it into speaker turns AND classify/map each turn, in a
    single call. Used when Sarvam-side diarization is disabled, so the transcript display and
    question mapping can still reflect real doctor/patient turn-taking instead of forcing one
    role onto the whole transcript. previous_utterance, when given, is the most recent still-
    unmapped utterance — a candidate for the FIRST turn to continue rather than a fresh one.
    Returns a list of {"speakerRole", "text", "mappings", "customQuestion", "continuesFromPrevious"}
    in chronological order — same shape classify_and_map_utterances produces, so the caller can
    treat both paths identically.r   r   r   r   r   r   r   rI   z$

Transcript to split and classify: Nr   rD   r   r   F)r   r   r   r   r   zCsegment_and_map_utterance: could not parse LLM response as JSON: %rzJsegment_and_map_utterance: LLM response was not a non-empty JSON array: %rr
   segmentIndexzEsegment_and_map_utterance: no valid segments parsed from response: %rc                     | d   S )Nr   r   )pairs    r   <lambda>z+segment_and_map_utterance.<locals>.<lambda>w  s
    $q' r   )keyuk   segment_and_map_utterance: segmentIndex sequence was not contiguous from 0 — using array order anyway: %r)r!   r   r   r   rk   _CONVERSATION_SEGMENT_PROMPTr   ry   r   r   r   rv   r   r   r   r   r   r-   r   r   r   r   rJ   sortr   r   )r   r   r   r   rN   r   r   r   rV   r   r0   fallbackr   r   r   indexedr   segment_indexr   parsed_classificationexpectedr   r   segments                           r   segment_and_map_utterancer   4  s     	23H3S2TUOEVWAm,-R&	{;WWG$))TaJbb^45GHHM%&d=/9_`j_klL:LI
ICff("ciik:GffZW-G ,jbdh  DI  J  KHG$
 fd#6cehi
 &//AdG/L/&(G Y$%	 89M xx$$DJJL 9$ M'V*?'V'VWXY ^`cdLL)L*E#g,'(Hda(*y	

 '..
7G..m X J  \^ab 0 :& 		  /s   'J$IAJ$I	A	J$I !.J$I=J$6JB6J$J"J$4J 
J$+I:7J$9I::J$JJ$JJ$rF   raw_transcriptc                    K   	 t        dd|  d| d       d {   }|j                         S 7 # t        $ r |cY S w xY ww)Nz#You are a medical intake assistant.zQuestion asked to patient: "z"
Transcript: "u  "
Extract ONLY the patient's direct answer to this medical question. Remove background conversation, side talk, and unrelated speech. Preserve all medical details in the answer — including symptoms, duration, timing, severity, and any descriptions the patient gave. If there is no valid patient response at all, return empty string. Return only the extracted response, nothing else.)rk   r!   	Exception)rF   r   results      r   extract_patient_answerr     sd     $1*8* 5*+ ,@A	
 	
 ||~	
  s,   A1 /1 A1 ?A?A)NENGLISH)N)r   ).__doc__r   loggingr   rd   app.core.configr   	getLogger__name__rv   rx   _SKIP_ANSWEREDrK   rL   r   r   r   r   compiler   
IGNORECASE	MULTILINEr   r)   tupler3   rT   rk   rq   r|   r   r   r   r   r   _DOCTOR_PRIORITY_INSTRUCTIONr   r   objectsetr   r   r   r   r   r   r   r   r   <module>r      sK     	  $			8	$) N ,O Z.  G/G >`T$Z `C ` rzz892::2BMMBLL4PQ 	; 	;d 	;S	 	;S T$Z E#PT*VY/DZ ( )-%%% % Dz	%
 #Y%% % 	%P7s 7# 7# 7$ )-III DzI #Y%	I
 I 
I0 )-||| | 	|
 Dz| #Y%| | 
|@ )-&8&8&8 &8 	&8
 #Y%&8 	&8V	A	A $7s 7c 7 7(73 73 73 7@ W )fT !!#$ %WW1W j +7BZ^y~ 'F '#c( 't 'Tt   '+	CY$ZCYDzCY DzCY t	CY
 
$ZCYN<0 !!#$ %V3 V R '+	K/K/DzK/ DzK/ t	K/
 
$ZK/\3   r   