nikeshn commited on
Commit
71af2fd
Β·
verified Β·
1 Parent(s): 8b06682

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -0
app.py CHANGED
@@ -935,6 +935,129 @@ async def chat(req: ChatRequest, request: Request):
935
  )
936
 
937
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
938
  # ── Static frontend ────────────────────────────────────────────────────────
939
  @app.get("/")
940
  async def root():
 
935
  )
936
 
937
 
938
+ # ── Journal meta lookup β€” ISSN + publisher auto-fill ─────────────────────
939
+ @app.get("/api/lookup-journal-meta")
940
+ async def lookup_journal_meta(request: Request, name: str):
941
+ """
942
+ Two-layer journal lookup:
943
+ Layer 1: SCImago (exact) β†’ OpenAlex (title search) β†’ returns ISSN + publisher
944
+ Layer 2: If not found, LLM corrects spelling only β€” never returns ISSN
945
+ """
946
+ ip = get_client_ip(request)
947
+ if not check_rate_limit(ip, "global"):
948
+ raise HTTPException(429, "Too many requests.")
949
+
950
+ name = name.strip()
951
+ if not name or len(name) < 3:
952
+ return {"found": False}
953
+
954
+ # ── Layer 1a: SCImago fuzzy title match ───────────────────────────────
955
+ from scimago_index import load_subject_index
956
+ rows = load_subject_index()
957
+ name_lower = name.lower()
958
+
959
+ # Exact match first
960
+ exact = next((r for r in rows if r["title"].lower() == name_lower), None)
961
+ if exact:
962
+ return {
963
+ "found": True,
964
+ "issn": exact.get("issn", ""),
965
+ "publisher": exact.get("publisher", ""),
966
+ "title": exact["title"],
967
+ "source": "SCImago",
968
+ "confidence": "high",
969
+ }
970
+
971
+ # Partial match β€” title contains all words
972
+ words = [w for w in name_lower.split() if len(w) > 2]
973
+ partial = next((r for r in rows if all(w in r["title"].lower() for w in words)), None)
974
+ if partial:
975
+ return {
976
+ "found": True,
977
+ "issn": partial.get("issn", ""),
978
+ "publisher": partial.get("publisher", ""),
979
+ "title": partial["title"],
980
+ "source": "SCImago",
981
+ "confidence": "medium",
982
+ }
983
+
984
+ # ── Layer 1b: OpenAlex title search ───────────────────────────────────
985
+ try:
986
+ oa_client = openalex._get_client()
987
+ r = await oa_client.get(
988
+ "https://api.openalex.org/sources",
989
+ params={"search": name, "per_page": 1, "filter": "type:journal"},
990
+ timeout=5.0,
991
+ )
992
+ if r.is_success:
993
+ results = r.json().get("results", [])
994
+ if results:
995
+ s = results[0]
996
+ title = s.get("display_name", "")
997
+ issns = s.get("issn", [])
998
+ issn_l = s.get("issn_l", "")
999
+ publisher = s.get("host_organization_name", "")
1000
+ best_issn = issn_l or (issns[0] if issns else "")
1001
+ ni = norm_issn(best_issn)
1002
+ if title:
1003
+ return {
1004
+ "found": True,
1005
+ "issn": ni or best_issn,
1006
+ "publisher": publisher,
1007
+ "title": title,
1008
+ "source": "OpenAlex",
1009
+ "confidence": "high" if title.lower() == name_lower else "medium",
1010
+ }
1011
+ except Exception as e:
1012
+ logger.warning(f"[LookupJournal] OpenAlex error: {e}")
1013
+
1014
+ # ── Layer 2: LLM spelling correction only β€” NO ISSN ──────────────────
1015
+ try:
1016
+ client = get_client()
1017
+ resp = await client.chat.completions.create(
1018
+ model=MODEL,
1019
+ response_format={"type": "json_object"},
1020
+ messages=[
1021
+ {
1022
+ "role": "system",
1023
+ "content": (
1024
+ "You are an academic journal name expert. "
1025
+ "The user has typed a journal name that may be misspelled or abbreviated. "
1026
+ "Return ONLY valid JSON: "
1027
+ "{corrected_name: string, confidence: high|medium|low, known: bool}. "
1028
+ "corrected_name: the most likely correct full journal name. "
1029
+ "If you do not recognise it, return the input unchanged. "
1030
+ "confidence: high if certain, medium if likely, low if guessing. "
1031
+ "known: true only if you are confident this journal exists. "
1032
+ "Do NOT return ISSN or publisher. Return JSON only."
1033
+ ),
1034
+ },
1035
+ {"role": "user", "content": f"Journal name input: {name}"},
1036
+ ],
1037
+ max_tokens=100,
1038
+ temperature=0.1,
1039
+ )
1040
+ raw = resp.choices[0].message.content or "{}"
1041
+ parsed = json.loads(raw)
1042
+ corrected = parsed.get("corrected_name", name).strip()
1043
+ confidence = parsed.get("confidence", "low")
1044
+ known = parsed.get("known", False)
1045
+
1046
+ # Only suggest if different from input and model is confident it exists
1047
+ if known and corrected.lower() != name_lower and confidence in ("high", "medium"):
1048
+ return {
1049
+ "found": False,
1050
+ "suggestion": corrected,
1051
+ "suggestion_confidence": confidence,
1052
+ "source": "LLM",
1053
+ "message": f"Did you mean: {corrected}?",
1054
+ }
1055
+ except Exception as e:
1056
+ logger.warning(f"[LookupJournal] LLM error: {e}")
1057
+
1058
+ return {"found": False}
1059
+
1060
+
1061
  # ── Static frontend ────────────────────────────────────────────────────────
1062
  @app.get("/")
1063
  async def root():