feat: govern knowledge assets and source citations

This commit is contained in:
key
2026-08-02 01:43:43 +08:00
parent cafb836cda
commit 699cc08050
144 changed files with 17205 additions and 453 deletions
+1
View File
@@ -77,6 +77,7 @@ export type VectorIndexStatus = {
qdrantDimension?: number;
qdrantPoints?: number;
fragments: number;
ungovernedFragments: number;
embeddedFragments: number;
embeddingModel?: string;
embeddingDimension?: number;
+653
View File
@@ -82,6 +82,384 @@ export type ProcessingRetryResponse = {
status: string;
};
export type QualityAsset = {
id: number;
knowledgeId: number;
attachmentId?: number;
docId: string;
sourceName: string;
sourceType: string;
sourceAuthority: string;
sourceVersion: string;
dataClass: string;
lifecycleStatus: string;
indexStatus: string;
applicableRegion?: string;
applicableProject?: string;
applicableRole?: string;
effectiveFrom?: string;
effectiveTo?: string;
currentVersionId: number;
semanticAnalysisStatus: 'PENDING' | 'PROCESSING' | 'COMPLETE' | 'NOT_REQUIRED' | 'FAILED' | 'DEAD_LETTER';
semanticLastError?: string;
semanticRetryCount: number;
issueCount: number;
blockingIssueCount: number;
updateTime: string;
};
export type QualityIssue = {
id: number;
reasonCode: string;
severity: 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL';
gateType: 'HARD' | 'SOFT';
evidenceJson: string;
recommendedAction: string;
detectorType: string;
detectorVersion: string;
status: string;
createTime: string;
};
export type QualityPrivacyPreview = {
versionId: number;
privacyStatus: 'NOT_PROCESSED' | 'CLEAN' | 'REDACTED' | 'BLOCKED';
redactionPolicyVersion?: string;
redactionSummaryJson?: string;
redactedSourceName?: string;
redactedContentPreview?: string;
};
export type QualityConflict = {
id: number;
conflictType: 'POLARITY' | 'VALUE' | 'APPLICABILITY';
status: 'PENDING_REVIEW' | 'CONFIRMED' | 'FALSE_POSITIVE' | 'RESOLVED';
leftText: string;
rightText: string;
leftAssetId: number;
leftSourceName: string;
rightAssetId: number;
rightSourceName: string;
evidenceJson: string;
reviewNote?: string;
createTime: string;
};
export type QualityReviewRequest = {
reason: string;
usageType: string;
sourceAuthority: string;
sourceVersion: string;
applicableRegion?: string;
applicableProject?: string;
applicableRole?: string;
effectiveFrom?: string;
effectiveTo?: string;
acceptedReasonCodes: string[];
supersedesAssetId?: number;
};
export type QualityPublishResult = {
assetId: number;
versionId: number;
lifecycleStatus: string;
indexStatus: string;
fragments: number;
reviewId?: number;
};
export type LegacyStageResult = {
discovered: number;
staged: number;
reparsed: number;
quarantined: number;
stagedAssetIds: number[];
failedAttachmentIds: number[];
nextCursor: number;
moreAvailable: boolean;
};
export type LegacyStagePreview = {
discovered: number;
sourceAvailable: number;
sourceUnavailable: number;
nextCursor: number;
moreAvailable: boolean;
};
export type KnowledgeGlossaryTerm = {
id: number;
term: string;
canonicalTerm: string;
aliases: string[];
definition?: string;
applicableRegion?: string;
applicableProject?: string;
applicableRole?: string;
versionNo: number;
status: 'DRAFT' | 'ACTIVE' | 'REJECTED' | 'DEPRECATED';
reviewerId?: number;
reviewReason?: string;
reviewedTime?: string;
createBy?: number;
createTime: string;
updateTime: string;
};
export type KnowledgeGlossaryDraft = {
term: string;
canonicalTerm: string;
aliases: string[];
definition?: string;
applicableRegion?: string;
applicableProject?: string;
applicableRole?: string;
};
export type ProcessingRuleStatus = 'DRAFT' | 'SHADOW' | 'PAUSED' | 'RETIRED';
export type ProcessingRule = {
id: number;
ruleCode: string;
versionNo: number;
stage: string;
riskClass: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
action: 'FLAG' | 'REVIEW' | 'QUARANTINE';
implementationType: 'DETERMINISTIC' | 'STATISTICAL' | 'LLM_ASSISTED';
scope: Record<string, unknown>;
config: Record<string, unknown>;
status: ProcessingRuleStatus;
ownerId: number;
supersedesRuleId?: number;
shadowStartedTime?: string;
retiredTime?: string;
createTime: string;
updateTime: string;
};
export type ProcessingRuleDraft = {
ruleCode: string;
stage: string;
riskClass: ProcessingRule['riskClass'];
action: ProcessingRule['action'];
implementationType: ProcessingRule['implementationType'];
scope: Record<string, unknown>;
config: Record<string, unknown>;
supersedesRuleId?: number;
};
export type ProcessingRuleMetrics = {
ruleId: number;
sampleCount: number;
matchedCount: number;
falseAllowCount: number;
falseBlockCount: number;
agreementRate: number;
falseAllowRate: number;
falseBlockRate: number;
};
export type RuleReviewSample = {
id: number;
ruleId: number;
sourceEvaluationId?: number;
assetId?: number;
versionId?: number;
sampleKey: string;
samplingStrategy: 'RANDOM' | 'RISK_WEIGHTED' | 'MISMATCH' | 'MANUAL';
status: 'PENDING' | 'REVIEWED';
reviewResult?: 'CONFIRMED' | 'FALSE_ALLOW' | 'FALSE_BLOCK' | 'NOT_APPLICABLE';
reviewNote?: string;
sampledBy: number;
sampledTime: string;
reviewedBy?: number;
reviewedTime?: string;
};
export type RuleSamplingResult = {
runId?: string;
candidateCount: number;
eligibleCount: number;
createdCount: number;
mismatchCount: number;
};
export type GoldenDatasetStatus = 'DRAFT' | 'FROZEN' | 'RETIRED';
export type GoldenDataset = {
id: number;
datasetCode: string;
versionNo: number;
name: string;
description?: string;
status: GoldenDatasetStatus;
sampleCount: number;
contentHash?: string;
ownerId: number;
frozenBy?: number;
frozenTime?: string;
createTime: string;
updateTime: string;
};
export type GoldenDatasetDraft = {
datasetCode: string;
name: string;
description?: string;
};
export type GoldenSample = {
id: number;
datasetId: number;
sampleKey: string;
riskClass: ProcessingRule['riskClass'];
expectedDecision: 'PASS' | 'REVIEW' | 'BLOCK';
reasonCodes: string[];
evidenceRef: string;
assetId?: number;
versionId?: number;
createdBy: number;
createTime: string;
};
export type GoldenSampleDraft = {
sampleKey: string;
riskClass: GoldenSample['riskClass'];
expectedDecision: GoldenSample['expectedDecision'];
reasonCodes: string[];
evidenceRef: string;
assetId?: number;
versionId?: number;
};
export type AcceptanceProfileStatus = 'DRAFT' | 'FROZEN' | 'RETIRED';
export type AcceptanceProfile = {
id: number;
ruleId: number;
versionNo: number;
riskClass: ProcessingRule['riskClass'];
minTotalSamples: number;
minGoldenSamples: number;
minReviewedSamples: number;
minAgreementRate: number;
maxFalseAllowRate: number;
maxFalseBlockRate: number;
minReviewCoverageRate: number;
status: AcceptanceProfileStatus;
ownerId: number;
freezeReason?: string;
frozenBy?: number;
frozenTime?: string;
createTime: string;
updateTime: string;
};
export type AcceptanceProfileDraft = Pick<
AcceptanceProfile,
| 'minTotalSamples'
| 'minGoldenSamples'
| 'minReviewedSamples'
| 'minAgreementRate'
| 'maxFalseAllowRate'
| 'maxFalseBlockRate'
| 'minReviewCoverageRate'
>;
export type RuleReadiness = {
evidenceReady: boolean;
enforcementEnabled: boolean;
ruleStatus: ProcessingRuleStatus;
riskClass: ProcessingRule['riskClass'];
profile?: AcceptanceProfile;
metrics: {
totalSamples: number;
goldenSamples: number;
matchedSamples: number;
falseAllowCount: number;
falseBlockCount: number;
reviewedSamples: number;
pendingMismatchReviews: number;
agreementRate: number;
falseAllowRate: number;
falseBlockRate: number;
reviewCoverageRate: number;
};
reasonCodes: string[];
};
export type PipelineRun = {
id: number;
runId: string;
assetId?: number;
inputVersionId?: number;
outputVersionId?: number;
stage: string;
processorName: string;
processorVersion: string;
triggerType: string;
status: 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED';
metrics: Record<string, unknown>;
errorCode?: string;
errorMessage?: string;
requestedBy: number;
startedTime: string;
endedTime?: string;
};
export type QualityIndexHealth = {
pending: number;
processing: number;
failed: number;
deadLetter: number;
oldestPendingSeconds: number;
staleAssets: number;
vectorMatched: boolean;
mysqlFragments?: number;
ungovernedFragments?: number;
qdrantPoints?: number;
vectorMessage: string;
healthy: boolean;
};
export type QualitySnapshot = {
totalAssets: number;
reviewPending: number;
quarantined: number;
published: number;
deprecated: number;
openHardFindings: number;
openSoftFindings: number;
semanticDeadLetter: number;
pendingDuplicateReviews: number;
pendingConflictReviews: number;
untraceablePublished: number;
expiredPublished: number;
publishedOpenFindings: number;
publishedMembershipGap: number;
publishedFragmentGap: number;
successfulQueries24h: number;
queriesMissingEvidence24h: number;
traceabilityRate: number;
queryEvidenceCoverage24h: number;
indexHealth: QualityIndexHealth;
healthy: boolean;
};
export type QualityAlert = {
id: number;
alertCode: string;
severity: 'WARNING' | 'ERROR' | 'CRITICAL';
status: 'OPEN' | 'RESOLVED';
message: string;
evidenceJson?: string;
occurrenceCount: number;
firstSeenTime: string;
lastSeenTime: string;
resolvedTime?: string;
detectorVersion: string;
};
export type LocalImportRequest = {
directory: string;
category: string;
@@ -128,6 +506,281 @@ export function retryProcessingTask(id: number): Promise<ApiResult<ProcessingRet
});
}
export function listQualityAssets(status = 'REVIEW_PENDING', limit = 100): Promise<ApiResult<QualityAsset[]>> {
return request({
url: '/api/knowledge/quality/assets',
method: 'get',
params: { status, limit }
});
}
export function listQualityIssues(assetId: number): Promise<ApiResult<QualityIssue[]>> {
return request({
url: `/api/knowledge/quality/assets/${assetId}/issues`,
method: 'get'
});
}
export function getQualityPrivacyPreview(assetId: number): Promise<ApiResult<QualityPrivacyPreview>> {
return request({
url: `/api/knowledge/quality/assets/${assetId}/privacy-preview`,
method: 'get'
});
}
export function listQualityConflicts(assetId: number): Promise<ApiResult<QualityConflict[]>> {
return request({
url: `/api/knowledge/quality/assets/${assetId}/conflicts`,
method: 'get'
});
}
export function reviewQualityConflict(
conflictId: number,
data: { decision: 'CONFIRMED' | 'FALSE_POSITIVE' | 'RESOLVED'; note: string }
): Promise<ApiResult<{ conflictId: number; status: string }>> {
return request({
url: `/api/knowledge/quality/conflicts/${conflictId}/review`,
method: 'post',
data
});
}
export function getQualityIndexHealth(): Promise<ApiResult<QualityIndexHealth>> {
return request({
url: '/api/knowledge/quality/index-health',
method: 'get'
});
}
export function getQualityMetrics(): Promise<ApiResult<QualitySnapshot>> {
return request({
url: '/api/knowledge/quality/metrics',
method: 'get'
});
}
export function listQualityAlerts(includeResolved = false, limit = 100): Promise<ApiResult<QualityAlert[]>> {
return request({
url: '/api/knowledge/quality/alerts',
method: 'get',
params: { includeResolved, limit }
});
}
export function publishQualityAsset(assetId: number, data: QualityReviewRequest): Promise<ApiResult<QualityPublishResult>> {
return request({
url: `/api/knowledge/quality/assets/${assetId}/publish`,
method: 'post',
data
});
}
export function withdrawQualityAsset(assetId: number, reason: string): Promise<ApiResult<QualityPublishResult>> {
return request({
url: `/api/knowledge/quality/assets/${assetId}/withdraw`,
method: 'post',
data: { reason }
});
}
export function stageLegacyQualityAssets(limit = 50, afterAttachmentId = 0): Promise<ApiResult<LegacyStageResult>> {
return request({
url: '/api/knowledge/quality/legacy/stage',
method: 'post',
params: { limit, afterAttachmentId },
headers: { repeatSubmit: false }
});
}
export function previewLegacyQualityAssets(limit = 50, afterAttachmentId = 0): Promise<ApiResult<LegacyStagePreview>> {
return request({
url: '/api/knowledge/quality/legacy/preview',
method: 'get',
params: { limit, afterAttachmentId }
});
}
export function listKnowledgeGlossary(status: KnowledgeGlossaryTerm['status'] = 'ACTIVE', limit = 200): Promise<ApiResult<KnowledgeGlossaryTerm[]>> {
return request({
url: '/api/knowledge/quality/glossary',
method: 'get',
params: { status, limit }
});
}
export function createKnowledgeGlossaryDraft(data: KnowledgeGlossaryDraft): Promise<ApiResult<KnowledgeGlossaryTerm>> {
return request({
url: '/api/knowledge/quality/glossary',
method: 'post',
data
});
}
export function approveKnowledgeGlossary(id: number, reason: string): Promise<ApiResult<KnowledgeGlossaryTerm>> {
return request({
url: `/api/knowledge/quality/glossary/${id}/approve`,
method: 'post',
data: { reason }
});
}
export function deprecateKnowledgeGlossary(id: number, reason: string): Promise<ApiResult<KnowledgeGlossaryTerm>> {
return request({
url: `/api/knowledge/quality/glossary/${id}/deprecate`,
method: 'post',
data: { reason }
});
}
export function rejectKnowledgeGlossary(id: number, reason: string): Promise<ApiResult<KnowledgeGlossaryTerm>> {
return request({
url: `/api/knowledge/quality/glossary/${id}/reject`,
method: 'post',
data: { reason }
});
}
export function listProcessingRules(status: ProcessingRuleStatus = 'SHADOW', limit = 100): Promise<ApiResult<ProcessingRule[]>> {
return request({
url: '/api/knowledge/quality/rules',
method: 'get',
params: { status, limit }
});
}
export function createProcessingRuleDraft(data: ProcessingRuleDraft): Promise<ApiResult<ProcessingRule>> {
return request({
url: '/api/knowledge/quality/rules',
method: 'post',
data
});
}
export function transitionProcessingRule(ruleId: number, targetStatus: ProcessingRuleStatus, reason: string): Promise<ApiResult<ProcessingRule>> {
return request({
url: `/api/knowledge/quality/rules/${ruleId}/status`,
method: 'post',
data: { targetStatus, reason }
});
}
export function getProcessingRuleMetrics(ruleId: number): Promise<ApiResult<ProcessingRuleMetrics>> {
return request({
url: `/api/knowledge/quality/rules/${ruleId}/metrics`,
method: 'get'
});
}
export function listRuleReviewSamples(
ruleId: number,
status: RuleReviewSample['status'] = 'PENDING',
limit = 100
): Promise<ApiResult<RuleReviewSample[]>> {
return request({
url: `/api/knowledge/quality/rules/${ruleId}/samples`,
method: 'get',
params: { status, limit }
});
}
export function reviewRuleSample(
sampleId: number,
data: { result: NonNullable<RuleReviewSample['reviewResult']>; note: string }
): Promise<ApiResult<RuleReviewSample>> {
return request({
url: `/api/knowledge/quality/rules/samples/${sampleId}/review`,
method: 'post',
data
});
}
export function scheduleRuleReviewSamples(limit = 200): Promise<ApiResult<RuleSamplingResult>> {
return request({
url: '/api/knowledge/quality/rules/samples/schedule',
method: 'post',
params: { limit },
headers: { repeatSubmit: false }
});
}
export function listPipelineRuns(status = 'ALL', limit = 50): Promise<ApiResult<PipelineRun[]>> {
return request({
url: '/api/knowledge/quality/pipeline-runs',
method: 'get',
params: { status, limit }
});
}
export function listGoldenDatasets(status: GoldenDatasetStatus = 'DRAFT', limit = 100): Promise<ApiResult<GoldenDataset[]>> {
return request({
url: '/api/knowledge/quality/golden-datasets',
method: 'get',
params: { status, limit }
});
}
export function createGoldenDatasetDraft(data: GoldenDatasetDraft): Promise<ApiResult<GoldenDataset>> {
return request({
url: '/api/knowledge/quality/golden-datasets',
method: 'post',
data
});
}
export function listGoldenSamples(datasetId: number, limit = 200): Promise<ApiResult<GoldenSample[]>> {
return request({
url: `/api/knowledge/quality/golden-datasets/${datasetId}/samples`,
method: 'get',
params: { limit }
});
}
export function addGoldenSample(datasetId: number, data: GoldenSampleDraft): Promise<ApiResult<GoldenSample>> {
return request({
url: `/api/knowledge/quality/golden-datasets/${datasetId}/samples`,
method: 'post',
data
});
}
export function freezeGoldenDataset(datasetId: number): Promise<ApiResult<GoldenDataset>> {
return request({
url: `/api/knowledge/quality/golden-datasets/${datasetId}/freeze`,
method: 'post'
});
}
export function listAcceptanceProfiles(ruleId: number): Promise<ApiResult<AcceptanceProfile[]>> {
return request({
url: `/api/knowledge/quality/rules/${ruleId}/acceptance-profiles`,
method: 'get'
});
}
export function createAcceptanceProfile(ruleId: number, data: AcceptanceProfileDraft): Promise<ApiResult<AcceptanceProfile>> {
return request({
url: `/api/knowledge/quality/rules/${ruleId}/acceptance-profiles`,
method: 'post',
data
});
}
export function freezeAcceptanceProfile(ruleId: number, profileId: number, reason: string): Promise<ApiResult<AcceptanceProfile>> {
return request({
url: `/api/knowledge/quality/rules/${ruleId}/acceptance-profiles/${profileId}/freeze`,
method: 'post',
data: { reason }
});
}
export function getRuleReadiness(ruleId: number): Promise<ApiResult<RuleReadiness>> {
return request({
url: `/api/knowledge/quality/rules/${ruleId}/readiness`,
method: 'get'
});
}
export function importLocalKnowledgeDocs(data: LocalImportRequest): Promise<ApiResult<LocalImportResponse>> {
return request({
url: '/api/knowledge/doc/import-local',
+9 -1
View File
@@ -92,7 +92,10 @@ export type AnswerFeedbackItem = {
position: string;
source: string;
reviewId?: number | null;
requestId?: string | null;
reviewStatus: string;
reviewAction?: string | null;
reviewNote?: string | null;
reviewer?: string | null;
reviewedTime?: string | null;
createTime: string;
@@ -102,6 +105,7 @@ export type AnswerFeedbackItem = {
export type AnswerFeedbackReviewResponse = {
id: number;
reviewStatus: string;
reviewAction: 'KEEP' | 'WITHDRAW';
reviewer?: string | null;
reviewedTime?: string | null;
};
@@ -194,10 +198,14 @@ export function listAnswerFeedback(status = '待复核', limit = 50): Promise<Ap
});
}
export function reviewAnswerFeedback(id: number): Promise<ApiResult<AnswerFeedbackReviewResponse>> {
export function reviewAnswerFeedback(
id: number,
data: { action: 'KEEP' | 'WITHDRAW'; note: string }
): Promise<ApiResult<AnswerFeedbackReviewResponse>> {
return request({
url: `/api/knowledge/answer-feedback/${id}/review`,
method: 'post',
data,
headers: { repeatSubmit: false }
});
}