Files
gupi-osint-board/src/types.ts
T

295 lines
8.2 KiB
TypeScript
Raw Normal View History

2026-08-22 17:02:30 +02:00
export type ExhibitType = 'folder' | 'document' | 'note' | 'event' | 'party' | 'claim'
export type PartyKind = 'person' | 'organization'
export type OrganizationKind = 'business' | 'public_body' | 'association' | 'informal_group' | 'other'
2026-08-14 12:43:11 +02:00
export type SourceFileType = 'image' | 'pdf' | 'web_capture' | 'email' | 'article' | 'filing' | 'price_list' | 'text' | 'file'
2026-08-22 19:28:06 +02:00
export type DocumentCaptureKind = 'unclassified' | 'photo' | 'scene' | 'clipping' | 'full_page'
2026-08-14 12:43:11 +02:00
2026-08-17 09:24:53 +02:00
export interface CanvasPlacement {
x: number
y: number
width: number
height: number
rotation: number
zIndex: number
hidden: boolean
}
export interface ExhibitBase extends CanvasPlacement {
id: string
type: ExhibitType
title: string
}
2026-08-14 12:43:11 +02:00
export interface DocumentRegion {
id: string
label: string
excerpt: string
date?: string
}
2026-08-17 09:24:53 +02:00
export interface FolderExhibit extends ExhibitBase {
type: 'folder'
content: string
isOpen: boolean
}
export interface DocumentExhibit extends ExhibitBase {
type: 'document'
2026-08-22 17:02:30 +02:00
/** Stable, board-local number used when citing this exhibit. */
displayNumber?: number
/** Author-mode reveal requirements. Omitted from play-mode payloads. */
requiredFlags?: string[]
2026-08-14 12:43:11 +02:00
publishedAt?: string
2026-08-17 09:24:53 +02:00
capturedAt?: string
sourceUri?: string
2026-08-22 17:02:30 +02:00
sourceCitation?: string
2026-08-14 12:43:11 +02:00
body: string[]
regions: DocumentRegion[]
assetId?: string
fileName?: string
mimeType?: string
fileSize?: number
fileType: SourceFileType
2026-08-22 19:28:06 +02:00
/** Evidentiary form on the board; independent of MIME/file type. */
captureKind: DocumentCaptureKind
2026-08-14 12:43:11 +02:00
metadata: Record<string, string>
}
export interface DocumentUploadAnalysis {
extractionStatus: 'succeeded' | 'unsupported' | 'failed'
matchedFlags: string[]
awardedFlags: string[]
2026-08-22 16:02:10 +02:00
goals: LevelGoal[]
}
export interface UploadedCaseDocument extends DocumentExhibit {
/** Transient upload response data; it is not part of persisted exhibit state. */
analysis: DocumentUploadAnalysis
}
2026-08-17 09:24:53 +02:00
export interface NoteExhibit extends ExhibitBase {
type: 'note'
2026-08-14 12:43:11 +02:00
content: string
}
2026-08-17 09:24:53 +02:00
export interface EventExhibit extends ExhibitBase {
type: 'event'
content: string
eventDate?: string
2026-08-14 12:43:11 +02:00
}
2026-08-17 09:24:53 +02:00
export interface PartyExhibit extends ExhibitBase {
type: 'party'
content: string
partyKind: PartyKind
organizationKind?: OrganizationKind
aliases: string[]
}
2026-08-22 17:02:30 +02:00
export interface ClaimExhibit extends ExhibitBase {
type: 'claim'
statement: string
}
export type Exhibit = FolderExhibit | DocumentExhibit | NoteExhibit | EventExhibit | PartyExhibit | ClaimExhibit
2026-08-17 09:24:53 +02:00
export type Evidence = Exclude<Exhibit, DocumentExhibit>
export type CaseDocument = DocumentExhibit
export type EvidenceType = Evidence['type']
interface ExhibitRelationBase {
id: string
fromExhibitId: string
toExhibitId: string
sortOrder: number
note?: string
}
export interface FolderMembership extends ExhibitRelationBase { type: 'contains' }
export interface EventSupportRelation extends ExhibitRelationBase { type: 'supports' }
export interface PartyAssociationRelation extends ExhibitRelationBase { type: 'concerns' }
export interface ProvenanceRelation extends ExhibitRelationBase { type: 'source'; sourceRegionId?: string }
export type ExhibitRelation = FolderMembership | EventSupportRelation | PartyAssociationRelation | ProvenanceRelation
2026-08-14 12:43:11 +02:00
export interface Connection {
id: string
2026-08-17 09:24:53 +02:00
fromExhibitId: string
toExhibitId: string
2026-08-14 16:37:50 +02:00
label?: string
/** Percentage from slack (0) to taut (100). */
tightness?: number
tagStyle?: 'luggage' | 'compact'
2026-08-14 18:26:20 +02:00
/** Percentage along the thread from its source exhibit. */
tagPosition?: number
/** Signed perpendicular distance from the thread, constrained by tightness. */
tagOffset?: number
2026-08-14 12:43:11 +02:00
}
export interface Viewport { x: number; y: number; zoom: number }
2026-08-14 15:40:54 +02:00
export interface TimelineRange { start: string; end: string }
2026-08-14 12:43:11 +02:00
2026-08-17 09:24:53 +02:00
export type BoardViewPlacement =
| { mode: 'docked'; dockEdge: 'top' | 'right' | 'bottom' | 'left'; size: number }
| { mode: 'canvas' | 'window'; x: number; y: number; width: number; height: number }
export interface TimelineView {
id: string
type: 'timeline'
placement: BoardViewPlacement
visible: boolean
zIndex: number
rangeMode: 'auto' | 'fixed'
range?: TimelineRange
}
export type BoardView = TimelineView
export interface TemporalFact {
id: string
exhibitId: string
kind: 'published' | 'captured' | 'occurred' | 'region_date'
start: string
end?: string
label: string
}
export interface BriefConcept {
id: string
label: string
context: string
expectedPartyKind?: PartyKind
resolvedPartyExhibitId?: string
}
export interface LevelBrief { body: string; concepts: BriefConcept[] }
2026-08-22 16:02:10 +02:00
export interface LevelGoal {
/** Present in author mode so the goal can be edited; omitted in play mode. */
id?: string
key: string
title: string
instructions: string
completionMessage: string
enabled?: boolean
/** Author-only success condition. Expected answer flags stay out of play payloads. */
requiredFlags?: string[]
status: 'pending' | 'complete'
completedAt?: string
/** True only in the response to the mutation that completed this goal. */
newlyCompleted: boolean
}
2026-08-22 17:02:30 +02:00
export type CaseReportSubmissionStatus = 'evidence_insufficient' | 'evidence_accepted_report_incomplete' | 'accepted'
export interface CaseReportEvidence {
connectionId: string
documentExhibitId: string
displayNumber: number
documentTitle: string
fileType: SourceFileType
relationText: string
publishedAt?: string
sourceCitation?: string
sourceUri?: string
evidenceAccepted: boolean
}
export interface CaseReportClaim {
claimExhibitId: string
statement: string
evidence: CaseReportEvidence[]
}
export interface CaseReport {
title: string
investigatorName: string
requiredForCompletion: boolean
status: 'draft' | CaseReportSubmissionStatus
feedback?: string
issues: string[]
claims: CaseReportClaim[]
}
export interface CaseReportSubmissionInput {
investigatorName: string
evidence: Array<Pick<CaseReportEvidence, 'connectionId' | 'documentExhibitId' | 'relationText' | 'publishedAt' | 'sourceCitation' | 'sourceUri'>>
}
2026-08-14 12:43:11 +02:00
export interface CaseState {
id: string
title: string
subtitle: string
2026-08-17 09:24:53 +02:00
exhibits: Exhibit[]
relations: ExhibitRelation[]
2026-08-14 12:43:11 +02:00
connections: Connection[]
2026-08-17 09:24:53 +02:00
views: BoardView[]
2026-08-14 12:43:11 +02:00
viewport: Viewport
brief: LevelBrief
2026-08-22 16:02:10 +02:00
goals: LevelGoal[]
2026-08-22 17:02:30 +02:00
report?: CaseReport
2026-08-17 09:24:53 +02:00
revision: number
2026-08-14 12:43:11 +02:00
updatedAt?: string
levelStatus?: string
sourceTemplateVersionId?: string
2026-08-14 12:43:11 +02:00
editingAllowed?: boolean
/** Visible documents that have not previously played their arrival flourish. */
newlyVisibleDocumentIds?: string[]
}
export interface LevelFlag {
key: string
earnedAt?: string
gatedDocumentCount: number
}
export interface EvidenceMatchAnchorDefinition {
id: string
phrase: string
minimumSimilarity: number
sortOrder: number
}
export interface EvidenceMatchRuleDefinition {
id: string
name: string
2026-08-22 16:02:10 +02:00
sourceLabel?: string
sourceUri?: string
flagKey: string
matcherVersion: 'char_trigram_v1'
minimumAnchorMatches: number
enabled: boolean
anchors: EvidenceMatchAnchorDefinition[]
2026-08-14 12:43:11 +02:00
}
2026-08-17 09:24:53 +02:00
2026-08-22 16:02:10 +02:00
export interface EvidenceSemanticRuleDefinition {
id: string
goalId: string
goalKey: string
name: string
targetSubject: string
relatedSubject?: string
assertion: string
successFlagKey: string
relatedFlagKey?: string
minimumConfidence: number
evaluatorVersion: string
enabled: boolean
}
export interface DocumentSemanticAnalysis {
status: 'not_needed' | 'unavailable' | 'pending' | 'succeeded' | 'failed'
subject?: 'target' | 'related' | 'ambiguous' | 'neither'
supportsClaim?: boolean
evidenceExcerpt?: string
confidence?: number
retryable: boolean
awardedFlags: string[]
goals: LevelGoal[]
}
2026-08-17 09:24:53 +02:00
export function isDocumentExhibit(exhibit: Exhibit): exhibit is DocumentExhibit { return exhibit.type === 'document' }
export function isEvidenceExhibit(exhibit: Exhibit): exhibit is Evidence { return exhibit.type !== 'document' }
export function isFolderExhibit(exhibit: Exhibit): exhibit is FolderExhibit { return exhibit.type === 'folder' }
export function isEventExhibit(exhibit: Exhibit): exhibit is EventExhibit { return exhibit.type === 'event' }
export function isPartyExhibit(exhibit: Exhibit): exhibit is PartyExhibit { return exhibit.type === 'party' }
2026-08-22 17:02:30 +02:00
export function isClaimExhibit(exhibit: Exhibit): exhibit is ClaimExhibit { return exhibit.type === 'claim' }