diff --git a/main.py b/main.py
new file mode 100644
index 0000000..2274f58
--- /dev/null
+++ b/main.py
@@ -0,0 +1,61 @@
+import io
+from flask import Flask
+from flask import render_template
+from flask import Response
+from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
+from services import get_cv, get_count_by_year, create_random_figure, get_top_edu_by_job
+
+app = Flask(__name__)
+
+
+@app.route("/")
+def cv_index():
+ cvs = get_cv()
+ res = ("\n".join((f"
{i + 1})
",
+ f"Желаемая зарплата: {cv['salary']}.
",
+ f"Образование: {cv['educationType']}.
")) for i, cv in enumerate(cvs))
+ return "".join(res)
+
+
+@app.route("/dashboard")
+def dashboard():
+ return render_template('d3.html', cvs=get_cv())
+
+
+@app.route("/years")
+def years():
+ cvs = get_count_by_year()
+ data = [cv["count"] for cv in cvs]
+ labels = [cv["year"] for cv in cvs]
+ return render_template('d4.html', cvs=cvs, data=data, labels=labels)
+
+
+@app.route("/edu/managers")
+def edu_managers():
+ cvs = get_top_edu_by_job(("manager", "менедж"))
+ data = [cv["count"] for cv in cvs]
+ labels = [cv["qualification"] for cv in cvs]
+ return render_template('d5.html', cvs=cvs, data=data, labels=labels, ql="менеджеров")
+
+
+@app.route("/edu/engineer")
+def edu_engineers():
+ cvs = get_top_edu_by_job(("engineer", "инжене"))
+ data = [cv["count"] for cv in cvs]
+ labels = [cv["qualification"] for cv in cvs]
+ return render_template('d5.html', cvs=cvs, data=data, labels=labels, ql="инженеров")
+
+
+@app.route('/plot.png')
+def plot_png():
+ output = io.BytesIO()
+ FigureCanvas(create_random_figure()).print_png(output)
+ return Response(output.getvalue(), mimetype='image/png')
+
+
+def main() -> None:
+ app.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/services.py b/services.py
new file mode 100644
index 0000000..b7fdef3
--- /dev/null
+++ b/services.py
@@ -0,0 +1,60 @@
+import sqlite3
+import random
+from matplotlib.figure import Figure
+
+DB_NAME = 'works.sqlite'
+
+
+def _ignore_case_collation(value1_, value2_):
+ value1_, value2_ = str(value1_), str(value2_)
+ if value1_.lower() == value2_.lower():
+ return 0
+ elif value1_.lower() < value2_.lower():
+ return -1
+ else:
+ return 1
+
+
+def get_connection():
+ con = sqlite3.connect(DB_NAME)
+ con.row_factory = lambda cursor, row: {col[0]: row[idx] for idx, col in enumerate(cursor.description)}
+ # Изначально sqlite работает только с ASCII символами.
+ con.create_collation("BINARY", _ignore_case_collation)
+ con.create_collation("NOCASE", _ignore_case_collation)
+ con.create_function("LOWER", 1, lambda value_: str(value_).lower())
+ con.create_function("UPPER", 1, lambda value_: str(value_).upper())
+
+ return con
+
+
+def get_cv():
+ query = "SELECT * FROM works LIMIT 20"
+ with get_connection() as con:
+ return list(con.execute(query))
+
+
+def get_count_by_year():
+ query = ("SELECT COUNT(*) as 'count', strftime('%Y', dateModify) as 'year' "
+ "FROM works WHERE year IS NOT NULL GROUP BY year")
+ with get_connection() as con:
+ return list(con.execute(query))
+
+
+def get_top_edu_by_job(job_titles: tuple[str, str], count: int = 15):
+ query = (f"SELECT LOWER(qualification) as 'qualification', count(*) as 'count' FROM works "
+ f"WHERE qualification IS NOT NULL AND "
+ f"(LOWER(jobTitle) like '%{job_titles[0]}%' OR LOWER(jobTitle) like '%{job_titles[1]}%')"
+ f"GROUP BY LOWER(qualification) "
+ f"ORDER BY count DESC LIMIT {count}")
+ with get_connection() as con:
+ return list(con.execute(query))
+
+
+def create_random_figure():
+ fig = Figure()
+ axis = fig.add_subplot(1, 1, 1)
+ xs = range(100)
+ ys = [random.randint(1, 50) for _ in xs]
+ axis.plot(xs, ys)
+
+ return fig
diff --git a/templates/d4.html b/templates/d4.html
new file mode 100644
index 0000000..a3ab6a3
--- /dev/null
+++ b/templates/d4.html
@@ -0,0 +1,226 @@
+
+
+
+
+
+
+
+
+ Dashboard Template · Bootstrap v5.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Количество резюме по годам
+
+
+
+
+ | # |
+ Год |
+ Количество |
+
+
+
+ {% for cv in cvs %}
+
+ | {{ loop.index }} |
+ {{ cv.year }} |
+ {{ cv.count }} |
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/d5.html b/templates/d5.html
new file mode 100644
index 0000000..a7356a6
--- /dev/null
+++ b/templates/d5.html
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+ Dashboard Template · Bootstrap v5.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Топ {{ cvs|length }} образований у {{ ql }}
+
+
+
+
+ | # |
+ Образование |
+ Количество |
+
+
+
+ {% for cv in cvs %}
+
+ | {{ loop.index }} |
+ {{ cv.qualification }} |
+ {{ cv.count }} |
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/works.sqlite b/works.sqlite
new file mode 100644
index 0000000..ba5b917
Binary files /dev/null and b/works.sqlite differ