tix/cli/cli_analytics.rs
1#[allow(clippy::wildcard_imports)]
2use super::*;
3
4/// The `tix analytics` subcommands (mission `burndown-scope-analytics-01M3C9J1`, WP05).
5///
6/// A thin adapter over the shared analytics DTOs. The daily fold, the unit conversion
7/// and the projection live in `crate::analytics`, and the versioned chart document is
8/// WP04's (`crate::commands::export::chart_document`), which every verb here calls rather
9/// than re-composing. This enum owns only the spelling of the surface: the scope, the
10/// period and the mode/unit are always explicit, and no flag here can change an issue, a
11/// cycle date, a milestone or a snapshot.
12#[derive(Debug, Clone, Subcommand)]
13pub enum AnalyticsCommand {
14 /// Print the daily remaining/completed series for one cycle or milestone
15 ///
16 /// A tab-separated table with a `date` column followed by the point's own fields —
17 /// `scope_total`, `remaining`, `completed`, `scope_added`, `completions` — and the
18 /// day's `coverage`. A value the record cannot support is `-`, never a zero, and a
19 /// day with no recorded history is `unknown` or `coverage_limited`. The scope,
20 /// period, mode, unit, formula version and coverage tally print above the header
21 /// row, and the findings render to stderr with their remedies.
22 Burndown {
23 #[command(flatten)]
24 args: AnalyticsScopeArgs,
25 },
26 /// Print the read-only end-date forecast over one cycle or milestone
27 ///
28 /// The planning end date is the scope's own (`--to` overrides it) and `--as-of` is
29 /// the caller's day, never a clock. A forecast the record cannot answer prints its
30 /// `status` (`insufficient_data` or `unknown`) with `-` for every value it could not
31 /// derive — no completion date is ever invented, and no planning date or issue
32 /// changes.
33 Projection {
34 #[command(flatten)]
35 args: AnalyticsScopeArgs,
36 /// The forecast origin, a `YYYY-MM-DD` calendar date
37 #[arg(long, value_name = "YYYY-MM-DD")]
38 as_of: String,
39 },
40 /// Write the deterministic local chart document (WP04's target)
41 ///
42 /// The document goes to stdout, or to `--output` through the atomic export writer the
43 /// other local targets use. `--mode` is required: the document states the mode it was
44 /// folded in, so a default would be a guess about which arithmetic produced the
45 /// numbers. `--as-of` asks the same document for a forecast. The remote
46 /// `gitlab`/`linear` namespaces are never consulted and cannot collide with a chart
47 /// document.
48 Export {
49 #[command(flatten)]
50 args: AnalyticsScopeArgs,
51 /// Ask the document for a forecast at this origin, a `YYYY-MM-DD` calendar date
52 #[arg(long, value_name = "YYYY-MM-DD")]
53 as_of: Option<String>,
54 /// Write the document to this path instead of stdout
55 #[arg(long, value_name = "PATH")]
56 output: Option<String>,
57 },
58}
59
60/// The scope, period, mode and unit flags every `tix analytics` verb shares.
61///
62/// The period is the scope's own planning data unless a bound is named here, and the mode
63/// is explicit rather than inferred: an estimate valuation is only drawn through the
64/// project's configured unit conversion, so a mode the record cannot support is refused
65/// instead of silently falling back to a count.
66#[derive(Debug, Clone, Args)]
67pub struct AnalyticsScopeArgs {
68 /// Scope kind: `cycle` or `milestone`
69 #[arg(long, value_name = "cycle|milestone")]
70 pub scope: String,
71 /// The scope's planning identity: a cycle name or stable id, or a milestone name
72 #[arg(long, value_name = "ID")]
73 pub id: String,
74 /// The series' inclusive start, `YYYY-MM-DD`; absent means the scope's own start
75 #[arg(long, value_name = "YYYY-MM-DD")]
76 pub from: Option<String>,
77 /// The series' inclusive end, `YYYY-MM-DD`; absent means the scope's own planning end
78 #[arg(long, value_name = "YYYY-MM-DD")]
79 pub to: Option<String>,
80 /// How the series counts: `count` (issue counts, the default) or `estimate`
81 ///
82 /// `estimate` requires `--unit` and a unit the project declares a conversion for;
83 /// a missing or incompatible unit is refused with the shared remedy, never guessed.
84 #[arg(long, value_name = "count|estimate")]
85 pub mode: Option<String>,
86 /// The unit estimate mode counts in; count mode ignores it (an issue count is unitless)
87 #[arg(long, value_name = "UNIT")]
88 pub unit: Option<String>,
89}