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 |
+
|