Jaspers Hub

jaspers / screener-mcp

pluginOfficialReviewed★ 0↓ 33

Jaspers Screener: Screens US SEC registrants by fundamentals, holders and insiders, then reads filings against written criteria with cited quotes.

3.6 KB
1import type { ReactElement } from 'react'
2import { gatheredText, verificationMark, type Citation, type CompanyResult, type Criterion } from './screener'
3
4// One company's answers and what they rest on: per criterion the verdict, the rationale, what the
5// verifier made of it, and the quotes as citations. The server has already checked every quote
6// against the paragraph it cites, so a card shows it as it came, with the link the server built:
7// the filing on sec.gov, scrolled to the quote.
8
9/** What one evidence call brought back for a company. */
10export interface Evidenced {
11 company: CompanyResult
12 citations: Record<string, Citation>
13}
14
15interface Props {
16 ticker: string
17 evidence: Evidenced | null
18 error: string | null
19 questions: Criterion[]
20 onClose: () => void
21 /** Opens an https link in the browser; the view's own frame cannot. */
22 onLink: (url: string) => void
23}
24
25export function EvidencePane({ ticker, evidence, error, questions, onClose, onLink }: Props): ReactElement {
26 const company = evidence?.company
27 return (
28 <div className="sc-evidence">
29 <div className="sc-evidence-head">
30 <span className="sc-ticker">{ticker}</span>
31 {company?.name && <span className="sc-status">{company.name}</span>}
32 {company?.verdict && <span className={`sc-v-${company.verdict}`}>{company.verdict}</span>}
33 <button type="button" className="sc-btn" style={{ marginLeft: 'auto' }} onClick={onClose} aria-label="Close the evidence">
34 ×
35 </button>
36 </div>
37 <div className="sc-evidence-body">
38 {error && <p className="sc-error">{error}</p>}
39 {!error && !company && <p className="sc-status">Reading the evidence…</p>}
40 {company?.error && <p className="sc-error">{company.error}</p>}
41 {company && company.status !== 'done' && !company.error && <p className="sc-status">This company has not been read yet.</p>}
42 {company?.criteria.map((answer) => {
43 const mark = verificationMark(answer.verification)
44 return (
45 <section key={answer.id} className="sc-criterion">
46 <p className="sc-question">{questions.find((q) => q.id === answer.id)?.question ?? answer.id}</p>
47 <p>
48 <span className={`sc-v-${answer.verdict}`}>{answer.verdict}</span>
49 <span className="sc-status">
50 {' '}
51 · {answer.verification || 'not verified'}
52 {answer.confidence !== null && ` · confidence ${answer.confidence.toFixed(2)}`}
53 </span>
54 </p>
55 {answer.rationale && <p>{answer.rationale}</p>}
56 {mark && <p className="sc-error">{mark}</p>}
57 {answer.note && <p className="sc-status">{answer.note}</p>}
58 {answer.evidence.map((id) => {
59 const citation = evidence?.citations[id]
60 return citation ? <Card key={id} citation={citation} onLink={onLink} /> : null
61 })}
62 {answer.gathered && <p className="sc-hint">{gatheredText(answer.gathered)}</p>}
63 </section>
64 )
65 })}
66 </div>
67 </div>
68 )
69}
70
71function Card({ citation, onLink }: { citation: Citation; onLink: (url: string) => void }): ReactElement {
72 const { url } = citation
73 return (
74 <blockquote className="sc-cite">
75 <p className="sc-quote">“{citation.quote}”</p>
76 <p className="sc-cite-foot">
77 <span>{citation.title}</span>
78 {url && (
79 <button type="button" className="sc-link" title="Opens the filing on sec.gov with the quote highlighted." onClick={() => onLink(url)}>
80 Open the filing
81 </button>
82 )}
83 </p>
84 </blockquote>
85 )
86}