hossam 0.3.15__py3-none-any.whl → 0.3.17__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hossam/hs_stats.py CHANGED
@@ -6,7 +6,7 @@ import numpy as np
6
6
  from typing import Tuple
7
7
  from itertools import combinations
8
8
  from pandas import DataFrame, Series, concat
9
- from pandas.api.types import is_bool_dtype
9
+ from pandas.api.types import is_bool_dtype, is_numeric_dtype
10
10
 
11
11
  from sklearn.metrics import (
12
12
  confusion_matrix,
@@ -274,7 +274,7 @@ def category_table(data: DataFrame, *fields: str):
274
274
  # ===================================================================
275
275
  # 범주형 변수 요약 (Categorical Variable Summary)
276
276
  # ===================================================================
277
- def category_summary(data: DataFrame, *fields: str):
277
+ def category_describe(data: DataFrame, *fields: str):
278
278
  """데이터프레임의 명목형(범주형) 변수에 대한 분포 편향을 요약한다.
279
279
 
280
280
  각 명목형 컬럼의 최다 범주와 최소 범주의 정보를 요약하여 데이터프레임으로 반환한다.
@@ -296,19 +296,19 @@ def category_summary(data: DataFrame, *fields: str):
296
296
  Examples:
297
297
  전체 명목형 컬럼에 대한 분포 편향 요약:
298
298
 
299
- >>> from hossam import category_summary
299
+ >>> from hossam import category_describe
300
300
  >>> import pandas as pd
301
301
  >>> df = pd.DataFrame({
302
302
  ... 'cut': ['Ideal', 'Premium', 'Good', 'Ideal', 'Premium'],
303
303
  ... 'color': ['E', 'F', 'G', 'E', 'F'],
304
304
  ... 'price': [100, 200, 150, 300, 120]
305
305
  ... })
306
- >>> result = category_summary(df)
306
+ >>> result = category_describe(df)
307
307
  >>> print(result)
308
308
 
309
309
  특정 컬럼만 분석:
310
310
 
311
- >>> result = category_summary(df, 'cut', 'color')
311
+ >>> result = category_describe(df, 'cut', 'color')
312
312
  >>> print(result)
313
313
 
314
314
  Notes:
@@ -360,6 +360,16 @@ def category_summary(data: DataFrame, *fields: str):
360
360
 
361
361
  return DataFrame(result)
362
362
 
363
+ # -------------------------------------------------------------------
364
+ # Backward-compatibility alias for categorical summary
365
+ # 기존 함수명(category_summary)을 계속 지원합니다.
366
+ def category_summary(data: DataFrame, *fields: str):
367
+ """Deprecated alias for category_describe.
368
+
369
+ 기존 코드 호환을 위해 유지됩니다. 내부적으로 category_describe를 호출합니다.
370
+ """
371
+ return category_describe(data, *fields)
372
+
363
373
  # ===================================================================
364
374
  # 정규성 검정 (Normal Test)
365
375
  # ===================================================================
@@ -443,7 +453,7 @@ def normal_test(data: DataFrame, columns: list | str | None = None, method: str
443
453
  if method == "n":
444
454
  method_name = "normaltest"
445
455
  s, p = normaltest(col_data)
446
- else: # method == "s"
456
+ else:
447
457
  method_name = "shapiro"
448
458
  s, p = shapiro(col_data)
449
459
 
@@ -1028,6 +1038,16 @@ def vif_filter(
1028
1038
 
1029
1039
  return result
1030
1040
 
1041
+ # -------------------------------------------------------------------
1042
+ # Backward-compatibility alias for describe (typo support)
1043
+ # 오타(discribe)로 사용된 경우를 지원하여 혼란을 줄입니다.
1044
+ def discribe(data: DataFrame, *fields: str, columns: list = None):
1045
+ """Deprecated alias for describe.
1046
+
1047
+ 내부적으로 describe를 호출합니다.
1048
+ """
1049
+ return describe(data, *fields, columns=columns)
1050
+
1031
1051
 
1032
1052
  # ===================================================================
1033
1053
  # x, y 데이터에 대한 추세선을 구한다.
@@ -1298,14 +1318,14 @@ def ols(df: DataFrame, yname: str, report=False):
1298
1318
  ... 'x2': np.random.normal(0, 1, 100)
1299
1319
  ... })
1300
1320
  >>> # 적합 결과만 반환
1301
- >>> fit = hs_linear(df, 'target')
1321
+ >>> fit = hs_ols(df, 'target')
1302
1322
  >>> print(fit.summary())
1303
1323
 
1304
1324
  >>> # 요약 리포트 반환
1305
- >>> fit, rdf, result_report, model_report, var_reports, eq = hs_linear(df, 'target', report=1)
1325
+ >>> fit, result, features = hs_ols(df, 'target', report=1)
1306
1326
 
1307
1327
  >>> # 풀 리포트 반환
1308
- >>> fit, pdf, rdf, result_report, model_report, var_reports, eq = hs_linear(df, 'target', report=2)
1328
+ >>> fit, pdf, rdf, result_report, model_report, var_reports, eq = hs_ols(df, 'target', report=2)
1309
1329
  """
1310
1330
  x = df.drop(yname, axis=1)
1311
1331
  y = df[yname]
@@ -1320,8 +1340,8 @@ def ols(df: DataFrame, yname: str, report=False):
1320
1340
  return linear_fit
1321
1341
  elif report == 1 or report == 'summary':
1322
1342
  # 요약 리포트 (full=False)
1323
- result = ols_report(linear_fit, df, full=False, alpha=0.05)
1324
- return linear_fit, result
1343
+ pdf, rdf, result_report, model_report, variable_reports, equation_text = ols_report(linear_fit, df, full=True, alpha=0.05)
1344
+ return linear_fit, pdf, rdf
1325
1345
  elif report == 2 or report == 'full' or report is True:
1326
1346
  # 풀 리포트 (full=True)
1327
1347
  pdf, rdf, result_report, model_report, variable_reports, equation_text = ols_report(linear_fit, df, full=True, alpha=0.05)
@@ -2094,7 +2114,7 @@ def corr_pairwise(
2094
2114
  cols = data.select_dtypes(include=[np.number]).columns.tolist()
2095
2115
  else:
2096
2116
  # fields 리스트에서 데이터에 있는 것만 선택하되, 숫자형만 필터링
2097
- cols = [c for c in fields if c in data.columns and pd.api.types.is_numeric_dtype(data[c])]
2117
+ cols = [c for c in fields if c in data.columns and is_numeric_dtype(data[c])]
2098
2118
 
2099
2119
  # 사용 가능한 컬럼이 2개 미만이면 상관분석 불가능
2100
2120
  if len(cols) < 2:
@@ -2702,3 +2722,271 @@ def predict(fit, data: DataFrame | Series) -> DataFrame | Series | float:
2702
2722
  f"모형 학습 시 사용한 특성과 입력 데이터의 특성이 일치하는지 확인하세요.\n"
2703
2723
  f"원본 오류: {str(e)}"
2704
2724
  )
2725
+
2726
+
2727
+ # ===================================================================
2728
+ # 확장된 기술통계량 (Extended Descriptive Statistics)
2729
+ # ===================================================================
2730
+ def describe(data: DataFrame, *fields: str, columns: list = None):
2731
+ """데이터프레임의 연속형 변수에 대한 확장된 기술통계량을 반환한다.
2732
+
2733
+ 각 연속형(숫자형) 컬럼의 기술통계량(describe)을 구하고, 이에 사분위수 범위(IQR),
2734
+ 이상치 경계값(UP, DOWN), 왜도(skew), 이상치 개수 및 비율, 분포 특성, 로그변환 필요성을
2735
+ 추가하여 반환한다.
2736
+
2737
+ Args:
2738
+ data (DataFrame): 분석 대상 데이터프레임.
2739
+ *fields (str): 분석할 컬럼명 목록. 지정하지 않으면 모든 숫자형 컬럼을 처리.
2740
+ columns (list, optional): 반환할 통계량 컬럼 목록. None이면 모든 통계량 반환.
2741
+
2742
+ Returns:
2743
+ DataFrame: 각 필드별 확장된 기술통계량을 포함한 데이터프레임.
2744
+ 행은 다음과 같은 통계량을 포함:
2745
+
2746
+ - count (float): 비결측치의 수
2747
+ - mean (float): 평균값
2748
+ - std (float): 표준편차
2749
+ - min (float): 최소값
2750
+ - 25% (float): 제1사분위수 (Q1)
2751
+ - 50% (float): 제2사분위수 (중앙값)
2752
+ - 75% (float): 제3사분위수 (Q3)
2753
+ - max (float): 최대값
2754
+ - iqr (float): 사분위 범위 (Q3 - Q1)
2755
+ - up (float): 이상치 상한 경계값 (Q3 + 1.5 * IQR)
2756
+ - down (float): 이상치 하한 경계값 (Q1 - 1.5 * IQR)
2757
+ - skew (float): 왜도
2758
+ - outlier_count (int): 이상치 개수
2759
+ - outlier_rate (float): 이상치 비율(%)
2760
+ - dist (str): 분포 특성 ("극단 우측 꼬리", "거의 대칭" 등)
2761
+ - log_need (str): 로그변환 필요성 ("높음", "중간", "낮음")
2762
+
2763
+ Examples:
2764
+ 전체 숫자형 컬럼에 대한 확장된 기술통계:
2765
+
2766
+ >>> from hossam import summary
2767
+ >>> import pandas as pd
2768
+ >>> df = pd.DataFrame({
2769
+ ... 'x': [1, 2, 3, 4, 5, 100],
2770
+ ... 'y': [10, 20, 30, 40, 50, 60],
2771
+ ... 'z': ['a', 'b', 'c', 'd', 'e', 'f']
2772
+ ... })
2773
+ >>> result = summary(df)
2774
+ >>> print(result)
2775
+
2776
+ 특정 컬럼만 분석:
2777
+
2778
+ >>> result = summary(df, 'x', 'y')
2779
+ >>> print(result)
2780
+
2781
+ Notes:
2782
+ - 숫자형이 아닌 컬럼은 자동으로 제외됩니다.
2783
+ - 결과는 필드(컬럼)가 행으로, 통계량이 열로 구성됩니다.
2784
+ - Tukey의 1.5 * IQR 규칙을 사용하여 이상치를 판정합니다.
2785
+ - 분포 특성은 왜도 값으로 판정합니다.
2786
+ - 로그변환 필요성은 왜도의 절댓값 크기로 판정합니다.
2787
+ """
2788
+ if not fields:
2789
+ fields = data.select_dtypes(include=['int', 'int32', 'int64', 'float', 'float32', 'float64']).columns
2790
+
2791
+ # 기술통계량 구하기
2792
+ desc = data[list(fields)].describe().T
2793
+
2794
+ # 추가 통계량 계산
2795
+ additional_stats = []
2796
+ for f in fields:
2797
+ # 숫자 타입이 아니라면 건너뜀
2798
+ if data[f].dtype not in [
2799
+ 'int',
2800
+ 'int32',
2801
+ 'int64',
2802
+ 'float',
2803
+ 'float32',
2804
+ 'float64',
2805
+ 'int64',
2806
+ 'float64',
2807
+ 'float32'
2808
+ ]:
2809
+ continue
2810
+
2811
+ # 사분위수
2812
+ q1 = data[f].quantile(q=0.25)
2813
+ q3 = data[f].quantile(q=0.75)
2814
+
2815
+ # 이상치 경계 (Tukey's fences)
2816
+ iqr = q3 - q1
2817
+ down = q1 - 1.5 * iqr
2818
+ up = q3 + 1.5 * iqr
2819
+
2820
+ # 왜도
2821
+ skew = data[f].skew()
2822
+
2823
+ # 이상치 개수 및 비율
2824
+ outlier_count = ((data[f] < down) | (data[f] > up)).sum()
2825
+ outlier_rate = (outlier_count / len(data)) * 100
2826
+
2827
+ # 분포 특성 판정 (왜도 기준)
2828
+ abs_skew = abs(skew)
2829
+ if abs_skew < 0.5:
2830
+ dist = "거의 대칭"
2831
+ elif abs_skew < 1.0:
2832
+ if skew > 0:
2833
+ dist = "약한 우측 꼬리"
2834
+ else:
2835
+ dist = "약한 좌측 꼬리"
2836
+ elif abs_skew < 2.0:
2837
+ if skew > 0:
2838
+ dist = "중간 우측 꼬리"
2839
+ else:
2840
+ dist = "중간 좌측 꼬리"
2841
+ else:
2842
+ if skew > 0:
2843
+ dist = "극단 우측 꼬리"
2844
+ else:
2845
+ dist = "극단 좌측 꼬리"
2846
+
2847
+ # 로그변환 필요성 판정
2848
+ if abs_skew < 0.5:
2849
+ log_need = "낮음"
2850
+ elif abs_skew < 1.0:
2851
+ log_need = "중간"
2852
+ else:
2853
+ log_need = "높음"
2854
+
2855
+ additional_stats.append({
2856
+ 'field': f,
2857
+ 'iqr': iqr,
2858
+ 'up': up,
2859
+ 'down': down,
2860
+ 'outlier_count': outlier_count,
2861
+ 'outlier_rate': outlier_rate,
2862
+ 'skew': skew,
2863
+ 'dist': dist,
2864
+ 'log_need': log_need
2865
+ })
2866
+
2867
+ additional_df = DataFrame(additional_stats).set_index('field')
2868
+
2869
+ # 결과 병합
2870
+ result = concat([desc, additional_df], axis=1)
2871
+
2872
+ # columns 파라미터가 지정된 경우 해당 컬럼만 필터링
2873
+ if columns is not None:
2874
+ result = result[columns]
2875
+
2876
+ return result
2877
+
2878
+
2879
+ # ===================================================================
2880
+ # 상관계수 및 효과크기 분석 (Correlation & Effect Size)
2881
+ # ===================================================================
2882
+ def corr_effect_size(data: DataFrame, dv: str, *fields: str, alpha: float = 0.05) -> DataFrame:
2883
+ """종속변수와의 편상관계수 및 효과크기를 계산한다.
2884
+
2885
+ 각 독립변수와 종속변수 간의 상관계수를 계산하되, 정규성과 선형성을 검사하여
2886
+ Pearson 또는 Spearman 상관계수를 적절히 선택한다.
2887
+ Cohen's d (효과크기)를 계산하여 상관 강도를 정량화한다.
2888
+
2889
+ Args:
2890
+ data (DataFrame): 분석 대상 데이터프레임.
2891
+ dv (str): 종속변수 컬럼 이름.
2892
+ *fields (str): 독립변수 컬럼 이름들. 지정하지 않으면 수치형 컬럼 중 dv 제외 모두 사용.
2893
+ alpha (float, optional): 유의수준. 기본 0.05.
2894
+
2895
+ Returns:
2896
+ DataFrame: 다음 컬럼을 포함한 데이터프레임:
2897
+ - Variable (str): 독립변수 이름
2898
+ - Correlation (float): 상관계수 (Pearson 또는 Spearman)
2899
+ - Corr_Type (str): 선택된 상관계수 종류 ('Pearson' 또는 'Spearman')
2900
+ - P-value (float): 상관계수의 유의확률
2901
+ - Cohens_d (float): 표준화된 효과크기
2902
+ - Effect_Size (str): 효과크기 분류 ('Large', 'Medium', 'Small', 'Negligible')
2903
+
2904
+ Examples:
2905
+ >>> from hossam import hs_stats
2906
+ >>> import pandas as pd
2907
+ >>> df = pd.DataFrame({'age': [20, 30, 40, 50],
2908
+ ... 'bmi': [22, 25, 28, 30],
2909
+ ... 'charges': [1000, 2000, 3000, 4000]})
2910
+ >>> result = hs_stats.corr_effect_size(df, 'charges', 'age', 'bmi')
2911
+ >>> print(result)
2912
+ """
2913
+
2914
+ # fields가 지정되지 않으면 수치형 컬럼 중 dv 제외 모두 사용
2915
+ if not fields:
2916
+ fields = [col for col in data.columns
2917
+ if is_numeric_dtype(data[col]) and col != dv]
2918
+
2919
+ # dv가 수치형인지 확인
2920
+ if not is_numeric_dtype(data[dv]):
2921
+ raise ValueError(f"Dependent variable '{dv}' must be numeric type")
2922
+
2923
+ results = []
2924
+
2925
+ for var in fields:
2926
+ if not is_numeric_dtype(data[var]):
2927
+ continue
2928
+
2929
+ # 결측치 제거
2930
+ valid_idx = data[[var, dv]].notna().all(axis=1)
2931
+ x = data.loc[valid_idx, var].values
2932
+ y = data.loc[valid_idx, dv].values
2933
+
2934
+ if len(x) < 3:
2935
+ continue
2936
+
2937
+ # 정규성 검사 (Shapiro-Wilk: n <= 5000 권장, 그 외 D'Agostino)
2938
+ method_x = 's' if len(x) <= 5000 else 'n'
2939
+ method_y = 's' if len(y) <= 5000 else 'n'
2940
+
2941
+ normal_x_result = normal_test(data[[var]], columns=[var], method=method_x)
2942
+ normal_y_result = normal_test(data[[dv]], columns=[dv], method=method_y)
2943
+
2944
+ # 정규성 판정 (p > alpha면 정규분포 가정)
2945
+ normal_x = normal_x_result.loc[var, 'p-val'] > alpha if var in normal_x_result.index else False
2946
+ normal_y = normal_y_result.loc[dv, 'p-val'] > alpha if dv in normal_y_result.index else False
2947
+
2948
+ # Pearson (모두 정규) vs Spearman (하나라도 비정규)
2949
+ if normal_x and normal_y:
2950
+ r, p = pearsonr(x, y)
2951
+ corr_type = 'Pearson'
2952
+ else:
2953
+ r, p = spearmanr(x, y)
2954
+ corr_type = 'Spearman'
2955
+
2956
+ # Cohen's d 계산 (상관계수에서 효과크기로 변환)
2957
+ # d = 2*r / sqrt(1-r^2)
2958
+ if r**2 < 1:
2959
+ d = (2 * r) / np.sqrt(1 - r**2)
2960
+ else:
2961
+ d = 0
2962
+
2963
+ # 효과크기 분류 (Cohen's d 기준)
2964
+ # Small: 0.2 < |d| <= 0.5
2965
+ # Medium: 0.5 < |d| <= 0.8
2966
+ # Large: |d| > 0.8
2967
+ abs_d = abs(d)
2968
+ if abs_d > 0.8:
2969
+ effect_size = 'Large'
2970
+ elif abs_d > 0.5:
2971
+ effect_size = 'Medium'
2972
+ elif abs_d > 0.2:
2973
+ effect_size = 'Small'
2974
+ else:
2975
+ effect_size = 'Negligible'
2976
+
2977
+ results.append({
2978
+ 'Variable': var,
2979
+ 'Correlation': r,
2980
+ 'Corr_Type': corr_type,
2981
+ 'P-value': p,
2982
+ 'Cohens_d': d,
2983
+ 'Effect_Size': effect_size
2984
+ })
2985
+
2986
+ result_df = DataFrame(results)
2987
+
2988
+ # 상관계수로 정렬 (절댓값 기준 내림차순)
2989
+ if len(result_df) > 0:
2990
+ result_df = result_df.sort_values('Correlation', key=lambda x: x.abs(), ascending=False).reset_index(drop=True)
2991
+
2992
+ return result_df
hossam/mcp/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Hossam MCP Layer
4
+
5
+ - 기존 공개 API는 유지하며, MCP 관련 코드는 이 패키지 내부에만 위치합니다.
6
+ - 서버는 명시적 엔트리포인트로만 실행됩니다 (`hossam-mcp`).
7
+ - 각 모듈별 `register(mcp)` 함수를 통해 MCP tool을 등록합니다.
8
+ """
9
+
10
+ __all__ = [
11
+ "server",
12
+ ]
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+ """MCP wrappers for hossam.hs_classroom"""
3
+ def register(mcp):
4
+ # 자동 등록: 언더바로 시작하지 않는 모든 공개 함수 노출(중복 방지)
5
+ import inspect as _inspect
6
+ import functools as _functools
7
+ import hossam.hs_classroom as _mod
8
+
9
+ for _name, _fn in _inspect.getmembers(_mod, _inspect.isfunction):
10
+ if _name.startswith("_"):
11
+ continue
12
+ _tool_name = f"hs_classroom_{_name}"
13
+ if mcp.get_tool_info(_tool_name):
14
+ continue
15
+
16
+ def _make_tool(fn=_fn, tool_name=_tool_name):
17
+ @mcp.tool(tool_name, description=fn.__doc__)
18
+ @_functools.wraps(fn)
19
+ def _tool(**kwargs):
20
+ return fn(**kwargs)
21
+
22
+ _make_tool()
hossam/mcp/hs_gis.py ADDED
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+ """MCP wrappers for hossam.hs_gis"""
3
+ def register(mcp):
4
+ from hossam.hs_gis import save_shape as _save_shape
5
+
6
+ @mcp.tool("hs_gis_save_shape")
7
+ def hs_save_shape(gdf_or_df, path: str, crs: str | None = None, lat_col: str = "latitude", lon_col: str = "longitude"):
8
+ """GeoDataFrame 또는 DataFrame을 Shapefile/GeoPackage로 저장합니다."""
9
+ _save_shape(gdf_or_df, path=path, crs=crs, lat_col=lat_col, lon_col=lon_col)
10
+ return {"saved": True, "path": path}
11
+
12
+ # 자동 등록: 언더바로 시작하지 않는 모든 공개 함수 노출(중복 방지)
13
+ import inspect as _inspect
14
+ import functools as _functools
15
+ import hossam.hs_gis as _mod
16
+
17
+ for _name, _fn in _inspect.getmembers(_mod, _inspect.isfunction):
18
+ if _name.startswith("_"):
19
+ continue
20
+ _tool_name = f"hs_gis_{_name}"
21
+ if mcp.get_tool_info(_tool_name):
22
+ continue
23
+
24
+ def _make_tool(fn=_fn, tool_name=_tool_name):
25
+ @mcp.tool(tool_name, description=fn.__doc__)
26
+ @_functools.wraps(fn)
27
+ def _tool(**kwargs):
28
+ return fn(**kwargs)
29
+
30
+ _make_tool()
hossam/mcp/hs_plot.py ADDED
@@ -0,0 +1,53 @@
1
+ # -*- coding: utf-8 -*-
2
+ """MCP wrappers for hossam.hs_plot
3
+
4
+ 시각화 함수는 파일 저장 경로(`save_path`)를 활용하는 사용을 권장합니다.
5
+ """
6
+ from typing import Any
7
+ from pandas import DataFrame
8
+
9
+ from hossam.hs_plot import (
10
+ lineplot as _lineplot,
11
+ boxplot as _boxplot,
12
+ kdeplot as _kdeplot,
13
+ )
14
+
15
+
16
+ def register(mcp):
17
+ # @mcp.tool("hs_lineplot")
18
+ # def hs_lineplot(df: DataFrame, xname: str | None = None, yname: str | None = None, hue: str | None = None, save_path: str | None = None, **params: Any):
19
+ # """선 그래프를 그립니다. 원격 환경에서는 `save_path`로 저장하여 활용하세요."""
20
+ # _lineplot(df=df, xname=xname, yname=yname, hue=hue, save_path=save_path, **params)
21
+ # return {"saved": bool(save_path), "path": save_path}
22
+
23
+ # @mcp.tool("hs_boxplot")
24
+ # def hs_boxplot(df: DataFrame, xname: str | None = None, yname: str | None = None, orient: str = "v", save_path: str | None = None, **params: Any):
25
+ # """상자그림(boxplot)을 그립니다. `save_path` 지정 시 파일로 저장합니다."""
26
+ # _boxplot(df=df, xname=xname, yname=yname, orient=orient, save_path=save_path, **params)
27
+ # return {"saved": bool(save_path), "path": save_path}
28
+
29
+ # @mcp.tool("hs_kdeplot")
30
+ # def hs_kdeplot(df: DataFrame, xname: str | None = None, yname: str | None = None, hue: str | None = None, fill: bool = False, quartile_split: bool = False, save_path: str | None = None, **params: Any):
31
+ # """KDE(커널 밀도) 그래프를 그립니다. 1D KDE는 `quartile_split`로 사분위별 분할 가능."""
32
+ # _kdeplot(df=df, xname=xname, yname=yname, hue=hue, fill=fill, quartile_split=quartile_split, save_path=save_path, **params)
33
+ # return {"saved": bool(save_path), "path": save_path}
34
+
35
+ # 자동 등록: 언더바로 시작하지 않는 모든 공개 함수 노출(중복 방지)
36
+ import inspect as _inspect
37
+ import functools as _functools
38
+ import hossam.hs_plot as _mod
39
+
40
+ for _name, _fn in _inspect.getmembers(_mod, _inspect.isfunction):
41
+ if _name.startswith("_"):
42
+ continue
43
+ _tool_name = f"hs_plot_{_name}"
44
+ if mcp.get_tool_info(_tool_name):
45
+ continue
46
+
47
+ def _make_tool(fn=_fn, tool_name=_tool_name):
48
+ @mcp.tool(tool_name, description=fn.__doc__)
49
+ @_functools.wraps(fn)
50
+ def _tool(**kwargs):
51
+ return fn(**kwargs)
52
+
53
+ _make_tool()
hossam/mcp/hs_prep.py ADDED
@@ -0,0 +1,61 @@
1
+ # -*- coding: utf-8 -*-
2
+ """MCP wrappers for hossam.hs_prep"""
3
+ from typing import List
4
+ from pandas import DataFrame
5
+
6
+ from hossam.hs_prep import (
7
+ standard_scaler as _standard_scaler,
8
+ minmax_scaler as _minmax_scaler,
9
+ set_category as _set_category,
10
+ get_dummies as _get_dummies,
11
+ replace_outliner as _replace_outliner,
12
+ )
13
+
14
+
15
+ def register(mcp):
16
+ # @mcp.tool("hs_standard_scaler")
17
+ # def hs_standard_scaler(data: DataFrame | list | dict, yname: str | None = None, save_path: str | None = None, load_path: str | None = None):
18
+ # """연속형 변수에 대해 Z-Score(Standard) 스케일링을 수행합니다."""
19
+ # return _standard_scaler(data, yname=yname, save_path=save_path, load_path=load_path)
20
+
21
+ # @mcp.tool("hs_minmax_scaler")
22
+ # def hs_minmax_scaler(data: DataFrame | list | dict, yname: str | None = None, save_path: str | None = None, load_path: str | None = None):
23
+ # """연속형 변수를 0~1로 정규화(MinMax Scaling)합니다."""
24
+ # return _minmax_scaler(data, yname=yname, save_path=save_path, load_path=load_path)
25
+
26
+ # @mcp.tool("hs_set_category")
27
+ # def hs_set_category(df: DataFrame, fields: List[str]):
28
+ # """지정된 컬럼을 pandas Categorical로 설정합니다."""
29
+ # return _set_category(df, *fields)
30
+
31
+ # @mcp.tool("hs_get_dummies")
32
+ # def hs_get_dummies(df: DataFrame, fields: List[str] | None = None, drop_first: bool = True, dtype: str = "int"):
33
+ # """명목형 변수를 더미 변수(원-핫)로 변환합니다."""
34
+ # fields = fields or []
35
+ # return _get_dummies(df, *fields, drop_first=drop_first, dtype=dtype)
36
+
37
+ # @mcp.tool("hs_replace_outliner")
38
+ # def hs_replace_outliner(df: DataFrame, method: str = "nan", fields: List[str] | None = None):
39
+ # """IQR 기준 이상치를 지정된 방식으로 대체합니다."""
40
+ # fields = fields or []
41
+ # return _replace_outliner(df, method=method, *fields)
42
+
43
+ # 자동 등록: 언더바로 시작하지 않는 모든 공개 함수 노출(중복 방지)
44
+ import inspect as _inspect
45
+ import functools as _functools
46
+ import hossam.hs_prep as _mod
47
+
48
+ for _name, _fn in _inspect.getmembers(_mod, _inspect.isfunction):
49
+ if _name.startswith("_"):
50
+ continue
51
+ _tool_name = f"hs_prep_{_name}"
52
+ if mcp.get_tool_info(_tool_name):
53
+ continue
54
+
55
+ def _make_tool(fn=_fn, tool_name=_tool_name):
56
+ @mcp.tool(tool_name, description=fn.__doc__)
57
+ @_functools.wraps(fn)
58
+ def _tool(**kwargs):
59
+ return fn(**kwargs)
60
+
61
+ _make_tool()
hossam/mcp/hs_stats.py ADDED
@@ -0,0 +1,25 @@
1
+ # -*- coding: utf-8 -*-
2
+ """MCP wrappers for hossam.hs_stats
3
+
4
+ 공개 API만 thin wrapper로 노출합니다.
5
+ """
6
+ def register(mcp):
7
+ # 자동 등록: 언더바로 시작하지 않는 모든 공개 함수 노출(중복 방지)
8
+ import inspect as _inspect
9
+ import functools as _functools
10
+ import hossam.hs_stats as _mod
11
+
12
+ for _name, _fn in _inspect.getmembers(_mod, _inspect.isfunction):
13
+ if _name.startswith("_"):
14
+ continue
15
+ _tool_name = f"hs_stats_{_name}"
16
+ if mcp.get_tool_info(_tool_name):
17
+ continue
18
+
19
+ def _make_tool(fn=_fn, tool_name=_tool_name):
20
+ @mcp.tool(tool_name, description=fn.__doc__)
21
+ @_functools.wraps(fn)
22
+ def _tool(**kwargs):
23
+ return fn(**kwargs)
24
+
25
+ _make_tool()
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+ """MCP wrappers for hossam.hs_timeserise"""
3
+ def register(mcp):
4
+ # 자동 등록: 언더바로 시작하지 않는 모든 공개 함수 노출(중복 방지)
5
+ import inspect as _inspect
6
+ import functools as _functools
7
+ import hossam.hs_timeserise as _mod
8
+
9
+ for _name, _fn in _inspect.getmembers(_mod, _inspect.isfunction):
10
+ if _name.startswith("_"):
11
+ continue
12
+ _tool_name = f"hs_timeserise_{_name}"
13
+ if mcp.get_tool_info(_tool_name):
14
+ continue
15
+
16
+ def _make_tool(fn=_fn, tool_name=_tool_name):
17
+ @mcp.tool(tool_name, description=fn.__doc__)
18
+ @_functools.wraps(fn)
19
+ def _tool(**kwargs):
20
+ return fn(**kwargs)
21
+
22
+ _make_tool()
hossam/mcp/hs_util.py ADDED
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+ """MCP wrappers for hossam.hs_util"""
3
+ def register(mcp):
4
+ # @mcp.tool("hs_pretty_table")
5
+ # def hs_pretty_table(df, tablefmt: str = "simple", headers: str | list = "keys"):
6
+ # """DataFrame을 단순 표 형태로 출력합니다. 원격 환경에서는 표 문자열을 반환합니다."""
7
+ # # pretty_table은 print만 수행하므로, 문자열을 반환하도록 보정
8
+ # from tabulate import tabulate
9
+ # s = tabulate(df, headers=headers, tablefmt=tablefmt, showindex=True, numalign="right")
10
+ # return s
11
+
12
+ # 자동 등록: 언더바로 시작하지 않는 모든 공개 함수 노출(중복 방지)
13
+ import inspect as _inspect
14
+ import functools as _functools
15
+ import hossam.hs_util as _mod
16
+
17
+ for _name, _fn in _inspect.getmembers(_mod, _inspect.isfunction):
18
+ if _name.startswith("_"):
19
+ continue
20
+ _tool_name = f"hs_util_{_name}"
21
+ if mcp.get_tool_info(_tool_name):
22
+ continue
23
+
24
+ def _make_tool(fn=_fn, tool_name=_tool_name):
25
+ @mcp.tool(tool_name, description=fn.__doc__)
26
+ @_functools.wraps(fn)
27
+ def _tool(**kwargs):
28
+ return fn(**kwargs)
29
+
30
+ _make_tool()