// 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 }
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) }