File size: 4,048 Bytes
19a3093
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""
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]