prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  In the DevDocNeeded model, consider bugs for which the dev-doc-needed keyword was removed as negative examples
  See also #79.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `bugbug/models/devdocneeded.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 # This Source Code Form is subject to the terms of the Mozilla Public
  3 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
  4 # You can obtain one at http://mozilla.org/MPL/2.0/.
  5 
  6 import xgboost
  7 from imblearn.under_sampling import RandomUnderSampler
  8 from sklearn.compose import ColumnTransformer
  9 from sklearn.feature_extraction import DictVectorizer
  10 from sklearn.pipeline import Pipeline
  11 
  12 from bugbug import bug_features
  13 from bugbug import bugzilla
  14 from bugbug.model import Model
  15 
  16 
  17 class DevDocNeededModel(Model):
  18     def __init__(self, lemmatization=False):
  19         Model.__init__(self, lemmatization)
  20 
  21         self.sampler = RandomUnderSampler(random_state=0)
  22 
  23         feature_extractors = [
  24             bug_features.has_str(),
  25             bug_features.has_regression_range(),
  26             bug_features.severity(),
  27             bug_features.keywords({'dev-doc-needed', 'dev-doc-complete'}),
  28             bug_features.is_coverity_issue(),
  29             bug_features.has_crash_signature(),
  30             bug_features.has_url(),
  31             bug_features.has_w3c_url(),
  32             bug_features.has_github_url(),
  33             bug_features.whiteboard(),
  34             bug_features.patches(),
  35             bug_features.landings(),
  36             bug_features.title(),
  37             bug_features.product(),
  38             bug_features.component(),
  39 
  40             bug_features.commit_added(),
  41             bug_features.commit_deleted(),
  42             bug_features.commit_types(),
  43         ]
  44 
  45         cleanup_functions = [
  46             bug_features.cleanup_fileref,
  47             bug_features.cleanup_url,
  48             bug_features.cleanup_synonyms,
  49         ]
  50 
  51         self.extraction_pipeline = Pipeline([
  52             ('bug_extractor', bug_features.BugExtractor(feature_extractors, cleanup_functions, rollback=True, rollback_when=self.rollback, commit_data=True)),
  53             ('union', ColumnTransformer([
  54                 ('data', DictVectorizer(), 'data'),
  55 
  56                 ('title', self.text_vectorizer(), 'title'),
  57 
  58                 ('comments', self.text_vectorizer(), 'comments'),
  59             ])),
  60         ])
  61 
  62         self.clf = xgboost.XGBClassifier(n_jobs=16)
  63         self.clf.set_params(predictor='cpu_predictor')
  64 
  65     def rollback(self, change):
  66         return change['field_name'] == 'keywords' and any(keyword in change['added'] for keyword in ['dev-doc-needed', 'dev-doc-complete'])
  67 
  68     def get_labels(self):
  69         classes = {}
  70 
  71         for bug_data in bugzilla.get_bugs():
  72             bug_id = int(bug_data['id'])
  73 
  74             for entry in bug_data['history']:
  75                 for change in entry['changes']:
  76                     if change['field_name'] == 'keywords' and any(keyword in change['added'] for keyword in ['dev-doc-needed', 'dev-doc-complete']):
  77                         classes[bug_id] = 1
  78 
  79             if bug_id not in classes:
  80                 classes[bug_id] = 0
  81 
  82         return classes
  83 
  84     def get_feature_names(self):
  85         return self.extraction_pipeline.named_steps['union'].get_feature_names()
  86
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/bugbug/models/devdocneeded.py b/bugbug/models/devdocneeded.py
  --- a/bugbug/models/devdocneeded.py
  +++ b/bugbug/models/devdocneeded.py
  @@ -73,7 +73,14 @@
   
               for entry in bug_data['history']:
                   for change in entry['changes']:
  -                    if change['field_name'] == 'keywords' and any(keyword in change['added'] for keyword in ['dev-doc-needed', 'dev-doc-complete']):
  +                    # Bugs that get dev-doc-needed removed from them at some point after it's been added (this suggests a false positive among human-analyzed bugs)
  +                    if change['field_name'] == 'keywords' and 'dev-doc-needed' in change['removed'] and 'dev-doc-complete' not in change['added']:
  +                        classes[bug_id] = 0
  +                    # Bugs that go from dev-doc-needed to dev-doc-complete are guaranteed to be good
  +                    # Bugs that go from not having dev-doc-needed to having dev-doc-complete are bugs
  +                    # that were missed by previous scans through content but someone realized it
  +                    # should have been flagged and updated the docs, found the docs already updated.
  +                    elif change['field_name'] == 'keywords' and any(keyword in change['added'] for keyword in ['dev-doc-needed', 'dev-doc-complete']):
                           classes[bug_id] = 1
   
               if bug_id not in classes:
  
verification_info: {"golden_diff": "diff --git a/bugbug/models/devdocneeded.py b/bugbug/models/devdocneeded.py\n--- a/bugbug/models/devdocneeded.py\n+++ b/bugbug/models/devdocneeded.py\n@@ -73,7 +73,14 @@\n \n             for entry in bug_data['history']:\n                 for change in entry['changes']:\n-                    if change['field_name'] == 'keywords' and any(keyword in change['added'] for keyword in ['dev-doc-needed', 'dev-doc-complete']):\n+                    # Bugs that get dev-doc-needed removed from them at some point after it's been added (this suggests a false positive among human-analyzed bugs)\n+                    if change['field_name'] == 'keywords' and 'dev-doc-needed' in change['removed'] and 'dev-doc-complete' not in change['added']:\n+                        classes[bug_id] = 0\n+                    # Bugs that go from dev-doc-needed to dev-doc-complete are guaranteed to be good\n+                    # Bugs that go from not having dev-doc-needed to having dev-doc-complete are bugs\n+                    # that were missed by previous scans through content but someone realized it\n+                    # should have been flagged and updated the docs, found the docs already updated.\n+                    elif change['field_name'] == 'keywords' and any(keyword in change['added'] for keyword in ['dev-doc-needed', 'dev-doc-complete']):\n                         classes[bug_id] = 1\n \n             if bug_id not in classes:\n", "issue": "In the DevDocNeeded model, consider bugs for which the dev-doc-needed keyword was removed as negative examples\nSee also #79.\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not distributed with this file,\n# You can obtain one at http://mozilla.org/MPL/2.0/.\n\nimport xgboost\nfrom imblearn.under_sampling import RandomUnderSampler\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.feature_extraction import DictVectorizer\nfrom sklearn.pipeline import Pipeline\n\nfrom bugbug import bug_features\nfrom bugbug import bugzilla\nfrom bugbug.model import Model\n\n\nclass DevDocNeededModel(Model):\n    def __init__(self, lemmatization=False):\n        Model.__init__(self, lemmatization)\n\n        self.sampler = RandomUnderSampler(random_state=0)\n\n        feature_extractors = [\n            bug_features.has_str(),\n            bug_features.has_regression_range(),\n            bug_features.severity(),\n            bug_features.keywords({'dev-doc-needed', 'dev-doc-complete'}),\n            bug_features.is_coverity_issue(),\n            bug_features.has_crash_signature(),\n            bug_features.has_url(),\n            bug_features.has_w3c_url(),\n            bug_features.has_github_url(),\n            bug_features.whiteboard(),\n            bug_features.patches(),\n            bug_features.landings(),\n            bug_features.title(),\n            bug_features.product(),\n            bug_features.component(),\n\n            bug_features.commit_added(),\n            bug_features.commit_deleted(),\n            bug_features.commit_types(),\n        ]\n\n        cleanup_functions = [\n            bug_features.cleanup_fileref,\n            bug_features.cleanup_url,\n            bug_features.cleanup_synonyms,\n        ]\n\n        self.extraction_pipeline = Pipeline([\n            ('bug_extractor', bug_features.BugExtractor(feature_extractors, cleanup_functions, rollback=True, rollback_when=self.rollback, commit_data=True)),\n            ('union', ColumnTransformer([\n                ('data', DictVectorizer(), 'data'),\n\n                ('title', self.text_vectorizer(), 'title'),\n\n                ('comments', self.text_vectorizer(), 'comments'),\n            ])),\n        ])\n\n        self.clf = xgboost.XGBClassifier(n_jobs=16)\n        self.clf.set_params(predictor='cpu_predictor')\n\n    def rollback(self, change):\n        return change['field_name'] == 'keywords' and any(keyword in change['added'] for keyword in ['dev-doc-needed', 'dev-doc-complete'])\n\n    def get_labels(self):\n        classes = {}\n\n        for bug_data in bugzilla.get_bugs():\n            bug_id = int(bug_data['id'])\n\n            for entry in bug_data['history']:\n                for change in entry['changes']:\n                    if change['field_name'] == 'keywords' and any(keyword in change['added'] for keyword in ['dev-doc-needed', 'dev-doc-complete']):\n                        classes[bug_id] = 1\n\n            if bug_id not in classes:\n                classes[bug_id] = 0\n\n        return classes\n\n    def get_feature_names(self):\n        return self.extraction_pipeline.named_steps['union'].get_feature_names()\n", "path": "bugbug/models/devdocneeded.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not distributed with this file,\n# You can obtain one at http://mozilla.org/MPL/2.0/.\n\nimport xgboost\nfrom imblearn.under_sampling import RandomUnderSampler\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.feature_extraction import DictVectorizer\nfrom sklearn.pipeline import Pipeline\n\nfrom bugbug import bug_features\nfrom bugbug import bugzilla\nfrom bugbug.model import Model\n\n\nclass DevDocNeededModel(Model):\n    def __init__(self, lemmatization=False):\n        Model.__init__(self, lemmatization)\n\n        self.sampler = RandomUnderSampler(random_state=0)\n\n        feature_extractors = [\n            bug_features.has_str(),\n            bug_features.has_regression_range(),\n            bug_features.severity(),\n            bug_features.keywords({'dev-doc-needed', 'dev-doc-complete'}),\n            bug_features.is_coverity_issue(),\n            bug_features.has_crash_signature(),\n            bug_features.has_url(),\n            bug_features.has_w3c_url(),\n            bug_features.has_github_url(),\n            bug_features.whiteboard(),\n            bug_features.patches(),\n            bug_features.landings(),\n            bug_features.title(),\n            bug_features.product(),\n            bug_features.component(),\n\n            bug_features.commit_added(),\n            bug_features.commit_deleted(),\n            bug_features.commit_types(),\n        ]\n\n        cleanup_functions = [\n            bug_features.cleanup_fileref,\n            bug_features.cleanup_url,\n            bug_features.cleanup_synonyms,\n        ]\n\n        self.extraction_pipeline = Pipeline([\n            ('bug_extractor', bug_features.BugExtractor(feature_extractors, cleanup_functions, rollback=True, rollback_when=self.rollback, commit_data=True)),\n            ('union', ColumnTransformer([\n                ('data', DictVectorizer(), 'data'),\n\n                ('title', self.text_vectorizer(), 'title'),\n\n                ('comments', self.text_vectorizer(), 'comments'),\n            ])),\n        ])\n\n        self.clf = xgboost.XGBClassifier(n_jobs=16)\n        self.clf.set_params(predictor='cpu_predictor')\n\n    def rollback(self, change):\n        return change['field_name'] == 'keywords' and any(keyword in change['added'] for keyword in ['dev-doc-needed', 'dev-doc-complete'])\n\n    def get_labels(self):\n        classes = {}\n\n        for bug_data in bugzilla.get_bugs():\n            bug_id = int(bug_data['id'])\n\n            for entry in bug_data['history']:\n                for change in entry['changes']:\n                    # Bugs that get dev-doc-needed removed from them at some point after it's been added (this suggests a false positive among human-analyzed bugs)\n                    if change['field_name'] == 'keywords' and 'dev-doc-needed' in change['removed'] and 'dev-doc-complete' not in change['added']:\n                        classes[bug_id] = 0\n                    # Bugs that go from dev-doc-needed to dev-doc-complete are guaranteed to be good\n                    # Bugs that go from not having dev-doc-needed to having dev-doc-complete are bugs\n                    # that were missed by previous scans through content but someone realized it\n                    # should have been flagged and updated the docs, found the docs already updated.\n                    elif change['field_name'] == 'keywords' and any(keyword in change['added'] for keyword in ['dev-doc-needed', 'dev-doc-complete']):\n                        classes[bug_id] = 1\n\n            if bug_id not in classes:\n                classes[bug_id] = 0\n\n        return classes\n\n    def get_feature_names(self):\n        return self.extraction_pipeline.named_steps['union'].get_feature_names()\n", "path": "bugbug/models/devdocneeded.py"}]}