# How to read the cubes

The graph `uruguay-cows` is the DICOSE declarations of 2024 and 2025, every row of every published table, as RDF: a W3C **Data Cube** of observations, **PROV** for where each came from, **SKOS** for the code lists. Nothing was averaged away on the way in. There are two ways to read it: the curated `cow_*` tools, which know the shapes and the traps, and SPARQL, which knows nothing and will do anything.

## The curated tools first

They answer the usual questions in one call, with provenance and warnings, and they refuse what they cannot answer rather than guess:

| Question | Tool |
| --- | --- |
| How many animals of a species, by department and category? | `cow_herd` (`species`, `departments`, `categories`, `year`) |
| Which departments have the most (or fewest) of something? National totals? | `cow_rank` (`metric`, `year`, `limit: 19` for every department) |
| Everything about one department, or one enumeration area | `cow_area_profile` (`department` by name or code) |
| This year against last year | `cow_compare` (`years: [2024, 2025]`, `metrics`, `entities`) |
| What does a metric mean? | `cow_metric` |
| Can I trust this year? | `cow_quality_report` (`year`) |
| Where did this number come from? | `cow_sources`, `cow_report` with `reportId: source_provenance` |
| A map | `cow_map` (`metric`, `year`) |

Departments may be named ("Florida", "San José", "treinta y tres") or coded ("7"); species by code or plain name ("dairy cattle", "sheep"). An unknown name is refused with the list of known ones. A year the graph does not hold is refused. An argument a tool does not take is refused. Every call has a budget (20 seconds); a call that outlives it is cancelled and says so.

## The named graphs

Each kind of thing lives in its own named graph, and SPARQL sees nothing unless you name the graph (wrap every pattern in `GRAPH <...> { }`, or `GRAPH ?g { }`):

| Named graph | Holds |
| --- | --- |
| `urn:sophia:garden:uruguay-cows:cow:observations:2025` (and `:2024`) | the observations, one per published row |
| `urn:sophia:garden:uruguay-cows:cow:derived:2025` | derived metrics per department and enumeration area (counts, sums, densities) |
| `urn:sophia:garden:uruguay-cows:cow:codelists:2025` | departments, enumeration areas, species, categories and the other code lists, as SKOS concepts with their labels |
| `urn:sophia:garden:uruguay-cows:cow:quality:2025` | that year's quality report: checks, violations, warnings |
| `urn:sophia:garden:uruguay-cows:cow:sources` | provenance: the catalogue datasets, their files and download links, sha256s, row counts, the licence |
| `urn:sophia:garden:uruguay-cows:cow:ontology` | the vocabulary (`https://sophia.dev/ontology/cattle-aggregates#`, prefix `cow:`) and its metric definitions |

## An observation

Every observation is `a cow:AggregateObservation` plus one of `cow:GeneralHoldingObservation`, `cow:AnimalFlowObservation`, `cow:AnimalCategoryStockObservation`, the land observations, or the milk observations. Each carries its dimensions (`cow:year`, `cow:department`, `cow:enumerationArea`, `cow:species`, `cow:animalCategory`, and the strata: police section, activity, line of business, legal nature, size stratum, MGAP specialization and production type) and its measures:

- `cow:animalsCount`: animals **PD+AD**, the holder's own animals inside the establishment plus other people's animals inside it. DICOSE's traditional count.
- `cow:animalsCountPf`: animals **PD+PF**, the holder's own animals inside plus its own animals kept elsewhere. MGAP uses this one for the dairy herd.
- `cow:holdersCount`, `cow:birthsCount`, `cow:deathsCount`, `cow:consumptionCount`, `cow:hectares`, `cow:milkLiters`, and their kin.

The two animal counts differ, legitimately: animals grazing on someone else's land are counted where they stand in one and with their owner in the other. Say which you mean.

Departments are `urn:sophia:cow:department/7` (Florida), species `urn:sophia:cow:species/11` (dairy cattle), categories `urn:sophia:cow:animal-category/11/2` (dairy cattle, milking cows). Category codes mean different things for different species (category 2 is breeding cows for beef cattle, milking cows for dairy cattle, breeding ewes for sheep), so the species is part of the category's address.

## The dairy cows of Florida, by hand

```sparql
PREFIX cow: <https://sophia.dev/ontology/cattle-aggregates#>
SELECT ?category (SUM(?pdad) AS ?animalsPdAd) (SUM(?pdpf) AS ?animalsPdPf) WHERE {
  GRAPH <urn:sophia:garden:uruguay-cows:cow:observations:2025> {
    ?obs a cow:AnimalCategoryStockObservation ;
         cow:department <urn:sophia:cow:department/7> ;
         cow:species <urn:sophia:cow:species/11> ;
         cow:animalCategory ?category ;
         cow:animalsCount ?pdad ;
         cow:animalsCountPf ?pdpf .
    VALUES ?category { <urn:sophia:cow:animal-category/11/2> <urn:sophia:cow:animal-category/11/3> }
  }
} GROUP BY ?category
```

`cow_herd` with `species: "dairy cattle", department: "Florida", categories: ["2", "3"], year: 2025` does the same, with labels, both measures, the source rows counted and the file named.

## A derived metric, by hand

```sparql
PREFIX cow: <https://sophia.dev/ontology/cattle-aggregates#>
SELECT ?department ?value WHERE {
  GRAPH <urn:sophia:garden:uruguay-cows:cow:derived:2025> {
    ?obs a cow:DerivedMetricObservation ;
         cow:metric cow:metric_bovine_count ;
         cow:department ?department ;
         cow:metricValue ?value .
    FILTER NOT EXISTS { ?obs cow:animalCategory ?c }
    FILTER NOT EXISTS { ?obs cow:enumerationArea ?a }
  }
} ORDER BY DESC(?value)
```

`bovine_count` is beef plus dairy cattle (species 1 and 11), PD+AD. The derived graph also holds per-enumeration-area values and per-category shares; filter for the level you want, or you will add departments to their own enumeration areas and be twice as impressed.

## Budgets

gardend's own `sparql_query` runs every query under a deadline and a row ceiling (on the public endpoint 10 seconds and 1,000 rows; for the owner 30 seconds by default, `timeoutMs` up to 120,000, and 50,000 rows), and cancels the engine when either is reached. A query that joins two hundred thousand observations to two hundred thousand observations will be stopped and will say so. Narrow it: one year, one department, one species.

---

*This guide is by Sophia Labs, under CC BY 4.0. The data it describes is MGAP's (and, for the catalogue, each publisher's), under the Licencia de Datos Abiertos – Uruguay.*

The cow-tools guide, The guide: https://cow-tools.sophia-labs.com/guide/reading-the-cubes/ (this page for people) · the whole guide: https://cow-tools.sophia-labs.com/guide/index.md · in Sophia: the graph `cow-tools-guide`, document `reading-the-cubes` (MCP `read_document` at https://cow-tools.sophia-labs.com/mcp). CC BY 4.0, credit "cow-tools, Sophia Labs".
