phase-10.5: screener enhancements
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { EdgarPoller } from '../server/domains/news/pollers/EdgarPoller.js';
|
||||
import { PrWirePoller } from '../server/domains/news/pollers/PrWirePoller.js';
|
||||
import { RssParser } from '../server/domains/news/rss.js';
|
||||
import { noopLogger } from '../server/domains/shared/utils/logger.js';
|
||||
|
||||
const EDGAR_ATOM = `<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Latest Filings</title>
|
||||
<entry>
|
||||
<title>8-K - APPLE INC (0000320193) (Filer)</title>
|
||||
<link rel="alternate" type="text/html" href="https://www.sec.gov/Archives/edgar/data/320193/000032019326000001-index.htm"/>
|
||||
<updated>2026-06-09T13:01:02-04:00</updated>
|
||||
<id>urn:tag:sec.gov,2008:accession-number=0000320193-26-000001</id>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>8-K - UNKNOWN CO (0009999999) (Filer)</title>
|
||||
<link rel="alternate" type="text/html" href="https://www.sec.gov/Archives/edgar/data/9999999/x-index.htm"/>
|
||||
<updated>2026-06-09T13:05:00-04:00</updated>
|
||||
<id>urn:tag:sec.gov,2008:accession-number=x</id>
|
||||
</entry>
|
||||
</feed>`;
|
||||
|
||||
const PRWIRE_RSS = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0"><channel>
|
||||
<item>
|
||||
<title>Acme Corp (NYSE: ACME) Announces Record Q2 Results</title>
|
||||
<link>https://www.example.com/acme-q2</link>
|
||||
<pubDate>Tue, 09 Jun 2026 12:00:00 GMT</pubDate>
|
||||
<description><![CDATA[Acme Corp (NYSE: ACME) and partner Beta Inc (Nasdaq: BETA) today announced...]]></description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Local bakery wins award</title>
|
||||
<link>https://www.example.com/bakery</link>
|
||||
<pubDate>Tue, 09 Jun 2026 11:00:00 GMT</pubDate>
|
||||
<description>No public companies here.</description>
|
||||
</item>
|
||||
</channel></rss>`;
|
||||
|
||||
test('news pollers', async (t) => {
|
||||
await t.test('EdgarPoller maps CIK to ticker and filters by universe', () => {
|
||||
const poller = new EdgarPoller(noopLogger, 'test-agent');
|
||||
poller.setTickerMap(new Map([['0000320193', 'AAPL']]));
|
||||
|
||||
const stories = poller.parseFeed(EDGAR_ATOM, '8-K', 'regulatory', new Set(['AAPL']));
|
||||
assert.equal(stories.length, 1); // unknown CIK dropped
|
||||
assert.deepEqual(stories[0].tickers, ['AAPL']);
|
||||
assert.equal(stories[0].source, 'edgar');
|
||||
assert.equal(stories[0].catalystHint, 'regulatory');
|
||||
assert.ok(stories[0].headline.startsWith('8-K filing:'));
|
||||
assert.ok(stories[0].headline.includes('APPLE INC'));
|
||||
assert.ok(stories[0].url.includes('sec.gov'));
|
||||
});
|
||||
|
||||
await t.test('EdgarPoller drops universe misses', () => {
|
||||
const poller = new EdgarPoller(noopLogger, 'test-agent');
|
||||
poller.setTickerMap(new Map([['0000320193', 'AAPL']]));
|
||||
const stories = poller.parseFeed(EDGAR_ATOM, '8-K', 'regulatory', new Set(['MSFT']));
|
||||
assert.equal(stories.length, 0);
|
||||
});
|
||||
|
||||
await t.test('PrWirePoller extracts exchange-tagged tickers', () => {
|
||||
const stories = PrWirePoller.parseFeed(PRWIRE_RSS);
|
||||
assert.equal(stories.length, 1); // bakery story has no tickers → skipped
|
||||
assert.deepEqual(stories[0].tickers.sort(), ['ACME', 'BETA']);
|
||||
assert.equal(stories[0].source, 'prwire');
|
||||
assert.ok(stories[0].publishedAt.startsWith('2026-06-09'));
|
||||
});
|
||||
|
||||
await t.test('extractTickers handles exchange tag variants', () => {
|
||||
assert.deepEqual(PrWirePoller.extractTickers('(NYSE: ABC)'), ['ABC']);
|
||||
assert.deepEqual(PrWirePoller.extractTickers('(Nasdaq: xyz)'), ['XYZ']);
|
||||
assert.deepEqual(PrWirePoller.extractTickers('(NYSE American: BRK.B)'), ['BRK.B']);
|
||||
assert.deepEqual(PrWirePoller.extractTickers('(OTCQB: TINY)'), ['TINY']);
|
||||
assert.deepEqual(PrWirePoller.extractTickers('no tags here'), []);
|
||||
});
|
||||
|
||||
await t.test('RssParser decodes entities and strips CDATA', () => {
|
||||
const block = '<item><title>A & B say "hi"</title></item>';
|
||||
assert.equal(RssParser.tag(block, 'title'), 'A & B say "hi"');
|
||||
const cdata = '<item><description><![CDATA[Text <b>bold</b> here]]></description></item>';
|
||||
assert.equal(RssParser.tag(cdata, 'description'), 'Text bold here');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user