Repository navigation
[AI] [FEAT] 계정 ID 생성 - #356
Hidden character warning
Conversation
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
Walkthrough이 PR은 Transformer 피처 스키마 비교용 신규 CLI(재학습·비교) 및 다수의 Kaggle/배포/추출·학습 스크립트를 추가하고, 포트폴리오·DB 레이어에 계정(account_code)/account_id 스코핑과 관련 스키마·레포지토리 변화를 도입합니다. ChangesTransformer / Training 및 비교 스크립트 집합
계정 스코핑(데이터베이스·레포지토리·파이프라인)
Kaggle·배포·추출·오케스트레이션 도구군
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: 종료 코드 반환
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Review rate limit: 0/1 reviews remaining, refill in 51 minutes and 51 seconds.Comment |
There was a problem hiding this comment.
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_idnullable 설계 확인 필요.
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
📒 Files selected for processing (8)
AI/backtests/compare_transformer_feature_schemas.pyAI/backtests/out/transformer_refactor_change_analysis_2026-04-03.mdAI/backtests/out/transformer_schema_compare_smoke/run_config.jsonAI/config/trading.default.jsonAI/config/trading.pyAI/libs/database/repository.pyAI/pipelines/daily_routine.pyschema.sql
| "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" |
There was a problem hiding this comment.
로컬 사용자 경로가 하드코딩되어 있어 다른 개발자/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.
| "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.
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
.gitignoreAI/backtests/compare_transformer_feature_schemas.pyAI/libs/database/repository.py
✅ Files skipped from review due to trivial changes (1)
- .gitignore
| 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", | ||
| ) |
There was a problem hiding this comment.
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.
| 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), | ||
| ) |
There was a problem hiding this comment.
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_idFK가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.
There was a problem hiding this comment.
🧹 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
📒 Files selected for processing (1)
AI/backtests/compare_transformer_feature_schemas.py
* 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)-및-다중-데일리-파이프라인-대응
There was a problem hiding this comment.
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 valueupload_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 valuedry-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 tradeoffAutoAddPolicy 사용 시 보안 고려사항
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
📒 Files selected for processing (20)
.gitignoreAI/modules/signal/core/artifact_paths.pyAI/modules/signal/models/TCN/train_kaggle.pyAI/modules/signal/models/TCN/wrapper.pyAI/modules/signal/models/__init__.pyAI/modules/signal/models/itransformer/train_kaggle.pyAI/modules/signal/models/patchtst/architecture.pyAI/modules/signal/models/patchtst/train.pyAI/modules/signal/models/patchtst/train_kaggle.pyAI/modules/signal/models/patchtst/wrapper.pyAI/modules/signal/models/transformer/train_kaggle.pyAI/pipelines/weekly_routine.pyAI/requirements.prod.txtAI/scripts/deploy_to_server.pyAI/scripts/download_weights.pyAI/scripts/extract_to_parquet.pyAI/scripts/run_xai_cron.shAI/scripts/trigger_training.pyAI/scripts/upload_to_kaggle.pytcn_wandb_sweep.py
✅ Files skipped from review due to trivial changes (1)
- AI/modules/signal/models/patchtst/architecture.py
| AI/backtests/results/ | ||
| AI/.venv/ | ||
| AI/data/weights/tcn/ | ||
| AI/data/kaggle_data/ |
There was a problem hiding this comment.
중복된 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/
*.parquetAlso 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).
| AI/docs/ | ||
|
|
||
| SISC_*.md | ||
| test_*.py |
There was a problem hiding this comment.
🧩 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에서 제외시키지 않도록 확인하세요.
| 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) |
There was a problem hiding this comment.
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')) |
There was a problem hiding this comment.
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.
| # PatchTST → Transformer 순서로 순차 실행 | ||
| # (iTransformer, TCN 머지 후 추가 예정) | ||
| # ───────────────────────────────────────────────────── |
There was a problem hiding this comment.
주석이 실제 코드와 불일치
주석에 "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.
| # 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.
| remote_dir_q = shlex.quote(model["remote_dir"]) | ||
| ssh.exec_command(f"mkdir -p {remote_dir_q}") |
There was a problem hiding this comment.
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}").
| 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 |
There was a problem hiding this comment.
/bin/sh -lc의 셸 인젝션 위험 및 로그인 셸 플래그 검토
두 가지 사항을 확인해 주세요.
-
셸 인젝션:
$XAI_COMMAND는 컨테이너 내부/bin/sh -c에 그대로 전달되므로, 값에 셸 메타문자(; && | $()등)가 포함될 경우 컨테이너 내에서 임의 명령이 실행됩니다. 현재는 운영자가 직접 설정하는 크론 변수이므로 의도된 설계이지만, 추후 이 변수를 외부 입력(API, 설정 파일 등)에서 받게 될 경우 인젝션 경로가 됩니다. 용도와 위협 모델을 주석으로 명시해 두는 것을 권장합니다. -
-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 데이터셋 업로드 중...") |
There was a problem hiding this comment.
불필요한 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.
| 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.
…라인-대응' of https://github.com/SISC-IT/sisc-web into 20260402-#350-executions-계정-분리(account_id)-및-다중-데일리-파이프라인-대응
Summary by CodeRabbit
새로운 기능
모델 학습
스크립트/자동화
설정
데이터베이스
문서/출력