Example #1
0
// This application will create a new record set in the patient matching test
// harness. This record set will be associated with a FHIR resource tag.
// It will generate a pair of matching patient records, apply the tag, and
// then upload them to the FHIR server. It will also upload an answer key.
func main() {
	fhirURL := flag.String("fhirURL", "", "URL for the patient matching test harness server")
	recordSetName := flag.String("name", "", "Name of the record set")

	flag.Parse()

	trimmedFhirURL := strings.TrimRight(*fhirURL, "/")

	recordSet := &ptm_models.RecordSet{Name: *recordSetName}

	recordSet.ResourceType = "Patient"
	recordSet.Parameters = generateRecordSetParameters(trimmedFhirURL, *recordSetName)

	recordSetURL := trimmedFhirURL + "/RecordSet"
	rsj, err := json.Marshal(recordSet)
	if err != nil {
		return
	}
	body := bytes.NewReader(rsj)
	resp, err := http.Post(recordSetURL, "application/json", body)
	if err != nil {
		fmt.Printf("Couldn't upload the resource: %s\n", err.Error())
		return
	}
	decoder := json.NewDecoder(resp.Body)
	decoder.Decode(recordSet)

	fuzzers := []PatientFuzzer{AbbreviateFirstName, TransposeBirthDayMonth, ShuffleLastName}
	tagCoding := models.Coding{System: tagURL, Code: tagValue(*recordSetName)}
	meta := &models.Meta{Tag: []models.Coding{tagCoding}}
	patientURL := trimmedFhirURL + "/Patient"
	var matches []Match
	for i := 0; i < 300; i++ {
		patient := ptgen.GenerateDemographics()
		patient.Meta = meta
		source, err := PostAndGetLocation(patient, patientURL)
		if err != nil {
			return
		}

		fuzzer := fuzzers[rand.Intn(3)]
		copy := CopyPatient(&patient)
		fuzzer(copy)
		target, err := PostAndGetLocation(copy, patientURL)
		if err != nil {
			return
		}
		matches = append(matches, Match{source, target})
	}
	bundle := &models.Bundle{}
	bundle.Type = "document"
	bundle.Id = bson.NewObjectId().Hex()
	comp := models.Composition{}
	comp.Date = &models.FHIRDateTime{Time: time.Now(), Precision: models.Timestamp}
	comp.Type = &models.CodeableConcept{Coding: []models.Coding{models.Coding{System: "http://loinc.org", Code: "11503-0"}}}
	comp.Title = "Answer Key for " + *recordSetName
	comp.Status = "final"
	comp.Subject = &models.Reference{Reference: fmt.Sprintf("%s/RecordSet/%s", trimmedFhirURL, recordSet.ID.Hex())}
	compEntry := models.BundleEntryComponent{}
	compEntry.Resource = &comp
	bundle.Entry = append(bundle.Entry, compEntry)
	for _, match := range matches {
		entry := models.BundleEntryComponent{}
		entry.FullUrl = match.Source
		linkType := models.BundleLinkComponent{Relation: "type", Url: "http://hl7.org/fhir/Patient"}
		linkRelated := models.BundleLinkComponent{Relation: "related", Url: match.Target}
		entry.Link = []models.BundleLinkComponent{linkType, linkRelated}
		score := 1.0
		entry.Search = &models.BundleEntrySearchComponent{Score: &score}
		bundle.Entry = append(bundle.Entry, entry)
	}
	PostAnswerKey(bundle, recordSet.ID.Hex(), trimmedFhirURL+"/AnswerKey")
}
func (dal *mongoDataAccessLayer) Search(baseURL url.URL, searchQuery search.Query) (*models.Bundle, error) {
	searcher := search.NewMongoSearcher(dal.Database)

	var result interface{}
	var err error
	usesIncludes := len(searchQuery.Options().Include) > 0
	usesRevIncludes := len(searchQuery.Options().RevInclude) > 0
	// Only use (slower) pipeline if it is needed
	if usesIncludes || usesRevIncludes {
		result = models.NewSlicePlusForResourceName(searchQuery.Resource, 0, 0)
		err = searcher.CreatePipeline(searchQuery).All(result)
	} else {
		result = models.NewSliceForResourceName(searchQuery.Resource, 0, 0)
		err = searcher.CreateQuery(searchQuery).All(result)
	}
	if err != nil {
		return nil, convertMongoErr(err)
	}

	includesMap := make(map[string]interface{})
	var entryList []models.BundleEntryComponent
	resultVal := reflect.ValueOf(result).Elem()
	for i := 0; i < resultVal.Len(); i++ {
		var entry models.BundleEntryComponent
		entry.Resource = resultVal.Index(i).Addr().Interface()
		entry.Search = &models.BundleEntrySearchComponent{Mode: "match"}
		entryList = append(entryList, entry)

		if usesIncludes || usesRevIncludes {
			rpi, ok := entry.Resource.(ResourcePlusRelatedResources)
			if ok {
				for k, v := range rpi.GetIncludedAndRevIncludedResources() {
					includesMap[k] = v
				}
			}
		}
	}

	for _, v := range includesMap {
		var entry models.BundleEntryComponent
		entry.Resource = v
		entry.Search = &models.BundleEntrySearchComponent{Mode: "include"}
		entryList = append(entryList, entry)
	}

	var bundle models.Bundle
	bundle.Id = bson.NewObjectId().Hex()
	bundle.Type = "searchset"
	bundle.Entry = entryList

	options := searchQuery.Options()

	// Need to get the true total (not just how many were returned in this response)
	var total uint32
	if resultVal.Len() == options.Count || resultVal.Len() == 0 {
		// Need to get total count from the server, since there may be more or the offset was too high
		intTotal, err := searcher.CreateQueryWithoutOptions(searchQuery).Count()
		if err != nil {
			return nil, convertMongoErr(err)
		}
		total = uint32(intTotal)
	} else {
		// We can figure out the total by adding the offset and # results returned
		total = uint32(options.Offset + resultVal.Len())
	}
	bundle.Total = &total

	// Add links for paging
	bundle.Link = generatePagingLinks(baseURL, searchQuery, total)

	return &bundle, nil
}
Example #3
0
func (rc *ResourceController) IndexHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	defer func() {
		if r := recover(); r != nil {
			rw.Header().Set("Content-Type", "application/json; charset=utf-8")
			switch x := r.(type) {
			case search.Error:
				rw.WriteHeader(x.HTTPStatus)
				json.NewEncoder(rw).Encode(x.OperationOutcome)
				return
			default:
				outcome := &models.OperationOutcome{
					Issue: []models.OperationOutcomeIssueComponent{
						models.OperationOutcomeIssueComponent{
							Severity: "fatal",
							Code:     "exception",
						},
					},
				}
				rw.WriteHeader(http.StatusInternalServerError)
				json.NewEncoder(rw).Encode(outcome)
			}
		}
	}()

	result := models.NewSliceForResourceName(rc.Name, 0, 0)

	// Create and execute the Mongo query based on the http query params
	searcher := search.NewMongoSearcher(Database)
	searchQuery := search.Query{Resource: rc.Name, Query: r.URL.RawQuery}
	mgoQuery := searcher.CreateQuery(searchQuery)

	// Horrible, horrible hack (for now) to ensure patients are sorted by name.  This is needed by
	// the frontend, else paging won't work correctly.  This should be removed when the general
	// sorting feature is implemented.
	if rc.Name == "Patient" {
		// To add insult to injury, mongo will not let us sort by family *and* given name:
		// Executor error: BadValue cannot sort with keys that are parallel arrays
		mgoQuery = mgoQuery.Sort("name.0.family.0" /*", name.0.given.0"*/, "_id")
	}

	err := mgoQuery.All(result)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	var entryList []models.BundleEntryComponent
	resultVal := reflect.ValueOf(result).Elem()
	for i := 0; i < resultVal.Len(); i++ {
		var entry models.BundleEntryComponent
		entry.Resource = resultVal.Index(i).Addr().Interface()
		entryList = append(entryList, entry)
	}

	var bundle models.Bundle
	bundle.Id = bson.NewObjectId().Hex()
	bundle.Type = "searchset"
	bundle.Entry = entryList

	options := searchQuery.Options()

	// Need to get the true total (not just how many were returned in this response)
	var total uint32
	if resultVal.Len() == options.Count || resultVal.Len() == 0 {
		// Need to get total count from the server, since there may be more or the offset was too high
		intTotal, err := searcher.CreateQueryWithoutOptions(searchQuery).Count()
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
		}
		total = uint32(intTotal)
	} else {
		// We can figure out the total by adding the offset and # results returned
		total = uint32(options.Offset + resultVal.Len())
	}
	bundle.Total = &total

	// Add links for paging
	bundle.Link = generatePagingLinks(r, searchQuery, total)

	context.Set(r, rc.Name, reflect.ValueOf(result).Elem().Interface())
	context.Set(r, "Resource", rc.Name)
	context.Set(r, "Action", "search")

	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	json.NewEncoder(rw).Encode(&bundle)
}