You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by "AndreiS-gh (via GitHub)" <gi...@apache.org> on 2026/07/08 12:19:21 UTC

[PR] feat(gh-copilot): add AI credit usage billing metrics collection [devlake]

AndreiS-gh opened a new pull request, #8980:
URL: https://github.com/apache/devlake/pull/8980

   ## Summary
   
   Add support for GitHub Copilot AI credit usage billing metrics collection via the GitHub Copilot billing API endpoints.
   
   This PR closes gaps identified in the [copilot-metrics-roi-toolkit](https://github.com/DevExpGbb/copilot-metrics-roi-toolkit) schema reconciliation, specifically the missing **Billing Usage** API group (3 endpoints).
   
   ## Changes
   
   ### New Models (3 tables)
   - `GhCopilotEnterpriseAiCreditUsage` — Enterprise-level AI credit tracking with cost center support
   - `GhCopilotOrgAiCreditUsage` — Organization-level AI credit tracking  
   - `GhCopilotUserAiCreditUsage` — Individual user-level AI credit tracking
   
   ### New Tasks
   - **Collector** (`ai_credit_collector.go`) — Routes requests to appropriate GitHub billing API endpoint based on connection type (enterprise/org/user) and filters by date range
   - **Extractor** (`ai_credit_extractor.go`) — Parses credit usage items and routes records to corresponding tables
   
   ### New Migration
   - `20260708_add_ai_credit_usage_metrics.go` — Registers all three billing tables
   
   ### E2E Tests
   - Added snapshot tables for all three billing levels with realistic test data
   
   ## Technical Details
   
   **GitHub Copilot Billing API Endpoints Covered:**
   - `GET /enterprises/{enterprise}/settings/billing/ai_credit/usage`
   - `GET /organizations/{org}/settings/billing/ai_credit/usage`
   - `GET /users/{username}/settings/billing/ai_credit/usage`
   
   **Data Structure (denormalized per model per entity per time period):**
   - Time period: year, month, day
   - Model name (e.g., gpt-4.1, claude-3)
   - Cost center (enterprise only)
   - Usage metrics: gross/net quantity, pricing, discounts
   
   **Connection Type Routing:**
   - Enterprise connections → `_tool_copilot_enterprise_ai_credit_usage`
   - Organization connections → `_tool_copilot_org_ai_credit_usage`
   - User-scoped connections → `_tool_copilot_user_ai_credit_usage`
   
   ## Testing
   - [x] Migration script registered in `migrationscripts/register.go`
   - [x] Models registered in `models.go` and `models_test.go`
   - [x] Subtasks registered in `register.go` and `subtasks.go`
   - [x] E2E snapshot tables added with test data
   - [x] Go formatting verified
   
   ## Related
   - Metrics parity gap analysis: copilot-metrics-roi-toolkit/blob/main/copilot-api-schemas/README.md
   - Reference PR: #8889 (user-teams metrics collection by @jaroslawgajewski)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] feat(gh-copilot): add AI credit usage billing metrics collection [devlake]

Posted by "ewega (via GitHub)" <gi...@apache.org>.
ewega commented on PR #8980:
URL: https://github.com/apache/devlake/pull/8980#issuecomment-5220433483

   I am realizing that this is identical to #9019. Will close this down


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] feat(gh-copilot): add AI credit usage billing metrics collection [devlake]

Posted by "ewega (via GitHub)" <gi...@apache.org>.
ewega commented on PR #8980:
URL: https://github.com/apache/devlake/pull/8980#issuecomment-4961414868

   Thanks for taking this on @AndreiS-gh ! Duplicate of #8965 and fixes #8961. Mind fixing tests?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] feat(gh-copilot): add AI credit usage billing metrics collection [devlake]

Posted by "AndreiS-gh (via GitHub)" <gi...@apache.org>.
AndreiS-gh commented on PR #8980:
URL: https://github.com/apache/devlake/pull/8980#issuecomment-5069476463

   > Thanks for taking this on @AndreiS-gh ! Duplicate of #8965 and fixes #8961. Mind fixing tests?
   
   can we rerun the pipeliens now ?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] feat(gh-copilot): add AI credit usage billing metrics collection [devlake]

Posted by "PankajChaudhari-Atos (via GitHub)" <gi...@apache.org>.
PankajChaudhari-Atos commented on PR #8980:
URL: https://github.com/apache/devlake/pull/8980#issuecomment-5070941664

   > Thanks for taking this on @AndreiS-gh ! Duplicate of #8965 and fixes #8961. Mind fixing tests?
   
   Hello @ewega 
   All the issues reported by the PR review bot, as well as the test failures, have been fixed. Could you please review the changes and proceed with the next steps?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] feat(gh-copilot): add AI credit usage billing metrics collection [devlake]

Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #8980:
URL: https://github.com/apache/devlake/pull/8980#discussion_r3573297938


##########
backend/plugins/gh-copilot/tasks/ai_credit_collector.go:
##########
@@ -0,0 +1,118 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+)
+
+const rawAiCreditUsageTable = "copilot_ai_credit_usage"
+
+// CollectAiCreditUsage collects AI credit usage data from the billing API endpoints.
+// Routes requests to enterprise, organization, or user endpoints based on connection configuration.
+func CollectAiCreditUsage(taskCtx plugin.SubTaskContext) errors.Error {
+	data, ok := taskCtx.TaskContext().GetData().(*GhCopilotTaskData)
+	if !ok {
+		return errors.Default.New("task data is not GhCopilotTaskData")
+	}
+	connection := data.Connection
+	connection.Normalize()
+
+	var urlTemplate string
+	var scope string
+
+	if connection.HasEnterprise() {
+		urlTemplate = fmt.Sprintf("enterprises/%s/settings/billing/ai_credit/usage", connection.Enterprise)
+		scope = connection.Enterprise
+	} else if connection.Organization != "" {
+		urlTemplate = fmt.Sprintf("organizations/%s/settings/billing/ai_credit/usage", connection.Organization)
+		scope = connection.Organization
+	} else {
+		// User-level credits are scoped to current authenticated user
+		urlTemplate = "user/settings/billing/ai_credit/usage"
+		scope = "user"
+	}
+
+	// Build query parameters for date range
+	now := time.Now().UTC()
+	params := url.Values{}
+	params.Set("year", strconv.Itoa(now.Year()))
+	params.Set("month", strconv.Itoa(int(now.Month())))
+	params.Set("day", strconv.Itoa(now.Day()))
+
+	apiCollector, err := helper.NewApiCollector(taskCtx, rawAiCreditUsageTable)
+	if err != nil {
+		return err
+	}

Review Comment:
   This collector uses helper.NewApiCollector with a (taskCtx, table) signature, but the pluginhelper/api package only exposes NewApiCollector(ApiCollectorArgs). As written, this will not compile and also doesn’t provide an ApiClient (required by ApiCollectorArgs). Refactor to the same pattern used by other gh-copilot collectors: CreateApiClient(...) + NewStatefulApiCollector(rawArgs) + InitCollector(helper.ApiCollectorArgs{...}).



##########
backend/plugins/gh-copilot/tasks/ai_credit_collector.go:
##########
@@ -0,0 +1,118 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+)
+
+const rawAiCreditUsageTable = "copilot_ai_credit_usage"
+
+// CollectAiCreditUsage collects AI credit usage data from the billing API endpoints.
+// Routes requests to enterprise, organization, or user endpoints based on connection configuration.
+func CollectAiCreditUsage(taskCtx plugin.SubTaskContext) errors.Error {
+	data, ok := taskCtx.TaskContext().GetData().(*GhCopilotTaskData)
+	if !ok {
+		return errors.Default.New("task data is not GhCopilotTaskData")
+	}
+	connection := data.Connection
+	connection.Normalize()
+
+	var urlTemplate string
+	var scope string
+
+	if connection.HasEnterprise() {
+		urlTemplate = fmt.Sprintf("enterprises/%s/settings/billing/ai_credit/usage", connection.Enterprise)
+		scope = connection.Enterprise
+	} else if connection.Organization != "" {
+		urlTemplate = fmt.Sprintf("organizations/%s/settings/billing/ai_credit/usage", connection.Organization)
+		scope = connection.Organization
+	} else {
+		// User-level credits are scoped to current authenticated user
+		urlTemplate = "user/settings/billing/ai_credit/usage"
+		scope = "user"
+	}
+
+	// Build query parameters for date range
+	now := time.Now().UTC()
+	params := url.Values{}
+	params.Set("year", strconv.Itoa(now.Year()))
+	params.Set("month", strconv.Itoa(int(now.Month())))
+	params.Set("day", strconv.Itoa(now.Day()))
+
+	apiCollector, err := helper.NewApiCollector(taskCtx, rawAiCreditUsageTable)
+	if err != nil {
+		return err
+	}
+
+	err = apiCollector.CollectPages(
+		func(col *helper.ApiCollector) errors.Error {
+			col.GetAttr("scope", scope)
+			col.SetRelation(fmt.Sprintf("%s-%s", data.Connection.ID, scope))
+			col.SetQuery("pageSize", "100")
+			for key, vals := range params {
+				col.SetQuery(key, vals[0])
+			}
+			col.GetNextPageCustomerizedPath(fmt.Sprintf("%s?%s", urlTemplate, params.Encode()), nil)
+			return nil

Review Comment:
   The helper.ApiCollector type does not provide GetAttr/SetRelation/SetQuery/GetNextPageCustomerizedPath/SaveRaw methods in helpers/pluginhelper/api. These calls will not compile. Use helper.ApiCollectorArgs (Query/ResponseParser/GetNextPageCustomData/etc.) with a stateful collector (as in seat_collector.go / user_metrics_collector.go) and return []json.RawMessage from ResponseParser instead of manually calling SaveRaw.



##########
backend/plugins/gh-copilot/tasks/ai_credit_extractor.go:
##########
@@ -0,0 +1,180 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"encoding/json"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/gh-copilot/models"
+)
+
+// aiCreditUsageRecord represents a single usage item from the AI credit usage API.
+type aiCreditUsageRecord struct {
+	Product          string  `json:"product"`
+	Sku              string  `json:"sku"`
+	Model            string  `json:"model"`
+	UnitType         string  `json:"unitType"`
+	PricePerUnit     float64 `json:"pricePerUnit"`
+	GrossQuantity    float64 `json:"grossQuantity"`
+	DiscountQuantity float64 `json:"discountQuantity"`
+	NetQuantity      float64 `json:"netQuantity"`
+	GrossAmount      float64 `json:"grossAmount"`
+	DiscountAmount   float64 `json:"discountAmount"`
+	NetAmount        float64 `json:"netAmount"`
+}
+
+// aiCreditResponseWrapper represents the wrapper around the API response containing time period and usage items.
+type aiCreditResponseWrapper struct {
+	TimePeriod struct {
+		Year  int `json:"year"`
+		Month int `json:"month"`
+		Day   int `json:"day"`
+	} `json:"timePeriod"`
+	Enterprise   string `json:"enterprise"`
+	Organization string `json:"organization"`
+	User         string `json:"user"`
+	Product      string `json:"product"`
+	Model        string `json:"model"`
+	CostCenter   struct {
+		Id   string `json:"id"`
+		Name string `json:"name"`
+	} `json:"costCenter"`
+}
+
+// ExtractAiCreditUsage parses AI credit usage records into the appropriate model tables.
+func ExtractAiCreditUsage(taskCtx plugin.SubTaskContext) errors.Error {
+	data, ok := taskCtx.TaskContext().GetData().(*GhCopilotTaskData)
+	if !ok {
+		return errors.Default.New("task data is not GhCopilotTaskData")
+	}
+	connection := data.Connection
+	connection.Normalize()
+
+	extractor, err := helper.NewApiExtractor(taskCtx, rawAiCreditUsageTable)
+	if err != nil {
+		return err
+	}

Review Comment:
   This extractor is constructed with helper.NewApiExtractor(taskCtx, rawAiCreditUsageTable), but pluginhelper/api only exposes NewApiExtractor(ApiExtractorArgs). This will not compile. Follow the pattern used elsewhere (e.g. metrics_extractor.go): build helper.ApiExtractorArgs with RawDataSubTaskArgs{Ctx, Table, Options} and an Extract func, then call extractor.Execute().



##########
backend/plugins/gh-copilot/tasks/ai_credit_extractor.go:
##########
@@ -0,0 +1,180 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"encoding/json"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/gh-copilot/models"
+)
+
+// aiCreditUsageRecord represents a single usage item from the AI credit usage API.
+type aiCreditUsageRecord struct {
+	Product          string  `json:"product"`
+	Sku              string  `json:"sku"`
+	Model            string  `json:"model"`
+	UnitType         string  `json:"unitType"`
+	PricePerUnit     float64 `json:"pricePerUnit"`
+	GrossQuantity    float64 `json:"grossQuantity"`
+	DiscountQuantity float64 `json:"discountQuantity"`
+	NetQuantity      float64 `json:"netQuantity"`
+	GrossAmount      float64 `json:"grossAmount"`
+	DiscountAmount   float64 `json:"discountAmount"`
+	NetAmount        float64 `json:"netAmount"`
+}
+
+// aiCreditResponseWrapper represents the wrapper around the API response containing time period and usage items.
+type aiCreditResponseWrapper struct {
+	TimePeriod struct {
+		Year  int `json:"year"`
+		Month int `json:"month"`
+		Day   int `json:"day"`
+	} `json:"timePeriod"`
+	Enterprise   string `json:"enterprise"`
+	Organization string `json:"organization"`
+	User         string `json:"user"`
+	Product      string `json:"product"`
+	Model        string `json:"model"`
+	CostCenter   struct {
+		Id   string `json:"id"`
+		Name string `json:"name"`
+	} `json:"costCenter"`
+}
+
+// ExtractAiCreditUsage parses AI credit usage records into the appropriate model tables.
+func ExtractAiCreditUsage(taskCtx plugin.SubTaskContext) errors.Error {
+	data, ok := taskCtx.TaskContext().GetData().(*GhCopilotTaskData)
+	if !ok {
+		return errors.Default.New("task data is not GhCopilotTaskData")
+	}
+	connection := data.Connection
+	connection.Normalize()
+
+	extractor, err := helper.NewApiExtractor(taskCtx, rawAiCreditUsageTable)
+	if err != nil {
+		return err
+	}
+
+	err = extractor.Extract(
+		rawAiCreditUsageTable,
+		func(row *helper.RawData) ([]interface{}, errors.Error) {
+			// Parse raw data
+			var record aiCreditUsageRecord
+			err := json.Unmarshal(row.Data, &record)
+			if err != nil {
+				return nil, errors.Convert(err)
+			}
+
+			// Extract wrapper info from row context
+			var wrapper aiCreditResponseWrapper
+			if scopeAttr, ok := row.Params["scope"]; ok {
+				if connection.HasEnterprise() {
+					wrapper.Enterprise = scopeAttr.(string)
+				} else if connection.Organization != "" {
+					wrapper.Organization = scopeAttr.(string)
+				}
+			}
+			wrapper.Product = record.Product
+			wrapper.Model = record.Model
+
+			// Parse date from row (need to extract from response context)
+			// For now, use current date - this should be enhanced to parse from API response
+			now := time.Now().UTC()
+			wrapper.TimePeriod.Year = now.Year()
+			wrapper.TimePeriod.Month = int(now.Month())
+			wrapper.TimePeriod.Day = now.Day()
+

Review Comment:
   Using time.Now() to populate Year/Month/Day will make ingested records nondeterministic and mismatched with the billing API’s requested/returned time period (and will not match the E2E snapshots you added). Populate the time period from the request input/params (preferred) or from fields in the API response if present, rather than the current clock.



##########
backend/plugins/gh-copilot/tasks/ai_credit_extractor.go:
##########
@@ -0,0 +1,180 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"encoding/json"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/gh-copilot/models"
+)

Review Comment:
   This file imports strconv but never uses it, which will fail compilation. Remove the unused import (or use it if intended).



##########
backend/plugins/gh-copilot/tasks/ai_credit_extractor.go:
##########
@@ -0,0 +1,180 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"encoding/json"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/gh-copilot/models"
+)
+
+// aiCreditUsageRecord represents a single usage item from the AI credit usage API.
+type aiCreditUsageRecord struct {
+	Product          string  `json:"product"`
+	Sku              string  `json:"sku"`
+	Model            string  `json:"model"`
+	UnitType         string  `json:"unitType"`
+	PricePerUnit     float64 `json:"pricePerUnit"`
+	GrossQuantity    float64 `json:"grossQuantity"`
+	DiscountQuantity float64 `json:"discountQuantity"`
+	NetQuantity      float64 `json:"netQuantity"`
+	GrossAmount      float64 `json:"grossAmount"`
+	DiscountAmount   float64 `json:"discountAmount"`
+	NetAmount        float64 `json:"netAmount"`
+}
+
+// aiCreditResponseWrapper represents the wrapper around the API response containing time period and usage items.
+type aiCreditResponseWrapper struct {
+	TimePeriod struct {
+		Year  int `json:"year"`
+		Month int `json:"month"`
+		Day   int `json:"day"`
+	} `json:"timePeriod"`
+	Enterprise   string `json:"enterprise"`
+	Organization string `json:"organization"`
+	User         string `json:"user"`
+	Product      string `json:"product"`
+	Model        string `json:"model"`
+	CostCenter   struct {
+		Id   string `json:"id"`
+		Name string `json:"name"`
+	} `json:"costCenter"`
+}
+
+// ExtractAiCreditUsage parses AI credit usage records into the appropriate model tables.
+func ExtractAiCreditUsage(taskCtx plugin.SubTaskContext) errors.Error {
+	data, ok := taskCtx.TaskContext().GetData().(*GhCopilotTaskData)
+	if !ok {
+		return errors.Default.New("task data is not GhCopilotTaskData")
+	}
+	connection := data.Connection
+	connection.Normalize()
+
+	extractor, err := helper.NewApiExtractor(taskCtx, rawAiCreditUsageTable)
+	if err != nil {
+		return err
+	}
+
+	err = extractor.Extract(
+		rawAiCreditUsageTable,
+		func(row *helper.RawData) ([]interface{}, errors.Error) {
+			// Parse raw data
+			var record aiCreditUsageRecord
+			err := json.Unmarshal(row.Data, &record)
+			if err != nil {
+				return nil, errors.Convert(err)
+			}
+
+			// Extract wrapper info from row context
+			var wrapper aiCreditResponseWrapper
+			if scopeAttr, ok := row.Params["scope"]; ok {
+				if connection.HasEnterprise() {
+					wrapper.Enterprise = scopeAttr.(string)
+				} else if connection.Organization != "" {
+					wrapper.Organization = scopeAttr.(string)
+				}
+			}

Review Comment:
   row.Params is a string (scope params JSON), not a map, so indexing row.Params["scope"] will not compile. If you need the enterprise/org identifier during extraction, either (a) carry it in RawDataSubTaskArgs.Options and unmarshal row.Params into the options struct, or (b) store it in RawData.Input by using a stateful collector Input iterator.



##########
backend/plugins/gh-copilot/tasks/ai_credit_extractor.go:
##########
@@ -0,0 +1,180 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"encoding/json"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/gh-copilot/models"
+)
+
+// aiCreditUsageRecord represents a single usage item from the AI credit usage API.
+type aiCreditUsageRecord struct {
+	Product          string  `json:"product"`
+	Sku              string  `json:"sku"`
+	Model            string  `json:"model"`
+	UnitType         string  `json:"unitType"`
+	PricePerUnit     float64 `json:"pricePerUnit"`
+	GrossQuantity    float64 `json:"grossQuantity"`
+	DiscountQuantity float64 `json:"discountQuantity"`
+	NetQuantity      float64 `json:"netQuantity"`
+	GrossAmount      float64 `json:"grossAmount"`
+	DiscountAmount   float64 `json:"discountAmount"`
+	NetAmount        float64 `json:"netAmount"`
+}
+
+// aiCreditResponseWrapper represents the wrapper around the API response containing time period and usage items.
+type aiCreditResponseWrapper struct {
+	TimePeriod struct {
+		Year  int `json:"year"`
+		Month int `json:"month"`
+		Day   int `json:"day"`
+	} `json:"timePeriod"`
+	Enterprise   string `json:"enterprise"`
+	Organization string `json:"organization"`
+	User         string `json:"user"`
+	Product      string `json:"product"`
+	Model        string `json:"model"`
+	CostCenter   struct {
+		Id   string `json:"id"`
+		Name string `json:"name"`
+	} `json:"costCenter"`
+}
+
+// ExtractAiCreditUsage parses AI credit usage records into the appropriate model tables.
+func ExtractAiCreditUsage(taskCtx plugin.SubTaskContext) errors.Error {
+	data, ok := taskCtx.TaskContext().GetData().(*GhCopilotTaskData)
+	if !ok {
+		return errors.Default.New("task data is not GhCopilotTaskData")
+	}
+	connection := data.Connection
+	connection.Normalize()
+
+	extractor, err := helper.NewApiExtractor(taskCtx, rawAiCreditUsageTable)
+	if err != nil {
+		return err
+	}
+
+	err = extractor.Extract(
+		rawAiCreditUsageTable,
+		func(row *helper.RawData) ([]interface{}, errors.Error) {
+			// Parse raw data
+			var record aiCreditUsageRecord
+			err := json.Unmarshal(row.Data, &record)
+			if err != nil {
+				return nil, errors.Convert(err)
+			}
+
+			// Extract wrapper info from row context
+			var wrapper aiCreditResponseWrapper
+			if scopeAttr, ok := row.Params["scope"]; ok {
+				if connection.HasEnterprise() {
+					wrapper.Enterprise = scopeAttr.(string)
+				} else if connection.Organization != "" {
+					wrapper.Organization = scopeAttr.(string)
+				}
+			}
+			wrapper.Product = record.Product
+			wrapper.Model = record.Model
+
+			// Parse date from row (need to extract from response context)
+			// For now, use current date - this should be enhanced to parse from API response
+			now := time.Now().UTC()
+			wrapper.TimePeriod.Year = now.Year()
+			wrapper.TimePeriod.Month = int(now.Month())
+			wrapper.TimePeriod.Day = now.Day()
+
+			var results []interface{}
+
+			// Route to appropriate table based on connection type
+			if connection.HasEnterprise() {
+				toolRecord := &models.GhCopilotEnterpriseAiCreditUsage{
+					ConnectionId:     data.Connection.ID,
+					ScopeId:          data.Options.ScopeId,
+					Year:             wrapper.TimePeriod.Year,
+					Month:            wrapper.TimePeriod.Month,
+					Day:              wrapper.TimePeriod.Day,
+					Enterprise:       wrapper.Enterprise,
+					Model:            record.Model,
+					Organization:     wrapper.Organization,
+					User:             wrapper.User,
+					Product:          record.Product,
+					CostCenterId:     wrapper.CostCenter.Id,
+					CostCenterName:   wrapper.CostCenter.Name,
+					GrossQuantity:    record.GrossQuantity,
+					DiscountQuantity: record.DiscountQuantity,
+					NetQuantity:      record.NetQuantity,
+					PricePerUnit:     record.PricePerUnit,
+					GrossAmount:      record.GrossAmount,
+					DiscountAmount:   record.DiscountAmount,
+					NetAmount:        record.NetAmount,
+				}
+				results = append(results, toolRecord)
+			} else if connection.Organization != "" {
+				toolRecord := &models.GhCopilotOrgAiCreditUsage{
+					ConnectionId:     data.Connection.ID,
+					ScopeId:          data.Options.ScopeId,
+					Year:             wrapper.TimePeriod.Year,
+					Month:            wrapper.TimePeriod.Month,
+					Day:              wrapper.TimePeriod.Day,
+					Organization:     wrapper.Organization,
+					Model:            record.Model,
+					User:             wrapper.User,
+					Product:          record.Product,
+					GrossQuantity:    record.GrossQuantity,
+					DiscountQuantity: record.DiscountQuantity,
+					NetQuantity:      record.NetQuantity,
+					PricePerUnit:     record.PricePerUnit,
+					GrossAmount:      record.GrossAmount,
+					DiscountAmount:   record.DiscountAmount,
+					NetAmount:        record.NetAmount,
+				}
+				results = append(results, toolRecord)
+			} else {
+				// User-level credits
+				toolRecord := &models.GhCopilotUserAiCreditUsage{
+					ConnectionId:     data.Connection.ID,
+					ScopeId:          data.Options.ScopeId,
+					Year:             wrapper.TimePeriod.Year,
+					Month:            wrapper.TimePeriod.Month,
+					Day:              wrapper.TimePeriod.Day,
+					User:             connection.GetEmail(), // Use authenticated user
+					Model:            record.Model,
+					Product:          record.Product,

Review Comment:
   connection.GetEmail() is called here, but GhCopilotConnection (and its embedded BaseConnection) doesn’t define GetEmail(), so this won’t compile. Also, the model field is documented as a GitHub username. Store the username from the API payload (or from connection config if user-scoped) instead of calling a non-existent method.



##########
backend/plugins/gh-copilot/models/migrationscripts/20260708_add_ai_credit_usage_metrics.go:
##########
@@ -0,0 +1,45 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package migrationscripts
+
+import (
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/helpers/migrationhelper"
+	"github.com/apache/incubator-devlake/plugins/gh-copilot/models"
+)

Review Comment:
   The import of core/models/migrationscripts/archived is unused, which will fail compilation. Remove it from the import block.



##########
backend/plugins/gh-copilot/models/user_ai_credit_usage.go:
##########
@@ -0,0 +1,52 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package models
+
+import (
+	"github.com/apache/incubator-devlake/core/models/common"
+)
+
+// GhCopilotUserAiCreditUsage tracks AI credit consumption at the individual user level.
+// One row per time period per model per authenticated user.
+type GhCopilotUserAiCreditUsage struct {
+	ConnectionId uint64 `gorm:"primaryKey" json:"connectionId"`
+	ScopeId      string `gorm:"primaryKey;type:varchar(255)" json:"scopeId"`
+	Year         int    `gorm:"primaryKey" json:"year"`
+	Month        int    `gorm:"primaryKey" json:"month"`
+	Day          int    `gorm:"primaryKey" json:"day"`
+
+	User  string `gorm:"primaryKey;type:varchar(255)" json:"user" gorm:"comment:GitHub username"`
+	Model string `gorm:"primaryKey;type:varchar(255)" json:"model" gorm:"comment:AI model name (e.g., gpt-4.1)"`
+
+	Product string `gorm:"type:varchar(32)" json:"product" gorm:"comment:Product name (e.g., copilot)"`

Review Comment:
   These struct fields repeat the `gorm:` tag key twice (e.g., `gorm:"..." ... gorm:"comment:..."`). Go struct tags do not merge duplicate keys; the second `gorm:` value will be ignored by reflect-based parsers (so the comment won’t be applied). Combine the comment into the single `gorm` tag (or drop it).



##########
backend/plugins/gh-copilot/models/enterprise_ai_credit_usage.go:
##########
@@ -0,0 +1,56 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package models
+
+import (
+	"github.com/apache/incubator-devlake/core/models/common"
+)
+
+// GhCopilotEnterpriseAiCreditUsage tracks AI credit consumption at the enterprise level.
+// One row per time period per model per entity (user, org, or cost center).
+type GhCopilotEnterpriseAiCreditUsage struct {
+	ConnectionId uint64 `gorm:"primaryKey" json:"connectionId"`
+	ScopeId      string `gorm:"primaryKey;type:varchar(255)" json:"scopeId"`
+	Year         int    `gorm:"primaryKey" json:"year"`
+	Month        int    `gorm:"primaryKey" json:"month"`
+	Day          int    `gorm:"primaryKey" json:"day"`
+
+	Enterprise   string `gorm:"primaryKey;type:varchar(255)" json:"enterprise" gorm:"comment:Enterprise slug"`
+	Model        string `gorm:"primaryKey;type:varchar(255)" json:"model" gorm:"comment:AI model name (e.g., gpt-4.1)"`
+	Organization string `gorm:"index;type:varchar(255)" json:"organization" gorm:"comment:Organization within enterprise, if specified"`
+	User         string `gorm:"index;type:varchar(255)" json:"user" gorm:"comment:Username, if specified"`
+

Review Comment:
   These fields repeat the `gorm:` tag key twice; reflect-based tag parsing will ignore the later `gorm:"comment:..."` part, so the comments won’t be applied. Combine the comment into the single `gorm` tag for each field.



##########
backend/plugins/gh-copilot/tasks/ai_credit_collector.go:
##########
@@ -0,0 +1,118 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+)
+
+const rawAiCreditUsageTable = "copilot_ai_credit_usage"
+
+// CollectAiCreditUsage collects AI credit usage data from the billing API endpoints.
+// Routes requests to enterprise, organization, or user endpoints based on connection configuration.
+func CollectAiCreditUsage(taskCtx plugin.SubTaskContext) errors.Error {
+	data, ok := taskCtx.TaskContext().GetData().(*GhCopilotTaskData)
+	if !ok {
+		return errors.Default.New("task data is not GhCopilotTaskData")
+	}
+	connection := data.Connection
+	connection.Normalize()
+
+	var urlTemplate string
+	var scope string
+
+	if connection.HasEnterprise() {
+		urlTemplate = fmt.Sprintf("enterprises/%s/settings/billing/ai_credit/usage", connection.Enterprise)
+		scope = connection.Enterprise
+	} else if connection.Organization != "" {
+		urlTemplate = fmt.Sprintf("organizations/%s/settings/billing/ai_credit/usage", connection.Organization)
+		scope = connection.Organization
+	} else {
+		// User-level credits are scoped to current authenticated user
+		urlTemplate = "user/settings/billing/ai_credit/usage"
+		scope = "user"

Review Comment:
   The PR description lists billing endpoints under `/organizations/{org}` and `/users/{username}`, but this collector uses `organizations/%s/...` and `user/...`. This mismatch makes it unclear which API you intend to call, and may break requests depending on what GitHub actually supports. Please align the path templates with the intended/validated endpoints (and keep them consistent with the PR description and existing gh-copilot usage of `orgs/{org}` elsewhere).



##########
backend/plugins/gh-copilot/models/enterprise_ai_credit_usage.go:
##########
@@ -0,0 +1,56 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package models
+
+import (
+	"github.com/apache/incubator-devlake/core/models/common"
+)
+
+// GhCopilotEnterpriseAiCreditUsage tracks AI credit consumption at the enterprise level.
+// One row per time period per model per entity (user, org, or cost center).
+type GhCopilotEnterpriseAiCreditUsage struct {
+	ConnectionId uint64 `gorm:"primaryKey" json:"connectionId"`
+	ScopeId      string `gorm:"primaryKey;type:varchar(255)" json:"scopeId"`
+	Year         int    `gorm:"primaryKey" json:"year"`
+	Month        int    `gorm:"primaryKey" json:"month"`
+	Day          int    `gorm:"primaryKey" json:"day"`
+
+	Enterprise   string `gorm:"primaryKey;type:varchar(255)" json:"enterprise" gorm:"comment:Enterprise slug"`
+	Model        string `gorm:"primaryKey;type:varchar(255)" json:"model" gorm:"comment:AI model name (e.g., gpt-4.1)"`
+	Organization string `gorm:"index;type:varchar(255)" json:"organization" gorm:"comment:Organization within enterprise, if specified"`
+	User         string `gorm:"index;type:varchar(255)" json:"user" gorm:"comment:Username, if specified"`
+
+	Product        string `gorm:"type:varchar(32)" json:"product" gorm:"comment:Product name (e.g., copilot)"`
+	CostCenterId   string `gorm:"index;type:varchar(255)" json:"costCenterId" gorm:"comment:Cost center identifier"`
+	CostCenterName string `gorm:"type:varchar(255)" json:"costCenterName" gorm:"comment:Cost center display name"`

Review Comment:
   These fields repeat the `gorm:` tag key twice (including Product/CostCenter fields). Combine the comment into the same `gorm` tag so the metadata isn’t dropped.



##########
backend/plugins/gh-copilot/tasks/ai_credit_collector.go:
##########
@@ -0,0 +1,118 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+)
+
+const rawAiCreditUsageTable = "copilot_ai_credit_usage"
+
+// CollectAiCreditUsage collects AI credit usage data from the billing API endpoints.
+// Routes requests to enterprise, organization, or user endpoints based on connection configuration.
+func CollectAiCreditUsage(taskCtx plugin.SubTaskContext) errors.Error {
+	data, ok := taskCtx.TaskContext().GetData().(*GhCopilotTaskData)
+	if !ok {
+		return errors.Default.New("task data is not GhCopilotTaskData")
+	}
+	connection := data.Connection
+	connection.Normalize()
+
+	var urlTemplate string
+	var scope string
+
+	if connection.HasEnterprise() {
+		urlTemplate = fmt.Sprintf("enterprises/%s/settings/billing/ai_credit/usage", connection.Enterprise)
+		scope = connection.Enterprise
+	} else if connection.Organization != "" {
+		urlTemplate = fmt.Sprintf("organizations/%s/settings/billing/ai_credit/usage", connection.Organization)
+		scope = connection.Organization
+	} else {
+		// User-level credits are scoped to current authenticated user
+		urlTemplate = "user/settings/billing/ai_credit/usage"
+		scope = "user"
+	}
+
+	// Build query parameters for date range
+	now := time.Now().UTC()
+	params := url.Values{}
+	params.Set("year", strconv.Itoa(now.Year()))
+	params.Set("month", strconv.Itoa(int(now.Month())))
+	params.Set("day", strconv.Itoa(now.Day()))
+

Review Comment:
   This collector claims to build query parameters for a date range, but it always uses the current UTC day only. That prevents backfill and incremental collection patterns used elsewhere in this plugin (day iterator + collector.GetSince()) and won’t populate historical periods expected by the new tables/snapshots. Consider iterating over the desired day range (like CollectUserMetrics/CollectUserTeams) and passing the requested day in each request so extraction can persist the correct Year/Month/Day.



##########
backend/plugins/gh-copilot/models/org_ai_credit_usage.go:
##########
@@ -0,0 +1,53 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package models
+
+import (
+	"github.com/apache/incubator-devlake/core/models/common"
+)
+
+// GhCopilotOrgAiCreditUsage tracks AI credit consumption at the organization level.
+// One row per time period per model per user (within the organization).
+type GhCopilotOrgAiCreditUsage struct {
+	ConnectionId uint64 `gorm:"primaryKey" json:"connectionId"`
+	ScopeId      string `gorm:"primaryKey;type:varchar(255)" json:"scopeId"`
+	Year         int    `gorm:"primaryKey" json:"year"`
+	Month        int    `gorm:"primaryKey" json:"month"`
+	Day          int    `gorm:"primaryKey" json:"day"`
+
+	Organization string `gorm:"primaryKey;type:varchar(255)" json:"organization" gorm:"comment:Organization name"`
+	Model        string `gorm:"primaryKey;type:varchar(255)" json:"model" gorm:"comment:AI model name (e.g., gpt-4.1)"`
+	User         string `gorm:"index;type:varchar(255)" json:"user" gorm:"comment:Username consuming the credits"`
+
+	Product string `gorm:"type:varchar(32)" json:"product" gorm:"comment:Product name (e.g., copilot)"`

Review Comment:
   These struct fields repeat the `gorm:` tag key twice (e.g., `gorm:"..." ... gorm:"comment:..."`). Only the first `gorm` tag will be read by reflect.StructTag.Get, so the comment metadata will be silently dropped. Combine everything into a single `gorm` tag per field.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] feat(gh-copilot): add AI credit usage billing metrics collection [devlake]

Posted by "ewega (via GitHub)" <gi...@apache.org>.
ewega closed pull request #8980: feat(gh-copilot): add AI credit usage billing metrics collection
URL: https://github.com/apache/devlake/pull/8980


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@devlake.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org