# Rust-Side Lineloss XLSX Export

## Problem

`collect_lineloss.js` runs on a remote page (`http://20.76.57.61:18080/gsllys`).
The script successfully queries API data (12 rows), but cannot call
`http://localhost:13313/.../faultDetailsExportXLSX` because the browser blocks
cross-origin requests from a remote page to `localhost`.

The original scene architecture had a local scene page acting as a proxy,
but skill mode has no local page -- so export is architecturally impossible
from the browser side.

## Decision

Move XLSX generation to the Rust side. JS only collects data; Rust generates
the `.xlsx` file locally after receiving the artifact.

Report log (`setReportLog`) is deferred to a later iteration.

## Design

### JS Changes (`collect_lineloss.js`)

1. Remove `exportWorkbook()` call and `writeReportLog()` call
2. Return artifact with `rows` array and `column_defs` array
3. Status is `ok` when rows > 0, `empty` when rows == 0, `error`/`blocked` unchanged

Artifact shape:
```json
{
  "type": "report-artifact",
  "report_name": "tq-lineloss-report",
  "status": "ok",
  "org": { "label": "...", "code": "..." },
  "period": { "mode": "month", "value": "2026-03" },
  "column_defs": [["ORG_NAME","供电单位"], ["YGDL","累计供电量"], ...],
  "rows": [
    {"ORG_NAME":"xxx", "YGDL":"12345.67", ...}
  ],
  "counts": { "rows": 12 }
}
```

### Rust Changes

#### New file: `src/compat/lineloss_xlsx_export.rs`

Generates a standard `.xlsx` file using `zip` crate + OpenXML XML strings.
Follows the pattern established in `openxml_office_tool.rs`.

Public API:
```rust
pub struct LinelossExportRequest {
    pub column_defs: Vec<(String, String)>,  // (key, chinese_header)
    pub rows: Vec<Map<String, Value>>,
    pub sheet_name: String,
    pub output_path: PathBuf,
}

pub fn export_lineloss_xlsx(request: &LinelossExportRequest) -> anyhow::Result<PathBuf>;
```

Internals:
- Build header row from `column_defs[*].1` (chinese names)
- Build data rows by looking up `column_defs[*].0` keys in each row map
- Generate `worksheet_xml` with inline string cells
- Package with standard OpenXML boilerplate (content_types, rels, workbook)
- Write to `output_path`

#### Modified: `src/compat/deterministic_submit.rs`

In `execute_deterministic_submit_with_browser_backend` (and the non-backend variant):

```
let output = execute_browser_script_skill_raw_output_with_browser_backend(...)?;
let artifact = parse_lineloss_artifact(&output);

if artifact has rows > 0 && column_defs present:
    let export_path = workspace_root/out/tq-lineloss-{timestamp}.xlsx
    export_lineloss_xlsx(LinelossExportRequest { ... })?
    // attach export_path to outcome summary

Ok(summarize_lineloss_output_with_export(&output, export_path))
```

#### Modified: `src/compat/mod.rs`

Add `pub mod lineloss_xlsx_export;`

### Output Path

`{workspace_root}/out/tq-lineloss-{org_label}-{period}-{timestamp_nanos}.xlsx`

### Error Handling

- XLSX generation failure: outcome status = `partial`, reason = `xlsx_export_failed`
- Artifact parse failure: fall through to existing `summarize_lineloss_output`

## Files Changed

| File | Change Type |
|------|-------------|
| `collect_lineloss.js` | Modify: remove export/log calls, add rows+column_defs to artifact |
| `src/compat/lineloss_xlsx_export.rs` | New: XLSX generation |
| `src/compat/deterministic_submit.rs` | Modify: post-process artifact, call XLSX export |
| `src/compat/mod.rs` | Modify: register new module |

## Requires Recompilation

Yes. Rust code changes require `cargo build`.
