Skip to content

Commit e20fd6a

Browse files
ToyamaEijietoyamaclaude
authored
fix(#85-#91): harden batch-analysis for stable overnight runs (#92)
* fix(#85,#86,#87,#88): harden batch-analysis for stable overnight runs - Add marimo >= 0.20.3 preflight check at Step 0 (#85) - Wrap marimo export session with timeout 600s at Step 3f (#86) - Add BQ type coercion guidance (dbdate, nullable Int64) at Step 3e (#88) - Add data volume estimation with strategy hints at Step 3e (#87) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use perl alarm for macOS-compatible timeout and fix Step 0 numbering - Replace GNU `timeout` with `perl -e 'alarm 600; exec @argv'` for macOS compatibility (timeout is not available by default on Darwin) - Fix duplicate step number "4" in Step 0 (now correctly numbered 1-7) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(#89,#90,#91): RUN_DIR unification, portable lib_dir, methodology code patterns - Accept BATCH_RUN_DIR env var in Step 0 to unify run directory (#89) - Use relative path for lib_dir injection in Cell 0 (#90) - Add Step 2.6 to analysis-design: enrich methodology with concrete code patterns for batch LLM fidelity (#91) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Codex review findings on batch-prompt - Handle pre-release marimo versions (e.g., 0.20.3rc1) in preflight check by using regex to extract major.minor.patch - Exclude ID/key columns from nullable Int64 -> float64 coercion to avoid precision loss on large integers - Add fallback for COUNT(*) estimation failure (skip to direct strategy) - Document perl alarm limitation (single-process, not process tree) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: etoyama <eijitoyama@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8640590 commit e20fd6a

2 files changed

Lines changed: 68 additions & 6 deletions

File tree

skills/analysis-design/SKILL.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,30 @@ If the interview did not cover methodology, ask now:
9999
- "What analysis method will you use? (e.g., OLS, t-test, chi-square, DID)"
100100
- Set `methodology = {method: "<answer>", reason: "<why this method>"}` at minimum.
101101

102+
### Step 2.6: Enrich Methodology with Code Patterns
103+
104+
The batch-analysis LLM (sonnet) generates notebooks from methodology content. Concrete code patterns dramatically improve generation fidelity.
105+
106+
**When `data_source` references a registered catalog source**, auto-generate a code pattern from the catalog schema and include it in `methodology.steps`:
107+
108+
```python
109+
methodology = {
110+
"method": "OLS",
111+
"package": "statsmodels",
112+
"reason": "線形回帰で相関を検証",
113+
"steps": [
114+
{
115+
"description": "from lib.accessor.bigquery import BigQueryAccessor\nacc = BigQueryAccessor(project_id='lmi-datau-prod')\nraw_df = acc.query_to_dataframe('''\n SELECT col1, col2, ...\n FROM `project.dataset.table`\n WHERE ...\n''')"
116+
}
117+
]
118+
}
119+
```
120+
121+
**Rules:**
122+
- If `data_source` is a BQ table: generate `BigQueryAccessor` pattern with actual table name, key columns from schema, and filter conditions
123+
- If methodology uses a specific library (e.g., lingam, shap, dowhy): include the exact API call with parameter names in a subsequent step
124+
- If the user provides their own code pattern, use it as-is — do not overwrite
125+
102126
### Step 3: Create the Design
103127

104128
```
@@ -161,6 +185,7 @@ Only provided fields are updated; all others remain unchanged.
161185
| `methodology.method` | str | free text (required, non-empty) ||
162186
| `methodology.package` | str | free text (optional) | `""` |
163187
| `methodology.reason` | str | free text (optional) | `""` |
188+
| `methodology.steps` | list[dict] | Each dict has `description` (str). Include concrete code patterns for batch LLM fidelity. | `[]` |
164189

165190
**Backward compatibility**: `role`, `tier`, `intent` fields are optional in input. If omitted, defaults are applied automatically.
166191

skills/batch-analysis/references/batch-prompt.md

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,18 @@ Execute the following steps in order. Log progress with markers for traceability
196196
4. If not found, use defaults:
197197
- `notebook_dir`: `.insight/runs/YYYYMMDD_HHmmss/{design_id}/`
198198
- `lib_dir`: none (disabled)
199-
4. Create run directory:
199+
5. **marimo version preflight check**:
200200
```bash
201-
RUN_DIR=".insight/runs/$(date +%Y%m%d_%H%M%S)"
201+
uv run python -c "import re, marimo; m = re.match(r'(\d+)\.(\d+)\.(\d+)', marimo.__version__); assert m and tuple(int(x) for x in m.groups()) >= (0, 20, 3), f'marimo >= 0.20.3 required for export session (found {marimo.__version__})';"
202+
```
203+
If the check fails, log the error and **stop the entire batch** — no design can be processed without `marimo export session`.
204+
6. Create run directory:
205+
```bash
206+
RUN_DIR="${BATCH_RUN_DIR:-.insight/runs/$(date +%Y%m%d_%H%M%S)}"
202207
mkdir -p "$RUN_DIR"
203208
```
204-
5. Record the `RUN_DIR` path for use throughout the session.
209+
If the host project passes `BATCH_RUN_DIR` via environment variable, use it to keep session.log and batch output in the same directory.
210+
7. Record the `RUN_DIR` path for use throughout the session.
205211

206212
### Step 1: lib_dir Cataloging (if configured)
207213

@@ -288,21 +294,52 @@ If `methodology.package` is specified:
288294
2. Create directory: `mkdir -p {notebook_dir}`
289295
3. If lib_dir is configured:
290296
- Read `{lib_dir}/CATALOG.md` for available utilities
291-
- Add `sys.path.insert(0, "{lib_dir}")` to Cell 0
297+
- Add lib_dir to Cell 0 using a **relative path from the notebook location**, not an absolute path:
298+
```python
299+
import os as _os
300+
sys.path.insert(0, _os.path.join(_os.path.dirname(__file__), _os.path.relpath("{lib_dir}", "{notebook_dir}")))
301+
```
292302
- Import relevant utility functions
293303
4. Generate notebook.py following the Cell Contract exactly:
294304
- Read the design's hypothesis, metrics, explanatory variables, chart specs, methodology, analysis_intent
295305
- Read the table schema (columns, types) from get_table_schema
296306
- Generate all 8 cells with appropriate content
297307
- Use the data source file path from catalog for CSV loading
298-
5. Write the notebook with the Write tool
308+
5. **BQ Type Coercion** (when Cell 2 uses `BigQueryAccessor.query_to_dataframe()`, not `pd.read_csv()`):
309+
Add the following type coercion block in Cell 2, immediately after the query execution:
310+
```python
311+
# Coerce BQ-specific types to pandas-standard types
312+
for col in raw_df.select_dtypes(include=["dbdate", "dbtime"]).columns:
313+
raw_df[col] = pd.to_datetime(raw_df[col])
314+
# Exclude ID/key columns (suffix _id, _key, _code) to avoid precision loss
315+
_numeric_cols = [c for c in raw_df.select_dtypes(include=["Int8", "Int16", "Int32", "Int64"]).columns
316+
if not re.search(r'_(id|key|code)$', c, re.IGNORECASE)]
317+
for col in _numeric_cols:
318+
raw_df[col] = raw_df[col].astype("float64")
319+
```
320+
This prevents downstream errors: `dbdate` breaks `groupby`/`idxmax`, nullable `Int64` breaks numpy ufuncs. ID/key columns are excluded to avoid float64 precision loss on large integers.
321+
6. **Data Volume Strategy** (when data_source is a BigQuery table):
322+
Before generating the analysis query, estimate row count as part of notebook generation:
323+
1. Build a `COUNT(*)` query from the design's `data_source` + `filter_conditions`
324+
2. Execute via `BigQueryAccessor`. If the COUNT fails (permissions, timeout, view complexity), skip estimation and use `direct` strategy as fallback
325+
3. Include the estimated row count and chosen strategy as a comment in Cell 2: `# Estimated rows: {count} -> strategy: {strategy}`
326+
4. The strategy is a **hint**, not a hard constraint — the agent chooses the final approach based on `methodology`:
327+
328+
| Row count | Strategy hint | Cell 2 guidance |
329+
|-----------|---------------|-----------------|
330+
| < 1M | `direct` | Pull all rows to pandas |
331+
| 1M - 10M | `sample` | Consider TABLESAMPLE or BQ-side WHERE to reduce rows |
332+
| > 10M | `agg_first` | Prefer BQ-side GROUP BY / PIVOT / QUALIFY before pull |
333+
7. Write the notebook with the Write tool
299334

300335
#### 3f. Execute Notebook
301336

302337
```bash
303-
cd {notebook_dir} && uv run marimo export session --force-overwrite notebook.py 2>&1
338+
cd {notebook_dir} && perl -e 'alarm 600; exec @ARGV' -- uv run marimo export session --force-overwrite notebook.py 2>&1
304339
```
305340

341+
The 600-second (10 min) timeout prevents indefinite hangs when marimo fails to exit on cell errors. `perl -e 'alarm ...; exec @ARGV'` is used instead of `timeout` for macOS compatibility (GNU `timeout` is not available by default on macOS). A timeout-killed process enters the error repair loop (Section 5) like any other failure. **Limitation**: the alarm signal is delivered only to the exec'd process, not to its child process tree. In practice this is sufficient because `marimo export session` hangs are single-process.
342+
306343
Check the result:
307344
- **Success**: session JSON exists AND cells 2, 3, 4, 6 have `text/markdown` output. Exit code alone is not sufficient (marimo may return exit code 1 with valid output on warnings).
308345
- **Failure**: session JSON missing OR cells 2/3/4/6 lack `text/markdown` output -> enter error repair loop (see Section 5)

0 commit comments

Comments
 (0)