Files
git.stella-ops.org/src/AdvisoryAI/StellaOps.AdvisoryAI/UnifiedSearch/UnifiedSearchModels.cs

253 lines
6.6 KiB
C#

using System.Text.Json;
namespace StellaOps.AdvisoryAI.UnifiedSearch;
public sealed record UnifiedChunk(
string ChunkId,
string DocId,
string Kind,
string Domain,
string Title,
string Body,
float[]? Embedding,
string? EntityKey,
string? EntityType,
string? Anchor,
string? SectionPath,
int SpanStart,
int SpanEnd,
DateTimeOffset? Freshness,
JsonDocument Metadata);
public sealed record UnifiedSearchRequest(
string Q,
int? K = null,
UnifiedSearchFilter? Filters = null,
bool IncludeSynthesis = true,
bool IncludeDebug = false,
AmbientContext? Ambient = null);
public sealed record UnifiedSearchFilter
{
public IReadOnlyList<string>? Domains { get; init; }
public IReadOnlyList<string>? EntityTypes { get; init; }
public string? EntityKey { get; init; }
public string? Product { get; init; }
public string? Version { get; init; }
public string? Service { get; init; }
public IReadOnlyList<string>? Tags { get; init; }
public string? Tenant { get; init; }
/// <summary>
/// User scopes extracted from the authenticated request context. Used by
/// <c>DomainWeightCalculator</c> to apply role-based domain biases (Sprint 106 / G6).
/// Not serialized in API responses.
/// </summary>
public IReadOnlyList<string>? UserScopes { get; init; }
/// <summary>
/// Optional user identifier for ephemeral session context carry-forward.
/// </summary>
public string? UserId { get; init; }
}
public sealed record AmbientContext
{
public string? CurrentRoute { get; init; }
public IReadOnlyList<string>? VisibleEntityKeys { get; init; }
public IReadOnlyList<string>? RecentSearches { get; init; }
public string? SessionId { get; init; }
public bool ResetSession { get; init; }
public AmbientAction? LastAction { get; init; }
}
public sealed record AmbientAction
{
public string Action { get; init; } = string.Empty;
public string? Source { get; init; }
public string? QueryHint { get; init; }
public string? Domain { get; init; }
public string? EntityKey { get; init; }
public string? Route { get; init; }
public DateTimeOffset? OccurredAt { get; init; }
}
public sealed record SearchSuggestion(string Text, string Reason);
public sealed record SearchRefinement(string Text, string Source);
public sealed record ContextAnswer(
string Status,
string Code,
string Summary,
string Reason,
string Evidence,
IReadOnlyList<ContextAnswerCitation>? Citations = null,
IReadOnlyList<ContextAnswerQuestion>? Questions = null);
public sealed record ContextAnswerCitation(
string EntityKey,
string Title,
string Domain,
string? Route = null);
public sealed record ContextAnswerQuestion(
string Query,
string Kind = "follow_up");
public sealed record UnifiedSearchResponse(
string Query,
int TopK,
IReadOnlyList<EntityCard> Cards,
SynthesisResult? Synthesis,
UnifiedSearchDiagnostics Diagnostics,
IReadOnlyList<SearchSuggestion>? Suggestions = null,
IReadOnlyList<SearchRefinement>? Refinements = null,
ContextAnswer? ContextAnswer = null);
public sealed record EntityCard
{
public string EntityKey { get; init; } = string.Empty;
public string EntityType { get; init; } = string.Empty;
public string Domain { get; init; } = "knowledge";
public string Title { get; init; } = string.Empty;
public string Snippet { get; init; } = string.Empty;
public double Score { get; init; }
public string? Severity { get; init; }
public IReadOnlyList<EntityCardAction> Actions { get; init; } = [];
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
public IReadOnlyList<string> Sources { get; init; } = [];
public EntityCardPreview? Preview { get; init; }
public IReadOnlyList<EntityCardFacet> Facets { get; init; } = [];
public IReadOnlyList<string> Connections { get; init; } = [];
public IReadOnlyDictionary<string, string> SynthesisHints { get; init; } =
new Dictionary<string, string>(StringComparer.Ordinal);
}
public sealed record EntityCardFacet
{
public string Domain { get; init; } = "knowledge";
public string Title { get; init; } = string.Empty;
public string Snippet { get; init; } = string.Empty;
public double Score { get; init; }
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
public IReadOnlyList<EntityCardAction> Actions { get; init; } = [];
}
public sealed record EntityCardPreview(
string ContentType,
string Content,
string? Language = null,
IReadOnlyList<PreviewField>? StructuredFields = null);
public sealed record PreviewField(string Label, string Value, string? Severity = null);
public sealed record EntityCardAction(
string Label,
string ActionType,
string? Route = null,
string? Command = null,
bool IsPrimary = false);
public sealed record SynthesisResult
{
public string Summary { get; init; } = string.Empty;
public string Template { get; init; } = string.Empty;
public string Confidence { get; init; } = "low";
public int SourceCount { get; init; }
public IReadOnlyList<string> DomainsCovered { get; init; } = [];
public IReadOnlyList<SynthesisCitation>? Citations { get; init; }
public double? GroundingScore { get; init; }
}
public sealed record SynthesisCitation
{
public int Index { get; init; }
public string EntityKey { get; init; } = string.Empty;
public string Title { get; init; } = string.Empty;
}
public sealed record UnifiedSearchDiagnostics(
int FtsMatches,
int VectorMatches,
int EntityCardCount,
long DurationMs,
bool UsedVector,
string Mode,
QueryPlan? Plan = null,
IReadOnlyList<FederationBackendDiagnostic>? Federation = null);
public sealed record FederationBackendDiagnostic(
string Backend,
int ResultCount,
long DurationMs,
bool TimedOut,
string Status);
public sealed record QueryPlan
{
public string OriginalQuery { get; init; } = string.Empty;
public string NormalizedQuery { get; init; } = string.Empty;
public string Intent { get; init; } = "explore";
public IReadOnlyList<EntityMention> DetectedEntities { get; init; } = [];
public IReadOnlyDictionary<string, double> DomainWeights { get; init; } =
new Dictionary<string, double>(StringComparer.Ordinal);
public IReadOnlyDictionary<string, double> ContextEntityBoosts { get; init; } =
new Dictionary<string, double>(StringComparer.Ordinal);
}
public sealed record EntityMention(
string Value,
string EntityType,
int StartIndex,
int Length);