cctally 1.24.0 → 1.26.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 +13 -0
- package/bin/_cctally_alerts.py +22 -0
- package/bin/_cctally_config.py +79 -10
- package/bin/_cctally_core.py +76 -0
- package/bin/_cctally_dashboard.py +135 -24
- package/bin/_cctally_forecast.py +486 -16
- package/bin/_cctally_milestones.py +162 -0
- package/bin/_cctally_parser.py +11 -4
- package/bin/_cctally_project.py +72 -0
- package/bin/_cctally_record.py +218 -1
- package/bin/_lib_alert_axes.py +6 -1
- package/bin/_lib_alerts_payload.py +70 -0
- package/bin/_lib_share.py +16 -4
- package/bin/cctally +18 -0
- package/dashboard/static/assets/index-C2F1_Mxt.js +18 -0
- package/dashboard/static/assets/{index-CsqqtRBB.css → index-D34qf0LE.css} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +1 -1
- package/dashboard/static/assets/index-DwuW39Tv.js +0 -18
package/bin/_lib_share.py
CHANGED
|
@@ -66,8 +66,14 @@ class DeltaCell:
|
|
|
66
66
|
|
|
67
67
|
@dataclass(frozen=True)
|
|
68
68
|
class ProjectCell:
|
|
69
|
-
"""Anonymization chokepoint — scrubber rewrites the `label` field.
|
|
69
|
+
"""Anonymization chokepoint — scrubber rewrites the `label` field.
|
|
70
|
+
|
|
71
|
+
`rank_cost` (#130): an explicit spend value used by
|
|
72
|
+
`_collect_project_costs` to rank anonymized labels, replacing the old
|
|
73
|
+
hidden-MoneyCell hack. When None, ranking falls back to summing sibling
|
|
74
|
+
MoneyCells (back-compat for every non-budget construction site)."""
|
|
70
75
|
label: str
|
|
76
|
+
rank_cost: float | None = None
|
|
71
77
|
|
|
72
78
|
Cell = TextCell | MoneyCell | PercentCell | DateCell | DeltaCell | ProjectCell
|
|
73
79
|
|
|
@@ -735,7 +741,8 @@ def _render_svg_footer(snap: ShareSnapshot, *, palette: dict,
|
|
|
735
741
|
|
|
736
742
|
def _collect_project_costs(snap: ShareSnapshot) -> dict[str, float]:
|
|
737
743
|
"""Walk rows: for each row containing a ProjectCell, sum MoneyCell values
|
|
738
|
-
in the same row under the project label
|
|
744
|
+
in the same row under the project label — unless the ProjectCell carries an
|
|
745
|
+
explicit ``rank_cost``, which takes precedence over the MoneyCell sum (#130).
|
|
739
746
|
|
|
740
747
|
Charts also contribute via ChartPoint.project_label + y_value (when y_value
|
|
741
748
|
is in $). For consistency we union both sources; rows take precedence on
|
|
@@ -744,13 +751,16 @@ def _collect_project_costs(snap: ShareSnapshot) -> dict[str, float]:
|
|
|
744
751
|
for row in snap.rows:
|
|
745
752
|
proj_label: str | None = None
|
|
746
753
|
money = 0.0
|
|
754
|
+
explicit_rank: float | None = None
|
|
747
755
|
for cell in row.cells.values():
|
|
748
756
|
if isinstance(cell, ProjectCell):
|
|
749
757
|
proj_label = cell.label
|
|
758
|
+
explicit_rank = cell.rank_cost
|
|
750
759
|
elif isinstance(cell, MoneyCell):
|
|
751
760
|
money += cell.usd
|
|
752
761
|
if proj_label is not None:
|
|
753
|
-
|
|
762
|
+
contribution = explicit_rank if explicit_rank is not None else money
|
|
763
|
+
costs[proj_label] = costs.get(proj_label, 0.0) + contribution
|
|
754
764
|
|
|
755
765
|
if snap.chart is not None:
|
|
756
766
|
chart_pts: list[ChartPoint] = []
|
|
@@ -825,7 +835,9 @@ def _apply_anon_mapping(
|
|
|
825
835
|
new_cells: dict[str, Cell] = {}
|
|
826
836
|
for key, cell in row.cells.items():
|
|
827
837
|
if isinstance(cell, ProjectCell) and cell.label in mapping:
|
|
828
|
-
new_cells[key] = ProjectCell(
|
|
838
|
+
new_cells[key] = ProjectCell(
|
|
839
|
+
mapping[cell.label], rank_cost=cell.rank_cost
|
|
840
|
+
)
|
|
829
841
|
else:
|
|
830
842
|
new_cells[key] = cell
|
|
831
843
|
new_rows.append(Row(cells=new_cells))
|
package/bin/cctally
CHANGED
|
@@ -376,11 +376,13 @@ _lib_alerts_payload = _load_sibling("_lib_alerts_payload")
|
|
|
376
376
|
_alert_text_weekly = _lib_alerts_payload._alert_text_weekly
|
|
377
377
|
_alert_text_five_hour = _lib_alerts_payload._alert_text_five_hour
|
|
378
378
|
_alert_text_budget = _lib_alerts_payload._alert_text_budget
|
|
379
|
+
_alert_text_project_budget = _lib_alerts_payload._alert_text_project_budget
|
|
379
380
|
_alert_text_projected = _lib_alerts_payload._alert_text_projected
|
|
380
381
|
_escape_applescript_string = _lib_alerts_payload._escape_applescript_string
|
|
381
382
|
_build_alert_payload_weekly = _lib_alerts_payload._build_alert_payload_weekly
|
|
382
383
|
_build_alert_payload_five_hour = _lib_alerts_payload._build_alert_payload_five_hour
|
|
383
384
|
_build_alert_payload_budget = _lib_alerts_payload._build_alert_payload_budget
|
|
385
|
+
_build_alert_payload_project_budget = _lib_alerts_payload._build_alert_payload_project_budget
|
|
384
386
|
_build_alert_payload_projected = _lib_alerts_payload._build_alert_payload_projected
|
|
385
387
|
|
|
386
388
|
_lib_alert_dispatch = _load_sibling("_lib_alert_dispatch")
|
|
@@ -628,6 +630,13 @@ cmd_sync_week = _cctally_sync_week.cmd_sync_week
|
|
|
628
630
|
# module-private (no external caller).
|
|
629
631
|
_cctally_project = _load_sibling("_cctally_project")
|
|
630
632
|
cmd_project = _cctally_project.cmd_project
|
|
633
|
+
# Shared per-project cost compute (#19/#121, spec §7.1). Re-exported so the
|
|
634
|
+
# per-project budget display (cmd_budget) and the Task 3 firing path reach it
|
|
635
|
+
# via the cctally namespace.
|
|
636
|
+
_sum_cost_by_project = _cctally_project._sum_cost_by_project
|
|
637
|
+
# Shared per-project label disambiguation (#130). Re-exported so the budget
|
|
638
|
+
# display, firing path, and dashboard SSE envelope reach the SAME primitive.
|
|
639
|
+
_project_budget_labels = _cctally_project._project_budget_labels
|
|
631
640
|
|
|
632
641
|
# Eager re-export of bin/_cctally_pricing_check.py — `cmd_pricing_check`
|
|
633
642
|
# is invoked via the parser's `set_defaults(func=c.cmd_pricing_check)`,
|
|
@@ -812,6 +821,7 @@ _PERCENT_NORMALIZE_DECIMALS = _cctally_record._PERCENT_NORMALIZE_DECIMALS
|
|
|
812
821
|
_normalize_percent = _cctally_record._normalize_percent
|
|
813
822
|
maybe_record_milestone = _cctally_record.maybe_record_milestone
|
|
814
823
|
maybe_record_budget_milestone = _cctally_record.maybe_record_budget_milestone
|
|
824
|
+
maybe_record_project_budget_milestone = _cctally_record.maybe_record_project_budget_milestone
|
|
815
825
|
maybe_record_projected_alert = _cctally_record.maybe_record_projected_alert
|
|
816
826
|
_weekly_pct_week_avg_projection = _cctally_record._weekly_pct_week_avg_projection
|
|
817
827
|
_compute_block_totals = _cctally_record._compute_block_totals
|
|
@@ -1107,6 +1117,11 @@ _render_forecast_terminal = _cctally_forecast._render_forecast_terminal
|
|
|
1107
1117
|
_BUDGET_JSON_SCHEMA_VERSION = _cctally_forecast._BUDGET_JSON_SCHEMA_VERSION
|
|
1108
1118
|
_cmd_budget_set = _cctally_forecast._cmd_budget_set
|
|
1109
1119
|
_cmd_budget_unset = _cctally_forecast._cmd_budget_unset
|
|
1120
|
+
# Per-project budget set/unset (#19/#121, spec §4.3 / §7).
|
|
1121
|
+
_resolve_project_budget_target = _cctally_forecast._resolve_project_budget_target
|
|
1122
|
+
_cmd_budget_set_project = _cctally_forecast._cmd_budget_set_project
|
|
1123
|
+
_cmd_budget_unset_project = _cctally_forecast._cmd_budget_unset_project
|
|
1124
|
+
_build_project_budget_rows = _cctally_forecast._build_project_budget_rows
|
|
1110
1125
|
_budget_render_unset = _cctally_forecast._budget_render_unset
|
|
1111
1126
|
_budget_verdict_ansi_code = _cctally_forecast._budget_verdict_ansi_code
|
|
1112
1127
|
_budget_render_terminal = _cctally_forecast._budget_render_terminal
|
|
@@ -2068,10 +2083,13 @@ get_milestone_cost_for_week = _cctally_milestones.get_milestone_cost_for
|
|
|
2068
2083
|
get_milestones_for_week = _cctally_milestones.get_milestones_for_week # forecast c.; tui shim; percent-breakdown c.
|
|
2069
2084
|
insert_percent_milestone = _cctally_milestones.insert_percent_milestone # record shim; idempotency-test mod.
|
|
2070
2085
|
insert_budget_milestone = _cctally_milestones.insert_budget_milestone # record shim
|
|
2086
|
+
insert_project_budget_milestone = _cctally_milestones.insert_project_budget_milestone # record shim; project-budget-config-test ns[]
|
|
2087
|
+
_project_crossings = _cctally_milestones._project_crossings # record shim; milestones c. (#130 firing/reconcile shared crossing arithmetic)
|
|
2071
2088
|
insert_projected_milestone = _cctally_milestones.insert_projected_milestone # record shim
|
|
2072
2089
|
_projected_levels_already_latched = _cctally_milestones._projected_levels_already_latched # record shim
|
|
2073
2090
|
_reconcile_budget_milestones_on_set = _cctally_milestones._reconcile_budget_milestones_on_set # test_budget_alerts ns[]
|
|
2074
2091
|
_reconcile_budget_on_config_write = _cctally_milestones._reconcile_budget_on_config_write # forecast/config/dashboard c.; test_forecast_ns_patch mod. patch
|
|
2092
|
+
_reconcile_project_budget_milestones_on_write = _cctally_milestones._reconcile_project_budget_milestones_on_write # forecast/config/dashboard c. (forward-only project-budget reconcile)
|
|
2075
2093
|
|
|
2076
2094
|
|
|
2077
2095
|
# === Update-banner predicate (spec §4.2) extracted to
|