solvi 0.8.0
Browse files- app.js +2 -2
- solvi_docs.py +5 -5
- usecases/bol.js +1 -1
- usecases/dpa.js +1 -1
- usecases/insurance.js +2 -2
- usecases/invoice.js +1 -1
- usecases/lease.js +1 -1
- usecases/offer.js +1 -1
- usecases/receipt.js +1 -1
- usecases/support.js +1 -1
app.js
CHANGED
|
@@ -222,10 +222,10 @@ async function loadPython() {
|
|
| 222 |
$("py-line").textContent = "Python (Pyodide) + solvi: installing numpy and solvi…";
|
| 223 |
await py.loadPackage(["numpy", "micropip"]);
|
| 224 |
try { // solvi's own code needs only numpy at import time: skip scipy (another ~10 MB)
|
| 225 |
-
await py.runPythonAsync("import micropip\nawait micropip.install('solvi
|
| 226 |
} catch (e) {
|
| 227 |
console.warn("solvi without dependencies failed, installing with dependencies", e);
|
| 228 |
-
await py.runPythonAsync("import micropip\nawait micropip.install('solvi
|
| 229 |
}
|
| 230 |
const src = await (await fetch(new URL("solvi_docs.py", import.meta.url))).text();
|
| 231 |
py.FS.writeFile("/home/pyodide/solvi_docs.py", src);
|
|
|
|
| 222 |
$("py-line").textContent = "Python (Pyodide) + solvi: installing numpy and solvi…";
|
| 223 |
await py.loadPackage(["numpy", "micropip"]);
|
| 224 |
try { // solvi's own code needs only numpy at import time: skip scipy (another ~10 MB)
|
| 225 |
+
await py.runPythonAsync("import micropip\nawait micropip.install('solvi==0.8.0', deps=False)\nimport solvi");
|
| 226 |
} catch (e) {
|
| 227 |
console.warn("solvi without dependencies failed, installing with dependencies", e);
|
| 228 |
+
await py.runPythonAsync("import micropip\nawait micropip.install('solvi==0.8.0')\nimport solvi");
|
| 229 |
}
|
| 230 |
const src = await (await fetch(new URL("solvi_docs.py", import.meta.url))).text();
|
| 231 |
py.FS.writeFile("/home/pyodide/solvi_docs.py", src);
|
solvi_docs.py
CHANGED
|
@@ -212,8 +212,8 @@ def run(payload_json):
|
|
| 212 |
res = system.ask(init)
|
| 213 |
except Exception as e: # noqa: BLE001
|
| 214 |
return json.dumps({"error": f"{type(e).__name__}: {e}", "traceback": traceback.format_exc(limit=4)})
|
| 215 |
-
rep = res.trace.replay(
|
| 216 |
-
_last.update(cat=cat, res=res)
|
| 217 |
by = {r.name: r for r in res.trace.records}
|
| 218 |
kinds = {n: part.kind for n, part in cat.parts.items()}
|
| 219 |
answers = []
|
|
@@ -234,7 +234,7 @@ def run(payload_json):
|
|
| 234 |
return json.dumps({
|
| 235 |
"answers": answers, "records": records, "flow": flow, "flow_text": str(res.flow),
|
| 236 |
"not_taken": sorted(res.flow.skipped.items()), "skipped_at_run": [list(x) for x in res.trace.skipped],
|
| 237 |
-
"computed_state": res.
|
| 238 |
"replay": {"ok": rep["ok"], "steps": rep["steps"], "mismatches": [[str(x) for x in m] for m in rep["mismatches"]]},
|
| 239 |
"ms": res.ms, "n_parts": len(cat.parts) + len(cat.rules), "auto": auto,
|
| 240 |
}, default=str)
|
|
@@ -256,7 +256,7 @@ def tamper():
|
|
| 256 |
"""Simulate a careful forger: change one computed fact in a copy of the trace and re-hash that record, then replay."""
|
| 257 |
if not _last:
|
| 258 |
return json.dumps({"error": "run the use case first"})
|
| 259 |
-
|
| 260 |
tr = copy.deepcopy(res.trace)
|
| 261 |
target = None
|
| 262 |
for kind in ("fn", "extract", "check"):
|
|
@@ -272,7 +272,7 @@ def tamper():
|
|
| 272 |
old = target.value
|
| 273 |
target.value = _tampered(old)
|
| 274 |
target.hash = vhash(target.body()) # re-hash the edited record so it looks self-consistent
|
| 275 |
-
rep = tr.replay(
|
| 276 |
return json.dumps({"name": target.name, "step": target.step, "from": _r(old), "to": _r(target.value),
|
| 277 |
"replay": {"ok": rep["ok"], "steps": rep["steps"],
|
| 278 |
"mismatches": [[str(x) for x in m] for m in rep["mismatches"]]}}, default=str)
|
|
|
|
| 212 |
res = system.ask(init)
|
| 213 |
except Exception as e: # noqa: BLE001
|
| 214 |
return json.dumps({"error": f"{type(e).__name__}: {e}", "traceback": traceback.format_exc(limit=4)})
|
| 215 |
+
rep = res.trace.replay(system) # with the System, replay also checks the stored answers
|
| 216 |
+
_last.update(cat=cat, res=res, system=system)
|
| 217 |
by = {r.name: r for r in res.trace.records}
|
| 218 |
kinds = {n: part.kind for n, part in cat.parts.items()}
|
| 219 |
answers = []
|
|
|
|
| 234 |
return json.dumps({
|
| 235 |
"answers": answers, "records": records, "flow": flow, "flow_text": str(res.flow),
|
| 236 |
"not_taken": sorted(res.flow.skipped.items()), "skipped_at_run": [list(x) for x in res.trace.skipped],
|
| 237 |
+
"computed_state": res.state_text(), "init_hash": res.trace.init_hash,
|
| 238 |
"replay": {"ok": rep["ok"], "steps": rep["steps"], "mismatches": [[str(x) for x in m] for m in rep["mismatches"]]},
|
| 239 |
"ms": res.ms, "n_parts": len(cat.parts) + len(cat.rules), "auto": auto,
|
| 240 |
}, default=str)
|
|
|
|
| 256 |
"""Simulate a careful forger: change one computed fact in a copy of the trace and re-hash that record, then replay."""
|
| 257 |
if not _last:
|
| 258 |
return json.dumps({"error": "run the use case first"})
|
| 259 |
+
res, system = _last["res"], _last["system"]
|
| 260 |
tr = copy.deepcopy(res.trace)
|
| 261 |
target = None
|
| 262 |
for kind in ("fn", "extract", "check"):
|
|
|
|
| 272 |
old = target.value
|
| 273 |
target.value = _tampered(old)
|
| 274 |
target.hash = vhash(target.body()) # re-hash the edited record so it looks self-consistent
|
| 275 |
+
rep = tr.replay(system)
|
| 276 |
return json.dumps({"name": target.name, "step": target.step, "from": _r(old), "to": _r(target.value),
|
| 277 |
"replay": {"ok": rep["ok"], "steps": rep["steps"],
|
| 278 |
"mismatches": [[str(x) for x in m] for m in rep["mismatches"]]}}, default=str)
|
usecases/bol.js
CHANGED
|
@@ -34,7 +34,7 @@ def release(consignee, containers):
|
|
| 34 |
return "release" if found(containers) else "hold"
|
| 35 |
|
| 36 |
QUESTIONS = [
|
| 37 |
-
Question("release", "Release the shipment?", Answer.choice(["release", "hold"]),
|
| 38 |
Question("insurance_by", "Who arranges cargo insurance?", Answer.choice(["buyer", "seller"])),
|
| 39 |
]
|
| 40 |
`,
|
|
|
|
| 34 |
return "release" if found(containers) else "hold"
|
| 35 |
|
| 36 |
QUESTIONS = [
|
| 37 |
+
Question("release", "Release the shipment?", Answer.choice(["release", "hold"]), requires=["port_allowed"]),
|
| 38 |
Question("insurance_by", "Who arranges cargo insurance?", Answer.choice(["buyer", "seller"])),
|
| 39 |
]
|
| 40 |
`,
|
usecases/dpa.js
CHANGED
|
@@ -35,7 +35,7 @@ def transfers_safeguarded(transfer_mechanism):
|
|
| 35 |
|
| 36 |
QUESTIONS = [
|
| 37 |
Question("approve", "Approve the vendor?", Answer.choice(["approve", "review", "reject"]),
|
| 38 |
-
|
| 39 |
Question("transfers_safeguarded", "Transfers outside the EEA safeguarded?", Answer.yes_no()),
|
| 40 |
]
|
| 41 |
`,
|
|
|
|
| 35 |
|
| 36 |
QUESTIONS = [
|
| 37 |
Question("approve", "Approve the vendor?", Answer.choice(["approve", "review", "reject"]),
|
| 38 |
+
requires=["breach_within_72h"]),
|
| 39 |
Question("transfers_safeguarded", "Transfers outside the EEA safeguarded?", Answer.yes_no()),
|
| 40 |
]
|
| 41 |
`,
|
usecases/insurance.js
CHANGED
|
@@ -36,8 +36,8 @@ def payout_band(payout, claim_amount):
|
|
| 36 |
return "full" if payout >= money(claim_amount) else "partial"
|
| 37 |
|
| 38 |
QUESTIONS = [
|
| 39 |
-
Question("covered", "Is the claim covered?", Answer.yes_no(),
|
| 40 |
-
Question("payout_band", "Payout", Answer.choice(["full", "partial", "none"]),
|
| 41 |
]
|
| 42 |
`,
|
| 43 |
docs: [
|
|
|
|
| 36 |
return "full" if payout >= money(claim_amount) else "partial"
|
| 37 |
|
| 38 |
QUESTIONS = [
|
| 39 |
+
Question("covered", "Is the claim covered?", Answer.yes_no(), requires=["cause_not_excluded"]),
|
| 40 |
+
Question("payout_band", "Payout", Answer.choice(["full", "partial", "none"]), requires=["cause_not_excluded"]),
|
| 41 |
]
|
| 42 |
`,
|
| 43 |
docs: [
|
usecases/invoice.js
CHANGED
|
@@ -42,7 +42,7 @@ def days_left(due, today):
|
|
| 42 |
|
| 43 |
QUESTIONS = [
|
| 44 |
Question("pay", "Payment decision", Answer.choice(["schedule", "escalate", "reject"]),
|
| 45 |
-
|
| 46 |
Question("days_left", "Time left to pay", Answer.choice(["overdue", "under a week", "a week or more"])),
|
| 47 |
]
|
| 48 |
`,
|
|
|
|
| 42 |
|
| 43 |
QUESTIONS = [
|
| 44 |
Question("pay", "Payment decision", Answer.choice(["schedule", "escalate", "reject"]),
|
| 45 |
+
requires=["iban_checksum", "not_overdue"]),
|
| 46 |
Question("days_left", "Time left to pay", Answer.choice(["overdue", "under a week", "a week or more"])),
|
| 47 |
]
|
| 48 |
`,
|
usecases/lease.js
CHANGED
|
@@ -39,7 +39,7 @@ def sign(notice_days):
|
|
| 39 |
return "sign" if notice_days <= 60 else "negotiate" # tenant should be able to leave on two months' notice
|
| 40 |
|
| 41 |
QUESTIONS = [
|
| 42 |
-
Question("sign", "Sign as is?", Answer.choice(["sign", "negotiate"]),
|
| 43 |
Question("pets_allowed", "Are pets allowed?", Answer.choice(["yes", "no", "not stated"])),
|
| 44 |
]
|
| 45 |
`,
|
|
|
|
| 39 |
return "sign" if notice_days <= 60 else "negotiate" # tenant should be able to leave on two months' notice
|
| 40 |
|
| 41 |
QUESTIONS = [
|
| 42 |
+
Question("sign", "Sign as is?", Answer.choice(["sign", "negotiate"]), requires=["deposit_within_cap"]),
|
| 43 |
Question("pets_allowed", "Are pets allowed?", Answer.choice(["yes", "no", "not stated"])),
|
| 44 |
]
|
| 45 |
`,
|
usecases/offer.js
CHANGED
|
@@ -38,7 +38,7 @@ def starts_within_60_days(start_date, today):
|
|
| 38 |
return (parse_date(start_date) - today).days <= 60
|
| 39 |
|
| 40 |
QUESTIONS = [
|
| 41 |
-
Question("send", "Send the offer as is?", Answer.choice(["send", "revise"]),
|
| 42 |
Question("band", "Salary vs approved band", Answer.choice(["below", "in band", "above"])),
|
| 43 |
Question("starts_within_60_days", "Starts within 60 days?", Answer.yes_no()),
|
| 44 |
]
|
|
|
|
| 38 |
return (parse_date(start_date) - today).days <= 60
|
| 39 |
|
| 40 |
QUESTIONS = [
|
| 41 |
+
Question("send", "Send the offer as is?", Answer.choice(["send", "revise"]), requires=["probation_legal"]),
|
| 42 |
Question("band", "Salary vs approved band", Answer.choice(["below", "in band", "above"])),
|
| 43 |
Question("starts_within_60_days", "Starts within 60 days?", Answer.yes_no()),
|
| 44 |
]
|
usecases/receipt.js
CHANGED
|
@@ -39,7 +39,7 @@ def weekend(purchase_date):
|
|
| 39 |
return purchase_date.weekday() >= 5
|
| 40 |
|
| 41 |
QUESTIONS = [
|
| 42 |
-
Question("reimburse", "Reimburse this receipt?", Answer.yes_no(),
|
| 43 |
Question("change_correct", "Is the change right (cash - total)?", Answer.yes_no()),
|
| 44 |
Question("weekend", "Bought on a weekend?", Answer.yes_no()),
|
| 45 |
]
|
|
|
|
| 39 |
return purchase_date.weekday() >= 5
|
| 40 |
|
| 41 |
QUESTIONS = [
|
| 42 |
+
Question("reimburse", "Reimburse this receipt?", Answer.yes_no(), requires=["not_too_old"]),
|
| 43 |
Question("change_correct", "Is the change right (cash - total)?", Answer.yes_no()),
|
| 44 |
Question("weekend", "Bought on a weekend?", Answer.yes_no()),
|
| 45 |
]
|
usecases/support.js
CHANGED
|
@@ -36,7 +36,7 @@ def urgency(deadline, today):
|
|
| 36 |
|
| 37 |
QUESTIONS = [
|
| 38 |
Question("route", "Which queue?", Answer.choice(["billing", "fulfilment", "general", "ask for order number"]),
|
| 39 |
-
|
| 40 |
Question("urgency", "Urgency", Answer.choice(["high", "normal"])),
|
| 41 |
]
|
| 42 |
`,
|
|
|
|
| 36 |
|
| 37 |
QUESTIONS = [
|
| 38 |
Question("route", "Which queue?", Answer.choice(["billing", "fulfilment", "general", "ask for order number"]),
|
| 39 |
+
requires=["has_order_number"]),
|
| 40 |
Question("urgency", "Urgency", Answer.choice(["high", "normal"])),
|
| 41 |
]
|
| 42 |
`,
|