заливка базы

This commit is contained in:
Dev PC
2025-07-03 01:35:09 +03:00
parent e7da06dcc1
commit ccde82adf3
25 changed files with 565320 additions and 0 deletions
+858
View File
@@ -0,0 +1,858 @@
import asyncio
from datetime import datetime
import json
import logging
import logging.config
import os
import aiohttp
from aiohttp import ClientSession
# AMI_CHANNEL_FILTER = []
AMI_CHANNEL_FILTER = ["PJSIP/Megafon_3", "PJSIP/rt_769402"]
ID_VARS = ("ConnectedLineNum", "CallerIDNum", "DestCallerIDNum", "Source")
CALL_DEBUG = True
CALL_DEBUG = False
FIXRECORDS = True
FIXRECORDS = False
MULTICHECK = True
MULTICHECK = False
OPERATORS = ("10", "12", "13")
records = {}
async def send_post_request(body: dict):
async def post_request(
url: str,
headers: dict = None,
json: dict = None,
**kwargs,
):
try:
async with ClientSession() as session:
async with session.post(
url, json=json, headers=headers, **kwargs
) as response:
response.raise_for_status()
return await response.json()
except aiohttp.ClientError as e:
return None
data = {"call": body}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer NjlmMmYzNThlOWNjYTI5ZGNlYTYzNz",
}
url = "http://188.64.134.62:3000/api/v2/telephony/common"
res = await post_request(url=url, headers=headers, json=data)
if CALL_DEBUG:
loggingDict("Server data", {"body": body, "response": res})
def redirect_ids(responsibles):
return responsibles
async def incoming_call(linkedid, client, exten):
return None
async def call_started(linkedid, responsible):
return None
async def call_lost(linkedid, responsible):
return None
async def call_finished(linkedid, duration):
body = {
"status": "call_finished",
"call_session_id": linkedid,
"duration": duration,
}
if FIXRECORDS:
await send_post_request(body)
return None
async def call_record_file(linkedid, uniqueid):
if CALL_DEBUG:
logging.info(f"Record ID: {linkedid} -> {uniqueid}")
body = {
"status": "call_record_file",
"call_session_id": linkedid,
"file_link": f"http://192.168.75.10:3050/{uniqueid}",
}
if FIXRECORDS:
await send_post_request(body)
return None
def loggingDict(title: str, data: dict) -> None:
logging.info(
f"{title}: %s",
json.dumps(data, indent=4, ensure_ascii=False).encode("utf-8").decode("utf-8"),
)
async def log_to_list(filename: str = None):
if not filename:
filename = "log/call.log" if CALL_DEBUG else "log/file.log"
events = []
with open(filename, "r", encoding="utf-8") as f:
lines = f.read().splitlines()
count = 0
event = {}
m_time = ""
line_num = 0
for line in lines:
try:
int(line[0:1])
m_time = line
except:
if line == "":
count += 1
else:
line_data = line.split(": ")
try:
event[line_data[0]] = line_data[1]
except Exception as e:
logging.exception(e)
logging.error(line_data)
if count == 3:
count = 0
if len(event) > 0:
events.append(event)
# loggingDict("event", event)
event = {"m_time": m_time, "line_num": line_num}
line_num += 1
events.append(event)
return events
async def csv_to_dict(filename: str = None):
if not filename:
filename = "log/cdr.csv"
asterisk_records = {}
with open(filename, "r", encoding="utf-8") as f:
# lines = f.readlines()
lines = f.read().splitlines()
titles = lines[0].strip().split(",")
lines.pop(0)
for line in lines:
event = {}
line_data = line.strip().split(",")
for i in range(len(titles)):
event[titles[i]] = line_data[i]
if (
event.get("peeraccount").startswith("external")
and event.get("accountcode") == "ANSWERED"
and event.get("dst") in OPERATORS
):
asterisk_records[event.get("sequence")] = {
"id": event.get("did"),
"time": event.get("calldate"),
"responsible": event.get("dst"),
"duration": event.get("amaflags"),
}
# logging.info(f"{event.get('sequence')} -> {event.get('did')}")
# loggingDict("event", event)
# loggingDict("asterisk_records", asterisk_records)
return asterisk_records
class CallHandler:
def __init__(self):
self.calls = {}
self.date = datetime.now().date()
self.finished = []
async def handle_event(self, event):
def check_linkedid(event):
linkedid = event.get("Linkedid")
try:
linkedid_split = linkedid.split(".")
uniqueid = event.get("Uniqueid")
uniqueid_split = uniqueid.split(".")
if int(linkedid_split[1]) <= int(uniqueid_split[1]):
return True
return False
except:
return False
def check_date():
if self.date != datetime.now().date():
self.date = datetime.now().date()
logging.info(f"Date changed to {self.date} and reset calls database")
self.finished = []
self.calls = {}
def channel_to_responsible(channel: str):
try:
if channel.startswith("Local"):
channel_data = channel.split("@")
resp = channel_data[0]
resp_data = resp.split("/")
responsible = resp_data[-1]
int(responsible)
return responsible
except:
pass
return None
try:
if check_linkedid(event):
linkedid = event.get("Linkedid")
if linkedid in self.finished:
return
if linkedid not in self.calls.keys():
check_date()
context = event.get("Context")
if context == "from-internal":
self.finished.append(linkedid)
if CALL_DEBUG:
loggingDict("INTERNAL", event)
return
if context != "from-trunk":
return
if len(AMI_CHANNEL_FILTER) > 0:
event_channel = event.get("Channel")
if len(event_channel) < 7:
return
for filter in AMI_CHANNEL_FILTER:
if event_channel.startswith(filter):
await self.incoming_call(event, linkedid)
if CALL_DEBUG:
loggingDict("NEW", event)
return
self.finished.append(linkedid)
if CALL_DEBUG:
loggingDict("FILTER", event)
return
else:
await self.incoming_call(event, linkedid)
if CALL_DEBUG:
loggingDict(f"NEW: {linkedid}", event)
return
else:
if CALL_DEBUG:
logging.info(
f"Time: {event.get('m_time')}, Line: {event.get('line_num')}"
)
if event.get("ChannelStateDesc") == "Ring":
if (
event.get("Context") == "macro-user-callerid"
and (
event.get("Event") == "VarSet"
or (
event.get("Event") != "Newexten"
and event.get("Variable") == "MACRO_DEPTH"
)
)
and event.get("Application")
not in ("ExecIf", "Goto", "Return")
) or (
event.get("Variable") == "DIALEDPEERNUMBER"
and event.get("Exten") in OPERATORS
):
try:
uniq = event.get("Uniqueid")
if uniq != linkedid:
if (
uniq
not in self.calls[linkedid]["records"].values()
):
target = "records"
else:
target = "records_duble"
if CALL_DEBUG:
logging.warning(
f"{uniq} -> {event.get('Channel')}"
)
uniq_data = uniq.split(".")
if int(uniq_data[1]) > int(linkedid.split(".")[1]):
responsible = channel_to_responsible(
event.get("Channel")
)
if responsible in OPERATORS:
if (
responsible
in self.calls[linkedid][
"records"
].keys()
):
resp_uniq = self.calls[linkedid][
"records"
][responsible]
if int(uniq_data[-1]) < int(
resp_uniq.split(".")[-1]
):
return
if (
responsible
not in self.calls[linkedid][
"responsibles"
]
):
self.calls[linkedid][
"responsibles"
].append(responsible)
if target == "records":
if (
responsible
in self.calls[linkedid][
target
].keys()
):
free_uniq = self.calls[linkedid][
target
][responsible]
self.calls[linkedid][target][
responsible
] = uniq
for (
dub_id,
dub_uniq,
) in self.calls[
linkedid
]["records_duble"].items():
if dub_uniq == free_uniq:
if (
dub_id
not in self.calls[
linkedid
][target].keys()
):
self.calls[linkedid][
target
][dub_id] = dub_uniq
self.calls[linkedid][
"records_duble"
].pop(dub_id)
else:
self.calls[linkedid][target][
responsible
] = uniq
else:
if (
uniq
not in self.calls[linkedid][
target
].values()
):
if CALL_DEBUG:
logging.warning(
f"{uniq} -> {responsible}"
)
if responsible not in self.calls[
linkedid
]["records"].keys() or (
responsible
in self.calls[linkedid][
"records"
].keys()
and self.calls[linkedid][
"records"
][responsible]
!= uniq
):
if CALL_DEBUG:
logging.warning(
f"Add record ID: {responsible} -> {uniq}, type: {target}"
)
self.calls[linkedid][target][
responsible
] = uniq
else:
if CALL_DEBUG:
loggingDict(
f"{responsible} -> {uniq}",
self.calls[linkedid][
"records"
],
)
if CALL_DEBUG:
loggingDict(
"records",
self.calls[linkedid]["records"],
)
loggingDict(
"records_duble",
self.calls[linkedid][
"records_duble"
],
)
loggingDict(
f"Add record ID: {responsible} -> {uniq}, type: {target}",
event,
)
return
except:
return
if (
event.get("Context") == "sub-record-check"
and (
".wav" in event.get("AppData")
or "external" in event.get("AppData")
)
and event.get("Exten")
== event.get("Extension")
== "recordcheck"
and event.get("Uniqueid") != linkedid
# and event.get("Uniqueid")
# not in self.calls[linkedid]["records"].values()
):
responsible = channel_to_responsible(event.get("Channel"))
if (
responsible in OPERATORS
and responsible
not in self.calls[linkedid]["responsibles"]
):
self.calls[linkedid]["records"][responsible] = (
event.get("Uniqueid")
)
if (
responsible
not in self.calls[linkedid]["responsibles"]
):
self.calls[linkedid]["responsibles"].append(
responsible
)
if self.calls[linkedid]["started"] is None:
if (
(
event.get("DialStatus") == "ANSWER"
and event.get("DestChannelStateDesc") == "Up"
)
or event.get("Variable")
in ("BRIDGEPVTCALLID", "BRIDGEPEER")
or event.get("BridgeTechnology") == "simple_bridge"
):
for var in ID_VARS:
answered = event.get(var)
if CALL_DEBUG:
logging.info(
f"{var} -> {answered} <= {self.calls[linkedid]['responsibles']} == {answered in self.calls[linkedid]['responsibles']}"
)
if answered in self.calls[linkedid]["responsibles"]:
if CALL_DEBUG:
logging.info(f"Call started: {linkedid}")
await self.call_started(
linkedid, answered, event.get("m_time")
)
if CALL_DEBUG:
loggingDict("Start", event)
break
if event.get("Disposition") == "NO ANSWER":
await self.call_lost(linkedid)
if CALL_DEBUG:
loggingDict("LOST", event)
return
else:
transfered = False
if (
event.get("BridgeTechnology") == "simple_bridge"
and event.get("Context") == "from-internal-xfer"
and event.get("ChannelStateDesc") == "Up"
):
new_responsible = event.get("Exten")
old_responsible = event.get("CallerIDNum")
transfered = True
if (
event.get("BridgeTechnology")
== event.get("ToBridgeTechnology")
== event.get("FromBridgeTechnology")
== "simple_bridge"
and event.get("CallerIDNum") not in OPERATORS
and event.get("ChannelStateDesc") == "Up"
):
new_responsible = event.get("CallerIDNum")
old_responsible = event.get("ConnectedLineNum")
transfered = True
if transfered:
self.call_transfered(
linkedid,
new_responsible,
old_responsible,
event.get("m_time"),
)
m_time = event.get("m_time")
pre_m_data = m_time.split(" ")
if len(pre_m_data) > 1:
m_time = pre_m_data[1]
m_time_data = m_time.split(":")
duration = int(
(
datetime.now().replace(
hour=int(m_time_data[0]),
minute=int(m_time_data[1]),
second=int(m_time_data[2]),
)
- self.calls[linkedid]["started"]
).total_seconds()
)
if (
(
(
"BillableSeconds" in event.keys()
and event.get("Disposition") == "ANSWERED"
and (
event.get("Uniqueid") != linkedid
or (
event.get("Cause-txt") == "Normal Clearing"
or event.get("Context")
== "macro-hangupcall"
or event.get("Application") == "Hangup"
)
)
)
or (
"TalkTime" in event.keys()
and event.get("Event") == "VarSet"
)
or (
event.get("Application") == "Hangup"
and event.get("Disposition") != "NO ANSWER"
and event.get("ChannelStateDesc") != "Ring"
and (
event.get("Uniqueid") != linkedid
or event.get("Cause-txt") == "Normal Clearing"
)
and duration > 1
)
or (
event.get("AppData") == "hangupcall,"
and event.get("ChannelStateDesc") == "Up"
and (
event.get("Uniqueid") != linkedid
or event.get("Context") == "ext-queues"
)
# and duration >= 1
)
or (
(
event.get("Context") == "macro-hangupcall"
and event.get("ChannelStateDesc") == "Up"
)
and (
(
event.get("ConnectedLineNum") in OPERATORS
or event.get("ConnectedLineNum")
in self.calls[linkedid]["responsibles"]
)
or (
(
event.get("CallerIDNum") in OPERATORS
or event.get("CallerIDNum")
in self.calls[linkedid]["responsibles"]
)
and event.get("Event") == "BridgeLeave"
)
)
and (
event.get("Uniqueid") != linkedid
or duration > 1
)
)
or (event.get("Event") == "Cdr")
)
and event.get("Context") != "from-internal-xfer"
and not event.get("Event").startswith("RTC")
):
if CALL_DEBUG:
loggingDict("Call ENDED?", event)
tolk_time = 0
for var in ("BillableSeconds", "TalkTime"):
if var in event.keys():
if CALL_DEBUG:
logging.info(f"{var} -> {event.get(var)}")
tolk_time += int(event.get(var))
break
if tolk_time > duration:
duration = tolk_time
if CALL_DEBUG:
logging.info(
f"{duration=}, {tolk_time=}, {m_time=}, {self.calls[linkedid]['started']=}"
)
transfer_duration = None
if self.calls[linkedid]["transfered"] is not None:
transfer_duration = int(
(
datetime.now().replace(
hour=int(m_time_data[0]),
minute=int(m_time_data[1]),
second=int(m_time_data[2]),
)
- self.calls[linkedid]["transfered"]
).total_seconds()
)
# if duration >= 1:
if duration >= 1 and (
transfer_duration is None or transfer_duration > 1
):
record_id = None
if (
event.get("AppData") == "hangupcall,"
and event.get("Cause") == "16"
and event.get("Context") == "ext-local"
and event.get("ConnectedLineNum") in OPERATORS
and event.get("Uniqueid") != linkedid
):
record_id = event.get("Uniqueid")
if record_id is None and duration < 2:
return
if event.get("Event") == "AttendedTransfer":
record_id = event.get("TransfereeUniqueid")
if (
event.get("Context") == "macro-hangupcall"
and event.get("Uniqueid") != linkedid
and event.get("ConnectedLineNum") in OPERATORS
and "BillableSeconds" not in event.keys()
):
record_id = event.get("Uniqueid")
if (
event.get("Application") == "Hangup"
and event.get("Membership") == "static"
and event.get("ConnectedLineNum") in OPERATORS
and event.get("Uniqueid") != linkedid
):
record_id = event.get("Uniqueid")
if record_id is None:
for var in ID_VARS:
answered = event.get(var)
if CALL_DEBUG:
logging.info(
f"{answered}, {var}, {self.calls[linkedid]['responsibles']}"
)
if (
answered
in self.calls[linkedid]["responsibles"]
):
try:
record_id = self.calls[linkedid][
"records"
][answered]
except:
record_id = self.calls[linkedid][
"records_duble"
][answered]
break
if record_id is None:
answered = channel_to_responsible(
event.get("Channel")
)
if answered in self.calls[linkedid]["responsibles"]:
try:
record_id = self.calls[linkedid]["records"][
answered
]
except:
record_id = self.calls[linkedid][
"records_duble"
][answered]
if record_id is None:
if CALL_DEBUG:
logging.warning(
f"Call not finished: {linkedid}"
)
return
await self.call_finished(
linkedid, duration, record_id, m_time
)
if CALL_DEBUG:
logging.info(
f"Call finished: {linkedid}: {duration} -> {record_id} <- {m_time}"
)
# loggingDict("FINISH", event)
return
if CALL_DEBUG:
logging.warning(f"Call not finished: {linkedid}")
except Exception as e:
# logging.error(
# f"{linkedid} Time: {event.get('m_time')}, Line: {event.get('line_num')} Error: {e}"
# )
# logging.info(f"Transfered: {self.calls[linkedid]['responsibles']}")
pass
async def incoming_call(self, event, linkedid):
self.calls[linkedid] = {
"responsibles": [],
"started": None,
"transfered": None,
"records": {},
"records_duble": {},
}
exten = event.get("Exten") if event.get("Exten") else event.get("Extension")
await incoming_call(linkedid, event.get("CallerIDNum"), exten)
def call_transfered(self, linkedid, new_responsible, old_responsible, m_time):
pre_m_data = m_time.split(" ")
if len(pre_m_data) > 1:
m_time = pre_m_data[1]
m_time_data = m_time.split(":")
try:
int(new_responsible)
if new_responsible not in self.calls[linkedid]["responsibles"]:
if old_responsible in self.calls[linkedid]["records"].keys():
self.calls[linkedid]["records"][new_responsible] = self.calls[
linkedid
]["records"][old_responsible]
self.calls[linkedid]["responsibles"].append(new_responsible)
self.calls[linkedid]["transfered"] = datetime.now().replace(
hour=int(m_time_data[0]),
minute=int(m_time_data[1]),
second=int(m_time_data[2]),
)
if CALL_DEBUG:
logging.warning(
f"Transfered: from {old_responsible} to {new_responsible}"
)
else:
if old_responsible in self.calls[linkedid]["records_duble"].keys():
self.calls[linkedid]["records"][new_responsible] = self.calls[
linkedid
]["records_duble"][old_responsible]
self.calls[linkedid]["responsibles"].append(new_responsible)
self.calls[linkedid]["transfered"] = datetime.now().replace(
hour=int(m_time_data[0]),
minute=int(m_time_data[1]),
second=int(m_time_data[2]),
)
if CALL_DEBUG:
logging.warning(
f"Transfered: from {old_responsible} to {new_responsible}"
)
except:
logging.info(f"Call not transfered: {new_responsible}")
async def call_started(self, linkedid, responsible, m_time):
pre_m_data = m_time.split(" ")
if len(pre_m_data) > 1:
m_time = pre_m_data[1]
m_time_data = m_time.split(":")
self.calls[linkedid]["started"] = datetime.now().replace(
hour=int(m_time_data[0]),
minute=int(m_time_data[1]),
second=int(m_time_data[2]),
)
# logging.info(f"Call started: ID={linkedid}, Responsible={responsible}")
await call_started(linkedid, redirect_ids(responsible))
async def call_finished(self, linkedid, duration, uniqueid, m_time):
# logging.info(
# f"Call finished: ID={linkedid}, Duration={duration}, UniqueID={uniqueid}"
# )
self.finished.append(linkedid)
self.calls.pop(linkedid)
records[linkedid] = {"duration": duration, "uniqueid": uniqueid, "time": m_time}
await call_finished(linkedid, duration)
await call_record_file(linkedid, uniqueid)
async def call_lost(self, linkedid):
# logging.info(
# f"Call lost: ID={linkedid}, responsibles: {self.calls[linkedid]['responsibles']}"
# )
await call_lost(linkedid, redirect_ids(self.calls[linkedid]["responsibles"]))
async def multiCheck():
path = "log/data/"
log_files = [f.split(".")[0] for f in os.listdir(path) if f.endswith(".log")]
return log_files
async def main():
if MULTICHECK and not CALL_DEBUG:
check_list = await multiCheck()
check_total = {
"OK": 0,
"Wrong ID": 0,
"Wrong Duration": 0,
"Error": 0,
}
else:
check_list = [None]
for filename in check_list:
events = (
await log_to_list(f"log/data/{filename}.log")
if MULTICHECK
else await log_to_list()
)
handler = CallHandler()
for event in events:
await handler.handle_event(event)
asterisk = (
await csv_to_dict(f"log/data/{filename}.csv")
if MULTICHECK
else await csv_to_dict()
)
report_date = filename if filename else datetime.now().strftime("%Y-%m-%d")
check_dict = {
"summary": {
"OK": 0,
"Wrong ID": 0,
"Wrong Duration": 0,
"Error": 0,
"date": report_date,
},
"details": {},
}
for linkedid, call_data in asterisk.items():
record = records.get(linkedid)
# logging.info(record)
if record is not None and record["uniqueid"] == call_data["id"]:
check_dict["details"][linkedid] = True
check_dict["summary"]["OK"] += 1
if not CALL_DEBUG:
if (int(record["duration"]) - int(call_data["duration"])) > -2:
# logging.info(
# f"{call_data['time']}: {linkedid} -> {call_data['id']} <{call_data['duration']}> (Asterisk) == {record['uniqueid']} <{record['duration']}> (Medods)"
# )
pass
else:
logging.warning(
f"{call_data['time']}: {linkedid} -> {call_data['id']} <{call_data['duration']}> (Asterisk) == {record['uniqueid']} <{record['duration']}> -{record['time']}- (Medods)"
)
check_dict["summary"]["Wrong Duration"] += 1
else:
check_dict["details"][linkedid] = False
if not CALL_DEBUG:
if linkedid not in handler.finished:
if record is None:
logging.error(
f"{call_data['time']}: {linkedid} -> {call_data['id']} [{call_data['responsible']}] (Asterisk) != {record} (Medods)"
)
check_dict["summary"]["Error"] += 1
else:
logging.warning(
f"{call_data['time']}: {linkedid} -> {call_data['id']} [{call_data['responsible']}] (Asterisk) != {record['uniqueid']} (Medods)"
)
check_dict["summary"]["Wrong ID"] += 1
if MULTICHECK and not CALL_DEBUG:
for key in check_total.keys():
check_total[key] += check_dict["summary"][key]
if not CALL_DEBUG:
loggingDict("Day result", check_dict["summary"])
if MULTICHECK and not CALL_DEBUG:
loggingDict("Total result", check_total)
if __name__ == "__main__":
logging.config.fileConfig("log.ini", disable_existing_loggers=False)
asyncio.run(main())