patchr 0.1.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.
- apps/__init__.py +2 -0
- apps/api/__init__.py +2 -0
- apps/api/main.py +652 -0
- apps/benchmarks/__init__.py +1 -0
- apps/benchmarks/main.py +20 -0
- apps/sandbox/__init__.py +1 -0
- apps/sandbox/main.py +20 -0
- apps/worker/__init__.py +2 -0
- apps/worker/main.py +15 -0
- apps/worker/verify.py +14 -0
- patchr/__init__.py +12 -0
- patchr/sdk/__init__.py +20 -0
- patchr/sdk/client.py +12 -0
- patchr-0.1.0.dist-info/METADATA +137 -0
- patchr-0.1.0.dist-info/RECORD +116 -0
- patchr-0.1.0.dist-info/WHEEL +5 -0
- patchr-0.1.0.dist-info/entry_points.txt +5 -0
- patchr-0.1.0.dist-info/licenses/LICENSE +17 -0
- patchr-0.1.0.dist-info/top_level.txt +3 -0
- picux/__init__.py +6 -0
- picux/agents/__init__.py +5 -0
- picux/agents/registry.py +204 -0
- picux/api/__init__.py +5 -0
- picux/api/service.py +5075 -0
- picux/audit/__init__.py +31 -0
- picux/audit/activity.py +97 -0
- picux/audit/observability.py +55 -0
- picux/audit/verification/__init__.py +21 -0
- picux/audit/verification/ledger.py +633 -0
- picux/benchmarks/__init__.py +5 -0
- picux/benchmarks/local.py +286 -0
- picux/config.py +140 -0
- picux/contracts/__init__.py +22 -0
- picux/contracts/handshake.py +122 -0
- picux/contracts/integration.py +385 -0
- picux/contracts/openapi.py +187 -0
- picux/contracts/protocol_map.py +152 -0
- picux/contracts/routes.py +980 -0
- picux/contracts/schema_catalog.py +125 -0
- picux/core/__init__.py +17 -0
- picux/core/models.py +148 -0
- picux/core/router.py +131 -0
- picux/core/runtime.py +42 -0
- picux/core/state_machine.py +38 -0
- picux/domains/__init__.py +2 -0
- picux/domains/bridge/HostRun.py +1104 -0
- picux/domains/bridge/__init__.py +6 -0
- picux/domains/bridge/engine.py +345 -0
- picux/domains/hunt/__init__.py +6 -0
- picux/domains/hunt/engine.py +307 -0
- picux/domains/hunt/models.py +88 -0
- picux/domains/pay/__init__.py +16 -0
- picux/domains/pay/adapters.py +607 -0
- picux/domains/pay/engine.py +950 -0
- picux/domains/pay/models.py +95 -0
- picux/domains/proxy/__init__.py +5 -0
- picux/domains/proxy/engine.py +466 -0
- picux/domains/resolve/__init__.py +5 -0
- picux/domains/resolve/engine.py +546 -0
- picux/orchestrator/__init__.py +3 -0
- picux/orchestrator/engine.py +2840 -0
- picux/portals/__init__.py +17 -0
- picux/portals/templates.py +272 -0
- picux/protocols/__init__.py +1 -0
- picux/protocols/a2a/__init__.py +6 -0
- picux/protocols/a2a/client.py +51 -0
- picux/protocols/a2a/envelope.py +132 -0
- picux/protocols/mcp/__init__.py +7 -0
- picux/protocols/mcp/client.py +69 -0
- picux/protocols/mcp/contract.py +67 -0
- picux/protocols/mcp/server.py +76 -0
- picux/sandbox/__init__.py +6 -0
- picux/sandbox/midnight_arbitrage.py +215 -0
- picux/sandbox/models.py +90 -0
- picux/sdk/__init__.py +13 -0
- picux/sdk/client.py +768 -0
- picux/sdk/external.py +245 -0
- picux/security/__init__.py +18 -0
- picux/security/auth.py +86 -0
- picux/security/config_validator.py +58 -0
- picux/security/policy.py +158 -0
- picux/security/secrets.py +144 -0
- picux/signals/__init__.py +1 -0
- picux/signals/community/__init__.py +24 -0
- picux/signals/community/adapters/__init__.py +7 -0
- picux/signals/community/adapters/reddit.py +37 -0
- picux/signals/community/adapters/shopify.py +23 -0
- picux/signals/community/adapters/web.py +23 -0
- picux/signals/community/disambiguation.py +51 -0
- picux/signals/community/intake.py +227 -0
- picux/signals/community/models.py +102 -0
- picux/signals/community/rules.py +91 -0
- picux/signals/community/scoring.py +64 -0
- picux/storage/__init__.py +41 -0
- picux/storage/agents.py +50 -0
- picux/storage/cases.py +440 -0
- picux/storage/channels.py +476 -0
- picux/storage/connectors.py +411 -0
- picux/storage/envelopes.py +137 -0
- picux/storage/escrows.py +168 -0
- picux/storage/events.py +989 -0
- picux/storage/keyspace.py +60 -0
- picux/storage/mandates.py +107 -0
- picux/storage/portals.py +222 -0
- picux/storage/postgres.py +2049 -0
- picux/storage/providers.py +148 -0
- picux/storage/proxy.py +231 -0
- picux/storage/receipts.py +131 -0
- picux/storage/signals.py +147 -0
- picux/storage/tasks.py +179 -0
- picux/tools/__init__.py +11 -0
- picux/tools/shared.py +2048 -0
- picux/verification/__init__.py +5 -0
- picux/verification/rollout.py +183 -0
- picux/workflows/__init__.py +5 -0
- picux/workflows/templates.py +74 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .models import HuntCriteria, HuntOffer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HuntDomain:
|
|
11
|
+
"""Deterministic discovery and ranking for HUNT telemetry."""
|
|
12
|
+
|
|
13
|
+
def discover(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
14
|
+
criteria = HuntCriteria.fromObj(payload.get("criteria") if isinstance(payload.get("criteria"), dict) else payload)
|
|
15
|
+
offers = [
|
|
16
|
+
HuntOffer.fromObj(item)
|
|
17
|
+
for item in payload.get("offers", payload.get("telemetry", []))
|
|
18
|
+
if isinstance(item, dict)
|
|
19
|
+
]
|
|
20
|
+
candidates = [self.scoreOffer(offer, criteria) for offer in offers]
|
|
21
|
+
candidates.sort(key=lambda item: (item["eligible"], item["score"], -item["netUsd"]), reverse=True)
|
|
22
|
+
selected = next((item for item in candidates if item["eligible"]), None)
|
|
23
|
+
sourceResponse = self.sourceResponse(payload, offers, candidates, selected)
|
|
24
|
+
handoff = self.handoff(payload)
|
|
25
|
+
aggregate = self.sourceAggregate(payload, offers, candidates, selected, sourceResponse, handoff)
|
|
26
|
+
stream = self.streamEvents(aggregate)
|
|
27
|
+
return {
|
|
28
|
+
"ok": bool(selected),
|
|
29
|
+
"criteria": criteria.toMap(),
|
|
30
|
+
"selected": selected or {},
|
|
31
|
+
"candidates": candidates,
|
|
32
|
+
"sourceResponse": sourceResponse,
|
|
33
|
+
"sourceAggregate": aggregate,
|
|
34
|
+
"aggregate": aggregate,
|
|
35
|
+
"stream": stream,
|
|
36
|
+
"handoff": handoff,
|
|
37
|
+
"response": {
|
|
38
|
+
"status": "selected" if selected else sourceResponse["status"],
|
|
39
|
+
"message": self.responseMessage(sourceResponse, selected),
|
|
40
|
+
"nextAction": "returnSelectedOption" if selected else "requestBetterSourceTelemetry",
|
|
41
|
+
"delivery": aggregate["delivery"],
|
|
42
|
+
},
|
|
43
|
+
"trace": self.trace(candidates),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
def scoreOffer(self, offer: HuntOffer | dict[str, Any], criteria: HuntCriteria | dict[str, Any]) -> dict[str, Any]:
|
|
47
|
+
item = offer if isinstance(offer, HuntOffer) else HuntOffer.fromObj(offer)
|
|
48
|
+
crit = criteria if isinstance(criteria, HuntCriteria) else HuntCriteria.fromObj(criteria)
|
|
49
|
+
reasons = self.reasons(item, crit)
|
|
50
|
+
latencyPenalty = min(20.0, item.latencyMs / 50.0) if item.latencyMs > 0 else 0.0
|
|
51
|
+
feePenalty = min(30.0, item.feesUsd)
|
|
52
|
+
stockBoost = min(10.0, max(0, item.stock) / 10.0)
|
|
53
|
+
score = round(max(0.0, item.discountPct + stockBoost - latencyPenalty - feePenalty), 3)
|
|
54
|
+
return {
|
|
55
|
+
**item.toMap(),
|
|
56
|
+
"eligible": not reasons,
|
|
57
|
+
"reasons": reasons,
|
|
58
|
+
"score": score,
|
|
59
|
+
"povRules": self.povRules(item, crit, reasons),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def reasons(self, offer: HuntOffer, criteria: HuntCriteria) -> list[str]:
|
|
63
|
+
reasons: list[str] = []
|
|
64
|
+
if criteria.itemId and offer.itemId != criteria.itemId:
|
|
65
|
+
reasons.append("itemMismatch")
|
|
66
|
+
if offer.discountPct < criteria.minDiscountPct:
|
|
67
|
+
reasons.append("discountBelowThreshold")
|
|
68
|
+
if criteria.maxNetUsd > 0 and offer.netUsd > criteria.maxNetUsd:
|
|
69
|
+
reasons.append("overBudget")
|
|
70
|
+
if offer.stock < criteria.minStock:
|
|
71
|
+
reasons.append("stockLimit")
|
|
72
|
+
if criteria.region and offer.region != criteria.region:
|
|
73
|
+
reasons.append("regionMismatch")
|
|
74
|
+
if criteria.maxLatencyMs > 0 and offer.latencyMs > criteria.maxLatencyMs:
|
|
75
|
+
reasons.append("latencyExceeded")
|
|
76
|
+
if offer.feesUsd > 0:
|
|
77
|
+
reasons.append("feeLeak")
|
|
78
|
+
return reasons
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def povRules(offer: HuntOffer, criteria: HuntCriteria, reasons: list[str]) -> list[str]:
|
|
82
|
+
rules = []
|
|
83
|
+
if offer.stock >= criteria.minStock:
|
|
84
|
+
rules.append("inventoryVerified")
|
|
85
|
+
if criteria.minDiscountPct > 0 and offer.discountPct >= criteria.minDiscountPct:
|
|
86
|
+
rules.append(f"priceArbitrage{int(criteria.minDiscountPct)}")
|
|
87
|
+
if criteria.region and offer.region == criteria.region:
|
|
88
|
+
rules.append("regionMatched")
|
|
89
|
+
if offer.feesUsd <= 0:
|
|
90
|
+
rules.append("hiddenFeeClear")
|
|
91
|
+
if not reasons:
|
|
92
|
+
rules.append("huntEligible")
|
|
93
|
+
return rules
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def trace(candidates: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
97
|
+
events = []
|
|
98
|
+
for idx, candidate in enumerate(candidates, start=1):
|
|
99
|
+
events.append(
|
|
100
|
+
{
|
|
101
|
+
"step": idx,
|
|
102
|
+
"phase": "hunt",
|
|
103
|
+
"name": "candidateRanked" if candidate["eligible"] else "candidatePruned",
|
|
104
|
+
"status": "selected" if idx == 1 and candidate["eligible"] else "observed",
|
|
105
|
+
"sourceId": candidate["sourceId"],
|
|
106
|
+
"score": candidate["score"],
|
|
107
|
+
"reasons": candidate["reasons"],
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
return events
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def sourceResponse(payload: dict[str, Any], offers: list[HuntOffer], candidates: list[dict[str, Any]], selected: dict[str, Any] | None) -> dict[str, Any]:
|
|
114
|
+
sourceTelemetry = payload.get("sourceTelemetry", {}) if isinstance(payload.get("sourceTelemetry"), dict) else {}
|
|
115
|
+
attempts = sourceTelemetry.get("attempts", []) if isinstance(sourceTelemetry.get("attempts"), list) else []
|
|
116
|
+
targetSources = [str(item) for item in sourceTelemetry.get("targetSources", []) if str(item)]
|
|
117
|
+
attemptedSources = [str(item.get("source", "")) for item in attempts if isinstance(item, dict) and item.get("source")]
|
|
118
|
+
failedSources = [str(item.get("source", "")) for item in attempts if isinstance(item, dict) and item.get("source") and not item.get("ok")]
|
|
119
|
+
successfulSources = [str(item.get("source", "")) for item in attempts if isinstance(item, dict) and item.get("source") and item.get("ok")]
|
|
120
|
+
listingSources = [str(item.get("source", "")) for item in attempts if isinstance(item, dict) and item.get("source") and int(item.get("listingCount", 0) or 0) > 0]
|
|
121
|
+
resultSources = [str(offer.meta.get("source", offer.sourceId) or offer.sourceId) for offer in offers]
|
|
122
|
+
listingCount = sum(int(item.get("listingCount", 0) or 0) for item in attempts if isinstance(item, dict))
|
|
123
|
+
filteredListingCount = sum(int(item.get("filteredListingCount", 0) or 0) for item in attempts if isinstance(item, dict))
|
|
124
|
+
marketplaceSet = sourceTelemetry.get("marketplaceSet", {}) if isinstance(sourceTelemetry.get("marketplaceSet"), dict) else {}
|
|
125
|
+
budget = sourceTelemetry.get("budget", {}) if isinstance(sourceTelemetry.get("budget"), dict) else {}
|
|
126
|
+
fx = sourceTelemetry.get("fx", {}) if isinstance(sourceTelemetry.get("fx"), dict) else {}
|
|
127
|
+
if selected:
|
|
128
|
+
status = "selected"
|
|
129
|
+
elif offers or candidates:
|
|
130
|
+
status = "noEligibleCandidate"
|
|
131
|
+
elif sourceTelemetry.get("networkAttempted"):
|
|
132
|
+
status = "searchedNoSourceBoundOffer"
|
|
133
|
+
else:
|
|
134
|
+
status = "needsSourceTelemetry"
|
|
135
|
+
return {
|
|
136
|
+
"status": status,
|
|
137
|
+
"adapter": sourceTelemetry.get("adapter", ""),
|
|
138
|
+
"rendered": sourceTelemetry.get("rendered", {}),
|
|
139
|
+
"marketplaceSet": marketplaceSet,
|
|
140
|
+
"budget": budget,
|
|
141
|
+
"fx": fx,
|
|
142
|
+
"targetSources": targetSources,
|
|
143
|
+
"attemptedSources": list(dict.fromkeys(attemptedSources)),
|
|
144
|
+
"successfulSources": list(dict.fromkeys(successfulSources)),
|
|
145
|
+
"failedSources": list(dict.fromkeys(failedSources)),
|
|
146
|
+
"listingSources": list(dict.fromkeys(listingSources)),
|
|
147
|
+
"resultSources": list(dict.fromkeys(resultSources)),
|
|
148
|
+
"networkAttempted": bool(sourceTelemetry.get("networkAttempted")),
|
|
149
|
+
"offerCount": int(sourceTelemetry.get("offerCount", len(offers)) or 0),
|
|
150
|
+
"listingCount": listingCount,
|
|
151
|
+
"filteredListingCount": filteredListingCount,
|
|
152
|
+
"candidateCount": len(candidates),
|
|
153
|
+
"selectedSourceId": str(selected.get("sourceId", "") if selected else ""),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def responseMessage(sourceResponse: dict[str, Any], selected: dict[str, Any] | None) -> str:
|
|
158
|
+
if selected:
|
|
159
|
+
return f"HUNT selected {selected.get('sourceId', '')} from source-bound telemetry."
|
|
160
|
+
targets = ", ".join(sourceResponse.get("targetSources", [])[:6])
|
|
161
|
+
if sourceResponse.get("networkAttempted"):
|
|
162
|
+
return f"HUNT searched {targets} and returned no eligible source-bound offer."
|
|
163
|
+
return f"HUNT prepared source targets {targets}; live source telemetry is required before ranking."
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def handoff(payload: dict[str, Any]) -> dict[str, str]:
|
|
167
|
+
raw = payload.get("handoff", payload.get("handsoff", {}))
|
|
168
|
+
raw = raw if isinstance(raw, dict) else {}
|
|
169
|
+
explicitSource = raw.get("source") or payload.get("handoffSource") or payload.get("handsoffSource")
|
|
170
|
+
source = str(explicitSource or payload.get("clientId", "orchestrator") or "orchestrator")
|
|
171
|
+
explicitTarget = raw.get("target") or payload.get("handoffTarget") or payload.get("handsoffTarget")
|
|
172
|
+
target = str(explicitTarget or (source if explicitSource else "orchestrator") or "orchestrator")
|
|
173
|
+
if target in {"source", "handoffSource", "handsoffSource"}:
|
|
174
|
+
target = source
|
|
175
|
+
return {
|
|
176
|
+
"source": source,
|
|
177
|
+
"target": target,
|
|
178
|
+
"channel": str(raw.get("channel", payload.get("handoffChannel", payload.get("channel", "api"))) or "api"),
|
|
179
|
+
"mode": str(raw.get("mode", payload.get("handoffMode", "return")) or "return"),
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def sourceAggregate(
|
|
184
|
+
payload: dict[str, Any],
|
|
185
|
+
offers: list[HuntOffer],
|
|
186
|
+
candidates: list[dict[str, Any]],
|
|
187
|
+
selected: dict[str, Any] | None,
|
|
188
|
+
sourceResponse: dict[str, Any],
|
|
189
|
+
handoff: dict[str, str],
|
|
190
|
+
) -> dict[str, Any]:
|
|
191
|
+
sourceTelemetry = payload.get("sourceTelemetry", {}) if isinstance(payload.get("sourceTelemetry"), dict) else {}
|
|
192
|
+
attempts = sourceTelemetry.get("attempts", []) if isinstance(sourceTelemetry.get("attempts"), list) else []
|
|
193
|
+
observations = sourceTelemetry.get("observations", []) if isinstance(sourceTelemetry.get("observations"), list) else []
|
|
194
|
+
sources = HuntDomain.aggregateSources(sourceResponse, attempts, offers)
|
|
195
|
+
deliveryMode = handoff.get("mode", "return")
|
|
196
|
+
aggregate = {
|
|
197
|
+
"kind": "huntSourceAggregate",
|
|
198
|
+
"aggregateId": "",
|
|
199
|
+
"status": sourceResponse.get("status", ""),
|
|
200
|
+
"handoff": handoff,
|
|
201
|
+
"delivery": {
|
|
202
|
+
"mode": deliveryMode,
|
|
203
|
+
"target": handoff.get("target", "orchestrator"),
|
|
204
|
+
"channel": handoff.get("channel", "api"),
|
|
205
|
+
"status": "streamReady" if deliveryMode == "stream" else "returned",
|
|
206
|
+
},
|
|
207
|
+
"marketplaceSet": sourceResponse.get("marketplaceSet", {}),
|
|
208
|
+
"budget": sourceResponse.get("budget", {}),
|
|
209
|
+
"fx": sourceResponse.get("fx", {}),
|
|
210
|
+
"adapter": sourceResponse.get("adapter", ""),
|
|
211
|
+
"rendered": sourceResponse.get("rendered", {}),
|
|
212
|
+
"sources": sources,
|
|
213
|
+
"observations": observations[:24],
|
|
214
|
+
"offers": [offer.toMap() for offer in offers[:24]],
|
|
215
|
+
"candidates": candidates[:24],
|
|
216
|
+
"result": {
|
|
217
|
+
"selected": selected or {},
|
|
218
|
+
"selectedSourceId": sourceResponse.get("selectedSourceId", ""),
|
|
219
|
+
"candidateCount": len(candidates),
|
|
220
|
+
"offerCount": sourceResponse.get("offerCount", 0),
|
|
221
|
+
"nextAction": "returnSelectedOption" if selected else "requestBetterSourceTelemetry",
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
aggregate["aggregateId"] = "huntAgg_" + hashlib.sha256(json.dumps(aggregate, sort_keys=True, default=str).encode("utf-8")).hexdigest()[:24]
|
|
225
|
+
return aggregate
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def aggregateSources(sourceResponse: dict[str, Any], attempts: list[Any], offers: list[HuntOffer]) -> list[dict[str, Any]]:
|
|
229
|
+
records: dict[str, dict[str, Any]] = {}
|
|
230
|
+
for source in sourceResponse.get("targetSources", []):
|
|
231
|
+
sourceId = str(source)
|
|
232
|
+
if sourceId:
|
|
233
|
+
records[sourceId] = {"source": sourceId, "status": "targeted", "ok": False, "attempted": False}
|
|
234
|
+
for attempt in attempts:
|
|
235
|
+
if not isinstance(attempt, dict):
|
|
236
|
+
continue
|
|
237
|
+
sourceId = str(attempt.get("source", "") or "")
|
|
238
|
+
if not sourceId:
|
|
239
|
+
continue
|
|
240
|
+
records[sourceId] = {
|
|
241
|
+
"source": sourceId,
|
|
242
|
+
"status": "read" if attempt.get("ok") else "failed",
|
|
243
|
+
"ok": bool(attempt.get("ok")),
|
|
244
|
+
"attempted": True,
|
|
245
|
+
"url": attempt.get("url", ""),
|
|
246
|
+
"statusCode": attempt.get("statusCode", 0),
|
|
247
|
+
"title": attempt.get("title", ""),
|
|
248
|
+
"matched": bool(attempt.get("matched")),
|
|
249
|
+
"listingCount": attempt.get("listingCount", 0),
|
|
250
|
+
"search": attempt.get("search", {}) if isinstance(attempt.get("search"), dict) else {},
|
|
251
|
+
"error": attempt.get("error", ""),
|
|
252
|
+
}
|
|
253
|
+
for offer in offers:
|
|
254
|
+
sourceId = str(offer.meta.get("source", offer.sourceId) or offer.sourceId)
|
|
255
|
+
if sourceId and sourceId not in records:
|
|
256
|
+
records[sourceId] = {"source": sourceId, "status": "candidateOnly", "ok": True, "attempted": False}
|
|
257
|
+
if sourceId:
|
|
258
|
+
records[sourceId]["offerCount"] = int(records[sourceId].get("offerCount", 0) or 0) + 1
|
|
259
|
+
records[sourceId]["ok"] = True
|
|
260
|
+
return list(records.values())
|
|
261
|
+
|
|
262
|
+
@staticmethod
|
|
263
|
+
def streamEvents(aggregate: dict[str, Any]) -> list[dict[str, Any]]:
|
|
264
|
+
events = [
|
|
265
|
+
{
|
|
266
|
+
"seq": 1,
|
|
267
|
+
"phase": "hunt",
|
|
268
|
+
"name": "sourceAggregate.started",
|
|
269
|
+
"status": "started",
|
|
270
|
+
"target": aggregate["delivery"]["target"],
|
|
271
|
+
"data": {"aggregateId": aggregate["aggregateId"], "sourceCount": len(aggregate.get("sources", []))},
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
for source in aggregate.get("sources", []):
|
|
275
|
+
name = "source.read" if source.get("ok") else ("source.failed" if source.get("attempted") else "source.targeted")
|
|
276
|
+
events.append(
|
|
277
|
+
{
|
|
278
|
+
"seq": len(events) + 1,
|
|
279
|
+
"phase": "hunt",
|
|
280
|
+
"name": name,
|
|
281
|
+
"status": source.get("status", ""),
|
|
282
|
+
"target": aggregate["delivery"]["target"],
|
|
283
|
+
"data": source,
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
for candidate in aggregate.get("candidates", []):
|
|
287
|
+
events.append(
|
|
288
|
+
{
|
|
289
|
+
"seq": len(events) + 1,
|
|
290
|
+
"phase": "hunt",
|
|
291
|
+
"name": "candidate.selected" if candidate.get("eligible") else "candidate.pruned",
|
|
292
|
+
"status": "selected" if candidate.get("eligible") and candidate.get("sourceId") == aggregate["result"].get("selectedSourceId") else "observed",
|
|
293
|
+
"target": aggregate["delivery"]["target"],
|
|
294
|
+
"data": {"sourceId": candidate.get("sourceId", ""), "score": candidate.get("score", 0), "reasons": candidate.get("reasons", [])},
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
events.append(
|
|
298
|
+
{
|
|
299
|
+
"seq": len(events) + 1,
|
|
300
|
+
"phase": "hunt",
|
|
301
|
+
"name": "sourceAggregate.delivered",
|
|
302
|
+
"status": aggregate["delivery"]["status"],
|
|
303
|
+
"target": aggregate["delivery"]["target"],
|
|
304
|
+
"data": {"aggregateId": aggregate["aggregateId"], "result": aggregate["result"], "delivery": aggregate["delivery"]},
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
return events
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class HuntOffer:
|
|
9
|
+
sourceId: str
|
|
10
|
+
itemId: str
|
|
11
|
+
listUsd: float
|
|
12
|
+
offerUsd: float
|
|
13
|
+
feesUsd: float = 0.0
|
|
14
|
+
stock: int = 0
|
|
15
|
+
region: str = ""
|
|
16
|
+
latencyMs: int = 0
|
|
17
|
+
meta: dict[str, Any] = field(default_factory=dict)
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def fromObj(cls, value: dict[str, Any]) -> "HuntOffer":
|
|
21
|
+
return cls(
|
|
22
|
+
sourceId=str(value.get("sourceId", value.get("vendorId", "")) or ""),
|
|
23
|
+
itemId=str(value.get("itemId", value.get("sku", "")) or ""),
|
|
24
|
+
listUsd=float(value.get("listUsd", value.get("listPriceUsd", 0.0)) or 0.0),
|
|
25
|
+
offerUsd=float(value.get("offerUsd", value.get("priceUsd", 0.0)) or 0.0),
|
|
26
|
+
feesUsd=float(value.get("feesUsd", value.get("hiddenFeeUsd", 0.0)) or 0.0),
|
|
27
|
+
stock=int(value.get("stock", value.get("qty", 0)) or 0),
|
|
28
|
+
region=str(value.get("region", "") or ""),
|
|
29
|
+
latencyMs=int(value.get("latencyMs", 0) or 0),
|
|
30
|
+
meta=value.get("meta", {}) if isinstance(value.get("meta", {}), dict) else {},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def netUsd(self) -> float:
|
|
35
|
+
return round(max(0.0, self.offerUsd + self.feesUsd), 2)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def discountPct(self) -> float:
|
|
39
|
+
if self.listUsd <= 0:
|
|
40
|
+
return 0.0
|
|
41
|
+
return round(((self.listUsd - self.offerUsd) / self.listUsd) * 100, 2)
|
|
42
|
+
|
|
43
|
+
def toMap(self) -> dict[str, Any]:
|
|
44
|
+
return {
|
|
45
|
+
"sourceId": self.sourceId,
|
|
46
|
+
"itemId": self.itemId,
|
|
47
|
+
"listUsd": round(self.listUsd, 2),
|
|
48
|
+
"offerUsd": round(self.offerUsd, 2),
|
|
49
|
+
"feesUsd": round(self.feesUsd, 2),
|
|
50
|
+
"netUsd": self.netUsd,
|
|
51
|
+
"discountPct": self.discountPct,
|
|
52
|
+
"stock": self.stock,
|
|
53
|
+
"region": self.region,
|
|
54
|
+
"latencyMs": self.latencyMs,
|
|
55
|
+
"meta": self.meta,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class HuntCriteria:
|
|
61
|
+
itemId: str = ""
|
|
62
|
+
minDiscountPct: float = 0.0
|
|
63
|
+
maxNetUsd: float = 0.0
|
|
64
|
+
minStock: int = 1
|
|
65
|
+
region: str = ""
|
|
66
|
+
maxLatencyMs: int = 0
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def fromObj(cls, value: dict[str, Any] | None) -> "HuntCriteria":
|
|
70
|
+
value = value or {}
|
|
71
|
+
return cls(
|
|
72
|
+
itemId=str(value.get("itemId", value.get("sku", "")) or ""),
|
|
73
|
+
minDiscountPct=float(value.get("minDiscountPct", 0.0) or 0.0),
|
|
74
|
+
maxNetUsd=float(value.get("maxNetUsd", 0.0) or 0.0),
|
|
75
|
+
minStock=int(value.get("minStock", 1) or 1),
|
|
76
|
+
region=str(value.get("region", "") or ""),
|
|
77
|
+
maxLatencyMs=int(value.get("maxLatencyMs", 0) or 0),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def toMap(self) -> dict[str, Any]:
|
|
81
|
+
return {
|
|
82
|
+
"itemId": self.itemId,
|
|
83
|
+
"minDiscountPct": round(self.minDiscountPct, 2),
|
|
84
|
+
"maxNetUsd": round(self.maxNetUsd, 2),
|
|
85
|
+
"minStock": self.minStock,
|
|
86
|
+
"region": self.region,
|
|
87
|
+
"maxLatencyMs": self.maxLatencyMs,
|
|
88
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""PAY domain primitives."""
|
|
2
|
+
|
|
3
|
+
from .adapters import PayRailAdapter, PayRailAdapterContext
|
|
4
|
+
from .engine import PayDomain
|
|
5
|
+
from .models import Money, ProofOfValue, SettlementAdapter, SettlementAdapterSpec, SettlementRequest
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Money",
|
|
9
|
+
"PayDomain",
|
|
10
|
+
"PayRailAdapter",
|
|
11
|
+
"PayRailAdapterContext",
|
|
12
|
+
"ProofOfValue",
|
|
13
|
+
"SettlementAdapter",
|
|
14
|
+
"SettlementAdapterSpec",
|
|
15
|
+
"SettlementRequest",
|
|
16
|
+
]
|