Jaspers Screener: Screens US SEC registrants by fundamentals, holders and insiders, then reads filings against written criteria with cited quotes.
| 1 | import type { ReactElement } from 'react' |
| 2 | import { 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. */ |
| 10 | export interface Evidenced { |
| 11 | company: CompanyResult |
| 12 | citations: Record<string, Citation> |
| 13 | } |
| 14 | |
| 15 | interface 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 | |
| 25 | export 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 | |
| 71 | function 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 | } |