Basementup commited on
Commit
142a4ac
Β·
verified Β·
1 Parent(s): ff5486a

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -135
app.py CHANGED
@@ -10,31 +10,51 @@ from pypdf import PdfReader
10
  import docx
11
  import traceback
12
 
13
- DATA_FILE = "legislation_rules.json" # Relative path for HF environment
 
14
 
15
- def load_data():
16
- if os.path.exists(DATA_FILE):
17
- with open(DATA_FILE, 'r') as f:
18
  try:
19
  return json.load(f)
20
  except:
21
  return []
22
  return []
23
 
24
- def save_data(data):
25
- with open(DATA_FILE, 'w') as f:
26
  json.dump(data, f, indent=2)
27
 
28
  def get_canonical_hash(text):
29
  return hashlib.sha256(text.strip().encode('utf-8')).hexdigest()
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def add_rule_manually(act, title, text, source):
32
- data = load_data()
33
  det_id = get_canonical_hash(text)
34
-
35
  if any(r['deterministic_id'] == det_id for r in data):
36
- return f"Warning: Rule '{title}' already exists (matching hash)."
37
-
38
  new_rule = {
39
  "act": act,
40
  "section_title": title,
@@ -44,152 +64,131 @@ def add_rule_manually(act, title, text, source):
44
  "added_at": datetime.now().isoformat()
45
  }
46
  data.append(new_rule)
47
- save_data(data)
48
- return f"Successfully added: {title} from {act}"
 
 
 
 
 
49
 
50
- def process_document(file_obj, act_name):
51
  """
52
- Robustly extracts text from PDF, DOCX, or TXT files.
53
  """
54
- if file_obj is None:
55
- return "Error: No file was uploaded."
 
56
 
57
- # Handle Gradio's file object (it can be a string path or a file-like object)
58
- file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
 
 
 
 
59
 
60
- if not os.path.exists(file_path):
61
- return f"Error: File not found at {file_path}. Please try uploading again."
62
 
63
- ext = os.path.splitext(file_path)[1].lower()
64
- text = ""
65
- title = os.path.basename(file_path)
 
66
 
67
- try:
68
- if ext == ".pdf":
69
- reader = PdfReader(file_path)
70
- for page in reader.pages:
71
- extracted = page.extract_text()
72
- if extracted:
73
- text += extracted + "\n"
74
- elif ext == ".docx":
75
- doc = docx.Document(file_path)
76
- text = "\n".join([para.text for para in doc.paragraphs])
77
- elif ext == ".txt":
78
- with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
79
- text = f.read()
80
- else:
81
- return f"Error: Unsupported file type '{ext}'. Use PDF, DOCX, or TXT."
82
-
83
- if not text.strip():
84
- return "Error: Could not extract any text. The file might be empty or an image-based PDF."
85
-
86
- return add_rule_manually(act_name, title, text, f"Uploaded File: {title}")
87
- except Exception as e:
88
- error_msg = traceback.format_exc()
89
- print(error_msg) # Log to HF console
90
- return f"Processing Failed: {str(e)}"
91
-
92
- def scrape_fca_prin():
93
- url = "https://handbook.fca.org.uk/handbook/PRIN/2/1.html"
94
- try:
95
- response = requests.get(url, timeout=10)
96
- soup = BeautifulSoup(response.content, 'html.parser')
97
- principles_table = soup.find('table')
98
- if not principles_table:
99
- return "Error: Could not find the Principles table. The FCA site structure may have changed."
100
- rows = principles_table.find_all('tr')
101
- added_count = 0
102
- for row in rows:
103
- cols = row.find_all('td')
104
- if len(cols) >= 2:
105
- title = cols[0].get_text(strip=True)
106
- text = cols[1].get_text(strip=True)
107
- status = add_rule_manually("FCA Handbook: PRIN", f"Principle {title}", text, url)
108
- if "Successfully" in status:
109
- added_count += 1
110
- return f"Successfully ingested {added_count} FCA Principles."
111
- except Exception as e:
112
- return f"FCA Scraping failed: {str(e)}"
113
 
114
- def sync_to_huggingface(token, dataset_id):
115
- if not token or not dataset_id:
116
- return "Error: HF Token and Dataset ID are required."
117
  api = HfApi()
118
  try:
119
- api.upload_file(
120
- path_or_fileobj=DATA_FILE,
121
- path_in_repo="legislation_rules.json",
122
- repo_id=dataset_id,
123
- repo_type="dataset",
124
- token=token
125
- )
126
- return f"Successfully synced dataset to {dataset_id}"
127
- except Exception as e:
128
- return f"Sync failed: {str(e)}"
129
 
130
- def view_dataset_stats():
131
- data = load_data()
132
- if not data:
133
- return "Dataset is currently empty."
134
- stats = f"Total Rules: {len(data)}\n"
135
  acts = {}
136
- for r in data:
137
- acts[r['act']] = acts.get(r['act'], 0) + 1
138
- for act, count in acts.items():
139
- stats += f"- {act}: {count} rules\n"
140
- return stats
141
 
142
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
143
- gr.Markdown("# βš–οΈ Legislation & FCA Dataset Manager")
144
- gr.Markdown("### Create and Expand your Deterministic Rules of Law Dataset")
145
 
146
- with gr.Tab("βž• Add Rule"):
 
147
  with gr.Row():
148
  with gr.Column():
149
- gr.Markdown("#### πŸ“‚ Option A: Document Ingestion")
150
- doc_act_name = gr.Textbox(label="Legislation/Source Name", placeholder="e.g., Consumer Rights Act 2015")
151
- doc_input = gr.File(label="Upload PDF, DOCX, or TXT")
152
- doc_btn = gr.Button("Process Document", variant="primary")
153
-
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  gr.Markdown("---")
155
- gr.Markdown("#### ✍️ Option B: Manual Entry")
156
- act_input = gr.Textbox(label="Act Name")
157
- title_input = gr.Textbox(label="Section Title")
158
- source_input = gr.Textbox(label="Source URL")
159
- text_input = gr.TextArea(label="Rule Text")
160
- add_btn = gr.Button("Add Manually")
161
-
162
  with gr.Column():
163
- status_out = gr.Textbox(label="Operation Status")
164
- stats_view = gr.Textbox(label="Dataset Inventory", value=view_dataset_stats())
165
  refresh_btn = gr.Button("Refresh Inventory")
166
 
167
- with gr.Tab("🏦 FCA Guidelines"):
168
- gr.Markdown("### πŸ› οΈ FCA Handbook Automation")
169
- fca_btn = gr.Button("Ingest FCA PRIN Principles", variant="secondary")
170
- fca_status = gr.Textbox(label="FCA Ingestion Status")
171
-
172
- with gr.Tab("☁️ Sync to Hugging Face"):
173
- hf_token = gr.Textbox(label="HF Write Token", type="password")
174
- hf_id = gr.Textbox(label="Dataset ID")
175
- sync_btn = gr.Button("Sync to Dataset")
176
- sync_status = gr.Textbox(label="Sync Status")
177
-
178
- add_btn.click(
179
- fn=add_rule_manually,
180
- inputs=[act_input, title_input, text_input, source_input],
181
- outputs=status_out
182
- )
183
-
184
- doc_btn.click(
185
- fn=process_document,
186
- inputs=[doc_input, doc_act_name],
187
- outputs=status_out
188
- )
189
-
190
- fca_btn.click(fn=scrape_fca_prin, outputs=fca_status)
191
- refresh_btn.click(fn=view_dataset_stats, outputs=stats_view)
192
- sync_btn.click(fn=sync_to_huggingface, inputs=[hf_token, hf_id], outputs=sync_status)
193
 
194
  if __name__ == "__main__":
195
  demo.launch()
 
10
  import docx
11
  import traceback
12
 
13
+ DATA_FILE = "legislation_rules.json"
14
+ RULINGS_FILE = "rulings_log.json"
15
 
16
+ def load_data(file_path):
17
+ if os.path.exists(file_path):
18
+ with open(file_path, 'r') as f:
19
  try:
20
  return json.load(f)
21
  except:
22
  return []
23
  return []
24
 
25
+ def save_data(data, file_path):
26
+ with open(file_path, 'w') as f:
27
  json.dump(data, f, indent=2)
28
 
29
  def get_canonical_hash(text):
30
  return hashlib.sha256(text.strip().encode('utf-8')).hexdigest()
31
 
32
+ def extract_text_from_any(file_obj):
33
+ if file_obj is None: return ""
34
+ file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
35
+ if not os.path.exists(file_path): return ""
36
+
37
+ ext = os.path.splitext(file_path)[1].lower()
38
+ text = ""
39
+ try:
40
+ if ext == ".pdf":
41
+ reader = PdfReader(file_path)
42
+ text = "\n".join([p.extract_text() for p in reader.pages if p.extract_text()])
43
+ elif ext == ".docx":
44
+ doc = docx.Document(file_path)
45
+ text = "\n".join([p.text for p in doc.paragraphs])
46
+ elif ext == ".txt":
47
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
48
+ text = f.read()
49
+ except Exception as e:
50
+ print(f"Extraction error: {e}")
51
+ return text
52
+
53
  def add_rule_manually(act, title, text, source):
54
+ data = load_data(DATA_FILE)
55
  det_id = get_canonical_hash(text)
 
56
  if any(r['deterministic_id'] == det_id for r in data):
57
+ return f"Warning: Rule '{title}' already exists."
 
58
  new_rule = {
59
  "act": act,
60
  "section_title": title,
 
64
  "added_at": datetime.now().isoformat()
65
  }
66
  data.append(new_rule)
67
+ save_data(data, DATA_FILE)
68
+ return f"Successfully added: {title}"
69
+
70
+ def process_rule_document(file_obj, act_name):
71
+ text = extract_text_from_any(file_obj)
72
+ if not text: return "Error: Could not extract text from rule document."
73
+ return add_rule_manually(act_name, os.path.basename(file_obj.name), text, f"File: {os.path.basename(file_obj.name)}")
74
 
75
+ def rule_on_issue(issue_description, issue_file, llm_endpoint, llm_key, llm_model):
76
  """
77
+ Framework 2.2: Regulatory Reasoning Engine with Document Intake.
78
  """
79
+ # Combine text input and file input
80
+ file_text = extract_text_from_any(issue_file)
81
+ combined_issue = f"{issue_description}\n\n[EXTRACTED FROM DOCUMENT]:\n{file_text}".strip()
82
 
83
+ if not combined_issue:
84
+ return "Error: No issue description or document provided.", ""
85
+
86
+ rules = load_data(DATA_FILE)
87
+ if not rules:
88
+ return "Error: Dataset is empty. Add rules (CRA, FCA, etc.) first.", ""
89
 
90
+ # Context window management
91
+ context = "\n".join([f"[{r['act']} - {r['section_title']}]: {r['text'][:600]}..." for r in rules[:20]])
92
 
93
+ prompt = f"""
94
+ You are a Regulatory Ruling Engine.
95
+ ISSUE TO ANALYZE:
96
+ {combined_issue}
97
 
98
+ DETERMINISTIC REFERENCE RULES:
99
+ {context}
100
+
101
+ TASK:
102
+ 1. Identify applicable rules.
103
+ 2. Provide a 'Formal Ruling' (Compliant/Non-Compliant/Warning).
104
+ 3. Cite Rule Titles and Deterministic IDs.
105
+ 4. Provide forensic justification.
106
+ """
107
+
108
+ ruling_text = "LLM Inference required for automated ruling."
109
+ if llm_endpoint and llm_key:
110
+ try:
111
+ headers = {"Authorization": f"Bearer {llm_key}", "Content-Type": "application/json"}
112
+ payload = {"model": llm_model, "messages": [{"role": "user", "content": prompt}], "temperature": 0}
113
+ response = requests.post(f"{llm_endpoint.rstrip('/')}/v1/chat/completions", json=payload, headers=headers, timeout=45)
114
+ ruling_text = response.json()['choices'][0]['message']['content']
115
+ except Exception as e:
116
+ ruling_text = f"Inference Error: {str(e)}"
117
+
118
+ # Log the ruling
119
+ rulings_log = load_data(RULINGS_FILE)
120
+ new_ruling = {
121
+ "timestamp": datetime.now().isoformat(),
122
+ "issue_summary": combined_issue[:200],
123
+ "ruling": ruling_text,
124
+ "ruling_hash": hashlib.sha256(ruling_text.encode()).hexdigest()
125
+ }
126
+ rulings_log.append(new_ruling)
127
+ save_data(rulings_log, RULINGS_FILE)
128
+
129
+ return ruling_text, f"Ruling Logged (ID: {new_ruling['ruling_hash'][:8]})"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
+ def sync_all(token, dataset_id):
132
+ if not token or not dataset_id: return "Error: Missing credentials."
 
133
  api = HfApi()
134
  try:
135
+ api.upload_file(path_or_fileobj=DATA_FILE, path_in_repo=DATA_FILE, repo_id=dataset_id, repo_type="dataset", token=token)
136
+ if os.path.exists(RULINGS_FILE):
137
+ api.upload_file(path_or_fileobj=RULINGS_FILE, path_in_repo=RULINGS_FILE, repo_id=dataset_id, repo_type="dataset", token=token)
138
+ return "Full Sync Successful."
139
+ except Exception as e: return f"Sync Failed: {str(e)}"
 
 
 
 
 
140
 
141
+ def view_stats():
142
+ data = load_data(DATA_FILE)
143
+ if not data: return "Empty"
 
 
144
  acts = {}
145
+ for r in data: acts[r['act']] = acts.get(r['act'], 0) + 1
146
+ return "\n".join([f"{k}: {v}" for k,v in acts.items()])
 
 
 
147
 
148
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
149
+ gr.Markdown("# βš–οΈ Legislation & FCA Ruling Manager")
 
150
 
151
+ with gr.Tab("πŸ›οΈ Rule on Issues"):
152
+ gr.Markdown("### πŸ“‚ Issue Document Intake")
153
  with gr.Row():
154
  with gr.Column():
155
+ issue_file = gr.File(label="Upload Issue Document (PDF/DOCX/TXT)", file_count="single")
156
+ issue_desc = gr.TextArea(label="Additional Context / Issue Description")
157
+ with gr.Accordion("βš™οΈ Inference Settings", open=False):
158
+ end = gr.Textbox(label="Endpoint", value="http://localhost:11434/v1")
159
+ key = gr.Textbox(label="Key", type="password")
160
+ mod = gr.Textbox(label="Model", value="llama3")
161
+ rule_btn = gr.Button("Generate Formal Ruling", variant="primary")
162
+ with gr.Column():
163
+ ruling_out = gr.Markdown(label="Official Ruling")
164
+ log_status = gr.Textbox(label="Audit Status")
165
+
166
+ with gr.Tab("βž• Manage Rules"):
167
+ with gr.Row():
168
+ with gr.Column():
169
+ gr.Markdown("#### πŸ“‚ Add Legislation/Handbook via File")
170
+ doc_act = gr.Textbox(label="Act/Source Name")
171
+ doc_in = gr.File(label="Upload Rule Document")
172
+ doc_btn = gr.Button("Process Rule")
173
  gr.Markdown("---")
174
+ gr.Markdown("#### ✍️ Manual Rule Entry")
175
+ m_act = gr.Textbox(label="Act"); m_tit = gr.Textbox(label="Title"); m_src = gr.Textbox(label="Source"); m_txt = gr.TextArea(label="Text")
176
+ m_btn = gr.Button("Add Rule")
 
 
 
 
177
  with gr.Column():
178
+ op_status = gr.Textbox(label="Status")
179
+ stats_out = gr.Textbox(label="Dataset Inventory", value=view_stats())
180
  refresh_btn = gr.Button("Refresh Inventory")
181
 
182
+ with gr.Tab("☁️ Sync & Audit"):
183
+ hf_t = gr.Textbox(label="HF Token", type="password"); hf_d = gr.Textbox(label="Dataset ID")
184
+ s_btn = gr.Button("Sync Everything to Dataset")
185
+ s_out = gr.Textbox(label="Sync Status")
186
+
187
+ rule_btn.click(fn=rule_on_issue, inputs=[issue_desc, issue_file, end, key, mod], outputs=[ruling_out, log_status])
188
+ doc_btn.click(fn=process_rule_document, inputs=[doc_in, doc_act], outputs=op_status)
189
+ m_btn.click(fn=add_rule_manually, inputs=[m_act, m_tit, m_txt, m_src], outputs=op_status)
190
+ refresh_btn.click(fn=view_stats, outputs=stats_out)
191
+ s_btn.click(fn=sync_all, inputs=[hf_t, hf_d], outputs=s_out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
  if __name__ == "__main__":
194
  demo.launch()