This commit is contained in:
@@ -29,8 +29,8 @@ func TestAgentBundleRequiresStandardQualityEvidence(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
admin := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000101"}
|
||||
approver := identity.Principal{Issuer: admin.Issuer, Subject: "00000000-0000-0000-0000-000000000102"}
|
||||
admin := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000104"}
|
||||
approver := identity.Principal{Issuer: admin.Issuer, Subject: "00000000-0000-0000-0000-000000000105"}
|
||||
tenantID := "10000000-0000-0000-0000-000000000010"
|
||||
configKey := "integration.agent.bundle." + uuid.NewString()
|
||||
invalidConfigKey := "integration.agent.bundle.invalid." + uuid.NewString()
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package opsconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"data-terminal-cloud/internal/identity"
|
||||
)
|
||||
|
||||
type ContentQualitySummary struct {
|
||||
ModelRef string `json:"model_ref"`
|
||||
PromptVersion string `json:"prompt_version"`
|
||||
StoryCandidates int64 `json:"story_candidates"`
|
||||
FactCandidates int64 `json:"fact_candidates"`
|
||||
Confirmed int64 `json:"confirmed"`
|
||||
Edited int64 `json:"edited"`
|
||||
Rejected int64 `json:"rejected"`
|
||||
Disabled int64 `json:"disabled"`
|
||||
ErrorTypes map[string]int64 `json:"error_types"`
|
||||
}
|
||||
|
||||
func (s *Service) ContentQuality(ctx context.Context, principal identity.Principal, tenantPublicID, modelRef, promptVersion string) ([]ContentQualitySummary, error) {
|
||||
currentActor, err := s.resolveActor(ctx, principal, tenantPublicID, "ops_viewer", "ops_operator", "ops_approver", "ops_admin")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modelRef = strings.TrimSpace(modelRef)
|
||||
promptVersion = strings.TrimSpace(promptVersion)
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
WITH generated AS (
|
||||
SELECT COALESCE(sr.model_ref,'unknown') model_ref,COALESCE(sr.prompt_version,'unknown') prompt_version,
|
||||
count(DISTINCT sd.id) story_candidates,count(DISTINCT fc.id) fact_candidates
|
||||
FROM story_draft sd JOIN story_revision sr ON sr.story_id=sd.id AND sr.revision_no=1
|
||||
LEFT JOIN fact_candidate fc ON fc.story_id=sd.id
|
||||
WHERE sd.tenant_id=$1::uuid AND (NULLIF($2,'') IS NULL OR sr.model_ref=$2) AND (NULLIF($3,'') IS NULL OR sr.prompt_version=$3)
|
||||
GROUP BY COALESCE(sr.model_ref,'unknown'),COALESCE(sr.prompt_version,'unknown')
|
||||
), signals AS (
|
||||
SELECT COALESCE(model_ref,'unknown') model_ref,COALESCE(prompt_version,'unknown') prompt_version,
|
||||
count(*) FILTER(WHERE action='CONFIRMED') confirmed,count(*) FILTER(WHERE action='EDITED') edited,
|
||||
count(*) FILTER(WHERE action='REJECTED') rejected,count(*) FILTER(WHERE action='DISABLED') disabled
|
||||
FROM content_review_signal WHERE tenant_id=$1::uuid
|
||||
AND (NULLIF($2,'') IS NULL OR model_ref=$2) AND (NULLIF($3,'') IS NULL OR prompt_version=$3)
|
||||
GROUP BY COALESCE(model_ref,'unknown'),COALESCE(prompt_version,'unknown')
|
||||
)
|
||||
SELECT COALESCE(g.model_ref,s.model_ref),COALESCE(g.prompt_version,s.prompt_version),
|
||||
COALESCE(g.story_candidates,0),COALESCE(g.fact_candidates,0),COALESCE(s.confirmed,0),COALESCE(s.edited,0),COALESCE(s.rejected,0),COALESCE(s.disabled,0)
|
||||
FROM generated g FULL JOIN signals s USING(model_ref,prompt_version)
|
||||
ORDER BY COALESCE(g.model_ref,s.model_ref),COALESCE(g.prompt_version,s.prompt_version)`, currentActor.tenantID, modelRef, promptVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query content quality summary: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
items := make([]ContentQualitySummary, 0)
|
||||
lookup := make(map[string]int)
|
||||
for rows.Next() {
|
||||
var item ContentQualitySummary
|
||||
item.ErrorTypes = map[string]int64{}
|
||||
if err := rows.Scan(&item.ModelRef, &item.PromptVersion, &item.StoryCandidates, &item.FactCandidates, &item.Confirmed, &item.Edited, &item.Rejected, &item.Disabled); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, item)
|
||||
lookup[item.ModelRef+"\x00"+item.PromptVersion] = len(items) - 1
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
errorRows, err := s.pool.Query(ctx, `SELECT COALESCE(model_ref,'unknown'),COALESCE(prompt_version,'unknown'),error_type,count(*) FROM content_review_signal
|
||||
WHERE tenant_id=$1::uuid AND error_type IS NOT NULL AND (NULLIF($2,'') IS NULL OR model_ref=$2) AND (NULLIF($3,'') IS NULL OR prompt_version=$3)
|
||||
GROUP BY COALESCE(model_ref,'unknown'),COALESCE(prompt_version,'unknown'),error_type`, currentActor.tenantID, modelRef, promptVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer errorRows.Close()
|
||||
for errorRows.Next() {
|
||||
var model, prompt, errorType string
|
||||
var count int64
|
||||
if err := errorRows.Scan(&model, &prompt, &errorType, &count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if index, ok := lookup[model+"\x00"+prompt]; ok {
|
||||
items[index].ErrorTypes[errorType] = count
|
||||
}
|
||||
}
|
||||
return items, errorRows.Err()
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package opsconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"data-terminal-cloud/internal/audit"
|
||||
"data-terminal-cloud/internal/identity"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func TestContentQualityAggregatesOnlySelectedTenantAndVersion(t *testing.T) {
|
||||
databaseURL := os.Getenv("TEST_DATABASE_URL")
|
||||
if databaseURL == "" {
|
||||
t.Skip("TEST_DATABASE_URL is not set")
|
||||
}
|
||||
ctx := context.Background()
|
||||
pool, err := pgxpool.New(ctx, databaseURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer pool.Close()
|
||||
resourceIDs := []string{uuid.NewString(), uuid.NewString(), uuid.NewString()}
|
||||
defer func() {
|
||||
_, _ = pool.Exec(ctx, `DELETE FROM content_review_signal WHERE resource_public_id=ANY($1::uuid[])`, resourceIDs)
|
||||
}()
|
||||
for index, action := range []string{"EDITED", "REJECTED", "CONFIRMED"} {
|
||||
errorType := any(nil)
|
||||
if index < 2 {
|
||||
errorType = "PERSON_ERROR"
|
||||
}
|
||||
if _, err := pool.Exec(ctx, `INSERT INTO content_review_signal(tenant_id,subject_id,resource_type,resource_public_id,action,error_type,reason,model_ref,prompt_version,generation_version,actor_id)
|
||||
VALUES('10000000-0000-0000-0000-000000000001','10000000-0000-0000-0000-000000000401','story',$1::uuid,$2,$3,$4,'quality-model-v1','quality-prompt-v2',1,'10000000-0000-0000-0000-000000000101')`, resourceIDs[index], action, errorType, "合成质量反馈"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
service, err := NewService(pool, audit.NewRepository())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
principal := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000104"}
|
||||
items, err := service.ContentQuality(ctx, principal, "10000000-0000-0000-0000-000000000010", "quality-model-v1", "quality-prompt-v2")
|
||||
if err != nil || len(items) != 1 || items[0].Edited != 1 || items[0].Rejected != 1 || items[0].Confirmed != 1 || items[0].ErrorTypes["PERSON_ERROR"] != 2 {
|
||||
t.Fatalf("quality=%+v err=%v", items, err)
|
||||
}
|
||||
outsider := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000103"}
|
||||
if _, err := service.ContentQuality(ctx, outsider, "10000000-0000-0000-0000-000000000010", "", ""); err != ErrAccessDenied {
|
||||
t.Fatalf("outsider quality error=%v", err)
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func TestResourceReferencesAndDeviceGroupsLifecycle(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
admin := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000101"}
|
||||
admin := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000104"}
|
||||
outsider := identity.Principal{Issuer: admin.Issuer, Subject: "00000000-0000-0000-0000-000000000103"}
|
||||
tenantID := "10000000-0000-0000-0000-000000000010"
|
||||
deviceID := "10000000-0000-0000-0000-000000000511"
|
||||
|
||||
@@ -335,7 +335,7 @@ func (s *Service) resolveActor(ctx context.Context, principal identity.Principal
|
||||
return actor{}, ErrInvalid
|
||||
}
|
||||
var result actor
|
||||
err := s.pool.QueryRow(ctx, `SELECT t.id::text,t.public_id::text,ua.id::text,ua.public_id::text,ua.platform_role FROM user_account ua JOIN tenant t ON t.id=ua.tenant_id AND t.status='active' WHERE t.public_id=$1::uuid AND ua.oidc_issuer=$2 AND ua.oidc_subject=$3 AND ua.status='active'`, tenantPublicID, principal.Issuer, principal.Subject).Scan(&result.tenantID, &result.tenantPublicID, &result.userID, &result.userPublicID, &result.role)
|
||||
err := s.pool.QueryRow(ctx, `SELECT t.id::text,t.public_id::text,ua.id::text,ua.public_id::text,ua.platform_role FROM user_account ua JOIN tenant t ON t.id=ua.tenant_id AND t.status='active' WHERE t.public_id=$1::uuid AND ua.oidc_issuer=$2 AND ua.oidc_subject=$3 AND ua.status='active' AND ua.account_kind='operator'`, tenantPublicID, principal.Issuer, principal.Subject).Scan(&result.tenantID, &result.tenantPublicID, &result.userID, &result.userPublicID, &result.role)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return actor{}, ErrAccessDenied
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ func TestRoutingConfigurationApprovalPublishAndRollback(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
admin := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000101"}
|
||||
approver := identity.Principal{Issuer: admin.Issuer, Subject: "00000000-0000-0000-0000-000000000102"}
|
||||
admin := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000104"}
|
||||
approver := identity.Principal{Issuer: admin.Issuer, Subject: "00000000-0000-0000-0000-000000000105"}
|
||||
outsider := identity.Principal{Issuer: admin.Issuer, Subject: "00000000-0000-0000-0000-000000000103"}
|
||||
tenantID := "10000000-0000-0000-0000-000000000010"
|
||||
configKey := "integration.routing." + uuid.NewString()
|
||||
|
||||
Reference in New Issue
Block a user