yoursdvniel commited on
Commit
40dca72
·
verified ·
1 Parent(s): 383eb71

Assignments page endpoints

Browse files
Files changed (1) hide show
  1. main.py +215 -0
main.py CHANGED
@@ -882,6 +882,47 @@ def _normalize_sme_intake_result(raw: Dict[str, Any]) -> Dict[str, Any]:
882
  ],
883
  }
884
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
885
  # -- route ---------------------------------------------------------------
886
 
887
  @app.route('/chat', methods=['POST'])
@@ -935,6 +976,180 @@ def chat():
935
  final_response = ask_gpt([system_msg, data_msg, user_msg])
936
  return jsonify({"reply": final_response})
937
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
938
  # --- NEW: lightweight Help/Assistant intent router -------------------------
939
  @app.route('/assist', methods=['POST'])
940
  def assist():
 
882
  ],
883
  }
884
 
885
+ def build_assignments_page_context(company_code, user_id, program_id, role, user_context):
886
+ """
887
+ Server-side mirror of the assignments page.
888
+
889
+ Must return:
890
+ - current user's department
891
+ - eligible SMEs
892
+ - each SME's required interventions from diagnosticPlans
893
+ - confirmation status per department
894
+ - existing assignedInterventions
895
+ - available consultants for department
896
+ - recurring / once-off assignment metadata
897
+ """
898
+ # Fetch:
899
+ # users / operationsStaff / consultants
900
+ # departments
901
+ # diagnosticPlans
902
+ # applications
903
+ # participants
904
+ # interventions
905
+ # assignedInterventions
906
+
907
+ return {
908
+ "page": "assignments",
909
+ "companyCode": company_code,
910
+ "programId": program_id or "all",
911
+ "department": {},
912
+ "rules": {
913
+ "smmeMustHaveAcceptedApplication": True,
914
+ "diagnosticPlanMustBeConfirmedByDepartment": True,
915
+ "diagnosticPlanMustBeConfirmedBySmme": True,
916
+ "groupedAssignmentRequiresSharedIntervention": True,
917
+ "recurringCannotSkipOpenPreviousCycle": True,
918
+ "onceOffCannotRepeatAfterCompletion": True,
919
+ },
920
+ "smes": [],
921
+ "interventions": [],
922
+ "assignments": [],
923
+ "consultants": []
924
+ }
925
+
926
  # -- route ---------------------------------------------------------------
927
 
928
  @app.route('/chat', methods=['POST'])
 
976
  final_response = ask_gpt([system_msg, data_msg, user_msg])
977
  return jsonify({"reply": final_response})
978
 
979
+ @app.route("/page-assist", methods=["POST"])
980
+ def page_assist():
981
+ data = request.json or {}
982
+
983
+ page = data.get("page")
984
+ message = data.get("message")
985
+ role = data.get("role")
986
+ company_code = data.get("companyCode")
987
+ user_id = data.get("userId")
988
+ program_id = data.get("programId") # optional, "all" allowed
989
+
990
+ if not page or not message or not role or not company_code or not user_id:
991
+ return jsonify({"error": "Missing page, message, role, companyCode, or userId"}), 400
992
+
993
+ if page != "assignments":
994
+ return jsonify({"error": "Unsupported page"}), 400
995
+
996
+ ctx = resolve_user_context(user_id, company_code)
997
+
998
+ assignment_context = build_assignments_page_context(
999
+ company_code=company_code,
1000
+ user_id=user_id,
1001
+ program_id=None if program_id in [None, "", "all"] else program_id,
1002
+ role=role,
1003
+ user_context=ctx,
1004
+ )
1005
+
1006
+ system_msg = {
1007
+ "role": "system",
1008
+ "content": """
1009
+ You are a page-aware assistant for the Interventions Assignments page.
1010
+
1011
+ Answer using ONLY the supplied page context.
1012
+ Explain blockers clearly.
1013
+ Do not invent SMEs, interventions, departments, or assignment rules.
1014
+ If asked why an intervention cannot be assigned, identify the exact reason:
1015
+ - not part of that SME's Diagnostic Plan
1016
+ - Diagnostic Plan not confirmed by both department and SME
1017
+ - not shared across selected SMEs
1018
+ - previous recurring cycle still open
1019
+ - once-off intervention already completed
1020
+ - missing facilitator, due date, target mode, or sub-intervention
1021
+ Return concise JSON:
1022
+ {
1023
+ "reply": "",
1024
+ "reasonCode": "",
1025
+ "affectedSmes": [],
1026
+ "eligibleSmes": [],
1027
+ "blockedSmes": [],
1028
+ "suggestedNextAction": ""
1029
+ }
1030
+ """
1031
+ }
1032
+
1033
+ user_msg = {
1034
+ "role": "user",
1035
+ "content": json.dumps({
1036
+ "question": message,
1037
+ "pageContext": assignment_context,
1038
+ }, ensure_ascii=False)
1039
+ }
1040
+
1041
+ ai_raw = ask_gpt([system_msg, user_msg])
1042
+ result = _extract_json_block(ai_raw)
1043
+
1044
+ return jsonify(to_jsonable(result))
1045
+
1046
+ @app.route("/assignments-agent", methods=["POST"])
1047
+ def assignments_agent():
1048
+ data = request.json or {}
1049
+
1050
+ company_code = data.get("companyCode")
1051
+ user_id = data.get("userId")
1052
+ role = data.get("role")
1053
+ message = data.get("message") or data.get("transcript")
1054
+ program_id = data.get("programId")
1055
+
1056
+ if not company_code or not user_id or not role or not message:
1057
+ return jsonify({"error": "Missing companyCode, userId, role, or message"}), 400
1058
+
1059
+ ctx = resolve_user_context(user_id, company_code)
1060
+
1061
+ assignment_context = build_assignments_page_context(
1062
+ company_code=company_code,
1063
+ user_id=user_id,
1064
+ program_id=None if program_id in [None, "", "all"] else program_id,
1065
+ role=role,
1066
+ user_context=ctx,
1067
+ )
1068
+
1069
+ system_msg = {
1070
+ "role": "system",
1071
+ "content": """
1072
+ You are an assignment action planner.
1073
+
1074
+ Extract the user's intent, but DO NOT save anything directly.
1075
+ Return strict JSON:
1076
+ {
1077
+ "intent": "assign_intervention|explain|unknown",
1078
+ "interventionName": "",
1079
+ "smeNames": [],
1080
+ "assignmentType": "singular|grouped|unknown",
1081
+ "assignTo": "consultant|operations|unknown",
1082
+ "consultantName": "",
1083
+ "dueDate": "",
1084
+ "targetMode": "hours|sessions|documents|milestones|percentage|unknown",
1085
+ "missingFields": [],
1086
+ "canExecute": false,
1087
+ "validationErrors": [],
1088
+ "confirmationMessage": ""
1089
+ }
1090
+
1091
+ Required fields before execution:
1092
+ - interventionName
1093
+ - at least one SME
1094
+ - assignTo
1095
+ - dueDate
1096
+ - targetMode
1097
+ - consultantName if assignTo is consultant
1098
+
1099
+ Validate against pageContext.
1100
+ """
1101
+ }
1102
+
1103
+ user_msg = {
1104
+ "role": "user",
1105
+ "content": json.dumps({
1106
+ "message": message,
1107
+ "pageContext": assignment_context,
1108
+ }, ensure_ascii=False)
1109
+ }
1110
+
1111
+ ai_raw = ask_gpt([system_msg, user_msg])
1112
+ plan = _extract_json_block(ai_raw)
1113
+
1114
+ if not plan.get("canExecute"):
1115
+ return jsonify({
1116
+ "mode": "needs_more_info",
1117
+ "plan": plan,
1118
+ "reply": plan.get("confirmationMessage") or "I need more information before creating the assignment."
1119
+ })
1120
+
1121
+ validation = validate_assignment_plan(plan, assignment_context)
1122
+
1123
+ if not validation["ok"]:
1124
+ return jsonify({
1125
+ "mode": "blocked",
1126
+ "reply": validation["message"],
1127
+ "validationErrors": validation["errors"],
1128
+ "plan": plan
1129
+ })
1130
+
1131
+ # Do not write immediately unless frontend sends confirmed=true
1132
+ if not data.get("confirmed"):
1133
+ return jsonify({
1134
+ "mode": "confirmation_required",
1135
+ "reply": validation["confirmationMessage"],
1136
+ "plan": plan
1137
+ })
1138
+
1139
+ saved = create_assignments_from_plan(
1140
+ plan=plan,
1141
+ company_code=company_code,
1142
+ user_id=user_id,
1143
+ user_context=ctx,
1144
+ assignment_context=assignment_context,
1145
+ )
1146
+
1147
+ return jsonify({
1148
+ "mode": "executed",
1149
+ "reply": f"Created {saved['count']} assignment(s) successfully.",
1150
+ "createdIds": saved["ids"]
1151
+ })
1152
+
1153
  # --- NEW: lightweight Help/Assistant intent router -------------------------
1154
  @app.route('/assist', methods=['POST'])
1155
  def assist():