rkneufeld-fuzzy-realty 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 1
3
+ :minor: 3
4
4
  :patch: 0
data/lib/fuzzy_realty.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  require 'stubs.rb'
2
+ require 'scores_table.rb'
3
+ require 'weights.rb'
4
+ require 'rulebase.rb'
2
5
  require 'rulebase.rb'
3
6
 
4
7
  module FuzzyRealty
@@ -14,26 +17,35 @@ module FuzzyRealty
14
17
  change = FuzzyRealty::METHODS[param.type].call(listing,param)
15
18
  # if score is bad and parameter is required then reduce it further
16
19
  change = (change < 0.70 and param.required) ? (change - 1) : change
20
+
17
21
  score += change * FuzzyRealty::WEIGHTS[param.type]
18
22
  end
19
- scores << {listing => score}
23
+ scores << { :score => score, :listing => listing}
20
24
  end
21
- return scores
25
+ return scores.sort {|a,b| b[:score] <=> a[:score]}
22
26
  end
23
27
  end
24
-
25
- # Have a table of ways to calculate the score modifier
26
- # --> i.e. price will allow cheaper, sqft will allow bigger, etc.
27
28
  end
28
29
 
29
30
  if __FILE__ == $0
30
- # Creating a library of stub models
31
- a = FuzzyRealty::Listing.new({:price => 112000, :sqft => 720, :location => 'A'})
32
- b = FuzzyRealty::Listing.new({:price => 142000, :sqft => 1420, :location => 'B'})
31
+ listings = []
32
+ 10000.times do |i|
33
+ listings << FuzzyRealty::Listing.new({
34
+ :price => 20_000 + rand(250_000),
35
+ :sqft => 300 + rand(2000),
36
+ :location => %W{A B C D}[rand(4)],
37
+ :style => %W{Bungalow Bi-level Split-level Two-story Condominium}[rand(5)]
38
+ })
39
+ end
33
40
  # The user wants price around 110k and in location A,
34
- p1 = FuzzyRealty::Parameter.new(false,:price,110000)
35
- p2 = FuzzyRealty::Parameter.new(true,:location, 'A')
36
-
37
- q = FuzzyRealty::Query.new([p1,p2])
38
- puts FuzzyRealty::ExpertSystem.scores([a,b],q).inspect
41
+ parameters = []
42
+ parameters << FuzzyRealty::Parameter.new(:price,250000)
43
+ parameters << FuzzyRealty::Parameter.new(:location, 'A',true)
44
+ parameters << FuzzyRealty::Parameter.new(:style,"Condominium",true)
45
+ parameters << FuzzyRealty::Parameter.new(:sqft,1575,true)
46
+ query = FuzzyRealty::Query.new(parameters)
47
+ scores = FuzzyRealty::ExpertSystem.scores(listings,query)
48
+ scores.each do |score|
49
+ puts "%.2f" % score[:score] + "\t\t#{score[:listing].inspect}"
50
+ end
39
51
  end
data/lib/rulebase.rb CHANGED
@@ -1,48 +1,60 @@
1
1
  module FuzzyRealty
2
2
  METHODS = {
3
+ # Not yet implemented
4
+ :bathroom => 1,
5
+ :garage => 1,
6
+ :deck => 1,
7
+
3
8
  # Price is match when desired is 90-105% of actual. Otherwise give a
4
9
  # reduced factor.
5
10
  :price =>
6
11
  lambda do |listing,param|
7
- actual,desired = param.desired,listing.price.to_f
8
- if (desired*0.90..desired*1.05) === actual
9
- 1
12
+ puts "Called price"
13
+ actual,desired = listing.price.to_f, param.desired
14
+ result = if (desired*0.90..desired*1.05) === actual
15
+ 1.0
10
16
  else
11
17
  1 - ((desired - actual) / actual).abs
12
18
  end
19
+ puts result
20
+ result
13
21
  end,
14
22
 
15
23
  # Location calc. does lookup to find score for desired and actual
16
24
  ## Currently just return 1 if exact, 0 otherwise
17
25
  :location =>
18
26
  lambda do |listing,param|
19
- FuzzyRealty::LOCN[param.desired][listing.location]
20
- end
21
- }
22
-
23
- # Chosen weights are largely arbitrary. Expert was consulted for relative ratings,
24
- # but as the knowledge engineer I was forced to pick the crisp values.
25
- # Experimentation with larger user groups would likely show specific values to do
26
- # better
27
- WEIGHTS = {
28
- :sqft => 15,
29
- :price => 10,
30
- :location => 25
31
- }
27
+ puts "Called location"
28
+ # Perform a quick lookup (i.e. FuzzyRealty::LOCN[:A][:A] => 1.0)
29
+ puts FuzzyRealty::LOCN[param.desired.to_sym][listing.location.to_sym]
30
+ FuzzyRealty::LOCN[param.desired.to_sym][listing.location.to_sym]
31
+ end,
32
+
33
+ :sqft =>
34
+ lambda do |listing,param|
35
+ puts "Called sqft"
36
+ actual, desired = listing.sqft, param.desired
37
+ result = if (actual + 50) >= desired
38
+ 1.0
39
+ elsif (actual + 150) >= desired
40
+ 0.8
41
+ elsif (actual + 300) >= desired
42
+ 0.5
43
+ else
44
+ 0.0
45
+ end
46
+ puts result
47
+ result
48
+ end,
49
+ # Style's follow lookup table similar to Location
32
50
 
33
- # A is a high-class suburb
34
- # B is a middle-class area
35
- # C is an older middle-class area
36
- # D is a "ghetto"
37
- # A B C D
38
- # A 1.0 0.75 0.2 0.0
39
- # B 0.75 1.00 0.4 0.1
40
- # C 0.2 0.4 1.0 0.6
41
- # D 0.0 0.1 0.6 1.0
42
- LOCN = {
43
- 'A' => {'A' => 1.0, 'B' => 0.75, 'C' => 0.2, 'D' => 0.0},
44
- 'B' => {'A' => 0.75, 'B' => 1.0, 'C' => 0.4, 'D' => 0.1},
45
- 'C' => {'A' => 0.2, 'B' => 0.4, 'C' => 1.0, 'D' => 0.6},
46
- 'D' => {'A' => 0.0, 'B' => 0.1, 'C' => 0.6, 'D' => 1.0}
51
+ :style =>
52
+ lambda do |listing,param|
53
+ puts "Called style"
54
+ desired = FuzzyRealty::STYLE[:Symbol][param.desired]
55
+ actual = FuzzyRealty::STYLE[:Symbol][listing.style]
56
+ puts FuzzyRealty::STYLE[desired][actual]
57
+ FuzzyRealty::STYLE[desired][actual]
58
+ end
47
59
  }
48
60
  end
@@ -0,0 +1,39 @@
1
+ module FuzzyRealty
2
+ # Table for looking up score of relative locations
3
+ #
4
+ # A is a high-class suburb
5
+ # B is a middle-class area
6
+ # C is an older middle-class area
7
+ # D is a "ghetto"
8
+ # A B C D
9
+ # A 1.0 0.75 0.2 0.0
10
+ # B 0.75 1.00 0.4 0.1
11
+ # C 0.2 0.4 1.0 0.6
12
+ # D 0.0 0.1 0.6 1.0
13
+ LOCN = {
14
+ :A => {:A => 1.0, :B => 0.75, :C => 0.2, :D => 0.0},
15
+ :B => {:A => 0.75, :B => 1.0, :C => 0.4, :D => 0.1},
16
+ :C => {:A => 0.2, :B => 0.4, :C => 1.0, :D => 0.6},
17
+ :D => {:A => 0.0, :B => 0.1, :C => 0.6, :D => 1.0}
18
+ }
19
+
20
+ # Table for lookup of score of desired and actual styles
21
+ # - Arbitrary values provied by my wife, at present the leanings aren't quite right
22
+ #
23
+ # Bu = Bungalow, Bi = Bilevel, Sp = Splitlevel, Tw = Two Story, Co = Condo
24
+ #| | Bu | Sp | Tw | Bi | Co |
25
+ #| Bu | 1 | 0.4 | 0.8 | 0.65 | 0.1 |
26
+ #| Sp | 0.5 | 1 | 0.7 | 0.95 | 0.2 |
27
+ #| Tw | 0.0 | 0.6 | 1 | 0.5 | 0.0 |
28
+ #| Bi | 0.75 | 0.6 | 0.8 | 1 | 0.0 |
29
+ #| Co | 0.7 | 0.0 | 0.0 | 0.0 | 1 |
30
+ STYLE = {
31
+ :Symbol => {"Bungalow" => :Bu, "Bi-level" => :Bi, "Split-level" => :Sp,
32
+ "Two-story" => :Tw, "Condominium" => :Co},
33
+ :Bu => { :Bu => 1, :Sp => 0.4, :Tw => 0.8, :Bi => 0.65, :Co => 0.1 },
34
+ :Sp => { :Bu => 0.5, :Sp => 1.0, :Tw => 0.7, :Bi => 0.95, :Co => 0.2 },
35
+ :Tw => { :Bu => 0.0, :Sp => 0.6, :Tw => 1.0, :Bi => 0.5, :Co => 0.0 },
36
+ :Bi => { :Bu => 0.75,:Sp => 0.6, :Tw => 0.8, :Bi => 1.0, :Co => 0.0 },
37
+ :Co => { :Bu => 0.7, :Sp => 0.0, :Tw => 0.0, :Bi => 0.0, :Co => 1.0 }
38
+ }
39
+ end
data/lib/stubs.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module FuzzyRealty
2
2
  class Listing
3
- attr_accessor :price, :sqft, :location
3
+ attr_accessor :price, :sqft, :location, :style
4
4
  def initialize(values={})
5
5
  values.each_key {|k| instance_variable_set(:"@#{k}", values[k])}
6
6
  end
@@ -14,8 +14,8 @@ module FuzzyRealty
14
14
  end
15
15
 
16
16
  class Parameter
17
- attr_accessor :required, :type, :desired
18
- def initialize(required,type,desired)
17
+ attr_accessor :required, :type, :desired, :style
18
+ def initialize(type,desired,required=false)
19
19
  @required,@type,@desired = required, type, desired
20
20
  end
21
21
  end
data/lib/weights.rb ADDED
@@ -0,0 +1,15 @@
1
+ module FuzzyRealty
2
+ # Chosen weights are largely arbitrary. Expert was consulted for relative ratings,
3
+ # but as the knowledge engineer I was forced to pick the crisp values.
4
+ # Experimentation with larger user groups would likely show specific values to do
5
+ # better
6
+ WEIGHTS = {
7
+ :sqft => 15,
8
+ :price => 10,
9
+ :location => 25,
10
+ :style => 18
11
+ }
12
+ def self.max_score
13
+ WEIGHTS.inject(0) {|sum,n| sum + n[1]}
14
+ end
15
+ end
@@ -1,7 +1,30 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class FuzzyRealtyTest < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
4
+
5
+ context "Score verification" do
6
+ setup do
7
+ @listing = FuzzyRealty::Listing.new({
8
+ :price => 250_000,
9
+ :sqft => 1_575,
10
+ :location => 'A',
11
+ :style => 'Condominium'
12
+ })
13
+ end
14
+
15
+ should "score perfect for perfect house" do
16
+ # The user wants price around 110k and in location A,
17
+ params = []
18
+ params << FuzzyRealty::Parameter.new(:price,250_000,true)
19
+ params << FuzzyRealty::Parameter.new(:location, 'A',true)
20
+ params << FuzzyRealty::Parameter.new(:style,"Condominium",true)
21
+ params << FuzzyRealty::Parameter.new(:sqft,1575,true)
22
+ query = FuzzyRealty::Query.new(params)
23
+
24
+ scores = FuzzyRealty::ExpertSystem.scores([@listing],query)
25
+ puts "%.2f" % scores[0][:score] + "\t\t#{scores[0][:listing].inspect}"
26
+ assert_equal(scores[0][:score], FuzzyRealty.max_score)
27
+ end
6
28
  end
29
+
7
30
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rkneufeld-fuzzy-realty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Neufeld
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-18 00:00:00 -07:00
12
+ date: 2009-04-19 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -30,7 +30,9 @@ files:
30
30
  - lib/TODO
31
31
  - lib/fuzzy_realty.rb
32
32
  - lib/rulebase.rb
33
+ - lib/scores_table.rb
33
34
  - lib/stubs.rb
35
+ - lib/weights.rb
34
36
  - test/fuzzy_realty_test.rb
35
37
  - test/test_helper.rb
36
38
  has_rdoc: true