hossam 0.3.18__py3-none-any.whl → 0.3.19__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]
@@ -376,15 +374,16 @@ def _balance_group_sizes_only(
376
374
  # ===================================================================
377
375
  # 조 편성 결과의 인원, 관심사, 점수 분포를 시각화한다
378
376
  # ===================================================================
379
- def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -> None:
377
+ def report_summary(df: DataFrame, width: int = hs_fig.width, height: int = hs_fig.height, dpi: int = hs_fig.dpi) -> None:
380
378
  """조 편성 결과의 요약 통계를 시각화합니다.
381
379
 
382
380
  조별 인원 분포, 관심사 분포, 평균점수 분포를 나타냅니다.
383
381
 
384
382
  Args:
385
383
  df: cluster_students 함수의 반환 결과 데이터프레임.
386
- figsize: 그래프 크기 (width, height). 기본값: (20, 4.2)
387
- dpi: 그래프 해상도. None이면 my_dpi 사용. 기본값: None
384
+ width: 그래프 넓이. 기본값: hs_fig.width
385
+ height: 그래프 높이. 기본값: hs_fig.height
386
+ dpi: 그래프 해상도. 기본값: hs_fig.dpi
388
387
 
389
388
  Examples:
390
389
  >>> from hossam.classroom import cluster_students, report_summary
@@ -392,9 +391,6 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
392
391
  >>> report_summary(df_result)
393
392
  """
394
393
 
395
- if dpi is None:
396
- dpi = my_dpi
397
-
398
394
  if df is None or len(df) == 0:
399
395
  print("데이터프레임이 비어있습니다")
400
396
  return
@@ -427,11 +423,15 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
427
423
  if has_score and has_avg:
428
424
  n_plots += 1
429
425
 
430
- fig, axes = plt.subplots(1, n_plots, figsize=figsize, dpi=dpi)
431
-
432
- # axes를 배열로 변환 (단일 플롯인 경우 대비)
433
- if n_plots == 1:
434
- axes = [axes]
426
+ # hs_plot.get_default_ax를 사용하여 Figure와 Axes 생성
427
+ fig, axes = hs_plot.get_default_ax(
428
+ width=width,
429
+ height=height,
430
+ rows=n_plots,
431
+ cols=1,
432
+ dpi=dpi,
433
+ flatten=True
434
+ )
435
435
 
436
436
  plot_idx = 0
437
437
 
@@ -439,7 +439,16 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
439
439
  group_sizes = df['조'].value_counts()
440
440
  group_sizes = group_sizes.reindex(ordered_labels, fill_value=0)
441
441
  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)
442
+
443
+ # hs_plot.barplot 사용
444
+ hs_plot.barplot(
445
+ df=plot_df,
446
+ xname='조',
447
+ yname='인원',
448
+ palette='Set2',
449
+ ax=axes[plot_idx]
450
+ )
451
+
443
452
  axes[plot_idx].set_title("조별 인원 분포", fontsize=12, fontweight='bold')
444
453
  axes[plot_idx].set_xlabel("조")
445
454
  axes[plot_idx].set_ylabel("인원")
@@ -449,59 +458,74 @@ def report_summary(df: DataFrame, figsize: tuple = (20, 4.2), dpi: int = None) -
449
458
 
450
459
  # ===== 2. 조별 관심사 분포 =====
451
460
  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,
461
+ # hs_plot.stackplot 사용을 위한 데이터 준비
462
+ interest_df = df[['조', '관심사']].copy()
463
+ interest_df['조'] = interest_df['조'].astype(str)
464
+
465
+ def custom_callback(ax):
466
+ ax.set_title("조별 관심사 분포 (%)", fontsize=12, fontweight='bold')
467
+ ax.set_xlabel("조")
468
+ ax.set_ylabel("비율 (%)")
469
+ # x축: 고정된 위치와 라벨 지정
470
+ xticks = ax.get_xticks()
471
+ xticklabels = [tick.get_text() for tick in ax.get_xticklabels()]
472
+ if len(xticklabels) == len(xticks):
473
+ ax.set_xticks(xticks)
474
+ ax.set_xticklabels(xticklabels, rotation=0)
475
+ # y축: 고정된 위치와 라벨 지정
476
+ yticks = ax.get_yticks()
477
+ ax.set_yticks(yticks)
478
+ ax.set_yticklabels([f'{int(y*100)}%' for y in yticks])
479
+ ax.set_ylim(0, 1)
480
+ # legend: 아티스트가 있을 때만
481
+ handles, labels = ax.get_legend_handles_labels()
482
+ if handles and any(l for l in labels if l):
483
+ ax.legend(title='관심사', bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=9)
484
+
485
+ hs_plot.stackplot(
486
+ df=interest_df,
487
+ xname='조',
488
+ hue='관심사',
489
+ palette='Set3',
466
490
  ax=axes[plot_idx],
467
- colormap='Set3',
468
- width=0.8
491
+ callback=custom_callback
469
492
  )
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)
493
+
476
494
  plot_idx += 1
477
495
 
478
496
  # ===== 3. 조별 평균점수 분포 =====
479
497
  if has_score and has_avg:
480
498
  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}
499
+ avg_by_group['조_str'] = avg_by_group['조'].astype(str)
500
+
501
+ # hs_plot.barplot 사용 (errorbar 지원)
502
+ hs_plot.barplot(
503
+ df=avg_by_group,
504
+ xname='조_str',
505
+ yname='mean',
506
+ palette='coolwarm',
507
+ ax=axes[plot_idx],
508
+ errorbar='sd',
509
+ err_kws={'linewidth': 2}
485
510
  )
511
+
486
512
  axes[plot_idx].set_title("조별 평균점수 분포 (±1 std)", fontsize=12, fontweight='bold')
487
513
  axes[plot_idx].set_xlabel("조")
488
514
  axes[plot_idx].set_ylabel("평균점수")
489
-
490
- # ylim 고정: 0~100
491
515
  axes[plot_idx].set_ylim(0, 100)
492
516
 
493
517
  for i, row in avg_by_group.iterrows():
494
518
  axes[plot_idx].text(i, row['mean'] + 2, f"{row['mean']:.1f}", ha='center', fontsize=10)
495
519
  plot_idx += 1
496
520
 
497
- plt.tight_layout()
498
- plt.show()
521
+ # hs_plot.finalize_plot을 사용하여 마무리
522
+ hs_plot.finalize_plot(axes, outparams=True, grid=False)
499
523
 
500
524
 
501
525
  # ===================================================================
502
526
  # 조별 점수 분포를 커널 밀도 추정(KDE) 그래프로 시각화한다
503
527
  # ===================================================================
504
- def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8), dpi: int = None) -> None:
528
+ 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
529
  """조별 점수 분포를 KDE(Kernel Density Estimation)로 시각화합니다.
506
530
 
507
531
  각 조의 점수 분포를 커널 밀도 추정으로 표시하고 평균 및 95% 신뢰구간을 나타냅니다.
@@ -510,18 +534,15 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
510
534
  df: cluster_students 함수의 반환 결과 데이터프레임.
511
535
  metric: 점수 기준 선택 ('total' 또는 'average').
512
536
  'total'이면 총점, 'average'이면 평균점수. 기본값: 'average'
513
- figsize: 그래프 크기 (width, height). 기본값: (20, 8)
514
- dpi: 그래프 해상도. None이면 my_dpi 사용. 기본값: None
537
+ width: 그래프 넓이. 기본값: hs_fig.width
538
+ height: 그래프 높이. 기본값: hs_fig.height
539
+ dpi: 그래프 해상도. 기본값: hs_fig.dpi
515
540
 
516
541
  Examples:
517
542
  >>> from hossam.classroom import cluster_students, report_kde
518
543
  >>> df_result = cluster_students(df, n_groups=5, score_cols=['국어', '영어', '수학'])
519
544
  >>> report_kde(df_result, metric='average')
520
545
  """
521
-
522
- if dpi is None:
523
- dpi = my_dpi
524
-
525
546
  if df is None or len(df) == 0:
526
547
  print("데이터프레임이 비어있습니다")
527
548
  return
@@ -530,15 +551,12 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
530
551
  print("데이터프레임에 '조' 컬럼이 없습니다")
531
552
  return
532
553
 
533
- # 필요한 컬럼 확인
534
554
  has_score = '총점' in df.columns
535
555
  has_avg = '평균점수' in df.columns
536
-
537
556
  if not has_score:
538
557
  print("점수 데이터가 없습니다")
539
558
  return
540
559
 
541
- # 혼합 타입 안전 정렬 라벨 준비
542
560
  labels = df['조'].unique().tolist()
543
561
  def _sort_key(v):
544
562
  try:
@@ -546,61 +564,27 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
546
564
  except (ValueError, TypeError):
547
565
  return (1, str(v))
548
566
  ordered_labels = sorted(labels, key=_sort_key)
549
-
550
567
  n_groups = len(ordered_labels)
551
568
 
552
- # 레이아웃 결정 (3열 기준)
553
- cols = 3
569
+ # 레이아웃 결정 (2열 기준)
570
+ cols = 2
554
571
  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()
572
+ fig, axes = hs_plot.get_default_ax(width=width, height=height, rows=rows, cols=cols, dpi=dpi, flatten=True)
565
573
 
566
574
  plot_idx = 0
567
-
568
- # 메트릭 컬럼 결정
569
575
  metric_col = '평균점수' if (metric or '').lower() == 'average' else '총점'
570
576
  if metric_col not in df.columns:
571
577
  print(f"'{metric_col}' 컬럼이 없습니다")
572
578
  return
573
579
 
574
- # ===== 각 조별 KDE =====
575
580
  for group in ordered_labels:
576
- group_series = df[df['조'] == group][metric_col].dropna()
581
+ group_df = df[df['조'] == group]
582
+ group_series = group_df[metric_col].dropna()
577
583
  n = group_series.size
578
584
  if n == 0:
579
585
  continue
580
586
 
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)
587
+ hs_plot.kde_confidence_interval(data=group_df, xnames=metric_col, ax=axes[plot_idx], callback=lambda ax: ax.set_title(f"{group}조"))
604
588
 
605
589
  plot_idx += 1
606
590
 
@@ -608,20 +592,19 @@ def report_kde(df: DataFrame, metric: str = 'average', figsize: tuple = (20, 8),
608
592
  for idx in range(plot_idx, len(axes)):
609
593
  fig.delaxes(axes[idx])
610
594
 
611
- plt.tight_layout()
612
- plt.show()
595
+ hs_plot.finalize_plot(axes)
613
596
 
614
597
 
615
598
  # ===================================================================
616
599
  # 조별로 학생 목록과 평균 점수를 요약하여 데이터프레임으로 반환한다
617
600
  # ===================================================================
618
- def group_summary(df: DataFrame, name_col: str = '학생번호') -> DataFrame:
601
+ def group_summary(df: DataFrame, name_col: str = '학생이름') -> DataFrame:
619
602
  """조별로 학생 목록과 평균 점수를 요약합니다.
620
603
 
621
604
  Args:
622
605
  df: cluster_students 함수의 반환 결과 데이터프레임.
623
606
  '조' 컬럼이 필수로 포함되어야 함.
624
- name_col: 학생 이름이 들어있는 컬럼명. 기본값: '학생번호'
607
+ name_col: 학생 이름이 들어있는 컬럼명. 기본값: '학생이름'
625
608
 
626
609
  Returns:
627
610
  조별 요약 정보가 담긴 데이터프레임.
@@ -688,7 +671,7 @@ def analyze_classroom(
688
671
  interest_col: str = None,
689
672
  max_iter: int = 200,
690
673
  score_metric: str = 'average',
691
- name_col: str = '학생번호',
674
+ name_col: str = '학생이름',
692
675
  show_summary: bool = True,
693
676
  show_kde: bool = True
694
677
  ) -> DataFrame:
@@ -707,7 +690,7 @@ def analyze_classroom(
707
690
  interest_col: 관심사 정보가 있는 컬럼명. 기본값: None
708
691
  max_iter: 균형 조정 최대 반복 횟수. 기본값: 200
709
692
  score_metric: 점수 기준 선택 ('total' 또는 'average'). 기본값: 'average'
710
- name_col: 학생 이름 컬럼명. 기본값: '학생번호'
693
+ name_col: 학생 이름 컬럼명. 기본값: '학생이름'
711
694
  show_summary: 요약 시각화 표시 여부. 기본값: True
712
695
  show_kde: KDE 시각화 표시 여부. 기본값: True
713
696
 
@@ -726,10 +709,6 @@ def analyze_classroom(
726
709
  >>> print(summary)
727
710
  """
728
711
 
729
- print("=" * 60)
730
- print("1. 학생 조 편성 중...")
731
- print("=" * 60)
732
-
733
712
  # 1. 조 편성
734
713
  df_result = cluster_students(
735
714
  df=df,
@@ -742,34 +721,13 @@ def analyze_classroom(
742
721
 
743
722
  print(f"\n✓ 조 편성 완료: {len(df_result)}명의 학생을 {n_groups}개 조로 배정\n")
744
723
 
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
724
  # 3. 요약 시각화
756
725
  if show_summary:
757
- print("=" * 60)
758
- print("3. 조 편성 요약 시각화 중...")
759
- print("=" * 60)
760
726
  report_summary(df_result)
761
- print("\n✓ 요약 시각화 완료\n")
762
727
 
763
728
  # 4. KDE 시각화
764
729
  if show_kde:
765
- print("=" * 60)
766
- print(f"4. 조별 {score_metric} KDE 시각화 중...")
767
- print("=" * 60)
768
730
  report_kde(df_result, metric=score_metric)
769
- print("\n✓ KDE 시각화 완료\n")
770
-
771
- print("=" * 60)
772
- print("전체 분석 완료!")
773
- print("=" * 60)
774
731
 
732
+ summary = group_summary(df_result, name_col=name_col)
775
733
  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.19
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=WfiGy2VHlfnqsslV4zmbJqjhvf2SuqCkcumpN81OCkE,26159
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.19.dist-info/licenses/LICENSE,sha256=nIqzhlcFY_2D6QtFsYjwU7BWkafo-rUJOQpDZ-DsauI,941
23
+ hossam-0.3.19.dist-info/METADATA,sha256=AxoQn7umetTYtF_Lz1CrxsDIcesCl-7TW75b7knA55g,5678
24
+ hossam-0.3.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ hossam-0.3.19.dist-info/entry_points.txt,sha256=0y7c7g_swXFS6See69i-4q_shcwUs-wIalfuvuYiA9I,53
26
+ hossam-0.3.19.dist-info/top_level.txt,sha256=_-7bwjhthHplWhywEaHIJX2yL11CQCaLjCNSBlk6wiQ,7
27
+ hossam-0.3.19.dist-info/RECORD,,