improve bazdid 6
This commit is contained in:
1 parent
99c25a8216
commit
fa73a734a0
21 files changed
+32760
-7985
No files matched your search
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env python
|
||||
"""شبیهسازیِ «تعویقِ کوتاهتر از تاریخ انجامِ هماهنگی» روی دیتابیس واقعی (کپی).
|
||||
|
||||
سناریوی گزارششده: در فرم «ایجاد وظیفه» نوع = هماهنگی،
|
||||
«تاریخ و ساعتی که کار باید انجام شود» = ۱۴۰۵/۰۸/۰۵ و
|
||||
«تا تاریخ پیگیری نشود» = ۱۴۰۵/۰۸/۰۳ (یعنی قرار **قبلتر** از تاریخ کار).
|
||||
|
||||
این اسکریپت روی یک **کپی موقت** از دیتابیس، یک مشتری آزمایشی میسازد و رفتار
|
||||
سیستم را روزبهروز چاپ میکند: منابع قرار، فهرست «نیازمند پیگیری»، «عقبافتاده»،
|
||||
و اینکه اسکنر پیامک در هر روز چه میکند. دیتابیس اصلی دستنخورده میماند.
|
||||
|
||||
اجرا::
|
||||
|
||||
.venv/Scripts/python.exe scripts/coordination_hold_order_probe.py
|
||||
.venv/Scripts/python.exe scripts/coordination_hold_order_probe.py --customer 5124
|
||||
"""
|
||||
import argparse
|
||||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if BASE_DIR not in sys.path:
|
||||
sys.path.insert(0, BASE_DIR)
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'khatibi.settings')
|
||||
|
||||
|
||||
def _copy_db(source_path, target_dir):
|
||||
target = os.path.join(target_dir, 'probe.sqlite3')
|
||||
shutil.copy2(source_path, target)
|
||||
for suffix in ('-wal', '-shm'):
|
||||
side = source_path + suffix
|
||||
if os.path.exists(side):
|
||||
shutil.copy2(side, target + suffix)
|
||||
return target
|
||||
|
||||
|
||||
def _day(offset):
|
||||
return datetime.datetime.combine(
|
||||
datetime.date.today() + datetime.timedelta(days=offset), datetime.time(12, 0),
|
||||
)
|
||||
|
||||
|
||||
|
||||
def run_variant(label, *, done, hold_offset, action_offset):
|
||||
"""یک سناریو را میسازد و رفتار روزبهروز را چاپ میکند."""
|
||||
from django.utils import timezone
|
||||
from admin_panel.models import (Action, Customer, PhoneNumber, SMSProviderConfig,
|
||||
SMSQueue)
|
||||
from admin_panel.sms.scanner import scan_visit
|
||||
from admin_panel.visit_policy import (appointment_sources_map, end_of_local_day,
|
||||
followups_on_date, last_visit_map,
|
||||
manual_holds_map, next_appointment_map,
|
||||
overdue_followups, set_visit_hold)
|
||||
|
||||
tz = timezone.get_current_timezone()
|
||||
now = timezone.now()
|
||||
today = now.astimezone(tz).date()
|
||||
|
||||
customer = Customer.objects.create(
|
||||
name_family='مشتری آزمایشی — %s' % label, address='آزمایش',
|
||||
)
|
||||
Customer.objects.filter(pk=customer.pk).update(
|
||||
installDate=today - datetime.timedelta(days=400))
|
||||
PhoneNumber.objects.create(customer=customer, phone_number='09120000777')
|
||||
customer.refresh_from_db()
|
||||
|
||||
action_dt = timezone.make_aware(_day(action_offset))
|
||||
hold_raw = timezone.make_aware(_day(hold_offset)) if hold_offset is not None else None
|
||||
action = Action.objects.create(
|
||||
customer=customer, actionType=Action.ActionType.HAMAHANGI,
|
||||
status=Action.Status.FINISH if done else Action.Status.WAITE,
|
||||
actionDateTime=action_dt,
|
||||
finishDateTime=now if done else None,
|
||||
desc='probe', result='probe' if done else None,
|
||||
)
|
||||
# همان مسیری که ویو طی میکند (شامل پیشفرض ۱۴ روز و «فقط رو به جلو»).
|
||||
from admin_panel.views.actions import _apply_visit_hold
|
||||
_apply_visit_hold(
|
||||
customer,
|
||||
{'actionType': 'hamahangi', 'holdRaw': hold_raw, 'status': action.status},
|
||||
status=action.status, is_new=True,
|
||||
)
|
||||
customer.refresh_from_db()
|
||||
|
||||
config = SMSProviderConfig.load()
|
||||
config.scan_since = now - datetime.timedelta(days=600)
|
||||
config.allow_backlog = True
|
||||
config.backlog_max = 50
|
||||
config.max_per_run = 200
|
||||
config.save()
|
||||
|
||||
print('\n' + '=' * 100)
|
||||
print('سناریو %s' % label)
|
||||
print(' تیک «انجام شد»: %s → status=%s' % ('خورده' if done else 'نخورده', action.status))
|
||||
print(' تاریخ کار (actionDateTime): %s' % action.actionDateTime.astimezone(tz))
|
||||
print(' تا تاریخ پیگیری نشود: %s' % (
|
||||
customer.visit_hold_until.astimezone(tz) if customer.visit_hold_until
|
||||
else 'خالی (هیچ تعویقی ثبت نشد)'))
|
||||
# اثرهای جانبی «تیک انجام شد»: لیست وظایف، شمارندهٔ داشبورد، نظرسنجی.
|
||||
print(' در «لیست وظایف باز» (ActionList/داشبورد): %s' % (
|
||||
'بله' if action.status == Action.Status.WAITE else 'خیر'))
|
||||
survey = (action.status == Action.Status.FINISH
|
||||
and action.actionType != 'vosol'
|
||||
and action.nazarDT is None
|
||||
and action.createdDT >= now - datetime.timedelta(days=60)
|
||||
and (action.finishDateTime is None
|
||||
or action.finishDateTime >= now - datetime.timedelta(days=30)))
|
||||
print(' در صف «نظرسنجی»: %s' % ('بله' if survey else 'خیر'))
|
||||
# اسکن کامل دیتابیس گران است؛ تصمیمِ اسکنر برای همین مشتری بازسازی میشود:
|
||||
# scanner.py:232 if customer.id in appointments: continue
|
||||
# scanner.py:240 trigger = visit_latest_due(anchor, now=now)
|
||||
from admin_panel.visit_policy import cycle_anchor_map, visit_latest_due
|
||||
|
||||
def scanner_decision(moment):
|
||||
appointments = next_appointment_map(now=moment)
|
||||
if customer.pk in appointments:
|
||||
return 'رد (قرار بعدی دارد)'
|
||||
anchors = cycle_anchor_map(now=moment)
|
||||
anchor = anchors.get(customer.pk)
|
||||
if anchor is None:
|
||||
return 'رد (لنگر ندارد)'
|
||||
trigger = visit_latest_due(anchor, now=moment)
|
||||
if trigger is None or trigger.date() > moment.astimezone(tz).date():
|
||||
return 'رد (موعد نرسیده)'
|
||||
return 'ارسال یادآوری دورهای'
|
||||
|
||||
for day_offset in range(0, action_offset + 3):
|
||||
moment = timezone.make_aware(_day(day_offset))
|
||||
sources = appointment_sources_map(now=moment)
|
||||
nexts = next_appointment_map(now=moment)
|
||||
holds = manual_holds_map(now=moment)
|
||||
entries = dict(sources.get(customer.pk) or {})
|
||||
day = moment.astimezone(tz).date()
|
||||
follow = followups_on_date(day, now=moment)
|
||||
overdue = overdue_followups(now=moment, target_date=day)
|
||||
visited = last_visit_map(now=moment, customer_ids=[customer.pk])
|
||||
badge_days = {value.astimezone(tz).date() for value in entries.values()}
|
||||
scan_cell = scanner_decision(moment)
|
||||
print(' %s | منابع=%-34s برچسب=%d قرار | قرار بعدی=%-11s | پیگیری=%s'
|
||||
' | عقبافتاده=%s | قرار دستی=%s | %s' % (
|
||||
day, '+'.join(sorted(entries)) or '—', len(badge_days),
|
||||
(nexts.get(customer.pk).astimezone(tz).date()
|
||||
if nexts.get(customer.pk) else '—'),
|
||||
'بله' if customer.pk in follow else 'خیر',
|
||||
'بله' if customer.pk in overdue else 'خیر',
|
||||
'بله' if customer.pk in holds else 'خیر',
|
||||
scan_cell,
|
||||
))
|
||||
_ = visited
|
||||
SMSQueue.objects.filter(customer=customer).delete()
|
||||
Action.objects.filter(customer=customer).delete()
|
||||
|
||||
|
||||
def show_customer(customer_id):
|
||||
"""وضعیت فعلی یک مشتری واقعی + خط زمانی اقدامهایش (فقط خواندن)."""
|
||||
from django.utils import timezone
|
||||
from admin_panel.models import Action, Customer
|
||||
from admin_panel.visit_policy import (appointment_sources_map, last_visit_map,
|
||||
manual_holds_map, next_appointment_map)
|
||||
|
||||
tz = timezone.get_current_timezone()
|
||||
customer = Customer.objects.filter(pk=customer_id).first()
|
||||
print('\n' + '=' * 100)
|
||||
print('وضعیت مشتری #%s' % customer_id)
|
||||
if customer is None:
|
||||
print(' مشتری پیدا نشد.')
|
||||
return
|
||||
print(' نام: %s | فعال: %s | لغو بازدید: %s' % (
|
||||
customer.name_family, customer.is_active, customer.laghv_bazdid))
|
||||
print(' visit_hold_until: %s' % customer.visit_hold_until)
|
||||
sources = appointment_sources_map()
|
||||
print(' منابع قرار: %s' % {
|
||||
k: str(v.astimezone(tz)) for k, v in (sources.get(customer.pk) or {}).items()})
|
||||
print(' قرار بعدی: %s' % next_appointment_map().get(customer.pk))
|
||||
print(' قرار دستی معتبر: %s' % (customer.pk in manual_holds_map()))
|
||||
print(' آخرین بازدید: %s' % last_visit_map().get(customer.pk))
|
||||
print(' اقدامها (۱۰ مورد آخر):')
|
||||
for act in Action.objects.filter(customer_id=customer_id).order_by('-createdDT')[:10]:
|
||||
print(' #%-6s %-10s %-7s action=%-19s finish=%-19s created=%s' % (
|
||||
act.pk, act.actionType, act.status,
|
||||
act.actionDateTime, act.finishDateTime, act.createdDT))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='شبیهسازی تعویقِ قبل از تاریخ کار')
|
||||
parser.add_argument('--customer', type=int, default=None, help='شناسهٔ مشتری واقعی')
|
||||
parser.add_argument('--skip-variants', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import connections
|
||||
|
||||
source_db = str(settings.DATABASES['default']['NAME'])
|
||||
if not os.path.isabs(source_db):
|
||||
source_db = os.path.join(BASE_DIR, source_db)
|
||||
tmp_dir = tempfile.mkdtemp(prefix='hold_probe_')
|
||||
settings.DATABASES['default']['NAME'] = _copy_db(source_db, tmp_dir)
|
||||
connections.close_all()
|
||||
print('دیتابیسِ کپیِ موقت: %s' % settings.DATABASES['default']['NAME'])
|
||||
|
||||
if not args.skip_variants:
|
||||
run_variant('بدون تیک «انجام شد»', done=False, hold_offset=3, action_offset=5)
|
||||
run_variant('با تیک «انجام شد»', done=True, hold_offset=3, action_offset=5)
|
||||
run_variant('تعویق بعد از تاریخ کار (سالم)', done=True, hold_offset=10,
|
||||
action_offset=5)
|
||||
run_variant('تعویق خالی + تیک (پیشفرض ۱۴ روز)', done=True, hold_offset=None,
|
||||
action_offset=5)
|
||||
run_variant('تعویق خالی + بدون تیک', done=False, hold_offset=None,
|
||||
action_offset=5)
|
||||
|
||||
if args.customer:
|
||||
show_customer(args.customer)
|
||||
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user