Quick answer
A look-through table replaces each fund position in a portfolio with the fund's own holdings, so you can see exposure by issuer, sector and country. A Nori agent collects the holdings reports as PDF, CSV or Excel, parses and checks them, maps every holding to your security master, multiplies weights through each fund layer, and writes the result to the warehouse. Holdings it cannot map go on an unmapped list for a person. A schedule refreshes the table when new reports arrive.
Why look-through is slow by hand
The arithmetic is simple. The work around it is not. Each fund administrator publishes holdings in its own layout, often as a PDF. Names are abbreviated, identifiers are missing or of mixed type, and report dates differ from fund to fund. An analyst spends most of a look-through cycle retyping and matching, and very little of it looking at exposure.
Those steps are repetitive, checkable and well suited to an agent. The agent does the parsing, matching and multiplication, and shows its checks. People decide the unmapped holdings and the conventions, such as how derivatives and cash are treated.
This page describes a data workflow. It does not give investment advice, and the table is only as complete as the holdings reports the funds provide.
Tables the agent reads and writes
-- your portfolio positions (read)
portfolio_position (portfolio_id, as_of_date, security_id, market_value, weight)
-- one row per line of a fund's holdings report (written by the parser)
lookthrough.fund_holding (
fund_security_id varchar, -- the fund, as a security in the master
report_date date, -- date the holdings are stated as of
line_no integer,
holding_name_raw varchar, -- exactly as printed
id_type varchar, -- ISIN, CUSIP, SEDOL, FIGI, TICKER, NONE
id_value varchar,
weight numeric(12,8),-- share of fund net assets
market_value numeric(20,2),
security_id varchar, -- null until mapped
map_method varchar, -- id_exact, name_match, manual, unmapped
source_file varchar,
source_page integer
);
-- the result (written by the calculation)
lookthrough.exposure (
portfolio_id varchar,
as_of_date date,
issuer_id varchar,
issuer_name varchar,
sector varchar,
country_of_risk char(2),
direct_weight numeric(12,8),
indirect_weight numeric(12,8),
total_weight numeric(12,8),
oldest_report_date date, -- stalest fund report behind this row
path_count integer -- number of fund chains contributing
);
Issuer, sector and country come from the security master, not from the fund report. That is why the mapping step matters: one issuer must have one issuer_id whether it appears as a direct holding, inside a fund, or under a slightly different name in a third report. If you do not have a master yet, start with how to build an agentic security master.
How to build the look-through, step by step
- Collect the reports. Save each fund's holdings report to a bucket or shared drive folder with a predictable name such as
holdings/2026Q3/fund_a_20260930.pdf. The agent can also fetch reports from administrator portals or mailboxes you connect as integrations. - Write the conventions. A short file in the repository states your choices: weights are a share of net assets, derivatives are shown at market value or at notional, cash is its own row, look-through stops at four fund layers.
- Ask in Slack.
@nori build the Q3 look-through for portfolio BAL-GROWTH from the reports in holdings/2026Q3. Follow lookthrough/conventions.md. Post the unmapped list when done. - The agent parses each report. CSV and Excel files are read directly. For PDFs it extracts the holdings tables page by page and keeps the page number for every line.
- It checks the parse. Weights must sum to the total printed in the report, within a tolerance you set. The line count must match the stated number of holdings where one is given. A report that fails is held back and flagged, not used.
- It maps holdings to the master. Identifier matches come first. Name matches are proposed only above a similarity threshold and marked
name_matchso a person can sample them. The rest areunmapped. - It computes exposure. Fund weights are multiplied down each chain and summed by issuer, then joined to sector and country.
- It reconciles and reports. Total weight across all rows, including cash, unmapped and uncovered remainder, must equal 100% of the portfolio. The agent posts the summary and opens a pull request for any parser or mapping-rule change.
Calculation example: one issuer through three paths
Portfolio BAL-GROWTH holds Example Motors plc three ways. All figures are illustrative.
- Directly: 1.00% of the portfolio.
- Through Fund A: the portfolio holds Fund A at 12.00%. Fund A's report shows Example Motors at 5.00% of its net assets.
- Through Fund B and Fund C: the portfolio holds Fund B at 8.00%. Fund B is a fund of funds with 50.00% in Fund C. Fund C holds Example Motors at 4.00%.
direct = 1.0000%
via Fund A 12.00% x 5.00% = 0.6000%
via Fund B > C 8.00% x 50.00% x 4.00% = 0.1600%
-------
look-through exposure to Example Motors = 1.7600%
direct_weight = 1.0000%
indirect_weight = 0.7600%
path_count = 3
The same multiplication runs for every holding in every fund. The agent writes the calculation as a recursive SQL model so the result can be rebuilt from fund_holding at any time:
with recursive chain as (
select p.portfolio_id, p.security_id, p.weight as path_weight, 1 as depth
from portfolio_position p
where p.as_of_date = '2026-09-30'
union all
select c.portfolio_id, h.security_id,
c.path_weight * h.weight, c.depth + 1
from chain c
join lookthrough.latest_fund_holding h
on h.fund_security_id = c.security_id
where c.depth < 4
)
select portfolio_id, security_id, sum(path_weight) as weight
from chain c
where c.depth = 4 -- depth limit reached: stop here
or not exists ( -- leaf: a security with no holdings report
select 1 from lookthrough.latest_fund_holding h
where h.fund_security_id = c.security_id
)
group by 1, 2
A fund with no usable report is a leaf. Its weight stays on the fund itself and is reported as not looked through. The same happens to a fund reached at the depth limit, which also stops funds that hold each other from looping. When a report's lines sum to less than 100% of net assets, the parser adds a not disclosed line for the remainder, so no weight is lost in the multiplication.
Worked example: the output table and the unmapped list
| Issuer | Sector | Country | Direct | Indirect | Total | Oldest report |
|---|---|---|---|---|---|---|
| Example Motors plc | Consumer discretionary | GB | 1.00% | 0.76% | 1.76% | 2026-06-30 |
| Northfield Industrial Corp | Industrials | US | 0.00% | 1.42% | 1.42% | 2026-09-30 |
| Republic of Exampleland | Government | XX | 2.50% | 0.31% | 2.81% | 2026-09-30 |
| Cash and equivalents | Cash | n/a | 3.10% | 0.94% | 4.04% | 2026-06-30 |
| Unmapped holdings | n/a | n/a | 0.00% | 0.38% | 0.38% | 2026-06-30 |
| Not looked through (Fund D) | Fund | LU | 4.00% | 0.00% | 4.00% | No report |
Three rows keep the table honest. Cash is shown, not dropped. Unmapped holdings carry their weight until someone maps them. A fund with no report is shown at its full weight and labelled. The Oldest report column shows that Fund C's holdings are a quarter old, so the Example Motors figure blends two dates.
The Slack summary leads with what needs a person:
Nori 07:26
Look-through for BAL-GROWTH as of 2026-09-30 is built.
9 funds held, 8 looked through, 1 without a report (Fund D, 4.00%).
2,317 holdings parsed, 2,296 mapped by identifier, 14 by name, 7 unmapped.
Weights reconcile to 100.00%.
Unmapped (0.38% of portfolio):
Fund C p.14 "EXMPL MTRS FIN 4.25 2031" no identifier 0.11%
suggestion: bond issued by Example Motors Finance, issuer Example Motors plc
Fund A p.6 "NTH IND CORP NEW" CUSIP not in master 0.09%
suggestion: see secmaster exception EXC-2026-1111-001
... 5 more in the thread
Held back: fund_e_20260930.pdf. Parsed weights sum to 97.2%, report
states 100.0%. Page 9 table looks truncated. Using the 2026-06-30 report.
An analyst confirms or corrects each suggestion in the thread. Confirmed mappings are saved, so the same abbreviated name maps on its own next quarter.
Refreshing on a schedule
Holdings reports arrive over several weeks after quarter end, so one quarterly run is not enough. Two automations cover it:
- Weekly check for new reports, on
0 7 * * 1. The agent looks for new files, parses them, rebuilds the affected portfolios, and posts only if exposure rows or the unmapped list changed. - Quarter-end rebuild, started by a one-time reminder or a webhook from your portfolio accounting export. It rebuilds every portfolio from the latest positions and lists funds whose reports are still missing.
Parser fixes and new mapping rules always arrive as pull requests. The table itself is rebuilt from stored inputs, so any quarter can be reproduced. For the wider operating picture, see AI agents for asset managers and Nori for finance teams.
Frequently asked questions
How accurate is PDF parsing of holdings reports?
It varies by layout, which is why every parse is checked. Weights must sum to the printed total and line counts must match where stated. A report that fails is held back and flagged with the page that looks wrong, so a bad parse does not reach the table.
What happens to holdings the agent cannot map?
They stay in the table as an unmapped row with their weight, and each one is listed in Slack with the source page and a suggested match. A person confirms or corrects it. Confirmed mappings are remembered for the next refresh.
How are derivatives, shorts and cash treated?
According to the conventions file you write. Common choices are market value or notional for derivatives, signed weights for short positions, and a separate cash row. The agent applies the file as written and does not choose a convention for you.
Can this be used for regulatory look-through reporting?
The table can be an input to your reporting process, and its lineage shows where each figure came from. Nori does not certify the output for any regulation. Your own operations, risk and compliance staff decide whether and how it is used.
What if fund reports have different dates?
The agent uses the latest valid report for each fund and records its date. Every exposure row carries the oldest report date behind it, so you can see where figures blend dates and which funds are overdue.
Do we need a security master first?
You need one consistent issuer and security key. An existing master works if the agent can read it. Without one, identifiers in the reports can serve as a starting key, but name variants will produce duplicates, so building the master first gives a cleaner table.