Skip to content

Commit 96b7888

Browse files
committed
[Req report] Display all annotations per requirement
1 parent ad874bf commit 96b7888

File tree

8 files changed

+210
-150
lines changed

8 files changed

+210
-150
lines changed

test2text/pages/reports/report_by_req.py

Lines changed: 128 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -3,138 +3,16 @@
33
import streamlit as st
44

55
from test2text.services.utils.math_utils import round_distance
6-
from test2text.services.repositories import requirements
7-
from test2text.services.repositories import test_cases
6+
from test2text.services.repositories import (
7+
requirements as requirements_repo,
8+
test_cases as test_cases_repo,
9+
annotations as annotations_repo,
10+
)
811

912
SUMMARY_LENGTH = 100
1013
LABELS_SUMMARY_LENGTH = 15
1114

1215

13-
def display_found_details(data: list):
14-
from test2text.services.utils import unpack_float32
15-
from test2text.services.visualisation.visualize_vectors import (
16-
minifold_vectors_2d,
17-
plot_2_sets_in_one_2d,
18-
minifold_vectors_3d,
19-
plot_2_sets_in_one_3d,
20-
)
21-
22-
def write_annotations(current_annotations: set[tuple]):
23-
st.write("id,", "Summary,", "Distance")
24-
for anno_id, anno_summary, _, distance in current_annotations:
25-
st.write(anno_id, anno_summary, round_distance(distance))
26-
27-
for (
28-
req_id,
29-
req_external_id,
30-
req_summary,
31-
req_embedding,
32-
), group in groupby(data, lambda x: x[0:4]):
33-
st.divider()
34-
with st.container():
35-
st.subheader(f" Inspect Requirement {req_external_id}")
36-
st.write(req_summary)
37-
current_test_cases = dict()
38-
for (
39-
_,
40-
_,
41-
_,
42-
_,
43-
anno_id,
44-
anno_summary,
45-
anno_embedding,
46-
distance,
47-
case_id,
48-
test_script,
49-
test_case,
50-
) in group:
51-
current_annotation = current_test_cases.get(test_case, set())
52-
current_test_cases.update({test_case: current_annotation})
53-
current_test_cases[test_case].add(
54-
(anno_id, anno_summary, anno_embedding, distance)
55-
)
56-
57-
t_cs, anno, viz = st.columns(3)
58-
with t_cs:
59-
with st.container(border=True):
60-
st.write("Test Cases")
61-
st.info("Test cases of chosen Requirement")
62-
st.radio(
63-
"Test cases name",
64-
current_test_cases.keys(),
65-
key="radio_choice",
66-
)
67-
st.markdown(
68-
"""
69-
<style>
70-
.stRadio > div {
71-
max-width: 100%;
72-
word-break: break-word;
73-
white-space: normal;
74-
}
75-
</style>
76-
""",
77-
unsafe_allow_html=True,
78-
)
79-
80-
if st.session_state["radio_choice"]:
81-
with anno:
82-
with st.container(border=True):
83-
st.write("Annotations")
84-
st.info("List of Annotations for chosen Test case")
85-
write_annotations(
86-
current_annotations=current_test_cases[
87-
st.session_state["radio_choice"]
88-
]
89-
)
90-
with viz:
91-
with st.container(border=True):
92-
st.write("Visualization")
93-
select = st.selectbox(
94-
"Choose type of visualization", ["2D", "3D"]
95-
)
96-
anno_embeddings = [
97-
unpack_float32(anno_emb)
98-
for _, _, anno_emb, _ in current_test_cases[
99-
st.session_state["radio_choice"]
100-
]
101-
]
102-
anno_labels = [
103-
f"{anno_id}"
104-
for anno_id, _, _, _ in current_test_cases[
105-
st.session_state["radio_choice"]
106-
]
107-
]
108-
requirement_vectors = np.array(
109-
[np.array(unpack_float32(req_embedding))]
110-
)
111-
annotation_vectors = np.array(anno_embeddings)
112-
if select == "2D":
113-
plot_2_sets_in_one_2d(
114-
minifold_vectors_2d(requirement_vectors),
115-
minifold_vectors_2d(annotation_vectors),
116-
"Requirement",
117-
"Annotations",
118-
first_labels=[f"{req_external_id}"],
119-
second_labels=anno_labels,
120-
)
121-
else:
122-
reqs_vectors_3d = minifold_vectors_3d(
123-
requirement_vectors
124-
)
125-
anno_vectors_3d = minifold_vectors_3d(
126-
annotation_vectors
127-
)
128-
plot_2_sets_in_one_3d(
129-
reqs_vectors_3d,
130-
anno_vectors_3d,
131-
"Requirement",
132-
"Annotations",
133-
first_labels=[f"{req_external_id}"],
134-
second_labels=anno_labels,
135-
)
136-
137-
13816
def make_a_report():
13917
from test2text.services.db import get_db_client
14018

@@ -161,27 +39,28 @@ def make_a_report():
16139

16240
with st.container(border=True):
16341
st.session_state.update({"req_form_submitting": True})
164-
data = requirements.fetch_filtered_requirements(
42+
requirements = requirements_repo.fetch_filtered_requirements(
16543
db,
16644
external_id=filter_id,
16745
text_content=filter_summary,
16846
smart_search_query=filter_embedding,
16947
)
17048

171-
requirements_dict = {
172-
req_id: f"{req_external_id} {summary[:SUMMARY_LENGTH]}..."
173-
for (req_id, req_external_id, summary) in data
49+
requirements = {
50+
req_id: (req_external_id, summary)
51+
for (req_id, req_external_id, summary) in requirements
17452
}
17553

17654
st.subheader("Choose 1 of filtered requirements")
17755
selected_requirement = st.selectbox(
17856
"Choose a requirement to work with",
179-
requirements_dict.keys(),
57+
requirements.keys(),
18058
key="filter_req_id",
181-
format_func=lambda x: requirements_dict[x],
59+
format_func=lambda x: f"{requirements[x][0]} {requirements[x][1][:SUMMARY_LENGTH]}...",
18260
)
18361

18462
if selected_requirement:
63+
requirement = db.requirements.get_by_id_raw(selected_requirement)
18564
st.subheader("Filter Test cases")
18665

18766
with st.expander("🔍 Filters"):
@@ -205,17 +84,130 @@ def make_a_report():
20584
)
20685
st.info("Limit of selected test cases")
20786

208-
rows = test_cases.fetch_test_cases_by_requirement(
87+
test_cases = test_cases_repo.fetch_test_cases_by_requirement(
20988
db, selected_requirement, filter_radius, filter_limit
21089
)
90+
test_cases = {
91+
tc_id: (test_script, test_case)
92+
for (tc_id, test_script, test_case) in test_cases
93+
}
21194

212-
if not rows:
95+
if not test_cases:
21396
st.error(
21497
"There is no requested data to inspect.\n"
21598
"Please check filters, completeness of the data or upload new annotations and requirements."
21699
)
217100
else:
218-
display_found_details(rows)
101+
from test2text.services.utils import unpack_float32
102+
from test2text.services.visualisation.visualize_vectors import (
103+
minifold_vectors_2d,
104+
plot_2_sets_in_one_2d,
105+
minifold_vectors_3d,
106+
plot_2_sets_in_one_3d,
107+
)
108+
109+
st.divider()
110+
with st.container():
111+
st.subheader(
112+
f" Inspect Requirement {requirements[selected_requirement][0]}"
113+
)
114+
st.write(requirements[selected_requirement][1])
115+
116+
t_cs, anno, viz = st.columns(3)
117+
with t_cs:
118+
with st.container(border=True):
119+
st.write("Test Cases")
120+
st.info("Test cases of chosen Requirement")
121+
st.radio(
122+
"Test cases name",
123+
test_cases.keys(),
124+
key="chosen_test_case",
125+
format_func=lambda tc_id: test_cases[tc_id][1],
126+
)
127+
st.markdown(
128+
"""
129+
<style>
130+
.stRadio > div {
131+
max-width: 100%;
132+
word-break: break-word;
133+
white-space: normal;
134+
}
135+
</style>
136+
""",
137+
unsafe_allow_html=True,
138+
)
139+
140+
if st.session_state["chosen_test_case"]:
141+
test_case = db.test_cases.get_by_id_raw(
142+
st.session_state["chosen_test_case"]
143+
)
144+
annotations = annotations_repo.fetch_annotations_by_test_case_with_distance_to_requirement(
145+
db,
146+
st.session_state["chosen_test_case"],
147+
requirement[3], # embedding
148+
)
149+
with anno:
150+
with st.container(border=True):
151+
st.write("Annotations")
152+
st.info(
153+
"List of Annotations for chosen Test case"
154+
)
155+
st.write("id,", "Summary,", "Distance")
156+
for (
157+
anno_id,
158+
anno_summary,
159+
_,
160+
distance,
161+
) in annotations:
162+
st.write(
163+
anno_id,
164+
anno_summary,
165+
round_distance(distance),
166+
)
167+
with viz:
168+
with st.container(border=True):
169+
st.write("Visualization")
170+
select = st.selectbox(
171+
"Choose type of visualization", ["2D", "3D"]
172+
)
173+
anno_embeddings = [
174+
unpack_float32(anno_emb)
175+
for _, _, anno_emb, _ in annotations
176+
]
177+
anno_labels = [
178+
f"{anno_id}"
179+
for anno_id, _, _, _ in annotations
180+
]
181+
requirement_vectors = np.array(
182+
[np.array(unpack_float32(requirement[3]))]
183+
)
184+
annotation_vectors = np.array(anno_embeddings)
185+
if select == "2D":
186+
plot_2_sets_in_one_2d(
187+
minifold_vectors_2d(
188+
requirement_vectors
189+
),
190+
minifold_vectors_2d(annotation_vectors),
191+
"Requirement",
192+
"Annotations",
193+
first_labels=[f"{requirement[1]}"],
194+
second_labels=anno_labels,
195+
)
196+
else:
197+
reqs_vectors_3d = minifold_vectors_3d(
198+
requirement_vectors
199+
)
200+
anno_vectors_3d = minifold_vectors_3d(
201+
annotation_vectors
202+
)
203+
plot_2_sets_in_one_3d(
204+
reqs_vectors_3d,
205+
anno_vectors_3d,
206+
"Requirement",
207+
"Annotations",
208+
first_labels=[f"{requirement[1]}"],
209+
second_labels=anno_labels,
210+
)
219211

220212

221213
if __name__ == "__main__":

test2text/services/db/tables/requirements.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,21 @@ def count(self) -> int:
7272
"""
7373
cursor = self.connection.execute("SELECT COUNT(*) FROM Requirements")
7474
return cursor.fetchone()[0]
75+
76+
def get_by_id_raw(
77+
self, req_id: int
78+
) -> Optional[tuple[int, str, str, Optional[bytes]]]:
79+
"""
80+
Retrieves a requirement by its ID.
81+
:param req_id: The ID of the requirement to retrieve.
82+
:return: A tuple containing the requirement's ID, external ID, summary, and embedding, or None if not found.
83+
"""
84+
cursor = self.connection.execute(
85+
"""
86+
SELECT id, external_id, summary, embedding
87+
FROM Requirements
88+
WHERE id = ?
89+
""",
90+
(req_id,),
91+
)
92+
return cursor.fetchone()

test2text/services/db/tables/test_case.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,23 @@ def count(self) -> int:
9393
"""
9494
cursor = self.connection.execute("SELECT COUNT(*) FROM TestCases")
9595
return cursor.fetchone()[0]
96+
97+
def get_by_id_raw(
98+
self, case_id: int
99+
) -> Optional[tuple[int, str, str, Optional[bytes]]]:
100+
"""
101+
Fetches a test case by its ID.
102+
:param case_id: The ID of the test case to fetch.
103+
:return: A tuple containing the test case's ID, test script, test case, and embedding (if available), or None if not found.
104+
"""
105+
cursor = self.connection.execute(
106+
"""
107+
SELECT id, test_script, test_case, embedding
108+
FROM TestCases
109+
WHERE id = ?
110+
""",
111+
(case_id,),
112+
)
113+
result = cursor.fetchone()
114+
cursor.close()
115+
return result
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
__all__ = ["fetch_annotations_by_test_case_with_distance_to_requirement"]
2+
3+
from .fetch_by_test_case import (
4+
fetch_annotations_by_test_case_with_distance_to_requirement,
5+
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from test2text.services.db import DbClient
2+
3+
4+
def fetch_annotations_by_test_case_with_distance_to_requirement(
5+
db: DbClient, test_case_id: int, requirement_embedding: bytes
6+
) -> list[tuple[int, str, bytes, float]]:
7+
sql = """
8+
SELECT
9+
Annotations.id as anno_id,
10+
Annotations.summary as anno_summary,
11+
Annotations.embedding as anno_embedding,
12+
vec_distance_L2(?, Annotations.embedding) as distance
13+
FROM
14+
Annotations
15+
JOIN CasesToAnnos ON Annotations.id = CasesToAnnos.annotation_id
16+
WHERE CasesToAnnos.case_id = ?
17+
ORDER BY
18+
distance ASC
19+
"""
20+
return db.conn.execute(sql, (requirement_embedding, test_case_id)).fetchall()

0 commit comments

Comments
 (0)