feat: service console auto-connect, settings panel, and batch of enhancements

- Auto-connect WebSocket on page load in service console
- Settings modal for editing sgclaw_config.json (API key, base URL, model, skills dir, etc.)
- UpdateConfig/ConfigUpdated protocol messages for remote config save
- save_to_path() for SgClawSettings serialization
- ConfigUpdated handler in sg_claw_client binary
- Protocol serialization tests for new message types
- HTML test assertions for auto-connect and settings UI
- Additional pending changes: deterministic submit, org units, lineloss xlsx export, browser script tool, and docs

🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
木炎
2026-04-14 14:32:46 +08:00
parent 6aa0c110bd
commit c60cd308ca
31 changed files with 4883 additions and 18 deletions

View File

@@ -234,11 +234,15 @@ fn execute_browser_script_impl(
let wrapped_script = wrap_browser_script(&script_body, &Value::Object(args.clone()));
eprintln!("[execute_browser_script_impl] 包装后脚本长度: {} 字节", wrapped_script.len());
eprintln!("[execute_browser_script_impl] 包装后脚本前500字符: {}",
eprintln!("[execute_browser_script_impl] 包装后脚本前500字符: {}",
if wrapped_script.len() > 500 { &wrapped_script[..500] } else { &wrapped_script });
eprintln!("[execute_browser_script_impl] 调用 browser_tool.invoke(Action::Eval)...");
let target_url = format!("http://{}", expected_domain);
let target_url = args.get("target_url")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.unwrap_or_else(|| format!("http://{}", expected_domain));
eprintln!("[execute_browser_script_impl] target_url: {}", target_url);
let result = match browser_tool.invoke(
Action::Eval,
json!({

View File

@@ -1,10 +1,12 @@
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use serde_json::{Map, Value};
use crate::browser::BrowserBackend;
use crate::compat::artifact_open::{open_exported_xlsx, PostExportOpen};
use crate::compat::direct_skill_runtime::DirectSubmitOutcome;
use crate::compat::lineloss_xlsx_export::{export_lineloss_xlsx, LinelossExportRequest};
use crate::config::SgClawSettings;
use crate::pipe::{BrowserPipeTool, PipeError, Transport};
@@ -13,6 +15,7 @@ pub struct DeterministicExecutionPlan {
pub instruction: String,
pub tool_name: String,
pub expected_domain: String,
pub target_url: String,
pub org_label: String,
pub org_code: String,
pub period_mode: String,
@@ -30,6 +33,7 @@ pub enum DeterministicSubmitDecision {
const DETERMINISTIC_SUFFIX: &str = "。。。";
const LINELLOSS_HOST: &str = "20.76.57.61";
const LINELLOSS_TARGET_URL: &str = "http://20.76.57.61:18080/gsllys/tqLinelossStatis/tqQualifyRateMonitor";
const LINELLOSS_TOOL: &str = "tq-lineloss-report.collect_lineloss";
pub fn decide_deterministic_submit(
@@ -85,6 +89,7 @@ pub fn decide_deterministic_submit(
instruction: normalized_instruction.to_string(),
tool_name: LINELLOSS_TOOL.to_string(),
expected_domain: LINELLOSS_HOST.to_string(),
target_url: LINELLOSS_TARGET_URL.to_string(),
org_label: resolved_org.label,
org_code: resolved_org.code,
period_mode: period_mode_name(&resolved_period.mode).to_string(),
@@ -110,7 +115,8 @@ pub fn execute_deterministic_submit<T: Transport + 'static>(
args,
)?;
Ok(summarize_lineloss_output(&output))
let export_path = try_export_lineloss_xlsx(&output, workspace_root);
Ok(summarize_lineloss_output_with_export(&output, export_path.as_deref()))
}
pub fn execute_deterministic_submit_with_browser_backend(
@@ -129,7 +135,8 @@ pub fn execute_deterministic_submit_with_browser_backend(
args,
)?;
Ok(summarize_lineloss_output(&output))
let export_path = try_export_lineloss_xlsx(&output, workspace_root);
Ok(summarize_lineloss_output_with_export(&output, export_path.as_deref()))
}
fn deterministic_submit_args(plan: &DeterministicExecutionPlan) -> Map<String, Value> {
@@ -138,6 +145,10 @@ fn deterministic_submit_args(plan: &DeterministicExecutionPlan) -> Map<String, V
"expected_domain".to_string(),
Value::String(plan.expected_domain.clone()),
);
args.insert(
"target_url".to_string(),
Value::String(plan.target_url.clone()),
);
args.insert(
"org_label".to_string(),
Value::String(plan.org_label.clone()),
@@ -256,6 +267,155 @@ fn summarize_lineloss_artifact(artifact: &Value) -> DirectSubmitOutcome {
}
}
fn summarize_lineloss_output_with_export(output: &str, export_path: Option<&Path>) -> DirectSubmitOutcome {
let mut outcome = summarize_lineloss_output(output);
if let Some(path) = export_path {
outcome.summary.push_str(&format!(" export_path={}", path.display()));
match open_exported_xlsx(path) {
PostExportOpen::Opened => {
outcome.summary.push_str(" 已自动打开Excel");
}
PostExportOpen::Failed(reason) => {
outcome.summary.push_str(&format!(" 自动打开Excel失败: {}", reason));
}
}
}
outcome
}
struct LinelossArtifactExportData {
sheet_name: String,
column_defs: Vec<(String, String)>,
rows: Vec<Map<String, Value>>,
}
fn extract_export_data(output: &str) -> Option<LinelossArtifactExportData> {
let payload: Value = serde_json::from_str(output).ok()?;
let artifact = payload
.as_object()
.and_then(|object| object.get("text"))
.unwrap_or(&payload);
let artifact = artifact.as_object()?;
if artifact.get("type").and_then(Value::as_str) != Some("report-artifact") {
return None;
}
let status = artifact.get("status").and_then(Value::as_str).unwrap_or("");
if !matches!(status, "ok" | "partial") {
return None;
}
let rows = artifact
.get("rows")
.and_then(Value::as_array)?;
if rows.is_empty() {
return None;
}
let rows: Vec<Map<String, Value>> = rows
.iter()
.filter_map(|row| row.as_object().cloned())
.collect();
if rows.is_empty() {
return None;
}
let column_defs: Vec<(String, String)> = artifact
.get("column_defs")
.and_then(Value::as_array)
.map(|defs| {
defs.iter()
.filter_map(|def| {
let arr = def.as_array()?;
let key = arr.first()?.as_str()?.to_string();
let label = arr.get(1)?.as_str()?.to_string();
Some((key, label))
})
.collect()
})
.unwrap_or_default();
// Fallback: if column_defs not in artifact, try "columns" array as keys
let column_defs = if column_defs.is_empty() {
let columns = artifact
.get("columns")
.and_then(Value::as_array)?;
columns
.iter()
.filter_map(|col| {
let key = col.as_str()?.to_string();
Some((key.clone(), key))
})
.collect()
} else {
column_defs
};
if column_defs.is_empty() {
return None;
}
let org_label = artifact
.get("org")
.and_then(Value::as_object)
.and_then(|org| org.get("label"))
.and_then(Value::as_str)
.unwrap_or("lineloss");
let period_mode = artifact
.get("period")
.and_then(Value::as_object)
.and_then(|p| p.get("mode"))
.and_then(Value::as_str)
.unwrap_or("month");
let period_value = artifact
.get("period")
.and_then(Value::as_object)
.and_then(|p| p.get("value"))
.and_then(Value::as_str)
.unwrap_or("");
let mode_label = if period_mode == "week" { "周度" } else { "月度" };
let sheet_name = format!("{org_label}{mode_label}线损分析报表({period_value})");
Some(LinelossArtifactExportData {
sheet_name,
column_defs,
rows,
})
}
fn try_export_lineloss_xlsx(
output: &str,
workspace_root: &Path,
) -> Option<PathBuf> {
let data = extract_export_data(output)?;
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or_default();
let out_dir = workspace_root.join("out");
let output_path = out_dir.join(format!("tq-lineloss-{nanos}.xlsx"));
let request = LinelossExportRequest {
sheet_name: data.sheet_name,
column_defs: data.column_defs,
rows: data.rows,
output_path,
};
match export_lineloss_xlsx(&request) {
Ok(path) => {
eprintln!("[deterministic_submit] XLSX exported to: {}", path.display());
Some(path)
}
Err(err) => {
eprintln!("[deterministic_submit] XLSX export failed: {err}");
None
}
}
}
fn strip_exact_deterministic_suffix(raw_instruction: &str) -> Option<&str> {
let without_suffix = raw_instruction.strip_suffix(DETERMINISTIC_SUFFIX)?;
if without_suffix.ends_with('。') {

View File

@@ -330,8 +330,7 @@ fn derive_expected_domain(task_context: &CompatTaskContext) -> Result<String, Pi
.filter(|value| !value.is_empty())
.ok_or_else(|| {
PipeError::Protocol(
"direct submit skill requires page_url so expected_domain can be derived"
.to_string(),
"当前命令需要浏览器页面上下文才能执行。请在浏览器中打开目标页面后重试,或在指令末尾添加'。。。'使用确定性提交。".to_string(),
)
})?;

View File

@@ -0,0 +1,223 @@
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use serde_json::{Map, Value};
use zip::write::FileOptions;
use zip::{CompressionMethod, ZipWriter};
pub struct LinelossExportRequest {
pub sheet_name: String,
pub column_defs: Vec<(String, String)>,
pub rows: Vec<Map<String, Value>>,
pub output_path: PathBuf,
}
pub fn export_lineloss_xlsx(request: &LinelossExportRequest) -> anyhow::Result<PathBuf> {
if request.rows.is_empty() {
anyhow::bail!("rows must not be empty");
}
if request.column_defs.is_empty() {
anyhow::bail!("column_defs must not be empty");
}
let sheet_xml = build_worksheet_xml(&request.column_defs, &request.rows);
write_xlsx(
&request.output_path,
&request.sheet_name,
&sheet_xml,
)?;
Ok(request.output_path.clone())
}
fn build_worksheet_xml(
column_defs: &[(String, String)],
rows: &[Map<String, Value>],
) -> String {
let mut xml_rows = Vec::with_capacity(rows.len() + 1);
// Header row (row 1)
let header_cells: Vec<String> = column_defs
.iter()
.enumerate()
.map(|(col_idx, (_key, label))| {
let col_letter = column_letter(col_idx);
format!(
"<c r=\"{col_letter}1\" t=\"inlineStr\"><is><t>{}</t></is></c>",
xml_escape(label)
)
})
.collect();
xml_rows.push(format!("<row r=\"1\">{}</row>", header_cells.join("")));
// Data rows (row 2+)
for (row_idx, row) in rows.iter().enumerate() {
let excel_row = row_idx + 2;
let cells: Vec<String> = column_defs
.iter()
.enumerate()
.map(|(col_idx, (key, _label))| {
let col_letter = column_letter(col_idx);
let value = row
.get(key)
.map(|v| value_to_string(v))
.unwrap_or_default();
format!(
"<c r=\"{col_letter}{excel_row}\" t=\"inlineStr\"><is><t>{}</t></is></c>",
xml_escape(&value)
)
})
.collect();
xml_rows.push(format!("<row r=\"{excel_row}\">{}</row>", cells.join("")));
}
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\
<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">\
<sheetData>{}</sheetData>\
</worksheet>",
xml_rows.join("")
)
}
fn column_letter(index: usize) -> String {
let mut result = String::new();
let mut n = index;
loop {
result.insert(0, (b'A' + (n % 26) as u8) as char);
if n < 26 {
break;
}
n = n / 26 - 1;
}
result
}
fn value_to_string(value: &Value) -> String {
match value {
Value::String(text) => text.clone(),
Value::Number(number) => number.to_string(),
Value::Bool(flag) => flag.to_string(),
Value::Null => String::new(),
other => other.to_string(),
}
}
fn xml_escape(value: &str) -> String {
value
.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}
fn write_xlsx(output_path: &Path, sheet_name: &str, sheet_xml: &str) -> anyhow::Result<()> {
if let Some(parent) = output_path.parent() {
fs::create_dir_all(parent)?;
}
if output_path.exists() {
fs::remove_file(output_path)?;
}
let file = fs::File::create(output_path)?;
let mut zip = ZipWriter::new(file);
let options = FileOptions::default().compression_method(CompressionMethod::Stored);
zip.start_file("[Content_Types].xml", options)?;
zip.write_all(content_types_xml().as_bytes())?;
zip.start_file("_rels/.rels", options)?;
zip.write_all(root_rels_xml().as_bytes())?;
zip.start_file("docProps/app.xml", options)?;
zip.write_all(app_xml().as_bytes())?;
zip.start_file("docProps/core.xml", options)?;
zip.write_all(core_xml().as_bytes())?;
zip.start_file("xl/workbook.xml", options)?;
zip.write_all(workbook_xml(&xml_escape(sheet_name)).as_bytes())?;
zip.start_file("xl/_rels/workbook.xml.rels", options)?;
zip.write_all(workbook_rels_xml().as_bytes())?;
zip.start_file("xl/worksheets/sheet1.xml", options)?;
zip.write_all(sheet_xml.as_bytes())?;
zip.finish()?;
Ok(())
}
fn content_types_xml() -> &'static str {
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
</Types>"#
}
fn root_rels_xml() -> &'static str {
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
</Relationships>"#
}
fn app_xml() -> &'static str {
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
<Application>sgClaw</Application>
</Properties>"#
}
fn core_xml() -> &'static str {
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:dcterms="http://purl.org/dc/terms/"
xmlns:dcmitype="http://purl.org/dc/dcmitype/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dc:title>台区线损报表</dc:title>
</cp:coreProperties>"#
}
fn workbook_xml(sheet_name: &str) -> String {
format!(
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheets>
<sheet name="{sheet_name}" sheetId="1" r:id="rId1"/>
</sheets>
</workbook>"#
)
}
fn workbook_rels_xml() -> &'static str {
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
</Relationships>"#
}
#[cfg(test)]
mod tests {
use super::column_letter;
#[test]
fn column_letter_maps_indices_correctly() {
assert_eq!(column_letter(0), "A");
assert_eq!(column_letter(1), "B");
assert_eq!(column_letter(6), "G");
assert_eq!(column_letter(25), "Z");
assert_eq!(column_letter(26), "AA");
}
}

View File

@@ -6,6 +6,7 @@ pub mod cron_adapter;
pub mod deterministic_submit;
pub mod direct_skill_runtime;
pub mod event_bridge;
pub mod lineloss_xlsx_export;
pub mod memory_adapter;
pub mod openxml_office_tool;
pub mod orchestration;

View File

@@ -5,29 +5,598 @@ pub(crate) struct OrgUnit {
}
pub(crate) const ORG_UNITS: &[OrgUnit] = &[
// ===== Province-level =====
OrgUnit {
label: "国网甘肃省电力公司",
code: "62101",
aliases: &["国网甘肃省电力公司", "甘肃省电力公司", "甘肃电力公司", "甘肃省公司"],
},
// ===== City-level (lv=2) =====
OrgUnit {
label: "国网兰州供电公司",
code: "62401",
aliases: &["国网兰州供电公司", "兰州供电公司", "兰州公司"],
},
OrgUnit {
label: "国网白银供电公司",
code: "62402",
aliases: &["国网白银供电公司", "白银供电公司", "白银公司"],
},
OrgUnit {
label: "国网天水供电公司",
code: "62403",
aliases: &["国网天水供电公司", "天水供电公司", "天水公司"],
},
OrgUnit {
label: "国网平凉供电公司",
code: "62404",
aliases: &["国网平凉供电公司", "平凉供电公司", "平凉公司"],
},
OrgUnit {
label: "国网金昌供电公司",
code: "62405",
aliases: &["国网金昌供电公司", "金昌供电公司", "金昌公司"],
},
OrgUnit {
label: "国网张掖供电公司",
code: "62406",
aliases: &["国网张掖供电公司", "张掖供电公司", "张掖公司"],
},
OrgUnit {
label: "国网陇南供电公司",
code: "62407",
aliases: &["国网陇南供电公司", "陇南供电公司", "陇南公司"],
},
OrgUnit {
label: "国网定西供电公司",
code: "62408",
aliases: &["国网定西供电公司", "定西供电公司", "定西公司"],
},
OrgUnit {
label: "国网庆阳供电公司",
code: "62409",
aliases: &["国网庆阳供电公司", "庆阳供电公司", "庆阳公司"],
},
OrgUnit {
label: "国网武威供电公司",
code: "62410",
aliases: &["国网武威供电公司", "武威供电公司", "武威公司"],
},
OrgUnit {
label: "国网酒泉供电公司",
code: "62411",
aliases: &["国网酒泉供电公司", "酒泉供电公司", "酒泉公司"],
},
OrgUnit {
label: "国网临夏供电公司",
code: "62412",
aliases: &["国网临夏供电公司", "临夏供电公司", "临夏公司"],
},
OrgUnit {
label: "国网甘南供电公司",
code: "62413",
aliases: &["国网甘南供电公司", "甘南供电公司", "甘南公司"],
},
OrgUnit {
label: "国网嘉峪关供电公司",
code: "62414",
aliases: &["国网嘉峪关供电公司", "嘉峪关供电公司", "嘉峪关公司"],
},
OrgUnit {
label: "国网兰州新区供电公司",
code: "62415",
aliases: &["国网兰州新区供电公司", "兰州新区供电公司", "兰州新区公司"],
},
// ===== 兰州供电公司 children (lv=3) =====
OrgUnit {
label: "城关供电分公司",
code: "6240108",
aliases: &["城关供电分公司", "城关分公司"],
},
OrgUnit {
label: "七里河供电分公司",
code: "6240109",
aliases: &["七里河供电分公司", "七里河分公司"],
},
OrgUnit {
label: "西固供电分公司",
code: "6240107",
aliases: &["西固供电分公司", "西固分公司"],
},
OrgUnit {
label: "安宁供电分公司",
code: "6240111",
aliases: &["安宁供电分公司", "安宁分公司"],
},
OrgUnit {
label: "红古供电分公司",
code: "6240102",
aliases: &["红古供电分公司", "红古分公司"],
},
OrgUnit {
label: "东岗供电分公司",
code: "6240110",
aliases: &["东岗供电分公司", "东岗分公司"],
},
OrgUnit {
label: "国网永登县供电公司",
code: "6240122",
aliases: &["国网永登县供电公司", "永登县供电公司", "永登县公司"],
},
OrgUnit {
label: "国网榆中县供电公司",
code: "6240121",
aliases: &["国网榆中县供电公司", "榆中县供电公司", "榆中县公司"],
},
OrgUnit {
label: "榆中城关供电所",
code: "624012108",
aliases: &["榆中城关供电所"],
label: "国网永靖县供电公司",
code: "6240123",
aliases: &["国网永靖县供电公司", "永靖县供电公司", "永靖县公司"],
},
OrgUnit {
label: "兰州客户服务中心",
code: "6240101",
aliases: &["兰州客户服务中心", "兰州客服中心"],
},
// ===== 白银供电公司 children (lv=3) =====
OrgUnit {
label: "城区供电分公司",
code: "6240201",
aliases: &["城区供电分公司", "城区分公司"],
},
OrgUnit {
label: "国网白银市城区供电分公司",
code: "6240201",
aliases: &["国网白银市城区供电分公司", "白银市城区供电分公司", "白银城区分公司"],
},
OrgUnit {
label: "国网皋兰县供电公司",
code: "6240223",
aliases: &["国网皋兰县供电公司", "皋兰县供电公司", "皋兰县公司"],
},
OrgUnit {
label: "国网靖远县供电公司",
code: "6240221",
aliases: &["国网靖远县供电公司", "靖远县供电公司", "靖远县公司"],
},
OrgUnit {
label: "国网景泰县供电公司",
code: "6240222",
aliases: &["国网景泰县供电公司", "景泰县供电公司", "景泰县公司"],
},
OrgUnit {
label: "国网会宁县供电公司",
code: "6240225",
aliases: &["国网会宁县供电公司", "会宁县供电公司", "会宁县公司"],
},
OrgUnit {
label: "国网白银市平川区供电公司",
code: "6240224",
aliases: &["国网白银市平川区供电公司", "白银市平川区供电公司", "平川区供电公司", "平川区公司"],
},
OrgUnit {
label: "白银客户服务中心",
code: "6240207",
aliases: &["白银客户服务中心", "白银客服中心"],
},
// ===== 天水供电公司 children (lv=3) =====
OrgUnit {
label: "国网天水市秦州区供电公司",
code: "6240323",
aliases: &["国网天水市秦州区供电公司", "天水市秦州区供电公司", "秦州区供电公司", "秦州区公司"],
},
OrgUnit {
label: "秦州区供电公司",
code: "6240323",
aliases: &["秦州区供电公司", "秦州区公司"],
},
OrgUnit {
label: "国网天水市麦积区供电公司",
code: "6240305",
aliases: &["国网天水市麦积区供电公司", "天水市麦积区供电公司", "麦积区供电公司", "麦积区公司"],
},
OrgUnit {
label: "国网麦积区供电公司",
code: "6240305",
aliases: &["国网麦积区供电公司", "麦积区供电公司", "麦积区公司"],
},
OrgUnit {
label: "国网武山县供电公司",
code: "6240321",
aliases: &["国网武山县供电公司", "武山县供电公司", "武山县公司"],
},
OrgUnit {
label: "武山县供电公司",
code: "6240321",
aliases: &["武山县供电公司", "武山县公司"],
},
OrgUnit {
label: "国网甘谷县供电公司",
code: "6240322",
aliases: &["国网甘谷县供电公司", "甘谷县供电公司", "甘谷县公司"],
},
OrgUnit {
label: "甘谷县供电公司",
code: "6240322",
aliases: &["甘谷县供电公司", "甘谷县公司"],
},
OrgUnit {
label: "国网秦安县供电公司",
code: "6240324",
aliases: &["国网秦安县供电公司", "秦安县供电公司", "秦安县公司"],
},
OrgUnit {
label: "清水县供电公司",
code: "6240325",
aliases: &["清水县供电公司", "清水县公司"],
},
OrgUnit {
label: "张家川县供电公司",
code: "6240326",
aliases: &["张家川县供电公司", "张家川县公司"],
},
OrgUnit {
label: "天水客户服务中心",
code: "6240306",
aliases: &["天水客户服务中心", "天水客服中心"],
},
// ===== 平凉供电公司 children (lv=3) =====
OrgUnit {
label: "国网崇信县供电公司",
code: "6240401",
aliases: &["国网崇信县供电公司", "崇信县供电公司", "崇信县公司"],
},
OrgUnit {
label: "国网庄浪县供电公司",
code: "6240402",
aliases: &["国网庄浪县供电公司", "庄浪县供电公司", "庄浪县公司"],
},
OrgUnit {
label: "国网泾川县供电公司",
code: "6240403",
aliases: &["国网泾川县供电公司", "泾川县供电公司", "泾川县公司"],
},
OrgUnit {
label: "国网静宁县供电公司",
code: "6240404",
aliases: &["国网静宁县供电公司", "静宁县供电公司", "静宁县公司"],
},
OrgUnit {
label: "国网崆峒区供电公司",
code: "6240405",
aliases: &["国网崆峒区供电公司", "崆峒区供电公司", "崆峒区公司"],
},
OrgUnit {
label: "国网华亭市公司",
code: "6240407",
aliases: &["国网华亭市公司", "华亭市公司"],
},
OrgUnit {
label: "国网灵台县供电公司",
code: "6240408",
aliases: &["国网灵台县供电公司", "灵台县供电公司", "灵台县公司"],
},
// ===== 金昌供电公司 children (lv=3) =====
OrgUnit {
label: "金川区供电公司",
code: "6240522",
aliases: &["金川区供电公司", "金川区公司"],
},
OrgUnit {
label: "国网永昌县供电公司",
code: "6240523",
aliases: &["国网永昌县供电公司", "永昌县供电公司", "永昌县公司"],
},
OrgUnit {
label: "城区供电服务中心",
code: "6240505",
aliases: &["城区供电服务中心"],
},
OrgUnit {
label: "金昌客户服务中心",
code: "6240507",
aliases: &["金昌客户服务中心", "金昌客服中心"],
},
// ===== 张掖供电公司 children (lv=3) =====
OrgUnit {
label: "国网甘州区供电公司",
code: "6240621",
aliases: &["国网甘州区供电公司", "甘州区供电公司", "甘州区公司"],
},
OrgUnit {
label: "肃南县供电公司",
code: "6240622",
aliases: &["肃南县供电公司", "肃南县公司"],
},
OrgUnit {
label: "国网高台县供电公司",
code: "6240623",
aliases: &["国网高台县供电公司", "高台县供电公司", "高台县公司"],
},
OrgUnit {
label: "国网山丹县供电公司",
code: "6240624",
aliases: &["国网山丹县供电公司", "山丹县供电公司", "山丹县公司"],
},
OrgUnit {
label: "国网民乐县供电公司",
code: "6240625",
aliases: &["国网民乐县供电公司", "民乐县供电公司", "民乐县公司"],
},
OrgUnit {
label: "国网临泽县供电公司",
code: "6240626",
aliases: &["国网临泽县供电公司", "临泽县供电公司", "临泽县公司"],
},
// ===== 陇南供电公司 children (lv=3) =====
OrgUnit {
label: "国网武都区供电公司",
code: "6240701",
aliases: &["国网武都区供电公司", "武都区供电公司", "武都区公司"],
},
OrgUnit {
label: "国网宕昌县供电公司",
code: "6240702",
aliases: &["国网宕昌县供电公司", "宕昌县供电公司", "宕昌县公司"],
},
OrgUnit {
label: "国网文县供电公司",
code: "6240703",
aliases: &["国网文县供电公司", "文县供电公司", "文县公司"],
},
OrgUnit {
label: "国网康县供电公司",
code: "6240704",
aliases: &["国网康县供电公司", "康县供电公司", "康县公司"],
},
OrgUnit {
label: "国网西和县供电公司",
code: "6240705",
aliases: &["国网西和县供电公司", "西和县供电公司", "西和县公司"],
},
OrgUnit {
label: "国网礼县供电公司",
code: "6240706",
aliases: &["国网礼县供电公司", "礼县供电公司", "礼县公司"],
},
OrgUnit {
label: "国网成县供电公司",
code: "6240707",
aliases: &["国网成县供电公司", "成县供电公司", "成县公司"],
},
OrgUnit {
label: "国网徽县供电公司",
code: "6240708",
aliases: &["国网徽县供电公司", "徽县供电公司", "徽县公司"],
},
OrgUnit {
label: "国网两当县供电公司",
code: "6240709",
aliases: &["国网两当县供电公司", "两当县供电公司", "两当县公司"],
},
// ===== 定西供电公司 children (lv=3) =====
OrgUnit {
label: "国网定西市安定区供电公司",
code: "6240801",
aliases: &["国网定西市安定区供电公司", "定西市安定区供电公司", "安定区供电公司", "安定区公司"],
},
OrgUnit {
label: "国网通渭县供电公司",
code: "6240802",
aliases: &["国网通渭县供电公司", "通渭县供电公司", "通渭县公司"],
},
OrgUnit {
label: "国网陇西县供电公司",
code: "6240803",
aliases: &["国网陇西县供电公司", "陇西县供电公司", "陇西县公司"],
},
OrgUnit {
label: "国网渭源县供电公司",
code: "6240804",
aliases: &["国网渭源县供电公司", "渭源县供电公司", "渭源县公司"],
},
OrgUnit {
label: "国网临洮县供电公司",
code: "6240805",
aliases: &["国网临洮县供电公司", "临洮县供电公司", "临洮县公司"],
},
OrgUnit {
label: "国网漳县供电公司",
code: "6240806",
aliases: &["国网漳县供电公司", "漳县供电公司", "漳县公司"],
},
OrgUnit {
label: "国网岷县供电公司",
code: "6240807",
aliases: &["国网岷县供电公司", "岷县供电公司", "岷县公司"],
},
// ===== 庆阳供电公司 children (lv=3) =====
OrgUnit {
label: "西峰区供电公司",
code: "6240901",
aliases: &["西峰区供电公司", "西峰区公司"],
},
OrgUnit {
label: "国网庆城县供电公司",
code: "6240902",
aliases: &["国网庆城县供电公司", "庆城县供电公司", "庆城县公司"],
},
OrgUnit {
label: "国网正宁县供电公司",
code: "6240903",
aliases: &["国网正宁县供电公司", "正宁县供电公司", "正宁县公司"],
},
OrgUnit {
label: "国网镇原县供电公司",
code: "6240904",
aliases: &["国网镇原县供电公司", "镇原县供电公司", "镇原县公司"],
},
OrgUnit {
label: "国网环县供电公司",
code: "6240905",
aliases: &["国网环县供电公司", "环县供电公司", "环县公司"],
},
OrgUnit {
label: "国网华池县供电公司",
code: "6240906",
aliases: &["国网华池县供电公司", "华池县供电公司", "华池县公司"],
},
OrgUnit {
label: "国网合水县供电公司",
code: "6240907",
aliases: &["国网合水县供电公司", "合水县供电公司", "合水县公司"],
},
OrgUnit {
label: "国网宁县供电公司",
code: "6240908",
aliases: &["国网宁县供电公司", "宁县供电公司", "宁县公司"],
},
// ===== 武威供电公司 children (lv=3) =====
OrgUnit {
label: "国网古浪县供电公司",
code: "6241001",
aliases: &["国网古浪县供电公司", "古浪县供电公司", "古浪县公司"],
},
OrgUnit {
label: "国网凉州区供电公司",
code: "6241002",
aliases: &["国网凉州区供电公司", "凉州区供电公司", "凉州区公司"],
},
OrgUnit {
label: "国网民勤县供电公司",
code: "6241003",
aliases: &["国网民勤县供电公司", "民勤县供电公司", "民勤县公司"],
},
OrgUnit {
label: "国网天祝县供电公司",
code: "6241004",
aliases: &["国网天祝县供电公司", "天祝县供电公司", "天祝县公司"],
},
// ===== 酒泉供电公司 children (lv=3) =====
OrgUnit {
label: "国网酒泉市肃州区供电公司",
code: "6241101",
aliases: &["国网酒泉市肃州区供电公司", "酒泉市肃州区供电公司", "肃州区供电公司", "肃州区公司"],
},
OrgUnit {
label: "国网金塔县供电公司",
code: "6241102",
aliases: &["国网金塔县供电公司", "金塔县供电公司", "金塔县公司"],
},
OrgUnit {
label: "国网玉门市供电公司",
code: "6241103",
aliases: &["国网玉门市供电公司", "玉门市供电公司", "玉门市公司"],
},
OrgUnit {
label: "国网瓜州县供电公司",
code: "6241104",
aliases: &["国网瓜州县供电公司", "瓜州县供电公司", "瓜州县公司"],
},
OrgUnit {
label: "国网敦煌市供电公司",
code: "6241105",
aliases: &["国网敦煌市供电公司", "敦煌市供电公司", "敦煌市公司"],
},
OrgUnit {
label: "国网肃北县供电公司",
code: "6241106",
aliases: &["国网肃北县供电公司", "肃北县供电公司", "肃北县公司"],
},
OrgUnit {
label: "国网阿克塞县供电公司",
code: "6241107",
aliases: &["国网阿克塞县供电公司", "阿克塞县供电公司", "阿克塞县公司"],
},
// ===== 临夏供电公司 children (lv=3) =====
OrgUnit {
label: "临夏市城关营业班",
code: "6241201",
aliases: &["临夏市城关营业班"],
},
OrgUnit {
label: "国网临夏县供电公司",
code: "6241202",
aliases: &["国网临夏县供电公司", "临夏县供电公司", "临夏县公司"],
},
OrgUnit {
label: "国网东乡县供电公司",
code: "6241203",
aliases: &["国网东乡县供电公司", "东乡县供电公司", "东乡县公司"],
},
OrgUnit {
label: "国网和政县供电公司",
code: "6241204",
aliases: &["国网和政县供电公司", "和政县供电公司", "和政县公司"],
},
OrgUnit {
label: "国网广河县供电公司",
code: "6241205",
aliases: &["国网广河县供电公司", "广河县供电公司", "广河县公司"],
},
OrgUnit {
label: "国网积石山县供电公司",
code: "6241206",
aliases: &["国网积石山县供电公司", "积石山县供电公司", "积石山县公司"],
},
OrgUnit {
label: "国网康乐县供电公司",
code: "6241207",
aliases: &["国网康乐县供电公司", "康乐县供电公司", "康乐县公司"],
},
// ===== 甘南供电公司 children (lv=3) =====
OrgUnit {
label: "国网合作市供电公司",
code: "6241301",
aliases: &["国网合作市供电公司", "合作市供电公司", "合作市公司"],
},
OrgUnit {
label: "国网夏河县供电公司",
code: "6241302",
aliases: &["国网夏河县供电公司", "夏河县供电公司", "夏河县公司"],
},
OrgUnit {
label: "国网卓尼县供电公司",
code: "6241303",
aliases: &["国网卓尼县供电公司", "卓尼县供电公司", "卓尼县公司"],
},
OrgUnit {
label: "国网临潭县供电公司",
code: "6241304",
aliases: &["国网临潭县供电公司", "临潭县供电公司", "临潭县公司"],
},
OrgUnit {
label: "国网碌曲县供电公司",
code: "6241305",
aliases: &["国网碌曲县供电公司", "碌曲县供电公司", "碌曲县公司"],
},
OrgUnit {
label: "国网玛曲县供电公司",
code: "6241306",
aliases: &["国网玛曲县供电公司", "玛曲县供电公司", "玛曲县公司"],
},
OrgUnit {
label: "国网迭部县供电公司",
code: "6241307",
aliases: &["国网迭部县供电公司", "迭部县供电公司", "迭部县公司"],
},
OrgUnit {
label: "国网舟曲县供电公司",
code: "6241308",
aliases: &["国网舟曲县供电公司", "舟曲县供电公司", "舟曲县公司"],
},
];