Skip to content

Feat(#36): 형태소·문자 기반 피싱 분류 모델 추가 및 성능 비교 (2/2) - #43

Merged
pearseona merged 5 commits into
developfrom
feat/36-compare-phishing-models-2
Aug 9, 2026
Merged

Feat(#36): 형태소·문자 기반 피싱 분류 모델 추가 및 성능 비교 (2/2)#43
pearseona merged 5 commits into
developfrom
feat/36-compare-phishing-models-2

Conversation

@pearseona

@pearseona pearseona commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

📝 개요

이 PR은 이슈 #36의 두 번째 작업 (2/2)으로, PR #38에서 구현한 재현 가능한 데이터 분할 및 공통 평가 파이프라인을 기반으로 신규 피싱 분류 모델을 추가하고 성능을 비교합니다.

형태소 TF-IDF 기반 Logistic Regression과 문자 n-gram TF-IDF 기반 Linear SVM을 구현했습니다. 기존 Naive Bayes 모델을 포함한 세 모델을 동일한 train/validation/test 데이터로 학습·평가하고, 비교 보고서와 재사용 가능한 모델 artifact를 자동 생성하도록 구성했습니다.

기존 Naive Bayes 운영 artifact와 분석 API 동작은 변경하지 않습니다.

🔗 관련 이슈

🎯 주요 변경 사항

1. 형태소 TF-IDF Logistic Regression 모델 추가

  • 공통 normalize_text() 전처리 사용
  • Kiwi 형태소 tokenizer 연결
  • 형태소 unigram/bigram TF-IDF 구성
  • class_weight="balanced" Logistic Regression 적용
  • 피싱 클래스 확률을 공통 ScoreType.PROBABILITY 형식으로 반환
  • 모델·전처리·벡터라이저 설정을 metadata에 기록
  • 입력 검증, 직렬화 및 예측 회귀 테스트 추가

2. 문자 n-gram TF-IDF Linear SVM 모델 추가

  • 공통 text_norm 입력 사용
  • char_wb 문자 n-gram TF-IDF 구성
  • class_weight="balanced" LinearSVC 적용
  • decision_function() 결과를 ScoreType.DECISION 형식으로 반환
  • 클래스 순서와 관계없이 큰 score가 항상 피싱 방향이 되도록 보정
  • validation 기반 decision threshold 선택
  • 입력 검증, score 방향, 직렬화 및 예측 테스트 추가

3. 비교 모델 artifact 저장·로딩 기능 추가

  • Logistic Regression 및 Linear SVM 비교 artifact 저장
  • 학습된 classifier와 fitted vectorizer 저장
  • validation에서 선택된 threshold 저장
  • 클래스 및 feature 수 기록
  • dataset fingerprint 및 split manifest 버전 기록
  • Python과 주요 라이브러리 버전 기록
  • artifact schema version 및 생성 시각 기록
  • SHA-256 checksum과 artifact ID를 통한 파일 조합 검증
  • 잘못된 schema, feature 불일치 및 서로 다른 artifact 조합 거부
  • 기존 Naive Bayes 운영 artifact와 비교 artifact 분리
  • 신뢰할 수 있는 로컬 joblib artifact만 로드하도록 보안 경계 명시

4. 세 모델 통합 학습·평가 실행기 추가

  • committed sms_split_v1.csv manifest 사용 강제
  • 세 모델에 동일한 train/validation/test split 적용
  • train split만 모델 및 vectorizer 학습에 사용
  • validation split만 threshold 선택에 사용
  • test split은 최종 성능 측정에만 사용
  • Precision, Recall, F1, F2 및 혼동행렬 계산
  • 평균 및 P95 단건 추론시간 측정
  • dataset 및 split manifest SHA-256 기록
  • 실행환경과 주요 라이브러리 버전 기록
  • JSON, CSV, Markdown 비교 보고서 자동 생성
  • 기존 NB 운영 artifact는 교체하지 않고 신규 모델만 비교 artifact로 저장

5. 최종 모델 비교 결과

Model Precision Recall F1 F2 FP FN
Naive Bayes structural 0.5447 1.0000 0.7053 0.8568 56 0
Morphological Logistic Regression 0.7386 0.9701 0.8387 0.9129 23 2
Character Linear SVM 0.7471 0.9701 0.8442 0.9155 22 2
  • Naive Bayes는 False Negative 0건과 Recall 1.0을 유지했지만 False Positive가 56건 발생했습니다.
  • Logistic Regression은 False Negative 2건이 발생했지만 False Positive를 23건으로 줄였습니다.
  • Linear SVM은 False Negative 2건, False Positive 22건으로 신규 모델 중 가장 높은 Precision과 F2를 기록했습니다.
  • 이 PR에서는 결과를 자동으로 비교·기록하며 운영 모델을 자동 교체하지 않습니다.

6. 생성 파일

비교 보고서:

  • reports/model_evaluation/model_comparison/model_evaluation.json
  • reports/model_evaluation/model_comparison/model_evaluation.csv
  • reports/model_evaluation/model_comparison/model_evaluation.md
  • reports/model_evaluation/model_comparison/comparison_run.json
  • reports/model_evaluation/model_comparison/comparison_run.md

비교 artifact:

  • artifacts/comparison/logistic_regression/model.joblib
  • artifacts/comparison/logistic_regression/metadata.json
  • artifacts/comparison/linear_svm/model.joblib
  • artifacts/comparison/linear_svm/metadata.json

📸 사진

✅ PR 체크리스트

  • 관련 이슈를 연결했습니다.
  • 구현 범위와 변경 이유를 설명했습니다.
  • 로컬 테스트(uvicorn 구동 또는 테스트 코드)를 통과했습니다.
  • API 변경 사항이 있다면 Swagger / API 명세에 반영했습니다.
  • 민감 정보(API Key, 시크릿 키 등)가 코드·로그·테스트 데이터에 포함되지 않았습니다.
  • 프론트엔드 또는 메인 백엔드(Spring)에 영향을 주는 응답 스키마 또는 Enum 변경이 있다면 팀에 공유했습니다.
  • 병합(Merge) 전 작업 브랜치를 삭제하지 않았습니다.

Summary by CodeRabbit

  • New Features

    • Added SMS phishing model comparison for Naive Bayes, Logistic Regression, and Linear SVM.
    • Added new Logistic Regression and Linear SVM classifiers with training, scoring, and metadata support.
    • Added validated model artifact saving and loading with integrity checks.
    • Added reproducible JSON and Markdown evaluation reports, including performance and latency metrics.
  • Tests

    • Added comprehensive coverage for classifiers, artifact handling, model comparison, validation, and reproducibility.
  • Chores

    • Updated test runtime and cache locations.

@pearseona pearseona self-assigned this Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds Logistic Regression and Linear SVM phishing classifiers, validated comparison-artifact persistence, and a CLI-driven evaluation workflow. It also adds reproducibility reports, committed artifact metadata, tests, and pytest runtime-directory configuration.

Changes

SMS model comparison

Layer / File(s) Summary
Classifier adapters and validation
data_science/SMSModel/modeling/*.py, tests/data_science/SMSModel/modeling/test_*.py
Adds Korean morph-tokenized Logistic Regression and character n-gram Linear SVM classifiers with validation, scoring, metadata, persistence checks, and threshold tests.
Validated comparison artifacts
data_science/SMSModel/modeling/comparison_artifacts.py, data_science/SMSModel/modeling/__init__.py, data_science/SMSModel/artifacts/comparison/*, tests/data_science/SMSModel/modeling/test_comparison_artifacts.py
Adds artifact save/load APIs with schema validation, checksums, atomic writes, overwrite protection, and payload consistency checks.
Comparison execution and reports
data_science/SMSModel/run_model_comparison.py, data_science/SMSModel/reports/model_evaluation/model_comparison/*, tests/data_science/SMSModel/test_model_comparison.py, pytest.ini, .gitignore
Adds shared-split evaluation for three models, threshold selection, latency measurements, artifact output, JSON/Markdown reports, CLI execution, and pytest runtime paths.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SplitManifest
  participant ComparisonRunner
  participant ModelSet
  participant ArtifactStore
  participant ReportStore
  SplitManifest->>ComparisonRunner: validate manifest and dataset fingerprint
  ComparisonRunner->>ModelSet: train and evaluate three models
  ModelSet->>ArtifactStore: save Logistic Regression and Linear SVM artifacts
  ComparisonRunner->>ReportStore: write comparison reports
Loading

Possibly related PRs

  • SafeFam/SafeFam_AI#38: Provides the shared SMS modeling and evaluation foundation extended by this change.

Suggested labels: feat

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the added morphological and character-based phishing models and their performance comparison.
Docstring Coverage ✅ Passed Docstring coverage is 94.67% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/36-compare-phishing-models-2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (10)
data_science/SMSModel/modeling/linear_svm.py (2)

33-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the whitespace-only line in __init__.

Line 34 contains trailing whitespace only. Most linters (for example Ruff W293) flag this.

♻️ Proposed cleanup
     def __init__(self) -> None:
-        
         self.vectorizer = TfidfVectorizer(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data_science/SMSModel/modeling/linear_svm.py` around lines 33 - 53, Remove
the whitespace-only line immediately inside the __init__ method of the model
class, leaving a blank line without trailing spaces while preserving the
surrounding initialization logic.

65-115: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider sharing the DataFrame validation logic.

_validate_dataframe here is nearly identical to LogisticRegressionPhishingClassifier._validate_dataframe. Only the required text column and the error message prefix differ. A shared helper in modeling/base.py would keep the label rules in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data_science/SMSModel/modeling/linear_svm.py` around lines 65 - 115, Extract
the shared DataFrame validation logic from
LinearSVMClassifier._validate_dataframe and
LogisticRegressionPhishingClassifier._validate_dataframe into a helper in
modeling/base.py. Parameterize the required text column and error-message
prefix, while keeping the existing label validation rules and both classifiers’
public behavior unchanged; update both _validate_dataframe methods to reuse the
helper.
tests/data_science/SMSModel/test_model_comparison.py (1)

160-203: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the overwrite guard.

_check_artifact_targets is the main safeguard against destroying committed artifacts, and no test exercises it. Add a test that points LOGISTIC_ARTIFACT_DIRECTORY at a tmp_path containing model.joblib, then asserts FileExistsError when overwrite_artifacts=False and no error when it is True.

💚 Proposed test
def test_existing_artifacts_block_the_run_without_overwrite(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    """기존 artifact가 있으면 --overwrite-artifacts 없이 중단합니다."""
    artifact_directory = tmp_path / "logistic_regression"
    artifact_directory.mkdir()
    (artifact_directory / "model.joblib").write_bytes(b"")

    monkeypatch.setattr(
        comparison,
        "LOGISTIC_ARTIFACT_DIRECTORY",
        artifact_directory,
    )
    monkeypatch.setattr(
        comparison,
        "LINEAR_SVM_ARTIFACT_DIRECTORY",
        tmp_path / "linear_svm",
    )

    with pytest.raises(FileExistsError, match="already exist"):
        comparison._check_artifact_targets(overwrite_artifacts=False)

    comparison._check_artifact_targets(overwrite_artifacts=True)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/data_science/SMSModel/test_model_comparison.py` around lines 160 - 203,
Add a test for comparison._check_artifact_targets that creates model.joblib
under a monkeypatched LOGISTIC_ARTIFACT_DIRECTORY, verifies FileExistsError when
overwrite_artifacts=False, and verifies no error when it is True. Use tmp_path
for isolated directories and patch LINEAR_SVM_ARTIFACT_DIRECTORY as needed to
keep the check focused on the existing logistic artifact.
data_science/SMSModel/run_model_comparison.py (4)

265-271: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add type annotations to the split parameters.

train_df, validation_df, and test_df have no annotations, unlike every other function in this module.

♻️ Proposed change
+import pandas as pd
 def _evaluate_models(
     *,
     models: list[BasePhishingClassifier],
-    train_df,
-    validation_df,
-    test_df,
+    train_df: pd.DataFrame,
+    validation_df: pd.DataFrame,
+    test_df: pd.DataFrame,
 ) -> list[ModelEvaluationResult]:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data_science/SMSModel/run_model_comparison.py` around lines 265 - 271, Update
the _evaluate_models function signature by adding the appropriate DataFrame type
annotation to train_df, validation_df, and test_df, matching the annotation
convention used by other functions in the module.

483-490: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The markdown slice depends on an undocumented layout.

Line 490 drops the first two lines of render_model_evaluation_markdown output. If that function adds a subtitle or a leading blank line, this runner silently deletes a table header row. Match the title line explicitly instead.

♻️ Proposed hardening
     common_lines = common_markdown.splitlines()
 
-    # 첫 줄 "# Phishing Model Evaluation"과 바로 다음 빈 줄을 제외
-    lines.extend(common_lines[2:])
+    # 최상위 제목과 뒤따르는 빈 줄만 제거하고 표 부분을 재사용
+    body_start = 0
+    if common_lines and common_lines[0].startswith("# "):
+        body_start = 1
+        while (
+            body_start < len(common_lines)
+            and not common_lines[body_start].strip()
+        ):
+            body_start += 1
+    lines.extend(common_lines[body_start:])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data_science/SMSModel/run_model_comparison.py` around lines 483 - 490, Update
the markdown reuse logic around render_model_evaluation_markdown so it locates
the "# Phishing Model Evaluation" title line explicitly, skips that title and
only its following blank line, then extends lines with the remaining content.
Remove the fixed common_lines[2:] assumption while preserving the table header
and subsequent markdown.

228-262: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

run_model_comparison.py re-implements logic that comparison_artifacts.py already exports. The runner duplicates the artifact filenames, the SHA-256 helper, and the fingerprint format validation. The two copies can drift, and the runner then accepts input that the artifact module rejects.

  • data_science/SMSModel/run_model_comparison.py#L228-L262: import MODEL_FILENAME and METADATA_FILENAME from data_science.SMSModel.modeling.comparison_artifacts instead of the literals "model.joblib" and "metadata.json".
  • data_science/SMSModel/run_model_comparison.py#L97-L108: remove the local _calculate_sha256 and reuse the equivalent helper from comparison_artifacts.py after promoting it to a public name.
  • data_science/SMSModel/run_model_comparison.py#L164-L181: replace the inline 64-character hex checks with _validate_dataset_fingerprint from comparison_artifacts.py, again after promoting it to a public name.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data_science/SMSModel/run_model_comparison.py` around lines 228 - 262,
Consolidate artifact constants and validation logic with
comparison_artifacts.py: in data_science/SMSModel/run_model_comparison.py lines
228-262, import and use MODEL_FILENAME and METADATA_FILENAME instead of literal
filenames; in lines 97-108, remove the local _calculate_sha256 and reuse the
promoted public helper from comparison_artifacts.py; and in lines 164-181,
replace inline fingerprint checks with the promoted public
_validate_dataset_fingerprint helper. Update comparison_artifacts.py to expose
the two helpers publicly while preserving their existing validation behavior.

659-682: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider saving the artifacts before the reports.

_save_run_reports runs first. If _save_new_model_artifacts then raises FileExistsError, the committed reports describe a run whose artifacts were never written. Saving the artifacts first keeps the two outputs consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data_science/SMSModel/run_model_comparison.py` around lines 659 - 682,
Reorder the final save operations so _save_new_model_artifacts completes before
_save_run_reports. Keep the existing arguments and return results unchanged,
ensuring reports are only committed after artifact creation succeeds.
data_science/SMSModel/reports/model_evaluation/model_comparison/comparison_run.json (1)

62-72: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The Naive Bayes baseline is degenerate on this test split.

true_negative is 0 and false_positive is 56, so the model labels every test row as phishing at the selected threshold. Recall 1.0 is therefore not informative. Note this in the comparison report so readers do not read the baseline as a valid operating point.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@data_science/SMSModel/reports/model_evaluation/model_comparison/comparison_run.json`
around lines 62 - 72, Update the comparison report’s Naive Bayes baseline entry
around test_metrics to explicitly note that the test split is degenerate: all
rows are classified as phishing at the selected threshold, with true_negative 0
and false_positive 56, making recall 1.0 uninformative. Preserve the existing
metric values and add the note in the report’s appropriate metadata or
explanatory field.
data_science/SMSModel/artifacts/comparison/logistic_regression/metadata.json (1)

1-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Committed artifact metadata has no matching model payload. Both directories commit metadata.json, but .gitignore excludes model.joblib. load_comparison_artifact requires both files, so a fresh clone raises FileNotFoundError for either directory. Document the regeneration step so consumers know the metadata is a record, not a loadable artifact.

  • data_science/SMSModel/artifacts/comparison/logistic_regression/metadata.json#L1-L54: add a README in data_science/SMSModel/artifacts/comparison/ that states model.joblib is generated, and give the command python -m data_science.SMSModel.run_model_comparison --overwrite-artifacts.
  • data_science/SMSModel/artifacts/comparison/linear_svm/metadata.json#L1-L54: cover this directory in the same README entry.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data_science/SMSModel/artifacts/comparison/logistic_regression/metadata.json`
around lines 1 - 54, Add a README under
data_science/SMSModel/artifacts/comparison/ documenting that model.joblib is
generated and that both logistic_regression/metadata.json and
linear_svm/metadata.json are metadata records rather than complete loadable
artifacts until regeneration. Include the exact regeneration command: python -m
data_science.SMSModel.run_model_comparison --overwrite-artifacts. Apply this
documentation for
data_science/SMSModel/artifacts/comparison/logistic_regression/metadata.json
(lines 1-54) and
data_science/SMSModel/artifacts/comparison/linear_svm/metadata.json (lines
1-54); neither metadata file requires a direct change.
data_science/SMSModel/modeling/comparison_artifacts.py (1)

625-632: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

The two os.replace calls are not atomic together.

If the second os.replace fails, model.joblib is new and metadata.json is still the old file. load_comparison_artifact then fails the checksum comparison, so the artifact directory becomes unloadable until the run is repeated. Consider replacing metadata.json first, or removing both files if the second replace raises.

♻️ Proposed hardening
-        os.replace(
-            temporary_model_path,
-            model_path,
-        )
-        os.replace(
-            temporary_metadata_path,
-            metadata_path,
-        )
+        os.replace(
+            temporary_model_path,
+            model_path,
+        )
+        try:
+            os.replace(
+                temporary_metadata_path,
+                metadata_path,
+            )
+        except OSError:
+            # metadata 교체 실패 시 불일치 상태를 남기지 않도록 정리
+            model_path.unlink(missing_ok=True)
+            metadata_path.unlink(missing_ok=True)
+            raise
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data_science/SMSModel/modeling/comparison_artifacts.py` around lines 625 -
632, Harden the replacement sequence around the two os.replace calls so a
failure during the second replacement cannot leave mismatched model and metadata
files. Replace metadata.json first, or, if the second replacement fails, remove
both destination artifacts to avoid an unloadable mixed-version directory;
preserve the existing successful replacement behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/data_science/SMSModel/modeling/test_comparison_artifacts.py`:
- Around line 499-502: Update the pytest.raises call’s match argument in the
relevant test to use a raw regex string for the existing pattern, preserving its
alternatives and expected ValueError behavior.

---

Nitpick comments:
In
`@data_science/SMSModel/artifacts/comparison/logistic_regression/metadata.json`:
- Around line 1-54: Add a README under
data_science/SMSModel/artifacts/comparison/ documenting that model.joblib is
generated and that both logistic_regression/metadata.json and
linear_svm/metadata.json are metadata records rather than complete loadable
artifacts until regeneration. Include the exact regeneration command: python -m
data_science.SMSModel.run_model_comparison --overwrite-artifacts. Apply this
documentation for
data_science/SMSModel/artifacts/comparison/logistic_regression/metadata.json
(lines 1-54) and
data_science/SMSModel/artifacts/comparison/linear_svm/metadata.json (lines
1-54); neither metadata file requires a direct change.

In `@data_science/SMSModel/modeling/comparison_artifacts.py`:
- Around line 625-632: Harden the replacement sequence around the two os.replace
calls so a failure during the second replacement cannot leave mismatched model
and metadata files. Replace metadata.json first, or, if the second replacement
fails, remove both destination artifacts to avoid an unloadable mixed-version
directory; preserve the existing successful replacement behavior.

In `@data_science/SMSModel/modeling/linear_svm.py`:
- Around line 33-53: Remove the whitespace-only line immediately inside the
__init__ method of the model class, leaving a blank line without trailing spaces
while preserving the surrounding initialization logic.
- Around line 65-115: Extract the shared DataFrame validation logic from
LinearSVMClassifier._validate_dataframe and
LogisticRegressionPhishingClassifier._validate_dataframe into a helper in
modeling/base.py. Parameterize the required text column and error-message
prefix, while keeping the existing label validation rules and both classifiers’
public behavior unchanged; update both _validate_dataframe methods to reuse the
helper.

In
`@data_science/SMSModel/reports/model_evaluation/model_comparison/comparison_run.json`:
- Around line 62-72: Update the comparison report’s Naive Bayes baseline entry
around test_metrics to explicitly note that the test split is degenerate: all
rows are classified as phishing at the selected threshold, with true_negative 0
and false_positive 56, making recall 1.0 uninformative. Preserve the existing
metric values and add the note in the report’s appropriate metadata or
explanatory field.

In `@data_science/SMSModel/run_model_comparison.py`:
- Around line 265-271: Update the _evaluate_models function signature by adding
the appropriate DataFrame type annotation to train_df, validation_df, and
test_df, matching the annotation convention used by other functions in the
module.
- Around line 483-490: Update the markdown reuse logic around
render_model_evaluation_markdown so it locates the "# Phishing Model Evaluation"
title line explicitly, skips that title and only its following blank line, then
extends lines with the remaining content. Remove the fixed common_lines[2:]
assumption while preserving the table header and subsequent markdown.
- Around line 228-262: Consolidate artifact constants and validation logic with
comparison_artifacts.py: in data_science/SMSModel/run_model_comparison.py lines
228-262, import and use MODEL_FILENAME and METADATA_FILENAME instead of literal
filenames; in lines 97-108, remove the local _calculate_sha256 and reuse the
promoted public helper from comparison_artifacts.py; and in lines 164-181,
replace inline fingerprint checks with the promoted public
_validate_dataset_fingerprint helper. Update comparison_artifacts.py to expose
the two helpers publicly while preserving their existing validation behavior.
- Around line 659-682: Reorder the final save operations so
_save_new_model_artifacts completes before _save_run_reports. Keep the existing
arguments and return results unchanged, ensuring reports are only committed
after artifact creation succeeds.

In `@tests/data_science/SMSModel/test_model_comparison.py`:
- Around line 160-203: Add a test for comparison._check_artifact_targets that
creates model.joblib under a monkeypatched LOGISTIC_ARTIFACT_DIRECTORY, verifies
FileExistsError when overwrite_artifacts=False, and verifies no error when it is
True. Use tmp_path for isolated directories and patch
LINEAR_SVM_ARTIFACT_DIRECTORY as needed to keep the check focused on the
existing logistic artifact.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1bb3ee0a-ed9a-4c44-81d5-26b55bd0b120

📥 Commits

Reviewing files that changed from the base of the PR and between 3822052 and 7a65bf5.

⛔ Files ignored due to path filters (1)
  • data_science/SMSModel/reports/model_evaluation/model_comparison/model_evaluation.csv is excluded by !**/*.csv
📒 Files selected for processing (19)
  • .gitignore
  • data_science/SMSModel/artifacts/comparison/linear_svm/metadata.json
  • data_science/SMSModel/artifacts/comparison/linear_svm/model.joblib
  • data_science/SMSModel/artifacts/comparison/logistic_regression/metadata.json
  • data_science/SMSModel/artifacts/comparison/logistic_regression/model.joblib
  • data_science/SMSModel/modeling/__init__.py
  • data_science/SMSModel/modeling/comparison_artifacts.py
  • data_science/SMSModel/modeling/linear_svm.py
  • data_science/SMSModel/modeling/logistic_regression.py
  • data_science/SMSModel/reports/model_evaluation/model_comparison/comparison_run.json
  • data_science/SMSModel/reports/model_evaluation/model_comparison/comparison_run.md
  • data_science/SMSModel/reports/model_evaluation/model_comparison/model_evaluation.json
  • data_science/SMSModel/reports/model_evaluation/model_comparison/model_evaluation.md
  • data_science/SMSModel/run_model_comparison.py
  • pytest.ini
  • tests/data_science/SMSModel/modeling/test_comparison_artifacts.py
  • tests/data_science/SMSModel/modeling/test_linear_svm.py
  • tests/data_science/SMSModel/modeling/test_logistic_regression.py
  • tests/data_science/SMSModel/test_model_comparison.py

Comment on lines +499 to +502
with pytest.raises(
ValueError,
match="not fitted|does not contain fitted classes",
):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a raw regex literal.

Line 501 passes a regex containing | as a non-raw string. This produces Ruff RUF043. Change match to a raw string.

Proposed fix
-        match="not fitted|does not contain fitted classes",
+        match=r"not fitted|does not contain fitted classes",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with pytest.raises(
ValueError,
match="not fitted|does not contain fitted classes",
):
with pytest.raises(
ValueError,
match=r"not fitted|does not contain fitted classes",
):
🧰 Tools
🪛 Ruff (0.16.1)

[warning] 501-501: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/data_science/SMSModel/modeling/test_comparison_artifacts.py` around
lines 499 - 502, Update the pytest.raises call’s match argument in the relevant
test to use a raw regex string for the existing pattern, preserving its
alternatives and expected ValueError behavior.

Source: Linters/SAST tools

@pearseona
pearseona merged commit 7b94217 into develop Aug 9, 2026
3 checks passed
@pearseona pearseona added the feat New feature or functional additions to the application label Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat New feature or functional additions to the application

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant