perceptkit 0.2.6__tar.gz → 0.2.7__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 (110) hide show
  1. {perceptkit-0.2.6 → perceptkit-0.2.7}/CHANGELOG.md +9 -0
  2. {perceptkit-0.2.6 → perceptkit-0.2.7}/PKG-INFO +1 -1
  3. {perceptkit-0.2.6 → perceptkit-0.2.7}/pyproject.toml +1 -1
  4. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/kit.py +2 -1
  5. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/retention.py +33 -7
  6. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_retention_entry.py +21 -3
  7. {perceptkit-0.2.6 → perceptkit-0.2.7}/uv.lock +1 -1
  8. {perceptkit-0.2.6 → perceptkit-0.2.7}/.github/workflows/ci.yml +0 -0
  9. {perceptkit-0.2.6 → perceptkit-0.2.7}/.github/workflows/release.yml +0 -0
  10. {perceptkit-0.2.6 → perceptkit-0.2.7}/.gitignore +0 -0
  11. {perceptkit-0.2.6 → perceptkit-0.2.7}/LICENSE +0 -0
  12. {perceptkit-0.2.6 → perceptkit-0.2.7}/NOTES-packaging.md +0 -0
  13. {perceptkit-0.2.6 → perceptkit-0.2.7}/NOTES-quickstart.md +0 -0
  14. {perceptkit-0.2.6 → perceptkit-0.2.7}/README.md +0 -0
  15. {perceptkit-0.2.6 → perceptkit-0.2.7}/docs/PerceptKit-/344/272/247/345/223/201/347/233/256/346/240/207/344/270/216/345/275/223/345/211/215/345/256/236/347/216/260/345/267/256/350/267/235/345/217/215/351/246/210.md" +0 -0
  16. {perceptkit-0.2.6 → perceptkit-0.2.7}/docs/PerceptKit-/346/204/237/347/237/245/345/255/227/346/256/265/344/270/216/345/255/230/345/202/250-/345/267/245/347/250/213/345/257/271/351/275/220/350/241/245/345/205/205/350/256/250/350/256/272.md" +0 -0
  17. {perceptkit-0.2.6 → perceptkit-0.2.7}/docs/reference-storage-mapping.md +0 -0
  18. {perceptkit-0.2.6 → perceptkit-0.2.7}/examples/end_to_end.py +0 -0
  19. {perceptkit-0.2.6 → perceptkit-0.2.7}/examples/ios_adapter.py +0 -0
  20. {perceptkit-0.2.6 → perceptkit-0.2.7}/examples/quickstart.py +0 -0
  21. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/__init__.py +0 -0
  22. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/algorithms/__init__.py +0 -0
  23. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/algorithms/attribution.py +0 -0
  24. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/algorithms/glance.py +0 -0
  25. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/algorithms/history.py +0 -0
  26. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/algorithms/identity.py +0 -0
  27. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/algorithms/observation.py +0 -0
  28. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/algorithms/streaks.py +0 -0
  29. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/algorithms/trend_models.py +0 -0
  30. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/algorithms/wake.py +0 -0
  31. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/catalog.py +0 -0
  32. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/conformance/__init__.py +0 -0
  33. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/conformance/memory.py +0 -0
  34. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/conformance/report.py +0 -0
  35. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/conformance/suite.py +0 -0
  36. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/conformance/wake.py +0 -0
  37. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/__init__.py +0 -0
  38. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/_time.py +0 -0
  39. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/availability.py +0 -0
  40. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/context.py +0 -0
  41. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/delivery.py +0 -0
  42. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/errors.py +0 -0
  43. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/event.py +0 -0
  44. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/observation.py +0 -0
  45. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/receipt.py +0 -0
  46. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/records.py +0 -0
  47. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/report.py +0 -0
  48. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/contracts/versioning.py +0 -0
  49. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/fields.py +0 -0
  50. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/manifest/__init__.py +0 -0
  51. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/manifest/checks.py +0 -0
  52. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/manifest/mapping.py +0 -0
  53. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/manifest/minimal.py +0 -0
  54. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/manifest/types.py +0 -0
  55. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/manifest/units.py +0 -0
  56. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/ports/__init__.py +0 -0
  57. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/ports/storage.py +0 -0
  58. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/ports/wake.py +0 -0
  59. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/processing/__init__.py +0 -0
  60. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/processing/aggregate.py +0 -0
  61. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/processing/dispatch.py +0 -0
  62. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/processing/normalize.py +0 -0
  63. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/processing/pipeline.py +0 -0
  64. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/processing/recompute.py +0 -0
  65. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/processing/recurrence.py +0 -0
  66. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/processing/scheduled.py +0 -0
  67. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/prompts.py +0 -0
  68. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/queries/__init__.py +0 -0
  69. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/queries/api.py +0 -0
  70. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/rules/__init__.py +0 -0
  71. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/rules/engine.py +0 -0
  72. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/rules/evaluators.py +0 -0
  73. {perceptkit-0.2.6 → perceptkit-0.2.7}/src/perceptkit/rules/types.py +0 -0
  74. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/fixtures/README.md +0 -0
  75. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/fixtures/ios_snapshot_no_data.json +0 -0
  76. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/fixtures/ios_snapshot_normal.json +0 -0
  77. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/fixtures/ios_snapshot_unauthorized.json +0 -0
  78. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_attribution.py +0 -0
  79. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_catalog.py +0 -0
  80. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_conformance.py +0 -0
  81. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_conformance_wake_report.py +0 -0
  82. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_contracts.py +0 -0
  83. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_delivery_and_records.py +0 -0
  84. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_docs_match_code.py +0 -0
  85. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_edge_cases.py +0 -0
  86. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_end_to_end.py +0 -0
  87. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_event_envelope.py +0 -0
  88. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_examples.py +0 -0
  89. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_export.py +0 -0
  90. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_identity.py +0 -0
  91. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_ios_fixture.py +0 -0
  92. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_isolation.py +0 -0
  93. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_manifest.py +0 -0
  94. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_no_host_leakage.py +0 -0
  95. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_observation.py +0 -0
  96. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_pipeline.py +0 -0
  97. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_projection.py +0 -0
  98. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_purity.py +0 -0
  99. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_queries.py +0 -0
  100. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_recompute.py +0 -0
  101. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_recurrence.py +0 -0
  102. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_regressions.py +0 -0
  103. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_retention.py +0 -0
  104. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_revision_recompute.py +0 -0
  105. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_rules.py +0 -0
  106. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_scheduled.py +0 -0
  107. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_source_mirror.py +0 -0
  108. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_streaks.py +0 -0
  109. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_trend_models.py +0 -0
  110. {perceptkit-0.2.6 → perceptkit-0.2.7}/tests/test_wake.py +0 -0
@@ -1,5 +1,14 @@
1
1
  # 变更记录
2
2
 
3
+ ## 0.2.7 — 2026-09-02
4
+
5
+ - **跳过原因带上稳定的 `code`(`SkippedSignal`)。** 0.2.6 的清理计划里,
6
+ 跳过理由只有一句中文 —— 这个包的注释都是中文,但**宿主的运维界面不一定是**,
7
+ 直接印进一个英文报告里就成了半中半英。一个库不该替宿主决定报告用什么语言:
8
+ `code` 稳定、机器可读,文案归宿主。`SkippedSignal` 仍然能按 `(signal, detail)`
9
+ 解包,老调用方不用改。
10
+ (0.2.6 发出去之后接 io 时当场撞到的 —— 那份英文运维报告里蹦出中文。)
11
+
3
12
  ## 0.2.6 — 2026-09-02
4
13
 
5
14
  外部审查那份清单里剩下的两条(P0-1 / P0-6 的可做部分)。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: perceptkit
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: 从设备信号判断:有没有发生值得留意的事、值不值得叫醒一次 agent、以及该怎么把此刻的状况讲给它听。不采集数据、不选数据库、不调模型 —— 存储由宿主实现 StoragePort,编排在包内。
5
5
  License:
6
6
  Apache License
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "perceptkit"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  description = "从设备信号判断:有没有发生值得留意的事、值不值得叫醒一次 agent、以及该怎么把此刻的状况讲给它听。不采集数据、不选数据库、不调模型 —— 存储由宿主实现 StoragePort,编排在包内。"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -190,7 +190,8 @@ class PerceptionKit:
190
190
  ],
191
191
  # 故意不删的也要列出来 —— 一份只说"删了 0 条"的报告,读不出
192
192
  # 「是没到期,还是规则写错了」。
193
- "skipped": [{"signal": k, "why": why} for k, why in plan.skipped],
193
+ "skipped": [{"signal": s.signal, "code": s.code, "detail": s.detail}
194
+ for s in plan.skipped],
194
195
  "removed": removed,
195
196
  }
196
197
 
@@ -93,6 +93,33 @@ class RetentionAction:
93
93
  before: _dt.date
94
94
 
95
95
 
96
+ @dataclass(frozen=True)
97
+ class SkippedSignal:
98
+ """故意不清的一个信号,以及为什么。
99
+
100
+ ``code`` 是稳定的机器可读标识,``detail`` 是给人看的一句话。
101
+ **宿主的运维界面该用 code 自己渲染** —— 一个库不该替宿主决定报告用什么
102
+ 语言。这里的 detail 是中文(这个包的注释都是中文),直接印进一个英文
103
+ 运维报告里就成了半中半英。
104
+ """
105
+
106
+ signal: str
107
+ code: str
108
+ detail: str
109
+
110
+ def __iter__(self):
111
+ """还能按 ``(signal, detail)` 解包 —— 老调用方不用一次全改。"""
112
+ return iter((self.signal, self.detail))
113
+
114
+
115
+ #: 稳定的跳过原因。宿主按这个渲染自己的文案。
116
+ SKIP_NO_HISTORY = "no_history"
117
+ SKIP_DETAILS_PERMANENT = "details_permanent"
118
+ SKIP_DETAILS_UNDECLARED = "details_undeclared"
119
+ SKIP_AGGREGATES_PERMANENT = "aggregates_permanent"
120
+ SKIP_AGGREGATES_UNDECLARED = "aggregates_undeclared"
121
+
122
+
96
123
  @dataclass
97
124
  class RetentionPlan:
98
125
  """要删什么,以及**故意不删什么、为什么**。
@@ -103,8 +130,7 @@ class RetentionPlan:
103
130
  """
104
131
 
105
132
  actions: list[RetentionAction] = _field(default_factory=list)
106
- #: ``(信号, 为什么跳过)``
107
- skipped: list[tuple[str, str]] = _field(default_factory=list)
133
+ skipped: list[SkippedSignal] = _field(default_factory=list)
108
134
 
109
135
 
110
136
  def plan_retention(
@@ -128,13 +154,13 @@ def plan_retention(
128
154
  for key in sorted(signals):
129
155
  sig = signals[key]
130
156
  if not sig.stores_history:
131
- plan.skipped.append((key, "不进历史表,没东西可清"))
157
+ plan.skipped.append(SkippedSignal(key, SKIP_NO_HISTORY, "不进历史表,没东西可清"))
132
158
  continue
133
159
 
134
160
  if sig.history_retention_days == PERMANENT:
135
- plan.skipped.append((key, "明细永久保存"))
161
+ plan.skipped.append(SkippedSignal(key, SKIP_DETAILS_PERMANENT, "明细永久保存"))
136
162
  elif sig.history_retention_days is None:
137
- plan.skipped.append((key, "没声明明细保留期 —— 跳过,不替它猜一个"))
163
+ plan.skipped.append(SkippedSignal(key, SKIP_DETAILS_UNDECLARED, "没声明明细保留期 —— 跳过,不替它猜一个"))
138
164
  else:
139
165
  plan.actions.append(RetentionAction(
140
166
  key, "observations",
@@ -142,9 +168,9 @@ def plan_retention(
142
168
 
143
169
  agg_days = sig.effective_aggregate_retention_days
144
170
  if agg_days == PERMANENT:
145
- plan.skipped.append((key, "日聚合永久保存"))
171
+ plan.skipped.append(SkippedSignal(key, SKIP_AGGREGATES_PERMANENT, "日聚合永久保存"))
146
172
  elif agg_days is None:
147
- plan.skipped.append((key, "没声明聚合保留期 —— 跳过,不替它猜一个"))
173
+ plan.skipped.append(SkippedSignal(key, SKIP_AGGREGATES_UNDECLARED, "没声明聚合保留期 —— 跳过,不替它猜一个"))
148
174
  else:
149
175
  plan.actions.append(RetentionAction(
150
176
  key, "aggregates",
@@ -42,7 +42,8 @@ def test_details_and_aggregates_get_their_own_cutoffs():
42
42
  # 明细 7 天要清,聚合永久不许出现在动作里。
43
43
  assert [a.kind for a in photo] == ["observations"]
44
44
  assert photo[0].before == (NOW - timedelta(days=7)).date()
45
- assert ("photo_library_added", "日聚合永久保存") in plan.skipped
45
+ assert any(sk.signal == "photo_library_added"
46
+ and sk.code == "aggregates_permanent" for sk in plan.skipped)
46
47
 
47
48
 
48
49
  def test_permanent_never_appears_as_something_to_delete():
@@ -62,14 +63,31 @@ def test_a_signal_with_no_declared_retention_is_skipped_not_defaulted():
62
63
  history_retention_days=None, aggregate_retention_days=None)
63
64
  plan = plan_retention({"audio_route": sig}, now=NOW)
64
65
  assert plan.actions == []
65
- assert all("不替它猜" in why for _, why in plan.skipped)
66
+ assert all(sk.code.endswith("undeclared") for sk in plan.skipped)
66
67
 
67
68
 
68
69
  def test_the_plan_says_why_it_skipped_things():
69
70
  """只说"删了 0 条"的报告,读不出「是没到期,还是规则写错了」。"""
70
71
  plan = _plan()
71
72
  assert plan.skipped
72
- assert all(why.strip() for _, why in plan.skipped)
73
+ assert all(sk.detail.strip() for sk in plan.skipped)
74
+
75
+
76
+ def test_skip_reasons_carry_a_stable_code_for_the_host_to_render():
77
+ """理由的**文字**是中文(这个包的注释都是中文)。宿主的运维界面不一定是。
78
+
79
+ 直接把 detail 印进一个英文报告里就成了半中半英 —— 一个库不该替宿主
80
+ 决定报告用什么语言。code 是稳定的,文案归宿主。
81
+ """
82
+ from perceptkit.retention import (
83
+ SKIP_AGGREGATES_PERMANENT, SKIP_DETAILS_PERMANENT, SKIP_NO_HISTORY,
84
+ SKIP_DETAILS_UNDECLARED, SKIP_AGGREGATES_UNDECLARED,
85
+ )
86
+ known = {SKIP_NO_HISTORY, SKIP_DETAILS_PERMANENT, SKIP_DETAILS_UNDECLARED,
87
+ SKIP_AGGREGATES_PERMANENT, SKIP_AGGREGATES_UNDECLARED}
88
+ plan = _plan()
89
+ assert {sk.code for sk in plan.skipped} <= known
90
+ assert all(sk.code for sk in plan.skipped)
73
91
 
74
92
 
75
93
  def test_a_dry_run_removes_nothing():
@@ -43,7 +43,7 @@ wheels = [
43
43
 
44
44
  [[package]]
45
45
  name = "perceptkit"
46
- version = "0.2.6"
46
+ version = "0.2.7"
47
47
  source = { editable = "." }
48
48
 
49
49
  [package.optional-dependencies]
File without changes
File without changes
File without changes