Repository: h1alexbel/samples-filter (language: python)

Issue:
Investigate learning algorithm for real/sample repo classification
Let's investigate learning algorithm and process for our classification task. This step is crucial to gather fundemental understanding what is the process of learning and how classification will work inside the algorithm.


Discussion / hints:
Some of these algorithms I found so far:
* Logistic Regression: Simple and effective for binary classification tasks.
* Random Forest: An ensemble method that combines multiple decision trees.
* K-Nearest Neighbours (KNN)
Let's investigate the _fundamental difference_ between **LR**, **RF** and **KNN** algorithms, and what is the best option for our use-case
@h1alexbel 2 puzzles [#81](https://github.com/h1alexbel/samples-filter/issues/81), [#82](https://github.com/h1alexbel/samples-filter/issues/82) are still not solved.
I performed a small comparison on traditional ML classification algorithms vs. HuggingFace Transformers used for text-classification. The key difference to my knowledge, is in the _data format_ that we should provide to the model during the training stage. For instance, for RF (Random Forest) we can provide these information:

* `full_name`
* `description`
* `created_at`
* `last_commit`
* `readme`
* `label`

model, with the help of RF learning algorithm, will build the trees based on these data and marked `label` value for each that row. To me, it's more flexible approach for our case, since we can use **not only text data**, but numeric and other data too.

When it comes to the text transformers like HuggingFace, they accept **only text** and can reason about text data. In our case it's a `readme`. So dataset we would provide to such model will look like this:

* `readme`
* `label`

model, using [deep learning](https://en.wikipedia.org/wiki/Deep_learning) and [self-attention mechanisms](https://arxiv.org/pdf/1706.03762) would learn the textfield READMEs together with their 
marked labels. This approach is quite powerful, more resource-needed and to me looks less flexible. Why? In the case of 
text transformers we can *classify* GitHub repositories in `SAMPLE` or `REAL` only using it's README. However, some of the repositories don't have README. So, they will be skipped or suboptimal analyzed. The solution I see, is too put more generic data about GitHub repository in this `text` for analysis. Let's say, we scrape the GitHub repository page, for instance `yegor256/takes` and gather all required information for to the model (similarly to the data format that used for RF algorithm). Now, we can present this data to the model in [prompt way](https://en.wikipedia.org/wiki/Prompt_engineering) (like we do with ChatGPT and similar models), and it should give us the prediction.
RQ2: What is the more performant in classification way? Machine Learning RF algorithm vs. transformers
@h1alexbel 5 puzzles [#81](https://github.com/h1alexbel/samples-filter/issues/81), [#82](https://github.com/h1alexbel/samples-filter/issues/82), [#88](https://github.com/h1alexbel/samples-filter/issues/88), [#89](https://github.com/h1alexbel/samples-filter/issues/89), [#90](https://github.com/h1alexbel/samples-filter/issues/90) are still not solved.
@h1alexbel 4 puzzles [#81](https://github.com/h1alexbel/samples-filter/issues/81), [#82](https://github.com/h1alexbel/samples-filter/issues/82), [#89](https://github.com/h1alexbel/samples-filter/issues/89), [#90](https://github.com/h1alexbel/samples-filter/issues/90) are still not solved; solved: [#88](https://github.com/h1alexbel/samples-filter/issues/88).
@h1alexbel 2 puzzles [#81](https://github.com/h1alexbel/samples-filter/issues/81), [#82](https://github.com/h1alexbel/samples-filter/issues/82) are still not solved; solved: [#88](https://github.com/h1alexbel/samples-filter/issues/88), [#89](https://github.com/h1alexbel/samples-filter/issues/89), [#90](https://github.com/h1alexbel/samples-filter/issues/90).

Fix patch:
diff --git a/model/README.md b/model/README.md
index 0b67ca2..d965996 100644
--- a/model/README.md
+++ b/model/README.md
@@ -18,14 +18,10 @@ You can use these models for repository classification:
 In order to use trained model with [Random-Forest] learning algorithm:
 
 ```python
-from joblib import load
+from src.rf_model import RfModel
 
-model = load('rf-classifier.joblib')
-vectorizer = load("rf-vec.joblib")
-input = "<input repository>"
-prediction = model.predict(vectorizer.transform([input]))
-
-print(prediction[0]) # 0 or 1
+prediction = RfModel().predict("<input here>")
+print(prediction) # 0 or 1
 ```
 
 `0` means that repository is real, while `1` means that repository is sample.
diff --git a/src/remote_asset.py b/src/remote_asset.py
new file mode 100644
index 0000000..18917e9
--- /dev/null
+++ b/src/remote_asset.py
@@ -0,0 +1,44 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2024 Aliaksei Bialiauski
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import requests
+
+"""
+Dump remote asset from GitHub branch locally.
+"""
+
+
+class RemoteAsset:
+    def __init__(self, ref):
+        self.ref = ref
+
+    def dump(self):
+        print(f"Downloading {self.ref} ...")
+        response = requests.get(
+            f"https://raw.githubusercontent.com/h1alexbel/samples-filter/random-forest/{self.ref}"
+        )
+        if response.status_code == 200:
+            with open(f"{self.ref}", "wb") as vfile:
+                print(f"Dumping {self.ref} locally...")
+                vfile.write(response.content)
+                return vfile.name
+        else:
+            raise FileNotFoundError(f"Failed to fetch ref: {self.ref}")
diff --git a/src/rf_model.py b/src/rf_model.py
new file mode 100644
index 0000000..55037bf
--- /dev/null
+++ b/src/rf_model.py
@@ -0,0 +1,37 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2024 Aliaksei Bialiauski
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import joblib
+
+from .remote_asset import RemoteAsset
+
+"""
+Random Forest Model.
+"""
+
+
+class RfModel:
+    def __init__(self, vec="rf-vec.joblib", ref="rf-classifier.joblib"):
+        self.vectorizer = joblib.load(RemoteAsset(vec).dump())
+        self.model = joblib.load(RemoteAsset(ref).dump())
+
+    def predict(self, input):
+        return self.model.predict(self.vectorizer.transform([input]))


Test patch:
diff --git a/tests/test_remote_asset.py b/tests/test_remote_asset.py
new file mode 100644
index 0000000..27c4264
--- /dev/null
+++ b/tests/test_remote_asset.py
@@ -0,0 +1,49 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2024 Aliaksei Bialiauski
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import os
+
+from src.remote_asset import RemoteAsset
+import unittest
+
+"""
+Test case for RemoteAsset.
+"""
+
+
+class TestRemoteAsset(unittest.TestCase):
+
+    def tearDown(self):
+        if os.path.exists("rf-vec.joblib"):
+            os.remove("rf-vec.joblib")
+
+    def test_dumps_assets(self):
+        dump = RemoteAsset("rf-vec.joblib").dump()
+        self.assertTrue(
+            os.path.exists(dump),
+            f"{dump} was not created, but should be"
+        )
+
+    def test_fails_invalid_ref(self):
+        with self.assertRaises(
+                FileNotFoundError,
+        ):
+            RemoteAsset("invalid-ref.joblib").dump()