كيفية تعديل نقاط الألعاب بدون تحديث القيم بشكل رجعي للأفعال التي يمكن تسجيل النقاط عليها سابقًا

بيان المشكلة

إذا كنت تستخدم إضافة Gamification الخاصة بـ Discourse لمنح المستخدمين نقاطًا مقابل مساهماتهم في مجتمعك، فقد تكون واجهت الحاجة إلى تعديل قيم النقاط بناءً على الاتجاهات الناشئة. في مجتمع مطوري SailPoint، نستخدم إضافة Gamification لتشغيل برنامج السفراء. يكسب المستخدمون الذين يقدمون مساهمات قيمة في مجتمعنا نقاطًا تُستخدم بدورها لتحديد مستوى الامتيازات التي يتلقونها. ومع نمو مجتمعنا والزيادة الهائلة في عدد المساهمات، قررنا أن القيم الأصلية التي خصصناها لأنواع معينة من المساهمات بحاجة إلى تعديل. يعمل تعديل قيم النقاط بشكل جيد إذا لم تقم أبدًا بإعادة حساب نقاطك، لكننا غالبًا ما نضطر إلى إعادة حساب النقاط عند دمج المستخدمين أو عند استخدام واجهة برمجة التطبيقات (API) الخارجية لـ Gamification لمنح نقاطًا مقابل مساهمات سابقة. إذا قمت بتعديل قيم نقاط Gamification وقمت بإعادة الحساب، فستُحسب المساهمات السابقة التي وُزعت عليها نقاط باستخدام القيم القديمة الآن باستخدام القيم الجديدة. هذا يمثل مشكلة إذا كنت تريد تعيين القيم الجديدة للمساهمات الجديدة مع ترك المساهمات القديمة بنفس قيمتها. في هذا الدليل، سأناقش حلاً قمت بإنشائه لضمان عدم تغيير نقاط المستخدمين السابقة عند تغيير قيم النقاط وتشغيل إعادة الحساب.

استخدام مستكشف البيانات لحساب النقاط الحالية والمقترحة

استيراد استعلام SQL

الخطوة الأولى في هذه العملية هي استيراد استعلام SQL التالي إلى إضافة مستكشف البيانات (Data Explorer) الخاصة بك. تم تعديل هذا الاستعلام من استعلام SQL الأصلي ليشمل الترقيم (pagination)، وإضافة أعمدة إضافية لـ user_name و name، والفرز حسب user_id. سينتج هذا الاستعلام جدولًا لدرجات المستخدمين ضمن نطاق زمني معين وباستخدام قيم الدرجات المحددة.

-- [params]
-- date :start_date
-- date :end_date
-- int :day_visited_score_value = 0
-- int :time_read_score_value = 0
-- int :posts_read_score_value = 0
-- int :posts_created_score_value = 6
-- int :topics_created_score_value = 0
-- int :likes_received_score_value = 3
-- int :likes_given_score_value = 0
-- int :solutions_score_value = 60
-- int :flag_created_score_value = 6
-- int :user_invited_score_value = 0
-- int :limit = 1000
-- int :page = 0

WITH visits AS (
  SELECT
    uv.user_id,
    COUNT(*) AS user_visits,
    COUNT(*) * :day_visited_score_value AS visits_score
  FROM user_visits uv
  WHERE uv.visited_at BETWEEN :start_date AND :end_date
  GROUP BY uv.user_id
),

     time_read AS (
       SELECT
         uv.user_id,
         SUM(uv.time_read) /3600 AS time_read,
         SUM(uv.time_read) /3600 * :time_read_score_value AS time_read_score
       FROM user_visits uv
       WHERE uv.visited_at BETWEEN :start_date AND :end_date
         AND uv.time_read >= 60
       GROUP BY uv.user_id
     ),

     posts_read AS (
       SELECT
         uv.user_id,
         SUM(uv.posts_read) AS posts_read,
         SUM(uv.posts_read) /100 * :posts_read_score_value AS posts_read_score
       FROM user_visits uv
       WHERE uv.visited_at BETWEEN :start_date AND :end_date
         AND uv.posts_read >= 5
       GROUP BY uv.user_id
     ),

     posts_created AS (
       SELECT
         p.user_id,
         COUNT(*) AS posts_created,
         COUNT(*) * :posts_created_score_value AS posts_created_score
       FROM posts p
              INNER JOIN topics t ON t.id = p.topic_id
       WHERE p.deleted_at IS NULL
         AND t.archetype <> 'private_message'
         AND p.wiki IS FALSE
         AND p.created_at::date BETWEEN :start_date AND :end_date
       GROUP BY p.user_id
     ),

     topics_created AS (
       SELECT
         t.user_id,
         COUNT(*) AS topics_created,
         COUNT(*) * :topics_created_score_value AS topics_created_score
       FROM topics t
       WHERE t.deleted_at IS NULL
         AND t.archetype <> 'private_message'
         AND t.created_at::date BETWEEN :start_date AND :end_date
       GROUP BY t.user_id
     ),

     likes_received AS (
       SELECT
         p.user_id,
         COUNT(*) AS likes_received,
         COUNT(*) * :likes_received_score_value AS likes_received_score
       FROM post_actions pa
              INNER JOIN posts p ON p.id = pa.post_id
              INNER JOIN topics t ON t.id = p.topic_id
       WHERE p.deleted_at IS NULL
         AND t.archetype <> 'private_message'
         AND p.wiki IS FALSE
         AND post_action_type_id = 2
         AND pa.created_at::date BETWEEN :start_date AND :end_date
       GROUP BY p.user_id
     ),

     likes_given AS (
       SELECT
         pa.user_id AS user_id,
         COUNT(*) AS likes_given,
         COUNT(*) * :likes_given_score_value AS likes_given_score
       FROM post_actions pa
              INNER JOIN posts p ON p.id = pa.post_id
              INNER JOIN topics t ON t.id = p.topic_id
       WHERE p.deleted_at IS NULL
         AND t.archetype <> 'private_message'
         AND p.wiki IS FALSE
         AND post_action_type_id = 2
         AND pa.created_at::date BETWEEN :start_date AND :end_date
       GROUP BY pa.user_id
     ),

     solutions AS (
       SELECT
         p.user_id,
         COUNT(st.topic_id) AS solutions,
         COUNT(st.topic_id) * :solutions_score_value AS solutions_score
       FROM discourse_solved_solved_topics st
              INNER JOIN topics t ON st.topic_id = t.id
              INNER JOIN posts p ON p.id = st.answer_post_id
       WHERE p.deleted_at IS NULL
         AND t.deleted_at IS NULL
         AND t.archetype <> 'private_message'
         AND p.user_id <> t.user_id
         AND st.updated_at::date BETWEEN :start_date AND :end_date
       GROUP BY p.user_id
     ),

     flags AS (
       SELECT
         r.created_by_id AS user_id,
         COUNT(*) AS flags,
         COUNT(*) * :flag_created_score_value AS flags_score
       FROM reviewables r
       WHERE created_at::date BETWEEN :start_date AND :end_date
         AND status = 1
       GROUP BY user_id
     ),

     invites AS (
       SELECT
         inv.invited_by_id AS user_id,
         SUM(inv.redemption_count) AS invites,
         (SUM(inv.redemption_count) * :user_invited_score_value)::int AS invites_score
       FROM invites inv
       WHERE inv.created_at::date BETWEEN :start_date AND :end_date
         AND inv.redemption_count > 0
       GROUP BY inv.invited_by_id
     )

SELECT
  u.id AS user_id,
  u.username AS username,
  u.name AS name,
  (
    COALESCE(v.visits_score,0) +
    COALESCE(tr.time_read_score,0) +
    COALESCE(pr.posts_read_score,0) +
    COALESCE(pc.posts_created_score,0) +
    COALESCE(tc.topics_created_score,0) +
    COALESCE(lr.likes_received_score,0) +
    COALESCE(lg.likes_given_score,0) +
    COALESCE(s.solutions_score,0) +
    COALESCE(f.flags_score,0) +
    COALESCE(i.invites_score,0)
    ) AS "Total Cheers",
  COALESCE(v.user_visits,0) || ' (' || COALESCE(v.visits_score,0) || ')' AS "Visits (cheers)",
  COALESCE(tr.time_read,0) || 'hrs' || ' (' || COALESCE(tr.time_read_score,0) || ')' AS "Time Read (cheers)",
  COALESCE(pr.posts_read,0) || ' (' || COALESCE(pr.posts_read_score,0) || ')' AS "Posts Read (cheers)",
  COALESCE(pc.posts_created,0) || ' (' || COALESCE(pc.posts_created_score,0) || ')' AS "Posts Created (cheers)",
  COALESCE(tc.topics_created,0) || ' (' || COALESCE(tc.topics_created_score,0) || ')'AS "Topics Created (cheers)",
  COALESCE(lr.likes_received,0) || ' (' || COALESCE(lr.likes_received_score,0) || ')' AS "Likes Received (cheers)",
  COALESCE(lg.likes_given,0) || ' (' || COALESCE(lg.likes_given_score,0) || ')' AS "Likes Given (cheers)",
  COALESCE(s.solutions,0) || ' (' || COALESCE(s.solutions_score,0) || ')'AS "Solutions (cheers)",
  COALESCE(f.flags,0) || ' (' || COALESCE(f.flags_score,0) || ')' AS "Agreed Flags (cheers)",
  COALESCE(i.invites,0) || ' (' || COALESCE(i.invites_score,0) || ')' AS "Invites Redeemed (cheers)"
FROM users u
       LEFT JOIN visits v ON v.user_id = u.id
       LEFT JOIN posts_read pr USING (user_id)
       LEFT JOIN time_read tr USING (user_id)
       LEFT JOIN flags f USING (user_id)
       LEFT JOIN posts_created pc USING (user_id)
       LEFT JOIN topics_created tc USING (user_id)
       LEFT JOIN likes_given lg USING (user_id)
       LEFT JOIN likes_received lr USING (user_id)
       LEFT JOIN solutions s USING (user_id)
       LEFT JOIN invites i USING (user_id)
WHERE u.id > 0
  AND u.id NOT IN (SELECT user_id FROM group_users WHERE group_id = 3)
ORDER BY user_id ASC
OFFSET :page * :limit
  LIMIT :limit

اختبار الاستعلام باستخدام قيم النقاط الحالية والمقترحة

بمجرد استيراد استعلام SQL، يجب عليك اختباره باستخدام إطار زمني يعكس الفترة التي لا تريد تغيير النقاط فيها. بالنسبة لنا، كان ذلك يعني من بداية المنتدى (2020-01-01) حتى اليوم السابق للتاريخ الذي أردنا فيه تحديث قيم النقاط (2024-05-15). قم بتحديث قيم الدرجات لتعكس قيم نقاطك الحالية، واختبر الاستعلام للتأكد من صحة القيم.

بعد ذلك، احتفظ بنفس الإطار الزمني ولكن غيّر قيم النقاط إلى ما تريد أن تكون عليه. لاحظ الفرق في القيم لنفس المستخدمين. في هذا المثال، كان لدى نيل إجمالي 1104 نقاط (أي Cheers) باستخدام قيم النقاط الحالية، وزادت نقاطه إلى 1245 باستخدام قيم النقاط الجديدة. نحتاج إلى طريقة لحساب الفرق بين النقاط قبل وبعد التعديل وضبط نقاط المستخدم بحيث لا يرى زيادة أو نقصانًا في النقاط بعد تنفيذ قيم النقاط الجديدة.

استخدام واجهة برمجة التطبيقات الخارجية لـ Gamification لضبط النقاط

باستخدام نيل كمثال، نقاطه الحالية هي 1104 ونقاطه بعد التغيير المقترح ستكون 1245. للتأكد من أنه لا يرى زيادة أو نقصانًا في النقاط، نحتاج إلى حساب الفرق بين هاتين القيمتين ثم تعيين هذا الفرق لدرجته. يتم حساب الفرق كـ currentValue - newValue، وهو ما يعني في حالة نيل: 1104 - 1245 = -141. هذا يعني أن نيل يحتاج إلى تعيين -141 نقطة. يمكننا استخدام واجهة برمجة التطبيقات الخارجية لـ Gamification لتعيين هذه النقاط لحساب المستخدم الخاص به بحيث تنعكس في درجة لوحة الصدارة الخاصة به. مكالمة واجهة برمجة التطبيقات التي نحتاج إلى إجرائها هي كالتالي:

curl --location 'https://my.discourse.com/admin/plugins/gamification/score_events' \
--header 'Api-Key: <your key>' \
--header 'Api-Username: <your username>' \
--header 'Content-Type: application/json' \
--data '{
    "user_id": "101",
    "date": "2024-05-15",
    "points": "-141",
    "description": "Gamification point adjustment"
}'

سيؤدي تنفيذ مكالمة واجهة برمجة التطبيقات هذه إلى ضبط الدرجة الإجمالية لنيل بحيث تؤثر قيم النقاط المقترحة فقط على الأحداث القابلة للدرجات الجديدة، بينما يتم إلغاء أي فرق في الأحداث القابلة للدرجات القديمة عن طريق الضبط. الآن نحتاج فقط إلى تطبيق هذه العملية على كل مستخدم في المجتمع.

أتمتة تعديلات النقاط

لأتمتة هذه العملية، قمت بإنشاء سكريبت بلغة Python يستغل واجهات برمجة تطبيقات Discourse لتشغيل استعلامات SQL، وحساب الفرق، وتعيين التعديلات لكل مستخدم. يقوم السكريبت أيضًا بإخراج ملف CSV يحتوي على سجل لجميع المستخدمين الذين تلقوا تعديلًا. التعليقات الموجودة في السكريبت تقوم بعمل جيد في وصف القيم التي تحتاج إلى تغييرها وكيف يعمل الأمر. إذا كانت هناك أي أسئلة حول كيفية جعل هذا يعمل، يرجى ترك تعليق أدناه.

المتطلبات

ستحتاج إلى إصدار حديث من Python 3. أنا أستخدم Python 3.9.6. ستحتاج أيضًا إلى تثبيت حزمة requests من PyPi.

python3 -m pip install requests

السكريبت

import requests
import secrets
from datetime import date
from datetime import timedelta
import time
import csv
import json

# يتم تخزين بيانات اعتماد واجهة برمجة التطبيقات في ملف سري. كيفية تزويدك بالأسرار يعود لك.
api_key = secrets.api_key
api_username = secrets.api_username

host = 'https://developer.sailpoint.com/discuss' # اسم المضيف (hostname) لحالة Discourse الخاصة بك

# رابط إلى موضوع في منتداك يصف التغييرات الحالية والمقترحة في النقاط. هذا شيء جيد امتلاكه
# حتى تتمكن من الرجوع إليه لفهم ما تم تغييره، وجعل التغييرات شفافة للمستخدمين.
point_adjustment_link = 'https://developer.sailpoint.com/discuss/t/update-to-ambassador-point-values-may-15th-2024/54178'

# الاسم الذي تريد إعطاؤه لملف CSV الخاص بك. سيتم إرفاق start_date و end_date عند إنشائه.
csv_name = 'sailpoint_developer_community_point_adjustment' 

max_requests_per_minute = 200 # الحد الأقصى للطلبات لكل فترة 1 دقيقة. قم بتعديل هذا ليكون عند أو أقل من حد معدل واجهة برمجة تطبيقات Discourse الخاص بك.
start_date = '2020-01-01' # بدأ المنتدى بعد هذا التاريخ، لذا سيضمن ذلك أخذ جميع النقاط في الاعتبار.

# لا يمكن أن يكون هذا التاريخ هو اليوم، وإلا سيفشل الاستعلام. ستستخدم هذه الصيغة تاريخ الأمس. يمكنك تعديله
# ليكون في الماضي البعيد إذا رغبت في ذلك.
end_date = (date.today() - timedelta(days = 1)).strftime('%Y-%m-%d') 

# معرف استعلام SQL الخاص بك. يمكنك العثور عليه بالنقر فوق استعلامك في إضافة مستكشف البيانات ثم البحث عن
# معلمة "id" في عنوان URL. على سبيل المثال، معرفي هو 66 كما هو موضح في هذا الرابط:
# https://developer.sailpoint.com/discuss/admin/plugins/explorer?id=66
query_id = '66'

# هذه هي قيم النقاط الحالية لإعدادات درجة Gamification الخاصة بك. كانت هذه الأربعة تنطبق على منتدانا
# ولكن يمكنك إضافة أنواع النقاط القابلة للدرجات الأخرى إذا كنت بحاجة إلى ذلك.
current_likes_received_score_value = '3'
current_solutions_score_value = '60'
current_posts_created_score_value = '6'
current_flag_created_score_value = '6'

# هذه هي قيم النقاط المقترحة التي سيتم تطبيقها على جميع النقاط الجديدة.
new_likes_received_score_value = '6'
new_solutions_score_value = '60'
new_posts_created_score_value = '3'
new_flag_created_score_value = '6'

# تقوم هذه الدالة بتشغيل استعلام SQL للحصول على قيم النقاط لجميع المستخدمين. سيقوم تلقائيًا بالترقيم حتى لا توجد المزيد من السجلات.
def getPointValues(query_id, start_date, end_date, limit, likes_received_score_value, solutions_score_value, posts_created_score_value, flag_created_score_value):
    rows = []
    page = 0

    headers = {
        'Api-Key': api_key,
        'Api-Username': api_username,
        'Content-Type': 'application/x-www-form-urlencoded'
    }
        
    payload = f'params={{"start_date":"{start_date}","end_date":"{end_date}","limit":"{str(limit)}","page":"{str(page)}","likes_received_score_value":"{likes_received_score_value}","solutions_score_value":"{solutions_score_value}","posts_created_score_value":"{posts_created_score_value}","flag_created_score_value":"{flag_created_score_value}"}}'
    r = requests.post(f'{host}/admin/plugins/explorer/queries/{query_id}/run', headers=headers, data=payload).json()
    rows += r['rows']

    # قم بالترقيم حتى نصل إلى الصفحة الأخيرة
    while len(r['rows']) == limit:
        page += 1
        payload = f'params={{"start_date":"{start_date}","end_date":"{end_date}","limit":"{str(limit)}","page":"{str(page)}","likes_received_score_value":"{likes_received_score_value}","solutions_score_value":"{solutions_score_value}","posts_created_score_value":"{posts_created_score_value}","flag_created_score_value":"{flag_created_score_value}"}}'
        r = requests.post(f'{host}/admin/plugins/explorer/queries/{query_id}/run', headers=headers, data=payload).json()
        rows += r['rows']

    return rows

# قم بإنشاء مصفوفة تعديلات النقاط التي يجب إجراؤها. إذا كان لدى المستخدم 0 نقاط، أو لا يوجد فرق في النقاط، فسيتم
# تصفيته حتى لا نقوم بمكالمات واجهة برمجة التطبيقات غير الضرورية. ستشمل هذه المصفوفة جميع المعلومات اللازمة لإجراء
# مكالمة واجهة برمجة التطبيقات الخارجية لـ Gamification بالإضافة إلى ملء ملف CSV.
def calculatePointAdjustments(old_values, new_values):
    point_adjustments = []

    for row in old_values:
        user_id = row[0]
        username = row[1]
        name = row[2]
        current_cheers = row[3]
        for i in range(len(new_values)):
            new_cheers = new_values[i][3]
            if new_values[i][0] == user_id:
                # احسب الفرق فقط إذا كان هناك فرق في النقاط.
                if current_cheers != new_cheers:
                    point_adjustments.append({
                        'user_id': user_id,
                        'username': username,
                        'name': name,
                        'current_points': current_cheers,
                        'new_points': new_cheers,
                        'difference': current_cheers - new_cheers,
                        'external_gamification_point_id': -1
                    })
                    new_values.pop(i) # قم بإزالة العنصر من القائمة لتسريع المعالجة
                    break
                else:
                    new_values.pop(i) # قم بإزالة العنصر من القائمة لتسريع المعالجة
                    break
    
    return point_adjustments

# احفظ مصفوفة تعديل النقاط في ملف CSV لحفظ السجلات الخاصة بك
def exportCSV(differences):
    fields = ['User ID', 'Username', 'Name', 'Current Points', 'New Points', 'Difference', 'External Gamification Point ID']
    rows = []
    for diff in differences:
        rows.append([diff['user_id'], diff['username'], diff['name'], diff['current_points'], diff['new_points'], diff['difference'], diff['external_gamification_point_id']])

    with open(f'{csv_name}_{start_date}_to_{end_date}.csv', 'w') as f:
        csv_writer = csv.writer(f)
        csv_writer.writerow(fields)
        csv_writer.writerows(rows)

# قم بتنفيذ واجهة برمجة تطبيقات Gamification لتعيين الفرق في النقاط لكل مستخدم. ستقوم هذه الدالة بتبطئة العملية عمداً
# للبقاء ضمن حد معدلك. في حالة الوصول إلى حد المعدل، ستستمر هذه الدالة في الانتظار حتى
# ينتهي حد المعدل ويمكنها المتابعة. إذا حدث خطأ في أي وقت في مكالمة واجهة برمجة التطبيقات، فسيتم حفظ تعديلات النقاط
# التي تم إكمالها بنجاح في ملف CSV لتعرف أيها مكتمل وأين تحتاج إلى الاستئناف.
def assignPointAdjustments(point_adjustments):
    assigned_point_adjustments = []
    # حد المعدل الذاتي لتجنب التأثير على التكاملات الأخرى
    sleep_time = 60 / max_requests_per_minute # عدد الثواني للانتظار بين كل مكالمة للبقاء ضمن حد الطلبات الأقصى
    endpoint = f'{host}/admin/plugins/gamification/score_events'
    headers = {
        'Api-Key': api_key,
        'Api-Username': api_username,
        'Content-Type': 'application/json'
    }

    for adjustment in point_adjustments:
        payload = json.dumps({
            "user_id": str(adjustment['user_id']),
            "date": end_date,
            "points": str(adjustment['difference']),
            "description": f'Adjusting points based on new gamification point values. Please see {point_adjustment_link}.'
        })

        start = time.time() # احتفظ بوقت التنفيذ. نظرًا لأن مكالمات واجهة برمجة التطبيقات تستغرق عدة مللي ثانية، فلا تحسب هذا الوقت ضمن وقت انتظار الطلب الأقصى.

        try:
            r = requests.post(endpoint, headers=headers, data=payload)
        except Exception as e:
            # في حالة الفشل، احفظ التعديلات المعينة في ملف CSV حتى نعرف أيها مكتمل بالفعل
            exportCSV(assigned_point_adjustments)
            raise

        # إذا كان الحد الأقصى للطلبات أقل من 300، فلا يجب أن نصل إلى حد المعدل. إذا حدث وأن وصلنا إلى حد المعدل، تعامل معه بشكل مناسب.
        while r.status_code == 429:
            wait_time = r.json()["extras"]["wait_seconds"] + 1
            print(f'Hit rate limit.  Sleeping for {wait_time} seconds')
            time.sleep(wait_time)
            try:
                r = requests.post(endpoint, headers=headers, data=payload)
                adjustment['external_gamification_point_id'] = r.json()["id"]
            except Exception as e:
                # في حالة الفشل، احفظ التعديلات المعينة في ملف CSV حتى نعرف أيها مكتمل بالفعل
                exportCSV(assigned_point_adjustments)
                raise
        
        if r.status_code != 200:
            exportCSV(assigned_point_adjustments)
            print(f'The last request returned a {r.status_code} error with the following error message\n{r.text}')
            print('Aborting adjustments and writing the successful adjustments to file')
            raise Exception("HTTP Error")

        end = time.time()
        if end - start < sleep_time:
            # انم فقط إذا كان وقت الطلب أقل من وقت انتظار الطلب الأقصى
            time.sleep(sleep_time - (end - start))
        
        adjustment['external_gamification_point_id'] = r.json()["id"]
        print(f'Assigned {adjustment["username"]} {adjustment["difference"]} points')
        assigned_point_adjustments.append(adjustment)

    return assigned_point_adjustments

limit = 1000 # هذا هو الحد الأقصى للطلبات لاستعلامات SQL.

current_values = getPointValues(query_id, start_date, end_date, limit, current_likes_received_score_value, current_solutions_score_value, current_posts_created_score_value, current_flag_created_score_value)
new_values = getPointValues(query_id, start_date, end_date, limit, new_likes_received_score_value, new_solutions_score_value, new_posts_created_score_value, new_flag_created_score_value)
point_adjustments = calculatePointAdjustments(current_values, new_values)
assigned_point_adjustments = assignPointAdjustments(point_adjustments)
exportCSV(assigned_point_adjustments)

خطوات تنفيذ تعديل النقاط

الآن بعد أن أصبح لدينا جميع القطع في مكانها، ستحتاج إلى اتباع هذه الخطوات لتنفيذ عملية تعديل النقاط. يوصى باختبار هذه العملية في مثيل اختبار لبيئة Discourse الخاصة بك أولاً حتى تتمكن من حل أي مشاكل.

  1. قم بتحديث المتغيرات في سكريبت Python بالقيم المطلوبة.
  2. غيّر قيم النقاط في إعدادات Gamification إلى القيم الجديدة.
  3. أعد حساب الدرجات لـ “كل الوقت” (All Time).
  4. شغّل سكريبت Python وانتظر حتى يكتمل. اعتمادًا على عدد المستخدمين في نظامك وما هو حد المعدل الخاص بك، قد يستغرق هذا بضع دقائق لإكماله. كان لدينا 2,200 مستخدم يحتاجون إلى تعديل. عند حد معدل 200 طلب/دقيقة، استغرق الأمر حوالي 15 دقيقة لإكماله.
  5. بمجرد الانتهاء، أعد حساب الدرجات لـ “كل الوقت” مرة أخرى، فقط للتأكد.
  6. احفظ ملف CSV في مكان آمن لسجلاتك، في حال احتجت إلى التراجع عن أي تغييرات.

قد يستغرق إعادة حساب النقاط بضع ساعات لإكماله. ستحتاج إلى الصبر لرؤية التعديلات تأخذ مفعولها. بمجرد اكتمال إعادة الحساب، تكون قد نجحت بنجاح في تعديل نقاط المستخدمين، وسيتم حساب النقاط المستقبلية بناءً على قيم النقاط الجديدة.

إجراء المزيد من التعديلات في المستقبل

إذا كنت في أي وقت بحاجة إلى إجراء تعديلات على قيم النقاط، يمكنك اتباع نفس العملية. الفرق الوحيد هو أنك تحتاج إلى تعيين start_date إلى end_date عندما قمت بتشغيل تعديل النقاط آخر مرة. على سبيل المثال، في المرة الأولى التي قمت فيها بتشغيل تعديل النقاط هذا، قمت بتعيين end_date إلى 2024-05-15. هذا تأكد من أن جميع النقاط المكتسبة بين بداية منتداي و 2024-05-15 تم تعديلها للحفاظ على القيم القديمة. لنفترض أن عامًا يمر وأريد تغيير قيم النقاط مرة أخرى. الآن أحتاج إلى تعيين start_date إلى 2024-05-15 و end_date إلى 2025-05-15. سيضمن ذلك أن تعديل النقاط ينطبق فقط على تلك الفترة ولا يتجاوز أي شيء من المرة الأولى التي قمت فيها بتشغيل تعديل النقاط.

6 إعجابات