Files

913 lines
25 KiB
Go

package sqlite
import (
"context"
"crypto/sha256"
"database/sql"
"encoding/hex"
"encoding/json"
"errors"
"strconv"
"cmroubao/backend-api/internal/domain"
"cmroubao/backend-api/internal/usecase"
)
func storeCandidateDecisionDataset(
ctx context.Context,
tx *sql.Tx,
write usecase.ExecutionResultWrite,
batch domain.ExecutionCandidateBatch,
expired bool,
) error {
var candidates []usecase.ExecutionCandidate
if err := json.Unmarshal([]byte(batch.CandidatesJSON), &candidates); err != nil {
return usecase.ErrRepositoryInvariant
}
var startedAt string
var appVersion, androidVersion, pddVersion sql.NullString
err := tx.QueryRowContext(
ctx,
`SELECT execution.started_at, device.app_version,
device.android_version, device.pdd_version
FROM task_executions AS execution
JOIN devices AS device ON device.id = execution.device_id
WHERE execution.id = ? AND execution.task_id = ?`,
write.ExecutionID,
write.TaskID,
).Scan(&startedAt, &appVersion, &androidVersion, &pddVersion)
if err != nil {
return repositoryFailure(err)
}
_, err = tx.ExecContext(
ctx,
`INSERT INTO candidate_search_runs (
execution_id, task_id, task_content_sha256, execution_mode,
search_query, app_version, android_version, pdd_version,
started_at, received_at, observation_count, collection_complete,
received_after_execution_expiry
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)`,
write.ExecutionID,
write.TaskID,
batch.TaskContentSHA256,
batch.ExecutionMode,
batch.SearchQuery,
nullableSQLString(appVersion),
nullableSQLString(androidVersion),
nullableSQLString(pddVersion),
startedAt,
formatTimestamp(write.Now),
len(candidates),
expired,
)
if err != nil {
return repositoryFailure(err)
}
for _, candidate := range candidates {
evidenceJSON, marshalErr := json.Marshal(candidate.EvidenceAssetIDs)
if marshalErr != nil {
return usecase.ErrRepositoryInvariant
}
collectionStatus := "PARTIAL"
if len(candidate.EvidenceAssetIDs) > 0 {
collectionStatus = "COMPLETE"
}
_, err = tx.ExecContext(
ctx,
`INSERT INTO candidate_observations (
execution_id, task_id, ordinal, title, sku_text, price_text,
product_url, image_url, evidence_asset_ids_json,
collection_status, observed_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
write.ExecutionID,
write.TaskID,
candidate.Ordinal,
candidate.Title,
candidate.SKUText,
candidate.Price,
candidate.ProductURL,
candidate.ImageURL,
string(evidenceJSON),
collectionStatus,
formatTimestamp(write.Now),
)
if err != nil {
return repositoryFailure(err)
}
_, err = tx.ExecContext(
ctx,
`INSERT INTO candidate_observation_identities (
candidate_key, execution_id, candidate_ordinal,
card_signature, detail_signature, detail_evidence_sha256,
specification_evidence_sha256, identity_version, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?)`,
candidateObservationKey(write.ExecutionID, candidate),
write.ExecutionID,
candidate.Ordinal,
candidate.CardSignature,
candidate.DetailSignature,
candidate.DetailEvidenceSHA256,
candidate.SpecificationEvidenceSHA256,
formatTimestamp(write.Now),
)
if err != nil {
return repositoryFailure(err)
}
}
if batch.ProvenanceJSON != nil {
var provenance usecase.ExecutionProvenance
if err := json.Unmarshal(
[]byte(*batch.ProvenanceJSON),
&provenance,
); err != nil {
return usecase.ErrRepositoryInvariant
}
resultHash := hashCandidateResult(
batch.CandidatesJSON,
batch.RecommendationJSON,
)
_, err = tx.ExecContext(
ctx,
`INSERT INTO model_runs (
execution_id, provider_id, model, prompt_version, schema_version,
recommendation_threshold, request_sha256, result_sha256,
created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
write.ExecutionID,
provenance.ProviderID,
provenance.Model,
provenance.PromptVersion,
provenance.SchemaVersion,
0.75,
write.RequestHash,
resultHash,
formatTimestamp(write.Now),
)
if err != nil {
return repositoryFailure(err)
}
for _, candidate := range candidates {
if candidate.Evaluation == nil {
return usecase.ErrRepositoryInvariant
}
evaluation := candidate.Evaluation
matchedJSON, marshalErr := json.Marshal(evaluation.Matched)
if marshalErr != nil {
return usecase.ErrRepositoryInvariant
}
missingJSON, marshalErr := json.Marshal(
evaluation.MissingOrUncertain,
)
if marshalErr != nil {
return usecase.ErrRepositoryInvariant
}
rejectionsJSON, marshalErr := json.Marshal(
evaluation.RejectionReasons,
)
if marshalErr != nil {
return usecase.ErrRepositoryInvariant
}
constraintsJSON, marshalErr := json.Marshal(
evaluation.HardConstraints,
)
if marshalErr != nil {
return usecase.ErrRepositoryInvariant
}
_, err = tx.ExecContext(
ctx,
`INSERT INTO candidate_evaluations (
execution_id, candidate_ordinal, decision, score,
confidence, matched_json, missing_or_uncertain_json,
rejection_reasons_json, hard_constraints_json, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
write.ExecutionID,
candidate.Ordinal,
evaluation.Decision,
evaluation.Score,
evaluation.Confidence,
string(matchedJSON),
string(missingJSON),
string(rejectionsJSON),
string(constraintsJSON),
formatTimestamp(write.Now),
)
if err != nil {
return repositoryFailure(err)
}
}
}
if batch.RecommendationJSON != nil {
var recommendation usecase.CandidateRecommendation
if err := json.Unmarshal(
[]byte(*batch.RecommendationJSON),
&recommendation,
); err != nil {
return usecase.ErrRepositoryInvariant
}
reasonsJSON, marshalErr := json.Marshal(recommendation.Reasons)
if marshalErr != nil {
return usecase.ErrRepositoryInvariant
}
_, err = tx.ExecContext(
ctx,
`INSERT INTO candidate_recommendations (
execution_id, candidate_ordinal, conclusion, policy_version,
reasons_json, created_at
) VALUES (?, ?, 'SUGGESTED', ?, ?, ?)`,
write.ExecutionID,
recommendation.CandidateOrdinal,
recommendation.PolicyVersion,
string(reasonsJSON),
formatTimestamp(write.Now),
)
if err != nil {
return repositoryFailure(err)
}
}
return nil
}
func (s *Store) StoreCandidateHumanReview(
ctx context.Context,
write usecase.ExecutionResultWrite,
candidate domain.CandidateHumanReview,
) (domain.CandidateHumanReview, bool, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return domain.CandidateHumanReview{}, false, repositoryFailure(err)
}
defer func() { _ = tx.Rollback() }()
record, found, err := lookupExecutionResultRequest(ctx, tx, write)
if err != nil {
return domain.CandidateHumanReview{}, false, err
}
if found {
if err := validateExecutionResultReplay(record, write); err != nil {
return domain.CandidateHumanReview{}, false, err
}
if record.ResourceID == nil {
return domain.CandidateHumanReview{}, false, usecase.ErrRepositoryInvariant
}
review, err := getCandidateHumanReview(ctx, tx, *record.ResourceID)
if err != nil {
return domain.CandidateHumanReview{}, false, err
}
if err := tx.Commit(); err != nil {
return domain.CandidateHumanReview{}, false, repositoryFailure(err)
}
return review, true, nil
}
task, _, expired, err := authorizeExecutionResult(ctx, tx, write)
if err != nil {
return domain.CandidateHumanReview{}, false, err
}
if usecase.TaskContentSHA256(task) != candidate.TaskContentSHA256 {
return domain.CandidateHumanReview{}, false, usecase.ErrTaskVersionConflict
}
if err := validateHumanReviewAgainstDataset(
ctx,
tx,
task,
candidate,
); err != nil {
return domain.CandidateHumanReview{}, false, err
}
latestID, latestVersion, err := latestCandidateHumanReview(
ctx,
tx,
write.ExecutionID,
)
if err != nil {
return domain.CandidateHumanReview{}, false, err
}
switch {
case latestID == nil && candidate.SupersedesReviewID != nil:
return domain.CandidateHumanReview{}, false, usecase.ErrTaskStateConflict
case latestID != nil && (candidate.SupersedesReviewID == nil ||
*candidate.SupersedesReviewID != *latestID):
return domain.CandidateHumanReview{}, false, usecase.ErrTaskStateConflict
}
candidate.Version = latestVersion + 1
candidate.ReceivedAfterExecutionExpiry = expired
_, err = tx.ExecContext(
ctx,
`INSERT INTO candidate_human_reviews (
id, execution_id, task_id, version, reason_schema_version, outcome,
selected_candidate_ordinal, primary_reason_code, note,
supersedes_review_id, actor_user_id, actor_device_id, created_at,
received_after_execution_expiry
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
candidate.ID,
candidate.ExecutionID,
candidate.TaskID,
candidate.Version,
candidate.ReasonSchemaVersion,
candidate.Outcome,
nullableInt(candidate.SelectedCandidateOrdinal),
candidate.PrimaryReasonCode,
candidate.Note,
nullableString(candidate.SupersedesReviewID),
candidate.ActorUserID,
nullableString(candidate.ActorDeviceID),
formatTimestamp(candidate.CreatedAt),
expired,
)
if err != nil {
return domain.CandidateHumanReview{}, false, repositoryFailure(err)
}
for _, item := range candidate.Items {
_, err = tx.ExecContext(
ctx,
`INSERT INTO candidate_human_review_items (
review_id, candidate_ordinal, label, primary_reason_code, note
) VALUES (?, ?, ?, ?, ?)`,
candidate.ID,
item.CandidateOrdinal,
item.Label,
item.PrimaryReasonCode,
item.Note,
)
if err != nil {
return domain.CandidateHumanReview{}, false, repositoryFailure(err)
}
for _, reason := range item.ReasonCodes {
_, err = tx.ExecContext(
ctx,
`INSERT INTO candidate_human_review_reasons (
review_id, candidate_ordinal, reason_code
) VALUES (?, ?, ?)`,
candidate.ID,
item.CandidateOrdinal,
reason,
)
if err != nil {
return domain.CandidateHumanReview{}, false, repositoryFailure(err)
}
}
}
if err := insertExecutionResultRequest(
ctx,
tx,
write,
&candidate.ID,
); err != nil {
return domain.CandidateHumanReview{}, false, err
}
if err := tx.Commit(); err != nil {
return domain.CandidateHumanReview{}, false, repositoryFailure(err)
}
return candidate, false, nil
}
func validateHumanReviewAgainstDataset(
ctx context.Context,
tx *sql.Tx,
task domain.PurchaseTask,
review domain.CandidateHumanReview,
) error {
rows, err := tx.QueryContext(
ctx,
`SELECT ordinal FROM candidate_observations
WHERE execution_id = ? AND task_id = ?
ORDER BY ordinal ASC`,
review.ExecutionID,
review.TaskID,
)
if err != nil {
return repositoryFailure(err)
}
defer rows.Close()
ordinals := make([]int, 0, 5)
for rows.Next() {
var ordinal int
if err := rows.Scan(&ordinal); err != nil {
return repositoryFailure(err)
}
ordinals = append(ordinals, ordinal)
}
if err := rows.Err(); err != nil {
return repositoryFailure(err)
}
if len(review.Items) != len(ordinals) {
return usecase.ErrTaskStateConflict
}
expected := make(map[int]struct{}, len(ordinals))
for _, ordinal := range ordinals {
expected[ordinal] = struct{}{}
}
for _, item := range review.Items {
if _, found := expected[item.CandidateOrdinal]; !found {
return usecase.ErrTaskStateConflict
}
for _, reason := range item.ReasonCodes {
if task.MaxBudgetCents == nil &&
(reason == "PRICE_ACCEPTABLE" || reason == "PRICE_TOO_HIGH") {
return usecase.ErrTaskStateConflict
}
}
}
return nil
}
func latestCandidateHumanReview(
ctx context.Context,
queryer queryRower,
executionID string,
) (*string, int, error) {
var id string
var version int
err := queryer.QueryRowContext(
ctx,
`SELECT id, version FROM candidate_human_reviews
WHERE execution_id = ?
ORDER BY version DESC
LIMIT 1`,
executionID,
).Scan(&id, &version)
if errors.Is(err, sql.ErrNoRows) {
return nil, 0, nil
}
if err != nil {
return nil, 0, repositoryFailure(err)
}
return &id, version, nil
}
func getCandidateHumanReview(
ctx context.Context,
queryer queryer,
reviewID string,
) (domain.CandidateHumanReview, error) {
var review domain.CandidateHumanReview
var selected sql.NullInt64
var supersedes, device sql.NullString
var createdAt string
err := queryer.QueryRowContext(
ctx,
`SELECT review.id, review.task_id, review.execution_id,
run.task_content_sha256, review.version,
review.reason_schema_version, review.outcome,
review.selected_candidate_ordinal, review.primary_reason_code,
review.note, review.supersedes_review_id, review.actor_user_id,
review.actor_device_id, review.created_at,
review.received_after_execution_expiry
FROM candidate_human_reviews AS review
JOIN candidate_search_runs AS run
ON run.execution_id = review.execution_id
WHERE review.id = ?`,
reviewID,
).Scan(
&review.ID,
&review.TaskID,
&review.ExecutionID,
&review.TaskContentSHA256,
&review.Version,
&review.ReasonSchemaVersion,
&review.Outcome,
&selected,
&review.PrimaryReasonCode,
&review.Note,
&supersedes,
&review.ActorUserID,
&device,
&createdAt,
&review.ReceivedAfterExecutionExpiry,
)
if errors.Is(err, sql.ErrNoRows) {
return domain.CandidateHumanReview{}, usecase.ErrRepositoryInvariant
}
if err != nil {
return domain.CandidateHumanReview{}, repositoryFailure(err)
}
if selected.Valid {
value := int(selected.Int64)
review.SelectedCandidateOrdinal = &value
}
review.SupersedesReviewID = nullableStringFromSQL(supersedes)
review.ActorDeviceID = nullableStringFromSQL(device)
review.CreatedAt, err = parseTimestamp(createdAt)
if err != nil {
return domain.CandidateHumanReview{}, repositoryFailure(err)
}
rows, err := queryer.QueryContext(
ctx,
`SELECT candidate_ordinal, label, primary_reason_code, note
FROM candidate_human_review_items
WHERE review_id = ?
ORDER BY candidate_ordinal ASC`,
reviewID,
)
if err != nil {
return domain.CandidateHumanReview{}, repositoryFailure(err)
}
defer rows.Close()
for rows.Next() {
var item domain.CandidateHumanReviewItem
if err := rows.Scan(
&item.CandidateOrdinal,
&item.Label,
&item.PrimaryReasonCode,
&item.Note,
); err != nil {
return domain.CandidateHumanReview{}, repositoryFailure(err)
}
reasons, err := candidateHumanReviewReasons(
ctx,
queryer,
reviewID,
item.CandidateOrdinal,
)
if err != nil {
return domain.CandidateHumanReview{}, err
}
item.ReasonCodes = reasons
review.Items = append(review.Items, item)
}
if err := rows.Err(); err != nil {
return domain.CandidateHumanReview{}, repositoryFailure(err)
}
return review, nil
}
func getCandidateDecisionDataset(
ctx context.Context,
queryer queryer,
taskID string,
executionID string,
) (*domain.CandidateDecisionDataset, error) {
var run domain.CandidateSearchRun
var appVersion, androidVersion, pddVersion sql.NullString
var startedAt, receivedAt string
err := queryer.QueryRowContext(
ctx,
`SELECT task_id, execution_id, task_content_sha256, execution_mode,
search_query, app_version, android_version, pdd_version,
started_at, received_at, observation_count, collection_complete,
received_after_execution_expiry
FROM candidate_search_runs
WHERE task_id = ? AND execution_id = ?`,
taskID,
executionID,
).Scan(
&run.TaskID,
&run.ExecutionID,
&run.TaskContentSHA256,
&run.ExecutionMode,
&run.SearchQuery,
&appVersion,
&androidVersion,
&pddVersion,
&startedAt,
&receivedAt,
&run.ObservationCount,
&run.CollectionComplete,
&run.ReceivedAfterExecutionExpiry,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, repositoryFailure(err)
}
run.AppVersion = nullableStringFromSQL(appVersion)
run.AndroidVersion = nullableStringFromSQL(androidVersion)
run.PDDVersion = nullableStringFromSQL(pddVersion)
run.StartedAt, err = parseTimestamp(startedAt)
if err == nil {
run.ReceivedAt, err = parseTimestamp(receivedAt)
}
if err != nil {
return nil, repositoryFailure(err)
}
dataset := &domain.CandidateDecisionDataset{
SearchRun: &run,
Observations: make([]domain.CandidateObservation, 0, run.ObservationCount),
Evaluations: make([]domain.CandidateEvaluationRecord, 0, run.ObservationCount),
HumanReviews: make([]domain.CandidateHumanReview, 0),
}
rows, err := queryer.QueryContext(
ctx,
`SELECT observation.task_id, observation.execution_id,
observation.ordinal, observation.title, observation.sku_text,
observation.price_text, observation.product_url,
observation.image_url, observation.evidence_asset_ids_json,
observation.collection_status, observation.observed_at,
identity.candidate_key, identity.card_signature,
identity.detail_signature, identity.detail_evidence_sha256,
identity.specification_evidence_sha256,
identity.identity_version, identity.created_at
FROM candidate_observations AS observation
LEFT JOIN candidate_observation_identities AS identity
ON identity.execution_id = observation.execution_id
AND identity.candidate_ordinal = observation.ordinal
WHERE observation.task_id = ? AND observation.execution_id = ?
ORDER BY observation.ordinal ASC`,
taskID,
executionID,
)
if err != nil {
return nil, repositoryFailure(err)
}
for rows.Next() {
var observation domain.CandidateObservation
var evidenceJSON, observedAt string
var candidateKey, cardSignature, detailSignature sql.NullString
var detailHash, specificationHash, identityAt sql.NullString
var identityVersion sql.NullInt64
if err := rows.Scan(
&observation.TaskID,
&observation.ExecutionID,
&observation.Ordinal,
&observation.Title,
&observation.SKUText,
&observation.PriceText,
&observation.ProductURL,
&observation.ImageURL,
&evidenceJSON,
&observation.CollectionStatus,
&observedAt,
&candidateKey,
&cardSignature,
&detailSignature,
&detailHash,
&specificationHash,
&identityVersion,
&identityAt,
); err != nil {
_ = rows.Close()
return nil, repositoryFailure(err)
}
if err := json.Unmarshal(
[]byte(evidenceJSON),
&observation.EvidenceAssetIDs,
); err != nil {
_ = rows.Close()
return nil, usecase.ErrRepositoryInvariant
}
observation.ObservedAt, err = parseTimestamp(observedAt)
if err != nil {
_ = rows.Close()
return nil, repositoryFailure(err)
}
if candidateKey.Valid {
identityCreatedAt, parseErr := parseTimestamp(identityAt.String)
if parseErr != nil {
_ = rows.Close()
return nil, repositoryFailure(parseErr)
}
observation.Identity = &domain.CandidateObservationIdentity{
CandidateKey: candidateKey.String,
ExecutionID: observation.ExecutionID,
CandidateOrdinal: observation.Ordinal,
CardSignature: cardSignature.String,
DetailSignature: detailSignature.String,
DetailEvidenceSHA256: detailHash.String,
SpecificationEvidenceSHA256: specificationHash.String,
IdentityVersion: int(identityVersion.Int64),
CreatedAt: identityCreatedAt,
}
}
dataset.Observations = append(dataset.Observations, observation)
}
if err := rows.Err(); err != nil {
_ = rows.Close()
return nil, repositoryFailure(err)
}
if err := rows.Close(); err != nil {
return nil, repositoryFailure(err)
}
if err := loadCandidateModelData(ctx, queryer, executionID, dataset); err != nil {
return nil, err
}
if err := loadCandidateHumanReviews(ctx, queryer, executionID, dataset); err != nil {
return nil, err
}
return dataset, nil
}
func loadCandidateModelData(
ctx context.Context,
queryer queryer,
executionID string,
dataset *domain.CandidateDecisionDataset,
) error {
var model domain.CandidateModelRun
var createdAt string
err := queryer.QueryRowContext(
ctx,
`SELECT execution_id, provider_id, model, prompt_version,
schema_version, recommendation_threshold, request_sha256,
result_sha256, created_at
FROM model_runs
WHERE execution_id = ?`,
executionID,
).Scan(
&model.ExecutionID,
&model.ProviderID,
&model.Model,
&model.PromptVersion,
&model.SchemaVersion,
&model.RecommendationThreshold,
&model.RequestSHA256,
&model.ResultSHA256,
&createdAt,
)
if err == nil {
model.CreatedAt, err = parseTimestamp(createdAt)
if err != nil {
return repositoryFailure(err)
}
dataset.ModelRun = &model
} else if !errors.Is(err, sql.ErrNoRows) {
return repositoryFailure(err)
}
rows, err := queryer.QueryContext(
ctx,
`SELECT execution_id, candidate_ordinal, decision, score,
confidence, matched_json, missing_or_uncertain_json,
rejection_reasons_json, hard_constraints_json, created_at
FROM candidate_evaluations
WHERE execution_id = ?
ORDER BY candidate_ordinal ASC`,
executionID,
)
if err != nil {
return repositoryFailure(err)
}
for rows.Next() {
var evaluation domain.CandidateEvaluationRecord
var evaluationAt string
if err := rows.Scan(
&evaluation.ExecutionID,
&evaluation.CandidateOrdinal,
&evaluation.Decision,
&evaluation.Score,
&evaluation.Confidence,
&evaluation.MatchedJSON,
&evaluation.MissingOrUncertainJSON,
&evaluation.RejectionReasonsJSON,
&evaluation.HardConstraintsJSON,
&evaluationAt,
); err != nil {
_ = rows.Close()
return repositoryFailure(err)
}
evaluation.CreatedAt, err = parseTimestamp(evaluationAt)
if err != nil {
_ = rows.Close()
return repositoryFailure(err)
}
dataset.Evaluations = append(dataset.Evaluations, evaluation)
}
if err := rows.Err(); err != nil {
_ = rows.Close()
return repositoryFailure(err)
}
if err := rows.Close(); err != nil {
return repositoryFailure(err)
}
var recommendation domain.CandidateRecommendationRecord
var reasonsJSON, recommendationAt string
err = queryer.QueryRowContext(
ctx,
`SELECT execution_id, candidate_ordinal, conclusion, policy_version,
reasons_json, created_at
FROM candidate_recommendations
WHERE execution_id = ?`,
executionID,
).Scan(
&recommendation.ExecutionID,
&recommendation.CandidateOrdinal,
&recommendation.Conclusion,
&recommendation.PolicyVersion,
&reasonsJSON,
&recommendationAt,
)
if err == nil {
recommendation.ReasonsJSON = reasonsJSON
recommendation.CreatedAt, err = parseTimestamp(recommendationAt)
if err != nil {
return repositoryFailure(err)
}
dataset.Recommendation = &recommendation
} else if !errors.Is(err, sql.ErrNoRows) {
return repositoryFailure(err)
}
return nil
}
func loadCandidateHumanReviews(
ctx context.Context,
queryer queryer,
executionID string,
dataset *domain.CandidateDecisionDataset,
) error {
rows, err := queryer.QueryContext(
ctx,
`SELECT id FROM candidate_human_reviews
WHERE execution_id = ?
ORDER BY version ASC`,
executionID,
)
if err != nil {
return repositoryFailure(err)
}
reviewIDs := make([]string, 0)
for rows.Next() {
var reviewID string
if err := rows.Scan(&reviewID); err != nil {
_ = rows.Close()
return repositoryFailure(err)
}
reviewIDs = append(reviewIDs, reviewID)
}
if err := rows.Err(); err != nil {
_ = rows.Close()
return repositoryFailure(err)
}
if err := rows.Close(); err != nil {
return repositoryFailure(err)
}
for _, reviewID := range reviewIDs {
review, err := getCandidateHumanReview(ctx, queryer, reviewID)
if err != nil {
return err
}
dataset.HumanReviews = append(dataset.HumanReviews, review)
}
return nil
}
func candidateHumanReviewReasons(
ctx context.Context,
queryer queryer,
reviewID string,
ordinal int,
) ([]string, error) {
rows, err := queryer.QueryContext(
ctx,
`SELECT reason_code FROM candidate_human_review_reasons
WHERE review_id = ? AND candidate_ordinal = ?
ORDER BY reason_code ASC`,
reviewID,
ordinal,
)
if err != nil {
return nil, repositoryFailure(err)
}
defer rows.Close()
reasons := make([]string, 0)
for rows.Next() {
var reason string
if err := rows.Scan(&reason); err != nil {
return nil, repositoryFailure(err)
}
reasons = append(reasons, reason)
}
if err := rows.Err(); err != nil {
return nil, repositoryFailure(err)
}
return reasons, nil
}
func hashCandidateResult(candidates string, recommendation *string) string {
digest := sha256.New()
_, _ = digest.Write([]byte(candidates))
_, _ = digest.Write([]byte{0})
if recommendation != nil {
_, _ = digest.Write([]byte(*recommendation))
}
return hex.EncodeToString(digest.Sum(nil))
}
func candidateObservationKey(
executionID string,
candidate usecase.ExecutionCandidate,
) string {
digest := sha256.New()
_, _ = digest.Write([]byte("cmroubao-candidate-v1"))
_, _ = digest.Write([]byte{0})
_, _ = digest.Write([]byte(executionID))
_, _ = digest.Write([]byte{0})
_, _ = digest.Write([]byte(strconv.Itoa(candidate.Ordinal)))
_, _ = digest.Write([]byte{0})
_, _ = digest.Write([]byte(candidate.DetailSignature))
_, _ = digest.Write([]byte{0})
_, _ = digest.Write([]byte(candidate.SpecificationEvidenceSHA256))
return hex.EncodeToString(digest.Sum(nil))
}
func nullableSQLString(value sql.NullString) any {
if !value.Valid {
return nil
}
return value.String
}
func nullableInt(value *int) any {
if value == nil {
return nil
}
return *value
}