@@ -22,6 +22,20 @@ pub struct StepIvrApp {
2222 step_index : u32 ,
2323 ivr_name : Option < String > ,
2424 rwi_gateway : Option < crate :: rwi:: RwiGatewayRef > ,
25+ /// Name of the route that dispatched this call into the IVR.
26+ route_name : Option < String > ,
27+ /// Route-level configured headers.
28+ route_headers : Option < HashMap < String , String > > ,
29+ /// Passthrough data set by the external provider (echoed back each step).
30+ custom_data : Option < serde_json:: Value > ,
31+ /// Previous step start time (RFC3339) for timing reporting.
32+ step_prev_start_time : Option < String > ,
33+ /// Previous step wall-clock duration in ms.
34+ step_prev_duration_ms : u64 ,
35+ /// How this session entered IVR: `None` (fresh inbound), `"agent"`, `"queue"`.
36+ transferred_from : Option < String > ,
37+ /// Last transfer target string (for EndReason classification).
38+ last_transfer_target : Option < String > ,
2539}
2640
2741#[ derive( Clone ) ]
@@ -48,6 +62,13 @@ impl StepIvrApp {
4862 step_index : 0 ,
4963 ivr_name : None ,
5064 rwi_gateway : None ,
65+ route_name : None ,
66+ route_headers : None ,
67+ custom_data : None ,
68+ step_prev_start_time : None ,
69+ step_prev_duration_ms : 0 ,
70+ transferred_from : None ,
71+ last_transfer_target : None ,
5172 }
5273 }
5374
@@ -64,6 +85,13 @@ impl StepIvrApp {
6485 step_index : 0 ,
6586 ivr_name : None ,
6687 rwi_gateway : None ,
88+ route_name : None ,
89+ route_headers : None ,
90+ custom_data : None ,
91+ step_prev_start_time : None ,
92+ step_prev_duration_ms : 0 ,
93+ transferred_from : None ,
94+ last_transfer_target : None ,
6795 }
6896 }
6997
@@ -98,6 +126,24 @@ impl StepIvrApp {
98126 self
99127 }
100128
129+ /// Set the route name that dispatched this call into the IVR.
130+ pub fn with_route_name ( mut self , name : Option < String > ) -> Self {
131+ self . route_name = name;
132+ self
133+ }
134+
135+ /// Set the route-level configured headers.
136+ pub fn with_route_headers ( mut self , headers : Option < HashMap < String , String > > ) -> Self {
137+ self . route_headers = headers;
138+ self
139+ }
140+
141+ /// Mark this session as re-entered from agent or queue.
142+ pub fn with_transferred_from ( mut self , from : Option < String > ) -> Self {
143+ self . transferred_from = from;
144+ self
145+ }
146+
101147 fn record_trace ( & self , entry : IvrTraceEntry ) {
102148 if let Some ( t) = self . effective_trace ( ) {
103149 let ent = entry. clone ( ) ;
@@ -258,9 +304,10 @@ impl StepIvrApp {
258304 error : None ,
259305 } ) ;
260306 match terminal {
261- TerminalAction :: Transfer ( target) => {
262- ( AppAction :: Transfer ( target) , "terminal" )
263- }
307+ TerminalAction :: Transfer ( target) => {
308+ self . last_transfer_target = Some ( target. clone ( ) ) ;
309+ ( AppAction :: Transfer ( target) , "terminal" )
310+ }
264311 TerminalAction :: Hangup { reason, code } => {
265312 ( AppAction :: Hangup { reason, code } , "terminal" )
266313 }
@@ -319,7 +366,9 @@ impl StepIvrApp {
319366 }
320367 }
321368
322- async fn request_next ( & self , event : Option < ProviderEvent > ) -> anyhow:: Result < ActionNode > {
369+ async fn request_next ( & mut self , event : Option < ProviderEvent > ) -> anyhow:: Result < ActionNode > {
370+ let now_rfc3339 = chrono:: Utc :: now ( ) . to_rfc3339 ( ) ;
371+ let prev_step_duration_ms = self . step_prev_duration_ms ;
323372 let ctx = ProviderContext {
324373 session_id : self
325374 . sess
@@ -350,12 +399,25 @@ impl StepIvrApp {
350399 variables : self . sess . variables . clone ( ) ,
351400 sip_headers : self . get_sip_headers ( ) ,
352401 event,
402+ route_name : self . route_name . clone ( ) ,
403+ route_headers : self . route_headers . clone ( ) ,
404+ custom_data : self . custom_data . clone ( ) ,
405+ step_start_time : Some ( self . step_prev_start_time . clone ( ) . unwrap_or_else ( || now_rfc3339. clone ( ) ) ) ,
406+ step_end_time : Some ( now_rfc3339. clone ( ) ) ,
407+ step_duration_ms : if prev_step_duration_ms > 0 { Some ( prev_step_duration_ms) } else { None } ,
408+ step_index : Some ( self . step_index ) ,
409+ transferred_from : self . transferred_from . clone ( ) ,
353410 } ;
354411
355412 let start = std:: time:: Instant :: now ( ) ;
356413 let result = self . provider . next_action ( ctx. clone ( ) ) . await ;
357414 let elapsed_ms = start. elapsed ( ) . as_millis ( ) as u64 ;
358415
416+ // Save step timing for the next ProviderContext.
417+ self . step_prev_start_time = Some ( now_rfc3339) ;
418+ self . step_prev_duration_ms = elapsed_ms;
419+ self . step_index += 1 ;
420+
359421 // Trace the provider call
360422 let trace_action_type = match & result {
361423 Ok ( node) => match & node. action {
@@ -680,9 +742,15 @@ impl CallApp for StepIvrApp {
680742 tenant_id : None ,
681743 ivr_id : None ,
682744 sip_headers : Some ( headers) ,
745+ route_name : self . route_name . clone ( ) ,
746+ route_headers : self . route_headers . clone ( ) ,
747+ custom_data : self . custom_data . clone ( ) ,
748+ transferred_from : self . transferred_from . clone ( ) ,
683749 } ;
684750 self . provider . on_session_start ( & sess_ctx) . await . ok ( ) ;
685751
752+ self . step_prev_start_time = Some ( chrono:: Utc :: now ( ) . to_rfc3339 ( ) ) ;
753+
686754 self . record_session_start (
687755 & context. call_info . session_id ,
688756 & context. call_info . caller ,
@@ -811,17 +879,30 @@ impl CallApp for StepIvrApp {
811879 async fn on_exit ( & mut self , reason : crate :: call:: app:: ExitReason ) -> anyhow:: Result < ( ) > {
812880 let end_reason = match reason {
813881 crate :: call:: app:: ExitReason :: Normal => EndReason :: Normal ,
814- crate :: call:: app:: ExitReason :: Hangup
815- | crate :: call:: app:: ExitReason :: RemoteHangup ( _) => EndReason :: Hangup ,
816- crate :: call:: app:: ExitReason :: Transferred => EndReason :: Transfer ( String :: new ( ) ) ,
882+ crate :: call:: app:: ExitReason :: Hangup => EndReason :: Hangup ,
883+ crate :: call:: app:: ExitReason :: RemoteHangup ( _) => EndReason :: UserHangup ,
884+ crate :: call:: app:: ExitReason :: Transferred => {
885+ // Determine transfer target type from the last action.
886+ let target = self . last_transfer_target . clone ( ) . unwrap_or_default ( ) ;
887+ if target. starts_with ( "queue:" ) {
888+ EndReason :: TransferToQueue ( target)
889+ } else if target. starts_with ( "toivr:" ) || target. starts_with ( "ivr:" ) {
890+ EndReason :: TransferToIvr ( target)
891+ } else {
892+ EndReason :: Transfer ( target)
893+ }
894+ }
817895 crate :: call:: app:: ExitReason :: Error ( e) => EndReason :: Error ( e) ,
818896 _ => EndReason :: Normal ,
819897 } ;
820898 self . provider . on_session_end ( & end_reason) . await . ok ( ) ;
821899 let status = match & end_reason {
822900 EndReason :: Normal => "completed" ,
823901 EndReason :: Transfer ( _) => "completed" ,
902+ EndReason :: TransferToQueue ( _) => "completed" ,
903+ EndReason :: TransferToIvr ( _) => "completed" ,
824904 EndReason :: Hangup => "completed" ,
905+ EndReason :: UserHangup => "completed" ,
825906 EndReason :: Error ( _) => "error" ,
826907 } ;
827908 self . record_session_end ( status) . await ;
@@ -1486,6 +1567,10 @@ mod tests {
14861567 tenant_id : None ,
14871568 ivr_id : None ,
14881569 sip_headers : None ,
1570+ route_name : None ,
1571+ route_headers : None ,
1572+ custom_data : None ,
1573+ transferred_from : None ,
14891574 } ;
14901575 step_provider
14911576 . on_session_start ( & session)
@@ -1502,6 +1587,14 @@ mod tests {
15021587 variables : HashMap :: new ( ) ,
15031588 sip_headers : None ,
15041589 event : Some ( ProviderEvent :: SessionStart ) ,
1590+ route_name : None ,
1591+ route_headers : None ,
1592+ custom_data : None ,
1593+ step_start_time : None ,
1594+ step_end_time : None ,
1595+ step_duration_ms : None ,
1596+ step_index : None ,
1597+ transferred_from : None ,
15051598 } ;
15061599 let prompt = step_provider. next_action ( ctx) . await . unwrap ( ) ;
15071600 assert ! (
@@ -1523,6 +1616,14 @@ mod tests {
15231616 variables : HashMap :: new ( ) ,
15241617 sip_headers : None ,
15251618 event : None ,
1619+ route_name : None ,
1620+ route_headers : None ,
1621+ custom_data : None ,
1622+ step_start_time : None ,
1623+ step_end_time : None ,
1624+ step_duration_ms : None ,
1625+ step_index : None ,
1626+ transferred_from : None ,
15261627 }
15271628 } ;
15281629 let transfer = step_provider. next_action ( ctx) . await . unwrap ( ) ;
0 commit comments