An address¶
An address is a point in typed dimensions. For a numeric fact the dimensions are
entity · attribute · period, and every verb that takes a coordinate takes one in
this shape:
Any subset is also an address. {"attribute": "revenue"} is every revenue, at every
period, for every entity in the workspace. Prose has one axis of its own,
clause_type; a coordinate cannot name both a numeric axis and clause_type (the two
live in different stores and one closed set cannot span them — the request is refused
with 422). source_id narrows any address to one document.
Why an address and not a query. A search returns whatever scored highest and cannot say how many there are. An address names a set the store can count, so the reply can carry a denominator — see a count. Everything Limma promises about completeness rests on this: it is only expressible over a set that can be enumerated.
Finding the address¶
The store names concepts its own way — it may say revenue where your document says
"net sales". Discover the names with mode="explore" (relevance-ranked retrieval) and
read entity / attribute / period on what comes back:
r = client.post("/v1/public/facts", json={
"workspace_id": WS, "question": "revenue 2023", "mode": "explore",
"output_mode": "compact", "limit": 5})
addresses = {(f["kind"], f["entity"], f["attribute"], f["period"]) for f in r.json()["facts"]}
for kind, entity, attribute, period in sorted(addresses, key=str):
print(kind, "|", entity, "|", attribute, "|", period)
claim | Acme Corp | headcount | FY2023
claim | Acme Corp | net_income | FY2023
claim | Acme Corp | revenue | FY2023
passage | None | None | None
Over MCP the same call is limma_facts(question, workspace_id=..., output_mode="compact",
limit=5) (workspace_id left out: the key's default workspace).
Enumerating at the address¶
Once you hold an address, ask for everything at it with mode="enumerate". question
is still required by the request model and is not used to rank anything in this mode:
r = client.post("/v1/public/facts", json={
"workspace_id": WS, "question": "revenue", "mode": "enumerate",
"coordinate": {"entity": "Acme Corp", "attribute": "revenue", "period": "FY2023"},
"output_mode": "compact"})
body = r.json()
print(body["enumeration"])
for fact in body["facts"]:
print(fact["handle"], "|", fact["value"], "|", fact["value_token"])
{'coordinate': {'entity': 'Acme Corp', 'attribute': 'revenue', 'period': 'FY2023'}, 'exist': 1, 'delivered': 1, 'truncated': False, 'bound': True}
claim:rev_fy2023::llm_page::0 | 1250000000.0 | 1,250
handle is the fact's id, and it is what every other verb accepts: limma_verify
resolves claim:<id> as a derivation operand, and the receipt fields on the same fact
are what limma_region re-reads (see a receipt). value is the stored,
scale-normalised number; value_token is what the page prints. They are different
things, and a fact can have a byte-true token and a wrong value — separating them is
the point.
The second address space¶
A relation with typed slots is addressed with relation_query —
{relation, where: [[slot, op, value]], active_in, entity_id} — beside the unchanged
coordinate (one address per request; naming both is a 422). It answers with the same
enumeration block and a relations block that echoes the query as asked, the query
as run after vocabulary expansion (where_run, expansion), the per-entity verdict
(entities.matched / bound / not_recorded) and the rows. Every example above is on the
coordinate lane; relations is the same page for the relation lane, with
its examples, and search asks conditions over several relations at once.