hlfw.ca

webbing

Download patch

ref: acc962a0381815da8dd9f4c42e0ca244084be432
parent: 3dbf2f21c2fd84309d71826d053906e54b40c2f3
author: Moses Olson, MD <52055478+nemo-olmax@users.noreply.github.com>
date: Mon Aug 12 06:51:47 PDT 2019

Add files via upload

--- /dev/null
+++ b/router/countries.go
@@ -1,0 +1,62 @@
+package router
+
+import (
+	"sort"
+	"strings"
+
+	"github.com/pariz/gountries"
+)
+
+type Country struct {
+	ID   string
+	Name string
+}
+
+type countries struct {
+	list []gountries.Country
+}
+
+var country *countries
+
+func init() {
+	var l []gountries.Country
+	c := gountries.New()
+	for _, c := range c.FindAllCountries() {
+		l = append(l, c)
+	}
+	country = &countries{
+		list: l,
+	}
+	sort.Sort(country)
+}
+
+func (c *countries) Len() int {
+	return len(c.list)
+}
+
+func (c *countries) Less(i, j int) bool {
+	switch strings.Compare(c.list[i].Name.Common, c.list[j].Name.Common) {
+	case -1:
+		return true
+	default:
+		return false
+	}
+}
+
+func (c *countries) Swap(i, j int) {
+	tmp := c.list[i]
+	c.list[i] = c.list[j]
+	c.list[j] = tmp
+}
+
+// TODO: Filter out any countries we don't support
+func listcountries() []Country {
+	var c []Country
+	for _, item := range country.list {
+		c = append(c, Country{
+			ID:   item.Name.Common,
+			Name: item.Name.Common,
+		})
+	}
+	return c
+}
--- /dev/null
+++ b/router/doctor.go
@@ -1,0 +1,37 @@
+package router
+
+type doctor struct {
+	Image string
+	AlmaMater string
+	Name string
+	Residency string
+	Current string
+	Country string
+	Specialty string
+	Rate string
+}
+
+func listdoctors() []doctor {
+	return []doctor{
+		{
+			Image:     "AbuzamzamMD.jpg",
+			AlmaMater: "University of Southern California School of Medicine",
+			Residency: "University of Southern California, San Diego. Internal Medicine Residency",
+			Name:      "Mark Abuzamzam, MD",
+			Current:   "Current Faculty at University of California Irvine Program Director",
+			Country:   "United States of America",
+			Specialty: "Internal Medicine and Addictions Medicine",
+			Rate:      "0.0013 BTC",
+		},
+		{
+			Image:     "WoodfinMD.jpg",
+			Name:      "Martha Woodfin, MD",
+			AlmaMater: "University Seoul School of Medicine",
+			Residency: "University of Las Vegas Nevada, Pediatric Medicine Residency",
+			Current:   "Current Staff at Mercy Hospital Jackson NC",
+			Country:   "United States of America",
+			Specialty: "Internal Medicine",
+			Rate:      "0.0011 BTC",
+		},
+	}
+}
\ No newline at end of file
--- /dev/null
+++ b/router/footer.go
@@ -1,0 +1,25 @@
+package router
+
+import (
+	"golang.org/x/text/message"
+)
+
+func footer(p *message.Printer) map[string]string {
+	return map[string]string{
+		"faq":         p.Sprintf("FAQ"),		
+		"help":        p.Sprintf("Help"),
+		"banner":      p.Sprintf("Over 12B patients served"),
+		"pay":         p.Sprintf("Payment Methods"),
+		"fees":        p.Sprintf("Prices and Fees"),
+		"verify":      p.Sprintf("Verification"),
+		"appt":        p.Sprintf("Appointments"),
+		"legal":       p.Sprintf("Legal"),
+		"privacy":     p.Sprintf("Privacy Policy"),
+		"aboutHeader": p.Sprintf("About Us"),
+		"about":       p.Sprintf("Olmax"),
+		"partHead":    p.Sprintf("Partnering"),
+		"partner":     p.Sprintf("Become A Partner"),
+		"provider":    p.Sprintf("Are You A Doctor?"),
+		"copy":        p.Sprintf("Copyright 2017, 2018, 2019"),
+	}
+}
--- /dev/null
+++ b/router/forms.go
@@ -1,0 +1,122 @@
+package router
+
+import (
+	"fmt"
+	"net/http"
+	"golang.org/x/text/message"
+	"olmax/db"
+	"olmax/session"
+)
+
+var formlist map[string]*Form
+
+type After uint8
+
+const (
+	ValidateLogin After = 1 << iota
+	SendSignup
+	SendReset
+	SetPassword
+)
+
+type Form struct {
+	Access    Access
+	After     After
+	Path      string
+	Redirect  string
+	Validator func(r *http.Request, p *message.Printer) []string
+}
+
+func init() {
+	formlist = make(map[string]*Form)
+}
+
+func AddPost(f *Form) {
+	formlist[f.Path + ".html"] = f
+}
+
+func parseform(p *page, w http.ResponseWriter, r *http.Request) []string {
+	var errors []string
+	form, ok := formlist[p.path]
+	if ! ok {
+		errors = append(errors, "No such page")
+		return errors
+	}
+	e := form.Validator(r, p.printer)
+	if len(e) > 0 {
+		errors = append(errors, e...)
+	}
+	var msg string
+	if form.After&ValidateLogin != 0 {
+		e = validateLogin(p.printer, p.session, r)
+		if len(e) > 0 {
+			errors = append(errors, e...)
+		}	
+	}
+	if form.After&SendSignup != 0 {
+		msg = signupEmail(p.printer, r)
+	}
+	if form.After&SendReset != 0 {
+		msg = resetPassword(p.printer, r)
+	}
+	if form.After&SetPassword != 0 {
+		e = setPassword(p.printer, p.session, r)
+		if len(e) > 0 {
+			errors = append(errors, e...)
+		}
+	}
+	if msg != "" {
+		fmt.Fprintf(w, "%s\n", msg)
+		return errors
+	}
+	if len(errors) == 0 {
+		http.Redirect(w, r, form.Redirect, 302)
+	}
+	return errors
+}
+
+func setPassword(p *message.Printer, us session.Session, r *http.Request) []string {
+	var errors []string
+	pass := r.PostFormValue("password")
+	repeat := r.PostFormValue("reenter")
+	if pass != repeat {
+		errors = append(errors, p.Sprint("Passwords do not match"))
+		return errors
+	}
+	token := r.PostFormValue("token")
+	if ! db.FindEntry(token) {
+		errors = append(errors, p.Sprint("Session expired"))
+		return errors
+	}
+	db.UpdateUserPassword(token, pass)
+	return errors
+}
+
+func validateLogin(p *message.Printer, us session.Session, r *http.Request) []string {
+	var errors []string
+	user := r.PostFormValue("email")
+	pass := r.PostFormValue("pass")
+	if db.ValidateLogin(user, pass) {
+		us.Set("username", user)
+		us.Set("login", "true")
+		return errors
+	}
+	errors = append(errors, p.Sprint("Invalid username or password"))
+	return errors
+}
+
+func signupEmail(p *message.Printer, r *http.Request) string {
+	first := r.PostFormValue("fname")
+	last := r.PostFormValue("lname")
+	email := r.PostFormValue("email")
+	pass := r.PostFormValue("pass")
+	em := sendsignup(first, last, email, pass, p)
+	return p.Sprintf("An email has been sent to the following email with instructions on finalizing your account creation: %s\n", em)
+}
+
+func resetPassword(p *message.Printer, r *http.Request) string{
+	em := sendreset(r.PostFormValue("email"), p)
+	return p.Sprintf("An email has been sent to the following email with a link to reset your password: %s\n", em)
+}
+
+
--- /dev/null
+++ b/router/header.go
@@ -1,0 +1,31 @@
+package router
+
+import (
+	"golang.org/x/text/message"
+)
+
+func header(p *message.Printer, status string) map[string]string {
+	return map[string]string{
+		// These go away, in the layout.go they'll be called these values added
+		"doctors":   p.Sprintf("Doctors"),
+		"provider":  p.Sprintf("Become A Provider"),
+		"whodoctor": p.Sprintf("Who can become a doctor"),
+		"howworks":  p.Sprintf("How It Works"),
+		"contact":   p.Sprintf("Contact Us"),
+		"faq":       p.Sprintf("FAQ"),
+		"pricing":   p.Sprintf("Pricing"),
+		"appts":     p.Sprintf("Appointments"),
+		"proc":      p.Sprintf("Payment Procedures"),
+		"payments":  p.Sprintf("Payment Methods"),
+		"fees":      p.Sprintf("Prices and Fees"),
+		"verify":    p.Sprintf("Verification"),
+		"phone":     p.Sprintf("Call toll free"),
+		"number":    p.Sprintf("1(555)555-1234"),
+		"email":     p.Sprintf("Email"),
+		"login":     p.Sprintf("Login"),
+		"logout":    p.Sprintf("Logout"),
+		"signup":    p.Sprintf("Sign Up"),
+		"profile":   p.Sprintf("Profile"),
+		"status":    status,
+	}
+}
--- /dev/null
+++ b/router/pages.go
@@ -1,0 +1,133 @@
+package router
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"html/template"
+	"path"
+	"strings"
+
+	"golang.org/x/text/message"
+)
+
+var pagecache map[string]*Page
+var countrylist []Country
+
+func init() {
+	pagecache = make(map[string]*Page)
+	countrylist = listcountries()
+}
+
+type Access uint8
+
+const (
+	GuestAuth Access = 1 << iota
+	PatientAuth
+	DoctorAuth
+)
+
+type IncludeExtra uint8
+
+const (
+	ListDoctors IncludeExtra = 1 << iota
+	ListServices
+	ListCountries
+	OneTimeToken
+)
+
+type Page struct {
+	Access Access
+	Extra  IncludeExtra
+	Css    string
+	Path   string
+	Data   func(p *message.Printer) map[string]interface{}
+	tmpl   *template.Template
+}
+
+func Add(p *Page) {
+	pagecache[p.Path+".html"] = p
+}
+
+// Walk all our templates and finally return applicable errors as an array
+func ValidateAndCache() []error {
+	var errs []error
+	hd := path.Join("templates", "header.tpl")
+	fd := path.Join("templates", "footer.tpl")
+	ld := path.Join("templates", "layout.tpl")
+	printer := message.NewPrinter(message.MatchLanguage("en"))
+	for _, item := range pagecache {
+		var err error
+		tp := path.Join("templates", item.Path) + ".tpl"
+
+		t := template.New(path.Base(tp))
+		item.tmpl, err = t.ParseFiles(tp, hd, fd, ld)
+		if err != nil {
+			errs = append(errs, fmt.Errorf("parsing in %s - %v", path.Dir(item.Path), err))
+			continue
+		}
+		p := &page{
+			printer: printer,
+			path: item.Path + ".html",
+		}
+		_, err = getdata(p, "")
+		if err != nil {
+			errs = append(errs, err)
+		}
+	}
+	return errs
+}
+
+func getdata(p *page, in string) ([]byte, error) {
+        cache, ok := pagecache[p.path]
+	if ! ok {
+		return nil,  fmt.Errorf("No such page: %s", p.path)
+	}
+	i := cache.Data(p.printer)
+	i["css"]     = cache.Css
+	i["header"]  = header(p.printer, p.status)
+	i["footer"]  = footer(p.printer)
+	i["basedir"] = getBaseDir(cache.Path)
+	if cache.Extra&ListDoctors != 0 {
+		i["doctors"] = listdoctors()
+	}
+	if cache.Extra&ListServices != 0 {
+		i["specialties"] = specialties(p.printer)
+	}
+	if cache.Extra&ListCountries != 0 {
+		i["countrylist"] = countrylist
+	}
+	if p.session != nil && cache.Extra&OneTimeToken != 0 {
+		i["token"] = p.session.Get("token")
+	}
+	//if cache.Extra&ClientName != 0 {
+	//	i["firstname"] = db.ClientName(p.session)
+	//}
+	//if cache.Extra&ClientSurname != 0 {
+	//	i["surname"] = db.ClientSurname(p.session)
+	//}
+	//if cache.Extra&ClientUsername != 0 {
+	//	i["username"] = db.ClientUsername(p.session)
+	//}
+	//if cache.Extra&FormErrors != 0 && p.session != nil {
+	//	i["errors"] = p.session.Get("errors")
+	//}
+	if p.session != nil {
+		i["username"] = p.session.Get("username")
+		i["errors"] = p.session.Get("errors")
+	}
+	return cache.render(i)
+}
+
+func (page *Page) render(i map[string]interface{}) ([]byte, error) {
+	var buf bytes.Buffer
+	data := bufio.NewWriter(&buf)
+	err := page.tmpl.ExecuteTemplate(data, "layout", i)
+	data.Flush()
+	return buf.Bytes(), err
+	
+}
+
+func getBaseDir(fp string) string {
+	return strings.Repeat("../", strings.Count(fp, "/"))	
+}
--- /dev/null
+++ b/router/reset.go
@@ -1,0 +1,60 @@
+package router
+
+import (
+	"bytes"
+	"fmt"
+	"log"
+	"net/smtp"
+	"time"
+
+	"github.com/google/uuid"
+	"golang.org/x/text/message"
+	"olmax/db"
+)
+
+func nextResetToken(old, user string) string {
+	if db.FindTempEntry(old) {
+		db.RemoveTempEntry(old)
+		u, _ := uuid.NewRandom()
+		token := u.String()
+		db.CreateTempEntry("", "", user, "", token)
+		go func() {
+			time.Sleep(time.Minute * 10)
+			db.RemoveTempEntry(token)
+		}()
+		return token
+	}
+	return ""
+}
+
+func sendreset(email string, p *message.Printer) string {
+	u, _ := uuid.NewRandom()
+	token := u.String()
+	if  db.UserExists(email) {	
+		db.CreateTempEntry("", "", email, "", token)
+		resetemail(token, email, p)
+		go func() {
+			time.Sleep(time.Minute * 10)
+			db.RemoveTempEntry(token)
+		}()
+	}
+	return "<a href=\"https://mail.google.com/mail/u?authuser=" + email + "\"/>"
+}
+
+func resetemail(token string, sendto string, p *message.Printer) {
+	var msg bytes.Buffer
+	msg.WriteString("From: ")
+	msg.WriteString("olmaxmedical@gmail.com" + "\n")
+	msg.WriteString("To: ")
+	msg.WriteString(sendto + "\n")
+	msg.WriteString(p.Sprintf("Subject: Olmax Medical - Reset Your Password\n\n"))
+	msg.WriteString(p.Sprintf("Please click the following link to reset your password "))
+	msg.WriteString(fmt.Sprintf("%s/reset/%s\n", url, token))
+	err := smtp.SendMail("smtp.gmail.com:587",
+		smtp.PlainAuth("", "olmaxmedical@gmail.com", "hunter2", "smtp.gmail.com"),
+		"olmaxmedical@gmail.com", []string{sendto}, msg.Bytes(),
+	)
+	if err != nil {
+		log.Printf("smtp error: %v", err)
+	}
+}
--- /dev/null
+++ b/router/run.go
@@ -1,0 +1,170 @@
+package router
+
+import (
+	"fmt"
+	"net/http"
+
+	"golang.org/x/text/message"
+	"olmax/db"
+	"olmax/session"
+)
+
+type handle struct {
+	manager *session.Manager
+}
+
+func Route(manager *session.Manager) error {
+	d := &handle {
+		manager: manager,
+	}
+	css := http.FileServer(http.Dir("resources/css/"))
+	jss := http.FileServer(http.Dir("resources/scripts"))
+	img := http.FileServer(http.Dir("resources/images/"))
+	mux := http.NewServeMux()
+	mux.Handle("/css/", http.StripPrefix("/css/", css))
+	mux.Handle("/scripts/", http.StripPrefix("/scripts/", jss))
+	mux.Handle("/images/", http.StripPrefix("/images/", img))
+	mux.HandleFunc("/activate/", d.activate)
+	mux.HandleFunc("/reset/", d.reset)
+	mux.HandleFunc("/logout.html", d.logout)
+	mux.HandleFunc("/profile.html", d.profile)
+	mux.HandleFunc("/", d.normal)
+	return http.ListenAndServe(":80", mux)
+}
+
+func (d *handle) activate(w http.ResponseWriter, r *http.Request) {
+	if len(r.URL.Path) != 46 && r.URL.Path[:9] != "/activate" {
+		http.Error(w, "Bad Request", 400)
+		return
+	}
+	validateSignupToken(w, r, r.URL.Path[10:])
+}
+
+func (d *handle) reset(w http.ResponseWriter, r *http.Request) {
+	if len(r.URL.Path) != 43 && r.URL.Path[:6] != "/reset" {
+		http.Error(w, "Bad Request", 400)
+		return
+	}
+	p := userLang(r)
+	user, _, us := getUser(d, w, r)
+	token := nextResetToken(r.URL.Path[7:], user)
+	if token == "" {
+		us.Set("errors", [1]string{p.Sprint("Token expired")})
+		return
+	}
+	us.Set("token", token)
+	r.URL.Path = "/newpassword.html"
+	d.normal(w, r)
+}
+
+type page struct {
+	printer *message.Printer
+	session session.Session
+	user    string
+	status  string
+	path    string
+}
+
+func (d *handle) normal(w http.ResponseWriter, r *http.Request) {
+	if r.URL.Path == "/" {
+		http.Redirect(w, r, "/index.html", 302)
+		return
+	}
+	user, status, us := getUser(d, w, r)
+	p := &page {
+		printer: userLang(r),
+		status: status,
+		user: user,
+		session: us,
+		path: r.URL.Path[1:],
+	}
+	switch r.Method {
+	case "GET":
+		get(p, w)
+	case "POST":
+		post(p, us, w, r);
+	}
+}
+
+func (d *handle) logout(w http.ResponseWriter, r *http.Request) {
+	d.manager.SessionDestroy(w, r)
+	http.Redirect(w, r, "/index.html", 302)
+	
+}
+
+func post(p *page, us session.Session, w http.ResponseWriter, r *http.Request) {
+	errors := parseform(p, w, r)
+	if len(errors) > 0 {
+		us.Set("errors", errors)
+		get(p, w)
+	}
+}
+
+func get(p *page, w http.ResponseWriter) {
+	var data []byte
+	var err error
+	switch db.UserRole(p.user) {
+	case db.DoctorAuth:
+		data, err = getdata(p, "doctor")
+	case db.PatientAuth:
+		data, err = getdata(p, "patient")
+	default:
+		data, err = getdata(p, "guest")
+	}
+	if err != nil {
+		http.Error(w, "Service Unavailable", 503)
+		return
+	}
+	fmt.Fprintf(w, "%s", data)	
+}
+
+func userLang(r *http.Request) *message.Printer {
+	accept := r.Header.Get("Accept-Language")
+	lang := r.FormValue("lang")
+	tag := message.MatchLanguage(lang, accept)
+	return message.NewPrinter(tag)
+}
+
+func getUser(d *handle, w http.ResponseWriter, r *http.Request) (string, string, session.Session) {
+	us := d.manager.SessionStart(w, r)
+	user, ok1 := us.Get("username").(string)
+	status, ok2 := us.Get("login").(string)
+	if ! ok1 || ! ok2 || status != "true" {
+		status = "false"
+	}
+	return user, status, us
+}
+
+// TODO: This will require actual client data from the database to populate the page
+func (d *handle) profile(w http.ResponseWriter, r *http.Request) {
+	user, status, us := getUser(d, w, r)
+	if status == "false" {
+		http.Error(w, "Unauthorized", 401)
+		return
+	}
+	p := &page {
+		printer: userLang(r),
+		status: status,
+		session: us,
+		user: user,
+	}
+	var data []byte
+	var err error
+	switch db.UserRole(user) {
+	case db.DoctorAuth:
+		p.path = "doctor/profile.html"
+		data, err = getdata(p, "doctor")
+	case db.PatientAuth:
+		p.path = "patient/profile.html"
+		data, err = getdata(p, "patient")
+	default:
+		http.Error(w, "Forbidden", 403)
+		return
+	}
+	if err != nil {
+		http.Error(w, "Service Unavailable", 503)
+		return
+	}
+	fmt.Fprintf(w, "%s", data)	
+}
+
--- /dev/null
+++ b/router/signup.go
@@ -1,0 +1,59 @@
+package router
+
+import (
+	"bytes"
+	"fmt"
+	"log"
+	"net/http"
+	"net/smtp"
+	"time"
+
+	"github.com/google/uuid"
+	"golang.org/x/text/message"
+	"olmax/db"
+)
+
+var url = "http://192.168.1.101"
+
+func sendsignup(first, last, email, pass string, p *message.Printer) string {
+	if ! db.UserExists(email) {
+		u, _ := uuid.NewRandom()
+		token := u.String()
+		db.CreateTempEntry(first, last, email, pass, token)
+		signupemail(token, email, p)
+		go func() {
+			// Blow away the entry unconditionally after 10 minutes
+			time.Sleep(time.Minute * 10)
+			db.RemoveTempEntry(token)
+		}()
+	}
+	return "<a href=\"https://mail.google.com/mail/u?authuser=" + email + "\"/>"
+}
+
+func validateSignupToken(w http.ResponseWriter, r *http.Request, token string) {
+	if db.FindTempEntry(token) {
+		db.CreateEntry(token)
+		http.Redirect(w, r, "/login.html", 302)
+		return
+	}
+	http.Error(w, "Bad Request", 400)
+
+}
+
+func signupemail(token string, sendto string, p *message.Printer) {
+	var msg bytes.Buffer
+	msg.WriteString("From: ")
+	msg.WriteString("olmaxmedical@gmail.com" + "\n")
+	msg.WriteString("To: ")
+	msg.WriteString(sendto + "\n")
+	msg.WriteString(p.Sprintf("Subject: Olmax Medical - Verify your new account\n\n"))
+	msg.WriteString(p.Sprintf("Please click the following link to finalize your account creation "))
+	msg.WriteString(fmt.Sprintf("%s/activate/%s\n", url, token))
+	err := smtp.SendMail("smtp.gmail.com:587",
+		smtp.PlainAuth("", "olmaxmedical@gmail.com", "hunter2", "smtp.gmail.com"),
+		"olmaxmedical@gmail.com", []string{sendto}, msg.Bytes(),
+	)
+	if err != nil {
+		log.Printf("smtp error: %v", err)
+	}
+}
--- /dev/null
+++ b/router/specialties.go
@@ -1,0 +1,51 @@
+package router
+
+import (
+	"golang.org/x/text/message"
+)
+
+// TODO: inverse function to get the actual specialty back from a whitelist
+type Specialty struct {
+	ID   string
+	Name string
+}
+
+func specialties(p *message.Printer) []Specialty {
+	return []Specialty{
+		{"acutepain", p.Sprintf("Acute Pain Medicine")},
+		{"anesthesiology", p.Sprintf("Anesthesiology")},
+		{"bariatric", p.Sprintf("Bariatric Surgery")},
+		{"cardiology", p.Sprintf("Cardiology")},
+		{"chiropractic", p.Sprintf("Chiropractics")},
+		{"chronic", p.Sprintf("Chronic Pain")},
+		{"critcare", p.Sprintf("Chronic Pain")},
+		{"dermatology", p.Sprintf("Dermatology")},
+		{"emergency", p.Sprintf("Emergency Medicine")},
+		{"endocrinology", p.Sprintf("Endocrinology")},
+		{"otolaringology", p.Sprintf("Ear Nose and Throat")},
+		{"familymedicine", p.Sprintf("Family Medicine")},
+		{"gastro", p.Sprintf("Gastrointestinology")},
+		{"headneck", p.Sprintf("Head and Neck")},
+		{"hematology", p.Sprintf("Hematology and Oncology")},
+		{"hepatology", p.Sprintf("Hepatology")},
+		{"hyperbaric", p.Sprintf("Hyperbaric")},
+		{"immunology", p.Sprintf("Immunology")},
+		{"diseases", p.Sprintf("Infectious Diseases")},
+		{"internal", p.Sprintf("Internal Medicine")},
+		{"neonatal", p.Sprintf("Neonatology")},
+		{"nephrology", p.Sprintf("Nephrology")},
+		{"neurology", p.Sprintf("Neurology")},
+		{"neurosurgery", p.Sprintf("Neurosurgery")},
+		{"obstetrics", p.Sprintf("Obstetrics and Gynecology")},
+		{"occupational", p.Sprintf("Occupational Medicine")},
+		{"opthamology", p.Sprintf("Opthamology")},
+		{"orthopedics", p.Sprintf("Orthopedic Surgery")},
+		{"palliative", p.Sprintf("Palliative Care")},
+		{"pediatrics", p.Sprintf("Pediatrics")},
+		{"podiatry", p.Sprintf("Podiatry")},
+		{"pulmonology", p.Sprintf("Pulmonology")},
+		{"radiology", p.Sprintf("Radiology")},
+		{"radiation", p.Sprintf("Radiaton Oncology")},
+		{"transplants", p.Sprintf("Transplant Surgery")},
+	}
+}