fuzzyworld

find-stone-beer / commit

Initial commit of beer-finding stuff.

352a2a024965ee95d6d95bf8382cc5167c333385 · Pavel Safronov · 2023-08-22 16:30:46 -0700

Initial commit of beer-finding stuff.
 .gitignore          |   1 +
 .vscode/launch.json |  28 ++++++
 Makefile            |   2 +
 README.md           |  25 ++++++
 beer.go             |  58 +++++++++++++
 find-stone.go       | 138 +++++++++++++++++++++++++++++
 go.mod              |  14 +++
 go.sum              |  15 ++++
 main.go             | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 main_test.go        |  16 ++++
 seller-info.go      |  49 +++++++++++
 seller.go           |  82 ++++++++++++++++++
 12 files changed, 673 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..06cf653
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+cache
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100755
index 0000000..2e1bef3
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,28 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+    {
+        "name": "Launch Package",
+        "type": "go",
+        "request": "launch",
+        "mode": "auto",
+        "program": "${fileDirname}"
+    },
+    {
+        "name": "Launch file",
+        "type": "go",
+        "request": "launch",
+        "mode": "debug",
+        "program": "${file}"
+    },
+        {
+            "type": "bashdb",
+            "request": "launch",
+            "name": "Debug repo.sh",
+            "cwd": "${workspaceFolder}",
+            "program": "${workspaceFolder}/repro.sh",
+            "terminalKind": "integrated"
+        }
+    ]
+}
+							
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..50ee7bf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+run:
+	go run .
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..5a785b0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,25 @@
+# find-stone-beer
+
+Work notes on find-stone-beer
+
+## Pushover info
+
+Application: FindStoneBeer
+secret data is in gitlab
+
+## removed code
+
+```
+testSeller, err := getSellerInfo("16826826")
+if err != nil {
+    panic(err)
+}
+fmt.Printf("Test seller 16826826: %v\n", testSeller)
+for _, beer := range testSeller.Beers {
+    foundBrand, brand := findStoneInfo.FindBrandById(beer.Brand)
+    if !foundBrand {
+        panic("Could not find brand")
+    }
+    fmt.Printf("Beer: %s (%s)\n", brand.Name, beer.Brand)
+}
+```
diff --git a/beer.go b/beer.go
new file mode 100644
index 0000000..7cd7684
--- /dev/null
+++ b/beer.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+	"encoding/json"
+	"strconv"
+	"time"
+)
+
+type Beer struct {
+	Brand     string    `json:"brand"`
+	Package   string    `json:"package"`
+	Delivered time.Time `json:"delivered"`
+	Expires   time.Time `json:"expires"`
+}
+
+func (b *Beer) UnmarshalJSON(data []byte) error {
+	var objmap map[string]json.RawMessage
+	err := json.Unmarshal(data, &objmap)
+	if err != nil {
+		return err
+	}
+	for key, value := range objmap {
+		if key == "brand" {
+			err = json.Unmarshal(value, &b.Brand)
+			if err != nil {
+				return err
+			}
+		} else if key == "package" {
+			err = json.Unmarshal(value, &b.Package)
+			if err != nil {
+				return err
+			}
+		} else if key == "delivered" {
+			var delivered string
+			err = json.Unmarshal(value, &delivered)
+			if err != nil {
+				return err
+			}
+			deliveredSeconds, err := strconv.ParseInt(delivered, 10, 64)
+			if err != nil {
+				return err
+			}
+			b.Delivered = time.Unix(deliveredSeconds, 0)
+		} else if key == "expires" {
+			var expires string
+			err = json.Unmarshal(value, &expires)
+			if err != nil {
+				return err
+			}
+			expiresSeconds, err := strconv.ParseInt(expires, 10, 64)
+			if err != nil {
+				return err
+			}
+			b.Expires = time.Unix(expiresSeconds, 0)
+		}
+	}
+	return nil
+}
diff --git a/find-stone.go b/find-stone.go
new file mode 100644
index 0000000..a721410
--- /dev/null
+++ b/find-stone.go
@@ -0,0 +1,138 @@
+package main
+
+import (
+	"encoding/json"
+)
+
+type Core struct {
+	Findstone Findstone `json:"findstone"`
+}
+
+func (c *Core) UnmarshalJSON(data []byte) error {
+	var objmap map[string]json.RawMessage
+	err := json.Unmarshal(data, &objmap)
+	if err != nil {
+		return err
+	}
+	for key, value := range objmap {
+		if key == "findstone" {
+			err = json.Unmarshal(value, &c.Findstone)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+type Findstone struct {
+	Brands       map[string]Brand       `json:"brands"`
+	PackageTypes map[string]PackageType `json:"package_types"`
+}
+
+func (f *Findstone) UnmarshalJSON(data []byte) error {
+	var objmap map[string]json.RawMessage
+	err := json.Unmarshal(data, &objmap)
+	if err != nil {
+		return err
+	}
+	for key, value := range objmap {
+		if key == "brands" {
+			err = json.Unmarshal(value, &f.Brands)
+			if err != nil {
+				return err
+			}
+		} else if key == "package_types" {
+			err = json.Unmarshal(value, &f.PackageTypes)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+func (f *Findstone) FindBrandById(targetId string) (bool, Brand) {
+	for id, brand := range f.Brands {
+		if id == targetId {
+			return true, brand
+		}
+		foundBrand, brand := brand.FindBrandById(targetId)
+		if foundBrand {
+			return true, brand
+		}
+	}
+	return false, Brand{}
+}
+
+type PackageType struct {
+	Name        string `json:"name"`
+	ReleaseDate int    `json:"release_date"`
+}
+
+type Brand struct {
+	Parent      *Brand
+	Name        string           `json:"name"`
+	ReleaseDate int              `json:"release_date"`
+	BrandColor  string           `json:"brand_color"`
+	BeerStyle   string           `json:"beer_style"`
+	Logo        string           `json:"logo"`
+	Abv         string           `json:"abv"`
+	Children    map[string]Brand `json:"children"`
+}
+
+func (b *Brand) UnmarshalJSON(data []byte) error {
+	brand := &struct {
+		Name        string                     `json:"name"`
+		ReleaseDate int                        `json:"release_date"`
+		BrandColor  string                     `json:"brand_color"`
+		BeerStyle   string                     `json:"beer_style"`
+		Logo        string                     `json:"logo"`
+		Abv         string                     `json:"abv"`
+		Children    map[string]json.RawMessage `json:"children"`
+	}{}
+	json.Unmarshal(data, &brand)
+
+	newBrand := Brand{
+		Name:        brand.Name,
+		ReleaseDate: brand.ReleaseDate,
+		BrandColor:  brand.BrandColor,
+		BeerStyle:   brand.BeerStyle,
+		Logo:        brand.Logo,
+		Abv:         brand.Abv,
+	}
+
+	newBrand.Children = map[string]Brand{}
+	for key, value := range brand.Children {
+		var childName string
+		err := json.Unmarshal(value, &childName)
+		if err != nil {
+			return err
+		}
+		childBrand := Brand{
+			Name:        childName,
+			ReleaseDate: brand.ReleaseDate,
+			BrandColor:  brand.BrandColor,
+			BeerStyle:   brand.BeerStyle,
+			Logo:        brand.Logo,
+			Abv:         brand.Abv,
+			Parent:      &newBrand,
+		}
+		newBrand.Children[key] = childBrand
+	}
+
+	*b = newBrand
+	return nil
+}
+
+func (b *Brand) FindBrandById(targetId string) (bool, Brand) {
+	for id, child := range b.Children {
+		if id == targetId {
+			return true, child
+		}
+		foundBrand, brand := child.FindBrandById(targetId)
+		if foundBrand {
+			return true, brand
+		}
+	}
+	return false, Brand{}
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..29e16d4
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,14 @@
+module find_stone_beer
+
+go 1.21
+
+require github.com/stretchr/testify v1.8.4
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/jftuga/geodist v1.0.0 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
+	golang.org/x/net v0.14.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..8699fbe
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,15 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/jftuga/geodist v1.0.0 h1:PFPQlZtj10u8ETAYTyxE0DWMl1bwA+Xzrqb4+oLkkC0=
+github.com/jftuga/geodist v1.0.0/go.mod h1:BohEDxpZ8S5ADAxW/9EKPSKWOVl0+3wHENIT40m4UO4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
+golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
+golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
+golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..50164ed
--- /dev/null
+++ b/main.go
@@ -0,0 +1,245 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"math"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/jftuga/geodist"
+	"golang.org/x/net/html"
+)
+
+var coords = geodist.Coord{Lat: 47.632, Lon: -122.153}
+var rangeMiles = 5
+var maxDeliveryWindowDays = 14
+
+func main() {
+	findStoneInfo, err := getFindstoneInfo()
+	if err != nil {
+		panic(err)
+	}
+
+	sellers, err := getSellers(coords)
+	if err != nil {
+		panic(err)
+	}
+
+	// filter sellers that sell arrogant bastard
+	arrogantBastardSellers := []Seller{}
+	for _, seller := range sellers {
+		for _, brandId := range seller.AllBrands {
+			foundBrand, brand := findStoneInfo.FindBrandById(brandId)
+			if foundBrand {
+				if strings.Contains(strings.ToLower(brand.Name), "arrogant") {
+					arrogantBastardSellers = append(arrogantBastardSellers, seller)
+				}
+			} else {
+				panic("Could not find brand")
+			}
+		}
+	}
+
+	messageSB := strings.Builder{}
+	now := time.Now()
+	messageSB.WriteString(fmt.Sprintf("Found %d sellers and %d Arrogant Bastard sellers\n", len(sellers), len(arrogantBastardSellers)))
+	for _, seller := range arrogantBastardSellers {
+		dist, _, err := geodist.VincentyDistance(coords, seller.Pos)
+		if err != nil {
+			panic(err)
+		}
+		if dist > float64(rangeMiles) {
+			continue
+		}
+
+		shouldPrint := false
+		sb := strings.Builder{}
+		sb.WriteString(fmt.Sprintf("%s (%d miles away) - %s\n", seller.Title, int(math.Ceil(dist)), seller.Addr))
+
+		sellerInfo, err := getSellerInfo(seller.Nid)
+		if err != nil {
+			panic(err)
+		}
+		for _, beer := range sellerInfo.Beers {
+			ok, brand := findStoneInfo.FindBrandById(beer.Brand)
+			if !ok {
+				panic("Could not find brand")
+			}
+			if !strings.Contains(strings.ToLower(brand.Name), "arrogant") {
+				continue
+			}
+			lastDeliveredDuration := now.Sub(beer.Delivered)
+			daysSinceDelivery := int(lastDeliveredDuration.Hours() / 24)
+			if daysSinceDelivery > maxDeliveryWindowDays {
+				continue
+			}
+			sb.WriteString(fmt.Sprintf("\t%s - Last delivered %d days ago, \n\n", brand.Name, daysSinceDelivery))
+			shouldPrint = true
+		}
+		if shouldPrint {
+			messageSB.WriteString(sb.String())
+			messageSB.WriteString("\n")
+		}
+	}
+
+	fmt.Print(messageSB.String())
+
+}
+
+func getSellers(coords geodist.Coord) ([]Seller, error) {
+	lat := coords.Lat
+	latInt := int(lat)
+	lon := coords.Lon
+	lonInt := int(lon)
+	url := fmt.Sprintf("https://find.stonebrewing.com/api/search/%f,%f/%d,%d", lat, lon, latInt, lonInt)
+	bytes, err := getCachedUrl(url, "find-stone-beer", "2006-01-02")
+	if err != nil {
+		return nil, err
+	}
+
+	var sellers []Seller
+	err = json.Unmarshal(bytes, &sellers)
+	if err != nil {
+		panic(err)
+	}
+	return sellers, nil
+}
+func getSellerInfo(id string) (SellerInfo, error) {
+	url := fmt.Sprintf("https://find.stonebrewing.com/api/location/%s", id)
+	bytes, err := getCachedUrl(url, fmt.Sprintf("find-stone-beer-seller-%s", id), "2006-01-02")
+	if err != nil {
+		return SellerInfo{}, err
+	}
+
+	var sellerInfo SellerInfo
+	err = json.Unmarshal(bytes, &sellerInfo)
+	if err != nil {
+		return SellerInfo{}, err
+	}
+	return sellerInfo, nil
+}
+func getFindstoneInfo() (Findstone, error) {
+	data, err := getCachedUrlFunc(
+		"https://find.stonebrewing.com",
+		"find-stone-beer-core",
+		"2006-01-02",
+		func(d []byte) ([]byte, error) {
+			reader := bytes.NewReader(d)
+			doc, err := html.Parse(reader)
+			if err != nil {
+				return nil, err
+			}
+
+			var findNodeWithText func(node *html.Node, text string) (bool, *html.Node)
+			findNodeWithText = func(node *html.Node, text string) (bool, *html.Node) {
+				if node.Type == html.TextNode && strings.Contains(node.Data, text) {
+					return true, node
+				}
+
+				if node.FirstChild != nil {
+					found, child := findNodeWithText(node.FirstChild, text)
+					if found {
+						return true, child
+					}
+				}
+
+				if node.NextSibling != nil {
+					found, sibling := findNodeWithText(node.NextSibling, text)
+					if found {
+						return true, sibling
+					}
+				}
+
+				return false, nil
+			}
+			found, node := findNodeWithText(doc, "6376")
+			if !found {
+				return nil, errors.New("could not find node")
+			}
+
+			firstCurly := strings.Index(node.Data, "{")
+			lastCurly := strings.LastIndex(node.Data, "}")
+			jsonText := node.Data[firstCurly : lastCurly+1]
+			return []byte(jsonText), nil
+		})
+	if err != nil {
+		return Findstone{}, err
+	}
+
+	var core = Core{}
+	err = json.Unmarshal(data, &core)
+	if err != nil {
+		return Findstone{}, err
+	}
+	return core.Findstone, nil
+}
+
+func getCachedUrl(url string, id string, cacheFileTimeFormat string) ([]byte, error) {
+	return getCachedUrlFunc(url, id, cacheFileTimeFormat, func(bytes []byte) ([]byte, error) {
+		return bytes, nil
+	})
+}
+func getCachedUrlFunc(url string, id string, cacheFileTimeFormat string, byteProcessor func([]byte) ([]byte, error)) ([]byte, error) {
+	now := time.Now()
+	nowFormatted := now.Format(cacheFileTimeFormat)
+	cacheFile := fmt.Sprintf("cache/%s-%s.txt", id, nowFormatted)
+	dir := filepath.Dir(cacheFile)
+	_, err := os.Stat(dir)
+	dirExists := err == nil || !os.IsNotExist(err)
+	if !dirExists {
+		err = os.Mkdir(dir, 0755)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	_, err = os.Stat(cacheFile)
+	fileExists := err == nil || !os.IsNotExist(err)
+	if fileExists {
+		f, err := os.Open(cacheFile)
+		if err != nil {
+			return nil, err
+		}
+		defer f.Close()
+		bytes, err := io.ReadAll(f)
+		if err != nil {
+			return nil, err
+		}
+		return bytes, nil
+	} else {
+		resp, err := http.Get(url)
+		if err != nil {
+			return nil, err
+		}
+		defer resp.Body.Close()
+
+		body, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return nil, err
+		}
+
+		body, err = byteProcessor(body)
+		if err != nil {
+			return nil, err
+		}
+
+		f, err := os.Create(cacheFile)
+		if err != nil {
+			return nil, err
+		}
+		defer f.Close()
+		_, err = f.Write(body)
+		if err != nil {
+			return nil, err
+		}
+
+		return body, nil
+	}
+}
diff --git a/main_test.go b/main_test.go
new file mode 100644
index 0000000..d2f3488
--- /dev/null
+++ b/main_test.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSellerInfoUmarshal(t *testing.T) {
+	jsonText := `{"phone":"4259993952","beers":[{"brand":"6396","package":"bottles_or_cans","delivered":"1689922800","expires":"1697698800"},{"brand":"6376","package":"bottles_or_cans","delivered":"1692169200","expires":"1699945200"},{"brand":"6406","package":"bottles_or_cans","delivered":"1692169200","expires":"1699945200"},{"brand":"2311","package":"cans","delivered":"1686294000","expires":"1694070000"},{"brand":"1616","package":"cans","delivered":"1685084400","expires":"1692860400"},{"brand":"6316","package":"cans","delivered":"1689922800","expires":"1697698800"},{"brand":"1551","package":"cans","delivered":"1686294000","expires":"1694070000"},{"brand":"6371","package":"bottles_or_cans","delivered":"1692169200","expires":"1699945200"},{"brand":"119","package":"bottles","delivered":"1688108400","expires":"1695884400"},{"brand":"5911","package":"cans","delivered":"1689318000","expires":"1697094000"},{"brand":"2121","package":"bottles","delivered":"1692342000","expires":"1700118000"}],"lat":"47.629479500000","lon":"-122.141579200000","json":null}`
+
+	sellerInfo := SellerInfo{}
+	err := json.Unmarshal([]byte(jsonText), &sellerInfo)
+	assert.Nil(t, err)
+}
diff --git a/seller-info.go b/seller-info.go
new file mode 100644
index 0000000..ff8b25f
--- /dev/null
+++ b/seller-info.go
@@ -0,0 +1,49 @@
+package main
+
+import (
+	"encoding/json"
+	"strconv"
+
+	"github.com/jftuga/geodist"
+)
+
+type SellerInfo struct {
+	Phone string
+	Beers []Beer
+	// Lat   float64
+	// Lon   float64
+	Pos  geodist.Coord
+	JSON any
+}
+
+func (s *SellerInfo) UnmarshalJSON(data []byte) error {
+	sellerInfo := &struct {
+		Phone string `json:"phone"`
+		Beers []Beer `json:"beers"`
+		Lat   string `json:"lat"`
+		Lon   string `json:"lon"`
+		JSON  any    `json:"json"`
+	}{}
+	json.Unmarshal(data, &sellerInfo)
+
+	newSellerInfo := SellerInfo{
+		Phone: sellerInfo.Phone,
+		Beers: sellerInfo.Beers,
+		JSON:  sellerInfo.JSON,
+	}
+
+	if len(sellerInfo.Lat) > 0 && len(sellerInfo.Lon) > 0 {
+		lat, err := strconv.ParseFloat(sellerInfo.Lat, 64)
+		if err != nil {
+			return err
+		}
+		lon, err := strconv.ParseFloat(sellerInfo.Lon, 64)
+		if err != nil {
+			return err
+		}
+		newSellerInfo.Pos = geodist.Coord{Lat: lat, Lon: lon}
+	}
+
+	*s = newSellerInfo
+	return nil
+}
diff --git a/seller.go b/seller.go
new file mode 100644
index 0000000..7f7cf2e
--- /dev/null
+++ b/seller.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+	"encoding/json"
+	"strconv"
+
+	"github.com/jftuga/geodist"
+)
+
+type Seller struct {
+	Nid              string
+	Title            string
+	Addr             string
+	Addr1            string
+	Addr2            string
+	Distance         float64
+	Phone            string
+	Cot              string
+	Pos              geodist.Coord
+	// Lat              float64
+	// Lng              float64
+	LocationCategory string
+	AllBrands        []string
+	AllPackages      []string
+}
+
+func (s *Seller) UnmarshalJSON(data []byte) error {
+	seller := &struct {
+		Nid              string   `json:"nid"`
+		Title            string   `json:"title"`
+		Addr             string   `json:"addr"`
+		Addr1            string   `json:"addr_1"`
+		Addr2            string   `json:"addr_2"`
+		Distance         string   `json:"distance"`
+		Phone            string   `json:"phone"`
+		Cot              string   `json:"cot"`
+		Lat              string   `json:"lat"`
+		Lng              string   `json:"lng"`
+		LocationCategory string   `json:"location_category"`
+		AllBrands        []string `json:"all_brands"`
+		AllPackages      []string `json:"all_packages"`
+	}{}
+	json.Unmarshal(data, &seller)
+
+	newSeller := Seller{
+		Nid:              seller.Nid,
+		Title:            seller.Title,
+		Addr:             seller.Addr,
+		Addr1:            seller.Addr1,
+		Addr2:            seller.Addr2,
+		Phone:            seller.Phone,
+		Cot:              seller.Cot,
+		LocationCategory: seller.LocationCategory,
+		AllBrands:        seller.AllBrands,
+		AllPackages:      seller.AllPackages,
+	}
+
+	if len(seller.Distance) != 0 {
+		dist, err := strconv.ParseFloat(seller.Distance, 64)
+		if err != nil {
+			return err
+		}
+		newSeller.Distance = dist
+	}
+
+	if len(seller.Lat) > 0 && len(seller.Lng) > 0 {
+		lat, err := strconv.ParseFloat(seller.Lat, 64)
+		if err != nil {
+			return err
+		}
+		
+		lng, err := strconv.ParseFloat(seller.Lng, 64)
+		if err != nil {
+			return err
+		}
+
+		newSeller.Pos = geodist.Coord{Lat: lat, Lon: lng}
+	}
+
+	*s = newSeller
+	return nil
+}