Classify Advisor data with FIBO

Many banks model their data with FIBO, the Financial Industry Business Ontology. This guide brings Advisor data into such a model already classified. In four calls it gets the regulators as FIBO individuals, an APRA standard typed with its FIBO class, its maker and the jurisdiction it is in force in, the standard's obligation rows linked to FIBO's legal obligation class, and a notification obligation found in the words of the AML/CTF Act.

Every call is one Advisor already answers, with fibo: true added. Without it the response is exactly what it was. The fibo block classifies a result; it adds no claim. FIBO is a trademark of the EDM Council.

Before you begin

Steps

  1. Get the regulators with list_issuing_authorities and fibo: true. Each of the seven bodies the block names (APRA, ASIC, AUSTRAC, the RBA, the AASB, the AUASB and the ATO) comes with two IRIs: organisation, the body (the Australian Prudential Regulation Authority), and role, the regulatory role it plays (Australia's prudential regulator). FIBO models a regulator that way: the role makes the rules, and the role is played by the organisation. The Parliament comes with an organisation and no role; other makers have null with a note.
  2. Find the standard with search_legislation and fibo: true. The title's block gives type (a legislative instrument is FIBO's Regulation, an Act StatuteLaw), made_by (the regulator's role, or the Parliament for an Act) and is_in_force_in, which appears only while the title is in force and carries the same confirmed_with_frl_at as its in_force block.
  3. List its obligations with list_obligations and fibo: true. Each row's fibo.expresses links it to a FIBO class by skos:closeMatch. A requirement or prohibition closely matches LegalObligation; an offence, a benefit condition and a deeming provision have class: null with the reason.
  4. Look up a notification obligation by its obligation_id. A requirement whose words give a report or notice to a party, notify or inform a party, or report to, lodge with or serve on a party closely matches NotificationObligation (rule OBL-13). The block quotes the words matched, names the recipient as the text names it, and gives named_in_text, the obligation's own name where the text gives one.
import asyncio
import json
import os

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

URL = os.environ.get("LAWATLAS_MCP_URL", "https://advisor.lawatlas.ai/api/mcp")


class AdvisorError(Exception):
    def __init__(self, error):
        super().__init__(f"{error['code']}: {error['message']}")
        self.code = error["code"]


async def call(session, tool, arguments):
    """Call one tool and return its JSON, raising on an error envelope."""
    result = await session.call_tool(tool, arguments)
    text = result.content[0].text
    try:
        body = json.loads(text)
    except ValueError:
        raise RuntimeError(text)  # plain text: a lapsed subscription or a malformed argument
    if isinstance(body, dict) and "error" in body:
        raise AdvisorError(body["error"])
    return body


def short(iri):
    """The last part of an IRI, for printing; store the whole IRI."""
    return iri.rsplit("/", 1)[-1] if iri else None


async def classify(session, regulators, standard_code, topic, obligation_ids):
    # 1. The regulators, as FIBO individuals: an organisation and the role it plays.
    authorities = await call(session, "list_issuing_authorities", {"fibo": True})
    header = authorities["fibo_alignment"]
    print(f"FIBO release {header['release']}, namespace {header['namespace']}")
    for a in authorities["items"]:
        if a["code"] in regulators:
            f = a["fibo"]
            print(f"  {a['code']}: organisation {f['organisation']}")
            print(f"  {' ' * len(a['code'])}  role         {f['role']}")

    # 2. A standard, typed with FIBO's class for its kind, its maker and where it is in force.
    found = await call(session, "search_legislation", {
        "query": standard_code, "issuing_authority": [regulators[0]], "limit": 3, "fibo": True})
    std = next(i for i in found["items"] if i.get("standard_code") == standard_code)
    f = std["fibo"]
    print(f"\n{standard_code} ({std['title_id']}): type {short(f['type'])} ({f['type_label']}), "
          f"made by {short(f['made_by'])}")
    if "is_in_force_in" in f:
        print(f"  in force in {short(f['is_in_force_in']['jurisdiction'])}, "
              f"confirmed with the Federal Register at {f['is_in_force_in']['confirmed_with_frl_at']}")

    # 3. Its obligation rows: each closely matches a FIBO class, never typed as one.
    page = await call(session, "list_obligations", {
        "title_id": std["title_id"], "topic": topic, "verified_only": True, "limit": 5, "fibo": True})
    print(f"\nFirst {len(page['items'])} verified rows under headings containing {topic!r}:")
    for r in page["items"]:
        e = r["fibo"]["expresses"]
        assert r["fibo"]["claim"] == page["claim"]  # the row repeats the response's claim
        print(f"  {r['obligation_id']}  {e['relation']} {e['class_label']}  ({e['basis']})")

    # 4. A notification obligation, found by rule OBL-13 in the provision's own words.
    lookup = await call(session, "list_obligations", {"obligation_id": obligation_ids, "fibo": True})
    for r in lookup["items"]:  # `lookup` says each id's status; `items` holds the rows
        e = r["fibo"]["expresses"]
        print(f"\n{r['obligation_id']} ({r['pinpoint']}): {e['relation']} {e['class_label']}")
        print(f"  recipient: {e.get('recipient')}")
        print(f"  named:     {e.get('named_in_text')}")
        print(f"  basis:     {e['basis']}")


async def main():
    headers = {"Authorization": f"Bearer {os.environ['LAWATLAS_API_KEY']}"}
    async with streamablehttp_client(URL, headers=headers) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            await classify(session, regulators=["APRA", "AUSTRAC"], standard_code="CPS 230",
                           topic="business continuity", obligation_ids=["C2006A00169/main/sec_41/GRC01"])


if __name__ == "__main__":
    asyncio.run(main())

Check it worked

Run python fibo_classify.py. Run on 30 October 2026 against a local Advisor holding the same corpus, it printed:

FIBO release FIBO master_2026Q3, commit 33a92cd, namespace https://lawatlas.ai/ontology/fibo-au/
  APRA: organisation https://lawatlas.ai/ontology/fibo-au/AURegulatoryAgencies/AustralianPrudentialRegulationAuthority
        role         https://lawatlas.ai/ontology/fibo-au/AURegulatoryAgencies/AustralianPrudentialRegulator
  AUSTRAC: organisation https://lawatlas.ai/ontology/fibo-au/AURegulatoryAgencies/AustralianTransactionReportsAndAnalysisCentre
           role         https://lawatlas.ai/ontology/fibo-au/AURegulatoryAgencies/AustralianFinancialIntelligenceAndAMLRegulator

CPS 230 (F2026L00475): type Regulation (regulation), made by AustralianPrudentialRegulator
  in force in AustralianJurisdiction, confirmed with the Federal Register at 2026-10-23T17:36:21.788051Z

First 5 verified rows under headings containing 'business continuity':
  F2026L00475/main/para_33/GRC01  skos:closeMatch legal obligation  (kind=requirement)
  F2026L00475/main/para_33/GRC02  skos:closeMatch legal obligation  (kind=requirement)
  F2026L00475/main/para_33/GRC03  skos:closeMatch legal obligation  (kind=requirement)
  F2026L00475/main/para_33/GRC04  skos:closeMatch legal obligation  (kind=requirement)
  F2026L00475/main/para_33/GRC05  skos:closeMatch legal obligation  (kind=requirement)

C2006A00169/main/sec_41/GRC01 (s 41(2)): skos:closeMatch notification obligation
  recipient: the AUSTRAC CEO
  named:     suspicious matter reporting obligation
  basis:     kind=requirement; OBL-13 give_recipient_document: "must give the AUSTRAC CEO a report"

What to notice:

Store it in your model

In the fibo blockWhat it isHow to store it
type on a titleThe FIBO class for the title's kindThe class of your node for the title
made_by on a titleThe maker as a FIBO individual: a regulator's role, or the ParliamentA link from the title to that IRI
is_in_force_in on a titleThe jurisdiction, and when the Federal Register last confirmed the statusA link to the jurisdiction, with the time beside it
organisation, role on a regulatorThe body, and the role it playsTwo individuals, linked as FIBO links a role to the organisation that plays it
expresses on an obligation rowThe closest FIBO class, by skos:closeMatchskos:closeMatch from your node for the row to the class, never rdf:type
recipient, named_in_textThe party and the obligation's name, as the text gives themText, as quoted

A row is never typed as a FIBO obligation because it is a quoted provision whose text expresses an obligation, not the obligation itself, and not a statement of whom it applies to. Store the link as Advisor gives it.

The Australian individuals have been offered to the EDM Council for FIBO. Until they are merged their IRIs are in LawAtlas.ai's namespace, lawatlas.ai/ontology/fibo-au, and each one answers with Turtle when asked with Accept: text/turtle, with RDF/XML for Accept: application/rdf+xml, and with a page otherwise. The response's fibo_alignment names the namespace and the FIBO release the block was checked against. APRA's role, as the published Turtle defines it:

<https://lawatlas.ai/ontology/fibo-au/AURegulatoryAgencies/AustralianPrudentialRegulator>
    a owl:NamedIndividual ;
    a cmns-rga:RegulatoryAgency ;
    rdfs:isDefinedBy <https://lawatlas.ai/ontology/fibo-au/AURegulatoryAgencies/> ;
    rdfs:label "Australian prudential regulatory agency role of the Australian Prudential Regulation Authority" ;
    skos:definition "regulatory agency role of the Australian Prudential Regulation Authority" ;
    cmns-av:adaptedFrom "https://www.apra.gov.au"^^<http://www.w3.org/2001/XMLSchema#anyURI> ;
    cmns-rlcmp:isPlayedBy la-au-rga:AustralianPrudentialRegulationAuthority .

What to do if it did not

ProblemSolution
KeyError: 'fibo'The call was made without fibo: true; the block is only sent when asked for
AdvisorError: FORBIDDEN from list_obligationsThe key's organisation owner has no active AI Pro subscription; renew it
StopIteration at step 2No title of that regulator carries that standard_code; try search_legislation with words from the standard's name
HTTP 401Check LAWATLAS_API_KEY; see Troubleshooting

Next steps