first commit of restructured project

This commit is contained in:
KS Jannette
2026-02-22 15:21:18 -05:00
commit 9fca234606
75 changed files with 8299 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
package repository
import (
"context"
"fmt"
"math"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/kjannette/trahn-backend/internal/models"
)
type SRRepo struct {
pool *pgxpool.Pool
}
func NewSRRepo(pool *pgxpool.Pool) *SRRepo {
return &SRRepo{pool: pool}
}
func (r *SRRepo) Record(ctx context.Context, sr *models.SupportResistance) (*models.SupportResistance, error) {
ts := sr.Timestamp
if ts.IsZero() {
ts = time.Now()
}
row := r.pool.QueryRow(ctx,
`INSERT INTO support_resistance_history
(timestamp, method, lookback_days, support, resistance, midpoint, avg_price, grid_recalculated)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
RETURNING *`,
ts, sr.Method, sr.LookbackDays, sr.Support, sr.Resistance,
sr.Midpoint, sr.AvgPrice, sr.GridRecalculated,
)
return scanSR(row)
}
func (r *SRRepo) GetLatest(ctx context.Context) (*models.SupportResistance, error) {
row := r.pool.QueryRow(ctx,
`SELECT * FROM support_resistance_history ORDER BY timestamp DESC LIMIT 1`,
)
sr, err := scanSR(row)
if err != nil {
if err.Error() == "no rows in result set" {
return nil, nil
}
return nil, err
}
return sr, nil
}
func (r *SRRepo) GetHistory(ctx context.Context, limit int) ([]models.SupportResistance, error) {
rows, err := r.pool.Query(ctx,
`SELECT * FROM support_resistance_history ORDER BY timestamp DESC LIMIT $1`,
limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
return collectSRs(rows)
}
func (r *SRRepo) NeedsRefresh(ctx context.Context, refreshHours int) (bool, error) {
latest, err := r.GetLatest(ctx)
if err != nil {
return false, err
}
if latest == nil {
return true, nil
}
age := time.Since(latest.Timestamp)
return age >= time.Duration(refreshHours)*time.Hour, nil
}
type ChangeAnalysis struct {
HasChanged bool `json:"hasChanged"`
ChangePercent *float64 `json:"changePercent"`
Previous *models.SupportResistance `json:"previous"`
Reason string `json:"reason"`
}
func (r *SRRepo) CheckSignificantChange(ctx context.Context, newSR *models.SupportResistance, thresholdPercent float64) (*ChangeAnalysis, error) {
previous, err := r.GetLatest(ctx)
if err != nil {
return nil, err
}
if previous == nil {
return &ChangeAnalysis{
HasChanged: true,
Reason: "First S/R fetch",
}, nil
}
pct := math.Abs((newSR.Midpoint - previous.Midpoint) / previous.Midpoint * 100)
changed := pct >= thresholdPercent
reason := "S/R stable"
if changed {
reason = fmt.Sprintf("Midpoint changed %.2f%%", pct)
}
return &ChangeAnalysis{
HasChanged: changed,
ChangePercent: &pct,
Previous: previous,
Reason: reason,
}, nil
}
// --- scan helpers ---
func scanSR(row scannable) (*models.SupportResistance, error) {
var sr models.SupportResistance
err := row.Scan(
&sr.ID, &sr.Timestamp, &sr.Method, &sr.LookbackDays,
&sr.Support, &sr.Resistance, &sr.Midpoint, &sr.AvgPrice,
&sr.GridRecalculated, &sr.CreatedAt,
)
if err != nil {
return nil, err
}
return &sr, nil
}
func collectSRs(rows rowsIter) ([]models.SupportResistance, error) {
var out []models.SupportResistance
for rows.Next() {
var sr models.SupportResistance
if err := rows.Scan(
&sr.ID, &sr.Timestamp, &sr.Method, &sr.LookbackDays,
&sr.Support, &sr.Resistance, &sr.Midpoint, &sr.AvgPrice,
&sr.GridRecalculated, &sr.CreatedAt,
); err != nil {
return nil, err
}
out = append(out, sr)
}
return out, rows.Err()
}