hossam 0.3.18__py3-none-any.whl → 0.3.20__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/__init__.py CHANGED
@@ -17,10 +17,10 @@ except Exception:
17
17
  hs_fig = SimpleNamespace(
18
18
  dpi=200,
19
19
  width=800,
20
- height=480,
20
+ height=450,
21
21
  font_size=9.5,
22
- font_weight="light",
23
- frame_width=0.5,
22
+ font_weight="normal",
23
+ frame_width=0.7,
24
24
  line_width=1.5,
25
25
  grid_alpha=0.3,
26
26
  grid_width=0.5,
hossam/hs_classroom.py CHANGED
@@ -7,7 +7,9 @@ from pandas import DataFrame, qcut, concat, to_numeric
7
7
  from kmodes.kmodes import KModes
8
8
  from matplotlib import pyplot as plt
9
9
  import seaborn as sns
10
- from hossam.hs_util import load_data, pretty_table
10
+ from .hs_util import load_data, pretty_table
11
+ from . import hs_fig
12
+ from . import hs_plot
11
13
 
12
14
  # ===================================================================
13
15
  # 학생들을 관심사와 성적으로 균형잡힌 조로 편성한다
@@ -74,10 +76,6 @@ def cluster_students(
74
76
  if interest_col is not None and not isinstance(interest_col, str):
75
77
  raise ValueError("interest_col은 문자열이어야 합니다")
76
78
 
77
- # 필수 컬럼 확인
78
- if '학생번호' not in df.columns:
79
- raise ValueError("데이터프레임에 '학생번호' 컬럼이 필요합니다")
80
-
81
79
  # 선택적 컬럼 확인
82
80
  if score_cols is not None:
83
81
  missing_cols = [col for col in score_cols if col not in df.columns]
@@ -91,6 +89,11 @@ def cluster_students(
91
89
 
92
90
  # ===== 1단계: 점수 기반 처리 =====
93
91
  if score_cols is not None:
92
+ # 결측치는 0점으로 대체
93
+ for s in score_cols:
94
+ df[s] = df[s].fillna(0)
95
+ print(df)
96
+
94
97
  # 총점/평균점수 계산
95
98
  df['총점'] = df[score_cols].sum(axis=1)
96
99
  df['평균점수'] = df[score_cols].mean(axis=1)
@@ -99,12 +102,22 @@ def cluster_students(
99
102
  metric_col = '총점' if (score_metric or '').lower() != 'average' else '평균점수'
100
103
 
101
104
  # 성적사분위 분류 (선택한 기준 사용)
102
- df['성적사분위'] = qcut(
103
- df[metric_col],
104
- q=[0, 0.25, 0.50, 0.75, 1.0],
105
- labels=['Q1', 'Q2', 'Q3', 'Q4'],
106
- duplicates='drop' # 중복된 값 처리
107
- )
105
+ quantiles = [0, 0.25, 0.50, 0.75, 1.0]
106
+ n_bins = len(quantiles) - 1
107
+ labels = [f"Q{i+1}" for i in range(n_bins)]
108
+ try:
109
+ df['성적사분위'] = qcut(
110
+ df[metric_col],
111
+ q=quantiles,
112
+ labels=labels,
113
+ duplicates='drop' # 중복된 값 처리
114
+ )
115
+ except ValueError:
116
+ # 구간이 줄어든 경우, bins 개수에 맞게 labels 재생성
117
+ import pandas as pd
118
+ bins = pd.qcut(df[metric_col], q=quantiles, duplicates='drop').cat.categories
119
+ labels = [f"Q{i+1}" for i in range(len(bins))]
120
+ df['성적사분위'] = pd.qcut(df[metric_col], q=quantiles, labels=labels, duplicates='drop')
108
121
 
109
122
  # 성적그룹 매핑
110
123
  df['성적그룹'] = df['성적사분위'].map({
@@ -376,15 +389,16 @@ def _balance_group_sizes_only(
376
389
  # ===================================================================
377
390
  # 조 편성 결과의 인원, 관심사, 점수 분포를 시각화한다
378
391
  # ===================================================================
379
- def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -> None:
392
+ def report_summary(df: DataFrame, width: int = hs_fig.width, height: int = hs_fig.height, dpi: int = hs_fig.dpi) -> None:
380
393
  """조 편성 결과의 요약 통계를 시각화합니다.
381
394
 
382
395
  조별 인원 분포, 관심사 분포, 평균점수 분포를 나타냅니다.
383
396
 
384
397
  Args:
385
398
  df: cluster_students 함수의 반환 결과 데이터프레임.
386
- figsize: 그래프 크기 (width, height). 기본값: (20, 4.2)
387
- dpi: 그래프 해상도. None이면 my_dpi 사용. 기본값: None
399
+ width: 그래프 넓이. 기본값: hs_fig.width
400
+ height: 그래프 높이. 기본값: hs_fig.height
401
+ dpi: 그래프 해상도. 기본값: hs_fig.dpi
388
402
 
389
403
  Examples:
390
404
  >>> from hossam.classroom import cluster_students, report_summary
@@ -392,9 +406,6 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
392
406
  >>> report_summary(df_result)
393
407
  """
394
408
 
395
- if dpi is None:
396
- dpi = my_dpi
397
-
398
409
  if df is None or len(df) == 0:
399
410
  print("데이터프레임이 비어있습니다")
400
411
  return
@@ -427,11 +438,15 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
427
438
  if has_score and has_avg:
428
439
  n_plots += 1
429
440
 
430
- fig, axes = plt.subplots(1, n_plots, figsize=figsize, dpi=dpi)
431
-
432
- # axes를 배열로 변환 (단일 플롯인 경우 대비)
433
- if n_plots == 1:
434
- axes = [axes]
441
+ # hs_plot.get_default_ax를 사용하여 Figure와 Axes 생성
442
+ fig, axes = hs_plot.get_default_ax(
443
+ width=width,
444
+ height=height,
445
+ rows=n_plots,
446
+ cols=1,
447
+ dpi=dpi,
448
+ flatten=True
449
+ )
435
450
 
436
451
  plot_idx = 0
437
452
 
@@ -439,7 +454,16 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
439
454
  group_sizes = df['조'].value_counts()
440
455
  group_sizes = group_sizes.reindex(ordered_labels, fill_value=0)
441
456
  plot_df = DataFrame({'조': group_sizes.index, '인원': group_sizes.values})
442
- sns.barplot(data=plot_df, x='조', y='인원', hue='조', ax=axes[plot_idx], palette='Set2', legend=False)
457
+
458
+ # hs_plot.barplot 사용
459
+ hs_plot.barplot(
460
+ df=plot_df,
461
+ xname='조',
462
+ yname='인원',
463
+ palette='Set2',
464
+ ax=axes[plot_idx]
465
+ )
466
+
443
467
  axes[plot_idx].set_title("조별 인원 분포", fontsize=12, fontweight='bold')
444
468
  axes[plot_idx].set_xlabel("조")
445
469
  axes[plot_idx].set_ylabel("인원")
@@ -449,59 +473,74 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
449
473
 
450
474
  # ===== 2. 조별 관심사 분포 =====
451
475
  if has_interest:
452
- interest_dist = (
453
- df.groupby(['조', '관심사'])
454
- .size()
455
- .unstack(fill_value=0)
456
- )
457
- interest_dist = interest_dist.reindex(index=ordered_labels)
458
-
459
- # 비율로 변환 (각 조가 100%가 되도록)
460
- interest_pct = interest_dist.div(interest_dist.sum(axis=1), axis=0) * 100
461
-
462
- # stacked bar chart로 그리기 (각 막대의 높이가 100%)
463
- interest_pct.plot(
464
- kind='bar',
465
- stacked=True,
476
+ # hs_plot.stackplot 사용을 위한 데이터 준비
477
+ interest_df = df[['조', '관심사']].copy()
478
+ interest_df['조'] = interest_df['조'].astype(str)
479
+
480
+ def custom_callback(ax):
481
+ ax.set_title("조별 관심사 분포 (%)", fontsize=12, fontweight='bold')
482
+ ax.set_xlabel("조")
483
+ ax.set_ylabel("비율 (%)")
484
+ # x축: 고정된 위치와 라벨 지정
485
+ xticks = ax.get_xticks()
486
+ xticklabels = [tick.get_text() for tick in ax.get_xticklabels()]
487
+ if len(xticklabels) == len(xticks):
488
+ ax.set_xticks(xticks)
489
+ ax.set_xticklabels(xticklabels, rotation=0)
490
+ # y축: 고정된 위치와 라벨 지정
491
+ yticks = ax.get_yticks()
492
+ ax.set_yticks(yticks)
493
+ ax.set_yticklabels([f'{int(y*100)}%' for y in yticks])
494
+ ax.set_ylim(0, 1)
495
+ # legend: 아티스트가 있을 때만
496
+ handles, labels = ax.get_legend_handles_labels()
497
+ if handles and any(l for l in labels if l):
498
+ ax.legend(title='관심사', bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=9)
499
+
500
+ hs_plot.stackplot(
501
+ df=interest_df,
502
+ xname='조',
503
+ hue='관심사',
504
+ palette='Set3',
466
505
  ax=axes[plot_idx],
467
- colormap='Set3',
468
- width=0.8
506
+ callback=custom_callback
469
507
  )
470
- axes[plot_idx].set_title("조별 관심사 분포 (%)", fontsize=12, fontweight='bold')
471
- axes[plot_idx].set_xlabel("조")
472
- axes[plot_idx].set_ylabel("비율 (%)")
473
- axes[plot_idx].set_xticklabels(axes[plot_idx].get_xticklabels(), rotation=0)
474
- axes[plot_idx].legend(title='관심사', bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=9)
475
- axes[plot_idx].set_ylim(0, 100)
508
+
476
509
  plot_idx += 1
477
510
 
478
511
  # ===== 3. 조별 평균점수 분포 =====
479
512
  if has_score and has_avg:
480
513
  avg_by_group = df.groupby('조')['평균점수'].agg(['mean', 'std']).reset_index()
481
- sns.barplot(
482
- x='조', y='mean', data=avg_by_group, hue='조',
483
- ax=axes[plot_idx], palette='coolwarm', legend=False,
484
- errorbar='sd', err_kws={'linewidth': 2}
514
+ avg_by_group['조_str'] = avg_by_group['조'].astype(str)
515
+
516
+ # hs_plot.barplot 사용 (errorbar 지원)
517
+ hs_plot.barplot(
518
+ df=avg_by_group,
519
+ xname='조_str',
520
+ yname='mean',
521
+ palette='coolwarm',
522
+ ax=axes[plot_idx],
523
+ errorbar='sd',
524
+ err_kws={'linewidth': 2}
485
525
  )
526
+
486
527
  axes[plot_idx].set_title("조별 평균점수 분포 (±1 std)", fontsize=12, fontweight='bold')
487
528
  axes[plot_idx].set_xlabel("조")
488
529
  axes[plot_idx].set_ylabel("평균점수")
489
-
490
- # ylim 고정: 0~100
491
530
  axes[plot_idx].set_ylim(0, 100)
492
531
 
493
532
  for i, row in avg_by_group.iterrows():
494
533
  axes[plot_idx].text(i, row['mean'] + 2, f"{row['mean']:.1f}", ha='center', fontsize=10)
495
534
  plot_idx += 1
496
535
 
497
- plt.tight_layout()
498
- plt.show()
536
+ # hs_plot.finalize_plot을 사용하여 마무리
537
+ hs_plot.finalize_plot(axes, outparams=True, grid=False)
499
538
 
500
539
 
501
540
  # ===================================================================
502
541
  # 조별 점수 분포를 커널 밀도 추정(KDE) 그래프로 시각화한다
503
542
  # ===================================================================
504
- def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8), dpi: int = None) -> None:
543
+ def report_kde(df: DataFrame, metric: str = 'average', width: int = hs_fig.width, height: int = hs_fig.height, dpi: int = hs_fig.dpi) -> None:
505
544
  """조별 점수 분포를 KDE(Kernel Density Estimation)로 시각화합니다.
506
545
 
507
546
  각 조의 점수 분포를 커널 밀도 추정으로 표시하고 평균 및 95% 신뢰구간을 나타냅니다.
@@ -510,18 +549,15 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
510
549
  df: cluster_students 함수의 반환 결과 데이터프레임.
511
550
  metric: 점수 기준 선택 ('total' 또는 'average').
512
551
  'total'이면 총점, 'average'이면 평균점수. 기본값: 'average'
513
- figsize: 그래프 크기 (width, height). 기본값: (20, 8)
514
- dpi: 그래프 해상도. None이면 my_dpi 사용. 기본값: None
552
+ width: 그래프 넓이. 기본값: hs_fig.width
553
+ height: 그래프 높이. 기본값: hs_fig.height
554
+ dpi: 그래프 해상도. 기본값: hs_fig.dpi
515
555
 
516
556
  Examples:
517
557
  >>> from hossam.classroom import cluster_students, report_kde
518
558
  >>> df_result = cluster_students(df, n_groups=5, score_cols=['국어', '영어', '수학'])
519
559
  >>> report_kde(df_result, metric='average')
520
560
  """
521
-
522
- if dpi is None:
523
- dpi = my_dpi
524
-
525
561
  if df is None or len(df) == 0:
526
562
  print("데이터프레임이 비어있습니다")
527
563
  return
@@ -530,15 +566,12 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
530
566
  print("데이터프레임에 '조' 컬럼이 없습니다")
531
567
  return
532
568
 
533
- # 필요한 컬럼 확인
534
569
  has_score = '총점' in df.columns
535
570
  has_avg = '평균점수' in df.columns
536
-
537
571
  if not has_score:
538
572
  print("점수 데이터가 없습니다")
539
573
  return
540
574
 
541
- # 혼합 타입 안전 정렬 라벨 준비
542
575
  labels = df['조'].unique().tolist()
543
576
  def _sort_key(v):
544
577
  try:
@@ -546,61 +579,27 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
546
579
  except (ValueError, TypeError):
547
580
  return (1, str(v))
548
581
  ordered_labels = sorted(labels, key=_sort_key)
549
-
550
582
  n_groups = len(ordered_labels)
551
583
 
552
- # 레이아웃 결정 (3열 기준)
553
- cols = 3
584
+ # 레이아웃 결정 (2열 기준)
585
+ cols = 2
554
586
  rows = (n_groups + cols - 1) // cols
555
-
556
- fig, axes = plt.subplots(rows, cols, figsize=figsize, dpi=dpi)
557
-
558
- # axes를 1D 배열로 평탄화
559
- if rows == 1 and cols == 1:
560
- axes = [axes]
561
- elif rows == 1 or cols == 1:
562
- axes = axes.flatten()
563
- else:
564
- axes = axes.flatten()
587
+ fig, axes = hs_plot.get_default_ax(width=width, height=height, rows=rows, cols=cols, dpi=dpi, flatten=True)
565
588
 
566
589
  plot_idx = 0
567
-
568
- # 메트릭 컬럼 결정
569
590
  metric_col = '평균점수' if (metric or '').lower() == 'average' else '총점'
570
591
  if metric_col not in df.columns:
571
592
  print(f"'{metric_col}' 컬럼이 없습니다")
572
593
  return
573
594
 
574
- # ===== 각 조별 KDE =====
575
595
  for group in ordered_labels:
576
- group_series = df[df['조'] == group][metric_col].dropna()
596
+ group_df = df[df['조'] == group]
597
+ group_series = group_df[metric_col].dropna()
577
598
  n = group_series.size
578
599
  if n == 0:
579
600
  continue
580
601
 
581
- ax_kde = axes[plot_idx]
582
- sns.kdeplot(group_series, ax=ax_kde, fill=True)
583
-
584
- # 평균선 및 95% 신뢰구간(평균에 대한) 표시
585
- mean_val = float(group_series.mean())
586
- std_val = float(group_series.std(ddof=1)) if n > 1 else float('nan')
587
- se = (std_val / (n ** 0.5)) if (n > 1 and std_val == std_val) else None
588
- if se is not None:
589
- ci_low = mean_val - 1.96 * se
590
- ci_high = mean_val + 1.96 * se
591
- ax_kde.axvspan(ci_low, ci_high, color='red', alpha=0.12, label='95% CI', zorder=9)
592
- # 평균 세로선 (최상위로)
593
- ax_kde.axvline(mean_val, color='red', linestyle='--', linewidth=1.5, label='Mean', zorder=10)
594
-
595
- # 제목/레이블
596
- ax_kde.set_title(f"{group}조 {metric_col} KDE", fontsize=12, fontweight='bold')
597
- ax_kde.set_xlabel(metric_col)
598
- ax_kde.set_ylabel("밀도")
599
-
600
- # 범례 표시 (Mean/CI가 있으면 노출)
601
- handles, labels_ = ax_kde.get_legend_handles_labels()
602
- if handles:
603
- ax_kde.legend(fontsize=9)
602
+ hs_plot.kde_confidence_interval(data=group_df, xnames=metric_col, ax=axes[plot_idx], callback=lambda ax: ax.set_title(f"{group}조"))
604
603
 
605
604
  plot_idx += 1
606
605
 
@@ -608,20 +607,19 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
608
607
  for idx in range(plot_idx, len(axes)):
609
608
  fig.delaxes(axes[idx])
610
609
 
611
- plt.tight_layout()
612
- plt.show()
610
+ hs_plot.finalize_plot(axes)
613
611
 
614
612
 
615
613
  # ===================================================================
616
614
  # 조별로 학생 목록과 평균 점수를 요약하여 데이터프레임으로 반환한다
617
615
  # ===================================================================
618
- def group_summary(df: DataFrame, name_col: str = '학생번호') -> DataFrame:
616
+ def group_summary(df: DataFrame, name_col: str = '학생이름') -> DataFrame:
619
617
  """조별로 학생 목록과 평균 점수를 요약합니다.
620
618
 
621
619
  Args:
622
620
  df: cluster_students 함수의 반환 결과 데이터프레임.
623
621
  '조' 컬럼이 필수로 포함되어야 함.
624
- name_col: 학생 이름이 들어있는 컬럼명. 기본값: '학생번호'
622
+ name_col: 학생 이름이 들어있는 컬럼명. 기본값: '학생이름'
625
623
 
626
624
  Returns:
627
625
  조별 요약 정보가 담긴 데이터프레임.
@@ -688,7 +686,7 @@ def analyze_classroom(
688
686
  interest_col: str = None,
689
687
  max_iter: int = 200,
690
688
  score_metric: str = 'average',
691
- name_col: str = '학생번호',
689
+ name_col: str = '학생이름',
692
690
  show_summary: bool = True,
693
691
  show_kde: bool = True
694
692
  ) -> DataFrame:
@@ -707,7 +705,7 @@ def analyze_classroom(
707
705
  interest_col: 관심사 정보가 있는 컬럼명. 기본값: None
708
706
  max_iter: 균형 조정 최대 반복 횟수. 기본값: 200
709
707
  score_metric: 점수 기준 선택 ('total' 또는 'average'). 기본값: 'average'
710
- name_col: 학생 이름 컬럼명. 기본값: '학생번호'
708
+ name_col: 학생 이름 컬럼명. 기본값: '학생이름'
711
709
  show_summary: 요약 시각화 표시 여부. 기본값: True
712
710
  show_kde: KDE 시각화 표시 여부. 기본값: True
713
711
 
@@ -726,10 +724,6 @@ def analyze_classroom(
726
724
  >>> print(summary)
727
725
  """
728
726
 
729
- print("=" * 60)
730
- print("1. 학생 조 편성 중...")
731
- print("=" * 60)
732
-
733
727
  # 1. 조 편성
734
728
  df_result = cluster_students(
735
729
  df=df,
@@ -742,34 +736,13 @@ def analyze_classroom(
742
736
 
743
737
  print(f"\n✓ 조 편성 완료: {len(df_result)}명의 학생을 {n_groups}개 조로 배정\n")
744
738
 
745
- print("=" * 60)
746
- print("2. 조별 요약 생성 중...")
747
- print("=" * 60)
748
-
749
- # 2. 조별 요약
750
- summary = group_summary(df_result, name_col=name_col)
751
- print("\n✓ 조별 요약:")
752
- pretty_table(summary, tablefmt="pretty")
753
- print()
754
-
755
739
  # 3. 요약 시각화
756
740
  if show_summary:
757
- print("=" * 60)
758
- print("3. 조 편성 요약 시각화 중...")
759
- print("=" * 60)
760
741
  report_summary(df_result)
761
- print("\n✓ 요약 시각화 완료\n")
762
742
 
763
743
  # 4. KDE 시각화
764
744
  if show_kde:
765
- print("=" * 60)
766
- print(f"4. 조별 {score_metric} KDE 시각화 중...")
767
- print("=" * 60)
768
745
  report_kde(df_result, metric=score_metric)
769
- print("\n✓ KDE 시각화 완료\n")
770
-
771
- print("=" * 60)
772
- print("전체 분석 완료!")
773
- print("=" * 60)
774
746
 
747
+ summary = group_summary(df_result, name_col=name_col)
775
748
  return summary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hossam
3
- Version: 0.3.18
3
+ Version: 0.3.20
4
4
  Summary: Hossam Data Helper
5
5
  Author-email: Lee Kwang-Ho <leekh4232@gmail.com>
6
6
  License-Expression: MIT
@@ -45,7 +45,7 @@ title: 🎓 Hossam Data Helper
45
45
 
46
46
  [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/downloads/)
47
47
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
48
- [![Version](https://img.shields.io/badge/version-0.3.8-green.svg)](https://pypi.org/project/hossam/)
48
+ [![Version](https://img.shields.io/badge/version-0.3.19-green.svg)](https://pypi.org/project/hossam/)
49
49
  [![Documentation](https://img.shields.io/badge/docs-py.hossam.kr-blue.svg)](https://py.hossam.kr)
50
50
 
51
51
  **Hossam**은 데이터 분석, 시각화, 통계 처리를 위한 종합 헬퍼 라이브러리입니다.
@@ -1,7 +1,7 @@
1
1
  hossam/NotoSansKR-Regular.ttf,sha256=0SCufUQwcVWrWTu75j4Lt_V2bgBJIBXl1p8iAJJYkVY,6185516
2
- hossam/__init__.py,sha256=j5tp7g9ljMRUo-aCeMXRXyIOXiXkUN9dHRRMGmiG2kU,2800
2
+ hossam/__init__.py,sha256=OHMVqGLMSbqM06zdwND9FITToVDG_fhzMYxreVzWEG8,2801
3
3
  hossam/data_loader.py,sha256=UpC_gn-xUWij0s-MO51qrzJNz3b5-RCz1N6esQMZUJM,6320
4
- hossam/hs_classroom.py,sha256=b2vzxHapxibnJwcRwWvOfLfczjF-G3ZdT9hIUt4z4oU,27407
4
+ hossam/hs_classroom.py,sha256=GxdrLermlJYcIgZ-6Y6ZE7DmVWyl8xe2mWfmsUbl9kw,26817
5
5
  hossam/hs_gis.py,sha256=9ER8gXG2Or0DZ1fpbJR84WsNVPcxu788FsNtR6LsEgo,11379
6
6
  hossam/hs_plot.py,sha256=7cngzrEeVeBvANyReI9kd3yHZGMOFZjvgBbBGA7rT2E,78467
7
7
  hossam/hs_prep.py,sha256=RMeM6QPR-dsk3X_crJity8Tva8xY-mOXlcG3L7W-qjg,36220
@@ -19,9 +19,9 @@ hossam/mcp/hs_timeserise.py,sha256=Uc5ZKLv_sBe1tUdSZ0pmBuAFOQ_yX2MH8Me3DVeBp7c,7
19
19
  hossam/mcp/hs_util.py,sha256=XsjOb9K5PUeAStgne-BluVUGyt0QEzN5wmFX77-yYGk,1237
20
20
  hossam/mcp/loader.py,sha256=Ib11QUG8gV1V0TkMUz1g5kGsFJCApiwiwQ9N1cCy7bA,857
21
21
  hossam/mcp/server.py,sha256=4B6ri8xwX22sA6OlNOWC4wN7Uwfgtt4gQ_HvROMNINo,21270
22
- hossam-0.3.18.dist-info/licenses/LICENSE,sha256=nIqzhlcFY_2D6QtFsYjwU7BWkafo-rUJOQpDZ-DsauI,941
23
- hossam-0.3.18.dist-info/METADATA,sha256=6HZVz9ZZp5P3bmwGPnTmYcD0dfl8DYRrLPi1TEGJ_Vs,5677
24
- hossam-0.3.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- hossam-0.3.18.dist-info/entry_points.txt,sha256=0y7c7g_swXFS6See69i-4q_shcwUs-wIalfuvuYiA9I,53
26
- hossam-0.3.18.dist-info/top_level.txt,sha256=_-7bwjhthHplWhywEaHIJX2yL11CQCaLjCNSBlk6wiQ,7
27
- hossam-0.3.18.dist-info/RECORD,,
22
+ hossam-0.3.20.dist-info/licenses/LICENSE,sha256=nIqzhlcFY_2D6QtFsYjwU7BWkafo-rUJOQpDZ-DsauI,941
23
+ hossam-0.3.20.dist-info/METADATA,sha256=Q8pNH5Ioj9Xw-Z9agrr2ambybQwA-zjy4C5oWdMH0yc,5678
24
+ hossam-0.3.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ hossam-0.3.20.dist-info/entry_points.txt,sha256=0y7c7g_swXFS6See69i-4q_shcwUs-wIalfuvuYiA9I,53
26
+ hossam-0.3.20.dist-info/top_level.txt,sha256=_-7bwjhthHplWhywEaHIJX2yL11CQCaLjCNSBlk6wiQ,7
27
+ hossam-0.3.20.dist-info/RECORD,,