feat(ownership): complete ownership intelligence module

Sprints 0-8: Types, schema, KSEI parser, entity resolution, DB CRUD,
Bing API client, import pipeline, query commands, graph traversal,
changes diff, entity resolution CLI, FTS5 search.

- KSEI PDF parser (mutool stext + quick-xml, 99.2% accuracy)
- 7 query commands: ticker, entity, search, cross-holders, concentration, flow, releases
- Ownership graph with recursive CTE (ASCII tree + Graphviz DOT)
- Release diff/changes between KSEI snapshots
- Entity resolution CLI: unresolved, map, merge
- FTS5 trigram search on entity names
- Feature-gated under 'ownership' (default-on)
- 82 tests passing
This commit is contained in:
Ciphercat 2026-03-07 17:31:04 +00:00
commit 6671e22976
23 changed files with 5351 additions and 47 deletions

47
tests/fixtures/bing_buyers_bbca.json vendored Normal file
View file

@ -0,0 +1,47 @@
[
{
"investorName": "Norges Bank Investment Management",
"investorType": "Government / Sovereign Wealth",
"sharesHeld": 278330000.0,
"sharesChanged": 3200000.0,
"percentageOfSharesOutstanding": 1.13,
"value": 275196270000.0,
"reportDate": "2024-12-31"
},
{
"investorName": "Dimensional Fund Advisors LP",
"investorType": "Institutional",
"sharesHeld": 156920000.0,
"sharesChanged": 14750000.0,
"percentageOfSharesOutstanding": 0.64,
"value": 155110680000.0,
"reportDate": "2024-12-31"
},
{
"investorName": "Baillie Gifford & Co",
"investorType": "Institutional",
"sharesHeld": 132450000.0,
"sharesChanged": 11200000.0,
"percentageOfSharesOutstanding": 0.54,
"value": 130965450000.0,
"reportDate": "2024-12-31"
},
{
"investorName": "BlackRock Inc",
"investorType": "Institutional",
"sharesHeld": 498112750.0,
"sharesChanged": 5600000.0,
"percentageOfSharesOutstanding": 2.02,
"value": 492523521250.0,
"reportDate": "2024-12-31"
},
{
"investorName": "Fidelity Management & Research",
"investorType": "Institutional",
"sharesHeld": 89340000.0,
"sharesChanged": 4100000.0,
"percentageOfSharesOutstanding": 0.36,
"value": 88346060000.0,
"reportDate": "2024-12-31"
}
]

56
tests/fixtures/bing_holders_bbca.json vendored Normal file
View file

@ -0,0 +1,56 @@
[
{
"investorName": "PT Dwimuria Investama Andalan",
"investorType": "Corporate",
"sharesHeld": 13533682440.0,
"sharesChanged": 0.0,
"percentageOfSharesOutstanding": 54.94,
"value": 13380524934600.0,
"reportDate": "2024-12-31"
},
{
"investorName": "Vanguard Group Inc",
"investorType": "Institutional",
"sharesHeld": 621438200.0,
"sharesChanged": -12000000.0,
"percentageOfSharesOutstanding": 2.52,
"value": 614321778000.0,
"reportDate": "2024-12-31"
},
{
"investorName": "BlackRock Inc",
"investorType": "Institutional",
"sharesHeld": 498112750.0,
"sharesChanged": 5600000.0,
"percentageOfSharesOutstanding": 2.02,
"value": 492523521250.0,
"reportDate": "2024-12-31"
},
{
"investorName": "Government Pension Investment Fund (Japan)",
"investorType": "Government / Sovereign Wealth",
"sharesHeld": 312045000.0,
"sharesChanged": 0.0,
"percentageOfSharesOutstanding": 1.27,
"value": 308564505000.0,
"reportDate": "2024-12-31"
},
{
"investorName": "Norges Bank Investment Management",
"investorType": "Government / Sovereign Wealth",
"sharesHeld": 278330000.0,
"sharesChanged": 3200000.0,
"percentageOfSharesOutstanding": 1.13,
"value": 275196270000.0,
"reportDate": "2024-12-31"
},
{
"investorName": "Capital Research and Management",
"investorType": "Institutional",
"sharesHeld": 248760000.0,
"sharesChanged": -8000000.0,
"percentageOfSharesOutstanding": 1.01,
"value": 245971800000.0,
"reportDate": "2024-12-31"
}
]

328
tests/fixtures/ksei_stext_sample.xml vendored Normal file
View file

@ -0,0 +1,328 @@
<document>
<page id="page1" width="792" height="612">
<char x="40.0" y="40.0" c="P"/>
<char x="41.2" y="40.0" c="E"/>
<char x="42.4" y="40.0" c="N"/>
<char x="43.6" y="40.0" c="G"/>
</page>
<page id="page2" width="792" height="612">
<char x="21.7" y="100.0" c="2"/>
<char x="22.8" y="100.0" c="7"/>
<char x="23.9" y="100.0" c="-"/>
<char x="25.0" y="100.0" c="F"/>
<char x="26.1" y="100.0" c="e"/>
<char x="27.2" y="100.0" c="b"/>
<char x="28.3" y="100.0" c="-"/>
<char x="29.4" y="100.0" c="2"/>
<char x="30.5" y="100.0" c="0"/>
<char x="31.6" y="100.0" c="2"/>
<char x="32.7" y="100.0" c="6"/>
<char x="55.3" y="100.0" c="B"/>
<char x="56.4" y="100.0" c="B"/>
<char x="57.5" y="100.0" c="C"/>
<char x="58.6" y="100.0" c="A"/>
<char x="75.0" y="100.0" c="B"/>
<char x="76.1" y="100.0" c="A"/>
<char x="77.2" y="100.0" c="N"/>
<char x="78.3" y="100.0" c="K"/>
<char x="79.4" y="100.0" c=" "/>
<char x="80.8" y="100.0" c="C"/>
<char x="81.9" y="100.0" c="E"/>
<char x="83.0" y="100.0" c="N"/>
<char x="84.1" y="100.0" c="T"/>
<char x="85.2" y="100.0" c="R"/>
<char x="86.3" y="100.0" c="A"/>
<char x="87.4" y="100.0" c="L"/>
<char x="88.5" y="100.0" c=" "/>
<char x="89.9" y="100.0" c="A"/>
<char x="91.0" y="100.0" c="S"/>
<char x="92.1" y="100.0" c="I"/>
<char x="93.2" y="100.0" c="A"/>
<char x="94.3" y="100.0" c=" "/>
<char x="95.7" y="100.0" c="T"/>
<char x="96.8" y="100.0" c="b"/>
<char x="97.9" y="100.0" c="k"/>
<char x="167.9" y="100.0" c="P"/>
<char x="169.0" y="100.0" c="T"/>
<char x="170.1" y="100.0" c=" "/>
<char x="171.5" y="100.0" c="D"/>
<char x="172.6" y="100.0" c="W"/>
<char x="173.7" y="100.0" c="I"/>
<char x="174.8" y="100.0" c="M"/>
<char x="175.9" y="100.0" c="U"/>
<char x="177.0" y="100.0" c="R"/>
<char x="178.1" y="100.0" c="I"/>
<char x="179.2" y="100.0" c="A"/>
<char x="180.3" y="100.0" c=" "/>
<char x="181.7" y="100.0" c="I"/>
<char x="182.8" y="100.0" c="N"/>
<char x="183.9" y="100.0" c="V"/>
<char x="185.0" y="100.0" c="E"/>
<char x="186.1" y="100.0" c="S"/>
<char x="187.2" y="100.0" c="T"/>
<char x="188.3" y="100.0" c="A"/>
<char x="189.4" y="100.0" c="M"/>
<char x="190.5" y="100.0" c="A"/>
<char x="191.6" y="100.0" c=" "/>
<char x="193.0" y="100.0" c="A"/>
<char x="194.1" y="100.0" c="N"/>
<char x="195.2" y="100.0" c="D"/>
<char x="196.3" y="100.0" c="A"/>
<char x="197.4" y="100.0" c="L"/>
<char x="198.5" y="100.0" c="A"/>
<char x="199.6" y="100.0" c="N"/>
<char x="435.0" y="100.0" c="C"/>
<char x="436.1" y="100.0" c="P"/>
<char x="468.0" y="100.0" c="L"/>
<char x="500.0" y="100.0" c="I"/>
<char x="501.1" y="100.0" c="D"/>
<char x="560.0" y="100.0" c="I"/>
<char x="561.1" y="100.0" c="D"/>
<char x="620.0" y="100.0" c="1"/>
<char x="621.1" y="100.0" c="3"/>
<char x="622.2" y="100.0" c="."/>
<char x="623.3" y="100.0" c="5"/>
<char x="624.4" y="100.0" c="3"/>
<char x="625.5" y="100.0" c="3"/>
<char x="626.6" y="100.0" c="."/>
<char x="627.7" y="100.0" c="6"/>
<char x="628.8" y="100.0" c="8"/>
<char x="629.9" y="100.0" c="2"/>
<char x="631.0" y="100.0" c="."/>
<char x="632.1" y="100.0" c="4"/>
<char x="633.2" y="100.0" c="4"/>
<char x="634.3" y="100.0" c="0"/>
<char x="656.0" y="100.0" c="0"/>
<char x="696.0" y="100.0" c="1"/>
<char x="697.1" y="100.0" c="3"/>
<char x="698.2" y="100.0" c="."/>
<char x="699.3" y="100.0" c="5"/>
<char x="700.4" y="100.0" c="3"/>
<char x="701.5" y="100.0" c="3"/>
<char x="702.6" y="100.0" c="."/>
<char x="703.7" y="100.0" c="6"/>
<char x="704.8" y="100.0" c="8"/>
<char x="705.9" y="100.0" c="2"/>
<char x="707.0" y="100.0" c="."/>
<char x="708.1" y="100.0" c="4"/>
<char x="709.2" y="100.0" c="4"/>
<char x="710.3" y="100.0" c="0"/>
<char x="748.0" y="100.0" c="5"/>
<char x="749.1" y="100.0" c="4"/>
<char x="750.2" y="100.0" c=","/>
<char x="751.3" y="100.0" c="9"/>
<char x="752.4" y="100.0" c="4"/>
<char x="21.7" y="101.9" c="2"/>
<char x="22.8" y="101.9" c="7"/>
<char x="23.9" y="101.9" c="-"/>
<char x="25.0" y="101.9" c="F"/>
<char x="26.1" y="101.9" c="e"/>
<char x="27.2" y="101.9" c="b"/>
<char x="28.3" y="101.9" c="-"/>
<char x="29.4" y="101.9" c="2"/>
<char x="30.5" y="101.9" c="0"/>
<char x="31.6" y="101.9" c="2"/>
<char x="32.7" y="101.9" c="6"/>
<char x="55.3" y="101.9" c="B"/>
<char x="56.4" y="101.9" c="B"/>
<char x="57.5" y="101.9" c="C"/>
<char x="58.6" y="101.9" c="A"/>
<char x="75.0" y="101.9" c="B"/>
<char x="76.1" y="101.9" c="A"/>
<char x="77.2" y="101.9" c="N"/>
<char x="78.3" y="101.9" c="K"/>
<char x="79.4" y="101.9" c=" "/>
<char x="80.8" y="101.9" c="C"/>
<char x="81.9" y="101.9" c="E"/>
<char x="83.0" y="101.9" c="N"/>
<char x="84.1" y="101.9" c="T"/>
<char x="85.2" y="101.9" c="R"/>
<char x="86.3" y="101.9" c="A"/>
<char x="87.4" y="101.9" c="L"/>
<char x="88.5" y="101.9" c=" "/>
<char x="89.9" y="101.9" c="A"/>
<char x="91.0" y="101.9" c="S"/>
<char x="92.1" y="101.9" c="I"/>
<char x="93.2" y="101.9" c="A"/>
<char x="94.3" y="101.9" c=" "/>
<char x="95.7" y="101.9" c="T"/>
<char x="96.8" y="101.9" c="b"/>
<char x="97.9" y="101.9" c="k"/>
<char x="167.9" y="101.9" c="P"/>
<char x="169.0" y="101.9" c="T"/>
<char x="170.1" y="101.9" c=" "/>
<char x="171.5" y="101.9" c="G"/>
<char x="172.6" y="101.9" c="I"/>
<char x="173.7" y="101.9" c="T"/>
<char x="174.8" y="101.9" c="A"/>
<char x="175.9" y="101.9" c=" "/>
<char x="177.3" y="101.9" c="S"/>
<char x="178.4" y="101.9" c="U"/>
<char x="179.5" y="101.9" c="R"/>
<char x="180.6" y="101.9" c="Y"/>
<char x="181.7" y="101.9" c="A"/>
<char x="182.8" y="101.9" c=" "/>
<char x="184.2" y="101.9" c="H"/>
<char x="185.3" y="101.9" c="A"/>
<char x="186.4" y="101.9" c="S"/>
<char x="187.5" y="101.9" c="T"/>
<char x="188.6" y="101.9" c="A"/>
<char x="435.0" y="101.9" c="C"/>
<char x="436.1" y="101.9" c="P"/>
<char x="468.0" y="101.9" c="L"/>
<char x="500.0" y="101.9" c="I"/>
<char x="501.1" y="101.9" c="D"/>
<char x="560.0" y="101.9" c="I"/>
<char x="561.1" y="101.9" c="D"/>
<char x="620.0" y="101.9" c="5"/>
<char x="621.1" y="101.9" c="."/>
<char x="622.2" y="101.9" c="2"/>
<char x="623.3" y="101.9" c="0"/>
<char x="624.4" y="101.9" c="0"/>
<char x="625.5" y="101.9" c="."/>
<char x="626.6" y="101.9" c="0"/>
<char x="627.7" y="101.9" c="0"/>
<char x="628.8" y="101.9" c="0"/>
<char x="629.9" y="101.9" c="."/>
<char x="631.0" y="101.9" c="0"/>
<char x="632.1" y="101.9" c="0"/>
<char x="633.2" y="101.9" c="0"/>
<char x="656.0" y="101.9" c="0"/>
<char x="696.0" y="101.9" c="5"/>
<char x="697.1" y="101.9" c="."/>
<char x="698.2" y="101.9" c="2"/>
<char x="699.3" y="101.9" c="0"/>
<char x="700.4" y="101.9" c="0"/>
<char x="701.5" y="101.9" c="."/>
<char x="702.6" y="101.9" c="0"/>
<char x="703.7" y="101.9" c="0"/>
<char x="704.8" y="101.9" c="0"/>
<char x="705.9" y="101.9" c="."/>
<char x="707.0" y="101.9" c="0"/>
<char x="708.1" y="101.9" c="0"/>
<char x="709.2" y="101.9" c="0"/>
<char x="748.0" y="101.9" c="2"/>
<char x="749.1" y="101.9" c="1"/>
<char x="750.2" y="101.9" c=","/>
<char x="751.3" y="101.9" c="1"/>
<char x="752.4" y="101.9" c="1"/>
</page>
<page id="page3" width="792" height="612">
<char x="21.7" y="120.0" c="2"/>
<char x="22.8" y="120.0" c="7"/>
<char x="23.9" y="120.0" c="-"/>
<char x="25.0" y="120.0" c="F"/>
<char x="26.1" y="120.0" c="e"/>
<char x="27.2" y="120.0" c="b"/>
<char x="28.3" y="120.0" c="-"/>
<char x="29.4" y="120.0" c="2"/>
<char x="30.5" y="120.0" c="0"/>
<char x="31.6" y="120.0" c="2"/>
<char x="32.7" y="120.0" c="6"/>
<char x="55.3" y="120.0" c="B"/>
<char x="56.4" y="120.0" c="B"/>
<char x="57.5" y="120.0" c="R"/>
<char x="58.6" y="120.0" c="I"/>
<char x="75.0" y="120.0" c="B"/>
<char x="76.1" y="120.0" c="A"/>
<char x="77.2" y="120.0" c="N"/>
<char x="78.3" y="120.0" c="K"/>
<char x="79.4" y="120.0" c=" "/>
<char x="80.8" y="120.0" c="R"/>
<char x="81.9" y="120.0" c="A"/>
<char x="83.0" y="120.0" c="K"/>
<char x="84.1" y="120.0" c="Y"/>
<char x="85.2" y="120.0" c="A"/>
<char x="86.3" y="120.0" c="T"/>
<char x="87.4" y="120.0" c=" "/>
<char x="88.8" y="120.0" c="I"/>
<char x="89.9" y="120.0" c="N"/>
<char x="91.0" y="120.0" c="D"/>
<char x="92.1" y="120.0" c="O"/>
<char x="93.2" y="120.0" c="N"/>
<char x="94.3" y="120.0" c="E"/>
<char x="95.4" y="120.0" c="S"/>
<char x="96.5" y="120.0" c="I"/>
<char x="97.6" y="120.0" c="A"/>
<char x="98.7" y="120.0" c=" "/>
<char x="100.1" y="120.0" c="("/>
<char x="101.2" y="120.0" c="P"/>
<char x="102.3" y="120.0" c="E"/>
<char x="103.4" y="120.0" c="R"/>
<char x="104.5" y="120.0" c="S"/>
<char x="105.6" y="120.0" c="E"/>
<char x="106.7" y="120.0" c="R"/>
<char x="107.8" y="120.0" c="O"/>
<char x="108.9" y="120.0" c=")"/>
<char x="110.0" y="120.0" c=" "/>
<char x="111.4" y="120.0" c="T"/>
<char x="112.5" y="120.0" c="b"/>
<char x="113.6" y="120.0" c="k"/>
<char x="167.9" y="120.0" c="N"/>
<char x="169.0" y="120.0" c="E"/>
<char x="170.1" y="120.0" c="G"/>
<char x="171.2" y="120.0" c="A"/>
<char x="172.3" y="120.0" c="R"/>
<char x="173.4" y="120.0" c="A"/>
<char x="174.5" y="120.0" c=" "/>
<char x="175.9" y="120.0" c="R"/>
<char x="177.0" y="120.0" c="E"/>
<char x="178.1" y="120.0" c="P"/>
<char x="179.2" y="120.0" c="U"/>
<char x="180.3" y="120.0" c="B"/>
<char x="181.4" y="120.0" c="L"/>
<char x="182.5" y="120.0" c="I"/>
<char x="183.6" y="120.0" c="K"/>
<char x="184.7" y="120.0" c=" "/>
<char x="186.1" y="120.0" c="I"/>
<char x="187.2" y="120.0" c="N"/>
<char x="188.3" y="120.0" c="D"/>
<char x="189.4" y="120.0" c="O"/>
<char x="190.5" y="120.0" c="N"/>
<char x="191.6" y="120.0" c="E"/>
<char x="192.7" y="120.0" c="S"/>
<char x="193.8" y="120.0" c="I"/>
<char x="194.9" y="120.0" c="A"/>
<char x="435.0" y="120.0" c="C"/>
<char x="436.1" y="120.0" c="P"/>
<char x="468.0" y="120.0" c="L"/>
<char x="500.0" y="120.0" c="I"/>
<char x="501.1" y="120.0" c="D"/>
<char x="560.0" y="120.0" c="I"/>
<char x="561.1" y="120.0" c="D"/>
<char x="620.0" y="120.0" c="6"/>
<char x="621.1" y="120.0" c="7"/>
<char x="622.2" y="120.0" c="."/>
<char x="623.3" y="120.0" c="0"/>
<char x="624.4" y="120.0" c="0"/>
<char x="625.5" y="120.0" c="0"/>
<char x="626.6" y="120.0" c="."/>
<char x="627.7" y="120.0" c="0"/>
<char x="628.8" y="120.0" c="0"/>
<char x="629.9" y="120.0" c="0"/>
<char x="631.0" y="120.0" c="."/>
<char x="632.1" y="120.0" c="0"/>
<char x="633.2" y="120.0" c="0"/>
<char x="634.3" y="120.0" c="0"/>
<char x="656.0" y="120.0" c="0"/>
<char x="696.0" y="120.0" c="6"/>
<char x="697.1" y="120.0" c="7"/>
<char x="698.2" y="120.0" c="."/>
<char x="699.3" y="120.0" c="0"/>
<char x="700.4" y="120.0" c="0"/>
<char x="701.5" y="120.0" c="0"/>
<char x="702.6" y="120.0" c="."/>
<char x="703.7" y="120.0" c="0"/>
<char x="704.8" y="120.0" c="0"/>
<char x="705.9" y="120.0" c="0"/>
<char x="707.0" y="120.0" c="."/>
<char x="708.1" y="120.0" c="0"/>
<char x="709.2" y="120.0" c="0"/>
<char x="710.3" y="120.0" c="0"/>
<char x="748.0" y="120.0" c="5"/>
<char x="749.1" y="120.0" c="3"/>
<char x="750.2" y="120.0" c=","/>
<char x="751.3" y="120.0" c="1"/>
<char x="752.4" y="120.0" c="9"/>
</page>
</document>