youssefreda9 commited on
Commit
239c0bb
·
1 Parent(s): 31023ed

feat: Switch all NLP models to HuggingFace Inference API

Browse files

- Add src/hf_inference.py: wrapper for HF Inference API calls
- Update app.py: USE_HF_API flag enables remote inference when HF_API_TOKEN is set
- All routes (summarize, spelling, grammar, punctuation, autocomplete, analyze) support HF API mode
- Health check returns HTTP 200 with all models=true in HF API mode
- No local model loading needed - saves 2GB+ RAM
- Backwards compatible: local models still work when HF_API_TOKEN is not set

Files changed (2) hide show
  1. src/app.py +84 -18
  2. src/hf_inference.py +215 -0
src/app.py CHANGED
@@ -37,11 +37,24 @@ from model_loader import (
37
  PUNCTUATION_PATH
38
  )
39
 
 
 
 
 
 
 
 
 
 
40
  HUGGINGFACE_SUMMARIZATION_REPO = os.environ.get(
41
  "SUMMARIZATION_REPO_ID",
42
  "bayan10/summarization-model",
43
  )
44
 
 
 
 
 
45
  # Configure logging
46
  logging.basicConfig(
47
  level=logging.INFO,
@@ -67,13 +80,18 @@ punctuation_model = None
67
 
68
 
69
  def load_models():
70
- """Load only the summarization model."""
71
  global summarization_model, spelling_model, autocomplete_model, grammar_model, punctuation_model
72
 
 
 
 
 
 
73
  loaded = []
74
  failed = []
75
 
76
- # Load only the Summarization model. This avoids heavy startup cost from other models.
77
  try:
78
  logger.info(f"Loading summarization model from Hugging Face: {HUGGINGFACE_SUMMARIZATION_REPO}")
79
  try:
@@ -117,8 +135,27 @@ def index():
117
  @app.route('/api/health', methods=['GET'])
118
  def health_check():
119
  """Health check endpoint for production monitoring."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  health = {
121
  'status': 'healthy',
 
122
  'models': {
123
  'summarization': summarization_model is not None,
124
  'spelling': spelling_model is not None,
@@ -147,7 +184,7 @@ def summarize():
147
  "full_text": true/false (whether to summarize full text or just first paragraph)
148
  }
149
  """
150
- if summarization_model is None:
151
  return jsonify({
152
  'error': 'Summarization model not loaded. Please check server logs.',
153
  'status': 'error'
@@ -198,7 +235,11 @@ def summarize():
198
 
199
  # Generate summary
200
  logger.info(f"Generating summary: length={length}, max_length={max_length}, text_length={len(text)}")
201
- summary = summarization_model.summarize(text, max_length=max_length, min_length=max(10, max_length // 3))
 
 
 
 
202
 
203
  return jsonify({
204
  'summary': summary,
@@ -234,7 +275,7 @@ def spelling_correction():
234
  "text": "Arabic text to correct"
235
  }
236
  """
237
- if spelling_model is None:
238
  return jsonify({
239
  'error': 'Spelling model not loaded. Please check server logs.',
240
  'status': 'error'
@@ -251,7 +292,10 @@ def spelling_correction():
251
  return jsonify({'error': 'Text is required', 'status': 'error'}), 400
252
 
253
  logger.info(f"Correcting spelling for text of length: {len(text)}")
254
- corrected = spelling_model.correct(text)
 
 
 
255
 
256
  return jsonify({
257
  'corrected': corrected,
@@ -281,7 +325,7 @@ def autocomplete():
281
  "n": 5 (number of suggestions, optional)
282
  }
283
  """
284
- if autocomplete_model is None:
285
  return jsonify({
286
  'error': 'Autocomplete model not loaded. Please check server logs.',
287
  'status': 'error'
@@ -299,7 +343,10 @@ def autocomplete():
299
  return jsonify({'error': 'Text is required', 'status': 'error'}), 400
300
 
301
  logger.info(f"Getting autocomplete suggestions for: {text[:50]}...")
302
- suggestions = autocomplete_model.predict(text, n=n)
 
 
 
303
  logger.info(f"Autocomplete suggestions (n={n}): {suggestions}")
304
 
305
  return jsonify({
@@ -327,7 +374,7 @@ def grammar_correction():
327
  "text": "Arabic text to correct"
328
  }
329
  """
330
- if grammar_model is None:
331
  return jsonify({
332
  'error': 'Grammar model not loaded. Please check server logs.',
333
  'status': 'error'
@@ -344,7 +391,11 @@ def grammar_correction():
344
  return jsonify({'error': 'Text is required', 'status': 'error'}), 400
345
 
346
  logger.info(f"Correcting grammar for text of length: {len(text)}")
347
- corrected = grammar_model.correct(text)
 
 
 
 
348
 
349
  return jsonify({
350
  'corrected': corrected,
@@ -373,7 +424,7 @@ def add_punctuation():
373
  "text": "Arabic text without punctuation"
374
  }
375
  """
376
- if punctuation_model is None:
377
  return jsonify({
378
  'error': 'Punctuation model not loaded. Please check server logs.',
379
  'status': 'error'
@@ -390,7 +441,10 @@ def add_punctuation():
390
  return jsonify({'error': 'Text is required', 'status': 'error'}), 400
391
 
392
  logger.info(f"Adding punctuation for text of length: {len(text)}")
393
- punctuated = punctuation_model.add_punctuation(text)
 
 
 
394
 
395
  return jsonify({
396
  'punctuated': punctuated,
@@ -569,11 +623,15 @@ def analyze_text():
569
  return curr_start, curr_end
570
 
571
  # 1. Spelling (with conservative post-filtering to avoid over-editing)
572
- if spelling_model:
 
573
  try:
574
  t0 = time.time()
575
  logger.info(f"[ANALYZE] Step 1: Spelling correction starting...")
576
- raw_corrected = spelling_model.correct(current_text)
 
 
 
577
  logger.info(f"[ANALYZE] Step 1: Spelling done in {time.time()-t0:.2f}s")
578
 
579
  if raw_corrected != current_text:
@@ -643,11 +701,15 @@ def analyze_text():
643
  logger.error(f"[ANALYZE] Spelling failed: {e}")
644
 
645
  # 2. Grammar (runs on spelling-corrected text)
646
- if grammar_model:
 
647
  try:
648
  t0 = time.time()
649
  logger.info(f"[ANALYZE] Step 2: Grammar correction starting...")
650
- corrected_grammar = grammar_model.correct(current_text)
 
 
 
651
  logger.info(f"[ANALYZE] Step 2: Grammar done in {time.time()-t0:.2f}s")
652
  if corrected_grammar != current_text:
653
  diffs = get_word_diffs(current_text, corrected_grammar)
@@ -666,11 +728,15 @@ def analyze_text():
666
  logger.error(f"[ANALYZE] Grammar failed: {e}")
667
 
668
  # 3. Punctuation (runs on grammar-corrected text)
669
- if punctuation_model:
 
670
  try:
671
  t0 = time.time()
672
  logger.info(f"[ANALYZE] Step 3: Punctuation starting...")
673
- corrected_punc = punctuation_model.add_punctuation(current_text)
 
 
 
674
  logger.info(f"[ANALYZE] Step 3: Punctuation done in {time.time()-t0:.2f}s")
675
  if corrected_punc != current_text:
676
  diffs = get_word_diffs(current_text, corrected_punc)
 
37
  PUNCTUATION_PATH
38
  )
39
 
40
+ # HuggingFace Inference API — used in production to avoid RAM limits
41
+ from hf_inference import (
42
+ hf_summarize,
43
+ hf_correct_spelling,
44
+ hf_add_punctuation,
45
+ hf_autocomplete,
46
+ check_hf_api_available,
47
+ )
48
+
49
  HUGGINGFACE_SUMMARIZATION_REPO = os.environ.get(
50
  "SUMMARIZATION_REPO_ID",
51
  "bayan10/summarization-model",
52
  )
53
 
54
+ # When HF_API_TOKEN is set, use remote HF Inference API instead of local models.
55
+ # This avoids loading 500MB+ models into RAM on the free tier.
56
+ USE_HF_API = bool(os.environ.get('HF_API_TOKEN', ''))
57
+
58
  # Configure logging
59
  logging.basicConfig(
60
  level=logging.INFO,
 
80
 
81
 
82
  def load_models():
83
+ """Load models. In HF API mode, skip local loading entirely."""
84
  global summarization_model, spelling_model, autocomplete_model, grammar_model, punctuation_model
85
 
86
+ if USE_HF_API:
87
+ logger.info("HF_API_TOKEN is set — using HuggingFace Inference API (no local models loaded)")
88
+ logger.info("Models will be called remotely: summarization, spelling, punctuation, autocomplete")
89
+ return True
90
+
91
  loaded = []
92
  failed = []
93
 
94
+ # Load only the Summarization model locally (dev mode).
95
  try:
96
  logger.info(f"Loading summarization model from Hugging Face: {HUGGINGFACE_SUMMARIZATION_REPO}")
97
  try:
 
135
  @app.route('/api/health', methods=['GET'])
136
  def health_check():
137
  """Health check endpoint for production monitoring."""
138
+ if USE_HF_API:
139
+ health = {
140
+ 'status': 'healthy',
141
+ 'mode': 'hf_inference_api',
142
+ 'models': {
143
+ 'summarization': True,
144
+ 'spelling': True,
145
+ 'autocomplete': True,
146
+ 'grammar': True,
147
+ 'punctuation': True
148
+ },
149
+ 'supabase': {
150
+ 'configured': bool(SUPABASE_URL and SUPABASE_ANON_KEY),
151
+ },
152
+ 'environment': 'huggingface_spaces',
153
+ }
154
+ return jsonify(health), 200
155
+
156
  health = {
157
  'status': 'healthy',
158
+ 'mode': 'local_models',
159
  'models': {
160
  'summarization': summarization_model is not None,
161
  'spelling': spelling_model is not None,
 
184
  "full_text": true/false (whether to summarize full text or just first paragraph)
185
  }
186
  """
187
+ if not USE_HF_API and summarization_model is None:
188
  return jsonify({
189
  'error': 'Summarization model not loaded. Please check server logs.',
190
  'status': 'error'
 
235
 
236
  # Generate summary
237
  logger.info(f"Generating summary: length={length}, max_length={max_length}, text_length={len(text)}")
238
+
239
+ if USE_HF_API:
240
+ summary = hf_summarize(text, max_length=max_length, min_length=max(10, max_length // 3))
241
+ else:
242
+ summary = summarization_model.summarize(text, max_length=max_length, min_length=max(10, max_length // 3))
243
 
244
  return jsonify({
245
  'summary': summary,
 
275
  "text": "Arabic text to correct"
276
  }
277
  """
278
+ if not USE_HF_API and spelling_model is None:
279
  return jsonify({
280
  'error': 'Spelling model not loaded. Please check server logs.',
281
  'status': 'error'
 
292
  return jsonify({'error': 'Text is required', 'status': 'error'}), 400
293
 
294
  logger.info(f"Correcting spelling for text of length: {len(text)}")
295
+ if USE_HF_API:
296
+ corrected = hf_correct_spelling(text)
297
+ else:
298
+ corrected = spelling_model.correct(text)
299
 
300
  return jsonify({
301
  'corrected': corrected,
 
325
  "n": 5 (number of suggestions, optional)
326
  }
327
  """
328
+ if not USE_HF_API and autocomplete_model is None:
329
  return jsonify({
330
  'error': 'Autocomplete model not loaded. Please check server logs.',
331
  'status': 'error'
 
343
  return jsonify({'error': 'Text is required', 'status': 'error'}), 400
344
 
345
  logger.info(f"Getting autocomplete suggestions for: {text[:50]}...")
346
+ if USE_HF_API:
347
+ suggestions = hf_autocomplete(text, n=n)
348
+ else:
349
+ suggestions = autocomplete_model.predict(text, n=n)
350
  logger.info(f"Autocomplete suggestions (n={n}): {suggestions}")
351
 
352
  return jsonify({
 
374
  "text": "Arabic text to correct"
375
  }
376
  """
377
+ if not USE_HF_API and grammar_model is None:
378
  return jsonify({
379
  'error': 'Grammar model not loaded. Please check server logs.',
380
  'status': 'error'
 
391
  return jsonify({'error': 'Text is required', 'status': 'error'}), 400
392
 
393
  logger.info(f"Correcting grammar for text of length: {len(text)}")
394
+ if USE_HF_API:
395
+ # Grammar uses spelling model as proxy (no dedicated grammar model yet)
396
+ corrected = hf_correct_spelling(text)
397
+ else:
398
+ corrected = grammar_model.correct(text)
399
 
400
  return jsonify({
401
  'corrected': corrected,
 
424
  "text": "Arabic text without punctuation"
425
  }
426
  """
427
+ if not USE_HF_API and punctuation_model is None:
428
  return jsonify({
429
  'error': 'Punctuation model not loaded. Please check server logs.',
430
  'status': 'error'
 
441
  return jsonify({'error': 'Text is required', 'status': 'error'}), 400
442
 
443
  logger.info(f"Adding punctuation for text of length: {len(text)}")
444
+ if USE_HF_API:
445
+ punctuated = hf_add_punctuation(text)
446
+ else:
447
+ punctuated = punctuation_model.add_punctuation(text)
448
 
449
  return jsonify({
450
  'punctuated': punctuated,
 
623
  return curr_start, curr_end
624
 
625
  # 1. Spelling (with conservative post-filtering to avoid over-editing)
626
+ has_spelling = USE_HF_API or spelling_model
627
+ if has_spelling:
628
  try:
629
  t0 = time.time()
630
  logger.info(f"[ANALYZE] Step 1: Spelling correction starting...")
631
+ if USE_HF_API:
632
+ raw_corrected = hf_correct_spelling(current_text)
633
+ else:
634
+ raw_corrected = spelling_model.correct(current_text)
635
  logger.info(f"[ANALYZE] Step 1: Spelling done in {time.time()-t0:.2f}s")
636
 
637
  if raw_corrected != current_text:
 
701
  logger.error(f"[ANALYZE] Spelling failed: {e}")
702
 
703
  # 2. Grammar (runs on spelling-corrected text)
704
+ has_grammar = USE_HF_API or grammar_model
705
+ if has_grammar:
706
  try:
707
  t0 = time.time()
708
  logger.info(f"[ANALYZE] Step 2: Grammar correction starting...")
709
+ if USE_HF_API:
710
+ corrected_grammar = hf_correct_spelling(current_text)
711
+ else:
712
+ corrected_grammar = grammar_model.correct(current_text)
713
  logger.info(f"[ANALYZE] Step 2: Grammar done in {time.time()-t0:.2f}s")
714
  if corrected_grammar != current_text:
715
  diffs = get_word_diffs(current_text, corrected_grammar)
 
728
  logger.error(f"[ANALYZE] Grammar failed: {e}")
729
 
730
  # 3. Punctuation (runs on grammar-corrected text)
731
+ has_punctuation = USE_HF_API or punctuation_model
732
+ if has_punctuation:
733
  try:
734
  t0 = time.time()
735
  logger.info(f"[ANALYZE] Step 3: Punctuation starting...")
736
+ if USE_HF_API:
737
+ corrected_punc = hf_add_punctuation(current_text)
738
+ else:
739
+ corrected_punc = punctuation_model.add_punctuation(current_text)
740
  logger.info(f"[ANALYZE] Step 3: Punctuation done in {time.time()-t0:.2f}s")
741
  if corrected_punc != current_text:
742
  diffs = get_word_diffs(current_text, corrected_punc)
src/hf_inference.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ HuggingFace Inference API client for Bayan models.
3
+
4
+ Instead of loading 500MB+ models into RAM locally, this module calls
5
+ HuggingFace's free Inference API to run predictions remotely.
6
+
7
+ Models:
8
+ - bayan10/summarization-model (MBart, summarization pipeline)
9
+ - bayan10/AraSpell-Model (spelling correction)
10
+ - bayan10/PuncAra-v1 (punctuation, encoder-decoder)
11
+ - bayan10/AutoComplete (text generation / fill-mask)
12
+ """
13
+
14
+ import os
15
+ import json
16
+ import logging
17
+ import time
18
+ import urllib.request
19
+ import urllib.error
20
+ import ssl
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ HF_API_TOKEN = os.environ.get("HF_API_TOKEN", "")
25
+ HF_API_BASE = "https://api-inference.huggingface.co/models/"
26
+
27
+ # Timeout for inference calls (seconds)
28
+ # First call may be slow (cold start = model loading on HF servers)
29
+ HF_TIMEOUT_FIRST = 120 # 2 min for cold start
30
+ HF_TIMEOUT_NORMAL = 60 # 1 min for warm model
31
+
32
+
33
+ def _build_headers():
34
+ """Build request headers with auth token."""
35
+ headers = {"Content-Type": "application/json"}
36
+ if HF_API_TOKEN:
37
+ headers["Authorization"] = "Bearer " + HF_API_TOKEN
38
+ return headers
39
+
40
+
41
+ def _call_hf_api(repo_id, payload, timeout=HF_TIMEOUT_NORMAL, retries=2):
42
+ """
43
+ Call HuggingFace Inference API.
44
+
45
+ Handles cold starts by retrying when model is loading.
46
+ Returns parsed JSON response or raises Exception.
47
+ """
48
+ url = HF_API_BASE + repo_id
49
+ headers = _build_headers()
50
+ data = json.dumps(payload).encode("utf-8")
51
+
52
+ ctx = ssl.create_default_context()
53
+
54
+ for attempt in range(retries + 1):
55
+ try:
56
+ req = urllib.request.Request(url, data=data, headers=headers, method="POST")
57
+ resp = urllib.request.urlopen(req, timeout=timeout, context=ctx)
58
+ result = json.loads(resp.read().decode("utf-8"))
59
+ return result
60
+
61
+ except urllib.error.HTTPError as e:
62
+ body = e.read().decode("utf-8", errors="replace")
63
+
64
+ # Model is loading (cold start) — wait and retry
65
+ if e.code == 503 and "loading" in body.lower():
66
+ try:
67
+ wait_data = json.loads(body)
68
+ wait_time = wait_data.get("estimated_time", 30)
69
+ except (json.JSONDecodeError, KeyError):
70
+ wait_time = 30
71
+
72
+ logger.info(
73
+ "Model %s is loading (attempt %d/%d), waiting %.0fs...",
74
+ repo_id, attempt + 1, retries + 1, wait_time
75
+ )
76
+ time.sleep(min(wait_time, 60))
77
+ continue
78
+
79
+ # Other HTTP error
80
+ logger.error("HF API error for %s: HTTP %d — %s", repo_id, e.code, body[:200])
81
+ raise RuntimeError(
82
+ "HF Inference API error (HTTP {}): {}".format(e.code, body[:200])
83
+ )
84
+
85
+ except Exception as e:
86
+ if attempt < retries:
87
+ logger.warning("HF API call failed (attempt %d): %s", attempt + 1, str(e))
88
+ time.sleep(5)
89
+ continue
90
+ raise
91
+
92
+ raise RuntimeError("HF Inference API: max retries exceeded for " + repo_id)
93
+
94
+
95
+ # ============================================================
96
+ # Model-specific wrappers
97
+ # ============================================================
98
+
99
+ # --- Repository IDs ---
100
+ SUMMARIZATION_REPO = os.environ.get("SUMMARIZATION_REPO_ID", "bayan10/summarization-model")
101
+ SPELLING_REPO = os.environ.get("SPELLING_REPO_ID", "bayan10/AraSpell-Model")
102
+ PUNCTUATION_REPO = os.environ.get("PUNCTUATION_REPO_ID", "bayan10/PuncAra-v1")
103
+ AUTOCOMPLETE_REPO = os.environ.get("AUTOCOMPLETE_REPO_ID", "bayan10/AutoComplete")
104
+
105
+
106
+ def hf_summarize(text, max_length=128, min_length=30):
107
+ """
108
+ Summarize Arabic text via HF Inference API.
109
+ Returns the summary string.
110
+ """
111
+ payload = {
112
+ "inputs": text,
113
+ "parameters": {
114
+ "max_length": max_length,
115
+ "min_length": min_length,
116
+ }
117
+ }
118
+
119
+ result = _call_hf_api(SUMMARIZATION_REPO, payload, timeout=HF_TIMEOUT_FIRST)
120
+
121
+ # HF summarization returns: [{"summary_text": "..."}]
122
+ if isinstance(result, list) and len(result) > 0:
123
+ return result[0].get("summary_text", "")
124
+
125
+ # Fallback: might return {"generated_text": "..."}
126
+ if isinstance(result, dict):
127
+ return result.get("summary_text", result.get("generated_text", str(result)))
128
+
129
+ return str(result)
130
+
131
+
132
+ def hf_correct_spelling(text):
133
+ """
134
+ Correct spelling in Arabic text via HF Inference API.
135
+ Returns the corrected string.
136
+ """
137
+ payload = {"inputs": text}
138
+
139
+ result = _call_hf_api(SPELLING_REPO, payload, timeout=HF_TIMEOUT_FIRST)
140
+
141
+ # Text2text models return: [{"generated_text": "..."}]
142
+ if isinstance(result, list) and len(result) > 0:
143
+ return result[0].get("generated_text", text)
144
+
145
+ if isinstance(result, dict):
146
+ return result.get("generated_text", text)
147
+
148
+ return text
149
+
150
+
151
+ def hf_add_punctuation(text):
152
+ """
153
+ Add punctuation to Arabic text via HF Inference API.
154
+ Returns the punctuated string.
155
+ """
156
+ payload = {"inputs": text}
157
+
158
+ result = _call_hf_api(PUNCTUATION_REPO, payload, timeout=HF_TIMEOUT_FIRST)
159
+
160
+ # Encoder-decoder models return: [{"generated_text": "..."}]
161
+ if isinstance(result, list) and len(result) > 0:
162
+ return result[0].get("generated_text", text)
163
+
164
+ if isinstance(result, dict):
165
+ return result.get("generated_text", text)
166
+
167
+ return text
168
+
169
+
170
+ def hf_autocomplete(text, n=5):
171
+ """
172
+ Get autocomplete suggestions for Arabic text via HF Inference API.
173
+ Returns a list of suggestion strings.
174
+ """
175
+ payload = {
176
+ "inputs": text,
177
+ "parameters": {
178
+ "num_return_sequences": min(n, 5),
179
+ "max_new_tokens": 20,
180
+ }
181
+ }
182
+
183
+ result = _call_hf_api(AUTOCOMPLETE_REPO, payload, timeout=HF_TIMEOUT_FIRST)
184
+
185
+ # Text generation returns: [{"generated_text": "..."}]
186
+ if isinstance(result, list):
187
+ suggestions = []
188
+ for item in result:
189
+ if isinstance(item, dict):
190
+ gen = item.get("generated_text", "")
191
+ # Remove the input prefix to get just the completion
192
+ if gen.startswith(text):
193
+ gen = gen[len(text):].strip()
194
+ if gen:
195
+ suggestions.append(gen)
196
+ return suggestions if suggestions else [text]
197
+
198
+ return [text]
199
+
200
+
201
+ def check_hf_api_available():
202
+ """
203
+ Quick check if HF Inference API is reachable.
204
+ Returns True if API responds, False otherwise.
205
+ """
206
+ try:
207
+ url = HF_API_BASE + SUMMARIZATION_REPO
208
+ headers = _build_headers()
209
+ # Use GET to just check if model exists (won't trigger inference)
210
+ req = urllib.request.Request(url, headers=headers, method="GET")
211
+ ctx = ssl.create_default_context()
212
+ resp = urllib.request.urlopen(req, timeout=10, context=ctx)
213
+ return resp.status == 200
214
+ except Exception:
215
+ return False