Notes from daily learning
In 2023 I set a New Year’s resolution to spend 30 minutes each workday learning.
I committed to publish notes each day to build up a growing doc of little things I’ve learned. Here’s that doc.
2023-02-28: Community energy
I’m obsessed with community energy companies and solar PV. I want to start one in Liverpool.
This morning I’ve been reading about recent community energy projects through their share offers.
Power Up North London
- 83kWp PV on a community sports centre
- raising £80,000 (£964 / kWp)
- interest rate 4% (capital repaid 5% over 20 years)
- Community Benefit Society
- “PUNL will enter into a licence and power purchase agreement with Camden and GLL. PUNL will receive income from the sale of the solar electricity to GLL who will in turn receive discounted renewable electricity.” (wtf is GLL?)
- summary and share offer document
Brighton Energy Cooperative
- PV at a wine estate
- 578kWp (wow)
- raising 482,050 (£834 / kWp)
- interest rate 5% for 2 years, 3.5% after
- Community Benefit Society
- 23 Solaredge 25kW inverters
- very thorough calculations in share offer
- “Revenue assumptions are based on where our solar electricity gets used. If it is sold to the site, revenue is determined by our power purchase agreement, which is fairly predictable as it’s fixed in BEC’s lease with the site and inflation-linked. The remainder of electricity is exported to the grid, and revenue here is based on a preliminary offer from Octopus energy for 27p per Kwh for the first 30 months. At that point revenue projections for export are based on our assumptions of where the export market will be; the BEC Board has judged that 7p to be a reasonable forecast of the export price we will receive after our 30-month contract with Octopus expires.”
- summary and share offer
Bath and West Community Energy
- PV on 10 schools
- 972 kWp across 10 sites
- raising £1.5M (£1,542 / kWp - seems wrong, too high?)
- interest rate 5%
- Community Benefit Society
- summary and share offer
2023-01-24: ChatGPT for parsing emails
Playing with ChatGPT to see if we might be able to automatically extract information from customer support emails.
I did a few iterations. It seems most useful if you stick to one task at a time. For example, extract a list of questions, then see if any of those questions match an FAQ, rather than explaining that all in one go.
Here’s a (pretty mind-blowing) example. Names / details have been changed.
My prompt:
You are an email parsing machine. Your task is to extract certain information
from emails.
I will give you an email from a customer. You will extract and quote the
following information and output it in JSON format:
"questions"
type: JSON array
description: a list of questions in the email. Quote them exactly, do not make
up questions.
"people_raw"
type: string
description: if the email mentions booking for a number of people, quote the
number exactly as written. otherwise output null.
* "people":
type: integer
description: if "people_raw" contains an integer, quote that integer. If it
contains a range, quote of the largest integer. Otherwise output null.
* "date_raw"
type: string
description: if the email mentions booking for a particular date, quote the
date. otherwise output null.
* "date"
type: string
description: parse "date_raw" in "YYYY-MM-DD" format or "????-MM-DD" format if
the year is missing.
Here are some examples.
Example 1:
Email:
"Hi, I'm looking at booking this for April.
Can you tell me where the treasure hunt begins?
Thanks, Jason"
Output:
{
"questions": [
"Can you tell me where the treasure hunt begins?"
],
"date_raw": null,
"date": null,
"people_raw": null,
"people": null
}
Example 2:
Email:
"Hello,
I'm planning my brother's Pirate Themed Stag do in Birmingham on 18th Feb. Your
site mentions hen dos so hopefully you'll accept us as a mixed group! Can I
book that afternoon for 10 people?
A couple of (stupid) questions:
Is the treasure hunt pirate themed?
Will the stop off pubs be ok with us being in fancy dress?
Many thanks,
Carl"
Output:
{
"questions": [
"Your site mentions hen dos so hopefully you'll accept us as a mixed group!",
"Is the treasure hunt pirate themed?",
"Will the stop off pubs be ok with us being in fancy dress?"
],
"date_raw": "18th Feb",
"date": "????-02-18",
"people_raw": "10",
"people": 10,
}
Please extract the information from the following email:
"Dear Sir/Madam,
This is Ben. I have come across your website and am really interested in
organising a team building day for my colleagues with your activities. I’m
still in the idea initiation stage, so only know the potential date (19 April)
but do not have numbers (probably 30-40 people). As the date will be around
Easter, I wonder if you offer something close to a themed treasure hunt (e.g.
about Easter).
I would also like to know how far in advance I need to book for an event at the
scale mentioned above, and anything you think I should beware.
Thank you,
Ben"
ChatGPT’s answer:
{
"questions": [
"I wonder if you offer something close to a themed treasure hunt (e.g. about Easter)",
"I would also like to know how far in advance I need to book for an event at the scale mentioned above, and anything you think I should beware."
],
"date_raw": "19 April",
"date": "????-04-19",
"people_raw": "30-40",
"people": 40
}
10⁄10 so far… 🤯
2023-01-18 Go linting
golangci-lint is amazing!
Example:
$ golangci-lint run ./...
game/handlers.go:35:7: ineffectual assignment to statusCode (ineffassign)
var statusCode = http.StatusInternalServerError
^
scheduler/scheduler.go:105:10: SA1015: using time.Tick leaks the underlying ticker, consider using it only in endless functions, tests and the main package, and use time.NewTicker here (staticcheck)
tick := time.Tick(100 * time.Millisecond)
^
structs/structs.go:52:2: SA9004: only the first constant in this group has an explicit type (staticcheck)
EventTypeUndefined eventType = ""
^
Learned so far:
time.Since
can be used instead oftime.Now().Sub
time.Tick
leaks the underlying ticker (I need to spend time understanding this one)
2023-01-10, 2023-01-11: Retrofitting: Triple glazing
Questions
Is it worth upgrading double glazing to triple glazing? Lots of questions:
- how do double and triple glazing U-values compare?
- my double glazing is warped and old: how does that affect its performance?
- what’s the deal with UPVC? does it warp in the sun? embodied carbon?
- how much does it cost to upgrade a whole house?
- what’s the CO2e saving over the lifetime?
- does it make sense to go triple without improving wall insulation?
U-values of single, double, triple glazing
According to abbeyglass.co.uk
- Single glazing: 5 W/m2K
- Double glazing: 3 W/m2K
- Triple glazing: 0.8 – 1.6 W/m2K
Green Building Store’s windows range from 0.71 to 0.80 W/m2K.
Aide memoire: U values of other things:
- Solid oak door: 3
- 50mm of PIR (e.g. Kingspan): 0.38 W/m2K
The BFRC (British Fenestration Rating Council) has a rating system A++ to E. Building Regulations now require all homes to have C graded windows.
U-values required by building regulations (June 2022)
https://www.firstinarchitecture.co.uk/a-quick-and-easy-guide-to-u-values/
- External Walls 0.18 W/m²K
- Party Walls 0.0 W/m²K
- Floor 0.13 W/m²K
- Roof 0.11 W/m²K
- Windows (whole window U-value) 1.2 W/m²K
Conductivity versus U-value
The thermal conductivity is a property of the material: how well it allows heat to flow through it (lower is better). Unit: W/mK
The U-value is the amount of heat transfer through a square metre of material for a given temperature difference (lower is better). Unit: W/m2K.
U-value = thermal conductivity / thickness (m)
For example, Kingspan Kooltherm K107 has a thermal conductivity of 0.019 W/mK.
So a 50mm board has a U-value of 0.019 / 0.05 = 0.38 W/m2K
Certifications
Passivhaus and EnerPHit
EnerPHit is a lower (but still high) standard than Passivhaus.
According to that website:
there is no reason why Passive-House-suitable windows should not be used whenever the opportunity arise
Old double glazing
Draughts around the window are a bad sign (we have this). Warped?
Online U-value calculators
- by Kingspan: u-valuecalculator.com
- by EcoTherm: uvaluecalculator.ecotherm.co.uk
Degree days & converting U-value to heating kWh
Good introduction article.
Thanks Ed for sharing the article comparing triple and double glazing that put me onto Degree Days.
- It’s calculated for a base temperature which is a function of a building, lower than its target temperature. Often fudged as 15.5°C for UK buildings.
- Degree days is the sum of the differences of measured temperature from the base temperature
- for example, imagine December’s temperature was 13.5°C day and night for the whole month. At a base temperature of 15.5°C, December would have had 31 × 2° = 62 degree days.
- It can be used with a U-value to estimate the actual rate of energy loss.
Example calculation:
- Manchester Airport (EGCC) had 331 heating degree days in December 2022 at a base temperature of 15.5°C (source)
- Assuming a double glazed window with U-value 3 W/m²K
- and an area of 4 m²
- and the building was kept at a constant temperature
- 331 ÷ 31 days = 10.7°K - that’s the (rough) average temperature difference between inside and outside the window
- Rate of heat loss through December: 3 W/m²K × 4 m² × 10.6°K = 127W
- Energy lost through the window over the whole month: 127W × 24 hours × 31 days = 94.6 kWh
- At a gas price of £0.07 / kWh, the window cost 94.6 kWh × £0.07 = £6.62 for December
Have I got this right, anyone?!
2023-01-06
Modify the GPS co-ordinates in an image
In Linux, the wonderful exiftool can modify GPS latitude and longitude in an image:
$ exiftool \
-GPSLatitude=53.420318 \
-GPSLatitudeRef=N \
-GPSLongitude=-2.988016 \
-GPSLongitudeRef=W \
myimage.jpg
1 image files updated
Then you can read it back in decimal format like this:
$ exiftool -c '%.10f' -GPSPosition myimage.jpg
GPS Position : 53.4203180000 N, 2.9880160000 W
Reminder:
- in GPS convention, longitude means “degrees east of the prime meridian” (Greenwich).
- a negative longitude means west of the prime meridian.
- exiftool displays a negative longitude as positive but with the W indicator (this is common)
As a check, search the co-ordinate in Google Maps to verify it (converting any W longitudes to negative.)
https://www.google.com/maps/?q=53.4203180000,-2.9880160000
Additional metadata
If you’re trying to obfuscate, beware that phone cameras add lots of other EXIF metadata like timestamps, phone model and so on.
An easy way to include this is by starting with a real photo containing GPS and other metadata. Overwrite the image content with your own, then rewrite the latitude, longitude and altitude.
2023-01-04
Didn’t properly understand the error from yesterday:
“fatal error: all goroutines are asleep - deadlock!”
This code is wrong (open in Go playground):
package main
import (
"fmt"
"time"
)
func main() {
const n = 5
results := make(chan int, n)
for i := 0; i < n; i++ {
go doSomethingSlow(i, results)
}
for result := range results {
// WRONG - this blows up with the error:
// fatal error: all goroutines are asleep - deadlock!
fmt.Println(result)
}
}
func doSomethingSlow(i int, results chan<- int) {
time.Sleep(1 * time.Second)
results <- i
}
Curious because all the goroutines ran and I saw the 5 results printed:
0
1
4
2
3
… so why the error?
Answer: the results
channel is never closed and so the range
loop just waits forever.
Recall that:
for result := range results {
...
}
is equivalent to:
for {
result, ok := <-results
if !ok {
// !ok only happens if channel gets closed,
// which it doesn't in above example
break
}
...
}
Simple!
I didn’t expect to see the code blow up: in C++ I think it would have just hung. Blowing up is more helpful, thanks Go! Stackoverflow answer
Fixing the above example… If we don’t want to close the channel, the simplest change is to track that we’ve received n
results rather than using the channel range
loop (playground):
package main
import (
"fmt"
"time"
)
func main() {
const n = 5
results := make(chan int, n)
for i := 0; i < n; i++ {
go doSomethingSlow(i, results)
}
for i := 0; i < n; i++ {
// we're only expecting n results, so we stop trying to receive
// after that, preventing deadlock.
result := <-results
fmt.Println(result)
}
}
func doSomethingSlow(i int, results chan<- int) {
time.Sleep(1 * time.Second)
results <- i
}
2023-01-03
Go: goroutines
- There’s no way to stop a goroutine (other than exiting)
- But you can ask a goroutine to stop itself.
Reading chapter 8 of The Go Programming Language
Started Exercise 8.2: making an FTP server. Code on Github
Go: channels
Aide memoire: the send and receive operator always points left <-
myChannel <- x
send x to myChannelx <- myChannel
receive into x<- myChannel
receive from myChannel, discarding valuex, ok <- myChannel
receive into x: ok is false if channel is closed AND drained
When we make a channel we have an optional capacity
, like a slice:
ch := make(chan int)
ch := make(chan int, 5)
A channel with 0 capacity is called unbuffered.
An unbuffered channel has no capacity to store a value: the receiver must be ready before the sender can complete. Send is blocked until receive is ready. The sending and receiving goroutines become synchronised.
Closing a channel
The sender goroutine can close a channel with close(ch)
.
If the receiver carries on reading, it will get a stream of zero-values.
The receiver can use x, ok <- myChannel
to check if it’s trying to access a closed, drained channel.
for / range
with channels
You can use for
with range
to automatically check ok
while receiving:
func printer(thingsToPrint chan int) {
for x := range thingsToPrint {
fmt.Println(x)
}
Wait for channels to finish
It’s not OK to do this:
results := make(chan i, 5)
for i := 0 ; i < 5 i++ {
go doSomethingSlow(results)
}
for result := range results {
// WRONG - not waiting for the goroutines to finish
// so you'll get the error:
// fatal error: all goroutines are asleep - deadlock!
}
Instead you’d have to do:
results := make(chan i, 5)
for i := 0 ; i < 5 i++ {
go doSomethingSlow(results)
}
for i := 0 ; i < 5 i++ {
result := <- results
}
Channel directions
Channels are 2-directional. To show sending-only or receiving-only intent you can cast them to unidirectional types
func printer(in <-chan int) {
// can *only* receive from in
x <- in
}
2023-01-01
Vim: word shortcuts
w
- start of next worde
- end of next wordb
- beginning of current word
Vim: insert text multiple times
e.g. to insert foo
30 times, type 30ifoo
ESC
Thanks to Interactive Vim tutorial