Repository: ubiquity-os-marketplace/text-conversation-rewards
Pull request #345: fix: add reward mechanism for original comment authors
Author: login: gentlementlegen
Created: 2025-05-10T14:21:00Z | Merged: 2025-05-20T16:13:45Z

Resolves #74

<!--
- You must link the issue number e.g. "Resolves #1234"
- Please do not replace the keyword "Resolves" https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->


Linked issue #74: "Originally posted by" crediting
In a conversation, using the context menu, we are able to instantly create a new issue and prefill the body with the comment contents. It is common that the original author of the comment does not receive credit for creating the issue (when another team member creates the issue for them.)

Detect when the following string is detected (full example with context):

```
Originally posted by @0x4007 in https://github.com/ubiquibot/conversation-rewards/pull/58#discussion_r1691159526
```

So it will be a regex like `^Originally posted by @<username regex> in <url regex>$`

And when calculating the rewards, the original author should receive part of the author credit. Make it configurable with a percentage. I would default to 50% of the credit. 
Linked issue comments: user: login: 0x4007
created_at: 2024-09-12T08:35:14Z
body: Not sure if two hours is appropriate. 
author_association: MEMBER
user: login: gentlementlegen
created_at: 2024-09-24T01:55:31Z
body: @0x4007 When you say "receive part of the author credit", what do you mean exactly, dividing the specification reward only between the two users, or the whole total, or the "original poster" gets a reward equal to a percentage of the issue creator without changing the issue creator reward?
author_association: MEMBER
user: login: 0x4007
created_at: 2024-09-24T10:19:09Z
body: > dividing the specification reward only between the two users

yes
author_association: MEMBER
user: login: ubiquity-os-beta[bot]
created_at: 2024-12-04T10:58:50Z
body: >[!NOTE]
>The following contributors may be suitable for this task:
>### [gentlementlegen](https://www.github.com/gentlementlegen)
> `77% Match` [ubiquity-os-marketplace/text-conversation-rewards#48](https://www.github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/48)
> `76% Match` [ubiquity-os-marketplace/text-conversation-rewards#155](https://www.github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/155)
> `76% Match` [ubiquity-os-marketplace/text-conversation-rewards#2](https://www.github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/2)
author_association: CONTRIBUTOR
user: login: ubiquity-os-beta[bot]
created_at: 2025-05-20T16:14:06Z
body: <details><summary><b><h3>&nbsp;<a href="https://pay.ubq.fi?claim=W3sidHlwZSI6ImVyYzIwLXBlcm1pdCIsInBlcm1pdCI6eyJwZXJtaXR0ZWQiOnsidG9rZW4iOiIweEM2ZWQ0ZjUyMGY2QTRlNERDMjcyNzM1MDkyMzliN0Y4QTY4ZDIwNjgiLCJhbW91bnQiOiIxMDEyNDIwMDAwMDAwMDAwMDAwMDAifSwibm9uY2UiOiI5NzE4MDk4Mjk5MDU1NzQ5Mzg5ODAwODc4MDY5MjQ1ODM0NzY2ODYzNTAzNzg3MDg3NTg3NjE3NTE1NDA5OTU0MDQ4MTk0MTc5MzI5NiIsImRlYWRsaW5lIjoiMTE1NzkyMDg5MjM3MzE2MTk1NDIzNTcwOTg1MDA4Njg3OTA3ODUzMjY5OTg0NjY1NjQwNTY0MDM5NDU3NTg0MDA3OTEzMTI5NjM5OTM1In0sInRyYW5zZmVyRGV0YWlscyI6eyJ0byI6IjB4MGZDMWI5MDliYTkyNjVBODQ2YjgyQ0Y0Q0UzNTJmYzNlN0VlQjJFRCIsInJlcXVlc3RlZEFtb3VudCI6IjEwMTI0MjAwMDAwMDAwMDAwMDAwMCJ9LCJvd25lciI6IjB4OTA1MWVEYTk2ZEI0MTljOTY3MTg5RjRBYzMwM2EyOTBGMzMyNzY4MCIsInNpZ25hdHVyZSI6IjB4ZDEwZDJmMGM0ZjA4YTUwOGFlMjFiYzBkYWJhYTVkMmMyZjdmMTcyNmNhOWJhNDMyMTk2MWIwY2QxYjk2OGQwMjRmMThhZGFiNTdiMDQ3MjE1ZjE5NDViYmFhNDAyZmYyNWE3NDNjYzAxOWRmOGZhYzNkNjNhYzEyMmMxYzdhMTIxYyIsIm5ldHdvcmtJZCI6MTAwfV0=" target="_blank" rel="noopener">[ 101.242 UUSD ]</a>&nbsp;</h3><h6>@gentlementlegen</h6></b></summary><h6>Contributions Overview</h6><table><thead><tr><th>View</th><th>Contribution</th><th>Count</th><th>Reward</th></tr></thead><tbody><tr><td>Issue</td><td>Task</td><td>1</td><td>100</td></tr><tr><td>Issue</td><td>Comment</td><td>1</td><td>1.242</td></tr></tbody></table><h6>Conversation Incentives</h6><table><thead><tr><th>Comment</th><th>Formatting</th><th>Relevance</th><th>Priority</th><th>Reward</th></tr></thead><tbody><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/74#issuecomment-2369951280" target="_blank" rel="noopener">@0x4007 When you say "receive part of the author credit", what d&hellip;</a></h6></td><td><details><summary>2.37</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 1&#13;  result: 0&#13;regex:&#13;  wordCount: 49&#13;  wordValue: 0.1&#13;  result: 2.37&#13;</pre></details></td><td>0.9</td><td>2</td><td>1.242</td></tr></tbody></table></details><details><summary><b><h3>&nbsp;<a href="https://pay.ubq.fi?claim=W3sidHlwZSI6ImVyYzIwLXBlcm1pdCIsInBlcm1pdCI6eyJwZXJtaXR0ZWQiOnsidG9rZW4iOiIweEM2ZWQ0ZjUyMGY2QTRlNERDMjcyNzM1MDkyMzliN0Y4QTY4ZDIwNjgiLCJhbW91bnQiOiIxMjE2ODAwMDAwMDAwMDAwMDAwMCJ9LCJub25jZSI6IjgyMjY3MTA2NjUxMzIxMDY3ODE5MjEzNzEyMDE1NjY0MDQ4ODYwNjE1NTk5MjAyMjY0MjUyNTQxNTU1NzY3NjAzODk5MTEwMzY1NzQ5IiwiZGVhZGxpbmUiOiIxMTU3OTIwODkyMzczMTYxOTU0MjM1NzA5ODUwMDg2ODc5MDc4NTMyNjk5ODQ2NjU2NDA1NjQwMzk0NTc1ODQwMDc5MTMxMjk2Mzk5MzUifSwidHJhbnNmZXJEZXRhaWxzIjp7InRvIjoiMHg0MDA3Q0UyMDgzYzdGM0UxODA5N2FlQjNBMzliYjhlQzE0OWEzNDFkIiwicmVxdWVzdGVkQW1vdW50IjoiMTIxNjgwMDAwMDAwMDAwMDAwMDAifSwib3duZXIiOiIweDkwNTFlRGE5NmRCNDE5Yzk2NzE4OUY0QWMzMDNhMjkwRjMzMjc2ODAiLCJzaWduYXR1cmUiOiIweDRjNThhOGYzNGYwMTE5NTVhZDQ4NzRiYjEyYzRjZTI1YjM5YjExODI1N2E3OWY0NmY2ZTNiNmI3ODI4MDIwYjczMjZjNzY1ZWUyZDVlYWVmYzY5ODQ0NjNmMDU1NTFhZjZhZTMxMWVjNTdiZTJkYjVkNjBmODBhMTc3MjkzYTUzMWMiLCJuZXR3b3JrSWQiOjEwMH1d" target="_blank" rel="noopener">[ 12.168 UUSD ]</a>&nbsp;</h3><h6>@0x4007</h6></b></summary><h6>Contributions Overview</h6><table><thead><tr><th>View</th><th>Contribution</th><th>Count</th><th>Reward</th></tr></thead><tbody><tr><td>Review</td><td>Code Review</td><td>1</td><td>6.6</td></tr><tr><td>Issue</td><td>Specification</td><td>1</td><td>3.35</td></tr><tr><td>Issue</td><td>Comment</td><td>2</td><td>0.202</td></tr><tr><td>Review</td><td>Comment</td><td>2</td><td>2.016</td></tr></tbody></table><h6>Review Details for&nbsp;<a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345" target="_blank" rel="noopener">#345</a></h6><table><thead><tr><th>Changes</th><th>Priority</th><th>Reward</th></tr></thead><tbody><tr><td>+328 -2</td><td>2</td><td>6.6</td></tr></tbody></table><h6>Conversation Incentives</h6><table><thead><tr><th>Comment</th><th>Formatting</th><th>Relevance</th><th>Priority</th><th>Reward</th></tr></thead><tbody><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/74" target="_blank" rel="noopener">In a conversation, using the context menu, we are able to instan&hellip;</a></h6></td><td><details><summary>2.61</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 4&#13;  result: 0&#13;regex:&#13;  wordCount: 98&#13;  wordValue: 0.1&#13;  result: 2.61&#13;</pre></details></td><td>1</td><td>1</td><td>3.35</td></tr><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/74#issuecomment-2345631344" target="_blank" rel="noopener">Not sure if two hours is appropriate.</a></h6></td><td><details><summary>0.69</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 1&#13;  result: 0&#13;regex:&#13;  wordCount: 7&#13;  wordValue: 0.1&#13;  result: 0.69&#13;</pre></details></td><td>0.1</td><td>2</td><td>0.166</td></tr><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/74#issuecomment-2370864896" target="_blank" rel="noopener">yes</a></h6></td><td><details><summary>0.14</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 1&#13;  result: 0&#13;regex:&#13;  wordCount: 1&#13;  wordValue: 0.1&#13;  result: 0.14&#13;</pre></details></td><td>0.1</td><td>2</td><td>0.036</td></tr><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#discussion_r2090946147" target="_blank" rel="noopener">Should have several</a></h6></td><td><details><summary>0.35</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 1&#13;  result: 0&#13;regex:&#13;  wordCount: 3&#13;  wordValue: 0.1&#13;  result: 0.35&#13;</pre></details></td><td>0.8</td><td>2</td><td>0.72</td></tr><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#discussion_r2090947689" target="_blank" rel="noopener">All defaults should be zero across all plugins, probably</a></h6></td><td><details><summary>0.84</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 1&#13;  result: 0&#13;regex:&#13;  wordCount: 9&#13;  wordValue: 0.1&#13;  result: 0.84&#13;</pre></details></td><td>0.6</td><td>2</td><td>1.296</td></tr></tbody></table></details><details><summary><b><h3>&nbsp;<a href="https://pay.ubq.fi?claim=W3sidHlwZSI6ImVyYzIwLXBlcm1pdCIsInBlcm1pdCI6eyJwZXJtaXR0ZWQiOnsidG9rZW4iOiIweEM2ZWQ0ZjUyMGY2QTRlNERDMjcyNzM1MDkyMzliN0Y4QTY4ZDIwNjgiLCJhbW91bnQiOiIyNTMxNjAwMDAwMDAwMDAwMDAwMCJ9LCJub25jZSI6IjU1MjUxOTUwNzAyMDI0MTQzMzczOTU2Njk0NzE1NTQ2OTc2MTcyNjA2NTg4NjkwMTYzNTg1NDYxODAzNjQyMzA4ODUxMzAxNDQ4NTM1IiwiZGVhZGxpbmUiOiIxMTU3OTIwODkyMzczMTYxOTU0MjM1NzA5ODUwMDg2ODc5MDc4NTMyNjk5ODQ2NjU2NDA1NjQwMzk0NTc1ODQwMDc5MTMxMjk2Mzk5MzUifSwidHJhbnNmZXJEZXRhaWxzIjp7InRvIjoiMHhBMEIxMUY0NzRkOEVDRTEyMDVkMzhjNjZkNUYyYkU4OTE3Njc1ZDYwIiwicmVxdWVzdGVkQW1vdW50IjoiMjUzMTYwMDAwMDAwMDAwMDAwMDAifSwib3duZXIiOiIweDkwNTFlRGE5NmRCNDE5Yzk2NzE4OUY0QWMzMDNhMjkwRjMzMjc2ODAiLCJzaWduYXR1cmUiOiIweGQ0MjhkMzYxODJiMDU1YzVmMWZhOGM1MTNlMTNjNGI0NzQ5YWJkMWJjY2I0OTJlYTMwNWU5MDQ0YmE1MGU4Y2MxYTA1NzNhNjZiMWFjNTI5NThjZDZlZTlkYTUwZmVmYzFlMGE3YmJlZGQ1ODAzYjUyM2M4NTRlNDgzYWNiZTE3MWMiLCJuZXR3b3JrSWQiOjEwMH1d" target="_blank" rel="noopener">[ 25.316 UUSD ]</a>&nbsp;</h3><h6>@whilefoo</h6></b></summary><h6>Contributions Overview</h6><table><thead><tr><th>View</th><th>Contribution</th><th>Count</th><th>Reward</th></tr></thead><tbody><tr><td>Review</td><td>Code Review</td><td>3</td><td>9.38</td></tr><tr><td>Review</td><td>Comment</td><td>5</td><td>15.936</td></tr></tbody></table><h6>Review Details for&nbsp;<a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345" target="_blank" rel="noopener">#345</a></h6><table><thead><tr><th>Changes</th><th>Priority</th><th>Reward</th></tr></thead><tbody><tr><td>+326 -2</td><td>2</td><td>6.56</td></tr><tr><td>+10 -8</td><td>2</td><td>0.36</td></tr><tr><td>+39 -84</td><td>2</td><td>2.46</td></tr></tbody></table><h6>Conversation Incentives</h6><table><thead><tr><th>Comment</th><th>Formatting</th><th>Relevance</th><th>Priority</th><th>Reward</th></tr></thead><tbody><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#pullrequestreview-2850638097" target="_blank" rel="noopener">QA: https://github.com/ubiquibot-whilefoo-testing/testing/issues&hellip;</a></h6></td><td><details><summary>2.66</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 2&#13;    a:&#13;      score: 1&#13;      elementCount: 1&#13;  result: 1&#13;regex:&#13;  wordCount: 24&#13;  wordValue: 0.1&#13;  result: 1.66&#13;</pre></details></td><td>0.5</td><td>2</td><td>3.43</td></tr><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#discussion_r2089764792" target="_blank" rel="noopener">This works only if the weight is &#96;0.5&#96; but not if it's i&hellip;</a></h6></td><td><details><summary>1.88</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 1&#13;  result: 0&#13;regex:&#13;  wordCount: 30&#13;  wordValue: 0.1&#13;  result: 1.88&#13;</pre></details></td><td>0.9</td><td>2</td><td>4.176</td></tr><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#discussion_r2089769895" target="_blank" rel="noopener">It'd be a good idea to also check if issue author got correct re&hellip;</a></h6></td><td><details><summary>1.48</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 1&#13;  result: 0&#13;regex:&#13;  wordCount: 20&#13;  wordValue: 0.1&#13;  result: 1.48&#13;</pre></details></td><td>0.8</td><td>2</td><td>3.024</td></tr><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#discussion_r2089772202" target="_blank" rel="noopener">I think we don't need to run these 2 modules because &#96;Conten&hellip;</a></h6></td><td><details><summary>1.38</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 1&#13;  result: 0&#13;regex:&#13;  wordCount: 18&#13;  wordValue: 0.1&#13;  result: 1.38&#13;</pre></details></td><td>0.7</td><td>2</td><td>2.366</td></tr><tr><td><h6><a href="https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#issuecomment-2891935061" target="_blank" rel="noopener">I used 0.7It looks like it's working: https://github.com/ubiqu&hellip;</a></h6></td><td><details><summary>1.9</summary><pre>content:&#13;  content:&#13;    p:&#13;      score: 0&#13;      elementCount: 2&#13;    a:&#13;      score: 1&#13;      elementCount: 1&#13;  result: 1&#13;regex:&#13;  wordCount: 10&#13;  wordValue: 0.1&#13;  result: 0.9&#13;</pre></details></td><td>0.6</td><td>2</td><td>2.94</td></tr></tbody></table></details>
<!-- Ubiquity - GithubCommentModule - GithubCommentModule.getBodyContent - f6835f2b289f1f6da0046854981270a01de2909d
{
  "workflowUrl": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/actions/runs/15142601570",
  "output": {
    "gentlementlegen": {
      "userId": 9807008,
      "total": 101.242,
      "task": {
        "reward": 100,
        "multiplier": 1
      },
      "comments": [
        {
          "id": 2369951280,
          "content": "@0x4007 When you say \"receive part of the author credit\", what do you mean exactly, dividing the specification reward only between the two users, or the whole total, or the \"original poster\" gets a reward equal to a percentage of the issue creator without changing the issue creator reward?",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/74#issuecomment-2369951280",
          "commentType": "ISSUE_ASSIGNEE",
          "score": {
            "reward": 1.242,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 1
                }
              },
              "result": 0
            },
            "priority": 2,
            "words": {
              "wordCount": 49,
              "wordValue": 0.1,
              "result": 2.37
            },
            "readability": {
              "fleschKincaid": 17.2510204081633,
              "syllables": 81,
              "sentences": 1,
              "score": 0.572510204081633
            },
            "multiplier": 0.25,
            "relevance": 0.9
          }
        }
      ],
      "reviewRewards": [],
      "simplificationReward": {
        "files": [],
        "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345"
      },
      "walletAddress": "0x0fC1b909ba9265A846b82CF4CE352fc3e7EeB2ED",
      "permitUrl": "https://pay.ubq.fi?claim=W3sidHlwZSI6ImVyYzIwLXBlcm1pdCIsInBlcm1pdCI6eyJwZXJtaXR0ZWQiOnsidG9rZW4iOiIweEM2ZWQ0ZjUyMGY2QTRlNERDMjcyNzM1MDkyMzliN0Y4QTY4ZDIwNjgiLCJhbW91bnQiOiIxMDEyNDIwMDAwMDAwMDAwMDAwMDAifSwibm9uY2UiOiI5NzE4MDk4Mjk5MDU1NzQ5Mzg5ODAwODc4MDY5MjQ1ODM0NzY2ODYzNTAzNzg3MDg3NTg3NjE3NTE1NDA5OTU0MDQ4MTk0MTc5MzI5NiIsImRlYWRsaW5lIjoiMTE1NzkyMDg5MjM3MzE2MTk1NDIzNTcwOTg1MDA4Njg3OTA3ODUzMjY5OTg0NjY1NjQwNTY0MDM5NDU3NTg0MDA3OTEzMTI5NjM5OTM1In0sInRyYW5zZmVyRGV0YWlscyI6eyJ0byI6IjB4MGZDMWI5MDliYTkyNjVBODQ2YjgyQ0Y0Q0UzNTJmYzNlN0VlQjJFRCIsInJlcXVlc3RlZEFtb3VudCI6IjEwMTI0MjAwMDAwMDAwMDAwMDAwMCJ9LCJvd25lciI6IjB4OTA1MWVEYTk2ZEI0MTljOTY3MTg5RjRBYzMwM2EyOTBGMzMyNzY4MCIsInNpZ25hdHVyZSI6IjB4ZDEwZDJmMGM0ZjA4YTUwOGFlMjFiYzBkYWJhYTVkMmMyZjdmMTcyNmNhOWJhNDMyMTk2MWIwY2QxYjk2OGQwMjRmMThhZGFiNTdiMDQ3MjE1ZjE5NDViYmFhNDAyZmYyNWE3NDNjYzAxOWRmOGZhYzNkNjNhYzEyMmMxYzdhMTIxYyIsIm5ldHdvcmtJZCI6MTAwfV0=",
      "payoutMode": "permit"
    },
    "0x4007": {
      "total": 12.168,
      "userId": 4975670,
      "comments": [
        {
          "id": 2345631344,
          "content": "Not sure if two hours is appropriate.",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/74#issuecomment-2345631344",
          "commentType": "ISSUE_AUTHOR",
          "score": {
            "reward": 0.166,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 1
                }
              },
              "result": 0
            },
            "priority": 2,
            "words": {
              "wordCount": 7,
              "wordValue": 0.1,
              "result": 0.69
            },
            "readability": {
              "fleschKincaid": 90.95857142857145,
              "syllables": 9,
              "sentences": 1,
              "score": 0.6904142857142855
            },
            "multiplier": 1,
            "relevance": 0.1
          }
        },
        {
          "id": 2370864896,
          "content": "yes",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/74#issuecomment-2370864896",
          "commentType": "ISSUE_AUTHOR",
          "score": {
            "reward": 0.036,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 1
                }
              },
              "result": 0
            },
            "priority": 2,
            "words": {
              "wordCount": 1,
              "wordValue": 0.1,
              "result": 0.14
            },
            "readability": {
              "fleschKincaid": 121.22000000000003,
              "syllables": 1,
              "sentences": 1,
              "score": 1
            },
            "multiplier": 1,
            "relevance": 0.1
          }
        },
        {
          "id": 2431326220,
          "content": "In a conversation, using the context menu, we are able to instantly create a new issue and prefill the body with the comment contents. It is common that the original author of the comment does not receive credit for creating the issue (when another team member creates the issue for them.)\r\nDetect when the following string is detected (full example with context):\r\n```\r\nOriginally posted by @0x4007 in https://github.com/ubiquibot/conversation-rewards/pull/58#discussion_r1691159526\r\n```\r\nSo it will be a regex like `^Originally posted by @<username regex> in <url regex>$`\r\nAnd when calculating the rewards, the original author should receive part of the author credit. Make it configurable with a percentage. I would default to 50% of the credit.",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/74",
          "commentType": "ISSUE_SPECIFICATION",
          "score": {
            "reward": 3.35,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 4
                }
              },
              "result": 0
            },
            "priority": 1,
            "words": {
              "wordCount": 98,
              "wordValue": 0.1,
              "result": 2.61
            },
            "readability": {
              "fleschKincaid": 54.86140816326531,
              "syllables": 153,
              "sentences": 5,
              "score": 0.9486140816326531
            },
            "multiplier": 1,
            "relevance": 1
          }
        },
        {
          "id": 2090946147,
          "content": "Should have several",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#discussion_r2090946147",
          "commentType": "PULL_COLLABORATOR",
          "diffHunk": "@@ -377,6 +377,14 @@\n                       }\n                     }\n                   },\n+                  \"originalAuthorWeight\": {\n+                    \"default\": 0.5,\n+                    \"description\": \"Percentage of reward given to original comment author when detected via 'Originally posted by' pattern. Value between 0.0 and 1.0.\",\n+                    \"minimum\": 0,\n+                    \"maximum\": 1,\n+                    \"examples\": [0.5],",
          "score": {
            "reward": 0.72,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 1
                }
              },
              "result": 0
            },
            "priority": 2,
            "words": {
              "wordCount": 3,
              "wordValue": 0.1,
              "result": 0.35
            },
            "readability": {
              "fleschKincaid": 62.79000000000002,
              "syllables": 5,
              "sentences": 1,
              "score": 0.9720999999999997
            },
            "multiplier": 1,
            "relevance": 0.8
          }
        },
        {
          "id": 2090947689,
          "content": "All defaults should be zero across all plugins, probably",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#discussion_r2090947689",
          "commentType": "PULL_COLLABORATOR",
          "diffHunk": "@@ -31,6 +32,7 @@ export class ContentEvaluatorModule extends BaseModule {\n   });\n   private readonly _fixedRelevances: { [k: string]: number } = {};\n   private _tokenLimit: number = 0;\n+  private readonly _originalAuthorWeight: number = 0.5;",
          "score": {
            "reward": 1.296,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 1
                }
              },
              "result": 0
            },
            "priority": 2,
            "words": {
              "wordCount": 9,
              "wordValue": 0.1,
              "result": 0.84
            },
            "readability": {
              "fleschKincaid": 56.70000000000002,
              "syllables": 15,
              "sentences": 1,
              "score": 0.9670000000000002
            },
            "multiplier": 1,
            "relevance": 0.6
          }
        }
      ],
      "reviewRewards": [
        {
          "reviews": [
            {
              "reviewId": 2843321525,
              "effect": {
                "addition": 328,
                "deletion": 2
              },
              "reward": 6.6,
              "priority": 2
            }
          ],
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345"
        }
      ],
      "walletAddress": "0x4007CE2083c7F3E18097aeB3A39bb8eC149a341d",
      "permitUrl": "https://pay.ubq.fi?claim=W3sidHlwZSI6ImVyYzIwLXBlcm1pdCIsInBlcm1pdCI6eyJwZXJtaXR0ZWQiOnsidG9rZW4iOiIweEM2ZWQ0ZjUyMGY2QTRlNERDMjcyNzM1MDkyMzliN0Y4QTY4ZDIwNjgiLCJhbW91bnQiOiIxMjE2ODAwMDAwMDAwMDAwMDAwMCJ9LCJub25jZSI6IjgyMjY3MTA2NjUxMzIxMDY3ODE5MjEzNzEyMDE1NjY0MDQ4ODYwNjE1NTk5MjAyMjY0MjUyNTQxNTU1NzY3NjAzODk5MTEwMzY1NzQ5IiwiZGVhZGxpbmUiOiIxMTU3OTIwODkyMzczMTYxOTU0MjM1NzA5ODUwMDg2ODc5MDc4NTMyNjk5ODQ2NjU2NDA1NjQwMzk0NTc1ODQwMDc5MTMxMjk2Mzk5MzUifSwidHJhbnNmZXJEZXRhaWxzIjp7InRvIjoiMHg0MDA3Q0UyMDgzYzdGM0UxODA5N2FlQjNBMzliYjhlQzE0OWEzNDFkIiwicmVxdWVzdGVkQW1vdW50IjoiMTIxNjgwMDAwMDAwMDAwMDAwMDAifSwib3duZXIiOiIweDkwNTFlRGE5NmRCNDE5Yzk2NzE4OUY0QWMzMDNhMjkwRjMzMjc2ODAiLCJzaWduYXR1cmUiOiIweDRjNThhOGYzNGYwMTE5NTVhZDQ4NzRiYjEyYzRjZTI1YjM5YjExODI1N2E3OWY0NmY2ZTNiNmI3ODI4MDIwYjczMjZjNzY1ZWUyZDVlYWVmYzY5ODQ0NjNmMDU1NTFhZjZhZTMxMWVjNTdiZTJkYjVkNjBmODBhMTc3MjkzYTUzMWMiLCJuZXR3b3JrSWQiOjEwMH1d",
      "payoutMode": "permit"
    },
    "whilefoo": {
      "total": 25.316,
      "userId": 139262667,
      "comments": [
        {
          "id": 2850638097,
          "content": "QA: https://github.com/ubiquibot-whilefoo-testing/testing/issues/64#issuecomment-2890868666\r\nI think we should limit to 2 decimals like usually and it'd be also great to display the weight in the comment",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#pullrequestreview-2850638097",
          "commentType": "PULL_COLLABORATOR",
          "score": {
            "reward": 3.43,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 2
                },
                "a": {
                  "score": 1,
                  "elementCount": 1
                }
              },
              "result": 1
            },
            "priority": 2,
            "words": {
              "wordCount": 24,
              "wordValue": 0.1,
              "result": 1.66
            },
            "readability": {
              "fleschKincaid": 56.1296428571429,
              "syllables": 55,
              "sentences": 2,
              "score": 0.9612964285714289
            },
            "multiplier": 1,
            "relevance": 0.5
          }
        },
        {
          "id": 2089764792,
          "content": "This works only if the weight is `0.5` but not if it's it's any other value. You should set original reward to `originalReward * (1 - originalAuthorWeight)` and return a new reward set to `originalReward * originalAuthorWeight`",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#discussion_r2089764792",
          "commentType": "PULL_COLLABORATOR",
          "diffHunk": "@@ -108,9 +113,67 @@ export class ContentEvaluatorModule extends BaseModule {\n     }\n \n     await Promise.all(promises);\n+    if (data?.self?.body) {\n+      await this._handleRewardsForOriginalAuthor(data.self.body, result);\n+    }\n     return result;\n   }\n \n+  /*\n+   * If the specification was created from the comment of another user, reward that user accordingly.\n+   */\n+  private async _handleRewardsForOriginalAuthor(body: string, result: Result) {\n+    const originalComment = extractOriginalAuthor(body);\n+    if (!originalComment) return;\n+\n+    const specReward = this._extractAndAdjustSpecReward(result);\n+    if (!specReward) return;\n+\n+    await this._addRewardForAuthor(result, originalComment.username, specReward);\n+  }\n+\n+  private _extractAndAdjustSpecReward(result: Result) {\n+    for (const resultKey of Object.keys(result)) {\n+      const comments = result[resultKey].comments;\n+      if (!comments) continue;\n+\n+      const spec = comments.find((comment) => comment.commentType & CommentAssociation.SPECIFICATION);\n+      if (spec && spec.score?.reward !== undefined) {\n+        spec.score.reward *= this._originalAuthorWeight;\n+        return { ...spec };\n+      }",
          "score": {
            "reward": 4.176,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 1
                }
              },
              "result": 0
            },
            "priority": 2,
            "words": {
              "wordCount": 30,
              "wordValue": 0.1,
              "result": 1.88
            },
            "readability": {
              "fleschKincaid": 81.63000000000002,
              "syllables": 39,
              "sentences": 2,
              "score": 0.7836999999999997
            },
            "multiplier": 1,
            "relevance": 0.9
          }
        },
        {
          "id": 2089769895,
          "content": "It'd be a good idea to also check if issue author got correct reward and with uneven weight like `0.6`",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#discussion_r2089769895",
          "commentType": "PULL_COLLABORATOR",
          "diffHunk": "@@ -0,0 +1,220 @@\n+/* eslint-disable sonarjs/no-nested-functions */\n+\n+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, jest } from \"@jest/globals\";\n+import { drop } from \"@mswjs/data\";\n+import { customOctokit as Octokit } from \"@ubiquity-os/plugin-sdk/octokit\";\n+import { Logs } from \"@ubiquity-os/ubiquity-os-logger\";\n+import { http, HttpResponse, passthrough } from \"msw\";\n+import { extractOriginalAuthor } from \"../src/helpers/original-author\";\n+import { parseGitHubUrl } from \"../src/start\";\n+import { ContextPlugin } from \"../src/types/plugin-input\";\n+import { db } from \"./__mocks__/db\";\n+import dbSeed from \"./__mocks__/db-seed.json\";\n+import { server } from \"./__mocks__/node\";\n+import cfg from \"./__mocks__/results/valid-configuration.json\";\n+import \"./helpers/permit-mock\";\n+import { mockWeb3Module } from \"./helpers/web3-mocks\";\n+import { Result } from \"../src/types/results\";\n+\n+const issueUrl = \"https://github.com/ubiquity-os/conversation-rewards/issues/71\";\n+\n+mockWeb3Module();\n+\n+jest.unstable_mockModule(\"@actions/github\", () => ({\n+  default: {},\n+  context: {\n+    runId: \"1\",\n+    payload: {\n+      repository: {\n+        html_url: \"https://github.com/ubiquity-os/conversation-rewards\",\n+      },\n+    },\n+    sha: \"1234\",\n+  },\n+}));\n+\n+jest.unstable_mockModule(\"@supabase/supabase-js\", () => {\n+  return {\n+    createClient: jest.fn(() => ({\n+      from: jest.fn(() => ({\n+        insert: jest.fn(() => ({})),\n+        select: jest.fn(() => ({\n+          eq: jest.fn(() => ({\n+            single: jest.fn(() => ({\n+              data: {\n+                id: 1,\n+              },\n+            })),\n+            eq: jest.fn(() => ({\n+              single: jest.fn(() => ({\n+                data: {\n+                  id: 1,\n+                },\n+              })),\n+            })),\n+          })),\n+        })),\n+      })),\n+    })),\n+  };\n+});\n+\n+jest.unstable_mockModule(\"../src/helpers/get-comment-details\", () => ({\n+  getMinimizedCommentStatus: jest.fn(),\n+}));\n+\n+jest.unstable_mockModule(\"../src/data-collection/collect-linked-pulls\", () => ({\n+  collectLinkedMergedPulls: jest.fn(() => []),\n+}));\n+\n+beforeAll(() => {\n+  server.listen();\n+  // eslint-disable-next-line @typescript-eslint/naming-convention\n+  PaymentModule.prototype._getNetworkExplorer = async (_networkId: number) => {\n+    return Promise.resolve(\"https://rpc\");\n+  };\n+});\n+afterEach(() => server.resetHandlers());\n+afterAll(() => server.close());\n+\n+const { IssueActivity } = await import(\"../src/issue-activity\");\n+const { ContentEvaluatorModule } = await import(\"../src/parser/content-evaluator-module\");\n+const { DataPurgeModule } = await import(\"../src/parser/data-purge-module\");\n+const { FormattingEvaluatorModule } = await import(\"../src/parser/formatting-evaluator-module\");\n+const { GithubCommentModule } = await import(\"../src/parser/github-comment-module\");\n+const { PaymentModule } = await import(\"../src/parser/payment-module\");\n+const { Processor } = await import(\"../src/parser/processor\");\n+const { UserExtractorModule } = await import(\"../src/parser/user-extractor-module\");\n+\n+jest.spyOn(ContentEvaluatorModule.prototype, \"_getRateLimitTokens\").mockImplementation(() => Promise.resolve(Infinity));\n+\n+jest\n+  .spyOn(ContentEvaluatorModule.prototype, \"_evaluateComments\")\n+  .mockImplementation((specificationBody, comments, allComments, prComments) => {\n+    return Promise.resolve(\n+      (() => {\n+        const relevance: { [k: string]: number } = {};\n+        comments.forEach((comment) => {\n+          relevance[`${comment.id}`] = 0.8;\n+        });\n+        prComments.forEach((comment) => {\n+          relevance[`${comment.id}`] = 0.7;\n+        });\n+        return relevance;\n+      })()\n+    );\n+  });\n+\n+describe(\"Content Evaluator Module Test\", () => {\n+  const issue = parseGitHubUrl(issueUrl);\n+  const ctx = {\n+    eventName: \"issues.closed\",\n+    payload: {\n+      issue: {\n+        html_url: issueUrl,\n+        number: 71,\n+        state_reason: \"completed\",\n+        assignees: [\n+          {\n+            id: 87654321,\n+            login: \"developer-1\",\n+          },\n+        ],\n+      },\n+      repository: {\n+        name: \"conversation-rewards\",\n+        owner: {\n+          login: \"ubiquity-os\",\n+          id: 76412717,\n+        },\n+      },\n+    },\n+    adapters: {\n+      supabase: {\n+        wallet: {\n+          getWalletByUserId: jest.fn(async () => \"0x1\"),\n+        },\n+      },\n+    },\n+    config: cfg,\n+    logger: new Logs(\"debug\"),\n+    octokit: new Octokit(),\n+    env: process.env,\n+  } as unknown as ContextPlugin;\n+  const activity = new IssueActivity(ctx, issue);\n+\n+  beforeEach(async () => {\n+    drop(db);\n+    for (const table of Object.keys(dbSeed)) {\n+      const tableName = table as keyof typeof dbSeed;\n+      for (const row of dbSeed[tableName]) {\n+        db[tableName].create(row);\n+      }\n+    }\n+    await activity.init();\n+  });\n+\n+  it(\"Should handle comments with original author references\", async () => {\n+    const commentId = 1571243461;\n+    const originalAuthorComment = {\n+      id: commentId,\n+      node_id: \"IC_kwDOKzVPS89fGhiO\",\n+      url: \"https://api.github.com/repos/ubiquity-os/conversation-rewards/issues/comments/1571243461\",\n+      html_url: \"https://github.com/ubiquity-os/conversation-rewards/issues/71#issuecomment-1571243461\",\n+      body: \"_Originally posted by @reviewer in [#69](https://github.com/ubiquity/work.ubq.fi/issues/69)_\\n\\nThe implementation looks good! One suggestion - consider adding a threshold value to filter out low-relevance comments.\",\n+      user: {\n+        login: \"test-user\",\n+        id: 12345678,\n+        node_id: \"MDQ6VXNlcjEyMzQ1Njc4\",\n+        avatar_url: \"https://avatars.githubusercontent.com/u/12345678?v=4\",\n+        gravatar_id: \"\",\n+        url: \"https://api.github.com/users/test-user\",\n+        html_url: \"https://github.com/test-user\",\n+        type: \"User\",\n+        site_admin: false,\n+      },\n+      created_at: \"2023-06-25T14:00:00Z\",\n+      updated_at: \"2023-06-25T14:00:00Z\",\n+      author_association: \"MEMBER\",\n+      performed_via_github_app: null,\n+    };\n+\n+    const extractedInfo = extractOriginalAuthor(originalAuthorComment.body);\n+    expect(extractedInfo).not.toBeNull();\n+    expect(extractedInfo?.username).toBe(\"reviewer\");\n+    expect(extractedInfo?.url).toBe(\"https://github.com/ubiquity/work.ubq.fi/issues/69\");\n+\n+    server.use(\n+      http.get(\"https://api.github.com/repos/ubiquity-os/conversation-rewards/issues/71/comments\", () => {\n+        const modifiedComments = [...activity.comments, originalAuthorComment];\n+        return HttpResponse.json(modifiedComments);\n+      })\n+    );\n+\n+    await activity.init();\n+\n+    const processor = new Processor(ctx);\n+    processor[\"_transformers\"] = [\n+      new UserExtractorModule(ctx),\n+      new DataPurgeModule(ctx),\n+      new FormattingEvaluatorModule(ctx),\n+      new ContentEvaluatorModule(ctx),\n+      new PaymentModule(ctx),\n+      new GithubCommentModule(ctx),\n+    ];\n+\n+    server.use(http.post(\"https://*\", () => passthrough()));\n+    await processor.run(activity);\n+\n+    const result = JSON.parse(processor.dump()) as Result;\n+\n+    const users = Object.keys(result);\n+    const hasOriginalAuthor = users.some((user) => user === \"reviewer\");\n+    expect(hasOriginalAuthor).toBeTruthy();\n+    expect(result[\"reviewer\"].total).toEqual(10.105);",
          "score": {
            "reward": 3.024,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 1
                }
              },
              "result": 0
            },
            "priority": 2,
            "words": {
              "wordCount": 20,
              "wordValue": 0.1,
              "result": 1.48
            },
            "readability": {
              "fleschKincaid": 68.09500000000004,
              "syllables": 28,
              "sentences": 1,
              "score": 0.9190499999999996
            },
            "multiplier": 1,
            "relevance": 0.8
          }
        },
        {
          "id": 2089772202,
          "content": "I think we don't need to run these 2 modules because `ContentEvaluatorModule` is the one we are testing.",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#discussion_r2089772202",
          "commentType": "PULL_COLLABORATOR",
          "diffHunk": "@@ -0,0 +1,220 @@\n+/* eslint-disable sonarjs/no-nested-functions */\n+\n+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, jest } from \"@jest/globals\";\n+import { drop } from \"@mswjs/data\";\n+import { customOctokit as Octokit } from \"@ubiquity-os/plugin-sdk/octokit\";\n+import { Logs } from \"@ubiquity-os/ubiquity-os-logger\";\n+import { http, HttpResponse, passthrough } from \"msw\";\n+import { extractOriginalAuthor } from \"../src/helpers/original-author\";\n+import { parseGitHubUrl } from \"../src/start\";\n+import { ContextPlugin } from \"../src/types/plugin-input\";\n+import { db } from \"./__mocks__/db\";\n+import dbSeed from \"./__mocks__/db-seed.json\";\n+import { server } from \"./__mocks__/node\";\n+import cfg from \"./__mocks__/results/valid-configuration.json\";\n+import \"./helpers/permit-mock\";\n+import { mockWeb3Module } from \"./helpers/web3-mocks\";\n+import { Result } from \"../src/types/results\";\n+\n+const issueUrl = \"https://github.com/ubiquity-os/conversation-rewards/issues/71\";\n+\n+mockWeb3Module();\n+\n+jest.unstable_mockModule(\"@actions/github\", () => ({\n+  default: {},\n+  context: {\n+    runId: \"1\",\n+    payload: {\n+      repository: {\n+        html_url: \"https://github.com/ubiquity-os/conversation-rewards\",\n+      },\n+    },\n+    sha: \"1234\",\n+  },\n+}));\n+\n+jest.unstable_mockModule(\"@supabase/supabase-js\", () => {\n+  return {\n+    createClient: jest.fn(() => ({\n+      from: jest.fn(() => ({\n+        insert: jest.fn(() => ({})),\n+        select: jest.fn(() => ({\n+          eq: jest.fn(() => ({\n+            single: jest.fn(() => ({\n+              data: {\n+                id: 1,\n+              },\n+            })),\n+            eq: jest.fn(() => ({\n+              single: jest.fn(() => ({\n+                data: {\n+                  id: 1,\n+                },\n+              })),\n+            })),\n+          })),\n+        })),\n+      })),\n+    })),\n+  };\n+});\n+\n+jest.unstable_mockModule(\"../src/helpers/get-comment-details\", () => ({\n+  getMinimizedCommentStatus: jest.fn(),\n+}));\n+\n+jest.unstable_mockModule(\"../src/data-collection/collect-linked-pulls\", () => ({\n+  collectLinkedMergedPulls: jest.fn(() => []),\n+}));\n+\n+beforeAll(() => {\n+  server.listen();\n+  // eslint-disable-next-line @typescript-eslint/naming-convention\n+  PaymentModule.prototype._getNetworkExplorer = async (_networkId: number) => {\n+    return Promise.resolve(\"https://rpc\");\n+  };\n+});\n+afterEach(() => server.resetHandlers());\n+afterAll(() => server.close());\n+\n+const { IssueActivity } = await import(\"../src/issue-activity\");\n+const { ContentEvaluatorModule } = await import(\"../src/parser/content-evaluator-module\");\n+const { DataPurgeModule } = await import(\"../src/parser/data-purge-module\");\n+const { FormattingEvaluatorModule } = await import(\"../src/parser/formatting-evaluator-module\");\n+const { GithubCommentModule } = await import(\"../src/parser/github-comment-module\");\n+const { PaymentModule } = await import(\"../src/parser/payment-module\");\n+const { Processor } = await import(\"../src/parser/processor\");\n+const { UserExtractorModule } = await import(\"../src/parser/user-extractor-module\");\n+\n+jest.spyOn(ContentEvaluatorModule.prototype, \"_getRateLimitTokens\").mockImplementation(() => Promise.resolve(Infinity));\n+\n+jest\n+  .spyOn(ContentEvaluatorModule.prototype, \"_evaluateComments\")\n+  .mockImplementation((specificationBody, comments, allComments, prComments) => {\n+    return Promise.resolve(\n+      (() => {\n+        const relevance: { [k: string]: number } = {};\n+        comments.forEach((comment) => {\n+          relevance[`${comment.id}`] = 0.8;\n+        });\n+        prComments.forEach((comment) => {\n+          relevance[`${comment.id}`] = 0.7;\n+        });\n+        return relevance;\n+      })()\n+    );\n+  });\n+\n+describe(\"Content Evaluator Module Test\", () => {\n+  const issue = parseGitHubUrl(issueUrl);\n+  const ctx = {\n+    eventName: \"issues.closed\",\n+    payload: {\n+      issue: {\n+        html_url: issueUrl,\n+        number: 71,\n+        state_reason: \"completed\",\n+        assignees: [\n+          {\n+            id: 87654321,\n+            login: \"developer-1\",\n+          },\n+        ],\n+      },\n+      repository: {\n+        name: \"conversation-rewards\",\n+        owner: {\n+          login: \"ubiquity-os\",\n+          id: 76412717,\n+        },\n+      },\n+    },\n+    adapters: {\n+      supabase: {\n+        wallet: {\n+          getWalletByUserId: jest.fn(async () => \"0x1\"),\n+        },\n+      },\n+    },\n+    config: cfg,\n+    logger: new Logs(\"debug\"),\n+    octokit: new Octokit(),\n+    env: process.env,\n+  } as unknown as ContextPlugin;\n+  const activity = new IssueActivity(ctx, issue);\n+\n+  beforeEach(async () => {\n+    drop(db);\n+    for (const table of Object.keys(dbSeed)) {\n+      const tableName = table as keyof typeof dbSeed;\n+      for (const row of dbSeed[tableName]) {\n+        db[tableName].create(row);\n+      }\n+    }\n+    await activity.init();\n+  });\n+\n+  it(\"Should handle comments with original author references\", async () => {\n+    const commentId = 1571243461;\n+    const originalAuthorComment = {\n+      id: commentId,\n+      node_id: \"IC_kwDOKzVPS89fGhiO\",\n+      url: \"https://api.github.com/repos/ubiquity-os/conversation-rewards/issues/comments/1571243461\",\n+      html_url: \"https://github.com/ubiquity-os/conversation-rewards/issues/71#issuecomment-1571243461\",\n+      body: \"_Originally posted by @reviewer in [#69](https://github.com/ubiquity/work.ubq.fi/issues/69)_\\n\\nThe implementation looks good! One suggestion - consider adding a threshold value to filter out low-relevance comments.\",\n+      user: {\n+        login: \"test-user\",\n+        id: 12345678,\n+        node_id: \"MDQ6VXNlcjEyMzQ1Njc4\",\n+        avatar_url: \"https://avatars.githubusercontent.com/u/12345678?v=4\",\n+        gravatar_id: \"\",\n+        url: \"https://api.github.com/users/test-user\",\n+        html_url: \"https://github.com/test-user\",\n+        type: \"User\",\n+        site_admin: false,\n+      },\n+      created_at: \"2023-06-25T14:00:00Z\",\n+      updated_at: \"2023-06-25T14:00:00Z\",\n+      author_association: \"MEMBER\",\n+      performed_via_github_app: null,\n+    };\n+\n+    const extractedInfo = extractOriginalAuthor(originalAuthorComment.body);\n+    expect(extractedInfo).not.toBeNull();\n+    expect(extractedInfo?.username).toBe(\"reviewer\");\n+    expect(extractedInfo?.url).toBe(\"https://github.com/ubiquity/work.ubq.fi/issues/69\");\n+\n+    server.use(\n+      http.get(\"https://api.github.com/repos/ubiquity-os/conversation-rewards/issues/71/comments\", () => {\n+        const modifiedComments = [...activity.comments, originalAuthorComment];\n+        return HttpResponse.json(modifiedComments);\n+      })\n+    );\n+\n+    await activity.init();\n+\n+    const processor = new Processor(ctx);\n+    processor[\"_transformers\"] = [\n+      new UserExtractorModule(ctx),\n+      new DataPurgeModule(ctx),\n+      new FormattingEvaluatorModule(ctx),\n+      new ContentEvaluatorModule(ctx),\n+      new PaymentModule(ctx),\n+      new GithubCommentModule(ctx),",
          "score": {
            "reward": 2.366,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 1
                }
              },
              "result": 0
            },
            "priority": 2,
            "words": {
              "wordCount": 18,
              "wordValue": 0.1,
              "result": 1.38
            },
            "readability": {
              "fleschKincaid": 85.16499999999999,
              "syllables": 22,
              "sentences": 1,
              "score": 0.7483500000000001
            },
            "multiplier": 1,
            "relevance": 0.7
          }
        },
        {
          "id": 2891935061,
          "content": "I used 0.7\r\nIt looks like it's working: https://github.com/ubiquibot-whilefoo-testing/testing/issues/64#issuecomment-2890868666",
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345#issuecomment-2891935061",
          "commentType": "PULL_COLLABORATOR",
          "score": {
            "reward": 2.94,
            "formatting": {
              "content": {
                "p": {
                  "score": 0,
                  "elementCount": 2
                },
                "a": {
                  "score": 1,
                  "elementCount": 1
                }
              },
              "result": 1
            },
            "priority": 2,
            "words": {
              "wordCount": 10,
              "wordValue": 0.1,
              "result": 0.9
            },
            "readability": {
              "fleschKincaid": 62.75857142857146,
              "syllables": 34,
              "sentences": 3,
              "score": 0.9724142857142855
            },
            "multiplier": 1,
            "relevance": 0.6
          }
        }
      ],
      "reviewRewards": [
        {
          "reviews": [
            {
              "reviewId": 2841535261,
              "effect": {
                "addition": 326,
                "deletion": 2
              },
              "reward": 6.56,
              "priority": 2
            },
            {
              "reviewId": 2850638097,
              "effect": {
                "addition": 10,
                "deletion": 8
              },
              "reward": 0.36,
              "priority": 2
            },
            {
              "reviewId": 2854641305,
              "effect": {
                "addition": 39,
                "deletion": 84
              },
              "reward": 2.46,
              "priority": 2
            }
          ],
          "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345"
        }
      ],
      "walletAddress": "0xA0B11F474d8ECE1205d38c66d5F2bE8917675d60",
      "permitUrl": "https://pay.ubq.fi?claim=W3sidHlwZSI6ImVyYzIwLXBlcm1pdCIsInBlcm1pdCI6eyJwZXJtaXR0ZWQiOnsidG9rZW4iOiIweEM2ZWQ0ZjUyMGY2QTRlNERDMjcyNzM1MDkyMzliN0Y4QTY4ZDIwNjgiLCJhbW91bnQiOiIyNTMxNjAwMDAwMDAwMDAwMDAwMCJ9LCJub25jZSI6IjU1MjUxOTUwNzAyMDI0MTQzMzczOTU2Njk0NzE1NTQ2OTc2MTcyNjA2NTg4NjkwMTYzNTg1NDYxODAzNjQyMzA4ODUxMzAxNDQ4NTM1IiwiZGVhZGxpbmUiOiIxMTU3OTIwODkyMzczMTYxOTU0MjM1NzA5ODUwMDg2ODc5MDc4NTMyNjk5ODQ2NjU2NDA1NjQwMzk0NTc1ODQwMDc5MTMxMjk2Mzk5MzUifSwidHJhbnNmZXJEZXRhaWxzIjp7InRvIjoiMHhBMEIxMUY0NzRkOEVDRTEyMDVkMzhjNjZkNUYyYkU4OTE3Njc1ZDYwIiwicmVxdWVzdGVkQW1vdW50IjoiMjUzMTYwMDAwMDAwMDAwMDAwMDAifSwib3duZXIiOiIweDkwNTFlRGE5NmRCNDE5Yzk2NzE4OUY0QWMzMDNhMjkwRjMzMjc2ODAiLCJzaWduYXR1cmUiOiIweGQ0MjhkMzYxODJiMDU1YzVmMWZhOGM1MTNlMTNjNGI0NzQ5YWJkMWJjY2I0OTJlYTMwNWU5MDQ0YmE1MGU4Y2MxYTA1NzNhNjZiMWFjNTI5NThjZDZlZTlkYTUwZmVmYzFlMGE3YmJlZGQ1ODAzYjUyM2M4NTRlNDgzYWNiZTE3MWMiLCJuZXR3b3JrSWQiOjEwMH1d",
      "payoutMode": "permit"
    }
  }
}
-->

<!-- UbiquityOS - GithubCommentModule.transform - f6835f2b289f1f6da0046854981270a01de2909d - @gentlementlegen - https://github.com/ubiquity-os-marketplace/text-conversation-rewards/actions/runs/15142601570
{
  "caller": "GithubCommentModule.transform"
}
-->

author_association: CONTRIBUTOR

Diff:

Reviews:
user: login: gentlementlegen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T03:43:59Z
user: login: gentlementlegen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T06:03:30Z
user: login: whilefoo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T21:21:01Z
user: login: gentlementlegen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T08:59:05Z
user: login: gentlementlegen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T08:59:29Z
user: login: 0x4007
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T11:24:36Z
user: login: gentlementlegen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T11:41:04Z
user: login: gentlementlegen
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T17:32:13Z
user: login: whilefoo
body: QA: https://github.com/ubiquibot-whilefoo-testing/testing/issues/64#issuecomment-2890868666
I think we should limit to 2 decimals like usually and it'd be also great to display the weight in the comment
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T12:45:12Z
user: login: whilefoo
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-20T15:17:49Z

Review comments:
diff_hunk: @@ -108,9 +113,62 @@ export class ContentEvaluatorModule extends BaseModule {
     }
 
     await Promise.all(promises);
+    if (data?.self?.body) {
+      await this._handleRewardsForOriginalAuthor(data.self.body, result);
+    }
     return result;
   }
 
+  /*
+   * If the specification was created from the comment of another user, reward that user accordingly.
+   */
+  private async _handleRewardsForOriginalAuthor(body: string, result: Result) {
+    const originalComment = extractOriginalAuthor(body);
+    if (!originalComment) return;
+
+    const specReward = this._extractAndAdjustSpecReward(result);
+    if (!specReward) return;
+
+    await this._addRewardForAuthor(result, originalComment.username, specReward);
+  }
+
+  private _extractAndAdjustSpecReward(result: Result) {
+    for (const resultKey of Object.keys(result)) {
+      const comments = result[resultKey].comments;
+      if (!comments) continue;
+
+      const spec = comments.find((comment) => comment.commentType & CommentAssociation.SPECIFICATION);
+      if (spec && spec.score?.reward !== undefined) {
+        spec.score.reward *= this._originalAuthorWeight;
+        return { ...spec };
+      }
+    }
+    return undefined;
+  }
+
+  private async _addRewardForAuthor(result: Result, username: string, specReward: GithubCommentScore) {
+    if (result[username]?.comments) {
+      result[username].comments.push(specReward);
+    } else {
+      const userId = await this._fetchGithubUserId(username);
path: src/parser/content-evaluator-module.ts
user: login: gentlementlegen
body: I realized that eventually that user could be a `Bot`, I'll add a check for that.
created_at: 2025-05-14T03:43:58Z
author_association: MEMBER
diff_hunk: @@ -108,9 +113,62 @@ export class ContentEvaluatorModule extends BaseModule {
     }
 
     await Promise.all(promises);
+    if (data?.self?.body) {
+      await this._handleRewardsForOriginalAuthor(data.self.body, result);
+    }
     return result;
   }
 
+  /*
+   * If the specification was created from the comment of another user, reward that user accordingly.
+   */
+  private async _handleRewardsForOriginalAuthor(body: string, result: Result) {
+    const originalComment = extractOriginalAuthor(body);
+    if (!originalComment) return;
+
+    const specReward = this._extractAndAdjustSpecReward(result);
+    if (!specReward) return;
+
+    await this._addRewardForAuthor(result, originalComment.username, specReward);
+  }
+
+  private _extractAndAdjustSpecReward(result: Result) {
+    for (const resultKey of Object.keys(result)) {
+      const comments = result[resultKey].comments;
+      if (!comments) continue;
+
+      const spec = comments.find((comment) => comment.commentType & CommentAssociation.SPECIFICATION);
+      if (spec && spec.score?.reward !== undefined) {
+        spec.score.reward *= this._originalAuthorWeight;
+        return { ...spec };
+      }
+    }
+    return undefined;
+  }
+
+  private async _addRewardForAuthor(result: Result, username: string, specReward: GithubCommentScore) {
+    if (result[username]?.comments) {
+      result[username].comments.push(specReward);
+    } else {
+      const userId = await this._fetchGithubUserId(username);
path: src/parser/content-evaluator-module.ts
user: login: gentlementlegen
body: [31ad77f](https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345/commits/31ad77f81349109c350e17188aa203e591c6b15d)
created_at: 2025-05-14T06:03:30Z
author_association: MEMBER
diff_hunk: @@ -108,9 +113,67 @@ export class ContentEvaluatorModule extends BaseModule {
     }
 
     await Promise.all(promises);
+    if (data?.self?.body) {
+      await this._handleRewardsForOriginalAuthor(data.self.body, result);
+    }
     return result;
   }
 
+  /*
+   * If the specification was created from the comment of another user, reward that user accordingly.
+   */
+  private async _handleRewardsForOriginalAuthor(body: string, result: Result) {
+    const originalComment = extractOriginalAuthor(body);
+    if (!originalComment) return;
+
+    const specReward = this._extractAndAdjustSpecReward(result);
+    if (!specReward) return;
+
+    await this._addRewardForAuthor(result, originalComment.username, specReward);
+  }
+
+  private _extractAndAdjustSpecReward(result: Result) {
+    for (const resultKey of Object.keys(result)) {
+      const comments = result[resultKey].comments;
+      if (!comments) continue;
+
+      const spec = comments.find((comment) => comment.commentType & CommentAssociation.SPECIFICATION);
+      if (spec && spec.score?.reward !== undefined) {
+        spec.score.reward *= this._originalAuthorWeight;
+        return { ...spec };
+      }
path: src/parser/content-evaluator-module.ts
user: login: whilefoo
body: This works only if the weight is `0.5` but not if it's it's any other value. You should set original reward to `originalReward * (1 - originalAuthorWeight)` and return a new reward set to `originalReward * originalAuthorWeight`
created_at: 2025-05-14T21:14:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,220 @@
+/* eslint-disable sonarjs/no-nested-functions */
+
+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
+import { drop } from "@mswjs/data";
+import { customOctokit as Octokit } from "@ubiquity-os/plugin-sdk/octokit";
+import { Logs } from "@ubiquity-os/ubiquity-os-logger";
+import { http, HttpResponse, passthrough } from "msw";
+import { extractOriginalAuthor } from "../src/helpers/original-author";
+import { parseGitHubUrl } from "../src/start";
+import { ContextPlugin } from "../src/types/plugin-input";
+import { db } from "./__mocks__/db";
+import dbSeed from "./__mocks__/db-seed.json";
+import { server } from "./__mocks__/node";
+import cfg from "./__mocks__/results/valid-configuration.json";
+import "./helpers/permit-mock";
+import { mockWeb3Module } from "./helpers/web3-mocks";
+import { Result } from "../src/types/results";
+
+const issueUrl = "https://github.com/ubiquity-os/conversation-rewards/issues/71";
+
+mockWeb3Module();
+
+jest.unstable_mockModule("@actions/github", () => ({
+  default: {},
+  context: {
+    runId: "1",
+    payload: {
+      repository: {
+        html_url: "https://github.com/ubiquity-os/conversation-rewards",
+      },
+    },
+    sha: "1234",
+  },
+}));
+
+jest.unstable_mockModule("@supabase/supabase-js", () => {
+  return {
+    createClient: jest.fn(() => ({
+      from: jest.fn(() => ({
+        insert: jest.fn(() => ({})),
+        select: jest.fn(() => ({
+          eq: jest.fn(() => ({
+            single: jest.fn(() => ({
+              data: {
+                id: 1,
+              },
+            })),
+            eq: jest.fn(() => ({
+              single: jest.fn(() => ({
+                data: {
+                  id: 1,
+                },
+              })),
+            })),
+          })),
+        })),
+      })),
+    })),
+  };
+});
+
+jest.unstable_mockModule("../src/helpers/get-comment-details", () => ({
+  getMinimizedCommentStatus: jest.fn(),
+}));
+
+jest.unstable_mockModule("../src/data-collection/collect-linked-pulls", () => ({
+  collectLinkedMergedPulls: jest.fn(() => []),
+}));
+
+beforeAll(() => {
+  server.listen();
+  // eslint-disable-next-line @typescript-eslint/naming-convention
+  PaymentModule.prototype._getNetworkExplorer = async (_networkId: number) => {
+    return Promise.resolve("https://rpc");
+  };
+});
+afterEach(() => server.resetHandlers());
+afterAll(() => server.close());
+
+const { IssueActivity } = await import("../src/issue-activity");
+const { ContentEvaluatorModule } = await import("../src/parser/content-evaluator-module");
+const { DataPurgeModule } = await import("../src/parser/data-purge-module");
+const { FormattingEvaluatorModule } = await import("../src/parser/formatting-evaluator-module");
+const { GithubCommentModule } = await import("../src/parser/github-comment-module");
+const { PaymentModule } = await import("../src/parser/payment-module");
+const { Processor } = await import("../src/parser/processor");
+const { UserExtractorModule } = await import("../src/parser/user-extractor-module");
+
+jest.spyOn(ContentEvaluatorModule.prototype, "_getRateLimitTokens").mockImplementation(() => Promise.resolve(Infinity));
+
+jest
+  .spyOn(ContentEvaluatorModule.prototype, "_evaluateComments")
+  .mockImplementation((specificationBody, comments, allComments, prComments) => {
+    return Promise.resolve(
+      (() => {
+        const relevance: { [k: string]: number } = {};
+        comments.forEach((comment) => {
+          relevance[`${comment.id}`] = 0.8;
+        });
+        prComments.forEach((comment) => {
+          relevance[`${comment.id}`] = 0.7;
+        });
+        return relevance;
+      })()
+    );
+  });
+
+describe("Content Evaluator Module Test", () => {
+  const issue = parseGitHubUrl(issueUrl);
+  const ctx = {
+    eventName: "issues.closed",
+    payload: {
+      issue: {
+        html_url: issueUrl,
+        number: 71,
+        state_reason: "completed",
+        assignees: [
+          {
+            id: 87654321,
+            login: "developer-1",
+          },
+        ],
+      },
+      repository: {
+        name: "conversation-rewards",
+        owner: {
+          login: "ubiquity-os",
+          id: 76412717,
+        },
+      },
+    },
+    adapters: {
+      supabase: {
+        wallet: {
+          getWalletByUserId: jest.fn(async () => "0x1"),
+        },
+      },
+    },
+    config: cfg,
+    logger: new Logs("debug"),
+    octokit: new Octokit(),
+    env: process.env,
+  } as unknown as ContextPlugin;
+  const activity = new IssueActivity(ctx, issue);
+
+  beforeEach(async () => {
+    drop(db);
+    for (const table of Object.keys(dbSeed)) {
+      const tableName = table as keyof typeof dbSeed;
+      for (const row of dbSeed[tableName]) {
+        db[tableName].create(row);
+      }
+    }
+    await activity.init();
+  });
+
+  it("Should handle comments with original author references", async () => {
+    const commentId = 1571243461;
+    const originalAuthorComment = {
+      id: commentId,
+      node_id: "IC_kwDOKzVPS89fGhiO",
+      url: "https://api.github.com/repos/ubiquity-os/conversation-rewards/issues/comments/1571243461",
+      html_url: "https://github.com/ubiquity-os/conversation-rewards/issues/71#issuecomment-1571243461",
+      body: "_Originally posted by @reviewer in [#69](https://github.com/ubiquity/work.ubq.fi/issues/69)_\n\nThe implementation looks good! One suggestion - consider adding a threshold value to filter out low-relevance comments.",
+      user: {
+        login: "test-user",
+        id: 12345678,
+        node_id: "MDQ6VXNlcjEyMzQ1Njc4",
+        avatar_url: "https://avatars.githubusercontent.com/u/12345678?v=4",
+        gravatar_id: "",
+        url: "https://api.github.com/users/test-user",
+        html_url: "https://github.com/test-user",
+        type: "User",
+        site_admin: false,
+      },
+      created_at: "2023-06-25T14:00:00Z",
+      updated_at: "2023-06-25T14:00:00Z",
+      author_association: "MEMBER",
+      performed_via_github_app: null,
+    };
+
+    const extractedInfo = extractOriginalAuthor(originalAuthorComment.body);
+    expect(extractedInfo).not.toBeNull();
+    expect(extractedInfo?.username).toBe("reviewer");
+    expect(extractedInfo?.url).toBe("https://github.com/ubiquity/work.ubq.fi/issues/69");
+
+    server.use(
+      http.get("https://api.github.com/repos/ubiquity-os/conversation-rewards/issues/71/comments", () => {
+        const modifiedComments = [...activity.comments, originalAuthorComment];
+        return HttpResponse.json(modifiedComments);
+      })
+    );
+
+    await activity.init();
+
+    const processor = new Processor(ctx);
+    processor["_transformers"] = [
+      new UserExtractorModule(ctx),
+      new DataPurgeModule(ctx),
+      new FormattingEvaluatorModule(ctx),
+      new ContentEvaluatorModule(ctx),
+      new PaymentModule(ctx),
+      new GithubCommentModule(ctx),
+    ];
+
+    server.use(http.post("https://*", () => passthrough()));
+    await processor.run(activity);
+
+    const result = JSON.parse(processor.dump()) as Result;
+
+    const users = Object.keys(result);
+    const hasOriginalAuthor = users.some((user) => user === "reviewer");
+    expect(hasOriginalAuthor).toBeTruthy();
+    expect(result["reviewer"].total).toEqual(10.105);
path: tests/specification.test.ts
user: login: whilefoo
body: It'd be a good idea to also check if issue author got correct reward and with uneven weight like `0.6`
created_at: 2025-05-14T21:17:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,220 @@
+/* eslint-disable sonarjs/no-nested-functions */
+
+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
+import { drop } from "@mswjs/data";
+import { customOctokit as Octokit } from "@ubiquity-os/plugin-sdk/octokit";
+import { Logs } from "@ubiquity-os/ubiquity-os-logger";
+import { http, HttpResponse, passthrough } from "msw";
+import { extractOriginalAuthor } from "../src/helpers/original-author";
+import { parseGitHubUrl } from "../src/start";
+import { ContextPlugin } from "../src/types/plugin-input";
+import { db } from "./__mocks__/db";
+import dbSeed from "./__mocks__/db-seed.json";
+import { server } from "./__mocks__/node";
+import cfg from "./__mocks__/results/valid-configuration.json";
+import "./helpers/permit-mock";
+import { mockWeb3Module } from "./helpers/web3-mocks";
+import { Result } from "../src/types/results";
+
+const issueUrl = "https://github.com/ubiquity-os/conversation-rewards/issues/71";
+
+mockWeb3Module();
+
+jest.unstable_mockModule("@actions/github", () => ({
+  default: {},
+  context: {
+    runId: "1",
+    payload: {
+      repository: {
+        html_url: "https://github.com/ubiquity-os/conversation-rewards",
+      },
+    },
+    sha: "1234",
+  },
+}));
+
+jest.unstable_mockModule("@supabase/supabase-js", () => {
+  return {
+    createClient: jest.fn(() => ({
+      from: jest.fn(() => ({
+        insert: jest.fn(() => ({})),
+        select: jest.fn(() => ({
+          eq: jest.fn(() => ({
+            single: jest.fn(() => ({
+              data: {
+                id: 1,
+              },
+            })),
+            eq: jest.fn(() => ({
+              single: jest.fn(() => ({
+                data: {
+                  id: 1,
+                },
+              })),
+            })),
+          })),
+        })),
+      })),
+    })),
+  };
+});
+
+jest.unstable_mockModule("../src/helpers/get-comment-details", () => ({
+  getMinimizedCommentStatus: jest.fn(),
+}));
+
+jest.unstable_mockModule("../src/data-collection/collect-linked-pulls", () => ({
+  collectLinkedMergedPulls: jest.fn(() => []),
+}));
+
+beforeAll(() => {
+  server.listen();
+  // eslint-disable-next-line @typescript-eslint/naming-convention
+  PaymentModule.prototype._getNetworkExplorer = async (_networkId: number) => {
+    return Promise.resolve("https://rpc");
+  };
+});
+afterEach(() => server.resetHandlers());
+afterAll(() => server.close());
+
+const { IssueActivity } = await import("../src/issue-activity");
+const { ContentEvaluatorModule } = await import("../src/parser/content-evaluator-module");
+const { DataPurgeModule } = await import("../src/parser/data-purge-module");
+const { FormattingEvaluatorModule } = await import("../src/parser/formatting-evaluator-module");
+const { GithubCommentModule } = await import("../src/parser/github-comment-module");
+const { PaymentModule } = await import("../src/parser/payment-module");
+const { Processor } = await import("../src/parser/processor");
+const { UserExtractorModule } = await import("../src/parser/user-extractor-module");
+
+jest.spyOn(ContentEvaluatorModule.prototype, "_getRateLimitTokens").mockImplementation(() => Promise.resolve(Infinity));
+
+jest
+  .spyOn(ContentEvaluatorModule.prototype, "_evaluateComments")
+  .mockImplementation((specificationBody, comments, allComments, prComments) => {
+    return Promise.resolve(
+      (() => {
+        const relevance: { [k: string]: number } = {};
+        comments.forEach((comment) => {
+          relevance[`${comment.id}`] = 0.8;
+        });
+        prComments.forEach((comment) => {
+          relevance[`${comment.id}`] = 0.7;
+        });
+        return relevance;
+      })()
+    );
+  });
+
+describe("Content Evaluator Module Test", () => {
+  const issue = parseGitHubUrl(issueUrl);
+  const ctx = {
+    eventName: "issues.closed",
+    payload: {
+      issue: {
+        html_url: issueUrl,
+        number: 71,
+        state_reason: "completed",
+        assignees: [
+          {
+            id: 87654321,
+            login: "developer-1",
+          },
+        ],
+      },
+      repository: {
+        name: "conversation-rewards",
+        owner: {
+          login: "ubiquity-os",
+          id: 76412717,
+        },
+      },
+    },
+    adapters: {
+      supabase: {
+        wallet: {
+          getWalletByUserId: jest.fn(async () => "0x1"),
+        },
+      },
+    },
+    config: cfg,
+    logger: new Logs("debug"),
+    octokit: new Octokit(),
+    env: process.env,
+  } as unknown as ContextPlugin;
+  const activity = new IssueActivity(ctx, issue);
+
+  beforeEach(async () => {
+    drop(db);
+    for (const table of Object.keys(dbSeed)) {
+      const tableName = table as keyof typeof dbSeed;
+      for (const row of dbSeed[tableName]) {
+        db[tableName].create(row);
+      }
+    }
+    await activity.init();
+  });
+
+  it("Should handle comments with original author references", async () => {
+    const commentId = 1571243461;
+    const originalAuthorComment = {
+      id: commentId,
+      node_id: "IC_kwDOKzVPS89fGhiO",
+      url: "https://api.github.com/repos/ubiquity-os/conversation-rewards/issues/comments/1571243461",
+      html_url: "https://github.com/ubiquity-os/conversation-rewards/issues/71#issuecomment-1571243461",
+      body: "_Originally posted by @reviewer in [#69](https://github.com/ubiquity/work.ubq.fi/issues/69)_\n\nThe implementation looks good! One suggestion - consider adding a threshold value to filter out low-relevance comments.",
+      user: {
+        login: "test-user",
+        id: 12345678,
+        node_id: "MDQ6VXNlcjEyMzQ1Njc4",
+        avatar_url: "https://avatars.githubusercontent.com/u/12345678?v=4",
+        gravatar_id: "",
+        url: "https://api.github.com/users/test-user",
+        html_url: "https://github.com/test-user",
+        type: "User",
+        site_admin: false,
+      },
+      created_at: "2023-06-25T14:00:00Z",
+      updated_at: "2023-06-25T14:00:00Z",
+      author_association: "MEMBER",
+      performed_via_github_app: null,
+    };
+
+    const extractedInfo = extractOriginalAuthor(originalAuthorComment.body);
+    expect(extractedInfo).not.toBeNull();
+    expect(extractedInfo?.username).toBe("reviewer");
+    expect(extractedInfo?.url).toBe("https://github.com/ubiquity/work.ubq.fi/issues/69");
+
+    server.use(
+      http.get("https://api.github.com/repos/ubiquity-os/conversation-rewards/issues/71/comments", () => {
+        const modifiedComments = [...activity.comments, originalAuthorComment];
+        return HttpResponse.json(modifiedComments);
+      })
+    );
+
+    await activity.init();
+
+    const processor = new Processor(ctx);
+    processor["_transformers"] = [
+      new UserExtractorModule(ctx),
+      new DataPurgeModule(ctx),
+      new FormattingEvaluatorModule(ctx),
+      new ContentEvaluatorModule(ctx),
+      new PaymentModule(ctx),
+      new GithubCommentModule(ctx),
path: tests/specification.test.ts
user: login: whilefoo
body: I think we don't need to run these 2 modules because `ContentEvaluatorModule` is the one we are testing.
created_at: 2025-05-14T21:19:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,220 @@
+/* eslint-disable sonarjs/no-nested-functions */
+
+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
+import { drop } from "@mswjs/data";
+import { customOctokit as Octokit } from "@ubiquity-os/plugin-sdk/octokit";
+import { Logs } from "@ubiquity-os/ubiquity-os-logger";
+import { http, HttpResponse, passthrough } from "msw";
+import { extractOriginalAuthor } from "../src/helpers/original-author";
+import { parseGitHubUrl } from "../src/start";
+import { ContextPlugin } from "../src/types/plugin-input";
+import { db } from "./__mocks__/db";
+import dbSeed from "./__mocks__/db-seed.json";
+import { server } from "./__mocks__/node";
+import cfg from "./__mocks__/results/valid-configuration.json";
+import "./helpers/permit-mock";
+import { mockWeb3Module } from "./helpers/web3-mocks";
+import { Result } from "../src/types/results";
+
+const issueUrl = "https://github.com/ubiquity-os/conversation-rewards/issues/71";
+
+mockWeb3Module();
+
+jest.unstable_mockModule("@actions/github", () => ({
+  default: {},
+  context: {
+    runId: "1",
+    payload: {
+      repository: {
+        html_url: "https://github.com/ubiquity-os/conversation-rewards",
+      },
+    },
+    sha: "1234",
+  },
+}));
+
+jest.unstable_mockModule("@supabase/supabase-js", () => {
+  return {
+    createClient: jest.fn(() => ({
+      from: jest.fn(() => ({
+        insert: jest.fn(() => ({})),
+        select: jest.fn(() => ({
+          eq: jest.fn(() => ({
+            single: jest.fn(() => ({
+              data: {
+                id: 1,
+              },
+            })),
+            eq: jest.fn(() => ({
+              single: jest.fn(() => ({
+                data: {
+                  id: 1,
+                },
+              })),
+            })),
+          })),
+        })),
+      })),
+    })),
+  };
+});
+
+jest.unstable_mockModule("../src/helpers/get-comment-details", () => ({
+  getMinimizedCommentStatus: jest.fn(),
+}));
+
+jest.unstable_mockModule("../src/data-collection/collect-linked-pulls", () => ({
+  collectLinkedMergedPulls: jest.fn(() => []),
+}));
+
+beforeAll(() => {
+  server.listen();
+  // eslint-disable-next-line @typescript-eslint/naming-convention
+  PaymentModule.prototype._getNetworkExplorer = async (_networkId: number) => {
+    return Promise.resolve("https://rpc");
+  };
+});
+afterEach(() => server.resetHandlers());
+afterAll(() => server.close());
+
+const { IssueActivity } = await import("../src/issue-activity");
+const { ContentEvaluatorModule } = await import("../src/parser/content-evaluator-module");
+const { DataPurgeModule } = await import("../src/parser/data-purge-module");
+const { FormattingEvaluatorModule } = await import("../src/parser/formatting-evaluator-module");
+const { GithubCommentModule } = await import("../src/parser/github-comment-module");
+const { PaymentModule } = await import("../src/parser/payment-module");
+const { Processor } = await import("../src/parser/processor");
+const { UserExtractorModule } = await import("../src/parser/user-extractor-module");
+
+jest.spyOn(ContentEvaluatorModule.prototype, "_getRateLimitTokens").mockImplementation(() => Promise.resolve(Infinity));
+
+jest
+  .spyOn(ContentEvaluatorModule.prototype, "_evaluateComments")
+  .mockImplementation((specificationBody, comments, allComments, prComments) => {
+    return Promise.resolve(
+      (() => {
+        const relevance: { [k: string]: number } = {};
+        comments.forEach((comment) => {
+          relevance[`${comment.id}`] = 0.8;
+        });
+        prComments.forEach((comment) => {
+          relevance[`${comment.id}`] = 0.7;
+        });
+        return relevance;
+      })()
+    );
+  });
+
+describe("Content Evaluator Module Test", () => {
+  const issue = parseGitHubUrl(issueUrl);
+  const ctx = {
+    eventName: "issues.closed",
+    payload: {
+      issue: {
+        html_url: issueUrl,
+        number: 71,
+        state_reason: "completed",
+        assignees: [
+          {
+            id: 87654321,
+            login: "developer-1",
+          },
+        ],
+      },
+      repository: {
+        name: "conversation-rewards",
+        owner: {
+          login: "ubiquity-os",
+          id: 76412717,
+        },
+      },
+    },
+    adapters: {
+      supabase: {
+        wallet: {
+          getWalletByUserId: jest.fn(async () => "0x1"),
+        },
+      },
+    },
+    config: cfg,
+    logger: new Logs("debug"),
+    octokit: new Octokit(),
+    env: process.env,
+  } as unknown as ContextPlugin;
+  const activity = new IssueActivity(ctx, issue);
+
+  beforeEach(async () => {
+    drop(db);
+    for (const table of Object.keys(dbSeed)) {
+      const tableName = table as keyof typeof dbSeed;
+      for (const row of dbSeed[tableName]) {
+        db[tableName].create(row);
+      }
+    }
+    await activity.init();
+  });
+
+  it("Should handle comments with original author references", async () => {
+    const commentId = 1571243461;
+    const originalAuthorComment = {
+      id: commentId,
+      node_id: "IC_kwDOKzVPS89fGhiO",
+      url: "https://api.github.com/repos/ubiquity-os/conversation-rewards/issues/comments/1571243461",
+      html_url: "https://github.com/ubiquity-os/conversation-rewards/issues/71#issuecomment-1571243461",
+      body: "_Originally posted by @reviewer in [#69](https://github.com/ubiquity/work.ubq.fi/issues/69)_\n\nThe implementation looks good! One suggestion - consider adding a threshold value to filter out low-relevance comments.",
+      user: {
+        login: "test-user",
+        id: 12345678,
+        node_id: "MDQ6VXNlcjEyMzQ1Njc4",
+        avatar_url: "https://avatars.githubusercontent.com/u/12345678?v=4",
+        gravatar_id: "",
+        url: "https://api.github.com/users/test-user",
+        html_url: "https://github.com/test-user",
+        type: "User",
+        site_admin: false,
+      },
+      created_at: "2023-06-25T14:00:00Z",
+      updated_at: "2023-06-25T14:00:00Z",
+      author_association: "MEMBER",
+      performed_via_github_app: null,
+    };
+
+    const extractedInfo = extractOriginalAuthor(originalAuthorComment.body);
+    expect(extractedInfo).not.toBeNull();
+    expect(extractedInfo?.username).toBe("reviewer");
+    expect(extractedInfo?.url).toBe("https://github.com/ubiquity/work.ubq.fi/issues/69");
+
+    server.use(
+      http.get("https://api.github.com/repos/ubiquity-os/conversation-rewards/issues/71/comments", () => {
+        const modifiedComments = [...activity.comments, originalAuthorComment];
+        return HttpResponse.json(modifiedComments);
+      })
+    );
+
+    await activity.init();
+
+    const processor = new Processor(ctx);
+    processor["_transformers"] = [
+      new UserExtractorModule(ctx),
+      new DataPurgeModule(ctx),
+      new FormattingEvaluatorModule(ctx),
+      new ContentEvaluatorModule(ctx),
+      new PaymentModule(ctx),
+      new GithubCommentModule(ctx),
+    ];
+
+    server.use(http.post("https://*", () => passthrough()));
+    await processor.run(activity);
+
+    const result = JSON.parse(processor.dump()) as Result;
+
+    const users = Object.keys(result);
+    const hasOriginalAuthor = users.some((user) => user === "reviewer");
+    expect(hasOriginalAuthor).toBeTruthy();
+    expect(result["reviewer"].total).toEqual(10.105);
path: tests/specification.test.ts
user: login: gentlementlegen
body: https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345/commits/d0fdb37c3a5662158b225231fa13c2097a64310b#diff-15cb8524ac063f777aec29a753ba34c0206ac45ca6d84df79260a90c15876170R29
created_at: 2025-05-15T08:59:05Z
author_association: MEMBER
diff_hunk: @@ -108,9 +113,67 @@ export class ContentEvaluatorModule extends BaseModule {
     }
 
     await Promise.all(promises);
+    if (data?.self?.body) {
+      await this._handleRewardsForOriginalAuthor(data.self.body, result);
+    }
     return result;
   }
 
+  /*
+   * If the specification was created from the comment of another user, reward that user accordingly.
+   */
+  private async _handleRewardsForOriginalAuthor(body: string, result: Result) {
+    const originalComment = extractOriginalAuthor(body);
+    if (!originalComment) return;
+
+    const specReward = this._extractAndAdjustSpecReward(result);
+    if (!specReward) return;
+
+    await this._addRewardForAuthor(result, originalComment.username, specReward);
+  }
+
+  private _extractAndAdjustSpecReward(result: Result) {
+    for (const resultKey of Object.keys(result)) {
+      const comments = result[resultKey].comments;
+      if (!comments) continue;
+
+      const spec = comments.find((comment) => comment.commentType & CommentAssociation.SPECIFICATION);
+      if (spec && spec.score?.reward !== undefined) {
+        spec.score.reward *= this._originalAuthorWeight;
+        return { ...spec };
+      }
path: src/parser/content-evaluator-module.ts
user: login: gentlementlegen
body: https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345/commits/d0fdb37c3a5662158b225231fa13c2097a64310b
created_at: 2025-05-15T08:59:29Z
author_association: MEMBER
diff_hunk: @@ -377,6 +377,14 @@
                       }
                     }
                   },
+                  "originalAuthorWeight": {
+                    "default": 0.5,
+                    "description": "Percentage of reward given to original comment author when detected via 'Originally posted by' pattern. Value between 0.0 and 1.0.",
+                    "minimum": 0,
+                    "maximum": 1,
+                    "examples": [0.5],
path: manifest.json
user: login: 0x4007
body: Should have several 
created_at: 2025-05-15T11:23:04Z
author_association: MEMBER
diff_hunk: @@ -31,6 +32,7 @@ export class ContentEvaluatorModule extends BaseModule {
   });
   private readonly _fixedRelevances: { [k: string]: number } = {};
   private _tokenLimit: number = 0;
+  private readonly _originalAuthorWeight: number = 0.5;
path: src/parser/content-evaluator-module.ts
user: login: 0x4007
body: All defaults should be zero across all plugins, probably 
created_at: 2025-05-15T11:23:49Z
author_association: MEMBER
diff_hunk: @@ -31,6 +32,7 @@ export class ContentEvaluatorModule extends BaseModule {
   });
   private readonly _fixedRelevances: { [k: string]: number } = {};
   private _tokenLimit: number = 0;
+  private readonly _originalAuthorWeight: number = 0.5;
path: src/parser/content-evaluator-module.ts
user: login: gentlementlegen
body: Probably should be done in https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/295
created_at: 2025-05-15T11:41:04Z
author_association: MEMBER
diff_hunk: @@ -377,6 +377,14 @@
                       }
                     }
                   },
+                  "originalAuthorWeight": {
+                    "default": 0.5,
+                    "description": "Percentage of reward given to original comment author when detected via 'Originally posted by' pattern. Value between 0.0 and 1.0.",
+                    "minimum": 0,
+                    "maximum": 1,
+                    "examples": [0.5],
path: manifest.json
user: login: gentlementlegen
body: https://github.com/ubiquity-os-marketplace/text-conversation-rewards/pull/345/commits/268c7c0a15fc78e9b250b6e94135e612a9c09851
created_at: 2025-05-15T17:32:13Z
author_association: MEMBER

PR comments:
user: login: ubiquity-os-beta[bot]
created_at: 2025-05-13T01:56:23Z
body: @gentlementlegen, you have used <code>**1**</code> of <code>**4**</code> available deadline extensions. Please provide an update on your progress. The new deadline is on <code>**May 17 at 09:10 AM UTC**</code>.
<!-- Ubiquity - Followup - constructBodyWithMetadata - undefined - https://github.com/ubiquity-os-marketplace/daemon-disqualifier/actions/runs/14986457392
{
  "taskAssignees": [
    9807008
  ],
  "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/74",
  "remainingExtensions": 4,
  "extensionsLimit": 4,
  "extensionTimeLapse": 302400000,
  "assignmentDate": "2025-05-10T09:10:25.000Z",
  "caller": "constructBodyWithMetadata"
}
-->
author_association: CONTRIBUTOR
user: login: ubiquity-os-beta[bot]
created_at: 2025-05-18T02:02:41Z
body: @gentlementlegen, you have used <code>**3**</code> of <code>**4**</code> available deadline extensions. Please provide an update on your progress. The new deadline is on <code>**May 24 at 09:10 AM UTC**</code>.
<!-- Ubiquity - Followup - constructBodyWithMetadata - undefined - https://github.com/ubiquity-os-marketplace/daemon-disqualifier/actions/runs/15091244304
{
  "taskAssignees": [
    9807008
  ],
  "url": "https://github.com/ubiquity-os-marketplace/text-conversation-rewards/issues/74",
  "remainingExtensions": 2,
  "extensionsLimit": 4,
  "extensionTimeLapse": 302400000,
  "assignmentDate": "2025-05-10T09:10:25.000Z",
  "caller": "constructBodyWithMetadata"
}
-->
author_association: CONTRIBUTOR
user: login: gentlementlegen
created_at: 2025-05-19T12:53:07Z
body: Interesting that we get these decimals, could you share the settings you used for the weight?

Yes that is a good idea, maybe I can add this inside of `formatting` then.

I changed the calculations to use Decimal to avoid the "9999" at the end please let me know if that fixes it! Also added the weight, only tested locally will do the QA on the repo in a sec.
author_association: MEMBER
user: login: whilefoo
created_at: 2025-05-19T18:29:12Z
body: > Interesting that we get these decimals, could you share the settings you used for the weight?

I used 0.7



> I changed the calculations to use Decimal to avoid the "9999" at the end please let me know if that fixes it!

It looks like it's working: https://github.com/ubiquibot-whilefoo-testing/testing/issues/64#issuecomment-2890868666
author_association: MEMBER
user: login: gentlementlegen
created_at: 2025-05-20T02:35:26Z
body: Let me know if that display is satisfactory, was not too sure about where to put this value
![image](https://github.com/user-attachments/assets/a41b102c-5599-4879-9ab7-87e9ffd035e7)

author_association: MEMBER