Spaces:
Sleeping
Sleeping
GitHub Action commited on
Commit ·
9ebcdd4
1
Parent(s): 656e173
sync: feat: integrate protected BrgyLink chatbot AI service
Browse files- app.py +18 -0
- chatbot_runtime/__init__.py +6 -0
- chatbot_runtime/data/intents_brgylink_curated.json +0 -0
- chatbot_runtime/knowledge_base.json +891 -0
- chatbot_runtime/service.py +121 -0
- chatbot_runtime/smart_classifier.py +909 -0
- test_app.py +22 -0
app.py
CHANGED
|
@@ -13,6 +13,7 @@ from werkzeug.exceptions import RequestEntityTooLarge
|
|
| 13 |
from utils.anticheat import AntiCheatEngine, AntiCheatIndeterminate, AntiCheatUnavailable
|
| 14 |
from utils.predictor import Predictor, PredictorUnavailable
|
| 15 |
from utils.verdict import get_verdict
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
load_dotenv()
|
|
@@ -28,6 +29,7 @@ AI_SERVICE_TOKEN = os.getenv('AI_SERVICE_TOKEN', '')
|
|
| 28 |
logger.info('Loading the Mission17 TensorFlow CNN verification service.')
|
| 29 |
anticheat = AntiCheatEngine()
|
| 30 |
predictor = Predictor()
|
|
|
|
| 31 |
|
| 32 |
|
| 33 |
def allowed_file(filename):
|
|
@@ -166,5 +168,21 @@ def predict():
|
|
| 166 |
return jsonify({'error': 'Processing failed'}), 500
|
| 167 |
|
| 168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
if __name__ == '__main__':
|
| 170 |
app.run(host='0.0.0.0', port=7860, debug=False)
|
|
|
|
| 13 |
from utils.anticheat import AntiCheatEngine, AntiCheatIndeterminate, AntiCheatUnavailable
|
| 14 |
from utils.predictor import Predictor, PredictorUnavailable
|
| 15 |
from utils.verdict import get_verdict
|
| 16 |
+
from chatbot_runtime.service import ChatService
|
| 17 |
|
| 18 |
|
| 19 |
load_dotenv()
|
|
|
|
| 29 |
logger.info('Loading the Mission17 TensorFlow CNN verification service.')
|
| 30 |
anticheat = AntiCheatEngine()
|
| 31 |
predictor = Predictor()
|
| 32 |
+
chatbot = ChatService()
|
| 33 |
|
| 34 |
|
| 35 |
def allowed_file(filename):
|
|
|
|
| 168 |
return jsonify({'error': 'Processing failed'}), 500
|
| 169 |
|
| 170 |
|
| 171 |
+
@app.route('/chat', methods=['POST'])
|
| 172 |
+
def chat():
|
| 173 |
+
"""Server-to-server BrgyLink AI chatbot endpoint.
|
| 174 |
+
|
| 175 |
+
It deliberately accepts only a short message and an optional opaque session
|
| 176 |
+
identifier. The mobile client never calls this Space directly and resident
|
| 177 |
+
history or account data is not sent to Hugging Face.
|
| 178 |
+
"""
|
| 179 |
+
auth_error = require_backend_service_token()
|
| 180 |
+
if auth_error:
|
| 181 |
+
return auth_error
|
| 182 |
+
|
| 183 |
+
status_code, payload = chatbot.respond(request.get_json(silent=True))
|
| 184 |
+
return jsonify(payload), status_code
|
| 185 |
+
|
| 186 |
+
|
| 187 |
if __name__ == '__main__':
|
| 188 |
app.run(host='0.0.0.0', port=7860, debug=False)
|
chatbot_runtime/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Bundled, safety-constrained BrgyLink chatbot runtime.
|
| 2 |
+
|
| 3 |
+
The source-of-truth project remains the separate BrgyLink-AI-Chatbot
|
| 4 |
+
repository. This package contains the minimal offline runtime required by the
|
| 5 |
+
shared Hugging Face AI service.
|
| 6 |
+
"""
|
chatbot_runtime/data/intents_brgylink_curated.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
chatbot_runtime/knowledge_base.json
ADDED
|
@@ -0,0 +1,891 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"metadata": {
|
| 3 |
+
"barangay_name": "Barangay Bagong Pag-asa, San Jacinto",
|
| 4 |
+
"status": "prototype_draft",
|
| 5 |
+
"last_updated": "2026-09-21",
|
| 6 |
+
"important": "Do not publish entries marked needs_staff_verification until an authorized barangay representative has confirmed them.",
|
| 7 |
+
"languages": [
|
| 8 |
+
"English",
|
| 9 |
+
"Tagalog",
|
| 10 |
+
"Ilocano",
|
| 11 |
+
"Pangasinan"
|
| 12 |
+
],
|
| 13 |
+
"municipality": "San Jacinto, Pangasinan"
|
| 14 |
+
},
|
| 15 |
+
"services": [
|
| 16 |
+
{
|
| 17 |
+
"intent": "clearance",
|
| 18 |
+
"title": "Barangay Clearance",
|
| 19 |
+
"needs_staff_verification": true,
|
| 20 |
+
"requirements": [
|
| 21 |
+
"Submit through BrgyLink Document Requests",
|
| 22 |
+
"Enter the request purpose",
|
| 23 |
+
"Attach a photo of a valid ID"
|
| 24 |
+
],
|
| 25 |
+
"fee_php": null,
|
| 26 |
+
"processing_time": null,
|
| 27 |
+
"answer": {
|
| 28 |
+
"english": "To request a Barangay Clearance, open Document Requests in BrgyLink, select Barangay Clearance, enter your purpose, attach a clear photo of your valid ID, and submit. You can track progress in the app. For fees and pickup details, please inquire at the barangay office.",
|
| 29 |
+
"tagalog": "Para humiling ng Barangay Clearance, buksan ang Document Requests sa BrgyLink, piliin ang Barangay Clearance, ilagay ang layunin, mag-attach ng malinaw na photo ng valid ID, at i-submit. Masusubaybayan ang progreso sa app. Para sa mga bayarin at pickup, magtanong sa opisina ng barangay.",
|
| 30 |
+
"ilocano": "Tapno agkiddaw iti Barangay Clearance, lukatan ti Document Requests iti BrgyLink, piliem ti Barangay Clearance, ikabil ti panggep, i-attach ti nalawag a ladawan ti valid ID, ken i-submit. Masubaybayam ti progreso iti app. Para kadagiti bayad ken pickup, saludsoden iti opisina ti barangay.",
|
| 31 |
+
"pangasinan": "Pian mangala na Barangay Clearance, lukatan so Document Requests ed BrgyLink, piliyen so Barangay Clearance, ilagay so gagala, i-attach so malinew ya photo na valid ID, tan i-submit. Nayarin bantayan so progreso ed app. Para ed saray bayar tan pickup, mantepet ed opisina na barangay."
|
| 32 |
+
},
|
| 33 |
+
"verified": false,
|
| 34 |
+
"verified_by": null,
|
| 35 |
+
"verified_at": null,
|
| 36 |
+
"expires_at": null,
|
| 37 |
+
"source": null,
|
| 38 |
+
"last_reviewed_at": null,
|
| 39 |
+
"review_notes": "",
|
| 40 |
+
"content_type": "barangay_fact",
|
| 41 |
+
"requires_verification": true
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"intent": "indigency",
|
| 45 |
+
"title": "Certificate of Indigency",
|
| 46 |
+
"needs_staff_verification": true,
|
| 47 |
+
"requirements": [
|
| 48 |
+
"Submit through BrgyLink Document Requests",
|
| 49 |
+
"Enter the request purpose",
|
| 50 |
+
"Attach a photo of a valid ID"
|
| 51 |
+
],
|
| 52 |
+
"fee_php": null,
|
| 53 |
+
"processing_time": null,
|
| 54 |
+
"answer": {
|
| 55 |
+
"english": "To request a Certificate of Indigency, open Document Requests in BrgyLink, choose Certificate of Indigency, state your purpose (e.g., medical or scholarship assistance), attach a valid ID, and submit. For eligibility requirements and processing details, please inquire at the barangay office.",
|
| 56 |
+
"tagalog": "Para humiling ng Certificate of Indigency, buksan ang Document Requests sa BrgyLink, piliin ang Certificate of Indigency, ilagay ang layunin (hal., medikal o scholarship), mag-attach ng valid ID, at i-submit. Para sa mga kinakailangan at detalye ng proseso, magtanong sa opisina ng barangay.",
|
| 57 |
+
"ilocano": "Tapno agkiddaw iti Certificate of Indigency, lukatan ti Document Requests iti BrgyLink, piliem ti Certificate of Indigency, ibaga ti panggep (kas pagarigan, medikal wenno scholarship), i-attach ti valid ID, ken i-submit. Para kadagiti kasapulan ken detalye ti proseso, saludsoden iti opisina ti barangay.",
|
| 58 |
+
"pangasinan": "Pian mangala na Certificate of Indigency, lukatan so Document Requests ed BrgyLink, piliyen so Certificate of Indigency, ilagay so gagala (singa medikal odino scholarship), i-attach so valid ID, tan i-submit. Para ed saray kailangan tan detalye na proseso, mantepet ed opisina na barangay."
|
| 59 |
+
},
|
| 60 |
+
"verified": false,
|
| 61 |
+
"verified_by": null,
|
| 62 |
+
"verified_at": null,
|
| 63 |
+
"expires_at": null,
|
| 64 |
+
"source": null,
|
| 65 |
+
"last_reviewed_at": null,
|
| 66 |
+
"review_notes": "",
|
| 67 |
+
"content_type": "barangay_fact",
|
| 68 |
+
"requires_verification": true
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"intent": "residency",
|
| 72 |
+
"title": "Certificate of Residency",
|
| 73 |
+
"needs_staff_verification": true,
|
| 74 |
+
"requirements": [
|
| 75 |
+
"Submit through BrgyLink Document Requests",
|
| 76 |
+
"Enter the request purpose",
|
| 77 |
+
"Attach a photo of a valid ID"
|
| 78 |
+
],
|
| 79 |
+
"fee_php": null,
|
| 80 |
+
"processing_time": null,
|
| 81 |
+
"answer": {
|
| 82 |
+
"english": "To request a Certificate of Residency, open Document Requests in BrgyLink, select Certificate of Residency, specify your purpose, attach a valid ID, and submit. For requirements and processing details, please inquire at the barangay office.",
|
| 83 |
+
"tagalog": "Para humiling ng Certificate of Residency, buksan ang Document Requests sa BrgyLink, piliin ang Certificate of Residency, ilagay ang layunin, mag-attach ng valid ID, at i-submit. Para sa mga kinakailangan at detalye ng proseso, magtanong sa opisina ng barangay.",
|
| 84 |
+
"ilocano": "Tapno agkiddaw iti Certificate of Residency, lukatan ti Document Requests iti BrgyLink, piliem ti Certificate of Residency, ibaga ti panggep, i-attach ti valid ID, ken i-submit. Para kadagiti kasapulan ken detalye ti proseso, saludsoden iti opisina ti barangay.",
|
| 85 |
+
"pangasinan": "Pian mangala na Certificate of Residency, lukatan so Document Requests ed BrgyLink, piliyen so Certificate of Residency, ilagay so gagala, i-attach so valid ID, tan i-submit. Para ed saray kailangan tan detalye na proseso, mantepet ed opisina na barangay."
|
| 86 |
+
},
|
| 87 |
+
"verified": false,
|
| 88 |
+
"verified_by": null,
|
| 89 |
+
"verified_at": null,
|
| 90 |
+
"expires_at": null,
|
| 91 |
+
"source": null,
|
| 92 |
+
"last_reviewed_at": null,
|
| 93 |
+
"review_notes": "",
|
| 94 |
+
"content_type": "barangay_fact",
|
| 95 |
+
"requires_verification": true
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"intent": "business_permit",
|
| 99 |
+
"title": "Barangay Business Clearance",
|
| 100 |
+
"needs_staff_verification": true,
|
| 101 |
+
"requirements": [
|
| 102 |
+
"Submit through BrgyLink Document Requests",
|
| 103 |
+
"Enter the request purpose",
|
| 104 |
+
"Attach a photo of a valid ID"
|
| 105 |
+
],
|
| 106 |
+
"fee_php": null,
|
| 107 |
+
"processing_time": null,
|
| 108 |
+
"answer": {
|
| 109 |
+
"english": "To apply for a Barangay Business Clearance, open Document Requests in BrgyLink, select Business Clearance, and submit your application. For specific requirements, fees, and processing details, please inquire at the barangay office.",
|
| 110 |
+
"tagalog": "Para mag-apply ng Barangay Business Clearance, buksan ang Document Requests sa BrgyLink, piliin ang Business Clearance, at i-submit ang application. Para sa mga kinakailangan, bayarin, at detalye ng proseso, magtanong sa opisina ng barangay.",
|
| 111 |
+
"ilocano": "Tapno ag-apply iti Barangay Business Clearance, lukatan ti Document Requests iti BrgyLink, piliem ti Business Clearance, ken i-submit ti aplikasion. Para kadagiti kasapulan, bayad, ken detalye ti proseso, saludsoden iti opisina ti barangay.",
|
| 112 |
+
"pangasinan": "Pian man-apply na Barangay Business Clearance, lukatan so Document Requests ed BrgyLink, piliyen so Business Clearance, tan i-submit so aplikasyon. Para ed saray kailangan, bayar, tan detalye na proseso, mantepet ed opisina na barangay."
|
| 113 |
+
},
|
| 114 |
+
"verified": false,
|
| 115 |
+
"verified_by": null,
|
| 116 |
+
"verified_at": null,
|
| 117 |
+
"expires_at": null,
|
| 118 |
+
"source": null,
|
| 119 |
+
"last_reviewed_at": null,
|
| 120 |
+
"review_notes": "",
|
| 121 |
+
"content_type": "barangay_fact",
|
| 122 |
+
"requires_verification": true
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"intent": "office_hours",
|
| 126 |
+
"title": "Office Hours and Location",
|
| 127 |
+
"needs_staff_verification": true,
|
| 128 |
+
"office_hours": null,
|
| 129 |
+
"office_address": null,
|
| 130 |
+
"contact": null,
|
| 131 |
+
"answer": {
|
| 132 |
+
"english": "I cannot confirm a physical address or live office schedule. For the location of Barangay Bagong Pag-asa, please check official BrgyLink Announcements or inquire directly with the barangay office. Use the same channels to confirm office hours before visiting.",
|
| 133 |
+
"tagalog": "Hindi ko makukumpirma ang pisikal na address o kasalukuyang office schedule. Para sa lokasyon ng Barangay Bagong Pag-asa, tingnan ang official Announcements sa BrgyLink o magtanong mismo sa opisina ng barangay. Doon din kumpirmahin ang oras ng opisina bago bumisita.",
|
| 134 |
+
"ilocano": "Saan a makumpirma ti chatbot ti pisikal a address wenno agdama nga iskediul ti opisina. Para iti lokasion ti Barangay Bagong Pag-asa, kitaem ti opisial nga Announcements iti BrgyLink wenno saludsodem mismo iti opisina ti barangay. Ikumpirmam met ti oras ti opisina sakbay a bumisita.",
|
| 135 |
+
"pangasinan": "Ag na-confirm na chatbot so pisikal ya address odino kasalukuyan ya schedule na opisina. Para ed lokasyon na Barangay Bagong Pag-asa, nengnengen so opisyal ya Announcements ed BrgyLink odino mantepet mismo ed opisina na barangay. I-confirm met so oras na opisina antes bumisita."
|
| 136 |
+
},
|
| 137 |
+
"verified": false,
|
| 138 |
+
"verified_by": null,
|
| 139 |
+
"verified_at": null,
|
| 140 |
+
"expires_at": null,
|
| 141 |
+
"source": null,
|
| 142 |
+
"last_reviewed_at": null,
|
| 143 |
+
"review_notes": "",
|
| 144 |
+
"content_type": "barangay_fact",
|
| 145 |
+
"requires_verification": true
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"intent": "fees",
|
| 149 |
+
"title": "Service Fees",
|
| 150 |
+
"needs_staff_verification": true,
|
| 151 |
+
"answer": {
|
| 152 |
+
"english": "Service fees for barangay certificates and clearances vary. For the current fee schedule, please inquire at the barangay office or check the Announcements section in BrgyLink.",
|
| 153 |
+
"tagalog": "Ang mga bayarin sa sertipiko at clearance ng barangay ay iba-iba. Para sa kasalukuyang listahan ng bayarin, magtanong sa opisina ng barangay o tingnan ang Announcements sa BrgyLink.",
|
| 154 |
+
"ilocano": "Dagiti bayad kadagiti sertipiko ken clearance ti barangay ket agduduma. Para iti agdama a listaan dagiti bayad, saludsoden iti opisina ti barangay wenno kitaen ti Announcements iti BrgyLink.",
|
| 155 |
+
"pangasinan": "Saray bayar ed sertipiko tan clearance na barangay et nanduruma. Para ed listaan na saray bayar natan, mantepet ed opisina na barangay odino nengnengen so Announcements ed BrgyLink."
|
| 156 |
+
},
|
| 157 |
+
"verified": false,
|
| 158 |
+
"verified_by": null,
|
| 159 |
+
"verified_at": null,
|
| 160 |
+
"expires_at": null,
|
| 161 |
+
"source": null,
|
| 162 |
+
"last_reviewed_at": null,
|
| 163 |
+
"review_notes": "",
|
| 164 |
+
"content_type": "barangay_fact",
|
| 165 |
+
"requires_verification": true
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"intent": "report_incident",
|
| 169 |
+
"title": "Complaint or Blotter",
|
| 170 |
+
"needs_staff_verification": true,
|
| 171 |
+
"answer": {
|
| 172 |
+
"english": "To file an incident report, open Blotter Reports in BrgyLink, choose File New Report, select the incident category, enter the details and location, and submit. You can track your report status in the app. For emergencies, call 911.",
|
| 173 |
+
"tagalog": "Para mag-file ng incident report, buksan ang Blotter Reports sa BrgyLink, piliin ang File New Report, piliin ang kategorya, ilagay ang mga detalye at lokasyon, at i-submit. Masusubaybayan ang status ng report sa app. Para sa emergency, tumawag sa 911.",
|
| 174 |
+
"ilocano": "Tapno ag-file iti incident report, lukatan ti Blotter Reports iti BrgyLink, piliem ti File New Report, piliem ti kategorya, ikabil dagiti detalye ken lokasion, ken i-submit. Masubaybayam ti status ti report iti app. Para iti emergency, umawag iti 911.",
|
| 175 |
+
"pangasinan": "Pian man-file na incident report, lukatan so Blotter Reports ed BrgyLink, piliyen so File New Report, piliyen so kategorya, ilagay so saray detalye tan lokasyon, tan i-submit. Nayarin bantayan so status na report ed app. Para ed emergency, tawagan so 911."
|
| 176 |
+
},
|
| 177 |
+
"verified": false,
|
| 178 |
+
"verified_by": null,
|
| 179 |
+
"verified_at": null,
|
| 180 |
+
"expires_at": null,
|
| 181 |
+
"source": null,
|
| 182 |
+
"last_reviewed_at": null,
|
| 183 |
+
"review_notes": "",
|
| 184 |
+
"content_type": "barangay_fact",
|
| 185 |
+
"requires_verification": true
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"intent": "emergency",
|
| 189 |
+
"title": "Emergency",
|
| 190 |
+
"needs_staff_verification": true,
|
| 191 |
+
"emergency_contacts": {
|
| 192 |
+
"national_emergency": "911",
|
| 193 |
+
"barangay_tanod": null,
|
| 194 |
+
"fire": null,
|
| 195 |
+
"ambulance": null
|
| 196 |
+
},
|
| 197 |
+
"answer": {
|
| 198 |
+
"english": "If there is immediate danger to life or property, call 911 immediately. Do not wait for a chatbot reply. The chatbot cannot dispatch help or contact emergency services for you.",
|
| 199 |
+
"tagalog": "Kung may agarang panganib sa buhay o ari-arian, tumawag agad sa 911. Huwag maghintay ng sagot ng chatbot. Hindi kayang magpadala ng tulong o tumawag sa emergency services para sa iyo ang chatbot.",
|
| 200 |
+
"ilocano": "No adda dagus a peggad iti biag wenno sanikua, umawag a dagus iti 911. Saan nga urayen ti sungbat ti chatbot. Saan a makapatulod ti chatbot iti tulong wenno umawag kadagiti emergency services para kenka.",
|
| 201 |
+
"pangasinan": "No walay tampol ya peligro ed bilay odino kayarian, tawagan ya tampol so 911. Ag-antayan so ebat na chatbot. Ag makapatulod na tulong so chatbot odino ontawag ed emergency services para ed sika."
|
| 202 |
+
},
|
| 203 |
+
"verified": false,
|
| 204 |
+
"verified_by": null,
|
| 205 |
+
"verified_at": null,
|
| 206 |
+
"expires_at": null,
|
| 207 |
+
"source": null,
|
| 208 |
+
"last_reviewed_at": null,
|
| 209 |
+
"review_notes": "",
|
| 210 |
+
"content_type": "barangay_fact",
|
| 211 |
+
"requires_verification": true
|
| 212 |
+
},
|
| 213 |
+
{
|
| 214 |
+
"intent": "garbage",
|
| 215 |
+
"title": "Garbage Collection",
|
| 216 |
+
"needs_staff_verification": true,
|
| 217 |
+
"schedule": null,
|
| 218 |
+
"answer": {
|
| 219 |
+
"english": "Garbage collection in Barangay Bagong Pag-asa follows a designated schedule per purok. For your collection day and segregation guidelines, please inquire at the barangay office or check the Announcements section in BrgyLink.",
|
| 220 |
+
"tagalog": "Ang hakot ng basura sa Barangay Bagong Pag-asa ay may takdang schedule bawat purok. Para sa araw ng koleksyon at mga panuntunan sa segregasyon, magtanong sa opisina ng barangay o tingnan ang Announcements sa BrgyLink.",
|
| 221 |
+
"ilocano": "Ti panagala ti basura iti Barangay Bagong Pag-asa ket addaan iti naikeddeng nga iskediul tunggal purok. Para iti aldaw ti koleksion ken paglintegan ti segregasion, saludsoden iti opisina ti barangay wenno kitaen ti Announcements iti BrgyLink.",
|
| 222 |
+
"pangasinan": "Say panangala na basura ed Barangay Bagong Pag-asa et walay niganan ya schedule ed balang purok. Para ed agew na koleksyon tan saray totontonen ed segregasyon, mantepet ed opisina na barangay odino nengnengen so Announcements ed BrgyLink."
|
| 223 |
+
},
|
| 224 |
+
"verified": false,
|
| 225 |
+
"verified_by": null,
|
| 226 |
+
"verified_at": null,
|
| 227 |
+
"expires_at": null,
|
| 228 |
+
"source": null,
|
| 229 |
+
"last_reviewed_at": null,
|
| 230 |
+
"review_notes": "",
|
| 231 |
+
"content_type": "barangay_fact",
|
| 232 |
+
"requires_verification": true
|
| 233 |
+
},
|
| 234 |
+
{
|
| 235 |
+
"intent": "good_moral",
|
| 236 |
+
"title": "Certificate of Good Moral Character",
|
| 237 |
+
"needs_staff_verification": true,
|
| 238 |
+
"requirements": [
|
| 239 |
+
"Submit through BrgyLink Document Requests",
|
| 240 |
+
"Enter the request purpose",
|
| 241 |
+
"Attach a photo of a valid ID"
|
| 242 |
+
],
|
| 243 |
+
"answer": {
|
| 244 |
+
"english": "To request a Certificate of Good Moral Character, open Document Requests in BrgyLink, select Good Moral Character, state your purpose, attach a valid ID, and submit. For requirements and processing details, please inquire at the barangay office.",
|
| 245 |
+
"tagalog": "Para humiling ng Certificate of Good Moral Character, buksan ang Document Requests sa BrgyLink, piliin ang Good Moral Character, ilagay ang layunin, mag-attach ng valid ID, at i-submit. Para sa mga kinakailangan at detalye ng proseso, magtanong sa opisina ng barangay.",
|
| 246 |
+
"ilocano": "Tapno agkiddaw iti Certificate of Good Moral Character, lukatan ti Document Requests iti BrgyLink, piliem ti Good Moral Character, ibaga ti panggep, i-attach ti valid ID, ken i-submit. Para kadagiti kasapulan ken detalye ti proseso, saludsoden iti opisina ti barangay.",
|
| 247 |
+
"pangasinan": "Pian mangala na Certificate of Good Moral Character, lukatan so Document Requests ed BrgyLink, piliyen so Good Moral Character, ilagay so gagala, i-attach so valid ID, tan i-submit. Para ed saray kailangan tan detalye na proseso, mantepet ed opisina na barangay."
|
| 248 |
+
},
|
| 249 |
+
"verified": false,
|
| 250 |
+
"verified_by": null,
|
| 251 |
+
"verified_at": null,
|
| 252 |
+
"expires_at": null,
|
| 253 |
+
"source": null,
|
| 254 |
+
"last_reviewed_at": null,
|
| 255 |
+
"review_notes": "",
|
| 256 |
+
"content_type": "barangay_fact",
|
| 257 |
+
"requires_verification": true
|
| 258 |
+
},
|
| 259 |
+
{
|
| 260 |
+
"intent": "barangay_id",
|
| 261 |
+
"title": "Barangay ID",
|
| 262 |
+
"needs_staff_verification": true,
|
| 263 |
+
"requirements": [
|
| 264 |
+
"Submit through BrgyLink Document Requests",
|
| 265 |
+
"Enter the request purpose",
|
| 266 |
+
"Attach a photo of a valid ID when requested"
|
| 267 |
+
],
|
| 268 |
+
"answer": {
|
| 269 |
+
"english": "To apply for a Barangay ID, open Document Requests in BrgyLink, select Barangay ID, provide your resident details, and submit. For specific requirements and processing time, please inquire at the barangay office.",
|
| 270 |
+
"tagalog": "Para mag-apply ng Barangay ID, buksan ang Document Requests sa BrgyLink, piliin ang Barangay ID, ilagay ang iyong mga detalye bilang residente, at i-submit. Para sa mga kinakailangan at oras ng proseso, magtanong sa opisina ng barangay.",
|
| 271 |
+
"ilocano": "Tapno ag-apply iti Barangay ID, lukatan ti Document Requests iti BrgyLink, piliem ti Barangay ID, ikabil dagiti detalye a kas residente, ken i-submit. Para kadagiti kasapulan ken oras ti proseso, saludsoden iti opisina ti barangay.",
|
| 272 |
+
"pangasinan": "Pian man-apply na Barangay ID, lukatan so Document Requests ed BrgyLink, piliyen so Barangay ID, ilagay so saray detalye bilang residente, tan i-submit. Para ed saray kailangan tan oras na proseso, mantepet ed opisina na barangay."
|
| 273 |
+
},
|
| 274 |
+
"verified": false,
|
| 275 |
+
"verified_by": null,
|
| 276 |
+
"verified_at": null,
|
| 277 |
+
"expires_at": null,
|
| 278 |
+
"source": null,
|
| 279 |
+
"last_reviewed_at": null,
|
| 280 |
+
"review_notes": "",
|
| 281 |
+
"content_type": "barangay_fact",
|
| 282 |
+
"requires_verification": true
|
| 283 |
+
},
|
| 284 |
+
{
|
| 285 |
+
"intent": "document_status",
|
| 286 |
+
"title": "Document Request Status",
|
| 287 |
+
"needs_staff_verification": false,
|
| 288 |
+
"statuses": [
|
| 289 |
+
"Pending",
|
| 290 |
+
"Processing",
|
| 291 |
+
"Ready for Pickup",
|
| 292 |
+
"Completed",
|
| 293 |
+
"Rejected"
|
| 294 |
+
],
|
| 295 |
+
"answer": {
|
| 296 |
+
"english": "To check the status of your document request, open Document Requests in BrgyLink. Your request will show as Pending, Processing, Ready for Pickup (available at the barangay hall), Completed, or Rejected with reason noted.",
|
| 297 |
+
"tagalog": "Upang suriin ang status ng iyong hiniling na dokumento, buksan ang 'Document Requests' sa BrgyLink. Dito makikita kung ito ay: Pending (nakabinbin), Processing (inaasikaso), Ready for Pickup (maaari nang kunin sa barangay hall), Completed, o Rejected. May kaukulang dahilan din kung sakaling ma-reject ang request.",
|
| 298 |
+
"ilocano": "Tapno makita ti kasasaad ti kineddawmo a dokumento, lukatan ti 'Document Requests' iti BrgyLink. Dita makitam no: Pending, Processing, Ready for Pickup (mabalinen nga alaen iti barangay hall), Completed, wenno Rejected.",
|
| 299 |
+
"pangasinan": "Pian nengnengen so status na kineddeng mon dokumento, lukatan so 'Document Requests' diad BrgyLink. Ditan nanengneng no: Pending, Processing, Ready for Pickup (nayari lay alaen ed barangay hall), Completed, odino Rejected."
|
| 300 |
+
},
|
| 301 |
+
"verified": false,
|
| 302 |
+
"verified_by": null,
|
| 303 |
+
"verified_at": null,
|
| 304 |
+
"expires_at": null,
|
| 305 |
+
"source": null,
|
| 306 |
+
"last_reviewed_at": null,
|
| 307 |
+
"review_notes": "",
|
| 308 |
+
"content_type": "barangay_fact",
|
| 309 |
+
"requires_verification": true
|
| 310 |
+
},
|
| 311 |
+
{
|
| 312 |
+
"intent": "registration",
|
| 313 |
+
"title": "Resident Registration",
|
| 314 |
+
"needs_staff_verification": false,
|
| 315 |
+
"steps": [
|
| 316 |
+
"Tap Create Account",
|
| 317 |
+
"Enter full name, email, username, phone number, and home address",
|
| 318 |
+
"Complete email verification"
|
| 319 |
+
],
|
| 320 |
+
"answer": {
|
| 321 |
+
"english": "To register, begin with your email in Step 1. Enter the six-digit code sent to that email before continuing. Then complete your resident details, upload clear photos of the required government-issued ID, create your password in the final step, accept the Privacy Notice and Terms of Use, and submit the registration for barangay review. Residents must be at least 15 years old. Never share your verification code.",
|
| 322 |
+
"tagalog": "Para mag-register, magsimula sa email sa Step 1. Ilagay ang anim na digit na code na ipinadala sa email bago magpatuloy. Kumpletuhin ang resident details, mag-upload ng malinaw na larawan ng hinihinging government-issued ID, gumawa ng password sa huling step, tanggapin ang Privacy Notice at Terms of Use, at isumite ang registration para sa barangay review. Kailangan ay hindi bababa sa 15 taong gulang. Huwag ibahagi ang verification code.",
|
| 323 |
+
"ilocano": "Tapno agparehistro, mangrugi iti email iti Step 1 ken ikabil ti innem a digit a code sakbay a mangtuloy. Kumpletoem dagiti resident details ken mang-upload iti nalawag a ladawan ti government-issued ID. Aramidem ti password iti maudi a step, awatem ti Privacy Notice ken Terms of Use, ken isumitem para iti barangay review. Masapul nga agtawen iti 15 wenno nangatngato. Saan nga ibagbaga ti verification code.",
|
| 324 |
+
"pangasinan": "Pian mag-register, manggapo ed email ed Step 1 tan isulat so anem ya digit a code antes ontuloy. Kumpletoen so resident details tan mang-upload na malinew ya litrato na government-issued ID. Gawaen so password ed unor ya step, awaten so Privacy Notice tan Terms of Use, tan isumite para ed barangay review. Kasapulan ya 15 taon odino mas matanda. Agmon iter so verification code."
|
| 325 |
+
},
|
| 326 |
+
"verified": false,
|
| 327 |
+
"verified_by": null,
|
| 328 |
+
"verified_at": null,
|
| 329 |
+
"expires_at": null,
|
| 330 |
+
"source": null,
|
| 331 |
+
"last_reviewed_at": null,
|
| 332 |
+
"review_notes": "",
|
| 333 |
+
"content_type": "barangay_fact",
|
| 334 |
+
"requires_verification": true,
|
| 335 |
+
"answer_variants": {
|
| 336 |
+
"id": {
|
| 337 |
+
"english": "Residents must be at least 15 to register. In the final signup step, select your ID type and take a clear photo: all details must be readable, with no glare, blur, or cut-off edges. Upload the front and back when requested; Passport uses the information page. If you do not have an accepted ID, ask the barangay office what to do. The chatbot cannot approve an ID or bypass the requirement.",
|
| 338 |
+
"tagalog": "Kailangan ay hindi bababa sa 15 taong gulang para mag-register. Sa huling signup step, piliin ang ID type at kumuha ng malinaw na larawan: nababasa ang detalye, walang glare o blur, at hindi putol ang gilid. I-upload ang front at back kung hinihingi; para sa Passport, information page ang kailangan. Kung walang tinatanggap na ID, magtanong sa opisina ng barangay. Hindi kayang mag-approve ng ID o lumampas sa requirement ang chatbot.",
|
| 339 |
+
"ilocano": "Masapul nga agtawen iti 15 wenno nangatngato tapno agparehistro. Iti maudi a signup step, piliem ti ID type ken alaem ti nalawag a ladawan: mabasa amin a detalye, awan glare wenno blur, ken saan a naputed ti igid. I-upload ti front ken back no kasapulan; ti Passport ket information page. No awan ti maawat nga ID, agsaludsod iti opisina ti barangay. Saan a maka-approve ti chatbot iti ID wenno makalabsing iti requirement.",
|
| 340 |
+
"pangasinan": "Kasapulan ya 15 taon odino mas matanda pian mag-register. Ed unor ya signup step, piliyen so ID type tan alaen so malinew ya litrato: nabasa so amin ya detalye, anggapo so glare odino blur, tan agputol so gilid. I-upload so front tan back no kasapulan; say Passport et information page. No anggapoy tinatanggap ya ID, mantepet ed opisina na barangay. Ag maka-approve na ID so chatbot odino makalampas ed requirement."
|
| 341 |
+
}
|
| 342 |
+
}
|
| 343 |
+
},
|
| 344 |
+
{
|
| 345 |
+
"intent": "account_help",
|
| 346 |
+
"title": "Login and Email OTP",
|
| 347 |
+
"needs_staff_verification": false,
|
| 348 |
+
"answer": {
|
| 349 |
+
"english": "For a registration code, check Inbox, Spam, and Junk and make sure the email was entered correctly. Wait for the resend timer in the app before requesting another code; an expired code needs a new request. Do not share the code. If your registration was submitted, wait for barangay review before signing in. For a forgotten password, use Forgot Password on the sign-in screen.",
|
| 350 |
+
"tagalog": "Para sa registration code, tingnan ang Inbox, Spam, at Junk at tiyaking tama ang email na inilagay. Hintayin ang resend timer sa app bago humiling ulit ng code; kung expired na ang code, humiling ng bago. Huwag ibahagi ang code. Kung naisumite na ang registration, hintayin muna ang barangay review bago mag-sign in. Para sa nakalimutang password, gamitin ang Forgot Password sa sign-in screen.",
|
| 351 |
+
"ilocano": "No awan ti registration code, kitaem ti Inbox, Spam, ken Junk ken siguraduem a husto ti email. Urayem ti resend timer iti app sakbay a mangkiddaw manen iti code. Saan nga ibagbaga ti code. No naipasa ti registration, urayem ti barangay review sakbay a sumrek.",
|
| 352 |
+
"pangasinan": "No anggapo so registration code, nengnengen so Inbox, Spam, tan Junk tan seguroen ya duga so email. Antayan so resend timer ed app antes mangikeddeng lamet na code. Agmon iter so code. No naisumite lay registration, antayan so barangay review antes sumrek."
|
| 353 |
+
},
|
| 354 |
+
"verified": false,
|
| 355 |
+
"verified_by": null,
|
| 356 |
+
"verified_at": null,
|
| 357 |
+
"expires_at": null,
|
| 358 |
+
"source": null,
|
| 359 |
+
"last_reviewed_at": null,
|
| 360 |
+
"review_notes": "",
|
| 361 |
+
"content_type": "barangay_fact",
|
| 362 |
+
"requires_verification": true,
|
| 363 |
+
"answer_variants": {
|
| 364 |
+
"password": {
|
| 365 |
+
"english": "On the sign-in screen, tap Forgot Password, enter the email for your account, and follow the reset instructions sent to it. Check Inbox, Spam, and Junk. Use a newly requested reset link if the old one is invalid or expired. Never send your password or reset link to the chatbot. The chatbot cannot reset your password for you.",
|
| 366 |
+
"tagalog": "Sa sign-in screen, i-tap ang Forgot Password, ilagay ang email ng account, at sundin ang reset instructions na ipinadala rito. Tingnan ang Inbox, Spam, at Junk. Humiling ng bagong reset link kung invalid o expired na ang luma. Huwag ipadala sa chatbot ang password o reset link. Hindi kayang i-reset ng chatbot ang password para sa iyo.",
|
| 367 |
+
"ilocano": "Iti sign-in screen, i-tap ti Forgot Password, ikabil ti email ti account, ken surotem ti reset instructions iti email. Kitaem ti Inbox, Spam, ken Junk. Mangikiddaw iti baro a reset link no invalid wenno expired ti daan. Saan nga ipatulod iti chatbot ti password wenno reset link. Saan a ma-reset ti chatbot ti password mo.",
|
| 368 |
+
"pangasinan": "Ed sign-in screen, i-tap so Forgot Password, isulat so email na account, tan suroten so reset instructions ed email. Nengnengen so Inbox, Spam, tan Junk. Mangikeddeng na balon reset link no invalid odino expired lay daan. Agmon ipatulod ed chatbot so password odino reset link. Ag na-reset na chatbot so password mo."
|
| 369 |
+
},
|
| 370 |
+
"otp": {
|
| 371 |
+
"english": "Check that the signup email is correct and look in Inbox, Spam, and Junk. If the code has not arrived, wait for the app's resend timer, then request a new code. Enter the latest six-digit code; request another if it is expired or invalid. You can edit an incorrect email on the verification screen. Never share your OTP. The chatbot cannot see your code or confirm delivery.",
|
| 372 |
+
"tagalog": "Tiyaking tama ang signup email at tingnan ang Inbox, Spam, at Junk. Kung wala pa ang code, hintayin ang resend timer sa app at humiling ng bagong code. Ilagay ang pinakabagong anim na digit na code; humiling ulit kung expired o invalid. Maaari mong itama ang email sa verification screen. Huwag ibahagi ang OTP. Hindi nakikita ng chatbot ang code at hindi nito makukumpirma ang delivery.",
|
| 373 |
+
"ilocano": "Siguraduem a husto ti signup email ken kitaem ti Inbox, Spam, ken Junk. No awan pay ti code, urayem ti resend timer iti app ken mangkiddaw iti baro a code. Ikabil ti kabaruan nga innem a digit a code; mangkiddaw manen no expired wenno invalid. Mabalin a baliwan ti email iti verification screen. Saan nga ibagbaga ti OTP. Saan a makita ti chatbot ti code wenno makumpirma ti delivery.",
|
| 374 |
+
"pangasinan": "Seguroen ya duga so signup email tan nengnengen so Inbox, Spam, tan Junk. No anggapo ni so code, antayan so resend timer ed app tan mangikeddeng na balon code. Isulat so pinakabalo ya anem ya digit a code; mangikeddeng lamet no expired odino invalid. Nayari ya ontan so email ed verification screen. Agmon iter so OTP. Ag nanengneng na chatbot so code odino nakumpirma so delivery."
|
| 375 |
+
},
|
| 376 |
+
"review": {
|
| 377 |
+
"english": "Email verification does not mean the resident account has been approved. Submitted registrations still need barangay review. If the account is rejected, read any rejection reason shown and contact the barangay office about corrections. If it is approved but you still cannot sign in, check the email and password and report the exact error without sharing credentials. The chatbot cannot see or change your approval status.",
|
| 378 |
+
"tagalog": "Ang email verification ay hindi pa approval ng resident account. Kailangan pa rin ng barangay review ang naisumiteng registration. Kung rejected, basahin ang ipinakitang rejection reason at magtanong sa opisina ng barangay tungkol sa pagwawasto. Kung approved pero hindi makapasok, tiyaking tama ang email at password at i-report ang eksaktong error nang hindi ibinabahagi ang credentials. Hindi nakikita o nababago ng chatbot ang approval status.",
|
| 379 |
+
"ilocano": "Ti email verification ket saan pay nga approval ti resident account. Kasapulan pay ti barangay review ti naipasa a registration. No rejected, basaem ti rejection reason a naipakita ken makiuman iti opisina ti barangay maipapan iti panagkorehir. No approved ngem saan a makasrek, kitaem ti email ken password ken i-report ti error nga awan ibagbaga a credentials. Saan a makita wenno baliwan ti chatbot ti approval status.",
|
| 380 |
+
"pangasinan": "Say email verification et agni approval na resident account. Kasapulan ni so barangay review na naisumiten registration. No rejected, basaen so rejection reason ya nanengneng tan mantepet ed opisina na barangay nipaakar ed panangkorehir. No approved balet agmakasumrek, nengnengen so email tan password tan i-report so error ya agiter na credentials. Ag nanengneng odino nabago na chatbot so approval status."
|
| 381 |
+
}
|
| 382 |
+
}
|
| 383 |
+
},
|
| 384 |
+
{
|
| 385 |
+
"intent": "announcements",
|
| 386 |
+
"title": "Barangay Announcements",
|
| 387 |
+
"needs_staff_verification": false,
|
| 388 |
+
"answer": {
|
| 389 |
+
"english": "For the latest community bulletins, typhoon advisories, health mission schedules, financial aid releases, and public hearings, open the Announcements screen in BrgyLink.",
|
| 390 |
+
"tagalog": "Para sa pinakabagong balita, bagyo o kalamidad alerts, health center schedules, pamamahagi ng ayuda, at public hearings, buksan ang 'Announcements' screen sa BrgyLink. Regular itong ina-update ng pamahalaang barangay.",
|
| 391 |
+
"ilocano": "Para kadagiti kabaroan a damag, pakaammo iti bagyo ken kalamidad, iskedyul ti health center, panagiwaras ti ayuda, ken public hearings, lukatan ti 'Announcements' screen iti BrgyLink. Kanayon a mapabaro daytoy ti barangay.",
|
| 392 |
+
"pangasinan": "Para ed saray balon balita, pakaammo ed bagyo tan kalamidad, iskedyul na health center, panagiwaras na ayuda, tan public hearings, lukatan so 'Announcements' screen diad BrgyLink. Regular yan ina-update na barangay."
|
| 393 |
+
},
|
| 394 |
+
"verified": false,
|
| 395 |
+
"verified_by": null,
|
| 396 |
+
"verified_at": null,
|
| 397 |
+
"expires_at": null,
|
| 398 |
+
"source": null,
|
| 399 |
+
"last_reviewed_at": null,
|
| 400 |
+
"review_notes": "",
|
| 401 |
+
"content_type": "barangay_fact",
|
| 402 |
+
"requires_verification": true
|
| 403 |
+
},
|
| 404 |
+
{
|
| 405 |
+
"intent": "sdg_mission",
|
| 406 |
+
"title": "Community Initiatives and SDG Missions",
|
| 407 |
+
"needs_staff_verification": false,
|
| 408 |
+
"steps": [
|
| 409 |
+
"Open Community Initiatives",
|
| 410 |
+
"Select an active program",
|
| 411 |
+
"Tap Upload Proof",
|
| 412 |
+
"Capture live photo evidence",
|
| 413 |
+
"Tap Analyze & Submit"
|
| 414 |
+
],
|
| 415 |
+
"answer": {
|
| 416 |
+
"english": "Open Civic Tasks & Community Events, choose an active civic task, and use Capture Live Photo to show your participation clearly. Retake unclear photos, then tap Submit Task Proof. Open Profile and Community Mission Submissions to see your own submission status and any rejection reason. Pending is not approval; the chatbot cannot see or change your result. Civic participation does not earn reward points in the current app. Follow the task instructions and do not use unrelated or reused photos.",
|
| 417 |
+
"tagalog": "Buksan ang Civic Tasks & Community Events, pumili ng aktibong civic task, at gamitin ang Capture Live Photo para malinaw na ipakita ang pakikilahok. Mag-retake kung malabo at i-tap ang Submit Task Proof. Sa Profile, tingnan ang Community Mission Submissions para sa sariling submission status at rejection reason kung mayroon. Hindi pa approval ang Pending; hindi nakikita o nababago ng chatbot ang resulta. Walang reward points ang civic participation sa kasalukuyang app. Sundin ang task instructions at huwag gumamit ng hindi kaugnay o lumang larawan.",
|
| 418 |
+
"ilocano": "Lukatan ti Civic Tasks & Community Events, piliem ti aktibo a civic task, ken usarem ti Capture Live Photo tapno nalawag ti panagpasetmo. I-retake no saan a nalawag ken i-tap ti Submit Task Proof. Iti Profile, kitaem ti Community Mission Submissions para iti bukodmo a submission status ken rejection reason no adda. Saan pay nga approval ti Pending; saan a makita wenno baliwan ti chatbot ti resulta. Awan ti reward points iti agdama nga app. Surotem dagiti task instructions ken saan nga usaren ti saan a kaugnay wenno daan a ladawan.",
|
| 419 |
+
"pangasinan": "Lukatan so Civic Tasks & Community Events, piliyen so aktibon civic task, tan usaren so Capture Live Photo pian malinew so pakikiba mo. I-retake no agmalinew tan i-tap so Submit Task Proof. Ed Profile, nengnengen so Community Mission Submissions para ed bukod mon submission status tan rejection reason no wala. Agni approval so Pending; ag nanengneng odino nabago na chatbot so resulta. Anggapoy reward points ed kasalukuyan ya app. Suroten so task instructions tan ag-usar na agkaugnay odino daan ya litrato."
|
| 420 |
+
},
|
| 421 |
+
"verified": false,
|
| 422 |
+
"verified_by": null,
|
| 423 |
+
"verified_at": null,
|
| 424 |
+
"expires_at": null,
|
| 425 |
+
"source": null,
|
| 426 |
+
"last_reviewed_at": null,
|
| 427 |
+
"review_notes": "",
|
| 428 |
+
"content_type": "barangay_fact",
|
| 429 |
+
"requires_verification": true
|
| 430 |
+
},
|
| 431 |
+
{
|
| 432 |
+
"intent": "suggestion",
|
| 433 |
+
"title": "Suggestions and Feedback",
|
| 434 |
+
"needs_staff_verification": false,
|
| 435 |
+
"answer": {
|
| 436 |
+
"english": "Do you have ideas or feedback to improve our barangay? Use the 'Feedback / Suggestions' feature in BrgyLink to send your thoughts directly to the barangay council. For formal disputes, please file a Blotter Report instead.",
|
| 437 |
+
"tagalog": "Mayroon ka bang mungkahi o puna para mapabuti ang ating barangay? Gamitin ang feature na 'Feedback / Suggestions' sa BrgyLink upang direktang maipadala ang iyong mensahe sa pamunuan. Para naman sa pormal na reklamo o insidente, gamitin ang 'Blotter Reports'.",
|
| 438 |
+
"ilocano": "Adda kadi singasingmo wenno feedback tapno mapasayaat ti barangaytayo? Usaren ti 'Feedback / Suggestions' iti BrgyLink tapno maipatulod a direkta kadagiti opisial. Para iti pormal a reklamo, usaren ti 'Blotter Reports'.",
|
| 439 |
+
"pangasinan": "Wala kasi suhestiyon mo odino feedback pian mas omparakep so barangay tayo? Usaren so 'Feedback / Suggestions' diad BrgyLink pian direktan nipawit ed saray opisyales. Para ed pormal ya reklamo, usaren so 'Blotter Reports'."
|
| 440 |
+
},
|
| 441 |
+
"verified": false,
|
| 442 |
+
"verified_by": null,
|
| 443 |
+
"verified_at": null,
|
| 444 |
+
"expires_at": null,
|
| 445 |
+
"source": null,
|
| 446 |
+
"last_reviewed_at": null,
|
| 447 |
+
"review_notes": "",
|
| 448 |
+
"content_type": "barangay_fact",
|
| 449 |
+
"requires_verification": true
|
| 450 |
+
},
|
| 451 |
+
{
|
| 452 |
+
"intent": "blotter_status",
|
| 453 |
+
"title": "Blotter Report Tracking",
|
| 454 |
+
"needs_staff_verification": false,
|
| 455 |
+
"answer": {
|
| 456 |
+
"english": "To track the status of your blotter complaint, open 'My Blotter Reports' in BrgyLink to see whether it is Under Review, Scheduled for Mediation, or Resolved. The chatbot cannot access or alter private case records.",
|
| 457 |
+
"tagalog": "Upang malaman ang estado ng iyong inihain na blotter, buksan ang 'My Blotter Reports' sa BrgyLink app. Dito makikita kung ang ulat ay Under Review, Scheduled for Mediation/Lupon, o Resolved na. Hindi maaaring baguhin o direktang basahin ng chatbot ang lihim na detalye ng iyong blotter.",
|
| 458 |
+
"ilocano": "Tapno maammuan ti kasasaad ti blottermo, lukatan ti 'My Blotter Reports' iti BrgyLink app. Makitam no daytoy ket Under Review, Para iti Lupon Hearing, wenno Naresolba. Saan a mabalin nga i-display wenno bagian ti chatbot ti personal a detalye ti kasom.",
|
| 459 |
+
"pangasinan": "Pian naamtaan so estado na blotter mo, lukatan so 'My Blotter Reports' diad BrgyLink app. Ditan nanengneng no say ulat et Under Review, Iskediyul parad Lupon Hearing, odino Na-ayos la. Ag nayarin direkta ya basaen na chatbot so personal ya detalye na blotter mo."
|
| 460 |
+
},
|
| 461 |
+
"verified": false,
|
| 462 |
+
"verified_by": null,
|
| 463 |
+
"verified_at": null,
|
| 464 |
+
"expires_at": null,
|
| 465 |
+
"source": null,
|
| 466 |
+
"last_reviewed_at": null,
|
| 467 |
+
"review_notes": "",
|
| 468 |
+
"content_type": "barangay_fact",
|
| 469 |
+
"requires_verification": true
|
| 470 |
+
},
|
| 471 |
+
{
|
| 472 |
+
"intent": "notifications",
|
| 473 |
+
"title": "BrgyLink Notifications",
|
| 474 |
+
"needs_staff_verification": false,
|
| 475 |
+
"answer": {
|
| 476 |
+
"english": "BrgyLink sends notifications for updates on your document requests, blotter reports, and urgent community advisories. Ensure notification permissions are enabled for BrgyLink in your phone settings.",
|
| 477 |
+
"tagalog": "Nagpapadala ang BrgyLink ng in-app at push notifications para sa updates sa iyong document request, status ng blotter, at mahahalagang anunsyo. Siguraduhing pinayagan (Enabled) ang notifications para sa BrgyLink sa settings ng iyong telepono.",
|
| 478 |
+
"ilocano": "Mangipatulod ti BrgyLink kadagiti notifications para kadagiti update iti document requests, blotter reports, ken anunsio. Siguraduem a naka-Enable ti notifications ti BrgyLink iti settings ti selponmo.",
|
| 479 |
+
"pangasinan": "Mangi-iiter so BrgyLink na notifications para ed updates ed document requests, blotter reports, tan anunsyo. Seguroen ya aka-Enable so notifications parad BrgyLink diad settings na telepono mo."
|
| 480 |
+
},
|
| 481 |
+
"verified": false,
|
| 482 |
+
"verified_by": null,
|
| 483 |
+
"verified_at": null,
|
| 484 |
+
"expires_at": null,
|
| 485 |
+
"source": null,
|
| 486 |
+
"last_reviewed_at": null,
|
| 487 |
+
"review_notes": "",
|
| 488 |
+
"content_type": "barangay_fact",
|
| 489 |
+
"requires_verification": true
|
| 490 |
+
},
|
| 491 |
+
{
|
| 492 |
+
"intent": "officials",
|
| 493 |
+
"title": "Barangay Officials",
|
| 494 |
+
"needs_staff_verification": true,
|
| 495 |
+
"answer": {
|
| 496 |
+
"english": "The chatbot does not have a confirmed roster of barangay officials. Please check the barangay office or the Announcements section in BrgyLink for the list of current officials.",
|
| 497 |
+
"tagalog": "Ang chatbot ay walang kumpirmadong roster ng mga opisyal ng barangay. Tingnan po ang opisina ng barangay o ang Announcements sa BrgyLink para sa listahan ng kasalukuyang mga opisyal.",
|
| 498 |
+
"ilocano": "Ti chatbot ket awan ti nakumpirma a roster dagiti opisyal ti barangay. Kitaen ti opisina ti barangay wenno ti Announcements iti BrgyLink para iti listaan dagiti agdama nga opisyal.",
|
| 499 |
+
"pangasinan": "Say chatbot et anggapoy nakumpirman roster na saray opisyal na barangay. Nengnengen so opisina na barangay odino so Announcements ed BrgyLink para ed listaan na saray opisyal natan."
|
| 500 |
+
},
|
| 501 |
+
"verified": false,
|
| 502 |
+
"verified_by": null,
|
| 503 |
+
"verified_at": null,
|
| 504 |
+
"expires_at": null,
|
| 505 |
+
"source": null,
|
| 506 |
+
"last_reviewed_at": null,
|
| 507 |
+
"review_notes": "",
|
| 508 |
+
"content_type": "barangay_fact",
|
| 509 |
+
"requires_verification": true
|
| 510 |
+
},
|
| 511 |
+
{
|
| 512 |
+
"intent": "greeting",
|
| 513 |
+
"title": "Greeting",
|
| 514 |
+
"needs_staff_verification": false,
|
| 515 |
+
"requirements": [],
|
| 516 |
+
"fee_php": null,
|
| 517 |
+
"processing_time": null,
|
| 518 |
+
"answer": {
|
| 519 |
+
"english": "Hello! I am BrgyLink AI, the virtual assistant for Barangay Bagong Pag-asa, San Jacinto. How can I help you with document requests, blotter reports, announcements, or barangay services today?",
|
| 520 |
+
"tagalog": "Magandang araw! Ako ang BrgyLink AI, ang virtual assistant ng Barangay Bagong Pag-asa, San Jacinto. Paano kita matutulungan sa mga dokumento, blotter, anunsyo, o serbisyo ng barangay ngayon?",
|
| 521 |
+
"ilocano": "Naimbag nga aldaw! Siak ni BrgyLink AI, ti virtual assistant ti Barangay Bagong Pag-asa, San Jacinto. Kasano a makatulongak kadagiti dokumento, blotter, anunsio, wenno serbisyo ti barangay tatta?",
|
| 522 |
+
"pangasinan": "Maabig ya agew! Siak si BrgyLink AI, so virtual assistant na Barangay Bagong Pag-asa, San Jacinto. Panon ta kan natulongan ed saray dokumento, blotter, anunsyo, odino serbisyo na barangay natan?"
|
| 523 |
+
},
|
| 524 |
+
"verified": false,
|
| 525 |
+
"verified_by": null,
|
| 526 |
+
"verified_at": null,
|
| 527 |
+
"expires_at": null,
|
| 528 |
+
"source": null,
|
| 529 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 530 |
+
"review_notes": "Imported non-service intent",
|
| 531 |
+
"content_type": "system_copy",
|
| 532 |
+
"requires_verification": false
|
| 533 |
+
},
|
| 534 |
+
{
|
| 535 |
+
"intent": "goodbye",
|
| 536 |
+
"title": "Goodbye",
|
| 537 |
+
"needs_staff_verification": false,
|
| 538 |
+
"requirements": [],
|
| 539 |
+
"fee_php": null,
|
| 540 |
+
"processing_time": null,
|
| 541 |
+
"answer": {
|
| 542 |
+
"english": "Thank you for using BrgyLink AI! If you need anything else, I am always here to assist. Stay safe and have a wonderful day!",
|
| 543 |
+
"tagalog": "Maraming salamat sa paggamit ng BrgyLink AI! Kung may iba ka pang katanungan, narito lamang ako handang tumulong. Mag-ingat ka palagi at magandang araw!",
|
| 544 |
+
"ilocano": "Agyamanak unay iti panagusar iti BrgyLink AI! No adda pay masapulmo, situtulokak a tumulong. Agannadka kanayon ken naimbag nga aldaw!",
|
| 545 |
+
"pangasinan": "Balbaleg ya salamat ed panangusar mo ed BrgyLink AI! No wala ni ray tepet mo diad ontumbok, wadya ak labat ya ontulong. Man-ingat ka lanang tan maabig ya agew!"
|
| 546 |
+
},
|
| 547 |
+
"verified": false,
|
| 548 |
+
"verified_by": null,
|
| 549 |
+
"verified_at": null,
|
| 550 |
+
"expires_at": null,
|
| 551 |
+
"source": null,
|
| 552 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 553 |
+
"review_notes": "Imported non-service intent",
|
| 554 |
+
"content_type": "system_copy",
|
| 555 |
+
"requires_verification": false
|
| 556 |
+
},
|
| 557 |
+
{
|
| 558 |
+
"intent": "thanks",
|
| 559 |
+
"title": "Thanks",
|
| 560 |
+
"needs_staff_verification": false,
|
| 561 |
+
"requirements": [],
|
| 562 |
+
"fee_php": null,
|
| 563 |
+
"processing_time": null,
|
| 564 |
+
"answer": {
|
| 565 |
+
"english": "You're very welcome! Glad to assist you as a resident of Barangay Bagong Pag-asa. Feel free to ask if you need further guidance.",
|
| 566 |
+
"tagalog": "Walang anuman! Ikinagagalak kong makatulong sa iyo bilang residente ng Barangay Bagong Pag-asa. Huwag mag-atubiling magtanong kung may kailangan ka pa.",
|
| 567 |
+
"ilocano": "Awan aniamanna kabsat! Naragsakak a makatulong kenka kas kailian iti Barangay Bagong Pag-asa. Agsaludsodka laeng no adda pay masapulmo.",
|
| 568 |
+
"pangasinan": "Anggapoy kabaliksan kabaleyan! Maliket ak ya makatulong ed sika bilang residente na Barangay Bagong Pag-asa. Tepet ka labat no wala ni nakaukol mo."
|
| 569 |
+
},
|
| 570 |
+
"verified": false,
|
| 571 |
+
"verified_by": null,
|
| 572 |
+
"verified_at": null,
|
| 573 |
+
"expires_at": null,
|
| 574 |
+
"source": null,
|
| 575 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 576 |
+
"review_notes": "Imported non-service intent",
|
| 577 |
+
"content_type": "system_copy",
|
| 578 |
+
"requires_verification": false
|
| 579 |
+
},
|
| 580 |
+
{
|
| 581 |
+
"intent": "solo_parent",
|
| 582 |
+
"title": "Solo Parent",
|
| 583 |
+
"needs_staff_verification": false,
|
| 584 |
+
"requirements": [],
|
| 585 |
+
"fee_php": null,
|
| 586 |
+
"processing_time": null,
|
| 587 |
+
"answer": {
|
| 588 |
+
"english": "For information about Solo Parent ID and welfare benefits in Barangay Bagong Pag-asa, please visit the barangay office or check the Announcements section in BrgyLink.",
|
| 589 |
+
"tagalog": "Para sa impormasyon tungkol sa Solo Parent ID at mga benepisyo sa Barangay Bagong Pag-asa, bumisita sa opisina ng barangay o tingnan ang Announcements sa BrgyLink.",
|
| 590 |
+
"ilocano": "Para iti impormasion maipapan iti Solo Parent ID ken dagiti benepisio iti Barangay Bagong Pag-asa, bisitaen ti opisina ti barangay wenno kitaen ti Announcements iti BrgyLink.",
|
| 591 |
+
"pangasinan": "Para ed impormasyon nipaakar ed Solo Parent ID tan saray benepisyo ed Barangay Bagong Pag-asa, bisitaen so opisina na barangay odino nengnengen so Announcements ed BrgyLink."
|
| 592 |
+
},
|
| 593 |
+
"verified": false,
|
| 594 |
+
"verified_by": null,
|
| 595 |
+
"verified_at": null,
|
| 596 |
+
"expires_at": null,
|
| 597 |
+
"source": null,
|
| 598 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 599 |
+
"review_notes": "Imported non-service intent",
|
| 600 |
+
"content_type": "barangay_fact",
|
| 601 |
+
"requires_verification": true
|
| 602 |
+
},
|
| 603 |
+
{
|
| 604 |
+
"intent": "senior_citizen",
|
| 605 |
+
"title": "Senior Citizen",
|
| 606 |
+
"needs_staff_verification": false,
|
| 607 |
+
"requirements": [],
|
| 608 |
+
"fee_php": null,
|
| 609 |
+
"processing_time": null,
|
| 610 |
+
"answer": {
|
| 611 |
+
"english": "For information about Senior Citizen ID and OSCA services in Barangay Bagong Pag-asa, please visit the barangay office or check the Announcements section in BrgyLink.",
|
| 612 |
+
"tagalog": "Para sa impormasyon tungkol sa Senior Citizen ID at mga serbisyo ng OSCA sa Barangay Bagong Pag-asa, bumisita sa opisina ng barangay o tingnan ang Announcements sa BrgyLink.",
|
| 613 |
+
"ilocano": "Para iti impormasion maipapan iti Senior Citizen ID ken serbisio ti OSCA iti Barangay Bagong Pag-asa, bisitaen ti opisina ti barangay wenno kitaen ti Announcements iti BrgyLink.",
|
| 614 |
+
"pangasinan": "Para ed impormasyon nipaakar ed Senior Citizen ID tan saray serbisyo na OSCA ed Barangay Bagong Pag-asa, bisitaen so opisina na barangay odino nengnengen so Announcements ed BrgyLink."
|
| 615 |
+
},
|
| 616 |
+
"verified": false,
|
| 617 |
+
"verified_by": null,
|
| 618 |
+
"verified_at": null,
|
| 619 |
+
"expires_at": null,
|
| 620 |
+
"source": null,
|
| 621 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 622 |
+
"review_notes": "Imported non-service intent",
|
| 623 |
+
"content_type": "barangay_fact",
|
| 624 |
+
"requires_verification": true
|
| 625 |
+
},
|
| 626 |
+
{
|
| 627 |
+
"intent": "lupon",
|
| 628 |
+
"title": "Lupon",
|
| 629 |
+
"needs_staff_verification": false,
|
| 630 |
+
"requirements": [],
|
| 631 |
+
"fee_php": null,
|
| 632 |
+
"processing_time": null,
|
| 633 |
+
"answer": {
|
| 634 |
+
"english": "For disputes that may be resolved at the barangay level, the Lupon Tagapamayapa handles conciliation and mediation. Please visit the barangay office to file a complaint or inquire about the process.",
|
| 635 |
+
"tagalog": "Para sa mga alitang maaaring resolbahin sa antas ng barangay, ang Lupon Tagapamayapa ang nag-aasikaso ng conciliation at mediation. Bumisita sa opisina ng barangay para mag-file ng reklamo o magtanong tungkol sa proseso.",
|
| 636 |
+
"ilocano": "Para kadagiti kolkol a mabalin a marisut iti lebel ti barangay, ti Lupon Tagapamayapa ti mangasikaso iti conciliation ken mediation. Bisitaen ti opisina ti barangay tapno ag-file iti reklamo wenno saludsoden ti proseso.",
|
| 637 |
+
"pangasinan": "Para ed saray alitan ya nayarin resolbien ed lebel na barangay, say Lupon Tagapamayapa so mangaasikaso na conciliation tan mediation. Bisitaen so opisina na barangay pian man-file na reklamo odino mantepet nipaakar ed proseso."
|
| 638 |
+
},
|
| 639 |
+
"verified": false,
|
| 640 |
+
"verified_by": null,
|
| 641 |
+
"verified_at": null,
|
| 642 |
+
"expires_at": null,
|
| 643 |
+
"source": null,
|
| 644 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 645 |
+
"review_notes": "Imported non-service intent",
|
| 646 |
+
"content_type": "barangay_fact",
|
| 647 |
+
"requires_verification": true
|
| 648 |
+
},
|
| 649 |
+
{
|
| 650 |
+
"intent": "aics_assistance",
|
| 651 |
+
"title": "Aics Assistance",
|
| 652 |
+
"needs_staff_verification": false,
|
| 653 |
+
"requirements": [],
|
| 654 |
+
"fee_php": null,
|
| 655 |
+
"processing_time": null,
|
| 656 |
+
"answer": {
|
| 657 |
+
"english": "For financial assistance, medical or burial aid (AICS), relief, or social welfare programs, please visit the Barangay Bagong Pag-asa office to inquire about available services and requirements.",
|
| 658 |
+
"tagalog": "Para sa tulong pinansyal, medikal o burial aid (AICS), relief, o mga programa ng social welfare, bumisita po sa opisina ng Barangay Bagong Pag-asa para magtanong tungkol sa mga available na serbisyo at kinakailangan.",
|
| 659 |
+
"ilocano": "Para iti tulong-pinansyal, medikal wenno burial aid (AICS), relief, wenno programa ti social welfare, bisitaen ti opisina ti Barangay Bagong Pag-asa tapno saludsoden dagiti magun-od a serbisio ken kasapulan.",
|
| 660 |
+
"pangasinan": "Para ed tulong-pinansyal, medikal odino burial aid (AICS), relief, odino saray programa na social welfare, bisitaen so opisina na Barangay Bagong Pag-asa pian mantepet nipaakar ed saray serbisyo tan kailangan."
|
| 661 |
+
},
|
| 662 |
+
"verified": false,
|
| 663 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 664 |
+
"review_notes": "Imported non-service intent",
|
| 665 |
+
"verified_by": null,
|
| 666 |
+
"verified_at": null,
|
| 667 |
+
"expires_at": null,
|
| 668 |
+
"source": null,
|
| 669 |
+
"content_type": "barangay_fact",
|
| 670 |
+
"requires_verification": true
|
| 671 |
+
},
|
| 672 |
+
{
|
| 673 |
+
"intent": "document_request",
|
| 674 |
+
"title": "Document Request",
|
| 675 |
+
"needs_staff_verification": false,
|
| 676 |
+
"requirements": [],
|
| 677 |
+
"fee_php": null,
|
| 678 |
+
"processing_time": null,
|
| 679 |
+
"answer": {
|
| 680 |
+
"english": "Which document do you need? You can request a Barangay Clearance, Certificate of Indigency, Certificate of Residency, Business Clearance, Certificate of Good Moral Character, or Barangay ID through Document Requests in BrgyLink.",
|
| 681 |
+
"tagalog": "Anong dokumento po ang kailangan ninyo? Maaari kayong humiling ng Barangay Clearance, Certificate of Indigency, Certificate of Residency, Business Clearance, Certificate of Good Moral Character, o Barangay ID sa pamamagitan ng Document Requests sa BrgyLink.",
|
| 682 |
+
"ilocano": "Ania a dokumento ti kasapulam? Mabalin ti agkiddaw iti Barangay Clearance, Certificate of Indigency, Certificate of Residency, Business Clearance, Certificate of Good Moral Character, wenno Barangay ID babaen ti Document Requests iti BrgyLink.",
|
| 683 |
+
"pangasinan": "Antoy dokumento ya kailangan mo? Nayari kayon mangala na Barangay Clearance, Certificate of Indigency, Certificate of Residency, Business Clearance, Certificate of Good Moral Character, odino Barangay ID diad Document Requests ed BrgyLink."
|
| 684 |
+
},
|
| 685 |
+
"verified": false,
|
| 686 |
+
"verified_by": null,
|
| 687 |
+
"verified_at": null,
|
| 688 |
+
"expires_at": null,
|
| 689 |
+
"source": null,
|
| 690 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 691 |
+
"review_notes": "Imported non-service intent",
|
| 692 |
+
"content_type": "barangay_fact",
|
| 693 |
+
"requires_verification": true
|
| 694 |
+
},
|
| 695 |
+
{
|
| 696 |
+
"intent": "language_support",
|
| 697 |
+
"title": "Language Support",
|
| 698 |
+
"needs_staff_verification": false,
|
| 699 |
+
"requirements": [],
|
| 700 |
+
"fee_php": null,
|
| 701 |
+
"processing_time": null,
|
| 702 |
+
"answer": {
|
| 703 |
+
"english": "Yes! I can understand and respond in Pangasinan, Ilocano, and Tagalog (as well as English). Feel free to ask your question in any of these languages!",
|
| 704 |
+
"tagalog": "Opo! Nakakaintindi at nakakasagot ako sa tatlong pangunahing wika: Tagalog, Ilocano, at Pangasinan (at pati na rin sa English). Maaari kang magtanong sa alinman sa mga wikang ito anumang oras!",
|
| 705 |
+
"ilocano": "Wen! Makaawat ken makasungbatak iti tallo a kangrunaan a pagsasao: Ilocano, Tagalog, ken Pangasinan (ken kasta met iti English). Mabalinmo ti agsaludsod iti aniaman kadagitoy a wika!",
|
| 706 |
+
"pangasinan": "On! Makatalos tan makasungbat ak ed taloran manunan salita: Pangasinan, Tagalog, tan Ilocano (tan pati met ed English). Nayari kayon mantepet ed dinanman ed sarayan salita ed anggan anton oras!"
|
| 707 |
+
},
|
| 708 |
+
"verified": false,
|
| 709 |
+
"verified_by": null,
|
| 710 |
+
"verified_at": null,
|
| 711 |
+
"expires_at": null,
|
| 712 |
+
"source": null,
|
| 713 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 714 |
+
"review_notes": "Imported non-service intent",
|
| 715 |
+
"content_type": "system_copy",
|
| 716 |
+
"requires_verification": false
|
| 717 |
+
},
|
| 718 |
+
{
|
| 719 |
+
"intent": "about_app",
|
| 720 |
+
"title": "About App",
|
| 721 |
+
"needs_staff_verification": false,
|
| 722 |
+
"requirements": [],
|
| 723 |
+
"fee_php": null,
|
| 724 |
+
"processing_time": null,
|
| 725 |
+
"answer": {
|
| 726 |
+
"english": "BrgyLink AI can guide you through BrgyLink topics: registration and email verification, password recovery, document requests, blotter reports, feedback, announcements, civic tasks, events, and where to check officials information. You may ask in English, Tagalog, Ilocano, or Pangasinan. I cannot access accounts, passwords, OTPs, IDs, personal submissions, live schedules, or emergency services.",
|
| 727 |
+
"tagalog": "Makakatulong ang BrgyLink AI sa mga paksa sa BrgyLink: registration at email verification, password recovery, document requests, blotter reports, feedback, announcements, civic tasks, events, at kung saan titingnan ang impormasyon tungkol sa officials. Maaari kang magtanong sa English, Tagalog, Ilocano, o Pangasinan. Hindi ko naa-access ang accounts, passwords, OTPs, IDs, personal submissions, live schedules, o emergency services.",
|
| 728 |
+
"ilocano": "Makatulong ti BrgyLink AI kadagiti paksa iti BrgyLink: registration ken email verification, password recovery, document requests, blotter reports, feedback, announcements, civic tasks, events, ken sadino a kitaen ti impormasyon maipapan kadagiti officials. Mabalin a mangsaludsod iti English, Tagalog, Ilocano, wenno Pangasinan. Saan a ma-access ti chatbot dagiti account, password, OTP, ID, personal submission, live schedule, wenno emergency services.",
|
| 729 |
+
"pangasinan": "Nayarin makatulong so BrgyLink AI ed saray paksa ed BrgyLink: registration tan email verification, password recovery, document requests, blotter reports, feedback, announcements, civic tasks, events, tan iner ya nengnengen so impormasyon nipaakar ed officials. Nayarin magtanong ed English, Tagalog, Ilocano, odino Pangasinan. Ag na-access na chatbot so accounts, passwords, OTPs, IDs, personal submissions, live schedules, odino emergency services."
|
| 730 |
+
},
|
| 731 |
+
"verified": false,
|
| 732 |
+
"verified_by": null,
|
| 733 |
+
"verified_at": null,
|
| 734 |
+
"expires_at": null,
|
| 735 |
+
"source": null,
|
| 736 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 737 |
+
"review_notes": "Imported non-service intent",
|
| 738 |
+
"content_type": "system_copy",
|
| 739 |
+
"requires_verification": false
|
| 740 |
+
},
|
| 741 |
+
{
|
| 742 |
+
"intent": "voter_registration",
|
| 743 |
+
"title": "Voter Registration",
|
| 744 |
+
"needs_staff_verification": false,
|
| 745 |
+
"requirements": [],
|
| 746 |
+
"fee_php": null,
|
| 747 |
+
"processing_time": null,
|
| 748 |
+
"answer": {
|
| 749 |
+
"english": "For voter registration information, please visit the barangay office or COMELEC. Check the Announcements section in BrgyLink for any posted registration schedules.",
|
| 750 |
+
"tagalog": "Para sa impormasyon tungkol sa voter registration, bumisita sa opisina ng barangay o COMELEC. Tingnan ang Announcements sa BrgyLink para sa mga naka-post na schedule ng registration.",
|
| 751 |
+
"ilocano": "Para iti impormasion maipapan iti voter registration, bisitaen ti opisina ti barangay wenno COMELEC. Kitaen ti Announcements iti BrgyLink para kadagiti naipablaak nga iskedyul ti registration.",
|
| 752 |
+
"pangasinan": "Para ed impormasyon nipaakar ed voter registration, bisitaen so opisina na barangay odino COMELEC. Nengnengen so Announcements ed BrgyLink para ed saray aka-post ya schedule na registration."
|
| 753 |
+
},
|
| 754 |
+
"verified": false,
|
| 755 |
+
"verified_by": null,
|
| 756 |
+
"verified_at": null,
|
| 757 |
+
"expires_at": null,
|
| 758 |
+
"source": null,
|
| 759 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 760 |
+
"review_notes": "Imported non-service intent",
|
| 761 |
+
"content_type": "barangay_fact",
|
| 762 |
+
"requires_verification": true
|
| 763 |
+
},
|
| 764 |
+
{
|
| 765 |
+
"intent": "health_services",
|
| 766 |
+
"title": "Health Services",
|
| 767 |
+
"needs_staff_verification": false,
|
| 768 |
+
"requirements": [],
|
| 769 |
+
"fee_php": null,
|
| 770 |
+
"processing_time": null,
|
| 771 |
+
"answer": {
|
| 772 |
+
"english": "For information about health services at the Barangay Health Center, including available programs and schedules, please visit the health center or check the Announcements section in BrgyLink.",
|
| 773 |
+
"tagalog": "Para sa impormasyon tungkol sa mga serbisyong pangkalusugan sa Barangay Health Center, kasama ang mga programa at schedule, bumisita sa health center o tingnan ang Announcements sa BrgyLink.",
|
| 774 |
+
"ilocano": "Para iti impormasion maipapan kadagiti serbisio ti salun-at iti Barangay Health Center, agraman dagiti programa ken iskediul, bisitaen ti health center wenno kitaen ti Announcements iti BrgyLink.",
|
| 775 |
+
"pangasinan": "Para ed impormasyon nipaakar ed saray serbisyo na salud ed Barangay Health Center, kaibay saray programa tan schedule, bisitaen so health center odino nengnengen so Announcements ed BrgyLink."
|
| 776 |
+
},
|
| 777 |
+
"verified": false,
|
| 778 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 779 |
+
"review_notes": "Imported non-service intent",
|
| 780 |
+
"verified_by": null,
|
| 781 |
+
"verified_at": null,
|
| 782 |
+
"expires_at": null,
|
| 783 |
+
"source": null,
|
| 784 |
+
"content_type": "barangay_fact",
|
| 785 |
+
"requires_verification": true
|
| 786 |
+
},
|
| 787 |
+
{
|
| 788 |
+
"intent": "events",
|
| 789 |
+
"title": "Community Events",
|
| 790 |
+
"needs_staff_verification": false,
|
| 791 |
+
"requirements": [],
|
| 792 |
+
"fee_php": null,
|
| 793 |
+
"processing_time": null,
|
| 794 |
+
"answer": {
|
| 795 |
+
"english": "Open the Events tab under Civic Tasks & Community Events, choose an active event, and read its date, time, location, and instructions. If proof is requested, take a clear participation photo and tap Submit Participation Proof. Submitted is not the same as Approved. Check the app's displayed status or ask the barangay office if no review status is shown. The chatbot cannot see live event details or your personal approval result.",
|
| 796 |
+
"tagalog": "Buksan ang Events tab sa Civic Tasks & Community Events, pumili ng aktibong event, at basahin ang petsa, oras, lugar, at tagubilin. Kung kailangan ng proof, kumuha ng malinaw na larawan ng pakikilahok at i-tap ang Submit Participation Proof. Hindi pareho ang Submitted at Approved. Tingnan ang status na ipinapakita ng app o magtanong sa opisina ng barangay kung walang review status. Hindi nakikita ng chatbot ang live event details o personal na approval result.",
|
| 797 |
+
"ilocano": "Lukatan ti Events tab iti Civic Tasks & Community Events, piliem ti aktibo nga event, ken basaem ti petsa, oras, lugar, ken tagubilin. No kasapulan ti proof, alaem ti nalawag a ladawan ti panagpaset ken i-tap ti Submit Participation Proof. Saan nga agpada ti Submitted ken Approved. Kitaem ti status iti app wenno agsaludsod iti opisina ti barangay no awan ti review status. Saan a makita ti chatbot ti live event details wenno personal a approval result.",
|
| 798 |
+
"pangasinan": "Lukatan so Events tab ed Civic Tasks & Community Events, piliyen so aktibon event, tan basaen so petsa, oras, lugar, tan tagubilin. No kasapulan so proof, alaen so malinew ya litrato na pakikiba tan i-tap so Submit Participation Proof. Agpareho so Submitted tan Approved. Nengnengen so status ed app odino mantepet ed opisina na barangay no anggapo so review status. Ag nanengneng na chatbot so live event details odino personal ya approval result."
|
| 799 |
+
},
|
| 800 |
+
"verified": false,
|
| 801 |
+
"verified_by": null,
|
| 802 |
+
"verified_at": null,
|
| 803 |
+
"expires_at": null,
|
| 804 |
+
"source": null,
|
| 805 |
+
"last_reviewed_at": null,
|
| 806 |
+
"review_notes": "App navigation copy only; event details remain in the live Events screen.",
|
| 807 |
+
"content_type": "system_copy",
|
| 808 |
+
"requires_verification": false
|
| 809 |
+
},
|
| 810 |
+
{
|
| 811 |
+
"intent": "fallback",
|
| 812 |
+
"title": "Fallback",
|
| 813 |
+
"needs_staff_verification": false,
|
| 814 |
+
"requirements": [],
|
| 815 |
+
"fee_php": null,
|
| 816 |
+
"processing_time": null,
|
| 817 |
+
"answer": {
|
| 818 |
+
"english": "I specialize in Barangay Bagong Pag-asa services including Document Requests (Clearance, Indigency, Residency, ID), Blotter Reports, AICS Assistance, Lupon mediation, and Office details. Could you please rephrase or ask about barangay services?",
|
| 819 |
+
"tagalog": "Ipagpaumanhin mo, hindi ko lubos na naintindihan ang iyong tanong. Makakatulong ako sa mga serbisyo ng Barangay Bagong Pag-asa tulad ng Document Requests (Clearance, Indigency, Residency, ID), Blotter Reports, Ayuda/AICS, Lupon, at Opisina. Maaari mo bang linawin o itanong ang tungkol sa serbisyo ng barangay?",
|
| 820 |
+
"ilocano": "Dispensaren, saanko unay a naawatan ti saludsodmo. Makatulongak kadagiti serbisyo ti Barangay Bagong Pag-asa kas iti Document Requests (Clearance, Indigency, Residency, ID), Blotter Reports, Ayuda/AICS, Lupon, ken Opisina. Mabalinmo kadi a palawagen wenno agsaludsod maipapan kadagiti serbisyo ti barangay?",
|
| 821 |
+
"pangasinan": "Pasensya la kabaleyan, agko unay atalosan so tepet mo. Makatulong ak ed saray serbisyo na Barangay Bagong Pag-asa singa Document Requests (Clearance, Indigency, Residency, ID), Blotter Reports, Ayuda/AICS, Lupon, tan Opisina. Nayarim kasi ya ipalinew odino mantepet nipaakar ed serbisyo na barangay?"
|
| 822 |
+
},
|
| 823 |
+
"verified": false,
|
| 824 |
+
"verified_by": null,
|
| 825 |
+
"verified_at": null,
|
| 826 |
+
"expires_at": null,
|
| 827 |
+
"source": null,
|
| 828 |
+
"last_reviewed_at": "2026-09-29T00:00:00Z",
|
| 829 |
+
"review_notes": "Imported non-service intent",
|
| 830 |
+
"content_type": "system_copy",
|
| 831 |
+
"requires_verification": false
|
| 832 |
+
},
|
| 833 |
+
{
|
| 834 |
+
"intent": "out_of_scope",
|
| 835 |
+
"title": "Out Of Scope",
|
| 836 |
+
"answer": {
|
| 837 |
+
"english": "I'm a barangay assistant. I don't know about that.",
|
| 838 |
+
"tagalog": "Ako ay isang barangay assistant. Hindi ko alam ang tungkol diyan.",
|
| 839 |
+
"ilocano": "Siak ket barangay assistant. Saanko nga ammo dayta.",
|
| 840 |
+
"pangasinan": "Siak et barangay assistant. Agko amta itan."
|
| 841 |
+
},
|
| 842 |
+
"verified": false,
|
| 843 |
+
"verified_by": null,
|
| 844 |
+
"verified_at": null,
|
| 845 |
+
"expires_at": null,
|
| 846 |
+
"source": null,
|
| 847 |
+
"last_reviewed_at": null,
|
| 848 |
+
"review_notes": "",
|
| 849 |
+
"content_type": "system_copy",
|
| 850 |
+
"requires_verification": false
|
| 851 |
+
},
|
| 852 |
+
{
|
| 853 |
+
"intent": "safety_threat",
|
| 854 |
+
"title": "Safety Threat",
|
| 855 |
+
"answer": {
|
| 856 |
+
"english": "Please contact emergency services (911) immediately.",
|
| 857 |
+
"tagalog": "Mangyaring tumawag sa emergency services (911) kaagad.",
|
| 858 |
+
"ilocano": "Pangngaasi nga umawag ti emergency services (911) a dagus.",
|
| 859 |
+
"pangasinan": "Tawagan so emergency services (911) a tampol."
|
| 860 |
+
},
|
| 861 |
+
"verified": false,
|
| 862 |
+
"verified_by": null,
|
| 863 |
+
"verified_at": null,
|
| 864 |
+
"expires_at": null,
|
| 865 |
+
"source": null,
|
| 866 |
+
"last_reviewed_at": null,
|
| 867 |
+
"review_notes": "",
|
| 868 |
+
"content_type": "barangay_fact",
|
| 869 |
+
"requires_verification": true
|
| 870 |
+
},
|
| 871 |
+
{
|
| 872 |
+
"intent": "medical_non_emergency",
|
| 873 |
+
"title": "Medical Non Emergency",
|
| 874 |
+
"answer": {
|
| 875 |
+
"english": "Please visit the barangay health center during operating hours.",
|
| 876 |
+
"tagalog": "Mangyaring pumunta sa health center ng barangay habang bukas.",
|
| 877 |
+
"ilocano": "Pangngaasi nga mapan iti health center ti barangay kabayatan nga lukat.",
|
| 878 |
+
"pangasinan": "Umapay ed health center na barangay legan na pakanengnengan."
|
| 879 |
+
},
|
| 880 |
+
"verified": false,
|
| 881 |
+
"verified_by": null,
|
| 882 |
+
"verified_at": null,
|
| 883 |
+
"expires_at": null,
|
| 884 |
+
"source": null,
|
| 885 |
+
"last_reviewed_at": null,
|
| 886 |
+
"review_notes": "",
|
| 887 |
+
"content_type": "barangay_fact",
|
| 888 |
+
"requires_verification": true
|
| 889 |
+
}
|
| 890 |
+
]
|
| 891 |
+
}
|
chatbot_runtime/service.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HTTP-independent request handling for the bundled BrgyLink chatbot."""
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
import threading
|
| 5 |
+
import time
|
| 6 |
+
from collections import OrderedDict
|
| 7 |
+
|
| 8 |
+
from .smart_classifier import (
|
| 9 |
+
MODEL_VERSION,
|
| 10 |
+
handle_message,
|
| 11 |
+
load_model,
|
| 12 |
+
new_session,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
LANG_ALIASES = {
|
| 17 |
+
"en": "english",
|
| 18 |
+
"english": "english",
|
| 19 |
+
"fil": "tagalog",
|
| 20 |
+
"tl": "tagalog",
|
| 21 |
+
"tagalog": "tagalog",
|
| 22 |
+
"ilo": "ilocano",
|
| 23 |
+
"ilocano": "ilocano",
|
| 24 |
+
"pag": "pangasinan",
|
| 25 |
+
"pangasinan": "pangasinan",
|
| 26 |
+
}
|
| 27 |
+
LANG_DISPLAY = {
|
| 28 |
+
"pangasinan": "Pangasinan",
|
| 29 |
+
"ilocano": "Ilocano",
|
| 30 |
+
"tagalog": "Tagalog / Filipino",
|
| 31 |
+
"english": "English",
|
| 32 |
+
}
|
| 33 |
+
SESSION_ID_PATTERN = re.compile(r"[A-Za-z0-9_.-]{1,128}")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class ChatService:
|
| 37 |
+
"""Bounded in-memory chat state around the safety-constrained classifier."""
|
| 38 |
+
|
| 39 |
+
max_message_length = 2000
|
| 40 |
+
max_sessions = 1000
|
| 41 |
+
session_ttl_seconds = 3600
|
| 42 |
+
|
| 43 |
+
def __init__(self):
|
| 44 |
+
self.model = load_model()
|
| 45 |
+
self.sessions = OrderedDict()
|
| 46 |
+
self.session_last_seen = {}
|
| 47 |
+
self.lock = threading.Lock()
|
| 48 |
+
|
| 49 |
+
def clear_sessions(self):
|
| 50 |
+
with self.lock:
|
| 51 |
+
self.sessions.clear()
|
| 52 |
+
self.session_last_seen.clear()
|
| 53 |
+
|
| 54 |
+
def _prune_sessions(self, now):
|
| 55 |
+
expired = [
|
| 56 |
+
key for key, seen in self.session_last_seen.items()
|
| 57 |
+
if now - seen >= self.session_ttl_seconds
|
| 58 |
+
]
|
| 59 |
+
for key in expired:
|
| 60 |
+
self.sessions.pop(key, None)
|
| 61 |
+
self.session_last_seen.pop(key, None)
|
| 62 |
+
while len(self.sessions) >= self.max_sessions:
|
| 63 |
+
key, _session = self.sessions.popitem(last=False)
|
| 64 |
+
self.session_last_seen.pop(key, None)
|
| 65 |
+
|
| 66 |
+
def respond(self, payload):
|
| 67 |
+
"""Return a stable (status_code, JSON-safe payload) response tuple."""
|
| 68 |
+
if not isinstance(payload, dict):
|
| 69 |
+
return 400, {"error": "A JSON object is required."}
|
| 70 |
+
|
| 71 |
+
message = payload.get("message")
|
| 72 |
+
if not isinstance(message, str) or not message.strip():
|
| 73 |
+
return 400, {"error": "Please enter a question or message."}
|
| 74 |
+
message = message.strip()
|
| 75 |
+
if len(message) > self.max_message_length:
|
| 76 |
+
return 400, {"error": f"Messages must be at most {self.max_message_length} characters."}
|
| 77 |
+
|
| 78 |
+
session_id = payload.get("session_id")
|
| 79 |
+
if session_id is not None and (
|
| 80 |
+
not isinstance(session_id, str) or not SESSION_ID_PATTERN.fullmatch(session_id)
|
| 81 |
+
):
|
| 82 |
+
return 400, {
|
| 83 |
+
"error": "Use a non-personal session_id of 1 to 128 letters, digits, dots, underscores or hyphens."
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
language = payload.get("language")
|
| 87 |
+
if language is not None:
|
| 88 |
+
if not isinstance(language, str) or language.lower().strip() not in LANG_ALIASES:
|
| 89 |
+
return 400, {"error": "Unsupported language."}
|
| 90 |
+
language = LANG_ALIASES[language.lower().strip()]
|
| 91 |
+
|
| 92 |
+
try:
|
| 93 |
+
started_at = time.time()
|
| 94 |
+
with self.lock:
|
| 95 |
+
now = time.monotonic()
|
| 96 |
+
if session_id and session_id not in self.sessions:
|
| 97 |
+
self._prune_sessions(now)
|
| 98 |
+
self.sessions[session_id] = new_session()
|
| 99 |
+
elif session_id and now - self.session_last_seen.get(session_id, 0) >= self.session_ttl_seconds:
|
| 100 |
+
self.sessions[session_id] = new_session()
|
| 101 |
+
|
| 102 |
+
chat_session = self.sessions[session_id] if session_id else new_session()
|
| 103 |
+
if session_id:
|
| 104 |
+
self.sessions.move_to_end(session_id)
|
| 105 |
+
self.session_last_seen[session_id] = now
|
| 106 |
+
if language:
|
| 107 |
+
chat_session["preferred_language"] = language
|
| 108 |
+
result = handle_message(message, session=chat_session, model=self.model)
|
| 109 |
+
|
| 110 |
+
detected_language = result.get("language", "english")
|
| 111 |
+
return 200, {
|
| 112 |
+
"response": result["response"],
|
| 113 |
+
"intent": result.get("intent", "unknown"),
|
| 114 |
+
"confidence": result.get("similarity", 1.0),
|
| 115 |
+
"language": detected_language,
|
| 116 |
+
"language_name": LANG_DISPLAY.get(detected_language, detected_language.capitalize()),
|
| 117 |
+
"processing_time_ms": round((time.time() - started_at) * 1000, 2),
|
| 118 |
+
"model_version": MODEL_VERSION,
|
| 119 |
+
}
|
| 120 |
+
except Exception:
|
| 121 |
+
return 500, {"error": "Failed to process chat message. Please try again."}
|
chatbot_runtime/smart_classifier.py
ADDED
|
@@ -0,0 +1,909 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
BrgyLink Smart Classifier v7 — Prototype Multilingual Offline NLP Engine.
|
| 3 |
+
Barangay assistant prototype for Barangay Bagong Pag-asa, San Jacinto.
|
| 4 |
+
Languages supported: Pangasinan, Ilocano, Tagalog, English.
|
| 5 |
+
|
| 6 |
+
IMPORTANT: This is a prototype. Answers come from knowledge_base.json.
|
| 7 |
+
Unverified facts are flagged. The chatbot does not provide legal or medical
|
| 8 |
+
advice and cannot dispatch emergency services.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
import hashlib
|
| 13 |
+
import math
|
| 14 |
+
import os
|
| 15 |
+
import pickle
|
| 16 |
+
import re
|
| 17 |
+
import tempfile
|
| 18 |
+
from collections import Counter, defaultdict
|
| 19 |
+
|
| 20 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 21 |
+
INTENTS_FILE = os.path.join(BASE_DIR, "data", "intents_brgylink_curated.json")
|
| 22 |
+
KB_FILE = os.path.join(BASE_DIR, "knowledge_base.json")
|
| 23 |
+
MODEL_FILE = os.path.join(BASE_DIR, "smart_classifier.pkl")
|
| 24 |
+
MODEL_VERSION = 7
|
| 25 |
+
FALLBACK = "out_of_scope"
|
| 26 |
+
|
| 27 |
+
# --- Tuned thresholds ---
|
| 28 |
+
CONFIDENCE_THRESHOLD = 0.18 # minimum score to accept an intent
|
| 29 |
+
MARGIN_THRESHOLD = 0.06 # minimum gap between #1 and #2 intents
|
| 30 |
+
|
| 31 |
+
# ---------------------------------------------------------------------------
|
| 32 |
+
# Document aliases -> standard display name
|
| 33 |
+
# ---------------------------------------------------------------------------
|
| 34 |
+
DOCUMENTS = {
|
| 35 |
+
"barangay clearance": "Barangay Clearance",
|
| 36 |
+
"clearance": "Barangay Clearance",
|
| 37 |
+
"indigency": "Certificate of Indigency",
|
| 38 |
+
"certificate of indigency": "Certificate of Indigency",
|
| 39 |
+
"residency": "Certificate of Residency",
|
| 40 |
+
"certificate of residency": "Certificate of Residency",
|
| 41 |
+
"business clearance": "Business Clearance",
|
| 42 |
+
"business permit": "Business Clearance",
|
| 43 |
+
"good moral": "Certificate of Good Moral Character",
|
| 44 |
+
"good moral character": "Certificate of Good Moral Character",
|
| 45 |
+
"barangay id": "Barangay ID",
|
| 46 |
+
"bgy id": "Barangay ID",
|
| 47 |
+
"barangay i.d": "Barangay ID",
|
| 48 |
+
"barangay identification": "Barangay ID",
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
DOC_TO_INTENT = {
|
| 52 |
+
"Barangay Clearance": "clearance",
|
| 53 |
+
"Certificate of Indigency": "indigency",
|
| 54 |
+
"Certificate of Residency": "residency",
|
| 55 |
+
"Business Clearance": "business_permit",
|
| 56 |
+
"Certificate of Good Moral Character": "good_moral",
|
| 57 |
+
"Barangay ID": "barangay_id",
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
# ---------------------------------------------------------------------------
|
| 61 |
+
# Alias normalization
|
| 62 |
+
# ---------------------------------------------------------------------------
|
| 63 |
+
ALIASES: list[tuple[re.Pattern, str]] = [
|
| 64 |
+
(re.compile(r"\bbgy\b"), "barangay"),
|
| 65 |
+
(re.compile(r"\bbrgy\b"), "barangay"),
|
| 66 |
+
(re.compile(r"\bcert\b"), "certificate"),
|
| 67 |
+
(re.compile(r"\bclrnce\b"), "clearance"),
|
| 68 |
+
(re.compile(r"\bindgncy\b"), "indigency"),
|
| 69 |
+
(re.compile(r"\bindigensy\b"), "indigency"),
|
| 70 |
+
(re.compile(r"\bindigincy\b"), "indigency"),
|
| 71 |
+
(re.compile(r"\bresdncy\b"), "residency"),
|
| 72 |
+
(re.compile(r"\bbus\.?\s*pmt\b"), "business permit"),
|
| 73 |
+
(re.compile(r"\bgood\s*moral\b"), "good moral"),
|
| 74 |
+
(re.compile(r"\bblotter\b"), "blotter report"),
|
| 75 |
+
(re.compile(r"\baics\b"), "aics assistance"),
|
| 76 |
+
(re.compile(r"\bdswd\b"), "dswd assistance"),
|
| 77 |
+
(re.compile(r"\bosca\b"), "osca senior citizen"),
|
| 78 |
+
(re.compile(r"\bbhc\b"), "barangay health center"),
|
| 79 |
+
(re.compile(r"\b4ps\b"), "4ps pantawid pamilya"),
|
| 80 |
+
(re.compile(r"\bsap\b"), "social amelioration program"),
|
| 81 |
+
(re.compile(r"\bkp\b"), "katarungang pambarangay"),
|
| 82 |
+
(re.compile(r"\bcomelec\b"), "comelec voter registration"),
|
| 83 |
+
(re.compile(r"\botp\b"), "one time password otp verification"),
|
| 84 |
+
(re.compile(r"\bpwd\b"), "person with disability pwd"),
|
| 85 |
+
]
|
| 86 |
+
|
| 87 |
+
# ---------------------------------------------------------------------------
|
| 88 |
+
# SAFETY-CRITICAL: Pre-classifier keyword rules
|
| 89 |
+
# These ALWAYS override the TF-IDF classifier.
|
| 90 |
+
# ---------------------------------------------------------------------------
|
| 91 |
+
_EMERGENCY_RE = re.compile(
|
| 92 |
+
r"\b(sunog|apoy|uram|fire|911|ambulance|ambulansya|saklolo|tulong\s+tulong|aksidente|"
|
| 93 |
+
r"inatake|heart\s*attack|aktibong\s*krimen|hostage|crime\s*in\s*progress|"
|
| 94 |
+
r"nahulog|drowning|earthquake|lindol|bagyo|typhoon|flood|baha|"
|
| 95 |
+
r"nawawala\w*\s*(na\s*)?(bata|tao|anak)|kidnap|active\s*shooter|nawawalang|"
|
| 96 |
+
r"weapons?|active\s*assault|imminent\s*killing|severe\s*bleeding|inability\s*to\s*breathe|"
|
| 97 |
+
r"unconsciousness|poisoning|binubugbog|binugbog|sinasaksak|may\s*baril|binabaril|"
|
| 98 |
+
r"assault|sinakal|pinugutan|patay|pinatay|pumatay|self[\s\-]?harm|suicide|"
|
| 99 |
+
r"papatayin|patayin|gustong\s*mamatay|ayaw\s*(ko\s*)?na\s*mabuhay|kill|nananaksak|"
|
| 100 |
+
r"nagdudugo|bleeding|hirap\s*huminga|difficulty\s*breathing|"
|
| 101 |
+
r"(?:cannot|can\s*t|cant|unable\s*to)\s*breathe|"
|
| 102 |
+
r"(?:not|stopped|stops)\s*breathing|chok(?:e|es|ed|ing)|"
|
| 103 |
+
r"(?:cannot|can\s*t|cant)\s*catch\s*(?:my|their|his|her|our)?\s*breath|"
|
| 104 |
+
r"struggling\s*to\s*breathe|gasping\s*(?:for\s*)?air|hurt\s*myself|"
|
| 105 |
+
r"hindi\s*(?:na\s*)?makahinga|di\s*makahinga|"
|
| 106 |
+
r"nauubusan\s*(?:ng\s*)?hangin|saan\s*a\s*makaanges|ag\s*makainawa|"
|
| 107 |
+
r"end\s*my\s*life|wakasan\s*(?:ang\s*)?(?:aking\s*)?buhay)\b",
|
| 108 |
+
re.IGNORECASE,
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
_THREAT_RE = re.compile(
|
| 112 |
+
r"\b(nangbabanta|nagbanta|banta|sinasaktan|inaabuso|"
|
| 113 |
+
r"domestic\s*violence|violence|abuse\w*|sinaktan|pinagsasaktan|"
|
| 114 |
+
r"hinoldap|holdup|rape|pangmomolestiya|molestiya|"
|
| 115 |
+
r"stalker|stalking|nananakit|pinagbabantaan|binabanta|"
|
| 116 |
+
r"threat\w*|harass\w*|haras\w*|pang-?aabuso|"
|
| 117 |
+
r"nag-aaway|nag-aalburoto|nagwawala|naghaharas|"
|
| 118 |
+
r"nagbabanta|nang-?aaway|mangdadakip)\b",
|
| 119 |
+
re.IGNORECASE,
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
_MEDICAL_SYMPTOM_RE = re.compile(
|
| 123 |
+
r"\b(lagnat|fever|ubo|cough|sakit\s*(ng|na)\s*(ulo|tiyan|dibdib|katawan)|"
|
| 124 |
+
r"pain|masakit|sumasakit|sugat|wound|allergy|"
|
| 125 |
+
r"hilo|dizzy|diarrhea|vomit|nausea|nilalagnat|may\s*sakit|"
|
| 126 |
+
r"nahihilo|nasusuka|"
|
| 127 |
+
r"sore|infection|impeksyon|skin\s*rash|singaw|"
|
| 128 |
+
r"buntis|pregnant|prenatal|masakit\s*ang|anak\s*ko\s*may\s*sakit|"
|
| 129 |
+
r"my\s*child\s*(?:has\s*a\s*fever|is\s*sick|needs\s*help))\b",
|
| 130 |
+
re.IGNORECASE,
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
_LEGAL_RE = re.compile(
|
| 134 |
+
r"\b(abogado|lawyer|attorney|legal\s*advice|kaso|court\s*case|"
|
| 135 |
+
r"legal\s*consultation|fiscal|hukom|judge|demanda|magdemanda|"
|
| 136 |
+
r"file\s*(?:a\s*)?case|sue|kasuhan|sampahan)\b",
|
| 137 |
+
re.IGNORECASE,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
_OUT_OF_SCOPE_RE = re.compile(
|
| 141 |
+
r"\b(passport|visa|nbi\s*clearance|psa|birth\s*certificate|"
|
| 142 |
+
r"driver.?s?\s*license|sss|pag.?ibig|phil\s*health|"
|
| 143 |
+
r"school\s*enrollment|tuition|"
|
| 144 |
+
r"basketball|nba|movie|joke|sing\s*(me\s*)?a\s*song|poem|"
|
| 145 |
+
r"weather\s*in|president\s*of|capital\s*of|recipe|"
|
| 146 |
+
r"pizza|burger|game|football|calculus|cryptocurrency|bitcoin|stock\s*market|"
|
| 147 |
+
r"renew\s*(my\s*)?passport|how\s*to\s*cook)\b",
|
| 148 |
+
re.IGNORECASE,
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
# App-flow routing is deliberately narrow. These rules only clarify the
|
| 152 |
+
# current BrgyLink registration flow; they run after the safety rules above
|
| 153 |
+
# and do not disclose account-specific information.
|
| 154 |
+
_SIGNUP_OTP_RE = re.compile(
|
| 155 |
+
r"(?=.*\b(?:otp|one\s*time\s*password|verification\s*code|email\s*code)\b)"
|
| 156 |
+
r"(?=.*\b(?:sign\s*up|signup|register|registration|mag-?register|rehistro|parehistro)\b)",
|
| 157 |
+
re.IGNORECASE,
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
_SIGNUP_RE = re.compile(
|
| 161 |
+
r"\b(sign\s*up|signup|register|registration|mag\s*register|"
|
| 162 |
+
r"rehistro|parehistro|agparehistro|magparehistro|panagparehistro)\b"
|
| 163 |
+
)
|
| 164 |
+
_CREATE_ACCOUNT_RE = re.compile(
|
| 165 |
+
r"\b(create|make|open|new|gumawa|gagawa|agaramid|manggawa)\b.{0,30}\b(?:resident\s*)?account\b|"
|
| 166 |
+
r"\baccount\b.{0,25}\b(create|make|open|new|gumawa)\b"
|
| 167 |
+
)
|
| 168 |
+
_VOTER_RE = re.compile(r"\b(voters?|vote|voting|botante|comelec|boto|eleksyon)\b")
|
| 169 |
+
_CODE_RE = re.compile(r"\b(otp|verification\s*code|email\s*code|code)\b")
|
| 170 |
+
_CODE_PROBLEM_RE = re.compile(
|
| 171 |
+
r"\b(expir\w*|incorrect|invalid|wrong|never|missing|resend|"
|
| 172 |
+
r"not\s*(?:receive\w*|arriv\w*)|did\s*not|didn\s*t|"
|
| 173 |
+
r"hindi|di\s*dumating|wala|naawat|awan|anggapo|mali|"
|
| 174 |
+
r"waiting|waited|does\s*not|doesn\s*t)\b"
|
| 175 |
+
)
|
| 176 |
+
_PASSWORD_RE = re.compile(r"\b(password|pass\s*word|pasword)\b")
|
| 177 |
+
_PASSWORD_PROBLEM_RE = re.compile(
|
| 178 |
+
r"\b(forgot\w*|forget\w*|lost|lose|losing|reset|recover\w*|nakalimut\w*|nalipat\w*|"
|
| 179 |
+
r"alingwan\w*|wrong|incorrect|invalid|mali|change|palitan)\b"
|
| 180 |
+
)
|
| 181 |
+
_ACCOUNT_RE = re.compile(r"\b(account|login|log\s*in|sign\s*in|sumrek|makapasok)\b")
|
| 182 |
+
_ACCOUNT_REVIEW_RE = re.compile(r"\b(pending|reject\w*|refus\w*|declin\w*|approv\w*|review|submitted|naisumite)\b")
|
| 183 |
+
_OFFICIALS_RE = re.compile(
|
| 184 |
+
r"\b(kapitan|captain|punong\s*barangay|kagawad|opisyal|officials?|"
|
| 185 |
+
r"barangay\s*(?:chairman|chairperson|council|leaders?)|councillors?|councilors?|tanod|sk\s*chairman)\b"
|
| 186 |
+
)
|
| 187 |
+
_BARANGAY_LOCATION_RE = re.compile(
|
| 188 |
+
r"\b(?:where\s*(?:is|are)|saan|nasaan|sadino|iner)\b.{0,30}"
|
| 189 |
+
r"\b(?:barangay|brgy)\b.{0,30}\bbagong\s*pag\s*asa\b|"
|
| 190 |
+
r"\bbagong\s*pag\s*asa\b.{0,30}\b(?:location|address|saan|nasaan|sadino|iner)\b"
|
| 191 |
+
)
|
| 192 |
+
_CAPABILITIES_RE = re.compile(
|
| 193 |
+
r"\b(?:what\s*(?:can|do)\s*(?:you|brgylink)|how\s*can\s*you\s*help|"
|
| 194 |
+
r"ano\s*(?:ang\s*)?(?:mga\s*)?(?:pwede|puwede|kaya)\s*(?:mong\s*)?gawin|"
|
| 195 |
+
r"anong\s*(?:mga\s*)?(?:tulong|serbisyo)\s*(?:ang\s*)?(?:kaya|pwede|puwede)|"
|
| 196 |
+
r"ania\s*(?:dagiti\s*)?(?:mabalin|tulong)|antoy\s*(?:saray\s*)?(?:tulong|sarag))\b"
|
| 197 |
+
)
|
| 198 |
+
_CIVIC_RE = re.compile(r"\b(sdg|civic\s*(?:tasks?|participation)|missions?|community\s*initiative)\b")
|
| 199 |
+
_EVENT_RE = re.compile(r"\b(events?|community\s*activities)\b")
|
| 200 |
+
_ID_RE = re.compile(r"\b(valid\s*id|government\s*(?:issued\s*)?id|passport|driver\w*\s*license)\b")
|
| 201 |
+
_ID_PHOTO_RE = re.compile(
|
| 202 |
+
r"(?=.*\b(?:id|identification|passport|licen[cs]e)\b)"
|
| 203 |
+
r"(?=.*\b(?:photos?|pictures?|upload\w*|attach\w*|image|readable|blurry|photograph\w*)\b)"
|
| 204 |
+
)
|
| 205 |
+
# Never confuse a request for private credentials or somebody else's records
|
| 206 |
+
# with help recovering the resident's own password. No records are accessed.
|
| 207 |
+
_PRIVATE_DATA_RE = re.compile(
|
| 208 |
+
r"\b(?:admin|administrator)\b.{0,30}\b(?:password|credentials|token|secret)\b|"
|
| 209 |
+
r"\b(?:password|credentials|token|secret)\b.{0,30}\b(?:admin|administrator)\b|"
|
| 210 |
+
r"\b(?:other|another|all)\s+residents?\b.{0,40}\b(?:ids?|records?|details|data)\b|"
|
| 211 |
+
r"\b(?:neighbou?r\w*|someone|somebody|another|other)\b.{0,40}\b(?:password|credentials|private\s*records)\b"
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
# ---------------------------------------------------------------------------
|
| 215 |
+
# Keyword-boost dictionary {pattern: {intent: boost_value}}
|
| 216 |
+
# ---------------------------------------------------------------------------
|
| 217 |
+
KEYWORD_BOOSTS: list[tuple[re.Pattern, dict]] = [
|
| 218 |
+
# Greetings
|
| 219 |
+
(re.compile(r"\b(hello|hi|hey|good\s*(morning|afternoon|evening)|kumusta|kamusta|naimbag|maabig|magandang)\b"), {"greeting": 0.50}),
|
| 220 |
+
|
| 221 |
+
# Goodbye & Farewell
|
| 222 |
+
(re.compile(r"\b(paalam|makaalis|agpakada|innakon|goodbye|bye\s*bye|quit|exit|signing\s*off)\b"), {"goodbye": 0.50}),
|
| 223 |
+
(re.compile(r"\b(salamat|maraming salamat|agyamanak|balbaleg)\b"), {"thanks": 0.25}),
|
| 224 |
+
|
| 225 |
+
# Document intents
|
| 226 |
+
(re.compile(r"\bclearance\b"), {"clearance": 0.30}),
|
| 227 |
+
(re.compile(r"\bindigenc[yi]\b"), {"indigency": 0.40}),
|
| 228 |
+
(re.compile(r"\bresidency\b"), {"residency": 0.35}),
|
| 229 |
+
(re.compile(r"\bkatibayan\s+na\s+nakatira\b"), {"residency": 0.60}),
|
| 230 |
+
(re.compile(r"\bproof\s+of\s+residen\w*\b"), {"residency": 0.55}),
|
| 231 |
+
(re.compile(r"\bgood moral\b(?!\s*morning)"), {"good_moral": 0.40}),
|
| 232 |
+
(re.compile(r"\bbarangay i\.?d\.?\b|\bbgy id\b"), {"barangay_id": 0.50}),
|
| 233 |
+
(re.compile(r"\bbusiness (clearance|permit)\b"), {"business_permit": 0.40}),
|
| 234 |
+
(re.compile(r"\b(negosyo|sari.?sari|tindahan)\b"), {"business_permit": 0.25}),
|
| 235 |
+
|
| 236 |
+
# Emergency (supplement safety pre-classifier)
|
| 237 |
+
(re.compile(r"\b(sunog|apoy|uram|fire|911|ambulance|ambulansya)\b"), {"emergency": 0.55}),
|
| 238 |
+
(re.compile(r"\b(saklolo|tulong.*tulong|aksidente|danger|peligro)\b"), {"emergency": 0.40}),
|
| 239 |
+
|
| 240 |
+
# Blotter / complaints / disputes
|
| 241 |
+
(re.compile(r"\b(blotter|reklamo|magreklamo|ireport|panagreklamo)\b"), {"report_incident": 0.40}),
|
| 242 |
+
(re.compile(r"\b(ingay|maingay|maingal|makapaingal|riri|away|nakawan|takew)\b"), {"report_incident": 0.35}),
|
| 243 |
+
|
| 244 |
+
# Lupon / Katarungang Pambarangay
|
| 245 |
+
(re.compile(r"\b(lupon|tagapamayapa|katarungang|mediation|conciliation|alitan|kolkol|kolkolan|pangkat)\b"), {"lupon": 0.50}),
|
| 246 |
+
|
| 247 |
+
# Assistance / Ayuda / AICS
|
| 248 |
+
(re.compile(r"\b(aics|ayuda|tulong.?salapi|tulong.?pinansyal|relief|4ps|pantawid|ponpon|burial)\b"), {"aics_assistance": 0.45}),
|
| 249 |
+
|
| 250 |
+
# Senior Citizen & Solo Parent
|
| 251 |
+
(re.compile(r"\b(senior citizen|osca|matatken|lallakay|babbaket)\b"), {"senior_citizen": 0.50}),
|
| 252 |
+
(re.compile(r"\b(solo parent|single parent|nag.?iisang magulang|agsolsolo a nagannak)\b"), {"solo_parent": 0.50}),
|
| 253 |
+
|
| 254 |
+
# Health (but not medical symptoms — those go to safety pre-classifier)
|
| 255 |
+
(re.compile(r"\b(health center|bhc|bakuna|vaccin\w*|prenatal|gamot|agas|bitamina|salun.?at)\b"), {"health_services": 0.45}),
|
| 256 |
+
|
| 257 |
+
# Garbage
|
| 258 |
+
(re.compile(r"\b(basura|garbage|trash|hakot|panangala na basura|panag.?ala ti basura|kolekta)\b"), {"garbage": 0.50}),
|
| 259 |
+
|
| 260 |
+
# Office hours / Location
|
| 261 |
+
(re.compile(r"\b(office hours|opening times|oras na opisina|oras ti opisina|tanggapan|schedule\s*(ng|na|ti)\s*opisina)\b|\b(?:bukas|open)\b.{0,25}\b(?:opisina|office|barangay hall)\b"), {"office_hours": 0.45}),
|
| 262 |
+
(re.compile(r"\b(where\s*(?:is|can\s*i\s*find)\s*(?:the\s*)?(?:office|barangay\s*hall)|nasaan\s*(ang\s*)?opisina|saan\s*(so|ti)\s*opisina|address|location\s*(of|ng|na)?\s*(the\s*)?(office|barangay)?)\b"), {"office_hours": 0.45}),
|
| 263 |
+
|
| 264 |
+
# Fees — must beat clearance when fee-related words present
|
| 265 |
+
(re.compile(r"\b(fees?|bayad|bayar|magkano|panpiga|mano ti bayad|mano so bayad|presyo|singil|cost|price|how\s*much)\b"), {"fees": 0.50}),
|
| 266 |
+
|
| 267 |
+
# Voter Registration
|
| 268 |
+
(re.compile(r"\b(voter|botante|comelec|rehistro.*boto|eleksyon)\b"), {"voter_registration": 0.50}),
|
| 269 |
+
|
| 270 |
+
# SDG / Community Initiatives
|
| 271 |
+
(re.compile(r"\b(sdg|community initiative|civic task|tree planting|clean.?up)\b"), {"sdg_mission": 0.50}),
|
| 272 |
+
|
| 273 |
+
# Officials
|
| 274 |
+
(re.compile(r"\b(kapitan|punong barangay|kagawad|opisyal|officials|tanod|sk chairman)\b"), {"officials": 0.45}),
|
| 275 |
+
|
| 276 |
+
# Status / Tracking
|
| 277 |
+
(re.compile(r"\b(status|track|ready for pickup|nasaan na|subaybayan|nabantayan)\b"), {"document_status": 0.30}),
|
| 278 |
+
|
| 279 |
+
# About App
|
| 280 |
+
(re.compile(r"\b(what is brgylink|ano ang brgylink|ania ti brgylink|antoy brgylink|features of brgylink)\b"), {"about_app": 0.50}),
|
| 281 |
+
]
|
| 282 |
+
|
| 283 |
+
# ---------------------------------------------------------------------------
|
| 284 |
+
# Helper responses (non-factual UI prompts)
|
| 285 |
+
# ---------------------------------------------------------------------------
|
| 286 |
+
HELPER_TEXTS = {
|
| 287 |
+
"choose_document": {
|
| 288 |
+
"english": "Which document do you need? You can request a Barangay Clearance, Certificate of Indigency, Certificate of Residency, Business Clearance, Certificate of Good Moral Character, or Barangay ID.",
|
| 289 |
+
"tagalog": "Anong dokumento po ang kailangan ninyo? Maaari kayong humiling ng Barangay Clearance, Certificate of Indigency, Certificate of Residency, Business Clearance, Certificate of Good Moral Character, o Barangay ID.",
|
| 290 |
+
"ilocano": "Ania a dokumento ti kasapulam kabsat? Mabalin ti agkiddaw iti Barangay Clearance, Certificate of Indigency, Certificate of Residency, Business Clearance, Certificate of Good Moral Character, wenno Barangay ID.",
|
| 291 |
+
"pangasinan": "Antoy dokumento ya kasapulan mo kabaleyan? Nayari kayon mangala na Barangay Clearance, Certificate of Indigency, Certificate of Residency, Business Clearance, Certificate of Good Moral Character, odino Barangay ID."
|
| 292 |
+
},
|
| 293 |
+
"status": {
|
| 294 |
+
"english": "Open Document Requests in BrgyLink to check the status of your application (Pending, Processing, Ready for Pickup, Completed, or Rejected). The chatbot cannot access personal request details.",
|
| 295 |
+
"tagalog": "Buksan ang Document Requests sa BrgyLink upang makita ang status ng iyong request (Pending, Processing, Ready for Pickup, Completed, o Rejected). Hindi maa-access ng chatbot ang personal na request details.",
|
| 296 |
+
"ilocano": "Lukatan ti Document Requests iti BrgyLink tapno makita ti kasasaad ti kineddawmo a dokumento (Pending, Processing, Ready for Pickup, Completed, wenno Rejected). Saan a maakses ti chatbot ti personal a request details.",
|
| 297 |
+
"pangasinan": "Lukatan so Document Requests ed BrgyLink pian nengnengen so status na kineddeng mon dokumento (Pending, Processing, Ready for Pickup, Completed, odino Rejected). Ag nayarin aksesan na chatbot so personal ya request details."
|
| 298 |
+
},
|
| 299 |
+
"legal_disclaimer": {
|
| 300 |
+
"english": "The chatbot cannot provide legal advice. For legal concerns, you may visit the barangay office to inquire about Lupon Tagapamayapa (mediation) if the dispute is within barangay jurisdiction, or consult a lawyer for matters beyond barangay scope.",
|
| 301 |
+
"tagalog": "Hindi makapagbigay ng legal advice ang chatbot. Para sa legal concerns, bumisita sa opisina ng barangay para sa Lupon Tagapamayapa (mediation) kung sakop ng barangay ang usapin, o kumonsulta sa abogado para sa mga bagay na lampas sa sakop ng barangay.",
|
| 302 |
+
"ilocano": "Saan a makaited ti chatbot iti legal advice. Para kadagiti legal concerns, bisitaem ti opisina ti barangay tapno umammo maipapan iti Lupon Tagapamayapa (mediation) no sakop ti barangay, wenno konsultaem ti abogado para kadagiti saan a sakop.",
|
| 303 |
+
"pangasinan": "Ag makaiter na legal advice so chatbot. Para ed saray legal concerns, bisitaen so opisina na barangay para ed Lupon Tagapamayapa (mediation) no sakop na barangay, odino konsultaen so abogado para ed saray agtaay ed sakop."
|
| 304 |
+
},
|
| 305 |
+
"unverified_disclaimer": {
|
| 306 |
+
"english": "\n\n(Note: This information is not verified. Please confirm with the barangay office.)",
|
| 307 |
+
"tagalog": "\n\n(Paalala: Ang impormasyong ito ay hindi pa verified. Pakikumpirma sa opisina ng barangay.)",
|
| 308 |
+
"ilocano": "\n\n(Pammalagip: Daytoy nga impormasion ket saan pay a verified. Pangngaasi a kumpirmaen iti opisina ti barangay.)",
|
| 309 |
+
"pangasinan": "\n\n(Paimano: Saya ya impormasyon et agni verified. Kumpirmaen ed opisina na barangay.)"
|
| 310 |
+
}
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
# ---------------------------------------------------------------------------
|
| 315 |
+
# Knowledge Base Loader
|
| 316 |
+
# ---------------------------------------------------------------------------
|
| 317 |
+
_kb_cache: dict | None = None
|
| 318 |
+
|
| 319 |
+
def load_knowledge_base() -> dict:
|
| 320 |
+
"""Load knowledge_base.json and index by intent."""
|
| 321 |
+
global _kb_cache
|
| 322 |
+
if _kb_cache is not None:
|
| 323 |
+
return _kb_cache
|
| 324 |
+
with open(KB_FILE, encoding="utf-8") as f:
|
| 325 |
+
kb = json.load(f)
|
| 326 |
+
index = {}
|
| 327 |
+
for svc in kb.get("services", []):
|
| 328 |
+
index[svc["intent"]] = svc
|
| 329 |
+
_kb_cache = index
|
| 330 |
+
return index
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def get_kb_answer(intent: str, language: str, variant: str | None = None) -> str:
|
| 334 |
+
"""Get the answer for an intent from the knowledge base.
|
| 335 |
+
|
| 336 |
+
Appends an unverified-data disclaimer only when content_type is
|
| 337 |
+
'barangay_fact' and the entry is not validly verified.
|
| 338 |
+
System copy (greetings, fallback, etc.) never gets the disclaimer.
|
| 339 |
+
"""
|
| 340 |
+
kb = load_knowledge_base()
|
| 341 |
+
entry = kb.get(intent)
|
| 342 |
+
|
| 343 |
+
def is_valid_answer(ans):
|
| 344 |
+
if not isinstance(ans, dict):
|
| 345 |
+
return False
|
| 346 |
+
for lang in ["english", "tagalog", "ilocano", "pangasinan"]:
|
| 347 |
+
if lang not in ans or not isinstance(ans[lang], str) or not ans[lang].strip():
|
| 348 |
+
return False
|
| 349 |
+
return True
|
| 350 |
+
|
| 351 |
+
if not entry or not is_valid_answer(entry.get("answer")):
|
| 352 |
+
# Fall through to out_of_scope
|
| 353 |
+
entry = kb.get("out_of_scope", {})
|
| 354 |
+
|
| 355 |
+
answer_obj = entry.get("answer", {})
|
| 356 |
+
# Variants are still curated KB content, never generated factual answers.
|
| 357 |
+
candidate = entry.get("answer_variants", {}).get(variant) if variant else None
|
| 358 |
+
if is_valid_answer(candidate):
|
| 359 |
+
answer_obj = candidate
|
| 360 |
+
text = answer_obj.get(language, answer_obj.get("english", ""))
|
| 361 |
+
|
| 362 |
+
# Only barangay_fact entries can carry the unverified disclaimer.
|
| 363 |
+
content_type = entry.get("content_type", "barangay_fact")
|
| 364 |
+
if content_type != "barangay_fact":
|
| 365 |
+
return text
|
| 366 |
+
|
| 367 |
+
import datetime
|
| 368 |
+
|
| 369 |
+
is_verified = entry.get("verified", False)
|
| 370 |
+
|
| 371 |
+
# Check verification requirements
|
| 372 |
+
if is_verified:
|
| 373 |
+
has_reqs = all(entry.get(f) for f in ["source", "verified_by", "verified_at", "expires_at"])
|
| 374 |
+
if not has_reqs:
|
| 375 |
+
is_verified = False
|
| 376 |
+
else:
|
| 377 |
+
try:
|
| 378 |
+
expiry_date = datetime.datetime.fromisoformat(entry["expires_at"].replace("Z", "+00:00"))
|
| 379 |
+
if datetime.datetime.now(datetime.timezone.utc) > expiry_date:
|
| 380 |
+
is_verified = False
|
| 381 |
+
except (ValueError, TypeError, AttributeError):
|
| 382 |
+
is_verified = False
|
| 383 |
+
|
| 384 |
+
if not is_verified:
|
| 385 |
+
disclaimer = HELPER_TEXTS["unverified_disclaimer"].get(language, HELPER_TEXTS["unverified_disclaimer"]["english"])
|
| 386 |
+
if disclaimer not in text:
|
| 387 |
+
text += disclaimer
|
| 388 |
+
|
| 389 |
+
return text
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
# ---------------------------------------------------------------------------
|
| 393 |
+
# Text preprocessing
|
| 394 |
+
# ---------------------------------------------------------------------------
|
| 395 |
+
def apply_aliases(text: str) -> str:
|
| 396 |
+
for pattern, replacement in ALIASES:
|
| 397 |
+
text = pattern.sub(replacement, text)
|
| 398 |
+
return text
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
def normalize(text: str) -> str:
|
| 402 |
+
return re.sub(r"\s+", " ", re.sub(r"[^\w\s]", " ", text.lower())).strip()
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
def char_ngrams(text: str, ns=(2, 3, 4, 5)) -> Counter:
|
| 406 |
+
padded = f" {text} "
|
| 407 |
+
c = Counter()
|
| 408 |
+
for n in ns:
|
| 409 |
+
for i in range(len(padded) - n + 1):
|
| 410 |
+
c[("c", padded[i:i+n])] += 1
|
| 411 |
+
return c
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
def word_ngrams(tokens: list[str], ns=(1, 2)) -> Counter:
|
| 415 |
+
c = Counter()
|
| 416 |
+
for n in ns:
|
| 417 |
+
for i in range(len(tokens) - n + 1):
|
| 418 |
+
gram = " ".join(tokens[i:i+n])
|
| 419 |
+
c[("w", gram)] += 1
|
| 420 |
+
return c
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
def featurize(text: str) -> Counter:
|
| 424 |
+
norm = normalize(apply_aliases(normalize(text)))
|
| 425 |
+
tokens = norm.split()
|
| 426 |
+
features = char_ngrams(norm)
|
| 427 |
+
features.update(word_ngrams(tokens))
|
| 428 |
+
return features
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
# ---------------------------------------------------------------------------
|
| 432 |
+
# Scored Language Detection (Ilocano, Pangasinan, Tagalog, English)
|
| 433 |
+
# ---------------------------------------------------------------------------
|
| 434 |
+
# Short common tokens that overlap between Pangasinan and English.
|
| 435 |
+
# These get reduced weight to prevent English from being classified as Pangasinan.
|
| 436 |
+
_SHORT_PAN_TOKENS = {"ed", "so", "ya", "et", "to", "mi", "yo", "da", "ak", "la"}
|
| 437 |
+
|
| 438 |
+
_LANG_RULES: dict[str, list[tuple[re.Pattern, float]]] = {
|
| 439 |
+
"ilocano": [
|
| 440 |
+
(re.compile(r"\b(dagiti|kadagiti|wenno|ket|tapno|ken|nga|met|pay|laeng|amin|ditoy|daytoy|kaniak|kenka|kadakayo|datayo|dakami|isuda|kasta)\b"), 1.4),
|
| 441 |
+
(re.compile(r"\b(mabalin|agkiddaw|kasano|wen|saan|apay|sadino|kaano|kaanu|mano ti|anian|ania|anya|masapulko|masapul)\b"), 2.5),
|
| 442 |
+
(re.compile(r"\b(ilocano|ilokano|siasino|nalipatak|makaanges|ubing|kabsat|kailian|agas|ubbing|kolkol|riri|uram|panagbiag|malungsot|agyamanak|agpakada|innakon|agsapa)\b"), 3.5),
|
| 443 |
+
(re.compile(r"\b(naimbag|aldaw|bigat|malem|rabii|dios ti agngina|makasungbatak|makaawat)\b"), 3.5),
|
| 444 |
+
(re.compile(r"\b(mangited|mangaramid|pangngeddeng|panangsalaysay|lukatan|piliem|isumitem|panagkiddaw)\b"), 2.0),
|
| 445 |
+
# iti/ti get reduced weight to avoid false positives on short text
|
| 446 |
+
(re.compile(r"\biti\b"), 0.6),
|
| 447 |
+
(re.compile(r"\bti\b"), 0.4),
|
| 448 |
+
],
|
| 449 |
+
"pangasinan": [
|
| 450 |
+
# High-confidence Pangasinan markers
|
| 451 |
+
(re.compile(r"\b(saray|diad|onla|odino|pian|kabaleyan|natan)\b"), 2.5),
|
| 452 |
+
(re.compile(r"\b(mano so|panon so|antoy|anto|panon|kapigan|kasapulan|amtaen|ugugaw|makapaingal|alitan|kolkolan|apoy|ponpon|mabiin|kailangan koy|panpiga|piga)\b"), 3.2),
|
| 453 |
+
(re.compile(r"\b(pangasinan|anggapo|agko|siopa|ugaw|makainawa|makatalos|anggad|nayarin|manggawa|man.?ingat|makaalis|balbaleg|maabig|kabuasan|ngarem|agew|labi|masantos)\b"), 3.5),
|
| 454 |
+
(re.compile(r"\b(say|inkuan|nanengneng|ipaliwawa|piliyen|mangikeddeng|nengnengen|silpin)\b"), 2.0),
|
| 455 |
+
# Pangasinan pronoun 'ak' (first-person) — medium weight
|
| 456 |
+
(re.compile(r"\bak\b"), 1.5),
|
| 457 |
+
# Short tokens that also appear in English — low weight
|
| 458 |
+
(re.compile(r"\b(ed|so|ya|et|tan|met|labat)\b"), 0.3),
|
| 459 |
+
],
|
| 460 |
+
"tagalog": [
|
| 461 |
+
(re.compile(r"\b(ang|ng|mga|sa|ay|ko|mo|po|opo|naman|lang|ba|dito|ito|yan|yun|natin|ninyo|amin|atin|nila|siya|sila)\b"), 0.8),
|
| 462 |
+
(re.compile(r"\b(magandang|kumusta|kamusta|salamat|paalam|sige|walang anuman|pasensya|maraming salamat|ingat po)\b"), 2.0),
|
| 463 |
+
(re.compile(r"\b(paano|anong|ano|saan|kailan|sino|bakit|hindi|yung|maling|kailangan|gusto|mayroon|magkano|bayad|libre|puwede|maaari)\b"), 2.0),
|
| 464 |
+
(re.compile(r"\b(tagalog|filipino|pilipino|kumuha|makuha|humiling|ireport|magreklamo|kapitan|kagawad|tanod|nakatira)\b"), 3.0),
|
| 465 |
+
],
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
def detect_language(message: str) -> str:
|
| 470 |
+
"""Detect whether text is Ilocano, Pangasinan, Tagalog, or English.
|
| 471 |
+
|
| 472 |
+
Uses a scoring system with safety checks against false positives
|
| 473 |
+
from short overlapping tokens (e.g. 'to', 'so', 'ed' which appear
|
| 474 |
+
in both English and Pangasinan).
|
| 475 |
+
"""
|
| 476 |
+
value = normalize(message)
|
| 477 |
+
tokens = set(value.split())
|
| 478 |
+
|
| 479 |
+
scores = {lang: 0.0 for lang in _LANG_RULES}
|
| 480 |
+
match_counts = {lang: 0 for lang in _LANG_RULES}
|
| 481 |
+
|
| 482 |
+
for lang, rules in _LANG_RULES.items():
|
| 483 |
+
for pattern, weight in rules:
|
| 484 |
+
matches = len(pattern.findall(value))
|
| 485 |
+
if matches > 0:
|
| 486 |
+
scores[lang] += matches * weight
|
| 487 |
+
match_counts[lang] += matches
|
| 488 |
+
|
| 489 |
+
best_lang = max(scores, key=scores.__getitem__)
|
| 490 |
+
best_score = scores[best_lang]
|
| 491 |
+
|
| 492 |
+
# Require a minimum threshold
|
| 493 |
+
if best_score <= 0.5:
|
| 494 |
+
return "english"
|
| 495 |
+
|
| 496 |
+
# Safety: If Pangasinan won but ALL matching tokens are short/ambiguous,
|
| 497 |
+
# and there are no high-confidence Pangasinan markers, default to English.
|
| 498 |
+
if best_lang == "pangasinan":
|
| 499 |
+
pan_only_short = tokens.issubset(
|
| 500 |
+
_SHORT_PAN_TOKENS | {t for t in tokens if len(t) <= 3}
|
| 501 |
+
| set(value.split())
|
| 502 |
+
)
|
| 503 |
+
# Check if any high-confidence Pangasinan word is present
|
| 504 |
+
high_conf_pan = re.search(
|
| 505 |
+
r"\b(saray|diad|onla|odino|pian|kabaleyan|pangasinan|makatalos|"
|
| 506 |
+
r"maabig|kabuasan|balbaleg|antoy|panon|kapigan|nayarin|mangikeddeng|"
|
| 507 |
+
r"nengnengen|say|anggad|ugugaw|kolkolan|panpiga|mabiin)\b",
|
| 508 |
+
value,
|
| 509 |
+
)
|
| 510 |
+
if not high_conf_pan and best_score < 2.0:
|
| 511 |
+
return "english"
|
| 512 |
+
|
| 513 |
+
return best_lang
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
# ---------------------------------------------------------------------------
|
| 517 |
+
# Language switch intent detection
|
| 518 |
+
# ---------------------------------------------------------------------------
|
| 519 |
+
_LANG_SWITCH = re.compile(
|
| 520 |
+
r"(sagot|sumagot|reply|response|respond|speak|salita|wika|sagutin|usaren|pansalita|answer|isagot).{0,30}"
|
| 521 |
+
r"(tagalog|filipino|ilocano|ilokano|pangasinan|english|ingles)"
|
| 522 |
+
r"|(tagalog|filipino|ilocano|ilokano|pangasinan|english|ingles).{0,30}"
|
| 523 |
+
r"(ang|lang|only|please|lamang|na lang|so usaren|ti usarem)",
|
| 524 |
+
re.IGNORECASE,
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
_LANG_SWITCH_ENGLISH = re.compile(
|
| 528 |
+
r"(answer|reply|speak|respond|talk).{0,20}(in\s+)?(english|ingles)"
|
| 529 |
+
r"|(english|ingles).{0,20}(please|lang|only|po)",
|
| 530 |
+
re.IGNORECASE,
|
| 531 |
+
)
|
| 532 |
+
|
| 533 |
+
def _extract_switch_language(message: str) -> str | None:
|
| 534 |
+
"""Extract the target language from a language-switch request."""
|
| 535 |
+
value = message.lower()
|
| 536 |
+
if _LANG_SWITCH_ENGLISH.search(value):
|
| 537 |
+
return "english"
|
| 538 |
+
m = _LANG_SWITCH.search(value)
|
| 539 |
+
if not m:
|
| 540 |
+
return None
|
| 541 |
+
full = m.group(0).lower()
|
| 542 |
+
if "ilocano" in full or "ilokano" in full:
|
| 543 |
+
return "ilocano"
|
| 544 |
+
if "pangasinan" in full:
|
| 545 |
+
return "pangasinan"
|
| 546 |
+
if "tagalog" in full or "filipino" in full:
|
| 547 |
+
return "tagalog"
|
| 548 |
+
if "english" in full or "ingles" in full:
|
| 549 |
+
return "english"
|
| 550 |
+
return None
|
| 551 |
+
|
| 552 |
+
|
| 553 |
+
def language_switch(message: str) -> bool:
|
| 554 |
+
return _extract_switch_language(message) is not None
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
# ---------------------------------------------------------------------------
|
| 558 |
+
# Training
|
| 559 |
+
# ---------------------------------------------------------------------------
|
| 560 |
+
def train() -> dict:
|
| 561 |
+
with open(INTENTS_FILE, "rb") as f:
|
| 562 |
+
training_bytes = f.read()
|
| 563 |
+
intents = json.loads(training_bytes)["intents"]
|
| 564 |
+
|
| 565 |
+
docs = [(item["tag"], pattern) for item in intents for pattern in item["patterns"]]
|
| 566 |
+
freq = Counter()
|
| 567 |
+
features_list = []
|
| 568 |
+
for _, text in docs:
|
| 569 |
+
fv = featurize(text)
|
| 570 |
+
features_list.append(fv)
|
| 571 |
+
freq.update(fv.keys())
|
| 572 |
+
|
| 573 |
+
N = len(docs)
|
| 574 |
+
idf = {k: math.log((1 + N) / (1 + cnt)) + 1 for k, cnt in freq.items()}
|
| 575 |
+
|
| 576 |
+
centroids = defaultdict(Counter)
|
| 577 |
+
counts = Counter()
|
| 578 |
+
for (tag, _), fv in zip(docs, features_list):
|
| 579 |
+
counts[tag] += 1
|
| 580 |
+
for k, v in fv.items():
|
| 581 |
+
centroids[tag][k] += v * idf.get(k, 1)
|
| 582 |
+
|
| 583 |
+
for tag in centroids:
|
| 584 |
+
n = counts[tag]
|
| 585 |
+
for k in centroids[tag]:
|
| 586 |
+
centroids[tag][k] /= n
|
| 587 |
+
|
| 588 |
+
model = {
|
| 589 |
+
"version": MODEL_VERSION,
|
| 590 |
+
"training_sha256": hashlib.sha256(training_bytes).hexdigest(),
|
| 591 |
+
"idf": idf,
|
| 592 |
+
"centroids": dict(centroids),
|
| 593 |
+
}
|
| 594 |
+
# An interrupted training run must not replace a working model with a
|
| 595 |
+
# partially written pickle. Save alongside the target and atomically swap.
|
| 596 |
+
fd, temporary_path = tempfile.mkstemp(prefix=".classifier-", dir=os.path.dirname(MODEL_FILE))
|
| 597 |
+
try:
|
| 598 |
+
with os.fdopen(fd, "wb") as f:
|
| 599 |
+
pickle.dump(model, f)
|
| 600 |
+
os.replace(temporary_path, MODEL_FILE)
|
| 601 |
+
finally:
|
| 602 |
+
if os.path.exists(temporary_path):
|
| 603 |
+
os.unlink(temporary_path)
|
| 604 |
+
return model
|
| 605 |
+
|
| 606 |
+
|
| 607 |
+
def load_model() -> dict:
|
| 608 |
+
if not os.path.exists(MODEL_FILE):
|
| 609 |
+
return train()
|
| 610 |
+
try:
|
| 611 |
+
with open(MODEL_FILE, "rb") as f:
|
| 612 |
+
model = pickle.load(f)
|
| 613 |
+
except (pickle.UnpicklingError, EOFError, ValueError, AttributeError):
|
| 614 |
+
return train()
|
| 615 |
+
with open(INTENTS_FILE, "rb") as f:
|
| 616 |
+
fingerprint = hashlib.sha256(f.read()).hexdigest()
|
| 617 |
+
if (not isinstance(model, dict) or model.get("version") != MODEL_VERSION
|
| 618 |
+
or model.get("training_sha256") != fingerprint
|
| 619 |
+
or not isinstance(model.get("idf"), dict)
|
| 620 |
+
or not isinstance(model.get("centroids"), dict) or not model["centroids"]):
|
| 621 |
+
return train()
|
| 622 |
+
return model
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
def cosine(left: Counter, right: dict) -> float:
|
| 626 |
+
dot = sum(v * right.get(k, 0) for k, v in left.items())
|
| 627 |
+
a = math.sqrt(sum(x * x for x in left.values()))
|
| 628 |
+
b = math.sqrt(sum(x * x for x in right.values()))
|
| 629 |
+
return dot / (a * b) if a and b else 0.0
|
| 630 |
+
|
| 631 |
+
|
| 632 |
+
def compute_boost(value: str) -> dict[str, float]:
|
| 633 |
+
boost = defaultdict(float)
|
| 634 |
+
for pattern, intent_boosts in KEYWORD_BOOSTS:
|
| 635 |
+
if pattern.search(value):
|
| 636 |
+
for intent, amount in intent_boosts.items():
|
| 637 |
+
boost[intent] += amount
|
| 638 |
+
return boost
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
def document_in(message: str) -> str | None:
|
| 642 |
+
value = normalize(apply_aliases(normalize(message)))
|
| 643 |
+
# Don't match "cert" alone — too ambiguous
|
| 644 |
+
return next((doc for key, doc in DOCUMENTS.items() if key != "cert" and key in value), None)
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
def new_session() -> dict:
|
| 648 |
+
return {
|
| 649 |
+
"preferred_language": None,
|
| 650 |
+
"pending": None,
|
| 651 |
+
"selected_document": None,
|
| 652 |
+
"last_intent": None,
|
| 653 |
+
"turn_count": 0,
|
| 654 |
+
}
|
| 655 |
+
|
| 656 |
+
|
| 657 |
+
def classify(message: str, model: dict) -> tuple[str, float, float]:
|
| 658 |
+
"""Classify a message. Returns (intent, score, margin).
|
| 659 |
+
|
| 660 |
+
margin is the gap between the top intent and the runner-up.
|
| 661 |
+
"""
|
| 662 |
+
fv = featurize(message)
|
| 663 |
+
vec = {k: v * model["idf"].get(k, 1) for k, v in fv.items()}
|
| 664 |
+
boost = compute_boost(normalize(message))
|
| 665 |
+
|
| 666 |
+
ranked = sorted(
|
| 667 |
+
(
|
| 668 |
+
(tag, cosine(vec, centroid) + boost.get(tag, 0))
|
| 669 |
+
for tag, centroid in model["centroids"].items()
|
| 670 |
+
),
|
| 671 |
+
key=lambda x: x[1],
|
| 672 |
+
reverse=True,
|
| 673 |
+
)
|
| 674 |
+
intent, score = ranked[0]
|
| 675 |
+
runner_up_score = ranked[1][1] if len(ranked) > 1 else 0.0
|
| 676 |
+
margin = score - runner_up_score
|
| 677 |
+
|
| 678 |
+
# Strict threshold + margin requirement
|
| 679 |
+
if score < CONFIDENCE_THRESHOLD or margin < MARGIN_THRESHOLD:
|
| 680 |
+
return FALLBACK, score, margin
|
| 681 |
+
|
| 682 |
+
return intent, score, margin
|
| 683 |
+
|
| 684 |
+
|
| 685 |
+
def _build(intent: str, score: float, language: str, response: str, session: dict) -> dict:
|
| 686 |
+
session["last_intent"] = intent
|
| 687 |
+
session["turn_count"] = session.get("turn_count", 0) + 1
|
| 688 |
+
return {
|
| 689 |
+
"intent": intent,
|
| 690 |
+
"similarity": round(score, 4),
|
| 691 |
+
"language": language,
|
| 692 |
+
"response": response,
|
| 693 |
+
"session": session,
|
| 694 |
+
}
|
| 695 |
+
|
| 696 |
+
|
| 697 |
+
# ---------------------------------------------------------------------------
|
| 698 |
+
# Main message handler
|
| 699 |
+
# ---------------------------------------------------------------------------
|
| 700 |
+
def handle_message(message: str, session: dict | None = None, model: dict | None = None) -> dict:
|
| 701 |
+
session = session or new_session()
|
| 702 |
+
model = model or load_model()
|
| 703 |
+
value = normalize(message)
|
| 704 |
+
|
| 705 |
+
# Reset command
|
| 706 |
+
if value in {"reset", "restart", "clear", "start over"}:
|
| 707 |
+
session.clear()
|
| 708 |
+
session.update(new_session())
|
| 709 |
+
return _build("reset", 1.0, "english", "Conversation reset.", session)
|
| 710 |
+
|
| 711 |
+
# --- Language detection ---
|
| 712 |
+
detected = detect_language(message)
|
| 713 |
+
language = session.get("preferred_language") or detected
|
| 714 |
+
|
| 715 |
+
# Language switch request
|
| 716 |
+
switch_lang = _extract_switch_language(message)
|
| 717 |
+
if switch_lang:
|
| 718 |
+
language = switch_lang
|
| 719 |
+
session["preferred_language"] = language
|
| 720 |
+
|
| 721 |
+
# ===================================================================
|
| 722 |
+
# SAFETY PRE-CLASSIFIER — always runs first, overrides everything
|
| 723 |
+
# ===================================================================
|
| 724 |
+
|
| 725 |
+
# 1. Emergency (life-threatening)
|
| 726 |
+
if _EMERGENCY_RE.search(value):
|
| 727 |
+
resp = get_kb_answer("emergency", language)
|
| 728 |
+
return _build("emergency", 1.0, language, resp, session)
|
| 729 |
+
|
| 730 |
+
# 2. Threats, violence, abuse, self-harm
|
| 731 |
+
if _THREAT_RE.search(value):
|
| 732 |
+
resp = get_kb_answer("safety_threat", language)
|
| 733 |
+
return _build("safety_threat", 1.0, language, resp, session)
|
| 734 |
+
|
| 735 |
+
# 3. Medical symptoms (non-emergency) — route to health guidance
|
| 736 |
+
if _MEDICAL_SYMPTOM_RE.search(value):
|
| 737 |
+
# Check if also emergency-level
|
| 738 |
+
resp = get_kb_answer("medical_non_emergency", language)
|
| 739 |
+
return _build("medical_non_emergency", 1.0, language, resp, session)
|
| 740 |
+
|
| 741 |
+
# 4. Legal questions
|
| 742 |
+
if _LEGAL_RE.search(value):
|
| 743 |
+
resp = HELPER_TEXTS["legal_disclaimer"][language]
|
| 744 |
+
return _build("legal_disclaimer", 1.0, language, resp, session)
|
| 745 |
+
|
| 746 |
+
# 5. Obvious out-of-scope topics
|
| 747 |
+
# Passport/licence *upload* guidance for signup is in scope, not advice
|
| 748 |
+
# about obtaining or renewing those national documents.
|
| 749 |
+
signup_id = bool((_SIGNUP_RE.search(value) or _CREATE_ACCOUNT_RE.search(value)) and _ID_RE.search(value))
|
| 750 |
+
national_document_request = re.search(
|
| 751 |
+
r"\b(renew|renewal|apply\s*for|obtain|get\s*(?:a|my))\b.{0,25}\b(passport|license)\b|"
|
| 752 |
+
r"\b(passport|license)\b.{0,25}\b(renew|renewal)\b", value
|
| 753 |
+
)
|
| 754 |
+
scope_value = _ID_RE.sub("", value) if signup_id and not national_document_request else value
|
| 755 |
+
if _PRIVATE_DATA_RE.search(value) or _OUT_OF_SCOPE_RE.search(scope_value):
|
| 756 |
+
resp = get_kb_answer("out_of_scope", language)
|
| 757 |
+
return _build("out_of_scope", 1.0, language, resp, session)
|
| 758 |
+
|
| 759 |
+
# Explicit app concepts resolve common short wording without lowering the
|
| 760 |
+
# classifier's uncertainty threshold. Recovery wins over signup timing.
|
| 761 |
+
if _CODE_RE.search(value) and _CODE_PROBLEM_RE.search(value):
|
| 762 |
+
return _build("account_help", 0.98, language, get_kb_answer("account_help", language, "otp"), session)
|
| 763 |
+
if _PASSWORD_RE.search(value) and (_PASSWORD_PROBLEM_RE.search(value) or re.search(r"\bagko\s*amta\b", value)):
|
| 764 |
+
return _build("account_help", 0.98, language, get_kb_answer("account_help", language, "password"), session)
|
| 765 |
+
signup_question = bool(_SIGNUP_RE.search(value) or _CREATE_ACCOUNT_RE.search(value))
|
| 766 |
+
if (_ACCOUNT_RE.search(value) or signup_question) and _ACCOUNT_REVIEW_RE.search(value):
|
| 767 |
+
return _build("account_help", 0.98, language, get_kb_answer("account_help", language, "review"), session)
|
| 768 |
+
if _ACCOUNT_RE.search(value) and not signup_question:
|
| 769 |
+
variant = "review" if _ACCOUNT_REVIEW_RE.search(value) else None
|
| 770 |
+
return _build("account_help", 0.98, language, get_kb_answer("account_help", language, variant), session)
|
| 771 |
+
if _EVENT_RE.search(value):
|
| 772 |
+
return _build("events", 0.98, language, get_kb_answer("events", language), session)
|
| 773 |
+
if _CIVIC_RE.search(value):
|
| 774 |
+
return _build("sdg_mission", 0.98, language, get_kb_answer("sdg_mission", language), session)
|
| 775 |
+
if _OFFICIALS_RE.search(value):
|
| 776 |
+
return _build("officials", 0.98, language, get_kb_answer("officials", language), session)
|
| 777 |
+
if _BARANGAY_LOCATION_RE.search(value):
|
| 778 |
+
return _build("office_hours", 0.98, language, get_kb_answer("office_hours", language), session)
|
| 779 |
+
if _CAPABILITIES_RE.search(value):
|
| 780 |
+
return _build("about_app", 0.98, language, get_kb_answer("about_app", language), session)
|
| 781 |
+
if _SIGNUP_RE.search(value) and _VOTER_RE.search(value):
|
| 782 |
+
return _build("voter_registration", 0.98, language, get_kb_answer("voter_registration", language), session)
|
| 783 |
+
if _SIGNUP_OTP_RE.search(value) and not _VOTER_RE.search(value):
|
| 784 |
+
resp = get_kb_answer("registration", language)
|
| 785 |
+
return _build("registration", 0.98, language, resp, session)
|
| 786 |
+
if signup_question and not _VOTER_RE.search(value):
|
| 787 |
+
variant = "id" if _ID_RE.search(value) else None
|
| 788 |
+
return _build("registration", 0.98, language, get_kb_answer("registration", language, variant), session)
|
| 789 |
+
if _ID_RE.search(value) and re.search(r"\b(?:\d{1,2}|minor|age)\b", value):
|
| 790 |
+
return _build("registration", 0.98, language, get_kb_answer("registration", language, "id"), session)
|
| 791 |
+
if _ID_PHOTO_RE.search(value):
|
| 792 |
+
return _build("registration", 0.98, language, get_kb_answer("registration", language, "id"), session)
|
| 793 |
+
|
| 794 |
+
# ===================================================================
|
| 795 |
+
# COMPOUND QUERY DETECTION
|
| 796 |
+
# ===================================================================
|
| 797 |
+
|
| 798 |
+
# "How much for clearance?" / "bayad sa clearance" → fees, not clearance
|
| 799 |
+
_COMPOUND_FEE_RE = re.compile(
|
| 800 |
+
r"\b(fees?|bayad|bayar|magkano|panpiga|cost|price|singil|how\s*much|mano)\b"
|
| 801 |
+
r".{0,30}\b(clearance|indigency|residency|good moral|barangay id|business|permit|certificate|sertipiko|dokumento)\b",
|
| 802 |
+
re.IGNORECASE,
|
| 803 |
+
)
|
| 804 |
+
_COMPOUND_FEE_RE2 = re.compile(
|
| 805 |
+
r"\b(clearance|indigency|residency|good moral|barangay id|business|permit|certificate|sertipiko|dokumento)\b"
|
| 806 |
+
r".{0,30}\b(fees?|bayad|bayar|magkano|panpiga|cost|price|singil|how\s*much|mano)\b",
|
| 807 |
+
re.IGNORECASE,
|
| 808 |
+
)
|
| 809 |
+
if _COMPOUND_FEE_RE.search(value) or _COMPOUND_FEE_RE2.search(value):
|
| 810 |
+
doc = document_in(message)
|
| 811 |
+
if doc:
|
| 812 |
+
session["selected_document"] = doc
|
| 813 |
+
resp = get_kb_answer("fees", language)
|
| 814 |
+
return _build("fees", 0.95, language, resp, session)
|
| 815 |
+
|
| 816 |
+
# "clearance for business" → business_permit
|
| 817 |
+
if re.search(r"\bclearance\b.{0,20}\bbusiness\b|\bbusiness\b.{0,20}\bclearance\b", value):
|
| 818 |
+
resp = get_kb_answer("business_permit", language)
|
| 819 |
+
return _build("business_permit", 0.95, language, resp, session)
|
| 820 |
+
|
| 821 |
+
# ===================================================================
|
| 822 |
+
# TF-IDF CLASSIFIER
|
| 823 |
+
# ===================================================================
|
| 824 |
+
intent, score, margin = classify(message, model)
|
| 825 |
+
|
| 826 |
+
# Blotter status check
|
| 827 |
+
if re.search(r"\b(blotter|reklamo|complaint|incident report)\b", value) and \
|
| 828 |
+
re.search(r"\b(status|track|history|nasaan|naitala|in-process|update|naasikaso|pakaamta)\b", value):
|
| 829 |
+
resp = get_kb_answer("blotter_status", language)
|
| 830 |
+
return _build("blotter_status", score, language, resp, session)
|
| 831 |
+
|
| 832 |
+
# Document status check — but only if the message is clearly about status
|
| 833 |
+
_STATUS_RE = re.compile(
|
| 834 |
+
r"\b(status|track|ready for pickup|nasaan na|pending ba|approved na|processing na|rejected|"
|
| 835 |
+
r"kailan makukuha|kailan maaayos|done na|naaprubaran|subaybayan|nabantayan)\b",
|
| 836 |
+
re.IGNORECASE,
|
| 837 |
+
)
|
| 838 |
+
_DOC_OR_NAME_RE = re.compile(
|
| 839 |
+
r"\b(document|dokumento|request|hiniling|kineddaw|inkerew|"
|
| 840 |
+
r"clearance|indigency|residency|business|good\s*moral|barangay\s*id|sertipiko|certificate)\b",
|
| 841 |
+
re.IGNORECASE,
|
| 842 |
+
)
|
| 843 |
+
if _STATUS_RE.search(value) and (intent == "document_status" or _DOC_OR_NAME_RE.search(value)):
|
| 844 |
+
resp = get_kb_answer("document_status", language)
|
| 845 |
+
return _build("document_status", score, language, resp, session)
|
| 846 |
+
|
| 847 |
+
# Single-word / short document direct route
|
| 848 |
+
document = document_in(message)
|
| 849 |
+
if document and len(value.split()) <= 4 and not session.get("pending"):
|
| 850 |
+
session["selected_document"] = document
|
| 851 |
+
doc_intent = DOC_TO_INTENT.get(document, "document_request")
|
| 852 |
+
resp = get_kb_answer(doc_intent, language)
|
| 853 |
+
return _build(doc_intent, 1.0, language, resp, session)
|
| 854 |
+
|
| 855 |
+
# Resolve pending document choice
|
| 856 |
+
if session.get("pending") == "document_choice" and document:
|
| 857 |
+
session["pending"] = None
|
| 858 |
+
session["selected_document"] = document
|
| 859 |
+
doc_intent = DOC_TO_INTENT.get(document, "document_request")
|
| 860 |
+
resp = get_kb_answer(doc_intent, language)
|
| 861 |
+
return _build(doc_intent, 1.0, language, resp, session)
|
| 862 |
+
|
| 863 |
+
# Lupon mediation
|
| 864 |
+
if re.search(r"\b(lupon|katarungang pambarangay|mediation|pangkat|alitan|kolkol|kolkolan)\b", value):
|
| 865 |
+
resp = get_kb_answer("lupon", language)
|
| 866 |
+
return _build("lupon", score, language, resp, session)
|
| 867 |
+
|
| 868 |
+
# Generic document request
|
| 869 |
+
generic_doc = bool(re.search(
|
| 870 |
+
r"\b(file|apply|request|submit|humiling|mag.?file|magfile|kailangan|masapul|kasapulan|mangikeddeng)\b.{0,30}\b(document|dokumento|certificate|sertipiko|cert)\b"
|
| 871 |
+
r"|\b(document|dokumento|certificate|sertipiko)\b.{0,30}\b(file|apply|request|submit|kailangan|masapul|mangikeddeng)\b",
|
| 872 |
+
value,
|
| 873 |
+
))
|
| 874 |
+
if generic_doc and not document:
|
| 875 |
+
session["pending"] = "document_choice"
|
| 876 |
+
return _build("document_request", score, language, HELPER_TEXTS["choose_document"][language], session)
|
| 877 |
+
|
| 878 |
+
# Named document mentioned
|
| 879 |
+
if document and intent not in {"fees", FALLBACK}:
|
| 880 |
+
session["selected_document"] = document
|
| 881 |
+
doc_intent = DOC_TO_INTENT.get(document, intent)
|
| 882 |
+
resp = get_kb_answer(doc_intent, language)
|
| 883 |
+
return _build(doc_intent, score, language, resp, session)
|
| 884 |
+
|
| 885 |
+
# ===================================================================
|
| 886 |
+
# ANSWER FROM KNOWLEDGE BASE
|
| 887 |
+
# ===================================================================
|
| 888 |
+
if switch_lang and intent in {FALLBACK, "fallback", "language_support"}:
|
| 889 |
+
intent = "language_support"
|
| 890 |
+
elif intent == "fallback":
|
| 891 |
+
intent = FALLBACK
|
| 892 |
+
resp = get_kb_answer(intent, language)
|
| 893 |
+
return _build(intent, score, language, resp, session)
|
| 894 |
+
|
| 895 |
+
|
| 896 |
+
if __name__ == "__main__":
|
| 897 |
+
m = train()
|
| 898 |
+
total_patterns = sum(
|
| 899 |
+
len(i["patterns"])
|
| 900 |
+
for i in json.load(open(INTENTS_FILE, encoding="utf-8"))["intents"]
|
| 901 |
+
)
|
| 902 |
+
print(
|
| 903 |
+
f"BrgyLink Smart Classifier v{MODEL_VERSION} (prototype) trained -> {MODEL_FILE}\n"
|
| 904 |
+
f" Intents : {len(m['centroids'])}\n"
|
| 905 |
+
f" Patterns: {total_patterns}\n"
|
| 906 |
+
f" Features: {len(m['idf'])} unique TF-IDF keys\n"
|
| 907 |
+
f" Languages: Pangasinan, Ilocano, Tagalog, English\n"
|
| 908 |
+
f" Note: This is a prototype. Answers sourced from knowledge_base.json."
|
| 909 |
+
)
|
test_app.py
CHANGED
|
@@ -71,6 +71,7 @@ class AIServerTests(unittest.TestCase):
|
|
| 71 |
service.app.config['TESTING'] = True
|
| 72 |
service.anticheat = ReadyAntiCheat()
|
| 73 |
service.predictor = ReadyPredictor()
|
|
|
|
| 74 |
self.client = service.app.test_client()
|
| 75 |
self.headers = {'Authorization': 'Bearer test-service-token'}
|
| 76 |
|
|
@@ -160,6 +161,27 @@ class AIServerTests(unittest.TestCase):
|
|
| 160 |
self.assertEqual(response.status_code, 200)
|
| 161 |
self.assertEqual(service.anticheat.register_calls, 0)
|
| 162 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
if __name__ == '__main__':
|
| 165 |
unittest.main()
|
|
|
|
| 71 |
service.app.config['TESTING'] = True
|
| 72 |
service.anticheat = ReadyAntiCheat()
|
| 73 |
service.predictor = ReadyPredictor()
|
| 74 |
+
service.chatbot.clear_sessions()
|
| 75 |
self.client = service.app.test_client()
|
| 76 |
self.headers = {'Authorization': 'Bearer test-service-token'}
|
| 77 |
|
|
|
|
| 161 |
self.assertEqual(response.status_code, 200)
|
| 162 |
self.assertEqual(service.anticheat.register_calls, 0)
|
| 163 |
|
| 164 |
+
def test_chat_requires_service_token_and_preserves_safety_overrides(self):
|
| 165 |
+
self.assertEqual(self.client.post('/chat', json={'message': 'hello'}).status_code, 401)
|
| 166 |
+
response = self.client.post(
|
| 167 |
+
'/chat',
|
| 168 |
+
headers=self.headers,
|
| 169 |
+
json={'message': 'Speak English, my baby cannot breathe'},
|
| 170 |
+
)
|
| 171 |
+
self.assertEqual(response.status_code, 200)
|
| 172 |
+
payload = response.get_json()
|
| 173 |
+
self.assertEqual(payload['intent'], 'emergency')
|
| 174 |
+
self.assertEqual(payload['language'], 'english')
|
| 175 |
+
self.assertNotIn('details', payload)
|
| 176 |
+
|
| 177 |
+
def test_chat_rejects_invalid_payloads_without_affecting_predict(self):
|
| 178 |
+
self.assertEqual(self.client.post('/chat', headers=self.headers, json={}).status_code, 400)
|
| 179 |
+
self.assertEqual(
|
| 180 |
+
self.client.post('/chat', headers=self.headers, json={'message': 'x' * 2001}).status_code,
|
| 181 |
+
400,
|
| 182 |
+
)
|
| 183 |
+
self.assertEqual(self.post_image().status_code, 200)
|
| 184 |
+
|
| 185 |
|
| 186 |
if __name__ == '__main__':
|
| 187 |
unittest.main()
|