یک پارسر برای قبض برق، آب و گاز؟ فیلدهایی که یکی نیستند
در نمونههای پاسخ، قبض گاز نام تاریخها و فیلد نوع را متفاوت از برق و آب مینویسد؛ با یک لایهٔ ترجمه میتوانید هر سه را در یک مدل داخلی ببرید.
اگر قرار است قبض برق، آب و گاز را با یک پارسر بخوانید، مطمئن باشید که فیلدهایشان همه یکی نیستند: در نمونههای پاسخ، گاز نام دو تاریخ و فیلد نوع را متفاوت مینویسد، و یک پارسر سادهلوحانه برای دو سرویس درست و برای سومی بیصدا خالی کار میکند.
این نوشته برای توسعهدهندهای است که وبسرویس استعلام قبض گاز را کنار برق و آب در یک محصول جمع میکند و میخواهد یک مدل دادهی واحد داشته باشد. میگوید کجا تفاوت هست، چطور مدل یکدست بسازید، و چرا باید اولین پاسخهای واقعی را خودتان با این جدول مقایسه کنید.
یک هشدار دربارهٔ منبع این جدول
جدول زیر از نمونههای پاسخ در مشخصات سرویسها درآمده است. نمونه، پاسخ زندهٔ یک قبض واقعی نیست. پس آن را شروع کار بدانید، نه ضمانت. اولین باری که هر سه سرویس را با شناسههای واقعی صدا میزنید، پاسخ خام را کنار این جدول بگذارید و اختلاف را همانجا ببینید. این گام پنج دقیقه است و از یک باگ هفتهها پنهان جلوگیری میکند.
تفاوتها
| مفهوم | برق و آب | گاز |
|---|---|---|
| تاریخ دورهٔ قبل | previous_date | previous_reading_date |
| تاریخ دورهٔ جاری | current_date | current_reading_date |
| نوع | account_type | consumption_type |
| مبلغ | amount | amount |
| شناسهها | bill_id، payment_id | bill_id، payment_id |
| نام و نشانی | full_name، address | full_name، address |
| مهلت پرداخت | payment_date | payment_date |
شش فیلد مشترکاند و سه فیلد فرق دارند. اگر پارسر شما current_date را بخواند و پاسخ گاز را بدهید، مقدار undefined یا null میگیرید. هیچ خطایی هم رخ نمیدهد و فقط ستون تاریخ گزارش گاز خالی میماند.
نکتهٔ دوم: در پاسخ قبضهای دیگر هم شکل فرق میکند. قبض تلفن ثابت و موبایل final_term و mid_term دارند و هر کدام فقط مبلغ و دو شناسه، بدون نام، نشانی و تاریخ. پس «یک مدل برای همهٔ قبوض» با خطر واقعی همراه است و اگر قصدش را دارید، باید اختیاریبودن فیلدها را در مدل بگذارید.
مدل پیشنهادی
به جای اینکه فیلدهای ارائهدهنده را مستقیم به رابط کاربری بدهید، یک لایهٔ ترجمه بسازید:
```ts type BillView = { kind: 'electricity' | 'water' | 'gas'; amount: number; billId: string; paymentId: string; ownerName: string | null; address: string | null; periodStart: string | null; periodEnd: string | null; deadline: string | null; categoryRaw: string | null; };
const pick = (d: Record<string, unknown>, ...keys: string[]): string | null => { for (const k of keys) { const v = d[k]; if (typeof v === 'string') return v; } return null; }; ```
سپس برای هر نوع، periodEnd را از current_date یا current_reading_date بردارید:
ts periodEnd: pick(d, 'current_date', 'current_reading_date'), periodStart: pick(d, 'previous_date', 'previous_reading_date'), categoryRaw: pick(d, 'account_type', 'consumption_type'),
دو تصمیم در این کد عمدی است. اول، فیلدهای غایب null میشوند و کد نمیشکند. دوم، categoryRaw همان متن خام است؛ دستهبندی خودتان را در یک لایهٔ جدا و روی همین مقدار بسازید.
چرا نوع را خام نگه دارید
دو فیلد «نوع» در دو سرویس معنای یکسان ندارند: یکی نوع اشتراک است و دیگری نوع مصرف. هر دو را در یک ستون ریختن مشکلی ندارد، به شرط اینکه مقدار خام کنارش باشد و دستهبندی را از رویش بسازید. مقدار ناشناخته را به «سایر» ببرید ولی خامش را دور نریزید. گزارش دستهبندیای که مقادیر جدید را بیصدا حذف میکند، از آن گزارشهایی است که ماهها بعد اشتباهش معلوم میشود.
در نمونهٔ پاسخ گاز، متن نوع با یای عربی و یک فاصلهٔ اضافی در ابتدا آمده است. پیش از هر مقایسهٔ رشتهای، ی و ک عربی را به فارسی تبدیل کنید و فاصلهٔ ابتدا و انتها را بُرید.
مبلغ و واحد
واحد amount را از متن پاسخ نمیشود فهمید. پیش از اینکه آن را جایی نمایش دهید یا جمع بزنید، یک قبض واقعی را با عدد برگشتی بسنجید و واحد را در لایهٔ خودتان ثابت کنید. به نظر من این بهتر از حدسزدن است، چون خطای واحد ده برابر در مبلغها به چشم نمیآید تا کسی شکایت کند.
خطاها یکیاند
خوشبختانه آنچه به رفتار API برمیگردد برای هر سه سرویس مثل هم است: ورودی billId است، ورودی نامعتبر invalid_input میگیرد و هزینه ندارد، و پاسخ تسویهشده مبلغ صفر دارد. پس منطق خطا و تلاش دوباره را یک بار بنویسید و روی هر سه بهکار ببرید. جدول کامل کدها در راهنمای فنی API است.
آنچه این رویکرد نمیکند
- ضمانت نمیکند ارائهدهندهها فردا همین نامها را نگه دارند. مدل ترجمه برای همین است: وقتی چیزی عوض شود، فقط یک تابع را اصلاح میکنید.
- مصرف به مترمکعب یا کیلوواتساعت نمیسازد، چون در پاسخ نیست.
- تفاوتهای تعرفه را یکدست نمیکند.
اگر سؤال شما بیشتر دربارهٔ ورودی و خروجی برق است، استعلام قبض برق صفحهٔ مناسبتری است.
تستهایی که ارزش دارند
پیش از اینکه لایهٔ ترجمه را باور کنید، سه تست کوچک بنویسید. یک پاسخ نمونهٔ برق، یک آب و یک گاز را بهعنوان دادهی ثابت در مخزن بگذارید و بررسی کنید که periodStart و periodEnd در هر سه پر میشوند. سپس پاسخی بسازید که یکی از فیلدهای تاریخ را ندارد و ببینید مدل شما null میدهد و نمیشکند. آخرین تست را با فیلد تازهای انجام دهید که در هیچ نمونهای نیست: پارسر باید آن را نادیده بگیرد.
اگر روزی سرویس فیلد جدیدی اضافه کرد، این تستها خراب نمیشوند و همین مطلوب است. نگهداشتن پاسخ خام کنار مدل ترجمهشده هم کمک میکند: وقتی کسی بپرسد «پاسخ اصلی چه بود؟»، لازم نیست دوباره استعلام بزنید و دوباره هزینه بدهید. برای دیدن فیلدهای قبض گاز صفحهٔ سرویس هم مفید است.
نگاشت نوع به دسته
یک مثال از دستهبندی بالای مقدار خام: جدولی بسازید که به ازای هر متن خام یک دسته بدهد («خانگی»، «تجاری»، «صنعتی»، «سایر») و اگر متنی ناشناخته بود، «سایر» برگرداند و آن متن را در جدول دیگری برای بازبینی بریزد. هفتهٔ بعد که جدول بازبینی را نگاه میکنید، مقدارهای تازه را میبینید و دستهها را کامل میکنید. این روش از گزارشهای ناقص بیصدا جلوگیری میکند.
پیشنهاد من
جدول بالا را کنار پاسخ واقعی بگذارید، فیلدها را به یک مدل داخلی ترجمه کنید، نوع را خام نگه دارید و غایببودن فیلد را خطا حساب نکنید. این چهار کار پارسر شما را در برابر تفاوتی که امروز دیدید و تفاوتی که فردا خواهد آمد مقاوم میکند.
وبسرویس مرتبط با این راهنما
API استعلام قبض گاز
مبلغ و مهلت پرداخت قبض گاز.
راهنماهای مرتبط
پرسش و تجربه کاربران
اگر این راهنما جوابتان را نداد، همینجا بپرسید. پرسشها را تیمملی ایپیآی پاسخ میدهد و هر نظر پیش از انتشار بررسی میشود.
هنوز پرسشی ثبت نشده، اولین نفر باشید.
