package queryapi import ( "errors" "fmt" "log/slog" "net/http" "time" "queryorchestration/internal/database/repository" "queryorchestration/internal/fieldextraction" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" openapi_types "github.com/oapi-codegen/runtime/types" ) // CreateFieldExtraction creates a new field extraction with single-value and array fields. // This endpoint creates a versioned field extraction record for a document. func (s *Controllers) CreateFieldExtraction(ctx echo.Context) error { var req FieldExtractionRequest if err := ctx.Bind(&req); err != nil { slog.Error("failed to parse field extraction request", "error", err) return echo.NewHTTPError(http.StatusBadRequest, "invalid request body") } // Validate required fields documentID := uuid.UUID(req.DocumentId) if documentID == uuid.Nil { return echo.NewHTTPError(http.StatusBadRequest, "documentId is required") } createdBy := string(req.CreatedBy) if createdBy == "" { return echo.NewHTTPError(http.StatusBadRequest, "createdBy is required") } // Convert API request to service input input := &fieldextraction.CreateFieldExtractionInput{ DocumentID: documentID, SingleFields: convertAPISingleFieldsToRepo(&req.SingleFields, createdBy), ArrayFields: convertAPIArrayFieldsToRepo(req.ArrayFields), } // Create field extraction via service extraction, err := s.svc.FieldExtraction.CreateFieldExtraction(ctx.Request().Context(), input) if err != nil { // Milestone 2.5: map the new typed sentinels from the service // layer plus the Postgres check_violation (Trigger 2 backstop) // onto clean HTTP status codes before falling through to 500. if errors.Is(err, fieldextraction.ErrDocumentNotFound) { return echo.NewHTTPError(http.StatusNotFound, "document not found") } if errors.Is(err, fieldextraction.ErrMutualExclusivityViolation) { return echo.NewHTTPError(http.StatusConflict, "document is bound to a custom schema and cannot accept legacy field extractions") } var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == "23514" { // Trigger 2 fired. Defense in depth — service-layer guard // already should have caught this, but if the lock path // was bypassed (regression) the DB enforces the same 409. return echo.NewHTTPError(http.StatusConflict, "document is bound to a custom schema and cannot accept legacy field extractions") } slog.Error("failed to create field extraction", "error", err, "document_id", documentID) return echo.NewHTTPError(http.StatusInternalServerError, "failed to create field extraction") } // Fetch the current extraction to get version info current, err := s.svc.FieldExtraction.GetCurrentFieldExtraction(ctx.Request().Context(), documentID) if err != nil { slog.Error("failed to get current field extraction after creation", "error", err, "document_id", documentID) return echo.NewHTTPError(http.StatusInternalServerError, "failed to get created field extraction") } // Fetch array fields for response arrayFields, err := s.svc.FieldExtraction.GetFieldExtractionArrayFields(ctx.Request().Context(), extraction.ID) if err != nil { slog.Error("failed to get array fields after creation", "error", err, "extraction_id", extraction.ID) return echo.NewHTTPError(http.StatusInternalServerError, "failed to get array fields") } // Build response response := buildFieldExtractionResponse(current, arrayFields) return ctx.JSON(http.StatusCreated, response) } // GetCurrentFieldExtraction retrieves the most recent field extraction for a document. // This returns the current (latest version) field extraction including all single and array fields. func (s *Controllers) GetCurrentFieldExtraction(ctx echo.Context, params GetCurrentFieldExtractionParams) error { documentID := uuid.UUID(params.DocumentId) if documentID == uuid.Nil { return echo.NewHTTPError(http.StatusBadRequest, "documentId is required") } // Get current field extraction current, err := s.svc.FieldExtraction.GetCurrentFieldExtraction(ctx.Request().Context(), documentID) if err != nil { slog.Error("failed to get current field extraction", "error", err, "document_id", documentID) return echo.NewHTTPError(http.StatusInternalServerError, "failed to get field extraction") } if current == nil { return echo.NewHTTPError(http.StatusNotFound, "no field extraction found for document") } // Fetch array fields arrayFields, err := s.svc.FieldExtraction.GetFieldExtractionArrayFields(ctx.Request().Context(), current.ID) if err != nil { slog.Error("failed to get array fields", "error", err, "extraction_id", current.ID) return echo.NewHTTPError(http.StatusInternalServerError, "failed to get array fields") } // Build response response := buildFieldExtractionResponse(current, arrayFields) return ctx.JSON(http.StatusOK, response) } // GetFieldExtractionHistory retrieves all versions of field extractions for a document. // This returns version metadata without the full field data for each version. func (s *Controllers) GetFieldExtractionHistory(ctx echo.Context, params GetFieldExtractionHistoryParams) error { documentID := uuid.UUID(params.DocumentId) if documentID == uuid.Nil { return echo.NewHTTPError(http.StatusBadRequest, "documentId is required") } // Get version history history, err := s.svc.FieldExtraction.GetFieldExtractionHistory(ctx.Request().Context(), documentID) if err != nil { slog.Error("failed to get field extraction history", "error", err, "document_id", documentID) return echo.NewHTTPError(http.StatusInternalServerError, "failed to get field extraction history") } // Convert to API response versions := make([]FieldExtractionVersion, len(history)) for i, h := range history { createdAt := time.Time{} if h.Createdat.Valid { createdAt = h.Createdat.Time } versions[i] = FieldExtractionVersion{ Id: openapi_types.UUID(h.ID), DocumentId: DocumentID(h.Documentid), //nolint:gosec // Version is a small number, int32 is sufficient Version: int32(h.Version), // Already 1-based in database CreatedAt: createdAt, CreatedBy: openapi_types.Email(h.Createdby), } } return ctx.JSON(http.StatusOK, struct { Versions []FieldExtractionVersion `json:"versions"` }{ Versions: versions, }) } // GetFieldExtractionByVersion retrieves a specific version of field extraction for a document. // This endpoint returns the full field extraction data for a specific version. func (s *Controllers) GetFieldExtractionByVersion(ctx echo.Context, params GetFieldExtractionByVersionParams) error { documentID := uuid.UUID(params.DocumentId) if documentID == uuid.Nil { return echo.NewHTTPError(http.StatusBadRequest, "documentId is required") } if params.Version < 1 { return echo.NewHTTPError(http.StatusBadRequest, "version must be at least 1") } // Get field extraction by version extraction, err := s.svc.FieldExtraction.GetFieldExtractionByVersion(ctx.Request().Context(), documentID, params.Version) if err != nil { slog.Error("failed to get field extraction by version", "error", err, "document_id", documentID, "version", params.Version) return echo.NewHTTPError(http.StatusInternalServerError, "failed to get field extraction") } if extraction == nil { return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("no field extraction found for document at version %d", params.Version)) } // Fetch array fields arrayFields, err := s.svc.FieldExtraction.GetFieldExtractionArrayFields(ctx.Request().Context(), extraction.ID) if err != nil { slog.Error("failed to get array fields", "error", err, "extraction_id", extraction.ID) return echo.NewHTTPError(http.StatusInternalServerError, "failed to get array fields") } // Build response response := buildFieldExtractionResponseFromVersionRow(extraction, arrayFields) return ctx.JSON(http.StatusOK, response) } // buildFieldExtractionResponse builds the API response from the repository types func buildFieldExtractionResponse(current *repository.Currentfieldextraction, arrayFields []*repository.Documentfieldextractionarrayfield) *FieldExtractionResponse { createdAt := time.Time{} if current.Createdat.Valid { createdAt = current.Createdat.Time } return &FieldExtractionResponse{ Id: openapi_types.UUID(current.ID), DocumentId: DocumentID(current.Documentid), //nolint:gosec // Version is a small number, int32 is sufficient Version: int32(current.Version), // Already 1-based in database SingleFields: convertRepoSingleFieldsToAPI(current), ArrayFields: convertRepoArrayFieldsToAPI(arrayFields), CreatedAt: createdAt, CreatedBy: openapi_types.Email(current.Createdby), } } // buildFieldExtractionResponseFromVersionRow builds the API response from the version-specific repository types func buildFieldExtractionResponseFromVersionRow(row *repository.GetFieldExtractionByVersionRow, arrayFields []*repository.Documentfieldextractionarrayfield) *FieldExtractionResponse { createdAt := time.Time{} if row.Createdat.Valid { createdAt = row.Createdat.Time } return &FieldExtractionResponse{ Id: openapi_types.UUID(row.ID), DocumentId: DocumentID(row.Documentid), //nolint:gosec // Version is a small number, int32 is sufficient Version: int32(row.Version), // Already 1-based in database SingleFields: convertRepoSingleFieldsToAPIFromVersionRow(row), ArrayFields: convertRepoArrayFieldsToAPI(arrayFields), CreatedAt: createdAt, CreatedBy: openapi_types.Email(row.Createdby), } } // convertRepoSingleFieldsToAPIFromVersionRow converts repository GetFieldExtractionByVersionRow to API SingleFields func convertRepoSingleFieldsToAPIFromVersionRow(row *repository.GetFieldExtractionByVersionRow) SingleFields { sf := SingleFields{ FileName: row.Filename, ContractTitle: row.Contracttitle, AareteDerivedAmendmentNum: row.Aaretederivedamendmentnum, ClientName: row.Clientname, PayerName: row.Payername, PayerState: row.Payerstate, ProviderState: row.Providerstate, FilenameTin: row.Filenametin, ProvGroupTin: row.Provgrouptin, ProvGroupNpi: row.Provgroupnpi, ProvGroupNameFull: row.Provgroupnamefull, ProvOtherTin: row.Provothertin, ProvOtherNpi: row.Provothernpi, ProvOtherNameFull: row.Provothernamefull, AutoRenewalInd: row.Autorenewalind, AutoRenewalTerm: row.Autorenewalterm, } // Convert dates if row.Aaretederivedeffectivedt.Valid { d := pgDateToAPIDate(row.Aaretederivedeffectivedt) sf.AareteDerivedEffectiveDt = &d } if row.Aaretederivedterminationdt.Valid { d := pgDateToAPIDate(row.Aaretederivedterminationdt) sf.AareteDerivedTerminationDt = &d } return sf } // convertAPISingleFieldsToRepo converts API SingleFields to repository AddFieldExtractionParams func convertAPISingleFieldsToRepo(sf *SingleFields, createdBy string) *repository.AddFieldExtractionParams { params := &repository.AddFieldExtractionParams{ Createdby: createdBy, } if sf == nil { return params } params.Filename = sf.FileName params.Contracttitle = sf.ContractTitle params.Aaretederivedamendmentnum = sf.AareteDerivedAmendmentNum params.Clientname = sf.ClientName params.Payername = sf.PayerName params.Payerstate = sf.PayerState params.Providerstate = sf.ProviderState params.Filenametin = sf.FilenameTin params.Provgrouptin = sf.ProvGroupTin params.Provgroupnpi = sf.ProvGroupNpi params.Provgroupnamefull = sf.ProvGroupNameFull params.Provothertin = sf.ProvOtherTin params.Provothernpi = sf.ProvOtherNpi params.Provothernamefull = sf.ProvOtherNameFull params.Autorenewalind = sf.AutoRenewalInd params.Autorenewalterm = sf.AutoRenewalTerm // Convert dates if sf.AareteDerivedEffectiveDt != nil { params.Aaretederivedeffectivedt = dateToPgDate(*sf.AareteDerivedEffectiveDt) } if sf.AareteDerivedTerminationDt != nil { params.Aaretederivedterminationdt = dateToPgDate(*sf.AareteDerivedTerminationDt) } return params } // convertRepoSingleFieldsToAPI converts repository Currentfieldextraction to API SingleFields func convertRepoSingleFieldsToAPI(cf *repository.Currentfieldextraction) SingleFields { sf := SingleFields{ FileName: cf.Filename, ContractTitle: cf.Contracttitle, AareteDerivedAmendmentNum: cf.Aaretederivedamendmentnum, ClientName: cf.Clientname, PayerName: cf.Payername, PayerState: cf.Payerstate, ProviderState: cf.Providerstate, FilenameTin: cf.Filenametin, ProvGroupTin: cf.Provgrouptin, ProvGroupNpi: cf.Provgroupnpi, ProvGroupNameFull: cf.Provgroupnamefull, ProvOtherTin: cf.Provothertin, ProvOtherNpi: cf.Provothernpi, ProvOtherNameFull: cf.Provothernamefull, AutoRenewalInd: cf.Autorenewalind, AutoRenewalTerm: cf.Autorenewalterm, } // Convert dates if cf.Aaretederivedeffectivedt.Valid { d := pgDateToAPIDate(cf.Aaretederivedeffectivedt) sf.AareteDerivedEffectiveDt = &d } if cf.Aaretederivedterminationdt.Valid { d := pgDateToAPIDate(cf.Aaretederivedterminationdt) sf.AareteDerivedTerminationDt = &d } return sf } // convertAPIArrayFieldsToRepo converts API ArrayFieldItem slice to repository AddFieldExtractionArrayFieldParams slice func convertAPIArrayFieldsToRepo(items []ArrayFieldItem) []*repository.AddFieldExtractionArrayFieldParams { result := make([]*repository.AddFieldExtractionArrayFieldParams, len(items)) for i, item := range items { result[i] = convertAPIArrayFieldItemToRepo(&item) } return result } // convertAPIArrayFieldItemToRepo converts a single API ArrayFieldItem to repository params func convertAPIArrayFieldItemToRepo(item *ArrayFieldItem) *repository.AddFieldExtractionArrayFieldParams { params := &repository.AddFieldExtractionArrayFieldParams{} if item == nil { return params } // String fields params.Exhibittitle = item.ExhibitTitle params.Exhibitpage = item.ExhibitPage params.Reimbprovtin = item.ReimbProvTin params.Reimbprovnpi = item.ReimbProvNpi params.Reimbprovname = item.ReimbProvName params.Aaretederivedclaimtypecd = item.AareteDerivedClaimTypeCd params.Aaretederivedproduct = item.AareteDerivedProduct params.Aaretederivedlob = item.AareteDerivedLob params.Aaretederivedprogram = item.AareteDerivedProgram params.Aaretederivednetwork = item.AareteDerivedNetwork params.Aaretederivedprovtype = item.AareteDerivedProvType params.Provtaxonomycd = item.ProvTaxonomyCd params.Provtaxonomycddesc = item.ProvTaxonomyCdDesc params.Provspecialtycd = item.ProvSpecialtyCd params.Provspecialtycddesc = item.ProvSpecialtyCdDesc params.Placeofservicecd = item.PlaceOfServiceCd params.Placeofservicecddesc = item.PlaceOfServiceCdDesc params.Billtypecd = item.BillTypeCd params.Billtypecddesc = item.BillTypeCdDesc params.Patientagemin = item.PatientAgeMin params.Patientagemax = item.PatientAgeMax params.Reimbterm = item.ReimbTerm params.Lobprogramrelationship = item.LobProgramRelationship params.Lobproductrelationship = item.LobProductRelationship params.Aaretederivedreimbmethod = item.AareteDerivedReimbMethod params.Unitofmeasure = item.UnitOfMeasure params.Additiondesc = item.AdditionDesc params.Aaretederivedadditionratechangetimeline = item.AareteDerivedAdditionRateChangeTimeline params.Aaretederivedfeeschedule = item.AareteDerivedFeeSchedule params.Aaretederivedfeescheduleversion = item.AareteDerivedFeeScheduleVersion params.Serviceterm = item.ServiceTerm params.Cpt4proccd = item.Cpt4ProcCd params.Cpt4proccddesc = item.Cpt4ProcCdDesc params.Cpt4procmod = item.Cpt4ProcMod params.Cpt4procmoddesc = item.Cpt4ProcModDesc params.Revenuecd = item.RevenueCd params.Revenuecddesc = item.RevenueCdDesc params.Diagcd = item.DiagCd params.Diagcddesc = item.DiagCdDesc params.Ndccd = item.NdcCd params.Ndccddesc = item.NdcCdDesc params.Claimadmittypecd = item.ClaimAdmitTypeCd params.Authadmittypedesc = item.AuthAdmitTypeDesc params.Claimstatuscd = item.ClaimStatusCd params.Claimstatuscddesc = item.ClaimStatusCdDesc params.Groupertype = item.GrouperType params.Groupercd = item.GrouperCd params.Groupercddesc = item.GrouperCdDesc params.Carveoutcd = item.CarveoutCd // Grouper extended fields params.Aaretederivedgrouperversion = item.AareteDerivedGrouperVersion params.Grouperalternativelevelofcare = item.GrouperAlternativeLevelOfCare params.Grouperseverity = item.GrouperSeverity params.Grouperriskofmortalitysubclass = item.GrouperRiskOfMortalitySubclass // Outlier fields params.Outlierterm = item.OutlierTerm params.Rangenbrdays = item.RangeNbrDays params.Outlierexclusioncd = item.OutlierExclusionCd params.Outlierexclusioncddesc = item.OutlierExclusionCdDesc // Facility adjustment fields params.Facilityadjustmentterm = item.FacilityAdjustmentTerm // Rate escalator fields params.Rateescalatordesc = item.RateEscalatorDesc // Stop loss fields params.Stoplossterm = item.StopLossTerm params.Stoplossexclusioncd = item.StopLossExclusionCd params.Stoplossexclusiondesc = item.StopLossExclusionDesc // Boolean fields params.Carveoutind = item.CarveoutInd params.Lesserofind = item.LesserOfInd params.Greaterofind = item.GreaterOfInd params.Defaultind = item.DefaultInd // Grouper extended boolean fields params.Grouperseverityind = item.GrouperSeverityInd params.Groupertransferind = item.GrouperTransferInd params.Grouperreadmissionsind = item.GrouperReadmissionsInd params.Grouperhacind = item.GrouperHacInd // Outlier boolean fields params.Outlierfirstdollarind = item.OutlierFirstDollarInd // Facility adjustment boolean fields params.Dshind = item.DshInd params.Imeind = item.ImeInd params.Ntapind = item.NtapInd params.Ucind = item.UcInd params.Gmeind = item.GmeInd // Rate escalator boolean fields params.Rateescalatorind = item.RateEscalatorInd // Stop loss boolean fields params.Stoplossfirstdollarind = item.StopLossFirstDollarInd // Numeric fields params.Reimbpctrate = float64ToPgNumeric(item.ReimbPctRate) params.Reimbfeerate = float64ToPgNumeric(item.ReimbFeeRate) params.Reimbconversionfactor = float64ToPgNumeric(item.ReimbConversionFactor) params.Triggercapthresholdamt = float64ToPgNumeric(item.TriggerCapThresholdAmt) params.Triggerbasethreshold = float64ToPgNumeric(item.TriggerBaseThreshold) params.Additionmaxfeerateinc = float64ToPgNumeric(item.AdditionMaxFeeRateInc) params.Additionmaxpctrateinc = float64ToPgNumeric(item.AdditionMaxPctRateInc) params.Grouperpctrate = float64ToPgNumeric(item.GrouperPctRate) params.Grouperbaserate = float64ToPgNumeric(item.GrouperBaseRate) // Outlier numeric fields params.Outlierfixedlossnbrdaysthreshold = float64ToPgNumeric(item.OutlierFixedLossNbrDaysThreshold) params.Outlierfixedlossthreshold = float64ToPgNumeric(item.OutlierFixedLossThreshold) params.Outliermaximum = float64ToPgNumeric(item.OutlierMaximum) params.Outliermaximumfrequency = float64ToPgNumeric(item.OutlierMaximumFrequency) params.Outlierpctrate = float64ToPgNumeric(item.OutlierPctRate) // Facility adjustment numeric fields params.Dshpctrate = float64ToPgNumeric(item.DshPctRate) params.Dshfeerate = float64ToPgNumeric(item.DshFeeRate) params.Imepctrate = float64ToPgNumeric(item.ImePctRate) params.Imefeerate = float64ToPgNumeric(item.ImeFeeRate) params.Ntappctrate = float64ToPgNumeric(item.NtapPctRate) params.Ntapfeerate = float64ToPgNumeric(item.NtapFeeRate) params.Ucpctrate = float64ToPgNumeric(item.UcPctRate) params.Ucfeerate = float64ToPgNumeric(item.UcFeeRate) params.Gmepctrate = float64ToPgNumeric(item.GmePctRate) params.Gmefeerate = float64ToPgNumeric(item.GmeFeeRate) // Rate escalator numeric fields params.Rateescalatormaxrateincpct = float64ToPgNumeric(item.RateEscalatorMaxRateIncPct) params.Rateescalatorratechangetimeline = float64ToPgNumeric(item.RateEscalatorRateChangeTimeline) // Stop loss numeric fields params.Stoplossrangenbrdays = float64ToPgNumeric(item.StopLossRangeNbrDays) params.Stoplossfixedlossthreshold = float64ToPgNumeric(item.StopLossFixedLossThreshold) params.Stoplossmaximum = float64ToPgNumeric(item.StopLossMaximum) params.Stoplossmaximumfrequency = float64ToPgNumeric(item.StopLossMaximumFrequency) params.Stoplossdailymaxrate = float64ToPgNumeric(item.StopLossDailyMaxRate) params.Stoplosspctrateonexcesscharges = float64ToPgNumeric(item.StopLossPctRateOnExcessCharges) // Date fields if item.ReimbEffectiveDt != nil { params.Reimbeffectivedt = dateToPgDate(*item.ReimbEffectiveDt) } if item.ReimbTerminationDt != nil { params.Reimbterminationdt = dateToPgDate(*item.ReimbTerminationDt) } return params } // convertRepoArrayFieldsToAPI converts repository array fields to API ArrayFieldItem slice func convertRepoArrayFieldsToAPI(fields []*repository.Documentfieldextractionarrayfield) []ArrayFieldItem { result := make([]ArrayFieldItem, len(fields)) for i, f := range fields { result[i] = convertRepoArrayFieldToAPI(f) } return result } // convertRepoArrayFieldToAPI converts a single repository array field to API ArrayFieldItem func convertRepoArrayFieldToAPI(f *repository.Documentfieldextractionarrayfield) ArrayFieldItem { item := ArrayFieldItem{ // String fields ExhibitTitle: f.Exhibittitle, ExhibitPage: f.Exhibitpage, ReimbProvTin: f.Reimbprovtin, ReimbProvNpi: f.Reimbprovnpi, ReimbProvName: f.Reimbprovname, AareteDerivedClaimTypeCd: f.Aaretederivedclaimtypecd, AareteDerivedProduct: f.Aaretederivedproduct, AareteDerivedLob: f.Aaretederivedlob, AareteDerivedProgram: f.Aaretederivedprogram, AareteDerivedNetwork: f.Aaretederivednetwork, AareteDerivedProvType: f.Aaretederivedprovtype, ProvTaxonomyCd: f.Provtaxonomycd, ProvTaxonomyCdDesc: f.Provtaxonomycddesc, ProvSpecialtyCd: f.Provspecialtycd, ProvSpecialtyCdDesc: f.Provspecialtycddesc, PlaceOfServiceCd: f.Placeofservicecd, PlaceOfServiceCdDesc: f.Placeofservicecddesc, BillTypeCd: f.Billtypecd, BillTypeCdDesc: f.Billtypecddesc, PatientAgeMin: f.Patientagemin, PatientAgeMax: f.Patientagemax, ReimbTerm: f.Reimbterm, LobProgramRelationship: f.Lobprogramrelationship, LobProductRelationship: f.Lobproductrelationship, AareteDerivedReimbMethod: f.Aaretederivedreimbmethod, UnitOfMeasure: f.Unitofmeasure, AdditionDesc: f.Additiondesc, AareteDerivedAdditionRateChangeTimeline: f.Aaretederivedadditionratechangetimeline, AareteDerivedFeeSchedule: f.Aaretederivedfeeschedule, AareteDerivedFeeScheduleVersion: f.Aaretederivedfeescheduleversion, ServiceTerm: f.Serviceterm, Cpt4ProcCd: f.Cpt4proccd, Cpt4ProcCdDesc: f.Cpt4proccddesc, Cpt4ProcMod: f.Cpt4procmod, Cpt4ProcModDesc: f.Cpt4procmoddesc, RevenueCd: f.Revenuecd, RevenueCdDesc: f.Revenuecddesc, DiagCd: f.Diagcd, DiagCdDesc: f.Diagcddesc, NdcCd: f.Ndccd, NdcCdDesc: f.Ndccddesc, ClaimAdmitTypeCd: f.Claimadmittypecd, AuthAdmitTypeDesc: f.Authadmittypedesc, ClaimStatusCd: f.Claimstatuscd, ClaimStatusCdDesc: f.Claimstatuscddesc, GrouperType: f.Groupertype, GrouperCd: f.Groupercd, GrouperCdDesc: f.Groupercddesc, CarveoutCd: f.Carveoutcd, // Grouper extended string fields AareteDerivedGrouperVersion: f.Aaretederivedgrouperversion, GrouperAlternativeLevelOfCare: f.Grouperalternativelevelofcare, GrouperSeverity: f.Grouperseverity, GrouperRiskOfMortalitySubclass: f.Grouperriskofmortalitysubclass, // Outlier string fields OutlierTerm: f.Outlierterm, RangeNbrDays: f.Rangenbrdays, OutlierExclusionCd: f.Outlierexclusioncd, OutlierExclusionCdDesc: f.Outlierexclusioncddesc, // Facility adjustment string fields FacilityAdjustmentTerm: f.Facilityadjustmentterm, // Rate escalator string fields RateEscalatorDesc: f.Rateescalatordesc, // Stop loss string fields StopLossTerm: f.Stoplossterm, StopLossExclusionCd: f.Stoplossexclusioncd, StopLossExclusionDesc: f.Stoplossexclusiondesc, // Boolean fields CarveoutInd: f.Carveoutind, LesserOfInd: f.Lesserofind, GreaterOfInd: f.Greaterofind, DefaultInd: f.Defaultind, // Grouper extended boolean fields GrouperSeverityInd: f.Grouperseverityind, GrouperTransferInd: f.Groupertransferind, GrouperReadmissionsInd: f.Grouperreadmissionsind, GrouperHacInd: f.Grouperhacind, // Outlier boolean fields OutlierFirstDollarInd: f.Outlierfirstdollarind, // Facility adjustment boolean fields DshInd: f.Dshind, ImeInd: f.Imeind, NtapInd: f.Ntapind, UcInd: f.Ucind, GmeInd: f.Gmeind, // Rate escalator boolean fields RateEscalatorInd: f.Rateescalatorind, // Stop loss boolean fields StopLossFirstDollarInd: f.Stoplossfirstdollarind, } // Numeric fields item.ReimbPctRate = pgNumericToFloat64(f.Reimbpctrate) item.ReimbFeeRate = pgNumericToFloat64(f.Reimbfeerate) item.ReimbConversionFactor = pgNumericToFloat64(f.Reimbconversionfactor) item.TriggerCapThresholdAmt = pgNumericToFloat64(f.Triggercapthresholdamt) item.TriggerBaseThreshold = pgNumericToFloat64(f.Triggerbasethreshold) item.AdditionMaxFeeRateInc = pgNumericToFloat64(f.Additionmaxfeerateinc) item.AdditionMaxPctRateInc = pgNumericToFloat64(f.Additionmaxpctrateinc) item.GrouperPctRate = pgNumericToFloat64(f.Grouperpctrate) item.GrouperBaseRate = pgNumericToFloat64(f.Grouperbaserate) // Outlier numeric fields item.OutlierFixedLossNbrDaysThreshold = pgNumericToFloat64(f.Outlierfixedlossnbrdaysthreshold) item.OutlierFixedLossThreshold = pgNumericToFloat64(f.Outlierfixedlossthreshold) item.OutlierMaximum = pgNumericToFloat64(f.Outliermaximum) item.OutlierMaximumFrequency = pgNumericToFloat64(f.Outliermaximumfrequency) item.OutlierPctRate = pgNumericToFloat64(f.Outlierpctrate) // Facility adjustment numeric fields item.DshPctRate = pgNumericToFloat64(f.Dshpctrate) item.DshFeeRate = pgNumericToFloat64(f.Dshfeerate) item.ImePctRate = pgNumericToFloat64(f.Imepctrate) item.ImeFeeRate = pgNumericToFloat64(f.Imefeerate) item.NtapPctRate = pgNumericToFloat64(f.Ntappctrate) item.NtapFeeRate = pgNumericToFloat64(f.Ntapfeerate) item.UcPctRate = pgNumericToFloat64(f.Ucpctrate) item.UcFeeRate = pgNumericToFloat64(f.Ucfeerate) item.GmePctRate = pgNumericToFloat64(f.Gmepctrate) item.GmeFeeRate = pgNumericToFloat64(f.Gmefeerate) // Rate escalator numeric fields item.RateEscalatorMaxRateIncPct = pgNumericToFloat64(f.Rateescalatormaxrateincpct) item.RateEscalatorRateChangeTimeline = pgNumericToFloat64(f.Rateescalatorratechangetimeline) // Stop loss numeric fields item.StopLossRangeNbrDays = pgNumericToFloat64(f.Stoplossrangenbrdays) item.StopLossFixedLossThreshold = pgNumericToFloat64(f.Stoplossfixedlossthreshold) item.StopLossMaximum = pgNumericToFloat64(f.Stoplossmaximum) item.StopLossMaximumFrequency = pgNumericToFloat64(f.Stoplossmaximumfrequency) item.StopLossDailyMaxRate = pgNumericToFloat64(f.Stoplossdailymaxrate) item.StopLossPctRateOnExcessCharges = pgNumericToFloat64(f.Stoplosspctrateonexcesscharges) // Date fields if f.Reimbeffectivedt.Valid { d := pgDateToAPIDate(f.Reimbeffectivedt) item.ReimbEffectiveDt = &d } if f.Reimbterminationdt.Valid { d := pgDateToAPIDate(f.Reimbterminationdt) item.ReimbTerminationDt = &d } return item } // dateToPgDate converts openapi_types.Date to pgtype.Date func dateToPgDate(d openapi_types.Date) pgtype.Date { return pgtype.Date{ Time: d.Time, Valid: true, } } // pgDateToAPIDate converts pgtype.Date to openapi_types.Date func pgDateToAPIDate(d pgtype.Date) openapi_types.Date { return openapi_types.Date{ Time: d.Time, } } // float64ToPgNumeric converts *float64 to pgtype.Numeric func float64ToPgNumeric(f *float64) pgtype.Numeric { if f == nil { return pgtype.Numeric{} } var num pgtype.Numeric // Scan doesn't accept float64 directly, need to convert to string _ = num.Scan(fmt.Sprintf("%v", *f)) return num } // pgNumericToFloat64 converts pgtype.Numeric to *float64 func pgNumericToFloat64(n pgtype.Numeric) *float64 { if !n.Valid { return nil } f, err := n.Float64Value() if err != nil || !f.Valid { return nil } return &f.Float64 }