Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -831,4 +831,342 @@ class RequirementsAnalysisModule:
|
|
831 |
for i, req in enumerate(requirements):
|
832 |
related = []
|
833 |
|
834 |
-
for j
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
831 |
for i, req in enumerate(requirements):
|
832 |
related = []
|
833 |
|
834 |
+
for j, similarity in enumerate(similarity_matrix[i]):
|
835 |
+
if i != j and similarity > 0.3: # Threshold for relationship
|
836 |
+
related.append({
|
837 |
+
"id": requirements[j]["id"],
|
838 |
+
"similarity": float(similarity),
|
839 |
+
"relationship_type": self._determine_relationship_type(req, requirements[j])
|
840 |
+
})
|
841 |
+
|
842 |
+
# Sort by similarity
|
843 |
+
related.sort(key=lambda x: x["similarity"], reverse=True)
|
844 |
+
|
845 |
+
# Add to requirement
|
846 |
+
req["related_requirements"] = related[:5] # Top 5 related requirements
|
847 |
+
|
848 |
+
def _determine_relationship_type(self, req1: Dict, req2: Dict) -> str:
|
849 |
+
"""
|
850 |
+
Determine the type of relationship between two requirements.
|
851 |
+
|
852 |
+
Args:
|
853 |
+
req1: First requirement
|
854 |
+
req2: Second requirement
|
855 |
+
|
856 |
+
Returns:
|
857 |
+
Relationship type string
|
858 |
+
"""
|
859 |
+
# Check for system relationships
|
860 |
+
systems1 = set(req1["extracted"]["systems"])
|
861 |
+
systems2 = set(req2["extracted"]["systems"])
|
862 |
+
|
863 |
+
if systems1.intersection(systems2):
|
864 |
+
return "same_system"
|
865 |
+
|
866 |
+
# Check for business domain relationships
|
867 |
+
domains1 = [d[0] for d in req1["extracted"]["business_domain"]]
|
868 |
+
domains2 = [d[0] for d in req2["extracted"]["business_domain"]]
|
869 |
+
|
870 |
+
if set(domains1).intersection(set(domains2)):
|
871 |
+
return "same_domain"
|
872 |
+
|
873 |
+
# Check for action relationships
|
874 |
+
actions1 = set(req1["extracted"]["actions"])
|
875 |
+
actions2 = set(req2["extracted"]["actions"])
|
876 |
+
|
877 |
+
if actions1.intersection(actions2):
|
878 |
+
return "similar_action"
|
879 |
+
|
880 |
+
# Default relationship type
|
881 |
+
return "related"
|
882 |
+
|
883 |
+
def map_requirements_to_processes(self, requirements: List[Dict], process_models: List[Dict]) -> Dict:
|
884 |
+
"""
|
885 |
+
Map requirements to process models based on content matching.
|
886 |
+
|
887 |
+
Args:
|
888 |
+
requirements: List of analyzed requirements
|
889 |
+
process_models: List of process model dictionaries
|
890 |
+
|
891 |
+
Returns:
|
892 |
+
Dictionary mapping process IDs to requirement IDs
|
893 |
+
"""
|
894 |
+
process_to_reqs = {}
|
895 |
+
req_to_process = {}
|
896 |
+
|
897 |
+
for process in process_models:
|
898 |
+
process_id = process.get("id", "unknown")
|
899 |
+
process_text = process.get("description", "") + " " + process.get("name", "")
|
900 |
+
process_doc = self.nlp(process_text)
|
901 |
+
|
902 |
+
# Find matching requirements
|
903 |
+
matching_reqs = []
|
904 |
+
|
905 |
+
for req in requirements:
|
906 |
+
req_text = req["text"]
|
907 |
+
req_doc = self.nlp(req_text)
|
908 |
+
|
909 |
+
# Calculate similarity
|
910 |
+
similarity = process_doc.similarity(req_doc)
|
911 |
+
|
912 |
+
if similarity > 0.6: # Threshold for matching
|
913 |
+
matching_reqs.append({
|
914 |
+
"req_id": req["id"],
|
915 |
+
"similarity": float(similarity)
|
916 |
+
})
|
917 |
+
req_to_process[req["id"]] = process_id
|
918 |
+
|
919 |
+
# Sort by similarity
|
920 |
+
matching_reqs.sort(key=lambda x: x["similarity"], reverse=True)
|
921 |
+
process_to_reqs[process_id] = matching_reqs
|
922 |
+
|
923 |
+
return {
|
924 |
+
"process_to_requirements": process_to_reqs,
|
925 |
+
"requirement_to_process": req_to_process
|
926 |
+
}
|
927 |
+
|
928 |
+
def evaluate_automation_potential(self, requirement: Dict) -> Dict:
|
929 |
+
"""
|
930 |
+
Evaluate the automation potential of a requirement.
|
931 |
+
|
932 |
+
Args:
|
933 |
+
requirement: Analyzed requirement
|
934 |
+
|
935 |
+
Returns:
|
936 |
+
Automation potential assessment
|
937 |
+
"""
|
938 |
+
# Basic score starts at 5 out of 10
|
939 |
+
score = 5
|
940 |
+
|
941 |
+
# Complexity factor (high complexity decreases score)
|
942 |
+
complexity = requirement["extracted"]["complexity"]
|
943 |
+
if complexity == "high":
|
944 |
+
score -= 2
|
945 |
+
elif complexity == "low":
|
946 |
+
score += 2
|
947 |
+
|
948 |
+
# Action factor (certain actions are more automatable)
|
949 |
+
automatable_actions = ["extract", "transfer", "copy", "move", "calculate",
|
950 |
+
"update", "generate", "validate", "verify", "send",
|
951 |
+
"notify", "schedule", "retrieve", "check"]
|
952 |
+
|
953 |
+
for action in requirement["extracted"]["actions"]:
|
954 |
+
if action in automatable_actions:
|
955 |
+
score += 0.5
|
956 |
+
|
957 |
+
# System factor (presence of systems increases score)
|
958 |
+
if requirement["extracted"]["systems"]:
|
959 |
+
score += len(requirement["extracted"]["systems"]) * 0.5
|
960 |
+
|
961 |
+
# Data elements factor (more data elements suggests more structure)
|
962 |
+
data_elements = requirement["extracted"]["data_elements"]
|
963 |
+
if data_elements:
|
964 |
+
score += min(len(data_elements) * 0.3, 2) # Cap at +2
|
965 |
+
|
966 |
+
# Cap score between 1-10
|
967 |
+
score = max(1, min(10, score))
|
968 |
+
|
969 |
+
# Determine category
|
970 |
+
category = "high" if score >= 7.5 else "medium" if score >= 5 else "low"
|
971 |
+
|
972 |
+
# Identify automation technology
|
973 |
+
tech = self._recommend_automation_technology(requirement, score)
|
974 |
+
|
975 |
+
return {
|
976 |
+
"automation_score": round(score, 1),
|
977 |
+
"automation_category": category,
|
978 |
+
"recommended_technology": tech,
|
979 |
+
"rationale": self._generate_automation_rationale(requirement, score, category)
|
980 |
+
}
|
981 |
+
|
982 |
+
def _recommend_automation_technology(self, requirement: Dict, score: float) -> str:
|
983 |
+
"""
|
984 |
+
Recommend suitable automation technology.
|
985 |
+
|
986 |
+
Args:
|
987 |
+
requirement: Analyzed requirement
|
988 |
+
score: Automation score
|
989 |
+
|
990 |
+
Returns:
|
991 |
+
Recommended technology
|
992 |
+
"""
|
993 |
+
complexity = requirement["extracted"]["complexity"]
|
994 |
+
actions = requirement["extracted"]["actions"]
|
995 |
+
|
996 |
+
# Decision tree for technology recommendation
|
997 |
+
if score >= 8:
|
998 |
+
if any(a in actions for a in ["extract", "scrape", "read"]):
|
999 |
+
return "RPA with OCR/Document Understanding"
|
1000 |
+
else:
|
1001 |
+
return "Traditional RPA"
|
1002 |
+
elif score >= 5:
|
1003 |
+
if complexity == "high":
|
1004 |
+
return "RPA with Human-in-the-Loop"
|
1005 |
+
elif any(a in actions for a in ["decide", "evaluate", "assess"]):
|
1006 |
+
return "RPA with Decision Automation"
|
1007 |
+
else:
|
1008 |
+
return "Traditional RPA"
|
1009 |
+
else:
|
1010 |
+
if any(a in actions for a in ["review", "approve"]):
|
1011 |
+
return "Workflow Automation"
|
1012 |
+
else:
|
1013 |
+
return "Partial Automation with Human Tasks"
|
1014 |
+
|
1015 |
+
def _generate_automation_rationale(self, requirement: Dict, score: float, category: str) -> str:
|
1016 |
+
"""
|
1017 |
+
Generate explanation for automation assessment.
|
1018 |
+
|
1019 |
+
Args:
|
1020 |
+
requirement: Analyzed requirement
|
1021 |
+
score: Automation score
|
1022 |
+
category: Automation category
|
1023 |
+
|
1024 |
+
Returns:
|
1025 |
+
Rationale text
|
1026 |
+
"""
|
1027 |
+
complexity = requirement["extracted"]["complexity"]
|
1028 |
+
|
1029 |
+
if category == "high":
|
1030 |
+
return (f"This requirement has {complexity} complexity but shows strong automation "
|
1031 |
+
f"potential due to clear structure and defined data elements. "
|
1032 |
+
f"Score of {score}/10 indicates this is a prime automation candidate.")
|
1033 |
+
elif category == "medium":
|
1034 |
+
return (f"This {complexity} complexity requirement has moderate automation potential. "
|
1035 |
+
f"Score of {score}/10 suggests partial automation with some human oversight.")
|
1036 |
+
else:
|
1037 |
+
return (f"The {complexity} complexity and ambiguous nature of this requirement "
|
1038 |
+
f"limits automation potential. Score of {score}/10 indicates this may "
|
1039 |
+
f"require significant human involvement or process redesign.")
|
1040 |
+
|
1041 |
+
def assess_requirements_automation_potential(self, requirements: List[Dict]) -> List[Dict]:
|
1042 |
+
"""
|
1043 |
+
Assess automation potential for a batch of requirements.
|
1044 |
+
|
1045 |
+
Args:
|
1046 |
+
requirements: List of analyzed requirements
|
1047 |
+
|
1048 |
+
Returns:
|
1049 |
+
Requirements with automation assessment added
|
1050 |
+
"""
|
1051 |
+
for req in requirements:
|
1052 |
+
req["automation_potential"] = self.evaluate_automation_potential(req)
|
1053 |
+
|
1054 |
+
return requirements
|
1055 |
+
|
1056 |
+
def generate_requirements_report(self, requirements: List[Dict]) -> Dict:
|
1057 |
+
"""
|
1058 |
+
Generate a summary report of requirements analysis.
|
1059 |
+
|
1060 |
+
Args:
|
1061 |
+
requirements: List of analyzed requirements
|
1062 |
+
|
1063 |
+
Returns:
|
1064 |
+
Report dictionary
|
1065 |
+
"""
|
1066 |
+
# Count by complexity
|
1067 |
+
complexity_counts = {"high": 0, "medium": 0, "low": 0}
|
1068 |
+
for req in requirements:
|
1069 |
+
complexity = req["extracted"]["complexity"]
|
1070 |
+
complexity_counts[complexity] += 1
|
1071 |
+
|
1072 |
+
# Count by automation potential
|
1073 |
+
if all("automation_potential" in req for req in requirements):
|
1074 |
+
automation_counts = {"high": 0, "medium": 0, "low": 0}
|
1075 |
+
for req in requirements:
|
1076 |
+
category = req["automation_potential"]["automation_category"]
|
1077 |
+
automation_counts[category] += 1
|
1078 |
+
else:
|
1079 |
+
automation_counts = None
|
1080 |
+
|
1081 |
+
# Find common systems
|
1082 |
+
all_systems = []
|
1083 |
+
for req in requirements:
|
1084 |
+
all_systems.extend(req["extracted"]["systems"])
|
1085 |
+
|
1086 |
+
system_counts = {}
|
1087 |
+
for system in all_systems:
|
1088 |
+
if system in system_counts:
|
1089 |
+
system_counts[system] += 1
|
1090 |
+
else:
|
1091 |
+
system_counts[system] = 1
|
1092 |
+
|
1093 |
+
# Sort systems by frequency
|
1094 |
+
top_systems = sorted(system_counts.items(), key=lambda x: x[1], reverse=True)[:5]
|
1095 |
+
|
1096 |
+
# Generate report
|
1097 |
+
report = {
|
1098 |
+
"total_requirements": len(requirements),
|
1099 |
+
"complexity_distribution": complexity_counts,
|
1100 |
+
"automation_potential": automation_counts,
|
1101 |
+
"top_systems": top_systems,
|
1102 |
+
"recommendations": self._generate_overall_recommendations(requirements)
|
1103 |
+
}
|
1104 |
+
|
1105 |
+
return report
|
1106 |
+
|
1107 |
+
def _generate_overall_recommendations(self, requirements: List[Dict]) -> List[str]:
|
1108 |
+
"""
|
1109 |
+
Generate overall recommendations based on requirements analysis.
|
1110 |
+
|
1111 |
+
Args:
|
1112 |
+
requirements: List of analyzed requirements
|
1113 |
+
|
1114 |
+
Returns:
|
1115 |
+
List of recommendation strings
|
1116 |
+
"""
|
1117 |
+
recommendations = []
|
1118 |
+
|
1119 |
+
# Check if automation assessment is available
|
1120 |
+
automation_available = all("automation_potential" in req for req in requirements)
|
1121 |
+
|
1122 |
+
if automation_available:
|
1123 |
+
# Count high automation potential requirements
|
1124 |
+
high_potential = [r for r in requirements
|
1125 |
+
if r["automation_potential"]["automation_category"] == "high"]
|
1126 |
+
|
1127 |
+
if len(high_potential) >= len(requirements) * 0.7:
|
1128 |
+
recommendations.append(
|
1129 |
+
"High automation potential across most requirements. "
|
1130 |
+
"Consider an end-to-end automation solution."
|
1131 |
+
)
|
1132 |
+
elif len(high_potential) >= len(requirements) * 0.3:
|
1133 |
+
recommendations.append(
|
1134 |
+
"Significant automation potential in a subset of requirements. "
|
1135 |
+
"Consider a phased automation approach starting with high-potential areas."
|
1136 |
+
)
|
1137 |
+
else:
|
1138 |
+
recommendations.append(
|
1139 |
+
"Limited automation potential in current requirements. "
|
1140 |
+
"Consider process redesign to increase automation potential."
|
1141 |
+
)
|
1142 |
+
|
1143 |
+
# Recommend technologies
|
1144 |
+
tech_counts = {}
|
1145 |
+
for req in requirements:
|
1146 |
+
tech = req["automation_potential"]["recommended_technology"]
|
1147 |
+
tech_counts[tech] = tech_counts.get(tech, 0) + 1
|
1148 |
+
|
1149 |
+
top_tech = max(tech_counts.items(), key=lambda x: x[1])[0]
|
1150 |
+
recommendations.append(f"Primary recommended technology: {top_tech}")
|
1151 |
+
|
1152 |
+
# Requirements quality recommendations
|
1153 |
+
completeness_issues = False
|
1154 |
+
for req in requirements:
|
1155 |
+
if (not req["extracted"]["actions"] or
|
1156 |
+
not req["extracted"]["systems"] or
|
1157 |
+
not req["extracted"]["data_elements"]):
|
1158 |
+
completeness_issues = True
|
1159 |
+
break
|
1160 |
+
|
1161 |
+
if completeness_issues:
|
1162 |
+
recommendations.append(
|
1163 |
+
"Some requirements lack necessary details. "
|
1164 |
+
"Consider refining requirements to specify actions, systems, and data elements."
|
1165 |
+
)
|
1166 |
+
|
1167 |
+
return recommendations
|
1168 |
+
|
1169 |
+
Version 2 of 2
|
1170 |
+
|
1171 |
+
|
1172 |
+
|