Skip to main content

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}