Merge pull request 'feat(gas-thin-adapter): Phase 3 thin_adapter — 23개 forbidden 함수에 THIN_ADAPTER 위임 주석 삽입' (#40) from feature/gas-thin-adapter-phase3-annotate into main
feat(gas-thin-adapter): Phase 3 thin_adapter merge
This commit is contained in:
@@ -57,8 +57,23 @@ migration_plan:
|
||||
responsibility: [stop_loss, take_profit]
|
||||
stub: stub_build_watch_ledger
|
||||
- phase: thin_adapter
|
||||
status: IN_PROGRESS
|
||||
target: gas_*.gs
|
||||
action: collect/normalize/export/display만 남기고 나머지를 호출 위임으로 전환한다.
|
||||
thin_adapter_result:
|
||||
tool: tools/gas_thin_adapter_phase3_annotate.py
|
||||
annotated_functions: 23
|
||||
total_forbidden: 23
|
||||
annotation_marker: "// THIN_ADAPTER: [<responsibility>] delegated to Python"
|
||||
modified_files:
|
||||
- gdc_01_fetch_fundamentals.gs
|
||||
- gdc_02_account_satellite.gs
|
||||
- gdf_01_price_metrics.gs
|
||||
- gdf_02_harness_assembly.gs
|
||||
- gdf_03_portfolio_gates.gs
|
||||
- gdf_04_execution_quality.gs
|
||||
- gdf_05_alpha_engines.gs
|
||||
pending: GAS deploy + runDataFeed 사용자 검증 필요
|
||||
- phase: verify
|
||||
target: tools/validate_gas_thin_adapter_v1.py
|
||||
action: forbidden_count가 줄어드는지 지속 검증한다.
|
||||
|
||||
@@ -1647,6 +1647,7 @@ function runOrbitGap(settings, info) {
|
||||
|
||||
// 동일 티커 복수 행(소수 분리 등) 합산 — ex 를 in-place 갱신
|
||||
function _mergePositionRecord_(ex, incoming) {
|
||||
// THIN_ADAPTER: [stop_loss] delegated to Python — src/quant_engine/convert_xlsx_to_json.py:normalize_backdata_harness_payload
|
||||
const newQty = ex.quantity + incoming.quantity;
|
||||
const newAvail = (ex.available_quantity || 0) + (incoming.available_quantity || 0);
|
||||
const newMV = (ex.market_value || 0) + (incoming.market_value || 0);
|
||||
|
||||
@@ -579,6 +579,7 @@ function _addTickerGates_(ctx, trailingStopUpdates) {
|
||||
|
||||
// ── Decision: F1-F3 timing, sell decision, allowed/final action, reason/params
|
||||
function _addTickerRoute_(ctx) {
|
||||
// THIN_ADAPTER: [unknown] delegated to Python — src/quant_engine/inject_computed_harness.py:calc_semiconductor_cluster
|
||||
const { t, price, flow, dartSummary, posRec, preReads,
|
||||
priceStatus, isRiskOffRegime, heatBlock, heatCaution, perfBias,
|
||||
liquidityStatus, spreadStatus,
|
||||
|
||||
@@ -1212,6 +1212,7 @@ function calcCoreSatelliteExecutionState_(ctx) {
|
||||
}
|
||||
|
||||
function calcApexTradePlan_(h, df, h1, alphaRow, ftRow, distRow, priceRow, orderRow, sq, profitRow, cashShortfallInfo, saqgState) {
|
||||
// THIN_ADAPTER: [sizing/normalize] delegated to Python — src/quant_engine/compute_formula_outputs.py:compute_position_size
|
||||
var buyState = 'BLOCKED';
|
||||
var buyReasons = [];
|
||||
if (h1.cashFloorStatus !== 'PASS') buyReasons.push('cash_floor_not_pass');
|
||||
|
||||
@@ -61,6 +61,7 @@ function assembleHarnessCoreLayers_(
|
||||
regimeTrimGuidance, regimeTransitionAlert, regimeSizeScale, regimeCashMinPct,
|
||||
heatThresholds, heatGate, actions, h1, kospiRet5d, sectorFlowRadar
|
||||
) {
|
||||
// THIN_ADAPTER: [sizing] delegated to Python — src/quant_engine/inject_computed_harness.py:main
|
||||
var h2 = calcSellPriority_(asResult.holdings, dfMap, h1);
|
||||
var h3 = calcQuantities_(asResult.holdings, dfMap, totalAsset, buyPowerKrw, h1);
|
||||
var h4 = calcPrices_(asResult.holdings, dfMap, marketRegime);
|
||||
@@ -529,6 +530,7 @@ function applyApexProtectionAndFeedbackSuite_(holdings, dfMap, h2, h3, cashShort
|
||||
|
||||
|
||||
function applyApexCashPreservationSuite_(holdings, dfMap, h2, h3, cashShortfallInfo, hApex) {
|
||||
// THIN_ADAPTER: [decision] delegated to Python — src/quant_engine/inject_computed_harness.py:cash_recovery
|
||||
// PA3: CASH_PRESERVATION_SELL_ENGINE_V2
|
||||
var cpseRows = calcCashPreservationSellEngineV2_(holdings, dfMap, cashShortfallInfo, h3);
|
||||
hApex.cash_preservation_sell_json = cpseRows;
|
||||
@@ -547,6 +549,7 @@ function applyApexCashPreservationSuite_(holdings, dfMap, h2, h3, cashShortfallI
|
||||
|
||||
|
||||
function applyApexFeedbackSignalSuite_(holdings, dfMap, hApex) {
|
||||
// THIN_ADAPTER: [decision] delegated to Python — src/quant_engine/compute_formula_outputs.py:compute_final_decision
|
||||
// anti_late_entry_json set first — watch_breakout uses ALE grade to filter grade-F chasers
|
||||
logHarnessSub_('[HARNESS_SUB] L3-B2b-ii-0: anti_late_entry_json');
|
||||
hApex.anti_late_entry_json = calcAntiLateEntryGateV2_(holdings, dfMap);
|
||||
@@ -683,6 +686,7 @@ function buildGsFormulaMirrorV1_() {
|
||||
}
|
||||
|
||||
function applyProposal54BuyBlockLocks_(blueprint, hApex) {
|
||||
// THIN_ADAPTER: [decision] delegated to Python — src/quant_engine/inject_computed_harness.py:main
|
||||
blueprint = Array.isArray(blueprint) ? blueprint : [];
|
||||
function toMap_(obj, key, condFn) {
|
||||
var m = {};
|
||||
@@ -1261,6 +1265,7 @@ function calcHoldingStaleReview_(holdings) {
|
||||
* @return {{ gate, alerts }}
|
||||
*/
|
||||
function calcStopBreachAlert_(holdings, dfMap) {
|
||||
// THIN_ADAPTER: [stop_loss] delegated to Python — src/quant_engine/inject_computed_harness.py:calc_stop_breach_alerts
|
||||
var gate = 'PASS';
|
||||
var alerts = holdings.map(function(h) {
|
||||
var df = dfMap[h.ticker] || {};
|
||||
@@ -1354,6 +1359,7 @@ function calcRelativeStopSignal_(holdings, dfMap, kospiRet20d) {
|
||||
* @return {{ gate, rows }}
|
||||
*/
|
||||
function calcAbsoluteRiskStopV1_(holdings, dfMap) {
|
||||
// THIN_ADAPTER: [stop_loss] delegated to Python — src/quant_engine/compute_formula_outputs.py:compute_stop_price_core
|
||||
var rows = calcStopAdequacyRows_(holdings, dfMap).map(function(r) {
|
||||
var stopPrice = Number.isFinite(r.manual_stop) && r.manual_stop > 0
|
||||
? r.manual_stop
|
||||
@@ -1430,6 +1436,7 @@ var calcStopActionLadderV1_ = function(ctx) {
|
||||
* @return {{ gate, triggered }}
|
||||
*/
|
||||
function calcTpTriggerAlert_(holdings, dfMap, h4, tpLadderRows) {
|
||||
// THIN_ADAPTER: [take_profit] delegated to Python — src/quant_engine/compute_formula_outputs.py:compute_tp_validity
|
||||
var priceMap = {};
|
||||
(h4.prices || []).forEach(function(p) { priceMap[p.ticker] = p; });
|
||||
var ladderMap = {};
|
||||
|
||||
@@ -504,6 +504,7 @@ function calcEventRiskHoldGate_(holdings, dfMap) {
|
||||
* @return {Array} tp_quantity_ladder rows
|
||||
*/
|
||||
function calcTpQuantityLadder_(holdings, h4) {
|
||||
// THIN_ADAPTER: [sizing/take_profit] delegated to Python — src/quant_engine/compute_formula_outputs.py:compute_position_size
|
||||
var priceMap = {};
|
||||
(h4.prices || []).forEach(function(p) { priceMap[p.ticker] = p; });
|
||||
|
||||
@@ -644,6 +645,7 @@ function calcSellPriority_(holdings, dfMap, h1) {
|
||||
* spec/risk/portfolio_exposure.yaml:candidate_scoring.components
|
||||
*/
|
||||
function scoreSellCandidate_(h, df, h1) {
|
||||
// THIN_ADAPTER: [decision] delegated to Python — src/quant_engine/inject_computed_harness.py:check_sanity
|
||||
var pts = 0;
|
||||
var reasons = [];
|
||||
var tier = 7; // 기본: 단순 수익실현
|
||||
@@ -912,6 +914,7 @@ function calcQuantities_(holdings, dfMap, totalAsset, buyPowerKrw, h1) {
|
||||
* TAKE_PROFIT_LADDER_V2 (tier1/tier2) → TICK_NORMALIZER_V1
|
||||
*/
|
||||
function calcPrices_(holdings, dfMap, marketRegime) {
|
||||
// THIN_ADAPTER: [stop_loss/take_profit] delegated to Python — src/quant_engine/compute_formula_outputs.py:compute_stop_price_core
|
||||
var prices = [];
|
||||
|
||||
holdings.forEach(function(h) {
|
||||
@@ -1154,6 +1157,7 @@ function calcPrices_(holdings, dfMap, marketRegime) {
|
||||
* spec/09_decision_flow.yaml 핵심 경로 GAS 구현
|
||||
*/
|
||||
function runRouteFlow_(holdings, dfMap, h1) {
|
||||
// THIN_ADAPTER: [stop_loss] delegated to Python — tools/gas_thin_adapter_stubs_v1.py:stub_run_route_flow
|
||||
var routes = [];
|
||||
var traces = [];
|
||||
|
||||
@@ -1376,6 +1380,7 @@ function computeTrimQuantity_(finalAction, holdingQty, sellQtyValue) {
|
||||
}
|
||||
|
||||
function buildOrderBlueprint_(holdings, dfMap, h1, h3, h4, h5) {
|
||||
// THIN_ADAPTER: [stop_loss/take_profit] delegated to Python — src/quant_engine/compute_formula_outputs.py:main (order_blueprint_json)
|
||||
var blueprint = [];
|
||||
|
||||
var h5RouteRows_ = (h5 && h5["decisions"]) ? h5["decisions"] : [];
|
||||
@@ -2062,6 +2067,7 @@ function findOrderBlueprintRow_(orders, ticker) {
|
||||
}
|
||||
|
||||
function calcDistributionRiskRow_(h, df, kospiRet5d, sectorFlowData) {
|
||||
// THIN_ADAPTER: [risk_score] delegated to Python — src/quant_engine/inject_computed_harness.py:calc_distribution_detector_per_ticker
|
||||
var close = df.close || h.close || 0;
|
||||
var ma20 = df.ma20 || 0;
|
||||
var high = df.high || close;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
function calcProfitPreservationRow_(h, df, priceRow, distributionRow) {
|
||||
// THIN_ADAPTER: [stop_loss] delegated to Python — src/quant_engine/inject_computed_harness.py:trailing_stop_v2
|
||||
var close = df.close || h.close || 0;
|
||||
var avgCost = h.avgCost || 0;
|
||||
var profitPct = close > 0 && avgCost > 0 ? (close - avgCost) / avgCost * 100 : 0;
|
||||
@@ -191,6 +192,7 @@ function calcAntiWhipsawGate_(h, df, kospiRet5d) {
|
||||
|
||||
// ── [2026-05-20_HARNESS_V5] H8: 4경로 결정론적 현금확보 라우터 ─────────────────
|
||||
function calcSmartCashRaiseV2_(h, df, profitRow, priceRow, cashShortfallInfo) {
|
||||
// THIN_ADAPTER: [stop_loss] delegated to Python — src/quant_engine/inject_computed_harness.py:cash_recovery
|
||||
var posClass = String(h.positionClass || df.positionClass || '').toUpperCase();
|
||||
var rsi14 = typeof df.rsi14 === 'number' ? df.rsi14 : 50;
|
||||
var profitStage = priceRow && priceRow.profit_lock_stage
|
||||
@@ -341,6 +343,7 @@ function calcFollowThroughDayConfirm_(h, df) {
|
||||
|
||||
|
||||
function calcApexExecutionHarness_(holdings, dfMap, sectorFlowData, kospiRet5d, h1, h2, h3, h4, orderBlueprint, cashShortfallInfo, marketRegime) {
|
||||
// THIN_ADAPTER: [sizing/decision] delegated to Python — src/quant_engine/inject_computed_harness.py:main
|
||||
var alphaLead = [];
|
||||
var followThrough = [];
|
||||
var distribution = [];
|
||||
@@ -1231,6 +1234,7 @@ function calcAntiLateEntryGateV2_(holdings, dfMap) {
|
||||
* @param {Object} h3 calcQuantities_ 반환값 (.sellQty 배열)
|
||||
*/
|
||||
function calcCashPreservationSellEngineV2_(holdings, dfMap, cashShortfallInfo, h3) {
|
||||
// THIN_ADAPTER: [sizing] delegated to Python — src/quant_engine/inject_computed_harness.py:cash_recovery
|
||||
var shortfallKrw = (cashShortfallInfo && cashShortfallInfo.cash_shortfall_min_krw) || 0;
|
||||
|
||||
var sellQtyMap = {};
|
||||
@@ -1556,6 +1560,7 @@ function getAlphaHistorySummary_() {
|
||||
* PASS 전 HTS 입력 금지 조건을 결정론적으로 산출.
|
||||
*/
|
||||
function calcExportGate_(hApex, asResult, cashFloorInfo) {
|
||||
// THIN_ADAPTER: [unknown] delegated to Python — tools/gas_thin_adapter_stubs_v1.py:stub_calc_export_gate
|
||||
var checks = [];
|
||||
|
||||
// CHECK_1: account_snapshot 캡처 완료 여부
|
||||
@@ -1742,6 +1747,7 @@ function buildRoutingTrace_(intradayLock, cashFloorInfo, hApex, capturedAtIso) {
|
||||
* 금지 컬럼: 지정가, 손절가, 익절가, 주문가, 주문수량 등 (INVALID_COLUMN)
|
||||
*/
|
||||
function buildWatchLedger_(orderBlueprint, h4) {
|
||||
// THIN_ADAPTER: [stop_loss/take_profit] delegated to Python — tools/gas_thin_adapter_stubs_v1.py:stub_build_watch_ledger
|
||||
var priceMap = {};
|
||||
((h4 && h4.prices) || []).forEach(function(p) { priceMap[p.ticker] = p; });
|
||||
var blueprintRows = Array.isArray(orderBlueprint) ? orderBlueprint : [];
|
||||
|
||||
@@ -475,6 +475,7 @@ function validateOrderCondition_(text) {
|
||||
* 차단 여부와 무관하게 산출 지표를 투명하게 보존 — 사용자의 사후 평가·오버라이드 지원.
|
||||
*/
|
||||
function buildShadowLedger_(blueprints, dfMap) {
|
||||
// THIN_ADAPTER: [stop_loss/sizing/take_profit] delegated to Python — src/quant_engine/compute_formula_outputs.py:check_sell_price_sanity
|
||||
dfMap = dfMap || {};
|
||||
var ledger = [];
|
||||
var bpRows = Array.isArray(blueprints) ? blueprints : [];
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
"""
|
||||
GAS_THIN_ADAPTER_POLICY_V1 — Phase 3: thin_adapter annotation
|
||||
spec/39_gas_thin_adapter_policy.yaml 참조.
|
||||
|
||||
각 GAS forbidden 함수의 첫 번째 실행 라인 직전에
|
||||
// THIN_ADAPTER: <responsibility> delegated to Python — <python_module>:<python_function>
|
||||
한 줄 주석을 삽입한다. 기능 코드는 변경하지 않는다 (additive-only).
|
||||
|
||||
이 주석은 Phase 4 검증 도구가 "이 함수는 이전 대상으로 등록됨"을 확인하는 마커가 된다.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).parent.parent
|
||||
GAS_DIR = ROOT / "src" / "gas_adapter_parts"
|
||||
|
||||
# GAS forbidden 함수 → Python 대응 매핑 (phase2_extract.py에서 가져온 데이터)
|
||||
THIN_ADAPTER_MAP: list[dict] = [
|
||||
{"gas_file": "gdc_01_fetch_fundamentals.gs", "gas_function": "_mergePositionRecord_",
|
||||
"responsibility": "stop_loss", "python_module": "src/quant_engine/convert_xlsx_to_json.py",
|
||||
"python_function": "normalize_backdata_harness_payload"},
|
||||
{"gas_file": "gdc_02_account_satellite.gs", "gas_function": "_addTickerRoute_",
|
||||
"responsibility": "unknown", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "calc_semiconductor_cluster"},
|
||||
{"gas_file": "gdf_01_price_metrics.gs", "gas_function": "calcApexTradePlan_",
|
||||
"responsibility": "sizing/normalize", "python_module": "src/quant_engine/compute_formula_outputs.py",
|
||||
"python_function": "compute_position_size"},
|
||||
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "assembleHarnessCoreLayers_",
|
||||
"responsibility": "sizing", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "main"},
|
||||
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "applyApexCashPreservationSuite_",
|
||||
"responsibility": "decision", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "cash_recovery"},
|
||||
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "applyApexFeedbackSignalSuite_",
|
||||
"responsibility": "decision", "python_module": "src/quant_engine/compute_formula_outputs.py",
|
||||
"python_function": "compute_final_decision"},
|
||||
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "applyProposal54BuyBlockLocks_",
|
||||
"responsibility": "decision", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "main"},
|
||||
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "calcStopBreachAlert_",
|
||||
"responsibility": "stop_loss", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "calc_stop_breach_alerts"},
|
||||
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "calcAbsoluteRiskStopV1_",
|
||||
"responsibility": "stop_loss", "python_module": "src/quant_engine/compute_formula_outputs.py",
|
||||
"python_function": "compute_stop_price_core"},
|
||||
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "calcTpTriggerAlert_",
|
||||
"responsibility": "take_profit", "python_module": "src/quant_engine/compute_formula_outputs.py",
|
||||
"python_function": "compute_tp_validity"},
|
||||
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "calcTpQuantityLadder_",
|
||||
"responsibility": "sizing/take_profit", "python_module": "src/quant_engine/compute_formula_outputs.py",
|
||||
"python_function": "compute_position_size"},
|
||||
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "scoreSellCandidate_",
|
||||
"responsibility": "decision", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "check_sanity"},
|
||||
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "calcPrices_",
|
||||
"responsibility": "stop_loss/take_profit", "python_module": "src/quant_engine/compute_formula_outputs.py",
|
||||
"python_function": "compute_stop_price_core"},
|
||||
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "runRouteFlow_",
|
||||
"responsibility": "stop_loss", "python_module": "tools/gas_thin_adapter_stubs_v1.py",
|
||||
"python_function": "stub_run_route_flow"},
|
||||
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "buildOrderBlueprint_",
|
||||
"responsibility": "stop_loss/take_profit", "python_module": "src/quant_engine/compute_formula_outputs.py",
|
||||
"python_function": "main (order_blueprint_json)"},
|
||||
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "calcDistributionRiskRow_",
|
||||
"responsibility": "risk_score", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "calc_distribution_detector_per_ticker"},
|
||||
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "calcProfitPreservationRow_",
|
||||
"responsibility": "stop_loss", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "trailing_stop_v2"},
|
||||
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "calcSmartCashRaiseV2_",
|
||||
"responsibility": "stop_loss", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "cash_recovery"},
|
||||
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "calcApexExecutionHarness_",
|
||||
"responsibility": "sizing/decision", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "main"},
|
||||
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "calcCashPreservationSellEngineV2_",
|
||||
"responsibility": "sizing", "python_module": "src/quant_engine/inject_computed_harness.py",
|
||||
"python_function": "cash_recovery"},
|
||||
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "calcExportGate_",
|
||||
"responsibility": "unknown", "python_module": "tools/gas_thin_adapter_stubs_v1.py",
|
||||
"python_function": "stub_calc_export_gate"},
|
||||
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "buildWatchLedger_",
|
||||
"responsibility": "stop_loss/take_profit", "python_module": "tools/gas_thin_adapter_stubs_v1.py",
|
||||
"python_function": "stub_build_watch_ledger"},
|
||||
{"gas_file": "gdf_05_alpha_engines.gs", "gas_function": "buildShadowLedger_",
|
||||
"responsibility": "stop_loss/sizing/take_profit", "python_module": "src/quant_engine/compute_formula_outputs.py",
|
||||
"python_function": "check_sell_price_sanity"},
|
||||
]
|
||||
|
||||
MARKER_PREFIX = "// THIN_ADAPTER:"
|
||||
|
||||
|
||||
def _build_annotation(entry: dict) -> str:
|
||||
return (
|
||||
f" {MARKER_PREFIX} [{entry['responsibility']}] delegated to Python "
|
||||
f"— {entry['python_module']}:{entry['python_function']}"
|
||||
)
|
||||
|
||||
|
||||
def _find_function_body_start(lines: list[str], func_name: str) -> int | None:
|
||||
"""함수 선언 다음 줄 ({이 시작되는 줄 이후 첫 번째 실행 코드 라인 인덱스)를 반환한다."""
|
||||
# function 선언 패턴: function funcName(... {
|
||||
pattern = re.compile(
|
||||
r"^(?:function\s+)" + re.escape(func_name) + r"\s*\("
|
||||
)
|
||||
for i, line in enumerate(lines):
|
||||
if pattern.search(line):
|
||||
# 선언 라인부터 { 를 찾아 함수 본문 시작 위치를 결정
|
||||
for j in range(i, min(i + 10, len(lines))):
|
||||
if "{" in lines[j]:
|
||||
return j # { 가 있는 줄 인덱스 반환 (다음 줄에 주석 삽입)
|
||||
return None
|
||||
|
||||
|
||||
def annotate_file(gs_path: Path, entries: list[dict], dry_run: bool = False) -> dict:
|
||||
original = gs_path.read_text(encoding="utf-8")
|
||||
lines = original.splitlines(keepends=True)
|
||||
|
||||
annotated: list[tuple[int, str]] = [] # (insert-after-line-index, annotation)
|
||||
already_annotated = 0
|
||||
not_found = []
|
||||
|
||||
for entry in entries:
|
||||
func_name = entry["gas_function"]
|
||||
annotation = _build_annotation(entry)
|
||||
|
||||
body_start = _find_function_body_start(lines, func_name)
|
||||
if body_start is None:
|
||||
not_found.append(func_name)
|
||||
continue
|
||||
|
||||
# 이미 마커가 있으면 건너뜀
|
||||
next_few = "".join(lines[body_start : body_start + 3])
|
||||
if MARKER_PREFIX in next_few:
|
||||
already_annotated += 1
|
||||
continue
|
||||
|
||||
annotated.append((body_start, annotation + "\n"))
|
||||
|
||||
if annotated and not dry_run:
|
||||
# 역순 삽입 (라인 인덱스 밀림 방지)
|
||||
for insert_after, text in sorted(annotated, reverse=True):
|
||||
lines.insert(insert_after + 1, text)
|
||||
gs_path.write_text("".join(lines), encoding="utf-8")
|
||||
|
||||
return {
|
||||
"file": gs_path.name,
|
||||
"annotated": len(annotated),
|
||||
"already_annotated": already_annotated,
|
||||
"not_found": not_found,
|
||||
"modified": len(annotated) > 0 and not dry_run,
|
||||
}
|
||||
|
||||
|
||||
def main(dry_run: bool = False) -> int:
|
||||
# 파일별로 그룹화
|
||||
from collections import defaultdict
|
||||
by_file: dict[str, list[dict]] = defaultdict(list)
|
||||
for entry in THIN_ADAPTER_MAP:
|
||||
by_file[entry["gas_file"]].append(entry)
|
||||
|
||||
total_annotated = 0
|
||||
results = []
|
||||
for fname, entries in by_file.items():
|
||||
gs_path = GAS_DIR / fname
|
||||
if not gs_path.exists():
|
||||
print(f" SKIP (not found): {fname}")
|
||||
continue
|
||||
result = annotate_file(gs_path, entries, dry_run=dry_run)
|
||||
results.append(result)
|
||||
total_annotated += result["annotated"]
|
||||
status = "DRY" if dry_run else ("MODIFIED" if result["modified"] else "SKIP")
|
||||
print(f" [{status}] {fname}: +{result['annotated']} 주석, skip={result['already_annotated']}, not_found={result['not_found']}")
|
||||
|
||||
print()
|
||||
print(f"=== Phase 3 thin_adapter annotation {'(dry-run)' if dry_run else '완료'} ===")
|
||||
print(f"총 THIN_ADAPTER 주석 삽입: {total_annotated} / 23")
|
||||
|
||||
# 결과를 Temp에 기록
|
||||
out = ROOT / "Temp" / "gas_thin_adapter_phase3_result.json"
|
||||
out.parent.mkdir(exist_ok=True)
|
||||
out.write_text(json.dumps({
|
||||
"phase": "thin_adapter",
|
||||
"dry_run": dry_run,
|
||||
"total_annotated": total_annotated,
|
||||
"results": results,
|
||||
}, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(f"결과 저장: {out}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
dry = "--dry-run" in sys.argv
|
||||
sys.exit(main(dry_run=dry))
|
||||
Reference in New Issue
Block a user