Wednesday, November 10, 2010

Hold it there Electric Cowboy, not so fast...

Well, it looks like the Leviton Evr-Green EVSE isn't going to be available for Christmas this year after all. They've pushed their release for the EVB22-3PM back until January with the EVB45-3PD (the 7.6kW unit, and the one I want) hopefully out by April, but no guarantees doesn't look to be available for another year, well after I theoretically take possession of my LEAF.  But what Chris at Leviton explained to me with respect to the 2010 EVSE tax credit was even more distressing: simply wiring the home in 2010 would not qualify for the tax credit even if the EVSE wasn't available.  Zounds!  So I called my Congressman and left a message with his office to please please please do what he can to get the EVSE tax credit extended into 2011, when most J1772-compliant EVSE equipment will finally become available to the general consumer and when EVs will finally be made available to the general public.  I also asked for him to confirm the wiring restriction with respect to the tax credit.  Talk about catch-22: you can have the credit for an EVSE but no EVSEs will be available until that credit expires!?  I repeat, Zounds! And here I finally got my code together to scan the Better Business Bureau website for all the local Electrical Contractors with A+ ratings after a couple hours hacking last night, as can be seen in this Google Spreadsheet (Note: The month index is 0-based, meaning January is 0).  The code to do this had to query every sub-page of the 276 registered contractors and thus took quite a while to run, but here's the simple Python script I used to compile my data:
#!/usr/bin/python

import urllib2, sgmllib, re
data = file('BBBData.csv', 'w')
data.write('Name,Address,Area,Phone,"Join Year","Join Month"'
           + ',Rating\n')

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
          'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
phone_pat = re.compile(r'phone:\s+\((?P\d{3})\)\s+' +
                       r'(?P\d{3}-\d{4})')
join_pat = re.compile(r'BBB\s+Accredited:\s+(?P' +
                      r'|'.join(months) +
                      r')\s+(?P\d{4})')
rating_pat = re.compile(r'Based on BBB files, this business '
                        +
                        r'has a BBB Rating of ([A-F][-+]?)')

class SGMLParse(sgmllib.SGMLParser):
    def __init__(self, verbose=False):
        sgmllib.SGMLParser.__init__(self, verbose)
        self.div_depth = 0
        self.stores = []
        self.inentry = False
        self.inname = False
        self.current_name = None
        self.inaddr = False
        self.current_addr = None
        self.current = None
        self.inphone = False
        self.current_join = None
        self.current_phone = None
        self.last_href = None
        self.current_rating = None

    def parse(self, text):
        self.feed(text)
        self.close()

    def start_div(self, attributes):
        self.div_depth += 1
        classes = [ ]
        attr = dict(attributes)
        if attr.has_key('class'):
            classes = attr['class'].split(' ')

        if 'StdList' in classes:
            self.inentry = self.div_depth
        elif 'StdListName' in classes:
            self.inname = self.div_depth
            self.current = ''
        elif 'StdListAddr' in classes:
            self.inaddr = self.div_depth
            self.current = ''
        elif 'StdListPhone' in classes:
            self.inphone = self.div_depth
            self.current = ''

    def end_div(self):
        if self.inentry == self.div_depth:
            # Collect data and append
            self.stores.append({'Name': self.current_name,
                                'Address': self.current_addr,
                                'Joined': self.current_join,
                                'Phone': self.current_phone,
                                'Rating': self.current_rating
                               })
            data.write('"'+self.current_name+'",')
            data.write('"'+self.current_addr+'",')
            data.write('"'+self.current_phone[0]+'",')
            data.write('"'+self.current_phone[1]+'",')
            data.write(''+str(self.current_join[1])+',')
            data.write(''+str(self.current_join[0])+',')
            data.write('"'+str(self.current_rating)+'"\n')

            self.current_name = None
            self.current_address = None
            self.current_join = None
            self.current_phone = None
            self.current_rating = None
            self.inentry = False
        elif self.inname == self.div_depth:
            self.current_name = self.current
            self.current = None
            self.inname = False
        elif self.inaddr == self.div_depth:
            self.current_addr = self.current
            self.current = None
            self.inaddr = False
        elif self.inphone == self.div_depth:
            for line in self.current.split('\n'):
                match = join_pat.match(line)
                if match:
                    self.current_join = (months.index(match.
                                            group('month')),
                                         int(match.
                                            group('year')))
                else:
                    match = phone_pat.match(line)
                    if match:
                        if self.current_phone != None:
                            print 'Duplicate Phone Number'
                        self.current_phone = (match.
                                                group(
                                                  'area'),
                                              match.
                                                group('num'))
            self.current = None
            self.inphone = False
        self.div_depth -= 1

    def start_a(self, attributes):
        attr = dict(attributes)
        if self.inentry:
            self.last_href = 'http://dc-easternpa.bbb.org/' \
                             + attr['href']
            self.current = ''

    def end_a(self):
        if self.inentry and self.current == 'BBB report':
            match = rating_pat.search(urllib2.urlopen(self.
                      last_href).read())
            if match:
                self.current_rating = match.group(1)
            self.current = None

        self.last_href = None

    def handle_data(self, data):
        if self.current is not None:
            self.current += data

    def start_br(self, attributes):
        if self.current is not None:
            self.current += '\n'

    def handle_entityref(self, entity):
        if entity.lower() == 'nbsp' and self.current is not \
             None:
            self.current += ' '

        return self.convert_entityref(entity)

BBBListHTML = urllib2.urlopen('http://dc-easternpa.bbb.org/'
                              + 'V2RosterByTob.asp?ID=10049')

p = SGMLParse()
p.parse(''.join(BBBListHTML.readlines()))

data.close()
# p.stores contains a list of dictionaries with each store's
# data
Note: Edited for line length

1 comment:

  1. Thanks for the update. I'm waiting on the Evr-Green EVSE to release, but it sounds like it won't be ready on time for my Nissan Leaf in January.

    ReplyDelete