Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions evals/skills/fetch-chart-data/contract.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ if fetch "$CHART.metadata.json?$PARAMS" "$META"; then
all_match "timespan looks like a year range" "$META" '.columns[] | select(has("timespan"))' \
'.timespan | test("^-?[0-9]+-[0-9]+$")'

# SKILL.md types descriptionKey as string[], and tells agents to pay special
# attention to it. A markdown bullet string and an array of strings need
# different handling, so the type has to be right.
all_match "descriptionKey is an array of strings, as documented" "$META" \
'.columns[] | select(has("descriptionKey"))' '.descriptionKey | type == "array"'
# SKILL.md tells agents to pay special attention to descriptionKey, so its
# type has to be right: a markdown bullet string and an array of strings need
# different handling.
all_match "descriptionKey is a string, as documented" "$META" \
'.columns[] | select(has("descriptionKey"))' '.descriptionKey | type == "string"'
all_match "descriptionKey is a markdown bulleted list" "$META" \
'.columns[] | select(has("descriptionKey"))' '.descriptionKey | test("^- ")'
note "descriptionKey type: $(jq -r '[.columns[] | select(has("descriptionKey")) | .descriptionKey | type] | unique | join(", ")' "$META")"
note "columns: $(jq -r '.columns | keys | join(", ")' "$META")"
fi
Expand All @@ -58,10 +60,10 @@ if fetch "$CHART.csv?$PARAMS" "$CSV_SHORT"; then
ok "short column names contain no spaces" \
test "$data_columns" = "${data_columns// /}"

# SKILL.md documents the first three columns as Entity, Code, Year. With the
# recommended useColumnShortNames=true they come back lowercased, so this
# check reports whether the doc matches the call the doc recommends.
csv_header "header is Entity,Code,Year as documented" "$CSV_SHORT" "Entity,Code,Year"
# useColumnShortNames=true lowercases the first three headers. SKILL.md now
# documents both forms and tells agents to match case-insensitively; these two
# checks pin down which call produces which.
csv_header "useColumnShortNames=true lowercases the first three headers" "$CSV_SHORT" "entity,code,year"
fi

section "CSV endpoint without useColumnShortNames"
Expand Down
28 changes: 26 additions & 2 deletions evals/skills/owid-catalog/contract_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,22 @@ def main() -> int:
("indicator results convert to a DataFrame", lambda: not indicators.to_frame().empty),
("an indicator result can .fetch() a single column", lambda: _nonempty(indicators[0].fetch())),
("an indicator result can .fetch_table()", lambda: _nonempty(indicators[0].fetch_table())),
# search() takes no sort argument; sorting happens on the returned
# ResponseSet, keyed by a field of the result.
(
"sort_by='relevance' is accepted",
lambda: search("CO2 emissions per capita", kind="indicator", sort_by="relevance") is not None,
"search() rejects a sort_by argument",
lambda: _raises(
TypeError,
lambda: search("CO2 emissions per capita", kind="indicator", sort_by="relevance"),
),
),
(
"indicator results expose score, popularity and n_charts",
lambda: {"score", "popularity", "n_charts"} <= set(indicators.to_frame(all_fields=True).columns),
),
*(
(f"indicator results re-sort by {key!r}", lambda k=key: indicators.sort_by(k, reverse=True) is not None)
for key in ("score", "popularity", "n_charts")
),
]
reason = "SKIP_SLOW=1" if skip_slow else "indicator search did not return results"
Expand All @@ -144,6 +157,17 @@ def _import() -> bool:
return True


def _raises(exc_type: type[BaseException], fn: Callable[[], Any]) -> bool:
"""True if fn raises exc_type. Used to pin down what the API refuses, so a
SKILL.md example cannot drift back into calling an argument that never
existed."""
try:
fn()
except exc_type:
return True
return False


def _nonempty(obj: Any) -> Any:
"""Assert the table or result set has rows, and return it so callers can
keep using the object rather than a bool."""
Expand Down
24 changes: 17 additions & 7 deletions evals/skills/search-charts/contract.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,14 @@ if fetch "$API?q=energy&hitsPerPage=50" "$BROAD"; then
jq_true "every type is one of the documented ChartRecordType values" "$BROAD" \
'[.results[].type] | unique | inside(["chart", "explorerView", "multiDimView"])'

# SKILL.md's BaseSearchChartHit marks objectID as required. If this fails,
# the schema in SKILL.md is stale — the field is not worth documenting.
all_match "objectID is present on every hit (documented as required)" "$BROAD" \
# BaseSearchChartHit must not document fields the API never sends: an agent
# that branches on one will always take the wrong branch.
none_match "objectID is absent, as the schema now reflects" "$BROAD" \
'.results[]' 'has("objectID")'
all_match "every hit has publishedAt and updatedAt" "$BROAD" '.results[]' \
'has("publishedAt") and has("updatedAt")'
all_match "publishedAt and updatedAt are ISO 8601 timestamps" "$BROAD" '.results[]' \
'(.publishedAt | test("^[0-9]{4}-[0-9]{2}-[0-9]{2}T")) and (.updatedAt | test("^[0-9]{4}-[0-9]{2}-[0-9]{2}T"))'
fi

section "Non-chart record types"
Expand All @@ -57,11 +61,17 @@ if fetch "$API?q=life+expectancy&hitsPerPage=100" "$NONCHART"; then
all_match "non-chart hits carry queryParams" "$NONCHART" \
'.results[] | select(.type != "chart")' 'has("queryParams")'

# SKILL.md marks explorerType required on SearchExplorerViewHit, and
# chartConfigId required on SearchMultiDimViewHit.
all_match "explorerView hits carry explorerType (documented as required)" "$NONCHART" \
all_match "non-chart hits carry containerTitle" "$NONCHART" \
'.results[] | select(.type != "chart")' 'has("containerTitle")'
none_match "containerTitle is absent on plain chart hits" "$NONCHART" \
'.results[] | select(.type == "chart")' 'has("containerTitle")'

# explorerType and chartConfigId were documented as required until this
# change corrected the schema. Keep asserting their absence so they cannot
# creep back into the docs without the API actually sending them.
none_match "explorerView hits have no explorerType, as the schema now reflects" "$NONCHART" \
'.results[] | select(.type == "explorerView")' 'has("explorerType")'
all_match "multiDimView hits carry chartConfigId (documented as required)" "$NONCHART" \
none_match "multiDimView hits have no chartConfigId, as the schema now reflects" "$NONCHART" \
'.results[] | select(.type == "multiDimView")' 'has("chartConfigId")'
fi

Expand Down
4 changes: 3 additions & 1 deletion skills/fetch-chart-data/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export type MetadataColumn = {
titleShort: string
titleLong: string
descriptionShort?: string
descriptionKey?: string[] // curated by experts at Our World In Data to collect important information or caveats about this data. If they are given, it might make sense to surface this information to the user.
descriptionKey?: string // a markdown bulleted list (one `- ` item per line), curated by experts at Our World In Data to collect important information or caveats about this data. If given, it might make sense to surface this information to the user.
descriptionProcessing?: string // notes about how this data was processed by Our World In Data in case it is not a straightforward republishing from the original source
shortUnit?: string
unit?: string
Expand Down Expand Up @@ -100,6 +100,8 @@ The first two columns in the CSV file are "Entity" and "Code". "Entity" is the n

The third column is either "Year" or "Day". If the data is annual, this is "Year" and contains only the year as an integer. If the column is "Day", the column contains a date string in the form "YYYY-MM-DD".

Note that `useColumnShortNames=true` lowercases these first three headers, so with the recommended base parameters they arrive as `entity,code,year` (or `entity,code,day`). Match column names case-insensitively rather than hardcoding either form.

The other columns are the data columns, which are the time series that power the chart.

Prefer to download the CSV and metadata into a file and process it from there.
7 changes: 5 additions & 2 deletions skills/owid-catalog/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,11 @@ print(results.to_frame().head(30).to_csv())
# Get all fields for deeper inspection
print(results.to_frame(all_fields=True).head(30).to_csv())

# Sort by relevance (default) or similarity
results = search("CO2 emissions per capita", kind="indicator", sort_by="relevance")
# Results arrive sorted by semantic similarity (the `score` field), with
# `popularity` breaking ties. `search()` takes no sort argument — re-sort the
# returned ResponseSet instead, using any field of the result:
results = search("CO2 emissions per capita", kind="indicator")
results = results.sort_by("popularity", reverse=True) # or "score", "n_charts"

# Fetch indicator data
tb = results[0].fetch() # single-column indicator
Expand Down
17 changes: 7 additions & 10 deletions skills/search-charts/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,19 @@ export enum ChartRecordType {
MultiDimView = "multiDimView",
}

export enum ExplorerType {
Grapher = "grapher",
Indicator = "indicator",
Csv = "csv",
}

type GrapherTabName = "LineChart" | "ScatterPlot" | "StackedArea" | "DiscreteBar" | "StackedDiscreteBar" | "SlopeChart" | "StackedBar" | "Marimekko" | "Table" | "WorldMap"
type GrapherTabName = "LineChart" | "ScatterPlot" | "StackedArea" | "DiscreteBar" | "StackedDiscreteBar" | "SlopeChart" | "StackedBar" | "Marimekko" | "Dumbbell" | "Table" | "WorldMap"

interface BaseSearchChartHit {
url: string
title: string
slug: string
availableEntities: string[]
originalAvailableEntities?: string[]
objectID: string
variantName?: string
subtitle?: string
availableTabs: GrapherTabName[]
publishedAt: string // ISO 8601 timestamp
updatedAt: string // ISO 8601 timestamp
}

type SearchChartViewHit = BaseSearchChartHit & {
Expand All @@ -49,14 +44,14 @@ type SearchChartViewHit = BaseSearchChartHit & {

type SearchExplorerViewHit = BaseSearchChartHit & {
type: ChartRecordType.ExplorerView
explorerType: ExplorerType
queryParams: string
containerTitle: string // title of the explorer this view belongs to
}

type SearchMultiDimViewHit = BaseSearchChartHit & {
type: ChartRecordType.MultiDimView
queryParams: string
chartConfigId: string
containerTitle: string // title of the multi-dimensional chart this view belongs to
}

export type SearchChartHit =
Expand Down Expand Up @@ -104,6 +99,8 @@ The `availableTabs` field indicates what visualizations a chart supports. Use th
| `ScatterPlot` | `scatter` | Scatter plot |
| `StackedArea` | `stacked-area` | Stacked area chart |
| `StackedBar` | `stacked-bar` | Stacked bar chart |
| `StackedDiscreteBar` | `stacked-discrete-bar` | Stacked bar chart for a single time point |
| `Dumbbell` | `dumbbell` | Dumbbell chart comparing two values per entity |

To display a specific visualization, append `?tab=<value>` to the chart URL. For example:
```
Expand Down
Loading