s-ttp commited on
Commit
dfd5b73
·
verified ·
1 Parent(s): c7db9b3

resolver: exact base-symbol match for ticker queries, whole-word name matching for name queries

Browse files
Files changed (2) hide show
  1. src/portfolio.py +8 -1
  2. tests/test_validator.py +2 -1
src/portfolio.py CHANGED
@@ -109,10 +109,17 @@ def resolve_symbol(query: str) -> dict:
109
  return m["av"].endswith(".FRK") or (m["av"].endswith(".LON") and m["av"][0] == "0")
110
  def _otc(m):
111
  return m["region"] == "United States" and len(m["av"]) == 5 and m["av"][-1] in "YF"
 
 
 
 
 
 
 
 
112
  home = [m for m in matches if m["region"] != "United States" and not _secondary(m)]
113
  us_primary = [m for m in matches if m["region"] == "United States" and not _otc(m)]
114
  rest = [m for m in matches if m not in home and m not in us_primary]
115
- us_shaped = _TICKER_RE.fullmatch(qU) and "." not in qU
116
  ordered = (us_primary + home) if us_shaped else (home + us_primary)
117
  if not us_shaped:
118
  ordered += rest # a US-shaped dead ticker must not land on a Frankfurt line — fall to the LLM
 
109
  return m["av"].endswith(".FRK") or (m["av"].endswith(".LON") and m["av"][0] == "0")
110
  def _otc(m):
111
  return m["region"] == "United States" and len(m["av"]) == 5 and m["av"][-1] in "YF"
112
+ us_shaped = bool(_TICKER_RE.fullmatch(qU) and "." not in qU)
113
+ if _TICKER_RE.fullmatch(qU): # a ticker query must match the listing's base symbol EXACTLY
114
+ base_q = qU.rsplit(".", 1)[0]
115
+ matches = [m for m in matches if m["av"].rsplit(".", 1)[0].replace(".", "-").upper() == base_q]
116
+ else: # a name query: every token must appear as a whole word in the name
117
+ toks = [t for t in re.split(r"[^a-z0-9]+", q.lower()) if len(t) > 1]
118
+ matches = [m for m in matches
119
+ if all(re.search(r"\b" + re.escape(t) + r"\b", m["name"].lower()) for t in toks)]
120
  home = [m for m in matches if m["region"] != "United States" and not _secondary(m)]
121
  us_primary = [m for m in matches if m["region"] == "United States" and not _otc(m)]
122
  rest = [m for m in matches if m not in home and m not in us_primary]
 
123
  ordered = (us_primary + home) if us_shaped else (home + us_primary)
124
  if not us_shaped:
125
  ordered += rest # a US-shaped dead ticker must not land on a Frankfurt line — fall to the LLM
tests/test_validator.py CHANGED
@@ -132,4 +132,5 @@ def test_resolver_demotes_secondary_venues(monkeypatch):
132
  monkeypatch.setattr(PF, "resolve_isin", lambda i: [])
133
  monkeypatch.setattr(PF, "call_llm", lambda *a, **k: '{"ticker": "XYZ", "name": "Block"}')
134
  assert PF.resolve_symbol("SQ")["symbol"] == "XYZ" # not the Frankfurt line
135
- assert PF.resolve_symbol("Block payments")["symbol"] == "SQ3.F" # a NAME may still land on the only listing found
 
 
132
  monkeypatch.setattr(PF, "resolve_isin", lambda i: [])
133
  monkeypatch.setattr(PF, "call_llm", lambda *a, **k: '{"ticker": "XYZ", "name": "Block"}')
134
  assert PF.resolve_symbol("SQ")["symbol"] == "XYZ" # not the Frankfurt line
135
+ assert PF.resolve_symbol("Block Inc")["symbol"] == "SQ3.F" # name tokens all present → the only listing found
136
+ assert PF.resolve_symbol("Block payments")["symbol"] == "XYZ" # 'payments' absent from the name → LLM path