cctally 1.99.1 → 1.101.0
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.
- package/CHANGELOG.md +88 -0
- package/bin/_cctally_alerts.py +13 -2
- package/bin/_cctally_cache.py +3 -1
- package/bin/_cctally_cache_report.py +103 -6
- package/bin/_cctally_dashboard.py +2083 -356
- package/bin/_cctally_dashboard_envelope.py +115 -31
- package/bin/_cctally_dashboard_perf.py +433 -0
- package/bin/_cctally_dashboard_share.py +101 -29
- package/bin/_cctally_dashboard_sources.py +1072 -214
- package/bin/_cctally_db.py +30 -14
- package/bin/_cctally_diff.py +20 -0
- package/bin/_cctally_doctor.py +1331 -1148
- package/bin/_cctally_forecast.py +304 -96
- package/bin/_cctally_milestone_history.py +10 -2
- package/bin/_cctally_parser.py +70 -1
- package/bin/_cctally_project.py +155 -47
- package/bin/_cctally_quota.py +40 -21
- package/bin/_cctally_record.py +44 -3
- package/bin/_cctally_refresh.py +60 -10
- package/bin/_cctally_share.py +9 -2
- package/bin/_cctally_source_analytics.py +40 -4
- package/bin/_cctally_statusline.py +53 -7
- package/bin/_cctally_tui.py +723 -232
- package/bin/_cctally_update.py +28 -22
- package/bin/_lib_alert_scope.py +685 -0
- package/bin/_lib_alerts_payload.py +112 -7
- package/bin/_lib_cache_report.py +110 -1
- package/bin/_lib_codex_pools.py +20 -8
- package/bin/_lib_dashboard_sources.py +237 -30
- package/bin/_lib_doctor.py +37 -0
- package/bin/_lib_forecast.py +12 -4
- package/bin/_lib_jsonl.py +4 -2
- package/bin/_lib_perf.py +132 -3
- package/bin/_lib_pricing.py +8 -7
- package/bin/_lib_render.py +31 -3
- package/bin/_lib_share_templates.py +150 -55
- package/bin/_lib_snapshot_cache.py +71 -13
- package/bin/_lib_source_analytics.py +2 -2
- package/bin/_lib_source_identity.py +50 -2
- package/bin/_lib_subscription_weeks.py +65 -0
- package/bin/_lib_tick_stats.py +538 -0
- package/bin/cctally +29 -7
- package/dashboard/static/assets/dashboardStream.shared-worker-1XTMV3nr.js +1 -0
- package/dashboard/static/assets/index-D6Eb9KDn.js +97 -0
- package/dashboard/static/assets/index-i3g7g8zo.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +4 -1
- package/dashboard/static/assets/index-C5NBB2w9.js +0 -97
- package/dashboard/static/assets/index-hJP4wlIO.css +0 -1
|
@@ -58,6 +58,25 @@ def _share_load_lib(*args, **kwargs):
|
|
|
58
58
|
return sys.modules["cctally"]._share_load_lib(*args, **kwargs)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def _optional_float(value):
|
|
62
|
+
"""``float(value)`` when there is a value, ``None`` when there is not.
|
|
63
|
+
|
|
64
|
+
#620 S1 D5. The idiom this replaces — ``float(getattr(x, "f", 0.0) or
|
|
65
|
+
0.0)`` — collapses three distinct states (a real zero, an explicit
|
|
66
|
+
``None``, and a missing attribute) onto one number, so a withheld
|
|
67
|
+
measurement leaves the builder indistinguishable from a measured zero.
|
|
68
|
+
Written as a helper rather than repeated inline because the reason is
|
|
69
|
+
the same at every site and the inline form reads as a default.
|
|
70
|
+
"""
|
|
71
|
+
if value is None:
|
|
72
|
+
return None
|
|
73
|
+
# No `except (TypeError, ValueError): return None`. On a surface whose
|
|
74
|
+
# entire purpose is telling "absent" apart from "present", reporting a
|
|
75
|
+
# malformed value as withheld is the same conflation the helper removes.
|
|
76
|
+
# A malformed value is a defect and must surface as one (#620 S1).
|
|
77
|
+
return float(value)
|
|
78
|
+
|
|
79
|
+
|
|
61
80
|
def _share_now_utc(*args, **kwargs):
|
|
62
81
|
return sys.modules["cctally"]._share_now_utc(*args, **kwargs)
|
|
63
82
|
|
|
@@ -631,9 +650,19 @@ def _build_weekly_share_panel_data(options: dict,
|
|
|
631
650
|
wsa = getattr(r, "week_start_at", "") or ""
|
|
632
651
|
start_date = wsa[:10] if isinstance(wsa, str) and len(wsa) >= 10 else wsa
|
|
633
652
|
cost = float(getattr(r, "cost_usd", 0.0) or 0.0)
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
653
|
+
# `WeeklyPeriodRow.used_pct` is `float | None` — `None` when the week
|
|
654
|
+
# has a cost snapshot but no usage snapshot. Coercing it to zero
|
|
655
|
+
# published `0.0%` for a week nothing measured, which reads as a real
|
|
656
|
+
# zero-usage week. Same defect as the rate below, same treatment.
|
|
657
|
+
used_pct_raw = _optional_float(getattr(r, "used_pct", None))
|
|
658
|
+
used_pct = None if used_pct_raw is None else used_pct_raw / 100.0
|
|
659
|
+
# `WeeklyPeriodRow.dollar_per_pct` is `float | None` — it is `None`
|
|
660
|
+
# whenever the week has no usage percentage to divide by. Coercing it
|
|
661
|
+
# to zero here published `$0.000` for a rate nothing measured, which
|
|
662
|
+
# is the same fabrication #620 S1 D5 removed on the current-week and
|
|
663
|
+
# trend panels. Pre-existing rather than introduced by D5, and swept
|
|
664
|
+
# with them because it is the same defect on the same surface.
|
|
665
|
+
dpp = _optional_float(getattr(r, "dollar_per_pct", None))
|
|
637
666
|
# Per-week top_projects: WeeklyPeriodRow doesn't carry a
|
|
638
667
|
# per-project rollup, but `week_start_at` / `week_end_at` give us
|
|
639
668
|
# an exact range — aggregate session_entries once per week so the
|
|
@@ -682,7 +711,9 @@ def _build_current_week_share_panel_data(options: dict,
|
|
|
682
711
|
return {
|
|
683
712
|
"kpi_cost_usd": 0.0,
|
|
684
713
|
"kpi_pct_used": 0.0,
|
|
685
|
-
|
|
714
|
+
# With no current week at all there is no rate to report, so
|
|
715
|
+
# this is withheld for the same reason as the populated path.
|
|
716
|
+
"kpi_dollar_per_pct": None,
|
|
686
717
|
"kpi_days_remaining": 0.0,
|
|
687
718
|
"daily_progression": [],
|
|
688
719
|
"top_projects": [],
|
|
@@ -728,7 +759,13 @@ def _build_current_week_share_panel_data(options: dict,
|
|
|
728
759
|
return {
|
|
729
760
|
"kpi_cost_usd": float(getattr(cw, "spent_usd", 0.0) or 0.0),
|
|
730
761
|
"kpi_pct_used": used_pct,
|
|
731
|
-
|
|
762
|
+
# #620 S1 D5: `TuiCurrentWeek.dollars_per_percent` is `float | None`,
|
|
763
|
+
# and `float(... or 0.0)` turned both a missing attribute and an
|
|
764
|
+
# explicit None into $0.00 — the fabrication D5 removes, restored one
|
|
765
|
+
# layer below the fix. A withheld rate stays withheld; the template
|
|
766
|
+
# renders it as `n/a`.
|
|
767
|
+
"kpi_dollar_per_pct": _optional_float(
|
|
768
|
+
getattr(cw, "dollars_per_percent", None)),
|
|
732
769
|
"kpi_days_remaining": days_remaining,
|
|
733
770
|
"daily_progression": progression,
|
|
734
771
|
"top_projects": top_projects,
|
|
@@ -751,25 +788,41 @@ def _build_trend_share_panel_data(options: dict,
|
|
|
751
788
|
wsa.strftime("%Y-%m-%d") if isinstance(wsa, dt.datetime)
|
|
752
789
|
else (str(wsa)[:10] if wsa else "")
|
|
753
790
|
)
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
791
|
+
# `TuiTrendRow.used_pct` is `float | None` (phantom weeks — a cost
|
|
792
|
+
# snapshot with no usage snapshot). See the weekly builder above.
|
|
793
|
+
used_pct_raw = _optional_float(getattr(r, "used_pct", None))
|
|
794
|
+
used_pct = None if used_pct_raw is None else used_pct_raw / 100.0
|
|
795
|
+
# #620 S1 D5, as above. Here the fabricated zero propagated further
|
|
796
|
+
# than the KPI: `cost_usd` is derived from the rate, so a zeroed rate
|
|
797
|
+
# also published a zero weekly cost for a week whose cost is unknown.
|
|
798
|
+
dpp = _optional_float(getattr(r, "dollars_per_percent", None))
|
|
757
799
|
weeks.append({
|
|
758
800
|
"start_date": start_date,
|
|
759
|
-
|
|
801
|
+
# A rate times a percentage is a cost only when both are known.
|
|
802
|
+
# With `used_pct` coerced to zero, a week with a real rate and no
|
|
803
|
+
# measured percentage published a $0.00 cost.
|
|
804
|
+
"cost_usd": (
|
|
805
|
+
None if (dpp is None or used_pct is None)
|
|
806
|
+
else dpp * (used_pct * 100.0)
|
|
807
|
+
), # ≈ row total
|
|
760
808
|
"pct_used": used_pct,
|
|
761
809
|
"dollar_per_pct": dpp,
|
|
762
810
|
})
|
|
763
811
|
# Compute 3-week delta: compare last row vs row-4-from-end.
|
|
764
|
-
delta
|
|
812
|
+
# A delta between two weeks is a fact only when both weeks are known.
|
|
813
|
+
# The previous default published `0.0`, which states "no change" — the
|
|
814
|
+
# same fabrication as a zeroed rate, one aggregate up (#620 S1 D5).
|
|
815
|
+
delta: dict = {"dpp_change_pct": None, "cost_change_usd": None}
|
|
765
816
|
if len(weeks) >= 4:
|
|
766
817
|
cur = weeks[-1]
|
|
767
818
|
ref = weeks[-4]
|
|
768
|
-
if ref["dollar_per_pct"]:
|
|
819
|
+
if ref["dollar_per_pct"] and cur["dollar_per_pct"] is not None:
|
|
769
820
|
delta["dpp_change_pct"] = (
|
|
770
821
|
(cur["dollar_per_pct"] - ref["dollar_per_pct"]) / ref["dollar_per_pct"]
|
|
771
822
|
)
|
|
772
|
-
delta
|
|
823
|
+
# A delta between two costs is only a fact when both are known.
|
|
824
|
+
if cur["cost_usd"] is not None and ref["cost_usd"] is not None:
|
|
825
|
+
delta["cost_change_usd"] = cur["cost_usd"] - ref["cost_usd"]
|
|
773
826
|
return {"weeks": weeks, "delta_3_weeks": delta}
|
|
774
827
|
|
|
775
828
|
|
|
@@ -923,8 +976,10 @@ def _build_forecast_share_panel_data(options: dict,
|
|
|
923
976
|
if fc is None:
|
|
924
977
|
return {
|
|
925
978
|
"projected_end_pct": 0.0,
|
|
926
|
-
|
|
927
|
-
|
|
979
|
+
# With no forecast at all there is no rate, so neither ceiling has
|
|
980
|
+
# a distance — the same withholding the populated path performs.
|
|
981
|
+
"days_to_100pct": None,
|
|
982
|
+
"days_to_90pct": None,
|
|
928
983
|
"daily_budgets": {
|
|
929
984
|
"avg": 0.0, "recent_24h": 0.0,
|
|
930
985
|
"until_90pct": 0.0, "until_100pct": 0.0,
|
|
@@ -943,10 +998,18 @@ def _build_forecast_share_panel_data(options: dict,
|
|
|
943
998
|
r_recent = float(r_recent_raw) if r_recent_raw is not None else r_avg
|
|
944
999
|
# End-of-week projected %
|
|
945
1000
|
projected_end_pct = (p_now + r_avg * remaining_hours) / 100.0
|
|
946
|
-
# Days to ceilings (simple inverse: hours-to-target / 24)
|
|
947
|
-
|
|
948
|
-
|
|
1001
|
+
# Days to ceilings (simple inverse: hours-to-target / 24).
|
|
1002
|
+
# The two exit conditions are opposite facts and must not share a value.
|
|
1003
|
+
# `p_now >= target_pct` means the target is already reached, and zero days
|
|
1004
|
+
# to it is true. `r_avg <= 0` means no rate was observed, so the target is
|
|
1005
|
+
# not reachable on any timeline this data describes — and in the no-usage
|
|
1006
|
+
# state BOTH hold (`p_now == 0`, `r_avg == 0`), so the shared `0.0`
|
|
1007
|
+
# rendered `Days->90% 0.0`, stating the opposite of the truth.
|
|
1008
|
+
def _days_to_ceiling(target_pct: float) -> "float | None":
|
|
1009
|
+
if p_now >= target_pct:
|
|
949
1010
|
return 0.0
|
|
1011
|
+
if r_avg <= 0:
|
|
1012
|
+
return None
|
|
950
1013
|
hours = (target_pct - p_now) / r_avg
|
|
951
1014
|
return max(0.0, hours / 24.0)
|
|
952
1015
|
days_to_100 = _days_to_ceiling(100.0)
|
|
@@ -954,27 +1017,36 @@ def _build_forecast_share_panel_data(options: dict,
|
|
|
954
1017
|
# Daily budgets — prefer ForecastView's pre-routed pair (issue #57)
|
|
955
1018
|
# when available; otherwise replay the legacy ``fc.budgets`` scan
|
|
956
1019
|
# inline so positionally-constructed fixture snapshots still work.
|
|
957
|
-
|
|
958
|
-
|
|
1020
|
+
# Unknown until a branch below supplies one. A ceiling budget that no
|
|
1021
|
+
# `BudgetRow` describes is not a $0.00/day budget.
|
|
1022
|
+
budgets: dict = {"avg": None, "recent_24h": None,
|
|
1023
|
+
"until_90pct": None, "until_100pct": None}
|
|
1024
|
+
# #620 S1 D5: no `or 0.0` on either branch. `ForecastView.budget_*_per_day_usd`
|
|
1025
|
+
# and `BudgetRow.dollars_per_day` are both `None` whenever the rate is
|
|
1026
|
+
# withheld, and coercing them republished $0.00 on exactly the surface
|
|
1027
|
+
# whose message is that nothing was measured.
|
|
959
1028
|
if fc_view is not None:
|
|
960
|
-
budgets["until_100pct"] =
|
|
961
|
-
fc_view
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
fc_view.budget_90_per_day_usd or 0.0,
|
|
965
|
-
)
|
|
1029
|
+
budgets["until_100pct"] = _optional_float(
|
|
1030
|
+
getattr(fc_view, "budget_100_per_day_usd", None))
|
|
1031
|
+
budgets["until_90pct"] = _optional_float(
|
|
1032
|
+
getattr(fc_view, "budget_90_per_day_usd", None))
|
|
966
1033
|
else:
|
|
967
1034
|
for b in getattr(fc, "budgets", None) or []:
|
|
968
1035
|
tp = getattr(b, "target_percent", None)
|
|
969
|
-
dpd =
|
|
1036
|
+
dpd = _optional_float(getattr(b, "dollars_per_day", None))
|
|
970
1037
|
if tp == 100:
|
|
971
1038
|
budgets["until_100pct"] = dpd
|
|
972
1039
|
elif tp == 90:
|
|
973
1040
|
budgets["until_90pct"] = dpd
|
|
974
1041
|
# avg / recent_24h: derive from dollars-per-percent × r_avg/r_recent.
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1042
|
+
# #620 S1 D5: the `or 0.0` fabrication is REMOVED rather than moved. With
|
|
1043
|
+
# no observed usage there is no rate, so a dollars-per-day figure derived
|
|
1044
|
+
# from it is unavailable; substituting zero here would restore the same
|
|
1045
|
+
# defect one layer below the fix.
|
|
1046
|
+
_raw_dpp = getattr(inputs, "dollars_per_percent", None) if inputs else None
|
|
1047
|
+
dpp = None if _raw_dpp is None else float(_raw_dpp)
|
|
1048
|
+
budgets["avg"] = None if dpp is None else dpp * r_avg * 24.0
|
|
1049
|
+
budgets["recent_24h"] = None if dpp is None else dpp * r_recent * 24.0
|
|
978
1050
|
# Projection curve — 7-day forward, using r_avg
|
|
979
1051
|
today = _share_now_utc().date()
|
|
980
1052
|
projection_curve: list[dict] = []
|