xiaohongshu
xiaohongshu
ieasyhydro-sdk
Expert guide for the ieasyhydro-python-sdk library for connecting to iEasyHydro High Frequency (HF) database. Use when: (1) Making API requests to iEasyHydro HF, (2) Retrieving discharge/meteorological data, (3) Debugging 422 or other API errors, (4) Formatting filters for get_data_values_for_sit...
Full skill instructions
SDK repository: https://github.com/hydrosolutions/ieasyhydro-python-sdk
pip install git+https://github.com/hydrosolutions/ieasyhydro-python-sdk
Environment variables:
IEASYHYDROHF_HOST=https://hf.ieasyhydro.org/api/v1/
IEASYHYDROHF_USERNAME=username
IEASYHYDROHF_PASSWORD=password
Initialization options:
from ieasyhydro_sdk.sdk import IEasyHydroHFSDK
# Option 1: From environment variables
sdk = IEasyHydroHFSDK()
# Option 2: Explicit configuration
sdk = IEasyHydroHFSDK(
host='https://hf.ieasyhydro.org/api/v1/',
username='username',
password='password',
)
| Method | Returns | Description |
|---|---|---|
get_discharge_sites() | List[dict] | Discharge stations (manual measurement sites) |
get_virtual_sites() | List[dict] | Virtual/calculated stations |
get_meteo_sites() | List[dict] | Meteorological stations |
get_data_values_for_site(filters) | dict | Time-series data matching filters |
get_norm_for_site(site_code, norm_type, norm_period, automatic) | List[float] | Historical norm values |
discharge_sites = sdk.get_discharge_sites()
virtual_sites = sdk.get_virtual_sites()
meteo_sites = sdk.get_meteo_sites()
{
'id': 96, # Unique internal ID
'site_code': '15054', # Station code (use this for queries)
'official_name': 'Ак-Сай - с.Көк-Сай',
'national_name': '',
'site_type': 'manual', # 'manual' or 'automatic'
'latitude': 42.8746,
'longitude': 74.5698,
'elevation': 0.0,
'country': 'Кыргызстан',
'basin': {
'official_name': 'Иссык-Куль',
'national_name': ''
},
'region': {
'official_name': 'ИССЫК-КУЛЬСКАЯ ОБЛАСТЬ',
'national_name': ''
},
'dangerous_discharge': 100.0,
'historical_discharge_maximum': None,
'historical_discharge_minimum': None,
'bulletin_order': 0,
'enabled_forecasts': {
'daily_forecast': False,
'decadal_forecast': False,
'monthly_forecast': False,
'pentad_forecast': False,
'seasonal_forecast': False
},
# Associations: for virtual/calculated stations only
# Formula: discharge = sum(weight_i * discharge_i) for each association
'associations': [
{
'name': 'Ак-Терек - с.Ак-Терек',
'id': 9,
'uuid': '110e8400-e29b-41d4-a716-446655440000',
'weight': 0.9,
'station_code': '12346'
}
]
}
Note: Meteo sites have enabled_forecasts: None.
filters = {
"site_codes": ["15013", "16159"], # Station codes (strings)
"variable_names": ["WDDA"], # Required: at least one metric
"local_date_time__gte": "2024-01-01T00:00:00Z", # Required: at least one timestamp
"local_date_time__lte": "2024-12-31T23:59:59Z",
}
response = sdk.get_data_values_for_site(filters=filters)
| Parameter | Type | Required | Description |
|---|---|---|---|
site_codes | List[str] | No* | Station codes like ["15013", "16159"] |
site_ids | List[int] | No* | Station IDs (internal database IDs) |
variable_names | List[str] | Yes | Metric codes: WDDA, WDD, WLD, etc. |
local_date_time__gte | str | Yes† | Local timestamp >= (ISO format) |
local_date_time__gt | str | No | Local timestamp > |
local_date_time__lte | str | No | Local timestamp <= |
local_date_time__lt | str | No | Local timestamp < |
local_date_time | str | No | Exact local timestamp match |
utc_date_time__gte | str | No | UTC timestamp >= |
utc_date_time__gt | str | No | UTC timestamp > |
utc_date_time__lte | str | No | UTC timestamp <= |
utc_date_time__lt | str | No | UTC timestamp < |
utc_date_time | str | No | Exact UTC timestamp match |
page | int | No | Page number for pagination |
page_size | int | No | Items per page (max 1000, default 10) |
*Either site_codes or site_ids can be used, but site_codes is recommended.
†At least one timestamp filter is required.
local_date_time should NOT include local timezone - treat it as UTC:
import datetime
# To get 8AM local metrics:
local_date_time = datetime.datetime(2025, 9, 25, 8, 0, tzinfo=datetime.timezone.utc).isoformat()
# Result: "2025-09-25T08:00:00+00:00"
# To get 8PM local metrics:
local_date_time = datetime.datetime(2025, 9, 25, 20, 0, tzinfo=datetime.timezone.utc).isoformat()
{
"count": 42, # Total number of results across all pages
"next": "https://...?page=2", # URL for next page (None if last page)
"previous": None, # URL for previous page
"results": [
{
"station_id": 123,
"station_uuid": "abc-123-def-456",
"station_code": "16159",
"station_name": "Station Name",
"station_type": "hydro",
"data": [
{
"variable_code": "WDDA",
"unit": "m3/s",
"values": [
{
"value": 156.0,
"value_type": "M",
"timestamp_local": "2024-03-01T08:00:00",
"timestamp_utc": "2024-03-01T02:00:00Z",
"value_code": None
}
]
}
]
}
]
}
Response notes:
values: []| Code | Description | Notes |
|---|---|---|
WLD | Water level daily | 8AM or 8PM values from KN-15 telegram |
WLDA | Water level daily average | Calculated from 8AM and 8PM values |
WLDC | Water level decadal | From KN-15 subgroup 966 |
WLDCA | Water level decade average | Decadal average for a period |
WDD | Water discharge daily | Morning reading, may be estimated from rating curve |
WDDA | Water discharge daily average | From daily avg water level + rating curve |
WDFA | Water discharge fiveday average | Pentadal average |
WDDCA | Water discharge decade average | Decadal average |
WTO | Water temperature observation | Daily from KN-15 telegram section 4 |
ATO | Air temperature observation | Daily from KN-15 telegram section 4 |
IPO | Ice phenomena observation | Complex: intensity + value code |
PD | Precipitation daily | Complex: value + duration code |
WTDA | Water temperature daily average | |
ATDA | Air temperature daily average | |
RCSA | River cross section area | From KN-15 subgroup 966 |
| Code | Description | Notes |
|---|---|---|
ATDCA | Air temperature decade average | Manual entry or KN-15 subgroup 988 |
PDCA | Precipitation decade average | Manual entry or KN-15 subgroup 988 |
ATMA | Air temperature monthly average | Manual entry or KN-15 subgroup 988 |
PMA | Precipitation monthly average | Manual entry or KN-15 subgroup 988 |
| Code | Description |
|---|---|
M | Manual measurement |
A | Automatic measurement |
E | Estimated value |
I | Imported value |
U | Unknown source |
O | Override (manually entered by hydrologist) |
The API supports page_size up to 1000 (updated January 2026).
page_size=1000 significantly improves performance (reduced maintenance run from 800s to 364s)def fetch_all_with_pagination(sdk, filters):
"""Fetch all results with pagination."""
all_results = []
page = 1
while True:
paginated_filters = {**filters, "page": page, "page_size": 1000}
response = sdk.get_data_values_for_site(filters=paginated_filters)
# Check for API error
if isinstance(response, dict) and 'status_code' in response:
print(f"Error: {response}")
break
results = response.get('results', [])
all_results.extend(results)
# Check if there are more pages
if not response.get('next'):
break
page += 1
return all_results
For better performance, fetch pages in parallel after getting the count from page 1:
from concurrent.futures import ThreadPoolExecutor, as_completed
def fetch_all_parallel(sdk, filters, max_workers=10):
"""Fetch all results using parallel page requests."""
PAGE_SIZE = 1000
# Step 1: Get first page to learn total count
first_response = sdk.get_data_values_for_site(
filters={**filters, "page": 1, "page_size": PAGE_SIZE}
)
if isinstance(first_response, dict) and 'status_code' in first_response:
return [] # Error
total_count = first_response.get('count', 0)
all_results = first_response.get('results', [])
total_pages = (total_count + PAGE_SIZE - 1) // PAGE_SIZE
if total_pages <= 1:
return all_results
# Step 2: Fetch remaining pages in parallel
def fetch_page(page_num):
resp = sdk.get_data_values_for_site(
filters={**filters, "page": page_num, "page_size": PAGE_SIZE}
)
if isinstance(resp, dict) and 'status_code' not in resp:
return resp.get('results', [])
return []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(fetch_page, p) for p in range(2, total_pages + 1)]
for future in as_completed(futures):
all_results.extend(future.result())
return all_results
Historical norm values for stations:
# Get decadal discharge norm (default)
norm = sdk.get_norm_for_site("11194", "discharge")
# Get monthly discharge norm
norm = sdk.get_norm_for_site("11194", "discharge", norm_period="m")
# Get pentad norms for automatic station
norm = sdk.get_norm_for_site("11194", "discharge", norm_period="p", automatic=True)
# Get monthly temperature norms
norm = sdk.get_norm_for_site("15194", "temperature", norm_period="m", automatic=False)
| Parameter | Values | Description |
|---|---|---|
norm_type | discharge, water_level, precipitation, temperature | Type of norm |
norm_period | d (decadal, default), p (pentad), m (monthly) | Time period |
automatic | True/False | Automatic vs manual station |
Returns a list of float values:
[11.76, 11.69, 11.64, ...] # 36 decadal values
Missing norms are None:
[2.50, None, 3.75, 0.23, None, None, None, 0.45, 0.50, None, None, None]
{
'status_code': 422,
'text': '{"detail": "Some data is invalid or missing", "code": "schema_error"}'
}
| Error | Cause | Solution |
|---|---|---|
| 422 "Some data is invalid" | Missing timestamp filter | Add local_date_time__gte or similar |
| 422 "Some data is invalid" | Missing variable_names | Add variable_names: ["WDDA"] |
| 422 "Invalid metric names" | Wrong variable code | Check metric codes table |
| 422 with large requests | Too many sites + large date range | Use batching or individual requests |
def fetch_robust(sdk, site_codes, base_filters, batch_size=10):
"""Try bulk, then batches, then individual requests."""
# 1. Try bulk request
bulk_filters = {**base_filters, "site_codes": site_codes}
response = sdk.get_data_values_for_site(filters=bulk_filters)
if isinstance(response, dict) and 'status_code' not in response:
return fetch_all_with_pagination(sdk, bulk_filters)
# 2. Try batches
all_results = []
for i in range(0, len(site_codes), batch_size):
batch = site_codes[i:i + batch_size]
batch_filters = {**base_filters, "site_codes": batch}
results = fetch_all_with_pagination(sdk, batch_filters)
all_results.extend(results)
if all_results:
return all_results
# 3. Fall back to individual requests
for code in site_codes:
single_filters = {**base_filters, "site_codes": [code]}
results = fetch_all_with_pagination(sdk, single_filters)
all_results.extend(results)
return all_results
from ieasyhydro_sdk.sdk import IEasyHydroHFSDK
from datetime import datetime, timezone, timedelta
sdk = IEasyHydroHFSDK()
# 1. Get all discharge sites
sites = sdk.get_discharge_sites()
site_codes = list(set(s['site_code'] for s in sites)) # Unique codes
print(f"Found {len(site_codes)} unique discharge sites")
# 2. Define date range
end_date = datetime.now(timezone.utc)
start_date = end_date - timedelta(days=30)
# 3. Build filters
filters = {
"site_codes": site_codes,
"variable_names": ["WDDA"],
"local_date_time__gte": start_date.strftime('%Y-%m-%dT00:00:00Z'),
"local_date_time__lte": end_date.strftime('%Y-%m-%dT23:59:59Z'),
}
# 4. Fetch with pagination
all_results = []
page = 1
while True:
filters['page'] = page
filters['page_size'] = 1000 # Max allowed
response = sdk.get_data_values_for_site(filters=filters)
if isinstance(response, dict) and 'status_code' in response:
print(f"Error: {response}")
break
all_results.extend(response.get('results', []))
if not response.get('next'):
break
page += 1
print(f"Fetched {len(all_results)} station records in {page} pages")
# 5. Process results
for station in all_results:
code = station['station_code']
name = station['station_name']
for var in station.get('data', []):
values = var.get('values', [])
if values:
latest = max(v['timestamp_local'][:10] for v in values)
print(f"{code} ({name}): {len(values)} values, latest: {latest}")
site_codes (strings) not site_ids - both work but codes are more reliablelocal_date_time acts as UTC - don't include local timezone offsetxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience