Skip to content

feat: Insights & Analytics — timeframe-aggregated envelope reporting - #110

Open
MF-Rozi wants to merge 5 commits into
masterfrom
feat/insights-analytics
Open

MF-Rozi wants to merge 5 commits into
masterfrom
feat/insights-analytics

Conversation

@MF-Rozi

@MF-Rozi MF-Rozi commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Description

What:

  1. Insights feature (roadmap Phase 2: Analytics Integration) — a new Insights tab (replacing the Coming Soon Stats placeholder) that answers: how much flowed in/out this period, how it compares to the previous period, and which pillars/envelopes drove it. Five timeframes: This Month, Last Quarter (previous calendar quarter), YTD, All Time, and a hand-built Custom date-range picker (fullscreen, year-grid dropdown 2000–2035, month chevrons, two-tap range selection).
  2. Domain aggregation (Clean Architecture)GetInsightsSummaryUseCase fetches the current window, the previous comparable window (prior month / prior quarter / same span last year / equal-length span before a custom range), and the category list; rolls transactions from Level-3 envelopes up to Level-1 pillars via the cycle-safe getRootPillar; bucket inflow = income + investment, outflow = expense; missing categories land in one stable "Uncategorized" bucket.
  3. State managementInsightsCubit fetches on demand (open, timeframe switch, pull-to-refresh) with a stale-response guard for rapid switches; periodLabel is computed per timeframe and rendered under the filter row.
  4. Atelier presentationTimeframeFilterRow (capsule segmented control, 5 scrollable pills), InsightsHeroCard (primary gradient #00113A → #002366, direction-aware delta chips: outflow up = red, inflow up = green, "NEW" after a silent window), PillarDistributionChart (multi-segment bar, PortfolioDistributionCard color precedent), EnvelopeDrillDownList (breadcrumbs, share bars, amounts).
  5. Nav — bottom bar slot renamed "Stats" → "Insights" (donut icon, same index); the easter-egg "visit the Stats tab" trigger relocated to the Insights page.

How:

  1. New insights feature folder (domain entities/usecase, presentation blocs/pages/widgets) registered via injectable; /stats route builds the page with BlocProvider(create: getIt<InsightsCubit>()..load()).
  2. Deltas handle empty previous windows with a "new" chip / dash contract instead of dividing by zero; All Time skips the comparison fetch entirely.
  3. Custom picker is a hand-built fullscreen page (Flutter's built-in M3 picker is a centered dialog with no year dropdown) — two-tap range selection with normalized boundaries; selecting a preset clears a picked custom range.

Why:

  1. Users record into a 3-level envelope hierarchy but had no way to see where money went; this is the roadmap's Phase 2 analytics milestone.
  2. On-demand fetch matches the DashboardCubit pattern (user decision); All Time and Custom remove the ambiguity that made "Last Quarter" confusing for short transaction histories.

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionality to change)
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore

Code Checklist

  • 163 tests pass (fvm flutter test), fvm flutter analyze --no-pub clean, dart format clean, and the CI-equivalent merged runner (very_good test --optimization) exits 0
  • Tested on device (POCO X6 Pro 5G, wireless ADB): all five timeframes against real data, custom range picking across months/years, pull-to-refresh, easter-egg ritual trigger on the Insights page

Implements U1 of docs/plans/2026-09-10-001-feat-insights-analytics-plan.md:

- InsightsTimeframe (thisMonth / lastQuarter / ytd) resolves to a current
  analysis window plus the previous comparable window for
  period-over-period deltas, all inclusive ranges.
- InsightsSummary/PillarInsight/EnvelopeInsight/InsightsDelta entities:
  inflow (income + investment) vs outflow (expense) totals, Level-1
  pillar rollups via the cycle-safe getRootPillar, per-envelope buckets
  with activity shares, and delta math with a "new" contract when the
  previous window is zero.
- GetInsightsSummaryUseCase fetches both windows plus categories in one
  call; missing categories land in one stable "Uncategorized" bucket
  per aggregation run. Amounts bucket by TransactionType (never
  CategoryType) so the investment-type/category-type mismatch stays
  inert.

Nine usecase tests cover the three timeframe boundaries, rollup shares,
the investment inflow rule, the uncategorized bucket, empty windows,
and both failure paths.
Implements U2 of docs/plans/2026-09-10-001-feat-insights-analytics-plan.md:

- InsightsState mirrors DashboardState: InsightsStatus
  (initial/loading/loaded/failure), selectedTimeframe (default thisMonth),
  summary?, and dartz failureOption with copyWith(clearSummary).
- InsightsCubit fetches on demand (load, selectTimeframe, refresh) and
  never subscribes to live streams. A request token discards stale
  responses so rapid timeframe switches can't overwrite the newest
  result. nowProvider is injected for deterministic test resolution.
- Registered via @Injectable (DI config regenerated).

Six cubit tests: initial state, loading->loaded transition order,
failure status with populated failureOption, timeframe switch fetching
new params only, refresh, and the stale-response guard (Completer-
driven). Note: mocktail's constructor param for the now-provider needed
a typedef (injectable can't resolve bare function types).
Implements U3 of docs/plans/2026-09-10-001-feat-insights-analytics-plan.md:

- TimeframeFilterRow: capsule segmented control (This Month / Last
  Quarter / YTD), gradient-filled active segment, Manrope/Inter.
- InsightsHeroCard: primary-gradient hero with total outflow/inflow and
  direction-aware delta chips (outflow up = red, inflow up = green;
  "NEW" when the previous window was silent, dash when fully silent).
- PillarDistributionChart: multi-segment outflow bar split by
  shareOfTotalOutflow with rotating segment colours (Portfolio-
  DistributionCard precedent), legend with share/amount, muted track
  when no spending. Income pillars take no bar segment.
- EnvelopeDrillDownList: per-pillar sections with envelope rows
  (icon, name, breadcrumb, share bar, amount); zero-activity pillars
  omitted.

Small plan-faithful amendment: EnvelopeInsight gained a breadcrumb field
computed in the usecase via getBreadcrumbPath so widgets stay data-dumb.

Eight widget tests cover the filter callback, delta chip variants
(positive/NEW/dash), proportional bar segments via ValueKeys, legend
shares, breadcrumbs, and silent-pillar omission.
…time (U4)

Implements U4 of docs/plans/2026-09-10-001-feat-insights-analytics-plan.md
plus two review-driven additions (Custom range and All Time timeframes):

- InsightsPage: timeframe filter, gradient hero, distribution chart, and
  drill-down inside a pull-to-refresh view; flash-on-failure keeps
  previously loaded content; dedicated empty state for silent periods;
  insightsCubit test seam. The easter-egg "visit the Stats tab" trigger
  moves here from the deleted placeholder page.
- Router: /stats now builds InsightsPage (BlocProvider +
  getIt<InsightsCubit>()..load()); MainLayout renames the nav slot to
  "Insights" (donut icon), keeping the same index. Placeholder page
  deleted.
- All Time timeframe: single unfiltered fetch, delta chips render a dash
  (no comparable previous window).
- Custom range: 4th pill opens a hand-built fullscreen picker page
  (Flutter's built-in M3 picker is a centered dialog with no year
  dropdown) — month/year dropdown opens a 2000-2035 year grid,
  month chevrons, two-tap range selection with normalized boundaries,
  Save gated on a complete range. Previous window = equal-length span
  immediately before the range, so deltas stay meaningful.
- Period label rendered under the filter row; cubit computes it per
  timeframe. Selecting a preset clears a picked custom range.

17 new tests: page render/refetch/failure/empty state, custom
normalization and fetch, preset-clears-custom, allTime single fetch,
custom passthrough with equal-length previous, five-pill rendering,
custom callback. 163 tests pass; analyze/format/merged-runner clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant