algo-cli-runtime 0.14.0__py3-none-any.whl

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 (237) hide show
  1. algo_cli/__init__.py +3 -0
  2. algo_cli/__main__.py +7 -0
  3. algo_cli/_internal/__init__.py +12 -0
  4. algo_cli/_internal/policy_chain.py +259 -0
  5. algo_cli/action_registry.py +1047 -0
  6. algo_cli/agent_blocks.py +550 -0
  7. algo_cli/agent_pipeline.py +1457 -0
  8. algo_cli/agent_threads.py +308 -0
  9. algo_cli/animations.py +316 -0
  10. algo_cli/cache_admission.py +209 -0
  11. algo_cli/capability_mask.py +66 -0
  12. algo_cli/chat_protocol.py +116 -0
  13. algo_cli/chatgpt_auth.py +510 -0
  14. algo_cli/chatgpt_client.py +657 -0
  15. algo_cli/code_rag.py +479 -0
  16. algo_cli/config.py +651 -0
  17. algo_cli/context_budget.py +679 -0
  18. algo_cli/credential_helpers.py +315 -0
  19. algo_cli/deliberation.py +29 -0
  20. algo_cli/display.py +1470 -0
  21. algo_cli/evals/__init__.py +21 -0
  22. algo_cli/evals/algorithm_effectiveness.py +560 -0
  23. algo_cli/evals/competitive_harness_rating.py +702 -0
  24. algo_cli/evals/cot_quality.py +220 -0
  25. algo_cli/evals/harness_retrieval_benchmark.py +401 -0
  26. algo_cli/evals/performance_regression.py +136 -0
  27. algo_cli/evals/scorecard_grading.py +308 -0
  28. algo_cli/evals/session_distribution.py +84 -0
  29. algo_cli/execution_guardrails.py +806 -0
  30. algo_cli/extensions_manifest.py +84 -0
  31. algo_cli/git_evidence.py +227 -0
  32. algo_cli/google_workspace.py +407 -0
  33. algo_cli/google_workspace_auth.py +523 -0
  34. algo_cli/harness.py +2587 -0
  35. algo_cli/identity.py +557 -0
  36. algo_cli/index_compute_lab.py +228 -0
  37. algo_cli/inference_harness.py +70 -0
  38. algo_cli/intelligence/__init__.py +1103 -0
  39. algo_cli/intelligence/acrobat_config.py +307 -0
  40. algo_cli/intelligence/acrobat_manifests.py +338 -0
  41. algo_cli/intelligence/acrobat_models.py +195 -0
  42. algo_cli/intelligence/acrobat_pipeline.py +295 -0
  43. algo_cli/intelligence/acrobat_runtime.py +302 -0
  44. algo_cli/intelligence/acrobat_security.py +261 -0
  45. algo_cli/intelligence/acrobat_workflows.py +226 -0
  46. algo_cli/intelligence/actionability.py +165 -0
  47. algo_cli/intelligence/adversarial_audit.py +136 -0
  48. algo_cli/intelligence/agent_arena.py +92 -0
  49. algo_cli/intelligence/agent_benchmark.py +236 -0
  50. algo_cli/intelligence/agent_runtime.py +171 -0
  51. algo_cli/intelligence/agents_as_tools.py +70 -0
  52. algo_cli/intelligence/artifact_binding.py +80 -0
  53. algo_cli/intelligence/autonomous_engineer.py +1976 -0
  54. algo_cli/intelligence/backpressure.py +99 -0
  55. algo_cli/intelligence/bloom_filter.py +186 -0
  56. algo_cli/intelligence/bonferroni.py +66 -0
  57. algo_cli/intelligence/boundary_compaction.py +98 -0
  58. algo_cli/intelligence/catalog_verifier.py +172 -0
  59. algo_cli/intelligence/cavecrew.py +118 -0
  60. algo_cli/intelligence/changelog.py +176 -0
  61. algo_cli/intelligence/checkpoint_resume.py +92 -0
  62. algo_cli/intelligence/circuit_breaker.py +88 -0
  63. algo_cli/intelligence/clarification_gate.py +101 -0
  64. algo_cli/intelligence/code_graph.py +180 -0
  65. algo_cli/intelligence/coderank.py +97 -0
  66. algo_cli/intelligence/consistent_hash.py +150 -0
  67. algo_cli/intelligence/consortium_synthesis.py +139 -0
  68. algo_cli/intelligence/construction/__init__.py +241 -0
  69. algo_cli/intelligence/construction/common.py +273 -0
  70. algo_cli/intelligence/construction/documents.py +496 -0
  71. algo_cli/intelligence/construction/labor_units.py +1395 -0
  72. algo_cli/intelligence/construction/payments.py +470 -0
  73. algo_cli/intelligence/construction/risk.py +784 -0
  74. algo_cli/intelligence/content_extractor.py +132 -0
  75. algo_cli/intelligence/context_adaptive.py +102 -0
  76. algo_cli/intelligence/context_ops.py +95 -0
  77. algo_cli/intelligence/count_min.py +145 -0
  78. algo_cli/intelligence/cow_state.py +103 -0
  79. algo_cli/intelligence/critic_loop.py +119 -0
  80. algo_cli/intelligence/cross_source.py +113 -0
  81. algo_cli/intelligence/daemon_mode.py +99 -0
  82. algo_cli/intelligence/dag_orchestration.py +151 -0
  83. algo_cli/intelligence/deep_research.py +155 -0
  84. algo_cli/intelligence/degenerate_detector.py +78 -0
  85. algo_cli/intelligence/delta_report.py +92 -0
  86. algo_cli/intelligence/discovery_event_log.py +92 -0
  87. algo_cli/intelligence/document_ingest.py +298 -0
  88. algo_cli/intelligence/dual_layer_validate.py +151 -0
  89. algo_cli/intelligence/echo_fidelity.py +73 -0
  90. algo_cli/intelligence/ema_tuning.py +104 -0
  91. algo_cli/intelligence/event_log.py +92 -0
  92. algo_cli/intelligence/evidence_graph.py +114 -0
  93. algo_cli/intelligence/extension_host.py +162 -0
  94. algo_cli/intelligence/extension_manifest.py +115 -0
  95. algo_cli/intelligence/falsification_suite.py +178 -0
  96. algo_cli/intelligence/finance/__init__.py +169 -0
  97. algo_cli/intelligence/finance/anomalies.py +135 -0
  98. algo_cli/intelligence/finance/ap_ar.py +351 -0
  99. algo_cli/intelligence/finance/cash.py +162 -0
  100. algo_cli/intelligence/finance/close.py +332 -0
  101. algo_cli/intelligence/finance/common.py +244 -0
  102. algo_cli/intelligence/finance/construction.py +135 -0
  103. algo_cli/intelligence/finance/controls.py +172 -0
  104. algo_cli/intelligence/finance/evidence.py +119 -0
  105. algo_cli/intelligence/finance/exceptions.py +157 -0
  106. algo_cli/intelligence/finance/reconciliations.py +254 -0
  107. algo_cli/intelligence/finance/revenue.py +109 -0
  108. algo_cli/intelligence/finance/tax.py +74 -0
  109. algo_cli/intelligence/finance/workpapers.py +111 -0
  110. algo_cli/intelligence/finding_record.py +120 -0
  111. algo_cli/intelligence/flow_dag.py +267 -0
  112. algo_cli/intelligence/gatherer.py +223 -0
  113. algo_cli/intelligence/golden_master.py +98 -0
  114. algo_cli/intelligence/graph_rag.py +195 -0
  115. algo_cli/intelligence/group_chat.py +143 -0
  116. algo_cli/intelligence/hash_dedup.py +145 -0
  117. algo_cli/intelligence/hyperloglog.py +128 -0
  118. algo_cli/intelligence/incremental_index.py +316 -0
  119. algo_cli/intelligence/index_store.py +16 -0
  120. algo_cli/intelligence/iteration_plan.py +133 -0
  121. algo_cli/intelligence/kernel_plugins.py +167 -0
  122. algo_cli/intelligence/lesson_catalog.py +135 -0
  123. algo_cli/intelligence/llm_fallback.py +169 -0
  124. algo_cli/intelligence/log2_histogram.py +267 -0
  125. algo_cli/intelligence/lsp_integration.py +147 -0
  126. algo_cli/intelligence/memory_evolution.py +117 -0
  127. algo_cli/intelligence/minhash_lsh.py +182 -0
  128. algo_cli/intelligence/multi_model_score.py +174 -0
  129. algo_cli/intelligence/multi_tier_grade.py +211 -0
  130. algo_cli/intelligence/negative_controls.py +113 -0
  131. algo_cli/intelligence/numeric_clamp.py +63 -0
  132. algo_cli/intelligence/occ_editor.py +66 -0
  133. algo_cli/intelligence/output_normalize.py +112 -0
  134. algo_cli/intelligence/parallel_delegation.py +98 -0
  135. algo_cli/intelligence/parallel_fanout.py +104 -0
  136. algo_cli/intelligence/permission_modes.py +105 -0
  137. algo_cli/intelligence/pre_push_gate.py +68 -0
  138. algo_cli/intelligence/prefetch.py +171 -0
  139. algo_cli/intelligence/process_framework.py +217 -0
  140. algo_cli/intelligence/project_graph.py +387 -0
  141. algo_cli/intelligence/query_expansion.py +146 -0
  142. algo_cli/intelligence/ralph_loop.py +117 -0
  143. algo_cli/intelligence/rate_limiter.py +153 -0
  144. algo_cli/intelligence/refactor_transaction.py +94 -0
  145. algo_cli/intelligence/research_workspace.py +108 -0
  146. algo_cli/intelligence/retraction_ledger.py +72 -0
  147. algo_cli/intelligence/saga_pattern.py +88 -0
  148. algo_cli/intelligence/session_fork.py +100 -0
  149. algo_cli/intelligence/shadow_editor.py +67 -0
  150. algo_cli/intelligence/shell_session.py +213 -0
  151. algo_cli/intelligence/source_registry.py +143 -0
  152. algo_cli/intelligence/spawn_scales.py +99 -0
  153. algo_cli/intelligence/stat_stability.py +104 -0
  154. algo_cli/intelligence/structural_validator.py +148 -0
  155. algo_cli/intelligence/subagent_spawner.py +111 -0
  156. algo_cli/intelligence/symmetric_verify.py +70 -0
  157. algo_cli/intelligence/task_classifier.py +129 -0
  158. algo_cli/intelligence/team_execution.py +122 -0
  159. algo_cli/intelligence/tiered_access.py +121 -0
  160. algo_cli/intelligence/utility_registry.py +159 -0
  161. algo_cli/intuition_engine.py +560 -0
  162. algo_cli/intuition_injector.py +82 -0
  163. algo_cli/kernels/__init__.py +5 -0
  164. algo_cli/kernels/manifest.py +763 -0
  165. algo_cli/main.py +3903 -0
  166. algo_cli/memory_candidates.py +541 -0
  167. algo_cli/memory_echo_veil.py +394 -0
  168. algo_cli/memory_runtime.py +112 -0
  169. algo_cli/model_info.py +548 -0
  170. algo_cli/model_profile.py +160 -0
  171. algo_cli/model_routing.py +74 -0
  172. algo_cli/oneshot.py +331 -0
  173. algo_cli/perf_telemetry.py +389 -0
  174. algo_cli/plugins.py +245 -0
  175. algo_cli/private_event_store.py +654 -0
  176. algo_cli/quantization/__init__.py +24 -0
  177. algo_cli/quantization/lloyd_max.py +98 -0
  178. algo_cli/quantization/turbo_quant.py +308 -0
  179. algo_cli/reasoning/__init__.py +46 -0
  180. algo_cli/reasoning/combinatorial.py +356 -0
  181. algo_cli/reasoning/graph_of_thought.py +297 -0
  182. algo_cli/reasoning/mcts.py +220 -0
  183. algo_cli/reasoning/neuro_symbolic.py +250 -0
  184. algo_cli/reasoning/react.py +246 -0
  185. algo_cli/reasoning/reflexion.py +225 -0
  186. algo_cli/reasoning/tree_of_thought.py +241 -0
  187. algo_cli/reasoning_bridge.py +150 -0
  188. algo_cli/reconciliation.py +284 -0
  189. algo_cli/reflex.py +385 -0
  190. algo_cli/resources/docs/ALGO.md +13958 -0
  191. algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
  192. algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
  193. algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
  194. algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
  195. algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
  196. algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
  197. algo_cli/resources/docs/main-split-map.md +35 -0
  198. algo_cli/resources/docs/privacy-and-context.md +48 -0
  199. algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
  200. algo_cli/resources/skills/README.md +26 -0
  201. algo_cli/resources/skills/algo-cli.md +59 -0
  202. algo_cli/resources/skills/edit-file-precision.md +49 -0
  203. algo_cli/resources/skills/harness-search-first.md +47 -0
  204. algo_cli/resources/skills/memory-recall-ritual.md +51 -0
  205. algo_cli/resources/skills/qol-algorithms.md +224 -0
  206. algo_cli/resources/skills/smart-error-recovery.md +56 -0
  207. algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
  208. algo_cli/retrieval_algorithms.py +127 -0
  209. algo_cli/runtime_qos.py +236 -0
  210. algo_cli/runtime_services.py +320 -0
  211. algo_cli/session_commands.py +95 -0
  212. algo_cli/session_mode.py +113 -0
  213. algo_cli/skills.py +430 -0
  214. algo_cli/slash_dispatch.py +1265 -0
  215. algo_cli/small_context.py +206 -0
  216. algo_cli/spawn_budget.py +89 -0
  217. algo_cli/task_ledger.py +84 -0
  218. algo_cli/task_router.py +197 -0
  219. algo_cli/tool_context.py +94 -0
  220. algo_cli/tool_contract.py +99 -0
  221. algo_cli/tool_policy.py +357 -0
  222. algo_cli/tool_runtime.py +647 -0
  223. algo_cli/tools.py +3056 -0
  224. algo_cli/url_scheme.py +174 -0
  225. algo_cli/verify.py +154 -0
  226. algo_cli/version_manifest.py +178 -0
  227. algo_cli/vision_screenshot_verify.py +76 -0
  228. algo_cli/workspace_resolver.py +68 -0
  229. algo_cli/x_account.py +209 -0
  230. algo_cli/xai_auth.py +374 -0
  231. algo_cli/xai_client.py +600 -0
  232. algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
  233. algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
  234. algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
  235. algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
  236. algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
  237. ollama_cli/__init__.py +67 -0
@@ -0,0 +1,178 @@
1
+ """H20 — Falsification Suite.
2
+
3
+ Multiple independent attack vectors on a claim.
4
+ Mined from GLOSSOPETRAE experiments/falsify/ (S1-S4 probes).
5
+
6
+ Each probe attacks the claim from a different angle. If any probe
7
+ succeeds in falsifying the claim, the claim is rejected.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import Any, Callable
13
+
14
+
15
+ @dataclass
16
+ class FalsificationProbe:
17
+ """A single falsification probe."""
18
+
19
+ name: str
20
+ description: str
21
+ attack_fn: Callable[[dict[str, Any]], bool] # Returns True if falsified
22
+
23
+
24
+ @dataclass
25
+ class ProbeResult:
26
+ """Result of a single probe."""
27
+
28
+ probe_name: str
29
+ falsified: bool
30
+ evidence: str = ""
31
+
32
+
33
+ @dataclass
34
+ class FalsificationResult:
35
+ """Combined result of all probes."""
36
+
37
+ entry_id: str
38
+ probe_results: list[ProbeResult] = field(default_factory=list)
39
+ any_falsified: bool = False
40
+ falsified_by: list[str] = field(default_factory=list)
41
+ survived_count: int = 0
42
+ total_count: int = 0
43
+
44
+
45
+ # Built-in probes
46
+ def probe_missing_evidence(entry_id: str, claims: dict[str, Any]) -> ProbeResult:
47
+ """S1: Check if claims lack evidence."""
48
+ falsified = False
49
+ evidence_parts: list[str] = []
50
+
51
+ for key, value in claims.items():
52
+ if value is None:
53
+ falsified = True
54
+ evidence_parts.append(f"{key} is None")
55
+ elif isinstance(value, (list, dict)) and len(value) == 0:
56
+ falsified = True
57
+ evidence_parts.append(f"{key} is empty")
58
+
59
+ return ProbeResult(
60
+ probe_name="missing-evidence",
61
+ falsified=falsified,
62
+ evidence="; ".join(evidence_parts) if evidence_parts else "All claims have values",
63
+ )
64
+
65
+
66
+ def probe_contradictory_claims(entry_id: str, claims: dict[str, Any]) -> ProbeResult:
67
+ """S2: Check for internal contradictions."""
68
+ falsified = False
69
+ evidence_parts: list[str] = []
70
+
71
+ # Check status vs tests
72
+ status = claims.get("status", "")
73
+ tests = claims.get("tests", [])
74
+ if status == "implemented" and not tests:
75
+ falsified = True
76
+ evidence_parts.append("Status 'implemented' but no tests")
77
+
78
+ # Check score vs status
79
+ score = claims.get("score", 0.0)
80
+ if status == "implemented" and isinstance(score, (int, float)) and score < 0.5:
81
+ falsified = True
82
+ evidence_parts.append(f"Status 'implemented' but score={score}")
83
+
84
+ return ProbeResult(
85
+ probe_name="contradictory-claims",
86
+ falsified=falsified,
87
+ evidence="; ".join(evidence_parts) if evidence_parts else "No contradictions found",
88
+ )
89
+
90
+
91
+ def probe_boundary_values(entry_id: str, claims: dict[str, Any]) -> ProbeResult:
92
+ """S3: Check for impossible boundary values."""
93
+ falsified = False
94
+ evidence_parts: list[str] = []
95
+
96
+ for key, value in claims.items():
97
+ if isinstance(value, (int, float)):
98
+ if value < 0.0 and "count" in key.lower():
99
+ falsified = True
100
+ evidence_parts.append(f"{key}={value} is negative")
101
+ if value > 1.0 and "rate" in key.lower():
102
+ falsified = True
103
+ evidence_parts.append(f"{key}={value} exceeds 1.0")
104
+
105
+ return ProbeResult(
106
+ probe_name="boundary-values",
107
+ falsified=falsified,
108
+ evidence="; ".join(evidence_parts) if evidence_parts else "All values in bounds",
109
+ )
110
+
111
+
112
+ def probe_unverifiable_sources(entry_id: str, claims: dict[str, Any]) -> ProbeResult:
113
+ """S4: Check if sources are unverifiable."""
114
+ falsified = False
115
+ evidence_parts: list[str] = []
116
+
117
+ source = claims.get("source", "")
118
+ if source is None:
119
+ falsified = True
120
+ evidence_parts.append("Source is None")
121
+ elif not isinstance(source, str):
122
+ falsified = True
123
+ evidence_parts.append("Source is not a string")
124
+ elif not source.strip():
125
+ falsified = True
126
+ evidence_parts.append("Source is empty string")
127
+
128
+ return ProbeResult(
129
+ probe_name="unverifiable-sources",
130
+ falsified=falsified,
131
+ evidence="; ".join(evidence_parts) if evidence_parts else "Sources verifiable",
132
+ )
133
+
134
+
135
+ # Default probe set
136
+ DEFAULT_PROBES: list[tuple[str, Callable[[str, dict], ProbeResult]]] = [
137
+ ("missing-evidence", probe_missing_evidence),
138
+ ("contradictory-claims", probe_contradictory_claims),
139
+ ("boundary-values", probe_boundary_values),
140
+ ("unverifiable-sources", probe_unverifiable_sources),
141
+ ]
142
+
143
+
144
+ def run_falsification(
145
+ entry_id: str,
146
+ claims: dict[str, Any],
147
+ probes: list[tuple[str, Callable[[str, dict], ProbeResult]]] | None = None,
148
+ model_client: Any | None = None,
149
+ ) -> FalsificationResult:
150
+ """Run a falsification suite against a claim set.
151
+
152
+ Args:
153
+ entry_id: The entry being tested.
154
+ claims: The claims to falsify.
155
+ probes: List of (name, probe_fn) tuples. Defaults to built-in probes.
156
+ model_client: Optional LLM for deeper probes.
157
+
158
+ Returns:
159
+ FalsificationResult with all probe results.
160
+ """
161
+ probe_list = probes or DEFAULT_PROBES
162
+ results: list[ProbeResult] = []
163
+
164
+ for name, fn in probe_list:
165
+ result = fn(entry_id, claims)
166
+ results.append(result)
167
+
168
+ falsified_by = [r.probe_name for r in results if r.falsified]
169
+ survived = sum(1 for r in results if not r.falsified)
170
+
171
+ return FalsificationResult(
172
+ entry_id=entry_id,
173
+ probe_results=results,
174
+ any_falsified=len(falsified_by) > 0,
175
+ falsified_by=falsified_by,
176
+ survived_count=survived,
177
+ total_count=len(results),
178
+ )
@@ -0,0 +1,169 @@
1
+ """Finance / CPA / Controller intelligence patterns B87-B105."""
2
+
3
+ from .anomalies import JournalEntry, JournalEntryAnomalyScorer, JournalEntryScore
4
+ from .ap_ar import (
5
+ APDuplicateDetector,
6
+ APInvoice,
7
+ APPayment,
8
+ ARAgingPrioritizer,
9
+ ARInvoice,
10
+ AgingBucket,
11
+ AgingReport,
12
+ CollectionAction,
13
+ DuplicateCandidate,
14
+ SoDConflict,
15
+ ThreeWayMatchResult,
16
+ Vendor,
17
+ VendorChangeEvent,
18
+ VendorRisk,
19
+ VendorRiskAnalyzer,
20
+ )
21
+ from .cash import CashFlowItem, CashForecast, CashForecastWeek, ForecastVariance, ForecastVarianceReport, ThirteenWeekCashForecast
22
+ from .close import (
23
+ AccrualEngine,
24
+ AccrualPolicy,
25
+ AccrualProposal,
26
+ BlockedTask,
27
+ CloseDAG,
28
+ ClosePackageAnalyzer,
29
+ ClosePackageInputs,
30
+ ClosePackageResult,
31
+ CloseTask,
32
+ ReversalProposal,
33
+ Variance,
34
+ VarianceExplanation,
35
+ VarianceExplanationMiner,
36
+ )
37
+ from .common import (
38
+ EvidenceItem,
39
+ EvidenceStatus,
40
+ FinanceException,
41
+ MaterialityPolicy,
42
+ Money,
43
+ ReviewGate,
44
+ RiskLevel,
45
+ Severity,
46
+ SourceRef,
47
+ decimalize,
48
+ hash_source_bytes,
49
+ month_bucket,
50
+ normalize_invoice_number,
51
+ normalize_vendor_name,
52
+ stable_exception_id,
53
+ within_tolerance,
54
+ )
55
+ from .construction import (
56
+ ConstructionWIPController,
57
+ FixedAssetRollForward,
58
+ JobCost,
59
+ PayrollReconResult,
60
+ PayrollReconciler,
61
+ RollForwardResult,
62
+ WIPException,
63
+ WIPReport,
64
+ WIPRow,
65
+ )
66
+ from .controls import Control, ControlGap, FinanceProcess, RCMRow, Risk, RiskControlMatrix, RiskControlMatrixCompiler
67
+ from .evidence import EvidenceBinder, EvidenceReport, EvidenceRequest
68
+ from .exceptions import ControllerExceptionQueue, ExceptionCluster, QueueSummary, WaiverResult, make_exception
69
+ from .reconciliations import BankItem, BankMatch, BankMatcher, MatchResult, ReconciliationAccount, ReconciliationTriage, RiskScore
70
+ from .revenue import ASC606DecisionDAG, PerformanceObligation, RevenueContract, RevenueDecision
71
+ from .tax import SalesUseTaxClassifier, TaxClassification, TaxLineResult, TaxPolicy
72
+ from .workpapers import TieOutResult, WorkpaperTieOut
73
+
74
+ __all__ = [
75
+ "JournalEntry",
76
+ "JournalEntryAnomalyScorer",
77
+ "JournalEntryScore",
78
+ "APDuplicateDetector",
79
+ "APInvoice",
80
+ "APPayment",
81
+ "ARAgingPrioritizer",
82
+ "ARInvoice",
83
+ "AgingBucket",
84
+ "AgingReport",
85
+ "CollectionAction",
86
+ "DuplicateCandidate",
87
+ "SoDConflict",
88
+ "ThreeWayMatchResult",
89
+ "Vendor",
90
+ "VendorChangeEvent",
91
+ "VendorRisk",
92
+ "VendorRiskAnalyzer",
93
+ "CashFlowItem",
94
+ "CashForecast",
95
+ "CashForecastWeek",
96
+ "ForecastVariance",
97
+ "ForecastVarianceReport",
98
+ "ThirteenWeekCashForecast",
99
+ "AccrualEngine",
100
+ "AccrualPolicy",
101
+ "AccrualProposal",
102
+ "BlockedTask",
103
+ "CloseDAG",
104
+ "ClosePackageAnalyzer",
105
+ "ClosePackageInputs",
106
+ "ClosePackageResult",
107
+ "CloseTask",
108
+ "ReversalProposal",
109
+ "Variance",
110
+ "VarianceExplanation",
111
+ "VarianceExplanationMiner",
112
+ "EvidenceItem",
113
+ "EvidenceStatus",
114
+ "FinanceException",
115
+ "MaterialityPolicy",
116
+ "Money",
117
+ "ReviewGate",
118
+ "RiskLevel",
119
+ "Severity",
120
+ "SourceRef",
121
+ "decimalize",
122
+ "hash_source_bytes",
123
+ "month_bucket",
124
+ "normalize_invoice_number",
125
+ "normalize_vendor_name",
126
+ "stable_exception_id",
127
+ "within_tolerance",
128
+ "ConstructionWIPController",
129
+ "FixedAssetRollForward",
130
+ "JobCost",
131
+ "PayrollReconResult",
132
+ "PayrollReconciler",
133
+ "RollForwardResult",
134
+ "WIPException",
135
+ "WIPReport",
136
+ "WIPRow",
137
+ "Control",
138
+ "ControlGap",
139
+ "FinanceProcess",
140
+ "RCMRow",
141
+ "Risk",
142
+ "RiskControlMatrix",
143
+ "RiskControlMatrixCompiler",
144
+ "EvidenceBinder",
145
+ "EvidenceReport",
146
+ "EvidenceRequest",
147
+ "ControllerExceptionQueue",
148
+ "ExceptionCluster",
149
+ "QueueSummary",
150
+ "WaiverResult",
151
+ "make_exception",
152
+ "BankItem",
153
+ "BankMatch",
154
+ "BankMatcher",
155
+ "MatchResult",
156
+ "ReconciliationAccount",
157
+ "ReconciliationTriage",
158
+ "RiskScore",
159
+ "ASC606DecisionDAG",
160
+ "PerformanceObligation",
161
+ "RevenueContract",
162
+ "RevenueDecision",
163
+ "SalesUseTaxClassifier",
164
+ "TaxClassification",
165
+ "TaxLineResult",
166
+ "TaxPolicy",
167
+ "TieOutResult",
168
+ "WorkpaperTieOut",
169
+ ]
@@ -0,0 +1,135 @@
1
+ """B90. Journal Entry Anomaly Scoring."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import date, datetime, time
7
+ from decimal import Decimal
8
+ from statistics import median
9
+ from typing import Any, Iterable
10
+
11
+ from .common import ReviewGate, RiskLevel, SourceRef, decimalize
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class JournalEntry:
16
+ id: str
17
+ account: str
18
+ amount: Decimal | int | str
19
+ entry_date: date | str
20
+ user: str
21
+ description: str = ""
22
+ manual: bool = True
23
+ approved_by: str | None = None
24
+ posted_at: datetime | str | None = None
25
+ source_refs: list[SourceRef] = field(default_factory=list)
26
+
27
+ def __post_init__(self) -> None:
28
+ object.__setattr__(self, "amount", decimalize(self.amount))
29
+ if isinstance(self.entry_date, str):
30
+ object.__setattr__(self, "entry_date", datetime.fromisoformat(self.entry_date[:10]).date())
31
+ if isinstance(self.posted_at, str):
32
+ object.__setattr__(self, "posted_at", datetime.fromisoformat(self.posted_at))
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class JournalEntryScore:
37
+ entry: JournalEntry
38
+ score: Decimal
39
+ level: RiskLevel
40
+ reasons: list[str]
41
+ source_refs: list[SourceRef]
42
+ review_gate: ReviewGate = ReviewGate.CONTROLLER_REVIEW
43
+
44
+
45
+ class JournalEntryAnomalyScorer:
46
+ """Weighted JE risk scoring for controller review, not auto-rejection."""
47
+
48
+ def score(
49
+ self,
50
+ entries: Iterable[JournalEntry | dict[str, Any]],
51
+ history: Iterable[JournalEntry | dict[str, Any]] | None = None,
52
+ ) -> list[JournalEntryScore]:
53
+ rows = [_coerce_entry(entry) for entry in entries]
54
+ hist = [_coerce_entry(entry) for entry in (history or [])]
55
+ account_medians = _median_amount_by_account(hist)
56
+ user_accounts = _accounts_by_user(hist)
57
+ scored = [self._score_one(entry, account_medians, user_accounts) for entry in rows]
58
+ return sorted(scored, key=lambda row: (row.score, abs(row.entry.amount), row.entry.id), reverse=True)
59
+
60
+ def _score_one(
61
+ self,
62
+ entry: JournalEntry,
63
+ account_medians: dict[str, Decimal],
64
+ user_accounts: dict[str, set[str]],
65
+ ) -> JournalEntryScore:
66
+ score = Decimal("0")
67
+ reasons: list[str] = []
68
+ amount = abs(entry.amount)
69
+ if entry.manual:
70
+ score += Decimal("100")
71
+ reasons.append("manual entry")
72
+ if not entry.approved_by:
73
+ score += Decimal("300")
74
+ reasons.append("missing approval")
75
+ if _is_round_amount(amount):
76
+ score += Decimal("125")
77
+ reasons.append("round-dollar amount")
78
+ if entry.posted_at and _outside_business_hours(entry.posted_at):
79
+ score += Decimal("175")
80
+ reasons.append("posted outside business hours")
81
+ if entry.entry_date.day >= 28:
82
+ score += Decimal("100")
83
+ reasons.append("period-end posting")
84
+ if not entry.description.strip():
85
+ score += Decimal("150")
86
+ reasons.append("blank description")
87
+ median_amount = account_medians.get(entry.account)
88
+ if median_amount and median_amount > 0 and amount > median_amount * Decimal("3"):
89
+ score += Decimal("250")
90
+ reasons.append("amount exceeds 3x historical median")
91
+ known_accounts = user_accounts.get(entry.user, set())
92
+ if known_accounts and entry.account not in known_accounts:
93
+ score += Decimal("200")
94
+ reasons.append("user unusual for account")
95
+ level = RiskLevel.CRITICAL if score >= 900 else RiskLevel.HIGH if score >= 600 else RiskLevel.MEDIUM if score >= 250 else RiskLevel.LOW
96
+ return JournalEntryScore(entry=entry, score=score, level=level, reasons=reasons, source_refs=list(entry.source_refs))
97
+
98
+
99
+ def _coerce_entry(entry: JournalEntry | dict[str, Any]) -> JournalEntry:
100
+ if isinstance(entry, JournalEntry):
101
+ return entry
102
+ return JournalEntry(
103
+ id=str(entry["id"]),
104
+ account=str(entry.get("account", "")),
105
+ amount=entry.get("amount", 0),
106
+ entry_date=entry.get("entry_date") or entry.get("date"),
107
+ user=str(entry.get("user", "")),
108
+ description=str(entry.get("description", "")),
109
+ manual=bool(entry.get("manual", True)),
110
+ approved_by=entry.get("approved_by"),
111
+ posted_at=entry.get("posted_at"),
112
+ source_refs=list(entry.get("source_refs", [])),
113
+ )
114
+
115
+
116
+ def _median_amount_by_account(entries: list[JournalEntry]) -> dict[str, Decimal]:
117
+ grouped: dict[str, list[Decimal]] = {}
118
+ for entry in entries:
119
+ grouped.setdefault(entry.account, []).append(abs(entry.amount))
120
+ return {account: decimalize(median(values)) for account, values in grouped.items() if values}
121
+
122
+
123
+ def _accounts_by_user(entries: list[JournalEntry]) -> dict[str, set[str]]:
124
+ out: dict[str, set[str]] = {}
125
+ for entry in entries:
126
+ out.setdefault(entry.user, set()).add(entry.account)
127
+ return out
128
+
129
+
130
+ def _is_round_amount(amount: Decimal) -> bool:
131
+ return amount != 0 and amount % Decimal("1000") == 0
132
+
133
+
134
+ def _outside_business_hours(value: datetime) -> bool:
135
+ return value.weekday() >= 5 or value.time() < time(6, 0) or value.time() > time(20, 0)