# ๋…ผ๋ฌธ ์ž๋™ ์ƒ์„ฑ ํ…Œ์ŠคํŠธ ๊ฐ€์ด๋“œ # Paper Auto-Generation Testing Guide ## 1. ํ•„์ˆ˜ ํ…Œ์ŠคํŠธ (Essential Tests) ๋…ผ๋ฌธ ์ž๋™ ์ƒ์„ฑ ํŒŒ์ดํ”„๋ผ์ธ์˜ ์‹ ๋ขฐ์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•œ **3๋‹จ๊ณ„ ํ…Œ์ŠคํŠธ ์ „๋žต**: ### Tier 1: ํ•ต์‹ฌ ํ†ต๊ณ„ ํ…Œ์ŠคํŠธ (Critical - Must Pass) **๋ชฉ์ **: ๋…ผ๋ฌธ์— ๋“ค์–ด๊ฐ€๋Š” ๋ชจ๋“  ์ˆซ์ž๊ฐ€ ์ •ํ™•ํ•œ์ง€ ๊ฒ€์ฆ #### 1.1 ๋ชจ๋ธ ๊ณ„์ˆ˜ ํ…Œ์ŠคํŠธ (H1/H2 Coefficients) ```python # test/integration/test_paper_results.py def test_h1_vagueness_coefficient_sign(): """H1: Vagueness ๊ณ„์ˆ˜๊ฐ€ ์Œ์ˆ˜์ธ์ง€ ํ™•์ธ (์ •๋ณด๋น„์šฉ ๊ฐ€์„ค)""" result = test_h1_early_funding(df) coef = result.params['z_vagueness'] assert coef < 0, "H1: Vagueness should reduce early funding" def test_h2_interaction_exists(): """H2: Vร—F ์ƒํ˜ธ์ž‘์šฉ ํ•ญ์ด ๋ชจ๋ธ์— ํฌํ•จ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ""" result = test_h2_main_growth(df) interaction_terms = [p for p in result.params.index if 'vagueness' in p and 'hardware' in p] assert len(interaction_terms) > 0, "H2: Vร—F interaction must exist" ``` **์ด๊ฒƒ์ด ์ค‘์š”ํ•œ ์ด์œ **: - ๋…ผ๋ฌธ์˜ ํ•ต์‹ฌ ์ฃผ์žฅ์ด ๋ฐ์ดํ„ฐ์—์„œ ์‹ค์ œ๋กœ ๋‚˜์˜ค๋Š”์ง€ ํ™•์ธ - ๊ณ„์ˆ˜ ๋ถ€ํ˜ธ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๋…ผ๋ฌธ ์ „์ฒด ๋‚ด๋Ÿฌํ‹ฐ๋ธŒ๊ฐ€ ๋ฐ”๋€œ - ๋ฆฌ๋ทฐ์–ด๊ฐ€ ์žฌํ˜„ํ•  ๋•Œ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™€์•ผ ํ•จ #### 1.2 ํ…Œ์ด๋ธ” ๊ฐ’ ์ผ์น˜ ํ…Œ์ŠคํŠธ (Table Validation) ```python def test_table1_matches_h1_model(): """Table 1์˜ ๊ณ„์ˆ˜๊ฐ€ H1 ๋ชจ๋ธ ๊ฒฐ๊ณผ์™€ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๋Š”์ง€""" result = test_h1_early_funding(df) # Generate table from scripts.generate_paper_tables import generate_table1_h1 latex_table = generate_table1_h1(df, output_path='/tmp/table1.tex') # Extract coefficient from LaTeX import re coef_match = re.search(r'Vagueness.*?(-?\d+\.\d+e[+-]\d+)', latex_table) table_coef = float(coef_match.group(1)) model_coef = result.params['z_vagueness'] # Must match to at least 3 significant figures assert abs(table_coef - model_coef) < 1e-10 ``` **์ด๊ฒƒ์ด ์ค‘์š”ํ•œ ์ด์œ **: - ์‚ฌ๋žŒ์ด ์†์œผ๋กœ LaTeX ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค๋ฉด ์˜คํƒ€ ๋ฐœ์ƒ - ์ž๋™ ์ƒ์„ฑ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ฐ’์„ ์ถ”์ถœํ•˜๋Š”์ง€ ํ™•์ธ - ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ํ…Œ์ด๋ธ”์ด ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜๋Š”์ง€ ํ™•์ธ #### 1.3 Figure ์ƒ์„ฑ ํ…Œ์ŠคํŠธ (Figure Generation) ```python def test_figure2_file_created(): """Figure 2 (Early Funding vs Vagueness)๊ฐ€ ์ƒ์„ฑ๋˜๋Š”์ง€""" from src.cli import cmd_generate_plots # Run plotting args = type('obj', (object,), {'dataset': 'all'}) cmd_generate_plots(args) # Check file exists fig_path = Path('paper/figures/fig2_early_funding.pdf') assert fig_path.exists(), "Figure 2 PDF must be created" assert fig_path.stat().st_size > 1000, "Figure 2 must not be empty" ``` **์ด๊ฒƒ์ด ์ค‘์š”ํ•œ ์ด์œ **: - LaTeX ์ปดํŒŒ์ผ ์‹œ ๊ทธ๋ฆผ์ด ์—†์œผ๋ฉด ์˜ค๋ฅ˜ ๋ฐœ์ƒ - ๊ทธ๋ฆผ์ด ๋นˆ ํŒŒ์ผ์ด๋ฉด ๋…ผ๋ฌธ์— ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ๋‚˜์˜ด - ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋๊นŒ์ง€ ์‹คํ–‰๋˜๋Š”์ง€ ํ™•์ธ --- ### Tier 2: ๋ฐ์ดํ„ฐ ํ’ˆ์งˆ ํ…Œ์ŠคํŠธ (Important - Should Pass) **๋ชฉ์ **: ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ถ„์„์— ์ ํ•ฉํ•œ์ง€ ํ™•์ธ #### 2.1 ์ƒ˜ํ”Œ ํฌ๊ธฐ ํ…Œ์ŠคํŠธ ```python def test_sample_size_sufficient(): """์ตœ์†Œ ์ƒ˜ํ”Œ ํฌ๊ธฐ ํ™•์ธ (ํ†ต๊ณ„์  ๊ฒ€์ •๋ ฅ)""" df = load_dataframe('data/processed/features_engineered.nc') # H1 requires at least 30 observations (rule of thumb) assert len(df) >= 30, f"Sample too small: {len(df)} < 30" # H2 requires balanced classes growth_counts = df['growth'].value_counts() minority_class = growth_counts.min() assert minority_class >= 10, f"Minority class too small: {minority_class}" ``` #### 2.2 ๊ฒฐ์ธก์น˜ ํ…Œ์ŠคํŠธ ```python def test_no_missing_values_in_key_vars(): """ํ•ต์‹ฌ ๋ณ€์ˆ˜์— ๊ฒฐ์ธก์น˜๊ฐ€ ์—†๋Š”์ง€ ํ™•์ธ""" df = load_dataframe('data/processed/features_engineered.nc') key_vars = ['vagueness', 'early_funding_musd', 'is_hardware', 'growth'] for var in key_vars: missing_pct = df[var].isna().sum() / len(df) * 100 assert missing_pct < 5, f"{var} has {missing_pct:.1f}% missing" ``` #### 2.3 ์ด์ƒ์น˜ ํ…Œ์ŠคํŠธ ```python def test_vagueness_range(): """Vagueness ์ ์ˆ˜๊ฐ€ ์œ ํšจํ•œ ๋ฒ”์œ„ ๋‚ด์— ์žˆ๋Š”์ง€""" df = load_dataframe('data/processed/features_engineered.nc') assert df['vagueness'].min() >= 0, "Vagueness cannot be negative" assert df['vagueness'].max() <= 100, "Vagueness cannot exceed 100" # Check for unrealistic values extreme_high = (df['vagueness'] > 95).sum() assert extreme_high < len(df) * 0.01, "Too many extreme vagueness scores" ``` --- ### Tier 3: ํŒŒ์ดํ”„๋ผ์ธ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (Good to Have) **๋ชฉ์ **: ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ์ด ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ์‹คํ–‰๋˜๋Š”์ง€ ํ™•์ธ #### 3.1 End-to-End ํ…Œ์ŠคํŠธ ```python def test_full_pipeline_runs(): """์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ (๋ฐ์ดํ„ฐ โ†’ ๋ถ„์„ โ†’ ๋…ผ๋ฌธ)""" import subprocess # Clean previous outputs subprocess.run(['make', 'clean-all'], check=True) # Run full pipeline result = subprocess.run(['make', 'all'], capture_output=True) # Check all outputs exist assert Path('data/processed/features_engineered.nc').exists() assert Path('paper/results_auto.tex').exists() assert Path('paper/tables/table1_h1.tex').exists() assert Path('paper/figures/fig2_early_funding.pdf').exists() ``` #### 3.2 ์žฌํ˜„์„ฑ ํ…Œ์ŠคํŠธ ```python def test_results_are_reproducible(): """๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋กœ ๋‘ ๋ฒˆ ์‹คํ–‰ํ•˜๋ฉด ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๋Š”์ง€""" df = load_dataframe('data/processed/features_engineered.nc') # Run H1 twice result1 = test_h1_early_funding(df) result2 = test_h1_early_funding(df) # Coefficients must be identical np.testing.assert_array_almost_equal( result1.params.values, result2.params.values, decimal=10, err_msg="H1 results not reproducible" ) ``` --- ## 2. ํ…Œ์ŠคํŠธ ํŒŒ์ผ ๊ตฌ์กฐ (Test Organization) ``` test/ โ”œโ”€โ”€ unit/ # Tier 1: ๋‹จ์œ„ ํ…Œ์ŠคํŠธ โ”‚ โ”œโ”€โ”€ test_models.py # H1/H2/H3/H4 ๋ชจ๋ธ ํ•จ์ˆ˜ ํ…Œ์ŠคํŠธ (53 tests) โ”‚ โ”œโ”€โ”€ test_features.py # Vagueness scorer ํ…Œ์ŠคํŠธ (25 tests) โ”‚ โ””โ”€โ”€ test_data_io.py # NetCDF I/O ํ…Œ์ŠคํŠธ (NEW) โ”‚ โ”œโ”€โ”€ integration/ # Tier 2: ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ โ”‚ โ”œโ”€โ”€ test_paper_results.py # ๋…ผ๋ฌธ ๊ฒฐ๊ณผ ๊ฒ€์ฆ (Table/Figure ์ผ์น˜) โ”‚ โ”œโ”€โ”€ test_data_quality.py # ๋ฐ์ดํ„ฐ ํ’ˆ์งˆ ๊ฒ€์‚ฌ (NEW) โ”‚ โ””โ”€โ”€ test_pipeline.py # ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ (NEW) โ”‚ โ”œโ”€โ”€ fixtures/ # ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ โ”‚ โ”œโ”€โ”€ sample_data.nc # ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ (50 companies) โ”‚ โ””โ”€โ”€ expected_outputs/ # ๊ธฐ๋Œ€ ์ถœ๋ ฅ๊ฐ’ โ”‚ โ”œโ”€โ”€ table1_expected.tex โ”‚ โ””โ”€โ”€ h1_expected_coef.json โ”‚ โ””โ”€โ”€ conftest.py # ๊ณต์œ  fixtures (pytest) ``` --- ## 3. ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๋ฐฉ๋ฒ• (How to Run Tests) ### ๋น ๋ฅธ ํ…Œ์ŠคํŠธ (Quick - 1๋ถ„) ```bash # ํ•ต์‹ฌ ๋ชจ๋ธ ํ…Œ์ŠคํŠธ๋งŒ (๊ณ„์ˆ˜๊ฐ€ ๋งž๋Š”์ง€) pytest test/unit/test_models.py::TestH1EarlyFunding -v --no-cov # ๋…ผ๋ฌธ ๊ฒฐ๊ณผ ๊ฒ€์ฆ (ํ…Œ์ด๋ธ” ์ผ์น˜ํ•˜๋Š”์ง€) pytest test/integration/test_paper_results.py -v --no-cov ``` ### ์ „์ฒด ํ…Œ์ŠคํŠธ (Full - 5๋ถ„) ```bash # ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํ–‰ + ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ make test # ๋˜๋Š” pytest test/ -v --cov=src --cov-report=html ``` ### ๋…ผ๋ฌธ ์ œ์ถœ ์ „ ๊ฒ€์ฆ (Before Submission - 10๋ถ„) ```bash # 1. ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ ์žฌ์‹คํ–‰ make clean-all make all # 2. ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํ–‰ make test # 3. ๋…ผ๋ฌธ ๊ฐ’ ๊ฒ€์ฆ make validate # 4. PDF ์ปดํŒŒ์ผ make paper ``` --- ## 4. ๋กœ์ปฌ ํ™˜๊ฒฝ ํ…Œ์ŠคํŠธ ์˜ˆ์‹œ (Local Testing Example) ### 4.1 ์„ค์น˜ (Installation) ```bash # 1. Clone repository git clone https://github.com/user/empirics_ent_strat_ops.git cd empirics_ent_strat_ops # 2. Install dependencies (NO pyarrow needed!) pip install -r requirements.txt # 3. Verify installation python -c "import xarray; import pandas; import statsmodels; print('โœ“ All dependencies OK')" ``` ### 4.2 ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ (Convert existing Parquet to NetCDF) ```bash # If you have existing .parquet files: python scripts/convert_to_netcdf.py --directory data/processed # Expected output: # Converting features_engineered.parquet... # โœ“ features_engineered.parquet (2.3 MB) # โ†’ features_engineered.nc (1.8 MB) # Ratio: 0.78x ``` ### 4.3 ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ (Run Full Pipeline) ```bash # Step-by-step (recommended for first time) make data # โ†’ data/processed/features_engineered.nc make analysis # โ†’ paper/results_auto.tex make tables # โ†’ paper/tables/*.tex make figures # โ†’ paper/figures/*.pdf make paper # โ†’ paper/output/main.pdf # Or all at once: make all ``` ### 4.4 ํ…Œ์ŠคํŠธ ์‹คํ–‰ (Run Tests) ```bash # Quick test (ํ•ต์‹ฌ๋งŒ) pytest test/unit/test_models.py -v --no-cov # Expected output: # test_h1_negative_vagueness_effect PASSED # test_h2_interaction_term_exists PASSED # ... # ======================== 53 passed in 2.34s ======================== # Full test (์ „์ฒด) make test # Expected output: # test/unit/test_models.py .................... [ 68%] # test/unit/test_features.py ............. [ 84%] # test/integration/test_paper_results.py .... [100%] # ======================== 78 passed in 4.12s ======================== ``` --- ## 5. ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ ๋Œ€์‘ (Troubleshooting) ### Case 1: H1 ๊ณ„์ˆ˜ ๋ถ€ํ˜ธ๊ฐ€ ๋ฐ”๋€œ ``` FAILED test_h1_negative_vagueness_effect AssertionError: H1: Vagueness should reduce early funding ``` **์›์ธ**: - ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€œ - ๋ชจ๋ธ ์ŠคํŽ™ ๋ณ€๊ฒฝ (๋ณ€์ˆ˜ ์ถ”๊ฐ€/์ œ๊ฑฐ) - ์ฝ”๋”ฉ ์˜ค๋ฅ˜ **๋Œ€์‘**: 1. ๋ฐ์ดํ„ฐ ํ™•์ธ: `df['vagueness'].describe()` - ๋ถ„ํฌ๊ฐ€ ์ด์ƒํ•œ๊ฐ€? 2. ๋ชจ๋ธ ํ™•์ธ: `test_h1_early_funding(df).summary()` - ์–ด๋–ค ๋ณ€์ˆ˜๊ฐ€ ๋ฌธ์ œ? 3. ์ด๋ก  ์žฌ๊ฒ€ํ† : H1 ๊ฐ€์„ค์ด ํ‹€๋ ธ์„ ์ˆ˜๋„ ์žˆ์Œ ### Case 2: ํ…Œ์ด๋ธ” ๊ฐ’ ๋ถˆ์ผ์น˜ ``` FAILED test_table1_matches_h1_model AssertionError: Table coefficient -0.234 != Model coefficient -0.235 ``` **์›์ธ**: - LaTeX ์ƒ์„ฑ ์Šคํฌ๋ฆฝํŠธ์—์„œ ๋ฐ˜์˜ฌ๋ฆผ ์ฐจ์ด - ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์‹œ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ **๋Œ€์‘**: 1. `scripts/generate_paper_tables.py` ํ™•์ธ 2. `format_coef_se()` ํ•จ์ˆ˜์˜ ์†Œ์ˆ˜์  ์ž๋ฆฌ์ˆ˜ ํ™•์ธ 3. ํ…Œ์ŠคํŠธ์˜ ํ—ˆ์šฉ ์˜ค์ฐจ ์กฐ์ • (`decimal=3` โ†’ `decimal=2`) ### Case 3: Figure ์ƒ์„ฑ ์‹คํŒจ ``` FileNotFoundError: paper/figures/fig2_early_funding.pdf not found ``` **์›์ธ**: - ๊ทธ๋ฆผ ์ƒ์„ฑ ์Šคํฌ๋ฆฝํŠธ ์˜ค๋ฅ˜ - ๊ฒฝ๋กœ ์˜คํƒ€ - ๋ฐ์ดํ„ฐ ๋ถ€์กฑ (๋นˆ ๊ทธ๋ฆผ) **๋Œ€์‘**: 1. ์ง์ ‘ ์‹คํ–‰: `python -m src.cli generate-plots --dataset all` 2. ๋กœ๊ทธ ํ™•์ธ: ์–ด๋А ๋‹จ๊ณ„์—์„œ ์‹คํŒจ? 3. ์ƒ˜ํ”Œ ํฌ๊ธฐ ํ™•์ธ: ๊ทธ๋ฆผ ๊ทธ๋ฆด ๋ฐ์ดํ„ฐ๊ฐ€ ์ถฉ๋ถ„ํ•œ๊ฐ€? --- ## 6. CI/CD ํ†ตํ•ฉ (GitHub Actions) ### ์ž๋™ ํ…Œ์ŠคํŠธ (Every Push) ```yaml # .github/workflows/test.yml name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install dependencies run: | pip install -r requirements.txt - name: Run unit tests run: pytest test/unit/ -v - name: Run integration tests run: pytest test/integration/ -v ``` ### ๋…ผ๋ฌธ ์ž๋™ ๋นŒ๋“œ (On Main Branch) ```yaml # .github/workflows/paper.yml name: Build Paper on: push: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run pipeline run: make all - name: Upload PDF uses: actions/upload-artifact@v3 with: name: paper path: paper/output/main.pdf ``` --- ## 7. ์ฒดํฌ๋ฆฌ์ŠคํŠธ (Checklist) ### ๋…ผ๋ฌธ ์ œ์ถœ ์ „ (Before Submission) - [ ] ๋ชจ๋“  ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (`make test`) - [ ] ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์™„๋ฃŒ (`make all`) - [ ] PDF ์ปดํŒŒ์ผ ์„ฑ๊ณต (`paper/output/main.pdf` ์กด์žฌ) - [ ] Table 1-2 ๊ฐ’์ด ๋ชจ๋ธ ๊ฒฐ๊ณผ์™€ ์ผ์น˜ - [ ] Figure 2-3 ํŒŒ์ผ ์ƒ์„ฑ๋จ - [ ] Results section ์ž๋™ ์ƒ์„ฑ๋จ (`paper/results_auto.tex`) - [ ] Git commit์— ๋ชจ๋“  ๋ณ€๊ฒฝ์‚ฌํ•ญ ํฌํ•จ - [ ] README์— ์žฌํ˜„ ๋ฐฉ๋ฒ• ๋ช…์‹œ ### ๋ฆฌ๋ทฐ ํ”ผ๋“œ๋ฐฑ ํ›„ (After Review) - [ ] ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ `make clean-all && make all` ์žฌ์‹คํ–‰ - [ ] ๋ชจ๋ธ ์ŠคํŽ™ ๋ณ€๊ฒฝ ์‹œ ํ…Œ์ŠคํŠธ ์—…๋ฐ์ดํŠธ - [ ] ์ƒˆ๋กœ์šด ๊ฐ€์„ค ์ถ”๊ฐ€ ์‹œ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ - [ ] ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์žฌ๊ฒ€์ฆ --- ## ์š”์•ฝ (Summary) **3๊ฐ€์ง€ ํ•ต์‹ฌ ํ…Œ์ŠคํŠธ**: 1. **๋ชจ๋ธ ๊ณ„์ˆ˜ ํ…Œ์ŠคํŠธ**: ๋…ผ๋ฌธ์˜ ํ•ต์‹ฌ ์ฃผ์žฅ์ด ๋ฐ์ดํ„ฐ์—์„œ ๋‚˜์˜ค๋Š”๊ฐ€? 2. **ํ…Œ์ด๋ธ” ๊ฒ€์ฆ ํ…Œ์ŠคํŠธ**: ์ž๋™ ์ƒ์„ฑ๋œ ํ…Œ์ด๋ธ”์ด ๋ชจ๋ธ ๊ฒฐ๊ณผ์™€ ์ผ์น˜ํ•˜๋Š”๊ฐ€? 3. **ํŒŒ์ดํ”„๋ผ์ธ E2E ํ…Œ์ŠคํŠธ**: ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ์˜ค๋ฅ˜ ์—†์ด ์‹คํ–‰๋˜๋Š”๊ฐ€? **ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ˆœ์„œ**: ```bash # 1. ๋น ๋ฅธ ๊ฒ€์ฆ (1๋ถ„) pytest test/unit/test_models.py -v --no-cov # 2. ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ (5๋ถ„) make all # 3. ๋ชจ๋“  ํ…Œ์ŠคํŠธ (5๋ถ„) make test # 4. ๋…ผ๋ฌธ ํ™•์ธ (์ˆ˜๋™) open paper/output/main.pdf ``` **์„ฑ๊ณต ๊ธฐ์ค€**: - ๋ชจ๋“  ํ…Œ์ŠคํŠธ ํ†ต๊ณผ (78/78 passed) - PDF ์ƒ์„ฑ ์„ฑ๊ณต - ํ…Œ์ด๋ธ”/๊ทธ๋ฆผ ์ž๋™ ์ƒ์„ฑ - Git์— ๋ชจ๋“  ๋ณ€๊ฒฝ์‚ฌํ•ญ commit ์ด์ œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ `make all`๋งŒ ์‹คํ–‰ํ•˜๋ฉด ๋…ผ๋ฌธ์ด ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰