diff --git a/answers/coops-tide-hilo.json b/answers/coops-tide-hilo.json index ca2c28c..a944800 100644 --- a/answers/coops-tide-hilo.json +++ b/answers/coops-tide-hilo.json @@ -7,13 +7,10 @@ "words": [ "tides", "tide", - "high tide", - "low tide", - "next high tide", - "next low tide", "tide times", "tide chart", - "when is high tide" + "tide table", + "tide schedule" ], "primary": true, "kind": "list", @@ -41,6 +38,82 @@ "step": "hour" } }, + { + "name": "next_high", + "label": "Next high tide", + "words": [ + "next high tide", + "high tide", + "when is high tide" + ], + "primary": false, + "kind": "list", + "path": "predictions", + "row": [ + { + "path": "t", + "label": "Time", + "type": "time" + }, + { + "path": "type", + "label": "High/Low", + "type": "text" + }, + { + "path": "v", + "label": "Height", + "type": "number", + "unit": "ft" + } + ], + "filter": { + "path": "type", + "equals": "H" + }, + "axis": { + "cell": "t", + "step": "hour" + } + }, + { + "name": "next_low", + "label": "Next low tide", + "words": [ + "next low tide", + "low tide", + "when is low tide" + ], + "primary": false, + "kind": "list", + "path": "predictions", + "row": [ + { + "path": "t", + "label": "Time", + "type": "time" + }, + { + "path": "type", + "label": "High/Low", + "type": "text" + }, + { + "path": "v", + "label": "Height", + "type": "number", + "unit": "ft" + } + ], + "filter": { + "path": "type", + "equals": "L" + }, + "axis": { + "cell": "t", + "step": "hour" + } + }, { "name": "first_tide_time", "label": "First tide today", diff --git a/answers/open-meteo-forecast.json b/answers/open-meteo-forecast.json index c3d5abd..afe39eb 100644 --- a/answers/open-meteo-forecast.json +++ b/answers/open-meteo-forecast.json @@ -229,6 +229,12 @@ "label": "Rain chance", "type": "number", "unit_path": "hourly_units.precipitation_probability" + }, + { + "path": "hourly.snowfall", + "label": "Snowfall", + "type": "number", + "unit_path": "hourly_units.snowfall" } ], "limit": 24, @@ -337,6 +343,6 @@ } } ], - "sample_url": "https://api.open-meteo.com/v1/forecast?latitude=32.7765&longitude=-79.9311¤t=temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,wind_speed_10m&hourly=temperature_2m,precipitation_probability,weather_code&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max,snowfall_sum,sunrise,sunset&temperature_unit=fahrenheit&wind_speed_unit=mph&precipitation_unit=inch&timezone=auto", - "checked": "2026-10-06" + "sample_url": "https://api.open-meteo.com/v1/forecast?latitude=32.7765&longitude=-79.9311¤t=temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,wind_speed_10m&hourly=temperature_2m,precipitation_probability,weather_code,snowfall&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max,snowfall_sum,sunrise,sunset&temperature_unit=fahrenheit&wind_speed_unit=mph&precipitation_unit=inch&timezone=auto", + "checked": "2026-10-08" } diff --git a/answers/swpc-kp-forecast.json b/answers/swpc-kp-forecast.json index f5efb6b..b97a6dd 100644 --- a/answers/swpc-kp-forecast.json +++ b/answers/swpc-kp-forecast.json @@ -11,7 +11,7 @@ "filter": {"path": "observed", "equals": "predicted"}, "row": [ {"path": "time_tag", "label": "Window starts", "type": "time", "utc": true}, - {"path": "kp", "label": "Kp", "type": "number"} + {"path": "kp", "label": "Kp", "type": "number", "unit": "Kp"} ], "axis": {"cell": "time_tag", "step": "hour"} }, @@ -24,7 +24,7 @@ "filter": {"path": "observed", "equals": "estimated"}, "row": [ {"path": "time_tag", "label": "Window starts", "type": "time", "utc": true}, - {"path": "kp", "label": "Kp", "type": "number"} + {"path": "kp", "label": "Kp", "type": "number", "unit": "Kp"} ], "may_be_empty": true, "axis": {"cell": "time_tag", "step": "hour"} @@ -38,7 +38,7 @@ "filter": {"path": "observed", "equals": "observed"}, "row": [ {"path": "time_tag", "label": "Window starts", "type": "time", "utc": true}, - {"path": "kp", "label": "Kp", "type": "number"} + {"path": "kp", "label": "Kp", "type": "number", "unit": "Kp"} ], "newest_first": true, "axis": {"cell": "time_tag", "step": "hour"} diff --git a/sources/curated/core.jsonl b/sources/curated/core.jsonl index 279ec80..4c8a1c6 100644 --- a/sources/curated/core.jsonl +++ b/sources/curated/core.jsonl @@ -227,7 +227,7 @@ {"access": {"auth": "none", "contact_ua": false, "docs_url": "https://help.nytimes.com/hc/en-us/articles/115014893428-Terms-of-Service", "headers": {}, "kind": "rss", "params": [], "url_template": "https://rss.nytimes.com/services/xml/rss/nyt/World.xml"}, "categories": ["news/topic_news"], "coverage": {"entity": "The New York Times", "geo": "US"}, "description": "NYT World RSS feed.", "examples": ["world news"], "freshness": {"cadence": "minutes"}, "id": "nyt-world", "kinds": ["latest_items"], "name": "NYT World", "notes": "", "origin": {"at": "2026-09-28T04:10:32Z", "by": "bootstrap/core_seed"}, "provider": {"authority": "primary", "id": "nyt", "name": "The New York Times", "url": "https://www.nytimes.com"}, "terms": {"note": "public RSS for personal, non-commercial use", "status": "terms_allow", "terms_url": "https://help.nytimes.com/hc/en-us/articles/115014893428-Terms-of-Service"}, "tier": "curated", "validation": {"bytes": 128033, "checked_at": "2026-09-28T04:10:40Z", "http": 200, "note": "xml 57 items", "robots": "allow", "status": "ok"}, "votes": {"no": 0, "yes": 0}} {"access": {"auth": "none", "contact_ua": false, "docs_url": "https://world.openfoodfacts.org/terms-of-use", "headers": {}, "kind": "http_json", "params": [{"example": "3017620422003", "fill": {"from": "text"}, "kind": "product", "name": "barcode", "required": true}], "url_template": "https://world.openfoodfacts.org/api/v2/product/{barcode}.json"}, "categories": ["health/nutrition"], "coverage": {"entity": "", "geo": "global"}, "description": "Ingredients, nutrition and scores for a barcode.", "examples": ["nutrition for this barcode"], "freshness": {"cadence": "static"}, "id": "off-product", "kinds": ["lookup"], "name": "Open Food Facts product", "notes": "", "origin": {"at": "2026-09-28T04:10:32Z", "by": "bootstrap/core_seed"}, "provider": {"authority": "community", "id": "openfoodfacts", "name": "Open Food Facts", "url": "https://world.openfoodfacts.org"}, "terms": {"note": "ODbL", "status": "open_license", "terms_url": "https://world.openfoodfacts.org/terms-of-use"}, "tier": "curated", "validation": {"bytes": 152819, "checked_at": "2026-09-28T04:10:40Z", "http": 200, "note": "json", "robots": "disallow", "status": "ok"}, "votes": {"no": 0, "yes": 0}} {"access": {"auth": "none", "contact_ua": false, "docs_url": "https://open-meteo.com/en/terms", "headers": {}, "kind": "http_json", "params": [{"example": "32.7765", "fill": {"field": "lat", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lat", "required": true}, {"example": "-79.9311", "fill": {"field": "lon", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lon", "required": true}], "url_template": "https://air-quality-api.open-meteo.com/v1/air-quality?latitude={lat}&longitude={lon}¤t=us_aqi,pm2_5,pm10,ozone&hourly=us_aqi&timezone=auto"}, "categories": ["weather/air_quality"], "coverage": {"entity": "", "geo": "global"}, "description": "US AQI, PM2.5, PM10 and ozone now and by hour for a latitude/longitude (model data).", "examples": ["air quality in Phoenix", "is the air safe today", "smoke levels"], "freshness": {"cadence": "hourly"}, "id": "open-meteo-air-quality", "kinds": ["current_value", "forecast"], "name": "Open-Meteo air quality", "notes": "", "origin": {"at": "2026-09-28T04:10:32Z", "by": "bootstrap/core_seed"}, "provider": {"authority": "primary", "id": "open-meteo", "name": "Open-Meteo", "url": "https://open-meteo.com"}, "terms": {"note": "CC BY 4.0; free for non-commercial use; attribution required", "status": "open_license", "terms_url": "https://open-meteo.com/en/terms"}, "tier": "curated", "validation": {"bytes": 3138, "checked_at": "2026-09-28T04:10:40Z", "http": 200, "note": "json", "robots": "disallow", "status": "ok"}, "votes": {"no": 0, "yes": 0}} -{"access": {"auth": "none", "contact_ua": false, "docs_url": "https://open-meteo.com/en/terms", "headers": {}, "kind": "http_json", "params": [{"example": "32.7765", "fill": {"field": "lat", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lat", "required": true}, {"example": "-79.9311", "fill": {"field": "lon", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lon", "required": true}], "url_template": "https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t=temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,wind_speed_10m&hourly=temperature_2m,precipitation_probability,weather_code&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max,snowfall_sum,sunrise,sunset&temperature_unit=fahrenheit&wind_speed_unit=mph&precipitation_unit=inch&timezone=auto"}, "categories": ["weather/forecast", "weather/current"], "coverage": {"entity": "", "geo": "global"}, "description": "Current conditions plus hourly and daily forecast for any latitude/longitude, US units: temperature, highs and lows, rain chance, snow and conditions such as thunderstorms, hail, fog or freezing rain, from this morning and this afternoon through tonight and the week ahead.", "examples": ["weather in Austin", "what's the temperature", "rain chance this weekend", "will it storm tonight", "frost tonight"], "freshness": {"cadence": "hourly"}, "id": "open-meteo-forecast", "kinds": ["forecast", "current_value", "trend"], "name": "Open-Meteo forecast", "notes": "", "origin": {"at": "2026-09-28T04:10:32Z", "by": "bootstrap/core_seed"}, "provider": {"authority": "primary", "id": "open-meteo", "name": "Open-Meteo", "url": "https://open-meteo.com"}, "terms": {"note": "CC BY 4.0; free for non-commercial use; attribution required", "status": "open_license", "terms_url": "https://open-meteo.com/en/terms"}, "tier": "curated", "validation": {"bytes": 5989, "checked_at": "2026-09-29T03:40:12Z", "http": 200, "note": "json", "robots": "allow", "status": "ok"}, "votes": {"no": 0, "yes": 0}} +{"access": {"auth": "none", "contact_ua": false, "docs_url": "https://open-meteo.com/en/terms", "headers": {}, "kind": "http_json", "params": [{"example": "32.7765", "fill": {"field": "lat", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lat", "required": true}, {"example": "-79.9311", "fill": {"field": "lon", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lon", "required": true}], "url_template": "https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t=temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,wind_speed_10m&hourly=temperature_2m,precipitation_probability,weather_code,snowfall&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max,snowfall_sum,sunrise,sunset&temperature_unit=fahrenheit&wind_speed_unit=mph&precipitation_unit=inch&timezone=auto"}, "categories": ["weather/forecast", "weather/current"], "coverage": {"entity": "", "geo": "global"}, "description": "Current conditions plus hourly and daily forecast for any latitude/longitude, US units: temperature, highs and lows, rain chance, snow and conditions such as thunderstorms, hail, fog or freezing rain, from this morning and this afternoon through tonight and the week ahead.", "examples": ["weather in Austin", "what's the temperature", "rain chance this weekend", "will it storm tonight", "frost tonight"], "freshness": {"cadence": "hourly"}, "id": "open-meteo-forecast", "kinds": ["forecast", "current_value", "trend"], "name": "Open-Meteo forecast", "notes": "", "origin": {"at": "2026-09-28T04:10:32Z", "by": "bootstrap/core_seed"}, "provider": {"authority": "primary", "id": "open-meteo", "name": "Open-Meteo", "url": "https://open-meteo.com"}, "terms": {"note": "CC BY 4.0; free for non-commercial use; attribution required", "status": "open_license", "terms_url": "https://open-meteo.com/en/terms"}, "tier": "curated", "validation": {"bytes": 5989, "checked_at": "2026-09-29T03:40:12Z", "http": 200, "note": "json", "robots": "allow", "status": "ok"}, "votes": {"no": 0, "yes": 0}} {"access": {"auth": "none", "contact_ua": false, "docs_url": "https://open-meteo.com/en/terms", "headers": {}, "kind": "http_json", "params": [{"example": "32.7765", "fill": {"field": "lat", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lat", "required": true}, {"example": "-79.9311", "fill": {"field": "lon", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lon", "required": true}, {"example": "2026-09-01", "fill": {"format": "%Y-%m-%d", "from": "clock", "offset_days": -30}, "kind": "date", "name": "start", "required": true}, {"example": "2026-09-20", "fill": {"format": "%Y-%m-%d", "from": "clock", "offset_days": 0}, "kind": "date", "name": "end", "required": true}], "url_template": "https://archive-api.open-meteo.com/v1/archive?latitude={lat}&longitude={lon}&start_date={start}&end_date={end}&daily=temperature_2m_max,temperature_2m_min,precipitation_sum&temperature_unit=fahrenheit&precipitation_unit=inch&timezone=auto"}, "categories": ["weather/climate_records"], "coverage": {"entity": "", "geo": "global"}, "description": "Daily historical highs, lows and precipitation for any date range and location.", "examples": ["how hot was last month", "rainfall this month compared to normal"], "freshness": {"cadence": "daily"}, "id": "open-meteo-history", "kinds": ["trend", "lookup"], "name": "Open-Meteo historical weather", "notes": "", "origin": {"at": "2026-09-28T04:10:32Z", "by": "bootstrap/core_seed"}, "provider": {"authority": "primary", "id": "open-meteo", "name": "Open-Meteo", "url": "https://open-meteo.com"}, "terms": {"note": "CC BY 4.0; free for non-commercial use; attribution required", "status": "open_license", "terms_url": "https://open-meteo.com/en/terms"}, "tier": "curated", "validation": {"bytes": 971, "checked_at": "2026-09-28T04:10:40Z", "http": 200, "note": "json", "robots": "disallow", "status": "ok"}, "votes": {"no": 0, "yes": 0}} {"access": {"auth": "none", "contact_ua": false, "docs_url": "https://open-meteo.com/en/terms", "headers": {}, "kind": "http_json", "params": [{"example": "32.7765", "fill": {"field": "lat", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lat", "required": true}, {"example": "-79.80", "fill": {"field": "lon", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lon", "required": true}], "url_template": "https://marine-api.open-meteo.com/v1/marine?latitude={lat}&longitude={lon}¤t=wave_height,wave_direction,wave_period,swell_wave_height,swell_wave_direction,swell_wave_period,sea_surface_temperature&hourly=wave_height&length_unit=imperial&temperature_unit=fahrenheit&timezone=auto"}, "categories": ["water/surf_waves", "water/water_temperature"], "coverage": {"entity": "", "geo": "global", "water": "ocean_coastal"}, "description": "Wave height, direction and period, swell, and sea-surface temperature for a coastal latitude/longitude (ocean and coast only: no data on inland lakes).", "examples": ["surf report Folly Beach", "wave height tomorrow", "swell direction Huntington Beach"], "freshness": {"cadence": "hourly"}, "id": "open-meteo-marine", "kinds": ["current_value", "forecast"], "name": "Open-Meteo marine", "notes": "", "origin": {"at": "2026-09-28T04:10:32Z", "by": "bootstrap/core_seed"}, "provider": {"authority": "primary", "id": "open-meteo", "name": "Open-Meteo", "url": "https://open-meteo.com"}, "terms": {"note": "CC BY 4.0; free for non-commercial use; attribution required", "status": "open_license", "terms_url": "https://open-meteo.com/en/terms"}, "tier": "curated", "validation": {"bytes": 4727, "checked_at": "2026-09-29T03:58:27Z", "http": 200, "note": "json", "robots": "disallow", "status": "ok"}, "votes": {"no": 0, "yes": 0}} {"access": {"auth": "none", "contact_ua": false, "docs_url": "https://open-meteo.com/en/docs", "headers": {}, "kind": "http_json", "params": [{"example": "32.7765", "fill": {"field": "lat", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lat", "required": true}, {"example": "-79.9311", "fill": {"field": "lon", "from": "resolver", "resolver": "place"}, "kind": "place", "name": "lon", "required": true}], "url_template": "https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&daily=sunrise,sunset&timezone=auto&forecast_days=7"}, "categories": ["sky/sun_moon"], "coverage": {"entity": "", "geo": "global"}, "description": "Sunrise and sunset times for a latitude/longitude in the place's own local time, today and the next six days.", "examples": ["what time is sunset", "sunrise tomorrow", "when is sunset in Denver", "sunrise times this week"], "freshness": {"cadence": "daily"}, "id": "open-meteo-sun", "kinds": ["next_event", "current_value"], "name": "Open-Meteo sunrise & sunset", "notes": "", "origin": {"at": "2026-09-29T04:00:00Z", "by": "data-fixes-0929"}, "provider": {"authority": "primary", "id": "open-meteo", "name": "Open-Meteo", "url": "https://open-meteo.com"}, "terms": {"note": "CC BY 4.0; free for non-commercial use; attribution required", "status": "open_license", "terms_url": "https://open-meteo.com/en/terms"}, "tier": "curated", "validation": {"bytes": 663, "checked_at": "2026-09-29T03:40:12Z", "http": 200, "note": "json", "robots": "disallow", "status": "ok"}, "votes": {"no": 0, "yes": 0}} diff --git a/sourcetool/answers.py b/sourcetool/answers.py index 575eef1..aef821a 100644 --- a/sourcetool/answers.py +++ b/sourcetool/answers.py @@ -233,8 +233,8 @@ def answer_problems(answers, params=()) -> list[str]: errs.append(f"{w}: measure must be one of {'/'.join(MEASURES)}") elif kind == "list": rows = a.get("row") - if not isinstance(rows, list) or not 1 <= len(rows) <= 4: - errs.append(f"{w}: row must hold 1-4 fields") + if not isinstance(rows, list) or not 1 <= len(rows) <= 5: + errs.append(f"{w}: row must hold 1-5 fields") else: for j, f in enumerate(rows): errs += _field_problems(f"{w} row[{j}]", f, ROW_KEYS, ROW_TYPES) @@ -245,8 +245,8 @@ def answer_problems(answers, params=()) -> list[str]: errs += _filter_problems(w, a["filter"]) else: cols = a.get("columns") - if not isinstance(cols, list) or not 2 <= len(cols) <= 4: - errs.append(f"{w}: columns must hold 2-4 fields") + if not isinstance(cols, list) or not 2 <= len(cols) <= 5: + errs.append(f"{w}: columns must hold 2-5 fields") else: for j, f in enumerate(cols): errs += _field_problems(f"{w} columns[{j}]", f, COLUMN_KEYS, ROW_TYPES) diff --git a/tests/fixtures/keyword_lint_baseline.json b/tests/fixtures/keyword_lint_baseline.json index 095dfb2..76a9780 100644 --- a/tests/fixtures/keyword_lint_baseline.json +++ b/tests/fixtures/keyword_lint_baseline.json @@ -382,7 +382,6 @@ "water/surf_waves: no curated source filed here speaks to 'swell forecast'", "water/surf_waves: no curated source filed here speaks to 'waist high'", "water/surf_waves: no curated source filed here speaks to 'wave forecast'", - "water/tides: no curated source filed here speaks to 'tide table'", "water/water_levels: no curated source filed here speaks to 'coastal flooding'", "water/water_levels: no curated source filed here speaks to 'flood at high tide'", "water/water_levels: no curated source filed here speaks to 'high tide flooding'", diff --git a/tests/fixtures/records/open-meteo-forecast.json b/tests/fixtures/records/open-meteo-forecast.json index c217c89..3e24693 100644 --- a/tests/fixtures/records/open-meteo-forecast.json +++ b/tests/fixtures/records/open-meteo-forecast.json @@ -31,7 +31,8 @@ "time": "iso8601", "temperature_2m": "°F", "precipitation_probability": "%", - "weather_code": "wmo code" + "weather_code": "wmo code", + "snowfall": "inch" }, "hourly": { "time": [ @@ -713,6 +714,176 @@ 51, 51, 51 + ], + "snowfall": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 ] }, "daily_units": { diff --git a/tests/test_answers.py b/tests/test_answers.py index 8f98c0e..9ea56d3 100644 --- a/tests/test_answers.py +++ b/tests/test_answers.py @@ -89,8 +89,8 @@ def test_paths(): def test_list_rules(): - bad([rows(row=[])], "1-4 fields") - bad([rows(row=[{"path": "a", "label": "A", "type": "text"}] * 5)], "1-4 fields") + bad([rows(row=[])], "1-5 fields") + bad([rows(row=[{"path": "a", "label": "A", "type": "text"}] * 6)], "1-5 fields") bad([rows(row=[{"path": "a", "label": "A", "type": "count"}])], "type must be") bad([rows(row=[{"path": "a", "label": "A", "type": "text", "codes": "wmo_weather"}])], "unknown keys") bad([rows(may_be_empty="yes")], "may_be_empty") @@ -98,8 +98,8 @@ def test_list_rules(): def test_columns_rules(): - bad([cols(columns=cols()["columns"][:1])], "2-4 fields") - bad([cols(columns=cols()["columns"] * 3)], "2-4 fields") + bad([cols(columns=cols()["columns"][:1])], "2-5 fields") + bad([cols(columns=cols()["columns"] * 3)], "2-5 fields") bad([cols(limit=0)], "limit") bad([cols(limit=True)], "limit") c = cols()