feat: add canonical URL handling and entity quality checks

- Implemented canonical URL strategies for city-specific domains and entities.
- Added helper functions for domain and city resolution.
- Created tests for canonical URL generation and domain resolution.
- Introduced entity quality checks for doctors and clinics to ensure meaningful content.
- Developed unique introductory texts for listing pages to avoid duplicate content.
- Established robots.txt policies for listing pages to manage indexing based on user filters.
- Enhanced specialty content with dynamic introductions and FAQs to improve SEO.
This commit is contained in:
hamed
2026-07-19 07:52:50 +03:30
parent 36816eded2
commit fd48b48613
48 changed files with 3951 additions and 733 deletions
+118
View File
@@ -0,0 +1,118 @@
// متن مقدمه و سوالات متداول صفحات فرود تخصص.
//
// دو الزام SEO اینجا با هم حل می‌شوند:
// ۱. رفع Thin Content — canonical درست، محتوای تکراری را غیرتکراری نمی‌کند.
// ۲. تنوع ساختاری — متن هر شهر/تخصص باید ساختار جمله‌بندی متفاوتی داشته باشد،
// نه یک قالب واحد با جای‌گذاری نام. برای همین از سه بلوکِ مستقل با گونه‌های
// متفاوت استفاده می‌شود که با هشِ (تخصص، شهر) انتخاب می‌شوند — خروجی پایدار
// (بین دو رندر تغییر نمی‌کند) ولی بین صفحات مختلف واگراست.
function hash(...parts) {
const key = parts.join("|");
let value = 0;
for (let i = 0; i < key.length; i++) {
value = (value * 31 + key.charCodeAt(i)) >>> 0;
}
return value;
}
// seed همیشه unsigned گرفته می‌شود؛ با >> علامت‌دار، هش‌های بزرگ‌تر از 2^31 منفی
// می‌شدند و ایندکس منفی به undefined می‌رسید.
const pick = (variants, seed) => variants[(seed >>> 0) % variants.length];
function openingVariants(specialty, city) {
return [
`اگر به دنبال متخصص ${specialty} در ${city} هستید، این صفحه فهرست کاملی از پزشکان فعال این حوزه را در اختیار شما می‌گذارد. هر پروفایل شامل تخصص‌ها، سوابق، محل مطب و امتیاز واقعی بیماران است تا بتوانید پیش از رزرو نوبت، انتخاب آگاهانه‌تری داشته باشید.`,
`انتخاب پزشک ${specialty} در ${city} وقتی ساده‌تر می‌شود که همهٔ گزینه‌ها را یک‌جا و با اطلاعات یکسان ببینید. در این صفحه، متخصصان ${specialty} شهر ${city} همراه با نشانی مطب، ساعات پذیرش و بازخورد مراجعان گردآوری شده‌اند.`,
`دسترسی به متخصص ${specialty} در ${city} دیگر نیازی به تماس‌های پی‌درپی و مراجعهٔ حضوری برای گرفتن نوبت ندارد. فهرست زیر پزشکان این تخصص را در ${city} نشان می‌دهد؛ از هر پروفایل می‌توانید مستقیم و آنلاین نوبت بگیرید.`,
`این صفحه به معرفی پزشکان ${specialty} در ${city} اختصاص دارد. هدف، کوتاه‌کردن مسیر میان «جست‌وجوی یک متخصص خوب» و «داشتن نوبت قطعی» است؛ بنابراین اطلاعات هر پزشک به‌شکلی چیده شده که مقایسه میان گزینه‌ها سریع انجام شود.`,
];
}
function bodyVariants(specialty, city, doctorCount) {
const countPhrase =
doctorCount > 0
? `در حال حاضر ${doctorCount} پزشک ${specialty} در ${city} در این سامانه ثبت شده‌اند`
: `فهرست پزشکان ${specialty} در ${city} به‌طور مداوم به‌روزرسانی می‌شود`;
return [
`${countPhrase}. برای هر پزشک، مواردی مانند مدرک و درجهٔ تخصصی، کد نظام پزشکی، خدمات قابل ارائه و موقعیت مکانی مطب نمایش داده می‌شود. امتیازها و نظرها از مراجعان واقعی همان پزشک جمع‌آوری شده‌اند، نه از منابع عمومی؛ به همین دلیل تصویر دقیق‌تری از کیفیت خدمات به دست می‌دهند.`,
`${countPhrase} و اطلاعات آن‌ها به‌صورت دوره‌ای بازبینی می‌شود. تفاوت این فهرست با جست‌وجوی ساده در آن است که هر رکورد به یک پروفایل کامل وصل است: خدمات، محل‌های پذیرش، ساعات کاری هر محل و امکان رزرو مستقیم. نتیجه آنکه مقایسهٔ چند پزشک در چند دقیقه ممکن می‌شود.`,
`${countPhrase}. برخی از این پزشکان بیش از یک محل پذیرش دارند — مطب شخصی و یک یا چند کلینیک — و ساعات کاری هر محل جداگانه اعلام شده است. هنگام رزرو، محل مراجعه را خودتان انتخاب می‌کنید تا نوبتی که می‌گیرید با برنامهٔ رفت‌وآمد شما هماهنگ باشد.`,
];
}
function closingVariants(specialty, city) {
return [
`پس از انتخاب پزشک، تقویم نوبت‌های خالی نمایش داده می‌شود و رزرو در همان لحظه قطعی می‌گردد؛ پیامک تأیید نیز برایتان ارسال خواهد شد. اگر برنامه‌تان تغییر کرد، لغو نوبت از بخش نوبت‌های من در حساب کاربری ممکن است و آن بازهٔ زمانی بلافاصله برای بیمار دیگری آزاد می‌شود. اگر تخصص موردنظرتان ${specialty} نیست، از فهرست تخصص‌های ${city} می‌توانید حوزهٔ درمانی دیگری را دنبال کنید و همان مسیر رزرو را طی کنید.`,
`نوبت‌گیری در همین صفحه انجام می‌شود: تاریخ و ساعت خالی را می‌بینید، انتخاب می‌کنید و تأیید نهایی را دریافت می‌کنید. هزینهٔ هر خدمت پیش از تأیید نمایش داده می‌شود تا انتخابتان با اطلاع کامل انجام شود. برای حوزه‌های درمانی دیگر در ${city}، صفحهٔ تخصص‌ها فهرست کامل رشته‌ها را در اختیارتان می‌گذارد و از هر رشته می‌توانید مستقیم به پزشکان همان حوزه برسید.`,
`تمام مراحل — از مقایسهٔ پزشکان ${specialty} تا دریافت تأییدیهٔ نوبت — آنلاین و بدون مراجعهٔ حضوری طی می‌شود؛ نیازی به تماس تلفنی در ساعات اداری نیست و رزرو شبانه‌روزی انجام می‌گیرد. کلینیک‌ها و مراکز درمانی ${city} نیز صفحهٔ اختصاصی خود را دارند و اگر ترجیح می‌دهید ابتدا مرکز درمانی را انتخاب کنید، از همان مسیر قابل رزرو هستند.`,
`برای رزرو کافی است روی پروفایل پزشک بزنید و از میان نوبت‌های خالی یکی را انتخاب کنید؛ تأییدیه بلافاصله صادر و پیامک می‌شود. سابقهٔ نوبت‌های شما در حساب کاربری باقی می‌ماند و مراجعهٔ بعدی به همان پزشک سریع‌تر انجام می‌شود. اگر ترجیح می‌دهید بر اساس مرکز درمانی جست‌وجو کنید، فهرست کلینیک‌های ${city} گزینهٔ مناسبی برای شروع است.`,
];
}
/** متن مقدمهٔ یکتا (۱۵۰–۳۰۰ کلمه) برای یک تخصص در یک شهر. */
export function buildSpecialtyIntro(specialtyName, cityName, doctorCount = 0) {
const seed = hash(specialtyName, cityName);
return [
pick(openingVariants(specialtyName, cityName), seed),
pick(bodyVariants(specialtyName, cityName, doctorCount), seed >>> 3),
pick(closingVariants(specialtyName, cityName), seed >>> 7),
].join(" ");
}
/** سوالات متداول صفحهٔ پزشک — منبع مشترک بلوک بصری و FAQPage schema. */
export function buildDoctorFaq(doctorName, specialtyName, cityName) {
const who = `دکتر ${doctorName}`;
return [
{
question: `چگونه از ${who} نوبت بگیرم؟`,
answer: `از همین صفحه، محل مراجعه و سپس تاریخ و ساعت خالی موردنظرتان را انتخاب کنید. رزرو در همان لحظه قطعی می‌شود و تأییدیه برایتان پیامک خواهد شد.`,
},
...(specialtyName
? [
{
question: `تخصص ${who} چیست؟`,
answer: `${who} در حوزهٔ ${specialtyName} فعالیت می‌کند. فهرست کامل خدمات قابل ارائه در همین صفحه آمده است.`,
},
]
: []),
...(cityName
? [
{
question: `مطب ${who} در کدام شهر است؟`,
answer: `محل یا محل‌های پذیرش ${who} در ${cityName} به همراه نشانی دقیق و ساعات کاری هر محل، در بخش موقعیت مکانی همین صفحه نمایش داده شده است.`,
},
]
: []),
{
question: `امکان لغو نوبت وجود دارد؟`,
answer: `بله. از بخش نوبت‌های من در حساب کاربری‌تان می‌توانید نوبت را لغو کنید؛ لغو زودهنگام آن بازهٔ زمانی را برای بیمار دیگری آزاد می‌کند.`,
},
];
}
/** سوالات متداول صفحهٔ تخصص — منبع مشترک بلوک بصری و FAQPage schema. */
export function buildSpecialtyFaq(specialtyName, cityName, doctorCount = 0) {
return [
{
question: `چطور از متخصص ${specialtyName} در ${cityName} نوبت بگیرم؟`,
answer: `از فهرست همین صفحه پزشک موردنظرتان را انتخاب کنید، وارد پروفایل او شوید و از تقویم نوبت‌های خالی، تاریخ و ساعت دلخواه را ثبت کنید. رزرو در همان لحظه قطعی می‌شود و تأییدیه برایتان پیامک می‌شود.`,
},
{
question: `چند پزشک ${specialtyName} در ${cityName} در دسترس است؟`,
answer:
doctorCount > 0
? `در حال حاضر ${doctorCount} پزشک ${specialtyName} در ${cityName} در این سامانه ثبت شده‌اند. این عدد با افزوده‌شدن پزشکان جدید به‌روزرسانی می‌شود.`
: `فهرست پزشکان ${specialtyName} در ${cityName} در حال تکمیل است و به‌مرور پزشکان جدید به آن افزوده می‌شوند.`,
},
{
question: `هزینهٔ ویزیت متخصص ${specialtyName} چقدر است؟`,
answer: `هزینهٔ ویزیت به نوع خدمت، درجهٔ تخصصی پزشک و پوشش بیمه بستگی دارد. مبلغ هر خدمت پیش از تأیید نهایی نوبت، در همان صفحهٔ رزرو نمایش داده می‌شود.`,
},
{
question: `اگر نتوانم در زمان نوبت مراجعه کنم چه کنم؟`,
answer: `از بخش نوبت‌های من در حساب کاربری‌تان می‌توانید نوبت را لغو کنید. لغو زودهنگام باعث می‌شود آن بازهٔ زمانی برای بیمار دیگری آزاد شود.`,
},
];
}