window_cleaning_2026 0.1.0
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +134 -0
- data/lib/window_cleaning_2026/calculator.rb +84 -0
- data/lib/window_cleaning_2026/version.rb +3 -0
- data/lib/window_cleaning_2026.rb +5 -0
- metadata +54 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 18bfa9886c1fea88f5664c4b1daa7e48f14fb26d6bc7d176a7de68fcc665a270
|
|
4
|
+
data.tar.gz: 42692afe3202002c72ee21721e901af0a0d4a3b38ce498b6a8d803e506f03107
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7ee5b9dffe9b51441cb5629509776e2f687e29643b3872c0aebe11374be5384c7349e284bd4e5eeb52b814472f25be4de24b7b9dff42bf256d74dd01833c49f6
|
|
7
|
+
data.tar.gz: 20f0987defb1c59a57eb930858bcd486c626a28a89a846f33ff0ec96dfb04b3cb7f6c0a122aee2ea6d869c4621b104e86e5e10ec713b7196025801cd78a6a6ba
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Binx Professional Cleaning
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Window Cleaning 2026 — Commercial Cost Calculator
|
|
2
|
+
|
|
3
|
+
A Ruby gem for estimating commercial window cleaning costs. Built and maintained by [Binx Professional Cleaning](https://www.binx.ca/) — a WSIB-covered, fully insured commercial and residential cleaning company operating in Northern Ontario since 2013.
|
|
4
|
+
|
|
5
|
+
Binx serves over 500 commercial facilities across North Bay, Sudbury, Timmins, and Sault Ste. Marie, including office towers, medical centres, retail plazas, government buildings, and multi-unit residential complexes. This gem encodes the same pricing model Binx uses for internal quoting.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why This Exists
|
|
10
|
+
|
|
11
|
+
Commercial window cleaning pricing in Northern Ontario is opaque. Facility managers at hospitals like North Bay Regional Health Centre, municipal buildings in Greater Sudbury, shopping centres along Algonquin Avenue, and office towers on Lakeshore Drive all face the same problem: every vendor quotes differently — per window, per pane, per square foot, per hour — making apples-to-apples comparison nearly impossible.
|
|
12
|
+
|
|
13
|
+
This calculator gives facility managers a transparent baseline estimate before going to market. It accounts for the variables that actually drive commercial window cleaning costs in Northern Ontario:
|
|
14
|
+
|
|
15
|
+
- **Labour rates** — Ontario's prevailing wage for commercial cleaning technicians ($28–$38/hr in the North Bay–Sudbury corridor as of 2026)
|
|
16
|
+
- **Access method complexity** — ground-level squeegee work vs. ladder vs. boom lift vs. rope access vs. swing stage, each with different equipment, insurance, and crew-size requirements
|
|
17
|
+
- **Window type and count** — standard single pane, double pane (common in newer Sudbury office builds), floor-to-ceiling (downtown North Bay towers), storefront (retail along Main Street), and specialty glass (atriums, skylights)
|
|
18
|
+
- **Service scope** — interior only, exterior only, or full-service (interior + exterior)
|
|
19
|
+
- **Cleaning frequency** — one-time, quarterly, monthly, or weekly schedules, with volume discounts reflecting route optimization
|
|
20
|
+
- **Materials surcharge** — cleaning solution, squeegee blades, and consumables (8% of labour)
|
|
21
|
+
- **Profit margin** — adjustable percentage applied to the subtotal
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
gem install window_cleaning_2026
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or add to your Gemfile:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
gem "window_cleaning_2026"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
require "window_cleaning_2026"
|
|
43
|
+
|
|
44
|
+
estimate = WindowCleaning2026::Calculator.calculate(
|
|
45
|
+
window_count: 120, # 120 windows
|
|
46
|
+
window_type: :double_pane, # double pane glass
|
|
47
|
+
access_method: :boom_lift, # boom lift required
|
|
48
|
+
service_type: :both, # interior + exterior
|
|
49
|
+
frequency: :quarterly, # quarterly schedule
|
|
50
|
+
labour_rate: 32.50, # $32.50/hr
|
|
51
|
+
margin_pct: 25.0 # 25% profit margin
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
puts estimate
|
|
55
|
+
# => {
|
|
56
|
+
# labour_cost: 643.50,
|
|
57
|
+
# materials_cost: 51.48,
|
|
58
|
+
# subtotal: 694.98,
|
|
59
|
+
# margin_amount: 173.75,
|
|
60
|
+
# final_price: 868.73,
|
|
61
|
+
# per_window_cost: 7.24
|
|
62
|
+
# }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Pricing Model
|
|
68
|
+
|
|
69
|
+
The calculation follows this pipeline:
|
|
70
|
+
|
|
71
|
+
1. **Base time per window** — each window type has a base cleaning duration in minutes (e.g., standard single pane = 4 min, specialty/atrium = 12 min)
|
|
72
|
+
2. **Service multiplier** — interior-only work is 45% of full-service time; exterior-only is 55%
|
|
73
|
+
3. **Access multiplier** — ground level (1.0×), ladder (1.4×), boom lift (2.2×), rope access (3.0×), swing stage (3.5×)
|
|
74
|
+
4. **Total labour time** — `base × service × access × window_count`
|
|
75
|
+
5. **Labour cost** — `(total_minutes / 60) × hourly_rate × frequency_discount`
|
|
76
|
+
6. **Materials** — 8% of labour cost
|
|
77
|
+
7. **Margin** — applied as a percentage of the subtotal (labour + materials)
|
|
78
|
+
8. **Final price** — subtotal + margin
|
|
79
|
+
|
|
80
|
+
### Access Method Details
|
|
81
|
+
|
|
82
|
+
| Method | Multiplier | Typical Use in Northern Ontario |
|
|
83
|
+
|--------|-----------|-------------------------------|
|
|
84
|
+
| Ground level | 1.0× | Single-storey retail along Algonquin Ave, strip malls |
|
|
85
|
+
| Ladder | 1.4× | Two-storey office buildings, North Bay downtown core |
|
|
86
|
+
| Boom lift | 2.2× | 3–6 storey buildings, Sudbury municipal complexes |
|
|
87
|
+
| Rope access | 3.0× | Tall structures without swing stage anchor points |
|
|
88
|
+
| Swing stage | 3.5× | High-rise — requires WSIB clearance, 2-person minimum crew |
|
|
89
|
+
|
|
90
|
+
### Frequency Discounts
|
|
91
|
+
|
|
92
|
+
| Schedule | Discount | Notes |
|
|
93
|
+
|----------|----------|-------|
|
|
94
|
+
| One-time | 0% | No recurring commitment |
|
|
95
|
+
| Quarterly | 10% | Standard for most Northern Ontario commercial contracts |
|
|
96
|
+
| Monthly | 20% | Medical facilities, food service, high-traffic retail |
|
|
97
|
+
| Weekly | 30% | Rare — typically food processing or cleanroom facilities |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Northern Ontario Market Context
|
|
102
|
+
|
|
103
|
+
Commercial window cleaning in Northern Ontario operates under conditions distinct from the GTA or Ottawa markets:
|
|
104
|
+
|
|
105
|
+
- **Seasonal constraints** — exterior cleaning is weather-dependent from roughly May through October; winter scheduling requires heated water systems and cold-weather PPE
|
|
106
|
+
- **Travel distance** — crews serving Timmins, Sault Ste. Marie, or Parry Sound from a North Bay base incur significant mobilization costs
|
|
107
|
+
- **WSIB requirements** — Ontario's Workplace Safety and Insurance Board requires clearance certificates for all commercial cleaning contractors; high-access work (boom lift, rope access, swing stage) carries additional classification premiums
|
|
108
|
+
- **Building stock** — Northern Ontario's commercial building stock skews older (1960s–1990s construction), with more single-pane and non-standard window configurations than newer southern Ontario builds
|
|
109
|
+
- **Labour market** — the North Bay–Sudbury corridor has a smaller labour pool for certified high-access technicians; rope access and swing stage crews are often shared across multiple contractors
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## About Binx Professional Cleaning
|
|
114
|
+
|
|
115
|
+
[Binx Professional Cleaning](https://www.binx.ca/) has operated in Northern Ontario since 2013, providing commercial and residential cleaning services across the North Bay–Sudbury corridor. The company maintains WSIB coverage, full commercial liability insurance, and employs over 70 staff serving 500+ commercial facilities.
|
|
116
|
+
|
|
117
|
+
Services include commercial window cleaning, post-construction cleanup, office janitorial, medical facility sanitation, and specialized high-access exterior cleaning for buildings up to 12 storeys.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
124
|
+
|
|
125
|
+
## Links
|
|
126
|
+
|
|
127
|
+
- **GitHub:** [DaveCookVectorLabs/window-cleaning-2026](https://github.com/DaveCookVectorLabs/window-cleaning-2026)
|
|
128
|
+
- **Documentation:** [window-cleaning-2026.readthedocs.io](https://window-cleaning-2026.readthedocs.io/)
|
|
129
|
+
- **PyPI:** [window-cleaning-2026](https://pypi.org/project/window-cleaning-2026/)
|
|
130
|
+
- **npm:** [@davecook/window-cleaning-2026](https://www.npmjs.com/package/@davecook/window-cleaning-2026)
|
|
131
|
+
- **Crates.io:** [window-cleaning-engine](https://crates.io/crates/window-cleaning-engine)
|
|
132
|
+
- **Maven Central:** [io.github.davecookvectorlabs:window-cleaning-engine](https://central.sonatype.com/artifact/io.github.davecookvectorlabs/window-cleaning-engine)
|
|
133
|
+
- **Docker Hub:** [davecook1985/window-cleaning-2026](https://hub.docker.com/r/davecook1985/window-cleaning-2026)
|
|
134
|
+
- **Dataset (Hugging Face):** [davecook1985/commercial-window-cleaning-costs-northern-ontario](https://huggingface.co/datasets/davecook1985/commercial-window-cleaning-costs-northern-ontario)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module WindowCleaning2026
|
|
2
|
+
# Base minutes per window by type — reflects typical commercial cleaning
|
|
3
|
+
# durations for Northern Ontario commercial facilities (office towers,
|
|
4
|
+
# retail storefronts, medical centres, multi-unit residential).
|
|
5
|
+
BASE_MINUTES = {
|
|
6
|
+
standard_single_pane: 4.0,
|
|
7
|
+
double_pane: 5.0,
|
|
8
|
+
floor_to_ceiling: 8.0,
|
|
9
|
+
storefront: 6.0,
|
|
10
|
+
specialty: 12.0,
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
# Service scope multiplier — interior glass takes less time than exterior
|
|
14
|
+
# due to reduced safety overhead and equipment setup.
|
|
15
|
+
SERVICE_MULTIPLIER = {
|
|
16
|
+
interior_only: 0.45,
|
|
17
|
+
exterior_only: 0.55,
|
|
18
|
+
both: 1.0,
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# Access method cost multiplier — each step up in access difficulty adds
|
|
22
|
+
# equipment rental, safety certification, and crew-size overhead.
|
|
23
|
+
# Swing stage work in Northern Ontario typically requires WSIB clearance
|
|
24
|
+
# and a minimum two-person crew.
|
|
25
|
+
ACCESS_MULTIPLIER = {
|
|
26
|
+
ground_level: 1.0,
|
|
27
|
+
ladder: 1.4,
|
|
28
|
+
boom_lift: 2.2,
|
|
29
|
+
rope_access: 3.0,
|
|
30
|
+
swing_stage: 3.5,
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
# Frequency discount — recurring schedules reduce per-visit cost through
|
|
34
|
+
# route optimization and retained building familiarity.
|
|
35
|
+
FREQUENCY_DISCOUNT = {
|
|
36
|
+
one_time: 1.0,
|
|
37
|
+
quarterly: 0.90,
|
|
38
|
+
monthly: 0.80,
|
|
39
|
+
weekly: 0.70,
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
MATERIALS_RATE = 0.08
|
|
43
|
+
|
|
44
|
+
class Calculator
|
|
45
|
+
# Calculate a commercial window cleaning cost estimate.
|
|
46
|
+
#
|
|
47
|
+
# @param window_count [Integer] total windows (must be > 0)
|
|
48
|
+
# @param window_type [Symbol] one of BASE_MINUTES keys
|
|
49
|
+
# @param access_method [Symbol] one of ACCESS_MULTIPLIER keys
|
|
50
|
+
# @param service_type [Symbol] one of SERVICE_MULTIPLIER keys
|
|
51
|
+
# @param frequency [Symbol] one of FREQUENCY_DISCOUNT keys
|
|
52
|
+
# @param labour_rate [Float] hourly wage in CAD (must be > 0)
|
|
53
|
+
# @param margin_pct [Float] profit margin percentage 0-100
|
|
54
|
+
# @return [Hash] detailed cost breakdown
|
|
55
|
+
def self.calculate(window_count:, window_type:, access_method:, service_type:, frequency:, labour_rate:, margin_pct:)
|
|
56
|
+
raise ArgumentError, "window_count must be positive" unless window_count.positive?
|
|
57
|
+
raise ArgumentError, "labour_rate must be positive" unless labour_rate.positive?
|
|
58
|
+
raise ArgumentError, "margin_pct must be 0-100" unless (0..100).cover?(margin_pct)
|
|
59
|
+
|
|
60
|
+
base = BASE_MINUTES.fetch(window_type)
|
|
61
|
+
service_m = SERVICE_MULTIPLIER.fetch(service_type)
|
|
62
|
+
access_m = ACCESS_MULTIPLIER.fetch(access_method)
|
|
63
|
+
freq_d = FREQUENCY_DISCOUNT.fetch(frequency)
|
|
64
|
+
|
|
65
|
+
time_per_window = base * service_m * access_m
|
|
66
|
+
total_minutes = time_per_window * window_count
|
|
67
|
+
labour_cost = (total_minutes / 60.0) * labour_rate * freq_d
|
|
68
|
+
materials_cost = labour_cost * MATERIALS_RATE
|
|
69
|
+
subtotal = labour_cost + materials_cost
|
|
70
|
+
margin_amount = subtotal * (margin_pct / 100.0)
|
|
71
|
+
final_price = subtotal + margin_amount
|
|
72
|
+
per_window_cost = final_price / window_count
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
labour_cost: labour_cost.round(2),
|
|
76
|
+
materials_cost: materials_cost.round(2),
|
|
77
|
+
subtotal: subtotal.round(2),
|
|
78
|
+
margin_amount: margin_amount.round(2),
|
|
79
|
+
final_price: final_price.round(2),
|
|
80
|
+
per_window_cost: per_window_cost.round(2),
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: window_cleaning_2026
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dave Cook
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-04-04 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A lightweight cost-estimation engine for commercial window cleaning jobs.
|
|
13
|
+
Models labour rates, access-method difficulty (ground level, ladder, boom lift,
|
|
14
|
+
rope access, swing stage), service scope, cleaning frequency discounts, materials
|
|
15
|
+
surcharge, and profit margins. Built by Binx Professional Cleaning — a WSIB-covered,
|
|
16
|
+
fully insured commercial and residential cleaning company operating in North Bay
|
|
17
|
+
and Sudbury, Northern Ontario since 2013.
|
|
18
|
+
email: dave@binx.ca
|
|
19
|
+
executables: []
|
|
20
|
+
extensions: []
|
|
21
|
+
extra_rdoc_files: []
|
|
22
|
+
files:
|
|
23
|
+
- LICENSE
|
|
24
|
+
- README.md
|
|
25
|
+
- lib/window_cleaning_2026.rb
|
|
26
|
+
- lib/window_cleaning_2026/calculator.rb
|
|
27
|
+
- lib/window_cleaning_2026/version.rb
|
|
28
|
+
homepage: https://github.com/DaveCookVectorLabs/window-cleaning-2026
|
|
29
|
+
licenses:
|
|
30
|
+
- MIT
|
|
31
|
+
metadata:
|
|
32
|
+
homepage_uri: https://github.com/DaveCookVectorLabs/window-cleaning-2026
|
|
33
|
+
source_code_uri: https://github.com/DaveCookVectorLabs/window-cleaning-2026/tree/main/engines/ruby
|
|
34
|
+
documentation_uri: https://window-cleaning-2026.readthedocs.io/
|
|
35
|
+
bug_tracker_uri: https://github.com/DaveCookVectorLabs/window-cleaning-2026/issues
|
|
36
|
+
rdoc_options: []
|
|
37
|
+
require_paths:
|
|
38
|
+
- lib
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: 2.7.0
|
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
requirements: []
|
|
50
|
+
rubygems_version: 3.6.3
|
|
51
|
+
specification_version: 4
|
|
52
|
+
summary: Commercial window cleaning cost calculator for Northern Ontario facility
|
|
53
|
+
managers
|
|
54
|
+
test_files: []
|