Skip to content

[AI] [FEAT] 계정 ID 생성 - #356

Merged
twq110 merged 19 commits into
mainfrom
20260402-#350-executions-계정-분리(account_id)-및-다중-데일리-파이프라인-대응
May 3, 2026

Hidden character warning

The head ref may contain hidden characters: "20260402-#350-executions-\uacc4\uc815-\ubd84\ub9ac(account_id)-\ubc0f-\ub2e4\uc911-\ub370\uc77c\ub9ac-\ud30c\uc774\ud504\ub77c\uc778-\ub300\uc751"
Merged

twq110 merged 19 commits into
mainfrom
20260402-#350-executions-계정-분리(account_id)-및-다중-데일리-파이프라인-대응

Conversation

@twq110

@twq110 twq110 commented Apr 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • 새로운 기능

    • 변환기 입력 스키마 비교용 CLI 재학습·비교 도구 추가(검증 곡선·요약·선택적 시각화 포함).
  • 모델 학습

    • 여러 모델용 로컬/Kaggle 학습 파이프라인 추가 및 학습/스케일러/체크포인트 저장 표준화.
  • 스크립트/자동화

    • 주간 파이프라인, 추출/업로드/다운로드/배포/트리거용 CLI 스크립트 추가로 배포·학습 워크플로우 자동화.
  • 설정

    • 파이프라인에 계정 코드 필드 추가 및 비어있음 검사 강화.
  • 데이터베이스

    • 계정명 테이블 및 계정별 거래 연동·인덱스 추가.
  • 문서/출력

    • 실행 구성·비교 리포트·분석 문서 자동 생성; 일부 산출물 버전관리 제외 처리.

@twq110
twq110 requested a review from discipline24 as a code owner April 6, 2026 07:04
@coderabbitai

coderabbitai Bot commented Apr 6, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@twq110 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 51 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7818ab54-00c5-4914-85f7-9363d8a2b2ba

📥 Commits

Reviewing files that changed from the base of the PR and between 9b478ca and 34a23bc.

📒 Files selected for processing (2)
  • AI/backtests/compare_transformer_feature_schemas.py
  • AI/libs/database/repository.py

Walkthrough

이 PR은 Transformer 피처 스키마 비교용 신규 CLI(재학습·비교) 및 다수의 Kaggle/배포/추출·학습 스크립트를 추가하고, 포트폴리오·DB 레이어에 계정(account_code)/account_id 스코핑과 관련 스키마·레포지토리 변화를 도입합니다.

Changes

Transformer / Training 및 비교 스크립트 집합

Layer / File(s) Summary
데이터 형태 / 상수
AI/modules/signal/models/.../*, AI/backtests/compare_transformer_feature_schemas.py
새 상수 PRE307_DYNAMIC_23, CURRENT_FIXED_17 정의 및 비교 대상 피처 스키마 선언.
데이터 로드·전처리
AI/modules/signal/models/transformer/train_kaggle.py, AI/modules/signal/models/itransformer/train_kaggle.py, AI/modules/signal/models/patchtst/*, AI/modules/signal/models/TCN/*
price_data parquet 로드, 티커별 기술지표·다중타임프레임 피처 계산, 존재하는 컬럼만 선택·결측/무한치 처리, 티커 단위 분할·시퀀스 생성 로직 추가/수정.
학습 파이프라인·모델 빌드
AI/modules/signal/models/*/train_*.py, AI/backtests/compare_transformer_feature_schemas.py
transformer/itransformer/patchtst/tcn 모델 빌드·컴파일 래퍼 추가, iTransformer 3-입력 계약, 체크포인트·EarlyStopping·ReduceLROnPlateau 콜백 사용.
비교·실행 엔트리포인트
AI/backtests/compare_transformer_feature_schemas.py
두 스키마 각각에 대해 데이터 필터·다운샘플·훈련·히스토리·요약 생성, val_curve_comparison.csv·comparison_summary.json·comparison_report.md·run_config.json 기록, 결정론성 시드 옵션 및 OOM/비결정 연산 예외 처리.
아티팩트 직렬화
AI/modules/signal/models/*/train_kaggle.py, AI/modules/signal/models/patchtst/*, AI/modules/signal/models/TCN/*, AI/backtests/...
모델 가중치, 스케일러(pickle), metadata/json, history.csv 저장 로직 추가/통일.
문서/레포 출력
AI/backtests/out/*
변화 분석 문서 및 예제 실행 구성(run_config.json) 추가.

계정 스코핑(데이터베이스·레포지토리·파이프라인)

Layer / File(s) Summary
DB 스키마
schema.sql
public.account_names 테이블 추가(유니크 account_code) 및 executions.account_id 컬럼·외래키·인덱스 추가, 레거시 계정 upsert 포함.
구성
AI/config/trading.default.json, AI/config/trading.py
PipelineConfig에 account_code: str 추가, 기본값 설정 및 빈값 검증(ValueError).
레포지토리 구현
AI/libs/database/repository.py
PortfolioRepository에 account_code 지원 추가, _resolve_account_id() 구현, account_id 캐싱, 조회·삭제·삽입( executions )에 account_id 필터 적용 및 관련 제어 흐름 조정.
파이프라인 호출 변경
AI/pipelines/daily_routine.py
기본 PortfolioRepository 생성 시 trading_config.pipeline.account_code 전달로 변경.

Kaggle·배포·추출·오케스트레이션 도구군

Layer / File(s) Summary
의존성·환경
AI/requirements.prod.txt, .gitignore
pyarrow, kaggle, paramiko, scp, sshtunnel 등 추가; AI 결과·아티팩트 디렉터리 및 민감파일(.kaggle 등) git무시 확장.
데이터 추출·패키징
AI/scripts/extract_to_parquet.py, AI/scripts/upload_to_kaggle.py
DB→parquet 추출(직접/Termius/SSH 터널 지원), parquet 패키징 및 Kaggle dataset 메타·코드 아카이브 생성/업로드.
Kaggle 트리거·다운로드
AI/scripts/trigger_training.py, AI/scripts/download_weights.py
Kaggle kernel 생성·푸시·상태대기 자동화, 커널 출력에서 가중치 선택적 다운로드(API/fallback CLI).
주간 오케스트레이터·배포
AI/pipelines/weekly_routine.py, AI/scripts/deploy_to_server.py, AI/scripts/run_xai_cron.sh
주간 파이프라인 실행기(추출→업로드→트레이닝→다운로드→배포), SSH 기반 원격 업로드 스크립트, 컨테이너 실행시 커맨드 오버라이드 지원.
모델별 Kaggle 학습 스크립트
AI/modules/signal/models/*/train_kaggle.py, tcn_wandb_sweep.py
PatchTST/Transformer/iTransformer/TCN용 Kaggle-friendly 학습 스크립트 추가 및 W&B 스윕 스크립트.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as compare_transformer_feature_schemas.py
    participant DL as DataLoader
    participant Model as build_transformer_model / Trainer
    participant FS as Filesystem

    User->>CLI: 실행 (args: schema, dates, out_dir, --deterministic ...)
    CLI->>FS: 출력 디렉토리 생성 및 run_config.json 기록
    CLI->>DL: 데이터 로드 (schema=dynamic23, train_end_date...)
    DL-->>CLI: 전처리된 X,y 및 메타
    CLI->>CLI: 전처리 검증·다운샘플·dtype 변환·train/val 분할
    CLI->>Model: 모델 빌드·컴파일
    Model->>Model: 학습(Checkpoint, EarlyStopping, ReduceLROnPlateau)
    Model-->>FS: 체크포인트·스케일러·history.csv 저장
    CLI->>FS: summary.json 기록
    CLI->>DL: 반복: schema=fixed17
    DL-->>CLI: 두번째 전처리 결과
    CLI->>Model: 두번째 모델 빌드·학습
    Model-->>FS: 두번째 아티팩트 저장
    CLI->>FS: val_curve_comparison.csv, comparison_summary.json, comparison_report.md 작성
    CLI-->>User: 종료 코드 반환
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

AI

Suggested reviewers

  • discipline24

Poem

🐇 나는 작은 토끼, 실험 밭을 건너,
두 스키마가 달려 누가 더 낮을까 보네.
시드 뿌려 결정론 길 닦고, 아티팩트 둥지에 저장하네.
비교표 펴고 곡선 보며 살짝 기뻐 뛰네.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 "[AI] [FEAT] 계정 ID 생성"으로 계정 ID 생성 기능을 명확하게 요약하며, 변경사항의 핵심인 account_code 및 account_id 시스템 추가를 반영하고 있습니다.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 20260402-#350-executions-계정-분리(account_id)-및-다중-데일리-파이프라인-대응

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
Review rate limit: 0/1 reviews remaining, refill in 51 minutes and 51 seconds.

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

@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: 4

🧹 Nitpick comments (3)
AI/backtests/compare_transformer_feature_schemas.py (2)

97-100: enable_op_determinism 실패 시 로깅 고려.

TensorFlow의 결정론적 연산 활성화는 선택적 기능이지만, 실패 시 조용히 무시하면 재현성 문제 디버깅이 어려울 수 있습니다.

♻️ 로깅 추가 제안
     try:
         tf.config.experimental.enable_op_determinism()
-    except Exception:
-        pass
+    except Exception as e:
+        print(f"[Warning] TF op determinism not enabled: {e}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/backtests/compare_transformer_feature_schemas.py` around lines 97 - 100,
The try/except around tf.config.experimental.enable_op_determinism() currently
swallows all errors; update it to catch Exception as e and log a warning with
the exception details so failures to enable determinism are visible for
debugging (use the module logger via logging.getLogger(__name__) or the existing
logger in this module, and ensure logging is imported/available). This change
should target the block containing tf.config.experimental.enable_op_determinism
to replace the silent pass with a descriptive warning including the exception
message/trace.

255-261: 불필요한 int() 캐스팅 제거 가능.

Static analysis에서 지적된 대로 이미 정수인 값에 대한 int() 호출은 불필요합니다.

♻️ 정리 제안
-        min_idx = int(val_loss_series.idxmin())
+        min_idx = val_loss_series.idxmin()
         best_val_loss = _safe_float(val_loss_series.iloc[min_idx])
-        best_epoch = int(history_df.loc[min_idx, "epoch"])
+        best_epoch = history_df.loc[min_idx, "epoch"]
         final_val_loss = _safe_float(val_loss_series.iloc[-1])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/backtests/compare_transformer_feature_schemas.py` around lines 255 - 261,
Remove redundant int() casts for fields that are already integers in the dict
assignment (keys "n_samples", "lookback", "horizons", "train_rows",
"epochs_ran"): stop wrapping len(y_class), lookback, len(raw_df), and
len(history_df) with int(), and simplify the horizons construction by removing
the int(h) cast if horizons/h values are already ints (the code building this
dict — the lines assigning "n_samples", "lookback", "horizons", "train_rows",
"epochs_ran" — should return the raw integer values or an identity-mapped
horizons list instead of re-casting to int()).
schema.sql (1)

223-223: account_id nullable 설계 확인 필요.

account_id가 NOT NULL 제약 없이 nullable로 정의되어 있습니다. 이는 기존 데이터와의 하위 호환성을 위한 의도적 설계로 보이며, repository 코드에서도 account_id IS NULL 케이스를 처리하고 있습니다.

향후 모든 실행이 account 기반으로 전환된 후에는 NOT NULL 제약 추가를 고려해 보세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@schema.sql` at line 223, The column "account_id" is defined nullable—confirm
this is intentional for backward compatibility with legacy executions and ensure
code handles NULLs; update the schema definition around "account_id" to include
a clarifying SQL comment or inline note referencing account_names.id and the
compatibility rationale, and add a TODO/migration note to plan adding a NOT NULL
constraint once all executions are account-bound; also verify repository logic
that checks for "account_id IS NULL" (search for usages of account_id in
repository code) and adjust tests to cover both NULL and non-NULL scenarios.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@AI/backtests/compare_transformer_feature_schemas.py`:
- Around line 29-53: PRE307_DYNAMIC_23 lists MA feature names with underscores
(ma_5_ratio, ma_20_ratio, ma_60_ratio) but add_technical_indicators() actually
generates ma5_ratio, ma20_ratio, ma60_ratio so DataLoader.create_dataset
silently drops those features; update PRE307_DYNAMIC_23 to use the actual
generated names (ma5_ratio, ma20_ratio, ma60_ratio) OR alternatively change
add_technical_indicators() to emit the underscored variants so names match;
ensure the change is consistent with
transformer_refactor_change_analysis_2026-04-03.md and run the dataset creation
(DataLoader.create_dataset) to verify the three MA features are included.
- Around line 159-161: The dataset creation silently drops missing features;
update the compare logic after calling loader.create_dataset (the call that
returns X_ts, X_ticker, X_sector, y_class, _, info) to compute the difference
between requested_features and the actual effective_features returned in info
(or derive effective_features from what was used) and emit a warning log
including schema_name and the list of missing features; if all requested
features are missing, also surface that via a clearer exception or a stronger
log entry to aid debugging.

In `@AI/backtests/out/transformer_schema_compare_smoke/run_config.json`:
- Line 22: The run config currently hardcodes a local Windows user path in the
JSON key "output_dir", which breaks CI/other developers; update the "output_dir"
value to a relative project path (e.g., under ./out/) or make it configurable
via an environment variable and fallback to a relative path, and ensure the
generated smoke-test output directory is added to .gitignore so local artifacts
aren't committed; locate the "output_dir" key in run_config.json and replace the
absolute path accordingly.

In `@AI/libs/database/repository.py`:
- Around line 350-365: The else branch currently only deletes from executions
when account_id is set, omitting xai_reports, portfolio_positions, and
portfolio_summary; update the block around the cursor.execute calls that
reference account_id and target_date so it either (A) issues DELETEs for
xai_reports, portfolio_positions, and portfolio_summary using the same date
filter (without account_id) to perform the intended global-date removals when
allow_global_chain_reset=1 and account_id is provided, or (B) (preferred for
per-account isolation) add account_id columns to those tables and then update
the DELETE statements to include "AND account_id = %s" and pass account_id
(mirroring the executions delete). Modify the code paths that call
cursor.execute (the statements using account_id and target_date) accordingly so
the else block consistently handles those three tables.

---

Nitpick comments:
In `@AI/backtests/compare_transformer_feature_schemas.py`:
- Around line 97-100: The try/except around
tf.config.experimental.enable_op_determinism() currently swallows all errors;
update it to catch Exception as e and log a warning with the exception details
so failures to enable determinism are visible for debugging (use the module
logger via logging.getLogger(__name__) or the existing logger in this module,
and ensure logging is imported/available). This change should target the block
containing tf.config.experimental.enable_op_determinism to replace the silent
pass with a descriptive warning including the exception message/trace.
- Around line 255-261: Remove redundant int() casts for fields that are already
integers in the dict assignment (keys "n_samples", "lookback", "horizons",
"train_rows", "epochs_ran"): stop wrapping len(y_class), lookback, len(raw_df),
and len(history_df) with int(), and simplify the horizons construction by
removing the int(h) cast if horizons/h values are already ints (the code
building this dict — the lines assigning "n_samples", "lookback", "horizons",
"train_rows", "epochs_ran" — should return the raw integer values or an
identity-mapped horizons list instead of re-casting to int()).

In `@schema.sql`:
- Line 223: The column "account_id" is defined nullable—confirm this is
intentional for backward compatibility with legacy executions and ensure code
handles NULLs; update the schema definition around "account_id" to include a
clarifying SQL comment or inline note referencing account_names.id and the
compatibility rationale, and add a TODO/migration note to plan adding a NOT NULL
constraint once all executions are account-bound; also verify repository logic
that checks for "account_id IS NULL" (search for usages of account_id in
repository code) and adjust tests to cover both NULL and non-NULL scenarios.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44e1ef06-819a-42f2-bc23-6d06f89c323e

📥 Commits

Reviewing files that changed from the base of the PR and between 5b142de and 262cd04.

📒 Files selected for processing (8)
  • AI/backtests/compare_transformer_feature_schemas.py
  • AI/backtests/out/transformer_refactor_change_analysis_2026-04-03.md
  • AI/backtests/out/transformer_schema_compare_smoke/run_config.json
  • AI/config/trading.default.json
  • AI/config/trading.py
  • AI/libs/database/repository.py
  • AI/pipelines/daily_routine.py
  • schema.sql

Comment thread AI/backtests/compare_transformer_feature_schemas.py
Comment thread AI/backtests/compare_transformer_feature_schemas.py
"lr_factor": 0.5,
"min_lr": 1e-06,
"verbose": 2,
"output_dir": "C:\\Users\\jaebin\\Documents\\GitHub\\sisc-web\\AI\\backtests\\out\\transformer_schema_compare_smoke"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

로컬 사용자 경로가 하드코딩되어 있어 다른 개발자/CI 환경에서 사용 불가.

C:\\Users\\jaebin\\... 경로는 특정 개발자의 로컬 환경에 종속되어 있습니다. 이 파일이 smoke test 결과물이라면 .gitignore에 추가하거나, 상대 경로로 변경해야 합니다.

🔧 상대 경로로 변경 제안
-  "output_dir": "C:\\Users\\jaebin\\Documents\\GitHub\\sisc-web\\AI\\backtests\\out\\transformer_schema_compare_smoke"
+  "output_dir": "AI/backtests/out/transformer_schema_compare_smoke"
📝 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
"output_dir": "C:\\Users\\jaebin\\Documents\\GitHub\\sisc-web\\AI\\backtests\\out\\transformer_schema_compare_smoke"
"output_dir": "AI/backtests/out/transformer_schema_compare_smoke"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/backtests/out/transformer_schema_compare_smoke/run_config.json` at line
22, The run config currently hardcodes a local Windows user path in the JSON key
"output_dir", which breaks CI/other developers; update the "output_dir" value to
a relative project path (e.g., under ./out/) or make it configurable via an
environment variable and fallback to a relative path, and ensure the generated
smoke-test output directory is added to .gitignore so local artifacts aren't
committed; locate the "output_dir" key in run_config.json and replace the
absolute path accordingly.

Comment thread AI/libs/database/repository.py

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
AI/libs/database/repository.py (2)

291-326: ⚠️ Potential issue | 🟠 Major

_resolve_account_id 호출 전 커넥션 누수 가능성

get_open_tickers에서도 동일한 패턴입니다. 라인 295의 self._resolve_account_id(conn)이 try/finally(라인 317/325) 밖에 있어, account_code가 미등록/비활성이거나 내부에서 예외가 발생하면 conn이 닫히지 않고 누수됩니다.

🛠️ 제안 수정
         conn = self._get_connection()
         if conn is None:
             return []
 
-        account_id = self._resolve_account_id(conn)
-        query = """
-            SELECT ticker
-            FROM public.executions
-            WHERE fill_date <= %s
-        """
-        query_params: list[Any] = [target_date]
-        if account_id is not None:
-            query += " AND account_id = %s "
-            query_params.append(account_id)
-        query += """
-            GROUP BY ticker
-            ...
-        """
-
         try:
+            account_id = self._resolve_account_id(conn)
+            query = """
+                SELECT ticker
+                FROM public.executions
+                WHERE fill_date <= %s
+            """
+            query_params: list[Any] = [target_date]
+            if account_id is not None:
+                query += " AND account_id = %s "
+                query_params.append(account_id)
+            query += """
+                GROUP BY ticker
+                HAVING SUM(
+                    CASE
+                        WHEN side = 'BUY' THEN qty
+                        WHEN side = 'SELL' THEN -qty
+                        ELSE 0
+                    END
+                ) > 0
+                ORDER BY ticker
+            """
             with conn.cursor() as cursor:
                 cursor.execute(query, tuple(query_params))
                 rows = cursor.fetchall()
             return [str(row[0]) for row in rows]
         ...
         finally:
             conn.close()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/libs/database/repository.py` around lines 291 - 326, The call to
_resolve_account_id(conn) in get_open_tickers is made before the try/finally
that closes conn, which can leak the DB connection if _resolve_account_id
raises; move the call into the try block (i.e., resolve account_id after
acquiring conn but before executing the query, inside the existing try that
opens the cursor), so any exception will hit the except/finally and ensure
conn.close() runs; update references to account_id and query_params within that
try scope and keep conn.close() in the finally as-is.

145-162: ⚠️ Potential issue | 🟠 Major

_resolve_account_id 호출 전 커서/커넥션 누수 가능성

라인 150에서 cursor를 열고 라인 159에서 self._resolve_account_id(conn)를 호출하지만, try/finally 블록은 라인 173부터 시작합니다. _resolve_account_id는 account_code가 DB에 없거나 비활성이면 ValueError를 raise하고(라인 114-127), 커넥션 획득 실패 시 RuntimeError를 raise합니다(라인 94-96). 이 경우 cursor와 conn이 닫히지 않고 누수됩니다.

🛠️ 제안 수정
         conn = self._get_connection()
         # DB 연결 실패 시 기본 상태 반환
         if conn is None:
             return {"cash": initial_cash, "qty": 0, "avg_price": 0.0, "pnl_realized_cum": 0.0}
-            
-        cursor = conn.cursor()
-        
-        # 기본 쿼리: 특정 티커의 거래 내역을 모두 가져옴
-        query = """
-            SELECT side, qty, fill_price, commission
-            FROM public.executions
-            WHERE ticker = %s 
-        """
-        params = [ticker]
-        account_id = self._resolve_account_id(conn)
-        if account_id is not None:
-            query += " AND account_id = %s "
-            params.append(account_id)
-        
-        # target_date가 주어지면 그 날짜 '이하'의 체결내역만 필터링하여 미래 데이터를 차단합니다.
-        if target_date:
-            query += " AND fill_date <= %s "
-            params.append(target_date)
-            
-        # 시간순으로 정렬하여 정확한 롤링 계산을 수행합니다.
-        # Deterministic ordering is important for reproducible position/cost accounting.
-        query += " ORDER BY fill_date ASC, created_at ASC, id ASC"
-        
+
+        cursor = None
         try:
+            cursor = conn.cursor()
+            query = """
+                SELECT side, qty, fill_price, commission
+                FROM public.executions
+                WHERE ticker = %s
+            """
+            params = [ticker]
+            account_id = self._resolve_account_id(conn)
+            if account_id is not None:
+                query += " AND account_id = %s "
+                params.append(account_id)
+            if target_date:
+                query += " AND fill_date <= %s "
+                params.append(target_date)
+            query += " ORDER BY fill_date ASC, created_at ASC, id ASC"
             cursor.execute(query, tuple(params))
             ...
         finally:
-            cursor.close()
+            if cursor is not None:
+                cursor.close()
             conn.close()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/libs/database/repository.py` around lines 145 - 162, The code opens a
cursor (cursor = conn.cursor()) before calling self._resolve_account_id(conn)
which can raise errors and leak cursor/conn; move the _resolve_account_id(conn)
call to occur before acquiring the cursor (or wrap the early portion in a
try/finally/with to ensure cursor and conn are closed on exception).
Specifically, ensure _resolve_account_id is invoked and its exceptions handled
(or the connection tested) before creating cursor in the function that builds
the executions query, and if you keep cursor creation before the call, wrap both
cursor and conn usage in a try/finally (or use context managers) so
cursor.close() and conn.close() always run when _resolve_account_id raises;
reference the symbols cursor, conn, and method _resolve_account_id to locate and
modify the code.
🧹 Nitpick comments (2)
AI/backtests/compare_transformer_feature_schemas.py (1)

483-526: 두 스키마 호출의 공통 kwargs 묶기(선택 사항).

_fit_single_schema 호출이 schema_name / requested_features / case_output_dir만 다르고 나머지 인자와 max_samples 정규화가 동일하게 반복됩니다. 공통 kwargs를 dict로 한 번만 만들고 차이 부분만 덮어쓰면 향후 파라미터 추가 시 동기화 누락 위험이 줄어듭니다.

♻️ 예시
-    dynamic_artifacts = _fit_single_schema(
-        schema_name="dynamic23",
-        requested_features=PRE307_DYNAMIC_23,
-        case_output_dir=output_dir / "dynamic23",
-        db_name=args.db_name,
-        ...
-        max_samples=(None if args.max_samples <= 0 else args.max_samples),
-        verbose=args.verbose,
-    )
-
-    print("[SchemaCompare] Training case 2/2: fixed17 (current schema)")
-    fixed_artifacts = _fit_single_schema(
-        schema_name="fixed17",
-        requested_features=CURRENT_FIXED_17,
-        case_output_dir=output_dir / "fixed17",
-        ...
-    )
+    common_kwargs = dict(
+        db_name=args.db_name,
+        lookback=args.lookback,
+        horizons=horizons,
+        data_start_date=args.data_start_date,
+        train_end_date=args.train_end_date,
+        test_size=args.test_size,
+        split_seed=args.split_seed,
+        global_seed=args.global_seed,
+        epochs=args.epochs,
+        batch_size=args.batch_size,
+        patience=args.patience,
+        lr_patience=args.lr_patience,
+        lr_factor=args.lr_factor,
+        min_lr=args.min_lr,
+        max_samples=(None if args.max_samples <= 0 else args.max_samples),
+        verbose=args.verbose,
+    )
+    dynamic_artifacts = _fit_single_schema(
+        schema_name="dynamic23",
+        requested_features=PRE307_DYNAMIC_23,
+        case_output_dir=output_dir / "dynamic23",
+        **common_kwargs,
+    )
+    print("[SchemaCompare] Training case 2/2: fixed17 (current schema)")
+    fixed_artifacts = _fit_single_schema(
+        schema_name="fixed17",
+        requested_features=CURRENT_FIXED_17,
+        case_output_dir=output_dir / "fixed17",
+        **common_kwargs,
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/backtests/compare_transformer_feature_schemas.py` around lines 483 - 526,
Both _fit_single_schema calls repeat the same kwargs (max_samples normalization,
db_name, lookback, horizons, etc.); create a single common_kwargs dict
containing all shared parameters including max_samples=(None if args.max_samples
<= 0 else args.max_samples) and other repeated args (db_name, lookback,
horizons, data_start_date, train_end_date, test_size, split_seed, global_seed,
epochs, batch_size, patience, lr_patience, lr_factor, min_lr, verbose), then
call _fit_single_schema twice by unpacking common_kwargs and overriding
schema_name/requested_features/case_output_dir (for dynamic_artifacts use
PRE307_DYNAMIC_23 and output_dir / "dynamic23"; for fixed_artifacts use
CURRENT_FIXED_17 and output_dir / "fixed17") to avoid duplication and keep
max_samples normalization centralized.
AI/libs/database/repository.py (1)

81-130: _resolve_account_id에 대한 몇 가지 개선 제안 (선택)

  • 스키마에 CONSTRAINT "uq_account_names_account_code" UNIQUE ("account_code")가 존재하므로, ORDER BY id ASC와 len(rows) > 1 검사는 사실상 실행되지 않는 dead-defense 경로입니다. 단일 row를 명시적으로 조회하려면 LIMIT 1 + fetchone() 형태가 더 간결합니다.
  • _account_id_cache는 인스턴스 수명 동안 무효화되지 않습니다. 파이프라인 수명이 짧다면 문제되지 않지만, 장기 실행 컨텍스트(예: 데몬)에서 is_active 토글을 반영하려면 TTL/명시적 invalidate가 필요합니다.
  • 외부에서 conn을 주입받은 경로(라인 99-109)에서 cursor.execute가 예외를 던지면 해당 트랜잭션이 aborted 상태로 남아 호출자의 다음 쿼리가 실패합니다. 호출자 측에서 rollback을 수행하는지 확인하거나, 주입된 conn이면 예외를 감싸 원인을 명확히 하는 편이 안전합니다.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@AI/backtests/compare_transformer_feature_schemas.py`:
- Around line 528-541: The code unconditionally reads CSVs into
dynamic_df/fixed_df and selects ["epoch","val_loss"], which can raise KeyError
if val_loss is missing and is also inefficient because the CSV was just written;
update the pipeline so _fit_single_schema returns the in-memory history (e.g.,
include history_df or a val_loss series in TrainArtifacts) and use that instead
of pd.read_csv, and make the merge defensive by checking for "val_loss" in
dynamic_df.columns/fixed_df.columns (or using .get) and creating a NaN-filled
column (e.g., dynamic23_val_loss/fixed17_val_loss) when absent before merging;
reference symbols: _fit_single_schema, TrainArtifacts, dynamic_df, fixed_df,
curve_df, and _save_curve_plot.

In `@AI/libs/database/repository.py`:
- Around line 375-392: The branch that deletes rows by run_id is asymmetric:
when account_id is None the code deletes both public.executions and
public.xai_reports by run_id, but when account_id is set it only deletes
public.executions; update the logic in the function containing these
cursor.execute calls so that the account-scoped branch also deletes matching
xai_reports (include account_id in the WHERE if xai_reports is account-scoped)
or, if the omission was intentional, add a clear comment referencing
allow_global_chain_reset and why xai_reports must be preserved for
account-scoped runs; check save_reports_to_db to confirm whether xai_reports
stores run_id and account_id to decide whether to delete by run_id only or by
run_id+account_id and apply the same deletion rule consistently.

---

Outside diff comments:
In `@AI/libs/database/repository.py`:
- Around line 291-326: The call to _resolve_account_id(conn) in get_open_tickers
is made before the try/finally that closes conn, which can leak the DB
connection if _resolve_account_id raises; move the call into the try block
(i.e., resolve account_id after acquiring conn but before executing the query,
inside the existing try that opens the cursor), so any exception will hit the
except/finally and ensure conn.close() runs; update references to account_id and
query_params within that try scope and keep conn.close() in the finally as-is.
- Around line 145-162: The code opens a cursor (cursor = conn.cursor()) before
calling self._resolve_account_id(conn) which can raise errors and leak
cursor/conn; move the _resolve_account_id(conn) call to occur before acquiring
the cursor (or wrap the early portion in a try/finally/with to ensure cursor and
conn are closed on exception). Specifically, ensure _resolve_account_id is
invoked and its exceptions handled (or the connection tested) before creating
cursor in the function that builds the executions query, and if you keep cursor
creation before the call, wrap both cursor and conn usage in a try/finally (or
use context managers) so cursor.close() and conn.close() always run when
_resolve_account_id raises; reference the symbols cursor, conn, and method
_resolve_account_id to locate and modify the code.

---

Nitpick comments:
In `@AI/backtests/compare_transformer_feature_schemas.py`:
- Around line 483-526: Both _fit_single_schema calls repeat the same kwargs
(max_samples normalization, db_name, lookback, horizons, etc.); create a single
common_kwargs dict containing all shared parameters including max_samples=(None
if args.max_samples <= 0 else args.max_samples) and other repeated args
(db_name, lookback, horizons, data_start_date, train_end_date, test_size,
split_seed, global_seed, epochs, batch_size, patience, lr_patience, lr_factor,
min_lr, verbose), then call _fit_single_schema twice by unpacking common_kwargs
and overriding schema_name/requested_features/case_output_dir (for
dynamic_artifacts use PRE307_DYNAMIC_23 and output_dir / "dynamic23"; for
fixed_artifacts use CURRENT_FIXED_17 and output_dir / "fixed17") to avoid
duplication and keep max_samples normalization centralized.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 19586bff-af9b-4202-9853-8f24beb14c29

📥 Commits

Reviewing files that changed from the base of the PR and between 262cd04 and 6d2e920.

📒 Files selected for processing (3)
  • .gitignore
  • AI/backtests/compare_transformer_feature_schemas.py
  • AI/libs/database/repository.py
✅ Files skipped from review due to trivial changes (1)
  • .gitignore

Comment on lines +528 to +541
dynamic_df = pd.read_csv(dynamic_artifacts.history_csv)
fixed_df = pd.read_csv(fixed_artifacts.history_csv)

curve_df = pd.DataFrame({"epoch": np.arange(1, max(len(dynamic_df), len(fixed_df)) + 1)})
curve_df = curve_df.merge(
dynamic_df[["epoch", "val_loss"]].rename(columns={"val_loss": "dynamic23_val_loss"}),
on="epoch",
how="left",
)
curve_df = curve_df.merge(
fixed_df[["epoch", "val_loss"]].rename(columns={"val_loss": "fixed17_val_loss"}),
on="epoch",
how="left",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

val_loss 누락 시 KeyError 가능 + 방금 쓴 CSV를 다시 읽는 비효율.

_fit_single_schema는 val_loss가 history에 없을 가능성을 방어적으로 처리(L288-297)하지만, 여기서는 dynamic_df[["epoch", "val_loss"]] / fixed_df[["epoch", "val_loss"]] 를 무조건 참조하므로 열이 없으면 _save_curve_plot의 가드(L354)에 도달하기 전에 KeyError가 발생합니다. 또한 방금 쓴 CSV를 다시 읽는 대신 이미 메모리에 있는 history_df(또는 val_loss 벡터)를 TrainArtifacts로 반환해 재사용하는 편이 I/O·복잡도 측면에서 자연스럽습니다.

♻️ 방어적 병합 예시
-    dynamic_df = pd.read_csv(dynamic_artifacts.history_csv)
-    fixed_df = pd.read_csv(fixed_artifacts.history_csv)
-
-    curve_df = pd.DataFrame({"epoch": np.arange(1, max(len(dynamic_df), len(fixed_df)) + 1)})
-    curve_df = curve_df.merge(
-        dynamic_df[["epoch", "val_loss"]].rename(columns={"val_loss": "dynamic23_val_loss"}),
-        on="epoch",
-        how="left",
-    )
-    curve_df = curve_df.merge(
-        fixed_df[["epoch", "val_loss"]].rename(columns={"val_loss": "fixed17_val_loss"}),
-        on="epoch",
-        how="left",
-    )
+    dynamic_df = pd.read_csv(dynamic_artifacts.history_csv)
+    fixed_df = pd.read_csv(fixed_artifacts.history_csv)
+
+    curve_df = pd.DataFrame({"epoch": np.arange(1, max(len(dynamic_df), len(fixed_df)) + 1)})
+    for df, prefix in ((dynamic_df, "dynamic23"), (fixed_df, "fixed17")):
+        if "val_loss" in df.columns:
+            curve_df = curve_df.merge(
+                df[["epoch", "val_loss"]].rename(columns={"val_loss": f"{prefix}_val_loss"}),
+                on="epoch",
+                how="left",
+            )
+        else:
+            curve_df[f"{prefix}_val_loss"] = np.nan
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/backtests/compare_transformer_feature_schemas.py` around lines 528 - 541,
The code unconditionally reads CSVs into dynamic_df/fixed_df and selects
["epoch","val_loss"], which can raise KeyError if val_loss is missing and is
also inefficient because the CSV was just written; update the pipeline so
_fit_single_schema returns the in-memory history (e.g., include history_df or a
val_loss series in TrainArtifacts) and use that instead of pd.read_csv, and make
the merge defensive by checking for "val_loss" in
dynamic_df.columns/fixed_df.columns (or using .get) and creating a NaN-filled
column (e.g., dynamic23_val_loss/fixed17_val_loss) when absent before merging;
reference symbols: _fit_single_schema, TrainArtifacts, dynamic_df, fixed_df,
curve_df, and _save_curve_plot.

Comment on lines 375 to +392
else:
if account_id is None:
cursor.execute("DELETE FROM public.executions WHERE run_id = %s", (run_id,))
cursor.execute("DELETE FROM public.xai_reports WHERE run_id = %s", (run_id,))
else:
cursor.execute(
"DELETE FROM public.executions WHERE run_id = %s AND account_id = %s",
(run_id, account_id),
)
else:
if account_id is None:
cursor.execute("DELETE FROM public.executions WHERE run_id = %s", (run_id,))
cursor.execute("DELETE FROM public.xai_reports WHERE run_id = %s", (run_id,))
else:
cursor.execute("DELETE FROM public.executions WHERE run_id = %s", (run_id,))
cursor.execute("DELETE FROM public.xai_reports WHERE run_id = %s", (run_id,))
else:
cursor.execute(
"DELETE FROM public.executions WHERE run_id = %s AND account_id = %s",
(run_id, account_id),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

account_id 지정 시 xai_reports 정리가 비대칭적입니다

account_id is None인 분기(라인 377-378, 386-387)에서는 executions와 xai_reports를 모두 run_id로 삭제하지만, account_id가 설정된 분기(라인 380-383, 389-392)에서는 executions만 삭제하고 xai_reports는 정리되지 않습니다.

  • xai_reports가 run_id 컬럼을 공유한다면(동일 PR의 save_reports_to_db에서 run_id가 기록됨), 재실행 시 이전 회차의 xai_reports가 남아 executions의 xai_report_id FK가 ON DELETE SET NULL에 의해 NULL이 되거나 신규 INSERT의 ON CONFLICT (ticker, date, signal)에 걸려 덮어쓰기가 발생할 수 있습니다.
  • 반대로 여러 계정이 같은 run_id를 공유할 가능성 때문에 의도적으로 생략했다면, 주석으로 의도를 명시해 주시기 바랍니다.

위의 allow_global_chain_reset 분기(라인 367-368)에는 해당 의도에 대한 주석이 있으나, 비-global 분기에는 없어 혼동을 유발합니다. 의도에 맞춰 xai_reports 삭제 추가 또는 근거 주석 중 하나를 선택해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/libs/database/repository.py` around lines 375 - 392, The branch that
deletes rows by run_id is asymmetric: when account_id is None the code deletes
both public.executions and public.xai_reports by run_id, but when account_id is
set it only deletes public.executions; update the logic in the function
containing these cursor.execute calls so that the account-scoped branch also
deletes matching xai_reports (include account_id in the WHERE if xai_reports is
account-scoped) or, if the omission was intentional, add a clear comment
referencing allow_global_chain_reset and why xai_reports must be preserved for
account-scoped runs; check save_reports_to_db to confirm whether xai_reports
stores run_id and account_id to decide whether to delete by run_id only or by
run_id+account_id and apply the same deletion rule consistently.

@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.

🧹 Nitpick comments (2)
AI/backtests/compare_transformer_feature_schemas.py (2)

101-105: 결정론 설정 실패가 조용히 무시되어 재현성 결과를 오도할 수 있습니다.

--deterministic 플래그로 호출했는데 enable_op_determinism()이 내부적으로 실패해도 예외가 삼켜지므로, 사용자는 결정론이 적용되지 않은 상태로 학습이 진행되는 것을 알지 못합니다. 비교 실험의 재현성/신뢰성을 보장하려면 실패 시 경고 로그를 남기는 편이 좋습니다. Ruff S110/BLE001 경고와도 일치합니다.

♻️ 제안 변경
     if deterministic:
         try:
             tf.config.experimental.enable_op_determinism()
-        except Exception:
-            pass
+        except Exception as exc:  # noqa: BLE001 - best-effort determinism
+            print(f"[SchemaCompare][Warning] enable_op_determinism failed: {exc}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/backtests/compare_transformer_feature_schemas.py` around lines 101 - 105,
The code currently swallows exceptions from
tf.config.experimental.enable_op_determinism() when deterministic is true;
change the except block to capture the exception (except Exception as e) and
emit a warning including the exception details (e.g., logger.warning or
logging.warning) so users know deterministic setup failed (aligns with Ruff
S110/BLE001) instead of silently continuing; keep the call inside the existing
if deterministic guard and include clear context in the warning message
mentioning enable_op_determinism().

176-176: 불필요한 int(len(...)) 캐스팅 제거 권장 (Ruff RUF046).

len() 반환값은 이미 int이므로 추가 래핑이 의미가 없습니다. 아래 위치(Line 176, 313, 316, 319, 338, 339)의 int(len(...)) 를 len(...) 로 단순화하면 정적 분석 경고도 함께 해소됩니다.

♻️ 제안 변경
-    raw_n_samples = int(len(y_class))
+    raw_n_samples = len(y_class)
...
-        "n_samples": int(len(y_class)),
+        "n_samples": len(y_class),
...
-        "train_rows": int(len(raw_df)),
+        "train_rows": len(raw_df),
...
-        "epochs_ran": int(len(history_df)),
+        "epochs_ran": len(history_df),
...
-        n_samples=int(len(y_class)),
-        epochs_ran=int(len(history_df)),
+        n_samples=len(y_class),
+        epochs_ran=len(history_df),

Also applies to: 313-339

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/backtests/compare_transformer_feature_schemas.py` at line 176, The
assignment raw_n_samples = int(len(y_class)) uses redundant int(len(...)) (Ruff
RUF046); replace int(len(...)) with len(...) wherever used in this module (e.g.,
the raw_n_samples assignment and the other occurrences flagged around the
compare_transformer_feature_schemas module) so each expression becomes
raw_n_samples = len(y_class) (and similarly simplify the other int(len(...))
instances to len(...)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@AI/backtests/compare_transformer_feature_schemas.py`:
- Around line 101-105: The code currently swallows exceptions from
tf.config.experimental.enable_op_determinism() when deterministic is true;
change the except block to capture the exception (except Exception as e) and
emit a warning including the exception details (e.g., logger.warning or
logging.warning) so users know deterministic setup failed (aligns with Ruff
S110/BLE001) instead of silently continuing; keep the call inside the existing
if deterministic guard and include clear context in the warning message
mentioning enable_op_determinism().
- Line 176: The assignment raw_n_samples = int(len(y_class)) uses redundant
int(len(...)) (Ruff RUF046); replace int(len(...)) with len(...) wherever used
in this module (e.g., the raw_n_samples assignment and the other occurrences
flagged around the compare_transformer_feature_schemas module) so each
expression becomes raw_n_samples = len(y_class) (and similarly simplify the
other int(len(...)) instances to len(...)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 36dd7d05-5531-4d09-8165-f115d59d644f

📥 Commits

Reviewing files that changed from the base of the PR and between 6d2e920 and fab1b22.

📒 Files selected for processing (1)
  • AI/backtests/compare_transformer_feature_schemas.py

vmgfh878-art and others added 12 commits April 30, 2026 15:53
* Kaggle 기반 자동 학습 파이프라인 구축

* Update train.yml

train day sunday->tuesday

* Update train.yml

* Update train.yml

* update notebooks

* update

* Update train.py

* update routine

주석 변경 머지한 모델들 주석처리 해제
upload to kaggle import 개선

* update

* DB 연결 수정

* update

* update kaggle notebook

* Update train_kaggle.py

* update

* Update train_kaggle.py

* 학습 메세지 간략화

* TCN 노트북 업데이트

* itransformer update

* Update train_kaggle.py

* 노트북 데이터 누수 방지

* 가중치 다운로드 파일 수정

* Update download_weights.py

* Update train_kaggle.py

* TCN 학습 파라미터 조정

* i트랜스포머 다운로드 관련 버전 변경

* TCN 파라미터 수정

* TCN 파라미터 조정

학습기간 2022.08 부터 하니까 0.7정도로 낮아지는 모습 보임. 2023부터 테스트 하는걸로 변경 후 학습해볼 예정

* TCN 개선

L2노름, 조기종료 도입

* TCN 파라미터 수정

규제 추가하여서, 이전 dropout channel 값으로 다시 테스트

* 캐글 노트북 트리거 단순화

캐글 환경 노트북만 사용하고 로컬은 따로 노트북 사용하지 않음.

* TCN 파라미터 조정

* update

* [AI] [FEAT] kaggle 연결 준비 완료

---------

Co-authored-by: twq110 <aaa11093@gmail.com>
* Kaggle 기반 자동 학습 파이프라인 구축

* Update train.yml

train day sunday->tuesday

* Update train.yml

* Update train.yml

* update notebooks

* update

* Update train.py

* update routine

주석 변경 머지한 모델들 주석처리 해제
upload to kaggle import 개선

* update

* DB 연결 수정

* update

* update kaggle notebook

* Update train_kaggle.py

* update

* Update train_kaggle.py

* 학습 메세지 간략화

* TCN 노트북 업데이트

* itransformer update

* Update train_kaggle.py

* 노트북 데이터 누수 방지

* 가중치 다운로드 파일 수정

* Update download_weights.py

* Update train_kaggle.py

* TCN 학습 파라미터 조정

* i트랜스포머 다운로드 관련 버전 변경

* TCN 파라미터 수정

* TCN 파라미터 조정

학습기간 2022.08 부터 하니까 0.7정도로 낮아지는 모습 보임. 2023부터 테스트 하는걸로 변경 후 학습해볼 예정

* TCN 개선

L2노름, 조기종료 도입

* TCN 파라미터 수정

규제 추가하여서, 이전 dropout channel 값으로 다시 테스트

* 캐글 노트북 트리거 단순화

캐글 환경 노트북만 사용하고 로컬은 따로 노트북 사용하지 않음.

* TCN 파라미터 조정

* update

* [AI] [FEAT] kaggle 연결 준비 완료

* [AI] [FIX] 캐글이 데이터를 못 읽는 버그 수정

---------

Co-authored-by: vmgfh878 <vmgfh878@gmail.com>
* Kaggle 기반 자동 학습 파이프라인 구축

* Update train.yml

train day sunday->tuesday

* Update train.yml

* Update train.yml

* update notebooks

* update

* Update train.py

* update routine

주석 변경 머지한 모델들 주석처리 해제
upload to kaggle import 개선

* update

* DB 연결 수정

* update

* update kaggle notebook

* Update train_kaggle.py

* update

* Update train_kaggle.py

* 학습 메세지 간략화

* TCN 노트북 업데이트

* itransformer update

* Update train_kaggle.py

* 노트북 데이터 누수 방지

* 가중치 다운로드 파일 수정

* Update download_weights.py

* Update train_kaggle.py

* TCN 학습 파라미터 조정

* i트랜스포머 다운로드 관련 버전 변경

* TCN 파라미터 수정

* TCN 파라미터 조정

학습기간 2022.08 부터 하니까 0.7정도로 낮아지는 모습 보임. 2023부터 테스트 하는걸로 변경 후 학습해볼 예정

* TCN 개선

L2노름, 조기종료 도입

* TCN 파라미터 수정

규제 추가하여서, 이전 dropout channel 값으로 다시 테스트

* 캐글 노트북 트리거 단순화

캐글 환경 노트북만 사용하고 로컬은 따로 노트북 사용하지 않음.

* TCN 파라미터 조정

* update

* [AI] [FEAT] kaggle 연결 준비 완료

* [AI] [FIX] 캐글이 데이터를 못 읽는 버그 수정

* [AI] [FIX] 디렉토리 하드코딩 수정

---------

Co-authored-by: vmgfh878 <vmgfh878@gmail.com>
* Kaggle 기반 자동 학습 파이프라인 구축

* Update train.yml

train day sunday->tuesday

* Update train.yml

* Update train.yml

* update notebooks

* update

* Update train.py

* update routine

주석 변경 머지한 모델들 주석처리 해제
upload to kaggle import 개선

* update

* DB 연결 수정

* update

* update kaggle notebook

* Update train_kaggle.py

* update

* Update train_kaggle.py

* 학습 메세지 간략화

* TCN 노트북 업데이트

* itransformer update

* Update train_kaggle.py

* 노트북 데이터 누수 방지

* 가중치 다운로드 파일 수정

* Update download_weights.py

* Update train_kaggle.py

* TCN 학습 파라미터 조정

* i트랜스포머 다운로드 관련 버전 변경

* TCN 파라미터 수정

* TCN 파라미터 조정

학습기간 2022.08 부터 하니까 0.7정도로 낮아지는 모습 보임. 2023부터 테스트 하는걸로 변경 후 학습해볼 예정

* TCN 개선

L2노름, 조기종료 도입

* TCN 파라미터 수정

규제 추가하여서, 이전 dropout channel 값으로 다시 테스트

* 캐글 노트북 트리거 단순화

캐글 환경 노트북만 사용하고 로컬은 따로 노트북 사용하지 않음.

* TCN 파라미터 조정

* update

* [AI] [FEAT] kaggle 연결 준비 완료

* [AI] [FIX] 캐글이 데이터를 못 읽는 버그 수정

* [AI] [FIX] 디렉토리 하드코딩 수정

* [AI] [FIX] zip 파일 임포트 되도록 수정

---------

Co-authored-by: vmgfh878 <vmgfh878@gmail.com>
* Kaggle 기반 자동 학습 파이프라인 구축

* Update train.yml

train day sunday->tuesday

* Update train.yml

* Update train.yml

* update notebooks

* update

* Update train.py

* update routine

주석 변경 머지한 모델들 주석처리 해제
upload to kaggle import 개선

* update

* DB 연결 수정

* update

* update kaggle notebook

* Update train_kaggle.py

* update

* Update train_kaggle.py

* 학습 메세지 간략화

* TCN 노트북 업데이트

* itransformer update

* Update train_kaggle.py

* 노트북 데이터 누수 방지

* 가중치 다운로드 파일 수정

* Update download_weights.py

* Update train_kaggle.py

* TCN 학습 파라미터 조정

* i트랜스포머 다운로드 관련 버전 변경

* TCN 파라미터 수정

* TCN 파라미터 조정

학습기간 2022.08 부터 하니까 0.7정도로 낮아지는 모습 보임. 2023부터 테스트 하는걸로 변경 후 학습해볼 예정

* TCN 개선

L2노름, 조기종료 도입

* TCN 파라미터 수정

규제 추가하여서, 이전 dropout channel 값으로 다시 테스트

* 캐글 노트북 트리거 단순화

캐글 환경 노트북만 사용하고 로컬은 따로 노트북 사용하지 않음.

* TCN 파라미터 조정

* update

* [AI] [FEAT] kaggle 연결 준비 완료

* [AI] [FIX] 캐글이 데이터를 못 읽는 버그 수정

* [AI] [FIX] 디렉토리 하드코딩 수정

* [AI] [FIX] zip 파일 임포트 되도록 수정

* [AI] [FIX] 경로 수정

---------

Co-authored-by: vmgfh878 <vmgfh878@gmail.com>
* Kaggle 기반 자동 학습 파이프라인 구축

* Update train.yml

train day sunday->tuesday

* Update train.yml

* Update train.yml

* update notebooks

* update

* Update train.py

* update routine

주석 변경 머지한 모델들 주석처리 해제
upload to kaggle import 개선

* update

* DB 연결 수정

* update

* update kaggle notebook

* Update train_kaggle.py

* update

* Update train_kaggle.py

* 학습 메세지 간략화

* TCN 노트북 업데이트

* itransformer update

* Update train_kaggle.py

* 노트북 데이터 누수 방지

* 가중치 다운로드 파일 수정

* Update download_weights.py

* Update train_kaggle.py

* TCN 학습 파라미터 조정

* i트랜스포머 다운로드 관련 버전 변경

* TCN 파라미터 수정

* TCN 파라미터 조정

학습기간 2022.08 부터 하니까 0.7정도로 낮아지는 모습 보임. 2023부터 테스트 하는걸로 변경 후 학습해볼 예정

* TCN 개선

L2노름, 조기종료 도입

* TCN 파라미터 수정

규제 추가하여서, 이전 dropout channel 값으로 다시 테스트

* 캐글 노트북 트리거 단순화

캐글 환경 노트북만 사용하고 로컬은 따로 노트북 사용하지 않음.

* TCN 파라미터 조정

* update

* [AI] [FEAT] kaggle 연결 준비 완료

* [AI] [FIX] 캐글이 데이터를 못 읽는 버그 수정

* [AI] [FIX] 디렉토리 하드코딩 수정

* [AI] [FIX] zip 파일 임포트 되도록 수정

* [AI] [FIX] 경로 수정

* [AI] [FIX] iTransformer: scaler fit 전 inf/-inf/NaN 정리 및 clip
TCN: parquet 경로 하드코딩 제거
TCN: scaler fit 전 inf/-inf/NaN 정리 및 clip

---------

Co-authored-by: vmgfh878 <vmgfh878@gmail.com>
* Kaggle 기반 자동 학습 파이프라인 구축

* Update train.yml

train day sunday->tuesday

* Update train.yml

* Update train.yml

* update notebooks

* update

* Update train.py

* update routine

주석 변경 머지한 모델들 주석처리 해제
upload to kaggle import 개선

* update

* DB 연결 수정

* update

* update kaggle notebook

* Update train_kaggle.py

* update

* Update train_kaggle.py

* 학습 메세지 간략화

* TCN 노트북 업데이트

* itransformer update

* Update train_kaggle.py

* 노트북 데이터 누수 방지

* 가중치 다운로드 파일 수정

* Update download_weights.py

* Update train_kaggle.py

* TCN 학습 파라미터 조정

* i트랜스포머 다운로드 관련 버전 변경

* TCN 파라미터 수정

* TCN 파라미터 조정

학습기간 2022.08 부터 하니까 0.7정도로 낮아지는 모습 보임. 2023부터 테스트 하는걸로 변경 후 학습해볼 예정

* TCN 개선

L2노름, 조기종료 도입

* TCN 파라미터 수정

규제 추가하여서, 이전 dropout channel 값으로 다시 테스트

* 캐글 노트북 트리거 단순화

캐글 환경 노트북만 사용하고 로컬은 따로 노트북 사용하지 않음.

* TCN 파라미터 조정

* update

* [AI] [FEAT] kaggle 연결 준비 완료

* [AI] [FIX] 캐글이 데이터를 못 읽는 버그 수정

* [AI] [FIX] 디렉토리 하드코딩 수정

* [AI] [FIX] zip 파일 임포트 되도록 수정

* [AI] [FIX] 경로 수정

* [AI] [FIX] iTransformer: scaler fit 전 inf/-inf/NaN 정리 및 clip
TCN: parquet 경로 하드코딩 제거
TCN: scaler fit 전 inf/-inf/NaN 정리 및 clip

* [AI] [FIX] TCN 모듈 변경

---------

Co-authored-by: vmgfh878 <vmgfh878@gmail.com>
…라인-대응' of https://github.com/SISC-IT/sisc-web into 20260402-#350-executions-계정-분리(account_id)-및-다중-데일리-파이프라인-대응

@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: 8

🧹 Nitpick comments (16)
AI/modules/signal/models/itransformer/train_kaggle.py (2)

372-373: 💤 Low value

불필요한 int() 캐스팅

len()은 이미 정수를 반환하므로 int() 래핑이 불필요합니다.

♻️ 제안된 수정
-        "n_train_samples": int(len(X_train)),
-        "n_val_samples"  : int(len(X_val)),
+        "n_train_samples": len(X_train),
+        "n_val_samples"  : len(X_val),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/itransformer/train_kaggle.py` around lines 372 -
373, Remove the unnecessary int() casts around len() when setting
n_train_samples and n_val_samples: use "n_train_samples": len(X_train) and
"n_val_samples": len(X_val) (references: X_train, X_val, the dict keys
n_train_samples/n_val_samples) to simplify the code and avoid redundant casting.

15-21: 💤 Low value

중복된 경로 탐색 함수 확인

_find_kaggle_parquet_dir() (lines 15-21)와 _find_kaggle_dataset_path() (lines 45-52)가 유사한 기능을 수행합니다. 하나로 통합하거나 명확한 용도 구분이 필요해 보입니다.

Also applies to: 45-52

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/itransformer/train_kaggle.py` around lines 15 - 21,
There are two duplicate helpers (_find_kaggle_parquet_dir and
_find_kaggle_dataset_path) that both search for the Kaggle parquet dataset;
consolidate them into a single function (choose one name, e.g.,
_find_kaggle_parquet_dir) that implements the full behavior (recursive glob for
"/kaggle/input/**/price_data.parquet", return dirname of first match, fallback
to os.environ["PARQUET_DIR"] or "/kaggle/input"), remove the other duplicate,
and update all callers to use the consolidated function so behavior and
fallbacks remain identical.
AI/modules/signal/models/TCN/train_kaggle.py (1)

153-157: 💤 Low value

예외 체인 누락

patchtst/train_kaggle.py와 동일한 이슈입니다. raise ... from e를 사용하여 원본 예외를 체인으로 연결하세요.

♻️ 제안된 수정
         except Exception as e:
             fail_count += 1
             print(f"\n[경고] {ticker} 피처 계산 실패 ({fail_count}/20): {e}")
             if fail_count >= 20:
-                raise RuntimeError("피처 계산 실패가 20개를 초과했습니다.")
+                raise RuntimeError("피처 계산 실패가 20개를 초과했습니다.") from e
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/TCN/train_kaggle.py` around lines 153 - 157, The
except block catching Exception as e in train_kaggle.py (the block that
increments fail_count, prints the warning for ticker, and raises RuntimeError
when fail_count >= 20) is missing exception chaining; change the raise
RuntimeError("피처 계산 실패가 20개를 초과했습니다.") to raise RuntimeError("피처 계산 실패가 20개를
초과했습니다.") from e so the original exception (e) is preserved and attached to the
new RuntimeError.
AI/modules/signal/models/patchtst/train_kaggle.py (2)

199-203: 💤 Low value

예외 체인 누락 및 로깅 개선 권장

정적 분석에서 지적된 대로, raise ... from e를 사용하여 원본 예외를 체인으로 연결하면 디버깅 시 스택 트레이스가 더 명확해집니다.

♻️ 제안된 수정
         except Exception as e:
             fail_count += 1
             print(f"\n[경고] {ticker} 피처 계산 실패 ({fail_count}/{fail_limit}): {e}")
             if fail_count >= fail_limit:
-                raise RuntimeError(f"피처 계산 실패가 {fail_limit}개를 초과했습니다.")
+                raise RuntimeError(f"피처 계산 실패가 {fail_limit}개를 초과했습니다.") from e
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/patchtst/train_kaggle.py` around lines 199 - 203,
The except block that increments fail_count when feature calculation fails
should preserve the original exception chain and improve logging: change the
raise RuntimeError to raise RuntimeError(f"피처 계산 실패가 {fail_limit}개를 초과했습니다.")
from e and include the original exception details in the log (e.g., use
logging.exception or include str(e) and traceback) for the ticker handling code
that uses variables fail_count, fail_limit and ticker in train_kaggle.py so the
original stack trace is retained for debugging.

336-336: 💤 Low value

불필요한 f-string 접두사 제거

플레이스홀더가 없는 f-string입니다.

♻️ 제안된 수정
-    print(f"\n>> 완료")
+    print("\n>> 완료")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/patchtst/train_kaggle.py` at line 336, The print
statement uses an unnecessary f-string prefix with no placeholders; update the
print call (the print(f"\n>> 완료") expression) to a normal string literal (remove
the leading 'f') so it becomes print("\n>> 완료"), keeping the same content and
formatting.
AI/modules/signal/models/transformer/train_kaggle.py (1)

209-209: 💤 Low value

불필요한 f-string 접두사 제거

플레이스홀더가 없는 f-string입니다.

♻️ 제안된 수정
-        print(f">> sector 병합 완료")
+        print(">> sector 병합 완료")
-    print(f"\n>> 완료")
+    print("\n>> 완료")

Also applies to: 311-311

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/transformer/train_kaggle.py` at line 209, Remove the
unnecessary f-string prefixes on plain string prints in train_kaggle.py—replace
print(f">> sector 병합 완료") (and the identical occurrence at the other location)
with a regular string literal print(">> sector 병합 완료") so there are no unused
f-strings; search for the exact string ">> sector 병합 완료" to locate and update
both instances.
tcn_wandb_sweep.py (1)

130-134: ⚡ Quick win

피처 계산 실패 시 무음 처리로 인해 디버깅이 어려울 수 있습니다.

다른 train_kaggle.py 스크립트들은 실패 횟수를 추적하고 임계값 초과 시 예외를 발생시키지만, 이 스크립트는 예외를 무음으로 무시합니다. 최소한 실패한 종목 수를 로깅하는 것을 권장합니다.

♻️ 제안된 수정
+    fail_count = 0
     for ticker in tqdm(raw_df['ticker'].unique(), desc="피처 계산"):
         df_t = raw_df[raw_df['ticker'] == ticker].copy()
         try:
             df_t = add_technical_indicators(df_t)
             processed.append(df_t)
         except Exception:
-            pass
+            fail_count += 1
+
+    if fail_count > 0:
+        print(f"[경고] {fail_count}개 종목 피처 계산 실패")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tcn_wandb_sweep.py` around lines 130 - 134, Replace the silent except block
around add_technical_indicators(df_t) with failure tracking and logging: catch
the Exception, increment a failure counter (e.g., failed_count) and log the
symbol identifier and exception details via the existing logger so failures are
visible (reference add_technical_indicators, df_t, processed list); additionally
mirror the other scripts' behavior by raising an exception when failed_count
exceeds a configurable threshold (e.g., max_failures) so the job fails fast
instead of silently continuing.
AI/pipelines/weekly_routine.py (1)

147-153: 💤 Low value

로그 메시지 언어 불일치

Step 5의 로그 메시지가 영어로 되어 있어 다른 단계들(한국어)과 일관성이 없습니다.

♻️ 수정 제안
     if not skip_deploy:
-        success = run_script("deploy_to_server.py", "server deploy")
+        success = run_script("deploy_to_server.py", "서버 배포")
         if not success:
-            log("[error] server deploy failed.")
+            log("[오류] 서버 배포 실패. 파이프라인 중단.")
             return False
     else:
-        log(">> [server deploy] skipped")
+        log(">> [서버 배포] 스킵")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/pipelines/weekly_routine.py` around lines 147 - 153, 로그 메시지 언어가 다른 단계들과
불일치합니다: skip_deploy 분기에서 사용하는 log(...) 메시지를 한국어로 바꿔 일관성 유지하세요; 수정 대상은
skip_deploy 변수 분기와 run_script("deploy_to_server.py", "server deploy") 호출 뒤의 실패
로그(log("[error] server deploy failed.")) 및 성공/스킵 로그(log(">> [server deploy]
skipped"))를 한국어 문구로 교체하여 다른 단계들과 같은 언어를 사용하도록 변경하세요.
AI/scripts/trigger_training.py (2)

153-157: 💤 Low value

upload_to_kaggle.py와 ignore 함수 중복

ignore_ai_files 함수가 upload_to_kaggle.py의 동일한 함수와 중복됩니다(두 파일 모두 skipped_dirs 세트를 정의). 불일치가 발생할 수 있으므로 공통 유틸리티로 추출하는 것을 고려하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/scripts/trigger_training.py` around lines 153 - 157, The ignore_ai_files
function duplicates the same skipped_dirs set found in upload_to_kaggle.py;
extract the shared logic into a common utility (e.g., a new function like
get_skipped_dirs or make_ignore_fn in a utils module) and replace both the
ignore_ai_files definition and the corresponding logic in upload_to_kaggle.py to
import and call that shared function; update usages such as ignore_ai_files and
the copytree call (shutil.copytree(project_root / "AI", work_dir / "AI",
ignore=ignore_ai_files)) to use the imported shared ignore function so the
skipped_dirs set is defined in one place.

193-199: 💤 Low value

dry-run 모드에서 임시 디렉터리가 즉시 삭제됨

dry_run=True일 때 생성된 커널 파일이 with tempfile.TemporaryDirectory() 블록 종료 시 즉시 삭제되어 사용자가 확인할 수 없습니다. dry-run의 목적이 파일 검증이라면, 디버깅을 위해 임시 경로를 출력하거나 삭제를 지연시키는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/scripts/trigger_training.py` around lines 193 - 199, The dry-run currently
creates the temp dir inside a with tempfile.TemporaryDirectory(...) context so
it is removed when the block exits, preventing user inspection; change the flow
so that when dry_run is True you create a non-auto-deleting directory (use
tempfile.mkdtemp(prefix=...) or create Path(tmp) without a context) or skip
entering the TemporaryDirectory context, call write_kernel_files(work_dir,
spec), print the absolute work_dir path (and a clear message), and return
without deleting it; reference the dry_run check and
write_kernel_files(work_dir, spec) to locate where to alter creation/cleanup
behavior.
AI/scripts/deploy_to_server.py (1)

78-79: ⚖️ Poor tradeoff

AutoAddPolicy 사용 시 보안 고려사항

AutoAddPolicy()는 알 수 없는 호스트 키를 자동으로 수락하여 MITM(중간자 공격)에 취약할 수 있습니다. 운영 환경에서는 RejectPolicy와 함께 known_hosts 파일을 사용하거나, 환경 변수로 호스트 키 지문을 전달하는 것이 더 안전합니다.

내부 인프라용이고 위험을 감수할 수 있다면 현재 상태로 유지해도 됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/scripts/deploy_to_server.py` around lines 78 - 79, The code sets
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) which auto-accepts
unknown host keys and is vulnerable to MITM; update the SSH host-key handling in
the paramiko.SSHClient setup to a safer approach: load known_hosts via
ssh.load_system_host_keys() or ssh.load_host_keys(...) and use
paramiko.RejectPolicy(), or implement a custom policy that verifies a host key
fingerprint provided via environment variable before accepting; modify the logic
around paramiko.SSHClient, set_missing_host_key_policy, and any bootstrapping
that reads env vars to support passing/validating a trusted fingerprint or
known_hosts file instead of AutoAddPolicy.
AI/scripts/upload_to_kaggle.py (1)

59-68: ⚡ Quick win

임시 디렉터리 정리 누락

CODE_SOURCE_DIR에 압축용 소스 코드가 복사되지만, ZIP 생성 후 정리되지 않습니다. 디스크 공간을 절약하려면 shutil.rmtree(CODE_SOURCE_DIR)를 추가하세요.

♻️ 수정 제안
     size = os.path.getsize(CODE_ARCHIVE_PATH) / (1024 * 1024)
     print(f">> Kaggle 코드 아카이브 생성: {CODE_ARCHIVE_NAME} ({size:.1f} MB)")
     print(f">> Kaggle 코드 디렉터리 생성: {CODE_SOURCE_DIR}")
+
+    # ZIP 생성 후 임시 디렉터리 정리
+    shutil.rmtree(CODE_SOURCE_DIR)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/scripts/upload_to_kaggle.py` around lines 59 - 68, The temporary source
directory (CODE_SOURCE_DIR) used to build the ZIP at CODE_ARCHIVE_PATH is not
removed after zipping; add cleanup to delete CODE_SOURCE_DIR after the ZipFile
context closes (e.g., call shutil.rmtree(CODE_SOURCE_DIR)) and handle errors so
it doesn't crash the script (wrap in try/except and log or print failures). Also
ensure shutil is imported at the top if missing and perform the removal only
after the zip write loop completes and the context manager has closed the
archive.
AI/modules/signal/models/TCN/wrapper.py (1)

222-233: 💤 Low value

예외 처리 시 예외 타입을 좁히는 것을 고려하세요

배치 추론에서 개별 티커 실패를 격리하는 로직은 적절합니다. 다만 정적 분석 도구가 지적한 대로, Exception을 포괄적으로 잡는 것보다 예상되는 예외 타입(예: ValueError, KeyError)을 명시하면 예기치 않은 버그를 조기에 발견할 수 있습니다.

현재 구조가 운영 안정성을 위해 의도적이라면 이대로 유지해도 됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/TCN/wrapper.py` around lines 222 - 233, Narrow the
broad except in the batch-prep loop: instead of "except Exception as e:" catch
the expected failure types (e.g., "except (ValueError, KeyError, TypeError) as
e:") around the code that processes feature_frame, scaler.transform, and appends
to tensor_list/valid_tickers; optionally add a separate "except Exception as e:"
that logs and re-raises for truly unexpected errors so you still isolate
per-ticker failures while surfacing unknown problems. Ensure you update the
except block that currently prints "[{ticker}] 전처리 실패로 배치 추론에서 제외됨: {e}" to use
the narrowed exception handling for feature_frame, self.seq_len,
self.scaler.transform, tensor_list, and valid_tickers operations.
AI/scripts/download_weights.py (1)

161-162: 💤 Low value

매직 넘버에 대한 설명 추가

100바이트 미만 파일을 건너뛰는 이유가 명확하지 않습니다. 빈 파일이나 오류 응답을 필터링하는 것이라면 상수로 추출하고 주석을 추가하세요.

♻️ 수정 제안
+MIN_VALID_FILE_SIZE = 100  # 오류 응답이나 빈 파일 필터링용
+
 # CLI 폴백 내부:
-                    if os.path.getsize(src) < 100:
+                    if os.path.getsize(src) < MIN_VALID_FILE_SIZE:
                         continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/scripts/download_weights.py` around lines 161 - 162, Replace the magic
number 100 in the file-size check with a named constant (e.g.,
MIN_VALID_FILE_SIZE) and add a brief comment explaining why files below that
threshold are skipped (e.g., to filter out empty files or short error/HTML
responses); update the conditional that uses os.path.getsize(src) to compare
against that constant (reference the existing variable src and the size-check
logic) so the intent is clear and maintainable.
AI/modules/signal/models/__init__.py (1)

14-17: 디렉터리 케이싱 일관성 유지 권장

patchtst 소문자 import 경로는 실제 디렉터리(AI/modules/signal/models/patchtst/)와 올바르게 일치합니다. 다만, 디렉터리 명명 규칙에 일관성이 없습니다: TCN만 대문자(TCN/)이고 나머지 모델들(transformer, itransformer, patchtst)은 모두 소문자입니다. 향후 유지보수성을 위해 모든 모델 디렉터리를 소문자로 통일하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/__init__.py` around lines 14 - 17, The code
currently handles model selection by matching normalized strings (e.g.,
normalized == "patchtst") and importing PatchTSTWrapper from a lowercase path,
but the TCN model directory is the only one capitalized; to ensure consistency,
rename the TCN directory to lowercase (tcn) and update all imports/paths and any
normalization checks that reference "TCN" to use "tcn" (e.g., adjust comparisons
and imports in the model selector that reference TCN/TCNWrapper), and verify
symbol names like PatchTSTWrapper, TransformerWrapper, ITransformerWrapper, and
any module import paths throughout the codebase so all model directories and
import strings are lowercased and consistent.
AI/modules/signal/models/patchtst/wrapper.py (1)

190-193: ⚡ Quick win

추상 메서드 시그니처와 불일치

BaseSignalModel.get_signals() 추상 메서드는 (df: pd.DataFrame, ticker_id: int, sector_id: int) -> Dict[str, float] 시그니처를 명시하고 있습니다. 현재 구현은 **kwargs로 작동하지만, 명시적 파라미터와 타입 힌트가 누락되어 있어 코드 가독성과 IDE 지원이 저하됩니다.

♻️ 기본 클래스 시그니처와 일치하도록 수정
-    def get_signals(self, df, **kwargs):
-        """BaseSignalModel 추상 메서드 구현 - predict()로 위임"""
-        return self.predict(df)
+    def get_signals(self, df: pd.DataFrame, ticker_id: int, sector_id: int) -> Dict[str, float]:
+        """BaseSignalModel 추상 메서드 구현 - predict()로 위임"""
+        # ticker_id, sector_id는 PatchTST에서 미사용
+        return self.predict(df)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/patchtst/wrapper.py` around lines 190 - 193, The
get_signals implementation in wrapper.py uses a generic **kwargs and thus
doesn't match BaseSignalModel.get_signals(df: pd.DataFrame, ticker_id: int,
sector_id: int) -> Dict[str, float]; update the signature of get_signals to
include the explicit parameters and type hints (df: pd.DataFrame, ticker_id:
int, sector_id: int) and return type Dict[str, float], then forward those
parameters to self.predict(...) (or adapt predict to accept them) so the method
signature and typing match BaseSignalModel and IDEs/static checkers can validate
usage; reference get_signals, predict, and BaseSignalModel when making the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.gitignore:
- Line 38: .gitignore contains a duplicated ignore pattern
"AI/data/kaggle_data/"; remove the redundant occurrence so the pattern appears
only once (keep a single "AI/data/kaggle_data/" entry and delete the duplicate).
- Line 49: 현재 .gitignore의 전역 패턴 "test_*.py"가 이미 커밋된
AI/backtests/test_portfolio_evaluation.py를 의도치 않게 무시하게 되므로, 패턴을 더 구체적으로 변경해 기존
테스트를 제외하지 않도록 수정하세요; 예를 들어 임시 테스트만 무시하려면 "test_temp_*.py"로 바꾸거나 특정 디렉토리만 무시하려면
"AI/test_*.py" 또는 루트 전용으로 제한하려면 "**/test_*.py" 같은 구체적 패턴을 사용하고, 필요 시 기존
AI/backtests/test_portfolio_evaluation.py를 .gitignore에서 제외시키지 않도록 확인하세요.

In `@AI/modules/signal/models/patchtst/wrapper.py`:
- Around line 168-174: The code assumes checkpoint['state_dict'] exists and will
KeyError on missing or corrupted checkpoints; update the logic in wrapper.py
where checkpoint['state_dict'] is accessed (the block that then strips "module."
and calls self.model.load_state_dict) to read state_dict via
checkpoint.get('state_dict') and explicitly check for None/invalid type, raising
a clear error (e.g., RuntimeError or KeyError) that includes contextual info
(checkpoint id/path or available keys) if missing, and only then proceed to
strip the "module." prefix and call self.model.load_state_dict(state_dict).

In `@AI/modules/signal/models/TCN/train_kaggle.py`:
- Line 350: The default for the CLI arg parser
(parser.add_argument("--output-dir", default=os.environ.get('WEIGHTS_DIR',
'/kaggle/working/tcn'))) does not match the default used by the train() wrapper
(which currently uses '/kaggle/working'), causing artifacts to be saved to
different locations; update train() to use the same '/kaggle/working/tcn'
default (or better, derive the path from the TCN artifact definition in
artifact_paths, e.g., root_dir / "tcn") so both parser.add_argument and train()
consistently point to the same output directory.

In `@AI/pipelines/weekly_routine.py`:
- Around line 126-128: The comment block in weekly_routine.py that says
"iTransformer, TCN 머지 후 추가 예정" is outdated; update the comment to reflect the
actual execution order and included models (PatchTST → Transformer →
iTransformer → TCN) because trigger_training.py's MODEL_SPECS already contains
all four models; locate the comment in weekly_routine.py (the block around the
sequence note) and replace the obsolete line with a concise, accurate note
stating the current model order and that all models are present.

In `@AI/scripts/deploy_to_server.py`:
- Around line 102-103: ssh.exec_command(f"mkdir -p {remote_dir_q}")를 호출만 하고 결과를
확인하지 않아 디렉터리 생성 실패 시 이후 SCP 업로드가 진행될 수 있습니다; ssh.exec_command 호출의 반환값(stdin,
stdout, stderr)을 받아(stdout, stderr 또는 stdout.channel.recv_exit_status()) 실행 상태와
stderr 내용을 확인하고, non-zero exit code 또는 stderr가 있으면 적절히 로깅(processLogger 또는
logger)하고 예외를 던지거나 함수를 조기 종료해 이후 업로드 작업을 막도록 수정하세요; 참조 심볼: remote_dir_q 및
ssh.exec_command(f"mkdir -p {remote_dir_q}").

In `@AI/scripts/run_xai_cron.sh`:
- Around line 75-80: The current docker run branch that executes /bin/sh -lc
"$XAI_COMMAND" is vulnerable to shell injection and unnecessarily invokes a
login shell; update the run_xai_cron.sh logic so it documents the threat model
for XAI_COMMAND (make explicit in a comment that this value is
operator-controlled and must not come from untrusted sources) and remove the
login flag by using /bin/sh -c instead of /bin/sh -lc, and if you expect future
external input consider replacing direct shell invocation with a safer
alternative (e.g., passing a single-entry exec array or validated/sanitized
command) when constructing the docker run invocation that uses the XAI_COMMAND
variable.

In `@AI/scripts/upload_to_kaggle.py`:
- Line 127: The print call in upload_to_kaggle.py uses an unnecessary f-string
prefix: locate the print statement printing ">> Kaggle 데이터셋 업로드 중..." and remove
the leading f so it becomes a normal string literal; update the print invocation
(the sole print(...) expression) to use plain quotes instead of f-strings.

---

Nitpick comments:
In `@AI/modules/signal/models/__init__.py`:
- Around line 14-17: The code currently handles model selection by matching
normalized strings (e.g., normalized == "patchtst") and importing
PatchTSTWrapper from a lowercase path, but the TCN model directory is the only
one capitalized; to ensure consistency, rename the TCN directory to lowercase
(tcn) and update all imports/paths and any normalization checks that reference
"TCN" to use "tcn" (e.g., adjust comparisons and imports in the model selector
that reference TCN/TCNWrapper), and verify symbol names like PatchTSTWrapper,
TransformerWrapper, ITransformerWrapper, and any module import paths throughout
the codebase so all model directories and import strings are lowercased and
consistent.

In `@AI/modules/signal/models/itransformer/train_kaggle.py`:
- Around line 372-373: Remove the unnecessary int() casts around len() when
setting n_train_samples and n_val_samples: use "n_train_samples": len(X_train)
and "n_val_samples": len(X_val) (references: X_train, X_val, the dict keys
n_train_samples/n_val_samples) to simplify the code and avoid redundant casting.
- Around line 15-21: There are two duplicate helpers (_find_kaggle_parquet_dir
and _find_kaggle_dataset_path) that both search for the Kaggle parquet dataset;
consolidate them into a single function (choose one name, e.g.,
_find_kaggle_parquet_dir) that implements the full behavior (recursive glob for
"/kaggle/input/**/price_data.parquet", return dirname of first match, fallback
to os.environ["PARQUET_DIR"] or "/kaggle/input"), remove the other duplicate,
and update all callers to use the consolidated function so behavior and
fallbacks remain identical.

In `@AI/modules/signal/models/patchtst/train_kaggle.py`:
- Around line 199-203: The except block that increments fail_count when feature
calculation fails should preserve the original exception chain and improve
logging: change the raise RuntimeError to raise RuntimeError(f"피처 계산 실패가
{fail_limit}개를 초과했습니다.") from e and include the original exception details in
the log (e.g., use logging.exception or include str(e) and traceback) for the
ticker handling code that uses variables fail_count, fail_limit and ticker in
train_kaggle.py so the original stack trace is retained for debugging.
- Line 336: The print statement uses an unnecessary f-string prefix with no
placeholders; update the print call (the print(f"\n>> 완료") expression) to a
normal string literal (remove the leading 'f') so it becomes print("\n>> 완료"),
keeping the same content and formatting.

In `@AI/modules/signal/models/patchtst/wrapper.py`:
- Around line 190-193: The get_signals implementation in wrapper.py uses a
generic **kwargs and thus doesn't match BaseSignalModel.get_signals(df:
pd.DataFrame, ticker_id: int, sector_id: int) -> Dict[str, float]; update the
signature of get_signals to include the explicit parameters and type hints (df:
pd.DataFrame, ticker_id: int, sector_id: int) and return type Dict[str, float],
then forward those parameters to self.predict(...) (or adapt predict to accept
them) so the method signature and typing match BaseSignalModel and IDEs/static
checkers can validate usage; reference get_signals, predict, and BaseSignalModel
when making the change.

In `@AI/modules/signal/models/TCN/train_kaggle.py`:
- Around line 153-157: The except block catching Exception as e in
train_kaggle.py (the block that increments fail_count, prints the warning for
ticker, and raises RuntimeError when fail_count >= 20) is missing exception
chaining; change the raise RuntimeError("피처 계산 실패가 20개를 초과했습니다.") to raise
RuntimeError("피처 계산 실패가 20개를 초과했습니다.") from e so the original exception (e) is
preserved and attached to the new RuntimeError.

In `@AI/modules/signal/models/TCN/wrapper.py`:
- Around line 222-233: Narrow the broad except in the batch-prep loop: instead
of "except Exception as e:" catch the expected failure types (e.g., "except
(ValueError, KeyError, TypeError) as e:") around the code that processes
feature_frame, scaler.transform, and appends to tensor_list/valid_tickers;
optionally add a separate "except Exception as e:" that logs and re-raises for
truly unexpected errors so you still isolate per-ticker failures while surfacing
unknown problems. Ensure you update the except block that currently prints
"[{ticker}] 전처리 실패로 배치 추론에서 제외됨: {e}" to use the narrowed exception handling for
feature_frame, self.seq_len, self.scaler.transform, tensor_list, and
valid_tickers operations.

In `@AI/modules/signal/models/transformer/train_kaggle.py`:
- Line 209: Remove the unnecessary f-string prefixes on plain string prints in
train_kaggle.py—replace print(f">> sector 병합 완료") (and the identical occurrence
at the other location) with a regular string literal print(">> sector 병합 완료") so
there are no unused f-strings; search for the exact string ">> sector 병합 완료" to
locate and update both instances.

In `@AI/pipelines/weekly_routine.py`:
- Around line 147-153: 로그 메시지 언어가 다른 단계들과 불일치합니다: skip_deploy 분기에서 사용하는 log(...)
메시지를 한국어로 바꿔 일관성 유지하세요; 수정 대상은 skip_deploy 변수 분기와
run_script("deploy_to_server.py", "server deploy") 호출 뒤의 실패 로그(log("[error]
server deploy failed.")) 및 성공/스킵 로그(log(">> [server deploy] skipped"))를 한국어 문구로
교체하여 다른 단계들과 같은 언어를 사용하도록 변경하세요.

In `@AI/scripts/deploy_to_server.py`:
- Around line 78-79: The code sets
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) which auto-accepts
unknown host keys and is vulnerable to MITM; update the SSH host-key handling in
the paramiko.SSHClient setup to a safer approach: load known_hosts via
ssh.load_system_host_keys() or ssh.load_host_keys(...) and use
paramiko.RejectPolicy(), or implement a custom policy that verifies a host key
fingerprint provided via environment variable before accepting; modify the logic
around paramiko.SSHClient, set_missing_host_key_policy, and any bootstrapping
that reads env vars to support passing/validating a trusted fingerprint or
known_hosts file instead of AutoAddPolicy.

In `@AI/scripts/download_weights.py`:
- Around line 161-162: Replace the magic number 100 in the file-size check with
a named constant (e.g., MIN_VALID_FILE_SIZE) and add a brief comment explaining
why files below that threshold are skipped (e.g., to filter out empty files or
short error/HTML responses); update the conditional that uses
os.path.getsize(src) to compare against that constant (reference the existing
variable src and the size-check logic) so the intent is clear and maintainable.

In `@AI/scripts/trigger_training.py`:
- Around line 153-157: The ignore_ai_files function duplicates the same
skipped_dirs set found in upload_to_kaggle.py; extract the shared logic into a
common utility (e.g., a new function like get_skipped_dirs or make_ignore_fn in
a utils module) and replace both the ignore_ai_files definition and the
corresponding logic in upload_to_kaggle.py to import and call that shared
function; update usages such as ignore_ai_files and the copytree call
(shutil.copytree(project_root / "AI", work_dir / "AI", ignore=ignore_ai_files))
to use the imported shared ignore function so the skipped_dirs set is defined in
one place.
- Around line 193-199: The dry-run currently creates the temp dir inside a with
tempfile.TemporaryDirectory(...) context so it is removed when the block exits,
preventing user inspection; change the flow so that when dry_run is True you
create a non-auto-deleting directory (use tempfile.mkdtemp(prefix=...) or create
Path(tmp) without a context) or skip entering the TemporaryDirectory context,
call write_kernel_files(work_dir, spec), print the absolute work_dir path (and a
clear message), and return without deleting it; reference the dry_run check and
write_kernel_files(work_dir, spec) to locate where to alter creation/cleanup
behavior.

In `@AI/scripts/upload_to_kaggle.py`:
- Around line 59-68: The temporary source directory (CODE_SOURCE_DIR) used to
build the ZIP at CODE_ARCHIVE_PATH is not removed after zipping; add cleanup to
delete CODE_SOURCE_DIR after the ZipFile context closes (e.g., call
shutil.rmtree(CODE_SOURCE_DIR)) and handle errors so it doesn't crash the script
(wrap in try/except and log or print failures). Also ensure shutil is imported
at the top if missing and perform the removal only after the zip write loop
completes and the context manager has closed the archive.

In `@tcn_wandb_sweep.py`:
- Around line 130-134: Replace the silent except block around
add_technical_indicators(df_t) with failure tracking and logging: catch the
Exception, increment a failure counter (e.g., failed_count) and log the symbol
identifier and exception details via the existing logger so failures are visible
(reference add_technical_indicators, df_t, processed list); additionally mirror
the other scripts' behavior by raising an exception when failed_count exceeds a
configurable threshold (e.g., max_failures) so the job fails fast instead of
silently continuing.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9aabbbcc-209d-40c6-9b73-b58644f15516

📥 Commits

Reviewing files that changed from the base of the PR and between fab1b22 and 9b478ca.

📒 Files selected for processing (20)
  • .gitignore
  • AI/modules/signal/core/artifact_paths.py
  • AI/modules/signal/models/TCN/train_kaggle.py
  • AI/modules/signal/models/TCN/wrapper.py
  • AI/modules/signal/models/__init__.py
  • AI/modules/signal/models/itransformer/train_kaggle.py
  • AI/modules/signal/models/patchtst/architecture.py
  • AI/modules/signal/models/patchtst/train.py
  • AI/modules/signal/models/patchtst/train_kaggle.py
  • AI/modules/signal/models/patchtst/wrapper.py
  • AI/modules/signal/models/transformer/train_kaggle.py
  • AI/pipelines/weekly_routine.py
  • AI/requirements.prod.txt
  • AI/scripts/deploy_to_server.py
  • AI/scripts/download_weights.py
  • AI/scripts/extract_to_parquet.py
  • AI/scripts/run_xai_cron.sh
  • AI/scripts/trigger_training.py
  • AI/scripts/upload_to_kaggle.py
  • tcn_wandb_sweep.py
✅ Files skipped from review due to trivial changes (1)
  • AI/modules/signal/models/patchtst/architecture.py

Comment thread .gitignore
AI/backtests/results/
AI/.venv/
AI/data/weights/tcn/
AI/data/kaggle_data/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

중복된 ignore 엔트리를 제거하세요.

AI/data/kaggle_data/ 패턴이 38번 줄과 50번 줄에 중복으로 추가되어 있습니다. 50번 줄의 중복 엔트리를 제거해주세요.

🔧 제안하는 수정
 AI/data/kaggle_data/
 
 AI/data/weights/itransformer/*
 !AI/data/weights/itransformer/.gitkeep
 
 AI/config/trading.local.json
 AI/tests/out/
 AI/backtests/out/
 AI/docs/
 
 SISC_*.md
 test_*.py
-AI/data/kaggle_data/
 *.parquet

Also applies to: 50-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 38, .gitignore contains a duplicated ignore pattern
"AI/data/kaggle_data/"; remove the redundant occurrence so the pattern appears
only once (keep a single "AI/data/kaggle_data/" entry and delete the duplicate).

Comment thread .gitignore
AI/docs/

SISC_*.md
test_*.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: 리포지토리에서 test_*.py 파일 검색

# 이미 Git에 추적되고 있는 test_*.py 파일 찾기
echo "=== 현재 Git에 추적되고 있는 test_*.py 파일들 ==="
git ls-files | grep 'test_.*\.py$'

# 추적되지 않은 test_*.py 파일도 확인 (새로 추가될 가능성)
echo -e "\n=== 작업 디렉토리의 모든 test_*.py 파일들 ==="
fd -t f 'test_.*\.py$'

Repository: SISC-IT/sisc-web

Length of output: 175


test_*.py 패턴이 기존 테스트 파일 AI/backtests/test_portfolio_evaluation.py를 제외합니다.

이미 리포지토리에 커밋된 AI/backtests/test_portfolio_evaluation.py 파일이 이 광범위한 패턴에 의해 무시되게 됩니다. 특정 임시 테스트 파일만 제외하려는 의도라면, 더 구체적인 패턴을 사용하세요:

  • test_temp_*.py (임시 테스트만 무시)
  • AI/test_*.py (특정 디렉토리의 테스트만 무시)
  • **/test_*.py (프로젝트 루트의 test_*.py만 무시)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 49, 현재 .gitignore의 전역 패턴 "test_*.py"가 이미 커밋된
AI/backtests/test_portfolio_evaluation.py를 의도치 않게 무시하게 되므로, 패턴을 더 구체적으로 변경해 기존
테스트를 제외하지 않도록 수정하세요; 예를 들어 임시 테스트만 무시하려면 "test_temp_*.py"로 바꾸거나 특정 디렉토리만 무시하려면
"AI/test_*.py" 또는 루트 전용으로 제한하려면 "**/test_*.py" 같은 구체적 패턴을 사용하고, 필요 시 기존
AI/backtests/test_portfolio_evaluation.py를 .gitignore에서 제외시키지 않도록 확인하세요.

Comment on lines +168 to +174
state_dict = checkpoint['state_dict']

# DataParallel로 학습된 경우 module. 접두사 제거
if all(k.startswith('module.') for k in state_dict.keys()):
state_dict = {k[len('module.'):]: v for k, v in state_dict.items()}

self.model.load_state_dict(state_dict)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

state_dict 키 누락 시 KeyError 발생 가능

Line 161에서 config는 .get()으로 안전하게 처리하지만, Line 168에서 state_dict는 직접 키 접근을 사용합니다. 레거시 체크포인트나 손상된 파일의 경우 명확한 오류 메시지 없이 실패할 수 있습니다.

🛡️ 일관된 에러 처리 제안
-        state_dict = checkpoint['state_dict']
+        state_dict = checkpoint.get('state_dict')
+        if state_dict is None:
+            raise ValueError(f"[PatchTST] 체크포인트에 state_dict 없음: {filepath}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/patchtst/wrapper.py` around lines 168 - 174, The
code assumes checkpoint['state_dict'] exists and will KeyError on missing or
corrupted checkpoints; update the logic in wrapper.py where
checkpoint['state_dict'] is accessed (the block that then strips "module." and
calls self.model.load_state_dict) to read state_dict via
checkpoint.get('state_dict') and explicitly check for None/invalid type, raising
a clear error (e.g., RuntimeError or KeyError) that includes contextual info
(checkpoint id/path or available keys) if missing, and only then proceed to
strip the "module." prefix and call self.model.load_state_dict(state_dict).

parser.add_argument("--dropout", type=float, default=0.2)
parser.add_argument("--channels", type=int, nargs="+", default=[32, 64, 64])
parser.add_argument("--patience", type=int, default=7)
parser.add_argument("--output-dir", default=os.environ.get('WEIGHTS_DIR', '/kaggle/working/tcn'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

output_dir 기본값 불일치

parse_args()의 기본값은 /kaggle/working/tcn이지만, train() 래퍼는 /kaggle/working을 사용합니다. 이로 인해 호출 방식에 따라 아티팩트가 다른 경로에 저장될 수 있습니다.

artifact_paths.py에서 TCN 경로가 root_dir / "tcn"으로 정의되어 있으므로, train() 래퍼도 /kaggle/working/tcn을 사용하는 것이 일관성 있습니다.

🐛 제안된 수정
 def train():
     """노트북에서 module.train()으로 호출하기 위한 래퍼"""
     import argparse
     args = argparse.Namespace(
         parquet_dir    = os.environ.get("PARQUET_DIR", _find_kaggle_dataset_path()),
         start_date     = "2015-01-01",
         end_date       = os.environ.get("END_DATE", date.today().isoformat()),
         seq_len        = 60,
         epochs         = 20,
         batch_size     = 64,
         learning_rate  = 1e-3,
         weight_decay   = 1e-4,
         kernel_size    = 3,
         dropout        = 0.2,
         channels       = [32, 64, 64],
         patience       = 7,
-        output_dir     = "/kaggle/working",
+        output_dir     = "/kaggle/working/tcn",
     )
     train_model(args)

Also applies to: 374-374

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/modules/signal/models/TCN/train_kaggle.py` at line 350, The default for
the CLI arg parser (parser.add_argument("--output-dir",
default=os.environ.get('WEIGHTS_DIR', '/kaggle/working/tcn'))) does not match
the default used by the train() wrapper (which currently uses
'/kaggle/working'), causing artifacts to be saved to different locations; update
train() to use the same '/kaggle/working/tcn' default (or better, derive the
path from the TCN artifact definition in artifact_paths, e.g., root_dir / "tcn")
so both parser.add_argument and train() consistently point to the same output
directory.

Comment on lines +126 to +128
# PatchTST → Transformer 순서로 순차 실행
# (iTransformer, TCN 머지 후 추가 예정)
# ─────────────────────────────────────────────────────

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

주석이 실제 코드와 불일치

주석에 "iTransformer, TCN 머지 후 추가 예정"이라고 되어 있지만, trigger_training.py의 MODEL_SPECS에는 이미 4개 모델(PatchTST, Transformer, iTransformer, TCN)이 모두 포함되어 있습니다. 주석을 업데이트하세요.

🔧 수정 제안
     # ─────────────────────────────────────────────────────
     # STEP 3. Kaggle 노트북 학습 트리거 + 완료 대기
-    # PatchTST → Transformer 순서로 순차 실행
-    # (iTransformer, TCN 머지 후 추가 예정)
+    # PatchTST → Transformer → iTransformer → TCN 순서로 순차 실행
     # ─────────────────────────────────────────────────────
📝 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
# PatchTST → Transformer 순서로 순차 실행
# (iTransformer, TCN 머지 후 추가 예정)
# ─────────────────────────────────────────────────────
# PatchTST → Transformer → iTransformer → TCN 순서로 순차 실행
# ─────────────────────────────────────────────────────
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/pipelines/weekly_routine.py` around lines 126 - 128, The comment block in
weekly_routine.py that says "iTransformer, TCN 머지 후 추가 예정" is outdated; update
the comment to reflect the actual execution order and included models (PatchTST
→ Transformer → iTransformer → TCN) because trigger_training.py's MODEL_SPECS
already contains all four models; locate the comment in weekly_routine.py (the
block around the sequence note) and replace the obsolete line with a concise,
accurate note stating the current model order and that all models are present.

Comment on lines +102 to +103
remote_dir_q = shlex.quote(model["remote_dir"])
ssh.exec_command(f"mkdir -p {remote_dir_q}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

mkdir 명령 실행 결과 확인 누락

ssh.exec_command()의 반환값(stdin, stdout, stderr)을 확인하지 않아 디렉터리 생성 실패 시 후속 SCP 업로드가 실패할 수 있습니다.

🔧 수정 제안
     remote_dir_q = shlex.quote(model["remote_dir"])
-    ssh.exec_command(f"mkdir -p {remote_dir_q}")
+    _, stdout, stderr = ssh.exec_command(f"mkdir -p {remote_dir_q}")
+    exit_status = stdout.channel.recv_exit_status()
+    if exit_status != 0:
+        print(f"   [경고] 원격 디렉터리 생성 실패: {stderr.read().decode().strip()}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/scripts/deploy_to_server.py` around lines 102 - 103,
ssh.exec_command(f"mkdir -p {remote_dir_q}")를 호출만 하고 결과를 확인하지 않아 디렉터리 생성 실패 시 이후
SCP 업로드가 진행될 수 있습니다; ssh.exec_command 호출의 반환값(stdin, stdout, stderr)을 받아(stdout,
stderr 또는 stdout.channel.recv_exit_status()) 실행 상태와 stderr 내용을 확인하고, non-zero
exit code 또는 stderr가 있으면 적절히 로깅(processLogger 또는 logger)하고 예외를 던지거나 함수를 조기 종료해
이후 업로드 작업을 막도록 수정하세요; 참조 심볼: remote_dir_q 및 ssh.exec_command(f"mkdir -p
{remote_dir_q}").

Comment on lines +75 to +80
if [[ -n "$XAI_COMMAND" ]]; then
echo "[INFO] Running custom XAI command: $XAI_COMMAND"
docker run "${run_args[@]}" "$IMAGE" /bin/sh -lc "$XAI_COMMAND"
else
docker run "${run_args[@]}" "$IMAGE"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

/bin/sh -lc의 셸 인젝션 위험 및 로그인 셸 플래그 검토

두 가지 사항을 확인해 주세요.

  1. 셸 인젝션: $XAI_COMMAND는 컨테이너 내부 /bin/sh -c에 그대로 전달되므로, 값에 셸 메타문자(; && | $() 등)가 포함될 경우 컨테이너 내에서 임의 명령이 실행됩니다. 현재는 운영자가 직접 설정하는 크론 변수이므로 의도된 설계이지만, 추후 이 변수를 외부 입력(API, 설정 파일 등)에서 받게 될 경우 인젝션 경로가 됩니다. 용도와 위협 모델을 주석으로 명시해 두는 것을 권장합니다.

  2. -l (로그인 셸) 플래그: /bin/sh -lc는 컨테이너 내 /etc/profile 및 ~/.profile을 소싱합니다. Distroless나 일부 최소화 이미지에서는 이 파일들이 없거나, 반대로 PATH 등 환경변수를 예기치 않게 변경하는 스크립트가 있을 수 있습니다. 로그인 환경 초기화가 명시적으로 필요하지 않다면 -l 플래그를 제거하는 것이 더 예측 가능한 동작을 보장합니다.

💡 제안 수정 (로그인 플래그 제거 + 의도 명시 주석)
 if [[ -n "$XAI_COMMAND" ]]; then
+  # WARNING: XAI_COMMAND is executed verbatim inside the container shell.
+  # Ensure this value is only set by trusted operators (e.g. cron env file).
   echo "[INFO] Running custom XAI command: $XAI_COMMAND"
-  docker run "${run_args[@]}" "$IMAGE" /bin/sh -lc "$XAI_COMMAND"
+  docker run "${run_args[@]}" "$IMAGE" /bin/sh -c "$XAI_COMMAND"
 else
   docker run "${run_args[@]}" "$IMAGE"
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/scripts/run_xai_cron.sh` around lines 75 - 80, The current docker run
branch that executes /bin/sh -lc "$XAI_COMMAND" is vulnerable to shell injection
and unnecessarily invokes a login shell; update the run_xai_cron.sh logic so it
documents the threat model for XAI_COMMAND (make explicit in a comment that this
value is operator-controlled and must not come from untrusted sources) and
remove the login flag by using /bin/sh -c instead of /bin/sh -lc, and if you
expect future external input consider replacing direct shell invocation with a
safer alternative (e.g., passing a single-entry exec array or
validated/sanitized command) when constructing the docker run invocation that
uses the XAI_COMMAND variable.

# ─────────────────────────────────────────────────────────────────────────────
# Kaggle 데이터셋 버전 업데이트
# ─────────────────────────────────────────────────────────────────────────────
print(f"\n>> Kaggle 데이터셋 업로드 중...")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

불필요한 f-string 접두사 제거

플레이스홀더가 없는 f-string입니다.

🔧 수정 제안
-print(f"\n>> Kaggle 데이터셋 업로드 중...")
+print("\n>> Kaggle 데이터셋 업로드 중...")
📝 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
print(f"\n>> Kaggle 데이터셋 업로드 중...")
print("\n>> Kaggle 데이터셋 업로드 중...")
🧰 Tools
🪛 Ruff (0.15.12)

[error] 127-127: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AI/scripts/upload_to_kaggle.py` at line 127, The print call in
upload_to_kaggle.py uses an unnecessary f-string prefix: locate the print
statement printing ">> Kaggle 데이터셋 업로드 중..." and remove the leading f so it
becomes a normal string literal; update the print invocation (the sole
print(...) expression) to use plain quotes instead of f-strings.

@twq110
twq110 merged commit e09e4d5 into main May 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants