Skip to content

Commit 4fda326

Browse files
author
testgen-ci-bot
committed
Merge remote-tracking branch 'origin/enterprise' into feat/TG-1090-mcp-monitors-l1
2 parents e0da5a3 + b11eaae commit 4fda326

4 files changed

Lines changed: 78 additions & 45 deletions

File tree

testgen/api/runs.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
from testgen.api.deps import db_session, resolve_job
77
from testgen.api.schemas import (
88
ErrorResponse,
9-
IssueBreakdown,
9+
IssueCounts,
1010
ProfilingRunResponse,
1111
ProfilingRunResult,
12-
TestBreakdown,
12+
ResultCounts,
1313
TestRunResponse,
1414
TestRunResult,
1515
)
@@ -42,14 +42,7 @@ def get_test_run(job: JobExecution = resolve_job("view", JobExecution.job_key ==
4242
counts = TestResult.count_by_status(test_run.id)
4343
result = TestRunResult(
4444
score=test_run.dq_score_test_run,
45-
tests=TestBreakdown(
46-
passed=counts.passed,
47-
failed=counts.failed,
48-
warning=counts.warning,
49-
error=counts.error,
50-
log=counts.log,
51-
dismissed=counts.dismissed,
52-
),
45+
result_counts=ResultCounts.model_validate(counts, from_attributes=True),
5346
)
5447

5548
test_suite_id = test_run.test_suite_id if test_run else None
@@ -80,18 +73,13 @@ def get_profiling_run(job: JobExecution = resolve_job("view", JobExecution.job_k
8073

8174
result = None
8275
if profiling_run:
83-
counts = HygieneIssue.count_by_likelihood(profiling_run.id)
76+
counts = HygieneIssue.count_for_run(profiling_run.id)
8477
result = ProfilingRunResult(
8578
score=profiling_run.dq_score_profiling,
8679
table_ct=profiling_run.table_ct,
8780
column_ct=profiling_run.column_ct,
8881
record_ct=profiling_run.record_ct,
89-
issues=IssueBreakdown(
90-
definite=counts.definite,
91-
likely=counts.likely,
92-
possible=counts.possible,
93-
dismissed=counts.dismissed,
94-
),
82+
issue_counts=IssueCounts.model_validate(counts, from_attributes=True),
9583
)
9684

9785
return ProfilingRunResponse(

testgen/api/schemas.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ class JobListResponse(BaseModel):
4848
# --- Test Runs ---
4949

5050

51-
class TestBreakdown(BaseModel):
52-
"""Counts of test results by outcome status."""
51+
class ResultCounts(BaseModel):
52+
"""Counts of test results by outcome status, with dismissed results separated."""
5353

5454
passed: int = 0
5555
failed: int = 0
@@ -63,7 +63,7 @@ class TestRunResult(BaseModel):
6363
"""Run-specific data populated when execution completes."""
6464

6565
score: float | None = None
66-
tests: TestBreakdown
66+
result_counts: ResultCounts
6767

6868

6969
class TestRunResponse(BaseModel):
@@ -81,12 +81,26 @@ class TestRunResponse(BaseModel):
8181
# --- Profiling Runs ---
8282

8383

84-
class IssueBreakdown(BaseModel):
85-
"""Counts of hygiene issues by likelihood category."""
84+
class HygieneIssueCounts(BaseModel):
85+
"""Counts of active data-quality hygiene issues by likelihood category."""
8686

8787
definite: int = 0
8888
likely: int = 0
8989
possible: int = 0
90+
91+
92+
class PotentialPiiCounts(BaseModel):
93+
"""Counts of active Potential PII findings by risk level."""
94+
95+
high: int = 0
96+
moderate: int = 0
97+
98+
99+
class IssueCounts(BaseModel):
100+
"""Profiling-finding breakdown: active counts by kind, plus a single dismissed total."""
101+
102+
hygiene_issues: HygieneIssueCounts
103+
potential_pii: PotentialPiiCounts
90104
dismissed: int = 0
91105

92106

@@ -97,7 +111,7 @@ class ProfilingRunResult(BaseModel):
97111
table_ct: int | None = None
98112
column_ct: int | None = None
99113
record_ct: int | None = None
100-
issues: IssueBreakdown
114+
issue_counts: IssueCounts
101115

102116

103117
class ProfilingRunResponse(BaseModel):

testgen/common/models/hygiene_issue.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from sqlalchemy.orm import aliased, relationship
1212
from sqlalchemy.sql.functions import func
1313

14-
from testgen.common.enums import Disposition
14+
from testgen.common.enums import Disposition, IssueLikelihood, PiiRisk
1515
from testgen.common.models import Base, get_current_session
1616
from testgen.common.models.entity import Entity
1717
from testgen.common.models.job_execution import JobExecution
@@ -23,12 +23,28 @@
2323

2424

2525
@dataclass
26-
class IssueLikelihoodCounts:
27-
"""Counts of hygiene issues by likelihood category, with dismissed/inactive separated."""
26+
class HygieneIssueCounts:
27+
"""Counts of active data-quality hygiene issues by likelihood category."""
2828

2929
definite: int = 0
3030
likely: int = 0
3131
possible: int = 0
32+
33+
34+
@dataclass
35+
class PotentialPiiCounts:
36+
"""Counts of active Potential PII findings by risk level."""
37+
38+
high: int = 0
39+
moderate: int = 0
40+
41+
42+
@dataclass
43+
class IssueCounts:
44+
"""Profiling-finding breakdown: active counts by kind, plus a single dismissed total."""
45+
46+
hygiene_issues: HygieneIssueCounts
47+
potential_pii: PotentialPiiCounts
3248
dismissed: int = 0
3349

3450

@@ -202,27 +218,37 @@ def select_count_by_priority(cls, profiling_run_id: UUID) -> dict[str, IssueCoun
202218
return result
203219

204220
@classmethod
205-
def count_by_likelihood(cls, profile_run_id: UUID) -> IssueLikelihoodCounts:
206-
"""Count hygiene issues by likelihood category for a single profiling run."""
207-
dismissed = func.coalesce(cls.disposition, "Confirmed").in_(("Dismissed", "Inactive"))
221+
def count_for_run(cls, profile_run_id: UUID) -> IssueCounts:
222+
"""Count profiling findings for a single run: active hygiene issues by likelihood,
223+
active Potential PII findings by risk level, and a single dismissed total."""
224+
dismissed = func.coalesce(cls.disposition, Disposition.CONFIRMED).in_(
225+
(Disposition.DISMISSED, Disposition.INACTIVE)
226+
)
227+
is_pii = HygieneIssueType.likelihood == IssueLikelihood.POTENTIAL_PII
208228

209-
def _count_active(likelihood_values: tuple[str, ...]):
210-
return func.sum(case((~dismissed & HygieneIssueType.likelihood.in_(likelihood_values), 1), else_=0))
229+
def _count(condition):
230+
return func.coalesce(func.sum(case((condition, 1), else_=0)), 0)
211231

212232
query = (
213233
select(
214-
_count_active(("Definite",)).label("definite"),
215-
_count_active(("Likely",)).label("likely"),
216-
_count_active(("Possible", "Potential PII")).label("possible"),
217-
func.sum(case((dismissed, 1), else_=0)).label("dismissed"),
234+
_count(~dismissed & (HygieneIssueType.likelihood == IssueLikelihood.DEFINITE)).label("definite"),
235+
_count(~dismissed & (HygieneIssueType.likelihood == IssueLikelihood.LIKELY)).label("likely"),
236+
_count(~dismissed & (HygieneIssueType.likelihood == IssueLikelihood.POSSIBLE)).label("possible"),
237+
_count(~dismissed & is_pii & (cls.priority == PiiRisk.HIGH)).label("high"),
238+
_count(~dismissed & is_pii & (cls.priority == PiiRisk.MODERATE)).label("moderate"),
239+
_count(dismissed).label("dismissed"),
218240
)
219241
.select_from(cls)
220242
.join(HygieneIssueType, HygieneIssueType.id == cls.type_id)
221243
.where(cls.profile_run_id == profile_run_id)
222244
)
223245

224246
row = get_current_session().execute(query).first()
225-
return IssueLikelihoodCounts(**{k: v for k, v in row._mapping.items() if v is not None})
247+
return IssueCounts(
248+
hygiene_issues=HygieneIssueCounts(definite=row.definite, likely=row.likely, possible=row.possible),
249+
potential_pii=PotentialPiiCounts(high=row.high, moderate=row.moderate),
250+
dismissed=row.dismissed,
251+
)
226252

227253
@classmethod
228254
def _priority_order(cls):

tests/unit/api/test_runs.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pytest
88

99
from testgen.api.runs import get_profiling_run, get_test_run
10-
from testgen.common.models.hygiene_issue import IssueLikelihoodCounts
10+
from testgen.common.models.hygiene_issue import HygieneIssueCounts, IssueCounts, PotentialPiiCounts
1111
from testgen.common.models.test_result import ResultStatusCounts
1212

1313
pytestmark = pytest.mark.unit
@@ -84,9 +84,9 @@ def test_get_test_run_completed(mock_tr_cls, mock_result_cls, mock_session):
8484
assert result.table_group_id == TABLE_GROUP_ID
8585
assert result.result is not None
8686
assert result.result.score == 0.95
87-
assert result.result.tests.passed == 90
88-
assert result.result.tests.failed == 5
89-
assert result.result.tests.dismissed == 12
87+
assert result.result.result_counts.passed == 90
88+
assert result.result.result_counts.failed == 5
89+
assert result.result.result_counts.dismissed == 12
9090

9191

9292
@patch(f"{MODULE}.TestRun")
@@ -111,8 +111,10 @@ def test_get_test_run_pending_no_run(mock_tr_cls):
111111
def test_get_profiling_run_completed(mock_pr_cls, mock_issue_cls):
112112
job = _mock_job()
113113
mock_pr_cls.get.return_value = _mock_profiling_run()
114-
mock_issue_cls.count_by_likelihood.return_value = IssueLikelihoodCounts(
115-
definite=5, likely=3, possible=8, dismissed=2,
114+
mock_issue_cls.count_for_run.return_value = IssueCounts(
115+
hygiene_issues=HygieneIssueCounts(definite=5, likely=3, possible=8),
116+
potential_pii=PotentialPiiCounts(high=4, moderate=6),
117+
dismissed=2,
116118
)
117119

118120
result = get_profiling_run(job)
@@ -123,9 +125,12 @@ def test_get_profiling_run_completed(mock_pr_cls, mock_issue_cls):
123125
assert result.result is not None
124126
assert result.result.score == 0.88
125127
assert result.result.table_ct == 10
126-
assert result.result.issues.definite == 5
127-
assert result.result.issues.likely == 3
128-
assert result.result.issues.dismissed == 2
128+
assert result.result.issue_counts.hygiene_issues.definite == 5
129+
assert result.result.issue_counts.hygiene_issues.likely == 3
130+
assert result.result.issue_counts.hygiene_issues.possible == 8
131+
assert result.result.issue_counts.potential_pii.high == 4
132+
assert result.result.issue_counts.potential_pii.moderate == 6
133+
assert result.result.issue_counts.dismissed == 2
129134

130135

131136
@patch(f"{MODULE}.ProfilingRun")

0 commit comments

Comments
 (0)