prodc 0.3.63__tar.gz → 0.3.64__tar.gz

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.
Files changed (47) hide show
  1. {prodc-0.3.63 → prodc-0.3.64}/PKG-INFO +1 -1
  2. {prodc-0.3.63 → prodc-0.3.64}/pyproject.toml +1 -1
  3. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/cli.py +3 -5
  4. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/model.py +37 -0
  5. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/status.py +1 -1
  6. {prodc-0.3.63 → prodc-0.3.64}/.gitignore +0 -0
  7. {prodc-0.3.63 → prodc-0.3.64}/CHANGELOG.md +0 -0
  8. {prodc-0.3.63 → prodc-0.3.64}/LICENSE +0 -0
  9. {prodc-0.3.63 → prodc-0.3.64}/README.md +0 -0
  10. {prodc-0.3.63 → prodc-0.3.64}/examples/quickstart/features/orders.feature +0 -0
  11. {prodc-0.3.63 → prodc-0.3.64}/examples/quickstart/needs/jordan.yaml +0 -0
  12. {prodc-0.3.63 → prodc-0.3.64}/examples/quickstart/prodc.toml +0 -0
  13. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/__init__.py +0 -0
  14. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/adapters.py +0 -0
  15. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/agent.md +0 -0
  16. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/board.py +0 -0
  17. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/callbacks.py +0 -0
  18. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/config.py +0 -0
  19. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/dedup.py +0 -0
  20. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/loader.py +0 -0
  21. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/py.typed +0 -0
  22. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/report.py +0 -0
  23. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/report_html.py +0 -0
  24. {prodc-0.3.63 → prodc-0.3.64}/src/prodc/writer.py +0 -0
  25. {prodc-0.3.63 → prodc-0.3.64}/tests/test_adapters.py +0 -0
  26. {prodc-0.3.63 → prodc-0.3.64}/tests/test_callbacks.py +0 -0
  27. {prodc-0.3.63 → prodc-0.3.64}/tests/test_cli.py +0 -0
  28. {prodc-0.3.63 → prodc-0.3.64}/tests/test_command_hook.py +0 -0
  29. {prodc-0.3.63 → prodc-0.3.64}/tests/test_dedup.py +0 -0
  30. {prodc-0.3.63 → prodc-0.3.64}/tests/test_description.py +0 -0
  31. {prodc-0.3.63 → prodc-0.3.64}/tests/test_drafts.py +0 -0
  32. {prodc-0.3.63 → prodc-0.3.64}/tests/test_external_adapter.py +0 -0
  33. {prodc-0.3.63 → prodc-0.3.64}/tests/test_gitlab_source.py +0 -0
  34. {prodc-0.3.63 → prodc-0.3.64}/tests/test_importance.py +0 -0
  35. {prodc-0.3.63 → prodc-0.3.64}/tests/test_loader.py +0 -0
  36. {prodc-0.3.63 → prodc-0.3.64}/tests/test_models.py +0 -0
  37. {prodc-0.3.63 → prodc-0.3.64}/tests/test_report.py +0 -0
  38. {prodc-0.3.63 → prodc-0.3.64}/tests/test_report_html.py +0 -0
  39. {prodc-0.3.63 → prodc-0.3.64}/tests/test_roles.py +0 -0
  40. {prodc-0.3.63 → prodc-0.3.64}/tests/test_sources.py +0 -0
  41. {prodc-0.3.63 → prodc-0.3.64}/tests/test_staleness.py +0 -0
  42. {prodc-0.3.63 → prodc-0.3.64}/tests/test_status.py +0 -0
  43. {prodc-0.3.63 → prodc-0.3.64}/tests/test_suggest.py +0 -0
  44. {prodc-0.3.63 → prodc-0.3.64}/tests/test_variants.py +0 -0
  45. {prodc-0.3.63 → prodc-0.3.64}/tests/test_who_variants.py +0 -0
  46. {prodc-0.3.63 → prodc-0.3.64}/tests/test_writer.py +0 -0
  47. {prodc-0.3.63 → prodc-0.3.64}/tests/test_writer_comments.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: prodc
3
- Version: 0.3.63
3
+ Version: 0.3.64
4
4
  Summary: Product management as code: roles, flows, and features traced to evidence.
5
5
  Project-URL: Homepage, https://gitlab.com/jorgeecardona/prodc
6
6
  Project-URL: Repository, https://gitlab.com/jorgeecardona/prodc
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "prodc"
3
- version = "0.3.63"
3
+ version = "0.3.64"
4
4
  description = "Product management as code: roles, flows, and features traced to evidence."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -49,6 +49,7 @@ from .model import (
49
49
  NeedState,
50
50
  Release,
51
51
  Role,
52
+ board_needs,
52
53
  ears_shape,
53
54
  )
54
55
  from .report import render_markdown
@@ -541,8 +542,7 @@ def _cmd_status(args: argparse.Namespace) -> int:
541
542
  # Draft needs (#60) are staged: kept off the counted board (and coverage, recurrence,
542
543
  # orphans) until promoted with `set <id> draft=false`, unless --include-drafts is passed.
543
544
  draft_count = sum(1 for n in needs if n.draft)
544
- if not args.include_drafts:
545
- needs = [n for n in needs if not n.draft]
545
+ needs = board_needs(needs, include_drafts=args.include_drafts)
546
546
  # Scoped coverage (#49): restrict the board + coverage to a subset, so the number people
547
547
  # act on (this client, this goal) isn't diluted by the whole mixed board.
548
548
  if args.client:
@@ -858,9 +858,7 @@ def _cmd_plan(args: argparse.Namespace) -> int:
858
858
  if loaded is None:
859
859
  return 2
860
860
  cfg, model = loaded
861
- needs = list(model.needs)
862
- if not args.include_drafts:
863
- needs = [n for n in needs if not n.draft] # drafts aren't backlog until promoted (#60)
861
+ needs = board_needs(list(model.needs), include_drafts=args.include_drafts) # drafts off (#60)
864
862
  results = resolve(needs, cfg.adapters, cfg.source_adapters)
865
863
  recurrence_counts = {rec.baseline.id: rec.count for rec in recurrence(needs)}
866
864
  print(
@@ -194,6 +194,43 @@ class Need:
194
194
  kinds = {s.kind for s in self.sources}
195
195
  return next((k for k in order if k in kinds), None)
196
196
 
197
+ @property
198
+ def counts_toward_coverage(self) -> bool:
199
+ """Whether this need is a unit of product coverage — what :func:`~prodc.status.summarize`
200
+ counts.
201
+
202
+ A leaf product need. A flow (a need with ``children``) is a roll-up of its leaves, so
203
+ counting it too would double-count (#43); a non-product need (infra/NFR/internal) is
204
+ excluded from product coverage (#48). Draft staging is a separate, upstream filter
205
+ (:func:`board_needs`), not part of this rule — so a draft still counts once promoted onto
206
+ the board (e.g. under ``--include-drafts``).
207
+ """
208
+ return not self.children and self.product
209
+
210
+ @property
211
+ def excluded_reason(self) -> str | None:
212
+ """Why this need is off the counted board, or ``None`` when it is on it.
213
+
214
+ The single home for the lifecycle exclusions, so a new state lands here once: ``"draft"``
215
+ (staged, not yet promoted, #60) and ``"nonproduct"`` (infra/NFR/internal, #48). A flow is
216
+ not excluded — it is a roll-up row on the board — so ``children`` is not a reason here
217
+ (that axis is :attr:`counts_toward_coverage`).
218
+ """
219
+ if self.draft:
220
+ return "draft"
221
+ if not self.product:
222
+ return "nonproduct"
223
+ return None
224
+
225
+
226
+ def board_needs(needs: list[Need], *, include_drafts: bool) -> list[Need]:
227
+ """The needs on the counted board: a draft (#60) is staged off it until promoted.
228
+
229
+ ``include_drafts`` keeps them on (the ``--include-drafts`` flag). The one place the
230
+ draft-staging rule lives, shared by ``status`` and ``plan`` so the two never diverge.
231
+ """
232
+ return list(needs) if include_drafts else [n for n in needs if not n.draft]
233
+
197
234
 
198
235
  class ArgKind(StrEnum):
199
236
  """A node type in the assurance argument (GSN: Goal Structuring Notation)."""
@@ -251,7 +251,7 @@ def summarize(results: list[NeedResult]) -> Summary:
251
251
  denominator too would double-count every flow (#43). Coverage is the leaves; a flow's own
252
252
  state is reported separately, never added on top of the children it aggregates.
253
253
  """
254
- leaves = [r for r in results if not r.need.children and r.need.product]
254
+ leaves = [r for r in results if r.need.counts_toward_coverage]
255
255
  by_state = {s: sum(1 for r in leaves if r.state is s) for s in NeedState}
256
256
  return Summary(
257
257
  total=len(leaves),
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes