@oqtopus-team/pi-qdash 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,211 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { QDashClient } from "@oqtopus-team/qdash-client";
3
+ import { Type } from "typebox";
4
+
5
+ import {
6
+ compareTimeseries,
7
+ csvInspectionText,
8
+ inspectExternalCsv,
9
+ loadExternalCsvSeries,
10
+ timeseriesComparisonText,
11
+ type CsvTimeFormat,
12
+ type ExternalCsvSpec,
13
+ type NumericTimeseries,
14
+ } from "../lib/timeseries-analysis.js";
15
+ import type { TimeseriesInvestigationContext } from "../lib/investigation-context.js";
16
+ import { toTextToolResult } from "../lib/results.js";
17
+ import { timeseriesPoints } from "../lib/timeseries-plot.js";
18
+
19
+ type ComparisonToolParams = {
20
+ profile?: string;
21
+ configPath?: string;
22
+ useEnv?: boolean;
23
+ chipId?: string;
24
+ startAt?: string;
25
+ endAt?: string;
26
+ withinHours?: number;
27
+ qdashSeries?: Array<{ parameter: string; qid?: string; tag?: string; label?: string; unit?: string; scale?: number; offset?: number }>;
28
+ csvSeries?: Array<ExternalCsvSpec & { timeFormat?: CsvTimeFormat }>;
29
+ resampleMinutes?: number;
30
+ smoothingWindowMinutes?: number;
31
+ detrend?: string;
32
+ normalize?: string;
33
+ maxInterpolationGapMinutes?: number;
34
+ periodSearch?: { minMinutes: number; maxMinutes: number; stepMinutes?: number };
35
+ includeAlignedData?: boolean;
36
+ useInvestigationContext?: boolean;
37
+ };
38
+
39
+ export type TimeseriesToolDependencies = {
40
+ makeClient: (params: { profile?: string; configPath?: string; useEnv?: boolean }) => Promise<QDashClient>;
41
+ defaultChipId: (client: QDashClient, chipId?: string) => Promise<string>;
42
+ investigationContext?: () => TimeseriesInvestigationContext | undefined;
43
+ };
44
+
45
+ export function registerTimeseriesComparisonTool(pi: ExtensionAPI, dependencies: TimeseriesToolDependencies): void {
46
+ pi.registerTool({
47
+ name: "qdash_inspect_timeseries_csv",
48
+ label: "QDash Inspect Timeseries CSV",
49
+ description: "Inspect a local CSV schema and numeric/time characteristics before mapping it into a QDash timeseries comparison. Read-only; never uploads the file.",
50
+ promptSnippet: "Inspect local CSV columns, numeric ranges, timestamps, and cadence before comparison",
51
+ promptGuidelines: [
52
+ "Use qdash_inspect_timeseries_csv before qdash_compare_timeseries when a local CSV schema, timestamp format, timezone, or numeric columns are not already known.",
53
+ "Do not infer the timezone of naive timestamps from qdash_inspect_timeseries_csv samples; ask for or supply an explicit timezone offset.",
54
+ ],
55
+ parameters: Type.Object({
56
+ path: Type.String({ description: "Local CSV path, absolute or relative to the current working directory." }),
57
+ delimiter: Type.Optional(Type.String({ description: "Single-character delimiter. Defaults to comma." })),
58
+ skipRows: Type.Optional(Type.Number({ description: "Rows before the header. Defaults to 0." })),
59
+ timeColumn: Type.Optional(Type.String({ description: "Optional timestamp column to validate." })),
60
+ timeFormat: Type.Optional(Type.String({ description: "iso, yyyy/M/d H:mm, yyyy-MM-dd HH:mm:ss, unix-seconds, or unix-milliseconds." })),
61
+ timezoneOffsetMinutes: Type.Optional(Type.Number({ description: "Required to validate timestamps without an explicit zone; JST is 540." })),
62
+ scanRows: Type.Optional(Type.Number({ description: "Rows to profile, from 1 to 100000. Defaults to 10000." })),
63
+ }),
64
+ async execute(_toolCallId, params: {
65
+ path: string;
66
+ delimiter?: string;
67
+ skipRows?: number;
68
+ timeColumn?: string;
69
+ timeFormat?: CsvTimeFormat;
70
+ timezoneOffsetMinutes?: number;
71
+ scanRows?: number;
72
+ }, _signal, _onUpdate, ctx) {
73
+ const inspection = inspectExternalCsv(params, ctx.cwd);
74
+ return toTextToolResult(csvInspectionText(inspection), inspection, {
75
+ tool: "qdash_inspect_timeseries_csv",
76
+ path: inspection.path,
77
+ });
78
+ },
79
+ });
80
+
81
+ pi.registerTool({
82
+ name: "qdash_compare_timeseries",
83
+ label: "QDash Compare Timeseries",
84
+ description: "Compare two or more QDash and/or local CSV numeric timeseries on a common time grid. Read-only; reports alignment, correlations, optional shared-period fits, phases, and statistical caveats without inferring causality.",
85
+ promptSnippet: "Compare generic QDash metrics and external CSV sensor timeseries",
86
+ promptGuidelines: [
87
+ "Use qdash_compare_timeseries when comparing multiple QDash metrics, targets, or local CSV sensor logs over time.",
88
+ "For qdash_compare_timeseries local CSV inputs, explicitly specify timestamp format and timezone offset when timestamps have no zone.",
89
+ "Treat qdash_compare_timeseries correlations and shared periods as evidence of association, not proof of causality.",
90
+ ],
91
+ parameters: Type.Object({
92
+ profile: Type.Optional(Type.String({ description: "QDash profile name. Defaults to env when QDASH_BASE_URL is set, otherwise 'default'." })),
93
+ configPath: Type.Optional(Type.String({ description: "Optional path to qdash config.ini." })),
94
+ useEnv: Type.Optional(Type.Boolean({ description: "Force QDASH_* environment variables instead of a profile." })),
95
+ chipId: Type.Optional(Type.String({ description: "Chip ID. Defaults to the active/default chip when omitted." })),
96
+ startAt: Type.Optional(Type.String({ description: "Optional common start timestamp as an ISO timestamp with timezone." })),
97
+ endAt: Type.Optional(Type.String({ description: "Optional common end timestamp as an ISO timestamp with timezone." })),
98
+ withinHours: Type.Optional(Type.Number({ description: "QDash lookback when startAt is omitted. Defaults to 168 hours." })),
99
+ useInvestigationContext: Type.Optional(Type.Boolean({ description: "Use missing series/window/transform defaults from the active investigation context. Defaults to true." })),
100
+ qdashSeries: Type.Optional(Type.Array(Type.Object({
101
+ parameter: Type.String({ description: "QDash task-result parameter name." }),
102
+ qid: Type.Optional(Type.String()),
103
+ tag: Type.Optional(Type.String()),
104
+ label: Type.Optional(Type.String({ description: "Unique display label. Defaults to parameter plus qid/returned series." })),
105
+ unit: Type.Optional(Type.String({ description: "Optional output unit override." })),
106
+ scale: Type.Optional(Type.Number({ description: "Multiply QDash values by this factor. Defaults to 1." })),
107
+ offset: Type.Optional(Type.Number({ description: "Add this value after scaling. Defaults to 0." })),
108
+ }), { maxItems: 12 })),
109
+ csvSeries: Type.Optional(Type.Array(Type.Object({
110
+ path: Type.String({ description: "Local CSV path, absolute or relative to the current working directory." }),
111
+ timeColumn: Type.String(),
112
+ valueColumns: Type.Array(Type.Object({
113
+ column: Type.String(),
114
+ label: Type.Optional(Type.String()),
115
+ unit: Type.Optional(Type.String()),
116
+ scale: Type.Optional(Type.Number()),
117
+ offset: Type.Optional(Type.Number()),
118
+ }), { minItems: 1, maxItems: 12 }),
119
+ timeFormat: Type.Optional(Type.String({ description: "iso, yyyy/M/d H:mm, yyyy-MM-dd HH:mm:ss, unix-seconds, or unix-milliseconds. Defaults to iso." })),
120
+ timezoneOffsetMinutes: Type.Optional(Type.Number({ description: "Required for timestamps without an explicit zone; for JST use 540." })),
121
+ delimiter: Type.Optional(Type.String({ description: "Single-character delimiter. Defaults to comma." })),
122
+ skipRows: Type.Optional(Type.Number({ description: "Rows to skip before the header. Defaults to 0." })),
123
+ filters: Type.Optional(Type.Record(Type.String(), Type.Array(Type.String()), { description: "Optional exact-match row filters keyed by column name." })),
124
+ }), { maxItems: 8 })),
125
+ resampleMinutes: Type.Optional(Type.Number({ description: "Common grid interval. Defaults to 1 minute." })),
126
+ smoothingWindowMinutes: Type.Optional(Type.Number({ description: "Centered moving-average window. Zero disables smoothing." })),
127
+ detrend: Type.Optional(Type.String({ description: "none or linear. Defaults to none." })),
128
+ normalize: Type.Optional(Type.String({ description: "none or zscore. Defaults to none." })),
129
+ maxInterpolationGapMinutes: Type.Optional(Type.Number({ description: "Do not interpolate across larger source gaps." })),
130
+ periodSearch: Type.Optional(Type.Object({
131
+ minMinutes: Type.Number(),
132
+ maxMinutes: Type.Number(),
133
+ stepMinutes: Type.Optional(Type.Number()),
134
+ })),
135
+ includeAlignedData: Type.Optional(Type.Boolean({ description: "Include transformed aligned rows in structured details. Defaults to false." })),
136
+ }),
137
+ async execute(_toolCallId, params: ComparisonToolParams, _signal, _onUpdate, ctx) {
138
+ const investigation = params.useInvestigationContext === false ? undefined : dependencies.investigationContext?.();
139
+ const analysis = investigation?.analysis;
140
+ const detrend = params.detrend ?? analysis?.detrend ?? "none";
141
+ const normalize = params.normalize ?? analysis?.normalize ?? "none";
142
+ if (detrend !== "none" && detrend !== "linear") throw new Error("detrend must be 'none' or 'linear'");
143
+ if (normalize !== "none" && normalize !== "zscore") throw new Error("normalize must be 'none' or 'zscore'");
144
+ const csvSpecs = (params.csvSeries ?? investigation?.csvSeries ?? []).map((spec) => ({
145
+ ...spec,
146
+ timezoneOffsetMinutes: spec.timezoneOffsetMinutes ?? investigation?.timezoneOffsetMinutes,
147
+ }));
148
+ const qdashSpecs = (params.qdashSeries ?? investigation?.qdashSeries ?? []).map((spec) => ({
149
+ ...spec,
150
+ tag: spec.tag ?? investigation?.tag,
151
+ }));
152
+ const csv = csvSpecs.flatMap((spec) => loadExternalCsvSeries(spec, ctx.cwd));
153
+ const qdash: NumericTimeseries[] = [];
154
+ let resolvedStartAt = params.startAt ?? investigation?.startAt;
155
+ let resolvedEndAt = params.endAt ?? investigation?.endAt;
156
+ if (qdashSpecs.length > 0) {
157
+ resolvedEndAt = resolvedEndAt ?? new Date().toISOString();
158
+ resolvedStartAt = resolvedStartAt ?? new Date(Date.parse(resolvedEndAt) - (params.withinHours ?? 168) * 3_600_000).toISOString();
159
+ const client = await dependencies.makeClient(params);
160
+ const chipId = await dependencies.defaultChipId(client, params.chipId);
161
+ const fetched = await Promise.all(qdashSpecs.map(async (spec) => {
162
+ const data = await client.getTaskResultsTimeseries({
163
+ chipId,
164
+ parameter: spec.parameter,
165
+ qid: spec.qid,
166
+ tag: spec.tag,
167
+ startAt: resolvedStartAt!,
168
+ endAt: resolvedEndAt!,
169
+ });
170
+ return { spec, points: timeseriesPoints(data) };
171
+ }));
172
+ for (const { spec, points } of fetched) {
173
+ const groups = new Map<string, typeof points>();
174
+ for (const point of points) {
175
+ if (!point.at) continue;
176
+ const group = spec.qid ?? point.series;
177
+ groups.set(group, [...(groups.get(group) ?? []), point]);
178
+ }
179
+ for (const [group, values] of groups) {
180
+ const suffix = spec.qid ? ` q${spec.qid}` : groups.size > 1 ? ` ${group}` : "";
181
+ qdash.push({
182
+ label: spec.label ? groups.size > 1 ? `${spec.label} ${group}` : spec.label : `${spec.parameter}${suffix}`,
183
+ unit: spec.unit ?? values.find((point) => point.unit)?.unit,
184
+ source: "qdash",
185
+ points: values.map((point) => ({
186
+ at: point.at!,
187
+ value: point.value * (spec.scale ?? 1) + (spec.offset ?? 0),
188
+ })),
189
+ });
190
+ }
191
+ }
192
+ }
193
+ const comparison = compareTimeseries([...qdash, ...csv], {
194
+ startAt: resolvedStartAt,
195
+ endAt: resolvedEndAt,
196
+ resampleMinutes: params.resampleMinutes ?? analysis?.resampleMinutes,
197
+ smoothingWindowMinutes: params.smoothingWindowMinutes ?? analysis?.smoothingWindowMinutes,
198
+ detrend,
199
+ normalize,
200
+ maxInterpolationGapMinutes: params.maxInterpolationGapMinutes ?? analysis?.maxInterpolationGapMinutes,
201
+ periodSearch: params.periodSearch ?? analysis?.periodSearch,
202
+ includeAlignedData: params.includeAlignedData,
203
+ });
204
+ return toTextToolResult(timeseriesComparisonText(comparison), comparison, {
205
+ tool: "qdash_compare_timeseries",
206
+ seriesCount: comparison.series.length,
207
+ investigation: investigation?.name,
208
+ });
209
+ },
210
+ });
211
+ }
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@oqtopus-team/pi-qdash",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Pi extension for querying QDash via @oqtopus-team/qdash-client",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "author": "OQTOPUS Team <oqtopus-team@googlegroups.com>",
8
+ "engines": {
9
+ "node": ">=20.0.0"
10
+ },
8
11
  "repository": {
9
12
  "type": "git",
10
13
  "url": "git+https://github.com/oqtopus-team/pi-qdash.git"
@@ -24,7 +27,7 @@
24
27
  "access": "public"
25
28
  },
26
29
  "dependencies": {
27
- "@oqtopus-team/qdash-client": "^0.1.3"
30
+ "@oqtopus-team/qdash-client": "^1.9.11"
28
31
  },
29
32
  "peerDependencies": {
30
33
  "@earendil-works/pi-coding-agent": "*",
@@ -39,7 +42,8 @@
39
42
  ]
40
43
  },
41
44
  "scripts": {
42
- "check": "tsc --noEmit"
45
+ "check": "tsc --noEmit",
46
+ "test": "rm -rf .test-dist && tsc --noEmit false --outDir .test-dist && node --test tests/*.test.mjs; status=$?; rm -rf .test-dist; exit $status"
43
47
  },
44
48
  "devDependencies": {
45
49
  "@earendil-works/pi-coding-agent": "^0.74.0",
@@ -15,7 +15,7 @@ Use the pi-qdash tools instead of scraping the UI or hand-writing auth headers.
15
15
  - `qdash_get_chip_metrics`, `qdash_list_chip_qubits`, `qdash_list_chip_couplings`
16
16
  - `qdash_list_cryostats`, `qdash_list_cooldowns`
17
17
  - `qdash_get_cooldown_wiring`, `qdash_wiring_insights`, `qdash_list_cooldown_wiring_events`
18
- - `qdash_get_timeseries`, `qdash_plot_timeseries`
18
+ - `qdash_get_timeseries`, `qdash_plot_timeseries`, `qdash_inspect_timeseries_csv`, `qdash_compare_timeseries`
19
19
  - `qdash_list_task_results`, `qdash_get_task_result`
20
20
  - `qdash_list_issues`
21
21
  - `qdash_list_flows`, `qdash_get_flow`
@@ -24,6 +24,7 @@ Use the pi-qdash tools instead of scraping the UI or hand-writing auth headers.
24
24
  - `qdash_list_forum_posts`, `qdash_get_forum_post`, `qdash_list_forum_replies`
25
25
  - `qdash_get_figure`, `qdash_get_task_figures`, `qdash_recent_calibration_figure`, `qdash_analyze_figure_json`, `qdash_build_qcal_evidence`
26
26
  - `qdash_create_forum_evidence_reply` for confirmed evidence curation replies with visible task figures
27
+ - `qdash_preview_forum_image_reply`, then `qdash_create_forum_image_reply` for confirmed uploads of locally generated analysis images
27
28
  3. Use harness overview tools for status and triage:
28
29
  - `qdash_investigate` for natural-language requests to investigate or compare recent calibration, target history, figures, failures, issues, and Forum context
29
30
  - `qdash_compare_calibration` for read-only before/after comparison of repeated target calibration results
@@ -64,6 +65,8 @@ Use these commands to make pi behave like a QDash-specific harness with persiste
64
65
  /qdash-use-chip [chip_id]
65
66
  /qdash-use-target qid <qid> | coupling <coupling_id>
66
67
  /qdash-use-agent-session <session_id>
68
+ /qdash-investigation-setup [json]
69
+ /qdash-clear-investigation
67
70
  /qdash-context
68
71
  /qdash-dashboard [limit]
69
72
  /qdash-wiring-insights
@@ -73,6 +76,43 @@ Use these commands to make pi behave like a QDash-specific harness with persiste
73
76
 
74
77
  Prefer the current context when the user has already selected a profile/chip/session. Tools use that context when parameters are omitted.
75
78
 
79
+ ## Cooldown-aware investigations
80
+
81
+ Treat cooldown identity as mandatory investigation context whenever comparing calibration results, coherence metrics, drift, failures, or Forum evidence across time:
82
+
83
+ 1. Call `qdash_list_cooldowns` for the chip early in the investigation and record the relevant `cooldown_id`, `started_at`, and `ended_at` boundaries.
84
+ 2. Assign every result or Forum observation used in a comparison to a cooldown. Prefer an explicit `cooldown_id`; when it is absent, infer membership only from the chip association and timestamp boundaries, and label it as inferred.
85
+ 3. Do not describe values from different cooldowns as one continuous degradation, recovery, or before/after trend. Partition the evidence by cooldown first. Cross-cooldown comparison is allowed only when it is explicitly identified as such.
86
+ 4. State plainly whether the compared evidence comes from the same cooldown, different cooldowns, or an unresolved cooldown context. Include the cooldown IDs in the report and in Forum evidence replies when relevant.
87
+ 5. Same cooldown does not guarantee identical measurement conditions. If cabling, attenuation, instruments, or wiring may have changed, inspect `qdash_get_cooldown_wiring` with history or `qdash_list_cooldown_wiring_events`, then distinguish a cooldown boundary from an in-cooldown wiring change.
88
+ 6. Do not treat a temporary disconnect/reconnect as a new cooldown unless QDash records a new cooldown. Describe it as an in-cooldown wiring event and report whether the wiring was nominally restored.
89
+ 7. Prefer same-cooldown, temporally adjacent measurements for physical interpretation. Older-cooldown evidence may provide context but must not be used to explain a current-cooldown anomaly without an explicit caveat.
90
+
91
+ Before publishing investigated evidence, verify that every date-based claim respects these rules.
92
+
93
+ ## Generic timeseries comparison
94
+
95
+ Use `qdash_compare_timeseries` when the investigation needs to align multiple
96
+ QDash metrics/targets, local CSV sensor logs, or both. For repeated calls, use
97
+ `/qdash-investigation-setup` to store the arbitrary series mappings, window,
98
+ timezone, and transform defaults; explicit tool arguments override the preset.
99
+ Do not assume a specific
100
+ CSV layout, metric name, target, unit, timestamp format, or timezone:
101
+
102
+ 1. Inspect unknown CSV headers, numeric coverage, and optional time cadence with
103
+ `qdash_inspect_timeseries_csv` first.
104
+ 2. Supply `timeColumn`, one or more `valueColumns`, and optional exact-match
105
+ `filters`; use `scale`/`offset` only for explicit unit conversion.
106
+ 3. For timestamps without a zone, require an explicit
107
+ `timezoneOffsetMinutes` (JST is `540`) rather than relying on the host clock.
108
+ 4. Start with raw/no-transform comparison, then state any smoothing,
109
+ interpolation, detrending, normalization, and period-search settings.
110
+ 5. Report overlap duration, source point counts, aligned point count, number of
111
+ observed cycles, correlations, phases, and explained fractions together.
112
+ 6. Treat correlation and shared periodicity as evidence of association. Do not
113
+ claim direct causality without intervention, longer observation, or evidence
114
+ that excludes a common driver.
115
+
76
116
  For cryostat wiring requests, start with `qdash_get_cooldown_wiring`. Pass `cooldownId` when the user names one; otherwise pass `cryoId`, or omit both to resolve the active/newest cooldown from the current/default chip. Prefer the compact `wiring.markdown`; request raw blocks only when structural or embedded BlockNote data is necessary, and request history when investigating wiring changes. Set `includeInsights` or call `qdash_wiring_insights` when the user asks what is characteristic, unusual, or worth checking in attenuation; summarize control/readout totals and affected qubits/MUXes instead of dumping the raw table.
77
117
 
78
118
  ## Configuration
@@ -90,14 +130,15 @@ If `QDASH_BASE_URL` is set and no profile is specified, the tools default to env
90
130
 
91
131
  When the user wants to preserve an investigated observation in QDash forum/notes:
92
132
 
93
- 1. Inspect the task result, task figure, timeseries/history, and related forum context first.
94
- 2. Summarize the observation as evidence, not as an automatic calibration decision.
133
+ 1. Inspect the task result, task figure, timeseries/history, related Forum context, and cooldown membership first.
134
+ 2. Summarize the observation as evidence, not as an automatic calibration decision. State whether compared observations are from the same cooldown, and separate in-cooldown wiring changes from cooldown boundaries.
95
135
  3. Prefer replying to an existing target/coupling forum thread when one exists.
96
136
  4. Use `qdash_create_forum_evidence_reply` for task-result evidence so figures are embedded as QDash UI image blocks (`/api/executions/figure?path=...`) and visible in the forum.
97
- 5. Include task and execution QDash Web URLs in the reply.
98
- 6. When mentioning history or trends, include links to representative historical task results.
99
- 7. Mark agent-authored evidence with a footer such as `— 🤖 by pi-qdash`.
100
- 8. Ask for confirmation before creating/updating the forum post.
137
+ 5. For locally generated analysis images, call `qdash_preview_forum_image_reply` first, show the exact paths and reply text, then use `qdash_create_forum_image_reply` only after explicit confirmation. Supported images are PNG, JPEG, GIF, and WebP up to 5 MB each.
138
+ 6. Include task and execution QDash Web URLs in the reply when applicable.
139
+ 7. When mentioning history or trends, include links to representative historical task results.
140
+ 8. Mark agent-authored evidence with a footer such as `— 🤖 by pi-qdash`.
141
+ 9. Ask for confirmation before uploading images or creating/updating the forum post.
101
142
 
102
143
  ## Safety
103
144
 
@@ -32,7 +32,8 @@ This skill defines how pi should behave as a conservative, confirmation-gated ca
32
32
 
33
33
  - Work one target at a time: one qubit or one coupling per action/session when possible.
34
34
  - Diagnose before changing parameters.
35
- - Prefer read-only inspection first: dashboard, failed task results, history, figures, forum posts, and task knowledge when available.
35
+ - Establish cooldown identity before interpreting history: partition evidence by `cooldown_id`, never present different cooldowns as one continuous trend, and distinguish cooldown boundaries from in-cooldown wiring changes.
36
+ - Prefer read-only inspection first: dashboard, cooldowns and wiring events, failed task results, history, figures, forum posts, and task knowledge when available.
36
37
  - Use narrow agent session policies with the smallest allowed task/action/parameter scope.
37
38
  - Do not commit or apply parameter candidates until a validation task succeeds and the user explicitly confirms.
38
39
  - Avoid blind parameter sweeps. If two conservative probes fail, step back to a diagnostic task.
@@ -42,6 +43,8 @@ This skill defines how pi should behave as a conservative, confirmation-gated ca
42
43
 
43
44
  1. Establish context:
44
45
  - profile and chip
46
+ - active/relevant `cooldown_id` and its time boundaries via `qdash_list_cooldowns`
47
+ - any in-cooldown wiring changes relevant to the target
45
48
  - failed task(s)
46
49
  - qid/coupling_id
47
50
  - recent executions and open forum/issue context
@@ -0,0 +1,155 @@
1
+ ---
2
+ name: qdash-timeseries-diagnosis
3
+ description: Diagnose common-mode drift, shared periodicity, phase relationships, and possible environmental coupling across arbitrary QDash metrics and external sensor CSV logs. Use when calibration metrics oscillate, drift together, appear delayed or anti-correlated, or may be influenced by cryostat/environmental cycles.
4
+ ---
5
+
6
+ # QDash Timeseries Diagnosis
7
+
8
+ Use this skill for evidence-based multi-timeseries investigation. Keep the
9
+ measurement schema and physical interpretation separate: the comparison tool
10
+ extracts mechanical evidence, while this skill guides conclusions and next
11
+ checks.
12
+
13
+ ## Principles
14
+
15
+ - Do not hard-code metric names, targets, sensors, units, CSV columns, periods,
16
+ or timezone assumptions.
17
+ - Preserve both raw and transformed results. A visually clean transformed plot
18
+ must not replace the raw evidence.
19
+ - Correlation, shared periodicity, and phase alignment establish association,
20
+ not causal direction.
21
+ - Consider a shared driver when multiple targets and an environmental sensor
22
+ move together. The sensor itself may be a proxy for an unmeasured actuator,
23
+ vibration, electrical noise, magnetic noise, or control cycle.
24
+ - Count observed cycles, not raw sample points, when judging periodic evidence.
25
+ - Resolve cooldown boundaries before interpreting drift or shared periodicity. Analyze each cooldown separately by default; never smooth, interpolate, correlate, or fit a period across a cooldown boundary unless an explicit cross-cooldown comparison is intended and clearly reported.
26
+
27
+ ## Workflow
28
+
29
+ ### 1. Fix the investigation scope
30
+
31
+ Record the reusable scope with `/qdash-investigation-setup` when multiple
32
+ comparison passes are expected. The JSON context accepts arbitrary QDash
33
+ series, CSV mappings, window/timezone, and transform defaults. Record:
34
+
35
+ - QDash profile and chip
36
+ - cooldown IDs and start/end boundaries from `qdash_list_cooldowns`
37
+ - any in-cooldown wiring events that overlap the window
38
+ - metrics and target IDs
39
+ - tags
40
+ - start/end timestamps
41
+ - timezone for every source
42
+ - expected cadence and units
43
+ - local CSV path, time column, value columns, and any row filters
44
+
45
+ For timestamps without an explicit zone, require a declared offset. Never infer
46
+ a timezone from the host machine.
47
+
48
+ ### 2. Inspect source integrity
49
+
50
+ Before comparing, use `qdash_inspect_timeseries_csv` for local files whose
51
+ schema or time encoding is not already established. Use `qdash_list_cooldowns`
52
+ to split the requested window at cooldown boundaries, and inspect cooldown
53
+ wiring events when hardware conditions may have changed. Then:
54
+
55
+ - verify first/last timestamps and overlap
56
+ - check source point counts and cadence
57
+ - identify duplicate timestamps, missing intervals, and long gaps
58
+ - verify scale and offset conversions
59
+ - ensure labels are unique
60
+
61
+ Do not bridge long outages. Set `maxInterpolationGapMinutes` explicitly when the
62
+ default is unsuitable for the slowest source.
63
+
64
+ ### 3. Establish a raw baseline
65
+
66
+ Call `qdash_compare_timeseries` first with:
67
+
68
+ - no smoothing
69
+ - no detrending
70
+ - no normalization
71
+ - no period search unless a period was specified independently
72
+
73
+ Report ranges, units, overlap, and raw correlation. Different units are fine for
74
+ correlation, but do not visually overlay incomparable scales without separate
75
+ axes or normalization.
76
+
77
+ ### 4. Compare oscillatory components
78
+
79
+ When looking for common cycles, rerun with explicit settings such as:
80
+
81
+ - a smoothing window justified by source cadence
82
+ - `detrend: linear` when slow drift would dominate
83
+ - `normalize: zscore` for shape/phase comparison
84
+ - a period-search range chosen from prior evidence or a broad physically
85
+ plausible interval
86
+
87
+ Report all transform settings with the result. Compare:
88
+
89
+ - full transformed correlation
90
+ - selected shared period
91
+ - per-series periodic explained fraction
92
+ - pairwise phase differences
93
+ - observed cycle count
94
+
95
+ A near-0° phase difference indicates in-phase periodic components. A near-180°
96
+ difference indicates anti-phase components. Intermediate phase may represent a
97
+ delay, but phase alone does not identify response direction.
98
+
99
+ ### 5. Grade the evidence conservatively
100
+
101
+ Use qualitative language:
102
+
103
+ - **weak**: unstable period/phase, low explained fraction, or fewer than about
104
+ three observed cycles
105
+ - **suggestive**: similar periods with plausible phase but short observation or
106
+ substantial residual variation
107
+ - **strong association**: repeated common period and stable phase across
108
+ multiple targets/sensors, with substantial explained fractions
109
+ - **causal evidence**: requires intervention, independently controlled input,
110
+ repeated response, or evidence excluding plausible shared drivers
111
+
112
+ These are reasoning categories, not fixed acceptance thresholds. Explain which
113
+ facts support the chosen category.
114
+
115
+ ### 6. Test alternatives
116
+
117
+ For apparent environmental or cryostat coupling, distinguish:
118
+
119
+ 1. the measured sensor variable directly affecting the metric
120
+ 2. a controller/refrigerator cycle affecting both
121
+ 3. periodic measurement or scheduling artifacts
122
+ 4. timestamp offset or resampling artifacts
123
+ 5. target-local behavior coincidentally near the same frequency
124
+
125
+ Check additional targets in different control/readout groups and, when
126
+ available, actuator state, compressor/pulse-tube, vibration, rack temperature,
127
+ magnetic field, pressure, and control-system logs.
128
+
129
+ ### 7. Recommend discriminating measurements
130
+
131
+ Prefer measurements that separate hypotheses:
132
+
133
+ - extend observation to cover at least 8–10 candidate cycles
134
+ - preserve the same cadence and synchronized clocks
135
+ - record actuator/controller state in addition to sensor output
136
+ - test multiple targets and control/readout groups
137
+ - perform a safe controlled setpoint or operating-state change when authorized
138
+ - compare lag/phase stability across independent runs
139
+
140
+ Do not initiate operational changes without an explicit plan and user
141
+ confirmation.
142
+
143
+ ## Reporting template
144
+
145
+ Include:
146
+
147
+ 1. sources and exact time window
148
+ 2. timezone and unit conversions
149
+ 3. raw-data integrity and overlap
150
+ 4. transformation settings
151
+ 5. raw/transformed correlations
152
+ 6. shared period, phase, explained fraction, and observed cycles
153
+ 7. strongest association statement
154
+ 8. causal alternatives still open
155
+ 9. next measurement that best discriminates those alternatives
@@ -17,7 +17,8 @@ Use this skill when investigating coupling-level calibration quality, especially
17
17
 
18
18
  - Treat `completed` as execution status, not proof of calibration quality.
19
19
  - Diagnose from evidence before suggesting operational actions.
20
- - Prefer read-only tools first: `qdash_target_report`, `qdash_list_task_results`, `qdash_get_task_result`, `qdash_analyze_figure_json`, and `qdash_get_task_figures`.
20
+ - Resolve cooldown membership before comparing calibrations or coherence limits. Keep different cooldowns separate, and distinguish a cooldown boundary from an in-cooldown wiring change.
21
+ - Prefer read-only tools first: `qdash_list_cooldowns`, `qdash_target_report`, `qdash_list_task_results`, `qdash_get_task_result`, `qdash_analyze_figure_json`, and `qdash_get_task_figures`.
21
22
  - Do not rerun RB first when validation quality is suspicious. Walk back through prerequisites.
22
23
  - Do not commit or apply candidates without explicit user confirmation, and only after downstream validation.
23
24
 
@@ -25,8 +26,8 @@ Use this skill when investigating coupling-level calibration quality, especially
25
26
 
26
27
  For a coupling `cA-B`:
27
28
 
28
- 1. Build context with `qdash_target_report` and recent task results for the coupling.
29
- 2. Inspect the latest tasks in this chain:
29
+ 1. Build context with `qdash_list_cooldowns`, `qdash_target_report`, and recent task results for the coupling. Record the cooldown ID for each result and inspect wiring events when measurement conditions may have changed.
30
+ 2. Inspect the latest same-cooldown tasks in this chain by default:
30
31
  - `CheckCrossResonance`
31
32
  - `CreateZX90`
32
33
  - `CheckZX90`