slice 6 fix (ornith-35): form4_tx double-.xml URL + correct Form 4 XML elements (rptOwnerCik/rptOwnerName/rptOwnerRelationship). qwopus re-review pending

This commit is contained in:
Investor Flow Build
2026-06-30 13:56:29 -04:00
parent 4ddf95e711
commit 97607e0bd4
+28 -7
View File
@@ -421,7 +421,7 @@ export class EdgarAdapter implements SourceFetch {
}
// --- Step 2: fetch the XML document ------------------------------------
const xmlUrl = `https://www.sec.gov/Archives/edgar/data/${padded}/${accessionNoDashes}/${xmlName}.xml`;
const xmlUrl = `https://www.sec.gov/Archives/edgar/data/${padded}/${accessionNoDashes}/${xmlName}`;
const xmlText = await this.edgarXmlFetch(xmlUrl);
// --- Step 3: parse transactions ----------------------------------------
@@ -626,13 +626,34 @@ function parseForm4Transactions(text: string): Array<{
if (!infotables) return transactions;
for (const table of infotables) {
// Extract reporter name.
const reporterMatch = table.match(/<reporterCik>\s*(\d+[^<]*)/);
const reporter = reporterMatch ? reporterMatch[1].trim() : '';
// Extract reporter CIK and name from <rptOwner>.
const rptOwnerMatch = table.match(/<rptOwner>([\s\S]*?)<\/rptOwner>/i);
let reporter = '';
if (rptOwnerMatch) {
const ownerBlock = rptOwnerMatch[1];
const cikMatch = ownerBlock.match(/<rptOwnerCik>\s*(\d+)/);
const nameMatch = ownerBlock.match(/<rptOwnerName>\s*([\s\S]*?)<\/rptOwnerName>/i);
reporter = cikMatch ? cikMatch[1].trim() : '';
if (nameMatch) {
reporter = nameMatch[1].replace(/<[^>]+>/g, '').trim();
}
}
// Extract relationship (direct/indirect).
const relMatch = table.match(/<directOrIndirectOwner>\s*([ADI])/i);
const relationship = relMatch ? (relMatch[1] === 'D' ? 'Direct' : 'Indirect') : '';
// Extract relationship from <rptOwnerRelationship>.
const relBlockMatch = table.match(/<rptOwnerRelationship>([\s\S]*?)<\/rptOwnerRelationship>/i);
let relationship = '';
if (relBlockMatch) {
const relBlock = relBlockMatch[1];
const parts: string[] = [];
if (/<isDirector>\s*1/i.test(relBlock)) parts.push('Director');
if (/<isOfficer>\s*1/i.test(relBlock)) {
const titleMatch = relBlock.match(/<officerTitle>\s*([\s\S]*?)<\/officerTitle>/i);
parts.push(titleMatch ? titleMatch[1].replace(/<[^>]+>/g, '').trim() : 'Officer');
}
if (/<isTenPercentOwner>\s*1/i.test(relBlock)) parts.push('10% Owner');
if (/<isOther>\s*1/i.test(relBlock)) parts.push('Other');
relationship = parts.join(', ');
}
// Extract security title.
const titleMatch = table.match(/<securityTitle>\s*([\s\S]*?)<\/securityTitle>/i);