Skip to main content

KDM (K-water Data Model) SDK for water resource data access

Project description

KDM SDK

Python 3.10+ License: MIT Tests Beta

๐Ÿš€ ๋ฒ ํƒ€ ์˜คํ”ˆ - K-water Data Model (KDM) ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” Python SDK์ž…๋‹ˆ๋‹ค.

K-water Data Model (KDM)์€ water.or.kr/kdm ๊ธฐ๋ฐ˜์˜ ์ˆ˜์ž์› ๋ฐ์ดํ„ฐ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ์ด SDK๋ฅผ ํ†ตํ•ด ๋Œ ์ˆ˜๋ฌธ ๋ฐ์ดํ„ฐ, ํ•˜์ฒœ ์ˆ˜์œ„, ๊ฐ•์šฐ๋Ÿ‰ ๋“ฑ์˜ ์ˆ˜์ž์› ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ์กฐํšŒํ•˜๊ณ  ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

English Documentation

์ฃผ์š” ๊ธฐ๋Šฅ

  • ์ง๊ด€์ ์ธ Query API - ๋ฉ”์„œ๋“œ ์ฒด์ด๋‹์œผ๋กœ ๊ฐ„๋‹จํ•œ ์ฟผ๋ฆฌ ์ž‘์„ฑ
  • ๋ฐฐ์น˜ ์ฟผ๋ฆฌ - ์—ฌ๋Ÿฌ ์‹œ์„ค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ‘๋ ฌ๋กœ ์กฐํšŒํ•˜์—ฌ ์„ฑ๋Šฅ ํ–ฅ์ƒ
  • ์ƒํ•˜๋ฅ˜ ์—ฐ๊ด€ ๋ถ„์„ - ๋Œ ๋ฐฉ๋ฅ˜๋Ÿ‰๊ณผ ํ•˜๋ฅ˜ ์ˆ˜์œ„์˜ ์ƒ๊ด€๊ด€๊ณ„ ๋ถ„์„
  • ๐Ÿ†• ๊ด€์ธก์†Œ ์ž๋™ ํƒ์ƒ‰ - ๋Œ์˜ ์ƒํ•˜๋ฅ˜ ๊ด€์ธก์†Œ ์ž๋™ ๊ฒ€์ƒ‰ (Basin ๋งค์นญ + ์ง€๋ฆฌ ๊ธฐ๋ฐ˜ ๊ฒ€์ƒ‰)
  • ๐Ÿ†• ์›๋ณธ ์‹œ์„ค์ฝ”๋“œ ์ œ๊ณต - K-water, ํ™˜๊ฒฝ๋ถ€ ๋“ฑ ์›์ฒœ ๊ธฐ๊ด€์˜ ์‹œ์„ค์ฝ”๋“œ๋กœ ์™ธ๋ถ€ ์‹œ์Šคํ…œ ์—ฐ๋™
  • ํ…œํ”Œ๋ฆฟ ์‹œ์Šคํ…œ - YAML ๋˜๋Š” Python์œผ๋กœ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฟผ๋ฆฌ ํ…œํ”Œ๋ฆฟ ์ž‘์„ฑ
  • pandas ํ†ตํ•ฉ - ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ DataFrame์œผ๋กœ ์ฆ‰์‹œ ๋ณ€ํ™˜
  • ๊ฐ„ํŽธํ•œ ๋‚ด๋ณด๋‚ด๊ธฐ - Excel, CSV, Parquet, JSON์œผ๋กœ ํ•œ ์ค„์— ์ €์žฅ
  • ์ž๋™ ํด๋ฐฑ - ์‹œ๊ฐ„ ๋‹จ์œ„ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ž๋™์œผ๋กœ ์ผ/์›” ๋‹จ์œ„ ์กฐํšŒ
  • ๋น„๋™๊ธฐ ์ง€์› - async/await ํŒจํ„ด์œผ๋กœ ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ ์กฐํšŒ
  • ํƒ€์ž… ํžŒํŠธ - ์ „์ฒด ์ฝ”๋“œ์— ํƒ€์ž… ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ IDE ์ง€์› ๊ฐ•ํ™”

SDK์˜ ์—ญํ• 

โœ… SDK๊ฐ€ ํ•˜๋Š” ์ผ

  • ๋ฐ์ดํ„ฐ ์กฐํšŒ: KDM ์ˆ˜์ž์› ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ์กฐํšŒ
  • ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜: pandas DataFrame์œผ๋กœ ์ž๋™ ๋ณ€ํ™˜
  • ๋ฐ์ดํ„ฐ ์ €์žฅ: Excel, CSV, Parquet, JSON์œผ๋กœ ํ•œ๊ธ€ ์ธ์ฝ”๋”ฉ ์ง€์›ํ•˜์—ฌ ์ €์žฅ

โŒ SDK๊ฐ€ ํ•˜์ง€ ์•Š๋Š” ์ผ

  • ์‹œ๊ฐํ™”: matplotlib, seaborn, plotly ๋“ฑ ์‚ฌ์šฉ
  • ํ†ต๊ณ„ ๋ถ„์„: pandas, scipy, numpy ๋“ฑ ์‚ฌ์šฉ
  • ๋ฐ์ดํ„ฐ ์ •์ œ: pandas ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ

์ฒ ํ•™: ์ด SDK๋Š” KDM ๋ฐ์ดํ„ฐ๋ฅผ pandas๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ๊นŒ์ง€๋งŒ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์ดํ›„๋Š” ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ฐ์ดํ„ฐ ๋ถ„์„ ๋Šฅ๋ ฅ์„ ํ™œ์šฉํ•˜์„ธ์š”!

examples/analyst_reference.py์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ„์„ ์˜ˆ์ œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

์„ค์น˜

# PyPI์—์„œ ์„ค์น˜ (๊ถŒ์žฅ) โญ
pip install kdm-sdk

# ๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€์šฉ (๋ถ„์„ ๋„๊ตฌ ํฌํ•จ)
pip install kdm-sdk[analyst]

# ๊ฐœ๋ฐœ์ž์šฉ (๊ฐœ๋ฐœ ๋„๊ตฌ ํฌํ•จ)
pip install kdm-sdk[dev]

# ๋˜๋Š” GitHub์—์„œ ์ตœ์‹  ๋ฒ„์ „ ์„ค์น˜
pip install git+https://github.com/kwatermywater/kdm-sdk.git

[analyst] ์˜ต์…˜์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค: pandas, jupyter, matplotlib, seaborn, plotly, openpyxl, pyarrow, scipy, statsmodels

์š”๊ตฌ์‚ฌํ•ญ

  • Python 3.10 ์ด์ƒ
  • KDM MCP Server (์šด์˜ ์„œ๋ฒ„: http://203.237.1.4/mcp/sse)
  • pandas 2.0+

์ฒ˜์Œ ์‚ฌ์šฉํ•˜์‹œ๋‚˜์š”?

๐Ÿ“š ๋ฐ์ดํ„ฐ ๊ฐ€์ด๋“œ ๋ฐ”๋กœ๊ฐ€๊ธฐ - ์ˆ˜์ž์› ๋ฐ์ดํ„ฐ๊ฐ€ ์ฒ˜์Œ์ด์‹  ๋ถ„๋“ค์„ ์œ„ํ•œ ์นœ์ ˆํ•œ ์„ค๋ช…์„œ

๊ฐ€์ด๋“œ ๋‚ด์šฉ:

  • ์‹œ์„ค ์œ ํ˜• (๋Œ, ์ˆ˜์œ„๊ด€์ธก์†Œ, ์šฐ๋Ÿ‰๊ด€์ธก์†Œ ๋“ฑ)
  • ์‹œ๊ฐ„ ๋‹จ์œ„ (์‹œ๊ฐ„๋ณ„, ์ผ๋ณ„, ์›”๋ณ„) ๋ฐ ์กฐํšŒ ๊ธฐ๊ฐ„ ๐Ÿ“…
  • ์ธก์ • ํ•ญ๋ชฉ (์ €์ˆ˜์œจ, ์œ ์ž…๋Ÿ‰, ๋ฐฉ๋ฅ˜๋Ÿ‰ ๋“ฑ) ๐Ÿ“Š
  • ์‹œ์„ค ๊ฒ€์ƒ‰ ๋ฐฉ๋ฒ•
  • ์šฉ์–ด ์„ค๋ช… (์ €์ˆ˜์œ„, CMS, TOC ๋“ฑ)
  • ์ดˆ๋ณด์ž์šฉ ์˜ˆ์ œ

๋น ๋ฅธ ํŒ:

# ๐Ÿ’ก ์–ด๋–ค ๋Œ์ด ์žˆ๋Š”์ง€ ๋ชจ๋ฅผ ๋•Œ
results = await client.search_facilities(query="๋Œ", limit=10)

# ๐Ÿ’ก ์ธก์ • ํ•ญ๋ชฉ์ด ๋ญ๊ฐ€ ์žˆ๋Š”์ง€ ๋ชจ๋ฅผ ๋•Œ
items = await client.list_measurements(site_name="์†Œ์–‘๊ฐ•๋Œ")

# ๐Ÿ’ก ์‹œ๊ฐ„ ๋‹จ์œ„๋ฅผ ๋ชจ๋ฅผ ๋•Œ (์ž๋™ ์„ ํƒ)
result = await KDMQuery().site("์†Œ์–‘๊ฐ•๋Œ").measurements(["์ €์ˆ˜์œจ"]) \
    .days(7).time_key("auto").execute()

ํด๋ผ์ด์–ธํŠธ ์„ ํƒ

ํด๋ผ์ด์–ธํŠธ ๋ฐฉ์‹ ํŠน์ง• ์–ธ์ œ ์‚ฌ์šฉ
MCPClient ๋™๊ธฐ (sync) requests๋งŒ ์‚ฌ์šฉ, ์„ค์ • ๊ฐ„๋‹จ ์Šคํฌ๋ฆฝํŠธ, ๊ฐ„๋‹จํ•œ ์กฐํšŒ
KDMQuery ๋น„๋™๊ธฐ (async) async/await, pandas DataFrame ๋ฐ˜ํ™˜ ๋Œ€๋Ÿ‰ ๋ฐฐ์น˜, ์ƒ๊ด€๊ด€๊ณ„ ๋ถ„์„

์ฒ˜์Œ ์‚ฌ์šฉํ•˜์‹ ๋‹ค๋ฉด MCPClient (๋™๊ธฐ ๋ฐฉ์‹)๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๋น ๋ฅธ ์‹œ์ž‘

MCPClient โ€” ๋™๊ธฐ ๋ฐฉ์‹ (๊ถŒ์žฅ)

from kdm_sdk import create_mcp_client

SERVER = "http://203.237.1.4:8001"

with create_mcp_client(SERVER) as mcp:
    # ๋Œ ์ €์ˆ˜์œจ (์ผ๋ณ„ 7์ผ)
    data = mcp.get_kdm_data("์†Œ์–‘๊ฐ•๋Œ", days=7)
    for row in data["data"]:
        v = row["values"]
        print(f"{row['datetime']}  ์ €์ˆ˜์œจ={v.get('์ €์ˆ˜์œจ',{}).get('value')}%")

    # HDAPS 10๋ถ„ ๋ฐ์ดํ„ฐ (๋‹น์ผ)
    hdaps = mcp.get_hdaps_timeseries("์†Œ์–‘๊ฐ•๋Œ",
                                      start_date="20260623",
                                      end_date="20260623")
    print(f"interval={hdaps['interval']}  rows={hdaps['total_rows']}")
    print(f"์ตœ์‹ : {hdaps['latest']}")

    # ๊ฐ€๋ญ„ ํ˜„ํ™ฉ (์ฃผ์˜ ์ด์ƒ)
    drought = mcp.get_drought_status(threshold="์ฃผ์˜")
    for dam in drought.get("dams", []):
        print(f"{dam['name']:16s}  ์ €์ˆ˜์œจ={dam.get('storage_rate')}%  ๋‹จ๊ณ„={dam['level']}")

    # ํ™์ˆ˜ ํ˜„ํ™ฉ (๊ด€์‹ฌ ์ด์ƒ)
    flood = mcp.get_flood_status(threshold="๊ด€์‹ฌ")

    # ์ˆ˜์œ„๊ด€์ธก์†Œ ์‹œ๊ฐ„๋ณ„
    wl = mcp.get_kdm_data("ํ•œ๊ฐ•(๋…ธ๋Ÿ‰์ง„)", facility_type="water_level",
                           time_key="h_1", days=3)

    # ๋Œ ์ €์ˆ˜์œจ ์ˆœ์œ„
    rankings = mcp.list_dams(query_type="rankings")

KDMQuery โ€” ๋น„๋™๊ธฐ ๋ฐฉ์‹ (pandas ๋ถ„์„)

๊ธฐ๋ณธ ์ฟผ๋ฆฌ (Fluent API)

import asyncio
from kdm_sdk import KDMQuery

async def main():
    # ๋Œ ์ €์ˆ˜์œจ ๋ฐ์ดํ„ฐ ์กฐํšŒ
    result = await KDMQuery() \
        .site("์†Œ์–‘๊ฐ•๋Œ", facility_type="dam") \
        .measurements(["์ €์ˆ˜์œจ", "์œ ์ž…๋Ÿ‰"]) \
        .days(7) \
        .execute()

    # pandas DataFrame์œผ๋กœ ๋ณ€ํ™˜
    df = result.to_dataframe()
    print(df.head())

asyncio.run(main())

๋ฐฐ์น˜ ์ฟผ๋ฆฌ (์—ฌ๋Ÿฌ ์‹œ์„ค ๋™์‹œ ์กฐํšŒ)

from kdm_sdk import KDMQuery

async def batch_query():
    query = KDMQuery()

    # ์—ฌ๋Ÿฌ ๋Œ ์ถ”๊ฐ€
    for dam in ["์†Œ์–‘๊ฐ•๋Œ", "์ถฉ์ฃผ๋Œ", "ํŒ”๋‹น๋Œ"]:
        query.site(dam, facility_type="dam") \
             .measurements(["์ €์ˆ˜์œจ"]) \
             .days(7) \
             .add()

    # ๋ณ‘๋ ฌ ์‹คํ–‰
    results = await query.execute_batch(parallel=True)

    # ๋‹จ์ผ DataFrame์œผ๋กœ ํ†ตํ•ฉ
    combined_df = results.aggregate()
    print(combined_df.groupby("site_name")["์ €์ˆ˜์œจ"].mean())

asyncio.run(batch_query())

์ƒํ•˜๋ฅ˜ ์ƒ๊ด€๊ด€๊ณ„ ๋ถ„์„

from kdm_sdk import FacilityPair

async def correlation_analysis():
    # ๋Œ ๋ฐฉ๋ฅ˜๊ฐ€ ํ•˜๋ฅ˜ ์ˆ˜์œ„์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ ๋ถ„์„
    from kdm_sdk import KDMClient
    import pandas as pd

    async with KDMClient() as client:
        # ์ƒ๋ฅ˜ ๋ฐ์ดํ„ฐ ์กฐํšŒ (๋Œ)
        upstream_result = await client.get_water_data(
            site_name="์†Œ์–‘๊ฐ•๋Œ",
            facility_type="dam",
            measurement_items=["๋ฐฉ๋ฅ˜๋Ÿ‰"],
            days=30,
            time_key="h_1"
        )

        # ํ•˜๋ฅ˜ ๋ฐ์ดํ„ฐ ์กฐํšŒ (์ˆ˜์œ„๊ด€์ธก์†Œ)
        downstream_result = await client.get_water_data(
            site_name="์ถ˜์ฒœ์‹œ(์ฒœ์ „๋ฆฌ)",
            facility_type="water_level",
            measurement_items=["์ˆ˜์œ„"],
            days=30,
            time_key="h_1"
        )

        # DataFrame ๋ณ€ํ™˜
        def to_df(data):
            records = []
            for item in data:
                record = {"datetime": item.get("datetime")}
                if "values" in item:
                    for key, val in item["values"].items():
                        record[key] = val.get("value")
                records.append(record)
            df = pd.DataFrame(records)
            if "datetime" in df.columns:
                df["datetime"] = pd.to_datetime(df["datetime"])
                df.set_index("datetime", inplace=True)
            return df

        upstream_df = to_df(upstream_result.get("data", []))
        downstream_df = to_df(downstream_result.get("data", []))

        # FacilityPair ์ƒ์„ฑ (lag_hours: ๊ธฐ๋ณธ ์‹œ๊ฐ„ ์ง€์—ฐ๊ฐ’ ์„ค์ • ๊ฐ€๋Šฅ)
        pair = FacilityPair(
            upstream_name="์†Œ์–‘๊ฐ•๋Œ",
            downstream_name="์ถ˜์ฒœ์‹œ(์ฒœ์ „๋ฆฌ)",
            upstream_type="dam",
            downstream_type="water_level",
            upstream_data=upstream_df,
            downstream_data=downstream_df,
            lag_hours=6.0  # ์„ ํƒ: ๊ธฐ๋ณธ ์‹œ๊ฐ„ ์ง€์—ฐ๊ฐ’ (to_dataframe()์—์„œ ์ž๋™ ์‚ฌ์šฉ)
        )

        # ์ตœ์  ์‹œ๊ฐ„์ฐจ ์ฐพ๊ธฐ (๋˜๋Š” ์œ„์—์„œ ์„ค์ •ํ•œ lag_hours ์‚ฌ์šฉ)
        correlation = pair.find_optimal_lag(max_lag_hours=12)
        print(f"์ตœ์  ์‹œ๊ฐ„์ฐจ: {correlation.lag_hours:.1f}์‹œ๊ฐ„")
        print(f"์ƒ๊ด€๊ณ„์ˆ˜: {correlation.correlation:.3f}")

asyncio.run(correlation_analysis())

ํ…œํ”Œ๋ฆฟ ๊ธฐ๋ฐ˜ ์ฟผ๋ฆฌ

from kdm_sdk.templates import TemplateBuilder

async def template_query():
    # ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ
    template = TemplateBuilder("์ฃผ๊ฐ„ ๋Œ ๋ชจ๋‹ˆํ„ฐ๋ง") \
        .site("์†Œ์–‘๊ฐ•๋Œ", facility_type="dam") \
        .measurements(["์ €์ˆ˜์œจ", "์œ ์ž…๋Ÿ‰", "๋ฐฉ๋ฅ˜๋Ÿ‰"]) \
        .days(7) \
        .time_key("h_1") \
        .build()

    # ํ…œํ”Œ๋ฆฟ ์‹คํ–‰
    result = await template.execute()
    df = result.to_dataframe()

    # ํ…œํ”Œ๋ฆฟ ์ €์žฅํ•˜์—ฌ ์žฌ์‚ฌ์šฉ
    template.save_yaml("templates/weekly_monitoring.yaml")
    # ๋˜๋Š” ๊ฐ„๋‹จํžˆ: template.save("weekly_monitoring.yaml")

asyncio.run(template_query())

ํ…œํ”Œ๋ฆฟ: ์ƒ๋ฅ˜-ํ•˜๋ฅ˜ ํŽ˜์–ด ๋ถ„์„

from kdm_sdk.templates import TemplateBuilder

async def pair_template():
    # add_pair()๋กœ ์ƒ๋ฅ˜-ํ•˜๋ฅ˜ ํŽ˜์–ด ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ
    template = TemplateBuilder("์†Œ์–‘๊ฐ•๋Œ ํ•˜๋ฅ˜ ์˜ํ–ฅ ๋ถ„์„") \
        .add_pair(
            upstream_name="์†Œ์–‘๊ฐ•๋Œ",
            downstream_name="์ถ˜์ฒœ์‹œ(์ฒœ์ „๋ฆฌ)",
            upstream_type="dam",
            downstream_type="water_level",
            upstream_measurements=["๋ฐฉ๋ฅ˜๋Ÿ‰"],
            downstream_measurements=["์ˆ˜์œ„"],
            lag_hours=6.0  # ์‹œ๊ฐ„ ์ง€์—ฐ๊ฐ’
        ) \
        .days(30) \
        .build()

    # ์‹คํ–‰ - FacilityPair ๋ฐ˜ํ™˜
    pair = await template.execute()

    # to_dataframe()์—์„œ lag_hours ์ž๋™ ์ ์šฉ
    df = pair.to_dataframe()
    print(df.head())

asyncio.run(pair_template())

๊ด€์ธก์†Œ ์ž๋™ ํƒ์ƒ‰ (์‹ ๊ทœ ๊ธฐ๋Šฅ)

from kdm_sdk import KDMClient

async def find_stations():
    async with KDMClient() as client:
        # ๋Œ์˜ ํ•˜๋ฅ˜ ์ˆ˜์œ„๊ด€์ธก์†Œ ์ž๋™ ๊ฒ€์ƒ‰
        result = await client.find_related_stations(
            dam_name="์†Œ์–‘๊ฐ•๋Œ",
            direction="downstream",
            station_type="water_level"
        )

        # ๋Œ ์ •๋ณด (์›๋ณธ ์‹œ์„ค์ฝ”๋“œ ํฌํ•จ)
        dam = result['dam']
        print(f"๋Œ: {dam['site_name']}")
        print(f"์›๋ณธ์ฝ”๋“œ: {dam['original_facility_code']}")  # K-water ์ฝ”๋“œ

        # ๊ด€๋ จ ๊ด€์ธก์†Œ ๋ชฉ๋ก
        for station in result['stations']:
            print(f"- {station['site_name']}: {station['original_facility_code']}")
            print(f"  ๋งค์นญ๋ฐฉ์‹: {station['match_type']}")  # network, basin, or geographic

asyncio.run(find_stations())

โœ… v0.2.2 ๊ฐœ์„ ์‚ฌํ•ญ: ๋ฌผํ๋ฆ„ ๋„คํŠธ์›Œํฌ ๊ทธ๋ž˜ํ”„ ๊ธฐ๋ฐ˜ ๊ฒ€์ƒ‰์œผ๋กœ ์ •ํ™•๋„ ๋Œ€ํญ ํ–ฅ์ƒ

  • ํ•˜๋ฅ˜(downstream) ๊ฒ€์ƒ‰: โœ… ์†Œ์–‘๊ฐ•๋Œ 10๊ฐœ, ํŒ”๋‹น๋Œ 10๊ฐœ (์ด์ „ 3๊ฐœ, 1๊ฐœ)
  • ์ƒ๋ฅ˜(upstream) ๊ฒ€์ƒ‰: โš ๏ธ MCP ์„œ๋ฒ„ ์—…๋ฐ์ดํŠธ ๋Œ€๊ธฐ ์ค‘ (ํ˜„์žฌ legacy fallback ์‚ฌ์šฉ)
  • match_type: "network" ํ•„๋“œ๋กœ ๊ฒฐ๊ณผ ์ถœ์ฒ˜ ํ™•์ธ ๊ฐ€๋Šฅ

๋ถ„์„๊ฐ€์šฉ ์›Œํฌํ”Œ๋กœ์šฐ: ํ•˜๋ฅ˜ ์˜ํ–ฅ ๋ถ„์„

๋Œ ๋ฐฉ๋ฅ˜๊ฐ€ ํ•˜๋ฅ˜ ์ˆ˜์œ„์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์„ ๋ถ„์„ํ•˜๋Š” ์ „์ฒด ์›Œํฌํ”Œ๋กœ์šฐ์ž…๋‹ˆ๋‹ค.

import asyncio
import pandas as pd
from kdm_sdk import KDMClient, FacilityPair

async def main():
    async with KDMClient() as client:
        # 1. ํ•˜๋ฅ˜ ๊ด€์ธก์†Œ ์ž๋™ ํƒ์ƒ‰
        result = await client.find_related_stations(
            dam_name="์†Œ์–‘๊ฐ•๋Œ",
            direction="downstream",
            limit=5
        )
        downstream_station = result["stations"][0]  # ์ฒซ ๋ฒˆ์งธ ๊ด€์ธก์†Œ ์„ ํƒ

        # 2. ๋Œ ๋ฐฉ๋ฅ˜๋Ÿ‰ + ํ•˜๋ฅ˜ ์ˆ˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ
        upstream_result = await client.get_water_data(
            site_name="์†Œ์–‘๊ฐ•๋Œ",
            facility_type="dam",
            measurement_items=["๋ฐฉ๋ฅ˜๋Ÿ‰"],
            days=30, time_key="h_1"
        )
        downstream_result = await client.get_water_data(
            site_name=downstream_station["site_name"],
            facility_type="water_level",
            measurement_items=["์ˆ˜์œ„"],
            days=30, time_key="h_1"
        )

        # 3. DataFrame ๋ณ€ํ™˜
        def to_df(data):
            records = []
            for item in data.get("data", []):
                record = {"datetime": item.get("datetime")}
                for key, val in item.get("values", {}).items():
                    record[key] = val.get("value")
                records.append(record)
            df = pd.DataFrame(records)
            df["datetime"] = pd.to_datetime(df["datetime"])
            return df.set_index("datetime")

        upstream_df = to_df(upstream_result)
        downstream_df = to_df(downstream_result)

        # 4. ์ตœ์  ์‹œ๊ฐ„์ฐจ(lag) ๋ถ„์„
        pair = FacilityPair(
            upstream_name="์†Œ์–‘๊ฐ•๋Œ",
            downstream_name=downstream_station["site_name"],
            upstream_data=upstream_df,
            downstream_data=downstream_df
        )
        correlation = pair.find_optimal_lag(max_lag_hours=12)
        print(f"์ตœ์  ์‹œ๊ฐ„์ฐจ: {correlation.lag_hours:.1f}์‹œ๊ฐ„")
        print(f"์ƒ๊ด€๊ณ„์ˆ˜: {correlation.correlation:.3f}")

        # 5. ์‹œ๊ฐ„์ฐจ ์ ์šฉ๋œ DataFrame ์ €์žฅ
        aligned_df = pair.to_dataframe(lag_hours=correlation.lag_hours)
        aligned_df.to_csv("analysis_result.csv", encoding="utf-8-sig")

asyncio.run(main())

์‹คํ–‰ ๊ฒฐ๊ณผ:

์ตœ์  ์‹œ๊ฐ„์ฐจ: 2.0์‹œ๊ฐ„
์ƒ๊ด€๊ณ„์ˆ˜: 0.847

ํ•ด์„: ์†Œ์–‘๊ฐ•๋Œ์—์„œ ๋ฐฉ๋ฅ˜ํ•˜๋ฉด ์•ฝ 2์‹œ๊ฐ„ ํ›„ ์ถ˜์ฒœ์‹œ(์ฒœ์ „๋ฆฌ)์—์„œ ์ˆ˜์œ„ ๋ณ€ํ™”๊ฐ€ ๊ด€์ธก๋ฉ๋‹ˆ๋‹ค.

๐Ÿ“ ์ „์ฒด ์ฝ”๋“œ: examples/downstream_analysis.py

๋ฌธ์„œ

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

kdm-sdk/
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ kdm_sdk/
โ”‚       โ”œโ”€โ”€ __init__.py           # ํŒจํ‚ค์ง€ exports
โ”‚       โ”œโ”€โ”€ client.py             # MCP ํด๋ผ์ด์–ธํŠธ
โ”‚       โ”œโ”€โ”€ query.py              # Fluent query API
โ”‚       โ”œโ”€โ”€ results.py            # ๊ฒฐ๊ณผ ๋ž˜ํผ
โ”‚       โ”œโ”€โ”€ facilities.py         # FacilityPair
โ”‚       โ””โ”€โ”€ templates/            # ํ…œํ”Œ๋ฆฟ ์‹œ์Šคํ…œ
โ”‚           โ”œโ”€โ”€ builder.py        # TemplateBuilder
โ”‚           โ”œโ”€โ”€ base.py           # Template ๊ธฐ๋ณธ ํด๋ž˜์Šค
โ”‚           โ””โ”€โ”€ loaders.py        # YAML/Python ๋กœ๋”
โ”œโ”€โ”€ tests/                        # ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ
โ”œโ”€โ”€ examples/                     # ์‚ฌ์šฉ ์˜ˆ์ œ
โ”‚   โ”œโ”€โ”€ basic_usage.py           # KDMClient ์˜ˆ์ œ
โ”‚   โ”œโ”€โ”€ query_usage.py           # Query API ์˜ˆ์ œ
โ”‚   โ”œโ”€โ”€ facility_pair_usage.py   # FacilityPair ์˜ˆ์ œ
โ”‚   โ””โ”€โ”€ templates/               # ํ…œํ”Œ๋ฆฟ ์˜ˆ์ œ
โ”œโ”€โ”€ docs/                         # ๋ฌธ์„œ
โ””โ”€โ”€ README.md                     # ์ด ํŒŒ์ผ

์˜ˆ์ œ

examples/ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ์ „์ฒด ์˜ˆ์ œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”:

ํ…Œ์ŠคํŠธ

# ์ „์ฒด ํ…Œ์ŠคํŠธ ์‹คํ–‰
pytest

# ํŠน์ • ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ ์‹คํ–‰
pytest tests/test_query.py -v

# ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ •
pytest --cov=kdm_sdk --cov-report=html

# ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰
pytest -m unit

# ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์‹คํ–‰ (MCP ์„œ๋ฒ„ ํ•„์š”)
pytest -m integration

์ฃผ์š” ์‚ฌ์šฉ ์‚ฌ๋ก€

1. ์—ฌ๋Ÿฌ ๋Œ ๋ชจ๋‹ˆํ„ฐ๋ง

query = KDMQuery()
for dam in ["์†Œ์–‘๊ฐ•๋Œ", "์ถฉ์ฃผ๋Œ", "ํŒ”๋‹น๋Œ", "๋Œ€์ฒญ๋Œ"]:
    query.site(dam).measurements(["์ €์ˆ˜์œจ"]).days(30).add()

results = await query.execute_batch(parallel=True)
df = results.aggregate()

2. ์ „๋…„ ๋Œ€๋น„ ๋น„๊ต

result = await KDMQuery() \
    .site("์žฅํฅ๋Œ") \
    .measurements(["์ €์ˆ˜์œจ"]) \
    .date_range("2024-06-01", "2024-06-30") \
    .compare_with_previous_year() \
    .execute()

3. ํ•˜๋ฅ˜ ์ˆ˜์œ„ ์˜ˆ์ธก

from kdm_sdk import KDMClient, FacilityPair
import pandas as pd

async with KDMClient() as client:
    # ์ƒ๋ฅ˜ ๋ฐ์ดํ„ฐ (๋Œ)
    upstream_result = await client.get_water_data(
        site_name="์†Œ์–‘๊ฐ•๋Œ",
        facility_type="dam",
        measurement_items=["๋ฐฉ๋ฅ˜๋Ÿ‰"],
        days=365,
        time_key="h_1"
    )

    # ํ•˜๋ฅ˜ ๋ฐ์ดํ„ฐ (๋Œ)
    downstream_result = await client.get_water_data(
        site_name="์˜์•”๋Œ",
        facility_type="dam",
        measurement_items=["์ˆ˜์œ„"],
        days=365,
        time_key="h_1"
    )

    # DataFrame ๋ณ€ํ™˜
    def to_df(data):
        records = []
        for item in data:
            record = {"datetime": item.get("datetime")}
            if "values" in item:
                for key, val in item["values"].items():
                    record[key] = val.get("value")
            records.append(record)
        df = pd.DataFrame(records)
        if "datetime" in df.columns:
            df["datetime"] = pd.to_datetime(df["datetime"])
            df.set_index("datetime", inplace=True)
        return df

    upstream_df = to_df(upstream_result.get("data", []))
    downstream_df = to_df(downstream_result.get("data", []))

    # FacilityPair ์ƒ์„ฑ
    pair = FacilityPair(
        upstream_name="์†Œ์–‘๊ฐ•๋Œ",
        downstream_name="์˜์•”๋Œ",
        upstream_data=upstream_df,
        downstream_data=downstream_df
    )

    # ์‹œ๊ฐ„์ฐจ๋ฅผ ๊ณ ๋ คํ•˜์—ฌ DataFrame ์ƒ์„ฑ (๋ฌผ์ด ์ด๋™ํ•˜๋Š”๋ฐ 5.5์‹œ๊ฐ„ ์†Œ์š”)
    df = pair.to_dataframe(lag_hours=5.5)

    # ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ ํ•™์Šต์— ์‚ฌ์šฉ
    X = df[["์†Œ์–‘๊ฐ•๋Œ_๋ฐฉ๋ฅ˜๋Ÿ‰"]]
    y = df["์˜์•”๋Œ_์ˆ˜์œ„"]

๊ฐœ๋ฐœ

ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ (TDD)

์ด ํ”„๋กœ์ ํŠธ๋Š” TDD ๋ฐฉ๋ฒ•๋ก ์œผ๋กœ ๊ฐœ๋ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค:

  1. Red - ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ ๋จผ์ € ์ž‘์„ฑ
  2. Green - ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•˜๋Š” ์ตœ์†Œํ•œ์˜ ์ฝ”๋“œ ๊ตฌํ˜„
  3. Refactor - ์ฝ”๋“œ ํ’ˆ์งˆ ๊ฐœ์„ 

ํ…Œ์ŠคํŠธ ์‹คํ–‰

# ๊ฐœ๋ฐœ ์˜์กด์„ฑ ์„ค์น˜
pip install -r requirements-dev.txt

# ํ…Œ์ŠคํŠธ ์‹คํ–‰
pytest -v

# ์ฝ”๋“œ ํฌ๋งทํŒ…
black src tests

# ํƒ€์ž… ์ฒดํฌ
mypy src

๊ธฐ์—ฌํ•˜๊ธฐ

๊ธฐ์—ฌ๋ฅผ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! PR ์ œ์ถœ ์ „ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๋Š”์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.

  1. ์ €์žฅ์†Œ ํฌํฌ
  2. ๊ธฐ๋Šฅ ๋ธŒ๋žœ์น˜ ์ƒ์„ฑ
  3. ์ƒˆ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€
  4. ๋ชจ๋“  ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ํ™•์ธ: pytest
  5. ์ฝ”๋“œ ํฌ๋งทํŒ…: black src tests
  6. Pull Request ์ œ์ถœ

๋ผ์ด์„ ์Šค

MIT License - ์ž์„ธํ•œ ๋‚ด์šฉ์€ LICENSE ํŒŒ์ผ์„ ์ฐธ์กฐํ•˜์„ธ์š”.

์ง€์›

๋ฌธ์˜์‚ฌํ•ญ ๋ฐ ์ด์Šˆ:

  • ์ €์žฅ์†Œ์— ์ด์Šˆ ์ƒ์„ฑ
  • ๋ฐ์ดํ„ฐ ๊ฐ€์ด๋“œ๋Š” DATA_GUIDE.md ์ฐธ์กฐ
  • ์‚ฌ์šฉ ํŒจํ„ด์€ ์˜ˆ์ œ ํ™•์ธ

๋ณ€๊ฒฝ ์ด๋ ฅ

๋ฒ„์ „ ํžˆ์Šคํ† ๋ฆฌ๋Š” CHANGELOG.md๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

๊ฐ์‚ฌ์˜ ๊ธ€

  • K-water์˜ ํ•œ๊ตญ ๋Œ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ์œ„ํ•ด ๊ฐœ๋ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค
  • ๋ฐ์ดํ„ฐ ์ ‘๊ทผ์„ ์œ„ํ•ด MCP (Model Context Protocol) ์‚ฌ์šฉ
  • ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ(TDD) ๋ฐฉ๋ฒ•๋ก ์œผ๋กœ ๊ฐœ๋ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค

๋ฒ ํƒ€ ์˜คํ”ˆ ์•ˆ๋‚ด

โš ๏ธ ํ˜„์žฌ ๋ฒ ํƒ€ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค.

์ด SDK๋Š” ๋ฒ ํƒ€ ํ…Œ์ŠคํŠธ ๋‹จ๊ณ„์— ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์ถฉ๋ถ„ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด์ฃผ์„ธ์š”.

์•Œ๋ ค์ง„ ์ œํ•œ์‚ฌํ•ญ:

  • ์ผ๋ถ€ ์ธก์ • ํ•ญ๋ชฉ์€ ๋ฐ์ดํ„ฐ ๊ฐ€์šฉ์„ฑ์— ๋”ฐ๋ผ ์กฐํšŒ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • MCP ์„œ๋ฒ„ ์‘๋‹ต ์‹œ๊ฐ„์€ ๋„คํŠธ์›Œํฌ ์ƒํƒœ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

ํ”ผ๋“œ๋ฐฑ:

  • GitHub Issues๋ฅผ ํ†ตํ•ด ๋ฒ„๊ทธ ๋ฆฌํฌํŠธ ๋ฐ ๊ธฐ๋Šฅ ์ œ์•ˆ์„ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค
  • ๋ฒ ํƒ€ ํ…Œ์Šคํ„ฐ๋ถ„๋“ค์˜ ํ”ผ๋“œ๋ฐฑ์ด SDK ๊ฐœ์„ ์— ํฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค

๋ฌธ์˜: GitHub Issues ๋˜๋Š” K-water ๋‹ด๋‹น์ž์—๊ฒŒ ์—ฐ๋ฝํ•ด์ฃผ์„ธ์š”.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kdm_sdk-0.3.2.tar.gz (102.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kdm_sdk-0.3.2-py3-none-any.whl (41.1 kB view details)

Uploaded Python 3

File details

Details for the file kdm_sdk-0.3.2.tar.gz.

File metadata

  • Download URL: kdm_sdk-0.3.2.tar.gz
  • Upload date:
  • Size: 102.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.7

File hashes

Hashes for kdm_sdk-0.3.2.tar.gz
Algorithm Hash digest
SHA256 06de25db85067ba15cc471ebcc210d82f671c9ca98ce58d5e97830d89fbc553c
MD5 972a0956fe75589a01fb0aef151e8f05
BLAKE2b-256 dc2bcccbc95b04958f18b9982a851ee391571196bb5c9a1946570b21c057255b

See more details on using hashes here.

File details

Details for the file kdm_sdk-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: kdm_sdk-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 41.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.7

File hashes

Hashes for kdm_sdk-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 6071ac0e46b995613ca00e5466b53ca5eb73979a7025229289e714756a8c5bfc
MD5 b3f209d716f9dd97918408d500421de1
BLAKE2b-256 ea72b186bb23d4a597a0a9ba7240f5828e3890fcb2184d9f8995be58fe98900d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page