Skip to content

Commit 017c55a

Browse files
committed
Restructure and implement basic functionality
1 parent 21f84f0 commit 017c55a

5 files changed

Lines changed: 101 additions & 19 deletions

File tree

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
fastapi>=0.95.0
22
pydantic>=1.10
3-
uvicorn>=0.21.1
3+
uvicorn>=0.21.1
4+
requests>=2.31.0
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from semantic_matcher.model import SemanticMatch, EquivalenceTable
2+
3+
4+
def return_simple_example_equivalence_table() -> EquivalenceTable:
5+
"""
6+
Returns a simple equivalence table with three semantic matches
7+
"""
8+
table = EquivalenceTable(matches={})
9+
table.add_semantic_match(
10+
SemanticMatch(
11+
base_semantic_id="s-heppner.com/semanticID/one",
12+
match_semantic_id="s-heppner.com/semanticID/1",
13+
score=1.,
14+
meta_information={"matchSource": "Defined by Sebastian Heppner"}
15+
)
16+
)
17+
table.add_semantic_match(
18+
SemanticMatch(
19+
base_semantic_id="s-heppner.com/semanticID/two",
20+
match_semantic_id="s-heppner.com/semanticID/2",
21+
score=1.,
22+
meta_information={"matchSource": "Defined by Sebastian Heppner"}
23+
)
24+
)
25+
table.add_semantic_match(
26+
SemanticMatch(
27+
base_semantic_id="s-heppner.com/semanticID/one",
28+
match_semantic_id="s-heppner.com/semanticID/two",
29+
score=0.8,
30+
meta_information={"matchSource": "Defined by Sebastian Heppner"}
31+
)
32+
)
33+
return table
34+
35+
36+
if __name__ == '__main__':
37+
e = return_simple_example_equivalence_table()
38+
e.to_file("example_equivalence_table.json")
Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional, Dict, List
1+
from typing import Dict, List
22

33
from pydantic import BaseModel
44

@@ -22,15 +22,25 @@ class EquivalenceTable(BaseModel):
2222

2323
def add_semantic_match(self, match: SemanticMatch) -> None:
2424
if self.matches.get(match.base_semantic_id) is not None:
25-
self.equivalence_table[match.base_semantic_id].append(match)
25+
self.matches[match.base_semantic_id].append(match)
2626
else:
27-
self.equivalence_table[match.base_semantic_id] = [match]
27+
self.matches[match.base_semantic_id] = [match]
2828

2929
def remove_semantic_match(self, match: SemanticMatch) -> None:
30-
if self.equivalence_table.get(match.base_semantic_id) is not None:
31-
self.equivalence_table.get(match.base_semantic_id).remove(match)
32-
if len(self.equivalence_table.get(match.base_semantic_id)) == 0:
33-
self.equivalence_table.pop(match.base_semantic_id)
30+
if self.matches.get(match.base_semantic_id) is not None:
31+
self.matches.get(match.base_semantic_id).remove(match)
32+
if len(self.matches.get(match.base_semantic_id)) == 0:
33+
self.matches.pop(match.base_semantic_id)
34+
35+
def get_local_matches(self, semantic_id: str, score_limit: float) -> List[SemanticMatch]:
36+
equivalence_table_result = self.matches.get(semantic_id)
37+
if equivalence_table_result is None:
38+
return []
39+
for match in equivalence_table_result:
40+
matching_result = []
41+
if match.score > score_limit:
42+
matching_result.append(match)
43+
return matching_result
3444

3545
def to_file(self, filename: str) -> None:
3646
with open(filename, "w") as file:
@@ -40,3 +50,4 @@ def to_file(self, filename: str) -> None:
4050
def from_file(cls, filename: str) -> "EquivalenceTable":
4151
with open(filename, "r") as file:
4252
return EquivalenceTable.model_validate_json(file.read())
53+

semantic_matcher/service.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
from typing import List, Optional
22

3+
import requests
34
from fastapi import APIRouter
45

5-
import matcher
6+
import model
67
import service_model
78

89

910
class SemanticMatchingService:
1011
"""
1112
Todo
1213
"""
13-
def __init__(self, semantic_matcher: matcher.SemanticMatcher):
14+
def __init__(self, equivalences: model.EquivalenceTable):
1415
self.router = APIRouter()
1516
self.router.add_api_route(
1617
"/get_match",
1718
self.get_matches,
1819
methods=["GET"]
1920
)
20-
self.semantic_matcher: matcher.SemanticMatcher = semantic_matcher
21+
self.equivalence_table: model.EquivalenceTable = equivalences
2122

2223
def get_matches(
2324
self,
@@ -29,14 +30,44 @@ def get_matches(
2930
Returns a matching score
3031
"""
3132
# Try first local matching
32-
matches: Optional[List[matcher.SemanticMatch]] = self.semantic_matcher.get_matches(request_body.semantic_id)
33-
if matches is None:
34-
matches = []
33+
matches: List[model.SemanticMatch] = self.equivalence_table.get_local_matches(
34+
semantic_id=request_body.semantic_id,
35+
score_limit=request_body.score_limit
36+
)
3537
# If the request asks us to only locally look, we're done already
3638
if request_body.local_only:
3739
return service_model.MatchResponse(matches=matches)
40+
# Now look for remote matches:
41+
additional_remote_matches: List[model.SemanticMatch] = []
42+
for match in matches:
43+
remote_matching_service = self._get_matcher_from_semantic_id(match.match_semantic_id)
44+
remote_matching_request = service_model.MatchRequest(
45+
semantic_id=match.match_semantic_id,
46+
# This is a simple "Ungleichung"
47+
# Unified score is multiplied: score(A->B) * score(B->C)
48+
# This score should be larger or equal than the requested score_limit:
49+
# score(A->B) * score(B->C) >= score_limit
50+
# score(A->B) is well known, as it is the `match.score`
51+
# => score(B->C) >= (score_limit/score(A->B))
52+
score_limit=float(request_body.score_limit/match.score),
53+
# If we already request a remote score, it does not make sense to choose `local_only`
54+
local_only=False,
55+
name=request_body.name,
56+
definition=request_body.definition
57+
)
58+
new_matches_response = requests.get(remote_matching_service, data=remote_matching_request)
59+
match_response: service_model.MatchResponse = service_model.MatchResponse.model_dump_json(
60+
new_matches_response.json()
61+
)
62+
additional_remote_matches.extend(match_response.matches)
63+
# Finally, put all matches together and return
64+
matches.extend(additional_remote_matches)
65+
return service_model.MatchResponse(matches=matches)
3866

39-
# Todo: There is a difference between searching and matching two given semantic IDs
67+
def _get_matcher_from_semantic_id(self, semantic_id: str) -> str:
68+
"""
69+
Finds the suiting `SemanticMatchingService` for the given `semantic_id`.
4070
41-
def _get_matcher_from_semantic_id(self, semantic_id: str):
42-
pass
71+
:returns: The endpoint with which the `SemanticMatchingService` can be accessed
72+
"""
73+
return "Todo" # todo

semantic_matcher/service_model.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from pydantic import BaseModel
44

5-
import matcher
5+
import model
66

77

88
class MatchRequest(BaseModel):
@@ -15,10 +15,11 @@ class MatchRequest(BaseModel):
1515
:ivar definition: Optional definition of the resolved semantic ID for NLP matching
1616
"""
1717
semantic_id: str
18+
score_limit: float
1819
local_only: bool = True
1920
name: Optional[str] = None
2021
definition: Optional[str] = None
2122

2223

2324
class MatchResponse(BaseModel):
24-
matches: List[matcher.SemanticMatch]
25+
matches: List[model.SemanticMatch]

0 commit comments

Comments
 (0)