mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
1107 lines
35 KiB
Python
1107 lines
35 KiB
Python
# Source of truth for the signal payload taxonomy — the shared contract between backend emitters,
|
|
# the facade's validation, and the inbox cards. The frontend TypeScript equivalents are generated
|
|
# from these models: the extras are declared on the `signals`-endpoint serializer (see
|
|
# `SignalNodeSerializer` in `serializers.py`), so they flow through the standard OpenAPI/Orval pipeline.
|
|
|
|
import enum
|
|
from typing import Annotated, Literal, get_args
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
from pydantic.fields import FieldInfo
|
|
|
|
from products.signals.backend.enums import ReportPriority, SignalSourceProduct, SignalSourceType
|
|
|
|
|
|
class ContractModel(BaseModel):
|
|
# Emitted payloads are validated against these models at the emit boundary; unknown fields are
|
|
# rejected rather than silently written to signal metadata (parity with the generated schema.py
|
|
# models these replaced, which all carried extra="forbid").
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
|
|
class SignalRemediation(ContractModel):
|
|
human: str
|
|
agent: str
|
|
priority: ReportPriority | None = None
|
|
|
|
|
|
class SignalExtraBase(ContractModel):
|
|
pass
|
|
|
|
|
|
class SignalInputBase(ContractModel):
|
|
source_id: str
|
|
description: str
|
|
weight: float
|
|
remediation: SignalRemediation | None = None
|
|
|
|
|
|
# ── Session replay ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
class SessionProblemEventEntry(ContractModel):
|
|
event: str
|
|
timestamp: str
|
|
current_url: str | None = None
|
|
event_type: str | None = None
|
|
interaction_text: str | None = None
|
|
|
|
|
|
class SessionProblemSignalExtra(SignalExtraBase):
|
|
session_id: str
|
|
segment_title: str
|
|
start_time: str
|
|
end_time: str
|
|
problem_type: Literal["confusion", "abandonment", "blocking_exception", "non_blocking_exception", "failure"]
|
|
distinct_id: str
|
|
session_start_time: str | None = None
|
|
session_end_time: str | None = None
|
|
session_duration: float | None = None
|
|
session_active_seconds: float | None = None
|
|
exported_asset_id: int | None = None
|
|
event_history: list[SessionProblemEventEntry] | None = None
|
|
|
|
|
|
class SessionProblemSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.SESSION_PROBLEM]
|
|
source_product: Literal[SignalSourceProduct.SESSION_REPLAY]
|
|
extra: SessionProblemSignalExtra
|
|
|
|
|
|
# ── LLM analytics ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
class LlmEvalSignalExtra(SignalExtraBase):
|
|
evaluation_id: str
|
|
target_event_id: str | None = None
|
|
target_event_type: str | None = None
|
|
trace_id: str
|
|
model: str | None = None
|
|
provider: str | None = None
|
|
|
|
|
|
class LlmEvaluationSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.EVALUATION]
|
|
source_product: Literal[SignalSourceProduct.LLM_ANALYTICS]
|
|
extra: LlmEvalSignalExtra
|
|
|
|
|
|
class LlmEvalReportSignalExtra(SignalExtraBase):
|
|
evaluation_id: str
|
|
evaluation_name: str
|
|
evaluation_description: str
|
|
report_id: str
|
|
report_run_id: str
|
|
period_start: str
|
|
period_end: str
|
|
|
|
|
|
class LlmEvaluationReportSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.EVALUATION_REPORT]
|
|
source_product: Literal[SignalSourceProduct.LLM_ANALYTICS]
|
|
extra: LlmEvalReportSignalExtra
|
|
|
|
|
|
# ── Zendesk ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class ZendeskTicketSignalExtra(SignalExtraBase):
|
|
url: str
|
|
type: str | None
|
|
tags: list[str]
|
|
created_at: str
|
|
priority: str | None
|
|
status: str
|
|
|
|
|
|
class ZendeskTicketSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.ZENDESK]
|
|
extra: ZendeskTicketSignalExtra
|
|
|
|
|
|
# ── GitHub ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class GithubIssueSignalExtra(SignalExtraBase):
|
|
html_url: str
|
|
number: int
|
|
labels: list[str]
|
|
created_at: str
|
|
updated_at: str
|
|
locked: bool
|
|
state: str
|
|
|
|
|
|
class GithubIssueSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.GITHUB]
|
|
extra: GithubIssueSignalExtra
|
|
|
|
|
|
# ── Linear ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class LinearIssueSignalExtra(SignalExtraBase):
|
|
url: str
|
|
identifier: str
|
|
number: int
|
|
priority: int
|
|
priority_label: str
|
|
labels: list[str]
|
|
state_name: str | None
|
|
state_type: str | None
|
|
team_name: str | None
|
|
created_at: str
|
|
updated_at: str
|
|
|
|
|
|
class LinearIssueSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.LINEAR]
|
|
extra: LinearIssueSignalExtra
|
|
|
|
|
|
# ── Jira ────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class JiraIssueSignalExtra(SignalExtraBase):
|
|
key: str
|
|
url: str | None
|
|
status: str | None
|
|
priority: str | None
|
|
assignee: str | None
|
|
labels: list[str]
|
|
created: str | None
|
|
updated: str | None
|
|
|
|
|
|
class JiraIssueSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.JIRA]
|
|
extra: JiraIssueSignalExtra
|
|
|
|
|
|
# ── Conversations ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
class ConversationsTicketImage(ContractModel):
|
|
url: str
|
|
author: str
|
|
|
|
|
|
class ConversationsTicketSignalExtra(SignalExtraBase):
|
|
ticket_number: int
|
|
channel_source: str
|
|
channel_detail: str | None
|
|
status: str
|
|
priority: str | None
|
|
created_at: str
|
|
email_subject: str | None
|
|
# Publicly fetchable media URLs pasted into the thread, so the research agent can inspect
|
|
# screenshots directly. Absent (rather than empty) when the thread has no attachments.
|
|
images: list[ConversationsTicketImage] | None = None
|
|
|
|
|
|
class ConversationsTicketSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.CONVERSATIONS]
|
|
extra: ConversationsTicketSignalExtra
|
|
|
|
|
|
# ── Error tracking ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
class ErrorTrackingSignalExtra(SignalExtraBase):
|
|
fingerprint: str
|
|
|
|
|
|
class ErrorTrackingSignalInput(SignalInputBase):
|
|
source_type: Literal[
|
|
SignalSourceType.ISSUE_CREATED, SignalSourceType.ISSUE_REOPENED, SignalSourceType.ISSUE_SPIKING
|
|
]
|
|
source_product: Literal[SignalSourceProduct.ERROR_TRACKING]
|
|
extra: ErrorTrackingSignalExtra
|
|
|
|
|
|
# ── pganalyze ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class PgAnalyzeIssueReference(ContractModel):
|
|
# pganalyze reference objects omit nullable keys entirely (e.g. index/table refs
|
|
# carry no queryText), so these need defaults to keep such refs valid.
|
|
kind: str | None = None
|
|
name: str | None = None
|
|
url: str | None = None
|
|
queryText: str | None = None
|
|
|
|
|
|
class PgAnalyzeIssueSignalExtra(SignalExtraBase):
|
|
severity: str | None
|
|
references: list[PgAnalyzeIssueReference]
|
|
database_id: str | None
|
|
server_human_id: str | None
|
|
server_name: str | None
|
|
synced_at: str
|
|
|
|
|
|
class PgAnalyzeIssueSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.PGANALYZE]
|
|
extra: PgAnalyzeIssueSignalExtra
|
|
|
|
|
|
# ── Endpoints ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class EndpointExecutionFailedSignalExtra(SignalExtraBase):
|
|
endpoint_name: str
|
|
endpoint_version: int | None
|
|
materialized: bool
|
|
saved_query_id: str | None
|
|
error_class: str
|
|
error_message: str
|
|
|
|
|
|
class EndpointExecutionFailedSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ENDPOINT_EXECUTION_FAILED]
|
|
source_product: Literal[SignalSourceProduct.ENDPOINTS]
|
|
extra: EndpointExecutionFailedSignalExtra
|
|
|
|
|
|
class EndpointBreakdownLimitExceededSignalExtra(SignalExtraBase):
|
|
endpoint_name: str
|
|
breakdown_limit: int
|
|
|
|
|
|
class EndpointBreakdownLimitExceededSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ENDPOINT_BREAKDOWN_LIMIT_EXCEEDED]
|
|
source_product: Literal[SignalSourceProduct.ENDPOINTS]
|
|
extra: EndpointBreakdownLimitExceededSignalExtra
|
|
|
|
|
|
# ── Signals scout ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
class SignalsScoutEvidenceEntry(ContractModel):
|
|
source_product: str
|
|
entity_id: str | None = None
|
|
summary: str
|
|
|
|
|
|
class SignalsScoutTimeRange(ContractModel):
|
|
date_from: str
|
|
date_to: str
|
|
|
|
|
|
class SignalsScoutSignalExtra(SignalExtraBase):
|
|
scout_run_id: str
|
|
task_run_id: str
|
|
task_id: str | None = None
|
|
finding_id: str
|
|
skill_name: str
|
|
skill_version: int
|
|
confidence: float
|
|
severity: ReportPriority | None = None
|
|
hypothesis: str | None = None
|
|
evidence: list[SignalsScoutEvidenceEntry]
|
|
dedupe_keys: list[str] | None = None
|
|
tags: list[str] | None = None
|
|
time_range: SignalsScoutTimeRange | None = None
|
|
mcp_trace_id: str | None = None
|
|
|
|
|
|
class SignalsScoutSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.CROSS_SOURCE_ISSUE]
|
|
source_product: Literal[SignalSourceProduct.SIGNALS_SCOUT]
|
|
extra: SignalsScoutSignalExtra
|
|
|
|
|
|
# ── Logs ────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class LogsAlertStateChangeSignalExtra(SignalExtraBase):
|
|
alert_id: str
|
|
alert_name: str
|
|
action: Literal["firing", "broken"]
|
|
threshold_count: int
|
|
threshold_operator: Literal["above", "below"]
|
|
window_minutes: int
|
|
result_count: int | None
|
|
consecutive_failures: int
|
|
filters: dict[str, object]
|
|
url: str
|
|
|
|
|
|
class LogsAlertStateChangeSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ALERT_STATE_CHANGE]
|
|
source_product: Literal[SignalSourceProduct.LOGS]
|
|
extra: LogsAlertStateChangeSignalExtra
|
|
|
|
|
|
# ── Replay vision ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
class ReplayVisionScannerFindingSignalExtra(SignalExtraBase):
|
|
scanner_id: str
|
|
scanner_name: str
|
|
scanner_type: str
|
|
observation_id: str
|
|
session_id: str
|
|
confidence: float
|
|
problem_type: str
|
|
start_time: float
|
|
end_time: float
|
|
url: str
|
|
exported_asset_id: int
|
|
distinct_id: str | None = None
|
|
recording_start_time: str | None = None
|
|
recording_end_time: str | None = None
|
|
recording_duration: float | None = None
|
|
recording_active_seconds: float | None = None
|
|
|
|
|
|
class ReplayVisionScannerFindingSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.SCANNER_FINDING]
|
|
source_product: Literal[SignalSourceProduct.REPLAY_VISION]
|
|
extra: ReplayVisionScannerFindingSignalExtra
|
|
|
|
|
|
# ── Product analytics ───────────────────────────────────────────────────────────
|
|
|
|
|
|
class AnalyticsAnomalyInvestigationSignalExtra(SignalExtraBase):
|
|
alert_id: str
|
|
alert_name: str
|
|
alert_check_id: str
|
|
insight_id: str
|
|
detector_type: str
|
|
verdict: Literal["true_positive", "false_positive", "inconclusive"]
|
|
url: str
|
|
insight_name: str | None = None
|
|
insight_short_id: str | None = None
|
|
triggered_dates: list[str] | None = None
|
|
notebook_short_id: str | None = None
|
|
|
|
|
|
class AnalyticsAnomalyInvestigationSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ANOMALY_INVESTIGATION]
|
|
source_product: Literal[SignalSourceProduct.ANALYTICS]
|
|
extra: AnalyticsAnomalyInvestigationSignalExtra
|
|
|
|
|
|
# ── Health checks ───────────────────────────────────────────────────────────────
|
|
|
|
HealthCheckSeverity = Literal["critical", "warning", "info"]
|
|
|
|
|
|
class HealthCheckSignalExtra(SignalExtraBase):
|
|
kind: str
|
|
severity: HealthCheckSeverity
|
|
issue_id: str
|
|
title: str
|
|
summary: str
|
|
link: str
|
|
url: str
|
|
payload: dict[str, object]
|
|
|
|
|
|
class HealthCheckSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.HEALTH_ISSUE]
|
|
source_product: Literal[SignalSourceProduct.HEALTH_CHECKS]
|
|
extra: HealthCheckSignalExtra
|
|
|
|
|
|
# ── Engineering analytics ───────────────────────────────────────────────────────
|
|
# CI signals; detection lives in products/engineering_analytics/backend/logic/signals.
|
|
|
|
|
|
class EngineeringAnalyticsCIFlakyCheckSignalExtra(SignalExtraBase):
|
|
"""One immutable flaky observation: failed then passed on a later attempt of the same run,
|
|
so only non-determinism can explain the flip."""
|
|
|
|
repo_owner: str
|
|
repo_name: str
|
|
workflow_name: str
|
|
job_name: str
|
|
run_id: int
|
|
head_sha: str
|
|
failed_attempt: int
|
|
passed_attempt: int
|
|
# Runs this job flapped on within the window.
|
|
flaky_count: int
|
|
window_days: int
|
|
|
|
|
|
class EngineeringAnalyticsCIFlakyCheckSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.CI_FLAKY_CHECK]
|
|
source_product: Literal[SignalSourceProduct.ENGINEERING_ANALYTICS]
|
|
extra: EngineeringAnalyticsCIFlakyCheckSignalExtra
|
|
|
|
|
|
class EngineeringAnalyticsCIBrokenDefaultBranchSignalExtra(SignalExtraBase):
|
|
repo_owner: str
|
|
repo_name: str
|
|
workflow_name: str
|
|
branch: str
|
|
# Success rate in [0, 1] over runs that reached a verdict (success / failure / timed_out).
|
|
# Cancelled and skipped runs are excluded: they decided nothing, and counting them makes any
|
|
# workflow whose concurrency group cancels superseded trunk runs read as permanently failing.
|
|
conclusive_success_rate: float
|
|
conclusive_run_count: int
|
|
latest_conclusion: str
|
|
window_hours: int
|
|
|
|
|
|
class EngineeringAnalyticsCIBrokenDefaultBranchSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.CI_BROKEN_DEFAULT_BRANCH]
|
|
source_product: Literal[SignalSourceProduct.ENGINEERING_ANALYTICS]
|
|
extra: EngineeringAnalyticsCIBrokenDefaultBranchSignalExtra
|
|
|
|
|
|
class EngineeringAnalyticsCIDurationRegressionSignalExtra(SignalExtraBase):
|
|
repo_owner: str
|
|
repo_name: str
|
|
workflow_name: str
|
|
current_p95_seconds: float
|
|
baseline_p95_seconds: float
|
|
# Fractional increase of current p95 over baseline (0.5 = +50%).
|
|
pct_increase: float
|
|
current_p50_seconds: float
|
|
baseline_p50_seconds: float
|
|
window_days: int
|
|
|
|
|
|
class EngineeringAnalyticsCIDurationRegressionSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.CI_DURATION_REGRESSION]
|
|
source_product: Literal[SignalSourceProduct.ENGINEERING_ANALYTICS]
|
|
extra: EngineeringAnalyticsCIDurationRegressionSignalExtra
|
|
|
|
|
|
# ── Report reviewer types ───────────────────────────────────────────────────────
|
|
|
|
|
|
class RelevantCommit(ContractModel):
|
|
sha: str
|
|
url: str
|
|
reason: str
|
|
|
|
|
|
class SignalReviewerUserInfo(ContractModel):
|
|
id: int
|
|
uuid: str
|
|
first_name: str
|
|
last_name: str
|
|
email: str
|
|
|
|
|
|
class EnrichedReviewer(ContractModel):
|
|
github_login: str
|
|
github_name: str | None
|
|
relevant_commits: list[RelevantCommit]
|
|
user: SignalReviewerUserInfo | None
|
|
reason: str | None = None
|
|
|
|
|
|
# ── Tier-1 data-warehouse inbox sources ──────────────────────────────────────────
|
|
|
|
|
|
class FreshdeskTicketSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
priority: str | None
|
|
type: str | None
|
|
tags: list
|
|
created_at: str | None
|
|
|
|
|
|
class FreshdeskTicketSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.FRESHDESK]
|
|
extra: FreshdeskTicketSignalExtra
|
|
|
|
|
|
class FreshserviceTicketSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
priority: str | None
|
|
type: str | None
|
|
category: str | None
|
|
tags: list
|
|
created_at: str | None
|
|
|
|
|
|
class FreshserviceTicketSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.FRESHSERVICE]
|
|
extra: FreshserviceTicketSignalExtra
|
|
|
|
|
|
class FrontConversationSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
tags: list
|
|
created_at: str | None
|
|
|
|
|
|
class FrontConversationSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.FRONT]
|
|
extra: FrontConversationSignalExtra
|
|
|
|
|
|
class GorgiasTicketSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
priority: str | None
|
|
channel: str | None
|
|
tags: list
|
|
created_datetime: str | None
|
|
|
|
|
|
class GorgiasTicketSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.GORGIAS]
|
|
extra: GorgiasTicketSignalExtra
|
|
|
|
|
|
class KustomerConversationSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
priority: str | None
|
|
tags: list
|
|
createdAt: str | None
|
|
|
|
|
|
class KustomerConversationSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.KUSTOMER]
|
|
extra: KustomerConversationSignalExtra
|
|
|
|
|
|
class DixaConversationSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
channel: str | None
|
|
tags: list
|
|
created_at: str | None
|
|
|
|
|
|
class DixaConversationSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.DIXA]
|
|
extra: DixaConversationSignalExtra
|
|
|
|
|
|
class PlainThreadSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
priority: str | None
|
|
labels: list
|
|
createdAt: str | None
|
|
|
|
|
|
class PlainThreadSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.PLAIN]
|
|
extra: PlainThreadSignalExtra
|
|
|
|
|
|
class GitlabIssueSignalExtra(SignalExtraBase):
|
|
state: str | None
|
|
labels: list
|
|
iid: str | None
|
|
project_id: str | None
|
|
created_at: str | None
|
|
|
|
|
|
class GitlabIssueSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.GITLAB]
|
|
extra: GitlabIssueSignalExtra
|
|
|
|
|
|
class GiteaIssueSignalExtra(SignalExtraBase):
|
|
state: str | None
|
|
labels: list
|
|
html_url: str | None
|
|
number: str | None
|
|
created_at: str | None
|
|
|
|
|
|
class GiteaIssueSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.GITEA]
|
|
extra: GiteaIssueSignalExtra
|
|
|
|
|
|
class ShortcutStorySignalExtra(SignalExtraBase):
|
|
story_type: str | None
|
|
labels: list
|
|
workflow_state_id: str | None
|
|
created_at: str | None
|
|
|
|
|
|
class ShortcutStorySignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.SHORTCUT]
|
|
extra: ShortcutStorySignalExtra
|
|
|
|
|
|
class SentryIssueSignalExtra(SignalExtraBase):
|
|
level: str | None
|
|
status: str | None
|
|
permalink: str | None
|
|
shortId: str | None
|
|
firstSeen: str | None
|
|
|
|
|
|
class SentryIssueSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.SENTRY]
|
|
extra: SentryIssueSignalExtra
|
|
|
|
|
|
class RollbarItemSignalExtra(SignalExtraBase):
|
|
level: str | None
|
|
status: str | None
|
|
environment: str | None
|
|
framework: str | None
|
|
last_occurrence_timestamp: str | None
|
|
|
|
|
|
class RollbarItemSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.ROLLBAR]
|
|
extra: RollbarItemSignalExtra
|
|
|
|
|
|
class BugsnagErrorSignalExtra(SignalExtraBase):
|
|
severity: str | None
|
|
status: str | None
|
|
context: str | None
|
|
first_seen: str | None
|
|
last_seen: str | None
|
|
|
|
|
|
class BugsnagErrorSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.BUGSNAG]
|
|
extra: BugsnagErrorSignalExtra
|
|
|
|
|
|
class HoneybadgerFaultSignalExtra(SignalExtraBase):
|
|
environment: str | None
|
|
component: str | None
|
|
action: str | None
|
|
tags: list
|
|
url: str | None
|
|
created_at: str | None
|
|
|
|
|
|
class HoneybadgerFaultSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.HONEYBADGER]
|
|
extra: HoneybadgerFaultSignalExtra
|
|
|
|
|
|
class RaygunErrorGroupSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
applicationUrl: str | None
|
|
lastOccurredAt: str | None
|
|
createdAt: str | None
|
|
|
|
|
|
class RaygunErrorGroupSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.ISSUE]
|
|
source_product: Literal[SignalSourceProduct.RAYGUN]
|
|
extra: RaygunErrorGroupSignalExtra
|
|
|
|
|
|
# ── Tier-2 security scanners + Tier-3 feedback/reviews ────────────────────────────
|
|
|
|
|
|
class SnykScannerFindingSignalExtra(SignalExtraBase):
|
|
effective_severity_level: str | None
|
|
status: str | None
|
|
type: str | None
|
|
created_at: str | None
|
|
|
|
|
|
class SnykScannerFindingSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.SCANNER_FINDING]
|
|
source_product: Literal[SignalSourceProduct.SNYK]
|
|
extra: SnykScannerFindingSignalExtra
|
|
|
|
|
|
class SonarqubeScannerFindingSignalExtra(SignalExtraBase):
|
|
severity: str | None
|
|
type: str | None
|
|
status: str | None
|
|
component: str | None
|
|
rule: str | None
|
|
creationDate: str | None
|
|
|
|
|
|
class SonarqubeScannerFindingSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.SCANNER_FINDING]
|
|
source_product: Literal[SignalSourceProduct.SONARQUBE]
|
|
extra: SonarqubeScannerFindingSignalExtra
|
|
|
|
|
|
class SemgrepScannerFindingSignalExtra(SignalExtraBase):
|
|
severity: str | None
|
|
confidence: str | None
|
|
status: str | None
|
|
state: str | None
|
|
created_at: str | None
|
|
|
|
|
|
class SemgrepScannerFindingSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.SCANNER_FINDING]
|
|
source_product: Literal[SignalSourceProduct.SEMGREP]
|
|
extra: SemgrepScannerFindingSignalExtra
|
|
|
|
|
|
class Rapid7InsightvmScannerFindingSignalExtra(SignalExtraBase):
|
|
severity: str | None
|
|
cvss_v3_score: str | None
|
|
published: str | None
|
|
added: str | None
|
|
|
|
|
|
class Rapid7InsightvmScannerFindingSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.SCANNER_FINDING]
|
|
source_product: Literal[SignalSourceProduct.RAPID7_INSIGHTVM]
|
|
extra: Rapid7InsightvmScannerFindingSignalExtra
|
|
|
|
|
|
class FeaturebaseFeedbackSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
tags: list
|
|
upvotes: str | None
|
|
createdAt: str | None
|
|
|
|
|
|
class FeaturebaseFeedbackSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.FEEDBACK]
|
|
source_product: Literal[SignalSourceProduct.FEATUREBASE]
|
|
extra: FeaturebaseFeedbackSignalExtra
|
|
|
|
|
|
class FrillFeedbackSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
vote_count: str | None
|
|
topics: list
|
|
created_at: str | None
|
|
|
|
|
|
class FrillFeedbackSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.FEEDBACK]
|
|
source_product: Literal[SignalSourceProduct.FRILL]
|
|
extra: FrillFeedbackSignalExtra
|
|
|
|
|
|
class AhaFeedbackSignalExtra(SignalExtraBase):
|
|
workflow_status: str | None
|
|
score: str | None
|
|
votes: str | None
|
|
url: str | None
|
|
created_at: str | None
|
|
|
|
|
|
class AhaFeedbackSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.FEEDBACK]
|
|
source_product: Literal[SignalSourceProduct.AHA]
|
|
extra: AhaFeedbackSignalExtra
|
|
|
|
|
|
class UservoiceFeedbackSignalExtra(SignalExtraBase):
|
|
state: str | None
|
|
vote_count: str | None
|
|
category_name: str | None
|
|
created_at: str | None
|
|
|
|
|
|
class UservoiceFeedbackSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.FEEDBACK]
|
|
source_product: Literal[SignalSourceProduct.USERVOICE]
|
|
extra: UservoiceFeedbackSignalExtra
|
|
|
|
|
|
class ProductboardFeedbackSignalExtra(SignalExtraBase):
|
|
state: str | None
|
|
tags: list
|
|
displayUrl: str | None
|
|
createdAt: str | None
|
|
|
|
|
|
class ProductboardFeedbackSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.FEEDBACK]
|
|
source_product: Literal[SignalSourceProduct.PRODUCTBOARD]
|
|
extra: ProductboardFeedbackSignalExtra
|
|
|
|
|
|
class CannyFeedbackSignalExtra(SignalExtraBase):
|
|
status: str | None
|
|
tags: list
|
|
score: str | None
|
|
voteCount: str | None
|
|
url: str | None
|
|
created: str | None
|
|
|
|
|
|
class CannyFeedbackSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.FEEDBACK]
|
|
source_product: Literal[SignalSourceProduct.CANNY]
|
|
extra: CannyFeedbackSignalExtra
|
|
|
|
|
|
class AsknicelyFeedbackSignalExtra(SignalExtraBase):
|
|
score: str | None
|
|
status: str | None
|
|
question_type: str | None
|
|
segment: str | None
|
|
created: str | None
|
|
|
|
|
|
class AsknicelyFeedbackSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.FEEDBACK]
|
|
source_product: Literal[SignalSourceProduct.ASKNICELY]
|
|
extra: AsknicelyFeedbackSignalExtra
|
|
|
|
|
|
class RetentlyFeedbackSignalExtra(SignalExtraBase):
|
|
score: str | None
|
|
ratingCategory: str | None
|
|
feedbackTopics: list
|
|
resolved: str | None
|
|
createdDate: str | None
|
|
|
|
|
|
class RetentlyFeedbackSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.FEEDBACK]
|
|
source_product: Literal[SignalSourceProduct.RETENTLY]
|
|
extra: RetentlyFeedbackSignalExtra
|
|
|
|
|
|
class AppfiguresReviewSignalExtra(SignalExtraBase):
|
|
stars: str | None
|
|
version: str | None
|
|
product: str | None
|
|
date: str | None
|
|
|
|
|
|
class AppfiguresReviewSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.REVIEW]
|
|
source_product: Literal[SignalSourceProduct.APPFIGURES]
|
|
extra: AppfiguresReviewSignalExtra
|
|
|
|
|
|
class AppfollowReviewSignalExtra(SignalExtraBase):
|
|
rating: str | None
|
|
store: str | None
|
|
app_version: str | None
|
|
date: str | None
|
|
|
|
|
|
class AppfollowReviewSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.REVIEW]
|
|
source_product: Literal[SignalSourceProduct.APPFOLLOW]
|
|
extra: AppfollowReviewSignalExtra
|
|
|
|
|
|
class JudgemeReviewsReviewSignalExtra(SignalExtraBase):
|
|
rating: str | None
|
|
product_title: str | None
|
|
verified: str | None
|
|
created_at: str | None
|
|
|
|
|
|
class JudgemeReviewsReviewSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.REVIEW]
|
|
source_product: Literal[SignalSourceProduct.JUDGEME_REVIEWS]
|
|
extra: JudgemeReviewsReviewSignalExtra
|
|
|
|
|
|
# ── OAuth-connected support sources ───────────────────────────────────────────────
|
|
|
|
|
|
class IntercomTicketSignalExtra(SignalExtraBase):
|
|
state: str | None
|
|
priority: str | None
|
|
admin_assignee_id: str | None
|
|
created_at: str | None
|
|
|
|
|
|
class IntercomTicketSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.INTERCOM]
|
|
extra: IntercomTicketSignalExtra
|
|
|
|
|
|
class HubspotTicketSignalExtra(SignalExtraBase):
|
|
hs_ticket_priority: str | None
|
|
hs_pipeline_stage: str | None
|
|
hs_ticket_category: str | None
|
|
createdate: str | None
|
|
|
|
|
|
class HubspotTicketSignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.TICKET]
|
|
source_product: Literal[SignalSourceProduct.HUBSPOT]
|
|
extra: HubspotTicketSignalExtra
|
|
|
|
|
|
# ── Search analytics ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
class GoogleSearchConsoleSearchOpportunitySignalExtra(SignalExtraBase):
|
|
page: str
|
|
query: str
|
|
date: str
|
|
clicks: int
|
|
impressions: int
|
|
ctr: float
|
|
position: float
|
|
|
|
|
|
class GoogleSearchConsoleSearchOpportunitySignalInput(SignalInputBase):
|
|
source_type: Literal[SignalSourceType.SEARCH_OPPORTUNITY]
|
|
source_product: Literal[SignalSourceProduct.GOOGLE_SEARCH_CONSOLE]
|
|
extra: GoogleSearchConsoleSearchOpportunitySignalExtra
|
|
|
|
|
|
# ── Union over all signal variants ──────────────────────────────────────────────
|
|
# Discrimination is by the composite (source_product, source_type) pair, resolved via
|
|
# SIGNAL_VARIANT_LOOKUP below — a single-field pydantic discriminator can't express it
|
|
# because llm_analytics/error_tracking map several (product, type) pairs to one variant and
|
|
# github/linear/pganalyze share source_type="issue".
|
|
|
|
SignalInput = Annotated[
|
|
SessionProblemSignalInput
|
|
| LlmEvaluationSignalInput
|
|
| LlmEvaluationReportSignalInput
|
|
| ZendeskTicketSignalInput
|
|
| GithubIssueSignalInput
|
|
| LinearIssueSignalInput
|
|
| JiraIssueSignalInput
|
|
| ConversationsTicketSignalInput
|
|
| ErrorTrackingSignalInput
|
|
| EndpointExecutionFailedSignalInput
|
|
| EndpointBreakdownLimitExceededSignalInput
|
|
| PgAnalyzeIssueSignalInput
|
|
| SignalsScoutSignalInput
|
|
| LogsAlertStateChangeSignalInput
|
|
| AnalyticsAnomalyInvestigationSignalInput
|
|
| HealthCheckSignalInput
|
|
| ReplayVisionScannerFindingSignalInput
|
|
| FreshdeskTicketSignalInput
|
|
| FreshserviceTicketSignalInput
|
|
| FrontConversationSignalInput
|
|
| GorgiasTicketSignalInput
|
|
| KustomerConversationSignalInput
|
|
| DixaConversationSignalInput
|
|
| PlainThreadSignalInput
|
|
| GitlabIssueSignalInput
|
|
| GiteaIssueSignalInput
|
|
| ShortcutStorySignalInput
|
|
| SentryIssueSignalInput
|
|
| RollbarItemSignalInput
|
|
| BugsnagErrorSignalInput
|
|
| HoneybadgerFaultSignalInput
|
|
| RaygunErrorGroupSignalInput
|
|
| SnykScannerFindingSignalInput
|
|
| SonarqubeScannerFindingSignalInput
|
|
| SemgrepScannerFindingSignalInput
|
|
| Rapid7InsightvmScannerFindingSignalInput
|
|
| FeaturebaseFeedbackSignalInput
|
|
| FrillFeedbackSignalInput
|
|
| AhaFeedbackSignalInput
|
|
| UservoiceFeedbackSignalInput
|
|
| ProductboardFeedbackSignalInput
|
|
| CannyFeedbackSignalInput
|
|
| AsknicelyFeedbackSignalInput
|
|
| RetentlyFeedbackSignalInput
|
|
| AppfiguresReviewSignalInput
|
|
| AppfollowReviewSignalInput
|
|
| JudgemeReviewsReviewSignalInput
|
|
| IntercomTicketSignalInput
|
|
| HubspotTicketSignalInput
|
|
| EngineeringAnalyticsCIFlakyCheckSignalInput
|
|
| EngineeringAnalyticsCIBrokenDefaultBranchSignalInput
|
|
| EngineeringAnalyticsCIDurationRegressionSignalInput
|
|
| GoogleSearchConsoleSearchOpportunitySignalInput,
|
|
Field(union_mode="left_to_right"),
|
|
]
|
|
|
|
SIGNAL_INPUT_VARIANTS: tuple[type[SignalInputBase], ...] = (
|
|
SessionProblemSignalInput,
|
|
LlmEvaluationSignalInput,
|
|
LlmEvaluationReportSignalInput,
|
|
ZendeskTicketSignalInput,
|
|
GithubIssueSignalInput,
|
|
LinearIssueSignalInput,
|
|
JiraIssueSignalInput,
|
|
ConversationsTicketSignalInput,
|
|
ErrorTrackingSignalInput,
|
|
EndpointExecutionFailedSignalInput,
|
|
EndpointBreakdownLimitExceededSignalInput,
|
|
PgAnalyzeIssueSignalInput,
|
|
SignalsScoutSignalInput,
|
|
LogsAlertStateChangeSignalInput,
|
|
AnalyticsAnomalyInvestigationSignalInput,
|
|
HealthCheckSignalInput,
|
|
ReplayVisionScannerFindingSignalInput,
|
|
FreshdeskTicketSignalInput,
|
|
FreshserviceTicketSignalInput,
|
|
FrontConversationSignalInput,
|
|
GorgiasTicketSignalInput,
|
|
KustomerConversationSignalInput,
|
|
DixaConversationSignalInput,
|
|
PlainThreadSignalInput,
|
|
GitlabIssueSignalInput,
|
|
GiteaIssueSignalInput,
|
|
ShortcutStorySignalInput,
|
|
SentryIssueSignalInput,
|
|
RollbarItemSignalInput,
|
|
BugsnagErrorSignalInput,
|
|
HoneybadgerFaultSignalInput,
|
|
RaygunErrorGroupSignalInput,
|
|
SnykScannerFindingSignalInput,
|
|
SonarqubeScannerFindingSignalInput,
|
|
SemgrepScannerFindingSignalInput,
|
|
Rapid7InsightvmScannerFindingSignalInput,
|
|
FeaturebaseFeedbackSignalInput,
|
|
FrillFeedbackSignalInput,
|
|
AhaFeedbackSignalInput,
|
|
UservoiceFeedbackSignalInput,
|
|
ProductboardFeedbackSignalInput,
|
|
CannyFeedbackSignalInput,
|
|
AsknicelyFeedbackSignalInput,
|
|
RetentlyFeedbackSignalInput,
|
|
AppfiguresReviewSignalInput,
|
|
AppfollowReviewSignalInput,
|
|
JudgemeReviewsReviewSignalInput,
|
|
IntercomTicketSignalInput,
|
|
HubspotTicketSignalInput,
|
|
EngineeringAnalyticsCIFlakyCheckSignalInput,
|
|
EngineeringAnalyticsCIBrokenDefaultBranchSignalInput,
|
|
EngineeringAnalyticsCIDurationRegressionSignalInput,
|
|
GoogleSearchConsoleSearchOpportunitySignalInput,
|
|
)
|
|
|
|
|
|
def _literal_values(field: FieldInfo) -> tuple[str, ...]:
|
|
"""The accepted string values for a variant's `source_product` / `source_type` Literal field."""
|
|
args = get_args(field.annotation)
|
|
return tuple(arg.value if isinstance(arg, enum.Enum) else arg for arg in args)
|
|
|
|
|
|
# (source_product, source_type) -> variant model. The pair is the discriminator: several products
|
|
# share a source_type ("issue", "ticket") and error_tracking maps three types to one variant, so a
|
|
# single-field discriminator can't express it. Built once at import.
|
|
def _build_variant_lookup() -> dict[tuple[str, str], type[SignalInputBase]]:
|
|
lookup: dict[tuple[str, str], type[SignalInputBase]] = {}
|
|
for variant in SIGNAL_INPUT_VARIANTS:
|
|
for product in _literal_values(variant.model_fields["source_product"]):
|
|
for source_type in _literal_values(variant.model_fields["source_type"]):
|
|
lookup[(product, source_type)] = variant
|
|
return lookup
|
|
|
|
|
|
SIGNAL_VARIANT_LOOKUP: dict[tuple[str, str], type[SignalInputBase]] = _build_variant_lookup()
|