aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbnewbold <bnewbold@robocracy.org>2012-09-21 13:03:46 +0200
committerbnewbold <bnewbold@robocracy.org>2012-09-21 13:03:46 +0200
commit575dd002be18f170917a98f8bca891b4bd615f78 (patch)
tree5db7245e184110fd0a403b052191fe6d5b3205d4
parentb0ecc94ef69d489dcc775038a0c6f65c1c960c8e (diff)
downloadbommom-575dd002be18f170917a98f8bca891b4bd615f78.zip
bommom-575dd002be18f170917a98f8bca891b4bd615f78.tar.gz
basic octopart pricing functionality
-rw-r--r--bommom.go5
-rw-r--r--core.go12
-rw-r--r--formats.go16
-rw-r--r--octopart.go63
-rw-r--r--octopart_test.go16
-rw-r--r--serve.go6
-rw-r--r--templates/bom_view.html12
7 files changed, 105 insertions, 25 deletions
diff --git a/bommom.go b/bommom.go
index 55071b8..cebff62 100644
--- a/bommom.go
+++ b/bommom.go
@@ -28,6 +28,7 @@ var (
listenPort = flag.Uint("port", 7070, "port to listen on (HTTP serve)")
listenHost = flag.String("host", "", "hostname to listen on (HTTP serve)")
sessionSecret = flag.String("sessionSecret", "12345", "cookie session secret")
+ octoApiKey = flag.String("octopartApiKey", "", "octopart.com API key (for pricing info)")
)
func main() {
@@ -88,6 +89,10 @@ func openAuthStore() {
auth = DummyAuth(true)
}
+func openPricingSource() {
+ pricingSource = NewOctopartClient(*octoApiKey)
+}
+
func dumpOut(fname string, bm *BomMeta, b *Bom) {
var outFile io.Writer
if fname == "" {
diff --git a/core.go b/core.go
index f6bc5e3..7a7f918 100644
--- a/core.go
+++ b/core.go
@@ -112,11 +112,21 @@ func makeTestBom() (*BomMeta, *Bom) {
Mpn: "WIDG0001",
Elements: []string{"W1", "W2"},
Offers: []Offer{o}}
+ li2 := LineItem{Manufacturer: "Texas Instruments",
+ Mpn: "NE555",
+ Elements: []string{"W1", "W2"},
+ Offers: []Offer{o}}
+ li3 := LineItem{Manufacturer: "STMicroelectronics",
+ Mpn: "L7905CV",
+ Elements: []string{"W1", "W2"},
+ Offers: []Offer{o}}
//li.AddOffer(o)
b := NewBom("test01")
b.AddLineItem(&li)
+ // tests that lines don't get duplicated
b.AddLineItem(&li)
- b.AddLineItem(&li)
+ b.AddLineItem(&li2)
+ b.AddLineItem(&li3)
bm := &BomMeta{Name: "Some Bom", Owner: "Some Owner", Description: "This is such a thing!", HeadVersion: b.Version, Homepage: "http://bommom.com", IsPublicView: true, IsPublicEdit: false}
return bm, b
}
diff --git a/formats.go b/formats.go
index a1d6146..15ac7bc 100644
--- a/formats.go
+++ b/formats.go
@@ -53,6 +53,22 @@ func DumpBomAsText(bm *BomMeta, b *Bom, out io.Writer) {
tabWriter.Flush()
}
+func DumpBomMarketInfo(bm *BomMeta, b *Bom, out io.Writer) {
+ fmt.Fprintln(out)
+ tabWriter := tabwriter.NewWriter(out, 2, 4, 1, ' ', 0)
+ // "by line item", not "by element"
+ fmt.Fprintf(tabWriter, "qty\tmanufacturer\tmpn\t\tavg_price\tfactor\n")
+ for _, li := range b.LineItems {
+ fmt.Fprintf(tabWriter, "%d\t%s\t%s\t\t%s\t%s\n",
+ len(li.Elements),
+ li.Manufacturer,
+ li.Mpn,
+ li.AggregateInfo["MarketPrice"],
+ li.AggregateInfo["MarketFactor"])
+ }
+ tabWriter.Flush()
+}
+
// --------------------- csv -----------------------
func DumpBomAsCSV(b *Bom, out io.Writer) {
diff --git a/octopart.go b/octopart.go
index 654c03d..9c7db89 100644
--- a/octopart.go
+++ b/octopart.go
@@ -6,6 +6,7 @@ import (
"log"
"net/http"
"net/url"
+ "strconv"
//"io/ioutil"
)
@@ -31,11 +32,6 @@ func NewOctopartClient(apikey string) *OctopartClient {
return oc
}
-func openPricingSource() {
- // TODO: pass through octopart API key here
- pricingSource = NewOctopartClient("")
-}
-
func (oc *OctopartClient) apiCall(method string, params map[string]string) (map[string]interface{}, error) {
paramString := "?apikey=" + oc.ApiKey
// TODO: assert clean-ness of params
@@ -76,6 +72,7 @@ func (oc *OctopartClient) bomApiCall(manufacturers, mpns []string) ([]map[string
listItem = make(map[string]string)
listItem["mpn_or_sku"] = mpns[i]
listItem["manufacturer"] = manufacturers[i]
+ //listItem["q"] = mpns[i]
listItem["limit"] = "1"
listItem["reference"] = manufacturers[i] + "|" + mpns[i]
queryList[i] = listItem
@@ -95,7 +92,10 @@ func (oc *OctopartClient) bomApiCall(manufacturers, mpns []string) ([]map[string
ret := make([]map[string]interface{}, len(mpns))
for i, rawresult := range response["results"].([]interface{}) {
result := rawresult.(map[string]interface{})
- hits := int(result["hits"].(float64))
+ hits := int(0)
+ if result["hits"] != nil {
+ hits = int(result["hits"].(float64))
+ }
reference := result["reference"].(string)
if hits == 0 {
ret[i] = nil
@@ -109,7 +109,7 @@ func (oc *OctopartClient) bomApiCall(manufacturers, mpns []string) ([]map[string
}
// this method checks the API query cache
-func (oc *OctopartClient) GetMarketInfoList(manufacturers, mpns []string) ([]interface{}, error) {
+func (oc *OctopartClient) GetMarketInfoList(manufacturers, mpns []string) ([]map[string]interface{}, error) {
if len(mpns) < 1 {
return nil, Error("no mpns strings passed in")
}
@@ -131,20 +131,30 @@ func (oc *OctopartClient) GetMarketInfoList(manufacturers, mpns []string) ([]int
}
}
// if necessary, fetch missing queryHashes remotely
- if len(mpnToQuery) > 0 {
- if _, err := oc.bomApiCall(manufacturersToQuery, mpnToQuery); err != nil {
+ for len(mpnToQuery) > 0 {
+ high := len(mpnToQuery)
+ if high >= 20 {
+ high = 20
+ }
+ if _, err := oc.bomApiCall(manufacturersToQuery[0:high], mpnToQuery[0:high]); err != nil {
return nil, err
}
+ mpnToQuery = mpnToQuery[high:len(mpnToQuery)]
+ manufacturersToQuery = manufacturersToQuery[high:len(manufacturersToQuery)]
}
// construct list of return info
- result := make([]interface{}, len(mpns))
+ result := make([]map[string]interface{}, len(mpns))
for i, _ := range mpns {
queryHash = manufacturers[i] + "|" + mpns[i]
value, hasKey := oc.infoCache[queryHash]
if hasKey != true {
return nil, Error("key should be in cache, but isn't: " + queryHash)
}
- result[i] = value
+ if value == nil || mpns[i] == "" || manufacturers[i] == "" {
+ result[i] = nil
+ } else {
+ result[i] = value.(map[string]interface{})
+ }
}
return result, nil
}
@@ -154,7 +164,7 @@ func (oc *OctopartClient) GetMarketInfo(manufacturer, mpn string) (map[string]in
if err != nil {
return nil, err
}
- return info[0].(map[string]interface{}), nil
+ return info[0], nil
}
func (oc *OctopartClient) GetExtraInfo(manufacturer, mpn string) (map[string]string, error) {
@@ -164,11 +174,16 @@ func (oc *OctopartClient) GetExtraInfo(manufacturer, mpn string) (map[string]str
}
// extract market price, total avail, and "availability factor" from
// market info
- log.Println(marketInfo)
ret := make(map[string]string)
- ret["MarketPrice"] = marketInfo["avg_price"].(string)
- ret["MarketFactor"] = marketInfo["market_availability"].(string)
- ret["MarketTotalAvailable"] = marketInfo["total_avail"].(string)
+ if marketInfo != nil {
+ if marketInfo["avg_price"].([]interface{})[0] != nil {
+ ret["MarketPrice"] = "$" + strconv.FormatFloat(marketInfo["avg_price"].([]interface{})[0].(float64), 'f', 2, 64)
+ } else {
+ ret["MarketPrice"] = ""
+ }
+ ret["MarketFactor"] = marketInfo["market_status"].(string)
+ ret["OctopartUrl"] = marketInfo["detail_url"].(string)
+ }
return ret, nil
}
@@ -188,5 +203,21 @@ func (oc *OctopartClient) AttachMarketInfo(li *LineItem) error {
}
func (oc *OctopartClient) AttachMarketInfoBom(b *Bom) error {
+ // first ensure the cache is primed
+ manufacturers := make([]string, len(b.LineItems))
+ mpns := make([]string, len(b.LineItems))
+ for i, li := range b.LineItems {
+ manufacturers[i] = li.Manufacturer
+ mpns[i] = li.Mpn
+ }
+ _, err := oc.GetMarketInfoList(manufacturers, mpns)
+ if err != nil {
+ log.Println(err.Error())
+ return err
+ }
+
+ for i := range b.LineItems {
+ oc.AttachMarketInfo(&(b.LineItems[i]))
+ }
return nil
}
diff --git a/octopart_test.go b/octopart_test.go
index d2c8fbd..c86ed9b 100644
--- a/octopart_test.go
+++ b/octopart_test.go
@@ -26,7 +26,7 @@ func TestGetMarketInfoList(t *testing.T) {
if r == nil {
log.Printf("\t%d: %s", i, "nil")
} else {
- log.Printf("\t%d: %s", i, r.(map[string]interface{})["detail_url"])
+ log.Printf("\t%d: %s", i, r["detail_url"])
}
}
log.Println("Running a second time, results should be cached...")
@@ -38,7 +38,7 @@ func TestGetMarketInfoList(t *testing.T) {
if r == nil {
log.Printf("\t%d: %s", i, "nil")
} else {
- log.Printf("\t%d: %s", i, r.(map[string]interface{})["detail_url"])
+ log.Printf("\t%d: %s", i, r["detail_url"])
}
}
log.Println("Running in single mode, result should be cached...")
@@ -47,9 +47,9 @@ func TestGetMarketInfoList(t *testing.T) {
t.Errorf("Error with api call: " + err.Error())
}
if result_single == nil {
- log.Printf("\t%d: %s", "nil")
+ log.Printf("\t%d: %s", 0, "nil")
} else {
- log.Printf("\t%d: %s", result_single["detail_url"])
+ log.Printf("\t%d: %s", 0, result_single["detail_url"])
}
}
@@ -58,6 +58,10 @@ func TestAttachInfo(t *testing.T) {
bm := &BomMeta{}
oc := NewOctopartClient("")
oc.AttachMarketInfoBom(b)
- t.Errorf("unimplemented")
- DumpBomAsText(bm, b, os.Stdout)
+ //t.Errorf("unimplemented")
+ //DumpBomAsText(bm, b, os.Stdout)
+ DumpBomMarketInfo(bm, b, os.Stdout)
+ log.Println("Running a second time, results should be cached...")
+ oc.AttachMarketInfoBom(b)
+ DumpBomMarketInfo(bm, b, os.Stdout)
}
diff --git a/serve.go b/serve.go
index e6eaca7..e1b68a5 100644
--- a/serve.go
+++ b/serve.go
@@ -146,6 +146,7 @@ func bomController(w http.ResponseWriter, r *http.Request, user, name string) (e
http.Error(w, "invalid bom name: "+name, 400)
return
}
+
context := make(map[string]interface{})
context["BomMeta"], context["Bom"], err = bomstore.GetHead(ShortName(user), ShortName(name))
context["Session"] = session.Values
@@ -153,6 +154,10 @@ func bomController(w http.ResponseWriter, r *http.Request, user, name string) (e
http.Error(w, "404 couldn't open bom: "+user+"/"+name, 404)
return nil
}
+ err = pricingSource.AttachMarketInfoBom(context["Bom"].(*Bom))
+ if err != nil {
+ log.Println("error attaching market info: " + err.Error())
+ }
err = tmplBomView.Execute(w, context)
return
}
@@ -275,6 +280,7 @@ func serveCmd() {
openBomStore()
openAuthStore()
+ openPricingSource()
// serve template static assets (images, CSS, JS)
http.Handle("/static/", http.FileServer(http.Dir(*templatePath+"/")))
diff --git a/templates/bom_view.html b/templates/bom_view.html
index 7eca3e3..94e99a8 100644
--- a/templates/bom_view.html
+++ b/templates/bom_view.html
@@ -11,11 +11,15 @@
<th>manufacturer
<th>mpn
<th>description
+ <th>category
+ <!--
<th>form_factor
<th>specs
- <th>category
<th>tag
+ -->
<th>comment
+ <th>price
+ <th>availability
</tr>
{{ range .Bom.LineItems }}
<tr>
@@ -24,11 +28,15 @@
<td>{{ .Manufacturer }}
<td>{{ .Mpn }}
<td>{{ .Description }}
+ <td>{{ .Category }}
+ <!--
<td>{{ .FormFactor }}
<td>{{ .Specs }}
- <td>{{ .Category }}
<td>{{ .Tag }}
+ -->
<td>{{ .Comment }}
+ <td><a href="{{ .AggregateInfo.OctopartUrl }}">{{ .AggregateInfo.MarketPrice }}</a>
+ <td>{{ .AggregateInfo.MarketFactor }}
</tr>
{{ end }}
</table>