1use super::*;
4
5pub(super) struct Prompt {
6 pub(super) name: &'static str,
7 title: &'static str,
8 description: &'static str,
9 arguments: &'static [(&'static str, &'static str, bool)],
11}
12
13pub(super) const PROMPTS: &[Prompt] = &[
14 Prompt {
15 name: "release_notes",
16 title: "Release notes",
17 description: "Release notes for a revision: what changed compared with the previous one.",
18 arguments: &[
19 ("item", "Item number, e.g. ASM-000003", true),
20 ("revision", "Revision code (default: latest)", false),
21 ],
22 },
23 Prompt {
24 name: "review_change",
25 title: "Review a change order",
26 description: "Prepares the review of a change order: scope, risks, open questions.",
27 arguments: &[("change", "Change number, e.g. ECO-000004", true)],
28 },
29 Prompt {
30 name: "release_check",
31 title: "Release check",
32 description: "Checks whether a revision and its structure are ready to be released.",
33 arguments: &[
34 ("item", "Item number", true),
35 ("revision", "Revision code (default: latest)", false),
36 ],
37 },
38 Prompt {
39 name: "worklist_triage",
40 title: "Worklist triage",
41 description: "Orders what is waiting for me into a short to-do list.",
42 arguments: &[],
43 },
44 Prompt {
45 name: "triage_requests",
46 title: "Prepare request triage",
47 description:
48 "Summarises the open change requests assigned to me (and new unassigned ones) so I \
49 can triage them; the decisions stay mine.",
50 arguments: &[],
51 },
52 Prompt {
53 name: "simulation_status",
54 title: "Simulation status",
55 description:
56 "Whether the simulations of a part or assembly – or one simulation – still match \
57 their bases, and how the results look.",
58 arguments: &[
59 (
60 "item",
61 "Part, assembly or simulation number, e.g. SIM-000003",
62 true,
63 ),
64 ("revision", "Revision code (default: latest)", false),
65 ],
66 },
67];
68
69pub fn prompts() -> Vec<Value> {
71 PROMPTS
72 .iter()
73 .map(|p| {
74 json!({
75 "name": p.name,
76 "title": p.title,
77 "description": p.description,
78 "arguments": p.arguments.iter().map(|(name, description, required)| json!({
79 "name": name, "description": description, "required": required,
80 })).collect::<Vec<_>>(),
81 })
82 })
83 .collect()
84}
85
86pub fn prompt(name: &str, args: &Value) -> Result<Value, String> {
88 let p = PROMPTS
89 .iter()
90 .find(|p| p.name == name)
91 .ok_or_else(|| format!("unknown prompt `{name}`"))?;
92 let arg = |key: &str| -> Option<String> {
93 args.get(key)
94 .and_then(Value::as_str)
95 .map(str::trim)
96 .filter(|s| !s.is_empty())
97 .map(str::to_string)
98 };
99 for (key, _, required) in p.arguments {
100 if *required && arg(key).is_none() {
101 return Err(format!("prompt `{name}` needs the argument `{key}`"));
102 }
103 }
104 let revision = arg("revision").unwrap_or_else(|| "latest".into());
105 let text = match name {
106 "release_notes" => {
107 let item = arg("item").unwrap_or_default();
108 format!(
109 "Write release notes for {item} revision {revision} in TeamCraft.\n\
110 1. Call compare_revisions with item={item} and to={revision} (it compares with the revision before).\n\
111 2. Call get_revision for {item}/{revision} for the description and the change orders it belongs to; \
112 if there is one, call get_change for its title and reason.\n\
113 Produce: a one-line summary; then short sections Attributes, Files (added, removed, new content) and BOM \
114 (added, removed, quantity changes), leaving out empty sections; then Open points (e.g. unreleased children, \
115 missing mass). Quote item numbers and revisions exactly. Do not invent reasons the data does not give."
116 )
117 }
118 "review_change" => {
119 let change = arg("change").unwrap_or_default();
120 format!(
121 "Help me review change order {change} in TeamCraft.\n\
122 1. Call get_change with change={change}.\n\
123 2. For each revision in it, call compare_revisions (item, to=its revision) to see what it changes; \
124 for assemblies also call bom_health with rule=released.\n\
125 3. Call suggest_reviewers with change={change} to see who else knows the affected items.\n\
126 Produce a concise review for an engineer: Scope (what is released, in one line each); Risks (readiness \
127 blockers and warnings, dependents from the impact analysis that are not in the change, check-outs, \
128 missing files or mass); Questions for the author; people who should also look at it (only from \
129 suggest_reviewers, with its reason; it never assigns them); a suggestion whether it looks ready. The decision \
130 (approve or reject) is mine – you cannot and should not make it."
131 )
132 }
133 "release_check" => {
134 let item = arg("item").unwrap_or_default();
135 format!(
136 "Check whether {item} revision {revision} is ready to be released.\n\
137 1. Call get_revision for {item}/{revision}.\n\
138 2. Call bom_health with item={item}, revision={revision}, rule=released.\n\
139 3. Call find_duplicate_files with item={item}, revision={revision}.\n\
140 4. If it is a new part (revision A), call find_similar_parts with item={item}, revision={revision} for \
141 existing parts of nearly the same shape (score 90 or more).\n\
142 Produce a checklist: Blocking (must be fixed or released together: unreleased or unresolved BOM lines and \
143 references, check-outs) and Warnings (missing mass or files, obsolete parts, duplicate files, near-duplicate \
144 existing parts, open \
145 checks). End with the items a change order would need. Offer to draft it with create_change_draft, but \
146 only create it if I say so."
147 )
148 }
149 "worklist_triage" => "Triage my TeamCraft worklist.\n\
150 Call my_worklist. Produce a short ordered to-do list: reviews waiting for my decision first (oldest \
151 first), then open impacts to check, then check-outs that have been open long (remind me to check in or \
152 cancel), then my own change orders that are drafts or were rejected. One line per entry with the item or \
153 change number and the next action. Say plainly if there is nothing to do."
154 .to_string(),
155 "triage_requests" => "Prepare the triage of my TeamCraft change requests.\n\
156 1. Call list_change_requests with scope=assigned and status=open.\n\
157 2. Call list_change_requests with status=new for requests nobody has looked at yet.\n\
158 3. For each request found (at most 10, highest priority and oldest due date first), call \
159 get_change_request; for an affected item that looks central, call get_item for its state, open \
160 change orders and simulations.\n\
161 Produce one short block per request: number and title; the problem in one sentence; affected items \
162 with their latest state; what the discussion already settled and what is open; duplicates or \
163 related requests; a suggested next step (ask the requester, check a simulation, candidate for \
164 accepting, deferring or rejecting – with the reason). You cannot triage, accept, defer or reject: \
165 those decisions are mine and are made in TeamCraft. Say plainly if there is nothing to triage."
166 .to_string(),
167 "simulation_status" => {
168 let item = arg("item").unwrap_or_default();
169 format!(
170 "Report the simulation status of {item} revision {revision} in TeamCraft.\n\
171 1. Call get_item with item={item}. If it is a simulation, call get_simulation with item={item} \
172 and revision={revision}, and compare_simulation_results with item={item} and to={revision} when \
173 there is an earlier revision.\n\
174 2. Otherwise call list_simulations with item={item}, then get_simulation for each simulation \
175 that is out of date or has failed results.\n\
176 Produce: a table of the simulations (number, revision, state, up to date or the out-of-date \
177 reason, results passed/failed); then the failed values with value, limit and load case; then \
178 what changed in the results between the revisions, if compared; then a recommendation which \
179 simulations need to be re-run or re-checked. Do not invent values the data does not give."
180 )
181 }
182 _ => unreachable!("prompt names are checked above"),
183 };
184 Ok(json!({
185 "description": p.description,
186 "messages": [{ "role": "user", "content": { "type": "text", "text": text } }],
187 }))
188}