blot-git 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. blot_git-0.0.1/.gitattributes +2 -0
  2. blot_git-0.0.1/.github/ISSUE_TEMPLATE/bench_case.md +19 -0
  3. blot_git-0.0.1/.github/ISSUE_TEMPLATE/bug_report.md +18 -0
  4. blot_git-0.0.1/.github/ISSUE_TEMPLATE/check_idea.md +14 -0
  5. blot_git-0.0.1/.github/pull_request_template.md +6 -0
  6. blot_git-0.0.1/.github/workflows/ci.yml +27 -0
  7. blot_git-0.0.1/.github/workflows/release.yml +48 -0
  8. blot_git-0.0.1/.gitignore +5 -0
  9. blot_git-0.0.1/.pre-commit-hooks.yaml +11 -0
  10. blot_git-0.0.1/CONTRIBUTING.md +34 -0
  11. blot_git-0.0.1/LICENSE +21 -0
  12. blot_git-0.0.1/PKG-INFO +46 -0
  13. blot_git-0.0.1/README.md +77 -0
  14. blot_git-0.0.1/assets/sora/blue/alert.svg +1 -0
  15. blot_git-0.0.1/assets/sora/blue/faces.svg +1 -0
  16. blot_git-0.0.1/assets/sora/blue/happy.svg +1 -0
  17. blot_git-0.0.1/assets/sora/blue/idle.svg +1 -0
  18. blot_git-0.0.1/assets/sora/blue/sad.svg +1 -0
  19. blot_git-0.0.1/assets/sora/blue/sleepy.svg +1 -0
  20. blot_git-0.0.1/assets/sora/blue/walk.svg +1 -0
  21. blot_git-0.0.1/assets/sora/blue/worried.svg +1 -0
  22. blot_git-0.0.1/assets/sora/build_sprites.py +150 -0
  23. blot_git-0.0.1/assets/sora/mustard/alert.svg +1 -0
  24. blot_git-0.0.1/assets/sora/mustard/faces.svg +1 -0
  25. blot_git-0.0.1/assets/sora/mustard/happy.svg +1 -0
  26. blot_git-0.0.1/assets/sora/mustard/idle.svg +1 -0
  27. blot_git-0.0.1/assets/sora/mustard/sad.svg +1 -0
  28. blot_git-0.0.1/assets/sora/mustard/sleepy.svg +1 -0
  29. blot_git-0.0.1/assets/sora/mustard/walk.svg +1 -0
  30. blot_git-0.0.1/assets/sora/mustard/worried.svg +1 -0
  31. blot_git-0.0.1/assets/sora/orange/alert.svg +1 -0
  32. blot_git-0.0.1/assets/sora/orange/faces.svg +1 -0
  33. blot_git-0.0.1/assets/sora/orange/happy.svg +1 -0
  34. blot_git-0.0.1/assets/sora/orange/idle.svg +1 -0
  35. blot_git-0.0.1/assets/sora/orange/sad.svg +1 -0
  36. blot_git-0.0.1/assets/sora/orange/sleepy.svg +1 -0
  37. blot_git-0.0.1/assets/sora/orange/walk.svg +1 -0
  38. blot_git-0.0.1/assets/sora/orange/worried.svg +1 -0
  39. blot_git-0.0.1/assets/sora/sage/alert.svg +1 -0
  40. blot_git-0.0.1/assets/sora/sage/faces.svg +1 -0
  41. blot_git-0.0.1/assets/sora/sage/happy.svg +1 -0
  42. blot_git-0.0.1/assets/sora/sage/idle.svg +1 -0
  43. blot_git-0.0.1/assets/sora/sage/sad.svg +1 -0
  44. blot_git-0.0.1/assets/sora/sage/sleepy.svg +1 -0
  45. blot_git-0.0.1/assets/sora/sage/walk.svg +1 -0
  46. blot_git-0.0.1/assets/sora/sage/worried.svg +1 -0
  47. blot_git-0.0.1/bench/README.md +19 -0
  48. blot_git-0.0.1/bench/cases.json +17 -0
  49. blot_git-0.0.1/docs/EXPLAINER.md +86 -0
  50. blot_git-0.0.1/docs/PRD.md +201 -0
  51. blot_git-0.0.1/docs/PYPI.md +22 -0
  52. blot_git-0.0.1/docs/competitors.md +39 -0
  53. blot_git-0.0.1/pyproject.toml +41 -0
  54. blot_git-0.0.1/src/blot/__init__.py +3 -0
  55. blot_git-0.0.1/src/blot/__main__.py +3 -0
  56. blot_git-0.0.1/src/blot/checks/__init__.py +18 -0
  57. blot_git-0.0.1/src/blot/checks/base.py +31 -0
  58. blot_git-0.0.1/src/blot/checks/cochange.py +24 -0
  59. blot_git-0.0.1/src/blot/checks/readded_bug.py +23 -0
  60. blot_git-0.0.1/src/blot/checks/removed_fix.py +22 -0
  61. blot_git-0.0.1/src/blot/classify.py +88 -0
  62. blot_git-0.0.1/src/blot/cli.py +108 -0
  63. blot_git-0.0.1/src/blot/config.py +71 -0
  64. blot_git-0.0.1/src/blot/diff.py +68 -0
  65. blot_git-0.0.1/src/blot/git.py +17 -0
  66. blot_git-0.0.1/src/blot/hooks.py +77 -0
  67. blot_git-0.0.1/src/blot/index.py +144 -0
  68. blot_git-0.0.1/src/blot/report.py +24 -0
  69. blot_git-0.0.1/src/blot/store.py +163 -0
  70. blot_git-0.0.1/tests/conftest.py +36 -0
  71. blot_git-0.0.1/tests/test_classify.py +58 -0
  72. blot_git-0.0.1/tests/test_cli.py +26 -0
  73. blot_git-0.0.1/tests/test_config.py +34 -0
  74. blot_git-0.0.1/tests/test_diff.py +39 -0
  75. blot_git-0.0.1/tests/test_hooks.py +54 -0
  76. blot_git-0.0.1/tests/test_index.py +78 -0
@@ -0,0 +1,2 @@
1
+ * text=auto eol=lf
2
+ *.svg text eol=lf
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: Bench case
3
+ about: Suggest a real change Blot should (or should not) warn about
4
+ labels: bench, good first issue
5
+ ---
6
+
7
+ **Repo and commit**
8
+ <!-- e.g. https://github.com/pallets/flask at d086db85 -->
9
+
10
+ **The change**
11
+ <!-- Which file and lines, and what the edit does. -->
12
+
13
+ **Expected result**
14
+ - Check: `removed-fix` / `readded-bug` / `forgot-related-file`
15
+ - Should Blot warn? yes / no
16
+ - Commit the warning should cite:
17
+
18
+ **Why**
19
+ <!-- Link the PR or issue that explains the original code. -->
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: Bug or false alarm
3
+ about: Blot crashed, was slow, or warned about something it shouldn't have
4
+ labels: bug
5
+ ---
6
+
7
+ **What happened**
8
+ <!-- Paste Blot's output. For a false alarm, say why the warning was wrong. -->
9
+
10
+ **What you expected**
11
+
12
+ **Repo / commit (if public)**
13
+ <!-- A public repo + commit lets us turn this into a bench case. -->
14
+
15
+ **Environment**
16
+ - OS:
17
+ - Python version:
18
+ - `blot --version`:
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: Check idea
3
+ about: Propose a new history-aware check
4
+ labels: enhancement
5
+ ---
6
+
7
+ **The mistake it catches**
8
+ <!-- One sentence, e.g. "Changing a config default that a past incident set on purpose." -->
9
+
10
+ **How history reveals it**
11
+ <!-- What in git log / blame / PRs / issues would signal this? -->
12
+
13
+ **How it stays quiet**
14
+ <!-- When should it NOT warn? False alarms are the main risk. -->
@@ -0,0 +1,6 @@
1
+ **What this changes**
2
+
3
+ **How it was tested**
4
+ - [ ] `pytest` passes
5
+ - [ ] New check or classifier change: added bench cases (one that warns, one that stays quiet)
6
+ - [ ] `blot check` stays under 500 ms on a large repo (if touching the check path)
@@ -0,0 +1,27 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: ${{ matrix.os }} / Python ${{ matrix.python }}
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, windows-latest, macos-latest]
16
+ python: ["3.10", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python }}
22
+ - name: Install
23
+ run: python -m pip install -e ".[dev]"
24
+ - name: CLI smoke test
25
+ run: blot --help
26
+ - name: Tests
27
+ run: python -m pytest -q
@@ -0,0 +1,48 @@
1
+ name: Release
2
+
3
+ # Publishing a GitHub release (tag vX.Y.Z) builds Blot and uploads it to PyPI.
4
+ # Uses PyPI trusted publishing: no API token is stored anywhere.
5
+
6
+ on:
7
+ release:
8
+ types: [published]
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.13"
18
+ - name: Check tag matches package version
19
+ run: |
20
+ version=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
21
+ if [ "v$version" != "${GITHUB_REF_NAME}" ]; then
22
+ echo "::error::Tag ${GITHUB_REF_NAME} does not match pyproject version v$version"
23
+ exit 1
24
+ fi
25
+ - name: Build
26
+ run: |
27
+ python -m pip install build twine
28
+ python -m build
29
+ python -m twine check dist/*
30
+ - uses: actions/upload-artifact@v4
31
+ with:
32
+ name: dist
33
+ path: dist/
34
+
35
+ publish:
36
+ needs: build
37
+ runs-on: ubuntu-latest
38
+ environment:
39
+ name: pypi
40
+ url: https://pypi.org/p/blot-git
41
+ permissions:
42
+ id-token: write
43
+ steps:
44
+ - uses: actions/download-artifact@v4
45
+ with:
46
+ name: dist
47
+ path: dist/
48
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,5 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .venv/
4
+ dist/
5
+ .pytest_cache/
@@ -0,0 +1,11 @@
1
+ # Lets users of the pre-commit framework add blot with:
2
+ # - repo: https://github.com/Sym-jay/Blot
3
+ # rev: v0.1.0
4
+ # hooks: [{id: blot}]
5
+ - id: blot
6
+ name: blot (history-aware checks)
7
+ entry: blot check --staged
8
+ language: python
9
+ pass_filenames: false
10
+ always_run: true
11
+ stages: [pre-commit]
@@ -0,0 +1,34 @@
1
+ # Contributing to Blot
2
+
3
+ Thanks for helping! There are three easy ways in.
4
+
5
+ ## 1. Add a benchmark case (no deep code knowledge needed)
6
+ `bench/cases.json` is a list of real changes from public repos, each with the warning blot
7
+ *should* (or should *not*) produce. Each new case is its own PR. See `bench/README.md`.
8
+
9
+ ## 2. Add or improve a check
10
+ Every check is one file in `src/blot/checks/` implementing `Check` from `checks/base.py`,
11
+ registered in `checks/__init__.py`. A new check needs:
12
+ - a unit test using the `make_repo` fixture in `tests/conftest.py`
13
+ - at least two bench cases (one that should warn, one that should stay quiet)
14
+
15
+ ## 3. Improve fix detection
16
+ `src/blot/classify.py` decides whether a commit is a bug fix or a refactor. Better heuristics
17
+ (more languages' commit conventions, issue-tracker formats) are very welcome. Measure the effect
18
+ with `blot bench`.
19
+
20
+ ## Dev setup
21
+ ```bash
22
+ python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
23
+ pip install -e ".[dev]"
24
+ pytest
25
+ ```
26
+
27
+ ## Ground rules
28
+ - Core stays **stdlib-only** and the `check` command stays **under 500 ms**.
29
+ - Quiet beats clever: a new warning must not raise the false-alarm rate on the bench.
30
+
31
+ ## Releasing (maintainers)
32
+ 1. Bump `version` in `pyproject.toml` and merge to `main`.
33
+ 2. Create a GitHub release with tag `vX.Y.Z` matching that version.
34
+ 3. The `Release` workflow builds and uploads to PyPI as `blot-git` (trusted publishing, no tokens).
blot_git-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shyam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.5
2
+ Name: blot-git
3
+ Version: 0.0.1
4
+ Summary: A pre-commit check that has read your project's history: warns when a change removes a fix, re-adds a fixed bug, or forgets a file that always changes too.
5
+ Project-URL: Homepage, https://github.com/Sym-jay/Blot
6
+ Project-URL: Issues, https://github.com/Sym-jay/Blot/issues
7
+ Author: Shyam
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: code-review,developer-tools,git,git-history,pre-commit
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Software Development :: Quality Assurance
18
+ Classifier: Topic :: Software Development :: Version Control :: Git
19
+ Requires-Python: >=3.10
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=8; extra == 'dev'
22
+ Provides-Extra: github
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Blot
26
+
27
+ **A pre-commit check that has read your project's history.**
28
+
29
+ Blot warns you *before you commit* when your change:
30
+
31
+ - **removes a fix**: deletes or edits code that was added to fix a bug
32
+ - **re-adds a bug**: brings back a line that a bug fix deliberately removed
33
+ - **forgets a related file**: changes `A` but not `B`, when the two almost always change together
34
+
35
+ It runs locally, needs no account or AI, and stays quiet unless the history gives it a real reason to speak.
36
+
37
+ ```bash
38
+ pip install blot-git
39
+ cd your-repo
40
+ blot index # one-time, builds .git/blot.db
41
+ blot install-hook # adds Blot to your pre-commit hook
42
+ ```
43
+
44
+ > **Status:** pre-alpha. Indexing and the git hook work; the three checks arrive in the next milestone.
45
+
46
+ Meet Sora the slime, read the docs, or contribute on GitHub: https://github.com/Sym-jay/Blot
@@ -0,0 +1,77 @@
1
+ # Blot
2
+
3
+ [![CI](https://github.com/Sym-jay/Blot/actions/workflows/ci.yml/badge.svg)](https://github.com/Sym-jay/Blot/actions/workflows/ci.yml)
4
+
5
+ <img src="assets/sora/orange/walk.svg" width="480" alt="Sora, the Blot slime, sliding back and forth">
6
+
7
+ **A pre-commit check that has read your project's history.**
8
+
9
+ Meet **Sora**, the slime who remembers every fix.
10
+
11
+ > Don't take down a fence until you know why it was put up.
12
+
13
+ Blot warns you *before you commit* when your change:
14
+
15
+ - **removes a fix**: deletes or edits code that was added to fix a bug
16
+ - **re-adds a bug**: brings back a line that a bug fix deliberately removed
17
+ - **forgets a related file**: changes `A` but not `B`, when the two almost always change together
18
+
19
+ It runs locally, needs no account or AI, and stays quiet unless the history gives it a real reason to speak.
20
+
21
+ ```
22
+ $ git commit -m "simplify static route"
23
+ blot: 1 warning
24
+ ! removed-fix src/flask/app.py:355
25
+ You're changing a line added by a bug fix:
26
+ 8efea0cc "Break reference cycle created by default in Flask instances" (fixes #3761)
27
+ ```
28
+
29
+ > **Status:** pre-alpha. Milestone M1 is done: `blot index` builds the history index (Flask's 3,800 commits in ~2 s) and `blot install-hook` wires up git. The three checks land in M2, so `blot check` doesn't warn yet. See the [roadmap in the PRD](docs/PRD.md#12-milestones-4-weeks-part-time).
30
+
31
+ ## Meet Sora
32
+
33
+ <img src="assets/sora/orange/faces.svg" width="168" alt="Sora cycling through expressions" align="right">
34
+
35
+ Sora is Blot's slime. You'll see a different face depending on what Blot finds:
36
+
37
+ | | Face | When |
38
+ |---|---|---|
39
+ | <img src="assets/sora/orange/idle.svg" width="84"> | Idle | Checking your commit |
40
+ | <img src="assets/sora/orange/happy.svg" width="84"> | Happy | Nothing to worry about |
41
+ | <img src="assets/sora/orange/alert.svg" width="84"> | Alert | Found a fence: this line fixed a bug |
42
+ | <img src="assets/sora/orange/worried.svg" width="84"> | Worried | Risky change, take a look |
43
+ | <img src="assets/sora/orange/sleepy.svg" width="84"> | Sleepy | Indexing history |
44
+ | <img src="assets/sora/orange/sad.svg" width="84"> | Sad | A fix is being removed |
45
+
46
+ Sora comes in four warm palettes:
47
+
48
+ <img src="assets/sora/orange/walk.svg" width="240" alt="Orange Sora"> <img src="assets/sora/sage/walk.svg" width="240" alt="Sage Sora">
49
+ <img src="assets/sora/blue/walk.svg" width="240" alt="Blue Sora"> <img src="assets/sora/mustard/walk.svg" width="240" alt="Mustard Sora">
50
+
51
+ ## Quickstart
52
+
53
+ Not on PyPI yet, so install from GitHub:
54
+
55
+ ```bash
56
+ pip install git+https://github.com/Sym-jay/Blot
57
+ cd your-repo
58
+ blot index # one-time, builds .git/blot.db (refreshes incrementally after that)
59
+ blot install-hook # adds Blot to .git/hooks/pre-commit without touching existing hooks
60
+ ```
61
+
62
+ Remove it any time with `blot install-hook --uninstall`. Settings go in `[tool.blot]` in `pyproject.toml` or a `.blot.toml` file (see `src/blot/config.py`).
63
+
64
+ Or with the [pre-commit](https://pre-commit.com) framework, see `.pre-commit-hooks.yaml`.
65
+
66
+ ## Docs
67
+
68
+ - [What is this? (simple explanation)](docs/EXPLAINER.md)
69
+ - [Product requirements](docs/PRD.md)
70
+ - [Competitor tests](docs/competitors.md)
71
+ - [Contributing](CONTRIBUTING.md)
72
+
73
+ ## License
74
+
75
+ MIT
76
+
77
+ Sora's body shape is based on [Animated Pixel Slime](https://rvros.itch.io/pixel-art-animated-slime) by rvros (CC0), recolored and given new expressions. Regenerate the sprites with `python assets/sora/build_sprites.py`.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 20" shape-rendering="crispEdges" role="img" aria-label="Sora, alert"><rect x="4" y="0" width="1" height="1" fill="#E8A33B"/><rect x="4" y="1" width="1" height="1" fill="#E8A33B"/><rect x="4" y="2" width="1" height="1" fill="#E8A33B"/><rect x="4" y="4" width="1" height="1" fill="#E8A33B"/><rect x="10" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="11" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="12" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="13" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="14" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="15" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="16" y="6" width="1" height="1" fill="#B9C7E0"/><rect x="7" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="8" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="9" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="10" y="7" width="1" height="1" fill="#6F86B8"/><rect x="11" y="7" width="1" height="1" fill="#6F86B8"/><rect x="12" y="7" width="1" height="1" fill="#6F86B8"/><rect x="13" y="7" width="1" height="1" fill="#6F86B8"/><rect x="14" y="7" width="1" height="1" fill="#6F86B8"/><rect x="15" y="7" width="1" height="1" fill="#6F86B8"/><rect x="16" y="7" width="1" height="1" fill="#6F86B8"/><rect x="17" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="18" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="19" y="7" width="1" height="1" fill="#B9C7E0"/><rect x="5" y="8" width="1" height="1" fill="#B9C7E0"/><rect x="6" y="8" width="1" height="1" fill="#B9C7E0"/><rect x="7" y="8" width="1" height="1" fill="#6F86B8"/><rect x="8" y="8" width="1" height="1" fill="#6F86B8"/><rect x="9" y="8" width="1" height="1" fill="#6F86B8"/><rect x="10" y="8" width="1" height="1" fill="#3F5689"/><rect x="11" y="8" width="1" height="1" fill="#3F5689"/><rect x="12" y="8" width="1" height="1" fill="#3F5689"/><rect x="13" y="8" width="1" height="1" fill="#3F5689"/><rect x="14" y="8" width="1" height="1" fill="#3F5689"/><rect x="15" y="8" width="1" height="1" fill="#3F5689"/><rect x="16" y="8" width="1" height="1" fill="#3F5689"/><rect x="17" y="8" width="1" height="1" fill="#6F86B8"/><rect x="18" y="8" width="1" height="1" fill="#6F86B8"/><rect x="19" y="8" width="1" height="1" fill="#6F86B8"/><rect x="20" y="8" width="1" height="1" fill="#B9C7E0"/><rect x="21" y="8" width="1" height="1" fill="#B9C7E0"/><rect x="4" y="9" width="1" height="1" fill="#B9C7E0"/><rect x="5" y="9" width="1" height="1" fill="#6F86B8"/><rect x="6" y="9" width="1" height="1" fill="#6F86B8"/><rect x="7" y="9" width="1" height="1" fill="#3F5689"/><rect x="8" y="9" width="1" height="1" fill="#3F5689"/><rect x="9" y="9" width="1" height="1" fill="#3F5689"/><rect x="10" y="9" width="1" height="1" fill="#3F5689"/><rect x="11" y="9" width="1" height="1" fill="#3F5689"/><rect x="12" y="9" width="1" height="1" fill="#3F5689"/><rect x="13" y="9" width="1" height="1" fill="#3F5689"/><rect x="14" y="9" width="1" height="1" fill="#3F5689"/><rect x="15" y="9" width="1" height="1" fill="#3F5689"/><rect x="16" y="9" width="1" height="1" fill="#3F5689"/><rect x="17" y="9" width="1" height="1" fill="#3F5689"/><rect x="18" y="9" width="1" height="1" fill="#3F5689"/><rect x="19" y="9" width="1" height="1" fill="#3F5689"/><rect x="20" y="9" width="1" height="1" fill="#6F86B8"/><rect x="21" y="9" width="1" height="1" fill="#6F86B8"/><rect x="22" y="9" width="1" height="1" fill="#B9C7E0"/><rect x="3" y="10" width="1" height="1" fill="#B9C7E0"/><rect x="4" y="10" width="1" height="1" fill="#6F86B8"/><rect x="5" y="10" width="1" height="1" fill="#3F5689"/><rect x="6" y="10" width="1" height="1" fill="#3F5689"/><rect x="7" y="10" width="1" height="1" fill="#3F5689"/><rect x="8" y="10" width="1" height="1" fill="#3F5689"/><rect x="9" y="10" width="1" height="1" fill="#3F5689"/><rect x="10" y="10" width="1" height="1" fill="#3F5689"/><rect x="11" y="10" width="1" height="1" fill="#3F5689"/><rect x="12" y="10" width="1" height="1" fill="#3F5689"/><rect x="13" y="10" width="1" height="1" fill="#3F5689"/><rect x="14" y="10" width="1" height="1" fill="#3F5689"/><rect x="15" y="10" width="1" height="1" fill="#3F5689"/><rect x="16" y="10" width="1" height="1" fill="#3F5689"/><rect x="17" y="10" width="1" height="1" fill="#3F5689"/><rect x="18" y="10" width="1" height="1" fill="#3F5689"/><rect x="19" y="10" width="1" height="1" fill="#3F5689"/><rect x="20" y="10" width="1" height="1" fill="#3F5689"/><rect x="21" y="10" width="1" height="1" fill="#3F5689"/><rect x="22" y="10" width="1" height="1" fill="#6F86B8"/><rect x="23" y="10" width="1" height="1" fill="#B9C7E0"/><rect x="2" y="11" width="1" height="1" fill="#B9C7E0"/><rect x="3" y="11" width="1" height="1" fill="#6F86B8"/><rect x="4" y="11" width="1" height="1" fill="#3F5689"/><rect x="5" y="11" width="1" height="1" fill="#3F5689"/><rect x="6" y="11" width="1" height="1" fill="#FFF6E8"/><rect x="7" y="11" width="1" height="1" fill="#D9663F"/><rect x="8" y="11" width="1" height="1" fill="#25345C"/><rect x="9" y="11" width="1" height="1" fill="#3F5689"/><rect x="10" y="11" width="1" height="1" fill="#3F5689"/><rect x="11" y="11" width="1" height="1" fill="#3F5689"/><rect x="12" y="11" width="1" height="1" fill="#3F5689"/><rect x="13" y="11" width="1" height="1" fill="#3F5689"/><rect x="14" y="11" width="1" height="1" fill="#3F5689"/><rect x="15" y="11" width="1" height="1" fill="#3F5689"/><rect x="16" y="11" width="1" height="1" fill="#3F5689"/><rect x="17" y="11" width="1" height="1" fill="#3F5689"/><rect x="18" y="11" width="1" height="1" fill="#3F5689"/><rect x="19" y="11" width="1" height="1" fill="#3F5689"/><rect x="20" y="11" width="1" height="1" fill="#3F5689"/><rect x="21" y="11" width="1" height="1" fill="#3F5689"/><rect x="22" y="11" width="1" height="1" fill="#3F5689"/><rect x="23" y="11" width="1" height="1" fill="#6F86B8"/><rect x="24" y="11" width="1" height="1" fill="#B9C7E0"/><rect x="2" y="12" width="1" height="1" fill="#B9C7E0"/><rect x="3" y="12" width="1" height="1" fill="#6F86B8"/><rect x="4" y="12" width="1" height="1" fill="#3F5689"/><rect x="5" y="12" width="1" height="1" fill="#25345C"/><rect x="6" y="12" width="1" height="1" fill="#FFF6E8"/><rect x="7" y="12" width="1" height="1" fill="#D9663F"/><rect x="8" y="12" width="1" height="1" fill="#25345C"/><rect x="9" y="12" width="1" height="1" fill="#25345C"/><rect x="10" y="12" width="1" height="1" fill="#3F5689"/><rect x="11" y="12" width="1" height="1" fill="#3F5689"/><rect x="12" y="12" width="1" height="1" fill="#3F5689"/><rect x="13" y="12" width="1" height="1" fill="#3F5689"/><rect x="14" y="12" width="1" height="1" fill="#3F5689"/><rect x="15" y="12" width="1" height="1" fill="#3F5689"/><rect x="16" y="12" width="1" height="1" fill="#3F5689"/><rect x="17" y="12" width="1" height="1" fill="#3F5689"/><rect x="18" y="12" width="1" height="1" fill="#3F5689"/><rect x="19" y="12" width="1" height="1" fill="#3F5689"/><rect x="20" y="12" width="1" height="1" fill="#3F5689"/><rect x="21" y="12" width="1" height="1" fill="#3F5689"/><rect x="22" y="12" width="1" height="1" fill="#3F5689"/><rect x="23" y="12" width="1" height="1" fill="#3F5689"/><rect x="24" y="12" width="1" height="1" fill="#6F86B8"/><rect x="25" y="12" width="1" height="1" fill="#B9C7E0"/><rect x="1" y="13" width="1" height="1" fill="#B9C7E0"/><rect x="2" y="13" width="1" height="1" fill="#6F86B8"/><rect x="3" y="13" width="1" height="1" fill="#3F5689"/><rect x="4" y="13" width="1" height="1" fill="#3F5689"/><rect x="5" y="13" width="1" height="1" fill="#25345C"/><rect x="6" y="13" width="1" height="1" fill="#D9663F"/><rect x="7" y="13" width="1" height="1" fill="#D9663F"/><rect x="8" y="13" width="1" height="1" fill="#25345C"/><rect x="9" y="13" width="1" height="1" fill="#25345C"/><rect x="10" y="13" width="1" height="1" fill="#25345C"/><rect x="11" y="13" width="1" height="1" fill="#3F5689"/><rect x="12" y="13" width="1" height="1" fill="#3F5689"/><rect x="13" y="13" width="1" height="1" fill="#3F5689"/><rect x="14" y="13" width="1" height="1" fill="#3F5689"/><rect x="15" y="13" width="1" height="1" fill="#3F5689"/><rect x="16" y="13" width="1" height="1" fill="#3F5689"/><rect x="17" y="13" width="1" height="1" fill="#3F5689"/><rect x="18" y="13" width="1" height="1" fill="#3F5689"/><rect x="19" y="13" width="1" height="1" fill="#3F5689"/><rect x="20" y="13" width="1" height="1" fill="#3F5689"/><rect x="21" y="13" width="1" height="1" fill="#3F5689"/><rect x="22" y="13" width="1" height="1" fill="#3F5689"/><rect x="23" y="13" width="1" height="1" fill="#3F5689"/><rect x="24" y="13" width="1" height="1" fill="#3F5689"/><rect x="25" y="13" width="1" height="1" fill="#6F86B8"/><rect x="26" y="13" width="1" height="1" fill="#B9C7E0"/><rect x="1" y="14" width="1" height="1" fill="#B9C7E0"/><rect x="2" y="14" width="1" height="1" fill="#6F86B8"/><rect x="3" y="14" width="1" height="1" fill="#3F5689"/><rect x="4" y="14" width="1" height="1" fill="#25345C"/><rect x="5" y="14" width="1" height="1" fill="#25345C"/><rect x="6" y="14" width="1" height="1" fill="#25345C"/><rect x="7" y="14" width="1" height="1" fill="#25345C"/><rect x="8" y="14" width="1" height="1" fill="#25345C"/><rect x="9" y="14" width="1" height="1" fill="#25345C"/><rect x="10" y="14" width="1" height="1" fill="#25345C"/><rect x="11" y="14" width="1" height="1" fill="#25345C"/><rect x="12" y="14" width="1" height="1" fill="#25345C"/><rect x="13" y="14" width="1" height="1" fill="#25345C"/><rect x="14" y="14" width="1" height="1" fill="#3F5689"/><rect x="15" y="14" width="1" height="1" fill="#3F5689"/><rect x="16" y="14" width="1" height="1" fill="#3F5689"/><rect x="17" y="14" width="1" height="1" fill="#3F5689"/><rect x="18" y="14" width="1" height="1" fill="#3F5689"/><rect x="19" y="14" width="1" height="1" fill="#3F5689"/><rect x="20" y="14" width="1" height="1" fill="#3F5689"/><rect x="21" y="14" width="1" height="1" fill="#3F5689"/><rect x="22" y="14" width="1" height="1" fill="#3F5689"/><rect x="23" y="14" width="1" height="1" fill="#3F5689"/><rect x="24" y="14" width="1" height="1" fill="#3F5689"/><rect x="25" y="14" width="1" height="1" fill="#3F5689"/><rect x="26" y="14" width="1" height="1" fill="#6F86B8"/><rect x="1" y="15" width="1" height="1" fill="#6F86B8"/><rect x="2" y="15" width="1" height="1" fill="#3F5689"/><rect x="3" y="15" width="1" height="1" fill="#3F5689"/><rect x="4" y="15" width="1" height="1" fill="#25345C"/><rect x="5" y="15" width="1" height="1" fill="#25345C"/><rect x="6" y="15" width="1" height="1" fill="#25345C"/><rect x="7" y="15" width="1" height="1" fill="#25345C"/><rect x="8" y="15" width="1" height="1" fill="#25345C"/><rect x="9" y="15" width="1" height="1" fill="#25345C"/><rect x="10" y="15" width="1" height="1" fill="#25345C"/><rect x="11" y="15" width="1" height="1" fill="#25345C"/><rect x="12" y="15" width="1" height="1" fill="#25345C"/><rect x="13" y="15" width="1" height="1" fill="#25345C"/><rect x="14" y="15" width="1" height="1" fill="#25345C"/><rect x="15" y="15" width="1" height="1" fill="#25345C"/><rect x="16" y="15" width="1" height="1" fill="#25345C"/><rect x="17" y="15" width="1" height="1" fill="#25345C"/><rect x="18" y="15" width="1" height="1" fill="#25345C"/><rect x="19" y="15" width="1" height="1" fill="#25345C"/><rect x="20" y="15" width="1" height="1" fill="#25345C"/><rect x="21" y="15" width="1" height="1" fill="#3F5689"/><rect x="22" y="15" width="1" height="1" fill="#3F5689"/><rect x="23" y="15" width="1" height="1" fill="#3F5689"/><rect x="24" y="15" width="1" height="1" fill="#3F5689"/><rect x="25" y="15" width="1" height="1" fill="#3F5689"/><rect x="26" y="15" width="1" height="1" fill="#6F86B8"/><rect x="0" y="16" width="1" height="1" fill="#6F86B8"/><rect x="1" y="16" width="1" height="1" fill="#6F86B8"/><rect x="2" y="16" width="1" height="1" fill="#3F5689"/><rect x="3" y="16" width="1" height="1" fill="#3F5689"/><rect x="4" y="16" width="1" height="1" fill="#3F5689"/><rect x="5" y="16" width="1" height="1" fill="#25345C"/><rect x="6" y="16" width="1" height="1" fill="#25345C"/><rect x="7" y="16" width="1" height="1" fill="#25345C"/><rect x="8" y="16" width="1" height="1" fill="#25345C"/><rect x="9" y="16" width="1" height="1" fill="#25345C"/><rect x="10" y="16" width="1" height="1" fill="#25345C"/><rect x="11" y="16" width="1" height="1" fill="#25345C"/><rect x="12" y="16" width="1" height="1" fill="#25345C"/><rect x="13" y="16" width="1" height="1" fill="#25345C"/><rect x="14" y="16" width="1" height="1" fill="#25345C"/><rect x="15" y="16" width="1" height="1" fill="#25345C"/><rect x="16" y="16" width="1" height="1" fill="#25345C"/><rect x="17" y="16" width="1" height="1" fill="#25345C"/><rect x="18" y="16" width="1" height="1" fill="#25345C"/><rect x="19" y="16" width="1" height="1" fill="#25345C"/><rect x="20" y="16" width="1" height="1" fill="#25345C"/><rect x="21" y="16" width="1" height="1" fill="#25345C"/><rect x="22" y="16" width="1" height="1" fill="#25345C"/><rect x="23" y="16" width="1" height="1" fill="#3F5689"/><rect x="24" y="16" width="1" height="1" fill="#3F5689"/><rect x="25" y="16" width="1" height="1" fill="#3F5689"/><rect x="26" y="16" width="1" height="1" fill="#6F86B8"/><rect x="27" y="16" width="1" height="1" fill="#6F86B8"/><rect x="0" y="17" width="1" height="1" fill="#6F86B8"/><rect x="1" y="17" width="1" height="1" fill="#6F86B8"/><rect x="2" y="17" width="1" height="1" fill="#6F86B8"/><rect x="3" y="17" width="1" height="1" fill="#3F5689"/><rect x="4" y="17" width="1" height="1" fill="#3F5689"/><rect x="5" y="17" width="1" height="1" fill="#3F5689"/><rect x="6" y="17" width="1" height="1" fill="#3F5689"/><rect x="7" y="17" width="1" height="1" fill="#3F5689"/><rect x="8" y="17" width="1" height="1" fill="#3F5689"/><rect x="9" y="17" width="1" height="1" fill="#3F5689"/><rect x="10" y="17" width="1" height="1" fill="#3F5689"/><rect x="11" y="17" width="1" height="1" fill="#3F5689"/><rect x="12" y="17" width="1" height="1" fill="#3F5689"/><rect x="13" y="17" width="1" height="1" fill="#3F5689"/><rect x="14" y="17" width="1" height="1" fill="#3F5689"/><rect x="15" y="17" width="1" height="1" fill="#3F5689"/><rect x="16" y="17" width="1" height="1" fill="#3F5689"/><rect x="17" y="17" width="1" height="1" fill="#3F5689"/><rect x="18" y="17" width="1" height="1" fill="#3F5689"/><rect x="19" y="17" width="1" height="1" fill="#3F5689"/><rect x="20" y="17" width="1" height="1" fill="#3F5689"/><rect x="21" y="17" width="1" height="1" fill="#3F5689"/><rect x="22" y="17" width="1" height="1" fill="#3F5689"/><rect x="23" y="17" width="1" height="1" fill="#3F5689"/><rect x="24" y="17" width="1" height="1" fill="#3F5689"/><rect x="25" y="17" width="1" height="1" fill="#6F86B8"/><rect x="26" y="17" width="1" height="1" fill="#6F86B8"/><rect x="27" y="17" width="1" height="1" fill="#6F86B8"/><rect x="0" y="18" width="1" height="1" fill="#6F86B8"/><rect x="1" y="18" width="1" height="1" fill="#6F86B8"/><rect x="2" y="18" width="1" height="1" fill="#6F86B8"/><rect x="3" y="18" width="1" height="1" fill="#6F86B8"/><rect x="4" y="18" width="1" height="1" fill="#6F86B8"/><rect x="5" y="18" width="1" height="1" fill="#6F86B8"/><rect x="6" y="18" width="1" height="1" fill="#6F86B8"/><rect x="7" y="18" width="1" height="1" fill="#6F86B8"/><rect x="8" y="18" width="1" height="1" fill="#6F86B8"/><rect x="9" y="18" width="1" height="1" fill="#6F86B8"/><rect x="10" y="18" width="1" height="1" fill="#6F86B8"/><rect x="11" y="18" width="1" height="1" fill="#6F86B8"/><rect x="12" y="18" width="1" height="1" fill="#6F86B8"/><rect x="13" y="18" width="1" height="1" fill="#6F86B8"/><rect x="14" y="18" width="1" height="1" fill="#6F86B8"/><rect x="15" y="18" width="1" height="1" fill="#6F86B8"/><rect x="16" y="18" width="1" height="1" fill="#6F86B8"/><rect x="17" y="18" width="1" height="1" fill="#6F86B8"/><rect x="18" y="18" width="1" height="1" fill="#6F86B8"/><rect x="19" y="18" width="1" height="1" fill="#6F86B8"/><rect x="20" y="18" width="1" height="1" fill="#6F86B8"/><rect x="21" y="18" width="1" height="1" fill="#6F86B8"/><rect x="22" y="18" width="1" height="1" fill="#6F86B8"/><rect x="23" y="18" width="1" height="1" fill="#6F86B8"/><rect x="24" y="18" width="1" height="1" fill="#6F86B8"/><rect x="25" y="18" width="1" height="1" fill="#6F86B8"/><rect x="26" y="18" width="1" height="1" fill="#6F86B8"/><rect x="27" y="18" width="1" height="1" fill="#6F86B8"/><rect x="1" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="2" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="3" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="4" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="5" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="6" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="7" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="8" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="9" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="10" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="11" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="12" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="13" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="14" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="15" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="16" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="17" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="18" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="19" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="20" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="21" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="22" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="23" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="24" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="25" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/><rect x="26" y="19" width="1" height="1" fill="#000000" fill-opacity="0.18"/></svg>