| Line 84... |
Line 84... |
| 84 |
|
84 |
|
| 85 |
/** Source/createdBy label for leads captured by the AI assistant. */
|
85 |
/** Source/createdBy label for leads captured by the AI assistant. */
|
| 86 |
private static final String AI_LEAD_SOURCE = "AI Assistant";
|
86 |
private static final String AI_LEAD_SOURCE = "AI Assistant";
|
| 87 |
|
87 |
|
| 88 |
/**
|
88 |
/**
|
| 89 |
* Assignee used for AI leads only when no active Sales L1 can be resolved from cs.position.
|
89 |
* Assignee used for AI leads only when no active BGC L1 can be resolved from cs.position.
|
| 90 |
* Same auth id the web capture has always routed to, so intake never fails on an empty pool.
|
90 |
* Same auth id the web capture has always routed to, so intake never fails on an empty pool.
|
| 91 |
*/
|
91 |
*/
|
| 92 |
private static final int AI_LEAD_FALLBACK_ASSIGNEE = 53;
|
92 |
private static final int AI_LEAD_FALLBACK_ASSIGNEE = 53;
|
| 93 |
|
93 |
|
| 94 |
@Autowired
|
94 |
@Autowired
|
| Line 805... |
Line 805... |
| 805 |
String recordingUrl = aiLeadRequest.getRecordingUrl();
|
805 |
String recordingUrl = aiLeadRequest.getRecordingUrl();
|
| 806 |
if (recordingUrl != null && !recordingUrl.trim().isEmpty()) {
|
806 |
if (recordingUrl != null && !recordingUrl.trim().isEmpty()) {
|
| 807 |
lead.setRecordingUrl(recordingUrl.trim());
|
807 |
lead.setRecordingUrl(recordingUrl.trim());
|
| 808 |
}
|
808 |
}
|
| 809 |
|
809 |
|
| 810 |
// Auto-assign to a randomly picked active Sales L1 so AI leads spread across the field team
|
810 |
// Auto-assign to a randomly picked active BGC L1 so AI leads spread across the lead desk
|
| 811 |
lead.setAssignTo(resolveAiLeadAssignee());
|
811 |
lead.setAssignTo(resolveAiLeadAssignee());
|
| 812 |
lead.setAuthId(lead.getAssignTo());
|
812 |
lead.setAuthId(lead.getAssignTo());
|
| 813 |
|
813 |
|
| 814 |
lead.setCreatedTimestamp(LocalDateTime.now());
|
814 |
lead.setCreatedTimestamp(LocalDateTime.now());
|
| 815 |
lead.setUpdatedTimestamp(LocalDateTime.now());
|
815 |
lead.setUpdatedTimestamp(LocalDateTime.now());
|
| Line 819... |
Line 819... |
| 819 |
return responseSender.ok(lead.getId());
|
819 |
return responseSender.ok(lead.getId());
|
| 820 |
|
820 |
|
| 821 |
}
|
821 |
}
|
| 822 |
|
822 |
|
| 823 |
/**
|
823 |
/**
|
| 824 |
* Picks the owner for an incoming AI lead: a uniformly random <b>active</b> Sales L1 from
|
824 |
* Picks the owner for an incoming AI lead: a uniformly random <b>active</b> BGC L1 from
|
| 825 |
* cs.position (category {@link ProfitMandiConstants#TICKET_CATEGORY_SALES}, escalation L1).
|
825 |
* cs.position (category {@link ProfitMandiConstants#TICKET_CATEGORY_BGC}, escalation L1).
|
| 826 |
* <p>
|
826 |
* <p>
|
| - |
|
827 |
* BGC is the desk that works AI/inbound leads before they reach field sales, and BGC L1-L3 is
|
| 827 |
* Sales L1 is the same position that drives Lead Management visibility, so the chosen user sees
|
828 |
* already one of the groups offered in the Lead Management "Assign To" list, so a BGC L1 owner
|
| 828 |
* the lead in their own list and their L2/L3 managers see it in their downline. Inactive users
|
829 |
* is a valid assignee the team can see and reassign. Field Sales (category
|
| - |
|
830 |
* {@link ProfitMandiConstants#TICKET_CATEGORY_SALES}) is deliberately NOT the pool here.
|
| - |
|
831 |
* <p>
|
| 829 |
* are filtered out here because {@code getAuthUserByCategoryId(int, EscalationType)} does not
|
832 |
* Inactive users are filtered out because {@code getAuthUserByCategoryId(int, EscalationType)}
|
| 830 |
* filter on active, and an inactive owner would strand the lead. Ids are already distinct per
|
833 |
* does not filter on active, and an inactive owner would strand the lead. Ids are already
|
| 831 |
* user, so holding several regional positions does not raise a person's odds of being picked.
|
834 |
* distinct per user, so holding several regional positions does not raise a person's odds.
|
| 832 |
*/
|
835 |
*/
|
| 833 |
private int resolveAiLeadAssignee() {
|
836 |
private int resolveAiLeadAssignee() {
|
| 834 |
List<AuthUser> salesL1 = csService.getAuthUserByCategoryId(
|
837 |
List<AuthUser> bgcL1 = csService.getAuthUserByCategoryId(
|
| 835 |
ProfitMandiConstants.TICKET_CATEGORY_SALES, EscalationType.L1);
|
838 |
ProfitMandiConstants.TICKET_CATEGORY_BGC, EscalationType.L1);
|
| 836 |
|
839 |
|
| 837 |
List<AuthUser> eligible = salesL1 == null ? Collections.<AuthUser>emptyList()
|
840 |
List<AuthUser> eligible = bgcL1 == null ? Collections.<AuthUser>emptyList()
|
| 838 |
: salesL1.stream()
|
841 |
: bgcL1.stream()
|
| 839 |
.filter(x -> Boolean.TRUE.equals(x.isActive()))
|
842 |
.filter(x -> Boolean.TRUE.equals(x.isActive()))
|
| 840 |
.collect(Collectors.toList());
|
843 |
.collect(Collectors.toList());
|
| 841 |
|
844 |
|
| 842 |
if (eligible.isEmpty()) {
|
845 |
if (eligible.isEmpty()) {
|
| 843 |
LOGGER.warn("AI lead intake: no active Sales L1 in cs.position, falling back to auth id {}",
|
846 |
LOGGER.warn("AI lead intake: no active BGC L1 in cs.position, falling back to auth id {}",
|
| 844 |
AI_LEAD_FALLBACK_ASSIGNEE);
|
847 |
AI_LEAD_FALLBACK_ASSIGNEE);
|
| 845 |
return AI_LEAD_FALLBACK_ASSIGNEE;
|
848 |
return AI_LEAD_FALLBACK_ASSIGNEE;
|
| 846 |
}
|
849 |
}
|
| 847 |
|
850 |
|
| 848 |
AuthUser assignee = eligible.get(ThreadLocalRandom.current().nextInt(eligible.size()));
|
851 |
AuthUser assignee = eligible.get(ThreadLocalRandom.current().nextInt(eligible.size()));
|
| 849 |
LOGGER.info("AI lead intake: assigned to Sales L1 {} ({}) picked from {} eligible",
|
852 |
LOGGER.info("AI lead intake: assigned to BGC L1 {} ({}) picked from {} eligible",
|
| 850 |
assignee.getId(), assignee.getFullName(), eligible.size());
|
853 |
assignee.getId(), assignee.getFullName(), eligible.size());
|
| 851 |
return assignee.getId();
|
854 |
return assignee.getId();
|
| 852 |
}
|
855 |
}
|
| 853 |
|
856 |
|
| 854 |
@RequestMapping(value = "/getPartnersList", method = RequestMethod.GET)
|
857 |
@RequestMapping(value = "/getPartnersList", method = RequestMethod.GET)
|