ResearchGo
الإطلاق قريبًامنصة لصالح CIBES Research Centre تنقل الورقة الأكاديمية من التقديم حتى النشر. اكتمل بناؤها، وهي في مرحلة الفحوصات الأخيرة قبل الإطلاق.
- دوري
- تصميم النظام وتطويره
- السنة
- 2026
- التقنيات المستخدمة
- Next.js · Supabase (EU) · Python · FastAPI · GROBID · OpenAlex · Stripe
مرّر أفقيًا لرؤية المخطط كاملًا.
المشكلة
يجري التقديم الأكاديمي عبر سلاسل رسائل البريد الإلكتروني وجداول البيانات. وتتباعد النسخ وتعليقات المراجعين ومتطلبات المجلات بمجرد أن يشارك أكثر من شخص، ويجيب المحررون يدويًا عن الأسئلة نفسها في كل مرة: أي نسخة هذه، وما الذي تغيّر، وهل المراجع سليمة، وأي مجلة تناسبها؟
كيف بُني
تطبيق Next.js على Supabase، وكل البيانات محفوظة داخل الاتحاد الأوروبي. يسجّل المؤلفون الدخول عبر ORCID أو البريد الإلكتروني، ويرفعون المخطوطة بصيغة PDF أو Word. يحلّلها worker مكتوب بـ Python (FastAPI) فور وصولها، وملفات PDF عبر GROBID، ثم يعيد النص المهيكل عبر callback موقّع ومختوم بطابع زمني يرفض المهام المزوّرة أو المعاد إرسالها. وكل تحليل ناجح يصبح نسخة جديدة تُخزَّن مشفّرة، لكن فقط إذا تغيّر النص فعلًا. ويقارن المحررون أي نسختين بتتبع تغييرات على طريقة Word، ويطابقون الاستشهادات داخل النص مع قائمة المراجع، ويحصلون على اقتراحات مجلات مرتبة حسب المعنى، باستخدام embeddings تُحسب على الـ worker الخاص بنا وبيانات المجلات من OpenAlex. وتتم المدفوعات عبر Stripe.
القرارات المهمة
لا نموذج لغوي حين تكفي قاعدة
تتبع التغييرات وفحص المراجع كود حتمي (deterministic)، دون أي ذكاء اصطناعي. فالمحرر يجب أن يثق بالـ diff، والقاعدة تعطي الإجابة نفسها في كل مرة.
المخطوطات لا تغادر خوادمنا أبدًا
مطابقة المجلات تحسب الـ embedding للورقة على الـ worker الخاص بنا، فلا يذهب أي نص من المخطوطة إلى طرف ثالث. والترتيب قائم على التشابه وحده. أما تفاصيل الفهرسة والوصول المفتوح والرسوم فتُعرض كوسوم ولا تغيّر الترتيب أبدًا.
كل مهمة موقّعة
تحمل المهام المتبادلة بين تطبيق الويب والـ worker توقيع HMAC وطابعًا زمنيًا. تُرفض المهمة المزوّرة أو المعاد إرسالها، وإذا توقف الـ worker في منتصف مهمة فلا تفقد المخطوطة مكانها.
كود من المستودع
function diffParagraphPair(before: string, after: string): DiffSegment[] {
return diffWordsWithSpace(before, after).map((part) => ({1
op: part.added ? "ins" : part.removed ? "del" : "equal",
text: part.value,
}));
}
export function diffManuscripts(before: string, after: string): DiffResult {
const a = splitParagraphs(before);
const b = splitParagraphs(after);
// …
const parts = diffArrays(a, b);2
let i = 0;
while (i < parts.length) {
const part = parts[i];
if (!part) break;
if (!part.added && !part.removed) {
// …
i++;
continue;
}
const next = parts[i + 1];
const paired = Boolean(part.removed && next?.added);3
const removedList = part.removed ? part.value : [];
const addedList = part.added ? part.value : paired && next ? next.value : [];
const pairs = Math.min(removedList.length, addedList.length);
for (let k = 0; k < pairs; k++) {
const segments = diffParagraphPair(removedList[k] as string, addedList[k] as string);
paragraphs.push({ kind: "changed", segments });
// …
}
// …
i += paired ? 2 : 1;
}
return { paragraphs, summary };
}
السطر 2. داخل الفقرة المعدّلة، يعمل الـ diff كلمةً كلمة ويحافظ على المسافات، فيُقرأ مثل تتبع التغييرات في Word.
السطر 12. المرور الأول: diff بخوارزمية أطول تسلسل جزئي مشترك (longest common subsequence) على مستوى الفقرات الكاملة يحدد ما بقي، وما أُضيف، وما حُذف.
السطر 23. الحذف الذي تليه إضافة مباشرة يُعدّ فقرة واحدة معدّلة، لا تغييرين منفصلين.
أين وصل الآن
اكتمل بناؤه وهو في الفحوصات الأخيرة. سيُطلق فور الانتهاء منها.