@xuda.io/account_module 1.2.2311 → 1.2.2312
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.mjs +141 -4
- package/index_ms.mjs +4 -0
- package/index_msa.mjs +4 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -7839,9 +7839,11 @@ const _list_owner_members = async function (owner_uid, profiles) {
|
|
|
7839
7839
|
const acc = await db_module.get_couch_doc_native('xuda_accounts', u);
|
|
7840
7840
|
const ai = acc?.account_info || {};
|
|
7841
7841
|
const name = ai.account_type === 'business' ? ai.business_name || 'Member' : `${ai.first_name || ''} ${ai.last_name || ''}`.trim() || ai.email || 'Member';
|
|
7842
|
-
|
|
7842
|
+
// owner is reported (UI-163) so the member card can say so without the
|
|
7843
|
+
// dashboard having to infer it from the order of this list.
|
|
7844
|
+
out.push({ uid: u, name, owner: u === owner_uid });
|
|
7843
7845
|
} catch (e) {
|
|
7844
|
-
out.push({ uid: u, name: 'Member' });
|
|
7846
|
+
out.push({ uid: u, name: 'Member', owner: u === owner_uid });
|
|
7845
7847
|
}
|
|
7846
7848
|
}
|
|
7847
7849
|
return out;
|
|
@@ -7882,7 +7884,139 @@ export const get_credit_management = async function (req) {
|
|
|
7882
7884
|
const profiles = await _list_owner_profiles(uid, app_id, account_doc?.account_profile_id);
|
|
7883
7885
|
const members = await _list_owner_members(uid, profiles);
|
|
7884
7886
|
|
|
7885
|
-
|
|
7887
|
+
// main_profile_id is reported (UI-163) so the spend chart can name the
|
|
7888
|
+
// owner's own profile: it is deliberately absent from `profiles` (no member
|
|
7889
|
+
// to scope a rule to), and without the id its spend can only be labelled
|
|
7890
|
+
// "some other profile".
|
|
7891
|
+
return { code: 1, data: { rules, can_edit, plan, entitlements, usage_breakdown, model_catalog, profiles, members, main_profile_id: account_doc?.account_profile_id || '' } };
|
|
7892
|
+
} catch (err) {
|
|
7893
|
+
return { code: -1, data: err.message };
|
|
7894
|
+
}
|
|
7895
|
+
};
|
|
7896
|
+
|
|
7897
|
+
/////////////////////////////////////////////////////////////////////////
|
|
7898
|
+
// USAGE HISTORY (UI-163)
|
|
7899
|
+
//
|
|
7900
|
+
// The drill-down behind the "N credits used" line on a profile or member card:
|
|
7901
|
+
// day by day spend, which models it went on, and the calls themselves. It reads
|
|
7902
|
+
// the same raw ai_usage docs _get_scoped_usage aggregates, and calibrates them
|
|
7903
|
+
// the same way, so a card's total and its history cannot tell two stories.
|
|
7904
|
+
//
|
|
7905
|
+
// Calibration: raw token cost is not the credit unit the ledger reports, so the
|
|
7906
|
+
// scan also covers the CURRENT PERIOD and the two totals give the conversion
|
|
7907
|
+
// factor (authoritative period spend / raw period cost). That is a unit
|
|
7908
|
+
// conversion, so it holds for any window inside the scan.
|
|
7909
|
+
/////////////////////////////////////////////////////////////////////////
|
|
7910
|
+
const _CREDIT_HISTORY_MAX_DOCS = 20000;
|
|
7911
|
+
const _CREDIT_HISTORY_MAX_DAYS = 180;
|
|
7912
|
+
const _CREDIT_HISTORY_SCAN_DAYS = 120;
|
|
7913
|
+
const _DAY_MS = 86400000;
|
|
7914
|
+
const _utc_day = function (ts) {
|
|
7915
|
+
return new Date(ts).toISOString().slice(0, 10);
|
|
7916
|
+
};
|
|
7917
|
+
const _raw_usage_cost = function (doc) {
|
|
7918
|
+
const c = doc?.cost || { input: 1, output: 1 };
|
|
7919
|
+
return ((Number(doc?.input_tokens) || 0) * (Number(c.input) || 0) + (Number(doc?.output_tokens) || 0) * (Number(c.output) || 0)) / 1e6;
|
|
7920
|
+
};
|
|
7921
|
+
|
|
7922
|
+
export const get_credit_usage_history = async function (req) {
|
|
7923
|
+
try {
|
|
7924
|
+
const uid = req?.uid || req?.token_ret?.data?.uid;
|
|
7925
|
+
if (!uid) return { code: -1, data: 'not authorized' };
|
|
7926
|
+
|
|
7927
|
+
const scope = ['profile', 'user'].includes(req?.scope) ? req.scope : 'account';
|
|
7928
|
+
const id = typeof req?.id === 'string' ? req.id : '';
|
|
7929
|
+
if (scope !== 'account' && !id) return { code: -1, data: 'id is required for a profile or member history' };
|
|
7930
|
+
|
|
7931
|
+
let days = Math.round(Number(req?.days) || 30);
|
|
7932
|
+
if (!(days >= 1)) days = 30;
|
|
7933
|
+
days = Math.min(_CREDIT_HISTORY_MAX_DAYS, days);
|
|
7934
|
+
|
|
7935
|
+
const account_doc = await db_module.get_couch_doc_native('xuda_accounts', uid);
|
|
7936
|
+
const period = _current_credit_period(account_doc);
|
|
7937
|
+
|
|
7938
|
+
// UTC day buckets, so the series is the same series wherever it is read.
|
|
7939
|
+
const today_start = Math.floor(Date.now() / _DAY_MS) * _DAY_MS;
|
|
7940
|
+
const window_from = today_start - (days - 1) * _DAY_MS;
|
|
7941
|
+
// Reach back to the period start even when the window is shorter (the factor
|
|
7942
|
+
// needs it), but never further than the scan bound: a yearly period must not
|
|
7943
|
+
// turn one drill-down into a full-history scan.
|
|
7944
|
+
const scan_from = Math.max(Math.min(window_from, period.start), today_start - _CREDIT_HISTORY_SCAN_DAYS * _DAY_MS);
|
|
7945
|
+
|
|
7946
|
+
// ignore_warning=true: same nested selector as _get_scoped_usage, and the
|
|
7947
|
+
// same index note applies (an index on account_profile_info.uid before any
|
|
7948
|
+
// prod rollout).
|
|
7949
|
+
const raw = await db_module.find_couch_query(
|
|
7950
|
+
'xuda_usage',
|
|
7951
|
+
{
|
|
7952
|
+
selector: { docType: 'ai_usage', 'account_profile_info.uid': uid, date_created_ts: { $gte: scan_from } },
|
|
7953
|
+
fields: ['date_created_ts', 'uid', 'model', 'source', 'prompt', 'input_tokens', 'output_tokens', 'cost', 'account_profile_info.account_profile_id'],
|
|
7954
|
+
limit: _CREDIT_HISTORY_MAX_DOCS,
|
|
7955
|
+
},
|
|
7956
|
+
true,
|
|
7957
|
+
);
|
|
7958
|
+
const docs = raw?.docs || [];
|
|
7959
|
+
|
|
7960
|
+
let raw_period_total = 0;
|
|
7961
|
+
for (const d of docs) if ((Number(d.date_created_ts) || 0) >= period.start) raw_period_total += _raw_usage_cost(d);
|
|
7962
|
+
const su = await _get_scoped_usage(uid, false);
|
|
7963
|
+
const factor = raw_period_total > 0 && su.total > 0 ? su.total / raw_period_total : 1;
|
|
7964
|
+
|
|
7965
|
+
const in_scope = function (d) {
|
|
7966
|
+
if (scope === 'profile') return d?.account_profile_info?.account_profile_id === id;
|
|
7967
|
+
if (scope === 'user') return d?.uid === id;
|
|
7968
|
+
return true;
|
|
7969
|
+
};
|
|
7970
|
+
|
|
7971
|
+
const series = {};
|
|
7972
|
+
for (let t = window_from; t <= today_start; t += _DAY_MS) series[_utc_day(t)] = 0;
|
|
7973
|
+
|
|
7974
|
+
const by_model = {};
|
|
7975
|
+
const events = [];
|
|
7976
|
+
let total = 0;
|
|
7977
|
+
for (const d of docs) {
|
|
7978
|
+
const ts = Number(d.date_created_ts) || 0;
|
|
7979
|
+
if (ts < window_from || !in_scope(d)) continue;
|
|
7980
|
+
const credits = _raw_usage_cost(d) * factor;
|
|
7981
|
+
total += credits;
|
|
7982
|
+
const day = _utc_day(ts);
|
|
7983
|
+
if (day in series) series[day] += credits;
|
|
7984
|
+
const code = _conf.ai_model_aliases?.[d.model] || d.model || 'unknown';
|
|
7985
|
+
by_model[code] = (by_model[code] || 0) + credits;
|
|
7986
|
+
events.push({
|
|
7987
|
+
ts,
|
|
7988
|
+
credits,
|
|
7989
|
+
model: code,
|
|
7990
|
+
model_name: _conf.ai_models?.[code]?.name || d.model || code,
|
|
7991
|
+
source: d.source || 'other',
|
|
7992
|
+
uid: d.uid || '',
|
|
7993
|
+
account_profile_id: d?.account_profile_info?.account_profile_id || '',
|
|
7994
|
+
prompt: typeof d.prompt === 'string' ? d.prompt : '',
|
|
7995
|
+
});
|
|
7996
|
+
}
|
|
7997
|
+
events.sort((a, b) => b.ts - a.ts);
|
|
7998
|
+
|
|
7999
|
+
const r2 = (n) => Math.round(n * 100) / 100;
|
|
8000
|
+
return {
|
|
8001
|
+
code: 1,
|
|
8002
|
+
data: {
|
|
8003
|
+
scope,
|
|
8004
|
+
id,
|
|
8005
|
+
days,
|
|
8006
|
+
total: r2(total),
|
|
8007
|
+
calls: events.length,
|
|
8008
|
+
// The scan is capped, so say so rather than quietly reporting a partial
|
|
8009
|
+
// history as the whole of it.
|
|
8010
|
+
truncated: docs.length >= _CREDIT_HISTORY_MAX_DOCS,
|
|
8011
|
+
series: Object.keys(series)
|
|
8012
|
+
.sort()
|
|
8013
|
+
.map((day) => ({ day, credits: r2(series[day]) })),
|
|
8014
|
+
by_model: Object.entries(by_model)
|
|
8015
|
+
.map(([code, credits]) => ({ code, name: _conf.ai_models?.[code]?.name || code, credits: r2(credits) }))
|
|
8016
|
+
.sort((a, b) => b.credits - a.credits),
|
|
8017
|
+
events: events.slice(0, 60).map((e) => ({ ...e, credits: Math.round(e.credits * 10000) / 10000 })),
|
|
8018
|
+
},
|
|
8019
|
+
};
|
|
7886
8020
|
} catch (err) {
|
|
7887
8021
|
return { code: -1, data: err.message };
|
|
7888
8022
|
}
|
|
@@ -9156,7 +9290,10 @@ const _newsletter_blast = async (issue, by_uid) => {
|
|
|
9156
9290
|
} catch (e) {
|
|
9157
9291
|
console.error('[newsletter] finalize failed:', e.message);
|
|
9158
9292
|
}
|
|
9159
|
-
|
|
9293
|
+
// No ops notification on completion: the "Publishing newsletter #N" alert
|
|
9294
|
+
// already announces the blast, and the result lands on the issue doc
|
|
9295
|
+
// (sent.count / sent.failed) plus the log line below.
|
|
9296
|
+
console.log(`[newsletter] #${issue.issue_number} published: sent=${sent} failed=${failed} recipients=${total}`);
|
|
9160
9297
|
};
|
|
9161
9298
|
|
|
9162
9299
|
export const newsletter_publish = async function (req = {}) {
|
package/index_ms.mjs
CHANGED
|
@@ -541,6 +541,10 @@ export const get_credit_management = async function (...args) {
|
|
|
541
541
|
return await broker.send_to_queue("get_credit_management", ...args);
|
|
542
542
|
};
|
|
543
543
|
|
|
544
|
+
export const get_credit_usage_history = async function (...args) {
|
|
545
|
+
return await broker.send_to_queue("get_credit_usage_history", ...args);
|
|
546
|
+
};
|
|
547
|
+
|
|
544
548
|
export const set_credit_rules = async function (...args) {
|
|
545
549
|
return await broker.send_to_queue("set_credit_rules", ...args);
|
|
546
550
|
};
|
package/index_msa.mjs
CHANGED
|
@@ -541,6 +541,10 @@ export const get_credit_management = function (...args) {
|
|
|
541
541
|
broker.send_to_queue_async("get_credit_management", ...args);
|
|
542
542
|
};
|
|
543
543
|
|
|
544
|
+
export const get_credit_usage_history = function (...args) {
|
|
545
|
+
broker.send_to_queue_async("get_credit_usage_history", ...args);
|
|
546
|
+
};
|
|
547
|
+
|
|
544
548
|
export const set_credit_rules = function (...args) {
|
|
545
549
|
broker.send_to_queue_async("set_credit_rules", ...args);
|
|
546
550
|
};
|