@@ -25,8 +25,9 @@ def __init__(self, vin):
2525 self .__nhtsa = nhtsa_decode (vin )
2626 self .__attribs = self .__get_attributes ()
2727 self .__model = self .__get_model ()
28- (self .__ids , self .__trims ) = self .__get_ids ()
29- self .__eco = self .__get_vehicle_economy ()
28+ if (self .__model != None ):
29+ self .__ids , self .__trims = self .__get_ids ()
30+ self .__eco = self .__get_vehicle_economy ()
3031
3132 @property
3233 def nhtsa (self ):
@@ -58,6 +59,15 @@ def id(self):
5859 # first one. We're guessing anyway, what with the fuzzy matching and all...
5960 return self .__ids [0 ]
6061
62+ @property
63+ def trim (self ):
64+ '''
65+ EPA trim for this vehicle.
66+ '''
67+ # FIXME: If we don't know which trim exactly, just pick the
68+ # first one. We're guessing anyway, what with the fuzzy matching and all...
69+ return self .__trims [0 ]
70+
6171 @property
6272 def eco (self ):
6373 '''
@@ -80,7 +90,7 @@ def __get_attributes(self):
8090 driveType = self .nhtsa ['DriveType' ]
8191 if 'AWD' in driveType :
8292 attributes .append ("AWD" )
83- if '4WD' in driveType or '4x4' in driveType :
93+ elif '4WD' in driveType or '4x4' in driveType :
8494 attributes .append ("4WD" )
8595 elif '4x2' in driveType :
8696 attributes .append ("2WD" )
@@ -94,9 +104,16 @@ def __get_attributes(self):
94104 attributes .append (self .nhtsa ['BodyClass' ])
95105 if 'Series' in self .nhtsa and self .nhtsa ['Series' ] != "" :
96106 attributes .append (self .nhtsa ['Series' ])
107+ if 'Series2' in self .nhtsa and self .nhtsa ['Series2' ] != "" :
108+ attributes .append (self .nhtsa ['Series2' ])
97109
98110 if 'DisplacementL' in self .nhtsa and self .nhtsa ['DisplacementL' ] != '' :
99111 attributes .append ('%s L' % self .nhtsa ['DisplacementL' ])
112+ # EPA sometimes likes to go all precise
113+ if '.' not in self .nhtsa ['DisplacementL' ]:
114+ attributes .append ('%s.0 L' % self .nhtsa ['DisplacementL' ])
115+ if 'EngineCylinders' in self .nhtsa and self .nhtsa ['EngineCylinders' ] != '' :
116+ attributes .append ('%s cyl' % self .nhtsa ['EngineCylinders' ])
100117
101118 if 'Manual' in self .nhtsa ['TransmissionStyle' ]:
102119 attributes .append ('MAN' )
@@ -106,6 +123,10 @@ def __get_attributes(self):
106123 attributes .append ('CVT' )
107124 attributes .append ('Variable' )
108125
126+ # Twin turbo is "Yes, Yes"!
127+ if 'Turbo' in self .nhtsa and 'Yes' in self .nhtsa ['Turbo' ]:
128+ attributes .append ('Turbo' )
129+
109130 return attributes
110131
111132 def __get_possible_models (self ):
@@ -171,40 +192,45 @@ def __get_possible_ids(self):
171192 return None
172193 return id2trim
173194
174- def __fuzzy_match (self , attributes , choices ):
195+ def __fuzzy_match (self , mustmatch , attributes , choices ):
175196 '''
176197 Given a base name and a bunch of attributes, find the choice that matches them the best.
177- name : string
198+ mustmatch : string
178199 attributes : string[]
179200 choices : dict mapping id to string
180201 Returns: array of ids of best matching choices
181202 '''
182203
183- best_matches = 0
184- best_ids = []
185- best_fraction = 0
204+ best_ids = [] # id of best matching trims
205+ best_len = 0 # len of best matching trims
206+ best_matched = 0
186207 for (key , val ) in choices .iteritems ():
187- n_matches = 0
188- chars_total = len (val )
208+ # optional mandatory attribute
209+ # to prevent [Q60 AWD] from matching Q85 AWD instead of Q60 AWD Coupe
210+ if mustmatch != None and mustmatch .upper () not in val .upper ():
211+ continue
212+ # Find choice that matches most chars from attributes.
213+ # In case of a tie, prefer shortest choice.
189214 chars_matched = 0
190215 for attrib in attributes :
191216 if attrib != "" and attrib .upper () in val .upper ():
192- chars_matched += len (attrib )
193- n_matches += 1
194- fraction = float (chars_matched ) / chars_total
195- #print "n_matches %d, chars_matched %d, chars_total %d, fraction %f for %s" % (n_matches, chars_matched,chars_total, fraction, val)
196- if (n_matches > best_matches ):
217+ if chars_matched == 0 :
218+ chars_matched = len (attrib )
219+ else :
220+ chars_matched += len (attrib ) + 1 # for space
221+ #print "chars_matched %d, for %s" % (chars_matched, val)
222+ if (chars_matched > best_matched ):
197223 best_ids = [key ]
198- best_matches = n_matches
199- best_fraction = fraction
200- elif (( n_matches > 0 ) and ( n_matches == best_matches ) ):
201- # Heuristic: favor most complete match (so missing an attribute like Hybrid hurts)
202- if fraction > best_fraction :
224+ best_len = len ( val )
225+ best_matched = chars_matched
226+ elif (chars_matched > 0 and chars_matched == best_matched ):
227+ if len ( val ) < best_len :
228+ #print "chars %d == %d, len %d < %d, breaking tie in favor of shorter trim" % (chars_matched, best_matched, len(val), best_len)
203229 best_ids = [key ]
204- best_matches = n_matches
205- best_fraction = fraction
206- elif fraction == best_fraction :
207- #print "%d == %d, marking tie" % (n_matches, best_matches )
230+ best_len = len ( val )
231+ best_matched = chars_matched
232+ elif len ( val ) == best_len :
233+ #print "chars %d == %d, len %d == %d, marking tie" % (chars_matched, best_matched, len(val), best_len )
208234 best_ids .append (key )
209235 if len (best_ids ) == 0 :
210236 print "epa:__fuzzy_match: no match found for vin %s" % self .vin
@@ -220,7 +246,31 @@ def __get_model(self):
220246 id2models = self .__get_possible_models ()
221247 if id2models == None :
222248 return None
223- ids = self .__fuzzy_match (self .__attribs , id2models )
249+ #print "Finding model for vin %s" % self.vin
250+ # Special case for Mercedes-Benz, which puts the real model in Series
251+ oldmodel = self .nhtsa ['Model' ]
252+ model = oldmodel .replace ('-Class' , '' )
253+ ids = self .__fuzzy_match (model , self .__attribs , id2models )
254+ if len (ids ) != 1 :
255+ # Second chance for alternate spellings
256+ if '4WD' in self .__attribs :
257+ tribs = self .__attribs
258+ tribs .append ('AWD' )
259+ print "Searching again with AWD"
260+ ids = self .__fuzzy_match (self .nhtsa ['Model' ], tribs , id2models )
261+ elif '2WD' in self .__attribs and 'FWD' not in self .__attribs :
262+ tribs = self .__attribs
263+ tribs .append ('RWD' )
264+ print "Searching again with RWD"
265+ ids = self .__fuzzy_match (self .nhtsa ['Model' ], tribs , id2models )
266+ elif 'Mazda' in self .nhtsa ['Model' ]:
267+ oldmodel = self .nhtsa ['Model' ]
268+ model = oldmodel .replace ('Mazda' , '' )
269+ tribs = self .__attribs
270+ tribs .append (model )
271+ print "Searching again with %s instead of %s" % (model , oldmodel )
272+ ids = self .__fuzzy_match (model , tribs , id2models )
273+
224274 if len (ids ) != 1 :
225275 print "epa:__get_model: Failed to find model for vin %s" % self .vin
226276 return None
@@ -238,7 +288,8 @@ def __get_ids(self):
238288 id2trim = self .__get_possible_ids ()
239289 if id2trim == None :
240290 return None
241- ids = self .__fuzzy_match (self .__attribs , id2trim )
291+ #print "Finding trims for vin %s" % self.vin
292+ ids = self .__fuzzy_match (None , self .__attribs , id2trim )
242293 if len (ids ) == 0 :
243294 print "epa:__get_id: No trims found for vin %s" % self .vin
244295 return None
0 commit comments