Commit ·
6d0e73e
1
Parent(s): 5ca3216
Prioritize explicit player payment service routing
Browse files
apps/chat/services/backend_orchestration.py
CHANGED
|
@@ -31,6 +31,17 @@ REQUIRED_FIELDS = {
|
|
| 31 |
}
|
| 32 |
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
def decide_chatbot_response(message: str, context: Dict[str, Any], bot_payload: Dict[str, Any]) -> Dict[str, Any]:
|
| 35 |
msg = (message or "").strip()
|
| 36 |
allowed = set(context.get("allowed_service_ids") or [])
|
|
@@ -50,7 +61,7 @@ def decide_chatbot_response(message: str, context: Dict[str, Any], bot_payload:
|
|
| 50 |
return {
|
| 51 |
"type": "CLARIFICATION_REQUIRED",
|
| 52 |
"service_id": service_id,
|
| 53 |
-
"confidence":
|
| 54 |
"missing_fields": missing,
|
| 55 |
"question": "Please provide " + ", ".join(missing) + " so I can understand the request.",
|
| 56 |
"safety": {"safe": True, "risk_level": "LOW"},
|
|
@@ -58,7 +69,7 @@ def decide_chatbot_response(message: str, context: Dict[str, Any], bot_payload:
|
|
| 58 |
return {
|
| 59 |
"type": "SERVICE_REQUEST",
|
| 60 |
"service_id": service_id,
|
| 61 |
-
"confidence":
|
| 62 |
"missing_fields": [],
|
| 63 |
"extracted_data": extracted,
|
| 64 |
"user_message": "The service intent and parameters were detected. The backend must authorize and execute it.",
|
|
@@ -91,7 +102,7 @@ def decide_chatbot_response(message: str, context: Dict[str, Any], bot_payload:
|
|
| 91 |
"type": "KNOWLEDGE_QA",
|
| 92 |
"answer": bot_payload.get("response") or bot_payload.get("answer") or "I could not find a confident answer.",
|
| 93 |
"response": bot_payload.get("response") or bot_payload.get("answer") or "I could not find a confident answer.",
|
| 94 |
-
"confidence":
|
| 95 |
"source": bot_payload.get("source") or "data.csv",
|
| 96 |
"matched_question": bot_payload.get("matched_question"),
|
| 97 |
"tenant_scope": context.get("tenant_scope") or "GLOBAL",
|
|
@@ -139,4 +150,4 @@ def finalize_service_response(payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 139 |
message = result.get("safe_summary") or "I need your confirmation before completing this action."
|
| 140 |
else:
|
| 141 |
message = result.get("safe_summary") or result.get("error") or f"I could not complete the {service_id.replace('_', ' ')} service."
|
| 142 |
-
return {"type": "SERVICE_FINAL_RESPONSE", "message": str(message), "response": str(message), "service_id": service_id, "status": status}
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
|
| 34 |
+
def _public_confidence(value: Any, default: float = 0.0) -> float:
|
| 35 |
+
"""Normalize internal percentage/ranking scores to the public 0..1 contract."""
|
| 36 |
+
try:
|
| 37 |
+
score = float(value)
|
| 38 |
+
except (TypeError, ValueError):
|
| 39 |
+
score = default
|
| 40 |
+
if score > 1.0:
|
| 41 |
+
score /= 100.0
|
| 42 |
+
return round(max(0.0, min(score, 1.0)), 4)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
def decide_chatbot_response(message: str, context: Dict[str, Any], bot_payload: Dict[str, Any]) -> Dict[str, Any]:
|
| 46 |
msg = (message or "").strip()
|
| 47 |
allowed = set(context.get("allowed_service_ids") or [])
|
|
|
|
| 61 |
return {
|
| 62 |
"type": "CLARIFICATION_REQUIRED",
|
| 63 |
"service_id": service_id,
|
| 64 |
+
"confidence": _public_confidence(request.get("confidence") or bot_payload.get("score"), 0.8),
|
| 65 |
"missing_fields": missing,
|
| 66 |
"question": "Please provide " + ", ".join(missing) + " so I can understand the request.",
|
| 67 |
"safety": {"safe": True, "risk_level": "LOW"},
|
|
|
|
| 69 |
return {
|
| 70 |
"type": "SERVICE_REQUEST",
|
| 71 |
"service_id": service_id,
|
| 72 |
+
"confidence": _public_confidence(request.get("confidence") or bot_payload.get("score"), 0.8),
|
| 73 |
"missing_fields": [],
|
| 74 |
"extracted_data": extracted,
|
| 75 |
"user_message": "The service intent and parameters were detected. The backend must authorize and execute it.",
|
|
|
|
| 102 |
"type": "KNOWLEDGE_QA",
|
| 103 |
"answer": bot_payload.get("response") or bot_payload.get("answer") or "I could not find a confident answer.",
|
| 104 |
"response": bot_payload.get("response") or bot_payload.get("answer") or "I could not find a confident answer.",
|
| 105 |
+
"confidence": _public_confidence(bot_payload.get("score") or bot_payload.get("confidence"), 0.0),
|
| 106 |
"source": bot_payload.get("source") or "data.csv",
|
| 107 |
"matched_question": bot_payload.get("matched_question"),
|
| 108 |
"tenant_scope": context.get("tenant_scope") or "GLOBAL",
|
|
|
|
| 150 |
message = result.get("safe_summary") or "I need your confirmation before completing this action."
|
| 151 |
else:
|
| 152 |
message = result.get("safe_summary") or result.get("error") or f"I could not complete the {service_id.replace('_', ' ')} service."
|
| 153 |
+
return {"type": "SERVICE_FINAL_RESPONSE", "message": str(message), "response": str(message), "service_id": service_id, "status": status}
|
apps/chat/services/service_detection.py
CHANGED
|
@@ -618,6 +618,27 @@ class ServiceDetector:
|
|
| 618 |
' pending ',
|
| 619 |
' overdue ',
|
| 620 |
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
if wants_personal_payments:
|
| 622 |
matches.append({
|
| 623 |
'service_id': 'get-pending-payments' if wants_pending_payment_list else 'get-payment-status',
|
|
|
|
| 618 |
' pending ',
|
| 619 |
' overdue ',
|
| 620 |
))
|
| 621 |
+
wants_player_payment_data = (
|
| 622 |
+
any(term in padded for term in (' player ', ' footballer ', ' athlete '))
|
| 623 |
+
and any(term in padded for term in (
|
| 624 |
+
' payment ',
|
| 625 |
+
' payments ',
|
| 626 |
+
' invoice ',
|
| 627 |
+
' invoices ',
|
| 628 |
+
' bill ',
|
| 629 |
+
' bills ',
|
| 630 |
+
' fee ',
|
| 631 |
+
' fees ',
|
| 632 |
+
))
|
| 633 |
+
)
|
| 634 |
+
if wants_player_payment_data:
|
| 635 |
+
matches.append({
|
| 636 |
+
'service_id': 'get-player-payments',
|
| 637 |
+
'confidence': 133.0,
|
| 638 |
+
'source': 'rule_based_operation',
|
| 639 |
+
'matched_example': 'payments for a player',
|
| 640 |
+
})
|
| 641 |
+
|
| 642 |
if wants_personal_payments:
|
| 643 |
matches.append({
|
| 644 |
'service_id': 'get-pending-payments' if wants_pending_payment_list else 'get-payment-status',
|
apps/chat/tests/test_service_integration.py
CHANGED
|
@@ -504,6 +504,43 @@ def test_mobile_service_prompts_route_before_kb():
|
|
| 504 |
assert detected[0]["service_id"] == service_id
|
| 505 |
|
| 506 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
def test_model_extraction_enriches_detected_service_parameters():
|
| 508 |
"""Detected services should be enriched by the trained model without replacing context IDs."""
|
| 509 |
from apps.chat.services.service_detection import get_service_detector
|
|
|
|
| 504 |
assert detected[0]["service_id"] == service_id
|
| 505 |
|
| 506 |
|
| 507 |
+
def test_explicit_player_payment_terms_beat_player_profile_match():
|
| 508 |
+
"""Payment nouns must select payment routing even when a player name is present."""
|
| 509 |
+
from apps.chat.services.service_detection import get_service_detector
|
| 510 |
+
|
| 511 |
+
detector = get_service_detector()
|
| 512 |
+
detected = detector.detect_service_requests(
|
| 513 |
+
"show invoices for player John Smith from 2026-06-01 to 2026-06-30",
|
| 514 |
+
context={"user_id": 1, "role": "ADMIN", "academy_id": 32},
|
| 515 |
+
threshold=70.0,
|
| 516 |
+
)
|
| 517 |
+
|
| 518 |
+
assert detected
|
| 519 |
+
assert detected[0]["service_id"] == "get-player-payments"
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
def test_public_service_confidence_is_normalized():
|
| 523 |
+
"""Internal router ranking scores must not leak outside the 0..1 API contract."""
|
| 524 |
+
from apps.chat.services.backend_orchestration import decide_chatbot_response
|
| 525 |
+
|
| 526 |
+
response = decide_chatbot_response(
|
| 527 |
+
"show player payments",
|
| 528 |
+
{},
|
| 529 |
+
{
|
| 530 |
+
"service_requests": [
|
| 531 |
+
{
|
| 532 |
+
"service_id": "get-player-payments",
|
| 533 |
+
"confidence": 133.0,
|
| 534 |
+
"parameters": {"player": "John Smith"},
|
| 535 |
+
}
|
| 536 |
+
]
|
| 537 |
+
},
|
| 538 |
+
)
|
| 539 |
+
|
| 540 |
+
assert response["type"] == "SERVICE_REQUEST"
|
| 541 |
+
assert response["confidence"] == 1.0
|
| 542 |
+
|
| 543 |
+
|
| 544 |
def test_model_extraction_enriches_detected_service_parameters():
|
| 545 |
"""Detected services should be enriched by the trained model without replacing context IDs."""
|
| 546 |
from apps.chat.services.service_detection import get_service_detector
|