teamcraft/mcp/tools/
insight_tools.rs1use super::*;
4
5pub(super) async fn bom_health(st: &AppState, me: &Actor, a: &Args<'_>) -> AppResult<Value> {
6 let rev = revision_arg(st, me, a).await?;
7 let rule = a.rule()?.unwrap_or_else(|| default_rule(&rev));
8 Ok(json!(insights::bom_health(&st.db, me, rev.id, rule).await?))
9}
10
11pub fn group_duplicates(rows: &[insights::DuplicateFile]) -> Vec<Value> {
13 let mut order: Vec<&str> = Vec::new();
14 let mut groups: BTreeMap<&str, Vec<&insights::DuplicateFile>> = BTreeMap::new();
15 for r in rows {
16 let entry = groups.entry(r.sha256.as_str()).or_default();
17 if entry.is_empty() {
18 order.push(r.sha256.as_str());
19 }
20 entry.push(r);
21 }
22 order
23 .into_iter()
24 .map(|sha| {
25 let copies = &groups[sha];
26 json!({
27 "sha256": sha,
28 "size_bytes": copies[0].size_bytes,
29 "copies": copies.iter().map(|c| json!({
30 "item": c.item_number,
31 "revision": c.revision_code,
32 "state": c.revision_state,
33 "filename": c.filename,
34 })).collect::<Vec<_>>(),
35 })
36 })
37 .collect()
38}
39
40pub(super) async fn find_duplicate_files(
41 st: &AppState,
42 me: &Actor,
43 a: &Args<'_>,
44) -> AppResult<Value> {
45 let limit = a.limit("limit", 100, 1000)?;
46 let revision = match a.opt_str("item")? {
47 Some(number) => {
48 Some(revision_at(st, me, &number, a.opt_str("revision")?.as_deref()).await?)
49 }
50 None => None,
51 };
52 let rows =
53 insights::duplicate_files(&st.db, me, revision.as_ref().map(|r| r.id), limit).await?;
54 Ok(json!({
55 "scope": revision.as_ref().map(revisions::label),
56 "groups": group_duplicates(&rows),
57 "truncated": rows.len() as i64 >= limit,
58 }))
59}
60
61pub(super) async fn find_similar_parts(
62 st: &AppState,
63 me: &Actor,
64 a: &Args<'_>,
65) -> AppResult<Value> {
66 let (number, code) = parse_item_ref(&a.req_str("item")?);
67 let code = match a.opt_str("revision")? {
68 Some(c) => Some(c),
69 None => code,
70 };
71 let rev = revision_at(st, me, &number, code.as_deref()).await?;
72 let options = similar::Options {
73 limit: a.limit("limit", 10, similar::MAX_LIMIT)?,
74 ignore_scale: a.flag("ignore_scale", false)?,
75 };
76 let file = a.opt_str("file")?;
77 let report = similar::for_revision(
78 st,
79 me,
80 &rev,
81 file.as_deref(),
82 options,
83 std::time::Duration::from_secs(60),
84 )
85 .await?;
86 Ok(json!({
87 "source": revisions::label(&rev),
88 "status": report.status,
89 "message": report.message,
90 "file": report.source.as_ref().map(|s| &s.filename),
91 "signature": report.signature,
92 "ignore_scale": report.ignore_scale,
93 "similar": report.candidates.iter().map(|c| json!({
94 "item": c.item_number,
95 "revision": c.revision_code,
96 "name": c.name,
97 "type": c.item_type,
98 "state": c.state,
99 "project": c.project_code,
100 "file": c.filename,
101 "score": c.score,
102 "shape": c.shape,
103 "size": c.size,
104 "identical": c.identical,
105 "explanation": c.explanation,
106 })).collect::<Vec<_>>(),
107 }))
108}