acceptance: stabilize zhihu hotlist excel flow

This commit is contained in:
zyl
2026-03-29 23:17:31 +08:00
parent e294fbb9b1
commit ef88487f4a
5 changed files with 287 additions and 59 deletions

View File

@@ -1,6 +1,7 @@
use async_trait::async_trait;
use serde::Deserialize;
use serde_json::{json, Value};
use std::collections::BTreeSet;
use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
@@ -79,21 +80,35 @@ impl Tool for OpenXmlOfficeTool {
.iter()
.map(|value| value.to_string())
.collect::<Vec<_>>();
if parsed.columns != expected_columns {
return Ok(failed_tool_result(
"unsupported columns: expected [rank, title, heat]".to_string(),
));
}
let column_order = match resolve_column_order(&parsed.columns, &expected_columns) {
Some(order) => order,
None => {
return Ok(failed_tool_result(
"unsupported columns: expected [rank, title, heat]".to_string(),
))
}
};
if parsed.rows.is_empty() {
return Ok(failed_tool_result("rows must not be empty".to_string()));
}
if parsed.rows.iter().any(|row| row.len() != parsed.columns.len()) {
return Ok(failed_tool_result(
"each row must match the declared columns length".to_string(),
));
}
if parsed.rows.iter().any(|row| row.len() != 3) {
return Ok(failed_tool_result(
"each row must contain exactly 3 values".to_string(),
));
}
let normalized_rows = parsed
.rows
.iter()
.map(|row| reorder_row(row, &column_order))
.collect::<Vec<_>>();
let job_root = create_job_root(&self.workspace_root)?;
let template_path = job_root.join("zhihu_hotlist_template.xlsx");
@@ -105,8 +120,8 @@ impl Tool for OpenXmlOfficeTool {
.map(PathBuf::from)
.unwrap_or_else(|| default_output_path(&self.workspace_root));
write_hotlist_template(&template_path, parsed.rows.len())?;
write_payload_json(&payload_path, &parsed.rows)?;
write_hotlist_template(&template_path, normalized_rows.len())?;
write_payload_json(&payload_path, &normalized_rows)?;
write_request_json(&request_path, &template_path, &payload_path, &output_path)?;
let rendered = run_openxml_cli(&request_path)?;
@@ -120,7 +135,7 @@ impl Tool for OpenXmlOfficeTool {
output: json!({
"sheet_name": DEFAULT_SHEET_NAME,
"output_path": artifact_path,
"row_count": parsed.rows.len(),
"row_count": normalized_rows.len(),
"renderer": OPENXML_OFFICE_TOOL_NAME
})
.to_string(),
@@ -156,6 +171,44 @@ fn default_output_path(workspace_root: &Path) -> PathBuf {
.join(format!("zhihu-hotlist-{nanos}.xlsx"))
}
fn resolve_column_order(
provided_columns: &[String],
expected_columns: &[String],
) -> Option<Vec<usize>> {
if provided_columns.len() != expected_columns.len() {
return None;
}
let provided_set = provided_columns
.iter()
.map(|value| value.trim().to_string())
.collect::<BTreeSet<_>>();
let expected_set = expected_columns
.iter()
.cloned()
.collect::<BTreeSet<_>>();
if provided_set != expected_set {
return None;
}
expected_columns
.iter()
.map(|expected| {
provided_columns
.iter()
.position(|provided| provided.trim() == expected)
})
.collect::<Option<Vec<_>>>()
}
fn reorder_row(row: &[Value], column_order: &[usize]) -> Vec<Value> {
column_order
.iter()
.map(|index| row[*index].clone())
.collect()
}
fn write_payload_json(path: &Path, rows: &[Vec<Value>]) -> anyhow::Result<()> {
let mut variables = BTreeMap::new();
for (idx, row) in rows.iter().enumerate() {