Animate mugshot party identification

This commit is contained in:
2026-08-22 19:49:44 +02:00
parent 10dcd567c6
commit 52f0a31f44
6 changed files with 114 additions and 10 deletions
+18 -3
View File
@@ -1,6 +1,6 @@
import type { ComponentType } from 'react'
import { BadgeCheck, BookOpen, Building2, CalendarClock, FileText, Image as ImageIcon, Pencil, UserRound } from 'lucide-react'
import type { CaseDocument, DocumentCaptureKind, DocumentExhibit, Evidence, Exhibit, ExhibitRelation, ExhibitType, SourceFileType, TemporalFact } from './types'
import type { CaseDocument, Connection, DocumentCaptureKind, DocumentExhibit, Evidence, Exhibit, ExhibitRelation, ExhibitType, PartyExhibit, SourceFileType, TemporalFact } from './types'
export type WidgetCommand =
| { type: 'open-document'; documentId: string }
@@ -142,20 +142,35 @@ export type DocumentCaptureDefinition = {
}
export const documentCaptureRegistry:Record<DocumentCaptureKind,DocumentCaptureDefinition> = {
unclassified:{ label:'Not sure',description:'Keep the standard evidence card for now.',defaultSize:{width:174,height:145} },
photo:{ label:'Mugshot',description:'A portrait or identifying photograph.',defaultSize:{width:188,height:240} },
photo:{ label:'Mugshot',description:'A portrait or identifying photograph.',defaultSize:{width:188,height:250} },
scene:{ label:'Image',description:'A place, situation, object, or event is shown.',defaultSize:{width:244,height:200} },
clipping:{ label:'Clip',description:'An extract captured from a larger source.',defaultSize:{width:210,height:194} },
full_page:{ label:'Document',description:'A complete page or formal document view.',defaultSize:{width:205,height:294} },
}
export function documentCapture(kind:DocumentCaptureKind) { return documentCaptureRegistry[kind] || documentCaptureRegistry.unclassified }
export type MugshotIdentification = { party:PartyExhibit;connection:Connection }
/** A Mugshot caption is a projection of its latest Party connection, never copied document metadata. */
export function mugshotIdentification(document:DocumentExhibit,exhibits:Exhibit[],connections:Connection[]):MugshotIdentification | null {
if (document.captureKind !== 'photo') return null
const byId=new Map(exhibits.map(exhibit => [exhibit.id,exhibit]))
for (let index=connections.length - 1;index >= 0;index--) {
const connection=connections[index]
const otherId=connection.fromExhibitId === document.id ? connection.toExhibitId : connection.toExhibitId === document.id ? connection.fromExhibitId : null
const other=otherId ? byId.get(otherId) : null
if (other?.type === 'party') return { party:other,connection }
}
return null
}
/** Context-sensitive starter copy. A Party is never treated as proof by default. */
export function defaultConnectionLabel(first:Exhibit,second:Exhibit) {
const types = new Set([first.type,second.type])
if (types.has('party') && types.has('claim')) return 'Subject of claim'
const document = first.type === 'document' ? first : second.type === 'document' ? second : null
if (document && types.has('party')) {
if (document.captureKind === 'photo') return 'Depicts…'
if (document.captureKind === 'photo') return 'Identified as…'
if (document.captureKind === 'scene') return 'Shows…'
return 'Concerns…'
}