NurseLex / cached_legislation.py
NurseCitizenDeveloper's picture
fix: resolve remaining merge conflicts across all affected files
27c849b
"""
NurseLex — Pre-cached legislation for offline lookup.
Loads full section text from nursing_sections.json (1,000+ sections).
Source: legislation.gov.uk (Crown Copyright, OGL v3.0) via i.AI Lex API.
"""
import os
import json
import logging
logger = logging.getLogger(__name__)
# --- Configuration ---
JSON_PATH = os.path.join(os.path.dirname(__file__), "nursing_sections.json")
# --- Load Sections ---
CACHED_SECTIONS = []
try:
if os.path.exists(JSON_PATH):
with open(JSON_PATH, "r", encoding="utf-8") as f:
CACHED_SECTIONS = json.load(f)
logger.info(f"Loaded {len(CACHED_SECTIONS)} sections from {JSON_PATH}")
else:
logger.warning(f"Metadata file {JSON_PATH} not found.")
except Exception as e:
logger.error(f"Error loading {JSON_PATH}: {e}")
# --- Keyword Map for Natural Language Shortcuts ---
# Direct section links for common nursing search terms
KEYWORD_MAP = {
"nurse holding power": ("ukpga/1983/20", "5(4)"),
"doctor holding power": ("ukpga/1983/20", "5(2)"),
"section 2": ("ukpga/1983/20", "2"),
"section 3": ("ukpga/1983/20", "3"),
"section 4": ("ukpga/1983/20", "4"),
"aftercare": ("ukpga/1983/20", "117"),
"section 117": ("ukpga/1983/20", "117"),
"leave of absence": ("ukpga/1983/20", "17"),
"section 17": ("ukpga/1983/20", "17"),
"place of safety": ("ukpga/1983/20", "136"),
"section 136": ("ukpga/1983/20", "136"),
"section 135": ("ukpga/1983/20", "135"),
"best interests": ("ukpga/2005/9", "4"),
"capacity test": ("ukpga/2005/9", "3"),
"functional test": ("ukpga/2005/9", "3"),
"mca principles": ("ukpga/2005/9", "1"),
"safeguarding": ("ukpga/2014/23", "42"),
"section 42": ("ukpga/2014/23", "42"),
"advocacy": ("ukpga/2014/23", "67"),
}
def search_cached(query: str, max_results: int = 5) -> list:
"""
Search local sections by keyword, title, or legislation ID.
Returns a list of section dictionaries.
"""
if not query:
return []
query = query.lower().strip()
results = []
# 1. Check Keyword Map First (High Precision)
for kw, (leg_id, sec_num) in KEYWORD_MAP.items():
if kw in query:
# Find the specific section in our list
for s in CACHED_SECTIONS:
if s.get("legislation_id") == leg_id and str(s.get("number")) == sec_num:
if s not in results:
results.append(s)
# Find closest matches if exact number not found (e.g. 5(4) vs 5)
if not results:
for s in CACHED_SECTIONS:
if s.get("legislation_id") == leg_id and str(s.get("number")).startswith(sec_num.split('(')[0]):
if s not in results:
results.append(s)
# 2. Text-based Search
# Sort sections by relevance (title match > text match)
scored_results = []
for s in CACHED_SECTIONS:
score = 0
title = s.get("title", "").lower()
text = s.get("text", "").lower()
leg_id = s.get("legislation_id", "").lower()
num = str(s.get("number", "")).lower()
# Exact section reference (e.g. "Section 5")
if f"section {num}" in query or f"s.{num}" in query or f"s {num}" in query:
score += 100
# Title matches
if query in title:
score += 50
# Word matches in title
for word in query.split():
if len(word) > 3 and word in title:
score += 10
# Content matches
if query in text:
score += 5
if score > 0:
scored_results.append((score, s))
# Sort and add to results
scored_results.sort(key=lambda x: x[0], reverse=True)
for _, s in scored_results:
if s not in results:
results.append(s)
if len(results) >= max_results:
break
return results[:max_results]