From 575dd002be18f170917a98f8bca891b4bd615f78 Mon Sep 17 00:00:00 2001 From: bnewbold Date: Fri, 21 Sep 2012 13:03:46 +0200 Subject: basic octopart pricing functionality --- bommom.go | 5 ++++ core.go | 12 +++++++++- formats.go | 16 +++++++++++++ octopart.go | 63 ++++++++++++++++++++++++++++++++++++------------- octopart_test.go | 16 ++++++++----- serve.go | 6 +++++ templates/bom_view.html | 12 ++++++++-- 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 @@ manufacturer mpn description + category + comment + price + availability {{ range .Bom.LineItems }} @@ -24,11 +28,15 @@ {{ .Manufacturer }} {{ .Mpn }} {{ .Description }} + {{ .Category }} + {{ .Comment }} + {{ .AggregateInfo.MarketPrice }} + {{ .AggregateInfo.MarketFactor }} {{ end }} -- cgit v1.2.3