diff --git a/api/compliance.go b/api/compliance.go index 93f6e2a4..5160411e 100644 --- a/api/compliance.go +++ b/api/compliance.go @@ -3,16 +3,22 @@ package api import ( "bytes" "context" + "encoding/json" "fmt" "io" "net/http" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" - "github.com/Gleipnir-Technology/nidus-sync/platform" + //"github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform/imagetile" "github.com/go-chi/chi/v5" + "github.com/paulmach/orb/geojson" "github.com/rs/zerolog/log" + "github.com/stephenafamo/scan" ) func getComplianceRequestImagePool(w http.ResponseWriter, r *http.Request) { @@ -23,28 +29,62 @@ func getComplianceRequestImagePool(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - comp, err := models.ComplianceReportRequests.Query( - models.Preload.ComplianceReportRequest.Site(), - models.SelectWhere.ComplianceReportRequests.PublicID.EQ(code), - ).One(ctx, db.PGInstance.BobDB) - if err != nil { - http.Error(w, "no comp", http.StatusInternalServerError) - return - } + /* + comp, err := models.ComplianceReportRequests.Query( + models.Preload.ComplianceReportRequest.Lead(), + models.SelectWhere.ComplianceReportRequests.PublicID.EQ(code), + ).One(ctx, db.PGInstance.BobDB) + if err != nil { + http.Error(w, "no comp", http.StatusInternalServerError) + return + } - site := comp.R.Site - org, err := models.FindOrganization(ctx, db.PGInstance.BobDB, site.OrganizationID) + lead := comp.R.Lead + site := lead.R.Site + */ + type _Row struct { + Envelope string `db:"parcel_envelope"` + OrganizationID int32 `db:"organization_id"` + } + row, err := bob.One(ctx, db.PGInstance.BobDB, psql.Select( + sm.Columns( + "ST_AsGeoJSON(ST_Envelope(parcel.geometry)) AS parcel_envelope", + "organization.id AS organization_id", + ), + sm.From("compliance_report_request"), + sm.InnerJoin("lead").OnEQ( + psql.Quote("compliance_report_request.lead_id"), + psql.Quote("organization.id"), + ), + sm.InnerJoin("organization").OnEQ( + psql.Quote("lead.organization_id"), + psql.Quote("organization.id"), + ), + sm.InnerJoin("site").On( + psql.And( + psql.Quote("lead.site_id").EQ(psql.Quote("site.id")), + psql.Quote("lead.site_version").EQ(psql.Quote("site.version")), + ), + ), + sm.InnerJoin("parcel").OnEQ( + psql.Quote("site.parcel_id"), + psql.Quote("parcel.id"), + ), + sm.Where(psql.Quote("compliance_report_request").EQ(psql.Arg(code))), + ), scan.StructMapper[_Row]()) + org, err := models.FindOrganization(ctx, db.PGInstance.BobDB, row.OrganizationID) if err != nil { http.Error(w, "no org", http.StatusInternalServerError) return } - envelope, err := platform.ParcelEnvelope(ctx, site.ParcelID) + var polygon geojson.Polygon + err = json.Unmarshal([]byte(row.Envelope), &polygon) if err != nil { - log.Error().Err(err).Msg("parcel envelop failure") - http.Error(w, "parcel env", http.StatusInternalServerError) + log.Error().Err(err).Msg("unmarshal json") + http.Error(w, "unmarshal envelope json", http.StatusInternalServerError) return } - ring := (*envelope)[0] + ring := polygon[0] p := ring[0] err = writeImage(ctx, w, org, 19, p[1], p[0]) if err != nil { diff --git a/api/signal.go b/api/signal.go index 256419b6..15ac79a9 100644 --- a/api/signal.go +++ b/api/signal.go @@ -3,13 +3,69 @@ package api import ( "context" "net/http" + "time" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" nhttp "github.com/Gleipnir-Technology/nidus-sync/http" + "github.com/Gleipnir-Technology/nidus-sync/platform" + "github.com/aarondl/opt/null" ) -type contentListSignal struct{} +type signal struct { + Addressed *time.Time `json:"addressed"` + Addressor *platform.User `json:"addressed"` + Created time.Time `json:"created"` + Creator platform.User `json:"creator"` + ID int32 `json:"id"` + Species string `json:"species"` + Type string `json:"type"` +} +type contentListSignal struct { + Signals []signal `json:"signals"` +} func listSignal(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*contentListSignal, *nhttp.ErrorWithStatus) { - return nil, nil + rows, err := models.Signals.Query( + models.SelectWhere.Signals.OrganizationID.EQ(org.ID), + sm.OrderBy("created").Desc(), + ).All(ctx, db.PGInstance.BobDB) + if err != nil { + return nil, nhttp.NewError("failed to get signals: %w", err) + } + users_by_id, err := platform.UsersByID(ctx, org) + if err != nil { + return nil, nhttp.NewError("users by id: %w", err) + } + signals := make([]signal, len(rows)) + for i, row := range rows { + var species string = "" + if row.Species.IsValue() { + species = row.Species.MustGet().String() + } + signals[i] = signal{ + Addressed: row.Addressed.Ptr(), + Addressor: userOrNil(users_by_id, row.Addressor), + Created: row.Created, + Creator: *users_by_id[row.Creator], + ID: row.ID, + Species: species, + Type: row.Type.String(), + } + } + return &contentListSignal{ + Signals: signals, + }, nil +} + +func userOrNil(usersByID map[int32]*platform.User, id null.Val[int32]) *platform.User { + if id.IsNull() { + return nil + } + u, ok := usersByID[id.MustGet()] + if !ok { + return nil + } + return u } diff --git a/html/template/sync/intelligence-root.html b/html/template/sync/intelligence-root.html index 2700eb67..56d9e7c1 100644 --- a/html/template/sync/intelligence-root.html +++ b/html/template/sync/intelligence-root.html @@ -7,253 +7,499 @@ src="//unpkg.com/maplibre-gl@5.0.1/dist/maplibre-gl.js" > + + {{ end }} {{ define "content" }} - - -
-
-

Daily Planning Workbench

-
- Signals and leads enter from the left, are investigated in the center, - and transformed into structured field assignments using tools on the - right. -
-
-
- -
- -
-
-
- Incoming Signals & Leads -
-
- -
-
Species
- - -
Signal Type
- - -
Sort By
- -
- -
- - -
-
Signals
- -
-
- Public Report – Service Request #1024 -
-
- Aedes aegypti • Standing Water Complaint -
-
- -
-
- Trap Spike – H3 8828308281fffff -
-
Culex pipiens • Adult Surge
-
- -
-
- Residual Expiring – Parcel 45-233-01 -
-
Reinspection Due
-
-
- -
- - -
-
Mosquito Control Plan Follow-Ups
- -
-
- Plan Follow-Up – Greenway HOA Basin -
-
- Residential Section • Verification Required -
- Plan -
- -
-
- Plan Follow-Up – Ag Irrigation Canal 7B -
-
Ag Section • Monitoring Window
- Plan -
-
- -
- - -
-
Existing Leads
- -
-
Lead #L-204
-
3 Signals • Aedes aegypti
-
- -
-
Lead #L-198
-
2 Signals • Culex pipiens
-
-
+
+ +
+
+

Daily Planning Workbench

+
+ Signals and leads enter from the left, are investigated in the center, + and transformed into structured field assignments using tools on the + right.
- -
-
-
- Active Investigation Workbench -
-
-
- Map Placeholder
- H3 Cells • Parcels • Signal Density • Lead Clusters +
+ +
+
+
+ Incoming Signals & Leads
+
+ +
+
Species
+ -
-
-
-
-
Selected Signals
-
3 Signals Selected
-
    -
  • Public Report – Aedes aegypti
  • -
  • Trap Spike – Culex pipiens
  • -
  • Plan Follow-Up – HOA Basin
  • -
+
Signal Type
+ + +
Sort By
+ +
+ +
+ + +
+
+ Signals + +
+ + +
+ +
+ + +
+
+ Mosquito Control Plan Follow-Ups +
+ + +
+ +
+ + +
+
Existing Leads
+ + +
+
+
+
+ + +
+
+
+ Active Investigation Workbench +
+
+
+ Map Placeholder
+ H3 Cells • Parcels • Signal Density • Lead Clusters +
+ +
+
+
+
+
Selected Signals
+
+ + + +
    + +
+ + +
-
-
-
-
-
Derived Lead Strength
-
Signal Convergence Score
-
- High Confidence +
+
+
+
Derived Lead Strength
+
Signal Convergence Score
+
+ + + + +
+
+
+
+ +
+
+
+
Related Communications
+
+ Inbound & outbound contact +
+
    +
  • Resident follow-up requested
  • +
  • HOA notification sent
  • +
+
+
+
+ +
+
+
+
Priority Context
+
Risk synthesis
+ +
- -
-
-
-
Related Communications
-
Inbound & outbound contact
-
    -
  • Resident follow-up requested
  • -
  • HOA notification sent
  • -
-
-
-
- -
-
-
-
Priority Context
-
Risk synthesis
- Elevated Aedes Risk -
-
-
-
- -
-
-
Transformation Tools
-
-
-
Signal → Lead
- - - + +
+
+
+ Transformation Tools
+
+
+
Signal → Lead
+ + + +
-
+
-
-
Lead → Field Assignment
- - - -
+
+
Lead → Field Assignment
+ + + +
-
+
-
-
Assignment Controls
- - - +
+
Assignment Controls
+ + + +