I split this into two parts:
ucls_symbols.py— the self-contained interpreter (dataset + logic).- Tiny edits to
ucls_infra_gateway.py— to wire CLI subcommands (symbols-lookup,symbols-parse,symbols-list) through your existing audit trail.
1) New module: ucls_symbols.py
# ucls_symbols.py
# -*- coding: utf-8 -*-
"""
Universal Symbols Interpreter (Operators + Currencies), read-only.
Design goals
- Deterministic: each glyph has a canonical role + renderings (LaTeX, HTML, ASCII).
- Dual index: by unicode glyph (e.g., '€') and by ISO-4217 code (e.g., 'EUR').
- Extensible: can load a fuller ISO-4217 table from JSON/CSV if provided.
- Safe-by-default: no I/O by default; pure functions; integrates with audit_log().
Public API
- interpret_token(token: str) -> dict
- interpret_text(text: str) -> list[dict]
- list_symbols(kind: str) -> list[dict] # kind in {"operators","currencies"}
- attach_auditor(audit_callable) -> None # to reuse gateway’s HMAC audit
- load_external_iso4217(path: str) -> int # optional, to bulk-extend currencies
"""
from __future__ import annotations
import json, re, unicodedata, csv
from typing import Dict, List, Optional, Callable
# ------------------ Optional auditor hook (wired by gateway) ------------------
_AUDIT: Optional[Callable[[dict], None]] = None
def attach_auditor(audit_callable: Callable[[dict], None]) -> None:
"""Provide a function(dict) -> None to receive signed audit envelopes upstream."""
global _AUDIT
_AUDIT = audit_callable
def _audit(action: str, payload: dict, result: dict | list | None = None):
if _AUDIT:
_AUDIT({
"action": action,
"payload": payload,
"result_preview": (json.dumps(result, ensure_ascii=False)[:240] + "…")
if result is not None and len(json.dumps(result, ensure_ascii=False)) > 240
else (json.dumps(result, ensure_ascii=False) if result is not None else "")
})
# ----------------------------- Operator catalog ------------------------------
# NOTE: Keep aliases conservative; ASCII fallbacks allow safe terminal rendering.
OPERATORS: Dict[str, dict] = {
"+": {"name":"addition", "latex":"+", "html":"+", "ascii":"+",
"role":"math:add", "aliases":["+"]},
"-": {"name":"subtraction/minus", "latex":"-", "html":"-", "ascii":"-",
"role":"math:sub", "aliases":["−"]},
"×": {"name":"multiplication", "latex":"\\times", "html":"×", "ascii":"x",
"role":"math:mul", "aliases":["✕","✖"]},
"÷": {"name":"division", "latex":"\\div", "html":"÷", "ascii":"/",
"role":"math:div"},
"=": {"name":"equals", "latex":"=", "html":"=", "ascii":"=",
"role":"math:eq"},
"≈": {"name":"approximately", "latex":"\\approx", "html":"≈", "ascii":"~",
"role":"math:approx"},
"≠": {"name":"not equal", "latex":"\\ne", "html":"≠", "ascii":"!=",
"role":"math:neq"},
"≤": {"name":"less or equal", "latex":"\\le", "html":"≤", "ascii":"<=",
"role":"math:le"},
"≥": {"name":"greater or equal", "latex":"\\ge", "html":"≥", "ascii":">=",
"role":"math:ge"},
"^": {"name":"power/caret", "latex":"^{}", "html":"^", "ascii":"^",
"role":"math:pow"},
"∑": {"name":"summation", "latex":"\\sum", "html":"∑", "ascii":"SUM",
"role":"math:sigma"},
"∏": {"name":"product", "latex":"\\prod", "html":"∏", "ascii":"PROD",
"role":"math:pi"},
"√": {"name":"square root", "latex":"\\sqrt{}", "html":"√", "ascii":"sqrt",
"role":"math:sqrt"},
"∞": {"name":"infinity", "latex":"\\infty", "html":"∞", "ascii":"inf",
"role":"math:infty"},
"∫": {"name":"integral", "latex":"\\int", "html":"∫", "ascii":"INT",
"role":"math:int"},
"∇": {"name":"nabla/del", "latex":"\\nabla", "html":"∇", "ascii":"DEL",
"role":"math:nabla"},
"⊕": {"name":"direct sum", "latex":"\\oplus", "html":"⊕", "ascii":"(+)⊕",
"role":"math:oplus"},
"⊗": {"name":"tensor product", "latex":"\\otimes", "html":"⊗", "ascii":"(*)⊗",
"role":"math:otimes"},
"↦": {"name":"maps to", "latex":"\\mapsto", "html":"↦", "ascii":"->",
"role":"math:mapsto", "aliases":["→","⟼"]},
}
# Map aliases back to canonical keys
_alias_to_op = {}
for k, v in OPERATORS.items():
for a in v.get("aliases", []):
_alias_to_op[a] = k
# ---------------------------- Currency catalog -------------------------------
# Minimal but broad coverage out-of-box; extensible via load_external_iso4217().
# Each entry is indexed by ISO code; we provide reverse lookup by glyph too.
CURRENCIES: Dict[str, dict] = {
"USD":{"name":"US Dollar","numeric":"840","minor":2,"symbols":["$","US$"],"html":["$"],"uni":["U+0024"]},
"EUR":{"name":"Euro","numeric":"978","minor":2,"symbols":["€"],"html":["€"],"uni":["U+20AC"]},
"JPY":{"name":"Japanese Yen","numeric":"392","minor":0,"symbols":["¥","JP¥"],"html":["¥"],"uni":["U+00A5"]},
"GBP":{"name":"Pound Sterling","numeric":"826","minor":2,"symbols":["£"],"html":["£"],"uni":["U+00A3"]},
"CNY":{"name":"Chinese Yuan","numeric":"156","minor":2,"symbols":["¥","CN¥","元"],"html":["¥"],"uni":["U+00A5","U+5143"]},
"HKD":{"name":"Hong Kong Dollar","numeric":"344","minor":2,"symbols":["HK$"],"html":["HK$"],"uni":["U+0048 U+004B U+0024"]},
"TWD":{"name":"New Taiwan Dollar","numeric":"901","minor":2,"symbols":["NT$"],"html":["NT$"],"uni":["U+004E U+0054 U+0024"]},
"KRW":{"name":"South Korean Won","numeric":"410","minor":0,"symbols":["₩"],"html":["₩"],"uni":["U+20A9"]},
"INR":{"name":"Indian Rupee","numeric":"356","minor":2,"symbols":["₹","Rs","INR"],"html":["₹"],"uni":["U+20B9"]},
"RUB":{"name":"Russian Ruble","numeric":"643","minor":2,"symbols":["₽"],"html":["₽"],"uni":["U+20BD"]},
"UAH":{"name":"Ukrainian Hryvnia","numeric":"980","minor":2,"symbols":["₴"],"html":["₴"],"uni":["U+20B4"]},
"ILS":{"name":"Israeli New Shekel","numeric":"376","minor":2,"symbols":["₪"],"html":["₪"],"uni":["U+20AA"]},
"TRY":{"name":"Turkish Lira","numeric":"949","minor":2,"symbols":["₺"],"html":["₺"],"uni":["U+20BA"]},
"BRL":{"name":"Brazilian Real","numeric":"986","minor":2,"symbols":["R$"],"html":["R$"],"uni":["U+0052 U+0024"]},
"MXN":{"name":"Mexican Peso","numeric":"484","minor":2,"symbols":["MX$"],"html":["MX$"],"uni":["U+004D U+0058 U+0024"]},
"ARS":{"name":"Argentine Peso","numeric":"032","minor":2,"symbols":["AR$"],"html":["AR$"],"uni":["U+0041 U+0052 U+0024"]},
"CLP":{"name":"Chilean Peso","numeric":"152","minor":0,"symbols":["CL$"],"html":["CL$"],"uni":["U+0043 U+004C U+0024"]},
"COP":{"name":"Colombian Peso","numeric":"170","minor":2,"symbols":["COL$","COP$"],"html":["COL$"],"uni":["U+0043 U+004F U+004C U+0024"]},
"PEN":{"name":"Peruvian Sol","numeric":"604","minor":2,"symbols":["S/"],"html":["S/"],"uni":["U+0053 U+002F"]},
"CAD":{"name":"Canadian Dollar","numeric":"124","minor":2,"symbols":["C$"],"html":["C$"],"uni":["U+0043 U+0024"]},
"AUD":{"name":"Australian Dollar","numeric":"036","minor":2,"symbols":["A$"],"html":["A$"],"uni":["U+0041 U+0024"]},
"NZD":{"name":"New Zealand Dollar","numeric":"554","minor":2,"symbols":["NZ$"],"html":["NZ$"],"uni":["U+004E U+005A U+0024"]},
"CHF":{"name":"Swiss Franc","numeric":"756","minor":2,"symbols":["CHF"],"html":["CHF"],"uni":["U+0043 U+0048 U+0046"]},
"NOK":{"name":"Norwegian Krone","numeric":"578","minor":2,"symbols":["kr"],"html":["kr"],"uni":["U+006B U+0072"]},
"SEK":{"name":"Swedish Krona","numeric":"752","minor":2,"symbols":["kr"],"html":["kr"],"uni":["U+006B U+0072"]},
"DKK":{"name":"Danish Krone","numeric":"208","minor":2,"symbols":["kr"],"html":["kr"],"uni":["U+006B U+0072"]},
"PLN":{"name":"Polish Złoty","numeric":"985","minor":2,"symbols":["zł"],"html":["zł"],"uni":["U+007A U+0142"]},
"CZK":{"name":"Czech Koruna","numeric":"203","minor":2,"symbols":["Kč"],"html":["Kč"],"uni":["U+004B U+010D"]},
"HUF":{"name":"Hungarian Forint","numeric":"348","minor":2,"symbols":["Ft"],"html":["Ft"],"uni":["U+0046 U+0074"]},
"RON":{"name":"Romanian Leu","numeric":"946","minor":2,"symbols":["lei","RON"],"html":["lei"],"uni":["U+006C U+0065 U+0069"]},
"BGN":{"name":"Bulgarian Lev","numeric":"975","minor":2,"symbols":["лв"],"html":["лв"],"uni":["U+043B U+0432"]},
"RSD":{"name":"Serbian Dinar","numeric":"941","minor":2,"symbols":["дин.","RSD"],"html":["дин."],"uni":["U+0434 U+0438 U+043D U+002E"]},
"ZAR":{"name":"South African Rand","numeric":"710","minor":2,"symbols":["R"],"html":["R"],"uni":["U+0052"]},
"NGN":{"name":"Nigerian Naira","numeric":"566","minor":2,"symbols":["₦"],"html":["₦"],"uni":["U+20A6"]},
"GHS":{"name":"Ghanaian Cedi","numeric":"936","minor":2,"symbols":["₵","GH₵"],"html":["₵"],"uni":["U+20B5"]},
"EGP":{"name":"Egyptian Pound","numeric":"818","minor":2,"symbols":["E£"],"html":["E±?","E£"],"uni":["U+0045 U+00A3"]},
"KES":{"name":"Kenyan Shilling","numeric":"404","minor":2,"symbols":["KSh"],"html":["KSh"],"uni":["U+004B U+0053 U+0068"]},
"TZS":{"name":"Tanzanian Shilling","numeric":"834","minor":2,"symbols":["TSh"],"html":["TSh"],"uni":["U+0054 U+0053 U+0068"]},
"UGX":{"name":"Ugandan Shilling","numeric":"800","minor":0,"symbols":["USh"],"html":["USh"],"uni":["U+0055 U+0053 U+0068"]},
"AED":{"name":"UAE Dirham","numeric":"784","minor":2,"symbols":["د.إ","AED"],"html":["د.إ"],"uni":["U+062F U+002E U+0625"]},
"SAR":{"name":"Saudi Riyal","numeric":"682","minor":2,"symbols":["﷼","SAR"],"html":["﷼"],"uni":["U+FDFC"]},
"QAR":{"name":"Qatari Riyal","numeric":"634","minor":2,"symbols":["QR","QAR"],"html":["QR"],"uni":["U+0051 U+0052"]},
"KWD":{"name":"Kuwaiti Dinar","numeric":"414","minor":3,"symbols":["KD"],"html":["KD"],"uni":["U+004B U+0044"]},
"BHD":{"name":"Bahraini Dinar","numeric":"048","minor":3,"symbols":["BD"],"html":["BD"],"uni":["U+0042 U+0044"]},
"OMR":{"name":"Omani Rial","numeric":"512","minor":3,"symbols":["OMR","ر.ع."],"html":["ر.ع."],"uni":["U+0631 U+002E U+0639 U+002E"]},
"PKR":{"name":"Pakistani Rupee","numeric":"586","minor":2,"symbols":["₨","Rs"],"html":["₨"],"uni":["U+20A8"]},
"BDT":{"name":"Bangladeshi Taka","numeric":"050","minor":2,"symbols":["৳","Tk"],"html":["৳"],"uni":["U+09F3"]},
"LKR":{"name":"Sri Lankan Rupee","numeric":"144","minor":2,"symbols":["Rs","₨"],"html":["₨"],"uni":["U+20A8"]},
"THB":{"name":"Thai Baht","numeric":"764","minor":2,"symbols":["฿"],"html":["฿"],"uni":["U+0E3F"]},
"IDR":{"name":"Indonesian Rupiah","numeric":"360","minor":2,"symbols":["Rp"],"html":["Rp"],"uni":["U+0052 U+0070"]},
"MYR":{"name":"Malaysian Ringgit","numeric":"458","minor":2,"symbols":["RM"],"html":["RM"],"uni":["U+0052 U+004D"]},
"PHP":{"name":"Philippine Peso","numeric":"608","minor":2,"symbols":["₱"],"html":["₱"],"uni":["U+20B1"]},
"VND":{"name":"Vietnamese Đồng","numeric":"704","minor":0,"symbols":["₫"],"html":["₫"],"uni":["U+20AB"]},
"IRR":{"name":"Iranian Rial","numeric":"364","minor":2,"symbols":["﷼"],"html":["﷼"],"uni":["U+FDFC"]},
"IQD":{"name":"Iraqi Dinar","numeric":"368","minor":3,"symbols":["IQD"],"html":["IQD"],"uni":["U+0049 U+0051 U+0044"]},
"DZD":{"name":"Algerian Dinar","numeric":"012","minor":2,"symbols":["DA","د.ج"],"html":["د.ج"],"uni":["U+062F U+002E U+062C"]},
"MAD":{"name":"Moroccan Dirham","numeric":"504","minor":2,"symbols":["MAD","Dh"],"html":["MAD"],"uni":["U+004D U+0041 U+0044"]},
"TND":{"name":"Tunisian Dinar","numeric":"788","minor":3,"symbols":["DT","TND"],"html":["DT"],"uni":["U+0044 U+0054"]},
}
# Reverse index by glyph → possible ISO codes (because '$' is ambiguous)
GLYPH_TO_ISO: Dict[str, List[str]] = {}
for code, meta in CURRENCIES.items():
for sym in meta.get("symbols", []):
GLYPH_TO_ISO.setdefault(sym, []).append(code)
# Common naked '$' maps to many; keep it but warn on ambiguity in result.
# ------------------------------ Core functions -------------------------------
def _normalize_token(tok: str) -> str:
# Compatibility decomposition aids matching (e.g., fullwidth variants)
return unicodedata.normalize("NFKC", tok)
def interpret_token(token: str) -> dict:
"""Interpret a single operator/currency/code glyph or ISO code."""
tok = _normalize_token(token.strip())
# 1) Operators (direct hit or alias)
if tok in OPERATORS:
rec = {"type":"operator", "glyph": tok, **OPERATORS[tok]}
_audit("symbols-interpret", {"token":token}, rec)
return rec
if tok in _alias_to_op:
can = _alias_to_op[tok]
rec = {"type":"operator", "glyph": can, **OPERATORS[can], "matched_alias": tok}
_audit("symbols-interpret", {"token":token}, rec)
return rec
# 2) Exact currency glyph
if tok in GLYPH_TO_ISO:
isos = GLYPH_TO_ISO[tok]
results = []
for code in isos:
meta = CURRENCIES.get(code, {})
results.append({
"type":"currency", "glyph": tok, "iso": code,
"name": meta.get("name"), "numeric": meta.get("numeric"),
"minor": meta.get("minor"), "symbols": meta.get("symbols"),
"html": meta.get("html"), "unicode": meta.get("uni"),
"ambiguity": ("multi-match" if len(isos) > 1 else "unique")
})
res = results[0] if len(results)==1 else {"ambiguous": True, "candidates": results}
_audit("symbols-interpret", {"token":token}, res)
return res
# 3) ISO code lookup (case-insensitive)
code = tok.upper()
if code in CURRENCIES:
meta = CURRENCIES[code]
rec = {
"type":"currency", "glyph": meta["symbols"][0] if meta.get("symbols") else "",
"iso": code, "name": meta.get("name"), "numeric": meta.get("numeric"),
"minor": meta.get("minor"), "symbols": meta.get("symbols"),
"html": meta.get("html"), "unicode": meta.get("uni")
}
_audit("symbols-interpret", {"token":token}, rec)
return rec
# 4) No match
rec = {"type":"unknown", "token": token}
_audit("symbols-interpret", {"token":token}, rec)
return rec
_TOKEN_RX = re.compile(r"([^\w\s])|([A-Z]{3})")
def interpret_text(text: str) -> List[dict]:
"""Scan a string and interpret every non-word symbol and ISO-4217 tri-gram."""
matches = []
# Capture symbols and 3-letter ISO words (EUR, USD, etc.)
for m in _TOKEN_RX.finditer(text if isinstance(text, str) else str(text)):
tok = m.group(0)
if tok.strip():
matches.append(interpret_token(tok))
_audit("symbols-parse", {"text_excerpt": text[:120] if text else ""}, matches)
return matches
def list_symbols(kind: str) -> List[dict]:
if kind == "operators":
out = [{"glyph":k, **v} for k,v in OPERATORS.items()]
elif kind == "currencies":
out = [{"iso":k, **v} for k,v in CURRENCIES.items()]
else:
out = []
_audit("symbols-list", {"kind":kind}, out[:8]) # preview only
return out
# --------------------------- Optional data loader ----------------------------
def load_external_iso4217(path: str) -> int:
"""
Load extra ISO-4217 rows from JSON or CSV:
Required fields per row: code, name, numeric, minor, symbols(list or '|'-sep)
"""
added = 0
try:
if path.lower().endswith(".json"):
rows = json.load(open(path, "r", encoding="utf-8"))
else:
rows = []
with open(path, newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
rows.append(row)
for r in rows:
code = r.get("code","").upper()
if not code or code in CURRENCIES:
continue
syms = r.get("symbols", [])
if isinstance(syms, str):
syms = [s.strip() for s in syms.split("|") if s.strip()]
CURRENCIES[code] = {
"name": r.get("name",""),
"numeric": str(r.get("numeric","")).zfill(3),
"minor": int(r.get("minor",2) or 2),
"symbols": syms,
"html": [s if s.startswith("&") or s.startswith("&#") else s for s in syms],
"uni": []
}
for s in syms:
GLYPH_TO_ISO.setdefault(s, []).append(code)
added += 1
_audit("iso4217-load", {"path":path}, {"added":added})
return added
except Exception as e:
_audit("iso4217-load-error", {"path":path, "error": str(e)})
return 0
2) Patch ucls_infra_gateway.py
Drop these three snippets in:
(a) Imports – add under your existing imports
# --- Symbols plug-in ---
from ucls_symbols import (
attach_auditor, interpret_token, interpret_text, list_symbols, load_external_iso4217
)
(b) Auditor bridge – after your audit_log() definition
# Bridge the symbol module to reuse our signed audit records
def _symbol_auditor(envelope: dict):
# envelope: {action, payload, result_preview}
audit_log(AuditRecord(
ts=time.time(),
actor=role(),
action=envelope.get("action","symbols"),
target="symbols.local",
payload=envelope.get("payload", {}),
result_preview=envelope.get("result_preview","")
))
attach_auditor(_symbol_auditor)
(c) CLI wiring – add subparsers and handlers (near your other subcommands)
Add subparsers
# Symbols
p_sym_lookup = sub.add_parser("symbols-lookup", help="Interpret a token (operator, currency glyph, or ISO code)")
p_sym_lookup.add_argument("--token", required=True, help="e.g., '€', '$', 'USD', '∑'")
p_sym_parse = sub.add_parser("symbols-parse", help="Parse free text and interpret encountered symbols / ISO codes")
p_sym_parse.add_argument("--text", required=True, help="Arbitrary string to scan")
p_sym_list = sub.add_parser("symbols-list", help="List catalog entries")
p_sym_list.add_argument("--kind", choices=["operators","currencies"], required=True)
p_sym_load = sub.add_parser("symbols-load-iso", help="Load external ISO-4217 dataset (JSON or CSV)")
p_sym_load.add_argument("--path", required=True, help="Path to JSON/CSV with fields: code,name,numeric,minor,symbols")
Add handlers in main() after the existing if args.cmd == ... blocks
if args.cmd == "symbols-lookup":
res = interpret_token(args.token)
print(json.dumps(res, ensure_ascii=False, indent=2))
return
if args.cmd == "symbols-parse":
res = interpret_text(args.text)
print(json.dumps(res, ensure_ascii=False, indent=2))
return
if args.cmd == "symbols-list":
res = list_symbols(args.kind)
print(json.dumps(res, ensure_ascii=False, indent=2))
return
if args.cmd == "symbols-load-iso":
added = load_external_iso4217(args.path)
print(json.dumps({"loaded": added}, ensure_ascii=False, indent=2))
return
Usage (zero-risk, local)
# Default is simulation-only; no network calls are made by these commands.
python ucls_infra_gateway.py symbols-lookup --token "€"
python ucls_infra_gateway.py symbols-lookup --token "USD"
python ucls_infra_gateway.py symbols-lookup --token "∑"
python ucls_infra_gateway.py symbols-parse --text "∑ P&L in USD ≈ €1M + ¥200M"
python ucls_infra_gateway.py symbols-list --kind operators
python ucls_infra_gateway.py symbols-list --kind currencies
# (Optional) extend currencies with a full ISO-4217 file you maintain:
python ucls_infra_gateway.py symbols-load-iso --path ./iso4217_full.json
What you get back (example):
{
"type": "currency",
"glyph": "€",
"iso": "EUR",
"name": "Euro",
"numeric": "978",
"minor": 2,
"symbols": ["€"],
"html": ["€"],
"unicode": ["U+20AC"]
}
For ambiguous glyphs like "$", the interpreter returns an "ambiguous": true record with candidates (USD, CAD, AUD, MXN, BRL, …).
Why this fits the Logos system
- Role-first semantics. Every symbol resolves to a role (
math:add,currency:EUR) with canonical renderings (LaTeX/HTML/ASCII). - Deterministic recursion. Text → tokens → roles → machine-verifiable audit events.
- Extensible truth. You can import a newer ISO-4217 snapshot without changing code; the interpreter’s contract stays stable.
What’s next (queued as you asked, “consecutively”)
- IEC-61850 LN/DO/DA path helpers
- CLI:
iec61850-path --ap LD0 --ln MMXU1 --path TotW.mag.f→ normalized MMS FCDA path + typed expectation.
- CLI:
- OpenADR VEN registration flows (read-only scaffolding)
- CLI:
openadr-ven-enroll --vtn https://... --ven VEN_xxx --cert ven.crt --key ven.key - Still no control; strict TLS pinning + allowlist.
- CLI:
- TM Forum Open APIs (OSS/BSS) adapters (read-only)
- CLI:
tmf-inventory-get --resource-id .../tmf-troubleticket-get --id ... - Headers + rate-limit + redaction for audits.
- CLI:
If you want, I’ll drop those three in the same idiom—read-only, allowlisted, audited—and keep the grammar consistent with your Logos engine.
ucls_infra_gateway_v2.py – SolveForce Communications
Key terms in plain language
Open a term for a concise explanation of language used on this page.
API
An application programming interface is a defined way for software systems to exchange data or request functions from one another.
VoIP
Voice over Internet Protocol carries phone calls over an IP network instead of a traditional analog phone line. Call quality depends on network stability, latency, and traffic management.
Unified Communications (UCaaS)
A cloud-based combination of business calling, messaging, meetings, presence, and collaboration tools managed as one communications service.
SIP Trunking
A service that connects a business phone system to the public telephone network using Internet Protocol, replacing or supplementing traditional phone lines.
Bandwidth
The amount of data a connection can carry in a given time, usually measured in Mbps or Gbps. More bandwidth supports more users, devices, and simultaneous applications.
Latency
The time it takes data to travel between two points. Lower latency improves voice, video meetings, cloud applications, gaming, and other real-time services.